diff --git a/src/main/NGECore.java b/src/main/NGECore.java index ee431544..8d52846a 100644 --- a/src/main/NGECore.java +++ b/src/main/NGECore.java @@ -627,6 +627,9 @@ public class NGECore { return resourceHistoryODB; } + public ObjectDatabase getAuctionODB() { + return auctionODB; + } public int getActiveClients() { int connections = 0; diff --git a/src/protocol/swg/auctionManagerClientListener/AuctionQueryHeadersMessage.java b/src/protocol/swg/auctionManagerClientListener/AuctionQueryHeadersMessage.java index 14f345ba..e0ce1fd6 100644 --- a/src/protocol/swg/auctionManagerClientListener/AuctionQueryHeadersMessage.java +++ b/src/protocol/swg/auctionManagerClientListener/AuctionQueryHeadersMessage.java @@ -63,7 +63,7 @@ public class AuctionQueryHeadersMessage extends SWGMessage { setMinPrice(data.getInt()); setMaxPrice(data.getInt()); setIncludeEntranceFee(data.get()); - data.skip(5); // unk + data.skip(6); // unk setVendorId(data.getLong()); setVendorFlag(data.get()); setOffset(data.getShort()); diff --git a/src/protocol/swg/auctionManagerClientListener/AuctionQueryHeadersResponseMessage.java b/src/protocol/swg/auctionManagerClientListener/AuctionQueryHeadersResponseMessage.java index 4c2776c9..a36bb523 100644 --- a/src/protocol/swg/auctionManagerClientListener/AuctionQueryHeadersResponseMessage.java +++ b/src/protocol/swg/auctionManagerClientListener/AuctionQueryHeadersResponseMessage.java @@ -21,22 +21,159 @@ ******************************************************************************/ package protocol.swg.auctionManagerClientListener; +import java.nio.ByteOrder; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.TreeSet; +import java.util.Vector; + import org.apache.mina.core.buffer.IoBuffer; +import engine.resources.common.CRC; import protocol.swg.SWGMessage; +import services.bazaar.AuctionItem; public class AuctionQueryHeadersResponseMessage extends SWGMessage { + + private int screen; + private int counter; + private LinkedHashSet locationList = new LinkedHashSet(); + private LinkedHashSet itemList = new LinkedHashSet(); + private long playerId; + private short offset; + private boolean continues; + + public AuctionQueryHeadersResponseMessage(long playerId, int screen, int counter, short offset, boolean continues) { + this.playerId = playerId; + this.screen = screen; + this.counter = counter; + this.offset = offset; + this.continues = continues; + } @Override public void deserialize(IoBuffer data) { // TODO Auto-generated method stub } + + public void addItem(AuctionItem item) { + locationList.add(item.getVuid()); + locationList.add(item.getOwnerName()); + + if(item.isAuction() && item.getStatus() == AuctionItem.FORSALE) + locationList.add(item.getBidderName()); + else + locationList.add(""); + + itemList.add(item); + } @Override public IoBuffer serialize() { - // TODO Auto-generated method stub - return null; + final IoBuffer result = IoBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN); + result.setAutoExpand(true); + + result.putShort((short) 8); + result.putInt(0xFA500E52); + result.putInt(counter); + result.putInt(screen); + + result.putInt(locationList.size()); + + for(String str : locationList) { + result.put(getAsciiString(str)); + } + + result.putInt(itemList.size()); + + for(AuctionItem item : itemList) { + result.put(getUnicodeString(item.getItemName())); + } + + result.putInt(itemList.size()); + + int i = 0; + for(AuctionItem item : itemList) { + + result.putLong(item.getObjectId()); + result.put((byte) i); + result.putInt(item.getPrice()); + result.putInt((int) ((item.getExpireTime() - System.currentTimeMillis()) / 1000)); + result.putInt(item.getPrice()); + result.putShort((short) 0); + result.put((byte) 0); + result.putInt(CRC.StringtoCRC(item.getItem().getTemplate())); + result.putShort((short) 0); + result.put((byte) 0); + result.put((byte) 1); + result.putInt(0); + result.putInt(0); + result.put((byte) 0); + result.putInt(2); + result.putInt(0); + result.putShort((short) 0); + result.putInt(item.getItemType()); + result.putInt(0); + result.putInt(0); + result.putInt(0); + +/* + result.put((byte) (item.isAuction() ? 0 : 1)); + result.putShort((short) getStringIndex(item.getVuid())); + result.putLong(item.getOwnerId()); + result.putShort((short) getStringIndex(item.getOwnerName())); + + if(item.isAuction() && item.getStatus() == AuctionItem.FORSALE) { + result.putLong(item.getBuyerId()); + result.putShort((short) getStringIndex(item.getBidderName())); + } else { + result.putLong(0); + result.putShort((short) getStringIndex("")); + } + + result.putInt(item.getProxyBid()); + result.putInt(item.getPrice()); + result.putInt(item.getItemType()); + + int options = 0; + + if(item.getOwnerId() == playerId && (item.getStatus() == AuctionItem.OFFERED || item.getStatus() == AuctionItem.FORSALE)) + options |= 0x800; + + result.putInt(item.getAuctionOptions() | options); + result.putInt(0); + */ + i++; + + } + + result.putShort(offset); + + if(continues) + result.put((byte) 1); + else + result.put((byte) 0); + + int size = result.position(); + IoBuffer result2 = IoBuffer.allocate(size).put(result.array(), 0, size); + + return result2.flip(); + } + + private int getStringIndex(String str) { + int i = 0; + for(String string : locationList) { + if(str.equals(string)) + return i; + i++; + } + return i; + } + + public void setContinues(boolean continues) { + this.continues = continues; } } diff --git a/src/protocol/swg/auctionManagerClientListener/BidAuctionMessage.java b/src/protocol/swg/auctionManagerClientListener/BidAuctionMessage.java index ea769785..b065b56e 100644 --- a/src/protocol/swg/auctionManagerClientListener/BidAuctionMessage.java +++ b/src/protocol/swg/auctionManagerClientListener/BidAuctionMessage.java @@ -28,13 +28,16 @@ import protocol.swg.SWGMessage; public class BidAuctionMessage extends SWGMessage { private long auctionId; + private long vendorId; private int myPrice; private int proxyPrice; + @Override public void deserialize(IoBuffer data) { data.skip(6); setAuctionId(data.getLong()); + setVendorId(data.getLong()); setMyPrice(data.getInt()); setProxyPrice(data.getInt()); } @@ -69,4 +72,12 @@ public class BidAuctionMessage extends SWGMessage { this.proxyPrice = proxyPrice; } + public long getVendorId() { + return vendorId; + } + + public void setVendorId(long vendorId) { + this.vendorId = vendorId; + } + } diff --git a/src/protocol/swg/auctionManagerClientListener/BidAuctionResponseMessage.java b/src/protocol/swg/auctionManagerClientListener/BidAuctionResponseMessage.java index 2ff213ef..06eff2f5 100644 --- a/src/protocol/swg/auctionManagerClientListener/BidAuctionResponseMessage.java +++ b/src/protocol/swg/auctionManagerClientListener/BidAuctionResponseMessage.java @@ -62,5 +62,10 @@ public class BidAuctionResponseMessage extends SWGMessage { result.putInt(status); return result.flip(); } + + public void setStatus(int status) { + this.status = status; + } + } diff --git a/src/protocol/swg/auctionManagerClientListener/CancelLiveAuctionResponseMessage.java b/src/protocol/swg/auctionManagerClientListener/CancelLiveAuctionResponseMessage.java index d820f45b..55e1ce02 100644 --- a/src/protocol/swg/auctionManagerClientListener/CancelLiveAuctionResponseMessage.java +++ b/src/protocol/swg/auctionManagerClientListener/CancelLiveAuctionResponseMessage.java @@ -59,5 +59,10 @@ public class CancelLiveAuctionResponseMessage extends SWGMessage { result.put((byte) 0); // unk return result.flip(); } + + public void setStatus(int status) { + this.status = status; + } + } diff --git a/src/protocol/swg/auctionManagerClientListener/CreateAuctionMessage.java b/src/protocol/swg/auctionManagerClientListener/CreateAuctionMessage.java index cdabef34..d05a6756 100644 --- a/src/protocol/swg/auctionManagerClientListener/CreateAuctionMessage.java +++ b/src/protocol/swg/auctionManagerClientListener/CreateAuctionMessage.java @@ -26,6 +26,7 @@ import java.nio.ByteBuffer; import org.apache.mina.core.buffer.IoBuffer; +import engine.resources.common.Utilities; import protocol.swg.SWGMessage; public class CreateAuctionMessage extends SWGMessage { @@ -40,10 +41,7 @@ public class CreateAuctionMessage extends SWGMessage { @Override public void deserialize(IoBuffer data) { data.skip(6); - setObjectId(data.getLong()); - setVendorId(data.getLong()); - setPrice(data.getInt()); - setDuration(data.getInt()); // in minutes + setObjectId(data.getLong()); int size = data.getInt(); try { setDescription(new String(ByteBuffer.allocate(size * 2).put(data.array(), data.position(), size * 2).array(), "UTF-16LE")); @@ -51,6 +49,10 @@ public class CreateAuctionMessage extends SWGMessage { e.printStackTrace(); } data.position(data.position() + size * 2); + + setVendorId(data.getLong()); + setPrice(data.getInt()); + setDuration(data.getInt()); // in minutes setPremium(data.get()); } @@ -100,8 +102,10 @@ public class CreateAuctionMessage extends SWGMessage { this.description = description; } - public byte getPremium() { - return premium; + public boolean getPremium() { + if(premium == 1) + return true; + return false; } public void setPremium(byte premium) { diff --git a/src/protocol/swg/auctionManagerClientListener/ItemSoldMessage.java b/src/protocol/swg/auctionManagerClientListener/CreateAuctionResponseMessage.java similarity index 90% rename from src/protocol/swg/auctionManagerClientListener/ItemSoldMessage.java rename to src/protocol/swg/auctionManagerClientListener/CreateAuctionResponseMessage.java index 6c22d1a1..8a067dff 100644 --- a/src/protocol/swg/auctionManagerClientListener/ItemSoldMessage.java +++ b/src/protocol/swg/auctionManagerClientListener/CreateAuctionResponseMessage.java @@ -27,7 +27,7 @@ import org.apache.mina.core.buffer.IoBuffer; import protocol.swg.SWGMessage; -public class ItemSoldMessage extends SWGMessage { +public class CreateAuctionResponseMessage extends SWGMessage { public static final int SUCCESS = 0; public static final int INVALIDAUCTIONER = 1; @@ -47,7 +47,7 @@ public class ItemSoldMessage extends SWGMessage { private long objectId; private int status; - public ItemSoldMessage(long objectId, int status) { + public CreateAuctionResponseMessage(long objectId, int status) { this.objectId = objectId; this.status = status; } @@ -61,11 +61,12 @@ public class ItemSoldMessage extends SWGMessage { @Override public IoBuffer serialize() { - IoBuffer result = IoBuffer.allocate(18).order(ByteOrder.LITTLE_ENDIAN); - result.putShort((short) 3); + IoBuffer result = IoBuffer.allocate(20).order(ByteOrder.LITTLE_ENDIAN); + result.putShort((short) 4); result.putInt(0x0E61CC92); result.putLong(objectId); result.putInt(status); + result.putShort((short) 0); return result.flip(); } diff --git a/src/protocol/swg/auctionManagerClientListener/GetAuctionDetailsResponse.java b/src/protocol/swg/auctionManagerClientListener/GetAuctionDetailsResponse.java index 4fe23725..458c59d6 100644 --- a/src/protocol/swg/auctionManagerClientListener/GetAuctionDetailsResponse.java +++ b/src/protocol/swg/auctionManagerClientListener/GetAuctionDetailsResponse.java @@ -21,12 +21,23 @@ ******************************************************************************/ package protocol.swg.auctionManagerClientListener; +import java.nio.ByteOrder; + import org.apache.mina.core.buffer.IoBuffer; +import engine.resources.objects.SWGObject; import protocol.swg.SWGMessage; +import resources.objects.tangible.TangibleObject; +import services.bazaar.AuctionItem; public class GetAuctionDetailsResponse extends SWGMessage { + private AuctionItem target; + + public GetAuctionDetailsResponse(AuctionItem target) { + this.target = target; + } + @Override public void deserialize(IoBuffer data) { // TODO Auto-generated method stub @@ -35,8 +46,30 @@ public class GetAuctionDetailsResponse extends SWGMessage { @Override public IoBuffer serialize() { - // TODO Auto-generated method stub - return null; + final IoBuffer result = IoBuffer.allocate(100).order(ByteOrder.LITTLE_ENDIAN); + result.setAutoExpand(true); + + result.putShort((short) 2); + result.putInt(0xFE0E644B); + result.putLong(target.getObjectId()); + result.put(getUnicodeString(target.getItemDescription())); + result.putInt(target.getItem().getAttributes().size()); + target.getItem().getAttributes().forEach((key, value) -> { + result.put(getAsciiString(key)); + result.put(getUnicodeString(value)); + }); + result.put(getAsciiString(target.getItem().getTemplate())); + if(((TangibleObject) target.getItem()).getCustomization() != null) { + result.putShort((short) ((TangibleObject) target.getItem()).getCustomization().length); + result.put(((TangibleObject) target.getItem()).getCustomization()); + } else + result.putShort((short) 0); + + + int size = result.position(); + IoBuffer result2 = IoBuffer.allocate(size).put(result.array(), 0, size); + + return result2.flip(); } } diff --git a/src/protocol/swg/auctionManagerClientListener/RetrieveAuctionItemResponseMessage.java b/src/protocol/swg/auctionManagerClientListener/RetrieveAuctionItemResponseMessage.java index 2d883695..ad9093da 100644 --- a/src/protocol/swg/auctionManagerClientListener/RetrieveAuctionItemResponseMessage.java +++ b/src/protocol/swg/auctionManagerClientListener/RetrieveAuctionItemResponseMessage.java @@ -58,5 +58,10 @@ public class RetrieveAuctionItemResponseMessage extends SWGMessage { result.putInt(status); return result.flip(); } + + public void setStatus(int status) { + this.status = status; + } + } diff --git a/src/resources/common/Opcodes.java b/src/resources/common/Opcodes.java index 59b9880d..1d0ec6d5 100644 --- a/src/resources/common/Opcodes.java +++ b/src/resources/common/Opcodes.java @@ -25,6 +25,8 @@ import engine.resources.common.CRC; public class Opcodes { + public static int AuctionQueryHeadersMessage = CRC.StringtoCRC("AuctionQueryHeadersMessage"); + public static int CreateImmediateAuctionMessage = CRC.StringtoCRC("CreateImmediateAuctionMessage"); public static int ChatEnterRoomById = CRC.StringtoCRC("ChatEnterRoomById"); public static int ChatSendToRoom = CRC.StringtoCRC("ChatSendToRoom"); public static int ChatDeletePersistentMessage = 0x8F251641; @@ -83,4 +85,10 @@ public class Opcodes { public static int ChatCreateRoom = CRC.StringtoCRC("ChatCreateRoom"); public static int ChatQueryRoom = 0x9CF2B192; public static int Unknown = 0x173B91C2; // packet sent to server on every character load-in + public static int GetAuctionDetails = CRC.StringtoCRC("GetAuctionDetails"); + public static int CancelLiveAuctionMessage = CRC.StringtoCRC("CancelLiveAuctionMessage"); + public static int BidAuctionMessage = CRC.StringtoCRC("BidAuctionMessage"); + public static int RetrieveAuctionItemMessage = CRC.StringtoCRC("RetrieveAuctionItemMessage"); + + } diff --git a/src/resources/objects/creature/CreatureObject.java b/src/resources/objects/creature/CreatureObject.java index 0dd25c94..e646c7f2 100644 --- a/src/resources/objects/creature/CreatureObject.java +++ b/src/resources/objects/creature/CreatureObject.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.atomic.LongAdder; import org.apache.mina.core.buffer.IoBuffer; @@ -1850,6 +1851,14 @@ public class CreatureObject extends TangibleObject implements IPersistent { } } + public int getInventoryItemCount() { + if(getSlottedObject("inventory") == null) + return 0; + LongAdder adder = new LongAdder(); + getSlottedObject("inventory").viewChildren(this, true, true, (obj) -> adder.increment()); + return adder.intValue(); + } + //public float getCooldown(String cooldownGroup) { //return ((float) getCooldown(cooldownGroup) / (float) 1000); //} diff --git a/src/services/bazaar/AuctionItem.java b/src/services/bazaar/AuctionItem.java index 8b134e97..92e1e41f 100644 --- a/src/services/bazaar/AuctionItem.java +++ b/src/services/bazaar/AuctionItem.java @@ -26,22 +26,26 @@ import com.sleepycat.je.Transaction; import com.sleepycat.persist.model.Entity; import com.sleepycat.persist.model.NotPersistent; import com.sleepycat.persist.model.PrimaryKey; -import engine.resources.objects.IPersistent; -@Entity(version=0) -public class AuctionItem implements IPersistent { +import engine.resources.objects.IPersistent; +import engine.resources.objects.SWGObject; + +@Entity(version=1) +public class AuctionItem implements IPersistent, Comparable { @PrimaryKey private long objectId; + private SWGObject item; private long ownerId; private long vendorId; private long buyerId; private long offerToId; private int itemType; private String ownerName; - private String bidderName; + private String bidderName = ""; private String itemName; private String itemDescription; + private String planet; private int price; private int proxyBid; private boolean auction; @@ -66,8 +70,9 @@ public class AuctionItem implements IPersistent { } - public AuctionItem(long objectId, long ownerId) { - this.objectId = objectId; + public AuctionItem(SWGObject item, long ownerId) { + this.item = item; + this.objectId = item.getObjectID(); this.ownerId = ownerId; } @@ -260,5 +265,31 @@ public class AuctionItem implements IPersistent { public Transaction getTransaction() { return txn; } + + public SWGObject getItem() { + return item; + } + + public void setItem(SWGObject item) { + this.item = item; + } + + public String getPlanet() { + return planet; + } + + public void setPlanet(String planet) { + this.planet = planet; + } + + @Override + public int compareTo(AuctionItem other) { + if(getExpireTime() < other.getExpireTime()) + return -1; + else if(getExpireTime() > other.getExpireTime()) + return 1; + else + return 0; + } } diff --git a/src/services/bazaar/BazaarService.java b/src/services/bazaar/BazaarService.java index 1700aa64..371d7990 100644 --- a/src/services/bazaar/BazaarService.java +++ b/src/services/bazaar/BazaarService.java @@ -22,31 +22,78 @@ package services.bazaar; import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.Vector; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.function.BiFunction; +import java.util.function.Function; + +import com.sleepycat.je.Transaction; +import com.sleepycat.persist.EntityCursor; import main.NGECore; import engine.clients.Client; import engine.resources.objects.SWGObject; +import engine.resources.scene.Planet; import engine.resources.scene.Point3D; import engine.resources.service.INetworkDispatch; import engine.resources.service.INetworkRemoteEvent; import resources.common.Opcodes; +import resources.common.OutOfBand; +import resources.common.ProsePackage; import resources.objects.building.BuildingObject; import resources.objects.creature.CreatureObject; +import resources.objects.intangible.IntangibleObject; +import resources.objects.manufacture.ManufactureSchematicObject; +import resources.objects.tangible.TangibleObject; import protocol.swg.ClientIdMsg; +import protocol.swg.auctionManagerClientListener.AuctionQueryHeadersMessage; +import protocol.swg.auctionManagerClientListener.AuctionQueryHeadersResponseMessage; +import protocol.swg.auctionManagerClientListener.BidAuctionMessage; +import protocol.swg.auctionManagerClientListener.BidAuctionResponseMessage; +import protocol.swg.auctionManagerClientListener.CancelLiveAuctionMessage; +import protocol.swg.auctionManagerClientListener.CancelLiveAuctionResponseMessage; import protocol.swg.auctionManagerClientListener.CommoditiesItemTypeListResponse; import protocol.swg.auctionManagerClientListener.CommoditiesResourceTypeListResponse; import protocol.swg.auctionManagerClientListener.CreateAuctionMessage; +import protocol.swg.auctionManagerClientListener.GetAuctionDetails; +import protocol.swg.auctionManagerClientListener.GetAuctionDetailsResponse; import protocol.swg.auctionManagerClientListener.IsVendorOwnerMessage; import protocol.swg.auctionManagerClientListener.IsVendorOwnerResponseMessage; -import protocol.swg.auctionManagerClientListener.ItemSoldMessage; +import protocol.swg.auctionManagerClientListener.CreateAuctionResponseMessage; +import protocol.swg.auctionManagerClientListener.RetrieveAuctionItemMessage; +import protocol.swg.auctionManagerClientListener.RetrieveAuctionItemResponseMessage; public class BazaarService implements INetworkDispatch { private NGECore core; + private Set auctionItems = new ConcurrentSkipListSet(); + private ConcurrentHashMap commodityLimit = new ConcurrentHashMap(); + private final static int COMMODITY_LIMIT = 25; + private final static int MAX_BAZAAR_SALE_PRICE = 10000000; + private final static int SALES_FEE = 20; + private final static int COMMODITY_EXPIRE = 604800; + private final static int VENDOR_EXPIRE = 2592000; public BazaarService(NGECore core) { this.core = core; + loadAuctionItems(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + System.out.println("Saving auction items..."); + auctionItems.forEach(item -> core.objectService.persistObject(item, Long.class, AuctionItem.class, core.getAuctionODB())); + })); + } + + private void loadAuctionItems() { + EntityCursor cursor = core.getAuctionODB().getCursor(Long.class, AuctionItem.class); + Iterator it = cursor.iterator(); + it.forEachRemaining(this::addAuctionItem); + cursor.close(); } @Override @@ -90,7 +137,7 @@ public class BazaarService implements INetworkDispatch { if(terminal == null) return; Point3D pos = terminal.getWorldPosition(); - session.write(new IsVendorOwnerResponseMessage(2, 0, terminalId, "tatooine.@tatooine_region_names:mos_eisley.@terminal_name:terminal_bazaar." + terminalId + "#" + pos.x + "," + pos.z).serialize()); + session.write(new IsVendorOwnerResponseMessage(2, 0, terminalId, getVendorUID((TangibleObject) terminal)).serialize()); }); @@ -116,6 +163,117 @@ public class BazaarService implements INetworkDispatch { }); + swgOpcodes.put(Opcodes.CancelLiveAuctionMessage, (session, data) -> { + + Client client = core.getClient(session); + + if (client == null) + return; + + SWGObject player = client.getParent(); + + if (player == null) + return; + + data = data.order(ByteOrder.LITTLE_ENDIAN); + data.position(0); + + CancelLiveAuctionMessage cancelLiveAuction = new CancelLiveAuctionMessage(); + cancelLiveAuction.deserialize(data); + + AuctionItem item = getAuctionItem(cancelLiveAuction.getObjectId()); + + if(item == null) + return; + + cancelItem(player, item); + + }); + + swgOpcodes.put(Opcodes.BidAuctionMessage, (session, data) -> { + + Client client = core.getClient(session); + + if (client == null) + return; + + SWGObject player = client.getParent(); + + if (player == null) + return; + + data = data.order(ByteOrder.LITTLE_ENDIAN); + data.position(0); + + BidAuctionMessage bidAuction = new BidAuctionMessage(); + bidAuction.deserialize(data); + + AuctionItem item = getAuctionItem(bidAuction.getAuctionId()); + //SWGObject vendor = core.objectService.getObject(bidAuction.getVendorId()); + if(item == null) + return; + SWGObject vendor = core.objectService.getObject(item.getVendorId()); + if(vendor == null) + return; + buyItem((CreatureObject) player, item, vendor, bidAuction.getMyPrice(), bidAuction.getProxyPrice()); + + }); + + swgOpcodes.put(Opcodes.RetrieveAuctionItemMessage, (session, data) -> { + + Client client = core.getClient(session); + + if (client == null) + return; + + SWGObject player = client.getParent(); + + if (player == null) + return; + + data = data.order(ByteOrder.LITTLE_ENDIAN); + data.position(0); + + RetrieveAuctionItemMessage retrieveItem = new RetrieveAuctionItemMessage(); + retrieveItem.deserialize(data); + + AuctionItem item = getAuctionItem(retrieveItem.getObjectId()); + SWGObject vendor = core.objectService.getObject(retrieveItem.getVendorId()); + + if(item == null || vendor == null) + return; + + retrieveItem((CreatureObject) player, item, vendor); + + }); + + swgOpcodes.put(Opcodes.GetAuctionDetails, (session, data) -> { + + Client client = core.getClient(session); + + if (client == null) + return; + + SWGObject player = client.getParent(); + + if (player == null) + return; + + data = data.order(ByteOrder.LITTLE_ENDIAN); + data.position(0); + + GetAuctionDetails request = new GetAuctionDetails(); + request.deserialize(data); + AuctionItem item = getAuctionItem(request.getObjectId()); + + if(item == null) + return; + + client.getSession().write(new GetAuctionDetailsResponse(item).serialize()); + + }); + + swgOpcodes.put(Opcodes.CreateAuctionMessage, (session, data) -> { Client client = core.getClient(session); @@ -125,6 +283,34 @@ public class BazaarService implements INetworkDispatch { SWGObject player = client.getParent(); + if (player == null) + return; + + + data = data.order(ByteOrder.LITTLE_ENDIAN); + data.position(0); + + CreateAuctionMessage createAuction = new CreateAuctionMessage(); + createAuction.deserialize(data); + SWGObject vendor = core.objectService.getObject(createAuction.getVendorId()); + SWGObject item = core.objectService.getObject(createAuction.getObjectId()); + + if(vendor == null || item == null || item instanceof CreatureObject || item instanceof BuildingObject) + return; + + addAuction((CreatureObject) player, item, vendor, createAuction.getPrice(), createAuction.getDuration(), createAuction.getDescription(), true, createAuction.getPremium()); + + }); + + swgOpcodes.put(Opcodes.CreateImmediateAuctionMessage, (session, data) -> { + + Client client = core.getClient(session); + + if (client == null) + return; + + SWGObject player = client.getParent(); + if (player == null) return; @@ -132,14 +318,41 @@ public class BazaarService implements INetworkDispatch { data.position(0); CreateAuctionMessage createAuction = new CreateAuctionMessage(); - + createAuction.deserialize(data); + SWGObject vendor = core.objectService.getObject(createAuction.getVendorId()); SWGObject item = core.objectService.getObject(createAuction.getObjectId()); if(vendor == null || item == null || item instanceof CreatureObject || item instanceof BuildingObject) return; + addAuction((CreatureObject) player, item, vendor, createAuction.getPrice(), createAuction.getDuration(), createAuction.getDescription(), false, createAuction.getPremium()); + + }); + + swgOpcodes.put(Opcodes.AuctionQueryHeadersMessage, (session, data) -> { + Client client = core.getClient(session); + + if (client == null) + return; + + SWGObject player = client.getParent(); + + if (player == null) + return; + + data = data.order(ByteOrder.LITTLE_ENDIAN); + data.position(0); + + AuctionQueryHeadersMessage queryHeaders = new AuctionQueryHeadersMessage(); + queryHeaders.deserialize(data); + SWGObject vendor = core.objectService.getObject(queryHeaders.getVendorId()); + + if(vendor == null) + return; + + getItemData(player, vendor, queryHeaders.getRange(), queryHeaders.getScreen(), queryHeaders.getCounter(), queryHeaders.getOffset(), queryHeaders.getItemTypeCRC(), queryHeaders.getMinPrice(), queryHeaders.getMaxPrice()); }); @@ -147,17 +360,381 @@ public class BazaarService implements INetworkDispatch { } + public void buyItem(CreatureObject player, AuctionItem item, SWGObject vendor, int price, int proxyPrice) { + + BidAuctionResponseMessage response = new BidAuctionResponseMessage(item.getObjectId(), BidAuctionResponseMessage.SUCCESS); + + if((price > MAX_BAZAAR_SALE_PRICE || proxyPrice > MAX_BAZAAR_SALE_PRICE) && item.isOnBazaar()) { + response.setStatus(BidAuctionResponseMessage.PRICEOVERFLOW); + player.getClient().getSession().write(response.serialize()); + return; + } + + if(price < 1 /*|| proxyPrice < 1 */|| price < item.getPrice()) { + response.setStatus(BidAuctionResponseMessage.INVALIDPRICE); + player.getClient().getSession().write(response.serialize()); + return; + } + + if(price > player.getBankCredits()) { + response.setStatus(BidAuctionResponseMessage.NOTENOUGHCREDITS); + player.getClient().getSession().write(response.serialize()); + return; + } + + if(item.isAuction()) + bidAuction(player, item, vendor, price, proxyPrice); + else + instantBuy(player, item, vendor); + + } + + public void instantBuy(CreatureObject player, AuctionItem item, SWGObject vendor) { + //System.out.println("test"); + + long timeUntilExpire = (item.getExpireTime() - System.currentTimeMillis()) / 1000; + long retrieveExpire = 0; + + if(item.isOnBazaar()) + retrieveExpire = timeUntilExpire + COMMODITY_EXPIRE; + else + retrieveExpire = timeUntilExpire + VENDOR_EXPIRE; + + item.setStatus(AuctionItem.SOLD); + item.setBuyerId(player.getObjectID()); + item.setExpireTime(retrieveExpire); + item.setBidderName(player.getCustomName()); + player.setBankCredits(player.getBankCredits() - item.getPrice()); + player.getClient().getSession().write(new BidAuctionResponseMessage(item.getObjectId(), BidAuctionResponseMessage.SUCCESS).serialize()); + + // TODO send mail with WP + + CreatureObject seller = (CreatureObject) core.objectService.getObject(item.getOwnerId()); + + if(seller == null) { + seller = core.objectService.getCreatureFromDB(item.getOwnerId()); + seller.setBankCredits(seller.getBankCredits() + item.getPrice()); + core.objectService.persistObject(seller, Long.class, CreatureObject.class, core.getAuctionODB()); + } + + } + + public void bidAuction(CreatureObject player, AuctionItem item, SWGObject vendor, int price, int proxyPrice) { + //System.out.println("test"); + } + + public void retrieveItem(CreatureObject player, AuctionItem item, SWGObject vendor) { + + RetrieveAuctionItemResponseMessage response = new RetrieveAuctionItemResponseMessage(item.getObjectId(), RetrieveAuctionItemResponseMessage.SUCCESS); + String closestCityName = core.mapService.getClosestCityName(player); + SWGObject vendorOfItem = core.objectService.getObject(item.getVendorId()); + + if(vendorOfItem == null) { + response.setStatus(RetrieveAuctionItemResponseMessage.DONTRETRIEVE); + player.getClient().getSession().write(response.serialize()); + return; + } + + + if(item.getItem() instanceof TangibleObject && player.getInventoryItemCount() >= 80) { + response.setStatus(RetrieveAuctionItemResponseMessage.FULLINVENTORY); + player.getClient().getSession().write(response.serialize()); + return; + } + + if(item.getStatus() == AuctionItem.RETRIEVED || item.getStatus() == AuctionItem.FORSALE) { + response.setStatus(RetrieveAuctionItemResponseMessage.NOTALLOWED); + player.getClient().getSession().write(response.serialize()); + return; + } + + if(item.getStatus() == AuctionItem.SOLD && item.getBuyerId() != player.getObjectID()) { + response.setStatus(RetrieveAuctionItemResponseMessage.NOTALLOWED); + player.getClient().getSession().write(response.serialize()); + return; + } + + if(!item.isOnBazaar() && !vendor.inRange(player.getWorldPosition(), 8)) { + response.setStatus(RetrieveAuctionItemResponseMessage.TOOFAR); + player.getClient().getSession().write(response.serialize()); + return; + } + + if(item.isOnBazaar() && !core.mapService.getClosestCityName(vendorOfItem).equals(closestCityName)) { + response.setStatus(RetrieveAuctionItemResponseMessage.TOOFAR); + player.getClient().getSession().write(response.serialize()); + return; + } + + item.setStatus(AuctionItem.RETRIEVED); + + if(item.getItem() instanceof TangibleObject) + player.getSlottedObject("inventory").add(item.getItem()); + else if(item.getItem() instanceof IntangibleObject) + player.getSlottedObject("datapad").add(item.getItem()); + + removeAuctionItem(item); + player.getClient().getSession().write(response.serialize()); + + } + + public void cancelItem(SWGObject player, AuctionItem item) { + + CancelLiveAuctionResponseMessage response = new CancelLiveAuctionResponseMessage(item.getObjectId(), CancelLiveAuctionResponseMessage.SUCCESS); + + long timeUntilExpire = (item.getExpireTime() - System.currentTimeMillis()) / 1000; + long retrieveExpire = 0; + + if(item.getStatus() == AuctionItem.FORSALE) { + + if(item.getOwnerId() != player.getObjectID()) { + response.setStatus(CancelLiveAuctionResponseMessage.NOTOWNER); + player.getClient().getSession().write(response.serialize()); + return; + } + + if(item.isOnBazaar()) + retrieveExpire = timeUntilExpire + COMMODITY_EXPIRE; + else + retrieveExpire = timeUntilExpire + VENDOR_EXPIRE; + + } else if(item.getStatus() == AuctionItem.OFFERED) { + // TODO implement offers + return; + } else { + response.setStatus(CancelLiveAuctionResponseMessage.ALREADYCOMPLETED); + player.getClient().getSession().write(response.serialize()); + return; + } + + // TODO: cancel auction once implemented + + item.setStatus(AuctionItem.EXPIRED); + item.setExpireTime(retrieveExpire); + player.getClient().getSession().write(response.serialize()); + + } + + public void getItemData(SWGObject player, SWGObject vendor, int range, int screen, int counter, short offset, int category, int minPrice, int maxPrice) { + + Set inRangeItems = getInRangeItems(range, player.getPlanet(), vendor, core.mapService.getClosestCityName(player)); + + Iterator it = inRangeItems.iterator(); + int displayedItems = 0; + + AuctionQueryHeadersResponseMessage response = new AuctionQueryHeadersResponseMessage(player.getObjectID(), screen, counter, offset, false); + + while(it.hasNext() && displayedItems < offset + 100) { + + AuctionItem item = it.next(); + + if((minPrice != 0 && item.getPrice() < minPrice) || (maxPrice != 0 && item.getPrice() > maxPrice)) + continue; + + switch(screen) { + + // Auctions + case 2: + if(item.getStatus() == AuctionItem.FORSALE) { + if((category & 255) != 0 && item.getItemType() == category) { + if(displayedItems >= offset) + response.addItem(item); + displayedItems++; + } else if((item.getItemType() & category) != 0) { + if(displayedItems >= offset) + response.addItem(item); + displayedItems++; + } else if(item.getItemType() < 256 && category == 8192) { + if(displayedItems >= offset) + response.addItem(item); + displayedItems++; + } else if(category == 0) { + if(displayedItems >= offset) + response.addItem(item); + displayedItems++; + } + } + break; + // My Items for sale + case 3: + if(item.getStatus() == AuctionItem.FORSALE && player.getObjectID() == item.getOwnerId()) + response.addItem(item); + break; + // My Auction Bids + case 4: + if(item.isAuction() && item.getStatus() == AuctionItem.FORSALE && player.getCustomName().equals(item.getBidderName())) + response.addItem(item); + break; + // Retrieve items + case 5: + if(item.getStatus() == AuctionItem.SOLD && player.getObjectID() == item.getBuyerId()) + response.addItem(item); + else if(item.getStatus() == AuctionItem.EXPIRED && player.getObjectID() == item.getOwnerId()) + response.addItem(item); + break; + // Offers to my vendor + case 6: + if(item.getStatus() == AuctionItem.OFFERED && player.getObjectID() == item.getOfferToId()) + response.addItem(item); + break; + // Vendor search TODO later + case 7: + break; + // Stock + case 8: + if(item.getStatus() == AuctionItem.SOLD && player.getObjectID() == item.getBuyerId()) + response.addItem(item); + else if(item.getStatus() == AuctionItem.EXPIRED && player.getObjectID() == item.getOwnerId()) + response.addItem(item); + break; + // Offers to other vendor + case 9: + if(item.getStatus() == AuctionItem.OFFERED && player.getObjectID() == item.getOwnerId()) + response.addItem(item); + break; + + + } + + } + + if(displayedItems == offset + 100) + response.setContinues(true); + + player.getClient().getSession().write(response.serialize()); + } + + public Set getInRangeItems(int range, Planet planet, SWGObject vendor, String closestCityName) { + + Set inRangeItems = new ConcurrentSkipListSet(); + String planetName = planet.getName(); + + auctionItems.forEach((item) -> { + + SWGObject vendorOfItem = core.objectService.getObject(item.getVendorId()); + if(vendorOfItem == null) + return; + if(range > 0 && !item.getPlanet().equals(planetName)) + return; + else if(range > 1 && !core.mapService.getClosestCityName(vendorOfItem).equals(closestCityName)) + return; + else if(range > 2 && item.getVendorId() != vendor.getObjectID()) + return; + + inRangeItems.add(item); + + }); + + return inRangeItems; + } + public void addAuction(CreatureObject player, SWGObject item, SWGObject vendor, int price, int duration, String description, boolean auction, boolean premium) { - ItemSoldMessage itemSoldMsg = new ItemSoldMessage(item.getObjectID(), ItemSoldMessage.SUCCESS); + CreateAuctionResponseMessage itemSoldMsg = new CreateAuctionResponseMessage(item.getObjectID(), CreateAuctionResponseMessage.SUCCESS); - if(item.getAttributes().containsKey("no_trade") || !item.isSubChildOf(player)) { - itemSoldMsg.setStatus(ItemSoldMessage.INVALIDITEM); + if(item.getAttributes().containsKey("no_trade") || !item.isSubChildOf(player) || (item instanceof IntangibleObject && !(item instanceof ManufactureSchematicObject))) { + itemSoldMsg.setStatus(CreateAuctionResponseMessage.INVALIDITEM); + player.getClient().getSession().write(itemSoldMsg.serialize()); + return; + } + + if(core.tradeService.getTradingObjectMap().get(item) != null) { + itemSoldMsg.setStatus(CreateAuctionResponseMessage.CANTSELLTRADINGITEM); + player.getClient().getSession().write(itemSoldMsg.serialize()); + return; + } + + if(getAuctionItem(item.getObjectID()) != null) { + itemSoldMsg.setStatus(CreateAuctionResponseMessage.ALREADYFORSALE); player.getClient().getSession().write(itemSoldMsg.serialize()); return; } + if(price < 1 || price > MAX_BAZAAR_SALE_PRICE) { + itemSoldMsg.setStatus(CreateAuctionResponseMessage.INVALIDPRICE); + player.getClient().getSession().write(itemSoldMsg.serialize()); + return; + } + if(commodityLimit.get(player) != null && commodityLimit.get(player) >= COMMODITY_LIMIT) { + itemSoldMsg.setStatus(CreateAuctionResponseMessage.TOOMANYITEMS); + player.getClient().getSession().write(itemSoldMsg.serialize()); + return; + } + + if(player.getBankCredits() < SALES_FEE || (premium && player.getBankCredits() < SALES_FEE * 5)) { + itemSoldMsg.setStatus(CreateAuctionResponseMessage.NOTENOUGHCREDITS); + player.getClient().getSession().write(itemSoldMsg.serialize()); + return; + } + + AuctionItem auctionItem = createAuctionItem(player, vendor, item, price, description, duration, auction, premium); + + core.objectService.destroyObject(item); + + if(vendor.getTemplate().contains("terminal_bazaar")) { + ProsePackage prose = new ProsePackage("base_player", "sale_fee"); + int salesFee = SALES_FEE; + if(premium) + salesFee *= 5; + + player.setBankCredits(player.getBankCredits() - salesFee); + prose.setDiInteger(salesFee); + player.sendSystemMessage(new OutOfBand(prose), (byte) 0); + } + + if(auction) { + + } + + player.getClient().getSession().write(itemSoldMsg.serialize()); + + } + + public AuctionItem createAuctionItem(SWGObject player, SWGObject vendor, SWGObject item, int price, String description, int duration, boolean auction, boolean premium) { + + AuctionItem auctionItem = new AuctionItem(item, player.getObjectID()); + auctionItem.setExpireTime(System.currentTimeMillis() + (COMMODITY_EXPIRE * 1000)); + auctionItem.setAuction(auction); + auctionItem.setItemDescription(description); + auctionItem.setPrice(price); + auctionItem.setItemType(item.getTemplateData().getAttribute("gameObjectType")); + auctionItem.setStatus(AuctionItem.FORSALE); + //auctionItem.setItemName("@" + item.getStfFilename() + ":" + item.getStfName()); + String name = item.getCustomName() == null ? "@" + item.getStfFilename() + ":" + item.getStfName() : item.getCustomName(); + auctionItem.setItemName(name); + auctionItem.setVendorId(vendor.getObjectID()); + auctionItem.setPlanet(vendor.getPlanet().getName()); + auctionItem.setVuid(getVendorUID((TangibleObject) vendor)); + auctionItem.setOwnerName(player.getCustomName()); + + if(vendor.getTemplate().contains("terminal_bazaar")) + auctionItem.setOnBazaar(true); + + addAuctionItem(auctionItem); + + return auctionItem; + } + + public String getVendorUID(TangibleObject vendor) { + + if(vendor.getAttachment("uid") != null) + return (String) vendor.getAttachment("uid"); + + String planet = ""; + if(vendor.getContainer() == null) + planet = vendor.getPlanet().getName(); + else + planet = vendor.getContainer().getPlanet().getName(); + + String uid = (planet + "."); + Point3D pos = vendor.getWorldPosition(); + + uid += ("@" + planet + "_region_names:" + core.mapService.getClosestCityName(vendor).toLowerCase().replace(" ", "_")); // TODO: replace this with city regions when they are implemented + uid += ("." + "@" + vendor.getStfFilename() + ":" + vendor.getStfName() + "."); + uid += (vendor.getObjectID() + "#" + pos.x + "," + pos.z); + vendor.setAttachment("uid", uid); + return uid; } @@ -165,6 +742,29 @@ public class BazaarService implements INetworkDispatch { public void shutdown() { } - - + + public AuctionItem getAuctionItem(long objectId) { + return auctionItems.parallelStream().filter(a -> a.getObjectId() == objectId).findFirst().orElse(null); + } + + public void addAuctionItem(AuctionItem auctionItem) { + auctionItems.add(auctionItem); + int commodityNumber = 0; + if(commodityLimit.get(auctionItem.getOwnerId()) != null) + commodityNumber = commodityLimit.get(auctionItem.getOwnerId()); + commodityLimit.put(auctionItem.getOwnerId(), commodityNumber + 1); + if(!core.getAuctionODB().contains(auctionItem.getObjectId(), Long.class, AuctionItem.class)) { + core.objectService.persistObject(auctionItem, Long.class, AuctionItem.class, core.getAuctionODB()); + } + } + + public void removeAuctionItem(AuctionItem auctionItem) { + auctionItems.remove(auctionItem); + int commodityNumber = 0; + if(commodityLimit.get(auctionItem.getOwnerId()) != null) + commodityNumber = commodityLimit.get(auctionItem.getOwnerId()); + commodityLimit.put(auctionItem.getOwnerId(), commodityNumber - 1); + core.objectService.deletePersistentObject(auctionItem, Long.class, AuctionItem.class, core.getAuctionODB(), auctionItem.getObjectId()); + } + } diff --git a/src/services/map/MapService.java b/src/services/map/MapService.java index 23b6437c..007f4281 100644 --- a/src/services/map/MapService.java +++ b/src/services/map/MapService.java @@ -117,6 +117,9 @@ public class MapService implements INetworkDispatch { public String getClosestCityName(SWGObject object) { + if(object.getPlanet() == null) + object.setPlanet(core.terrainService.getPlanetByID(object.getPlanetId())); + Vector locations = locationMap.get(object.getPlanet()); float closestDistance = Float.MAX_VALUE; String closestName = ""; diff --git a/src/services/object/ObjectService.java b/src/services/object/ObjectService.java index e8494e41..57c69859 100644 --- a/src/services/object/ObjectService.java +++ b/src/services/object/ObjectService.java @@ -88,6 +88,8 @@ import engine.resources.container.Traverser; import engine.resources.container.WorldCellPermissions; import engine.resources.container.WorldPermissions; import engine.resources.database.DatabaseConnection; +import engine.resources.database.ObjectDatabase; +import engine.resources.objects.IPersistent; import engine.resources.objects.SWGObject; import engine.resources.scene.Planet; import engine.resources.scene.Point3D; @@ -116,6 +118,7 @@ import resources.objects.tangible.TangibleObject; import resources.objects.tool.SurveyTool; import resources.objects.waypoint.WaypointObject; import resources.objects.weapon.WeaponObject; +import services.bazaar.AuctionItem; @SuppressWarnings("unused") @@ -1185,4 +1188,16 @@ public class ObjectService implements INetworkDispatch { return count.get(); } + public void persistObject(IPersistent object, Class keyClass, Class valueClass, ObjectDatabase odb) { + object.createTransaction(odb.getEnvironment()); + core.getAuctionODB().put(object, keyClass, valueClass, object.getTransaction()); + object.getTransaction().commitSync(); + } + + public void deletePersistentObject(IPersistent object, Class keyClass, Class valueClass, ObjectDatabase odb, Object key) { + object.createTransaction(odb.getEnvironment()); + core.getAuctionODB().delete(key, keyClass, valueClass, object.getTransaction()); + object.getTransaction().commitSync(); + } + }