// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2022 // www.magicbane.com package engine.gameManager; import engine.Enum; import engine.loot.*; import engine.net.DispatchMessage; import engine.net.client.msg.ErrorPopupMsg; import engine.net.client.msg.chat.ChatSystemMsg; import engine.objects.*; import org.pmw.tinylog.Logger; import java.util.ArrayList; import java.util.HashMap; import java.util.concurrent.ThreadLocalRandom; /** * Class contains static methods for data from Magicbane's loot tables */ public enum LootManager { LOOTMANAGER; // Newer tables public static HashMap> _genTables = new HashMap<>(); public static HashMap> _itemTables = new HashMap<>(); public static HashMap> _modTables = new HashMap<>(); public static HashMap> _modTypeTables = new HashMap<>(); // Drop Rates public static float NORMAL_DROP_RATE; public static float NORMAL_EXP_RATE; public static float NORMAL_GOLD_RATE; public static float HOTZONE_DROP_RATE; public static float HOTZONE_EXP_RATE; public static float HOTZONE_GOLD_RATE; public static HashMap> _bootySetMap = new HashMap<>(); // Bootstrap routine to initialize the Loot Manager public static void init() { // Load loot tables from database. _genTables = DbManager.LootQueries.LOAD_GEN_ITEM_TABLES(); _itemTables = DbManager.LootQueries.LOAD_ITEM_TABLES(); _modTables = DbManager.LootQueries.LOAD_MOD_TABLES(); _modTypeTables = DbManager.LootQueries.LOAD_MOD_TYPE_TABLES(); // Cache drop rate values from Config manager. NORMAL_DROP_RATE = Float.parseFloat(ConfigManager.MB_NORMAL_DROP_RATE.getValue()); NORMAL_EXP_RATE = Float.parseFloat(ConfigManager.MB_NORMAL_EXP_RATE.getValue()); NORMAL_GOLD_RATE = Float.parseFloat(ConfigManager.MB_NORMAL_GOLD_RATE.getValue()); HOTZONE_DROP_RATE = Float.parseFloat(ConfigManager.MB_HOTZONE_DROP_RATE.getValue()); HOTZONE_EXP_RATE = Float.parseFloat(ConfigManager.MB_HOTZONE_EXP_RATE.getValue()); HOTZONE_GOLD_RATE = Float.parseFloat(ConfigManager.MB_HOTZONE_GOLD_RATE.getValue()); } public static void GenerateMobLoot(Mob mob, boolean fromDeath) { //determine if mob is in hotzone boolean inHotzone = ZoneManager.inHotZone(mob.getLoc()); //iterate the booty sets if (mob.getMobBase().bootySet != 0 && _bootySetMap.containsKey(mob.getMobBase().bootySet) == true) RunBootySet(_bootySetMap.get(mob.getMobBase().bootySet), mob, inHotzone, fromDeath); if (mob.bootySet != 0 && _bootySetMap.containsKey(mob.bootySet) == true) RunBootySet(_bootySetMap.get(mob.bootySet), mob, inHotzone, fromDeath); //lastly, check mobs inventory for godly or disc runes to send a server announcement if (!fromDeath) for (Item it : mob.getInventory()) { ItemBase ib = it.getItemBase(); if (ib.isDiscRune() || ib.getName().toLowerCase().contains("of the gods")) { ChatSystemMsg chatMsg = new ChatSystemMsg(null, mob.getName() + " in " + mob.getParentZone().getName() + " has found the " + ib.getName() + ". Are you tough enough to take it?"); chatMsg.setMessageType(10); chatMsg.setChannel(Enum.ChatChannelType.SYSTEM.getChannelID()); DispatchMessage.dispatchMsgToAll(chatMsg); } } } private static void RunBootySet(ArrayList entries, Mob mob, boolean inHotzone, boolean fromDeath) { boolean hotzoneWasRan = false; float dropRate = 1.0f; if (fromDeath) { GenerateEquipmentDrop(mob); return; } // Iterate all entries in this bootySet and process accordingly for (BootySetEntry bse : entries) { switch (bse.bootyType) { case "GOLD": GenerateGoldDrop(mob, bse, inHotzone); break; case "LOOT": if (mob.getSafeZone() == false) dropRate = LootManager.NORMAL_DROP_RATE; if (inHotzone == true) dropRate = LootManager.HOTZONE_DROP_RATE; if (ThreadLocalRandom.current().nextInt(1, 100 + 1) < (bse.dropChance * dropRate)) GenerateLootDrop(mob, bse.genTable, false); //generate normal loot drop // Generate hotzone loot if in hotzone // Only one bite at the hotzone apple per bootyset. if (inHotzone == true && hotzoneWasRan == false) if (_genTables.containsKey(bse.genTable + 1) && ThreadLocalRandom.current().nextInt(1, 100 + 1) < (bse.dropChance * dropRate)) { GenerateLootDrop(mob, bse.genTable + 1, true); //generate loot drop from hotzone table hotzoneWasRan = true; } break; case "ITEM": GenerateInventoryDrop(mob, bse); break; } } } public static MobLoot getGenTableItem(int genTableID, AbstractCharacter mob, Boolean inHotzone) { if (mob == null || _genTables.containsKey(genTableID) == false) return null; MobLoot outItem; int genRoll = ThreadLocalRandom.current().nextInt(1,100 + 1); GenTableEntry selectedRow = GenTableEntry.rollTable(genTableID, genRoll, 1.0f); if (selectedRow == null) return null; int itemTableId = selectedRow.itemTableID; if (_itemTables.containsKey(itemTableId) == false) return null; //gets the 1-320 roll for this mob int itemTableRoll = 0; int objectType = mob.getObjectType().ordinal(); if(mob.getObjectType().ordinal() == 52) { //52 = player character itemTableRoll = ThreadLocalRandom.current().nextInt(1,320 + 1); } else{ itemTableRoll = TableRoll(mob.level, inHotzone); } ItemTableEntry tableRow = ItemTableEntry.rollTable(itemTableId, itemTableRoll); if (tableRow == null) return null; int itemUUID = tableRow.cacheID; if (itemUUID == 0) return null; if (ItemBase.getItemBase(itemUUID).getType().ordinal() == Enum.ItemType.RESOURCE.ordinal()) { int amount = ThreadLocalRandom.current().nextInt(tableRow.minSpawn, tableRow.maxSpawn + 1); return new MobLoot(mob, ItemBase.getItemBase(itemUUID), amount, false); } outItem = new MobLoot(mob, ItemBase.getItemBase(itemUUID), false); Enum.ItemType outType = outItem.getItemBase().getType(); if(selectedRow.pModTable != 0){ try { outItem = GeneratePrefix(mob, outItem, genTableID, genRoll, inHotzone); outItem.setIsID(false); } catch (Exception e) { Logger.error("Failed to GeneratePrefix for item: " + outItem.getName()); } } if(selectedRow.sModTable != 0){ try { outItem = GenerateSuffix(mob, outItem, genTableID, genRoll, inHotzone); outItem.setIsID(false); } catch (Exception e) { Logger.error("Failed to GenerateSuffix for item: " + outItem.getName()); } } return outItem; } private static MobLoot GeneratePrefix(AbstractCharacter mob, MobLoot inItem, int genTableID, int genRoll, Boolean inHotzone) { GenTableEntry selectedRow = GenTableEntry.rollTable(genTableID, genRoll, 1.0f); if (selectedRow == null) return inItem; int prefixroll = ThreadLocalRandom.current().nextInt(1, 100 + 1); ModTypeTableEntry prefixTable = ModTypeTableEntry.rollTable(selectedRow.pModTable, prefixroll); if (prefixTable == null) return inItem; int prefixTableRoll = 0; if(mob.getObjectType().ordinal() == 52) { prefixTableRoll = ThreadLocalRandom.current().nextInt(1,320 + 1); } else{ prefixTableRoll = TableRoll(mob.level, inHotzone); } ModTableEntry prefixMod = ModTableEntry.rollTable(prefixTable.modTableID, prefixTableRoll); if (prefixMod == null) return inItem; if (prefixMod.action.length() > 0) { inItem.setPrefix(prefixMod.action); inItem.addPermanentEnchantment(prefixMod.action, 0, prefixMod.level, true); } return inItem; } private static MobLoot GenerateSuffix(AbstractCharacter mob, MobLoot inItem, int genTableID, int genRoll, Boolean inHotzone) { GenTableEntry selectedRow = GenTableEntry.rollTable(genTableID, genRoll, 1.0f); if (selectedRow == null) return inItem; int suffixRoll = ThreadLocalRandom.current().nextInt(1, 100 + 1); ModTypeTableEntry suffixTable = ModTypeTableEntry.rollTable(selectedRow.sModTable, suffixRoll); if (suffixTable == null) return inItem; int suffixTableRoll = 0; if(mob.getObjectType().ordinal() == 52) { suffixTableRoll = ThreadLocalRandom.current().nextInt(1,320 + 1); } else{ suffixTableRoll = TableRoll(mob.level, inHotzone); } ModTableEntry suffixMod = ModTableEntry.rollTable(suffixTable.modTableID, suffixTableRoll); if (suffixMod == null) return inItem; if (suffixMod.action.length() > 0) { inItem.setSuffix(suffixMod.action); inItem.addPermanentEnchantment(suffixMod.action, 0, suffixMod.level, false); } return inItem; } public static int TableRoll(int mobLevel, Boolean inHotzone) { if (mobLevel > 65) mobLevel = 65; int max = (int) (4.882 * mobLevel + 127.0); if (max > 319) max = 319; int min = (int) (4.469 * mobLevel - 3.469); if (min < 70) min = 70; if (inHotzone) min += mobLevel; int roll = ThreadLocalRandom.current().nextInt(min, max + 1); return roll; } public static void GenerateGoldDrop(Mob mob, BootySetEntry bse, Boolean inHotzone) { int chanceRoll = ThreadLocalRandom.current().nextInt(1, 100 + 1); //early exit, failed to hit minimum chance roll if (chanceRoll > bse.dropChance) return; //determine and add gold to mob inventory int high = bse.highGold; int low = bse.lowGold; int gold = ThreadLocalRandom.current().nextInt(low, high + 1); if (inHotzone == true) gold = (int) (gold * HOTZONE_GOLD_RATE); else gold = (int) (gold * NORMAL_GOLD_RATE); if (gold > 0) { MobLoot goldAmount = new MobLoot(mob, gold); mob.getCharItemManager().addItemToInventory(goldAmount); } } public static void GenerateLootDrop(Mob mob, int tableID, Boolean inHotzone) { try { MobLoot toAdd = getGenTableItem(tableID, mob, inHotzone); if (toAdd != null) mob.getCharItemManager().addItemToInventory(toAdd); } catch (Exception e) { //TODO chase down loot generation error, affects roughly 2% of drops int i = 0; } } public static void GenerateEquipmentDrop(Mob mob) { //do equipment here int dropCount = 0; if (mob.getEquip() != null) for (MobEquipment me : mob.getEquip().values()) { if (me.getDropChance() == 0) continue; float equipmentRoll = ThreadLocalRandom.current().nextInt(1, 100 + 1); float dropChance = me.getDropChance() * 100; if (equipmentRoll > dropChance) continue; MobLoot ml = new MobLoot(mob, me.getItemBase(), false); if (ml != null && dropCount < 1) { ml.setIsID(true); ml.setDurabilityCurrent((short) (ml.getDurabilityCurrent() - ThreadLocalRandom.current().nextInt(5) + 1)); mob.getCharItemManager().addItemToInventory(ml); dropCount = 1; //break; // Exit on first successful roll. } } } public static void GenerateInventoryDrop(Mob mob, BootySetEntry bse) { int chanceRoll = ThreadLocalRandom.current().nextInt(1, 100 + 1); //early exit, failed to hit minimum chance roll if (chanceRoll > bse.dropChance) return; MobLoot lootItem = new MobLoot(mob, ItemBase.getItemBase(bse.itemBase), true); if (lootItem != null) mob.getCharItemManager().addItemToInventory(lootItem); } public static void peddleFate(PlayerCharacter playerCharacter, Item gift) { //get table ID for the itembase ID int tableID = 0; if (_bootySetMap.get(gift.getItemBaseID()) != null) tableID = _bootySetMap.get(gift.getItemBaseID()).get(ThreadLocalRandom.current().nextInt(_bootySetMap.get(gift.getItemBaseID()).size())).genTable; if (tableID == 0) return; //get the character item manager CharacterItemManager itemMan = playerCharacter.getCharItemManager(); if (itemMan == null) return; //check if player owns the gift he is trying to open if (itemMan.doesCharOwnThisItem(gift.getObjectUUID()) == false) return; //roll 1-100 for the gen table selection int genRoll = ThreadLocalRandom.current().nextInt(1, 100 + 1); GenTableEntry selectedRow = GenTableEntry.rollTable(tableID, genRoll, LootManager.NORMAL_DROP_RATE); if(selectedRow == null) return; //roll 220-320 for the item table selection int itemRoll = ThreadLocalRandom.current().nextInt(220, 320 + 1); ItemTableEntry selectedItem = ItemTableEntry.rollTable(selectedRow.itemTableID, itemRoll); if (selectedItem == null) return; //create the item from the table, quantity is always 1 MobLoot winnings = new MobLoot(playerCharacter, ItemBase.getItemBase(selectedItem.cacheID), 1, false); if (winnings == null) return; //early exit if the inventory of the player will not old the item if (itemMan.hasRoomInventory(winnings.getItemBase().getWeight()) == false) { ErrorPopupMsg.sendErrorPopup(playerCharacter, 21); return; } //determine if the winning item needs a prefix if(selectedRow.pModTable != 0){ int prefixRoll = ThreadLocalRandom.current().nextInt(220,320 + 1); ModTableEntry prefix = ModTableEntry.rollTable(selectedRow.pModTable, prefixRoll); if(prefix != null) winnings.addPermanentEnchantment(prefix.action, 0, prefix.level, true); } //determine if the winning item needs a suffix if(selectedRow.sModTable != 0){ int suffixRoll = ThreadLocalRandom.current().nextInt(220,320 + 1); ModTableEntry suffix = ModTableEntry.rollTable(selectedRow.sModTable, suffixRoll); if (suffix != null) winnings.addPermanentEnchantment(suffix.action, 0, suffix.level, true); } winnings.setIsID(true); //remove gift from inventory itemMan.consume(gift); //add winnings to player inventory Item playerWinnings = winnings.promoteToItem(playerCharacter); itemMan.addItemToInventory(playerWinnings); itemMan.updateInventory(); } }