From e926424554d2290e67eb327e095a4f6179283995 Mon Sep 17 00:00:00 2001 From: Andrew Zhu Date: Sat, 29 Jun 2024 18:37:14 -0700 Subject: [PATCH] feat: update for 7.0 --- AutoSweep.csproj | 6 +-- Configuration.cs | 12 +---- Paissa/Client.cs | 84 +++++++++++++++++-------------- Paissa/LotteryObserver.cs | 15 +++--- Paissa/Utils.cs | 15 ++++-- Paissa/WardObserver.cs | 18 +++---- Plugin.cs | 102 +++++++++++++++++++------------------- packages.lock.json | 8 +-- 8 files changed, 134 insertions(+), 126 deletions(-) diff --git a/AutoSweep.csproj b/AutoSweep.csproj index 15a991b..61c128b 100644 --- a/AutoSweep.csproj +++ b/AutoSweep.csproj @@ -3,7 +3,7 @@ autoSweep zhudotexe - net7.0-windows + net8.0-windows 9 true true @@ -14,7 +14,7 @@ true true autoSweep - 1.4.3.0 + 1.4.4.0 @@ -68,7 +68,7 @@ - + diff --git a/Configuration.cs b/Configuration.cs index 1188e83..3619d6e 100644 --- a/Configuration.cs +++ b/Configuration.cs @@ -1,15 +1,11 @@ using System; using Dalamud.Configuration; using Dalamud.Game.Text; -using Dalamud.Plugin; namespace AutoSweep { [Serializable] public class Configuration : IPluginConfiguration { // the below exist just to make saving less cumbersome - [NonSerialized] - private DalamudPluginInterface pluginInterface; - public bool Enabled { get; set; } = true; public string OutputFormatString { get; set; } = ""; public OutputFormat OutputFormat { get; set; } = OutputFormat.Simple; @@ -28,12 +24,8 @@ public class Configuration : IPluginConfiguration { public bool ChatSweepAlert { get; set; } = true; // thank the user for contributions in chat/"began sweep" public int Version { get; set; } = 4; - public void Initialize(DalamudPluginInterface pluginInterface) { - this.pluginInterface = pluginInterface; - } - public void Save() { - pluginInterface.SavePluginConfig(this); + Plugin.PluginInterface.SavePluginConfig(this); } } @@ -64,4 +56,4 @@ public enum OutputFormat { Custom = 4 } -} +} \ No newline at end of file diff --git a/Paissa/Client.cs b/Paissa/Client.cs index debe885..b5c7b81 100644 --- a/Paissa/Client.cs +++ b/Paissa/Client.cs @@ -20,9 +20,7 @@ public class PaissaClient : IDisposable { private string sessionToken; // dalamud - private readonly IClientState clientState; - private readonly IChatGui chat; - private readonly IPluginLog log; + private Plugin plugin; // ingest debounce private readonly DebounceDispatcher ingestDebounceDispatcher = new DebounceDispatcher(1200); @@ -41,10 +39,8 @@ public class PaissaClient : IDisposable { public event EventHandler OnPlotUpdate; public event EventHandler OnPlotSold; - public PaissaClient(IClientState clientState, IChatGui chatGui, IPluginLog log) { - this.clientState = clientState; - chat = chatGui; - this.log = log; + public PaissaClient(Plugin plugin) { + this.plugin = plugin; http = new HttpClient(); ReconnectWS(); } @@ -60,23 +56,23 @@ public void Dispose() { /// Make a POST request to register the current character's content ID. /// public async Task Hello() { - PlayerCharacter player = clientState.LocalPlayer; + IPlayerCharacter player = Plugin.ClientState.LocalPlayer; if (player == null) return; var homeworld = player.HomeWorld.GameData; if (homeworld == null) return; var charInfo = new Dictionary { - { "cid", clientState.LocalContentId }, + { "cid", Plugin.ClientState.LocalContentId }, { "name", player.Name.ToString() }, { "world", homeworld.Name.ToString() }, { "worldId", player.HomeWorld.Id } }; string content = JsonConvert.SerializeObject(charInfo); - log.Debug(content); + Plugin.PluginLog.Debug(content); var response = await Post("/hello", content, false); if (response.IsSuccessStatusCode) { string respText = await response.Content.ReadAsStringAsync(); sessionToken = JsonConvert.DeserializeObject(respText).session_token; - log.Info("Completed PaissaDB HELLO"); + Plugin.PluginLog.Info("Completed PaissaDB HELLO"); } } @@ -99,7 +95,8 @@ public void PostWardInfo(HousingWardInfo wardInfo, int serverTimestamp) { /// /// Fire and forget a POST request for a placard's lottery info. /// - public void PostLotteryInfo(uint worldId, ushort districtId, ushort wardId, ushort plotId, PlacardSaleInfo saleInfo) { + public void PostLotteryInfo(uint worldId, ushort districtId, ushort wardId, ushort plotId, + PlacardSaleInfo saleInfo) { var data = new Dictionary { { "event_type", "LOTTERY_INFO" }, { "client_timestamp", new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds() }, @@ -124,7 +121,8 @@ public void PostLotteryInfo(uint worldId, ushort districtId, ushort wardId, usho /// The DistrictDetail public async Task GetDistrictDetailAsync(short worldId, short districtId) { HttpResponseMessage response = await http.GetAsync($"{apiBase}/worlds/{worldId}/{districtId}"); - log.Debug($"GET {apiBase}/worlds/{worldId}/{districtId} returned {response.StatusCode} ({response.ReasonPhrase})"); + Plugin.PluginLog.Debug( + $"GET {apiBase}/worlds/{worldId}/{districtId} returned {response.StatusCode} ({response.ReasonPhrase})"); response.EnsureSuccessStatusCode(); string respText = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(respText); @@ -136,7 +134,7 @@ private void queueIngest(object data) { ingestDebounceDispatcher.Debounce(() => { string bulkIngestData = JsonConvert.SerializeObject(ingestDataQueue); PostFireAndForget("/ingest", bulkIngestData); - log.Debug($"Bulk ingesting {ingestDataQueue.Count} entries ({bulkIngestData.Length}B)"); + Plugin.PluginLog.Debug($"Bulk ingesting {ingestDataQueue.Count} entries ({bulkIngestData.Length}B)"); ingestDataQueue.Clear(); }); } @@ -145,55 +143,66 @@ private async void PostFireAndForget(string route, string content, bool auth = t await Post(route, content, auth, retries); } - private async Task Post(string route, string content, bool auth = true, ushort retries = 5) { + private async Task + Post(string route, string content, bool auth = true, ushort retries = 5) { HttpResponseMessage response = null; - log.Verbose(content); + Plugin.PluginLog.Verbose(content); for (var i = 0; i < retries; i++) { HttpRequestMessage request; if (auth) { if (sessionToken == null) { - log.Warning("Trying to send authed request but no session token!"); + Plugin.PluginLog.Warning("Trying to send authed request but no session token!"); await Hello(); continue; } + request = new HttpRequestMessage(HttpMethod.Post, $"{apiBase}{route}") { Content = new StringContent(content, Encoding.UTF8, "application/json"), Headers = { Authorization = new AuthenticationHeaderValue("Bearer", sessionToken) } }; - } else { + } + else { request = new HttpRequestMessage(HttpMethod.Post, $"{apiBase}{route}") { Content = new StringContent(content, Encoding.UTF8, "application/json"), }; } + try { response = await http.SendAsync(request); - log.Debug($"{request.Method} {request.RequestUri} returned {response.StatusCode} ({response.ReasonPhrase})"); + Plugin.PluginLog.Debug( + $"{request.Method} {request.RequestUri} returned {response.StatusCode} ({response.ReasonPhrase})"); if (!response.IsSuccessStatusCode) { string respText = await response.Content.ReadAsStringAsync(); - log.Warning($"{request.Method} {request.RequestUri} returned {response.StatusCode} ({response.ReasonPhrase}):\n{respText}"); - } else { + Plugin.PluginLog.Warning( + $"{request.Method} {request.RequestUri} returned {response.StatusCode} ({response.ReasonPhrase}):\n{respText}"); + } + else { break; } - } catch (Exception e) { - log.Warning(e, $"{request.Method} {request.RequestUri} raised an error:"); } + catch (Exception e) { + Plugin.PluginLog.Warning(e, $"{request.Method} {request.RequestUri} raised an error:"); + } + // if our request failed, exponential backoff for 2 * (i + 1) seconds if (i + 1 < retries) { int toDelay = 2000 * (i + 1) + new Random().Next(500, 1_500); - log.Warning($"Request {i} failed, waiting for {toDelay}ms before retry..."); + Plugin.PluginLog.Warning($"Request {i} failed, waiting for {toDelay}ms before retry..."); await Task.Delay(toDelay); } } // todo better error handling if (response == null) { - chat.PrintError("There was an error connecting to PaissaDB."); - } else if (!response.IsSuccessStatusCode) { - chat.PrintError($"There was an error connecting to PaissaDB: {response.ReasonPhrase}"); + Plugin.Chat.PrintError("There was an error connecting to PaissaDB."); + } + else if (!response.IsSuccessStatusCode) { + Plugin.Chat.PrintError($"There was an error connecting to PaissaDB: {response.ReasonPhrase}"); } + return response; } @@ -209,23 +218,25 @@ private void ReconnectWS() { ws.OnError += OnWSError; try { ws.Connect(); - } catch (PlatformNotSupportedException) { + } + catch (PlatformNotSupportedException) { // idk why this happens but it doesn't seem to affect the ws // silence for now to avoid polluting logs // todo what is happening here? // https://github.com/zhudotexe/FFXIV_PaissaHouse/issues/14 } - log.Debug("ReconnectWS complete"); + + Plugin.PluginLog.Debug("ReconnectWS complete"); }); } private void OnWSOpen(object sender, EventArgs e) { - log.Information("WebSocket connected"); + Plugin.PluginLog.Information("WebSocket connected"); } private void OnWSMessage(object sender, MessageEventArgs e) { if (!e.IsText) return; - log.Verbose($">>>> R: {e.Data}"); + Plugin.PluginLog.Verbose($">>>> R: {e.Data}"); var message = JsonConvert.DeserializeObject(e.Data); switch (message.Type) { case "plot_open": @@ -240,20 +251,20 @@ private void OnWSMessage(object sender, MessageEventArgs e) { case "ping": break; default: - log.Warning($"Got unknown WS message: {e.Data}"); + Plugin.PluginLog.Warning($"Got unknown WS message: {e.Data}"); break; } } private void OnWSClose(object sender, CloseEventArgs e) { - log.Information($"WebSocket closed ({e.Code}: {e.Reason})"); + Plugin.PluginLog.Information($"WebSocket closed ({e.Code}: {e.Reason})"); // reconnect if unexpected close or server restarting if ((!e.WasClean || e.Code == 1012) && !disposed) WSReconnectSoon(); } private void OnWSError(object sender, ErrorEventArgs e) { - log.Warning(e.Exception, $"WebSocket error: {e.Message}"); + Plugin.PluginLog.Warning(e.Exception, $"WebSocket error: {e.Message}"); if (!disposed) WSReconnectSoon(); } @@ -261,10 +272,11 @@ private void OnWSError(object sender, ErrorEventArgs e) { private void WSReconnectSoon() { if (ws.IsAlive) return; int t = new Random().Next(5_000, 15_000); - log.Warning($"WebSocket closed unexpectedly: will reconnect to socket in {t / 1000f:F3} seconds"); + Plugin.PluginLog.Warning( + $"WebSocket closed unexpectedly: will reconnect to socket in {t / 1000f:F3} seconds"); Task.Run(async () => await Task.Delay(t)).ContinueWith(_ => { if (!disposed) ReconnectWS(); }); } } -} +} \ No newline at end of file diff --git a/Paissa/LotteryObserver.cs b/Paissa/LotteryObserver.cs index ae8d467..1aa0649 100644 --- a/Paissa/LotteryObserver.cs +++ b/Paissa/LotteryObserver.cs @@ -20,12 +20,13 @@ private delegate void HandlePlacardSaleInfoDelegate( long a8 ); - [Signature("E8 ?? ?? ?? ?? 48 8B B4 24 ?? ?? ?? ?? 48 8B 6C 24 ?? E9", DetourName = nameof(OnPlacardSaleInfo))] + // easy way to find sig: search for scalar 7043 + [Signature("E8 ?? ?? ?? ?? 48 8B 74 24 ?? 48 8B 6C 24 ?? E9", DetourName = nameof(OnPlacardSaleInfo))] private Hook? placardSaleInfoHook; public LotteryObserver(Plugin plugin) { this.plugin = plugin; - plugin.InteropProvider.InitializeFromAttributes(this); + Plugin.InteropProvider.InitializeFromAttributes(this); placardSaleInfoHook?.Enable(); } @@ -51,20 +52,20 @@ long a8 PlacardSaleInfo saleInfo = PlacardSaleInfo.Read(placardSaleInfoPtr); - plugin.PluginLog.Debug( + Plugin.PluginLog.Debug( $"Got PlacardSaleInfo: PurchaseType={saleInfo.PurchaseType}, TenantType={saleInfo.TenantType}, available={saleInfo.AvailabilityType}, until={saleInfo.PhaseEndsAt}, numEntries={saleInfo.EntryCount}"); - plugin.PluginLog.Debug( + Plugin.PluginLog.Debug( $"unknown1={saleInfo.Unknown1}, unknown2={saleInfo.Unknown2}, unknown3={saleInfo.Unknown3}, unknown4={BitConverter.ToString(saleInfo.Unknown4)}"); - plugin.PluginLog.Debug( + Plugin.PluginLog.Debug( $"housingType={housingType}, territoryTypeId={territoryTypeId}, wardId={wardId}, plotId={plotId}, apartmentNumber={apartmentNumber}, placardSaleInfoPtr={placardSaleInfoPtr}, a8={a8}"); // get information about the world from the clientstate - World world = plugin.ClientState.LocalPlayer?.CurrentWorld.GameData; + World world = Plugin.ClientState.LocalPlayer?.CurrentWorld.GameData; if (world is null) return; SeString place = plugin.Territories.GetRow(territoryTypeId)?.PlaceName.Value?.Name; SeString worldName = world.Name; - plugin.PluginLog.Info($"Plot {place} {wardId + 1}-{plotId + 1} ({worldName}) has {saleInfo.EntryCount} lottery entries."); + Plugin.PluginLog.Info($"Plot {place} {wardId + 1}-{plotId + 1} ({worldName}) has {saleInfo.EntryCount} lottery entries."); plugin.PaissaClient.PostLotteryInfo(world.RowId, territoryTypeId, wardId, plotId, saleInfo); } diff --git a/Paissa/Utils.cs b/Paissa/Utils.cs index 69c3249..dc40807 100644 --- a/Paissa/Utils.cs +++ b/Paissa/Utils.cs @@ -38,13 +38,15 @@ string houseSizeName .Replace("{houseSizeName}", houseSizeName); } - public static bool ConfigEnabledForPlot(Plugin plugin, ushort worldId, ushort districtId, ushort size, PurchaseSystem purchaseSystem) { + public static bool ConfigEnabledForPlot(Plugin plugin, ushort worldId, ushort districtId, ushort size, + PurchaseSystem purchaseSystem) { if (!plugin.Configuration.Enabled) return false; // does the config want notifs for this world? World eventWorld = plugin.Worlds.GetRow(worldId); if (!(plugin.Configuration.AllNotifs - || plugin.Configuration.HomeworldNotifs && worldId == plugin.ClientState.LocalPlayer?.HomeWorld.Id - || plugin.Configuration.DatacenterNotifs && eventWorld?.DataCenter.Row == plugin.ClientState.LocalPlayer?.HomeWorld.GameData?.DataCenter.Row)) + || plugin.Configuration.HomeworldNotifs && worldId == Plugin.ClientState.LocalPlayer?.HomeWorld.Id + || plugin.Configuration.DatacenterNotifs && eventWorld?.DataCenter.Row == + Plugin.ClientState.LocalPlayer?.HomeWorld.GameData?.DataCenter.Row)) return false; // get the district config DistrictNotifConfig districtNotifs; @@ -67,6 +69,7 @@ public static bool ConfigEnabledForPlot(Plugin plugin, ushort worldId, ushort di default: return false; } + // what about house sizes in this district? bool notifEnabled; switch (size) { @@ -82,10 +85,12 @@ public static bool ConfigEnabledForPlot(Plugin plugin, ushort worldId, ushort di default: return false; } + // and FC/individual purchase? - PurchaseSystem purchaseSystemMask = (districtNotifs.FreeCompany ? PurchaseSystem.FreeCompany : 0) | (districtNotifs.Individual ? PurchaseSystem.Individual : 0); + PurchaseSystem purchaseSystemMask = (districtNotifs.FreeCompany ? PurchaseSystem.FreeCompany : 0) | + (districtNotifs.Individual ? PurchaseSystem.Individual : 0); notifEnabled = notifEnabled && (purchaseSystem & purchaseSystemMask) != 0; return notifEnabled; } } -} +} \ No newline at end of file diff --git a/Paissa/WardObserver.cs b/Paissa/WardObserver.cs index e60c04e..9945f7e 100644 --- a/Paissa/WardObserver.cs +++ b/Paissa/WardObserver.cs @@ -15,12 +15,12 @@ private delegate void HandleHousingWardInfoDelegate( IntPtr housingWardInfoPtr ); - [Signature("40 55 57 41 54 41 56 41 57 48 8D AC 24 ?? ?? ?? ?? B8", DetourName = nameof(OnHousingWardInfo))] + [Signature("40 55 53 41 54 41 55 41 57 48 8D AC 24 ?? ?? ?? ?? B8", DetourName = nameof(OnHousingWardInfo))] private Hook? housingWardInfoHook; public WardObserver(Plugin plugin) { this.plugin = plugin; - plugin.InteropProvider.InitializeFromAttributes(this); + Plugin.InteropProvider.InitializeFromAttributes(this); SweepState = new SweepState(Utils.NumWardsPerDistrict); housingWardInfoHook?.Enable(); } @@ -38,7 +38,7 @@ IntPtr dataPtr if (!plugin.Configuration.Enabled) return; HousingWardInfo wardInfo = HousingWardInfo.Read(dataPtr); int serverTimestamp = Marshal.ReadInt32(dataPtr - 0x8); - plugin.PluginLog.Debug($"Got HousingWardInfo for ward: {wardInfo.LandIdent.WardNumber} territory: {wardInfo.LandIdent.TerritoryTypeId}"); + Plugin.PluginLog.Debug($"Got HousingWardInfo for ward: {wardInfo.LandIdent.WardNumber} territory: {wardInfo.LandIdent.TerritoryTypeId}"); // if the current wardinfo is for a different district than the last swept one, print the header // or if the last sweep was > 10m ago @@ -49,13 +49,13 @@ IntPtr dataPtr SeString districtName = plugin.Territories.GetRow((uint)wardInfo.LandIdent.TerritoryTypeId)?.PlaceName.Value?.Name; SeString worldName = plugin.Worlds.GetRow((uint)wardInfo.LandIdent.WorldId)?.Name; if (plugin.Configuration.ChatSweepAlert) { - plugin.Chat.Print($"Began sweep for {districtName} ({worldName})"); + Plugin.Chat.Print($"Began sweep for {districtName} ({worldName})"); } } // if we've seen this ward already, ignore it if (SweepState.Contains(wardInfo)) { - plugin.PluginLog.Debug($"Skipped processing HousingWardInfo for ward: {wardInfo.LandIdent.WardNumber} because we have seen it already"); + Plugin.PluginLog.Debug($"Skipped processing HousingWardInfo for ward: {wardInfo.LandIdent.WardNumber} because we have seen it already"); return; } @@ -68,7 +68,7 @@ IntPtr dataPtr // if that's all the wards, display the district summary and thanks if (SweepState.IsComplete) OnFinishedDistrictSweep(wardInfo); - plugin.PluginLog.Debug($"Done processing HousingWardInfo for ward: {wardInfo.LandIdent.WardNumber}"); + Plugin.PluginLog.Debug($"Done processing HousingWardInfo for ward: {wardInfo.LandIdent.WardNumber}"); } /// @@ -79,9 +79,9 @@ private void OnFinishedDistrictSweep(HousingWardInfo housingWardInfo) { SeString districtName = plugin.Territories.GetRow((uint)SweepState.DistrictId)?.PlaceName.Value?.Name; - plugin.Chat.Print($"Swept all {Utils.NumWardsPerDistrict} wards. Thank you for your contribution!"); - plugin.Chat.Print($"Here's a summary of open plots in {districtName}:"); - plugin.Chat.Print($"{districtName}: {SweepState.OpenHouses.Count} open plots."); + Plugin.Chat.Print($"Swept all {Utils.NumWardsPerDistrict} wards. Thank you for your contribution!"); + Plugin.Chat.Print($"Here's a summary of open plots in {districtName}:"); + Plugin.Chat.Print($"{districtName}: {SweepState.OpenHouses.Count} open plots."); foreach (OpenHouse openHouse in SweepState.OpenHouses) plugin.OnFoundOpenHouse((uint)SweepState.WorldId, (uint)SweepState.DistrictId, openHouse.WardNum, openHouse.PlotNum, openHouse.HouseInfoEntry.HousePrice); } diff --git a/Plugin.cs b/Plugin.cs index 022d193..5ffaed2 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -6,24 +6,26 @@ using Dalamud.Game.Text; using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling.Payloads; +using Dalamud.IoC; using Dalamud.Plugin; using Dalamud.Plugin.Services; using Lumina.Excel; using Lumina.Excel.GeneratedSheets; namespace AutoSweep { - public class Plugin : IDalamudPlugin { + public sealed class Plugin : IDalamudPlugin { public string Name => "PaissaHouse"; // frameworks/data - internal readonly DalamudPluginInterface PluginInterface; + [PluginService] internal static IDalamudPluginInterface PluginInterface { get; private set; } = null!; + [PluginService] internal static IChatGui Chat { get; private set; } = null!; + [PluginService] internal static IClientState ClientState { get; private set; } = null!; + [PluginService] internal static ICommandManager Commands { get; private set; } = null!; + [PluginService] internal static IFramework Framework { get; private set; } = null!; + [PluginService] internal static IPluginLog PluginLog { get; private set; } = null!; + [PluginService] internal static IGameInteropProvider InteropProvider { get; private set; } = null!; + [PluginService] internal static IDataManager DataManager { get; private set; } = null!; internal readonly Configuration Configuration; - internal readonly IChatGui Chat; - internal readonly IClientState ClientState; - internal readonly ICommandManager Commands; - internal readonly IFramework Framework; - internal readonly IPluginLog PluginLog; - internal readonly IGameInteropProvider InteropProvider; internal readonly PaissaClient PaissaClient; internal readonly ExcelSheet HousingLandSets; @@ -37,51 +39,34 @@ public class Plugin : IDalamudPlugin { private readonly PluginUI ui; private bool clientNeedsHello = true; - public Plugin( - DalamudPluginInterface pi, - IChatGui chat, - IDataManager data, - ICommandManager commands, - IClientState clientState, - IFramework framework, - IPluginLog log, - IGameInteropProvider interopProvider - ) { - PluginInterface = pi; - Chat = chat; - Commands = commands; - ClientState = clientState; - Framework = framework; - PluginLog = log; - InteropProvider = interopProvider; - + public Plugin() { // setup - Configuration = pi.GetPluginConfig() as Configuration ?? new Configuration(); - Configuration.Initialize(pi); + Configuration = PluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); ui = new PluginUI(Configuration); - Territories = data.GetExcelSheet(); - Worlds = data.GetExcelSheet(); - HousingLandSets = data.GetExcelSheet(); + Territories = DataManager.GetExcelSheet(); + Worlds = DataManager.GetExcelSheet(); + HousingLandSets = DataManager.GetExcelSheet(); - commands.AddHandler(Utils.HouseCommandName, new CommandInfo(OnHouseCommand) { + Commands.AddHandler(Utils.HouseCommandName, new CommandInfo(OnHouseCommand) { HelpMessage = "View all houses available for sale." }); - commands.AddHandler(Utils.CommandName, new CommandInfo(OnCommand) { - HelpMessage = $"Configure PaissaHouse settings.\n\"{Utils.CommandName} reset\" to reset a sweep if sweeping the same district multiple times in a row." + Commands.AddHandler(Utils.CommandName, new CommandInfo(OnCommand) { + HelpMessage = + $"Configure PaissaHouse settings.\n\"{Utils.CommandName} reset\" to reset a sweep if sweeping the same district multiple times in a row." }); - chatLinkPayload = pi.AddChatLinkHandler(0, OnChatLinkClick); + chatLinkPayload = PluginInterface.AddChatLinkHandler(0, OnChatLinkClick); // event hooks - pi.UiBuilder.Draw += DrawUI; - pi.UiBuilder.OpenConfigUi += DrawConfigUI; - framework.Update += OnUpdateEvent; - clientState.Login += OnLogin; + PluginInterface.UiBuilder.Draw += DrawUI; + PluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI; + Framework.Update += OnUpdateEvent; + ClientState.Login += OnLogin; // paissa setup wardObserver = new WardObserver(this); lotteryObserver = new LotteryObserver(this); - PaissaClient = new PaissaClient(clientState, chat, log); + PaissaClient = new PaissaClient(this); PaissaClient.OnPlotOpened += OnPlotOpened; PaissaClient.OnPlotUpdate += OnPlotUpdate; @@ -149,25 +134,30 @@ private void OnUpdateEvent(IFramework f) { /// private void OnPlotOpened(object sender, PlotOpenedEventArgs e) { if (e.PlotDetail == null) return; - bool notifEnabled = Utils.ConfigEnabledForPlot(this, e.PlotDetail.world_id, e.PlotDetail.district_id, e.PlotDetail.size, e.PlotDetail.purchase_system); + bool notifEnabled = Utils.ConfigEnabledForPlot(this, e.PlotDetail.world_id, e.PlotDetail.district_id, + e.PlotDetail.size, e.PlotDetail.purchase_system); if (!notifEnabled) return; // we only notify on PlotOpen if the purchase type is FCFS or we know it is available - if (!((e.PlotDetail.purchase_system & PurchaseSystem.Lottery) == 0 || e.PlotDetail.lotto_phase == AvailabilityType.Available)) return; + if (!((e.PlotDetail.purchase_system & PurchaseSystem.Lottery) == 0 || + e.PlotDetail.lotto_phase == AvailabilityType.Available)) return; World eventWorld = Worlds.GetRow(e.PlotDetail.world_id); - OnFoundOpenHouse(e.PlotDetail.world_id, e.PlotDetail.district_id, e.PlotDetail.ward_number, e.PlotDetail.plot_number, e.PlotDetail.price, + OnFoundOpenHouse(e.PlotDetail.world_id, e.PlotDetail.district_id, e.PlotDetail.ward_number, + e.PlotDetail.plot_number, e.PlotDetail.price, $"New plot available for purchase on {eventWorld?.Name}: "); } private void OnPlotUpdate(object sender, PlotUpdateEventArgs e) { if (e.PlotUpdate == null) return; - bool notifEnabled = Utils.ConfigEnabledForPlot(this, e.PlotUpdate.world_id, e.PlotUpdate.district_id, e.PlotUpdate.size, e.PlotUpdate.purchase_system); + bool notifEnabled = Utils.ConfigEnabledForPlot(this, e.PlotUpdate.world_id, e.PlotUpdate.district_id, + e.PlotUpdate.size, e.PlotUpdate.purchase_system); if (!notifEnabled) return; // we only notify on PlotUpdate if the purchase type is lottery and it is available now and was not before if (!((e.PlotUpdate.purchase_system & PurchaseSystem.Lottery) == PurchaseSystem.Lottery && e.PlotUpdate.previous_lotto_phase != AvailabilityType.Available && e.PlotUpdate.lotto_phase == AvailabilityType.Available)) return; World eventWorld = Worlds.GetRow(e.PlotUpdate.world_id); - OnFoundOpenHouse(e.PlotUpdate.world_id, e.PlotUpdate.district_id, e.PlotUpdate.ward_number, e.PlotUpdate.plot_number, e.PlotUpdate.price, + OnFoundOpenHouse(e.PlotUpdate.world_id, e.PlotUpdate.district_id, e.PlotUpdate.ward_number, + e.PlotUpdate.plot_number, e.PlotUpdate.price, $"New plot available for purchase on {eventWorld?.Name}: "); } @@ -175,15 +165,19 @@ private void OnPlotUpdate(object sender, PlotUpdateEventArgs e) { /// /// Display the details of an open plot in the user's preferred format. /// - internal void OnFoundOpenHouse(uint worldId, uint territoryTypeId, int wardNumber, int plotNumber, uint? price, string messagePrefix = "") { + internal void OnFoundOpenHouse(uint worldId, uint territoryTypeId, int wardNumber, int plotNumber, uint? price, + string messagePrefix = "") { PlaceName place = Territories.GetRow(territoryTypeId)?.PlaceName.Value; // languages like German do not use NameNoArticle (#2) - Lumina.Text.SeString districtName = place?.NameNoArticle.RawString.Length > 0 ? place.NameNoArticle : place?.Name; + Lumina.Text.SeString districtName = + place?.NameNoArticle.RawString.Length > 0 ? place.NameNoArticle : place?.Name; Lumina.Text.SeString worldName = Worlds.GetRow(worldId)?.Name; HousingLandSet landSet = HousingLandSets.GetRow(Utils.TerritoryTypeIdToLandSetId(territoryTypeId)); byte? houseSize = landSet?.PlotSize[plotNumber]; - uint realPrice = price.GetValueOrDefault(landSet?.InitialPrice[plotNumber] ?? 0); // if price is null, it's probably the default price (landupdate) + uint realPrice = + price.GetValueOrDefault(landSet?.InitialPrice[plotNumber] ?? + 0); // if price is null, it's probably the default price (landupdate) string districtNameNoSpaces = districtName?.ToString().Replace(" ", ""); int wardNum = wardNumber + 1; @@ -198,17 +192,21 @@ internal void OnFoundOpenHouse(uint worldId, uint territoryTypeId, int wardNumbe string output; switch (Configuration.OutputFormat) { case OutputFormat.Pings: - output = $"{messagePrefix}@{houseSizeName}{districtNameNoSpaces} {wardNum}-{plotNum} ({housePriceMillions:F3}m)"; + output = + $"{messagePrefix}@{houseSizeName}{districtNameNoSpaces} {wardNum}-{plotNum} ({housePriceMillions:F3}m)"; break; case OutputFormat.Custom: var template = $"{messagePrefix}{Configuration.OutputFormatString}"; - output = Utils.FormatCustomOutputString(template, districtName?.ToString(), districtNameNoSpaces, worldName, wardNum.ToString(), + output = Utils.FormatCustomOutputString(template, districtName?.ToString(), districtNameNoSpaces, + worldName, wardNum.ToString(), plotNum.ToString(), realPrice.ToString(), housePriceMillions.ToString("F3"), houseSizeName); break; default: - output = $"{messagePrefix}{districtName} {wardNum}-{plotNum} ({houseSizeName}, {housePriceMillions:F3}m)"; + output = + $"{messagePrefix}{districtName} {wardNum}-{plotNum} ({houseSizeName}, {housePriceMillions:F3}m)"; break; } + SendChatToConfiguredChannel(output); } @@ -230,4 +228,4 @@ private void DrawConfigUI() { ui.SettingsVisible = true; } } -} +} \ No newline at end of file diff --git a/packages.lock.json b/packages.lock.json index 7395c0f..0c68f30 100644 --- a/packages.lock.json +++ b/packages.lock.json @@ -1,12 +1,12 @@ { "version": 1, "dependencies": { - "net7.0-windows7.0": { + "net8.0-windows7.0": { "DalamudPackager": { "type": "Direct", - "requested": "[2.1.12, )", - "resolved": "2.1.12", - "contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg==" + "requested": "[2.1.13, )", + "resolved": "2.1.13", + "contentHash": "rMN1omGe8536f4xLMvx9NwfvpAc9YFFfeXJ1t4P4PE6Gu8WCIoFliR1sh07hM+bfODmesk/dvMbji7vNI+B/pQ==" }, "DebounceThrottle": { "type": "Direct",