Adjustments for grouped drop bonus chance.

This commit is contained in:
MobiusDevelopment 2023-04-03 12:48:45 +03:00
parent 9d3f1711e1
commit 260d4534a5
58 changed files with 4919 additions and 4088 deletions

View File

@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -757,161 +757,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -757,161 +757,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -757,161 +757,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -757,161 +757,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -757,161 +757,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -757,161 +757,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -757,161 +757,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -757,161 +757,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -757,161 +757,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -757,161 +757,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -757,161 +757,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -757,161 +757,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -97,6 +97,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -696,158 +696,178 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -962,63 +982,71 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
}
// finally
return new ItemHolder(itemId, (int) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// finally return null;
return new ItemHolder(itemId, (int) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -97,6 +97,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -696,158 +696,178 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -962,63 +982,71 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// finally return null;
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -98,6 +98,7 @@ DropChanceMultiplierByItemId = 57,1;21747,0;21748,0;21749,0
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -696,158 +696,178 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -962,63 +982,71 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// finally return null;
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -757,161 +757,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -758,161 +758,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1101,70 +1120,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -758,161 +758,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1101,70 +1120,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -758,161 +758,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1101,70 +1120,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -758,161 +758,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1101,70 +1120,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -773,161 +773,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1116,70 +1135,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -773,161 +773,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1116,70 +1135,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -773,161 +773,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1116,70 +1135,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -773,161 +773,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1116,70 +1135,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -757,161 +757,180 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -772,161 +772,184 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if (item.getId() == Inventory.LCOIN_ID)
{
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE_LCOIN, 1);
}
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1041,70 +1064,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -772,161 +772,184 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if (item.getId() == Inventory.LCOIN_ID)
{
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE_LCOIN, 1);
}
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1041,70 +1064,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -790,161 +790,184 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if (item.getId() == Inventory.LCOIN_ID)
{
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE_LCOIN, 1);
}
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1059,70 +1082,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**

View File

@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1
# Maximum drop occurrences. # Maximum drop occurrences.
# Note: Items that have 100% drop chance without server rate multipliers # Note: Items that have 100% drop chance without server rate multipliers
# are not counted by this value. They will drop as extra drops. # are not counted by this value. They will drop as extra drops.
# Also grouped drops with total chance over 100% break this configuration.
DropMaxOccurrencesNormal = 2 DropMaxOccurrencesNormal = 2
DropMaxOccurrencesRaidboss = 7 DropMaxOccurrencesRaidboss = 7

View File

