Skip to content

Commit

Permalink
feat: update for 7.0
Browse files Browse the repository at this point in the history
  • Loading branch information
zhudotexe committed Jun 30, 2024
1 parent 72e1a41 commit e926424
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 126 deletions.
6 changes: 3 additions & 3 deletions AutoSweep.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<Title>autoSweep</Title>
<Authors>zhudotexe</Authors>
<TargetFramework>net7.0-windows</TargetFramework>
<TargetFramework>net8.0-windows</TargetFramework>
<LangVersion>9</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Deterministic>true</Deterministic>
Expand All @@ -14,7 +14,7 @@
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<AssemblyName>autoSweep</AssemblyName>
<AssemblyVersion>1.4.3.0</AssemblyVersion>
<AssemblyVersion>1.4.4.0</AssemblyVersion>
</PropertyGroup>

<PropertyGroup>
Expand Down Expand Up @@ -68,7 +68,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="DalamudPackager" Version="2.1.12" />
<PackageReference Include="DalamudPackager" Version="2.1.13" />
<PackageReference Include="DebounceThrottle" Version="2.0.0" />
<PackageReference Include="WebSocketSharp.Standard" Version="1.0.3" />
</ItemGroup>
Expand Down
12 changes: 2 additions & 10 deletions Configuration.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -64,4 +56,4 @@ public enum OutputFormat {

Custom = 4
}
}
}
84 changes: 48 additions & 36 deletions Paissa/Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -41,10 +39,8 @@ public class PaissaClient : IDisposable {
public event EventHandler<PlotUpdateEventArgs> OnPlotUpdate;
public event EventHandler<PlotSoldEventArgs> 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();
}
Expand All @@ -60,23 +56,23 @@ public void Dispose() {
/// Make a POST request to register the current character's content ID.
/// </summary>
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<string, object> {
{ "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<HelloResponse>(respText).session_token;
log.Info("Completed PaissaDB HELLO");
Plugin.PluginLog.Info("Completed PaissaDB HELLO");
}
}

Expand All @@ -99,7 +95,8 @@ public void PostWardInfo(HousingWardInfo wardInfo, int serverTimestamp) {
/// <summary>
/// Fire and forget a POST request for a placard's lottery info.
/// </summary>
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<string, object> {
{ "event_type", "LOTTERY_INFO" },
{ "client_timestamp", new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds() },
Expand All @@ -124,7 +121,8 @@ public void PostLotteryInfo(uint worldId, ushort districtId, ushort wardId, usho
/// <returns>The DistrictDetail</returns>
public async Task<DistrictDetail> 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<DistrictDetail>(respText);
Expand All @@ -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();
});
}
Expand All @@ -145,55 +143,66 @@ private async void PostFireAndForget(string route, string content, bool auth = t
await Post(route, content, auth, retries);
}

private async Task<HttpResponseMessage> Post(string route, string content, bool auth = true, ushort retries = 5) {
private async Task<HttpResponseMessage>
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;
}

Expand All @@ -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<WSMessage>(e.Data);
switch (message.Type) {
case "plot_open":
Expand All @@ -240,31 +251,32 @@ 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();
}

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();
});
}
}
}
}
15 changes: 8 additions & 7 deletions Paissa/LotteryObserver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<HandlePlacardSaleInfoDelegate>? placardSaleInfoHook;

public LotteryObserver(Plugin plugin) {
this.plugin = plugin;
plugin.InteropProvider.InitializeFromAttributes(this);
Plugin.InteropProvider.InitializeFromAttributes(this);
placardSaleInfoHook?.Enable();
}

Expand All @@ -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);
}
Expand Down
15 changes: 10 additions & 5 deletions Paissa/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -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;
}
}
}
}
Loading

0 comments on commit e926424

Please sign in to comment.