Debug markers, client implementation

This commit is contained in:
Liza 2023-02-05 04:21:24 +01:00
parent 74d18c95c6
commit b959355121
7 changed files with 126 additions and 6 deletions

89
Pal.Client/Hooks.cs Normal file
View File

@ -0,0 +1,89 @@
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Hooking;
using Dalamud.Logging;
using Dalamud.Memory;
using Dalamud.Utility.Signatures;
using System;
using System.Text;
namespace Pal.Client
{
internal unsafe class Hooks
{
#pragma warning disable CS0649
private delegate nint ActorVfxCreateDelegate(char* a1, nint a2, nint a3, float a4, char a5, ushort a6, char a7);
[Signature("40 53 55 56 57 48 81 EC ?? ?? ?? ?? 0F 29 B4 24 ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 0F B6 AC 24 ?? ?? ?? ?? 0F 28 F3 49 8B F8", DetourName = nameof(ActorVfxCreate))]
private Hook<ActorVfxCreateDelegate> ActorVfxCreateHook { get; init; } = null!;
#pragma warning restore CS0649
public Hooks()
{
SignatureHelper.Initialise(this);
ActorVfxCreateHook.Enable();
}
/// <summary>
/// Even with a pomander of sight, the BattleChara's position for the trap remains at {0, 0, 0} until it is activated.
/// Upon exploding, the trap's position is moved to the exact location that the pomander of sight would have revealed.
///
/// That exact position appears to be used for VFX playing when you walk into it - even if you barely walk into the
/// outer ring of an otter/luring/impeding/landmine trap, the VFX plays at the exact center and not at your character's
/// location.
///
/// Especially at higher floors, you're more likely to walk into an undiscovered trap compared to e.g. 51-60,
/// and you probably don't want to/can't use sight on every floor - yet the trap location is still useful information.
///
/// Some (but not all) chests also count as BattleChara named 'Trap', however the effect upon opening isn't played via
/// ActorVfxCreate even if they explode (but probably as a Vfx with static location, doesn't matter for here).
///
/// Landmines and luring traps also don't play a VFX attached to their BattleChara.
///
/// otter: vfx/common/eff/dk05th_stdn0t.avfx <br/>
/// toading: vfx/common/eff/dk05th_stdn0t.avfx <br/>
/// enfeebling: vfx/common/eff/dk05th_stdn0t.avfx <br/>
/// landmine: none <br/>
/// luring: none <br/>
/// impeding: vfx/common/eff/dk05ht_ipws0t.avfx (one of silence/pacification) <br/>
/// impeding: vfx/common/eff/dk05ht_slet0t.avfx (the other of silence/pacification) <br/>
///
/// It is of course annoying that, when testing, almost all traps are landmines.
/// There's also vfx/common/eff/dk01gd_inv0h.avfx for e.g. impeding when you're invulnerable, but not sure if that
/// has other trigger conditions.
/// </summary>
public nint ActorVfxCreate(char* a1, nint a2, nint a3, float a4, char a5, ushort a6, char a7)
{
try
{
if (Service.Plugin.IsInDeepDungeon())
{
var vfxPath = MemoryHelper.ReadString(new nint(a1), Encoding.ASCII, 256);
var obj = Service.ObjectTable.CreateObjectReference(a2);
/*
if (Service.Configuration.BetaKey == "VFX")
Service.Chat.Print($"{vfxPath} on {obj}");
*/
if (obj is BattleChara bc && (bc.NameId == /* potd */ 5042 || bc.NameId == /* hoh */ 7395))
{
if (vfxPath == "vfx/common/eff/dk05th_stdn0t.avfx" || vfxPath == "vfx/common/eff/dk05ht_ipws0t.avfx")
{
Service.Plugin.NextUpdateObjects.Enqueue(obj.Address);
}
}
}
}
catch (Exception e)
{
PluginLog.Error(e, "VFX Create Hook failed");
}
return ActorVfxCreateHook.Original(a1, a2, a3, a4, a5, a6, a7);
}
public void Dispose()
{
ActorVfxCreateHook?.Dispose();
}
}
}

View File

@ -62,7 +62,7 @@ namespace Pal.Client
localState = new LocalState(territoryType)
{
Markers = new ConcurrentBag<Marker>(save.Markers),
Markers = new ConcurrentBag<Marker>(save.Markers.Where(o => o.Type != Marker.EType.Debug)),
};
version = save.Version;
}

View File