@ -790,161 +790,184 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
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 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); final double levelGapChanceToDrop = Util.map(levelDifference, -Config.DROP_ITEM_MAX_LEVEL_DIFFERENCE, -Config.DROP_ITEM_MIN_LEVEL_DIFFERENCE, Config.DROP_ITEM_MIN_LEVEL_GAP_CHANCE, 100d);
int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
List<ItemHolder> calculatedDrops = null; List<ItemHolder> calculatedDrops = null;
for (DropGroupHolder group : _dropGroups) int dropOccurrenceCounter = victim.isRaid() ? Config.DROP_MAX_OCCURRENCES_RAIDBOSS : Config.DROP_MAX_OCCURRENCES_NORMAL;
if (dropOccurrenceCounter > 0)
{ {
if (dropOccurrenceCounter <= 0) List<ItemHolder> randomDrops = null;
ItemHolder cachedItem = null;
double totalChance; // total group chance is 100
for (DropGroupHolder group : _dropGroups)
{ {
break; totalChance = 0;
} GROUP_DROP: for (DropHolder dropItem : group.getDropList())
double groupRate = 1;
final List<DropHolder> groupDrops = group.getDropList();
for (DropHolder dropItem : groupDrops)
{
final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion();
// chance
double rateChance = 1;
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
if (itemChance != null)
{ {
if (itemChance <= 0) final int itemId = dropItem.getItemId();
{ final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
continue; final boolean champion = victim.isChampion();
}
rateChance *= itemChance; // chance
if (champion && (itemId == Inventory.ADENA_ID)) double rateChance = 1;
if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{ {
rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE;
{ }
rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
}
else if (victim.isRaid())
{
rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
}
else
{
rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
// premium chance
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb chance? :) rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid chance? :) rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER;
} }
else else
{ {
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1);
}
}
// keep lowest to avoid chance by id configuration conflicts
groupRate = Math.min(groupRate, rateChance);
}
// bonus drop rate effect
groupRate *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate))
{
double totalChance = 0; // total group chance is 100
final double dropChance = Rnd.nextDouble() * 100;
GROUP_DROP: for (DropHolder dropItem : groupDrops)
{
if (dropOccurrenceCounter <= 0)
{
break;
} }
// calculate if item will drop // premium chance
totalChance += dropItem.getChance(); if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
if (dropChance >= totalChance)
{ {
continue; if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null)
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb chance? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid chance? :)
}
else
{
rateChance *= Config.PREMIUM_RATE_DROP_CHANCE;
}
}
// bonus drop rate effect
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE, 1);
if (item.getId() == Inventory.LCOIN_ID)
{
rateChance *= killer.getStat().getMul(Stat.BONUS_DROP_RATE_LCOIN, 1);
}
// only use total chance on x1, custom rates break this logic because total chance is more than 100%
if (rateChance == 1)
{
totalChance += dropItem.getChance();
}
else
{
totalChance = dropItem.getChance();
}
final double groupItemChance = totalChance * (group.getChance() / 100) * rateChance;
// check if maximum drop occurrences have been reached
// items that have 100% drop chance without server rate multipliers drop normally
if ((dropOccurrenceCounter == 0) && (groupItemChance < 100) && (randomDrops != null) && (calculatedDrops != null))
{
if ((rateChance == 1) && !randomDrops.isEmpty()) // custom rates break this logic because total chance is more than 100%
{
// remove highest chance item (temporarily if no other item replaces it)
cachedItem = randomDrops.remove(0);
calculatedDrops.remove(cachedItem);
}
dropOccurrenceCounter = 1;
} }
// check level gap that may prevent to drop item // check level gap that may prevent to drop item
if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop))
{ {
continue; continue GROUP_DROP;
} }
// skip zero chance drops // calculate chances
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance);
if ((itemChance != null) && (itemChance <= 0)) if (drop == null)
{ {
continue; continue GROUP_DROP;
} }
// create the drop // create lists
final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); if (randomDrops == null)
{
// create list randomDrops = new ArrayList<>(dropOccurrenceCounter);
}
if (calculatedDrops == null) if (calculatedDrops == null)
{ {
calculatedDrops = new ArrayList<>(dropOccurrenceCounter); calculatedDrops = new ArrayList<>(dropOccurrenceCounter);
} }
// finally // finally
final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId());
if (itemChance != null) if (itemChance != null)
{ {
if ((dropItem.getChance() * itemChance) < 100) if ((groupItemChance * itemChance) < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
} }
else if (dropItem.getChance() < 100) else if (groupItemChance < 100)
{ {
dropOccurrenceCounter--; dropOccurrenceCounter--;
if (rateChance == 1) // custom rates break this logic because total chance is more than 100%
{
randomDrops.add(drop);
}
} }
calculatedDrops.add(drop); calculatedDrops.add(drop);
// no more drops from this group // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100%
break GROUP_DROP; if (rateChance == 1)
{
break GROUP_DROP;
}
} }
} }
}
// add temporarily removed item when not replaced
// champion extra drop if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null))
if (victim.isChampion())
{
if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
{ {
return calculatedDrops; calculatedDrops.add(cachedItem);
} }
if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) // clear random drops
if (randomDrops != null)
{ {
return calculatedDrops; randomDrops.clear();
randomDrops = null;
} }
// create list // champion extra drop
if (calculatedDrops == null) if (victim.isChampion())
{ {
calculatedDrops = new ArrayList<>(); if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE))
} {
return calculatedDrops;
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) }
{ if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE))
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); {
return calculatedDrops;
}
// create list
if (calculatedDrops == null)
{
calculatedDrops = new ArrayList<>();
}
if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS))
{
calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS);
}
} }
} }
@ -1059,70 +1082,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable
} }
/** /**
* @param group
* @param dropItem * @param dropItem
* @param victim * @param victim
* @param killer * @param killer
* @param chance
* @return ItemHolder * @return ItemHolder
*/ */
private ItemHolder calculateGroupDrop(DropHolder dropItem, Creature victim, Creature killer) private ItemHolder calculateGroupDrop(DropGroupHolder group, DropHolder dropItem, Creature victim, Creature killer, double chance)
{ {
final int itemId = dropItem.getItemId(); final int itemId = dropItem.getItemId();
final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId);
final boolean champion = victim.isChampion(); final boolean champion = victim.isChampion();
// calculate amount // calculate if item will drop
double rateAmount = 1; if ((Rnd.nextDouble() * 100) < chance)
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); // amount is calculated after chance returned success
if (champion && (itemId == Inventory.ADENA_ID)) double rateAmount = 1;
if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{ {
rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId);
} if (champion && (itemId == Inventory.ADENA_ID))
} {
else if (item.hasExImmediateEffect()) rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT;
{ }
rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
}
else if (victim.isRaid())
{
rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
}
else
{
rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
}
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
} }
else if (item.hasExImmediateEffect()) else if (item.hasExImmediateEffect())
{ {
// TODO: Premium herb amount? :) rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER;
} }
else if (victim.isRaid()) else if (victim.isRaid())
{ {
// TODO: Premium raid amount? :) rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER;
} }
else else
{ {
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT; rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1);
} }
// premium amount
if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus())
{
if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null)
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId);
}
else if (item.hasExImmediateEffect())
{
// TODO: Premium herb amount? :)
}
else if (victim.isRaid())
{
// TODO: Premium raid amount? :)
}
else
{
rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT;
}
// bonus drop amount effect
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
// bonus drop amount effect return null;
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_AMOUNT, 1);
if (itemId == Inventory.ADENA_ID)
{
rateAmount *= killer.getStat().getMul(Stat.BONUS_DROP_ADENA, 1);
}
// finally
return new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount));
} }
/** /**