/* * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ package com.l2jserver.gameserver.model; import static com.l2jserver.gameserver.model.itemcontainer.PcInventory.MAX_ADENA; import java.util.List; import java.util.logging.Logger; import javolution.util.FastList; import javolution.util.FastSet; import com.l2jserver.Config; import com.l2jserver.gameserver.datatables.ItemTable; import com.l2jserver.gameserver.model.actor.instance.L2PcInstance; import com.l2jserver.gameserver.model.itemcontainer.PcInventory; import com.l2jserver.gameserver.network.SystemMessageId; import com.l2jserver.gameserver.network.serverpackets.InventoryUpdate; import com.l2jserver.gameserver.network.serverpackets.ItemList; import com.l2jserver.gameserver.network.serverpackets.StatusUpdate; import com.l2jserver.gameserver.network.serverpackets.SystemMessage; import com.l2jserver.gameserver.templates.item.L2EtcItemType; import com.l2jserver.gameserver.templates.item.L2Item; import com.l2jserver.gameserver.util.Util; /** * @author Advi * */ public class TradeList { public class TradeItem { private int _objectId; private final L2Item _item; private int _enchant; private int _type2; private long _count; private long _storeCount; private long _price; private final byte _elemAtkType; private final int _elemAtkPower; private int[] _elemDefAttr = { 0, 0, 0, 0, 0, 0 }; public TradeItem(L2ItemInstance item, long count, long price) { _objectId = item.getObjectId(); _item = item.getItem(); _enchant = item.getEnchantLevel(); _type2 = item.getCustomType2(); _count = count; _price = price; _elemAtkType = item.getAttackElementType(); _elemAtkPower = item.getAttackElementPower(); for (byte i = 0; i < 6; i++) _elemDefAttr[i] = item.getElementDefAttr(i); } public TradeItem(L2Item item, long count, long price) { _objectId = 0; _item = item; _enchant = 0; _type2 = 0; _count = count; _storeCount = count; _price = price; _elemAtkType = Elementals.NONE; _elemAtkPower = 0; } public TradeItem(TradeItem item, long count, long price) { _objectId = item.getObjectId(); _item = item.getItem(); _enchant = item.getEnchant(); _type2 = 0; _count = count; _storeCount = count; _price = price; _elemAtkType = item.getAttackElementType(); _elemAtkPower = item.getAttackElementPower(); for (byte i = 0; i < 6; i++) _elemDefAttr[i] = item.getElementDefAttr(i); } public void setObjectId(int objectId) { _objectId = objectId; } public int getObjectId() { return _objectId; } public L2Item getItem() { return _item; } public void setEnchant(int enchant) { _enchant = enchant; } public int getEnchant() { return _enchant; } public int getCustomType2() { return _type2; } public void setCount(long count) { _count = count; } public long getCount() { return _count; } public long getStoreCount() { return _storeCount; } public void setPrice(long price) { _price = price; } public long getPrice() { return _price; } public byte getAttackElementType() { return _elemAtkType; } public int getAttackElementPower() { return _elemAtkPower; } public int getElementDefAttr(byte i) { return _elemDefAttr[i]; } } private static final Logger _log = Logger.getLogger(TradeList.class.getName()); private final L2PcInstance _owner; private L2PcInstance _partner; private final List _items; private String _title; private boolean _packaged; private boolean _confirmed = false; private boolean _locked = false; public TradeList(L2PcInstance owner) { _items = new FastList(); _owner = owner; } public L2PcInstance getOwner() { return _owner; } public void setPartner(L2PcInstance partner) { _partner = partner; } public L2PcInstance getPartner() { return _partner; } public void setTitle(String title) { _title = title; } public String getTitle() { return _title; } public boolean isLocked() { return _locked; } public boolean isConfirmed() { return _confirmed; } public boolean isPackaged() { return _packaged; } public void setPackaged(boolean value) { _packaged = value; } /** * Retrieves items from TradeList */ public TradeItem[] getItems() { return _items.toArray(new TradeItem[_items.size()]); } /** * Returns the list of items in inventory available for transaction * @return L2ItemInstance : items in inventory */ public TradeList.TradeItem[] getAvailableItems(PcInventory inventory) { FastList list = FastList.newInstance(); for (TradeList.TradeItem item : _items) { item = new TradeItem(item, item.getCount(), item.getPrice()); inventory.adjustAvailableItem(item); list.add(item); } TradeList.TradeItem[] result = list.toArray(new TradeList.TradeItem[list.size()]); FastList.recycle(list); return result; } /** * Returns Item List size */ public int getItemCount() { return _items.size(); } /** * Adjust available item from Inventory by the one in this list * @param item : L2ItemInstance to be adjusted * @return TradeItem representing adjusted item */ public TradeItem adjustAvailableItem(L2ItemInstance item) { if (item.isStackable()) { for (TradeItem exclItem : _items) { if (exclItem.getItem().getItemId() == item.getItemId()) { if (item.getCount() <= exclItem.getCount()) return null; else return new TradeItem(item, item.getCount() - exclItem.getCount(), item.getReferencePrice()); } } } return new TradeItem(item, item.getCount(), item.getReferencePrice()); } /** * Adjust ItemRequest by corresponding item in this list using its ObjectId * @param item : ItemRequest to be adjusted */ public void adjustItemRequest(ItemRequest item) { for (TradeItem filtItem : _items) { if (filtItem.getObjectId() == item.getObjectId()) { if (filtItem.getCount() < item.getCount()) item.setCount(filtItem.getCount()); return; } } item.setCount(0); } /** * Add simplified item to TradeList * @param objectId : int * @param count : int * @return */ public synchronized TradeItem addItem(int objectId, long count) { return addItem(objectId, count, 0); } /** * Add item to TradeList * @param objectId : int * @param count : long * @param price : long * @return */ public synchronized TradeItem addItem(int objectId, long count, long price) { if (isLocked()) { _log.warning(_owner.getName() + ": Attempt to modify locked TradeList!"); return null; } L2Object o = L2World.getInstance().findObject(objectId); if (!(o instanceof L2ItemInstance)) { _log.warning(_owner.getName() + ": Attempt to add invalid item to TradeList!"); return null; } L2ItemInstance item = (L2ItemInstance) o; if (!(item.isTradeable() || (getOwner().isGM() && Config.GM_TRADE_RESTRICTED_ITEMS)) || item.getItemType() == L2EtcItemType.QUEST) return null; if (count <= 0 || count > item.getCount()) return null; if (!item.isStackable() && count > 1) { _log.warning(_owner.getName() + ": Attempt to add non-stackable item to TradeList with count > 1!"); return null; } if ((PcInventory.MAX_ADENA / count) < price) { _log.warning(_owner.getName() + ": Attempt to overflow adena !"); return null; } for (TradeItem checkitem : _items) { if (checkitem.getObjectId() == objectId) return null; } TradeItem titem = new TradeItem(item, count, price); _items.add(titem); // If Player has already confirmed this trade, invalidate the confirmation invalidateConfirmation(); return titem; } /** * Add item to TradeList * @param objectId : int * @param count : long * @param price : long * @return */ public synchronized TradeItem addItemByItemId(int itemId, long count, long price) { if (isLocked()) { _log.warning(_owner.getName() + ": Attempt to modify locked TradeList!"); return null; } L2Item item = ItemTable.getInstance().getTemplate(itemId); if (item == null) { _log.warning(_owner.getName() + ": Attempt to add invalid item to TradeList!"); return null; } if (!item.isTradeable() || item.getItemType() == L2EtcItemType.QUEST) return null; if (!item.isStackable() && count > 1) { _log.warning(_owner.getName() + ": Attempt to add non-stackable item to TradeList with count > 1!"); return null; } if ((PcInventory.MAX_ADENA / count) < price) { _log.warning(_owner.getName() + ": Attempt to overflow adena !"); return null; } TradeItem titem = new TradeItem(item, count, price); _items.add(titem); // If Player has already confirmed this trade, invalidate the confirmation invalidateConfirmation(); return titem; } /** * Remove item from TradeList * @param objectId : int * @param count : int * @return */ public synchronized TradeItem removeItem(int objectId, int itemId, long count) { if (isLocked()) { _log.warning(_owner.getName() + ": Attempt to modify locked TradeList!"); return null; } for (TradeItem titem : _items) { if (titem.getObjectId() == objectId || titem.getItem().getItemId() == itemId) { // If Partner has already confirmed this trade, invalidate the confirmation if (_partner != null) { TradeList partnerList = _partner.getActiveTradeList(); if (partnerList == null) { _log.warning(_partner.getName() + ": Trading partner (" + _partner.getName() + ") is invalid in this trade!"); return null; } partnerList.invalidateConfirmation(); } // Reduce item count or complete item if (count != -1 && titem.getCount() > count) titem.setCount(titem.getCount() - count); else _items.remove(titem); return titem; } } return null; } /** * Update items in TradeList according their quantity in owner inventory */ public synchronized void updateItems() { for (TradeItem titem : _items) { L2ItemInstance item = _owner.getInventory().getItemByObjectId(titem.getObjectId()); if (item == null || titem.getCount() < 1) removeItem(titem.getObjectId(), -1, -1); else if (item.getCount() < titem.getCount()) titem.setCount(item.getCount()); } } /** * Lockes TradeList, no further changes are allowed */ public void lock() { _locked = true; } /** * Clears item list */ public synchronized void clear() { _items.clear(); _locked = false; } /** * Confirms TradeList * @return : boolean */ public boolean confirm() { if (_confirmed) return true; // Already confirmed // If Partner has already confirmed this trade, proceed exchange if (_partner != null) { TradeList partnerList = _partner.getActiveTradeList(); if (partnerList == null) { _log.warning(_partner.getName() + ": Trading partner (" + _partner.getName() + ") is invalid in this trade!"); return false; } // Synchronization order to avoid deadlock TradeList sync1, sync2; if (getOwner().getObjectId() > partnerList.getOwner().getObjectId()) { sync1 = partnerList; sync2 = this; } else { sync1 = this; sync2 = partnerList; } synchronized (sync1) { synchronized (sync2) { _confirmed = true; if (partnerList.isConfirmed()) { partnerList.lock(); lock(); if (!partnerList.validate()) return false; if (!validate()) return false; doExchange(partnerList); } else _partner.onTradeConfirm(_owner); } } } else _confirmed = true; return _confirmed; } /** * Cancels TradeList confirmation */ public void invalidateConfirmation() { _confirmed = false; } /** * Validates TradeList with owner inventory */ private boolean validate() { // Check for Owner validity if (_owner == null || L2World.getInstance().getPlayer(_owner.getObjectId()) == null) { _log.warning("Invalid owner of TradeList"); return false; } // Check for Item validity for (TradeItem titem : _items) { L2ItemInstance item = _owner.checkItemManipulation(titem.getObjectId(), titem.getCount(), "transfer"); if (item == null || item.getCount() < 1) { _log.warning(_owner.getName() + ": Invalid Item in TradeList"); return false; } } return true; } /** * Transfers all TradeItems from inventory to partner */ private boolean TransferItems(L2PcInstance partner, InventoryUpdate ownerIU, InventoryUpdate partnerIU) { for (TradeItem titem : _items) { L2ItemInstance oldItem = _owner.getInventory().getItemByObjectId(titem.getObjectId()); if (oldItem == null) return false; L2ItemInstance newItem = _owner.getInventory().transferItem("Trade", titem.getObjectId(), titem.getCount(), partner.getInventory(), _owner, _partner); if (newItem == null) return false; // Add changes to inventory update packets if (ownerIU != null) { if (oldItem.getCount() > 0 && oldItem != newItem) ownerIU.addModifiedItem(oldItem); else ownerIU.addRemovedItem(oldItem); } if (partnerIU != null) { if (newItem.getCount() > titem.getCount()) partnerIU.addModifiedItem(newItem); else partnerIU.addNewItem(newItem); } } return true; } /** * Count items slots */ public int countItemsSlots(L2PcInstance partner) { int slots = 0; for (TradeItem item : _items) { if (item == null) continue; L2Item template = ItemTable.getInstance().getTemplate(item.getItem().getItemId()); if (template == null) continue; if (!template.isStackable()) slots += item.getCount(); else if (partner.getInventory().getItemByItemId(item.getItem().getItemId()) == null) slots++; } return slots; } /** * Calc weight of items in tradeList */ public int calcItemsWeight() { long weight = 0; for (TradeItem item : _items) { if (item == null) continue; L2Item template = ItemTable.getInstance().getTemplate(item.getItem().getItemId()); if (template == null) continue; weight += item.getCount() * template.getWeight(); } return (int) Math.min(weight, Integer.MAX_VALUE); } /** * Proceeds with trade */ private void doExchange(TradeList partnerList) { boolean success = false; // check weight and slots if ((!getOwner().getInventory().validateWeight(partnerList.calcItemsWeight())) || !(partnerList.getOwner().getInventory().validateWeight(calcItemsWeight()))) { partnerList.getOwner().sendPacket(new SystemMessage(SystemMessageId.WEIGHT_LIMIT_EXCEEDED)); getOwner().sendPacket(new SystemMessage(SystemMessageId.WEIGHT_LIMIT_EXCEEDED)); } else if ((!getOwner().getInventory().validateCapacity(partnerList.countItemsSlots(getOwner()))) || (!partnerList.getOwner().getInventory().validateCapacity(countItemsSlots(partnerList.getOwner())))) { partnerList.getOwner().sendPacket(new SystemMessage(SystemMessageId.SLOTS_FULL)); getOwner().sendPacket(new SystemMessage(SystemMessageId.SLOTS_FULL)); } else { // Prepare inventory update packet InventoryUpdate ownerIU = Config.FORCE_INVENTORY_UPDATE ? null : new InventoryUpdate(); InventoryUpdate partnerIU = Config.FORCE_INVENTORY_UPDATE ? null : new InventoryUpdate(); // Transfer items partnerList.TransferItems(getOwner(), partnerIU, ownerIU); TransferItems(partnerList.getOwner(), ownerIU, partnerIU); // Send inventory update packet if (ownerIU != null) _owner.sendPacket(ownerIU); else _owner.sendPacket(new ItemList(_owner, false)); if (partnerIU != null) _partner.sendPacket(partnerIU); else _partner.sendPacket(new ItemList(_partner, false)); // Update current load as well StatusUpdate playerSU = new StatusUpdate(_owner); playerSU.addAttribute(StatusUpdate.CUR_LOAD, _owner.getCurrentLoad()); _owner.sendPacket(playerSU); playerSU = new StatusUpdate(_partner); playerSU.addAttribute(StatusUpdate.CUR_LOAD, _partner.getCurrentLoad()); _partner.sendPacket(playerSU); success = true; } // Finish the trade partnerList.getOwner().onTradeFinish(success); getOwner().onTradeFinish(success); } /** * Buy items from this PrivateStore list * @return int: result of trading. 0 - ok, 1 - canceled (no adena), 2 - failed (item error) */ public synchronized int privateStoreBuy(L2PcInstance player, FastSet items) { if (_locked) return 1; if (!validate()) { lock(); return 1; } int slots = 0; int weight = 0; long totalPrice = 0; final PcInventory ownerInventory = _owner.getInventory(); final PcInventory playerInventory = player.getInventory(); for (ItemRequest item : items) { boolean found = false; for (TradeItem ti : _items) { if (ti.getObjectId() == item.getObjectId()) { if (ti.getPrice() == item.getPrice()) { if (ti.getCount() < item.getCount()) item.setCount(ti.getCount()); found = true; } break; } } // item with this objectId and price not found in tradelist if (!found) { if (isPackaged()) { Util.handleIllegalPlayerAction(player, "[TradeList.privateStoreBuy()] Player " + player.getName() + " tried to cheat the package sell and buy only a part of the package! Ban this player for bot usage!", Config.DEFAULT_PUNISH); return 2; } item.setCount(0); continue; } // check for overflow in the single item if ((MAX_ADENA / item.getCount()) < item.getPrice()) { // private store attempting to overflow - disable it lock(); return 1; } totalPrice += item.getCount() * item.getPrice(); // check for overflow of the total price if (MAX_ADENA < totalPrice || totalPrice < 0) { // private store attempting to overflow - disable it lock(); return 1; } // Check if requested item is available for manipulation L2ItemInstance oldItem = _owner.checkItemManipulation(item.getObjectId(), item.getCount(), "sell"); if (oldItem == null || !oldItem.isTradeable()) { // private store sell invalid item - disable it lock(); return 2; } L2Item template = ItemTable.getInstance().getTemplate(item.getItemId()); if (template == null) continue; weight += item.getCount() * template.getWeight(); if (!template.isStackable()) slots += item.getCount(); else if (playerInventory.getItemByItemId(item.getItemId()) == null) slots++; } if (totalPrice > playerInventory.getAdena()) { player.sendPacket(new SystemMessage(SystemMessageId.YOU_NOT_ENOUGH_ADENA)); return 1; } if (!playerInventory.validateWeight(weight)) { player.sendPacket(new SystemMessage(SystemMessageId.WEIGHT_LIMIT_EXCEEDED)); return 1; } if (!playerInventory.validateCapacity(slots)) { player.sendPacket(new SystemMessage(SystemMessageId.SLOTS_FULL)); return 1; } // Prepare inventory update packets final InventoryUpdate ownerIU = new InventoryUpdate(); final InventoryUpdate playerIU = new InventoryUpdate(); final L2ItemInstance adenaItem = playerInventory.getAdenaInstance(); playerInventory.reduceAdena("PrivateStore", totalPrice, player, _owner); playerIU.addItem(adenaItem); ownerInventory.addAdena("PrivateStore", totalPrice, _owner, player); ownerIU.addItem(ownerInventory.getAdenaInstance()); boolean ok = true; // Transfer items for (ItemRequest item : items) { if (item.getCount() == 0) continue; // Check if requested item is available for manipulation L2ItemInstance oldItem = _owner.checkItemManipulation(item.getObjectId(), item.getCount(), "sell"); if (oldItem == null) { // should not happens - validation already done lock(); ok = false; break; } // Proceed with item transfer L2ItemInstance newItem = ownerInventory.transferItem("PrivateStore", item.getObjectId(), item.getCount(), playerInventory, _owner, player); if (newItem == null) { ok = false; break; } removeItem(item.getObjectId(), -1, item.getCount()); // Add changes to inventory update packets if (oldItem.getCount() > 0 && oldItem != newItem) ownerIU.addModifiedItem(oldItem); else ownerIU.addRemovedItem(oldItem); if (newItem.getCount() > item.getCount()) playerIU.addModifiedItem(newItem); else playerIU.addNewItem(newItem); // Send messages about the transaction to both players if (newItem.isStackable()) { SystemMessage msg = new SystemMessage(SystemMessageId.C1_PURCHASED_S3_S2_S); msg.addString(player.getName()); msg.addItemName(newItem); msg.addItemNumber(item.getCount()); _owner.sendPacket(msg); msg = new SystemMessage(SystemMessageId.PURCHASED_S3_S2_S_FROM_C1); msg.addString(_owner.getName()); msg.addItemName(newItem); msg.addItemNumber(item.getCount()); player.sendPacket(msg); } else { SystemMessage msg = new SystemMessage(SystemMessageId.C1_PURCHASED_S2); msg.addString(player.getName()); msg.addItemName(newItem); _owner.sendPacket(msg); msg = new SystemMessage(SystemMessageId.PURCHASED_S2_FROM_C1); msg.addString(_owner.getName()); msg.addItemName(newItem); player.sendPacket(msg); } } // Send inventory update packet _owner.sendPacket(ownerIU); player.sendPacket(playerIU); if (ok) return 0; else return 2; } /** * Sell items to this PrivateStore list * @return : boolean true if success */ public synchronized boolean privateStoreSell(L2PcInstance player, ItemRequest[] items) { if (_locked) return false; boolean ok = false; final PcInventory ownerInventory = _owner.getInventory(); final PcInventory playerInventory = player.getInventory(); // Prepare inventory update packet final InventoryUpdate ownerIU = new InventoryUpdate(); final InventoryUpdate playerIU = new InventoryUpdate(); long totalPrice = 0; for (ItemRequest item : items) { // searching item in tradelist using itemId boolean found = false; for (TradeItem ti : _items) { if (ti.getItem().getItemId() == item.getItemId()) { // price should be the same if (ti.getPrice() == item.getPrice()) { // if requesting more than available - decrease count if (ti.getCount() < item.getCount()) item.setCount(ti.getCount()); found = item.getCount() > 0; } break; } } // not found any item in the tradelist with same itemId and price // maybe another player already sold this item ? if (!found) continue; // check for overflow in the single item if ((MAX_ADENA / item.getCount()) < item.getPrice()) { lock(); break; } long _totalPrice = totalPrice + item.getCount() * item.getPrice(); // check for overflow of the total price if (MAX_ADENA < _totalPrice || _totalPrice < 0) { lock(); break; } if (ownerInventory.getAdena() < _totalPrice) continue; // Check if requested item is available for manipulation int objectId = item.getObjectId(); L2ItemInstance oldItem = player.checkItemManipulation(objectId, item.getCount(), "sell"); // private store - buy use same objectId for buying several non-stackable items if (oldItem == null) { // searching other items using same itemId oldItem = playerInventory.getItemByItemId(item.getItemId()); if (oldItem == null) continue; objectId = oldItem.getObjectId(); oldItem = player.checkItemManipulation(objectId, item.getCount(), "sell"); if (oldItem == null) continue; } if (!oldItem.isTradeable()) continue; // Proceed with item transfer L2ItemInstance newItem = playerInventory.transferItem("PrivateStore", objectId, item.getCount(), ownerInventory, player, _owner); if (newItem == null) continue; removeItem(-1, item.getItemId(), item.getCount()); ok = true; // increase total price only after successful transaction totalPrice = _totalPrice; // Add changes to inventory update packets if (oldItem.getCount() > 0 && oldItem != newItem) playerIU.addModifiedItem(oldItem); else playerIU.addRemovedItem(oldItem); if (newItem.getCount() > item.getCount()) ownerIU.addModifiedItem(newItem); else ownerIU.addNewItem(newItem); // Send messages about the transaction to both players if (newItem.isStackable()) { SystemMessage msg = new SystemMessage(SystemMessageId.PURCHASED_S3_S2_S_FROM_C1); msg.addString(player.getName()); msg.addItemName(newItem); msg.addItemNumber(item.getCount()); _owner.sendPacket(msg); msg = new SystemMessage(SystemMessageId.C1_PURCHASED_S3_S2_S); msg.addString(_owner.getName()); msg.addItemName(newItem); msg.addItemNumber(item.getCount()); player.sendPacket(msg); } else { SystemMessage msg = new SystemMessage(SystemMessageId.PURCHASED_S2_FROM_C1); msg.addString(player.getName()); msg.addItemName(newItem); _owner.sendPacket(msg); msg = new SystemMessage(SystemMessageId.C1_PURCHASED_S2); msg.addString(_owner.getName()); msg.addItemName(newItem); player.sendPacket(msg); } } if (totalPrice > 0) { // Transfer adena if (totalPrice > ownerInventory.getAdena()) // should not happens, just a precaution return false; final L2ItemInstance adenaItem = ownerInventory.getAdenaInstance(); ownerInventory.reduceAdena("PrivateStore", totalPrice, _owner, player); ownerIU.addItem(adenaItem); playerInventory.addAdena("PrivateStore", totalPrice, player, _owner); playerIU.addItem(playerInventory.getAdenaInstance()); } if (ok) { // Send inventory update packet _owner.sendPacket(ownerIU); player.sendPacket(playerIU); } return ok; } }