Adjustments for grouped drop bonus chance.
This commit is contained in:
parent
9d3f1711e1
commit
260d4534a5
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user