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