@ -53,7 +53,7 @@ namespace Pal.Client
/// <summary>
/// To make rollbacks of local data easier, keep track of the version which was used to write the marker initially.
/// </summary>
public string SinceVersion { get; set; }
public string? SinceVersion { get; set; }
[JsonIgnore]
public Element? SplatoonElement { get; set; }
@ -86,7 +86,7 @@ namespace Pal.Client
}
public bool IsPermanent() => Type == EType.Trap || Type == EType.Hoard;
public bool IsPermanent() => Type == EType.Trap || Type == EType.Hoard || Type == EType.Debug;
public enum EType
{
@ -95,6 +95,7 @@ namespace Pal.Client
#region Permanent Markers
Trap = ObjectType.Trap,
Hoard = ObjectType.Hoard,
Debug = ObjectType.Debug,
#endregion
# region Markers that only show up if they're currently visible

View File

@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<LangVersion>11.0</LangVersion>
<Version>2.3</Version>
<Version>2.4</Version>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@ -41,6 +41,7 @@ namespace Pal.Client
{ Marker.EType.Trap, new MarkerConfig { Radius = 1.7f } },
{ Marker.EType.Hoard, new MarkerConfig { Radius = 1.7f, OffsetY = -0.03f } },
{ Marker.EType.SilverCoffer, new MarkerConfig { Radius = 1f } },
{ Marker.EType.Debug, new MarkerConfig { Radius = 1.5f } },
};
private LocalizedChatMessages _localizedChatMessages = new();
@ -53,6 +54,7 @@ namespace Pal.Client
public string? DebugMessage { get; set; }
internal Queue<IQueueOnFrameworkThread> EarlyEventQueue { get; } = new();
internal Queue<IQueueOnFrameworkThread> LateEventQueue { get; } = new();
internal ConcurrentQueue<nint> NextUpdateObjects { get; } = new();
public string Name => "Palace Pal";
@ -79,6 +81,7 @@ namespace Pal.Client
Service.Plugin = this;
Service.Configuration = (Configuration?)pluginInterface.GetPluginConfig() ?? pluginInterface.Create<Configuration>()!;
Service.Configuration.Migrate();
Service.Hooks = new Hooks();
var agreementWindow = pluginInterface.Create<AgreementWindow>();
if (agreementWindow is not null)
@ -174,6 +177,10 @@ namespace Pal.Client
DebugNearest(m => m.Type == Marker.EType.Hoard);
break;
case "dnear":
DebugNearest(m => m.Type == Marker.EType.Debug);
break;
default:
Service.Chat.PrintError($"[Palace Pal] Unknown sub-command '{arguments}' for '{command}'.");
break;
@ -199,6 +206,7 @@ namespace Pal.Client
Service.WindowSystem.RemoveAllWindows();
Service.RemoteApi.Dispose();
Service.Hooks.Dispose();
try
{
@ -274,6 +282,7 @@ namespace Pal.Client
{
LastTerritory = Service.ClientState.TerritoryType;
TerritorySyncState = SyncState.NotAttempted;
NextUpdateObjects.Clear();
if (IsInDeepDungeon())
GetFloorMarkers(LastTerritory);
@ -414,6 +423,12 @@ namespace Pal.Client
marker.SplatoonElement = element;
elements.Add(element);
}
else if (marker.Type == Marker.EType.Debug && Service.Configuration.BetaKey == "VFX")
{
var element = CreateSplatoonElement(marker.Type, marker.Position, DetermineColor(marker, visibleMarkers));
marker.SplatoonElement = element;
elements.Add(element);
}
}
}
@ -444,13 +459,15 @@ namespace Pal.Client
else
return COLOR_INVISIBLE;
}
else
else if (marker.Type == Marker.EType.Hoard)
{
if (PomanderOfIntuition == PomanderState.Inactive || !Service.Configuration.OnlyVisibleHoardAfterPomander || visibleMarkers.Any(x => x == marker))
return ImGui.ColorConvertFloat4ToU32(Service.Configuration.HoardColor);
else
return COLOR_INVISIBLE;
}
else
return ImGui.ColorConvertFloat4ToU32(new Vector4(1, 0.5f, 1, 0.4f));
}
private void HandleEphemeralMarkers(IList<Marker> visibleMarkers, bool recreateLayout)
@ -596,7 +613,7 @@ namespace Pal.Client
var playerPosition = Service.ClientState.LocalPlayer?.Position;
if (playerPosition == null)
return;
Service.Chat.Print($"[Pal] {playerPosition}");
Service.Chat.Print($"[Palace Pal] {playerPosition}");
var nearbyMarkers = state.Markers
.Where(m => predicate(m))
@ -641,6 +658,13 @@ namespace Pal.Client
}
}
while (NextUpdateObjects.TryDequeue(out nint address))
{
var obj = Service.ObjectTable.FirstOrDefault(x => x.Address == address);
if (obj != null && obj.Position.Length() > 0.1)
result.Add(new Marker(Marker.EType.Debug, obj.Position) { Seen = true });
}
return result;
}

View File

@ -27,5 +27,6 @@ namespace Pal.Client
public static WindowSystem WindowSystem { get; set; } = new(typeof(Service).AssemblyQualifiedName);
internal static RemoteApi RemoteApi { get; set; } = new RemoteApi();
public static Configuration Configuration { get; set; } = null!;
internal static Hooks Hooks { get; set; } = null!;
}
}

View File

@ -306,6 +306,11 @@ namespace Pal.Client.Windows
int silverCoffers = plugin.EphemeralMarkers.Count(x => x.Type == Marker.EType.SilverCoffer);
ImGui.Text($"{silverCoffers} silver coffer{(silverCoffers == 1 ? "" : "s")} visible on current floor");
}
if (Service.Configuration.BetaKey == "VFX")
{
int debugMarkers = currentFloor.Markers.Count(x => x.Type == Marker.EType.Debug);
ImGui.Text($"{debugMarkers} debug marker{(debugMarkers == 1 ? "" : "s")}");
}
ImGui.Text($"Pomander of Sight: {plugin.PomanderOfSight}");
ImGui.Text($"Pomander of Intuition: {plugin.PomanderOfIntuition}");