Files
HSUI/Helpers/PullTimerHelper.cs
T

235 lines
6.8 KiB
C#

/*
Copyright(c) 2021 xorus (https://github.com/xorus/EngageTimer)
Modifications Copyright(c) 2021 HSUI
09/21/2021 - Extracted code to hook the game's pulltimer functions.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Hooking;
using Dalamud.Logging;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
namespace HSUI.Helpers
{
public unsafe class PullTimerHelper
{
#region Singleton
private PullTimerHelper()
{
PullTimerState = new PullTimerState();
try
{
_countdownTimerHook = Plugin.GameInteropProvider.HookFromAddress<AgentInterface.Delegates.Update>(
AgentModule.Instance()->GetAgentByInternalId(AgentId.CountDownSettingDialog)->VirtualTable->Update,
CountdownTimerFunc);
_countdownTimerHook?.Enable();
}
catch
{
Plugin.Logger.Error("PullTimeHelper CountdownTimer Hook failed!!!");
}
}
public static void Initialize() { Instance = new PullTimerHelper(); }
public static PullTimerHelper Instance { get; private set; } = null!;
~PullTimerHelper()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (!disposing)
{
return;
}
_countdownTimerHook?.Disable();
_countdownTimerHook?.Dispose();
Instance = null!;
}
#endregion
private DateTime _combatTimeEnd;
private DateTime _combatTimeStart;
private ulong _agentData;
public bool CountDownRunning;
private int _countDownStallTicks;
private readonly Hook<AgentInterface.Delegates.Update>? _countdownTimerHook;
public float LastCountDownValue;
private bool _shouldRestartCombatTimer = true;
private bool _lastMaxValueSet = false;
public readonly PullTimerState PullTimerState;
public void Update()
{
if (PullTimerState.Mocked)
{
return;
}
UpdateCountDown();
UpdateEncounterTimer();
PullTimerState.InInstance = Plugin.Condition[ConditionFlag.BoundByDuty];
}
private void CountdownTimerFunc(AgentInterface* agentInterface, uint frameCount)
{
_agentData = (ulong)agentInterface;
_countdownTimerHook?.Original(agentInterface, frameCount);
}
private void UpdateEncounterTimer()
{
if (Plugin.Condition[ConditionFlag.InCombat])
{
PullTimerState.InCombat = true;
if (_shouldRestartCombatTimer)
{
_shouldRestartCombatTimer = false;
_combatTimeStart = DateTime.Now;
}
_combatTimeEnd = DateTime.Now;
}
else
{
PullTimerState.InCombat = false;
_shouldRestartCombatTimer = true;
}
PullTimerState.CombatStart = _combatTimeStart;
PullTimerState.CombatDuration = _combatTimeEnd - _combatTimeStart;
PullTimerState.CombatEnd = _combatTimeEnd;
}
private void UpdateCountDown()
{
PullTimerState.CountingDown = false;
if (_agentData == 0)
{
return;
}
byte countdownActive = Marshal.PtrToStructure<byte>((IntPtr)_agentData + 0x38);
if (countdownActive == 0)
{
_lastMaxValueSet = false;
return;
}
float countDownPointerValue = Marshal.PtrToStructure<float>((IntPtr)_agentData + 0x2c);
// is last value close enough (workaround for floating point approx)
if (Math.Abs(countDownPointerValue - LastCountDownValue) < 0.001f)
{
_countDownStallTicks++;
}
else
{
_countDownStallTicks = 0;
CountDownRunning = true;
}
if (_countDownStallTicks > 50)
{
CountDownRunning = false;
}
if (countDownPointerValue > 0 && CountDownRunning)
{
PullTimerState.CountDownValue = countDownPointerValue;
PullTimerState.CountingDown = true;
}
if (!_lastMaxValueSet && CountDownRunning)
{
PullTimerState.CountDownMax = countDownPointerValue;
_lastMaxValueSet = true;
}
else if (_lastMaxValueSet && countDownPointerValue <= 0)
{
_lastMaxValueSet = false;
}
LastCountDownValue = countDownPointerValue;
}
}
public class PullTimerState
{
private bool _inCombat;
private bool _countingDown;
public TimeSpan CombatDuration { get; set; }
public DateTime CombatEnd { get; set; }
public DateTime CombatStart { get; set; }
public bool Mocked { get; set; }
public bool InCombat
{
get => _inCombat;
set
{
if (_inCombat == value)
{
return;
}
_inCombat = value;
InCombatChanged?.Invoke(this, EventArgs.Empty);
}
}
public bool CountingDown
{
get => _countingDown;
set
{
if (_countingDown == value)
{
return;
}
_countingDown = value;
CountingDownChanged?.Invoke(this, EventArgs.Empty);
}
}
public bool InInstance { get; set; }
public float CountDownValue { get; set; } = 0f;
public float CountDownMax { get; set; } = 0f;
public event EventHandler? InCombatChanged;
public event EventHandler? CountingDownChanged;
}
}