forked from MagicBane/Server
				
			
				 283 changed files with 2304 additions and 3493 deletions
			
			
		| @ -0,0 +1,408 @@@@ -0,0 +1,408 @@ | ||||
| // • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
 | ||||
| // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
 | ||||
| // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
 | ||||
| // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
 | ||||
| // ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
 | ||||
| //      Magicbane Emulator Project © 2013 - 2022
 | ||||
| //                www.magicbane.com
 | ||||
| 
 | ||||
| package engine.gameManager; | ||||
| 
 | ||||
| import engine.loot.ModTableEntry; | ||||
| import engine.loot.ModTypeTableEntry; | ||||
| import engine.loot.WorkOrder; | ||||
| import engine.mbEnums; | ||||
| import engine.net.DispatchMessage; | ||||
| import engine.net.client.msg.ItemProductionMsg; | ||||
| import engine.objects.City; | ||||
| import engine.objects.Item; | ||||
| import engine.objects.ItemTemplate; | ||||
| import engine.objects.NPC; | ||||
| import engine.powers.EffectsBase; | ||||
| import org.pmw.tinylog.Logger; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashMap; | ||||
| import java.util.concurrent.*; | ||||
| import java.util.concurrent.atomic.AtomicInteger; | ||||
| 
 | ||||
