mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 10:17:22 +01:00
Add MarketBoard service and associated interfaces, test and data widget (#1822)
* Add MarketBoard service and associated interfaces, test and data widget * Dispose of events properly * Make listings readonly lists + provide internal list for internal use * Rename CatalogId to ItemId on interfaces, have kept CatalogId internally as it's technically correct * Removed RetainerOwnerId from the public interface * Removed NextCatalogId from the public interface * Updated test text * Null events in scoped service disposal
This commit is contained in:
parent
a35ae5fdf3
commit
e160746d42
12 changed files with 1131 additions and 27 deletions
165
Dalamud/Game/Marketboard/MarketBoard.cs
Normal file
165
Dalamud/Game/Marketboard/MarketBoard.cs
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
using Dalamud.IoC;
|
||||
using Dalamud.IoC.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
|
||||
namespace Dalamud.Game.MarketBoard;
|
||||
|
||||
using Network.Internal;
|
||||
using Network.Structures;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides access to market board events
|
||||
/// </summary>
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.EarlyLoadedService]
|
||||
internal class MarketBoard : IInternalDisposableService, IMarketBoard
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly NetworkHandlers networkHandlers = Service<NetworkHandlers>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MarketBoard"/> class.
|
||||
/// </summary>
|
||||
[ServiceManager.ServiceConstructor]
|
||||
public MarketBoard()
|
||||
{
|
||||
this.networkHandlers.MbHistoryObservable.Subscribe(this.OnMbHistory);
|
||||
this.networkHandlers.MbPurchaseObservable.Subscribe(this.OnPurchase);
|
||||
this.networkHandlers.MbOfferingsObservable.Subscribe(this.OnOfferings);
|
||||
this.networkHandlers.MbPurchaseSentObservable.Subscribe(this.OnPurchaseSent);
|
||||
this.networkHandlers.MbTaxesObservable.Subscribe(this.OnTaxRates);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IMarketBoard.HistoryReceivedDelegate? HistoryReceived;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IMarketBoard.ItemPurchasedDelegate? ItemPurchased;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IMarketBoard.OfferingsReceivedDelegate? OfferingsReceived;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IMarketBoard.PurchaseRequestedDelegate? PurchaseRequested;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IMarketBoard.TaxRatesReceivedDelegate? TaxRatesReceived;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void DisposeService()
|
||||
{
|
||||
this.HistoryReceived = null;
|
||||
this.ItemPurchased = null;
|
||||
this.OfferingsReceived = null;
|
||||
this.PurchaseRequested = null;
|
||||
this.TaxRatesReceived = null;
|
||||
}
|
||||
|
||||
private void OnMbHistory(MarketBoardHistory marketBoardHistory)
|
||||
{
|
||||
this.HistoryReceived?.Invoke(marketBoardHistory);
|
||||
}
|
||||
|
||||
private void OnPurchase(MarketBoardPurchase marketBoardHistory)
|
||||
{
|
||||
this.ItemPurchased?.Invoke(marketBoardHistory);
|
||||
}
|
||||
|
||||
private void OnOfferings(MarketBoardCurrentOfferings currentOfferings)
|
||||
{
|
||||
this.OfferingsReceived?.Invoke(currentOfferings);
|
||||
}
|
||||
|
||||
private void OnPurchaseSent(MarketBoardPurchaseHandler purchaseHandler)
|
||||
{
|
||||
this.PurchaseRequested?.Invoke(purchaseHandler);
|
||||
}
|
||||
|
||||
private void OnTaxRates(MarketTaxRates taxRates)
|
||||
{
|
||||
this.TaxRatesReceived?.Invoke(taxRates);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Plugin scoped version of MarketBoard.
|
||||
/// </summary>
|
||||
[PluginInterface]
|
||||
[InterfaceVersion("1.0")]
|
||||
[ServiceManager.ScopedService]
|
||||
#pragma warning disable SA1015
|
||||
[ResolveVia<IMarketBoard>]
|
||||
#pragma warning restore SA1015
|
||||
internal class MarketBoardPluginScoped : IInternalDisposableService, IMarketBoard
|
||||
{
|
||||
[ServiceManager.ServiceDependency]
|
||||
private readonly MarketBoard marketBoardService = Service<MarketBoard>.Get();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MarketBoardPluginScoped"/> class.
|
||||
/// </summary>
|
||||
internal MarketBoardPluginScoped()
|
||||
{
|
||||
this.marketBoardService.HistoryReceived += this.OnHistoryReceived;
|
||||
this.marketBoardService.ItemPurchased += this.OnItemPurchased;
|
||||
this.marketBoardService.OfferingsReceived += this.OnOfferingsReceived;
|
||||
this.marketBoardService.PurchaseRequested += this.OnPurchaseRequested;
|
||||
this.marketBoardService.TaxRatesReceived += this.OnTaxRatesReceived;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IMarketBoard.HistoryReceivedDelegate? HistoryReceived;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IMarketBoard.ItemPurchasedDelegate? ItemPurchased;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IMarketBoard.OfferingsReceivedDelegate? OfferingsReceived;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IMarketBoard.PurchaseRequestedDelegate? PurchaseRequested;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public event IMarketBoard.TaxRatesReceivedDelegate? TaxRatesReceived;
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IInternalDisposableService.DisposeService()
|
||||
{
|
||||
this.marketBoardService.HistoryReceived -= this.OnHistoryReceived;
|
||||
this.marketBoardService.ItemPurchased -= this.OnItemPurchased;
|
||||
this.marketBoardService.OfferingsReceived -= this.OnOfferingsReceived;
|
||||
this.marketBoardService.PurchaseRequested -= this.OnPurchaseRequested;
|
||||
this.marketBoardService.TaxRatesReceived -= this.OnTaxRatesReceived;
|
||||
|
||||
this.HistoryReceived = null;
|
||||
this.ItemPurchased = null;
|
||||
this.OfferingsReceived = null;
|
||||
this.PurchaseRequested = null;
|
||||
this.TaxRatesReceived = null;
|
||||
}
|
||||
|
||||
private void OnHistoryReceived(IMarketBoardHistory history)
|
||||
{
|
||||
this.HistoryReceived?.Invoke(history);
|
||||
}
|
||||
|
||||
private void OnItemPurchased(IMarketBoardPurchase purchase)
|
||||
{
|
||||
this.ItemPurchased?.Invoke(purchase);
|
||||
}
|
||||
|
||||
private void OnOfferingsReceived(IMarketBoardCurrentOfferings currentOfferings)
|
||||
{
|
||||
this.OfferingsReceived?.Invoke(currentOfferings);
|
||||
}
|
||||
|
||||
private void OnPurchaseRequested(IMarketBoardPurchaseHandler purchaseHandler)
|
||||
{
|
||||
this.PurchaseRequested?.Invoke(purchaseHandler);
|
||||
}
|
||||
|
||||
private void OnTaxRatesReceived(IMarketTaxRates taxRates)
|
||||
{
|
||||
this.TaxRatesReceived?.Invoke(taxRates);
|
||||
}
|
||||
}
|
||||
|
|
@ -209,6 +209,18 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
|
||||
private event Action<nint>? MarketBoardPurchaseRequestSent;
|
||||
|
||||
public IObservable<MarketBoardPurchase> MbPurchaseObservable => this.mbPurchaseObservable;
|
||||
|
||||
public IObservable<MarketBoardHistory> MbHistoryObservable => this.mbHistoryObservable;
|
||||
|
||||
public IObservable<MarketTaxRates> MbTaxesObservable => this.mbTaxesObservable;
|
||||
|
||||
public IObservable<MarketBoardItemRequest> MbItemRequestObservable => this.mbItemRequestObservable;
|
||||
|
||||
public IObservable<MarketBoardCurrentOfferings> MbOfferingsObservable => this.mbOfferingsObservable;
|
||||
|
||||
public IObservable<MarketBoardPurchaseHandler> MbPurchaseSentObservable => this.mbPurchaseSentObservable;
|
||||
|
||||
/// <summary>
|
||||
/// Disposes of managed and unmanaged resources.
|
||||
/// </summary>
|
||||
|
|
@ -301,7 +313,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
private IObservable<List<MarketBoardCurrentOfferings.MarketBoardItemListing>> OnMarketBoardListingsBatch(
|
||||
IObservable<MarketBoardItemRequest> start)
|
||||
{
|
||||
var offeringsObservable = this.mbOfferingsObservable.Publish().RefCount();
|
||||
var offeringsObservable = this.MbOfferingsObservable.Publish().RefCount();
|
||||
|
||||
void LogEndObserved(MarketBoardCurrentOfferings offerings)
|
||||
{
|
||||
|
|
@ -315,7 +327,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
Log.Verbose(
|
||||
"Observed element of request {RequestId} with {NumListings} listings",
|
||||
offerings.RequestId,
|
||||
offerings.ItemListings.Count);
|
||||
offerings.InternalItemListings.Count);
|
||||
}
|
||||
|
||||
IObservable<MarketBoardCurrentOfferings> UntilBatchEnd(MarketBoardItemRequest request)
|
||||
|
|
@ -327,7 +339,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
}
|
||||
|
||||
return offeringsObservable
|
||||
.Where(offerings => offerings.ItemListings.All(l => l.CatalogId == request.CatalogId))
|
||||
.Where(offerings => offerings.InternalItemListings.All(l => l.CatalogId == request.CatalogId))
|
||||
.Skip(totalPackets - 1)
|
||||
.Do(LogEndObserved);
|
||||
}
|
||||
|
|
@ -343,7 +355,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
new List<MarketBoardCurrentOfferings.MarketBoardItemListing>(),
|
||||
(agg, next) =>
|
||||
{
|
||||
agg.AddRange(next.ItemListings);
|
||||
agg.AddRange(next.InternalItemListings);
|
||||
return agg;
|
||||
}));
|
||||
}
|
||||
|
|
@ -351,14 +363,14 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
private IObservable<List<MarketBoardHistory.MarketBoardHistoryListing>> OnMarketBoardSalesBatch(
|
||||
IObservable<MarketBoardItemRequest> start)
|
||||
{
|
||||
var historyObservable = this.mbHistoryObservable.Publish().RefCount();
|
||||
var historyObservable = this.MbHistoryObservable.Publish().RefCount();
|
||||
|
||||
void LogHistoryObserved(MarketBoardHistory history)
|
||||
{
|
||||
Log.Verbose(
|
||||
"Observed history for item {CatalogId} with {NumSales} sales",
|
||||
history.CatalogId,
|
||||
history.HistoryListings.Count);
|
||||
history.InternalHistoryListings.Count);
|
||||
}
|
||||
|
||||
IObservable<MarketBoardHistory> UntilBatchEnd(MarketBoardItemRequest request)
|
||||
|
|
@ -379,7 +391,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
new List<MarketBoardHistory.MarketBoardHistoryListing>(),
|
||||
(agg, next) =>
|
||||
{
|
||||
agg.AddRange(next.HistoryListings);
|
||||
agg.AddRange(next.InternalHistoryListings);
|
||||
return agg;
|
||||
}));
|
||||
}
|
||||
|
|
@ -394,7 +406,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
request.AmountToArrive);
|
||||
}
|
||||
|
||||
var startObservable = this.mbItemRequestObservable
|
||||
var startObservable = this.MbItemRequestObservable
|
||||
.Where(request => request.Ok).Do(LogStartObserved)
|
||||
.Publish()
|
||||
.RefCount();
|
||||
|
|
@ -450,7 +462,7 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
|
||||
private IDisposable HandleMarketTaxRates()
|
||||
{
|
||||
return this.mbTaxesObservable
|
||||
return this.MbTaxesObservable
|
||||
.Where(this.ShouldUpload)
|
||||
.SubscribeOn(ThreadPoolScheduler.Instance)
|
||||
.Subscribe(
|
||||
|
|
@ -476,8 +488,8 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
|
||||
private IDisposable HandleMarketBoardPurchaseHandler()
|
||||
{
|
||||
return this.mbPurchaseSentObservable
|
||||
.Zip(this.mbPurchaseObservable)
|
||||
return this.MbPurchaseSentObservable
|
||||
.Zip(this.MbPurchaseObservable)
|
||||
.Where(this.ShouldUpload)
|
||||
.SubscribeOn(ThreadPoolScheduler.Instance)
|
||||
.Subscribe(
|
||||
|
|
@ -544,7 +556,8 @@ internal unsafe class NetworkHandlers : IInternalDisposableService
|
|||
{
|
||||
try
|
||||
{
|
||||
if (eventId == 7 && responseId == 8)
|
||||
// Event ID 0 covers the crystarium, 7 covers all other cities
|
||||
if (eventId is 7 or 0 && responseId == 8)
|
||||
this.MarketBoardTaxesReceived?.InvokeSafely((nint)args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace Dalamud.Game.Network.Structures;
|
|||
/// <summary>
|
||||
/// This class represents the current market board offerings from a game network packet.
|
||||
/// </summary>
|
||||
public class MarketBoardCurrentOfferings
|
||||
public class MarketBoardCurrentOfferings : IMarketBoardCurrentOfferings
|
||||
{
|
||||
private MarketBoardCurrentOfferings()
|
||||
{
|
||||
|
|
@ -16,7 +16,10 @@ public class MarketBoardCurrentOfferings
|
|||
/// <summary>
|
||||
/// Gets the list of individual item listings.
|
||||
/// </summary>
|
||||
public List<MarketBoardItemListing> ItemListings { get; } = new();
|
||||
IReadOnlyList<IMarketBoardItemListing> IMarketBoardCurrentOfferings.ItemListings => this.InternalItemListings;
|
||||
|
||||
internal List<MarketBoardItemListing> InternalItemListings { get; set; } = new List<MarketBoardItemListing>();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the listing end index.
|
||||
|
|
@ -45,6 +48,8 @@ public class MarketBoardCurrentOfferings
|
|||
using var stream = new UnmanagedMemoryStream((byte*)dataPtr.ToPointer(), 1544);
|
||||
using var reader = new BinaryReader(stream);
|
||||
|
||||
var listings = new List<MarketBoardItemListing>();
|
||||
|
||||
for (var i = 0; i < 10; i++)
|
||||
{
|
||||
var listingEntry = new MarketBoardItemListing();
|
||||
|
|
@ -64,6 +69,8 @@ public class MarketBoardCurrentOfferings
|
|||
reader.ReadUInt16(); // durability
|
||||
reader.ReadUInt16(); // spiritbond
|
||||
|
||||
var materiaList = new List<IItemMateria>();
|
||||
|
||||
for (var materiaIndex = 0; materiaIndex < 5; materiaIndex++)
|
||||
{
|
||||
var materiaVal = reader.ReadUInt16();
|
||||
|
|
@ -74,9 +81,11 @@ public class MarketBoardCurrentOfferings
|
|||
};
|
||||
|
||||
if (materiaEntry.MateriaId != 0)
|
||||
listingEntry.Materia.Add(materiaEntry);
|
||||
materiaList.Add(materiaEntry);
|
||||
}
|
||||
|
||||
listingEntry.Materia = materiaList;
|
||||
|
||||
reader.ReadUInt16();
|
||||
reader.ReadUInt32();
|
||||
|
||||
|
|
@ -92,9 +101,10 @@ public class MarketBoardCurrentOfferings
|
|||
reader.ReadUInt32();
|
||||
|
||||
if (listingEntry.CatalogId != 0)
|
||||
output.ItemListings.Add(listingEntry);
|
||||
listings.Add(listingEntry);
|
||||
}
|
||||
|
||||
output.InternalItemListings = listings;
|
||||
output.ListingIndexEnd = reader.ReadByte();
|
||||
output.ListingIndexStart = reader.ReadByte();
|
||||
output.RequestId = reader.ReadUInt16();
|
||||
|
|
@ -105,7 +115,7 @@ public class MarketBoardCurrentOfferings
|
|||
/// <summary>
|
||||
/// This class represents the current market board offering of a single item from the <see cref="MarketBoardCurrentOfferings"/> network packet.
|
||||
/// </summary>
|
||||
public class MarketBoardItemListing
|
||||
public class MarketBoardItemListing : IMarketBoardItemListing
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MarketBoardItemListing"/> class.
|
||||
|
|
@ -119,6 +129,9 @@ public class MarketBoardCurrentOfferings
|
|||
/// </summary>
|
||||
public ulong ArtisanId { get; internal set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public uint ItemId => this.CatalogId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the catalog ID.
|
||||
/// </summary>
|
||||
|
|
@ -147,7 +160,7 @@ public class MarketBoardCurrentOfferings
|
|||
/// <summary>
|
||||
/// Gets the list of materia attached to this item.
|
||||
/// </summary>
|
||||
public List<ItemMateria> Materia { get; } = new();
|
||||
public IReadOnlyList<IItemMateria> Materia { get; internal set; } = new List<IItemMateria>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of attached materia.
|
||||
|
|
@ -202,7 +215,7 @@ public class MarketBoardCurrentOfferings
|
|||
/// <summary>
|
||||
/// This represents the materia slotted to an <see cref="MarketBoardItemListing"/>.
|
||||
/// </summary>
|
||||
public class ItemMateria
|
||||
public class ItemMateria : IItemMateria
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ItemMateria"/> class.
|
||||
|
|
@ -223,3 +236,132 @@ public class MarketBoardCurrentOfferings
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An interface that represents the current market board offerings.
|
||||
/// </summary>
|
||||
public interface IMarketBoardCurrentOfferings
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the list of individual item listings.
|
||||
/// </summary>
|
||||
IReadOnlyList<IMarketBoardItemListing> ItemListings { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the listing end index.
|
||||
/// </summary>
|
||||
int ListingIndexEnd { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the listing start index.
|
||||
/// </summary>
|
||||
int ListingIndexStart { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the request ID.
|
||||
/// </summary>
|
||||
int RequestId { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An interface that represents the current market board offering of a single item from the <see cref="IMarketBoardCurrentOfferings"/>.
|
||||
/// </summary>
|
||||
public interface IMarketBoardItemListing
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the artisan ID.
|
||||
/// </summary>
|
||||
ulong ArtisanId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item ID.
|
||||
/// </summary>
|
||||
uint ItemId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the item is HQ.
|
||||
/// </summary>
|
||||
bool IsHq { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item quantity.
|
||||
/// </summary>
|
||||
uint ItemQuantity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time this offering was last reviewed.
|
||||
/// </summary>
|
||||
DateTime LastReviewTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the listing ID.
|
||||
/// </summary>
|
||||
ulong ListingId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of materia attached to this item.
|
||||
/// </summary>
|
||||
IReadOnlyList<IItemMateria> Materia { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the amount of attached materia.
|
||||
/// </summary>
|
||||
int MateriaCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this item is on a mannequin.
|
||||
/// </summary>
|
||||
bool OnMannequin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player name.
|
||||
/// </summary>
|
||||
string PlayerName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the price per unit.
|
||||
/// </summary>
|
||||
uint PricePerUnit { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the city ID of the retainer selling the item.
|
||||
/// </summary>
|
||||
int RetainerCityId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ID of the retainer selling the item.
|
||||
/// </summary>
|
||||
ulong RetainerId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the retainer.
|
||||
/// </summary>
|
||||
string RetainerName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the stain or applied dye of the item.
|
||||
/// </summary>
|
||||
int StainId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total tax.
|
||||
/// </summary>
|
||||
uint TotalTax { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An interface that represents the materia slotted to an <see cref="IMarketBoardItemListing"/>.
|
||||
/// </summary>
|
||||
public interface IItemMateria
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the materia index.
|
||||
/// </summary>
|
||||
int Index { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the materia ID.
|
||||
/// </summary>
|
||||
int MateriaId { get; }
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ namespace Dalamud.Game.Network.Structures;
|
|||
/// <summary>
|
||||
/// This class represents the market board history from a game network packet.
|
||||
/// </summary>
|
||||
public class MarketBoardHistory
|
||||
public class MarketBoardHistory : IMarketBoardHistory
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MarketBoardHistory"/> class.
|
||||
|
|
@ -26,10 +26,17 @@ public class MarketBoardHistory
|
|||
/// </summary>
|
||||
public uint CatalogId2 { get; private set; }
|
||||
|
||||
public uint ItemId => this.CatalogId;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of individual item history listings.
|
||||
/// Gets the list of individual item listings.
|
||||
/// </summary>
|
||||
public List<MarketBoardHistoryListing> HistoryListings { get; } = new();
|
||||
IReadOnlyList<IMarketBoardHistoryListing> IMarketBoardHistory.HistoryListings => this.InternalHistoryListings;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a list of individual item listings.
|
||||
/// </summary>
|
||||
internal List<MarketBoardHistoryListing> InternalHistoryListings { get; set; } = new List<MarketBoardHistoryListing>();
|
||||
|
||||
/// <summary>
|
||||
/// Read a <see cref="MarketBoardHistory"/> object from memory.
|
||||
|
|
@ -53,6 +60,7 @@ public class MarketBoardHistory
|
|||
return output;
|
||||
}
|
||||
|
||||
var historyListings = new List<MarketBoardHistoryListing>();
|
||||
for (var i = 0; i < 20; i++)
|
||||
{
|
||||
var listingEntry = new MarketBoardHistoryListing
|
||||
|
|
@ -69,19 +77,21 @@ public class MarketBoardHistory
|
|||
listingEntry.BuyerName = Encoding.UTF8.GetString(reader.ReadBytes(33)).TrimEnd('\u0000');
|
||||
listingEntry.NextCatalogId = reader.ReadUInt32();
|
||||
|
||||
output.HistoryListings.Add(listingEntry);
|
||||
historyListings.Add(listingEntry);
|
||||
|
||||
if (listingEntry.NextCatalogId == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
output.InternalHistoryListings = historyListings;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class represents the market board history of a single item from the <see cref="MarketBoardHistory"/> network packet.
|
||||
/// </summary>
|
||||
public class MarketBoardHistoryListing
|
||||
public class MarketBoardHistoryListing : IMarketBoardHistoryListing
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MarketBoardHistoryListing"/> class.
|
||||
|
|
@ -126,3 +136,55 @@ public class MarketBoardHistory
|
|||
public uint SalePrice { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An interface that represents the market board history from the game.
|
||||
/// </summary>
|
||||
public interface IMarketBoardHistory
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the item ID.
|
||||
/// </summary>
|
||||
uint ItemId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of individual item history listings.
|
||||
/// </summary>
|
||||
IReadOnlyList<IMarketBoardHistoryListing> HistoryListings { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An interface that represents the market board history of a single item from <see cref="IMarketBoardHistory"/>.
|
||||
/// </summary>
|
||||
public interface IMarketBoardHistoryListing
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the buyer's name.
|
||||
/// </summary>
|
||||
string BuyerName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the item is HQ.
|
||||
/// </summary>
|
||||
bool IsHq { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the item is on a mannequin.
|
||||
/// </summary>
|
||||
bool OnMannequin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the time of purchase.
|
||||
/// </summary>
|
||||
DateTime PurchaseTime { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the quantity.
|
||||
/// </summary>
|
||||
uint Quantity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sale price.
|
||||
/// </summary>
|
||||
uint SalePrice { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ 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.
|
||||
/// </summary>
|
||||
public class MarketBoardPurchase
|
||||
public class MarketBoardPurchase : IMarketBoardPurchase
|
||||
{
|
||||
private MarketBoardPurchase()
|
||||
{
|
||||
|
|
@ -41,3 +41,20 @@ public class MarketBoardPurchase
|
|||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An interface that represents market board purchase information. This message is received from the
|
||||
/// server when a purchase is made at a market board.
|
||||
/// </summary>
|
||||
public interface IMarketBoardPurchase
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the item ID of the item that was purchased.
|
||||
/// </summary>
|
||||
uint CatalogId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the quantity of the item that was purchased.
|
||||
/// </summary>
|
||||
uint ItemQuantity { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ 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.
|
||||
/// </summary>
|
||||
public class MarketBoardPurchaseHandler
|
||||
public class MarketBoardPurchaseHandler : IMarketBoardPurchaseHandler
|
||||
{
|
||||
private MarketBoardPurchaseHandler()
|
||||
{
|
||||
|
|
@ -59,3 +59,35 @@ public class MarketBoardPurchaseHandler
|
|||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An interface that represents market board purchase information. This message is sent from the
|
||||
/// client when a purchase is made at a market board.
|
||||
/// </summary>
|
||||
public interface IMarketBoardPurchaseHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the object ID of the retainer associated with the sale.
|
||||
/// </summary>
|
||||
ulong RetainerId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object ID of the item listing.
|
||||
/// </summary>
|
||||
ulong ListingId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the item ID of the item that was purchased.
|
||||
/// </summary>
|
||||
uint CatalogId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the quantity of the item that was purchased.
|
||||
/// </summary>
|
||||
uint ItemQuantity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unit price of the item.
|
||||
/// </summary>
|
||||
uint PricePerUnit { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ namespace Dalamud.Game.Network.Structures;
|
|||
/// This class represents the "Result Dialog" packet. This is also used e.g. for reduction results, but we only care about tax rates.
|
||||
/// We can do that by checking the "Category" field.
|
||||
/// </summary>
|
||||
public class MarketTaxRates
|
||||
public class MarketTaxRates : IMarketTaxRates
|
||||
{
|
||||
private MarketTaxRates()
|
||||
{
|
||||
|
|
@ -100,3 +100,49 @@ public class MarketTaxRates
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An interface that represents the tax rates received by the client when interacting with a retainer vocate.
|
||||
/// </summary>
|
||||
public interface IMarketTaxRates
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the category of this ResultDialog packet.
|
||||
/// </summary>
|
||||
uint Category { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tax rate in Limsa Lominsa.
|
||||
/// </summary>
|
||||
uint LimsaLominsaTax { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tax rate in Gridania.
|
||||
/// </summary>
|
||||
uint GridaniaTax { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tax rate in Ul'dah.
|
||||
/// </summary>
|
||||
uint UldahTax { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tax rate in Ishgard.
|
||||
/// </summary>
|
||||
uint IshgardTax { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tax rate in Kugane.
|
||||
/// </summary>
|
||||
uint KuganeTax { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tax rate in the Crystarium.
|
||||
/// </summary>
|
||||
uint CrystariumTax { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tax rate in the Crystarium.
|
||||
/// </summary>
|
||||
uint SharlayanTax { get; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ internal class DataWindow : Window, IDisposable
|
|||
new IconBrowserWidget(),
|
||||
new ImGuiWidget(),
|
||||
new KeyStateWidget(),
|
||||
new MarketBoardWidget(),
|
||||
new NetworkMonitorWidget(),
|
||||
new ObjectTableWidget(),
|
||||
new PartyListWidget(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,302 @@
|
|||
using System.Collections.Concurrent;
|
||||
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
using Game.MarketBoard;
|
||||
using Game.Network.Structures;
|
||||
|
||||
/// <summary>
|
||||
/// Widget to display market board events.
|
||||
/// </summary>
|
||||
internal class MarketBoardWidget : IDataWindowWidget
|
||||
{
|
||||
private readonly ConcurrentQueue<(IMarketBoardHistory MarketBoardHistory, IMarketBoardHistoryListing Listing)> marketBoardHistoryQueue = new();
|
||||
private readonly ConcurrentQueue<(IMarketBoardCurrentOfferings MarketBoardCurrentOfferings, IMarketBoardItemListing Listing)> marketBoardCurrentOfferingsQueue = new();
|
||||
private readonly ConcurrentQueue<IMarketBoardPurchase> marketBoardPurchasesQueue = new();
|
||||
private readonly ConcurrentQueue<IMarketBoardPurchaseHandler> marketBoardPurchaseRequestsQueue = new();
|
||||
private readonly ConcurrentQueue<IMarketTaxRates> marketTaxRatesQueue = new();
|
||||
|
||||
private bool trackMarketBoard;
|
||||
private int trackedEvents;
|
||||
|
||||
/// <summary> Finalizes an instance of the <see cref="MarketBoardWidget"/> class. </summary>
|
||||
~MarketBoardWidget()
|
||||
{
|
||||
if (this.trackMarketBoard)
|
||||
{
|
||||
this.trackMarketBoard = false;
|
||||
var marketBoard = Service<MarketBoard>.GetNullable();
|
||||
if (marketBoard != null)
|
||||
{
|
||||
marketBoard.HistoryReceived -= this.MarketBoardHistoryReceived;
|
||||
marketBoard.OfferingsReceived -= this.MarketBoardOfferingsReceived;
|
||||
marketBoard.ItemPurchased -= this.MarketBoardItemPurchased;
|
||||
marketBoard.PurchaseRequested -= this.MarketBoardPurchaseRequested;
|
||||
marketBoard.TaxRatesReceived -= this.TaxRatesReceived;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string[]? CommandShortcuts { get; init; } = { "marketboard" };
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string DisplayName { get; init; } = "Market Board";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Ready { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Load()
|
||||
{
|
||||
this.trackMarketBoard = false;
|
||||
this.trackedEvents = 0;
|
||||
this.marketBoardHistoryQueue.Clear();
|
||||
this.marketBoardPurchaseRequestsQueue.Clear();
|
||||
this.marketBoardPurchasesQueue.Clear();
|
||||
this.marketTaxRatesQueue.Clear();
|
||||
this.marketBoardCurrentOfferingsQueue.Clear();
|
||||
this.Ready = true;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Draw()
|
||||
{
|
||||
var marketBoard = Service<MarketBoard>.Get();
|
||||
if (ImGui.Checkbox("Track MarketBoard Events", ref this.trackMarketBoard))
|
||||
{
|
||||
if (this.trackMarketBoard)
|
||||
{
|
||||
marketBoard.HistoryReceived += this.MarketBoardHistoryReceived;
|
||||
marketBoard.OfferingsReceived += this.MarketBoardOfferingsReceived;
|
||||
marketBoard.ItemPurchased += this.MarketBoardItemPurchased;
|
||||
marketBoard.PurchaseRequested += this.MarketBoardPurchaseRequested;
|
||||
marketBoard.TaxRatesReceived += this.TaxRatesReceived;
|
||||
}
|
||||
else
|
||||
{
|
||||
marketBoard.HistoryReceived -= this.MarketBoardHistoryReceived;
|
||||
marketBoard.OfferingsReceived -= this.MarketBoardOfferingsReceived;
|
||||
marketBoard.ItemPurchased -= this.MarketBoardItemPurchased;
|
||||
marketBoard.PurchaseRequested -= this.MarketBoardPurchaseRequested;
|
||||
marketBoard.TaxRatesReceived -= this.TaxRatesReceived;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X / 2);
|
||||
if (ImGui.DragInt("Stored Number of Events", ref this.trackedEvents, 0.1f, 1, 512))
|
||||
{
|
||||
this.trackedEvents = Math.Clamp(this.trackedEvents, 1, 512);
|
||||
}
|
||||
|
||||
if (ImGui.Button("Clear Stored Events"))
|
||||
{
|
||||
this.marketBoardHistoryQueue.Clear();
|
||||
}
|
||||
|
||||
using (var tabBar = ImRaii.TabBar("marketTabs"))
|
||||
{
|
||||
if (tabBar)
|
||||
{
|
||||
using (var tabItem = ImRaii.TabItem("History"))
|
||||
{
|
||||
if (tabItem)
|
||||
{
|
||||
ImGuiTable.DrawTable(string.Empty, this.marketBoardHistoryQueue, this.DrawMarketBoardHistory, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg, "Item ID", "Quantity", "Is HQ?", "Sale Price", "Buyer Name", "Purchase Time");
|
||||
}
|
||||
}
|
||||
|
||||
using (var tabItem = ImRaii.TabItem("Offerings"))
|
||||
{
|
||||
if (tabItem)
|
||||
{
|
||||
ImGuiTable.DrawTable(string.Empty, this.marketBoardCurrentOfferingsQueue, this.DrawMarketBoardCurrentOfferings, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg, "Item ID", "Quantity", "Is HQ?", "Price Per Unit", "Buyer Name", "Retainer Name", "Last Review Time");
|
||||
}
|
||||
}
|
||||
|
||||
using (var tabItem = ImRaii.TabItem("Purchases"))
|
||||
{
|
||||
if (tabItem)
|
||||
{
|
||||
ImGuiTable.DrawTable(string.Empty, this.marketBoardPurchasesQueue, this.DrawMarketBoardPurchases, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg, "Item ID", "Quantity");
|
||||
}
|
||||
}
|
||||
|
||||
using (var tabItem = ImRaii.TabItem("Purchase Requests"))
|
||||
{
|
||||
if (tabItem)
|
||||
{
|
||||
ImGuiTable.DrawTable(string.Empty, this.marketBoardPurchaseRequestsQueue, this.DrawMarketBoardPurchaseRequests, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg, "Item ID", "Quantity", "Price Per Unit", "Listing ID", "Retainer ID");
|
||||
}
|
||||
}
|
||||
|
||||
using (var tabItem = ImRaii.TabItem("Taxes"))
|
||||
{
|
||||
if (tabItem)
|
||||
{
|
||||
ImGuiTable.DrawTable(string.Empty, this.marketTaxRatesQueue, this.DrawMarketTaxRates, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg, "Uldah", "Limsa Lominsa", "Gridania", "Ishgard", "Kugane", "Crystarium", "Sharlayan");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TaxRatesReceived(IMarketTaxRates marketTaxRates)
|
||||
{
|
||||
this.marketTaxRatesQueue.Enqueue(marketTaxRates);
|
||||
|
||||
while (this.marketTaxRatesQueue.Count > this.trackedEvents)
|
||||
{
|
||||
this.marketTaxRatesQueue.TryDequeue(out _);
|
||||
}
|
||||
}
|
||||
|
||||
private void MarketBoardPurchaseRequested(IMarketBoardPurchaseHandler marketBoardPurchaseHandler)
|
||||
{
|
||||
this.marketBoardPurchaseRequestsQueue.Enqueue(marketBoardPurchaseHandler);
|
||||
|
||||
while (this.marketBoardPurchaseRequestsQueue.Count > this.trackedEvents)
|
||||
{
|
||||
this.marketBoardPurchaseRequestsQueue.TryDequeue(out _);
|
||||
}
|
||||
}
|
||||
|
||||
private void MarketBoardItemPurchased(IMarketBoardPurchase marketBoardPurchase)
|
||||
{
|
||||
this.marketBoardPurchasesQueue.Enqueue(marketBoardPurchase);
|
||||
|
||||
while (this.marketBoardPurchasesQueue.Count > this.trackedEvents)
|
||||
{
|
||||
this.marketBoardPurchasesQueue.TryDequeue(out _);
|
||||
}
|
||||
}
|
||||
|
||||
private void MarketBoardOfferingsReceived(IMarketBoardCurrentOfferings marketBoardCurrentOfferings)
|
||||
{
|
||||
foreach (var listing in marketBoardCurrentOfferings.ItemListings)
|
||||
{
|
||||
this.marketBoardCurrentOfferingsQueue.Enqueue((marketBoardCurrentOfferings, listing));
|
||||
}
|
||||
|
||||
while (this.marketBoardCurrentOfferingsQueue.Count > this.trackedEvents)
|
||||
{
|
||||
this.marketBoardCurrentOfferingsQueue.TryDequeue(out _);
|
||||
}
|
||||
}
|
||||
|
||||
private void MarketBoardHistoryReceived(IMarketBoardHistory marketBoardHistory)
|
||||
{
|
||||
foreach (var listing in marketBoardHistory.HistoryListings)
|
||||
{
|
||||
this.marketBoardHistoryQueue.Enqueue((marketBoardHistory, listing));
|
||||
}
|
||||
|
||||
while (this.marketBoardHistoryQueue.Count > this.trackedEvents)
|
||||
{
|
||||
this.marketBoardHistoryQueue.TryDequeue(out _);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMarketBoardHistory((IMarketBoardHistory History, IMarketBoardHistoryListing Listing) data)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.History.ItemId.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.Listing.Quantity.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.Listing.IsHq.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.Listing.SalePrice.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.Listing.BuyerName);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.Listing.PurchaseTime.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
private void DrawMarketBoardCurrentOfferings((IMarketBoardCurrentOfferings MarketBoardCurrentOfferings, IMarketBoardItemListing Listing) data)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.Listing.ItemId.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.Listing.ItemQuantity.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.Listing.IsHq.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.Listing.PricePerUnit.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.Listing.PlayerName);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.Listing.RetainerName);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.Listing.LastReviewTime.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
private void DrawMarketBoardPurchases(IMarketBoardPurchase data)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.CatalogId.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.ItemQuantity.ToString());
|
||||
}
|
||||
|
||||
private void DrawMarketBoardPurchaseRequests(IMarketBoardPurchaseHandler data)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.CatalogId.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.ItemQuantity.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.PricePerUnit.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.ListingId.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.RetainerId.ToString());
|
||||
}
|
||||
|
||||
private void DrawMarketTaxRates(IMarketTaxRates data)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.UldahTax.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.LimsaLominsaTax.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.GridaniaTax.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.IshgardTax.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.KuganeTax.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.CrystariumTax.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(data.SharlayanTax.ToString());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,259 @@
|
|||
namespace Dalamud.Interface.Internal.Windows.SelfTest.AgingSteps;
|
||||
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
using Game.MarketBoard;
|
||||
using Game.Network.Structures;
|
||||
|
||||
using ImGuiNET;
|
||||
|
||||
/// <summary>
|
||||
/// Tests the various market board events
|
||||
/// </summary>
|
||||
internal class MarketBoardAgingStep : IAgingStep
|
||||
{
|
||||
private SubStep currentSubStep;
|
||||
private bool eventsSubscribed;
|
||||
|
||||
private IMarketBoardHistoryListing? historyListing;
|
||||
private IMarketBoardItemListing? itemListing;
|
||||
private IMarketTaxRates? marketTaxRate;
|
||||
private IMarketBoardPurchaseHandler? marketBoardPurchaseRequest;
|
||||
private IMarketBoardPurchase? marketBoardPurchase;
|
||||
|
||||
private enum SubStep
|
||||
{
|
||||
History,
|
||||
Offerings,
|
||||
PurchaseRequests,
|
||||
Purchases,
|
||||
Taxes,
|
||||
Done,
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public string Name => "Test MarketBoard";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public SelfTestStepResult RunStep()
|
||||
{
|
||||
if (!this.eventsSubscribed)
|
||||
{
|
||||
this.SubscribeToEvents();
|
||||
}
|
||||
|
||||
ImGui.Text($"Testing: {this.currentSubStep.ToString()}");
|
||||
|
||||
switch (this.currentSubStep)
|
||||
{
|
||||
case SubStep.History:
|
||||
|
||||
if (this.historyListing == null)
|
||||
{
|
||||
ImGui.Text("Goto a Market Board. Open any item that has historical sale listings.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text("Does one of the historical sales match this information?");
|
||||
ImGui.Separator();
|
||||
ImGui.Text($"Quantity: {this.historyListing.Quantity.ToString()}");
|
||||
ImGui.Text($"Buyer: {this.historyListing.BuyerName}");
|
||||
ImGui.Text($"Sale Price: {this.historyListing.SalePrice.ToString()}");
|
||||
ImGui.Text($"Purchase Time: {this.historyListing.PurchaseTime.ToString(CultureInfo.InvariantCulture)}");
|
||||
ImGui.Separator();
|
||||
if (ImGui.Button("Looks Correct / Skip"))
|
||||
{
|
||||
this.currentSubStep++;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("No"))
|
||||
{
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case SubStep.Offerings:
|
||||
|
||||
if (this.itemListing == null)
|
||||
{
|
||||
ImGui.Text("Goto a Market Board. Open any item that has sale listings.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text("Does one of the sales match this information?");
|
||||
ImGui.Separator();
|
||||
ImGui.Text($"Quantity: {this.itemListing.ItemQuantity.ToString()}");
|
||||
ImGui.Text($"Price Per Unit: {this.itemListing.PricePerUnit}");
|
||||
ImGui.Text($"Retainer Name: {this.itemListing.RetainerName}");
|
||||
ImGui.Text($"Is HQ?: {(this.itemListing.IsHq ? "Yes" : "No")}");
|
||||
ImGui.Separator();
|
||||
if (ImGui.Button("Looks Correct / Skip"))
|
||||
{
|
||||
this.currentSubStep++;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("No"))
|
||||
{
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case SubStep.PurchaseRequests:
|
||||
if (this.marketBoardPurchaseRequest == null)
|
||||
{
|
||||
ImGui.Text("Goto a Market Board. Purchase any item, the cheapest you can find.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text("Does this information match the purchase you made? This is testing the request to the server.");
|
||||
ImGui.Separator();
|
||||
ImGui.Text($"Quantity: {this.marketBoardPurchaseRequest.ItemQuantity.ToString()}");
|
||||
ImGui.Text($"Item ID: {this.marketBoardPurchaseRequest.CatalogId}");
|
||||
ImGui.Text($"Price Per Unit: {this.marketBoardPurchaseRequest.PricePerUnit}");
|
||||
ImGui.Separator();
|
||||
if (ImGui.Button("Looks Correct / Skip"))
|
||||
{
|
||||
this.currentSubStep++;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("No"))
|
||||
{
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case SubStep.Purchases:
|
||||
if (this.marketBoardPurchase == null)
|
||||
{
|
||||
ImGui.Text("Goto a Market Board. Purchase any item, the cheapest you can find.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text("Does this information match the purchase you made? This is testing the response from the server.");
|
||||
ImGui.Separator();
|
||||
ImGui.Text($"Quantity: {this.marketBoardPurchase.ItemQuantity.ToString()}");
|
||||
ImGui.Text($"Item ID: {this.marketBoardPurchase.CatalogId}");
|
||||
ImGui.Separator();
|
||||
if (ImGui.Button("Looks Correct / Skip"))
|
||||
{
|
||||
this.currentSubStep++;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("No"))
|
||||
{
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case SubStep.Taxes:
|
||||
if (this.marketTaxRate == null)
|
||||
{
|
||||
ImGui.Text("Goto a Retainer Vocate and talk to then. Click the 'View market tax rates' menu item.");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Text("Does this market tax rate information look correct?");
|
||||
ImGui.Separator();
|
||||
ImGui.Text($"Uldah: {this.marketTaxRate.UldahTax.ToString()}");
|
||||
ImGui.Text($"Gridania: {this.marketTaxRate.GridaniaTax.ToString()}");
|
||||
ImGui.Text($"Limsa Lominsa: {this.marketTaxRate.LimsaLominsaTax.ToString()}");
|
||||
ImGui.Text($"Ishgard: {this.marketTaxRate.IshgardTax.ToString()}");
|
||||
ImGui.Text($"Kugane: {this.marketTaxRate.KuganeTax.ToString()}");
|
||||
ImGui.Text($"Crystarium: {this.marketTaxRate.CrystariumTax.ToString()}");
|
||||
ImGui.Text($"Sharlayan: {this.marketTaxRate.SharlayanTax.ToString()}");
|
||||
ImGui.Separator();
|
||||
if (ImGui.Button("Looks Correct / Skip"))
|
||||
{
|
||||
this.currentSubStep++;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("No"))
|
||||
{
|
||||
return SelfTestStepResult.Fail;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SubStep.Done:
|
||||
return SelfTestStepResult.Pass;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
return SelfTestStepResult.Waiting;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void CleanUp()
|
||||
{
|
||||
this.currentSubStep = SubStep.History;
|
||||
this.historyListing = null;
|
||||
this.marketTaxRate = null;
|
||||
this.marketBoardPurchase = null;
|
||||
this.marketBoardPurchaseRequest = null;
|
||||
this.itemListing = null;
|
||||
this.UnsubscribeFromEvents();
|
||||
}
|
||||
|
||||
private void SubscribeToEvents()
|
||||
{
|
||||
var marketBoard = Service<MarketBoard>.Get();
|
||||
marketBoard.HistoryReceived += this.OnHistoryReceived;
|
||||
marketBoard.OfferingsReceived += this.OnOfferingsReceived;
|
||||
marketBoard.ItemPurchased += this.OnItemPurchased;
|
||||
marketBoard.PurchaseRequested += this.OnPurchaseRequested;
|
||||
marketBoard.TaxRatesReceived += this.OnTaxRatesReceived;
|
||||
this.eventsSubscribed = true;
|
||||
}
|
||||
|
||||
private void UnsubscribeFromEvents()
|
||||
{
|
||||
var marketBoard = Service<MarketBoard>.Get();
|
||||
marketBoard.HistoryReceived -= this.OnHistoryReceived;
|
||||
marketBoard.OfferingsReceived -= this.OnOfferingsReceived;
|
||||
marketBoard.ItemPurchased -= this.OnItemPurchased;
|
||||
marketBoard.PurchaseRequested -= this.OnPurchaseRequested;
|
||||
marketBoard.TaxRatesReceived -= this.OnTaxRatesReceived;
|
||||
this.eventsSubscribed = false;
|
||||
}
|
||||
|
||||
private void OnTaxRatesReceived(IMarketTaxRates marketTaxRates)
|
||||
{
|
||||
this.marketTaxRate = marketTaxRates;
|
||||
}
|
||||
|
||||
private void OnPurchaseRequested(IMarketBoardPurchaseHandler marketBoardPurchaseHandler)
|
||||
{
|
||||
this.marketBoardPurchaseRequest = marketBoardPurchaseHandler;
|
||||
}
|
||||
|
||||
private void OnItemPurchased(IMarketBoardPurchase purchase)
|
||||
{
|
||||
this.marketBoardPurchase = purchase;
|
||||
}
|
||||
|
||||
private void OnOfferingsReceived(IMarketBoardCurrentOfferings marketBoardCurrentOfferings)
|
||||
{
|
||||
if (marketBoardCurrentOfferings.ItemListings.Count != 0)
|
||||
{
|
||||
this.itemListing = marketBoardCurrentOfferings.ItemListings.First();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHistoryReceived(IMarketBoardHistory marketBoardHistory)
|
||||
{
|
||||
if (marketBoardHistory.HistoryListings.Count != 0)
|
||||
{
|
||||
this.historyListing = marketBoardHistory.HistoryListings.First();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +44,7 @@ internal class SelfTestWindow : Window
|
|||
new HandledExceptionAgingStep(),
|
||||
new DutyStateAgingStep(),
|
||||
new GameConfigAgingStep(),
|
||||
new MarketBoardAgingStep(),
|
||||
new LogoutEventAgingStep(),
|
||||
};
|
||||
|
||||
|
|
|
|||
64
Dalamud/Plugin/Services/IMarketBoard.cs
Normal file
64
Dalamud/Plugin/Services/IMarketBoard.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
namespace Dalamud.Plugin.Services;
|
||||
|
||||
using Game.Network.Structures;
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to market board related events as the client receives/sends them.
|
||||
/// </summary>
|
||||
public interface IMarketBoard
|
||||
{
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="HistoryReceived"/> event.
|
||||
/// </summary>
|
||||
/// <param name="history">The historical listings for a particular item on the market board.</param>
|
||||
public delegate void HistoryReceivedDelegate(IMarketBoardHistory history);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="ItemPurchased"/> event.
|
||||
/// </summary>
|
||||
/// <param name="purchase">The item that has been purchased.</param>
|
||||
public delegate void ItemPurchasedDelegate(IMarketBoardPurchase purchase);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="OfferingsReceived"/> event.
|
||||
/// </summary>
|
||||
/// <param name="currentOfferings">The current offerings for a particular item on the market board.</param>
|
||||
public delegate void OfferingsReceivedDelegate(IMarketBoardCurrentOfferings currentOfferings);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="PurchaseRequested"/> event.
|
||||
/// </summary>
|
||||
/// <param name="purchaseRequested">The details about the item being purchased.</param>
|
||||
public delegate void PurchaseRequestedDelegate(IMarketBoardPurchaseHandler purchaseRequested);
|
||||
|
||||
/// <summary>
|
||||
/// A delegate type used with the <see cref="PurchaseRequested"/> event.
|
||||
/// </summary>
|
||||
/// <param name="taxRates">The new tax rates.</param>
|
||||
public delegate void TaxRatesReceivedDelegate(IMarketTaxRates taxRates);
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when historical sale listings are received for a specific item on the market board.
|
||||
/// </summary>
|
||||
public event HistoryReceivedDelegate HistoryReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when a item is purchased on the market board.
|
||||
/// </summary>
|
||||
public event ItemPurchasedDelegate ItemPurchased;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when current offerings are received for a specific item on the market board.
|
||||
/// </summary>
|
||||
public event OfferingsReceivedDelegate OfferingsReceived;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when a player requests to purchase an item from the market board.
|
||||
/// </summary>
|
||||
public event PurchaseRequestedDelegate PurchaseRequested;
|
||||
|
||||
/// <summary>
|
||||
/// Event that fires when the client receives new tax rates. These events only occur when accessing a retainer vocate and requesting the tax rates.
|
||||
/// </summary>
|
||||
public event TaxRatesReceivedDelegate TaxRatesReceived;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue