qstbak/QuestionableCompanion/QuestionableCompanion.Services/DeathHandlerService.cs
2025-12-04 04:39:08 +10:00

252 lines
7 KiB
C#

using System;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.NativeWrapper;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Lumina.Excel;
using Lumina.Excel.Sheets;
namespace QuestionableCompanion.Services;
public class DeathHandlerService : IDisposable
{
private readonly ICondition condition;
private readonly IPluginLog log;
private readonly IClientState clientState;
private readonly ICommandManager commandManager;
private readonly IFramework framework;
private readonly Configuration config;
private readonly IGameGui gameGui;
private readonly IDataManager dataManager;
private bool wasDead;
private Vector3 deathPosition = Vector3.Zero;
private uint deathTerritoryId;
private DateTime deathTime = DateTime.MinValue;
private bool hasClickedYesNo;
private bool needsTeleportBack;
private bool isRotationActive;
public bool IsDead { get; private set; }
public TimeSpan TimeSinceDeath
{
get
{
if (!IsDead || !(deathTime != DateTime.MinValue))
{
return TimeSpan.Zero;
}
return DateTime.Now - deathTime;
}
}
public DeathHandlerService(ICondition condition, IPluginLog log, IClientState clientState, ICommandManager commandManager, IFramework framework, Configuration config, IGameGui gameGui, IDataManager dataManager)
{
this.condition = condition;
this.log = log;
this.clientState = clientState;
this.commandManager = commandManager;
this.framework = framework;
this.config = config;
this.gameGui = gameGui;
this.dataManager = dataManager;
log.Information("[DeathHandler] Service initialized");
}
public void SetRotationActive(bool active)
{
isRotationActive = active;
}
public unsafe void Update()
{
if (clientState.LocalPlayer == null || !clientState.IsLoggedIn || !config.EnableDeathHandling)
{
return;
}
IPlayerCharacter player = clientState.LocalPlayer;
uint currentHp = player.CurrentHp;
if (currentHp == 0 && !wasDead)
{
IsDead = true;
wasDead = true;
deathTime = DateTime.Now;
deathPosition = player.Position;
deathTerritoryId = clientState.TerritoryType;
hasClickedYesNo = false;
needsTeleportBack = false;
string territoryName = GetTerritoryName(deathTerritoryId);
log.Warning("[DeathHandler] ========================================");
log.Warning("[DeathHandler] === PLAYER DIED (0% HP) ===");
log.Warning("[DeathHandler] ========================================");
log.Information($"[DeathHandler] Death Position: {deathPosition}");
log.Information($"[DeathHandler] Death Territory: {territoryName} ({deathTerritoryId})");
}
if (IsDead && !hasClickedYesNo)
{
try
{
AtkUnitBasePtr addonPtr = gameGui.GetAddonByName("SelectYesno");
if (addonPtr != IntPtr.Zero)
{
log.Information("[DeathHandler] SelectYesNo addon detected - clicking YES");
AtkUnitBase* addon = (AtkUnitBase*)(nint)addonPtr;
if (addon != null && addon->IsVisible)
{
AtkValue* values = stackalloc AtkValue[1];
*values = new AtkValue
{
Type = FFXIVClientStructs.FFXIV.Component.GUI.ValueType.Int,
Int = 0
};
addon->FireCallback(1u, values);
hasClickedYesNo = true;
log.Information("[DeathHandler] ✓ SelectYesNo callback fired (YES)");
log.Information("[DeathHandler] Waiting for respawn...");
}
}
}
catch (Exception ex)
{
log.Error("[DeathHandler] Error clicking SelectYesNo: " + ex.Message);
}
}
if (wasDead && currentHp != 0 && !player.IsDead)
{
log.Information("[DeathHandler] ========================================");
log.Information("[DeathHandler] === PLAYER RESPAWNED ===");
log.Information("[DeathHandler] ========================================");
IsDead = false;
wasDead = false;
needsTeleportBack = true;
log.Information("[DeathHandler] Preparing to teleport back to death location...");
}
if (needsTeleportBack && !IsDead && currentHp != 0 && (DateTime.Now - deathTime).TotalSeconds >= (double)config.DeathRespawnDelay)
{
TeleportBackToDeathLocation();
needsTeleportBack = false;
}
}
private string GetTerritoryName(uint territoryId)
{
try
{
ExcelSheet<TerritoryType> territorySheet = dataManager.GetExcelSheet<TerritoryType>();
if (territorySheet == null)
{
return territoryId.ToString();
}
if (!territorySheet.HasRow(territoryId))
{
return territoryId.ToString();
}
RowRef<PlaceName> placeNameRef = territorySheet.GetRow(territoryId).PlaceName;
if (placeNameRef.RowId == 0)
{
return territoryId.ToString();
}
PlaceName? placeName = placeNameRef.ValueNullable;
if (!placeName.HasValue)
{
return territoryId.ToString();
}
string name = placeName.Value.Name.ExtractText();
if (!string.IsNullOrEmpty(name))
{
return name;
}
}
catch (Exception ex)
{
log.Error("[DeathHandler] Error getting territory name: " + ex.Message);
}
return territoryId.ToString();
}
private void TeleportBackToDeathLocation()
{
try
{
string territoryName = GetTerritoryName(deathTerritoryId);
log.Information("[DeathHandler] ========================================");
log.Information("[DeathHandler] === TELEPORTING BACK TO DEATH LOCATION ===");
log.Information("[DeathHandler] ========================================");
log.Information($"[DeathHandler] Target Territory: {territoryName} ({deathTerritoryId})");
log.Information($"[DeathHandler] Target Position: {deathPosition}");
if (clientState.TerritoryType == deathTerritoryId)
{
log.Information("[DeathHandler] Already in death territory - using /li to teleport to position");
framework.RunOnFrameworkThread(delegate
{
try
{
string text = $"/li {deathPosition.X:F2}, {deathPosition.Y:F2}, {deathPosition.Z:F2}";
commandManager.ProcessCommand(text);
log.Information("[DeathHandler] ✓ Teleport to position: " + text);
}
catch (Exception ex2)
{
log.Error("[DeathHandler] Failed to send teleport command: " + ex2.Message);
}
}).Wait();
}
else
{
log.Information("[DeathHandler] Different territory - teleporting to death territory");
framework.RunOnFrameworkThread(delegate
{
try
{
string text = "/li " + territoryName;
commandManager.ProcessCommand(text);
log.Information("[DeathHandler] ✓ Teleport to territory: " + text);
}
catch (Exception ex2)
{
log.Error("[DeathHandler] Failed to send teleport command: " + ex2.Message);
}
}).Wait();
}
deathPosition = Vector3.Zero;
deathTerritoryId = 0u;
deathTime = DateTime.MinValue;
log.Information("[DeathHandler] Death handling complete");
}
catch (Exception ex)
{
log.Error("[DeathHandler] Error during teleport: " + ex.Message);
}
}
public void Reset()
{
IsDead = false;
wasDead = false;
needsTeleportBack = false;
hasClickedYesNo = false;
deathPosition = Vector3.Zero;
deathTerritoryId = 0u;
deathTime = DateTime.MinValue;
log.Information("[DeathHandler] State reset");
}
public void Dispose()
{
}
}