// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2022 // www.magicbane.com package engine.loot; import engine.gameManager.DbManager; import engine.gameManager.ForgeManager; import engine.mbEnums; import engine.objects.Item; import engine.objects.ItemTemplate; import engine.objects.NPC; import engine.objects.Warehouse; import org.json.JSONArray; import org.json.JSONObject; import java.time.Duration; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.ArrayList; import java.util.EnumSet; import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; public class WorkOrder implements Delayed { // MB Dev notes: // Class defines a Forge rolling request made through a vendor // interaction; submitted to the ForgeManager singleton for completion. // // A workOrder once created will last until all items are either // completed or junked. They are persisted in the table dyn_workorders. public int workOrderID; public NPC vendor; public int slots_used; public int total_to_produce; public int total_produced; public boolean multiple_slot_request; public HashMap production_cost = new HashMap<>(); public HashMap production_cost_total = new HashMap<>(); public int templateID; public String item_name_override; public int prefixToken; public int suffixToken; public long rollingDuration; public long completionTime; public boolean runCompleted = false; public boolean runCanceled = false; // This collection is serialized to the vendor rolling window in ManageNPCMsg. public ConcurrentHashMap.KeySetView cooking = ConcurrentHashMap.newKeySet(); public WorkOrder() { } public WorkOrder(JSONObject jsonWorkOrder) { // This constructor is used to load workOrders from disk // during bootstrap. (dyn_workorders) this.workOrderID = jsonWorkOrder.getInt("workOrderID"); this.vendor = NPC.getNPC(jsonWorkOrder.getInt("vendor")); this.slots_used = jsonWorkOrder.getInt("slots_used"); this.total_to_produce = jsonWorkOrder.getInt("total_to_produce"); this.total_produced = jsonWorkOrder.getInt("total_produced"); this.multiple_slot_request = jsonWorkOrder.getBoolean("multiple_slot_request"); this.templateID = jsonWorkOrder.getInt("templateID"); this.item_name_override = jsonWorkOrder.getString("item_name_override"); this.prefixToken = jsonWorkOrder.getInt("prefixToken"); this.suffixToken = jsonWorkOrder.getInt("suffixToken"); this.slots_used = jsonWorkOrder.getInt("slots_used"); this.rollingDuration = jsonWorkOrder.getLong("rollingDuration"); this.completionTime = jsonWorkOrder.getLong("completionTime"); this.runCompleted = jsonWorkOrder.getBoolean("runCompleted"); JSONObject productionCostMap = jsonWorkOrder.getJSONObject("production_cost"); for (String key : productionCostMap.keySet()) { mbEnums.ResourceType resourceType = mbEnums.ResourceType.valueOf(key); int value = productionCostMap.getInt(key); this.production_cost.put(resourceType, value); } JSONObject productionTotalCostMap = jsonWorkOrder.getJSONObject("production_cost_total"); for (String key : productionTotalCostMap.keySet()) { mbEnums.ResourceType resourceType = mbEnums.ResourceType.valueOf(key); int value = productionTotalCostMap.getInt(key); this.production_cost_total.put(resourceType, value); } // Reconstruct cooking items JSONArray tokenList = jsonWorkOrder.getJSONArray("cookingTokens"); for (Object o : tokenList) { int prefix = ((JSONArray) o).getInt(0); int suffix = ((JSONArray) o).getInt(1); Item cookingItem = ForgeManager.forgeItem(this); cookingItem.prefixToken = prefix; cookingItem.suffixToken = suffix; cookingItem.setDateToUpgrade(this.completionTime); } } public static int validate(WorkOrder workOrder) { // Validate that a workOrder can be completed by both // the vendor and the forge. int validation_result = 0; ItemTemplate template = ItemTemplate.templates.get(workOrder.templateID); if (workOrder.vendor.getBuilding() == null) return 58; //58: The formula is beyond the means of this facility if (!workOrder.vendor.charItemManager.hasRoomInventory(template.item_wt)) return 30; //30: That person cannot carry that item if (!workOrder.vendor.getItemModTable().contains((template.modTable))) return 59; //59: This hireling does not have this formula if (!Warehouse.calcCostOverrun(workOrder).isEmpty()) return 10; //18: You can't really afford that // Forge must be protected in order to access warehouse. if (ForgeManager.calcProductionCost(workOrder).size() > 1) if (!EnumSet.of(mbEnums.ProtectionState.PROTECTED, mbEnums.ProtectionState.CONTRACT).contains(workOrder.vendor.building.protectionState)) return 193; //193: Production denied: This building must be protected to gain access to warehouse return validation_result; } public static boolean withdrawWorkOrderCost(WorkOrder workOrder) { if (workOrder.vendor.building.getCity() == null) return false; int strongbox = workOrder.vendor.building.getStrongboxValue(); // Early exit if strongbox can cover gold only roll. if (workOrder.production_cost_total.size() == 1 && workOrder.production_cost_total.get(mbEnums.ResourceType.GOLD) <= strongbox) { workOrder.vendor.building.setStrongboxValue(strongbox - workOrder.production_cost_total.get(mbEnums.ResourceType.GOLD)); workOrder.production_cost_total.put(mbEnums.ResourceType.GOLD, 0); return true; } // Warehouse is required after this point Warehouse warehouse = workOrder.vendor.building.getCity().warehouse; if (warehouse == null) return false; int overflowAmount = workOrder.production_cost_total.get(mbEnums.ResourceType.GOLD) - strongbox; workOrder.vendor.building.setStrongboxValue(0); workOrder.production_cost_total.put(mbEnums.ResourceType.GOLD, workOrder.production_cost_total.get(mbEnums.ResourceType.GOLD) - overflowAmount); // Deduct total production cost from warehouse workOrder.production_cost_total.forEach((key, value) -> warehouse.resources.put(key, warehouse.resources.get(key) - value)); DbManager.WarehouseQueries.UPDATE_WAREHOUSE(warehouse); return true; } @Override public long getDelay(TimeUnit unit) { long timeRemaining = completionTime - System.currentTimeMillis(); return unit.convert(timeRemaining, TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed o) { return Long.compare(this.completionTime, ((WorkOrder) o).completionTime); } public static JSONObject toJson(WorkOrder workOrder) { // Workorders are persisted in JSON format. JSONObject jsonWorkOrder = new JSONObject(); jsonWorkOrder.put("workOrderID", workOrder.workOrderID); jsonWorkOrder.put("vendor", workOrder.vendor.getObjectUUID()); jsonWorkOrder.put("slots_used", workOrder.slots_used); jsonWorkOrder.put("total_to_produce", workOrder.total_to_produce); jsonWorkOrder.put("total_produced", workOrder.total_produced); jsonWorkOrder.put("multiple_slot_request", workOrder.multiple_slot_request); jsonWorkOrder.put("production_cost", workOrder.production_cost); jsonWorkOrder.put("production_cost_total", workOrder.production_cost_total); jsonWorkOrder.put("templateID", workOrder.templateID); jsonWorkOrder.put("item_name_override", workOrder.item_name_override); jsonWorkOrder.put("prefixToken", workOrder.prefixToken); jsonWorkOrder.put("suffixToken", workOrder.suffixToken); jsonWorkOrder.put("rollingDuration", workOrder.rollingDuration); jsonWorkOrder.put("completionTime", workOrder.completionTime); jsonWorkOrder.put("runCompleted", workOrder.runCompleted); ArrayList cookingTokens = new ArrayList<>(); for (Item item : workOrder.cooking) cookingTokens.add(new Integer[]{item.prefixToken, item.suffixToken}); jsonWorkOrder.put("cookingTokens", cookingTokens); return jsonWorkOrder; } public String toString() { LocalDateTime localDateTime = Instant.ofEpochMilli(this.completionTime) .atZone(ZoneId.systemDefault()).toLocalDateTime(); Duration duration = Duration.ofMillis(this.rollingDuration); String outSTring = "\r\nwordOrderID: " + this.workOrderID + "\r\n" + "vendor: " + this.vendor.getObjectUUID() + "\r\n" + "slots_used: " + this.slots_used + "\r\n" + "total_to_produce: " + this.total_to_produce + "\r\n" + "total_produced: " + this.total_produced + "\r\n" + "templateID: " + this.templateID + "\r\n" + "item_name_override: " + this.item_name_override + "\r\n" + "prefixToken: " + this.prefixToken + "\r\n" + "suffixToken: " + this.suffixToken + "\r\n" + "rollingDuration: " + duration + "\r\n" + "completionTime: " + localDateTime + "\r\n" + "runCompleted: " + this.runCompleted + "\r\n" + "runCanceled: " + this.runCanceled + "\r\n" + "productionCost: " + this.production_cost.toString() + "\r\n" + "totalProductionCost:: " + this.production_cost_total.toString(); return outSTring; } }