punish v6.8.18.0

This commit is contained in:
alydev 2025-10-09 07:47:19 +10:00
commit cfb4dea47e
316 changed files with 554088 additions and 0 deletions

View file

@ -0,0 +1,293 @@
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameUI;
using Microsoft.Extensions.Logging;
using Questionable.Functions;
using Questionable.Model.Gathering;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Gathering;
internal static class DoGather
{
internal sealed record Task(GatheringController.GatheringRequest Request, GatheringNode Node, bool RevisitRequired) : ITask, IRevisitAware
{
public bool RevisitTriggered { get; private set; }
public void OnRevisit()
{
RevisitTriggered = true;
}
public override string ToString()
{
return "DoGather" + (RevisitRequired ? " if revist" : "");
}
}
internal sealed class GatherExecutor(GatheringController gatheringController, GameFunctions gameFunctions, IGameGui gameGui, IClientState clientState, ICondition condition, ILogger<GatherExecutor> logger) : TaskExecutor<Task>()
{
private bool _wasGathering;
private bool _usedLuck;
private SlotInfo? _slotToGather;
private Queue<EAction>? _actionQueue;
protected override bool Start()
{
return true;
}
public unsafe override ETaskResult Update()
{
Task task = base.Task;
if ((object)task != null && task.RevisitRequired && !task.RevisitTriggered)
{
logger.LogInformation("No revisit");
return ETaskResult.TaskComplete;
}
if (gatheringController.HasNodeDisappeared(base.Task.Node))
{
logger.LogInformation("Node disappeared");
return ETaskResult.TaskComplete;
}
if (gameFunctions.GetFreeInventorySlots() == 0)
{
throw new TaskException("Inventory full");
}
if (condition[ConditionFlag.Gathering])
{
if (gameGui.TryGetAddonByName<AtkUnitBase>("GatheringMasterpiece", out var _))
{
return ETaskResult.TaskComplete;
}
_wasGathering = true;
if (gameGui.TryGetAddonByName<AddonGathering>("Gathering", out var addonPtr2))
{
if (gatheringController.HasRequestedItems())
{
addonPtr2->FireCallbackInt(-1);
}
else
{
List<SlotInfo> list = ReadSlots(addonPtr2);
if (base.Task.Request.Collectability > 0)
{
SlotInfo slotInfo = list.Single((SlotInfo x) => x.ItemId == base.Task.Request.ItemId);
addonPtr2->FireCallbackInt(slotInfo.Index);
}
else
{
NodeCondition nodeCondition = new NodeCondition(addonPtr2->AtkValues[110].UInt, addonPtr2->AtkValues[111].UInt);
if (_actionQueue != null && _actionQueue.TryPeek(out var result))
{
if (gameFunctions.UseAction(result))
{
logger.LogInformation("Used action {Action} on node", result);
_actionQueue.Dequeue();
}
return ETaskResult.StillRunning;
}
_actionQueue = GetNextActions(nodeCondition, list);
if (_actionQueue == null)
{
logger.LogInformation("Skipping the rest of gathering...");
addonPtr2->FireCallbackInt(-1);
return ETaskResult.TaskComplete;
}
if (_actionQueue.Count == 0)
{
SlotInfo slotInfo2 = _slotToGather ?? list.SingleOrDefault((SlotInfo x) => x.ItemId == base.Task.Request.ItemId) ?? list.MinBy((SlotInfo x) => x.ItemId);
switch (slotInfo2?.ItemId)
{
case 2u:
case 3u:
case 4u:
case 5u:
case 6u:
case 7u:
case 8u:
case 9u:
case 10u:
case 11u:
case 12u:
case 13u:
case 14u:
case 15u:
case 16u:
case 17u:
case 18u:
case 19u:
if (InventoryManager.Instance()->GetInventoryItemCount(slotInfo2.ItemId, isHq: false, checkEquipped: true, checkArmory: true, 0) == 9999)
{
slotInfo2 = null;
}
break;
}
if (slotInfo2 != null)
{
addonPtr2->FireCallbackInt(slotInfo2.Index);
}
else
{
addonPtr2->FireCallbackInt(-1);
}
}
}
}
}
}
if (!_wasGathering || condition[ConditionFlag.Gathering])
{
return ETaskResult.StillRunning;
}
return ETaskResult.TaskComplete;
}
private unsafe List<SlotInfo> ReadSlots(AddonGathering* addonGathering)
{
List<SlotInfo> list = new List<SlotInfo>();
for (int i = 0; i < 8; i++)
{
uint num = addonGathering->ItemIds[i];
if (num != 0)
{
AtkComponentCheckBox* value = addonGathering->GatheredItemComponentCheckbox[i].Value;
if (!int.TryParse(value->UldManager.SearchNodeById(10u)->GetAsAtkTextNode()->NodeText.ToString(), out var result))
{
result = 0;
}
if (!int.TryParse(value->UldManager.SearchNodeById(16u)->GetAsAtkTextNode()->NodeText.ToString(), out var result2))
{
result2 = 0;
}
AtkTextNode* asAtkTextNode = value->UldManager.SearchNodeById(31u)->GetAsAtkComponentNode()->Component->UldManager.SearchNodeById(7u)->GetAsAtkTextNode();
if (!asAtkTextNode->IsVisible() || !int.TryParse(asAtkTextNode->NodeText.ToString(), out var result3))
{
result3 = 1;
}
SlotInfo item = new SlotInfo(i, num, result, result2, result3);
list.Add(item);
}
}
logger.LogTrace("Slots: {Slots}", string.Join(", ", list));
return list;
}
private Queue<EAction>? GetNextActions(NodeCondition nodeCondition, List<SlotInfo> slots)
{
if (_slotToGather != null && slots.All((SlotInfo x) => x.Index != _slotToGather.Index))
{
_slotToGather = null;
}
Queue<EAction> queue = new Queue<EAction>();
if (!gameFunctions.HasStatus(EStatus.GatheringRateUp))
{
if (base.Task.Request.AlternativeItemId != 0)
{
SlotInfo slotInfo = slots.Single((SlotInfo x) => x.ItemId == base.Task.Request.AlternativeItemId);
if (slotInfo.GatheringChance == 100)
{
_slotToGather = slotInfo;
return queue;
}
if (slotInfo.GatheringChance > 0)
{
if (slotInfo.GatheringChance >= 95 && CanUseAction(EAction.SharpVision1, EAction.FieldMastery1))
{
_slotToGather = slotInfo;
queue.Enqueue(PickAction(EAction.SharpVision1, EAction.FieldMastery1));
return queue;
}
if (slotInfo.GatheringChance >= 85 && CanUseAction(EAction.SharpVision2, EAction.FieldMastery2))
{
_slotToGather = slotInfo;
queue.Enqueue(PickAction(EAction.SharpVision2, EAction.FieldMastery2));
return queue;
}
if (slotInfo.GatheringChance >= 50 && CanUseAction(EAction.SharpVision3, EAction.FieldMastery3))
{
_slotToGather = slotInfo;
queue.Enqueue(PickAction(EAction.SharpVision3, EAction.FieldMastery3));
return queue;
}
}
}
SlotInfo slotInfo2 = slots.SingleOrDefault((SlotInfo x) => x.ItemId == base.Task.Request.ItemId);
if (slotInfo2 == null)
{
if (!_usedLuck && nodeCondition.CurrentIntegrity == nodeCondition.MaxIntegrity && CanUseAction(EAction.LuckOfTheMountaineer, EAction.LuckOfThePioneer))
{
_usedLuck = true;
queue.Enqueue(PickAction(EAction.LuckOfTheMountaineer, EAction.LuckOfThePioneer));
return queue;
}
if (_usedLuck)
{
if (nodeCondition.CurrentIntegrity != nodeCondition.MaxIntegrity)
{
return null;
}
_slotToGather = slots.MinBy((SlotInfo x) => x.ItemId);
return queue;
}
}
slotInfo2 = slots.SingleOrDefault((SlotInfo x) => x.ItemId == base.Task.Request.ItemId);
if ((object)slotInfo2 != null)
{
int gatheringChance = slotInfo2.GatheringChance;
if (gatheringChance > 0 && gatheringChance < 100)
{
if (slotInfo2.GatheringChance >= 95 && CanUseAction(EAction.SharpVision1, EAction.FieldMastery1))
{
queue.Enqueue(PickAction(EAction.SharpVision1, EAction.FieldMastery1));
return queue;
}
if (slotInfo2.GatheringChance >= 85 && CanUseAction(EAction.SharpVision2, EAction.FieldMastery2))
{
queue.Enqueue(PickAction(EAction.SharpVision2, EAction.FieldMastery2));
return queue;
}
if (slotInfo2.GatheringChance >= 50 && CanUseAction(EAction.SharpVision3, EAction.FieldMastery3))
{
queue.Enqueue(PickAction(EAction.SharpVision3, EAction.FieldMastery3));
return queue;
}
}
}
}
return queue;
}
private EAction PickAction(EAction minerAction, EAction botanistAction)
{
if (clientState.LocalPlayer?.ClassJob.RowId == 16)
{
return minerAction;
}
return botanistAction;
}
private unsafe bool CanUseAction(EAction minerAction, EAction botanistAction)
{
EAction actionId = PickAction(minerAction, botanistAction);
return ActionManager.Instance()->GetActionStatus(ActionType.Action, (uint)actionId, 3758096384uL, checkRecastActive: true, checkCastingActive: true, null) == 0;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
private sealed record SlotInfo(int Index, uint ItemId, int GatheringChance, int BoonChance, int Quantity);
private sealed record NodeCondition(uint CurrentIntegrity, uint MaxIntegrity);
}

View file

@ -0,0 +1,199 @@
using System.Collections.Generic;
using Dalamud.Game.Text;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameUI;
using Microsoft.Extensions.Logging;
using Questionable.Functions;
using Questionable.Model.Gathering;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Gathering;
internal static class DoGatherCollectable
{
internal sealed record Task(GatheringController.GatheringRequest Request, GatheringNode Node, bool RevisitRequired) : ITask, IRevisitAware
{
public bool RevisitTriggered { get; private set; }
public void OnRevisit()
{
RevisitTriggered = true;
}
public override string ToString()
{
return $"DoGatherCollectable({SeIconChar.Collectible.ToIconString()}/{Request.Collectability}){(RevisitRequired ? " if revist" : "")}";
}
}
internal sealed class GatherCollectableExecutor(GatheringController gatheringController, GameFunctions gameFunctions, IClientState clientState, IGameGui gameGui, ILogger<GatherCollectableExecutor> logger) : TaskExecutor<Task>()
{
private Queue<EAction>? _actionQueue;
private bool? _expectedScrutiny;
protected override bool Start()
{
return true;
}
public unsafe override ETaskResult Update()
{
if (base.Task.RevisitRequired && !base.Task.RevisitTriggered)
{
logger.LogInformation("No revisit");
return ETaskResult.TaskComplete;
}
if (gatheringController.HasNodeDisappeared(base.Task.Node))
{
logger.LogInformation("Node disappeared");
return ETaskResult.TaskComplete;
}
if (gatheringController.HasRequestedItems())
{
if (gameGui.TryGetAddonByName<AtkUnitBase>("GatheringMasterpiece", out var addonPtr))
{
addonPtr->FireCallbackInt(1);
return ETaskResult.StillRunning;
}
if (gameGui.TryGetAddonByName<AtkUnitBase>("Gathering", out addonPtr))
{
addonPtr->FireCallbackInt(-1);
return ETaskResult.TaskComplete;
}
}
if (gameFunctions.GetFreeInventorySlots() == 0)
{
throw new TaskException("Inventory full");
}
NodeCondition nodeCondition = GetNodeCondition();
if (nodeCondition == null)
{
return ETaskResult.TaskComplete;
}
if (_expectedScrutiny.HasValue)
{
if (nodeCondition.ScrutinyActive != _expectedScrutiny)
{
return ETaskResult.StillRunning;
}
_expectedScrutiny = null;
return ETaskResult.StillRunning;
}
if (_actionQueue != null && _actionQueue.TryPeek(out var result))
{
if (gameFunctions.UseAction(result))
{
bool? expectedScrutiny;
switch (result)
{
case EAction.ScrutinyMiner:
case EAction.ScrutinyBotanist:
expectedScrutiny = true;
break;
case EAction.ScourMiner:
case EAction.MeticulousMiner:
case EAction.ScourBotanist:
case EAction.MeticulousBotanist:
expectedScrutiny = false;
break;
default:
expectedScrutiny = null;
break;
}
_expectedScrutiny = expectedScrutiny;
logger.LogInformation("Used action {Action} on node", result);
_actionQueue.Dequeue();
}
return ETaskResult.StillRunning;
}
if (nodeCondition.CollectabilityToGoal(base.Task.Request.Collectability) != 0)
{
_actionQueue = GetNextActions(nodeCondition);
if (_actionQueue != null)
{
foreach (EAction item in _actionQueue)
{
logger.LogInformation("Next Actions {Action}", item);
}
return ETaskResult.StillRunning;
}
}
_actionQueue = new Queue<EAction>();
_actionQueue.Enqueue(PickAction(EAction.CollectMiner, EAction.CollectBotanist));
return ETaskResult.StillRunning;
}
private unsafe NodeCondition? GetNodeCondition()
{
if (gameGui.TryGetAddonByName<AtkUnitBase>("GatheringMasterpiece", out var addonPtr))
{
AtkValue* atkValues = addonPtr->AtkValues;
return new NodeCondition(atkValues[13].UInt, atkValues[14].UInt, atkValues[62].UInt, atkValues[63].UInt, atkValues[54].Bool, atkValues[48].UInt, atkValues[51].UInt);
}
return null;
}
private Queue<EAction> GetNextActions(NodeCondition nodeCondition)
{
uint currentGp = clientState.LocalPlayer.CurrentGp;
logger.LogTrace("Getting next actions (with {GP} GP, {MeticulousCollectability}~ meticulous, {ScourCollectability}~ scour)", currentGp, nodeCondition.CollectabilityFromMeticulous, nodeCondition.CollectabilityFromScour);
Queue<EAction> queue = new Queue<EAction>();
uint num = nodeCondition.CollectabilityToGoal(base.Task.Request.Collectability);
if (num <= nodeCondition.CollectabilityFromMeticulous)
{
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ meticulous", num, nodeCondition.CollectabilityFromMeticulous);
queue.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist));
return queue;
}
if (num <= nodeCondition.CollectabilityFromScour)
{
logger.LogTrace("Can get all needed {NeededCollectability} from {Collectability}~ scour", num, nodeCondition.CollectabilityFromScour);
queue.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist));
return queue;
}
if (!nodeCondition.ScrutinyActive && currentGp >= 200)
{
logger.LogTrace("Still missing {NeededCollectability} collectability, scrutiny inactive", num);
queue.Enqueue(PickAction(EAction.ScrutinyMiner, EAction.ScrutinyBotanist));
return queue;
}
if (nodeCondition.ScrutinyActive)
{
logger.LogTrace("Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ meticulous", num, nodeCondition.CollectabilityFromMeticulous);
queue.Enqueue(PickAction(EAction.MeticulousMiner, EAction.MeticulousBotanist));
return queue;
}
logger.LogTrace("Scrutiny active, need {NeededCollectability} and we expect {Collectability}~ scour", num, nodeCondition.CollectabilityFromScour);
queue.Enqueue(PickAction(EAction.ScourMiner, EAction.ScourBotanist));
return queue;
}
private EAction PickAction(EAction minerAction, EAction botanistAction)
{
if (clientState.LocalPlayer?.ClassJob.RowId == 16)
{
return minerAction;
}
return botanistAction;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
private sealed record NodeCondition(uint CurrentCollectability, uint MaxCollectability, uint CurrentIntegrity, uint MaxIntegrity, bool ScrutinyActive, uint CollectabilityFromScour, uint CollectabilityFromMeticulous)
{
public uint CollectabilityToGoal(uint goal)
{
if (goal >= CurrentCollectability)
{
return goal - CurrentCollectability;
}
return (CurrentCollectability == 0) ? 1u : 0u;
}
}
}

