Fix aethernet names, add new emotes, add EquipItem

This commit is contained in:
Liza 2024-06-13 17:35:33 +02:00
parent 1861d947fc
commit b74f69c981
Signed by: liza
GPG Key ID: 7199F8D727D55F67
10 changed files with 216 additions and 32 deletions

View File

@ -93,6 +93,7 @@
"AttuneAetherCurrent",
"Combat",
"UseItem",
"EquipItem",
"Say",
"Emote",
"WaitForNpcAtPosition",
@ -226,27 +227,27 @@
"AethernetShortcut": {
"type": "array",
"description": "A pair of aethernet locations (from + to) to use as a shortcut",
"minItems": 1,
"minItems": 2,
"maxItems": 2,
"items": {
"type": "string",
"enum": [
"[Gridania] Aetheryte Plaza",
"[Gridania] Archer's Guild",
"[Gridania] Leatherworker's Guild & Shaded Bower",
"[Gridania] Lancer's Guild",
"[Gridania] Conjurer's Guild",
"[Gridania] Botanist's Guild",
"[Gridania] Archers' Guild",
"[Gridania] Leatherworkers' Guild & Shaded Bower",
"[Gridania] Lancers' Guild",
"[Gridania] Conjurers' Guild",
"[Gridania] Botanists' Guild",
"[Gridania] Mih Khetto's Amphitheatre",
"[Gridania] Blue Badger Gate (Central Shroud)",
"[Gridania] Yellow Serpent Gate (North Shroud)",
"[Gridania] White Wolf Gate (Central Shroud)",
"[Gridania] Airship Landing",
"[Ul'dah] Aetheryte Plaza",
"[Ul'dah] Adventurer's Guild",
"[Ul'dah] Thaumaturge's Guild",
"[Ul'dah] Gladiator's Guild",
"[Ul'dah] Miner's Guild",
"[Ul'dah] Adventurers' Guild",
"[Ul'dah] Thaumaturges' Guild",
"[Ul'dah] Gladiators' Guild",
"[Ul'dah] Miners' Guild",
"[Ul'dah] Weavers' Guild",
"[Ul'dah] Goldsmiths' Guild",
"[Ul'dah] Sapphire Avenue Exchange",
@ -257,12 +258,12 @@
"[Ul'dah] The Chamber of Rule",
"[Ul'dah] Airship Landing",
"[Limsa Lominsa] Aetheryte Plaza",
"[Limsa Lominsa] Arcanist's Guild",
"[Limsa Lominsa] Fishermen's Guild",
"[Limsa Lominsa] Hawker's Alley",
"[Limsa Lominsa] Arcanists' Guild",
"[Limsa Lominsa] Fishermens' Guild",
"[Limsa Lominsa] Hawkers' Alley",
"[Limsa Lominsa] The Aftcastle",
"[Limsa Lominsa] Culinarian's Guild",
"[Limsa Lominsa] Marauder's Guild",
"[Limsa Lominsa] Culinarians' Guild",
"[Limsa Lominsa] Marauders' Guild",
"[Limsa Lominsa] Zephyr Gate (Middle La Noscea)",
"[Limsa Lominsa] Tempest Gate (Lower La Noscea)",
"[Limsa Lominsa] Airship Landing",
@ -525,6 +526,20 @@
]
}
},
{
"if": {
"properties": {
"InteractionType": {
"const": "EquipItem"
}
}
},
"then": {
"required": [
"ItemId"
]
}
},
{
"if": {
"properties": {
@ -543,7 +558,10 @@
"wave",
"rally",
"deny",
"pray"
"pray",
"slap",
"doubt",
"psych"
]
}
},

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Numerics;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Application.Network.WorkDefinitions;
using FFXIVClientStructs.FFXIV.Client.Game;
@ -15,7 +16,7 @@ namespace Questionable.Controller.Steps.BaseFactory;
internal static class WaitAtEnd
{
internal sealed class Factory(IServiceProvider serviceProvider, IClientState clientState) : ITaskFactory
internal sealed class Factory(IServiceProvider serviceProvider, IClientState clientState, ICondition condition) : ITaskFactory
{
public IEnumerable<ITask> CreateAllTasks(Quest quest, QuestSequence sequence, QuestStep step)
{
@ -30,6 +31,13 @@ internal static class WaitAtEnd
switch (step.InteractionType)
{
case EInteractionType.Combat:
var notInCombat = new WaitConditionTask(() => !condition[ConditionFlag.InCombat], "Wait(not in combat)");
return [
serviceProvider.GetRequiredService<WaitDelay>(),
notInCombat,
Next(quest, sequence, step)
];
case EInteractionType.WaitForManualProgress:
case EInteractionType.ShouldBeAJump:
case EInteractionType.Instruction:

View File

@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using Lumina.Excel.GeneratedSheets;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Questionable.Controller.Steps.BaseTasks;
using Questionable.Model.V1;
using Quest = Questionable.Model.Quest;
namespace Questionable.Controller.Steps.InteractionFactory;
internal static class EquipItem
{
internal sealed class Factory(IServiceProvider serviceProvider) : ITaskFactory
{
public ITask? CreateTask(Quest quest, QuestSequence sequence, QuestStep step)
{
if (step.InteractionType != EInteractionType.EquipItem)
return null;
ArgumentNullException.ThrowIfNull(step.ItemId);
return serviceProvider.GetRequiredService<DoEquip>()
.With(step.ItemId.Value);
}
}
internal sealed class DoEquip(IDataManager dataManager, ILogger<DoEquip> logger)
: AbstractDelayedTask(TimeSpan.FromSeconds(1))
{
private static readonly IReadOnlyList<InventoryType> SourceInventoryTypes =
[
InventoryType.ArmoryMainHand,
InventoryType.ArmoryOffHand,
InventoryType.ArmoryHead,
InventoryType.ArmoryBody,
InventoryType.ArmoryHands,
InventoryType.ArmoryLegs,
InventoryType.ArmoryFeets,
InventoryType.ArmoryEar,
InventoryType.ArmoryNeck,
InventoryType.ArmoryWrist,
InventoryType.ArmoryRings,
InventoryType.Inventory1,
InventoryType.Inventory2,
InventoryType.Inventory3,
InventoryType.Inventory4,
];
private uint _itemId;
private Item _item = null!;
private List<ushort> _targetSlots = [];
public ITask With(uint itemId)
{
_itemId = itemId;
_item = dataManager.GetExcelSheet<Item>()!.GetRow(itemId) ??
throw new ArgumentOutOfRangeException(nameof(itemId));
_targetSlots = GetEquipSlot(_item) ?? throw new InvalidOperationException("Not a piece of equipment");
return this;
}
protected override unsafe bool StartInternal()
{
var inventoryManager = InventoryManager.Instance();
if (inventoryManager == null)
return false;
var equippedContainer = inventoryManager->GetInventoryContainer(InventoryType.EquippedItems);
if (equippedContainer == null)
return false;
if (_targetSlots.Any(slot => equippedContainer->GetInventorySlot(slot)->ItemID == _itemId))
{
logger.LogInformation("Already equipped {Item}, skipping step", _item.Name?.ToString());
return false;
}
foreach (InventoryType sourceInventoryType in SourceInventoryTypes)
{
var sourceContainer = inventoryManager->GetInventoryContainer(sourceInventoryType);
if (sourceContainer == null)
continue;
if (inventoryManager->GetItemCountInContainer(_itemId, sourceInventoryType, true) == 0 &&
inventoryManager->GetItemCountInContainer(_itemId, sourceInventoryType) == 0)
continue;
for (ushort sourceSlot = 0; sourceSlot < sourceContainer->Size; sourceSlot++)
{
var sourceItem = sourceContainer->GetInventorySlot(sourceSlot);
if (sourceItem == null || sourceItem->ItemID != _itemId)
continue;
// Move the item to the first available slot
ushort targetSlot = _targetSlots
.Where(x => inventoryManager->GetInventorySlot(InventoryType.EquippedItems, x)->ItemID == 0)
.Concat(_targetSlots).First();
logger.LogInformation(
"Equipping item from {SourceInventory}, {SourceSlot} to {TargetInventory}, {TargetSlot}",
sourceInventoryType, sourceSlot, InventoryType.EquippedItems, targetSlot);
int result = inventoryManager->MoveItemSlot(sourceInventoryType, sourceSlot,
InventoryType.EquippedItems, targetSlot, 1);
logger.LogInformation("MoveItemSlot result: {Result}", result);
return true;
}
}
return false;
}
protected override unsafe ETaskResult UpdateInternal()
{
InventoryManager* inventoryManager = InventoryManager.Instance();
if (inventoryManager == null)
return ETaskResult.StillRunning;
if (_targetSlots.Any(x =>
inventoryManager->GetInventorySlot(InventoryType.EquippedItems, x)->ItemID == _itemId))
return ETaskResult.TaskComplete;
return ETaskResult.StillRunning;
}
private static List<ushort>? GetEquipSlot(Item item)
{
return item.EquipSlotCategory.Row switch
{
>= 1 and <= 11 => [(ushort)(item.EquipSlotCategory.Row - 1)],
12 => [11, 12], // rings
17 => [14], // soul crystal
_ => null
};
}
public override string ToString() => $"Equip({_item.Name})";
}
}

View File

@ -11,11 +11,11 @@ internal sealed class AethernetShortcutConverter : JsonConverter<AethernetShortc
private static readonly Dictionary<EAetheryteLocation, string> EnumToString = new()
{
{ EAetheryteLocation.Gridania, "[Gridania] Aetheryte Plaza" },
{ EAetheryteLocation.GridaniaArcher, "[Gridania] Archer's Guild" },
{ EAetheryteLocation.GridaniaLeatherworker, "[Gridania] Leatherworker's Guild & Shaded Bower" },
{ EAetheryteLocation.GridaniaLancer, "[Gridania] Lancer's Guild" },
{ EAetheryteLocation.GridaniaConjurer, "[Gridania] Conjurer's Guild" },
{ EAetheryteLocation.GridaniaBotanist, "[Gridania] Botanist's Guild" },
{ EAetheryteLocation.GridaniaArcher, "[Gridania] Archers' Guild" },
{ EAetheryteLocation.GridaniaLeatherworker, "[Gridania] Leatherworkers' Guild & Shaded Bower" },
{ EAetheryteLocation.GridaniaLancer, "[Gridania] Lancers' Guild" },
{ EAetheryteLocation.GridaniaConjurer, "[Gridania] Conjurers' Guild" },
{ EAetheryteLocation.GridaniaBotanist, "[Gridania] Botanists' Guild" },
{ EAetheryteLocation.GridaniaAmphitheatre, "[Gridania] Mih Khetto's Amphitheatre" },
{ EAetheryteLocation.GridaniaBlueBadgerGate, "[Gridania] Blue Badger Gate (Central Shroud)" },
{ EAetheryteLocation.GridaniaYellowSerpentGate, "[Gridania] Yellow Serpent Gate (North Shroud)" },
@ -23,10 +23,10 @@ internal sealed class AethernetShortcutConverter : JsonConverter<AethernetShortc
{ EAetheryteLocation.GridaniaAirship, "[Gridania] Airship Landing" },
{ EAetheryteLocation.Uldah, "[Ul'dah] Aetheryte Plaza" },
{ EAetheryteLocation.UldahAdventurers, "[Ul'dah] Adventurer's Guild" },
{ EAetheryteLocation.UldahThaumaturge, "[Ul'dah] Thaumaturge's Guild" },
{ EAetheryteLocation.UldahGladiator, "[Ul'dah] Gladiator's Guild" },
{ EAetheryteLocation.UldahMiner, "[Ul'dah] Miner's Guild" },
{ EAetheryteLocation.UldahAdventurers, "[Ul'dah] Adventurers' Guild" },
{ EAetheryteLocation.UldahThaumaturge, "[Ul'dah] Thaumaturges' Guild" },
{ EAetheryteLocation.UldahGladiator, "[Ul'dah] Gladiators' Guild" },
{ EAetheryteLocation.UldahMiner, "[Ul'dah] Miners' Guild" },
{ EAetheryteLocation.UldahWeaver, "[Ul'dah] Weavers' Guild" },
{ EAetheryteLocation.UldahGoldsmith, "[Ul'dah] Goldsmiths' Guild" },
{ EAetheryteLocation.UldahSapphireAvenue, "[Ul'dah] Sapphire Avenue Exchange" },
@ -38,12 +38,12 @@ internal sealed class AethernetShortcutConverter : JsonConverter<AethernetShortc
{ EAetheryteLocation.UldahAirship, "[Ul'dah] Airship Landing" },
{ EAetheryteLocation.Limsa, "[Limsa Lominsa] Aetheryte Plaza" },
{ EAetheryteLocation.LimsaArcanist, "[Limsa Lominsa] Arcanist's Guild" },
{ EAetheryteLocation.LimsaFisher, "[Limsa Lominsa] Fishermen's Guild" },
{ EAetheryteLocation.LimsaHawkersAlley, "[Limsa Lominsa] Hawker's Alley" },
{ EAetheryteLocation.LimsaArcanist, "[Limsa Lominsa] Arcanists' Guild" },
{ EAetheryteLocation.LimsaFisher, "[Limsa Lominsa] Fishermens' Guild" },
{ EAetheryteLocation.LimsaHawkersAlley, "[Limsa Lominsa] Hawkers' Alley" },
{ EAetheryteLocation.LimsaAftcastle, "[Limsa Lominsa] The Aftcastle" },
{ EAetheryteLocation.LimsaCulinarian, "[Limsa Lominsa] Culinarian's Guild" },
{ EAetheryteLocation.LimsaMarauder, "[Limsa Lominsa] Marauder's Guild" },
{ EAetheryteLocation.LimsaCulinarian, "[Limsa Lominsa] Culinarians' Guild" },
{ EAetheryteLocation.LimsaMarauder, "[Limsa Lominsa] Marauders' Guild" },
{ EAetheryteLocation.LimsaZephyrGate, "[Limsa Lominsa] Zephyr Gate (Middle La Noscea)" },
{ EAetheryteLocation.LimsaTempestGate, "[Limsa Lominsa] Tempest Gate (Lower La Noscea)" },
{ EAetheryteLocation.LimsaAirship, "[Limsa Lominsa] Airship Landing" },

View File

@ -11,5 +11,8 @@ internal sealed class EmoteConverter() : EnumConverter<EEmote>(Values)
{ EEmote.Rally, "rally" },
{ EEmote.Deny, "deny" },
{ EEmote.Pray, "pray" },
{ EEmote.Slap, "slap" },
{ EEmote.Doubt, "doubt" },
{ EEmote.Psych, "psych" },
};
}

View File

@ -13,6 +13,7 @@ internal sealed class InteractionTypeConverter() : EnumConverter<EInteractionTyp
{ EInteractionType.AttuneAetherCurrent, "AttuneAetherCurrent" },
{ EInteractionType.Combat, "Combat" },
{ EInteractionType.UseItem, "UseItem" },
{ EInteractionType.EquipItem, "EquipItem" },
{ EInteractionType.Say, "Say" },
{ EInteractionType.Emote, "Emote" },
{ EInteractionType.WaitForObjectAtPosition, "WaitForNpcAtPosition" },

View File

@ -13,4 +13,7 @@ internal enum EEmote
Rally = 34,
Deny = 25,
Pray = 58,
Slap = 111,
Doubt = 12,
Psych = 30,
}

View File

@ -13,6 +13,7 @@ internal enum EInteractionType
AttuneAetherCurrent,
Combat,
UseItem,
EquipItem,
Say,
Emote,
WaitForObjectAtPosition,

View File

@ -55,6 +55,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddSingleton(dataManager);
serviceCollection.AddSingleton(sigScanner);
serviceCollection.AddSingleton(objectTable);
serviceCollection.AddSingleton(pluginLog);
serviceCollection.AddSingleton(condition);
serviceCollection.AddSingleton(chatGui);
serviceCollection.AddSingleton(commandManager);
@ -91,6 +92,7 @@ public sealed class QuestionablePlugin : IDalamudPlugin
serviceCollection.AddTaskWithFactory<Jump.Factory, Jump.DoJump>();
serviceCollection.AddTaskWithFactory<Say.Factory, Say.UseChat>();
serviceCollection.AddTaskWithFactory<UseItem.Factory, UseItem.UseOnGround, UseItem.UseOnObject, UseItem.Use>();
serviceCollection.AddTaskWithFactory<EquipItem.Factory, EquipItem.DoEquip>();
serviceCollection
.AddTaskWithFactory<WaitAtEnd.Factory,

View File

@ -106,7 +106,11 @@ internal sealed class DebugWindow : LWindow, IPersistableWindowConfig
var qw = questWork.Value;
string vars = "";
for (int i = 0; i < 6; ++i)
{
vars += qw.Variables[i] + " ";
if (i % 2 == 1)
vars += " ";
}
// For combat quests, a sequence to kill 3 enemies works a bit like this:
// Trigger enemies → 0
@ -115,7 +119,7 @@ internal sealed class DebugWindow : LWindow, IPersistableWindowConfig
// Last enemy → increase sequence, reset variable to 0
// The order in which enemies are killed doesn't seem to matter.
// If multiple waves spawn, this continues to count up (e.g. 1 enemy from wave 1, 2 enemies from wave 2, 1 from wave 3) would count to 3 then 0
ImGui.Text($"QW: {vars.Trim()} / {qw.Flags}");
ImGui.Text($"QW: {vars.Trim()}");
}
else
ImGui.TextUnformatted("(Not accepted)");