Agent skill
SND TextAdvance Integration
Use this skill when implementing automatic dialog/cutscene advancement in SND macros using the TextAdvance plugin. Covers dialog control, cutscene skipping, and quest interaction automation.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/snd-textadvance
SKILL.md
TextAdvance Integration for SND
This skill covers integration with the TextAdvance plugin for automatic dialog and cutscene advancement in SND macros.
Source: https://github.com/NightmareXIV/TextAdvance/blob/master/TextAdvance/Services/IPCProvider.cs
Prerequisites
-- Always check plugin availability first
if not HasPlugin("TextAdvance") then
yield("/echo [Script] TextAdvance plugin not available")
StopFlag = true
return
end
Plugin Availability
--- Check if TextAdvance is available
-- @return boolean - True if available
function HasTextAdvance()
for plugin in luanet.each(Svc.PluginInterface.InstalledPlugins) do
if plugin.InternalName == "TextAdvance" and plugin.IsLoaded then
return true
end
end
return false
end
--- Check if TextAdvance IPC is available
-- @return boolean - True if IPC available
function IsTextAdvanceIPCAvailable()
return IPC.TextAdvance ~= nil
end
Official IPC API Reference
State Query Methods
--- Check if TextAdvance is enabled
-- @return boolean - True if enabled
IPC.TextAdvance.IsEnabled()
--- Check if TextAdvance is paused (blocklisted)
-- @return boolean - True if paused
IPC.TextAdvance.IsPaused()
--- Check if in external control mode
-- @return boolean - True if under external control
IPC.TextAdvance.IsInExternalControl()
--- Check if TextAdvance task manager is busy
-- @return boolean - True if busy with movement/interaction
IPC.TextAdvance.IsBusy()
Configuration Query Methods
--- Check if quest accept is enabled
-- @return boolean - True if enabled
IPC.TextAdvance.GetEnableQuestAccept()
--- Check if quest complete is enabled
-- @return boolean - True if enabled
IPC.TextAdvance.GetEnableQuestComplete()
--- Check if reward pick is enabled
-- @return boolean - True if enabled
IPC.TextAdvance.GetEnableRewardPick()
--- Check if cutscene ESC is enabled
-- @return boolean - True if enabled
IPC.TextAdvance.GetEnableCutsceneEsc()
--- Check if cutscene skip confirm is enabled
-- @return boolean - True if enabled
IPC.TextAdvance.GetEnableCutsceneSkipConfirm()
--- Check if request hand-in is enabled
-- @return boolean - True if enabled
IPC.TextAdvance.GetEnableRequestHandin()
--- Check if request fill is enabled
-- @return boolean - True if enabled
IPC.TextAdvance.GetEnableRequestFill()
--- Check if talk skip is enabled
-- @return boolean - True if enabled
IPC.TextAdvance.GetEnableTalkSkip()
--- Check if auto-interact is enabled
-- @return boolean - True if enabled
IPC.TextAdvance.GetEnableAutoInteract()
External Control Methods
--- Enable external control of TextAdvance
-- @param requester string - Unique identifier for the requester
-- @param config table - ExternalTerritoryConfig object
-- @return boolean - True if control was acquired
IPC.TextAdvance.EnableExternalControl(requester, config)
--- Disable external control of TextAdvance
-- @param requester string - Unique identifier (must match EnableExternalControl)
-- @return boolean - True if control was released
IPC.TextAdvance.DisableExternalControl(requester)
Movement and Interaction Methods
--- Enqueue move and interact with target
-- @param moveData table - MoveData object with position, dataID, noInteract
-- MoveData structure:
-- {
-- Position = Vector3(x, y, z), -- Destination position
-- DataID = uint, -- Object DataID (0 for any targetable)
-- NoInteract = boolean, -- If true, don't interact after moving
-- Mount = boolean or nil, -- Force mount state (nil = auto)
-- Fly = boolean or nil -- Force fly state (nil = auto)
-- }
IPC.TextAdvance.EnqueueMoveAndInteract(moveData)
--- Enqueue move to 2D point (ignores Y coordinate)
-- @param moveData table - MoveData object
-- @param distance number - Distance threshold to consider "arrived"
IPC.TextAdvance.EnqueueMoveTo2DPoint(moveData, distance)
--- Enqueue move to 3D point (includes Y coordinate)
-- @param moveData table - MoveData object
-- @param distance number - Distance threshold to consider "arrived"
IPC.TextAdvance.EnqueueMoveTo3DPoint(moveData, distance)
--- Stop all movement and interaction tasks
IPC.TextAdvance.Stop()
Example: Using IPC Methods
--- Check if TextAdvance will handle quest dialogs
function WillAutoAcceptQuest()
if not HasPlugin("TextAdvance") then
return false
end
return IPC.TextAdvance.IsEnabled() and
IPC.TextAdvance.GetEnableQuestAccept() and
not IPC.TextAdvance.IsPaused()
end
--- Move to NPC and interact using TextAdvance IPC
-- @param x number - X coordinate
-- @param y number - Y coordinate
-- @param z number - Z coordinate
-- @param dataId number - NPC DataID (0 for any targetable at position)
-- @return boolean - True if task was enqueued
function MoveAndInteractWithNPC(x, y, z, dataId)
if not HasPlugin("TextAdvance") then
return false
end
local moveData = {
Position = Vector3(x, y, z),
DataID = dataId or 0,
NoInteract = false,
Mount = nil, -- Let TextAdvance decide
Fly = nil -- Let TextAdvance decide
}
IPC.TextAdvance.EnqueueMoveAndInteract(moveData)
-- Wait for task to start
yield("/wait 0.5")
-- Wait for completion
while IPC.TextAdvance.IsBusy() do
yield("/wait 0.5")
end
return true
end
--- External control example (advanced)
function TakeControlOfTextAdvance()
local requester = "MyScript"
local config = {
-- ExternalTerritoryConfig structure
-- (See TextAdvance source for details)
}
local success = IPC.TextAdvance.EnableExternalControl(requester, config)
if success then
yield("/echo [Script] TextAdvance external control acquired")
end
return success
end
function ReleaseControlOfTextAdvance()
local requester = "MyScript"
IPC.TextAdvance.DisableExternalControl(requester)
yield("/echo [Script] TextAdvance external control released")
end
TextAdvance Control Commands
Note: Most users should use the command-based control methods below. The IPC methods above are primarily for advanced scenarios like external control, state queries, and integrated movement/interaction systems.
Enable/Disable TextAdvance
--- Enable TextAdvance auto-advance
-- @return boolean - True if command sent
function EnableTextAdvance()
if not HasTextAdvance() then
yield("/echo [Script] TextAdvance not available")
return false
end
yield("/at e") -- Alternative: "/textadvance enable"
yield("/wait 0.2")
return true
end
--- Disable TextAdvance auto-advance
-- @return boolean - True if command sent
function DisableTextAdvance()
if not HasTextAdvance() then
return false
end
yield("/at d") -- Alternative: "/textadvance disable"
yield("/wait 0.2")
return true
end
--- Toggle TextAdvance
-- @return boolean - True if command sent
function ToggleTextAdvance()
if not HasTextAdvance() then
return false
end
yield("/at") -- Toggle command
yield("/wait 0.2")
return true
end
Advanced Commands
--- Enable quick dialog (faster advancement)
-- @return boolean - True if command sent
function EnableQuickDialog()
if not HasTextAdvance() then
return false
end
yield("/at qd on")
yield("/wait 0.2")
return true
end
--- Disable quick dialog
-- @return boolean - True if command sent
function DisableQuickDialog()
if not HasTextAdvance() then
return false
end
yield("/at qd off")
yield("/wait 0.2")
return true
end
--- Enable cutscene skip
-- @return boolean - True if command sent
function EnableCutsceneSkip()
if not HasTextAdvance() then
return false
end
yield("/at cs on")
yield("/wait 0.2")
return true
end
--- Disable cutscene skip
-- @return boolean - True if command sent
function DisableCutsceneSkip()
if not HasTextAdvance() then
return false
end
yield("/at cs off")
yield("/wait 0.2")
return true
end
--- Enable auto-accept quests
-- @return boolean - True if command sent
function EnableAutoAcceptQuest()
if not HasTextAdvance() then
return false
end
yield("/at qa on")
yield("/wait 0.2")
return true
end
--- Disable auto-accept quests
-- @return boolean - True if command sent
function DisableAutoAcceptQuest()
if not HasTextAdvance() then
return false
end
yield("/at qa off")
yield("/wait 0.2")
return true
end
Dialog State Detection
Check Dialog Addons
--- Check if any dialog addon is visible
-- @return boolean, string - True and addon name if dialog is open
function IsDialogOpen()
local dialogAddons = {
"Talk",
"SelectString",
"SelectYesno",
"SelectIconString",
"CutSceneSelectString",
"JournalDetail",
"JournalResult",
"Request",
}
for _, addonName in ipairs(dialogAddons) do
if IsAddonVisible(addonName) then
return true, addonName
end
end
return false, nil
end
--- Check if Talk addon is visible
-- @return boolean - True if visible
function IsTalkOpen()
return IsAddonVisible("Talk")
end
--- Check if cutscene is playing
-- @return boolean - True if in cutscene
function IsInCutscene()
local CharacterCondition = {
watchingCutscene = 10,
watchingCutscene2 = 11,
dutyRecorderPlayback = 98,
}
return Svc.Condition[CharacterCondition.watchingCutscene] or
Svc.Condition[CharacterCondition.watchingCutscene2] or
Svc.Condition[CharacterCondition.dutyRecorderPlayback]
end
--- Check if in quest interaction
-- @return boolean - True if in quest event
function IsInQuestEvent()
local CharacterCondition = {
occupiedInQuestEvent = 32,
occupiedInEvent = 31,
}
return Svc.Condition[CharacterCondition.occupiedInQuestEvent] or
Svc.Condition[CharacterCondition.occupiedInEvent]
end
--- Helper function to check addon visibility
-- @param addonName string - Name of the addon
-- @return boolean - True if visible
function IsAddonVisible(addonName)
local addon = Addons.GetAddon(addonName)
return addon and addon.Ready and addon.Visible
end
Quest Interaction Patterns
NPC Interaction with TextAdvance
--- Interact with NPC and wait for dialog with TextAdvance handling
-- @param npcName string - Name of the NPC (optional, uses target)
-- @param timeout number - Maximum wait time (default: 30)
-- @return boolean - True if interaction completed
function InteractWithNPC(npcName, timeout)
timeout = timeout or 30
-- Enable TextAdvance for this interaction
EnableTextAdvance()
-- Interact
if npcName then
yield("/target " .. npcName)
yield("/wait 0.3")
end
yield("/interact")
-- Wait for dialog to appear
local startTime = os.clock()
while (os.clock() - startTime) < timeout do
local dialogOpen, addonName = IsDialogOpen()
if dialogOpen then
yield("/echo [Script] Dialog opened: " .. (addonName or "Unknown"))
return true
end
-- Check if in quest event
if IsInQuestEvent() then
return true
end
yield("/wait 0.5")
end
yield("/echo [Script] NPC interaction timeout")
return false
end
--- Wait for dialog to close (interaction complete)
-- @param timeout number - Maximum wait time (default: 60)
-- @return boolean - True if dialog closed
function WaitForDialogClose(timeout)
timeout = timeout or 60
local startTime = os.clock()
while (os.clock() - startTime) < timeout do
local dialogOpen = IsDialogOpen()
if not dialogOpen and not IsInQuestEvent() and not IsInCutscene() then
return true
end
yield("/wait 0.5")
end
return false
end
--- Complete NPC interaction (interact and wait for completion)
-- @param npcName string - Name of the NPC (optional)
-- @param timeout number - Maximum wait time (default: 60)
-- @return boolean - True if interaction fully completed
function CompleteNPCInteraction(npcName, timeout)
timeout = timeout or 60
-- Interact with NPC
if not InteractWithNPC(npcName, 10) then
return false
end
-- Wait for dialog/interaction to complete
return WaitForDialogClose(timeout)
end
Quest Workflow
--- Accept a quest from NPC
-- @param npcName string - Quest giver NPC name (optional)
-- @param timeout number - Timeout (default: 60)
-- @return boolean - True if quest accepted
function AcceptQuest(npcName, timeout)
timeout = timeout or 60
-- Enable auto-accept
EnableAutoAcceptQuest()
EnableTextAdvance()
-- Interact with quest giver
if not InteractWithNPC(npcName, 10) then
return false
end
-- Wait for quest acceptance to complete
local startTime = os.clock()
while (os.clock() - startTime) < timeout do
-- Check if JournalDetail opened (quest offered)
if IsAddonVisible("JournalDetail") then
-- TextAdvance should auto-accept, but we can force it
yield("/wait 0.5")
if IsAddonVisible("JournalDetail") then
yield("/click JournalDetail Accept")
end
end
-- Check if dialog closed (quest accepted)
if not IsDialogOpen() and not IsInQuestEvent() then
yield("/echo [Script] Quest accepted")
return true
end
yield("/wait 0.5")
end
return false
end
--- Complete/turn in a quest
-- @param npcName string - NPC name (optional)
-- @param timeout number - Timeout (default: 60)
-- @return boolean - True if quest completed
function CompleteQuest(npcName, timeout)
timeout = timeout or 60
EnableTextAdvance()
-- Interact with NPC
if not InteractWithNPC(npcName, 10) then
return false
end
-- Wait for completion dialog
local startTime = os.clock()
while (os.clock() - startTime) < timeout do
-- Check if JournalResult opened (quest completion)
if IsAddonVisible("JournalResult") then
yield("/wait 0.5")
if IsAddonVisible("JournalResult") then
yield("/click JournalResult Complete")
end
end
-- Check if Request addon appeared (item turn-in)
if IsAddonVisible("Request") then
yield("/wait 0.5")
yield("/click Request HandIn")
end
-- Check if dialog closed (quest completed)
if not IsDialogOpen() and not IsInQuestEvent() then
yield("/echo [Script] Quest completed")
return true
end
yield("/wait 0.5")
end
return false
end
Cutscene Handling
Cutscene Skip
--- Skip current cutscene
-- @param timeout number - Timeout (default: 30)
-- @return boolean - True if cutscene ended
function SkipCutscene(timeout)
timeout = timeout or 30
if not IsInCutscene() then
return true -- Not in cutscene
end
EnableCutsceneSkip()
local startTime = os.clock()
while IsInCutscene() and (os.clock() - startTime) < timeout do
-- Try to skip
yield("/click SelectYesno Yes") -- Confirm skip if prompted
yield("/wait 0.5")
end
return not IsInCutscene()
end
--- Wait for cutscene to end (with auto-skip enabled)
-- @param timeout number - Timeout (default: 120)
-- @return boolean - True if cutscene ended
function WaitForCutsceneEnd(timeout)
timeout = timeout or 120
local startTime = os.clock()
while IsInCutscene() and (os.clock() - startTime) < timeout do
yield("/wait 1")
end
return not IsInCutscene()
end
TextAdvance Configuration Presets
Automation Presets
--- Configure TextAdvance for full automation
function ConfigureForFullAutomation()
if not HasTextAdvance() then
return false
end
EnableTextAdvance()
EnableQuickDialog()
EnableCutsceneSkip()
EnableAutoAcceptQuest()
yield("/echo [Script] TextAdvance configured for full automation")
return true
end
--- Configure TextAdvance for manual play
function ConfigureForManualPlay()
if not HasTextAdvance() then
return false
end
DisableTextAdvance()
DisableQuickDialog()
DisableCutsceneSkip()
DisableAutoAcceptQuest()
yield("/echo [Script] TextAdvance disabled for manual play")
return true
end
--- Configure TextAdvance for quest grinding
function ConfigureForQuestGrinding()
if not HasTextAdvance() then
return false
end
EnableTextAdvance()
EnableQuickDialog()
EnableCutsceneSkip()
EnableAutoAcceptQuest()
yield("/echo [Script] TextAdvance configured for quest grinding")
return true
end
--- Configure TextAdvance for story content
function ConfigureForStoryContent()
if not HasTextAdvance() then
return false
end
DisableTextAdvance() -- Let player read dialog
DisableCutsceneSkip() -- Watch cutscenes
DisableAutoAcceptQuest() -- Manual quest acceptance
yield("/echo [Script] TextAdvance configured for story content")
return true
end
Integration with Other Plugins
TextAdvance + Lifestream
--- Teleport and talk to NPC
-- @param aetheryteId number - Aetheryte to teleport to
-- @param npcName string - NPC to talk to
-- @return boolean - True if successful
function TeleportAndTalkToNPC(aetheryteId, npcName)
-- Enable TextAdvance
EnableTextAdvance()
-- Teleport
if IPC.Lifestream then
yield("/tp " .. aetheryteId)
-- Wait for teleport
yield("/wait 3")
while Svc.Condition[45] do -- BetweenAreas
yield("/wait 1")
end
yield("/wait 2")
end
-- Navigate and talk
return CompleteNPCInteraction(npcName, 60)
end
TextAdvance + vnavmesh
--- Navigate to NPC and interact
-- @param position Vector3 - NPC position
-- @param npcName string - NPC name
-- @return boolean - True if successful
function NavigateAndInteract(position, npcName)
-- Enable TextAdvance
EnableTextAdvance()
-- Navigate to position
if IPC.vnavmesh then
IPC.vnavmesh.PathfindAndMoveTo(position, false)
-- Wait for arrival
local timeout = 60
local startTime = os.clock()
while IPC.vnavmesh.IsRunning() and (os.clock() - startTime) < timeout do
yield("/wait 0.5")
end
end
-- Interact with NPC
return CompleteNPCInteraction(npcName, 60)
end
State Machine Integration
CharacterState = {
idle = Idle,
interacting = Interacting,
inCutscene = InCutscene,
-- ... other states
}
function Interacting()
-- TextAdvance handles dialogs automatically
-- Just wait for interaction to complete
if not IsDialogOpen() and not IsInQuestEvent() then
yield("/echo [Script] Interaction complete")
DisableTextAdvance()
State = CharacterState.idle
return
end
-- Check for cutscene transition
if IsInCutscene() then
State = CharacterState.inCutscene
return
end
-- Still interacting, stay in this state
end
function InCutscene()
-- TextAdvance with cutscene skip enabled handles this
-- Just wait for cutscene to end
if not IsInCutscene() then
yield("/echo [Script] Cutscene ended")
State = CharacterState.idle
return
end
-- Still in cutscene
end
Error Handling
--- Safe TextAdvance command
-- @param command string - TextAdvance command
-- @return boolean - True if command sent
function SafeTextAdvanceCommand(command)
if not HasTextAdvance() then
yield("/echo [Script] TextAdvance not available")
return false
end
local success = pcall(function()
yield(command)
end)
return success
end
--- Interaction with timeout and retry
-- @param interactFunc function - Function to perform interaction
-- @param maxRetries number - Maximum retries (default: 3)
-- @param timeout number - Timeout per attempt (default: 30)
-- @return boolean - True if successful
function InteractWithRetry(interactFunc, maxRetries, timeout)
maxRetries = maxRetries or 3
timeout = timeout or 30
for attempt = 1, maxRetries do
local success = interactFunc(timeout)
if success then
return true
end
yield("/echo [Script] Interaction attempt " .. attempt .. " failed, retrying...")
yield("/wait 2")
end
yield("/echo [Script] Interaction failed after " .. maxRetries .. " attempts")
return false
end
Configuration Variables
configs:
EnableTextAdvance:
default: true
description: Enable TextAdvance during automation
QuickDialog:
default: true
description: Enable quick dialog advancement
AutoCutsceneSkip:
default: true
description: Automatically skip cutscenes
AutoAcceptQuests:
default: false
description: Automatically accept quests
DialogTimeout:
default: 60
description: Dialog interaction timeout in seconds
CutsceneTimeout:
default: 120
description: Cutscene skip timeout in seconds
Character Conditions Reference
-- Conditions related to dialog/interaction
local CharacterCondition = {
occupiedInEvent = 31, -- In event/interaction
occupiedInQuestEvent = 32, -- In quest event
watchingCutscene = 10, -- Watching cutscene
watchingCutscene2 = 11, -- Alternative cutscene flag
dutyRecorderPlayback = 98, -- Watching duty recording
betweenAreas = 45, -- Loading screen
casting = 27, -- Casting (can't interact)
}
--- Check if player can interact
-- @return boolean - True if can interact
function CanInteract()
return not Svc.Condition[CharacterCondition.casting] and
not Svc.Condition[CharacterCondition.betweenAreas] and
not IsInCutscene() and
Player.Available
end
Best Practices
- Enable TextAdvance before interactions - Call
EnableTextAdvance()before NPC interactions - Disable when not needed - Don't leave TextAdvance enabled permanently
- Use appropriate timeouts - 30-60s for dialogs, 120s for cutscenes
- Check dialog state before and after interactions
- Handle cutscenes separately - They may interrupt normal flow
- Use configuration presets for consistent behavior
- Wait for dialog close before continuing automation
- Handle item turn-in (Request addon) during quest completion
- Retry failed interactions - Network issues can cause failures
- Integrate with navigation for complete quest automation
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?