View file

@ -0,0 +1,70 @@
using System.Globalization;
using System.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.Movement;
using Questionable.Functions;
using Questionable.Model;
using Questionable.Model.Gathering;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Gathering;
internal static class MoveToLandingLocation
{
internal sealed record Task(ushort TerritoryId, bool FlyBetweenNodes, GatheringNode GatheringNode) : ITask
{
public override string ToString()
{
return $"Land/{FlyBetweenNodes}";
}
}
internal sealed class MoveToLandingLocationExecutor(MoveExecutor moveExecutor, GameFunctions gameFunctions, IObjectTable objectTable, ILogger<MoveToLandingLocationExecutor> logger) : TaskExecutor<Task>(), IToastAware, ITaskExecutor
{
private ITask _moveTask;
protected override bool Start()
{
GatheringLocation location = base.Task.GatheringNode.Locations.First();
if (base.Task.GatheringNode.Locations.Count > 1)
{
IGameObject gameObject = objectTable.SingleOrDefault((IGameObject x) => x.ObjectKind == ObjectKind.GatheringPoint && x.DataId == base.Task.GatheringNode.DataId && x.IsTargetable);
if (gameObject == null)
{
return false;
}
location = base.Task.GatheringNode.Locations.Single((GatheringLocation x) => Vector3.Distance(x.Position, gameObject.Position) < 0.1f);
}
var (vector, num, num2) = GatheringMath.CalculateLandingLocation(location);
logger.LogInformation("Preliminary landing location: {Location}, with degrees = {Degrees}, range = {Range}", vector.ToString("G", CultureInfo.InvariantCulture), num, num2);
bool flag = base.Task.FlyBetweenNodes && gameFunctions.IsFlyingUnlocked(base.Task.TerritoryId);
ushort territoryId = base.Task.TerritoryId;
Vector3 destination = vector;
float? stopDistance = 0.25f;
uint? dataId = base.Task.GatheringNode.DataId;
bool fly = flag;
_moveTask = new MoveTask(territoryId, destination, null, stopDistance, dataId, DisableNavmesh: false, null, fly, Land: false, IgnoreDistanceToObject: true, RestartNavigation: true, EInteractionType.Gather);
return moveExecutor.Start(_moveTask);
}
public override ETaskResult Update()
{
return moveExecutor.Update();
}
public bool OnErrorToast(SeString message)
{
return moveExecutor.OnErrorToast(message);
}
public override bool ShouldInterruptOnDamage()
{
return moveExecutor.ShouldInterruptOnDamage();
}
}
}

