Skip to content

Commit 9ad596d

Browse files
committed
feat: Add OrderbotHook, a template class for create orderbot hooks. Also add xml doc to nuget package
1 parent 0cbbf67 commit 9ad596d

File tree

6 files changed

+255
-37
lines changed

6 files changed

+255
-37
lines changed

Helpers/Navigation.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ public static class Navigation
4747
}
4848
catch (Exception ex)
4949
{
50-
Log.Error("NavGraph.GetPathAsync failed with an exception");
50+
Log.Error($"NavGraph.GetPathAsync failed with an exception");
51+
Log.Error(ex.ToString());
5152
return null;
5253
}
5354
}

Helpers/OrderbotHook.cs

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
using System.Windows.Media;
7+
using ff14bot;
8+
using ff14bot.Behavior;
9+
using ff14bot.Managers;
10+
using LlamaLibrary.Logging;
11+
using TreeSharp;
12+
13+
namespace LlamaLibrary.Helpers;
14+
15+
/// <summary>
16+
/// Base decorator used to create and register orderbot hooks with the TreeHooks system.
17+
/// </summary>
18+
/// <remarks>
19+
/// Derive from this class and implement <see cref="HookName"/>, <see cref="HookDescription"/>,
20+
/// <see cref="ShouldRun(object)"/> and <see cref="Run"/> to provide hook behavior. The constructor
21+
/// creates a coroutine child that invokes the hook logic and attaches it as the decorator's child.
22+
/// </remarks>
23+
public abstract class OrderbotHook : Decorator
24+
{
25+
/// <summary>
26+
/// Logger instance used by the hook for informational and diagnostic messages.
27+
/// </summary>
28+
protected readonly LLogger Log;
29+
30+
private bool _added;
31+
private bool _includeBusyCheck;
32+
33+
/// <summary>
34+
/// Initializes a new instance of the <see cref="OrderbotHook"/> class.
35+
/// </summary>
36+
/// <param name="logLevel">
37+
/// The log level used to construct the <see cref="LLogger"/> for this hook.
38+
/// Defaults to <see cref="LogLevel.Information"/>.
39+
/// </param>
40+
/// <param name="includeBusyCheck">
41+
/// If <c>true</c>, the default busy check is applied in <see cref="CanRun(object)"/> to prevent running
42+
/// while moving, in combat, dead, in instance, or within a fate. Defaults to <c>true</c>.
43+
/// </param>
44+
/// <remarks>
45+
/// The constructor constructs the logger, creates an <see cref="ActionRunCoroutine"/> child that invokes the hook logic,
46+
/// assigns it as a child of this decorator, and stores the busy-check preference.
47+
/// </remarks>
48+
protected OrderbotHook(LogLevel logLevel = LogLevel.Information, bool includeBusyCheck = true)
49+
{
50+
Log = new LLogger(HookName, Colors.DarkOrange, logLevel);
51+
Composite func = new ActionRunCoroutine(async result => await HookRun());
52+
func.Parent = this;
53+
Children = new List<Composite> { func };
54+
_includeBusyCheck = includeBusyCheck;
55+
}
56+
57+
/// <summary>
58+
/// Gets the hook's display name.
59+
/// </summary>
60+
/// <value>The name used for logging, diagnostics and identification of the hook.</value>
61+
protected abstract string HookName { get; }
62+
63+
/// <summary>
64+
/// Gets a short description of the hook's purpose.
65+
/// </summary>
66+
/// <value>A human-readable description shown in logs and diagnostics.</value>
67+
protected abstract string HookDescription { get; }
68+
69+
/// <summary>
70+
/// Gets the location key within <c>TreeHooks</c> where this hook will be registered.
71+
/// </summary>
72+
/// <value>The hook registration location; defaults to <c>TreeStart</c>.</value>
73+
/// <remarks>
74+
/// Override to change where the hook is attached (for example, <c>TreeStart</c>, <c>DeathReturnLogic</c> ,<c>DeathReviveLogic</c> ,<c>PoiAction</c> ,<c>Pull</c> ,<c>RoutineCombat</c> ,<c>HotspotPoi</c> ,<c>PoiAction2</c> ,<c>SetDeathPoi</c> ,<c>SetCombatPoi</c> ,<c>SetHotspotPoi</c> ,<c>SelectPoiType</c> ).
75+
/// </remarks>
76+
protected virtual string Location => "TreeStart";
77+
78+
/// <summary>
79+
/// Determines whether this hook should run for the provided behavior-tree <paramref name="context"/>.
80+
/// </summary>
81+
/// <param name="context">The behavior-tree context passed into <see cref="CanRun(object)"/>.</param>
82+
/// <returns><c>true</c> if the hook should run for the given context; otherwise <c>false</c>.</returns>
83+
/// <remarks>Implementations should be quick and non-blocking. This is used by the decorator to decide execution.</remarks>
84+
protected abstract bool ShouldRun(object context);
85+
86+
/// <summary>
87+
/// Executes the hook's logic asynchronously.
88+
/// </summary>
89+
/// <returns>
90+
/// A <see cref="Task"/> that completes with <c>true</c> if the hook completed successfully
91+
/// (and any downstream behavior should consider the run successful); otherwise <c>false</c>.
92+
/// </returns>
93+
/// <remarks>This method is invoked when <see cref="ShouldRun(object)"/> returns <c>true</c>. Implementations may perform async work.</remarks>
94+
protected abstract Task<bool> Run();
95+
96+
/// <summary>
97+
/// Determines whether the decorator can run in the given <paramref name="context"/>.
98+
/// </summary>
99+
/// <param name="context">The behavior-tree context.</param>
100+
/// <returns><c>false</c> if the default busy check disallows running; otherwise returns the result of <see cref="ShouldRun(object)"/>.</returns>
101+
protected override bool CanRun(object context)
102+
{
103+
if (_includeBusyCheck && DefaultBusyCheck())
104+
{
105+
return false;
106+
}
107+
108+
return ShouldRun(context);
109+
}
110+
111+
/// <summary>
112+
/// Internal wrapper that times and logs execution of <see cref="Run"/>.
113+
/// </summary>
114+
/// <returns>The boolean result returned by <see cref="Run"/>.</returns>
115+
private async Task<bool> HookRun()
116+
{
117+
var timer = Stopwatch.StartNew();
118+
Log.Information($"{HookName} started");
119+
var result = await Run();
120+
timer.Stop();
121+
Log.Information($"{HookName} took {timer.ElapsedMilliseconds:N0}ms to complete");
122+
return result;
123+
}
124+
125+
/// <summary>
126+
/// Adds this hook to the <c>TreeHooks</c> collection at <see cref="Location"/>, if not already added.
127+
/// </summary>
128+
public virtual void AddHook()
129+
{
130+
if (_added || TreeHooks.Instance.Hooks.TryGetValue(Location, out var list) && list.Any(Equals))
131+
{
132+
return;
133+
}
134+
135+
TreeHooks.Instance.AddHook(Location, this);
136+
Log.Information($"{Location} hook added ({Guid})");
137+
TreeHooks.Instance.OnHooksCleared += OnHooksCleared;
138+
_added = true;
139+
}
140+
141+
/// <summary>
142+
/// Removes this hook from the <c>TreeHooks</c> collection at <see cref="Location"/> if it was added.
143+
/// </summary>
144+
public virtual void RemoveHook()
145+
{
146+
if (!_added)
147+
{
148+
return;
149+
}
150+
151+
TreeHooks.Instance.RemoveHook(Location, this);
152+
Log.Information($"{Location} hook Removed ({Guid})");
153+
TreeHooks.Instance.OnHooksCleared -= OnHooksCleared;
154+
_added = false;
155+
}
156+
157+
/// <summary>
158+
/// Returns the hook display name and Guid.
159+
/// </summary>
160+
/// <returns>The value of <see cref="HookName"/>.</returns>
161+
public override string ToString()
162+
{
163+
return $"{HookName} {Guid}";
164+
}
165+
166+
/// <summary>
167+
/// Handler called when <see cref="TreeHooks.Instance"/> raises <c>OnHooksCleared</c>.
168+
/// The hook will attempt to re-add itself after hooks are cleared.
169+
/// </summary>
170+
private void OnHooksCleared(object sender, EventArgs args)
171+
{
172+
_added = false;
173+
Log.Information($"{Location} hook Removed ({Guid}) on HooksCleared");
174+
AddHook();
175+
}
176+
177+
/// <summary>
178+
/// Determines equality by comparing the <see cref="Composite.Guid"/> values of the objects.
179+
/// </summary>
180+
/// <param name="obj">The object to compare with the current instance.</param>
181+
/// <returns><c>true</c> if the objects have the same <c>Guid</c>; otherwise <c>false</c>.</returns>
182+
public override bool Equals(object? obj)
183+
{
184+
return obj switch
185+
{
186+
null => false,
187+
OrderbotHook other => Guid == other.Guid,
188+
Composite composite => Guid == composite.Guid,
189+
_ => false
190+
};
191+
}
192+
193+
/// <summary>
194+
/// Returns the hash code for this instance, derived from the underlying <c>Guid</c>.
195+
/// </summary>
196+
/// <returns>A 32-bit signed integer hash code.</returns>
197+
public override int GetHashCode()
198+
{
199+
return Guid.GetHashCode();
200+
}
201+
202+
/// <summary>
203+
/// Performs the default busy check used to prevent hooks from running when the player is unavailable.
204+
/// </summary>
205+
/// <returns>
206+
/// <c>true</c> if the player is moving, occupied, in combat, dead, inside an instance, or within a fate; otherwise <c>false</c>.
207+
/// </returns>
208+
public static bool DefaultBusyCheck()
209+
{
210+
return MovementManager.IsMoving || MovementManager.IsOccupied || Core.Me.InCombat || !Core.Me.IsAlive || DutyManager.InInstance || FateManager.WithinFate;
211+
}
212+
}

JsonObjects/JsonSettings.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ public JsonSettings(string settingsFilePath) : base(settingsFilePath)
1919

2020
public static T Instance => _instance ??= new T();
2121

22-
public event PropertyChangedEventHandler? PropertyChanged;
22+
public new event PropertyChangedEventHandler? PropertyChanged;
2323

24-
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
24+
protected new void OnPropertyChanged([CallerMemberName] string? propertyName = null)
2525
{
2626
Save();
2727
var handler = PropertyChanged;

LlamaLibrary.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
<RequireLicenseAcceptance>false</RequireLicenseAcceptance>
2727
<PackageReadmeFile>Readme.md</PackageReadmeFile>
2828
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
29+
<GenerateDocumentationFile>true</GenerateDocumentationFile>
30+
<NoWarn>$(NoWarn);1591</NoWarn>
2931
</PropertyGroup>
3032
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
3133
<OutputPath>bin\x64\Release\</OutputPath>

Loaders/CompiledLoader.cs

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,13 @@ protected virtual async Task<bool> NeedsUpdate()
298298
return false;
299299
}
300300

301-
return localVersion != remoteVersion;
301+
var result = localVersion != remoteVersion;
302+
if (result)
303+
{
304+
Log.Information($"Update needed: Local Version {localVersion} != Remote Version {remoteVersion}");
305+
}
306+
307+
return result;
302308
}
303309

