From 260d4534a53f6218e671e88ef3e029fc02a1e312 Mon Sep 17 00:00:00 2001 From: MobiusDevelopment Date: Mon, 3 Apr 2023 12:48:45 +0300 Subject: [PATCH] Adjustments for grouped drop bonus chance. --- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 292 ++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 292 ++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 292 ++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 311 +++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 315 ++++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 315 ++++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 315 ++++++++++-------- .../dist/game/config/Rates.ini | 1 + .../model/actor/templates/NpcTemplate.java | 315 ++++++++++-------- 58 files changed, 4919 insertions(+), 4088 deletions(-) diff --git a/L2J_Mobius_01.0_Ertheia/dist/game/config/Rates.ini b/L2J_Mobius_01.0_Ertheia/dist/game/config/Rates.ini index 7ed268b302..577f9f5464 100644 --- a/L2J_Mobius_01.0_Ertheia/dist/game/config/Rates.ini +++ b/L2J_Mobius_01.0_Ertheia/dist/game/config/Rates.ini @@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index efa092c5ee..60e82d7569 100644 --- a/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_01.0_Ertheia/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_02.5_Underground/dist/game/config/Rates.ini b/L2J_Mobius_02.5_Underground/dist/game/config/Rates.ini index 7ed268b302..577f9f5464 100644 --- a/L2J_Mobius_02.5_Underground/dist/game/config/Rates.ini +++ b/L2J_Mobius_02.5_Underground/dist/game/config/Rates.ini @@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index efa092c5ee..60e82d7569 100644 --- a/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_02.5_Underground/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_03.0_Helios/dist/game/config/Rates.ini b/L2J_Mobius_03.0_Helios/dist/game/config/Rates.ini index 80c443070b..678d4e2d2f 100644 --- a/L2J_Mobius_03.0_Helios/dist/game/config/Rates.ini +++ b/L2J_Mobius_03.0_Helios/dist/game/config/Rates.ini @@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index efa092c5ee..60e82d7569 100644 --- a/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_03.0_Helios/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_04.0_GrandCrusade/dist/game/config/Rates.ini b/L2J_Mobius_04.0_GrandCrusade/dist/game/config/Rates.ini index 80c443070b..678d4e2d2f 100644 --- a/L2J_Mobius_04.0_GrandCrusade/dist/game/config/Rates.ini +++ b/L2J_Mobius_04.0_GrandCrusade/dist/game/config/Rates.ini @@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index efa092c5ee..60e82d7569 100644 --- a/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_04.0_GrandCrusade/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_05.0_Salvation/dist/game/config/Rates.ini b/L2J_Mobius_05.0_Salvation/dist/game/config/Rates.ini index 80c443070b..678d4e2d2f 100644 --- a/L2J_Mobius_05.0_Salvation/dist/game/config/Rates.ini +++ b/L2J_Mobius_05.0_Salvation/dist/game/config/Rates.ini @@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index efa092c5ee..60e82d7569 100644 --- a/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_05.0_Salvation/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_05.5_EtinasFate/dist/game/config/Rates.ini b/L2J_Mobius_05.5_EtinasFate/dist/game/config/Rates.ini index 80c443070b..678d4e2d2f 100644 --- a/L2J_Mobius_05.5_EtinasFate/dist/game/config/Rates.ini +++ b/L2J_Mobius_05.5_EtinasFate/dist/game/config/Rates.ini @@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index efa092c5ee..60e82d7569 100644 --- a/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_05.5_EtinasFate/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_06.0_Fafurion/dist/game/config/Rates.ini b/L2J_Mobius_06.0_Fafurion/dist/game/config/Rates.ini index 80c443070b..678d4e2d2f 100644 --- a/L2J_Mobius_06.0_Fafurion/dist/game/config/Rates.ini +++ b/L2J_Mobius_06.0_Fafurion/dist/game/config/Rates.ini @@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index efa092c5ee..60e82d7569 100644 --- a/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_06.0_Fafurion/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_07.0_PreludeOfWar/dist/game/config/Rates.ini b/L2J_Mobius_07.0_PreludeOfWar/dist/game/config/Rates.ini index 80c443070b..678d4e2d2f 100644 --- a/L2J_Mobius_07.0_PreludeOfWar/dist/game/config/Rates.ini +++ b/L2J_Mobius_07.0_PreludeOfWar/dist/game/config/Rates.ini @@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index efa092c5ee..60e82d7569 100644 --- a/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_07.0_PreludeOfWar/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_08.2_Homunculus/dist/game/config/Rates.ini b/L2J_Mobius_08.2_Homunculus/dist/game/config/Rates.ini index b236500fa8..9dbdcbe8ab 100644 --- a/L2J_Mobius_08.2_Homunculus/dist/game/config/Rates.ini +++ b/L2J_Mobius_08.2_Homunculus/dist/game/config/Rates.ini @@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index 1e8e6b4ff3..ad6f5f514b 100644 --- a/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_08.2_Homunculus/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/config/Rates.ini b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/config/Rates.ini index b236500fa8..9dbdcbe8ab 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/config/Rates.ini +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/dist/game/config/Rates.ini @@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index 1e8e6b4ff3..ad6f5f514b 100644 --- a/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_09.2_ReturnOfTheQueenAnt/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_10.2_MasterClass/dist/game/config/Rates.ini b/L2J_Mobius_10.2_MasterClass/dist/game/config/Rates.ini index 1245b8dbec..25f11df186 100644 --- a/L2J_Mobius_10.2_MasterClass/dist/game/config/Rates.ini +++ b/L2J_Mobius_10.2_MasterClass/dist/game/config/Rates.ini @@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index 1e8e6b4ff3..ad6f5f514b 100644 --- a/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_10.2_MasterClass/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_10.3_MasterClass/dist/game/config/Rates.ini b/L2J_Mobius_10.3_MasterClass/dist/game/config/Rates.ini index 1245b8dbec..25f11df186 100644 --- a/L2J_Mobius_10.3_MasterClass/dist/game/config/Rates.ini +++ b/L2J_Mobius_10.3_MasterClass/dist/game/config/Rates.ini @@ -112,6 +112,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_10.3_MasterClass/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_10.3_MasterClass/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index 1e8e6b4ff3..ad6f5f514b 100644 --- a/L2J_Mobius_10.3_MasterClass/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_10.3_MasterClass/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_CT_0_Interlude/dist/game/config/Rates.ini b/L2J_Mobius_CT_0_Interlude/dist/game/config/Rates.ini index 9f6c528043..077fa100bb 100644 --- a/L2J_Mobius_CT_0_Interlude/dist/game/config/Rates.ini +++ b/L2J_Mobius_CT_0_Interlude/dist/game/config/Rates.ini @@ -97,6 +97,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index 7e77348501..a395182faf 100644 --- a/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_CT_0_Interlude/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // keep lowest to avoid chance by id configuration conflicts - groupRate = Math.min(groupRate, rateChance); - } - - if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate)) - { - double totalChance = 0; // total group chance is 100 - final double dropChance = Rnd.nextDouble() * 100; - GROUP_DROP: for (DropHolder dropItem : groupDrops) - { - if (dropOccurrenceCounter <= 0) - { - break; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -962,63 +982,71 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 new ItemHolder(itemId, (int) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount)); + return null; } /** diff --git a/L2J_Mobius_CT_2.4_Epilogue/dist/game/config/Rates.ini b/L2J_Mobius_CT_2.4_Epilogue/dist/game/config/Rates.ini index 9f6c528043..077fa100bb 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/dist/game/config/Rates.ini +++ b/L2J_Mobius_CT_2.4_Epilogue/dist/game/config/Rates.ini @@ -97,6 +97,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index 7398fa3751..655c483993 100644 --- a/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_CT_2.4_Epilogue/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // keep lowest to avoid chance by id configuration conflicts - groupRate = Math.min(groupRate, rateChance); - } - - if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate)) - { - double totalChance = 0; // total group chance is 100 - final double dropChance = Rnd.nextDouble() * 100; - GROUP_DROP: for (DropHolder dropItem : groupDrops) - { - if (dropOccurrenceCounter <= 0) - { - break; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -962,63 +982,71 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount)); + return null; } /** diff --git a/L2J_Mobius_CT_2.6_HighFive/dist/game/config/Rates.ini b/L2J_Mobius_CT_2.6_HighFive/dist/game/config/Rates.ini index 3c97546f79..b8d9cd8da4 100644 --- a/L2J_Mobius_CT_2.6_HighFive/dist/game/config/Rates.ini +++ b/L2J_Mobius_CT_2.6_HighFive/dist/game/config/Rates.ini @@ -98,6 +98,7 @@ DropChanceMultiplierByItemId = 57,1;21747,0;21748,0;21749,0 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index 7398fa3751..655c483993 100644 --- a/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_CT_2.6_HighFive/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // keep lowest to avoid chance by id configuration conflicts - groupRate = Math.min(groupRate, rateChance); - } - - if ((Rnd.nextDouble() * 100) < (group.getChance() * groupRate)) - { - double totalChance = 0; // total group chance is 100 - final double dropChance = Rnd.nextDouble() * 100; - GROUP_DROP: for (DropHolder dropItem : groupDrops) - { - if (dropOccurrenceCounter <= 0) - { - break; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -962,63 +982,71 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 new ItemHolder(itemId, (long) (Rnd.get(dropItem.getMin(), dropItem.getMax()) * rateAmount)); + return null; } /** diff --git a/L2J_Mobius_Classic_1.0/dist/game/config/Rates.ini b/L2J_Mobius_Classic_1.0/dist/game/config/Rates.ini index c8040c2a9d..8f761e7cb1 100644 --- a/L2J_Mobius_Classic_1.0/dist/game/config/Rates.ini +++ b/L2J_Mobius_Classic_1.0/dist/game/config/Rates.ini @@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_Classic_1.0/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_Classic_1.0/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index 764da47bed..3956b9b902 100644 --- a/L2J_Mobius_Classic_1.0/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_Classic_1.0/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_Classic_1.5_AgeOfSplendor/dist/game/config/Rates.ini b/L2J_Mobius_Classic_1.5_AgeOfSplendor/dist/game/config/Rates.ini index c8040c2a9d..8f761e7cb1 100644 --- a/L2J_Mobius_Classic_1.5_AgeOfSplendor/dist/game/config/Rates.ini +++ b/L2J_Mobius_Classic_1.5_AgeOfSplendor/dist/game/config/Rates.ini @@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_Classic_1.5_AgeOfSplendor/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_Classic_1.5_AgeOfSplendor/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index a71f93218f..c6a8586af1 100644 --- a/L2J_Mobius_Classic_1.5_AgeOfSplendor/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_Classic_1.5_AgeOfSplendor/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1101,70 +1120,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_Classic_2.0_Saviors/dist/game/config/Rates.ini b/L2J_Mobius_Classic_2.0_Saviors/dist/game/config/Rates.ini index c8040c2a9d..8f761e7cb1 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/dist/game/config/Rates.ini +++ b/L2J_Mobius_Classic_2.0_Saviors/dist/game/config/Rates.ini @@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index a71f93218f..c6a8586af1 100644 --- a/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_Classic_2.0_Saviors/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1101,70 +1120,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_Classic_2.5_Zaken/dist/game/config/Rates.ini b/L2J_Mobius_Classic_2.5_Zaken/dist/game/config/Rates.ini index c8040c2a9d..8f761e7cb1 100644 --- a/L2J_Mobius_Classic_2.5_Zaken/dist/game/config/Rates.ini +++ b/L2J_Mobius_Classic_2.5_Zaken/dist/game/config/Rates.ini @@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_Classic_2.5_Zaken/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_Classic_2.5_Zaken/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index a71f93218f..c6a8586af1 100644 --- a/L2J_Mobius_Classic_2.5_Zaken/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_Classic_2.5_Zaken/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1101,70 +1120,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_Classic_2.7_Antharas/dist/game/config/Rates.ini b/L2J_Mobius_Classic_2.7_Antharas/dist/game/config/Rates.ini index c8040c2a9d..8f761e7cb1 100644 --- a/L2J_Mobius_Classic_2.7_Antharas/dist/game/config/Rates.ini +++ b/L2J_Mobius_Classic_2.7_Antharas/dist/game/config/Rates.ini @@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_Classic_2.7_Antharas/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_Classic_2.7_Antharas/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index a71f93218f..c6a8586af1 100644 --- a/L2J_Mobius_Classic_2.7_Antharas/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_Classic_2.7_Antharas/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1101,70 +1120,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_Classic_2.8_SevenSigns/dist/game/config/Rates.ini b/L2J_Mobius_Classic_2.8_SevenSigns/dist/game/config/Rates.ini index c8040c2a9d..8f761e7cb1 100644 --- a/L2J_Mobius_Classic_2.8_SevenSigns/dist/game/config/Rates.ini +++ b/L2J_Mobius_Classic_2.8_SevenSigns/dist/game/config/Rates.ini @@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_Classic_2.8_SevenSigns/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_Classic_2.8_SevenSigns/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index dd2b743596..22a38ba053 100644 --- a/L2J_Mobius_Classic_2.8_SevenSigns/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_Classic_2.8_SevenSigns/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1116,70 +1135,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_Classic_2.9.5_Saviors/dist/game/config/Rates.ini b/L2J_Mobius_Classic_2.9.5_Saviors/dist/game/config/Rates.ini index c8040c2a9d..8f761e7cb1 100644 --- a/L2J_Mobius_Classic_2.9.5_Saviors/dist/game/config/Rates.ini +++ b/L2J_Mobius_Classic_2.9.5_Saviors/dist/game/config/Rates.ini @@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_Classic_2.9.5_Saviors/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_Classic_2.9.5_Saviors/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index dd2b743596..22a38ba053 100644 --- a/L2J_Mobius_Classic_2.9.5_Saviors/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_Classic_2.9.5_Saviors/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1116,70 +1135,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_Classic_2.9_SecretOfEmpire/dist/game/config/Rates.ini b/L2J_Mobius_Classic_2.9_SecretOfEmpire/dist/game/config/Rates.ini index c8040c2a9d..8f761e7cb1 100644 --- a/L2J_Mobius_Classic_2.9_SecretOfEmpire/dist/game/config/Rates.ini +++ b/L2J_Mobius_Classic_2.9_SecretOfEmpire/dist/game/config/Rates.ini @@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_Classic_2.9_SecretOfEmpire/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_Classic_2.9_SecretOfEmpire/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index dd2b743596..22a38ba053 100644 --- a/L2J_Mobius_Classic_2.9_SecretOfEmpire/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_Classic_2.9_SecretOfEmpire/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1116,70 +1135,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/config/Rates.ini b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/config/Rates.ini index c8040c2a9d..8f761e7cb1 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/dist/game/config/Rates.ini +++ b/L2J_Mobius_Classic_3.0_TheKamael/dist/game/config/Rates.ini @@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index dd2b743596..22a38ba053 100644 --- a/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_Classic_3.0_TheKamael/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1116,70 +1135,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_Classic_Interlude/dist/game/config/Rates.ini b/L2J_Mobius_Classic_Interlude/dist/game/config/Rates.ini index c8040c2a9d..8f761e7cb1 100644 --- a/L2J_Mobius_Classic_Interlude/dist/game/config/Rates.ini +++ b/L2J_Mobius_Classic_Interlude/dist/game/config/Rates.ini @@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index 764da47bed..3956b9b902 100644 --- a/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_Classic_Interlude/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1026,70 +1045,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/config/Rates.ini b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/config/Rates.ini index 121009c5fc..6d1cee0b8e 100644 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/config/Rates.ini +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/dist/game/config/Rates.ini @@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index 95222d418a..72a9890d41 100644 --- a/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_Essence_4.2_DwellingOfSpirits/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1041,70 +1064,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_Essence_5.2_FrostLord/dist/game/config/Rates.ini b/L2J_Mobius_Essence_5.2_FrostLord/dist/game/config/Rates.ini index 121009c5fc..6d1cee0b8e 100644 --- a/L2J_Mobius_Essence_5.2_FrostLord/dist/game/config/Rates.ini +++ b/L2J_Mobius_Essence_5.2_FrostLord/dist/game/config/Rates.ini @@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index 95222d418a..72a9890d41 100644 --- a/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_Essence_5.2_FrostLord/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1041,70 +1064,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_Essence_6.2_Vanguard/dist/game/config/Rates.ini b/L2J_Mobius_Essence_6.2_Vanguard/dist/game/config/Rates.ini index 121009c5fc..6d1cee0b8e 100644 --- a/L2J_Mobius_Essence_6.2_Vanguard/dist/game/config/Rates.ini +++ b/L2J_Mobius_Essence_6.2_Vanguard/dist/game/config/Rates.ini @@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index b07cbfce9f..fc0de3ecc9 100644 --- a/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_Essence_6.2_Vanguard/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1059,70 +1082,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /** diff --git a/L2J_Mobius_Essence_6.3_Crusader/dist/game/config/Rates.ini b/L2J_Mobius_Essence_6.3_Crusader/dist/game/config/Rates.ini index 121009c5fc..6d1cee0b8e 100644 --- a/L2J_Mobius_Essence_6.3_Crusader/dist/game/config/Rates.ini +++ b/L2J_Mobius_Essence_6.3_Crusader/dist/game/config/Rates.ini @@ -109,6 +109,7 @@ DropChanceMultiplierByItemId = 57,1 # Maximum drop occurrences. # Note: Items that have 100% drop chance without server rate multipliers # 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 DropMaxOccurrencesRaidboss = 7 diff --git a/L2J_Mobius_Essence_6.3_Crusader/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java b/L2J_Mobius_Essence_6.3_Crusader/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java index b07cbfce9f..fc0de3ecc9 100644 --- a/L2J_Mobius_Essence_6.3_Crusader/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java +++ b/L2J_Mobius_Essence_6.3_Crusader/java/org/l2jmobius/gameserver/model/actor/templates/NpcTemplate.java @@ -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 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 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 randomDrops = null; + ItemHolder cachedItem = null; + double totalChance; // total group chance is 100 + for (DropGroupHolder group : _dropGroups) { - break; - } - - double groupRate = 1; - final List 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) + totalChance = 0; + GROUP_DROP: for (DropHolder dropItem : group.getDropList()) { - if (itemChance <= 0) - { - continue; - } + final int itemId = dropItem.getItemId(); + final ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); + final boolean champion = victim.isChampion(); - rateChance *= itemChance; - if (champion && (itemId == Inventory.ADENA_ID)) + // chance + double rateChance = 1; + if (Config.RATE_DROP_CHANCE_BY_ID.get(itemId) != null) { - rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; - } - } - else if (item.hasExImmediateEffect()) - { - rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; - } - else - { - rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); - } - - // premium chance - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId) != null) - { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE_BY_ID.get(itemId); + rateChance *= Config.RATE_DROP_CHANCE_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateChance *= Config.CHAMPION_ADENAS_REWARDS_CHANCE; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb chance? :) + rateChance *= Config.RATE_HERB_DROP_CHANCE_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid chance? :) + rateChance *= Config.RATE_RAID_DROP_CHANCE_MULTIPLIER; } else { - rateChance *= Config.PREMIUM_RATE_DROP_CHANCE; - } - } - - // 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; + rateChance *= Config.RATE_DEATH_DROP_CHANCE_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_CHANCE : 1); } - // calculate if item will drop - totalChance += dropItem.getChance(); - if (dropChance >= totalChance) + // premium chance + if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) { - 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 if ((Rnd.nextDouble() * 100) > (dropItem.getItemId() == Inventory.ADENA_ID ? levelGapChanceToDropAdena : levelGapChanceToDrop)) { - continue; + continue GROUP_DROP; } - // skip zero chance drops - final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); - if ((itemChance != null) && (itemChance <= 0)) + // calculate chances + final ItemHolder drop = calculateGroupDrop(group, dropItem, victim, killer, groupItemChance); + if (drop == null) { - continue; + continue GROUP_DROP; } - // create the drop - final ItemHolder drop = calculateGroupDrop(dropItem, victim, killer); - - // create list + // create lists + if (randomDrops == null) + { + randomDrops = new ArrayList<>(dropOccurrenceCounter); + } if (calculatedDrops == null) { calculatedDrops = new ArrayList<>(dropOccurrenceCounter); } // finally + final Float itemChance = Config.RATE_DROP_CHANCE_BY_ID.get(dropItem.getItemId()); if (itemChance != null) { - if ((dropItem.getChance() * itemChance) < 100) + if ((groupItemChance * itemChance) < 100) { 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--; + if (rateChance == 1) // custom rates break this logic because total chance is more than 100% + { + randomDrops.add(drop); + } } calculatedDrops.add(drop); - // no more drops from this group - break GROUP_DROP; + // no more drops from this group, only use on x1, custom rates break this logic because total chance is more than 100% + if (rateChance == 1) + { + break GROUP_DROP; + } } } - } - - // champion extra drop - if (victim.isChampion()) - { - if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + + // add temporarily removed item when not replaced + if ((dropOccurrenceCounter > 0) && (cachedItem != null) && (calculatedDrops != null)) { - 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 - if (calculatedDrops == null) + // champion extra drop + if (victim.isChampion()) { - calculatedDrops = new ArrayList<>(); - } - - if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) - { - calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + if ((victim.getLevel() < killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_LOWER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + if ((victim.getLevel() > killer.getLevel()) && (Rnd.get(100) < Config.CHAMPION_REWARD_HIGHER_LEVEL_ITEM_CHANCE)) + { + return calculatedDrops; + } + + // create list + if (calculatedDrops == null) + { + calculatedDrops = new ArrayList<>(); + } + + if (!calculatedDrops.containsAll(Config.CHAMPION_REWARD_ITEMS)) + { + calculatedDrops.addAll(Config.CHAMPION_REWARD_ITEMS); + } } } @@ -1059,70 +1082,78 @@ public class NpcTemplate extends CreatureTemplate implements IIdentifiable } /** + * @param group * @param dropItem * @param victim * @param killer + * @param chance * @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 ItemTemplate item = ItemTable.getInstance().getTemplate(itemId); final boolean champion = victim.isChampion(); - // calculate amount - double rateAmount = 1; - if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) + // calculate if item will drop + if ((Rnd.nextDouble() * 100) < chance) { - rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); - if (champion && (itemId == Inventory.ADENA_ID)) + // amount is calculated after chance returned success + double rateAmount = 1; + if (Config.RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) { - rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; - } - } - else if (item.hasExImmediateEffect()) - { - rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; - } - else if (victim.isRaid()) - { - rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; - } - else - { - rateAmount *= Config.RATE_DEATH_DROP_AMOUNT_MULTIPLIER * (champion ? Config.CHAMPION_REWARDS_AMOUNT : 1); - } - - // premium amount - if (Config.PREMIUM_SYSTEM_ENABLED && (killer.getActingPlayer() != null) && killer.getActingPlayer().hasPremiumStatus()) - { - if (Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId) != null) - { - rateAmount *= Config.PREMIUM_RATE_DROP_AMOUNT_BY_ID.get(itemId); + rateAmount *= Config.RATE_DROP_AMOUNT_BY_ID.get(itemId); + if (champion && (itemId == Inventory.ADENA_ID)) + { + rateAmount *= Config.CHAMPION_ADENAS_REWARDS_AMOUNT; + } } else if (item.hasExImmediateEffect()) { - // TODO: Premium herb amount? :) + rateAmount *= Config.RATE_HERB_DROP_AMOUNT_MULTIPLIER; } else if (victim.isRaid()) { - // TODO: Premium raid amount? :) + rateAmount *= Config.RATE_RAID_DROP_AMOUNT_MULTIPLIER; } 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 - 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)); + return null; } /**