MagicBot
7 months ago
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