304310
protected virtual Version? GetLocalVersion()
@@ -377,28 +383,42 @@ private static void Clean(string directory)
377383

378384
public async Task<T> Load(string directory)
379385
{
380-
Log.Information($"Loading {ProjectName}...");
381-
LocalFolderName = directory;
382-
383-
if (!Debug)
386+
var timer = Stopwatch.StartNew();
387+
try
384388
{
385-
await Update().ConfigureAwait(false);
386-
_client?.Dispose();
387-
}
389+
Log.Information($"Loading {ProjectName}...");
390+
LocalFolderName = directory;
388391

389-
UnblockAll();
392+
if (!Debug)
393+
{
394+
await Update().ConfigureAwait(false);
395+
_client?.Dispose();
396+
}
390397

391-
if (LibraryClass.SafeMode)
392-
{
393-
Log.Information($"Safe mode enabled, skipping load of {ProjectName}");
394-
return null;
395-
}
398+
UnblockAll();
396399

397-
CompiledAssembly.Refresh();
400+
if (LibraryClass.SafeMode)
401+
{
402+
Log.Information($"Safe mode enabled, skipping load of {ProjectName}");
403+
return null;
404+
}
405+
406+
CompiledAssembly.Refresh();
398407

399-
if (CompiledAssembly.Exists)
408+
if (CompiledAssembly.Exists)
409+
{
410+
return Load();
411+
}
412+
}
413+
catch (Exception e)
414+
{
415+
Log.Error($"Failed to load {ProjectName}");
416+
Log.Exception(e);
417+
}
418+
finally
400419
{
401-
return Load();
420+
timer.Stop();
421+
Log.Information($"Load finished in {timer.ElapsedMilliseconds:N0}ms");
402422
}
403423

404424
return null;

Memory/PatternFinders/ISearcher.cs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,6 @@ public interface ISearcher : IDisposable
66
{
77
public IntPtr ImageBase { get; }
88

9-
/// <summary>
10-
/// Expects pattern to be in the following format:
11-
/// 48 8D 05 ?? ?? ?? ?? 48 C7 43 ?? ?? ?? ?? ?? 48 8D 4B ?? 48 89 03 66 C7 43 ?? ?? ?? Add 3 TraceRelative
12-
/// Available Commands:
13-
/// Add # - Shifts the searcher this # is from the start of the pattern. So add 1 moves us to byte 2. add 2 moves to byte 3 etc.
14-
/// Sub # - Shifts the searcher this # is from the start of the pattern. so sub 1 moves us to byte -1. sub 2 moves us to byte -2 etc.
15-
/// Read8 - Reads a byte from the resulting address
16-
/// Read16 - Reads 2 bytes (16bits) from the resulting address
17-
/// Read32 - Reads 4 bytes (32bits) from the resulting address
18-
/// Read64 - Reads 8 bytes (64bits) from the resulting address
19-
/// TraceRelative - Follow the relative address used in calls and lea's
20-
/// TraceCall - Should basically do Add 1 TraceRelative on a pattern for a function call ie E8 ?? ?? ?? ?? Doesn't really work right now.
21-
/// </summary>
22-
/// <param name="pattern">Hex based pattern with ? as wildcards.</param>
23-
/// <returns>Pointer to memory address.</returns>
24-
//public IntPtr FindSingle(string pattern);
25-
269
/// <summary>
2710
/// Expects pattern to be in the following format:
2811
/// 48 8D 05 ?? ?? ?? ?? 48 C7 43 ?? ?? ?? ?? ?? 48 8D 4B ?? 48 89 03 66 C7 43 ?? ?? ?? Add 3 TraceRelative

0 commit comments

Comments
 (0)