Files
Server/src/engine/net/client/handlers/ItemProductionMsgHandler.java
T
MagicBotandClaude Opus 5.5 6b4e9815e0 Switch Item.template and MobLoot to CObjectTemplate
Item.template is now the cache template from CObjectTemplate.lookup
instead of the legacy DB ItemTemplate, so item.template.cacheTemplate.X
becomes item.template.X. MobLoot, LootManager, ItemManager.isTwoHanded/
isShield and CombatManager.getSwingAnimation take a CObjectTemplate.

No value changes: every getter already read the cache template through
the bridge, and the lookup resolves the same 7586 ids as
static_item_templates with the same item_type (offline parity p17.py,
0 differences).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 07:55:28 -04:00

483 lines
16 KiB
Java

// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.net.client.handlers;
import engine.gameManager.*;
import engine.loot.WorkOrder;
import engine.mbEnums;
import engine.mbEnums.ItemType;
import engine.net.Dispatch;
import engine.net.client.ClientConnection;
import engine.net.client.msg.ClientNetMsg;
import engine.net.client.msg.ErrorPopupMsg;
import engine.net.client.msg.ItemProductionMsg;
import engine.net.client.msg.ManageNPCMsg;
import engine.objects.*;
import org.pmw.tinylog.Logger;
import java.util.HashMap;
/*
* @Summary: Processes application protocol message which modifies
* hireling inventory through rolling, junking or depositing.
*/
public class ItemProductionMsgHandler extends AbstractClientMsgHandler {
public ItemProductionMsgHandler() {
super();
}
@Override
protected boolean _handleNetMsg(ClientNetMsg baseMsg, ClientConnection origin) {
// Member variable declaration
PlayerCharacter player;
NPC vendor;
ItemProductionMsg msg;
Dispatch dispatch;
// Member variable assignment
msg = (ItemProductionMsg) baseMsg;
player = origin.getPlayerCharacter();
if (player == null)
return true;
// Grab reference to vendor we are interacting with
vendor = (NPC) DbManager.getObject(mbEnums.GameObjectType.NPC, msg.npcUUID);
// Oops?
if (vendor == null)
return true;
// Process Request
switch (msg.actionType) {
case PRODUCE:
// Create new work order
WorkOrder workOrder = new WorkOrder();
workOrder.total_to_produce = msg.total_to_produce;
workOrder.vendor = vendor;
workOrder.templateID = msg.itemUUID;
workOrder.prefixToken = msg.pToken;
workOrder.suffixToken = msg.sToken;
workOrder.item_name_override = msg.name;
workOrder.multiple_slot_request = (msg.size != 0);
// Submit workOder to begin rolling items
int validation_result = ForgeManager.submit(workOrder);
// workOrder could not be completed
// Report back to the user the reason why
if (validation_result != 0) {
ErrorPopupMsg.sendErrorPopup(player, validation_result);
return true;
}
break;
case JUNK:
junkItem(msg.itemUUID, vendor, origin);
break;
case RECYCLE:
recycleItem(msg.items, vendor, origin);
msg.actionType = mbEnums.ProductionActionType.TAKE;
dispatch = Dispatch.borrow(player, msg);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
break;
case COMPLETE:
completeItem(msg.itemUUID, vendor);
break;
case DEPOSIT:
depositItem(msg.itemUUID, vendor, origin);
break;
case SETPRICE:
setItemPrice(msg.itemUUID, msg.itemPrice, vendor, origin);
break;
case TAKE:
takeItem(msg.items, vendor, origin);
dispatch = Dispatch.borrow(player, msg);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
break;
}
return true;
}
private static void completeItem(int itemUUID, NPC vendor) {
Item virtualItem = Item.getFromCache(itemUUID);
WorkOrder workOrder = ForgeManager.itemWorkOrderLookup.get(virtualItem);
City city = workOrder.vendor.building.getCity();
if (city == null)
return;
city.transactionLock.writeLock().lock();
try {
// Remove virtual item from the vendor rolling window
// (Add / Completed / Remove)
ItemProductionMsg outMsg = new ItemProductionMsg(workOrder.vendor.building, workOrder.vendor, virtualItem, mbEnums.ProductionActionType.CONFIRM_SETPRICE, true);
DispatchManager.dispatchMsgToInterestArea(workOrder.vendor, outMsg, mbEnums.DispatchChannel.SECONDARY, 700, false, false);
// Remove virtualItem from collections
workOrder.cooking.remove(virtualItem);
ForgeManager.itemWorkOrderLookup.remove(virtualItem);
DbManager.removeFromCache(virtualItem);
workOrder.slots_used.decrementAndGet();
// Update workOrder on disk
if (workOrder.cooking.isEmpty()) {
ForgeManager.vendorWorkOrderLookup.get(vendor).remove(workOrder);
DbManager.WarehouseQueries.DELETE_WORKORDER(workOrder);
} else
DbManager.WarehouseQueries.WRITE_WORKORDER(workOrder);
// Persist item and add to vendor inventory
virtualItem.containerType = mbEnums.ItemContainerType.INVENTORY;
Item completedItem = DbManager.ItemQueries.PERSIST(virtualItem);
// Apply Item effects for Prefix and Suffix tokens
completedItem.prefixToken = virtualItem.prefixToken;
completedItem.suffixToken = virtualItem.suffixToken;
ItemManager.applyItemEffects(completedItem);
vendor.charItemManager.addItemToInventory(completedItem);
// Add persisted items to the vendor inventory window
ItemProductionMsg outMsg1 = new ItemProductionMsg(vendor.building, vendor, completedItem, mbEnums.ProductionActionType.DEPOSIT, true);
DispatchManager.dispatchMsgToInterestArea(vendor, outMsg1, mbEnums.DispatchChannel.SECONDARY, 700, false, false);
ItemProductionMsg outMsg2 = new ItemProductionMsg(vendor.building, vendor, completedItem, mbEnums.ProductionActionType.CONFIRM_DEPOSIT, true);
DispatchManager.dispatchMsgToInterestArea(vendor, outMsg2, mbEnums.DispatchChannel.SECONDARY, 700, false, false);
} catch (Exception e) {
Logger.error(e);
} finally {
city.transactionLock.writeLock().unlock();
}
}
private static void setItemPrice(int itemUUID, int itemPrice, NPC vendor, ClientConnection origin) {
Item targetItem;
ItemProductionMsg outMsg;
Dispatch dispatch;
PlayerCharacter player = origin.getPlayerCharacter();
if (player == null)
return;
targetItem = Item.getFromCache(itemUUID);
if (targetItem == null)
return;
if (!DbManager.ItemQueries.UPDATE_VALUE(targetItem, itemPrice)) {
ChatManager.chatInfoError(origin.getPlayerCharacter(), "Failed to set price! Contact CCR For help.");
return;
}
targetItem.setValue(itemPrice);
targetItem.magicValue = targetItem.value;
outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, mbEnums.ProductionActionType.DEPOSIT, true);
dispatch = Dispatch.borrow(player, outMsg);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, mbEnums.ProductionActionType.SETPRICE, true);
dispatch = Dispatch.borrow(player, outMsg);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
}
// Method sets the price on an item in the vendor inventory
private static void depositItem(int itemUUID, NPC vendor, ClientConnection origin) {
Item targetItem;
ItemProductionMsg outMsg;
CharacterItemManager itemMan;
Dispatch dispatch;
PlayerCharacter player = origin.getPlayerCharacter();
if (player == null)
return;
if (origin.sellLock.tryLock()) {
try {
targetItem = Item.getFromCache(itemUUID);
if (targetItem == null)
return;
if (targetItem.template.getItemType().equals(ItemType.GOLD))
return;
if (!vendor.charItemManager.hasRoomInventory(targetItem.template.getItemWt())) {
ErrorPopupMsg.sendErrorPopup(player, 21);
return;
}
itemMan = origin.getPlayerCharacter().charItemManager;
if (itemMan == null)
return;
if (vendor.charItemManager.getInventoryWeight() > 500) {
ErrorPopupMsg.sendErrorPopup(player, 21);
return;
}
if (!targetItem.validForInventory(origin, player, itemMan)) {
ErrorPopupMsg.sendErrorPopup(player, 19);
return;
}
// Transfer item from player to vendor's inventory
if (!itemMan.sellToNPC(targetItem, vendor)) {
ErrorPopupMsg.sendErrorPopup(player, 109);
return;
}
outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, mbEnums.ProductionActionType.DEPOSIT, true);
dispatch = Dispatch.borrow(player, outMsg);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, mbEnums.ProductionActionType.CONFIRM_DEPOSIT, true);
dispatch = Dispatch.borrow(player, outMsg);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
origin.getPlayerCharacter().charItemManager.updateInventory();
} catch (Exception e) {
Logger.error(e);
} finally {
origin.sellLock.unlock();
}
}
}
// Method completes an item that has been previously rolled
// adding it to the Vendor inventory
private static void recycleItem(HashMap<Integer, Integer> itemList, NPC vendor, ClientConnection origin) {
Item targetItem;
ItemProductionMsg outMsg;
int totalValue = 0;
Dispatch dispatch;
if (vendor.getBuilding() == null)
return;
PlayerCharacter player = origin.getPlayerCharacter();
if (player == null)
return;
if (itemList == null)
return;
if (origin.sellLock.tryLock()) {
try {
for (int itemUUID : itemList.keySet()) {
int itemValue = 0;
targetItem = Item.getFromCache(itemUUID);
if (targetItem == null)
continue;
if (targetItem.template.getItemType().equals(ItemType.GOLD))
return;
if (!vendor.charItemManager.doesCharOwnThisItem(targetItem.getObjectUUID()))
continue;
if (!vendor.charItemManager.inventoryContains(targetItem))
continue;
itemValue = targetItem.template.getItemValue();
if (vendor.getBuilding().getStrongboxValue() + itemValue > vendor.getBuilding().getMaxGold()) {
ErrorPopupMsg.sendErrorPopup(player, 201);
break;
}
switch (targetItem.template.getItemType()) {
case EMPLOYMENTCONTRACT:
case CHARTER:
case DEED:
case REALMCHARTER:
case SCROLL:
case POTION:
continue;
}
totalValue += itemValue;
vendor.charItemManager.recycle(targetItem);
outMsg = new ItemProductionMsg(vendor.getBuilding(), vendor, targetItem, mbEnums.ProductionActionType.TAKE, true);
dispatch = Dispatch.borrow(origin.getPlayerCharacter(), outMsg);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
}
// Refund a portion of the gold
vendor.getBuilding().transferGold(totalValue, false);
} catch (Exception e) {
Logger.error(e);
} finally {
origin.sellLock.unlock();
}
}
}
// Method handles recycling of an item
private static void junkItem(int itemUUID, NPC vendor, ClientConnection origin) {
Item virtualItem;
ManageNPCMsg outMsg;
Dispatch dispatch;
PlayerCharacter player = origin.getPlayerCharacter();
if (player == null)
return;
// Cannot junk items without a forge!
if (vendor.getBuilding() == null)
return;
// junk nothing?
virtualItem = Item.getFromCache(itemUUID);
if (virtualItem == null)
return;
WorkOrder workOrder = ForgeManager.itemWorkOrderLookup.get(virtualItem);
// If this virtual item was already processed then
// it will have been removed from the workOrder.
if (workOrder == null)
return;
if (!workOrder.cooking.contains(virtualItem))
return;
City city = workOrder.vendor.building.getCity();
if (city == null)
return;
city.transactionLock.writeLock().lock();
try {
workOrder.cooking.remove(virtualItem);
DbManager.removeFromCache(virtualItem);
ForgeManager.itemWorkOrderLookup.remove(virtualItem);
// Update total_to_produce accounting for the slot being
// removed while workOrder is not completed.
if (!workOrder.runCompleted.get())
if (workOrder.multiple_slot_request && workOrder.slots_used.get() > 1) {
int itemsPerSlot = workOrder.total_to_produce / workOrder.slots_used.get();
workOrder.total_to_produce = workOrder.total_to_produce - itemsPerSlot;
}
// Slot is no longer allocated to this workOrder.
workOrder.slots_used.decrementAndGet();
// Update workOrder on disk
if (workOrder.cooking.isEmpty()) {
ForgeManager.vendorWorkOrderLookup.get(vendor).remove(workOrder);
DbManager.WarehouseQueries.DELETE_WORKORDER(workOrder);
} else
DbManager.WarehouseQueries.WRITE_WORKORDER(workOrder);
// Refresh vendor's inventory to client
outMsg = new ManageNPCMsg(vendor);
outMsg.setMessageType(1);
dispatch = Dispatch.borrow(player, outMsg);
DispatchManager.dispatchMsgDispatch(dispatch, mbEnums.DispatchChannel.SECONDARY);
} catch (Exception e) {
Logger.error(e);
} finally {
city.transactionLock.writeLock().unlock();
}
}
private static void takeItem(HashMap<Integer, Integer> itemList, NPC vendor, ClientConnection origin) {
Item targetItem;
PlayerCharacter player = origin.getPlayerCharacter();
if (player == null)
return;
for (int itemUUID : itemList.keySet()) {
targetItem = Item.getFromCache(itemUUID);
if (targetItem == null)
return;
if (targetItem.template.getItemType().equals(ItemType.GOLD))
return;
if (!vendor.charItemManager.inventoryContains(targetItem))
return;
if (!player.charItemManager.hasRoomInventory(targetItem.template.getItemWt()))
return;
player.charItemManager.buyFromNPC(targetItem, vendor);
}
player.charItemManager.updateInventory();
}
}