Skip to content

Commit

Permalink
Merge commit 'refs/pull/315/head' of https://github.com/awgil/ffxiv_b…
Browse files Browse the repository at this point in the history
…ossmod

# Conflicts:
#	BossMod/Config/ConfigNode.cs
#	BossMod/Config/ConfigUI.cs
#	BossMod/Framework/Plugin.cs
#	BossMod/Framework/Service.cs
#	BossMod/Replay/ReplayManagementConfig.cs
#	BossMod/Replay/ReplayManagementWindow.cs
  • Loading branch information
awgil committed Apr 3, 2024
2 parents e20db83 + 4a89c93 commit d86b111
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 31 deletions.
2 changes: 1 addition & 1 deletion BossMod/Config/ConfigNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public PropertyComboAttribute(string falseText, string trueText)
}
}

// attribute that specifies slider should be used for displaying float property
// attribute that specifies slider should be used for displaying float/int property
[AttributeUsage(AttributeTargets.Field)]
public class PropertySliderAttribute : Attribute
{
Expand Down
26 changes: 26 additions & 0 deletions BossMod/Config/ConfigUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public static void DrawNode(ConfigNode node, ConfigRoot root, UITree tree, World
bool v => DrawProperty(props, node, field, v),
Enum v => DrawProperty(props, node, field, v),
float v => DrawProperty(props, node, field, v),
int v => DrawProperty(props, node, field, v),
GroupAssignment v => DrawProperty(props, node, field, v, root, tree, ws),
_ => false
};
Expand Down Expand Up @@ -171,6 +172,31 @@ private static bool DrawProperty(PropertyDisplayAttribute props, ConfigNode node
return true;
}

private static bool DrawProperty(PropertyDisplayAttribute props, ConfigNode node, FieldInfo member, int v)
{
var slider = member.GetCustomAttribute<PropertySliderAttribute>();
if (slider != null)
{
var flags = ImGuiSliderFlags.None;
if (slider.Logarithmic)
flags |= ImGuiSliderFlags.Logarithmic;
if (ImGui.DragInt(props.Label, ref v, slider.Speed, (int)slider.Min, (int)slider.Max, "%d", flags))
{
member.SetValue(node, v);
node.NotifyModified();
}
}
else
{
if (ImGui.InputInt(props.Label, ref v))
{
member.SetValue(node, v);
node.NotifyModified();
}
}
return true;
}

private static bool DrawProperty(PropertyDisplayAttribute props, ConfigNode node, FieldInfo member, GroupAssignment v, ConfigRoot root, UITree tree, WorldState ws)
{
var group = member.GetCustomAttribute<GroupDetailsAttribute>();
Expand Down
2 changes: 1 addition & 1 deletion BossMod/Framework/Plugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public Plugin(
_wndBossmod = new(_bossmod);
_wndBossmodPlan = new(_bossmod);
_wndBossmodHints = new(_bossmod);
_wndReplay = new(_ws, dalamud.ConfigDirectory);
_wndReplay = new(_ws, new(dalamud.ConfigDirectory.FullName + "/replays"));
_wndDebug = new(_ws, _autorotation);

dalamud.UiBuilder.DisableAutomaticUiHide = true;
Expand Down
5 changes: 4 additions & 1 deletion BossMod/Framework/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Globalization;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;

namespace BossMod;

Expand Down Expand Up @@ -246,7 +247,9 @@ public static int UpperBound<TKey, TValue>(this SortedList<TKey, TValue> list, T

// sort elements of a list by key
public static void SortBy<TValue, TKey>(this List<TValue> list, Func<TValue, TKey> proj) where TKey : notnull, IComparable => list.Sort((l, r) => proj(l).CompareTo(proj(r)));
public static void SortBy<TValue, TKey>(this TValue[] arr, Func<TValue, TKey> proj) where TKey : notnull, IComparable => Array.Sort(arr, (l, r) => proj(l).CompareTo(proj(r)));
public static void SortByReverse<TValue, TKey>(this List<TValue> list, Func<TValue, TKey> proj) where TKey : notnull, IComparable => list.Sort((l, r) => proj(r).CompareTo(proj(l)));
public static void SortByReverse<TValue, TKey>(this TValue[] arr, Func<TValue, TKey> proj) where TKey : notnull, IComparable => Array.Sort(arr, (l, r) => proj(r).CompareTo(proj(l)));

// get enumerable of zero or one elements, depending on whether argument is null
public static IEnumerable<T> ZeroOrOne<T>(T? value) where T : struct
Expand Down Expand Up @@ -336,7 +339,7 @@ public static string StringToIdentifier(string v)
v = v.Replace("'", null);
v = v.Replace('-', ' ');
v = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(v);
v = v.Replace(" ", null);
v = Regex.Replace(v, "[^a-zA-Z0-9]", "");
return v;
}
}
10 changes: 10 additions & 0 deletions BossMod/Replay/ReplayManagementConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ public class ReplayManagementConfig : ConfigNode
[PropertyDisplay("Show replay management UI")]
public bool ShowUI = false;

[PropertyDisplay("Auto record replays on duty start")]
public bool AutoRecord = false;

[PropertyDisplay("Auto stop replays on duty end")]
public bool AutoStop = false;

[PropertyDisplay("Max replays to keep before removal")]
[PropertySlider(0, 1000)]
public int MaxReplays = 0;

[PropertyDisplay("Store server packets in the replay")]
public bool DumpServerPackets = false;

Expand Down
125 changes: 103 additions & 22 deletions BossMod/Replay/ReplayManagementWindow.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using System.IO;

namespace BossMod;
Expand All @@ -19,16 +20,21 @@ public class ReplayManagementWindow : UIWindow
_ws = ws;
_logDir = logDir;
_config = Service.Config.Get<ReplayManagementConfig>();
_config.Modified += ApplyConfig;
_manager = new(logDir.FullName);
ApplyConfig(null, EventArgs.Empty);
UpdateTitle();

_ws.CurrentZoneChanged += OnZoneChanged;
_config.Modified += OnConfigChanged;
if (!UpdateAutoRecord(_ws.CurrentCFCID))
UpdateTitle();

RespectCloseHotkey = false;
IsOpen = _config.ShowUI;
}

protected override void Dispose(bool disposing)
{
_config.Modified -= ApplyConfig;
_config.Modified -= OnConfigChanged;
_ws.CurrentZoneChanged -= OnZoneChanged;
_recorder?.Dispose();
_manager.Dispose();
}
Expand All @@ -49,25 +55,12 @@ public override void PreOpenCheck()

public override void Draw()
{
if (ImGui.Button(_recorder == null ? "Start recording" : "Stop recording"))
if (ImGui.Button(!IsRecording() ? "Start recording" : "Stop recording"))
{
if (_recorder == null)
{
try
{
_recorder = new(_ws, _config.WorldLogFormat, true, _logDir, "World");
}
catch (Exception ex)
{
Service.Log($"Failed to start recording: {ex}");
}
}
if (!IsRecording())
StartRecording();
else
{
_recorder.Dispose();
_recorder = null;
}
UpdateTitle();
StopRecording();
}

if (_recorder != null)
Expand All @@ -85,11 +78,99 @@ public override void Draw()
_manager.Draw();
}

public void StartRecording()
{
if (IsRecording())
return; // already recording

// if there are too many replays, delete oldest
if (_config.MaxReplays > 0)
{
try
{
var replays = _logDir.GetFiles();
replays.SortBy(f => f.LastWriteTime);
foreach (var f in replays.Take(replays.Length - _config.MaxReplays))
f.Delete();
}
catch (Exception ex)
{
Service.Log($"Failed to delete old replays: {ex}");
}
}

try
{
_recorder = new(_ws, _config.WorldLogFormat, true, _logDir, GetPrefix());
}
catch (Exception ex)
{
Service.Log($"Failed to start recording: {ex}");
}

UpdateTitle();
}

public void StopRecording()
{
_recorder?.Dispose();
_recorder = null;
UpdateTitle();
}

public bool IsRecording() => _recorder != null;

public override void OnClose()
{
SetVisible(false);
}

private void ApplyConfig(object? sender, EventArgs args) => IsOpen = _config.ShowUI;
private void UpdateTitle() => WindowName = $"Replay recording: {(_recorder != null ? "in progress..." : "idle")}{_windowID}";

private bool UpdateAutoRecord(uint cfcId)
{
if (!IsRecording() && _config.AutoRecord && cfcId != 0)
{
StartRecording();
return true;
}

if (IsRecording() && _config.AutoStop && cfcId == 0)
{
StopRecording();
return true;
}

return false;
}

private void OnConfigChanged(object? sender, EventArgs args) => IsOpen = _config.ShowUI;
private void OnZoneChanged(object? sender, WorldState.OpZoneChange op) => UpdateAutoRecord(op.CFCID);

private unsafe string GetPrefix()
{
string? prefix = null;
if (_ws.CurrentCFCID != 0)
prefix ??= Service.LuminaRow<ContentFinderCondition>(_ws.CurrentCFCID)?.Name.ToString();
if (_ws.CurrentZone != 0)
prefix ??= Service.LuminaRow<TerritoryType>(_ws.CurrentZone)?.PlaceName.Value?.NameNoArticle.ToString();
prefix ??= "World";
prefix = Utils.StringToIdentifier(prefix);

var player = _ws.Party.Player();
if (player != null)
prefix += $"_{player.Class}{player.Level}_{player.Name.Replace(" ", null)}";

var cf = FFXIVClientStructs.FFXIV.Client.Game.UI.ContentsFinder.Instance();
if (cf->IsUnrestrictedParty)
prefix += "_U";
if (cf->IsLevelSync)
prefix += "_LS";
if (cf->IsMinimalIL)
prefix += "_MI";
if (cf->IsSilenceEcho)
prefix += "_NE";

return prefix;
}
}
7 changes: 1 addition & 6 deletions TODO
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,7 @@ network rework:
- utility to inject custom ipcs to the stream for debugging?

general:
- live replay
-- checkbox - record ops to temp struct
-- start record == rundown + start listening to main worldstate events
-- on bossmodule activation == mark as 'interesting'
-- on bossmodule deactivation or zone change == if current is interesting, move it to last (discard prev last); clear and restart current
-- button to 'keep' last (add as temp to replay manager) or to 'save' last (save into replay file)
- autoreplay improvements - react to module manager transitions?
- better timing tracking for: statuses, gauges, cooldowns, cast times, anim lock, ...
- constrain bossmodules to zone id (e.g. for T04)
- revise module categories - consider merging fates/hunts/quests/gold saucer?/pvp? into outdoor?/casual?
Expand Down

0 comments on commit d86b111

Please sign in to comment.