diff --git a/AutoSweep.csproj b/AutoSweep.csproj
index d3d72eb..ef5aaf7 100644
--- a/AutoSweep.csproj
+++ b/AutoSweep.csproj
@@ -14,7 +14,7 @@
true
true
autoSweep
- 1.4.2.0
+ 1.4.3.0
@@ -68,7 +68,7 @@
-
+
diff --git a/Paissa/Client.cs b/Paissa/Client.cs
index a436ebf..debe885 100644
--- a/Paissa/Client.cs
+++ b/Paissa/Client.cs
@@ -6,10 +6,8 @@
using System.Text;
using System.Threading.Tasks;
using AutoSweep.Structures;
-using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Objects.SubKinds;
-using Dalamud.Game.Gui;
-using Dalamud.Logging;
+using Dalamud.Plugin.Services;
using DebounceThrottle;
using Newtonsoft.Json;
using WebSocketSharp;
@@ -22,8 +20,9 @@ public class PaissaClient : IDisposable {
private string sessionToken;
// dalamud
- private readonly ClientState clientState;
- private readonly ChatGui chat;
+ private readonly IClientState clientState;
+ private readonly IChatGui chat;
+ private readonly IPluginLog log;
// ingest debounce
private readonly DebounceDispatcher ingestDebounceDispatcher = new DebounceDispatcher(1200);
@@ -42,9 +41,10 @@ public class PaissaClient : IDisposable {
public event EventHandler OnPlotUpdate;
public event EventHandler OnPlotSold;
- public PaissaClient(ClientState clientState, ChatGui chatGui) {
+ public PaissaClient(IClientState clientState, IChatGui chatGui, IPluginLog log) {
this.clientState = clientState;
chat = chatGui;
+ this.log = log;
http = new HttpClient();
ReconnectWS();
}
@@ -61,21 +61,22 @@ public void Dispose() {
///
public async Task Hello() {
PlayerCharacter player = clientState.LocalPlayer;
- if (player == null)
- return;
+ if (player == null) return;
+ var homeworld = player.HomeWorld.GameData;
+ if (homeworld == null) return;
var charInfo = new Dictionary {
{ "cid", clientState.LocalContentId },
{ "name", player.Name.ToString() },
- { "world", player.HomeWorld.GameData.Name.ToString() },
+ { "world", homeworld.Name.ToString() },
{ "worldId", player.HomeWorld.Id }
};
string content = JsonConvert.SerializeObject(charInfo);
- PluginLog.Debug(content);
+ log.Debug(content);
var response = await Post("/hello", content, false);
if (response.IsSuccessStatusCode) {
string respText = await response.Content.ReadAsStringAsync();
sessionToken = JsonConvert.DeserializeObject(respText).session_token;
- PluginLog.Log("Completed PaissaDB HELLO");
+ log.Info("Completed PaissaDB HELLO");
}
}
@@ -123,7 +124,7 @@ 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}");
- PluginLog.Debug($"GET {apiBase}/worlds/{worldId}/{districtId} returned {response.StatusCode} ({response.ReasonPhrase})");
+ log.Debug($"GET {apiBase}/worlds/{worldId}/{districtId} returned {response.StatusCode} ({response.ReasonPhrase})");
response.EnsureSuccessStatusCode();
string respText = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject(respText);
@@ -135,7 +136,7 @@ private void queueIngest(object data) {
ingestDebounceDispatcher.Debounce(() => {
string bulkIngestData = JsonConvert.SerializeObject(ingestDataQueue);
PostFireAndForget("/ingest", bulkIngestData);
- PluginLog.Debug($"Bulk ingesting {ingestDataQueue.Count} entries ({bulkIngestData.Length}B)");
+ log.Debug($"Bulk ingesting {ingestDataQueue.Count} entries ({bulkIngestData.Length}B)");
ingestDataQueue.Clear();
});
}
@@ -146,13 +147,13 @@ private async void PostFireAndForget(string route, string content, bool auth = t
private async Task Post(string route, string content, bool auth = true, ushort retries = 5) {
HttpResponseMessage response = null;
- PluginLog.Verbose(content);
+ log.Verbose(content);
for (var i = 0; i < retries; i++) {
HttpRequestMessage request;
if (auth) {
if (sessionToken == null) {
- PluginLog.LogWarning("Trying to send authed request but no session token!");
+ log.Warning("Trying to send authed request but no session token!");
await Hello();
continue;
}
@@ -169,20 +170,20 @@ private async Task Post(string route, string content, bool
}
try {
response = await http.SendAsync(request);
- PluginLog.Debug($"{request.Method} {request.RequestUri} returned {response.StatusCode} ({response.ReasonPhrase})");
+ log.Debug($"{request.Method} {request.RequestUri} returned {response.StatusCode} ({response.ReasonPhrase})");
if (!response.IsSuccessStatusCode) {
string respText = await response.Content.ReadAsStringAsync();
- PluginLog.Warning($"{request.Method} {request.RequestUri} returned {response.StatusCode} ({response.ReasonPhrase}):\n{respText}");
+ log.Warning($"{request.Method} {request.RequestUri} returned {response.StatusCode} ({response.ReasonPhrase}):\n{respText}");
} else {
break;
}
} catch (Exception e) {
- PluginLog.Warning(e, $"{request.Method} {request.RequestUri} raised an error:");
+ log.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);
- PluginLog.Warning($"Request {i} failed, waiting for {toDelay}ms before retry...");
+ log.Warning($"Request {i} failed, waiting for {toDelay}ms before retry...");
await Task.Delay(toDelay);
}
}
@@ -214,17 +215,17 @@ private void ReconnectWS() {
// todo what is happening here?
// https://github.com/zhudotexe/FFXIV_PaissaHouse/issues/14
}
- PluginLog.Debug("ReconnectWS complete");
+ log.Debug("ReconnectWS complete");
});
}
private void OnWSOpen(object sender, EventArgs e) {
- PluginLog.Information("WebSocket connected");
+ log.Information("WebSocket connected");
}
private void OnWSMessage(object sender, MessageEventArgs e) {
if (!e.IsText) return;
- PluginLog.Verbose($">>>> R: {e.Data}");
+ log.Verbose($">>>> R: {e.Data}");
var message = JsonConvert.DeserializeObject(e.Data);
switch (message.Type) {
case "plot_open":
@@ -239,20 +240,20 @@ private void OnWSMessage(object sender, MessageEventArgs e) {
case "ping":
break;
default:
- PluginLog.Warning($"Got unknown WS message: {e.Data}");
+ log.Warning($"Got unknown WS message: {e.Data}");
break;
}
}
private void OnWSClose(object sender, CloseEventArgs e) {
- PluginLog.Information($"WebSocket closed ({e.Code}: {e.Reason})");
+ log.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) {
- PluginLog.LogWarning(e.Exception, $"WebSocket error: {e.Message}");
+ log.Warning(e.Exception, $"WebSocket error: {e.Message}");
if (!disposed)
WSReconnectSoon();
}
@@ -260,7 +261,7 @@ private void OnWSError(object sender, ErrorEventArgs e) {
private void WSReconnectSoon() {
if (ws.IsAlive) return;
int t = new Random().Next(5_000, 15_000);
- PluginLog.Warning($"WebSocket closed unexpectedly: will reconnect to socket in {t / 1000f:F3} seconds");
+ log.Warning($"WebSocket closed unexpectedly: will reconnect to socket in {t / 1000f:F3} seconds");
Task.Run(async () => await Task.Delay(t)).ContinueWith(_ => {
if (!disposed) ReconnectWS();
});
diff --git a/Paissa/LotteryObserver.cs b/Paissa/LotteryObserver.cs
index 60b7f82..ae8d467 100644
--- a/Paissa/LotteryObserver.cs
+++ b/Paissa/LotteryObserver.cs
@@ -1,7 +1,6 @@
using System;
using AutoSweep.Structures;
using Dalamud.Hooking;
-using Dalamud.Logging;
using Dalamud.Utility.Signatures;
using Lumina.Excel.GeneratedSheets;
using Lumina.Text;
@@ -22,16 +21,16 @@ long a8
);
[Signature("E8 ?? ?? ?? ?? 48 8B B4 24 ?? ?? ?? ?? 48 8B 6C 24 ?? E9", DetourName = nameof(OnPlacardSaleInfo))]
- private Hook? PlacardSaleInfoHook { get; init; }
+ private Hook? placardSaleInfoHook;
public LotteryObserver(Plugin plugin) {
- SignatureHelper.Initialise(this);
this.plugin = plugin;
- PlacardSaleInfoHook?.Enable();
+ plugin.InteropProvider.InitializeFromAttributes(this);
+ placardSaleInfoHook?.Enable();
}
public void Dispose() {
- PlacardSaleInfoHook?.Dispose();
+ placardSaleInfoHook?.Dispose();
}
public void OnPlacardSaleInfo(
@@ -44,7 +43,7 @@ public void OnPlacardSaleInfo(
IntPtr placardSaleInfoPtr,
long a8
) {
- PlacardSaleInfoHook!.Original(agentBase, housingType, territoryTypeId, wardId, plotId, apartmentNumber, placardSaleInfoPtr, a8);
+ placardSaleInfoHook!.Original(agentBase, housingType, territoryTypeId, wardId, plotId, apartmentNumber, placardSaleInfoPtr, a8);
// if the plot is owned, ignore it
if (housingType != HousingType.UnownedHouse) return;
@@ -52,10 +51,11 @@ long a8
PlacardSaleInfo saleInfo = PlacardSaleInfo.Read(placardSaleInfoPtr);
- PluginLog.LogDebug(
+ plugin.PluginLog.Debug(
$"Got PlacardSaleInfo: PurchaseType={saleInfo.PurchaseType}, TenantType={saleInfo.TenantType}, available={saleInfo.AvailabilityType}, until={saleInfo.PhaseEndsAt}, numEntries={saleInfo.EntryCount}");
- PluginLog.LogDebug($"unknown1={saleInfo.Unknown1}, unknown2={saleInfo.Unknown2}, unknown3={saleInfo.Unknown3}, unknown4={BitConverter.ToString(saleInfo.Unknown4)}");
- PluginLog.LogDebug(
+ plugin.PluginLog.Debug(
+ $"unknown1={saleInfo.Unknown1}, unknown2={saleInfo.Unknown2}, unknown3={saleInfo.Unknown3}, unknown4={BitConverter.ToString(saleInfo.Unknown4)}");
+ 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
@@ -64,7 +64,7 @@ long a8
SeString place = plugin.Territories.GetRow(territoryTypeId)?.PlaceName.Value?.Name;
SeString worldName = world.Name;
- PluginLog.LogInformation($"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 bcf9081..69c3249 100644
--- a/Paissa/Utils.cs
+++ b/Paissa/Utils.cs
@@ -1,5 +1,3 @@
-using Dalamud.Game.ClientState;
-using Dalamud.Logging;
using Lumina.Excel.GeneratedSheets;
namespace AutoSweep.Paissa {
@@ -46,7 +44,7 @@ public static bool ConfigEnabledForPlot(Plugin plugin, ushort worldId, ushort di
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.DatacenterNotifs && eventWorld?.DataCenter.Row == plugin.ClientState.LocalPlayer?.HomeWorld.GameData?.DataCenter.Row))
return false;
// get the district config
DistrictNotifConfig districtNotifs;
@@ -67,7 +65,6 @@ public static bool ConfigEnabledForPlot(Plugin plugin, ushort worldId, ushort di
districtNotifs = plugin.Configuration.Empyrean;
break;
default:
- PluginLog.Warning($"Unknown district in plot open event: {districtId}");
return false;
}
// what about house sizes in this district?
@@ -83,7 +80,6 @@ public static bool ConfigEnabledForPlot(Plugin plugin, ushort worldId, ushort di
notifEnabled = districtNotifs.Large;
break;
default:
- PluginLog.Warning($"Unknown plot size in plot open event: {size}");
return false;
}
// and FC/individual purchase?
diff --git a/Paissa/WardObserver.cs b/Paissa/WardObserver.cs
index 4ba06c6..e60c04e 100644
--- a/Paissa/WardObserver.cs
+++ b/Paissa/WardObserver.cs
@@ -1,23 +1,44 @@
using System;
using System.Runtime.InteropServices;
using AutoSweep.Structures;
-using Dalamud.Logging;
+using Dalamud.Hooking;
+using Dalamud.Utility.Signatures;
using Lumina.Text;
namespace AutoSweep.Paissa {
- public class WardObserver {
+ public unsafe class WardObserver {
private Plugin plugin;
internal readonly SweepState SweepState;
+ private delegate void HandleHousingWardInfoDelegate(
+ void* agentBase,
+ IntPtr housingWardInfoPtr
+ );
+
+ [Signature("40 55 57 41 54 41 56 41 57 48 8D AC 24 ?? ?? ?? ?? B8", DetourName = nameof(OnHousingWardInfo))]
+ private Hook? housingWardInfoHook;
+
public WardObserver(Plugin plugin) {
this.plugin = plugin;
+ plugin.InteropProvider.InitializeFromAttributes(this);
SweepState = new SweepState(Utils.NumWardsPerDistrict);
+ housingWardInfoHook?.Enable();
}
- public void OnHousingWardInfo(IntPtr dataPtr) {
+ public void Dispose() {
+ housingWardInfoHook?.Dispose();
+ }
+
+ public void OnHousingWardInfo(
+ void* agentBase,
+ IntPtr dataPtr
+ ) {
+ housingWardInfoHook!.Original(agentBase, dataPtr);
+
+ if (!plugin.Configuration.Enabled) return;
HousingWardInfo wardInfo = HousingWardInfo.Read(dataPtr);
int serverTimestamp = Marshal.ReadInt32(dataPtr - 0x8);
- PluginLog.LogDebug($"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
@@ -34,7 +55,7 @@ public void OnHousingWardInfo(IntPtr dataPtr) {
// if we've seen this ward already, ignore it
if (SweepState.Contains(wardInfo)) {
- PluginLog.LogDebug($"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;
}
@@ -47,7 +68,7 @@ public void OnHousingWardInfo(IntPtr dataPtr) {
// if that's all the wards, display the district summary and thanks
if (SweepState.IsComplete) OnFinishedDistrictSweep(wardInfo);
- PluginLog.LogDebug($"Done processing HousingWardInfo for ward: {wardInfo.LandIdent.WardNumber}");
+ plugin.PluginLog.Debug($"Done processing HousingWardInfo for ward: {wardInfo.LandIdent.WardNumber}");
}
///
diff --git a/Plugin.cs b/Plugin.cs
index e4f773c..022d193 100644
--- a/Plugin.cs
+++ b/Plugin.cs
@@ -1,19 +1,13 @@
-using System;
-using System.Diagnostics;
+using System.Diagnostics;
using System.Threading.Tasks;
using AutoSweep.Paissa;
using AutoSweep.Structures;
-using Dalamud.Data;
-using Dalamud.Game;
-using Dalamud.Game.ClientState;
using Dalamud.Game.Command;
-using Dalamud.Game.Gui;
-using Dalamud.Game.Network;
using Dalamud.Game.Text;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
-using Dalamud.Logging;
using Dalamud.Plugin;
+using Dalamud.Plugin.Services;
using Lumina.Excel;
using Lumina.Excel.GeneratedSheets;
@@ -23,13 +17,13 @@ public class Plugin : IDalamudPlugin {
// frameworks/data
internal readonly DalamudPluginInterface PluginInterface;
- internal readonly ChatGui Chat;
- internal readonly ClientState ClientState;
- internal readonly CommandManager Commands;
internal readonly Configuration Configuration;
- internal readonly DataManager Data;
- internal readonly Framework Framework;
- internal readonly GameNetwork Network;
+ 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;
@@ -45,20 +39,21 @@ public class Plugin : IDalamudPlugin {
public Plugin(
DalamudPluginInterface pi,
- ChatGui chat,
- GameNetwork network,
- DataManager data,
- CommandManager commands,
- ClientState clientState,
- Framework framework
+ IChatGui chat,
+ IDataManager data,
+ ICommandManager commands,
+ IClientState clientState,
+ IFramework framework,
+ IPluginLog log,
+ IGameInteropProvider interopProvider
) {
PluginInterface = pi;
Chat = chat;
- Network = network;
- Data = data;
Commands = commands;
ClientState = clientState;
Framework = framework;
+ PluginLog = log;
+ InteropProvider = interopProvider;
// setup
Configuration = pi.GetPluginConfig() as Configuration ?? new Configuration();
@@ -78,7 +73,6 @@ Framework framework
chatLinkPayload = pi.AddChatLinkHandler(0, OnChatLinkClick);
// event hooks
- network.NetworkMessage += OnNetworkEvent;
pi.UiBuilder.Draw += DrawUI;
pi.UiBuilder.OpenConfigUi += DrawConfigUI;
framework.Update += OnUpdateEvent;
@@ -87,16 +81,15 @@ Framework framework
// paissa setup
wardObserver = new WardObserver(this);
lotteryObserver = new LotteryObserver(this);
- PaissaClient = new PaissaClient(clientState, chat);
+ PaissaClient = new PaissaClient(clientState, chat, log);
PaissaClient.OnPlotOpened += OnPlotOpened;
PaissaClient.OnPlotUpdate += OnPlotUpdate;
- PluginLog.LogDebug($"Initialization complete: configVersion={Configuration.Version}");
+ PluginLog.Debug($"Initialization complete: configVersion={Configuration.Version}");
}
public void Dispose() {
ui.Dispose();
- Network.NetworkMessage -= OnNetworkEvent;
Framework.Update -= OnUpdateEvent;
ClientState.Login -= OnLogin;
Commands.RemoveHandler(Utils.CommandName);
@@ -104,6 +97,7 @@ public void Dispose() {
PluginInterface.RemoveChatLinkHandler();
PaissaClient?.Dispose();
lotteryObserver.Dispose();
+ wardObserver.Dispose();
}
// ==== dalamud events ====
@@ -138,25 +132,17 @@ private void OnChatLinkClick(uint cmdId, SeString seString) {
});
}
- private void OnLogin(object _, EventArgs __) {
+ private void OnLogin() {
clientNeedsHello = true;
}
- private void OnUpdateEvent(Framework f) {
+ private void OnUpdateEvent(IFramework f) {
if (clientNeedsHello && ClientState?.LocalPlayer != null && PaissaClient != null) {
clientNeedsHello = false;
Task.Run(async () => await PaissaClient.Hello());
}
}
- private void OnNetworkEvent(IntPtr dataPtr, ushort opCode, uint sourceActorId, uint targetActorId, NetworkMessageDirection direction) {
- if (!Configuration.Enabled) return;
- if (direction != NetworkMessageDirection.ZoneDown) return;
- if (!Data.IsDataReady) return;
- if (opCode == Data.ServerOpCodes["HousingWardInfo"]) wardObserver.OnHousingWardInfo(dataPtr);
- }
-
-
// ==== paissa events ====
///
/// Hook to call when a new plot open event is received over the websocket.
@@ -228,7 +214,7 @@ internal void OnFoundOpenHouse(uint worldId, uint territoryTypeId, int wardNumbe
// ==== helpers ====
private void SendChatToConfiguredChannel(string message) {
- Chat.PrintChat(new XivChatEntry {
+ Chat.Print(new XivChatEntry {
Name = "[PaissaHouse]",
Message = message,
Type = Configuration.ChatType
diff --git a/packages.lock.json b/packages.lock.json
index 3b2a6e2..7395c0f 100644
--- a/packages.lock.json
+++ b/packages.lock.json
@@ -4,9 +4,9 @@
"net7.0-windows7.0": {
"DalamudPackager": {
"type": "Direct",
- "requested": "[2.1.11, )",
- "resolved": "2.1.11",
- "contentHash": "9qlAWoRRTiL/geAvuwR/g6Bcbrd/bJJgVnB/RurBiyKs6srsP0bvpoo8IK+Eg8EA6jWeM6/YJWs66w4FIAzqPw=="
+ "requested": "[2.1.12, )",
+ "resolved": "2.1.12",
+ "contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg=="
},
"DebounceThrottle": {
"type": "Direct",