From 44e4e8e1410a9f2fff89d6e14692cdd65bf58c6c Mon Sep 17 00:00:00 2001 From: ditzy Date: Sun, 11 Aug 2024 02:41:35 -0500 Subject: [PATCH] automatically update the turtle session when a mark is detected --- ScoutHelper/Config/Configuration.cs | 1 + ScoutHelper/Localization/Strings.Designer.cs | 11 +---- ScoutHelper/Localization/Strings.resx | 5 +-- ScoutHelper/Managers/HuntHelperManager.cs | 43 ++++++++++++++++++- ScoutHelper/Managers/TurtleManager.cs | 22 ++++++++-- .../Models/Http/TurtleTrainUpdateRequest.cs | 6 +++ ScoutHelper/Utils/HttpUtils.cs | 14 ++++-- ScoutHelper/Utils/XivExtensions.cs | 12 ++++++ ScoutHelper/Windows/MainWindow.cs | 32 +++++++------- 9 files changed, 108 insertions(+), 38 deletions(-) diff --git a/ScoutHelper/Config/Configuration.cs b/ScoutHelper/Config/Configuration.cs index 031d3c0..b33747e 100644 --- a/ScoutHelper/Config/Configuration.cs +++ b/ScoutHelper/Config/Configuration.cs @@ -29,6 +29,7 @@ public class Configuration : IPluginConfiguration { public string TurtleApiBaseUrl = "https://scout.wobbuffet.net"; public string TurtleApiTrainPath = "/api/v1/scout"; public TimeSpan TurtleApiTimeout = TimeSpan.FromSeconds(5); + public bool IncludeNameInTurtleSession = true; public string CopyTemplate = Constants.DefaultCopyTemplate; public bool IsCopyModeFullText = false; diff --git a/ScoutHelper/Localization/Strings.Designer.cs b/ScoutHelper/Localization/Strings.Designer.cs index 3866f07..99b8cf0 100644 --- a/ScoutHelper/Localization/Strings.Designer.cs +++ b/ScoutHelper/Localization/Strings.Designer.cs @@ -358,16 +358,7 @@ internal static string TurtleCollabButton { } /// - /// Looks up a localized string similar to click to leave the current session.. - /// - internal static string TurtleCollabButtonActiveTooltip { - get { - return ResourceManager.GetString("TurtleCollabButtonActiveTooltip", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to start or join a collaborative scouting session on turtle.. + /// Looks up a localized string similar to manage or join a collaborative turtle session.. /// internal static string TurtleCollabButtonTooltip { get { diff --git a/ScoutHelper/Localization/Strings.resx b/ScoutHelper/Localization/Strings.resx index a174d14..1b46a65 100644 --- a/ScoutHelper/Localization/Strings.resx +++ b/ScoutHelper/Localization/Strings.resx @@ -112,10 +112,7 @@ COLLAB - start or join a collaborative scouting session on turtle. - - - click to leave the current session. + manage or join a collaborative turtle session. INSTANCES diff --git a/ScoutHelper/Managers/HuntHelperManager.cs b/ScoutHelper/Managers/HuntHelperManager.cs index b7f3757..a6c545b 100644 --- a/ScoutHelper/Managers/HuntHelperManager.cs +++ b/ScoutHelper/Managers/HuntHelperManager.cs @@ -4,8 +4,10 @@ using ScoutHelper.Models; using System; using System.Collections.Generic; +using System.Threading.Tasks; using Dalamud.Plugin; using Dalamud.Plugin.Services; +using ScoutHelper.Utils; namespace ScoutHelper.Managers; @@ -13,29 +15,42 @@ public class HuntHelperManager : IDisposable { private const uint SupportedVersion = 1; private readonly IPluginLog _log; + private readonly IChatGui _chat; + private readonly TurtleManager _turtleManager; private readonly ICallGateSubscriber _cgGetVersion; private readonly ICallGateSubscriber _cgEnable; private readonly ICallGateSubscriber _cgDisable; private readonly ICallGateSubscriber> _cgGetTrainList; + private readonly ICallGateSubscriber _cgMarkSeen; public bool Available { get; private set; } = false; - public HuntHelperManager(IDalamudPluginInterface pluginInterface, IPluginLog log) { + public HuntHelperManager( + IDalamudPluginInterface pluginInterface, + IPluginLog log, + IChatGui chat, + TurtleManager turtleManager + ) { _log = log; + _chat = chat; + _turtleManager = turtleManager; _cgGetVersion = pluginInterface.GetIpcSubscriber("HH.GetVersion"); _cgEnable = pluginInterface.GetIpcSubscriber("HH.Enable"); _cgDisable = pluginInterface.GetIpcSubscriber("HH.Disable"); _cgGetTrainList = pluginInterface.GetIpcSubscriber>("HH.GetTrainList"); + _cgMarkSeen = pluginInterface.GetIpcSubscriber("HH.channel.MarkSeen"); CheckVersion(); _cgEnable.Subscribe(OnEnable); _cgDisable.Subscribe(OnDisable); + _cgMarkSeen.Subscribe(OnMarkSeen); } public void Dispose() { _cgEnable.Unsubscribe(OnEnable); _cgDisable.Unsubscribe(OnDisable); + _cgMarkSeen.Unsubscribe(OnMarkSeen); } private void OnEnable(uint version) { @@ -67,6 +82,32 @@ private void CheckVersion(uint? version = null) { } } + private void OnMarkSeen(TrainMob mark) { + if (!_turtleManager.IsTurtleCollabbing) return; + + _turtleManager.UpdateCurrentSession(mark.AsSingletonList()) + .ContinueWith( + task => { + switch (task.Result) { + case TurtleHttpStatus.Success: + _chat.TaggedPrint($"added {mark.Name} to the turtle session."); + break; + case TurtleHttpStatus.NoSupportedMobs: + _chat.TaggedPrint($"{mark.Name} was seen, but is not supported by turtle and will not be added to the session."); + break; + case TurtleHttpStatus.HttpError: + _chat.TaggedPrintError($"something went wrong when adding {mark.Name} to the turtle session ;-;."); + break; + } + }, + TaskContinuationOptions.OnlyOnRanToCompletion + ) + .ContinueWith( + task => _log.Error(task.Exception, "failed to update turtle session"), + TaskContinuationOptions.OnlyOnFaulted + ); + } + public Result, string> GetTrainList() { if (!Available) { return "Hunt Helper is not currently available ;-;"; diff --git a/ScoutHelper/Managers/TurtleManager.cs b/ScoutHelper/Managers/TurtleManager.cs index 1349394..73a025e 100644 --- a/ScoutHelper/Managers/TurtleManager.cs +++ b/ScoutHelper/Managers/TurtleManager.cs @@ -3,12 +3,11 @@ using System.IO; using System.Linq; using System.Net.Http; -using System.Numerics; -using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using CSharpFunctionalExtensions; using Dalamud.Plugin.Services; +using Dalamud.Utility; using Newtonsoft.Json; using ScoutHelper.Config; using ScoutHelper.Models; @@ -27,10 +26,11 @@ namespace ScoutHelper.Managers; public partial class TurtleManager { [GeneratedRegex(@"(?:/scout)?/?(?\w+)/(?\w+)/?\s*$")] private static partial Regex CollabLinkRegex(); + private static HttpClient HttpClient { get; } = new(); private readonly IPluginLog _log; private readonly Configuration _conf; - private static HttpClient HttpClient { get; } = new(); + private readonly IClientState _clientState; private MobDict MobIdToTurtleId { get; } private TerritoryDict TerritoryIdToTurtleData { get; } @@ -38,15 +38,19 @@ public partial class TurtleManager { private string _currentCollabSession = ""; private string _currentCollabPassword = ""; + public bool IsTurtleCollabbing { get; private set; } = false; + public TurtleManager( IPluginLog log, Configuration conf, + IClientState clientState, ScoutHelperOptions options, TerritoryManager territoryManager, MobManager mobManager ) { _log = log; _conf = conf; + _clientState = clientState; HttpClient.BaseAddress = new Uri(_conf.TurtleApiBaseUrl); HttpClient.DefaultRequestHeaders.UserAgent.Add(Constants.UserAgent); @@ -62,9 +66,18 @@ MobManager mobManager _currentCollabSession = match.Groups["session"].Value; _currentCollabPassword = match.Groups["password"].Value; + IsTurtleCollabbing = true; return (_currentCollabSession, _currentCollabPassword); } + public void RejoinLastCollabSession() { + if (_currentCollabSession.IsNullOrEmpty() || _currentCollabPassword.IsNullOrEmpty()) + throw new Exception("cannot rejoin the last turtle collab session as there is no last session."); + IsTurtleCollabbing = true; + } + + public void LeaveCollabSession() => IsTurtleCollabbing = false; + public async Task UpdateCurrentSession(IList train) { var turtleSupportedMobs = train.Where(mob => MobIdToTurtleId.ContainsKey(mob.MobId)).AsList(); if (turtleSupportedMobs.IsEmpty()) @@ -75,6 +88,7 @@ public async Task UpdateCurrentSession(IList train) _log, new TurtleTrainUpdateRequest( _currentCollabPassword, + _clientState.PlayerTag().Where(_ => _conf.IncludeNameInTurtleSession), turtleSupportedMobs.Select( mob => (TerritoryIdToTurtleData[mob.TerritoryId].TurtleId, @@ -83,7 +97,7 @@ public async Task UpdateCurrentSession(IList train) mob.Position) ) ), - requestContent => HttpClient.PutAsync($"{_conf.TurtleApiTrainPath}/{_currentCollabSession}", requestContent) + requestContent => HttpClient.PatchAsync($"{_conf.TurtleApiTrainPath}/{_currentCollabSession}", requestContent) ).TapError( error => { if (error.ErrorType == HttpErrorType.Timeout) { diff --git a/ScoutHelper/Models/Http/TurtleTrainUpdateRequest.cs b/ScoutHelper/Models/Http/TurtleTrainUpdateRequest.cs index 1ac8dc5..8780f51 100644 --- a/ScoutHelper/Models/Http/TurtleTrainUpdateRequest.cs +++ b/ScoutHelper/Models/Http/TurtleTrainUpdateRequest.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using System.Numerics; +using CSharpFunctionalExtensions; using Newtonsoft.Json; namespace ScoutHelper.Models.Http; @@ -8,14 +9,19 @@ namespace ScoutHelper.Models.Http; public record struct TurtleTrainUpdateRequest { [JsonProperty("collaborator_password")] public string CollaboratorPassword { get; } + + [JsonProperty("update_user")] + public string? UpdateUser { get; } [JsonProperty("sightings")] public IList Sightings { get; } public TurtleTrainUpdateRequest( string collaboratorPassword, + Maybe updateUser, IEnumerable<(uint zoneId, uint instance, uint mobId, Vector2 position)> marks ) { CollaboratorPassword = collaboratorPassword; + UpdateUser = updateUser.GetValueOrDefault(); Sightings = marks .Select(mark => new TurtleTrainUpdateMark(mark.zoneId, mark.instance, mark.mobId, mark.position)) .AsList(); diff --git a/ScoutHelper/Utils/HttpUtils.cs b/ScoutHelper/Utils/HttpUtils.cs index 6d073c7..91ec9b2 100644 --- a/ScoutHelper/Utils/HttpUtils.cs +++ b/ScoutHelper/Utils/HttpUtils.cs @@ -10,6 +10,12 @@ namespace ScoutHelper.Utils; public static class HttpUtils { + private static readonly JsonSerializerSettings JsonSerializerSettings = new() { + DateFormatString = "yyyy'-'MM'-'dd'T'HH':'mm':'ssK", + DateTimeZoneHandling = DateTimeZoneHandling.Utc, + NullValueHandling = NullValueHandling.Ignore, + }; + public static Task> DoRequest( IPluginLog log, T requestObject, @@ -31,13 +37,13 @@ public static async Task> DoRequest( Func> requestAction ) { try { - var requestPayload = JsonConvert.SerializeObject(requestObject); - log.Debug("Request body: {0}", requestPayload); + var requestPayload = JsonConvert.SerializeObject(requestObject, JsonSerializerSettings); + log.Debug("Request body: {0:l}", requestPayload); var requestContent = new StringContent(requestPayload, Encoding.UTF8, Constants.MediaTypeJson); var response = await requestAction(requestContent); log.Debug( - "Request: {0}\n\nResponse: {1}", + "Request: {0:l}\n\nResponse: {1:l}", response.RequestMessage!.ToString(), response.ToString() ); @@ -45,7 +51,7 @@ Func> requestAction response.EnsureSuccessStatusCode(); var responseJson = await response.Content.ReadAsStringAsync(); - log.Debug("Response body: {0}", responseJson); + log.Debug("Response body: {0:l}", responseJson); return responseJson; } catch (TimeoutException) { return new HttpError(HttpErrorType.Timeout); diff --git a/ScoutHelper/Utils/XivExtensions.cs b/ScoutHelper/Utils/XivExtensions.cs index bc79651..1ff3aa2 100644 --- a/ScoutHelper/Utils/XivExtensions.cs +++ b/ScoutHelper/Utils/XivExtensions.cs @@ -15,6 +15,18 @@ public static partial class XivExtensions { public static string WorldName(this IClientState clientState) => clientState.LocalPlayer?.CurrentWorld.GameData?.Name.ToString() ?? "Not Found"; + public static Maybe PlayerTag(this IClientState clientState) => + clientState + .LocalPlayer + .AsMaybe() + .Select( + player => { + var playerName = player.Name.TextValue; + var worldName = player.HomeWorld.GameData?.Name?.RawString ?? "Unknown World"; + return $"{playerName}@{worldName}"; + } + ); + public static string PluginFilePath(this IDalamudPluginInterface pluginInterface, string dataFilename) => Path.Combine( pluginInterface.AssemblyLocation.Directory?.FullName!, dataFilename diff --git a/ScoutHelper/Windows/MainWindow.cs b/ScoutHelper/Windows/MainWindow.cs index ed61c1e..4592132 100644 --- a/ScoutHelper/Windows/MainWindow.cs +++ b/ScoutHelper/Windows/MainWindow.cs @@ -5,6 +5,7 @@ using System.Threading.Tasks; using CSharpFunctionalExtensions; using Dalamud.Interface; +using Dalamud.Interface.Components; using Dalamud.Interface.Utility; using Dalamud.Interface.Windowing; using Dalamud.Plugin.Services; @@ -60,7 +61,6 @@ public class MainWindow : Window, IDisposable { private Vector4 _noticeAckButtonColor = DangerFgColor; // turtle stuff - private bool _isTurtleCollabbing = false; private string _collabInput = ""; private string _collabLink = ""; private bool _closeTurtleCollabPopup = false; @@ -348,10 +348,10 @@ private unsafe void DrawTurtleButtons() { ); if (ImGui.IsItemHovered()) ImGuiPlus.CreateTooltip( - _isTurtleCollabbing ? Strings.TurtleButtonActiveCollabTooltip : Strings.TurtleButtonTooltip + _turtleManager.IsTurtleCollabbing ? Strings.TurtleButtonActiveCollabTooltip : Strings.TurtleButtonTooltip ); if (turtlePressed) { - if (_isTurtleCollabbing) { + if (_turtleManager.IsTurtleCollabbing) { PushLatestMobsToTurtle(); } else { _chat.TaggedPrint("Generating Turtle link..."); @@ -368,7 +368,7 @@ private unsafe void DrawTurtleButtons() { ImGui.SameLine(); ImGui.SetCursorPosX(ImGui.GetCursorPosX() - itemSpacing.X + itemSpacing.Y); - var collabColor = _isTurtleCollabbing + var collabColor = _turtleManager.IsTurtleCollabbing ? *ImGui.GetStyleColorVec4(ImGuiCol.ButtonActive) : *ImGui.GetStyleColorVec4(ImGuiCol.Button); var turtleCollabPressed = ImGuiPlus @@ -381,22 +381,25 @@ private unsafe void DrawTurtleButtons() { ImDrawFlags.RoundCornersRight ) ); - if (ImGui.IsItemHovered()) - ImGuiPlus.CreateTooltip( - _isTurtleCollabbing ? Strings.TurtleCollabButtonActiveTooltip : Strings.TurtleCollabButtonTooltip - ); - if (turtleCollabPressed) { - if (_isTurtleCollabbing) _isTurtleCollabbing = false; - else { - ImGui.OpenPopup("turtle collab popup"); - } - } + if (ImGui.IsItemHovered()) ImGuiPlus.CreateTooltip(Strings.TurtleCollabButtonTooltip); + if (turtleCollabPressed) ImGui.OpenPopup("turtle collab popup"); } private void DrawTurtleCollabPopup() { var contentWidth = 1.5f * _buttonSize.Value.X; ImGui.PushTextWrapPos(contentWidth); + ImGuiPlus.Heading("SESSION", centered: true); + + ImGui.Checkbox("include name", ref _conf.IncludeNameInTurtleSession); + ImGui.SameLine(); + ImGuiComponents.HelpMarker("share your character name in the turtle session, so others can see that you contributed."); + + ImGui.BeginDisabled(!_turtleManager.IsTurtleCollabbing); + if (ImGui.Button("LEAVE SESSION")) _turtleManager.LeaveCollabSession(); + ImGui.EndDisabled(); + + ImGuiPlus.Separator(); ImGuiPlus.Heading("NEW", centered: true); ImGui.TextWrapped("start a new scout session on turtle for other scouters to join and contribute to."); if (ImGui.Button("START NEW SESSION", _buttonSize.Value with { X = contentWidth })) { @@ -448,7 +451,6 @@ private bool JoinTurtleCollabSession(string collabLink) { sessionInfo => { _collabLink = $"{_conf.TurtleBaseUrl}{_conf.TurtleTrainPath}/{sessionInfo.slug}/{sessionInfo.password}"; _chat.TaggedPrint($"joined turtle session: {_collabLink}"); - _isTurtleCollabbing = true; _alreadyContributedMobs.Clear(); }, () => _chat.TaggedPrintError($"failed to parse collab link. please ensure it is a valid link.\n{collabLink}")