| public enum ForgeManager implements Runnable { | ||||
| 
 | ||||
|     // MB Dev notes:
 | ||||
|     // Class implements forge rolling mechanics for Magicbane.
 | ||||
|     //
 | ||||
|     // .submit(workOrder) may be called from any thread: (ItemProductionMsgHandler).
 | ||||
|     // Concurrency is managed by the same lock used by the warehouse (city.cityTransactionLock).
 | ||||
|     // WorkOrders are persisted then reconstituted at bootstrap using table dyn.workorders.
 | ||||
|     //
 | ||||
|     // Replaces garbage code that looked as if written by a mental patient with face boils.
 | ||||
| 
 | ||||
|     FORGE_MANAGER; | ||||
| 
 | ||||
|     public static final BlockingQueue<WorkOrder> forge = new DelayQueue<>(); | ||||
|     public static final AtomicInteger workOrderCounter = new AtomicInteger(0); | ||||
|     public static final ConcurrentHashMap<NPC, ConcurrentHashMap.KeySetView<WorkOrder, Boolean>> vendorWorkOrderLookup = new ConcurrentHashMap<>(); | ||||
|     public static final ConcurrentHashMap<Item, WorkOrder> itemWorkOrderLookup = new ConcurrentHashMap<>(); | ||||
| 
 | ||||
|     @Override | ||||
| 
 | ||||
|     public void run() { | ||||
| 
 | ||||
|         WorkOrder workOrder; | ||||
| 
 | ||||
|         while (true) { | ||||
| 
 | ||||
|             // .forge is a delayQueue (blocking priority queue using an epoc sort)
 | ||||
|             // workOrders are popped and processed when their completion time has passed.
 | ||||
| 
 | ||||
|             try { | ||||
|                 workOrder = forge.take(); | ||||
| 
 | ||||
|                 // This workOrder has completed production.
 | ||||
| 
 | ||||
|                 if (workOrder.total_produced >= workOrder.total_to_produce) { | ||||
| 
 | ||||
|                     // Set items as completed in the window.
 | ||||
|                     // First CONFIRM_PRODUCE adds virtual item to the interface.
 | ||||
|                     // Second CONFIRM_PRODUCE sets virtual item to complete.
 | ||||
| 
 | ||||
|                     for (Item workOrderItem : workOrder.cooking) { | ||||
|                         workOrderItem.flags.add(mbEnums.ItemFlags.Identified); | ||||
|                         ItemProductionMsg outMsg = new ItemProductionMsg(workOrder.vendor.building, workOrder.vendor, workOrderItem, mbEnums.ProductionActionType.CONFIRM_PRODUCE, true); | ||||
|                         DispatchMessage.dispatchMsgToInterestArea(workOrder.vendor, outMsg, mbEnums.DispatchChannel.SECONDARY, 700, false, false); | ||||
|                     } | ||||
| 
 | ||||
|                     workOrder.runCompleted = true; | ||||
| 
 | ||||
|                     // Update workOrder on disk
 | ||||
| 
 | ||||
|                     DbManager.WarehouseQueries.WRITE_WORKORDER(workOrder); | ||||
|                 } | ||||
| 
 | ||||
|                 if (workOrder.runCompleted) | ||||
|                     continue; | ||||
| 
 | ||||
|                 // Move current cooking batch to vendor inventory
 | ||||
| 
 | ||||
|                 completeWorkOrderBatch(workOrder); | ||||
| 
 | ||||
|                 // Create new set of in-memory only virtual items
 | ||||
| 
 | ||||
|                 forgeWorkOrderBatch(workOrder); | ||||
| 
 | ||||
|                 // enQueue this workOrder again; back into the oven
 | ||||
|                 // until all items for this workOrder are completed.
 | ||||
| 
 | ||||
|                 forge.add(workOrder); | ||||
| 
 | ||||
|                 // Debugging: Logger.info(workOrder.toString());
 | ||||
| 
 | ||||
|             } catch (Exception e) { | ||||
|                 Logger.error(e); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static void start() { | ||||
| 
 | ||||
|         Thread forgeManager; | ||||
|         forgeManager = new Thread(FORGE_MANAGER); | ||||
|         forgeManager.setName("Forge Manager"); | ||||
|         forgeManager.start(); | ||||
|     } | ||||
| 
 | ||||
|     public static int submit(WorkOrder workOrder) { | ||||
| 
 | ||||
|         // Must have a city to roll anything
 | ||||
| 
 | ||||
|         City city = workOrder.vendor.building.getCity(); | ||||
| 
 | ||||
|         if (city == null) | ||||
|             return 58; //58: The formula is beyond the means of this facility
 | ||||
| 
 | ||||
|         // Concurrency is rightly managed by same lock as warehouse
 | ||||
| 
 | ||||
|         city.transactionLock.writeLock().lock(); | ||||
| 
 | ||||
|         // Make sure vendor can roll the formulae, warehouse can
 | ||||
|         // afford this wordOrder and other related checks.
 | ||||
| 
 | ||||
|         int validation_result = WorkOrder.validate(workOrder); | ||||
| 
 | ||||
|         // The return code is used by the caller (ItemProductionMsgHandler)
 | ||||
|         // for display of a popup error message to the player.
 | ||||
| 
 | ||||
|         if (validation_result != 0) | ||||
|             return validation_result; | ||||
| 
 | ||||
|         try { | ||||
|             // Configure this production run.
 | ||||
| 
 | ||||
|             workOrder.workOrderID = workOrderCounter.incrementAndGet(); | ||||
|             workOrder.rollingDuration = ForgeManager.calcRollingDuration(workOrder); | ||||
|             workOrder.completionTime = System.currentTimeMillis() + workOrder.rollingDuration; | ||||
|             workOrder.slots_used = calcAvailableSlots(workOrder); | ||||
| 
 | ||||
|             workOrder.total_produced = 0; | ||||
| 
 | ||||
|             // Single item configuration
 | ||||
| 
 | ||||
|             if (!workOrder.multiple_slot_request && workOrder.total_to_produce == 0) | ||||
|                 workOrder.total_to_produce = 1; | ||||
| 
 | ||||
|             // Set total cost for production run
 | ||||
| 
 | ||||
|             workOrder.total_to_produce *= workOrder.slots_used; | ||||
| 
 | ||||
|             workOrder.production_cost = calcProductionCost(workOrder); | ||||
|             workOrder.production_cost_total.putAll(workOrder.production_cost); | ||||
|             workOrder.production_cost_total.forEach((key, value) -> workOrder.production_cost_total.put(key, value * workOrder.total_to_produce)); | ||||
| 
 | ||||
|             // Withdraw gold and resource costs.  Availability has previously been validated.
 | ||||
| 
 | ||||
|             if (!WorkOrder.withdrawWorkOrderCost(workOrder)) | ||||
|                 return 58; //58: The formula is beyond the means of this facility
 | ||||
| 
 | ||||
|             // Create new batch of virtual items
 | ||||
| 
 | ||||
|             forgeWorkOrderBatch(workOrder); | ||||
| 
 | ||||
|             // Enqueue workOrder in the .forge and then
 | ||||
|             // add the workOrder to it's vendor
 | ||||
| 
 | ||||
|             vendorWorkOrderLookup.get(workOrder.vendor).add(workOrder); | ||||
|             forge.add(workOrder); | ||||
| 
 | ||||
|             // PERSIST workOrder (dyn_workorders)
 | ||||
| 
 | ||||
|             DbManager.WarehouseQueries.WRITE_WORKORDER(workOrder); | ||||
| 
 | ||||
|         } catch (Exception e) { | ||||
|             Logger.error(e); | ||||
|         } finally { | ||||
|             city.transactionLock.writeLock().unlock(); | ||||
|         } | ||||
|         Logger.info(workOrder.toString()); | ||||
|         return validation_result; | ||||
|     } | ||||
| 
 | ||||
|     public static long calcRollingDuration(WorkOrder workOrder) { | ||||
| 
 | ||||
|         float rollingDuration; | ||||
| 
 | ||||
|         rollingDuration = workOrder.vendor.getBuilding().getRank() * -5L + 40; | ||||
|         rollingDuration = TimeUnit.MINUTES.toMillis((long) rollingDuration); | ||||
|         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(); | ||||
| 
 | ||||
|         // Subtract the slots currently assigned to other workOrders for this vendor
 | ||||
| 
 | ||||
|         for (WorkOrder npcWorkOrder : ForgeManager.vendorWorkOrderLookup.get(workOrder.vendor)) | ||||
|             availableSlots = availableSlots - npcWorkOrder.cooking.size(); | ||||
| 
 | ||||
|         // Single item rolls are always a single slot
 | ||||
| 
 | ||||
|         if (availableSlots > 0 && !workOrder.multiple_slot_request) | ||||
|             availableSlots = 1; | ||||
| 
 | ||||
|         return availableSlots; | ||||
|     } | ||||
| 
 | ||||
|     public static HashMap<mbEnums.ResourceType, Integer> calcProductionCost(WorkOrder workOrder) { | ||||
| 
 | ||||
|         // Calculate production cost for a single run of the workOrder
 | ||||
| 
 | ||||
|         HashMap<mbEnums.ResourceType, Integer> 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); | ||||
|             production_cost.putAll(PowersManager._effect_costMaps.get(prefix.getIDString())); | ||||
|         } | ||||
| 
 | ||||
|         if (workOrder.suffixToken != 0) { | ||||
|             EffectsBase suffix = PowersManager.getEffectByToken(workOrder.suffixToken); | ||||
|             production_cost.putAll(PowersManager._effect_costMaps.get(suffix.getIDString())); | ||||
|         } | ||||
| 
 | ||||
|         return production_cost; | ||||
|     } | ||||
| 
 | ||||
|     public static Item forgeItem(WorkOrder workOrder) { | ||||
| 
 | ||||
|         // Create new virtual item from specified template
 | ||||
| 
 | ||||
|         ItemTemplate template = ItemTemplate.templates.get(workOrder.templateID); | ||||
|         Item forgedItem = new Item(workOrder.templateID); | ||||
| 
 | ||||
|         // forgedItem gets a negative id; a virtual item which is not persisted
 | ||||
| 
 | ||||
|         forgedItem.objectUUID = ItemManager.lastNegativeID.getAndDecrement(); | ||||
|         forgedItem.containerType = mbEnums.ItemContainerType.FORGE; | ||||
|         forgedItem.ownerID = workOrder.vendor.getObjectUUID(); | ||||
| 
 | ||||
|         // item.upgradeDate is serialized (ItemProductionMsg)
 | ||||
|         // for vendor forge window completion time.
 | ||||
| 
 | ||||
|         forgedItem.setDateToUpgrade(workOrder.completionTime); | ||||
| 
 | ||||
|         // Assign a prefix and suffix to this item if random rolled
 | ||||
| 
 | ||||
|         if (workOrder.prefixToken == 0) | ||||
|             forgedItem.prefixToken = calcRandomMod(workOrder.vendor, mbEnums.ItemModType.PREFIX, template.modTable); | ||||
|         else | ||||
|             forgedItem.prefixToken = workOrder.prefixToken; | ||||
| 
 | ||||
|         if (workOrder.suffixToken == 0) | ||||
|             forgedItem.suffixToken = calcRandomMod(workOrder.vendor, mbEnums.ItemModType.SUFFIX, template.modTable); | ||||
|         else | ||||
|             forgedItem.suffixToken = workOrder.suffixToken; | ||||
| 
 | ||||
|         // Random rolled items are unidentified until completed
 | ||||
| 
 | ||||
|         if (workOrder.prefixToken == 0 && workOrder.suffixToken == 0) | ||||
|             forgedItem.flags.remove(mbEnums.ItemFlags.Identified); | ||||
|         else | ||||
|             forgedItem.flags.add(mbEnums.ItemFlags.Identified); | ||||
| 
 | ||||
|         // Add virtual item to in-memory caches
 | ||||
| 
 | ||||
|         workOrder.cooking.add(forgedItem); | ||||
|         DbManager.addToCache(forgedItem); | ||||
|         itemWorkOrderLookup.put(forgedItem, workOrder); | ||||
| 
 | ||||
|         return forgedItem; | ||||
|     } | ||||
| 
 | ||||
|     public static void completeWorkOrderBatch(WorkOrder workOrder) { | ||||
| 
 | ||||
|         ArrayList<Item> toRemove = new ArrayList<>(); | ||||
| 
 | ||||
|         for (Item virutalItem : workOrder.cooking) { | ||||
| 
 | ||||
|             // Identify completed items
 | ||||
| 
 | ||||
|             virutalItem.flags.add(mbEnums.ItemFlags.Identified); | ||||
|             virutalItem.containerType = mbEnums.ItemContainerType.INVENTORY; | ||||
| 
 | ||||
|             // Persist item
 | ||||
| 
 | ||||
|             Item completedItem = DbManager.ItemQueries.PERSIST(virutalItem); | ||||
| 
 | ||||
|             // Copy Prefix and Suffix tokens from virtual item.
 | ||||
| 
 | ||||
|             completedItem.prefixToken = virutalItem.prefixToken; | ||||
|             completedItem.suffixToken = virutalItem.suffixToken; | ||||
| 
 | ||||
|             // Add effects to these tokens.  Writes to disk.
 | ||||
| 
 | ||||
|             ItemManager.applyItemEffects(completedItem); | ||||
| 
 | ||||
|             // Add to the vendor inventory
 | ||||
| 
 | ||||
|             workOrder.vendor.charItemManager.addItemToInventory(completedItem); | ||||
| 
 | ||||
|             ItemProductionMsg outMsg1 = new ItemProductionMsg(workOrder.vendor.building, workOrder.vendor, completedItem, mbEnums.ProductionActionType.DEPOSIT, true); | ||||
|             DispatchMessage.dispatchMsgToInterestArea(workOrder.vendor, outMsg1, mbEnums.DispatchChannel.SECONDARY, 700, false, false); | ||||
|             ItemProductionMsg outMsg2 = new ItemProductionMsg(workOrder.vendor.building, workOrder.vendor, completedItem, mbEnums.ProductionActionType.CONFIRM_DEPOSIT, true); | ||||
|             DispatchMessage.dispatchMsgToInterestArea(workOrder.vendor, outMsg2, mbEnums.DispatchChannel.SECONDARY, 700, false, false); | ||||
| 
 | ||||
|             toRemove.add(virutalItem); | ||||
|         } | ||||
| 
 | ||||
|         for (Item virtualItem : toRemove) { | ||||
| 
 | ||||
|             // Remove virtual items from the forge window
 | ||||
| 
 | ||||
|             ItemProductionMsg outMsg = new ItemProductionMsg(workOrder.vendor.building, workOrder.vendor, virtualItem, mbEnums.ProductionActionType.CONFIRM_SETPRICE, true); | ||||
|             DispatchMessage.dispatchMsgToInterestArea(workOrder.vendor, outMsg, mbEnums.DispatchChannel.SECONDARY, 700, false, false); | ||||
| 
 | ||||
|             // Remove virtual item from all collections
 | ||||
| 
 | ||||
|             workOrder.cooking.remove(virtualItem); | ||||
|             itemWorkOrderLookup.remove(virtualItem); | ||||
|             DbManager.removeFromCache(virtualItem); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static void forgeWorkOrderBatch(WorkOrder workOrder) { | ||||
| 
 | ||||
|         // Completion time for this batch is in the future
 | ||||
| 
 | ||||
|         workOrder.completionTime = System.currentTimeMillis() + workOrder.rollingDuration; | ||||
| 
 | ||||
|         for (int i = 0; i < workOrder.slots_used; ++i) { | ||||
| 
 | ||||
|             Item forged_item = forgeItem(workOrder); | ||||
| 
 | ||||
|             // Update NPC window
 | ||||
| 
 | ||||
|             ItemProductionMsg outMsg = new ItemProductionMsg(workOrder.vendor.building, workOrder.vendor, forged_item, mbEnums.ProductionActionType.CONFIRM_PRODUCE, true); | ||||
|             DispatchMessage.dispatchMsgToInterestArea(workOrder.vendor, outMsg, mbEnums.DispatchChannel.SECONDARY, 700, false, false); | ||||
|             workOrder.total_produced = workOrder.total_produced + 1; | ||||
|         } | ||||
| 
 | ||||
|         // Write updated workOrder to disk
 | ||||
| 
 | ||||
|         DbManager.WarehouseQueries.WRITE_WORKORDER(workOrder); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static int calcRandomMod(NPC vendor, mbEnums.ItemModType itemModType, int modTable) { | ||||
| 
 | ||||
|         // Random prefix or suffix token based on item.template.modtable
 | ||||
| 
 | ||||
|         int modifier = 0; | ||||
|         ModTypeTableEntry modTypeTableEntry = null; | ||||
|         ModTableEntry modTableEntry; | ||||
|         int rollForModifier; | ||||
| 
 | ||||
|         switch (itemModType) { | ||||
|             case PREFIX: | ||||
|                 int randomPrefix = vendor.getModTypeTable().get(vendor.getItemModTable().indexOf(modTable)); | ||||
|                 modTypeTableEntry = ModTypeTableEntry.rollTable(randomPrefix, ThreadLocalRandom.current().nextInt(1, 100 + 1)); | ||||
|                 break; | ||||
|             case SUFFIX: | ||||
|                 int randomSuffix = vendor.getModSuffixTable().get(vendor.getItemModTable().indexOf(modTable)); | ||||
|                 modTypeTableEntry = ModTypeTableEntry.rollTable(randomSuffix, ThreadLocalRandom.current().nextInt(1, 100 + 1)); | ||||
|                 break; | ||||
|         } | ||||
| 
 | ||||
|         if (modTypeTableEntry == null) | ||||
|             return 0; | ||||
| 
 | ||||
|         rollForModifier = ThreadLocalRandom.current().nextInt(1, 100 + 1); | ||||
| 
 | ||||
|         if (rollForModifier < 80) { | ||||
|             int randomModifier = LootManager.TableRoll(vendor.getLevel(), false); | ||||
|             modTableEntry = ModTableEntry.rollTable(modTypeTableEntry.modTableID, randomModifier); | ||||
|             EffectsBase effectsBase = PowersManager.getEffectByIDString(modTableEntry.action); | ||||
|             modifier = effectsBase.getToken(); | ||||
|         } | ||||
| 
 | ||||
|         return modifier; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,255 @@@@ -0,0 +1,255 @@ | ||||
| // • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
 | ||||
| // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
 | ||||
| // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
 | ||||
| // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
 | ||||
| // ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
 | ||||
| //      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; then passed 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<mbEnums.ResourceType, Integer> production_cost = new HashMap<>(); | ||||
|     public HashMap<mbEnums.ResourceType, Integer> 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<Item, Boolean> 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(); | ||||
| 
 | ||||
|         // Strongbox can cover total gold cost;
 | ||||
| 
 | ||||
|         if (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); | ||||
| 
 | ||||
|             // Early exit for Strongbox covering gold only rolls
 | ||||
| 
 | ||||
|             if (workOrder.production_cost_total.size() == 1) | ||||
|                 return true; | ||||
|         } else { | ||||
|             int remainingAmount = 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) - remainingAmount); | ||||
|         } | ||||
| 
 | ||||
|         // There is an overflow at this point and a warehouse is required
 | ||||
| 
 | ||||
|         Warehouse warehouse = workOrder.vendor.building.getCity().warehouse; | ||||
| 
 | ||||
|         if (warehouse == null) | ||||
|             return false; | ||||
| 
 | ||||
|         // 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<Integer[]> 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; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -1,135 +0,0 @@@@ -1,135 +0,0 @@ | ||||
| // • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
 | ||||
| // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
 | ||||
| // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
 | ||||
| // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
 | ||||
| // ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
 | ||||
| //      Magicbane Emulator Project © 2013 - 2022
 | ||||
| //                www.magicbane.com
 | ||||
| 
 | ||||
| 
 | ||||
| package engine.net; | ||||
| 
 | ||||
| import engine.mbEnums.DispatchChannel; | ||||
| import engine.objects.ProducedItem; | ||||
| import org.pmw.tinylog.Logger; | ||||
| 
 | ||||
| import java.util.HashSet; | ||||
| import java.util.concurrent.DelayQueue; | ||||
| import java.util.concurrent.atomic.LongAdder; | ||||
| 
 | ||||
| /** | ||||
|  * Thread blocks until MagicBane dispatch messages are | ||||
|  * enqueued then processes them in FIFO order. The collection | ||||
|  * is thread safe. | ||||
|  * <p> | ||||
|  * Any large messages not time sensitive such as load object | ||||
|  * sent to more than a single individual should be spawned | ||||
|  * individually on a DispatchMessageThread. | ||||
|  */ | ||||
| 
 | ||||
| 
 | ||||
| public enum ItemProductionManager implements Runnable { | ||||
| 
 | ||||
|     ITEMPRODUCTIONMANAGER; | ||||
| 
 | ||||
| 
 | ||||
|     // Instance variables
 | ||||
| 
 | ||||
|     @SuppressWarnings("unchecked") // Cannot have arrays of generics in java.
 | ||||
|     private static final DelayQueue<ItemQueue> producedQueue = new DelayQueue<>(); | ||||
|     public static volatile long[] messageCount = new long[DispatchChannel.values().length]; | ||||
| 
 | ||||
|     // Class variables
 | ||||
|     public static LongAdder[] dispatchCount = new LongAdder[DispatchChannel.values().length]; | ||||
| 
 | ||||
| 
 | ||||
|     // Performance metrics
 | ||||
|     public static volatile long[] maxRecipients = new long[DispatchChannel.values().length]; | ||||
|     public static LongAdder dispatchPoolSize = new LongAdder(); | ||||
|     public static HashSet<ProducedItem> FailedItems = new HashSet<>(); | ||||
|     public Thread itemProductionThread = null; | ||||
|     private ItemQueue itemQueue; | ||||
|     private long nextFailedItemAudit; | ||||
| 
 | ||||
|     // Thread constructor
 | ||||
| 
 | ||||
|     public static void send(ItemQueue item) { | ||||
| 
 | ||||
|         // Don't queue up empty dispatches!
 | ||||
| 
 | ||||
|         if (item == null) | ||||
|             return; | ||||
| 
 | ||||
|         producedQueue.add(item); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static String getNetstatString() { | ||||
| 
 | ||||
|         String outString = null; | ||||
|         String newLine = System.getProperty("line.separator"); | ||||
|         outString = "[LUA_NETSTA()]" + newLine; | ||||
|         outString += "poolSize: " + dispatchPoolSize.longValue() + '\n'; | ||||
| 
 | ||||
|         for (DispatchChannel dispatchChannel : DispatchChannel.values()) { | ||||
| 
 | ||||
|             outString += "Channel: " + dispatchChannel.name() + '\n'; | ||||
|             outString += "Dispatches: " + dispatchCount[dispatchChannel.getChannelID()].longValue() + '\n'; | ||||
|             outString += "Messages: " + messageCount[dispatchChannel.getChannelID()] + '\n'; | ||||
|             outString += "maxRecipients: " + maxRecipients[dispatchChannel.getChannelID()] + '\n'; | ||||
|         } | ||||
|         return outString; | ||||
|     } | ||||
| 
 | ||||
|     public void startMessagePump() { | ||||
| 
 | ||||
|         itemProductionThread = new Thread(this); | ||||
|         itemProductionThread.setName("ItemProductionManager"); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public void initialize() { | ||||
|         itemProductionThread.start(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void run() { | ||||
| 
 | ||||
| 
 | ||||
|         while (true) { | ||||
|             try { | ||||
| 
 | ||||
|                 this.itemQueue = producedQueue.take(); | ||||
| 
 | ||||
|                 if (this.itemQueue == null) { | ||||
|                     return; | ||||
|                 } | ||||
| 
 | ||||
|                 if (this.itemQueue != null) { | ||||
|                     if (this.itemQueue.item == null) { | ||||
|                         this.itemQueue.release(); | ||||
|                         return; | ||||
|                     } | ||||
| 
 | ||||
| 
 | ||||
|                     boolean created = this.itemQueue.item.finishProduction(); | ||||
| 
 | ||||
|                     if (!created) | ||||
|                         FailedItems.add(this.itemQueue.item); | ||||
| 
 | ||||
|                     this.itemQueue.release(); | ||||
| 
 | ||||
| 
 | ||||
|                 } | ||||
| 
 | ||||
| 
 | ||||
|             } catch (Exception e) { | ||||
|                 Logger.error(e); | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // For Debugging:
 | ||||
|     //Logger.error("MessageDispatcher", messageDispatch.msg.getOpcodeAsString() + " sent to " + messageDispatch.playerList.size() + " players");
 | ||||
| } | ||||
| @ -1,86 +0,0 @@@@ -1,86 +0,0 @@ | ||||
| // • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
 | ||||
| // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
 | ||||
| // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
 | ||||
| // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
 | ||||
| // ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
 | ||||
| //      Magicbane Emulator Project © 2013 - 2022
 | ||||
| //                www.magicbane.com
 | ||||
| 
 | ||||
| 
 | ||||
| package engine.net; | ||||
| 
 | ||||
| import engine.objects.ProducedItem; | ||||
| 
 | ||||
| import java.util.concurrent.ConcurrentLinkedQueue; | ||||
| import java.util.concurrent.Delayed; | ||||
| import java.util.concurrent.TimeUnit; | ||||
| 
 | ||||
| import static engine.net.MessageDispatcher.itemPoolSize; | ||||
| 
 | ||||
| /** | ||||
|  * Data class holds a message and a distribution list | ||||
|  */ | ||||
| 
 | ||||
| public class ItemQueue implements Delayed { | ||||
| 
 | ||||
|     private static final ConcurrentLinkedQueue<ItemQueue> itemPool = new ConcurrentLinkedQueue<>(); | ||||
| 
 | ||||
|     public ProducedItem item; | ||||
|     public long delayTime; | ||||
| 
 | ||||
| 
 | ||||
|     public ItemQueue(ProducedItem item, long delayTime) { | ||||
|         this.item = item; | ||||
|         this.delayTime = System.currentTimeMillis() + delayTime; | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public static ItemQueue borrow(ProducedItem item, long delayTime) { | ||||
| 
 | ||||
|         ItemQueue itemQueue; | ||||
| 
 | ||||
|         itemQueue = itemPool.poll(); | ||||
| 
 | ||||
|         if (itemQueue == null) { | ||||
|             itemQueue = new ItemQueue(item, delayTime); | ||||
|         } else { | ||||
|             itemQueue.item = item; | ||||
|             itemQueue.delayTime = System.currentTimeMillis() + delayTime; | ||||
|             itemPoolSize.decrement(); | ||||
|         } | ||||
| 
 | ||||
|         return itemQueue; | ||||
|     } | ||||
| 
 | ||||
|     public void reset() { | ||||
|         this.item = null; | ||||
|         this.delayTime = 0; | ||||
|     } | ||||
| 
 | ||||
|     public void release() { | ||||
|         this.reset(); | ||||
|         itemPool.add(this); | ||||
|         itemPoolSize.increment(); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int compareTo(Delayed another) { | ||||
|         ItemQueue anotherTask = (ItemQueue) another; | ||||
| 
 | ||||
|         if (this.delayTime < anotherTask.delayTime) { | ||||
|             return -1; | ||||
|         } | ||||
| 
 | ||||
|         if (this.delayTime > anotherTask.delayTime) { | ||||
|             return 1; | ||||
|         } | ||||
| 
 | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public long getDelay(TimeUnit unit) { | ||||
|         long difference = delayTime - System.currentTimeMillis(); | ||||
|         return unit.convert(difference, TimeUnit.MILLISECONDS); | ||||
|     } | ||||
| } | ||||
Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in new issue