SND Combat Plugin Integration
Use this skill when implementing combat automation in SND macros using rotation plugins like BossMod, RSR (RotationSolver Reborn), or Wrath. Covers plugin control, state management, and combat patterns.
$ 설치
git clone https://github.com/vaoan/SND /tmp/SND && cp -r /tmp/SND/.claude/skills/snd-combat ~/.claude/skills/SND// tip: Run this command in your terminal to install the skill
SKILL.md
name: SND Combat Plugin Integration description: Use this skill when implementing combat automation in SND macros using rotation plugins like BossMod, RSR (RotationSolver Reborn), or Wrath. Covers plugin control, state management, and combat patterns.
Combat Plugin Integration for SND
This skill covers integration with combat/rotation plugins for SND macros.
Supported Plugins
| Plugin | Internal Name | Description |
|---|---|---|
| BossMod | BossMod | Boss mechanics and auto-rotation |
| RotationSolver Reborn | RotationSolver | Advanced rotation automation |
| Wrath | WrathCombo | Combo-based rotation helper |
Plugin Availability
Check Plugin Availability
--- Check if a combat plugin is loaded
-- @param pluginName string - Plugin internal name
-- @return boolean - True if available
function HasCombatPlugin(pluginName)
for plugin in luanet.each(Svc.PluginInterface.InstalledPlugins) do
if plugin.InternalName == pluginName and plugin.IsLoaded then
return true
end
end
return false
end
--- Check if any combat plugin is available
-- @return boolean, string - True and plugin name if found
function HasAnyCombatPlugin()
local plugins = {"BossMod", "RotationSolver", "WrathCombo"}
for _, name in ipairs(plugins) do
if HasCombatPlugin(name) then
return true, name
end
end
return false, nil
end
--- Get available combat plugin
-- @return string|nil - Plugin name or nil
function GetCombatPlugin()
local has, name = HasAnyCombatPlugin()
return has and name or nil
end
BossMod Integration
BossMod AI Control
--- Enable BossMod AI
-- @return boolean - True if enabled
function EnableBossModAI()
if not HasCombatPlugin("BossMod") then
yield("/echo [Script] BossMod not available")
return false
end
yield("/bmai on")
yield("/wait 0.3")
return true
end
--- Disable BossMod AI
-- @return boolean - True if disabled
function DisableBossModAI()
if not HasCombatPlugin("BossMod") then
return false
end
yield("/bmai off")
yield("/wait 0.3")
return true
end
--- Toggle BossMod AI
function ToggleBossModAI()
if not HasCombatPlugin("BossMod") then
return false
end
yield("/bmai")
yield("/wait 0.3")
return true
end
--- Set BossMod follow target
-- @param targetName string - Target to follow (or "off")
function SetBossModFollow(targetName)
if not HasCombatPlugin("BossMod") then
return false
end
if targetName == "off" or targetName == nil then
yield("/bmfollow off")
else
yield("/bmfollow " .. targetName)
end
yield("/wait 0.3")
return true
end
BossMod Positional Commands
--- Move BossMod AI to position
-- @param direction string - "front", "back", "left", "right", "flank"
function BossModMoveToPositional(direction)
if not HasCombatPlugin("BossMod") then
return false
end
yield("/bmpos " .. direction)
return true
end
RotationSolver Reborn (RSR) Integration
RSR State Control
--- Check if RSR is available via IPC
-- @return boolean - True if available
function IsRSRAvailable()
return IPC.RSR ~= nil
end
--- Enable RSR auto rotation
-- @return boolean - True if enabled
function EnableRSR()
if not HasCombatPlugin("RotationSolver") then
yield("/echo [Script] RSR not available")
return false
end
yield("/rotation auto")
yield("/wait 0.3")
return true
end
--- Disable RSR auto rotation
-- @return boolean - True if disabled
function DisableRSR()
if not HasCombatPlugin("RotationSolver") then
return false
end
yield("/rotation cancel")
yield("/wait 0.3")
return true
end
--- Set RSR to manual mode
function SetRSRManual()
if not HasCombatPlugin("RotationSolver") then
return false
end
yield("/rotation manual")
yield("/wait 0.3")
return true
end
--- Get RSR state (if IPC available)
-- @return string|nil - Current state
function GetRSRState()
if IPC.RSR and IPC.RSR.GetState then
return IPC.RSR.GetState()
end
return nil
end
RSR Configuration Commands
--- Set RSR targeting mode
-- @param mode string - "hostile", "friendly", "all"
function SetRSRTargetMode(mode)
if not HasCombatPlugin("RotationSolver") then
return false
end
yield("/rotation targetmode " .. mode)
yield("/wait 0.2")
return true
end
--- Enable/disable RSR AoE
-- @param enabled boolean - True to enable
function SetRSRAoE(enabled)
if not HasCombatPlugin("RotationSolver") then
return false
end
local cmd = enabled and "/rotation aoe on" or "/rotation aoe off"
yield(cmd)
yield("/wait 0.2")
return true
end
Wrath Integration
Wrath Control
--- Check if Wrath is available
-- @return boolean - True if available
function IsWrathAvailable()
return HasCombatPlugin("WrathCombo")
end
--- Enable Wrath auto rotation
-- @return boolean - True if enabled
function EnableWrath()
if not HasCombatPlugin("WrathCombo") then
yield("/echo [Script] Wrath not available")
return false
end
yield("/wrath on")
yield("/wait 0.3")
return true
end
--- Disable Wrath auto rotation
-- @return boolean - True if disabled
function DisableWrath()
if not HasCombatPlugin("WrathCombo") then
return false
end
yield("/wrath off")
yield("/wait 0.3")
return true
end
--- Toggle Wrath
function ToggleWrath()
if not HasCombatPlugin("WrathCombo") then
return false
end
yield("/wrath")
yield("/wait 0.3")
return true
end
Universal Combat Control
Unified Combat Plugin Interface
--- Combat plugin interface
CombatPlugin = {
plugin = nil, -- Active plugin name
}
--- Initialize combat plugin interface
-- @return boolean - True if a plugin is available
function CombatPlugin.Init()
local has, name = HasAnyCombatPlugin()
CombatPlugin.plugin = name
return has
end
--- Enable auto combat
-- @return boolean - True if enabled
function CombatPlugin.Enable()
if not CombatPlugin.plugin then
CombatPlugin.Init()
end
if CombatPlugin.plugin == "BossMod" then
return EnableBossModAI()
elseif CombatPlugin.plugin == "RotationSolver" then
return EnableRSR()
elseif CombatPlugin.plugin == "WrathCombo" then
return EnableWrath()
end
yield("/echo [Script] No combat plugin available")
return false
end
--- Disable auto combat
-- @return boolean - True if disabled
function CombatPlugin.Disable()
if not CombatPlugin.plugin then
return false
end
if CombatPlugin.plugin == "BossMod" then
return DisableBossModAI()
elseif CombatPlugin.plugin == "RotationSolver" then
return DisableRSR()
elseif CombatPlugin.plugin == "WrathCombo" then
return DisableWrath()
end
return false
end
--- Get current combat plugin name
-- @return string|nil - Plugin name
function CombatPlugin.GetName()
return CombatPlugin.plugin
end
Combat State Checking
Player Combat State
--- Character conditions for combat
local CharacterCondition = {
inCombat = 26,
casting = 27,
inCombat2 = 34, -- Alternative combat flag
unconscious = 35,
boundByDuty = 37,
}
--- Check if player is in combat
-- @return boolean - True if in combat
function IsInCombat()
return Svc.Condition[CharacterCondition.inCombat] or
Svc.Condition[CharacterCondition.inCombat2]
end
--- Check if player is casting
-- @return boolean - True if casting
function IsCasting()
return Svc.Condition[CharacterCondition.casting]
end
--- Check if player is dead
-- @return boolean - True if dead
function IsDead()
return Svc.Condition[CharacterCondition.unconscious]
end
--- Check if in duty
-- @return boolean - True if in duty
function IsInDuty()
return Svc.Condition[CharacterCondition.boundByDuty]
end
--- Wait until out of combat
-- @param timeout number - Maximum wait time (default: 30)
-- @return boolean - True if out of combat
function WaitUntilOutOfCombat(timeout)
timeout = timeout or 30
local startTime = os.clock()
while IsInCombat() and (os.clock() - startTime) < timeout do
yield("/wait 0.5")
end
return not IsInCombat()
end
--- Wait until in combat
-- @param timeout number - Maximum wait time (default: 10)
-- @return boolean - True if in combat
function WaitUntilInCombat(timeout)
timeout = timeout or 10
local startTime = os.clock()
while not IsInCombat() and (os.clock() - startTime) < timeout do
yield("/wait 0.5")
end
return IsInCombat()
end
Target Management
--- Check if player has a target
-- @return boolean - True if has target
function HasTarget()
return Svc.Targets.Target ~= nil
end
--- Get current target
-- @return GameObject|nil - Current target
function GetTarget()
return Svc.Targets.Target
end
--- Set target
-- @param obj GameObject - Object to target
function SetTarget(obj)
if obj then
Svc.Targets.Target = obj
end
end
--- Clear current target
function ClearTarget()
Svc.Targets.Target = nil
end
--- Check if target is attackable
-- @return boolean - True if attackable
function IsTargetAttackable()
local target = GetTarget()
if not target then
return false
end
return target.IsTargetable and
not target.IsDead and
target.ObjectKind == ObjectKind.BattleNpc
end
--- Get distance to target
-- @return number - Distance in yalms
function GetTargetDistance()
local target = GetTarget()
if not target or not Player.Available then
return 99999
end
local playerPos = Player.Position
local targetPos = target.Position
local dx = playerPos.X - targetPos.X
local dy = playerPos.Y - targetPos.Y
local dz = playerPos.Z - targetPos.Z
return math.sqrt(dx*dx + dy*dy + dz*dz)
end
--- Check if target is in range
-- @param maxRange number - Maximum range (default: 25)
-- @return boolean - True if in range
function IsTargetInRange(maxRange)
maxRange = maxRange or 25
return GetTargetDistance() <= maxRange
end
Enemy Detection
--- Find nearest enemy
-- @param maxRange number - Search range (default: 30)
-- @return GameObject|nil - Nearest enemy
function FindNearestEnemy(maxRange)
maxRange = maxRange or 30
local nearest = nil
local nearestDist = maxRange
local objectTable = Svc.ObjectTable
if not objectTable then
return nil
end
for i = 0, objectTable.Length - 1 do
local obj = objectTable[i]
if obj and
obj.ObjectKind == ObjectKind.BattleNpc and
obj.IsTargetable and
not obj.IsDead and
IsHostile(obj) then
local dist = GetDistanceToObject(obj)
if dist < nearestDist then
nearest = obj
nearestDist = dist
end
end
end
return nearest
end
--- Check if object is hostile
-- @param obj GameObject - The object
-- @return boolean - True if hostile
function IsHostile(obj)
if not obj then
return false
end
-- Check if enemy (red icon)
-- This may vary based on game state
return obj.StatusFlags and (obj.StatusFlags & 1) ~= 0
end
--- Target nearest enemy
-- @param maxRange number - Search range (default: 30)
-- @return boolean - True if target acquired
function TargetNearestEnemy(maxRange)
local enemy = FindNearestEnemy(maxRange)
if enemy then
SetTarget(enemy)
return true
end
return false
end
--- Count enemies in range
-- @param range number - Range to check (default: 30)
-- @return number - Enemy count
function CountEnemiesInRange(range)
range = range or 30
local count = 0
local objectTable = Svc.ObjectTable
if not objectTable then
return 0
end
for i = 0, objectTable.Length - 1 do
local obj = objectTable[i]
if obj and
obj.ObjectKind == ObjectKind.BattleNpc and
obj.IsTargetable and
not obj.IsDead and
IsHostile(obj) then
local dist = GetDistanceToObject(obj)
if dist <= range then
count = count + 1
end
end
end
return count
end
Combat Patterns
Basic Combat Loop
--- Basic combat loop with rotation plugin
-- @param targetFunc function - Function to acquire targets
-- @param timeout number - Combat timeout (default: 300)
-- @return boolean - True if combat completed successfully
function BasicCombatLoop(targetFunc, timeout)
timeout = timeout or 300
local startTime = os.clock()
-- Initialize combat plugin
if not CombatPlugin.Init() then
yield("/echo [Script] No combat plugin available")
return false
end
-- Enable rotation
CombatPlugin.Enable()
while (os.clock() - startTime) < timeout do
-- Check stop conditions
if StopFlag then
CombatPlugin.Disable()
return false
end
-- Acquire target if needed
if not HasTarget() or not IsTargetAttackable() then
if targetFunc then
targetFunc()
else
TargetNearestEnemy(30)
end
end
-- Check if combat should continue
if not HasTarget() and not IsInCombat() then
-- No targets and not in combat, exit
break
end
-- Move to target if needed
if HasTarget() and not IsTargetInRange(25) then
local target = GetTarget()
if target and IPC.vnavmesh then
IPC.vnavmesh.PathfindAndMoveTo(target.Position, false)
end
elseif IPC.vnavmesh and IPC.vnavmesh.IsRunning() then
-- Stop moving when in range
IPC.vnavmesh.Stop()
end
yield("/wait 0.5")
end
-- Cleanup
CombatPlugin.Disable()
WaitUntilOutOfCombat(30)
return true
end
Pull and Fight Pattern
--- Pull an enemy and fight until dead
-- @param target GameObject - Enemy to pull
-- @param maxRange number - Attack range (default: 25)
-- @return boolean - True if enemy killed
function PullAndFight(target, maxRange)
maxRange = maxRange or 25
if not target then
return false
end
-- Target the enemy
SetTarget(target)
yield("/wait 0.2")
-- Move into range
while GetTargetDistance() > maxRange do
if IPC.vnavmesh then
IPC.vnavmesh.PathfindAndMoveTo(target.Position, false)
end
yield("/wait 0.3")
end
if IPC.vnavmesh then
IPC.vnavmesh.Stop()
end
-- Enable combat
CombatPlugin.Enable()
-- Wait for enemy to die
local timeout = 120
local startTime = os.clock()
while (os.clock() - startTime) < timeout do
local currentTarget = GetTarget()
-- Check if target is dead
if not currentTarget or currentTarget.IsDead then
CombatPlugin.Disable()
return true
end
-- Stay in range
if GetTargetDistance() > maxRange + 5 then
if IPC.vnavmesh then
IPC.vnavmesh.PathfindAndMoveTo(currentTarget.Position, false)
end
end
yield("/wait 0.5")
end
CombatPlugin.Disable()
return false
end
AoE Combat Pattern
--- AoE combat pattern for multiple enemies
-- @param centerPos Vector3 - Center position for AoE
-- @param pullRadius number - Radius to pull enemies (default: 15)
-- @param timeout number - Combat timeout (default: 180)
-- @return boolean - True if all enemies killed
function AoECombat(centerPos, pullRadius, timeout)
pullRadius = pullRadius or 15
timeout = timeout or 180
local startTime = os.clock()
-- Enable combat plugin
CombatPlugin.Enable()
-- Move to center
if IPC.vnavmesh then
IPC.vnavmesh.PathfindAndMoveTo(centerPos, false)
while IPC.vnavmesh.IsRunning() do
yield("/wait 0.3")
end
end
-- Combat loop
while (os.clock() - startTime) < timeout do
local enemyCount = CountEnemiesInRange(pullRadius + 10)
if enemyCount == 0 and not IsInCombat() then
-- All enemies dead
break
end
-- Target nearest if needed
if not HasTarget() or not IsTargetAttackable() then
TargetNearestEnemy(pullRadius + 10)
end
-- Stay in center area
local playerPos = Player.Position
local dx = playerPos.X - centerPos.X
local dz = playerPos.Z - centerPos.Z
local distFromCenter = math.sqrt(dx*dx + dz*dz)
if distFromCenter > pullRadius then
if IPC.vnavmesh then
IPC.vnavmesh.PathfindAndMoveTo(centerPos, false)
end
end
yield("/wait 0.5")
end
CombatPlugin.Disable()
WaitUntilOutOfCombat(30)
return CountEnemiesInRange(pullRadius + 10) == 0
end
Dungeon/Duty Support
Duty Combat Management
--- Combat management for duties/dungeons
DutyCombat = {
enabled = false,
plugin = nil,
}
--- Initialize duty combat
function DutyCombat.Init()
DutyCombat.enabled = CombatPlugin.Init()
DutyCombat.plugin = CombatPlugin.plugin
return DutyCombat.enabled
end
--- Start duty combat mode
function DutyCombat.Start()
if not DutyCombat.enabled then
DutyCombat.Init()
end
CombatPlugin.Enable()
yield("/echo [Script] Combat enabled for duty")
end
--- Stop duty combat mode
function DutyCombat.Stop()
CombatPlugin.Disable()
yield("/echo [Script] Combat disabled")
end
--- Handle trash pack
-- @param packCenter Vector3 - Center of trash pack
-- @param timeout number - Pack timeout (default: 120)
function DutyCombat.HandleTrashPack(packCenter, timeout)
timeout = timeout or 120
AoECombat(packCenter, 15, timeout)
end
--- Handle boss fight
-- @param bossTarget GameObject - Boss to fight
-- @param timeout number - Boss timeout (default: 600)
function DutyCombat.HandleBoss(bossTarget, timeout)
timeout = timeout or 600
if not bossTarget then
return false
end
-- Target boss
SetTarget(bossTarget)
CombatPlugin.Enable()
local startTime = os.clock()
while (os.clock() - startTime) < timeout do
local boss = GetTarget()
-- Check if boss is dead
if not boss or boss.IsDead then
yield("/echo [Script] Boss defeated!")
CombatPlugin.Disable()
return true
end
-- Handle death
if IsDead() then
yield("/echo [Script] Player died during boss!")
-- Wait for raise or release
yield("/wait 10")
end
yield("/wait 0.5")
end
CombatPlugin.Disable()
return false
end
State Machine Integration
CharacterState = {
idle = Idle,
combat = Combat,
postCombat = PostCombat,
-- ... other states
}
function Combat()
-- Enable rotation plugin
CombatPlugin.Enable()
-- Combat logic
if not HasTarget() then
TargetNearestEnemy(30)
end
-- Check exit conditions
if not IsInCombat() and not HasTarget() then
State = CharacterState.postCombat
return
end
-- Stay in combat state
end
function PostCombat()
-- Disable rotation plugin
CombatPlugin.Disable()
-- Wait for combat to fully end
WaitUntilOutOfCombat(10)
-- Transition to next state
State = CharacterState.idle
end
Configuration Variables
configs:
CombatPlugin:
default: "auto"
description: Combat plugin to use (auto, BossMod, RSR, Wrath)
is_choice: true
choices: ["auto", "BossMod", "RSR", "Wrath"]
EnableAutoCombat:
default: true
description: Enable automatic combat during automation
CombatRange:
default: 25
description: Maximum combat engagement range
CombatTimeout:
default: 300
description: Combat timeout in seconds
EnableAoE:
default: true
description: Enable AoE skills during combat
PullRadius:
default: 15
description: Radius for pulling enemies
Helper Functions
--- Get distance to a game object
-- @param obj GameObject - The object
-- @return number - Distance in yalms
function GetDistanceToObject(obj)
if not obj or not Player.Available then
return 99999
end
local playerPos = Player.Position
local objPos = obj.Position
local dx = playerPos.X - objPos.X
local dy = playerPos.Y - objPos.Y
local dz = playerPos.Z - objPos.Z
return math.sqrt(dx*dx + dy*dy + dz*dz)
end
--- Object kind constants
ObjectKind = {
None = 0,
Pc = 1,
BattleNpc = 2,
EventNpc = 3,
Treasure = 4,
Aetheryte = 5,
GatheringPoint = 6,
EventObj = 7,
MountType = 8,
Companion = 9,
Retainer = 10,
AreaObject = 11,
Housing = 12,
Cutscene = 13,
CardStand = 14,
}
Best Practices
- Initialize combat plugin early - Call
CombatPlugin.Init()at script start - Always disable after combat - Prevent unintended actions
- Use unified interface -
CombatPlugin.Enable()/Disable()works with any plugin - Check plugin availability - Don't assume any specific plugin
- Handle combat timeout - Don't get stuck in combat loops
- Wait for out-of-combat before transitions
- Use appropriate ranges - 25y for ranged, 3y for melee
- Handle player death - Check
IsDead()in combat loops - Let plugins handle rotations - Don't implement manual skill usage
- Integrate with navigation - Move to targets when out of range
