forked from liza/Workshoppa
🎉 Initial rework complete
This commit is contained in:
commit
8b8245bf0a
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/.idea
|
||||||
|
*.user
|
16
Workshoppa.sln
Normal file
16
Workshoppa.sln
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Workshoppa", "Workshoppa\Workshoppa.csproj", "{4C2E2AD7-D897-4476-A17A-838932D95223}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{4C2E2AD7-D897-4476-A17A-838932D95223}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4C2E2AD7-D897-4476-A17A-838932D95223}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4C2E2AD7-D897-4476-A17A-838932D95223}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4C2E2AD7-D897-4476-A17A-838932D95223}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
3
Workshoppa/.gitignore
vendored
Normal file
3
Workshoppa/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/dist
|
||||||
|
/obj
|
||||||
|
/bin
|
161
Workshoppa/Callback.cs
Normal file
161
Workshoppa/Callback.cs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using Dalamud.Memory;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||||
|
|
||||||
|
namespace Workshoppa;
|
||||||
|
|
||||||
|
public sealed unsafe class Callback
|
||||||
|
{
|
||||||
|
private delegate byte AtkUnitBase_FireCallbackDelegate(AtkUnitBase* @base, int valueCount, AtkValue* values,
|
||||||
|
byte updateState);
|
||||||
|
|
||||||
|
private readonly AtkUnitBase_FireCallbackDelegate FireCallback;
|
||||||
|
|
||||||
|
public static readonly AtkValue ZeroAtkValue = new() { Type = 0, Int = 0 };
|
||||||
|
|
||||||
|
public Callback(SigScanner sigScanner)
|
||||||
|
{
|
||||||
|
var ptr = sigScanner.ScanText("E8 ?? ?? ?? ?? 8B 4C 24 20 0F B6 D8");
|
||||||
|
FireCallback = Marshal.GetDelegateForFunctionPointer<AtkUnitBase_FireCallbackDelegate>(ptr);
|
||||||
|
PluginLog.Information($"Initialized Callback module, FireCallback = 0x{ptr:X16}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FireRaw(AtkUnitBase* @base, int valueCount, AtkValue* values, byte updateState = 0)
|
||||||
|
{
|
||||||
|
FireCallback(@base, valueCount, values, updateState);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Fire(AtkUnitBase* @base, bool updateState, params object[] values)
|
||||||
|
{
|
||||||
|
if (@base == null) throw new Exception("Null UnitBase");
|
||||||
|
var atkValues = (AtkValue*)Marshal.AllocHGlobal(values.Length * sizeof(AtkValue));
|
||||||
|
if (atkValues == null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (var i = 0; i < values.Length; i++)
|
||||||
|
{
|
||||||
|
var v = values[i];
|
||||||
|
switch (v)
|
||||||
|
{
|
||||||
|
case uint uintValue:
|
||||||
|
atkValues[i].Type = ValueType.UInt;
|
||||||
|
atkValues[i].UInt = uintValue;
|
||||||
|
break;
|
||||||
|
case int intValue:
|
||||||
|
atkValues[i].Type = ValueType.Int;
|
||||||
|
atkValues[i].Int = intValue;
|
||||||
|
break;
|
||||||
|
case float floatValue:
|
||||||
|
atkValues[i].Type = ValueType.Float;
|
||||||
|
atkValues[i].Float = floatValue;
|
||||||
|
break;
|
||||||
|
case bool boolValue:
|
||||||
|
atkValues[i].Type = ValueType.Bool;
|
||||||
|
atkValues[i].Byte = (byte)(boolValue ? 1 : 0);
|
||||||
|
break;
|
||||||
|
case string stringValue:
|
||||||
|
{
|
||||||
|
atkValues[i].Type = ValueType.String;
|
||||||
|
var stringBytes = Encoding.UTF8.GetBytes(stringValue);
|
||||||
|
var stringAlloc = Marshal.AllocHGlobal(stringBytes.Length + 1);
|
||||||
|
Marshal.Copy(stringBytes, 0, stringAlloc, stringBytes.Length);
|
||||||
|
Marshal.WriteByte(stringAlloc, stringBytes.Length, 0);
|
||||||
|
atkValues[i].String = (byte*)stringAlloc;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case AtkValue rawValue:
|
||||||
|
{
|
||||||
|
atkValues[i] = rawValue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new ArgumentException($"Unable to convert type {v.GetType()} to AtkValue");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if false
|
||||||
|
List<string> callbackValues = new();
|
||||||
|
for (var i = 0; i < values.Length; i++)
|
||||||
|
{
|
||||||
|
callbackValues.Add(
|
||||||
|
$" Value {i}: [input: {values[i]}/{values[i]?.GetType().Name}] -> {DecodeValue(atkValues[i])})");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PluginLog.Verbose(
|
||||||
|
$"Firing callback: {MemoryHelper.ReadStringNullTerminated((nint)@base->Name)}, valueCount = {values.Length}, updateStatte = {updateState}, values:\n");
|
||||||
|
FireRaw(@base, values.Length, atkValues, (byte)(updateState ? 1 : 0));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
for (var i = 0; i < values.Length; i++)
|
||||||
|
{
|
||||||
|
if (atkValues[i].Type == ValueType.String)
|
||||||
|
{
|
||||||
|
Marshal.FreeHGlobal(new IntPtr(atkValues[i].String));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Marshal.FreeHGlobal(new IntPtr(atkValues));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string DecodeValues(int cnt, AtkValue* values)
|
||||||
|
{
|
||||||
|
var atkValueList = new List<string>();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for (var i = 0; i < cnt; i++)
|
||||||
|
{
|
||||||
|
atkValueList.Add(DecodeValue(values[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
PluginLog.Error("Could not decode values", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join("\n", atkValueList);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string DecodeValue(AtkValue a)
|
||||||
|
{
|
||||||
|
var str = new StringBuilder(a.Type.ToString()).Append(": ");
|
||||||
|
switch (a.Type)
|
||||||
|
{
|
||||||
|
case ValueType.Int:
|
||||||
|
{
|
||||||
|
str.Append(a.Int);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ValueType.String:
|
||||||
|
{
|
||||||
|
str.Append(Marshal.PtrToStringUTF8(new IntPtr(a.String)));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ValueType.UInt:
|
||||||
|
{
|
||||||
|
str.Append(a.UInt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ValueType.Bool:
|
||||||
|
{
|
||||||
|
str.Append(a.Byte != 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
str.Append($"Unknown Type: {a.Int}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str.ToString();
|
||||||
|
}
|
||||||
|
}
|
25
Workshoppa/Configuration.cs
Normal file
25
Workshoppa/Configuration.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Dalamud.Configuration;
|
||||||
|
|
||||||
|
namespace Workshoppa;
|
||||||
|
|
||||||
|
internal sealed class Configuration : IPluginConfiguration
|
||||||
|
{
|
||||||
|
public int Version { get; set; } = 1;
|
||||||
|
|
||||||
|
public CurrentItem? CurrentlyCraftedItem = null;
|
||||||
|
public List<QueuedItem> ItemQueue = new();
|
||||||
|
|
||||||
|
internal sealed class QueuedItem
|
||||||
|
{
|
||||||
|
public uint WorkshopItemId { get; set; }
|
||||||
|
public int Quantity { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class CurrentItem
|
||||||
|
{
|
||||||
|
public uint WorkshopItemId { get; set; }
|
||||||
|
public bool StartedCrafting { get; set; }
|
||||||
|
public bool FinishedCrafting { get; set; }
|
||||||
|
}
|
||||||
|
}
|
114
Workshoppa/External/DalamudReflector.cs
vendored
Normal file
114
Workshoppa/External/DalamudReflector.cs
vendored
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
|
||||||
|
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Workshoppa.External;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Originally part of ECommons by NightmareXIV.
|
||||||
|
///
|
||||||
|
/// https://github.com/NightmareXIV/ECommons/blob/master/ECommons/Reflection/DalamudReflector.cs
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class DalamudReflector : IDisposable
|
||||||
|
{
|
||||||
|
private readonly DalamudPluginInterface _pluginInterface;
|
||||||
|
private readonly Framework _framework;
|
||||||
|
private readonly Dictionary<string, IDalamudPlugin> _pluginCache = new();
|
||||||
|
private bool _pluginsChanged = false;
|
||||||
|
|
||||||
|
public DalamudReflector(DalamudPluginInterface pluginInterface, Framework framework)
|
||||||
|
{
|
||||||
|
_pluginInterface = pluginInterface;
|
||||||
|
_framework = framework;
|
||||||
|
var pm = GetPluginManager();
|
||||||
|
pm.GetType().GetEvent("OnInstalledPluginsChanged")!.AddEventHandler(pm, OnInstalledPluginsChanged);
|
||||||
|
|
||||||
|
_framework.Update += FrameworkUpdate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_framework.Update -= FrameworkUpdate;
|
||||||
|
|
||||||
|
var pm = GetPluginManager();
|
||||||
|
pm.GetType().GetEvent("OnInstalledPluginsChanged")!.RemoveEventHandler(pm, OnInstalledPluginsChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FrameworkUpdate(Framework framework)
|
||||||
|
{
|
||||||
|
if (_pluginsChanged)
|
||||||
|
{
|
||||||
|
_pluginsChanged = false;
|
||||||
|
_pluginCache.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object GetPluginManager()
|
||||||
|
{
|
||||||
|
return _pluginInterface.GetType().Assembly.GetType("Dalamud.Service`1", true)!
|
||||||
|
.MakeGenericType(
|
||||||
|
_pluginInterface.GetType().Assembly.GetType("Dalamud.Plugin.Internal.PluginManager", true)!)
|
||||||
|
.GetMethod("Get")!.Invoke(null, BindingFlags.Default, null, Array.Empty<object>(), null)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetDalamudPlugin(string internalName, out IDalamudPlugin? instance, bool suppressErrors = false,
|
||||||
|
bool ignoreCache = false)
|
||||||
|
{
|
||||||
|
if (!ignoreCache && _pluginCache.TryGetValue(internalName, out instance))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var pluginManager = GetPluginManager();
|
||||||
|
var installedPlugins =
|
||||||
|
(System.Collections.IList)pluginManager.GetType().GetProperty("InstalledPlugins")!.GetValue(
|
||||||
|
pluginManager)!;
|
||||||
|
|
||||||
|
foreach (var t in installedPlugins)
|
||||||
|
{
|
||||||
|
if ((string?)t.GetType().GetProperty("Name")!.GetValue(t) == internalName)
|
||||||
|
{
|
||||||
|
var type = t.GetType().Name == "LocalDevPlugin" ? t.GetType().BaseType : t.GetType();
|
||||||
|
var plugin = (IDalamudPlugin?)type!
|
||||||
|
.GetField("instance", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(t);
|
||||||
|
if (plugin == null)
|
||||||
|
{
|
||||||
|
PluginLog.Warning($"[DalamudReflector] Found requested plugin {internalName} but it was null");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
instance = plugin;
|
||||||
|
_pluginCache[internalName] = plugin;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
instance = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (!suppressErrors)
|
||||||
|
{
|
||||||
|
PluginLog.Error(e, $"Can't find {internalName} plugin: {e.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
instance = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInstalledPluginsChanged()
|
||||||
|
{
|
||||||
|
PluginLog.Verbose("Installed plugins changed event fired");
|
||||||
|
_pluginsChanged = true;
|
||||||
|
}
|
||||||
|
}
|
53
Workshoppa/External/YesAlreadyIpc.cs
vendored
Normal file
53
Workshoppa/External/YesAlreadyIpc.cs
vendored
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
|
||||||
|
namespace Workshoppa.External;
|
||||||
|
|
||||||
|
internal sealed class YesAlreadyIpc
|
||||||
|
{
|
||||||
|
private readonly DalamudReflector _dalamudReflector;
|
||||||
|
|
||||||
|
public YesAlreadyIpc(DalamudReflector dalamudReflector)
|
||||||
|
{
|
||||||
|
_dalamudReflector = dalamudReflector;
|
||||||
|
}
|
||||||
|
|
||||||
|
private object? GetConfiguration()
|
||||||
|
{
|
||||||
|
if (_dalamudReflector.TryGetDalamudPlugin("Yes Already", out var plugin))
|
||||||
|
{
|
||||||
|
var pluginService = plugin!.GetType().Assembly.GetType("YesAlready.Service");
|
||||||
|
return pluginService!.GetProperty("Configuration", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool? DisableIfNecessary()
|
||||||
|
{
|
||||||
|
object? configuration = GetConfiguration();
|
||||||
|
if (configuration == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var property = configuration.GetType().GetProperty("Enabled")!;
|
||||||
|
bool enabled = (bool)property.GetValue(configuration)!;
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
property.SetValue(configuration, false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Enable()
|
||||||
|
{
|
||||||
|
object? configuration = GetConfiguration();
|
||||||
|
if (configuration == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
var property = configuration.GetType().GetProperty("Enabled")!;
|
||||||
|
property.SetValue(configuration, true);
|
||||||
|
}
|
||||||
|
}
|
17
Workshoppa/GameData/CraftItem.cs
Normal file
17
Workshoppa/GameData/CraftItem.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace Workshoppa.GameData;
|
||||||
|
|
||||||
|
public class CraftItem
|
||||||
|
{
|
||||||
|
public uint ItemId { get; set; }
|
||||||
|
public uint IconId { get; set; }
|
||||||
|
public string? ItemName { get; set; }
|
||||||
|
public int CrafterIconId { get; set; }
|
||||||
|
public uint ItemCountPerStep { get; set; }
|
||||||
|
public uint ItemCountNQ { get; set; }
|
||||||
|
public uint ItemCountHQ { get; set; }
|
||||||
|
public uint Experience { get; set; }
|
||||||
|
public uint StepsComplete { get; set; }
|
||||||
|
public uint StepsTotal { get; set; }
|
||||||
|
public bool Finished { get; set; }
|
||||||
|
public uint CrafterMinimumLevel { get; set; }
|
||||||
|
}
|
21
Workshoppa/GameData/CraftState.cs
Normal file
21
Workshoppa/GameData/CraftState.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using Dalamud.Memory;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
|
||||||
|
namespace Workshoppa.GameData;
|
||||||
|
|
||||||
|
public sealed class CraftState
|
||||||
|
{
|
||||||
|
public required uint ResultItem { get; init; }
|
||||||
|
public required uint StepsComplete { get; init; }
|
||||||
|
public required uint StepsTotal { get; init; }
|
||||||
|
public required List<CraftItem> Items { get; init; }
|
||||||
|
|
||||||
|
public bool IsPhaseComplete() => Items.All(x => x.Finished || x.StepsComplete == x.StepsTotal);
|
||||||
|
|
||||||
|
public bool IsCraftComplete() => StepsComplete == StepsTotal - 1 && IsPhaseComplete();
|
||||||
|
}
|
73
Workshoppa/GameData/WorkshopCache.cs
Normal file
73
Workshoppa/GameData/WorkshopCache.cs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
|
||||||
|
namespace Workshoppa.GameData;
|
||||||
|
|
||||||
|
internal sealed class WorkshopCache
|
||||||
|
{
|
||||||
|
public WorkshopCache(DataManager dataManager)
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Dictionary<ushort, Item> itemMapping = dataManager.GetExcelSheet<CompanyCraftSupplyItem>()!
|
||||||
|
.Where(x => x.RowId > 0)
|
||||||
|
.ToDictionary(x => (ushort)x.RowId, x => x.Item.Value!);
|
||||||
|
|
||||||
|
Crafts = dataManager.GetExcelSheet<CompanyCraftSequence>()!
|
||||||
|
.Where(x => x.RowId > 0)
|
||||||
|
.Select(x => new WorkshopCraft
|
||||||
|
{
|
||||||
|
WorkshopItemId = x.RowId,
|
||||||
|
ResultItem = x.ResultItem.Row,
|
||||||
|
Name = x.ResultItem.Value!.Name.ToString(),
|
||||||
|
Category = (WorkshopCraftCategory)x.CompanyCraftDraftCategory.Row,
|
||||||
|
Type = x.CompanyCraftType.Row,
|
||||||
|
Phases = x.CompanyCraftPart.Where(part => part.Row != 0)
|
||||||
|
.SelectMany(part =>
|
||||||
|
part.Value!.CompanyCraftProcess
|
||||||
|
.Where(y => y.Value!.UnkData0.Any(z => z.SupplyItem > 0))
|
||||||
|
.Select(y => (Type: part.Value!.CompanyCraftType.Value, Process: y)))
|
||||||
|
.Select(y => new WorkshopCraftPhase
|
||||||
|
{
|
||||||
|
Name = y.Type!.Name.ToString(),
|
||||||
|
Items = y.Process.Value!.UnkData0
|
||||||
|
.Where(item => item.SupplyItem > 0)
|
||||||
|
.Select(item => new WorkshopCraftItem
|
||||||
|
{
|
||||||
|
ItemId = itemMapping[item.SupplyItem].RowId,
|
||||||
|
Name = itemMapping[item.SupplyItem].Name.ToString(),
|
||||||
|
SetQuantity = item.SetQuantity,
|
||||||
|
SetsRequired = item.SetsRequired,
|
||||||
|
})
|
||||||
|
.ToList()
|
||||||
|
.AsReadOnly(),
|
||||||
|
})
|
||||||
|
.ToList()
|
||||||
|
.AsReadOnly(),
|
||||||
|
})
|
||||||
|
.ToList()
|
||||||
|
.AsReadOnly();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
PluginLog.Error(e, "Unable to load cached items");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
/waitaddon "CompanyCraftRecipeNoteBook" <maxwait.30>
|
||||||
|
/pcall CompanyCraftRecipeNoteBook false 2 0 1u 16u 548u 1505u 715u 0
|
||||||
|
/wait 0.3
|
||||||
|
/pcall CompanyCraftRecipeNoteBook false 1 0 0 0 548u 0 0 0
|
||||||
|
*/
|
||||||
|
|
||||||
|
public IReadOnlyList<WorkshopCraft> Crafts { get; private set; } = new List<WorkshopCraft>();
|
||||||
|
}
|
13
Workshoppa/GameData/WorkshopCraft.cs
Normal file
13
Workshoppa/GameData/WorkshopCraft.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Workshoppa.GameData;
|
||||||
|
|
||||||
|
internal sealed class WorkshopCraft
|
||||||
|
{
|
||||||
|
public required uint WorkshopItemId { get; init; }
|
||||||
|
public required uint ResultItem { get; init; }
|
||||||
|
public required string Name { get; init; }
|
||||||
|
public required WorkshopCraftCategory Category { get; init; }
|
||||||
|
public required uint Type { get; init; }
|
||||||
|
public required IReadOnlyList<WorkshopCraftPhase> Phases { get; init; }
|
||||||
|
}
|
8
Workshoppa/GameData/WorkshopCraftCategory.cs
Normal file
8
Workshoppa/GameData/WorkshopCraftCategory.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace Workshoppa.GameData;
|
||||||
|
|
||||||
|
public enum WorkshopCraftCategory
|
||||||
|
{
|
||||||
|
AetherialWheels = 0,
|
||||||
|
AirshipsSubmersibles = 1,
|
||||||
|
Housing = 2,
|
||||||
|
}
|
10
Workshoppa/GameData/WorkshopCraftItem.cs
Normal file
10
Workshoppa/GameData/WorkshopCraftItem.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace Workshoppa.GameData;
|
||||||
|
|
||||||
|
internal sealed class WorkshopCraftItem
|
||||||
|
{
|
||||||
|
public required uint ItemId { get; init; }
|
||||||
|
public required string Name { get; init; }
|
||||||
|
public required int SetQuantity { get; init; }
|
||||||
|
public required int SetsRequired { get; init; }
|
||||||
|
public int TotalQuantity => SetQuantity * SetsRequired;
|
||||||
|
}
|
9
Workshoppa/GameData/WorkshopCraftPhase.cs
Normal file
9
Workshoppa/GameData/WorkshopCraftPhase.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Workshoppa.GameData;
|
||||||
|
|
||||||
|
internal sealed class WorkshopCraftPhase
|
||||||
|
{
|
||||||
|
public required string Name { get; init; }
|
||||||
|
public required IReadOnlyList<WorkshopCraftItem> Items { get; init; }
|
||||||
|
}
|
21
Workshoppa/Stage.cs
Normal file
21
Workshoppa/Stage.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
namespace Workshoppa;
|
||||||
|
|
||||||
|
public enum Stage
|
||||||
|
{
|
||||||
|
TakeItemFromQueue,
|
||||||
|
TargetFabricationStation,
|
||||||
|
|
||||||
|
OpenCraftingLog,
|
||||||
|
SelectCraftCategory,
|
||||||
|
SelectCraft,
|
||||||
|
ConfirmCraft,
|
||||||
|
|
||||||
|
SelectCraftBranch,
|
||||||
|
ContributeMaterials,
|
||||||
|
ConfirmMaterialDelivery,
|
||||||
|
|
||||||
|
ConfirmCollectProduct,
|
||||||
|
|
||||||
|
RequestStop,
|
||||||
|
Stopped,
|
||||||
|
}
|
163
Workshoppa/Windows/MainWindow.cs
Normal file
163
Workshoppa/Windows/MainWindow.cs
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Interface;
|
||||||
|
using Dalamud.Interface.Components;
|
||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using ImGuiNET;
|
||||||
|
using Workshoppa.GameData;
|
||||||
|
|
||||||
|
namespace Workshoppa.Windows;
|
||||||
|
|
||||||
|
internal sealed class MainWindow : Window
|
||||||
|
{
|
||||||
|
private readonly WorkshopPlugin _plugin;
|
||||||
|
private readonly DalamudPluginInterface _pluginInterface;
|
||||||
|
private readonly Configuration _configuration;
|
||||||
|
private readonly WorkshopCache _workshopCache;
|
||||||
|
|
||||||
|
private string _searchString = string.Empty;
|
||||||
|
|
||||||
|
public MainWindow(WorkshopPlugin plugin, DalamudPluginInterface pluginInterface, Configuration configuration, WorkshopCache workshopCache)
|
||||||
|
: base("Workshoppa###WorkshoppaMainWindow")
|
||||||
|
{
|
||||||
|
_plugin = plugin;
|
||||||
|
_pluginInterface = pluginInterface;
|
||||||
|
_configuration = configuration;
|
||||||
|
_workshopCache = workshopCache;
|
||||||
|
|
||||||
|
Position = new Vector2(100, 100);
|
||||||
|
PositionCondition = ImGuiCond.FirstUseEver;
|
||||||
|
|
||||||
|
SizeConstraints = new WindowSizeConstraints
|
||||||
|
{
|
||||||
|
MinimumSize = new Vector2(350, 50),
|
||||||
|
MaximumSize = new Vector2(500, 500),
|
||||||
|
};
|
||||||
|
|
||||||
|
Flags = ImGuiWindowFlags.AlwaysAutoResize | ImGuiWindowFlags.NoCollapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool NearFabricationStation { get; set; } = false;
|
||||||
|
public ButtonState State { get; set; } = ButtonState.None;
|
||||||
|
|
||||||
|
public override void Draw()
|
||||||
|
{
|
||||||
|
var currentItem = _configuration.CurrentlyCraftedItem;
|
||||||
|
if (currentItem != null)
|
||||||
|
{
|
||||||
|
var currentCraft = _workshopCache.Crafts.Single(x => x.WorkshopItemId == currentItem.WorkshopItemId);
|
||||||
|
ImGui.Text($"Currently Crafting: {currentCraft.Name}");
|
||||||
|
|
||||||
|
ImGui.BeginDisabled(!NearFabricationStation);
|
||||||
|
if (_plugin.CurrentStage == Stage.Stopped)
|
||||||
|
{
|
||||||
|
if (currentItem.StartedCrafting)
|
||||||
|
{
|
||||||
|
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Play, "Resume"))
|
||||||
|
State = ButtonState.Resume;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Play, "Start Crafting"))
|
||||||
|
State = ButtonState.Start;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
ImGui.BeginDisabled(!ImGui.GetIO().KeyCtrl);
|
||||||
|
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Times, "Cancel"))
|
||||||
|
{
|
||||||
|
State = ButtonState.Pause;
|
||||||
|
_configuration.CurrentlyCraftedItem = null;
|
||||||
|
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
if (ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled) && !ImGui.GetIO().KeyCtrl)
|
||||||
|
ImGui.SetTooltip(
|
||||||
|
$"Hold CTRL to remove this as craft. You have to manually use the fabrication station to cancel or finish this craft before you can continue using the queue.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.BeginDisabled(_plugin.CurrentStage == Stage.RequestStop);
|
||||||
|
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Pause, "Pause"))
|
||||||
|
State = ButtonState.Pause;
|
||||||
|
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
}
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ImGui.Text("Currently Crafting: ---");
|
||||||
|
|
||||||
|
ImGui.BeginDisabled(!NearFabricationStation || _configuration.ItemQueue.Sum(x => x.Quantity) == 0 || _plugin.CurrentStage != Stage.Stopped);
|
||||||
|
if (ImGuiComponents.IconButtonWithText(FontAwesomeIcon.Play, "Start Crafting"))
|
||||||
|
State = ButtonState.Start;
|
||||||
|
ImGui.EndDisabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.Text("Queue:");
|
||||||
|
//ImGui.BeginDisabled();
|
||||||
|
for (int i = 0; i < _configuration.ItemQueue.Count; ++ i)
|
||||||
|
{
|
||||||
|
ImGui.PushID($"ItemQueue{i}");
|
||||||
|
var item = _configuration.ItemQueue[i];
|
||||||
|
var craft = _workshopCache.Crafts.Single(x => x.WorkshopItemId == item.WorkshopItemId);
|
||||||
|
|
||||||
|
ImGui.SetNextItemWidth(100);
|
||||||
|
int quantity = item.Quantity;
|
||||||
|
if (ImGui.InputInt(craft.Name, ref quantity))
|
||||||
|
{
|
||||||
|
item.Quantity = Math.Max(0, quantity);
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.PopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||||
|
if (ImGui.BeginCombo("##CraftSelection", "Add Craft..."))
|
||||||
|
{
|
||||||
|
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
|
||||||
|
ImGui.InputTextWithHint("", "Filter...", ref _searchString, 256);
|
||||||
|
|
||||||
|
foreach (var craft in _workshopCache.Crafts
|
||||||
|
.Where(x => x.Name.ToLower().Contains(_searchString.ToLower()))
|
||||||
|
.OrderBy(x => x.WorkshopItemId))
|
||||||
|
{
|
||||||
|
if (ImGui.Selectable($"{craft.Name}##SelectCraft{craft.WorkshopItemId}"))
|
||||||
|
{
|
||||||
|
_configuration.ItemQueue.Add(new Configuration.QueuedItem
|
||||||
|
{
|
||||||
|
WorkshopItemId = craft.WorkshopItemId,
|
||||||
|
Quantity = 1,
|
||||||
|
});
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndCombo();
|
||||||
|
}
|
||||||
|
//ImGui.EndDisabled();
|
||||||
|
|
||||||
|
ImGui.Separator();
|
||||||
|
ImGui.Text($"Stage: {_plugin.CurrentStage}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Save()
|
||||||
|
{
|
||||||
|
_pluginInterface.SavePluginConfig(_configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ButtonState
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
Start,
|
||||||
|
Resume,
|
||||||
|
Pause,
|
||||||
|
Stop,
|
||||||
|
}
|
||||||
|
}
|
122
Workshoppa/WorkshopPlugin.Craft.cs
Normal file
122
Workshoppa/WorkshopPlugin.Craft.cs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using Workshoppa.GameData;
|
||||||
|
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||||
|
|
||||||
|
namespace Workshoppa;
|
||||||
|
|
||||||
|
partial class WorkshopPlugin
|
||||||
|
{
|
||||||
|
private uint? _contributingItemId;
|
||||||
|
|
||||||
|
private void SelectCraftBranch()
|
||||||
|
{
|
||||||
|
if (SelectSelectString("contrib", 0, s => s.StartsWith("Contribute materials.")))
|
||||||
|
CurrentStage = Stage.ContributeMaterials;
|
||||||
|
else if (SelectSelectString("advance", 0, s => s.StartsWith("Advance to the next phase of production.")))
|
||||||
|
{
|
||||||
|
PluginLog.Information("Phase is complete");
|
||||||
|
CurrentStage = Stage.TargetFabricationStation;
|
||||||
|
_continueAt = DateTime.Now.AddSeconds(3);
|
||||||
|
}
|
||||||
|
else if (SelectSelectString("complete", 0, s => s.StartsWith("Complete the construction of")))
|
||||||
|
{
|
||||||
|
PluginLog.Information("Item is almost complete, confirming last cutscene");
|
||||||
|
CurrentStage = Stage.TargetFabricationStation;
|
||||||
|
_continueAt = DateTime.Now.AddSeconds(3);
|
||||||
|
}
|
||||||
|
else if (SelectSelectString("collect", 0, s => s == "Collect finished product."))
|
||||||
|
{
|
||||||
|
PluginLog.Information("Item is complete");
|
||||||
|
CurrentStage = Stage.ConfirmCollectProduct;
|
||||||
|
_continueAt = DateTime.Now.AddSeconds(0.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void ContributeMaterials()
|
||||||
|
{
|
||||||
|
AtkUnitBase* addonMaterialDelivery = GetMaterialDeliveryAddon();
|
||||||
|
if (addonMaterialDelivery == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CraftState? craftState = ReadCraftState(addonMaterialDelivery);
|
||||||
|
if (craftState == null || craftState.ResultItem == 0)
|
||||||
|
{
|
||||||
|
PluginLog.Warning("Could not parse craft state");
|
||||||
|
_continueAt = DateTime.Now.AddSeconds(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < craftState.Items.Count; ++i)
|
||||||
|
{
|
||||||
|
var item = craftState.Items[i];
|
||||||
|
if (item.Finished)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!HasItemInSingleSlot(item.ItemId, item.ItemCountPerStep))
|
||||||
|
{
|
||||||
|
PluginLog.Error($"Can't contribute item {item.ItemId} to craft, couldn't find {item.ItemCountPerStep}x in a single inventory slot");
|
||||||
|
CurrentStage = Stage.RequestStop;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginLog.Information($"Contributing {item.ItemCountPerStep}x {item.ItemName}");
|
||||||
|
_contributingItemId = item.ItemId;
|
||||||
|
var contributeMaterial = stackalloc AtkValue[]
|
||||||
|
{
|
||||||
|
new() { Type = ValueType.Int, Int = 0 },
|
||||||
|
new() { Type = ValueType.UInt, Int = i },
|
||||||
|
new() { Type = ValueType.UInt, UInt = item.ItemCountPerStep },
|
||||||
|
new() { Type = 0, Int = 0 }
|
||||||
|
};
|
||||||
|
addonMaterialDelivery->FireCallback(4, contributeMaterial);
|
||||||
|
CurrentStage = Stage.ConfirmMaterialDelivery;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void ConfirmMaterialDelivery()
|
||||||
|
{
|
||||||
|
AtkUnitBase* addonMaterialDelivery = GetMaterialDeliveryAddon();
|
||||||
|
if (addonMaterialDelivery == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CraftState? craftState = ReadCraftState(addonMaterialDelivery);
|
||||||
|
if (craftState == null || craftState.ResultItem == 0)
|
||||||
|
{
|
||||||
|
PluginLog.Warning("Could not parse craft state");
|
||||||
|
_continueAt = DateTime.Now.AddSeconds(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SelectSelectYesno(0, s => s.StartsWith("Contribute") && s.EndsWith("to the company project?")))
|
||||||
|
{
|
||||||
|
var item = craftState.Items.Single(x => x.ItemId == _contributingItemId);
|
||||||
|
item.StepsComplete++;
|
||||||
|
if (craftState.IsPhaseComplete())
|
||||||
|
{
|
||||||
|
CurrentStage = Stage.TargetFabricationStation;
|
||||||
|
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CurrentStage = Stage.ContributeMaterials;
|
||||||
|
_continueAt = DateTime.Now.AddSeconds(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfirmCollectProduct()
|
||||||
|
{
|
||||||
|
if (SelectSelectYesno(0, s => s.StartsWith("Retrieve")))
|
||||||
|
{
|
||||||
|
_configuration.CurrentlyCraftedItem = null;
|
||||||
|
_pluginInterface.SavePluginConfig(_configuration);
|
||||||
|
|
||||||
|
CurrentStage = Stage.TakeItemFromQueue;
|
||||||
|
_continueAt = DateTime.Now.AddSeconds(0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
133
Workshoppa/WorkshopPlugin.CraftingLog.cs
Normal file
133
Workshoppa/WorkshopPlugin.CraftingLog.cs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using ValueType = FFXIVClientStructs.FFXIV.Component.GUI.ValueType;
|
||||||
|
|
||||||
|
namespace Workshoppa;
|
||||||
|
|
||||||
|
partial class WorkshopPlugin
|
||||||
|
{
|
||||||
|
private bool InteractWithFabricationStation(GameObject fabricationStation)
|
||||||
|
{
|
||||||
|
InteractWithTarget(fabricationStation);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TakeItemFromQueue()
|
||||||
|
{
|
||||||
|
if (_configuration.CurrentlyCraftedItem == null)
|
||||||
|
{
|
||||||
|
while (_configuration.ItemQueue.Count > 0 && _configuration.CurrentlyCraftedItem == null)
|
||||||
|
{
|
||||||
|
var firstItem = _configuration.ItemQueue[0];
|
||||||
|
if (firstItem.Quantity > 0)
|
||||||
|
{
|
||||||
|
_configuration.CurrentlyCraftedItem = new Configuration.CurrentItem
|
||||||
|
{
|
||||||
|
WorkshopItemId = firstItem.WorkshopItemId,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (firstItem.Quantity > 1)
|
||||||
|
firstItem.Quantity--;
|
||||||
|
else
|
||||||
|
_configuration.ItemQueue.Remove(firstItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_configuration.ItemQueue.Remove(firstItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
_pluginInterface.SavePluginConfig(_configuration);
|
||||||
|
if (_configuration.CurrentlyCraftedItem != null)
|
||||||
|
CurrentStage = Stage.TargetFabricationStation;
|
||||||
|
else
|
||||||
|
CurrentStage = Stage.RequestStop;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
CurrentStage = Stage.TargetFabricationStation;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenCraftingLog()
|
||||||
|
{
|
||||||
|
if (SelectSelectString("craftlog", 0, s => s == "View company crafting log."))
|
||||||
|
CurrentStage = Stage.SelectCraftCategory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void SelectCraftCategory()
|
||||||
|
{
|
||||||
|
AtkUnitBase* addonCraftingLog = GetCompanyCraftingLogAddon();
|
||||||
|
if (addonCraftingLog == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var craft = GetCurrentCraft();
|
||||||
|
PluginLog.Information($"Selecting category {craft.Category} and type {craft.Type}");
|
||||||
|
var selectCategory = stackalloc AtkValue[]
|
||||||
|
{
|
||||||
|
new() { Type = ValueType.Int, Int = 2 },
|
||||||
|
new() { Type = 0, Int = 0 },
|
||||||
|
new() { Type = ValueType.UInt, UInt = (uint)craft.Category },
|
||||||
|
new() { Type = ValueType.UInt, UInt = craft.Type },
|
||||||
|
new() { Type = ValueType.UInt, Int = 0 },
|
||||||
|
new() { Type = ValueType.UInt, Int = 0 },
|
||||||
|
new() { Type = ValueType.UInt, Int = 0 },
|
||||||
|
new() { Type = 0, Int = 0 }
|
||||||
|
};
|
||||||
|
addonCraftingLog->FireCallback(8, selectCategory);
|
||||||
|
CurrentStage = Stage.SelectCraft;
|
||||||
|
_continueAt = DateTime.Now.AddSeconds(0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void SelectCraft()
|
||||||
|
{
|
||||||
|
AtkUnitBase* addonCraftingLog = GetCompanyCraftingLogAddon();
|
||||||
|
if (addonCraftingLog == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var craft = GetCurrentCraft();
|
||||||
|
var atkValues = addonCraftingLog->AtkValues;
|
||||||
|
|
||||||
|
uint shownItemCount = atkValues[13].UInt;
|
||||||
|
var visibleItems = Enumerable.Range(0, (int)shownItemCount)
|
||||||
|
.Select(i => new
|
||||||
|
{
|
||||||
|
WorkshopItemId = atkValues[14 + 4 * i].UInt,
|
||||||
|
Name = ReadAtkString(atkValues[17 + 4 * i]),
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (visibleItems.All(x => x.WorkshopItemId != craft.WorkshopItemId))
|
||||||
|
{
|
||||||
|
PluginLog.Error($"Could not find {craft.Name} in current list, is it unlocked?");
|
||||||
|
CurrentStage = Stage.RequestStop;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginLog.Information($"Selecting craft {craft.WorkshopItemId}");
|
||||||
|
var selectCraft = stackalloc AtkValue[]
|
||||||
|
{
|
||||||
|
new() { Type = ValueType.Int, Int = 1 },
|
||||||
|
new() { Type = 0, Int = 0 },
|
||||||
|
new() { Type = 0, Int = 0 },
|
||||||
|
new() { Type = 0, Int = 0 },
|
||||||
|
new() { Type = ValueType.UInt, UInt = craft.WorkshopItemId },
|
||||||
|
new() { Type = 0, Int = 0 },
|
||||||
|
new() { Type = 0, Int = 0 },
|
||||||
|
new() { Type = 0, Int = 0 }
|
||||||
|
};
|
||||||
|
addonCraftingLog->FireCallback(8, selectCraft);
|
||||||
|
CurrentStage = Stage.ConfirmCraft;
|
||||||
|
_continueAt = DateTime.Now.AddSeconds(0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConfirmCraft()
|
||||||
|
{
|
||||||
|
if (SelectSelectYesno(0, s => s.StartsWith("Craft ")))
|
||||||
|
{
|
||||||
|
_configuration.CurrentlyCraftedItem!.StartedCrafting = true;
|
||||||
|
_pluginInterface.SavePluginConfig(_configuration);
|
||||||
|
|
||||||
|
CurrentStage = Stage.TargetFabricationStation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
266
Workshoppa/WorkshopPlugin.GameFunctions.cs
Normal file
266
Workshoppa/WorkshopPlugin.GameFunctions.cs
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
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;
|
||||||
|
using Workshoppa.GameData;
|
||||||
|
|
||||||
|
namespace Workshoppa;
|
||||||
|
|
||||||
|
partial class WorkshopPlugin
|
||||||
|
{
|
||||||
|
private unsafe void InteractWithTarget(GameObject obj)
|
||||||
|
{
|
||||||
|
PluginLog.Information($"Setting target to {obj}");
|
||||||
|
/*
|
||||||
|
if (_targetManager.Target == null || _targetManager.Target != obj)
|
||||||
|
{
|
||||||
|
_targetManager.Target = obj;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
TargetSystem.Instance()->InteractWithObject(
|
||||||
|
(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)obj.Address, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetDistanceToEventObject(int npcId, out GameObject? o)
|
||||||
|
{
|
||||||
|
foreach (var obj in _objectTable)
|
||||||
|
{
|
||||||
|
if (obj.ObjectKind == ObjectKind.EventObj)
|
||||||
|
{
|
||||||
|
if (GetNpcId(obj) == npcId)
|
||||||
|
{
|
||||||
|
o = obj;
|
||||||
|
return Vector3.Distance(_clientState.LocalPlayer!.Position, obj.Position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
o = null;
|
||||||
|
return float.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetNpcId(GameObject obj)
|
||||||
|
{
|
||||||
|
return Marshal.ReadInt32(obj.Address + 128);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe bool TryGetAddonByName<T>(string addonName, out T* addonPtr)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
var a = _gameGui.GetAddonByName(addonName);
|
||||||
|
if (a != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
addonPtr = (T*)a;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
addonPtr = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe bool IsAddonReady(AtkUnitBase* addon)
|
||||||
|
{
|
||||||
|
return addon->IsVisible && addon->UldManager.LoadedState == AtkLoadState.Loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe AtkUnitBase* GetCompanyCraftingLogAddon()
|
||||||
|
{
|
||||||
|
if (TryGetAddonByName<AtkUnitBase>("CompanyCraftRecipeNoteBook", out var addon) && IsAddonReady(addon))
|
||||||
|
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;
|
||||||
|
|
||||||
|
AtkUnitBase* addon = GetAddonById(addonId);
|
||||||
|
if (IsAddonReady(addon))
|
||||||
|
return addon;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe AtkUnitBase* GetAddonById(uint id)
|
||||||
|
{
|
||||||
|
var unitManagers = &AtkStage.GetSingleton()->RaptureAtkUnitManager->AtkUnitManager.DepthLayerOneList;
|
||||||
|
for (var i = 0; i < 18; i++)
|
||||||
|
{
|
||||||
|
var unitManager = &unitManagers[i];
|
||||||
|
var unitBaseArray = &(unitManager->AtkUnitEntries);
|
||||||
|
for (var j = 0; j < unitManager->Count; j++)
|
||||||
|
{
|
||||||
|
var unitBase = unitBaseArray[j];
|
||||||
|
if (unitBase->ID == id)
|
||||||
|
{
|
||||||
|
return unitBase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe bool SelectSelectString(string marker, int choice, Predicate<string> predicate)
|
||||||
|
{
|
||||||
|
if (TryGetAddonByName<AddonSelectString>("SelectString", out var addonSelectString) &&
|
||||||
|
IsAddonReady(&addonSelectString->AtkUnitBase))
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
PluginLog.Information($"SelectSelectString for {marker}, Choice would be '{text}'");
|
||||||
|
if (predicate(text))
|
||||||
|
{
|
||||||
|
addonSelectString->AtkUnitBase.FireCallbackInt(choice);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe bool SelectSelectYesno(int choice, Predicate<string> predicate)
|
||||||
|
{
|
||||||
|
if (TryGetAddonByName<AddonSelectYesno>("SelectYesno", out var addonSelectYesno) &&
|
||||||
|
IsAddonReady(&addonSelectYesno->AtkUnitBase))
|
||||||
|
{
|
||||||
|
var text = MemoryHelper.ReadSeString(&addonSelectYesno->PromptText->NodeText).ToString();
|
||||||
|
text = text.Replace("\n", "").Replace("\r", "");
|
||||||
|
if (predicate(text))
|
||||||
|
{
|
||||||
|
PluginLog.Information($"Selecting choice {choice} for '{text}'");
|
||||||
|
addonSelectYesno->AtkUnitBase.FireCallbackInt(choice);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PluginLog.Warning($"Text {text} does not match");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe string? ReadAtkString(AtkValue atkValue)
|
||||||
|
{
|
||||||
|
if (atkValue.String != null)
|
||||||
|
return MemoryHelper.ReadSeStringNullTerminated(new nint(atkValue.String)).ToString();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
ItemName = ReadAtkString(atkValues[36 + i]),
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
PluginLog.Warning(e, "Could not parse CompanyCraftMaterial info");
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint ParseAtkItemCountHq(AtkValue atkValue)
|
||||||
|
{
|
||||||
|
// NQ / HQ string
|
||||||
|
// I have no clue, but it doesn't seme like the available HQ item count is strored anywhere in the atkvalues??
|
||||||
|
string? s = ReadAtkString(atkValue);
|
||||||
|
if (s != null)
|
||||||
|
{
|
||||||
|
var parts = s.Replace("\ue03c", "").Split('/');
|
||||||
|
if (parts.Length > 1)
|
||||||
|
{
|
||||||
|
return uint.Parse(parts[1].Replace(",", "").Replace(".", "").Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
232
Workshoppa/WorkshopPlugin.cs
Normal file
232
Workshoppa/WorkshopPlugin.cs
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Game.ClientState.Conditions;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Game.Command;
|
||||||
|
using Dalamud.Game.Gui;
|
||||||
|
using Dalamud.Interface.Windowing;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using Dalamud.Memory;
|
||||||
|
using Dalamud.Plugin;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Control;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
|
using Workshoppa.External;
|
||||||
|
using Workshoppa.GameData;
|
||||||
|
using Workshoppa.Windows;
|
||||||
|
|
||||||
|
namespace Workshoppa;
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "UnusedType.Global")]
|
||||||
|
public sealed partial class WorkshopPlugin : IDalamudPlugin
|
||||||
|
{
|
||||||
|
private const int FabricationStationId = 0x1E98F4;
|
||||||
|
private readonly IReadOnlyList<ushort> _workshopTerritories = new ushort[] { 423, 424, 425, 653, 984 }.AsReadOnly();
|
||||||
|
private readonly WindowSystem _windowSystem = new WindowSystem(nameof(WorkshopPlugin));
|
||||||
|
|
||||||
|
private readonly DalamudPluginInterface _pluginInterface;
|
||||||
|
private readonly GameGui _gameGui;
|
||||||
|
private readonly Framework _framework;
|
||||||
|
private readonly Condition _condition;
|
||||||
|
private readonly ClientState _clientState;
|
||||||
|
private readonly ObjectTable _objectTable;
|
||||||
|
private readonly CommandManager _commandManager;
|
||||||
|
|
||||||
|
private readonly Configuration _configuration;
|
||||||
|
private readonly YesAlreadyIpc _yesAlreadyIpc;
|
||||||
|
private readonly WorkshopCache _workshopCache;
|
||||||
|
private readonly MainWindow _mainWindow;
|
||||||
|
|
||||||
|
private Stage _currentStageInternal = Stage.Stopped;
|
||||||
|
private DateTime _continueAt = DateTime.MinValue;
|
||||||
|
private (bool Saved, bool? PreviousState) _yesAlreadyState = (false, null);
|
||||||
|
|
||||||
|
public WorkshopPlugin(DalamudPluginInterface pluginInterface, GameGui gameGui, Framework framework,
|
||||||
|
Condition condition, ClientState clientState, ObjectTable objectTable, DataManager dataManager,
|
||||||
|
CommandManager commandManager)
|
||||||
|
{
|
||||||
|
_pluginInterface = pluginInterface;
|
||||||
|
_gameGui = gameGui;
|
||||||
|
_framework = framework;
|
||||||
|
_condition = condition;
|
||||||
|
_clientState = clientState;
|
||||||
|
_objectTable = objectTable;
|
||||||
|
_commandManager = commandManager;
|
||||||
|
|
||||||
|
var dalamudReflector = new DalamudReflector(_pluginInterface, _framework);
|
||||||
|
_yesAlreadyIpc = new YesAlreadyIpc(dalamudReflector);
|
||||||
|
_configuration = (Configuration?)_pluginInterface.GetPluginConfig() ?? new Configuration();
|
||||||
|
_workshopCache = new WorkshopCache(dataManager);
|
||||||
|
|
||||||
|
_mainWindow = new(this, _pluginInterface, _configuration, _workshopCache) { IsOpen = true };
|
||||||
|
_windowSystem.AddWindow(_mainWindow);
|
||||||
|
|
||||||
|
_pluginInterface.UiBuilder.Draw += _windowSystem.Draw;
|
||||||
|
_pluginInterface.UiBuilder.OpenMainUi += _mainWindow.Toggle;
|
||||||
|
_framework.Update += FrameworkUpdate;
|
||||||
|
_commandManager.AddHandler("/ws", new CommandInfo(ProcessCommand));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Name => "Workshop Plugin";
|
||||||
|
|
||||||
|
internal Stage CurrentStage
|
||||||
|
{
|
||||||
|
get => _currentStageInternal;
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
if (_currentStageInternal != value)
|
||||||
|
{
|
||||||
|
PluginLog.Information($"Changing stage from {_currentStageInternal} to {value}");
|
||||||
|
_currentStageInternal = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FrameworkUpdate(Framework framework)
|
||||||
|
{
|
||||||
|
if (!_clientState.IsLoggedIn ||
|
||||||
|
!_workshopTerritories.Contains(_clientState.TerritoryType) ||
|
||||||
|
_condition[ConditionFlag.BoundByDuty] ||
|
||||||
|
GetDistanceToEventObject(FabricationStationId, out var fabricationStation) >= 5f)
|
||||||
|
{
|
||||||
|
_mainWindow.NearFabricationStation = false;
|
||||||
|
}
|
||||||
|
else if (DateTime.Now >= _continueAt)
|
||||||
|
{
|
||||||
|
_mainWindow.NearFabricationStation = true;
|
||||||
|
|
||||||
|
if (_mainWindow.State is MainWindow.ButtonState.Pause or MainWindow.ButtonState.Stop)
|
||||||
|
{
|
||||||
|
_mainWindow.State = MainWindow.ButtonState.None;
|
||||||
|
if (CurrentStage != Stage.Stopped)
|
||||||
|
{
|
||||||
|
RestoreYesAlready();
|
||||||
|
CurrentStage = Stage.Stopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (_mainWindow.State is MainWindow.ButtonState.Start or MainWindow.ButtonState.Resume && CurrentStage == Stage.Stopped)
|
||||||
|
{
|
||||||
|
_mainWindow.State = MainWindow.ButtonState.None;
|
||||||
|
CurrentStage = Stage.TakeItemFromQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CurrentStage != Stage.Stopped && CurrentStage != Stage.RequestStop && !_yesAlreadyState.Saved)
|
||||||
|
SaveYesAlready();
|
||||||
|
|
||||||
|
switch (CurrentStage)
|
||||||
|
{
|
||||||
|
case Stage.TakeItemFromQueue:
|
||||||
|
TakeItemFromQueue();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage.TargetFabricationStation:
|
||||||
|
if (InteractWithFabricationStation(fabricationStation!))
|
||||||
|
{
|
||||||
|
if (_configuration.CurrentlyCraftedItem is { StartedCrafting: true })
|
||||||
|
CurrentStage = Stage.SelectCraftBranch;
|
||||||
|
else
|
||||||
|
CurrentStage = Stage.OpenCraftingLog;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage.OpenCraftingLog:
|
||||||
|
OpenCraftingLog();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage.SelectCraftCategory:
|
||||||
|
SelectCraftCategory();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage.SelectCraft:
|
||||||
|
SelectCraft();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage.ConfirmCraft:
|
||||||
|
ConfirmCraft();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage.RequestStop:
|
||||||
|
RestoreYesAlready();
|
||||||
|
CurrentStage = Stage.Stopped;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage.SelectCraftBranch:
|
||||||
|
SelectCraftBranch();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage.ContributeMaterials:
|
||||||
|
ContributeMaterials();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage.ConfirmMaterialDelivery:
|
||||||
|
ConfirmMaterialDelivery();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage.ConfirmCollectProduct:
|
||||||
|
ConfirmCollectProduct();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Stage.Stopped:
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
PluginLog.Warning($"Unknown stage {CurrentStage}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private WorkshopCraft GetCurrentCraft()
|
||||||
|
{
|
||||||
|
return _workshopCache.Crafts.Single(x => x.WorkshopItemId == _configuration.CurrentlyCraftedItem!.WorkshopItemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessCommand(string command, string arguments) => _mainWindow.Toggle();
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_commandManager.RemoveHandler("/ws");
|
||||||
|
_pluginInterface.UiBuilder.Draw -= _windowSystem.Draw;
|
||||||
|
_pluginInterface.UiBuilder.OpenMainUi -= _mainWindow.Toggle;
|
||||||
|
_framework.Update -= FrameworkUpdate;
|
||||||
|
|
||||||
|
RestoreYesAlready();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveYesAlready()
|
||||||
|
{
|
||||||
|
if (_yesAlreadyState.Saved)
|
||||||
|
{
|
||||||
|
PluginLog.Information("Not overwriting yesalready state");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_yesAlreadyState = (true, _yesAlreadyIpc.DisableIfNecessary());
|
||||||
|
PluginLog.Information($"Previous yesalready state: {_yesAlreadyState.PreviousState}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestoreYesAlready()
|
||||||
|
{
|
||||||
|
if (_yesAlreadyState.Saved)
|
||||||
|
{
|
||||||
|
PluginLog.Information($"Restoring previous yesalready state: {_yesAlreadyState.PreviousState}");
|
||||||
|
if (_yesAlreadyState.PreviousState == true)
|
||||||
|
_yesAlreadyIpc.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
_yesAlreadyState = (false, null);
|
||||||
|
}
|
||||||
|
}
|
69
Workshoppa/Workshoppa.csproj
Normal file
69
Workshoppa/Workshoppa.csproj
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0-windows</TargetFramework>
|
||||||
|
<Version>1.0</Version>
|
||||||
|
<LangVersion>11.0</LangVersion>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
|
<OutputPath>dist</OutputPath>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<DebugType>portable</DebugType>
|
||||||
|
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
|
||||||
|
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||||
|
<DebugType>portable</DebugType>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<DalamudLibPath>$(appdata)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))'">
|
||||||
|
<DalamudLibPath>$(DALAMUD_HOME)/</DalamudLibPath>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="DalamudPackager" Version="2.1.11"/>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="Dalamud">
|
||||||
|
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="ImGui.NET">
|
||||||
|
<HintPath>$(DalamudLibPath)ImGui.NET.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="ImGuiScene">
|
||||||
|
<HintPath>$(DalamudLibPath)ImGuiScene.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Lumina">
|
||||||
|
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Lumina.Excel">
|
||||||
|
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Newtonsoft.Json">
|
||||||
|
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="FFXIVClientStructs">
|
||||||
|
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="FFXIVClientStructs">
|
||||||
|
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<Target Name="RenameLatestZip" AfterTargets="PackagePlugin">
|
||||||
|
<Exec Command="rename $(OutDir)$(AssemblyName)\latest.zip $(AssemblyName)-$(Version).zip"/>
|
||||||
|
</Target>
|
||||||
|
</Project>
|
7
Workshoppa/Workshoppa.json
Normal file
7
Workshoppa/Workshoppa.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"Name": "Workshop Turn-In",
|
||||||
|
"Author": "Liza Carvelli",
|
||||||
|
"Punchline": "",
|
||||||
|
"Description": "",
|
||||||
|
"RepoUrl": "https://git.carvel.li/liza/Workshoppa"
|
||||||
|
}
|
13
Workshoppa/packages.lock.json
Normal file
13
Workshoppa/packages.lock.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"dependencies": {
|
||||||
|
"net7.0-windows7.0": {
|
||||||
|
"DalamudPackager": {
|
||||||
|
"type": "Direct",
|
||||||
|
"requested": "[2.1.11, )",
|
||||||
|
"resolved": "2.1.11",
|
||||||
|
"contentHash": "9qlAWoRRTiL/geAvuwR/g6Bcbrd/bJJgVnB/RurBiyKs6srsP0bvpoo8IK+Eg8EA6jWeM6/YJWs66w4FIAzqPw=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
global.json
Normal file
7
global.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"sdk": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"rollForward": "latestMinor",
|
||||||
|
"allowPrerelease": false
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user