// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2022 // www.magicbane.com package engine.gameManager; import engine.loot.WorkOrder; import engine.mbEnums; import engine.net.client.msg.ErrorPopupMsg; import engine.objects.*; import engine.powers.EffectsBase; import org.pmw.tinylog.Logger; import java.util.HashMap; import java.util.concurrent.BlockingQueue; import java.util.concurrent.DelayQueue; import java.util.concurrent.atomic.AtomicInteger; public enum ForgeManager implements Runnable { FORGE_MANAGER; private static final BlockingQueue workOrders = new DelayQueue(); public static final AtomicInteger wordOrderCounter = new AtomicInteger(0); public static HashMap oven = new HashMap(); @Override public void run() { while (true) { try { WorkOrder workOrder = workOrders.take(); // Completed or canceled work orders are not re-enqueued if (workOrder.runCanceled || workOrder.runCompleted) continue; // Create negative ID items to add to collections for (int i = 0; i < workOrder.slots_used; ++i) { // Create workOrder items; one for each slot // assigned to this workOrder. // if Prefix and suffix are null random roll item // otherwise roll what was asked for workOrder.total_produced = workOrder.total_produced + 1; } Logger.info("item forged:" + workOrder.workOrderID + " (" + workOrder.total_produced + "/" + workOrder.total_to_produce + ")"); if (workOrder.total_produced >= workOrder.total_to_produce) { Logger.info("workOrder has completed: " + workOrder.workOrderID); workOrder.runCompleted = true; workOrder.vendor.workOrders.remove(workOrder); continue; } // Persist current items that are cooking // after removing the negative id item from all collections. // Add new item to the vendors inventory // Resubmit workOrder workOrder.completionTime = System.currentTimeMillis() + workOrder.rollingDuration; workOrders.add(workOrder); Logger.info(workOrder.toString()); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void start() { Thread forgeManager; forgeManager = new Thread(FORGE_MANAGER); forgeManager.setName("Forge Manager"); forgeManager.start(); } public static void submit(WorkOrder workOrder) { workOrder.workOrderID = wordOrderCounter.incrementAndGet(); workOrder.rollingDuration = ForgeManager.calcRollingDuration(workOrder); workOrder.completionTime = System.currentTimeMillis() + workOrder.rollingDuration; workOrder.slots_used = calcAvailableSlots(workOrder); // Cost to execute this workOrder workOrder.production_cost = calcProductionCost(workOrder); // Set total cost for this production run workOrder.production_cost_total.putAll(workOrder.production_cost); workOrder.production_cost_total.forEach((key, value) -> workOrder.production_cost_total.compute(key, (k, v) -> v * workOrder.total_to_produce)); // Single item rolls are total_to_produce of 0; if (workOrder.slots_used > 0 && workOrder.total_to_produce == 0) workOrder.slots_used = 1; // Create in-memory items and add to collections forgeItems(workOrder); Logger.info(workOrder.toString()); workOrder.vendor.workOrders.add(workOrder); workOrders.add(workOrder); } public static boolean validate(PlayerCharacter playerCharacter, WorkOrder workOrder) { ItemTemplate template = ItemTemplate.templates.get(workOrder.templateID); if (!workOrder.vendor.charItemManager.hasRoomInventory(template.item_wt)) { if (playerCharacter != null) ErrorPopupMsg.sendErrorPopup(playerCharacter, 30); //30: That person cannot carry that item return false; } if (!workOrder.vendor.getItemModTable().contains(((byte) template.modTable))) { if (playerCharacter != null) ErrorPopupMsg.sendErrorPopup(playerCharacter, 59); //59: This hireling does not have this formula return false; } if (!calcCostOverrun(workOrder).isEmpty()) { if (playerCharacter != null) ErrorPopupMsg.sendErrorPopup(playerCharacter, 18); //18: You can't really afford that return false; } // Forge must be protected in order to access warehouse. if (workOrder.production_cost_total.size() > 1) if (!workOrder.vendor.building.protectionState.equals(mbEnums.ProtectionState.PROTECTED)) { if (playerCharacter != null) ErrorPopupMsg.sendErrorPopup(playerCharacter, 193); //193: Production denied: This building must be protected to gain access to warehouse } return true; } public static long calcRollingDuration(WorkOrder workOrder) { float rollingDuration; rollingDuration = workOrder.vendor.getBuilding().getRank() * -5L + 40; rollingDuration *= 60000; rollingDuration *= Float.parseFloat(ConfigManager.MB_PRODUCTION_RATE.getValue()); ItemTemplate template = ItemTemplate.templates.get(workOrder.templateID); // Bane circles if (template.item_bane_rank > 0) rollingDuration = (long) template.item_bane_rank * 60 * 60 * 3 * 1000 * Float.parseFloat(ConfigManager.MB_PRODUCTION_RATE.getValue()); return (long) rollingDuration; } public static int calcAvailableSlots(WorkOrder workOrder) { // Slots available in a forge are based on the npc rank int availableSlots = workOrder.vendor.getRank(); // Slots currently used up by the npc workOrders for (WorkOrder npcWorkOrder : workOrder.vendor.workOrders) availableSlots = availableSlots - npcWorkOrder.slots_used; // Slot count override for single item production if (workOrder.total_to_produce == 0 && availableSlots > 1) availableSlots = 1; return availableSlots; } public static HashMap calcProductionCost(WorkOrder workOrder) { // Calculate the production cost for a single run of this workOrder HashMap production_cost = new HashMap<>(); ItemTemplate template = ItemTemplate.templates.get(workOrder.templateID); // Add gold and resource costs from template production_cost.put(mbEnums.ResourceType.GOLD, template.item_value); production_cost.putAll(template.item_resource_cost); // Calculate cost of prefix and suffix if (workOrder.prefixToken != 0) { EffectsBase prefix = PowersManager.getEffectByToken(workOrder.prefixToken); EffectsBase prefixValue = PowersManager.getEffectByIDString(prefix.getIDString() + 'A'); production_cost.putAll(prefixValue.getResourcesForEffect()); } if (workOrder.suffixToken != 0) { EffectsBase suffix = PowersManager.getEffectByToken(workOrder.suffixToken); EffectsBase suffixValue = PowersManager.getEffectByIDString(suffix.getIDString() + 'A'); production_cost.putAll(suffixValue.getResourcesForEffect()); } return production_cost; } public static HashMap calcCostOverrun(WorkOrder workOrder) { HashMap costMap = new HashMap<>(); Warehouse warehouse; // See if we can meet gold only requirements and early exit if (workOrder.production_cost_total.size() == 1) { if (workOrder.vendor.building.getStrongboxValue() > workOrder.production_cost_total.get(mbEnums.ResourceType.GOLD)) return costMap; } // Gold deficit exists so a warehouse is required warehouse = workOrder.vendor.building.getCity() == null ? null : workOrder.vendor.building.getCity().warehouse; if (warehouse == null) return workOrder.production_cost_total; // Method returns a map of resourceType that a transaction overdrafts. HashMap overflowMap = new HashMap<>(); for (mbEnums.ResourceType resourceType : workOrder.production_cost_total.keySet()) { int debit = warehouse.resources.get(resourceType) - workOrder.production_cost_total.get(resourceType); // Locked resources are always unavailable if (debit < 0 || warehouse.locked.contains(resourceType)) overflowMap.put(resourceType, debit); } return overflowMap; } public static Item forgeItem(WorkOrder workOrder) { Item forgedItem = new Item(workOrder.templateID); forgedItem.objectUUID = MobLoot.lastNegativeID.getAndDecrement(); forgedItem.containerType = mbEnums.ItemContainerType.FORGE; forgedItem.ownerID = workOrder.vendor.getObjectUUID(); // Forged items are unidentified until completed forgedItem.flags.remove(mbEnums.ItemFlags.Identified); return forgedItem; } public static void forgeItems(WorkOrder workOrder) { for (int i = 0; i < workOrder.slots_used; ++i) { Item forged_item = forgeItem(workOrder); ForgeManager.oven.put(forged_item, workOrder); workOrder.cooking.add(forged_item); workOrder.total_produced = workOrder.total_produced + 1; Logger.info("Forged item: " + forged_item.objectUUID + " of template " + forged_item.templateID); } } }