View file

@ -0,0 +1,108 @@
using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI;
using LLib.GameUI;
using Microsoft.Extensions.Logging;
using Questionable.Model;
using Questionable.Model.Questing;
namespace Questionable.Controller.Steps.Gathering;
internal static class TurnInDelivery
{
internal sealed class Factory : SimpleTaskFactory
{
public override ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
if (!(quest.Id is SatisfactionSupplyNpcId) || sequence.Sequence != 1)
{
return null;
}
return new Task();
}
}
internal sealed record Task : ITask
{
public override string ToString()
{
return "WeeklyDeliveryTurnIn";
}
}
internal sealed class SatisfactionSupplyTurnIn(ILogger<SatisfactionSupplyTurnIn> logger) : TaskExecutor<Task>()
{
private ushort? _remainingAllowances;
protected override bool Start()
{
return true;
}
public unsafe override ETaskResult Update()
{
AgentSatisfactionSupply* ptr = AgentSatisfactionSupply.Instance();
if (ptr == null || !ptr->IsAgentActive())
{
if (_remainingAllowances.HasValue)
{
return ETaskResult.TaskComplete;
}
return ETaskResult.StillRunning;
}
uint addonId = ptr->GetAddonId();
if (addonId == 0)
{
if (_remainingAllowances.HasValue)
{
return ETaskResult.TaskComplete;
}
return ETaskResult.StillRunning;
}
AtkUnitBase* addonById = LAddon.GetAddonById(addonId);
if (addonById == null || !LAddon.IsAddonReady(addonById))
{
return ETaskResult.StillRunning;
}
ushort remainingAllowances = ptr->NpcData.RemainingAllowances;
if (remainingAllowances == 0)
{
logger.LogInformation("No remaining weekly allowances");
addonById->FireCallbackInt(0);
return ETaskResult.TaskComplete;
}
if (InventoryManager.Instance()->GetInventoryItemCount(ptr->Items[1].Id, isHq: false, checkEquipped: true, checkArmory: true, (short)ptr->Items[1].Collectability1) == 0)
{
logger.LogInformation("Inventory has no {ItemId}", ptr->Items[1].Id);
addonById->FireCallbackInt(0);
return ETaskResult.TaskComplete;
}
if (_remainingAllowances == remainingAllowances)
{
return ETaskResult.StillRunning;
}
logger.LogInformation("Attempting turn-in (remaining allowances: {RemainingAllowances})", remainingAllowances);
_remainingAllowances = remainingAllowances;
AtkValue* values = stackalloc AtkValue[2]
{
new AtkValue
{
Type = ValueType.Int,
Int = 1
},
new AtkValue
{
Type = ValueType.Int,
Int = 1
}
};
addonById->FireCallback(2u, values);
return ETaskResult.StillRunning;
}
public override bool ShouldInterruptOnDamage()
{
return false;
}
}
}