2023-10-01 20:50:21 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2024-03-20 18:52:54 +00:00
|
|
|
|
using System.Globalization;
|
2023-10-01 20:50:21 +00:00
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Numerics;
|
|
|
|
|
using Dalamud.Game.ClientState.Objects.Enums;
|
|
|
|
|
using Dalamud.Game.ClientState.Objects.Types;
|
|
|
|
|
using Dalamud.Memory;
|
|
|
|
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
|
|
|
|
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
|
|
|
|
using FFXIVClientStructs.FFXIV.Client.UI;
|
|
|
|
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
|
|
|
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
2023-10-11 08:52:48 +00:00
|
|
|
|
using LLib.GameUI;
|
2023-10-01 20:50:21 +00:00
|
|
|
|
using Workshoppa.GameData;
|
|
|
|
|
|
|
|
|
|
namespace Workshoppa;
|
|
|
|
|
|
|
|
|
|
partial class WorkshopPlugin
|
|
|
|
|
{
|
|
|
|
|
private unsafe void InteractWithTarget(GameObject obj)
|
|
|
|
|
{
|
2023-10-04 22:07:36 +00:00
|
|
|
|
_pluginLog.Information($"Setting target to {obj}");
|
2023-10-01 20:50:21 +00:00
|
|
|
|
/*
|
|
|
|
|
if (_targetManager.Target == null || _targetManager.Target != obj)
|
|
|
|
|
{
|
|
|
|
|
_targetManager.Target = obj;
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
TargetSystem.Instance()->InteractWithObject(
|
|
|
|
|
(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)obj.Address, false);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 19:56:12 +00:00
|
|
|
|
private float GetDistanceToEventObject(IReadOnlyList<uint> npcIds, out GameObject? o)
|
2023-10-01 20:50:21 +00:00
|
|
|
|
{
|
2023-10-14 14:26:06 +00:00
|
|
|
|
Vector3? localPlayerPosition = _clientState.LocalPlayer?.Position;
|
|
|
|
|
if (localPlayerPosition != null)
|
2023-10-01 20:50:21 +00:00
|
|
|
|
{
|
2023-10-14 14:26:06 +00:00
|
|
|
|
foreach (var obj in _objectTable)
|
2023-10-01 20:50:21 +00:00
|
|
|
|
{
|
2023-10-14 14:26:06 +00:00
|
|
|
|
if (obj.ObjectKind == ObjectKind.EventObj)
|
2023-10-01 20:50:21 +00:00
|
|
|
|
{
|
2023-10-14 14:26:06 +00:00
|
|
|
|
if (npcIds.Contains(GetNpcId(obj)))
|
|
|
|
|
{
|
|
|
|
|
o = obj;
|
2023-11-17 14:13:13 +00:00
|
|
|
|
float distance = Vector3.Distance(localPlayerPosition.Value,
|
|
|
|
|
obj.Position + new Vector3(0, -2, 0));
|
2023-10-14 14:26:06 +00:00
|
|
|
|
if (distance > 0.01)
|
|
|
|
|
return distance;
|
|
|
|
|
}
|
2023-10-01 20:50:21 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
o = null;
|
|
|
|
|
return float.MaxValue;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 19:56:12 +00:00
|
|
|
|
private unsafe uint GetNpcId(GameObject obj)
|
2023-10-01 20:50:21 +00:00
|
|
|
|
{
|
2023-10-05 19:56:12 +00:00
|
|
|
|
return ((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)obj.Address)->GetNpcID();
|
2023-10-01 20:50:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private unsafe AtkUnitBase* GetCompanyCraftingLogAddon()
|
|
|
|
|
{
|
2023-11-17 14:13:13 +00:00
|
|
|
|
if (_gameGui.TryGetAddonByName<AtkUnitBase>("CompanyCraftRecipeNoteBook", out var addon) &&
|
|
|
|
|
LAddon.IsAddonReady(addon))
|
2023-10-01 20:50:21 +00:00
|
|
|
|
return addon;
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// This actually has different addons depending on the craft, e.g. SubmarinePartsMenu.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
private unsafe AtkUnitBase* GetMaterialDeliveryAddon()
|
|
|
|
|
{
|
|
|
|
|
var agentInterface = AgentModule.Instance()->GetAgentByInternalId(AgentId.CompanyCraftMaterial);
|
|
|
|
|
if (agentInterface != null && agentInterface->IsAgentActive())
|
|
|
|
|
{
|
|
|
|
|
var addonId = agentInterface->GetAddonID();
|
|
|
|
|
if (addonId == 0)
|
|
|
|
|
return null;
|
|
|
|
|
|
2023-10-11 08:52:48 +00:00
|
|
|
|
AtkUnitBase* addon = LAddon.GetAddonById(addonId);
|
|
|
|
|
if (LAddon.IsAddonReady(addon))
|
2023-10-01 20:50:21 +00:00
|
|
|
|
return addon;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private unsafe bool SelectSelectString(string marker, int choice, Predicate<string> predicate)
|
|
|
|
|
{
|
2023-10-11 08:52:48 +00:00
|
|
|
|
if (_gameGui.TryGetAddonByName<AddonSelectString>("SelectString", out var addonSelectString) &&
|
|
|
|
|
LAddon.IsAddonReady(&addonSelectString->AtkUnitBase))
|
2023-10-01 20:50:21 +00:00
|
|
|
|
{
|
|
|
|
|
int entries = addonSelectString->PopupMenu.PopupMenu.EntryCount;
|
|
|
|
|
if (entries < choice)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
var textPointer = addonSelectString->PopupMenu.PopupMenu.EntryNames[choice];
|
|
|
|
|
if (textPointer == null)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
var text = MemoryHelper.ReadSeStringNullTerminated((nint)textPointer).ToString();
|
2023-10-04 22:07:36 +00:00
|
|
|
|
_pluginLog.Verbose($"SelectSelectString for {marker}, Choice would be '{text}'");
|
2023-10-01 20:50:21 +00:00
|
|
|
|
if (predicate(text))
|
|
|
|
|
{
|
|
|
|
|
addonSelectString->AtkUnitBase.FireCallbackInt(choice);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private unsafe bool SelectSelectYesno(int choice, Predicate<string> predicate)
|
|
|
|
|
{
|
2023-10-11 08:52:48 +00:00
|
|
|
|
if (_gameGui.TryGetAddonByName<AddonSelectYesno>("SelectYesno", out var addonSelectYesno) &&
|
|
|
|
|
LAddon.IsAddonReady(&addonSelectYesno->AtkUnitBase))
|
2023-10-01 20:50:21 +00:00
|
|
|
|
{
|
|
|
|
|
var text = MemoryHelper.ReadSeString(&addonSelectYesno->PromptText->NodeText).ToString();
|
2024-03-20 18:52:54 +00:00
|
|
|
|
text = text
|
|
|
|
|
.Replace("\n", "", StringComparison.Ordinal)
|
|
|
|
|
.Replace("\r", "", StringComparison.Ordinal);
|
2023-10-01 20:50:21 +00:00
|
|
|
|
if (predicate(text))
|
|
|
|
|
{
|
2023-10-04 22:07:36 +00:00
|
|
|
|
_pluginLog.Information($"Selecting choice {choice} for '{text}'");
|
2023-10-01 20:50:21 +00:00
|
|
|
|
addonSelectYesno->AtkUnitBase.FireCallbackInt(choice);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2023-10-04 22:07:36 +00:00
|
|
|
|
_pluginLog.Verbose($"Text {text} does not match");
|
2023-10-01 20:50:21 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private unsafe CraftState? ReadCraftState(AtkUnitBase* addonMaterialDelivery)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var atkValues = addonMaterialDelivery->AtkValues;
|
|
|
|
|
if (addonMaterialDelivery->AtkValuesCount == 157 && atkValues != null)
|
|
|
|
|
{
|
|
|
|
|
uint resultItem = atkValues[0].UInt;
|
|
|
|
|
uint stepsComplete = atkValues[6].UInt;
|
|
|
|
|
uint stepsTotal = atkValues[7].UInt;
|
|
|
|
|
uint listItemCount = atkValues[11].UInt;
|
|
|
|
|
List<CraftItem> items = Enumerable.Range(0, (int)listItemCount)
|
|
|
|
|
.Select(i => new CraftItem
|
|
|
|
|
{
|
|
|
|
|
ItemId = atkValues[12 + i].UInt,
|
|
|
|
|
IconId = atkValues[24 + i].UInt,
|
2023-10-11 08:52:48 +00:00
|
|
|
|
ItemName = atkValues[36 + i].ReadAtkString(),
|
2023-10-01 20:50:21 +00:00
|
|
|
|
CrafterIconId = atkValues[48 + i].Int,
|
|
|
|
|
ItemCountPerStep = atkValues[60 + i].UInt,
|
|
|
|
|
ItemCountNQ = atkValues[72 + i].UInt,
|
|
|
|
|
ItemCountHQ = ParseAtkItemCountHq(atkValues[84 + i]),
|
|
|
|
|
Experience = atkValues[96 + i].UInt,
|
|
|
|
|
StepsComplete = atkValues[108 + i].UInt,
|
|
|
|
|
StepsTotal = atkValues[120 + i].UInt,
|
|
|
|
|
Finished = atkValues[132 + i].UInt > 0,
|
|
|
|
|
CrafterMinimumLevel = atkValues[144 + i].UInt,
|
|
|
|
|
})
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
return new CraftState
|
|
|
|
|
{
|
|
|
|
|
ResultItem = resultItem,
|
|
|
|
|
StepsComplete = stepsComplete,
|
|
|
|
|
StepsTotal = stepsTotal,
|
|
|
|
|
Items = items,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
2023-10-04 22:07:36 +00:00
|
|
|
|
_pluginLog.Warning(e, "Could not parse CompanyCraftMaterial info");
|
2023-10-01 20:50:21 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-20 18:52:54 +00:00
|
|
|
|
private static uint ParseAtkItemCountHq(AtkValue atkValue)
|
2023-10-01 20:50:21 +00:00
|
|
|
|
{
|
|
|
|
|
// NQ / HQ string
|
|
|
|
|
// I have no clue, but it doesn't seme like the available HQ item count is strored anywhere in the atkvalues??
|
2023-10-11 08:52:48 +00:00
|
|
|
|
string? s = atkValue.ReadAtkString();
|
2023-10-01 20:50:21 +00:00
|
|
|
|
if (s != null)
|
|
|
|
|
{
|
2024-03-20 18:52:54 +00:00
|
|
|
|
var parts = s.Replace("\ue03c", "", StringComparison.Ordinal).Split('/');
|
2023-10-01 20:50:21 +00:00
|
|
|
|
if (parts.Length > 1)
|
|
|
|
|
{
|
2024-03-20 18:52:54 +00:00
|
|
|
|
return uint.Parse(
|
|
|
|
|
parts[1]
|
|
|
|
|
.Replace(",", "", StringComparison.Ordinal)
|
|
|
|
|
.Replace(".", "", StringComparison.Ordinal)
|
|
|
|
|
.Trim(),
|
|
|
|
|
CultureInfo.InvariantCulture);
|
2023-10-01 20:50:21 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private unsafe bool HasItemInSingleSlot(uint itemId, uint count)
|
|
|
|
|
{
|
|
|
|
|
var inventoryManger = InventoryManager.Instance();
|
|
|
|
|
if (inventoryManger == null)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
for (InventoryType t = InventoryType.Inventory1; t <= InventoryType.Inventory4; ++t)
|
|
|
|
|
{
|
|
|
|
|
var container = inventoryManger->GetInventoryContainer(t);
|
|
|
|
|
for (int i = 0; i < container->Size; ++i)
|
|
|
|
|
{
|
|
|
|
|
var item = container->GetInventorySlot(i);
|
|
|
|
|
if (item == null)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (item->ItemID == itemId && item->Quantity >= count)
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2023-10-13 20:08:22 +00:00
|
|
|
|
|
2023-10-24 22:19:42 +00:00
|
|
|
|
public bool HasFreeInventorySlot() => GetFreeInventorySlots() > 0;
|
|
|
|
|
|
|
|
|
|
public unsafe int GetFreeInventorySlots()
|
2023-10-13 20:08:22 +00:00
|
|
|
|
{
|
|
|
|
|
var inventoryManger = InventoryManager.Instance();
|
|
|
|
|
if (inventoryManger == null)
|
2023-10-24 22:19:42 +00:00
|
|
|
|
return 0;
|
2023-10-13 20:08:22 +00:00
|
|
|
|
|
2023-10-24 22:19:42 +00:00
|
|
|
|
int count = 0;
|
2023-10-13 20:08:22 +00:00
|
|
|
|
for (InventoryType t = InventoryType.Inventory1; t <= InventoryType.Inventory4; ++t)
|
|
|
|
|
{
|
|
|
|
|
var container = inventoryManger->GetInventoryContainer(t);
|
|
|
|
|
for (int i = 0; i < container->Size; ++i)
|
|
|
|
|
{
|
|
|
|
|
var item = container->GetInventorySlot(i);
|
|
|
|
|
if (item == null || item->ItemID == 0)
|
2023-10-24 22:19:42 +00:00
|
|
|
|
++count;
|
2023-10-13 20:08:22 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-24 22:19:42 +00:00
|
|
|
|
return count;
|
2023-10-13 20:08:22 +00:00
|
|
|
|
}
|
2023-11-17 14:13:13 +00:00
|
|
|
|
|
|
|
|
|
public unsafe int DetermineMaxStackSize(uint itemId)
|
|
|
|
|
{
|
|
|
|
|
var inventoryManger = InventoryManager.Instance();
|
|
|
|
|
if (inventoryManger == null)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
int max = 0;
|
|
|
|
|
for (InventoryType t = InventoryType.Inventory1; t <= InventoryType.Inventory4; ++t)
|
|
|
|
|
{
|
|
|
|
|
var container = inventoryManger->GetInventoryContainer(t);
|
|
|
|
|
for (int i = 0; i < container->Size; ++i)
|
|
|
|
|
{
|
|
|
|
|
var item = container->GetInventorySlot(i);
|
|
|
|
|
if (item == null || item->ItemID == 0)
|
|
|
|
|
return 99;
|
|
|
|
|
|
|
|
|
|
if (item->ItemID == itemId)
|
|
|
|
|
{
|
|
|
|
|
max += (999 - (int)item->Quantity);
|
|
|
|
|
if (max >= 99)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Math.Min(99, max);
|
|
|
|
|
}
|
2023-10-01 20:50:21 +00:00
|
|
|
|
}
|