forked from MagicBane/Server
MagicBot
8 months ago
283 changed files with 2304 additions and 3493 deletions
@ -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 @@ |
|||||||
|
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
||||||
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
||||||
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
||||||
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
||||||
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
||||||
|
// 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 @@ |
|||||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
||||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
||||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
||||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
||||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
||||||
// 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 @@ |
|||||||
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
||||||
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
||||||
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
||||||
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
||||||
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
||||||
// 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