Created: 11 days ago on 06/03/2025, 08:48:10 AM
Description: Description included in script
This script was crafted with ChatGPT 04-mini-high using the SAGAS documentation as reference, but also using Elly Strikehursts bard script for a base
--[[
AFK Bard Trainer (Peacemaking → Discordance Sequence, with Journal Checks)
Author: Elly Strikehurst Ingame
Last Updated: 2025-06-04
Description:
This script continuously trains Bard skills “Peacemaking” and “Discordance” on the nearest hostile targets.
It uses a Tambourine (graphic 0x0E9D) from your backpack when prompted. The script supports three modes:
1. Peacemaking only
2. Discordance only
3. Combined Peacemaking → Discordance sequence (casts Peacemaking first, then Discordance on the same target).
Features:
• Finds the nearest hostile mobile (notoriety = Enemy) within configurable ranges for each skill.
• Casts “SongOfPeacemaking” or “SongOfDiscordance” and waits for the “What instrument shall you play?” prompt.
- Automatically “uses” a Tambourine from your backpack to satisfy the prompt.
• Waits for the respective targeting prompt (“Whom do you wish to calm” or “Choose the target for your song of discordance”)
and targets the hostile mobile.
• Parses journal messages to adjust delays:
- “That creature is already being calmed.” → if Peacemaking prompt appears but target already calmed, waits 5 seconds.
- “You play your hypnotic music, stopping the battle.” → after successful Peacemaking, waits a normal training delay (7 seconds).
- “You must wait a few seconds before you can play another song.” → delay 5 seconds before retrial.
- “That creature is already in discord.” → if Discordance prompt appears but target already in Discord, waits 5 seconds.
- “Discord effect ended on target” → if Discord ended, waits 2 seconds to allow re-cast.
• Maintains independent timers for each skill, or a combined timer when both are enabled sequentially.
• Prints helpful overhead messages for status, errors, and cooldowns.
Configuration Parameters:
usePeacemaking = true -- enable Peacemaking training
useDiscordance = true -- enable Discordance training
peacemakingRange = 12 -- tile range to search for targets for Peacemaking
discordanceRange = 12 -- tile range to search for targets for Discordance
How It Works:
- If both usePeacemaking and useDiscordance are true, the script enters a “combined” mode:
1. Stage 0: Cast Peacemaking on the nearest hostile within peacemakingRange.
Depending on journal feedback, sets a delay before casting Discordance.
2. Stage 1: Cast Discordance on the same hostile (if still in range), then set a delay before returning to Stage 0.
- If only one skill is enabled, it will cast that skill on any hostile in range, rechecking after the appropriate delay.
- Journal monitoring ensures the script adapts to cooldown messages, “already” messages, and “effect ended” messages,
rather than blindly waiting fixed intervals.
Usage:
1. Place a Tambourine (graphic 0x0E9D) in your backpack.
2. Run this script in the UO Sagas Assistant.
3. AFK—your character will cast Peacemaking and/or Discordance on the nearest hostile, switching instruments as needed.
Note:
- Make sure you have sufficient mana, reagents (if required by your server), and that you are in an area
where hostile targets can be found.
- Adjust peacemakingRange, discordanceRange, and delays if your shard’s requirements differ.
]]
-- Enable/disable skills
local usePeacemaking = true
local useDiscordance = true
-- Ranges (tiles) to find nearest hostile
local peacemakingRange = 12
local discordanceRange = 12
-- Internal State
local peacemakingTimer = 0 -- next time allowed to cast Peacemaking
local discordanceTimer = 0 -- next time allowed to cast Discordance
local combinedTimer = 0 -- controls sequential flow when both enabled
local combinedStage = 0 -- 0 = ready for Peacemaking, 1 = ready for Discordance
local lastOverheadTime = 0
-- Helper: calculate Euclidean distance
local function getDistance(x1, y1, x2, y2)
return math.sqrt((x2 - x1) ^ 2 + (y2 - y1) ^ 2)
end
-- Helper: sort a list of mobiles by distance to player
local function sortByDistance(enemies)
local px, py = Player.X, Player.Y
table.sort(enemies, function(a, b)
return getDistance(px, py, a.X, a.Y) < getDistance(px, py, b.X, b.Y)
end)
return enemies
end
-- Helper: find the first Tambourine (graphic 0x0E9D) in backpack
local function findTambourine()
local items = Items.FindByFilter({ graphics = { 0x0E9D }, onground = false }) or {}
for _, it in ipairs(items) do
if it.RootContainer == Player.Serial then
return it
end
end
return nil
end
-- Helper: find the nearest hostile (notoriety 5 = Enemy) within a given range
local function getNearestHostileInRange(range)
local filter = {
human = false,
notoriety = { 5 },
rangemax = range,
dead = false
}
local enemies = Mobiles.FindByFilter(filter) or {}
if #enemies == 0 then return nil end
enemies = sortByDistance(enemies)
return enemies[1]
end
-- Peacemaking Logic (returns next delay in seconds)
local function songPeacemakingOn(target)
-- Cast SongOfPeacemaking
Journal.Clear()
if not Spells.Cast("SongOfPeacemaking") then
Messages.Overhead("Failed to cast Peacemaking", 33, Player.Serial)
return 2
end
-- Wait up to 3s for “What instrument shall you play” prompt
local startInstr = os.clock()
while os.clock() - startInstr < 3 do
if Journal.Contains("What instrument shall you play") then
Journal.Clear()
local tamb = findTambourine()
if tamb then
Player.UseObject(tamb.Serial)
Messages.Overhead("Playing Tambourine for Peacemaking", 68, Player.Serial)
else
Messages.Overhead("No Tambourine!", 33, Player.Serial)
return 2
end
break
end
Pause(50)
end
-- Wait up to 3s for relevant prompts:
local startTarget = os.clock()
while os.clock() - startTarget < 3 do
-- Cooldown message
if Journal.Contains("You must wait a few seconds before you can play another song") then
Journal.Clear()
Messages.Overhead("Peacemaking: Cooldown message", 33, Player.Serial)
return 5
end
-- Already being calmed
if Journal.Contains("That creature is already being calmed.") then
Journal.Clear()
Messages.Overhead("Already being calmed", 33, Player.Serial)
return 5
end
-- “Whom do you wish to calm” prompt
if Journal.Contains("Whom do you wish to calm") then
Journal.Clear()
if Targeting.WaitForTarget(3000) then
Targeting.Target(target.Serial)
Messages.Overhead("Peacemaking → " .. (target.Name or "Unknown"), 70, Player.Serial)
-- After success, wait for “You play your hypnotic music, stopping the battle”
local startFinish = os.clock()
while os.clock() - startFinish < 2 do
if Journal.Contains("You play your hypnotic music, stopping the battle") then
Journal.Clear()
break
end
Pause(50)
end
return 7 -- normal training delay
else
Messages.Overhead("Peacemaking: No target cursor", 33, Player.Serial)
return 2
end
end
Pause(50)
end
-- Prompt never appeared
Messages.Overhead("Peacemaking: Prompt timeout", 33, Player.Serial)
return 2
end
-- Discordance Logic (returns next delay in seconds)
local function songDiscordanceOn(target)
-- Cast SongOfDiscordance
Journal.Clear()
if not Spells.Cast("SongOfDiscordance") then
Messages.Overhead("Failed to cast Discordance", 33, Player.Serial)
return 2
end
-- Wait up to 3s for “What instrument shall you play” prompt
local startInstr = os.clock()
while os.clock() - startInstr < 3 do
if Journal.Contains("What instrument shall you play") then
Journal.Clear()
local tamb = findTambourine()
if tamb then
Player.UseObject(tamb.Serial)
Messages.Overhead("Playing Tambourine for Discordance", 68, Player.Serial)
else
Messages.Overhead("No Tambourine!", 33, Player.Serial)
return 2
end
break
end
Pause(50)
end
-- Wait up to 3s for relevant prompts:
local startTarget = os.clock()
while os.clock() - startTarget < 3 do
-- Already in discord
if Journal.Contains("That creature is already in discord.") then
Journal.Clear()
Messages.Overhead("Already in Discord", 33, Player.Serial)
return 5
end
-- Discord ended
if Journal.Contains("Discord effect ended on target") then
Journal.Clear()
Messages.Overhead("Discord ended, can recast soon", 32, Player.Serial)
return 2
end
-- “Choose the target for your song of discordance” prompt
if Journal.Contains("Choose the target for your song of discordance") then
Journal.Clear()
if Targeting.WaitForTarget(3000) then
Targeting.Target(target.Serial)
Messages.Overhead("Discordance → " .. (target.Name or "Unknown"), 70, Player.Serial)
return 7 -- normal training delay
else
Messages.Overhead("Discordance: No target cursor", 33, Player.Serial)
return 2
end
end
Pause(50)
end
-- Prompt never appeared
Messages.Overhead("Discordance: Prompt timeout", 33, Player.Serial)
return 2
end
-- Combined Logic (Peacemaking → Discordance Sequence)
local function songPeacemakeThenDiscord()
local now = os.time()
if now < combinedTimer then return end
-- Find nearest hostile in both ranges
local targetPM = getNearestHostileInRange(peacemakingRange)
local targetDC = getNearestHostileInRange(discordanceRange)
-- If no hostile at all, message once
if not (targetPM or targetDC) then
if now > lastOverheadTime then
Messages.Overhead("No hostiles in range", 32, Player.Serial)
lastOverheadTime = now + 1
end
return
end
-- Prefer a common target (if in both ranges) or whichever exists
local target = targetPM or targetDC
-- Stage 0: attempt Peacemaking first
if combinedStage == 0 and usePeacemaking and targetPM then
local delay = songPeacemakingOn(target)
combinedStage = 1
combinedTimer = now + delay
return
end
-- Stage 1: attempt Discordance next
if combinedStage == 1 and useDiscordance and targetDC then
local delay = songDiscordanceOn(target)
combinedStage = 0
combinedTimer = now + delay
return
end
-- If targetPM is nil but Stage 0, skip to Stage 1
if combinedStage == 0 and not targetPM then
combinedStage = 1
combinedTimer = now
return
end
-- If targetDC is nil but Stage 1, reset to Stage 0
if combinedStage == 1 and not targetDC then
combinedStage = 0
combinedTimer = now
return
end
end
-- Initialization
Messages.Overhead(
"Bard Trainer Loaded (Peacemaking=" ..
tostring(usePeacemaking) ..
", Discordance=" ..
tostring(useDiscordance) ..
")",
70,
Player.Serial
)
-- Main Loop
repeat
if not Player.IsDead then
if usePeacemaking and useDiscordance then
songPeacemakeThenDiscord()
else
-- Independent Peacemaking
if usePeacemaking then
local now = os.time()
if now >= peacemakingTimer then
local target = getNearestHostileInRange(peacemakingRange)
if target then
local delay = songPeacemakingOn(target)
peacemakingTimer = now + delay
elseif now > lastOverheadTime then
Messages.Overhead("No hostiles in range", 32, Player.Serial)
lastOverheadTime = now + 1
end
end
end
-- Independent Discordance
if useDiscordance then
local now = os.time()
if now >= discordanceTimer then
local target = getNearestHostileInRange(discordanceRange)
if target then
local delay = songDiscordanceOn(target)
discordanceTimer = now + delay
elseif now > lastOverheadTime then
Messages.Overhead("No hostiles in range", 32, Player.Serial)
lastOverheadTime = now + 1
end
end
end
end
end
Pause(100)
until false