Fix aethernet names, add new emotes, add EquipItem
This commit is contained in:
parent
1861d947fc
commit
b74f69c981
@ -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"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -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:
|
||||
|
144
Questionable/Controller/Steps/InteractionFactory/EquipItem.cs
Normal file
144
Questionable/Controller/Steps/InteractionFactory/EquipItem.cs
Normal 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})";
|
||||
}
|
||||
}
|
@ -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" },
|
||||
|
@ -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" },
|
||||
};
|
||||
}
|
||||
|
@ -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" },
|
||||
|
@ -13,4 +13,7 @@ internal enum EEmote
|
||||
Rally = 34,
|
||||
Deny = 25,
|
||||
Pray = 58,
|
||||
Slap = 111,
|
||||
Doubt = 12,
|
||||
Psych = 30,
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ internal enum EInteractionType
|
||||
AttuneAetherCurrent,
|
||||
Combat,
|
||||
UseItem,
|
||||
EquipItem,
|
||||
Say,
|
||||
Emote,
|
||||
WaitForObjectAtPosition,
|
||||
|
@ -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,
|
||||
|
@ -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)");
|
||||
|
Loading…
Reference in New Issue
Block a user