diff --git a/Dalamud.sln.DotSettings b/Dalamud.sln.DotSettings index 822d0841c..ba114a776 100644 --- a/Dalamud.sln.DotSettings +++ b/Dalamud.sln.DotSettings @@ -51,5 +51,8 @@ True True True + True True - True \ No newline at end of file + True + True + True \ No newline at end of file diff --git a/Dalamud/Game/Network/MarketBoardUploaders/IMarketBoardUploader.cs b/Dalamud/Game/Network/MarketBoardUploaders/IMarketBoardUploader.cs index c6f2f0303..9820ac237 100644 --- a/Dalamud/Game/Network/MarketBoardUploaders/IMarketBoardUploader.cs +++ b/Dalamud/Game/Network/MarketBoardUploaders/IMarketBoardUploader.cs @@ -18,5 +18,11 @@ namespace Dalamud.Game.Network.MarketBoardUploaders /// /// The tax rate data being uploaded. void UploadTax(MarketTaxRates taxRates); + + /// + /// Upload information about a purchase this client has made. + /// + /// The purchase handler data associated with the sale. + void UploadPurchase(MarketBoardPurchaseHandler purchaseHandler); } } diff --git a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisItemListingDeleteRequest.cs b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisItemListingDeleteRequest.cs new file mode 100644 index 000000000..f6994e60f --- /dev/null +++ b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisItemListingDeleteRequest.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; + +namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis +{ + /// + /// Request payload for market board purchases. + /// + public class UniversalisItemListingDeleteRequest + { + /// + /// Gets or sets the object ID of the retainer associated with the sale. + /// + [JsonProperty("retainerID")] + public string RetainerId { get; set; } + + /// + /// Gets or sets the object ID of the item listing. + /// + [JsonProperty("listingID")] + public string ListingId { get; set; } + + /// + /// Gets or sets the quantity of the item that was purchased. + /// + [JsonProperty("quantity")] + public uint Quantity { get; set; } + + /// + /// Gets or sets the unit price of the item. + /// + [JsonProperty("pricePerUnit")] + public uint PricePerUnit { get; set; } + + /// + /// Gets or sets the uploader ID. + /// + [JsonProperty("uploaderID")] + public string UploaderId { get; set; } + } +} diff --git a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs index cffa73491..58c7810ad 100644 --- a/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs +++ b/Dalamud/Game/Network/MarketBoardUploaders/Universalis/UniversalisMarketBoardUploader.cs @@ -41,12 +41,14 @@ namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders Log.Verbose("Starting Universalis upload."); var uploader = this.dalamud.ClientState.LocalContentId; - var listingsRequestObject = new UniversalisItemListingsUploadRequest(); - listingsRequestObject.WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0; - listingsRequestObject.UploaderId = uploader.ToString(); - listingsRequestObject.ItemId = request.CatalogId; + var listingsRequestObject = new UniversalisItemListingsUploadRequest + { + WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0, + UploaderId = uploader.ToString(), + ItemId = request.CatalogId, + Listings = new List(), + }; - listingsRequestObject.Listings = new List(); foreach (var marketBoardItemListing in request.Listings) { var universalisListing = new UniversalisItemListingsEntry @@ -62,9 +64,9 @@ namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders PricePerUnit = marketBoardItemListing.PricePerUnit, Quantity = marketBoardItemListing.ItemQuantity, RetainerCity = marketBoardItemListing.RetainerCityId, + Materia = new List(), }; - universalisListing.Materia = new List(); foreach (var itemMateria in marketBoardItemListing.Materia) { universalisListing.Materia.Add(new UniversalisItemMateria @@ -78,15 +80,17 @@ namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders } var upload = JsonConvert.SerializeObject(listingsRequestObject); - client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", upload); Log.Verbose(upload); + client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", upload); - var historyRequestObject = new UniversalisHistoryUploadRequest(); - historyRequestObject.WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0; - historyRequestObject.UploaderId = uploader.ToString(); - historyRequestObject.ItemId = request.CatalogId; + var historyRequestObject = new UniversalisHistoryUploadRequest + { + WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0, + UploaderId = uploader.ToString(), + ItemId = request.CatalogId, + Entries = new List(), + }; - historyRequestObject.Entries = new List(); foreach (var marketBoardHistoryListing in request.History) { historyRequestObject.Entries.Add(new UniversalisHistoryEntry @@ -103,8 +107,8 @@ namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders client.Headers.Add(HttpRequestHeader.ContentType, "application/json"); var historyUpload = JsonConvert.SerializeObject(historyRequestObject); - client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", historyUpload); Log.Verbose(historyUpload); + client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", historyUpload); Log.Verbose("Universalis data upload for item#{0} completed.", request.CatalogId); } @@ -114,27 +118,56 @@ namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders { using var client = new WebClient(); - var taxRatesRequest = new UniversalisTaxUploadRequest(); - taxRatesRequest.WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0; - taxRatesRequest.UploaderId = this.dalamud.ClientState.LocalContentId.ToString(); - - taxRatesRequest.TaxData = new UniversalisTaxData + var taxRatesRequest = new UniversalisTaxUploadRequest { - LimsaLominsa = taxRates.LimsaLominsaTax, - Gridania = taxRates.GridaniaTax, - Uldah = taxRates.UldahTax, - Ishgard = taxRates.IshgardTax, - Kugane = taxRates.KuganeTax, - Crystarium = taxRates.CrystariumTax, + WorldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0, + UploaderId = this.dalamud.ClientState.LocalContentId.ToString(), + TaxData = new UniversalisTaxData + { + LimsaLominsa = taxRates.LimsaLominsaTax, + Gridania = taxRates.GridaniaTax, + Uldah = taxRates.UldahTax, + Ishgard = taxRates.IshgardTax, + Kugane = taxRates.KuganeTax, + Crystarium = taxRates.CrystariumTax, + }, }; client.Headers.Add(HttpRequestHeader.ContentType, "application/json"); var historyUpload = JsonConvert.SerializeObject(taxRatesRequest); - client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", historyUpload); Log.Verbose(historyUpload); + client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", historyUpload); Log.Verbose("Universalis tax upload completed."); } + + /// + public void UploadPurchase(MarketBoardPurchaseHandler purchaseHandler) + { + using var client = new WebClient(); + client.Headers.Add(HttpRequestHeader.ContentType, "application/json"); + client.Headers.Add(HttpRequestHeader.Authorization, ApiKey); + + var itemId = purchaseHandler.CatalogId; + var worldId = this.dalamud.ClientState.LocalPlayer?.CurrentWorld.Id ?? 0; + + var purchaseRequest = new UniversalisItemListingDeleteRequest + { + PricePerUnit = purchaseHandler.PricePerUnit, + Quantity = purchaseHandler.ItemQuantity, + ListingId = purchaseHandler.ListingId.ToString(), + RetainerId = purchaseHandler.RetainerId.ToString(), + UploaderId = this.dalamud.ClientState.LocalContentId.ToString(), + }; + + var requestPath = ApiBase + $"/api/{worldId}/{itemId}/delete"; + var purchaseUpload = JsonConvert.SerializeObject(purchaseRequest); + Log.Verbose($"Making request to {requestPath}"); + Log.Verbose(purchaseUpload); + client.UploadString(requestPath, "POST", purchaseUpload); + + Log.Verbose("Universalis purchase upload completed."); + } } } diff --git a/Dalamud/Game/Network/NetworkHandlers.cs b/Dalamud/Game/Network/NetworkHandlers.cs index d5fa25adb..0b7aa7694 100644 --- a/Dalamud/Game/Network/NetworkHandlers.cs +++ b/Dalamud/Game/Network/NetworkHandlers.cs @@ -15,9 +15,9 @@ using Serilog; namespace Dalamud.Game.Network { /// - /// This class handles network notifications and uploading Marketboard data. + /// This class handles network notifications and uploading market board data. /// - public class NetworkHandlers + internal class NetworkHandlers { private readonly Dalamud dalamud; @@ -26,12 +26,14 @@ namespace Dalamud.Game.Network private readonly bool optOutMbUploads; private readonly IMarketBoardUploader uploader; + private MarketBoardPurchaseHandler marketBoardPurchaseHandler; + /// /// Initializes a new instance of the class. /// /// The Dalamud instance. - /// Whether the client should opt out of marketboard uploads. - internal NetworkHandlers(Dalamud dalamud, bool optOutMbUploads) + /// Whether the client should opt out of market board uploads. + public NetworkHandlers(Dalamud dalamud, bool optOutMbUploads) { this.dalamud = dalamud; this.optOutMbUploads = optOutMbUploads; @@ -48,12 +50,22 @@ namespace Dalamud.Game.Network private void OnNetworkMessage(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction) { - if (direction != NetworkMessageDirection.ZoneDown) - return; - if (!this.dalamud.Data.IsDataReady) return; + if (direction == NetworkMessageDirection.ZoneUp) + { + if (!this.optOutMbUploads) + { + if (opCode == this.dalamud.Data.ClientOpCodes["MarketBoardPurchaseHandler"]) + { + this.marketBoardPurchaseHandler = MarketBoardPurchaseHandler.Read(dataPtr); + } + } + + return; + } + if (opCode == this.dalamud.Data.ServerOpCodes["CfNotifyPop"]) { var data = new byte[64]; @@ -256,6 +268,26 @@ namespace Dalamud.Game.Network Log.Error(ex, "Market Board data upload failed."); } } + + if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardPurchase"]) + { + if (this.marketBoardPurchaseHandler == null) + return; + + var purchase = MarketBoardPurchase.Read(dataPtr); + + // Transaction succeeded + if (purchase.ItemQuantity == this.marketBoardPurchaseHandler.ItemQuantity + && (purchase.CatalogId == this.marketBoardPurchaseHandler.CatalogId + || purchase.CatalogId == this.marketBoardPurchaseHandler.CatalogId + 1000000)) + { // HQ + Log.Information("Bought " + purchase.ItemQuantity + "x " + this.marketBoardPurchaseHandler.CatalogId + " for " + (this.marketBoardPurchaseHandler.PricePerUnit * purchase.ItemQuantity) + " gils, listing id is " + this.marketBoardPurchaseHandler.ListingId); + var handler = this.marketBoardPurchaseHandler; // Capture the object so that we don't pass in a null one when the task starts. + Task.Run(() => this.uploader.UploadPurchase(handler)); + } + + this.marketBoardPurchaseHandler = null; + } } } } diff --git a/Dalamud/Game/Network/Structures/MarketBoardPurchase.cs b/Dalamud/Game/Network/Structures/MarketBoardPurchase.cs new file mode 100644 index 000000000..ed8051154 --- /dev/null +++ b/Dalamud/Game/Network/Structures/MarketBoardPurchase.cs @@ -0,0 +1,40 @@ +using System; +using System.IO; + +namespace Dalamud.Game.Network.Structures +{ + /// + /// Represents market board purchase information. This message is received from the + /// server when a purchase is made at a market board. + /// + internal class MarketBoardPurchase + { + /// + /// Gets the item ID of the item that was purchased. + /// + public uint CatalogId { get; private set; } + + /// + /// Gets the quantity of the item that was purchased. + /// + public uint ItemQuantity { get; private set; } + + /// + /// Reads market board purchase information from the struct at the provided pointer. + /// + /// A pointer to a struct containing market board purchase information from the server. + /// An object representing the data read. + public static unsafe MarketBoardPurchase Read(IntPtr dataPtr) + { + var output = new MarketBoardPurchase(); + + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); + output.CatalogId = reader.ReadUInt32(); + stream.Position += 4; + output.ItemQuantity = reader.ReadUInt32(); + + return output; + } + } +} diff --git a/Dalamud/Game/Network/Structures/MarketBoardPurchaseHandler.cs b/Dalamud/Game/Network/Structures/MarketBoardPurchaseHandler.cs new file mode 100644 index 000000000..98d7007b0 --- /dev/null +++ b/Dalamud/Game/Network/Structures/MarketBoardPurchaseHandler.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; + +namespace Dalamud.Game.Network.Structures +{ + /// + /// Represents market board purchase information. This message is sent from the + /// client when a purchase is made at a market board. + /// + internal class MarketBoardPurchaseHandler + { + /// + /// Gets the object ID of the retainer associated with the sale. + /// + public ulong RetainerId { get; private set; } + + /// + /// Gets the object ID of the item listing. + /// + public ulong ListingId { get; private set; } + + /// + /// Gets the item ID of the item that was purchased. + /// + public uint CatalogId { get; private set; } + + /// + /// Gets the quantity of the item that was purchased. + /// + public uint ItemQuantity { get; private set; } + + /// + /// Gets the unit price of the item. + /// + public uint PricePerUnit { get; private set; } + + /// + /// Reads market board purchase information from the struct at the provided pointer. + /// + /// A pointer to a struct containing market board purchase information from the client. + /// An object representing the data read. + public static unsafe MarketBoardPurchaseHandler Read(IntPtr dataPtr) + { + var output = new MarketBoardPurchaseHandler(); + + using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544); + using var reader = new BinaryReader(stream); + output.RetainerId = reader.ReadUInt64(); + output.ListingId = reader.ReadUInt64(); + output.CatalogId = reader.ReadUInt32(); + output.ItemQuantity = reader.ReadUInt32(); + output.PricePerUnit = reader.ReadUInt32(); + + return output; + } + } +} diff --git a/build/build.csproj.DotSettings b/build/build.csproj.DotSettings index c8947fcec..7bc28484c 100644 --- a/build/build.csproj.DotSettings +++ b/build/build.csproj.DotSettings @@ -1,4 +1,4 @@ - + DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW @@ -20,6 +20,7 @@ True True True + True True True True