|
|
|
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
|
|
// Magicbane Emulator Project © 2013 - 2022
|
|
|
|
// www.magicbane.com
|
|
|
|
|
|
|
|
package engine.loot;
|
|
|
|
|
|
|
|
import engine.gameManager.DbManager;
|
|
|
|
import engine.gameManager.ForgeManager;
|
|
|
|
import engine.mbEnums;
|
|
|
|
import engine.objects.Item;
|
|
|
|
import engine.objects.ItemTemplate;
|
|
|
|
import engine.objects.NPC;
|
|
|
|
import engine.objects.Warehouse;
|
|
|
|
import org.json.JSONArray;
|
|
|
|
import org.json.JSONObject;
|
|
|
|
|
|
|
|
import java.time.Duration;
|
|
|
|
import java.time.Instant;
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
import java.time.ZoneId;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.EnumSet;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
import java.util.concurrent.Delayed;
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
public class WorkOrder implements Delayed {
|
|
|
|
|
|
|
|
// MB Dev notes:
|
|
|
|
// Class defines a Forge rolling request made through a vendor
|
|
|
|
// interaction; submitted to the ForgeManager singleton for completion.
|
|
|
|
//
|
|
|
|
// A workOrder once created will last until all items are either
|
|
|
|
// completed or junked. They are persisted in the table dyn_workorders.
|
|
|
|
|
|
|
|
public int workOrderID;
|
|
|
|
public NPC vendor;
|
|
|
|
public int slots_used;
|
|
|
|
public int total_to_produce;
|
|
|
|
public int total_produced;
|
|
|
|
public boolean multiple_slot_request;
|
|
|
|
public HashMap<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;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|