Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
299 changes: 299 additions & 0 deletions Core/MultiBotComm.lua
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ local function ensureBridgeState()
state.combatSeq = state.combatSeq or 0
state.positionSeq = state.positionSeq or 0
state.lootSeq = state.lootSeq or 0
state.formationSeq = state.formationSeq or 0
state.formationCommands = state.formationCommands or {}
state.formationQuerySeq = state.formationQuerySeq or 0
state.formationQueryActive = state.formationQueryActive or nil
return state
end

Expand Down Expand Up @@ -399,6 +403,228 @@ function Comm.RunPositionCommand(scope, target, command)
return Comm.Send("RUN", "POSITION~" .. scope .. "~" .. urlEncodeField(target) .. "~" .. token .. "~" .. urlEncodeField(command))
end

function Comm.RunFormationCommand(scope, target, formation, callback)
local state = ensureBridgeState()

if not state.connected then
return false
end

scope = string.upper(trim(scope or "GROUP"))
target = trim(target or "")
formation = string.lower(trim(formation or ""))

local allowed = {
arrow = true,
queue = true,
near = true,
melee = true,
line = true,
circle = true,
chaos = true,
shield = true,
}

if scope ~= "GROUP" or target ~= "" or not allowed[formation] then
return false
end

state.formationSeq = (tonumber(state.formationSeq) or 0) + 1
local token = tostring(math.floor(safeNow() * 1000)) .. "-formation-" .. tostring(state.formationSeq)
state.formationCommands[token] = {
formation = formation,
callback = type(callback) == "function" and callback or nil,
startedAt = safeNow(),
}

if not Comm.Send("RUN", "FORMATION~" .. scope .. "~~" .. token .. "~" .. urlEncodeField(formation)) then
state.formationCommands[token] = nil
return false
end

if MultiBot and type(MultiBot.TimerAfter) == "function" then
MultiBot.TimerAfter(5.0, function()
local bridge = ensureBridgeState()
local pending = bridge.formationCommands[token]
if not pending then
return
end

bridge.formationCommands[token] = nil
local result = {
status = "timeout",
scope = scope,
target = target,
token = token,
success = 0,
failure = 0,
formation = formation,
}

if type(pending.callback) == "function" then
pending.callback(result)
end

if MultiBot.OnFormationCommandApplied then
MultiBot.OnFormationCommandApplied(result)
end

systemMessage(L("formation.confirm.timeout"))
end)
end

return token
end
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function Comm.RequestFormations(callback)
local state = ensureBridgeState()

if not state.connected then
return false
end

state.formationQuerySeq = (tonumber(state.formationQuerySeq) or 0) + 1
local token = tostring(math.floor(safeNow() * 1000)) .. "-formations-" .. tostring(state.formationQuerySeq)

state.formationQueryActive = {
token = token,
callback = type(callback) == "function" and callback or nil,
startedAt = safeNow(),
expected = 0,
items = {},
begun = false,
}

if not Comm.Send("GET", "FORMATIONS~GROUP~~" .. token) then
state.formationQueryActive = nil
return false
end

if MultiBot and type(MultiBot.TimerAfter) == "function" then
MultiBot.TimerAfter(5.0, function()
local bridge = ensureBridgeState()
local active = bridge.formationQueryActive
if not active or active.token ~= token then
return
end

bridge.formationQueryActive = nil
local result = {
token = token,
status = "timeout",
expected = tonumber(active.expected or 0) or 0,
sent = #(active.items or {}),
items = active.items or {},
}

if type(active.callback) == "function" then
active.callback(result)
end

if MultiBot.OnFormationQueryCompleted then
MultiBot.OnFormationQueryCompleted(result)
end
end)
end

return token
end

local function getActiveFormationQuery(token)
local active = ensureBridgeState().formationQueryActive
token = trim(token)

if type(active) ~= "table" or token == "" or active.token ~= token then
return nil
end

return active
end

function Comm.ApplyFormationsBeginPayload(payload)
local token, countText = splitOnce(payload or "", "~")
token = trim(token)

local active = getActiveFormationQuery(token)
if not active then
return false
end

active.expected = tonumber(countText or "0") or 0
active.items = {}
active.begun = true

debugPrint("ADDON:RX", "FORMATIONS_BEGIN", token, active.expected)
return true
end

function Comm.ApplyFormationsItemPayload(payload)
local token, rest = splitOnce(payload or "", "~")
local encodedBotName, encodedFormation = splitOnce(rest or "", "~")
token = trim(token)

local active = getActiveFormationQuery(token)
if not active or not active.begun then
return false
end

local botName = trim(urlDecodeField(encodedBotName))
local formation = string.lower(trim(urlDecodeField(encodedFormation)))

if botName == "" then
return false
end

if formation == "" then
formation = "?"
end

active.items[#active.items + 1] = {
botName = botName,
formation = formation,
}

debugPrint("ADDON:RX", "FORMATIONS_ITEM", token, botName, formation)
return true
end

function Comm.ApplyFormationsEndPayload(payload)
local token, sentText = splitOnce(payload or "", "~")
token = trim(token)

local state = ensureBridgeState()
local active = getActiveFormationQuery(token)
if not active or not active.begun then
return false
end

table.sort(active.items, function(left, right)
return string.lower(left.botName or "") < string.lower(right.botName or "")
end)

state.formationQueryActive = nil

local result = {
token = token,
status = "ok",
expected = tonumber(active.expected or 0) or 0,
sent = tonumber(sentText or "0") or 0,
items = active.items or {},
}

debugPrint("ADDON:RX", "FORMATIONS_END", token, result.sent)

if type(active.callback) == "function" then
active.callback(result)
end

if MultiBot.OnFormationQueryCompleted then
MultiBot.OnFormationQueryCompleted(result)
end

return true
end

function Comm.RequestOutfits(name)
local state = ensureBridgeState()
name = trim(name)
Expand Down Expand Up @@ -892,6 +1118,8 @@ function Comm.MarkDisconnected(reason)
state.outfitCommands = {}
state.trainerActive = nil
state.trainerCommands = {}
state.formationCommands = {}
state.formationQueryActive = nil
end

local function parseBridgeDetailPayload(payload)
Expand Down Expand Up @@ -3087,6 +3315,75 @@ function Comm.HandleAddonMessage(prefix, message, distribution, sender)
return Comm.ApplyProfessionRecipeCraftPayload(payload)
end

if opcode == "FORMATIONS_BEGIN" then
state.connected = true
state.lastError = nil
return Comm.ApplyFormationsBeginPayload(payload)
end

if opcode == "FORMATIONS_ITEM" then
state.connected = true
state.lastError = nil
return Comm.ApplyFormationsItemPayload(payload)
end

if opcode == "FORMATIONS_END" then
state.connected = true
state.lastError = nil
return Comm.ApplyFormationsEndPayload(payload)
end

if opcode == "FORMATION_ACK" then
local scope, rest = splitOnce(payload or "", "~")
local target, rest2 = splitOnce(rest or "", "~")
local token, rest3 = splitOnce(rest2 or "", "~")
local successText, rest4 = splitOnce(rest3 or "", "~")
local failureText, encodedFormation = splitOnce(rest4 or "", "~")

scope = string.upper(trim(scope))
target = trim(urlDecodeField(target))
token = trim(token)
local success = tonumber(successText or "0") or 0
local failure = tonumber(failureText or "0") or 0
local formation = string.lower(trim(urlDecodeField(encodedFormation)))

state.connected = true
state.lastError = nil
debugPrint("ADDON:RX", "FORMATION_ACK", payload or "")

local pending = state.formationCommands[token]
state.formationCommands[token] = nil

local result = {
scope = scope,
target = target,
token = token,
success = success,
failure = failure,
formation = formation,
}

if pending and type(pending.callback) == "function" then
pending.callback(result)
end

if MultiBot.OnFormationCommandApplied then
MultiBot.OnFormationCommandApplied(result)
end

if success <= 0 then
systemMessage(L("formation.confirm.none"))
elseif failure > 0 then
systemMessage(string.format(
L("formation.confirm.partial"),
success,
failure
))
end

return true
end

if opcode == "RTI_ACK" then
state.connected = true
state.lastError = nil
Expand Down Expand Up @@ -3208,6 +3505,8 @@ function Comm.OnPlayerEnteringWorld()
state.questActive = {}
state.gameObjects = {}
state.gameObjectActive = {}
state.formationCommands = {}
state.formationQueryActive = nil
state.talentSpecs = {}
state.talentSpecActive = nil
state.inventoryActive = nil
Expand Down
20 changes: 20 additions & 0 deletions Locales/MultiBotAceLocale-deDE.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,26 @@ local deDEValues = {
["info.trainer.reason.NO_MATCHING_SPELL"] = "Dieser Zauber ist nicht mehr verfügbar.",
["info.trainer.reason.TOO_EXPENSIVE"] = "Der Bot hat nicht genug verfügbares Geld.",
["tips.outfits.equip"] = "Linksklick: Ausrüsten\nRechtsklick: Ersetzen",
-- Chatless formation query
["formation.query.title"] = "Aktuelle Formationen",
["formation.query.unavailable"] = "Bridge nicht verfügbar.",
["formation.query.timeout"] = "Zeitüberschreitung bei der Formationsabfrage.",
["formation.query.empty"] = "Kein steuerbarer Bot in der Gruppe oder im Schlachtzug gefunden.",
["formation.query.mixed"] = "Formationsstatus: gemischt",
["formation.query.common"] = "Aktuelle Formation: %s (%d Bot(s))",
["formation.confirm.none"] = "Die Formation wurde auf keinen Bot in der Gruppe oder im Schlachtzug angewendet.",
["formation.confirm.partial"] = "Formation auf %d Bot(s) angewendet; bei %d Bot(s) fehlgeschlagen.",
["formation.confirm.timeout"] = "Zeitüberschreitung beim Formationsbefehl.",
["formation.name.arrow"] = "Pfeil",
["formation.name.queue"] = "Kolonne",
["formation.name.near"] = "Nah",
["formation.name.melee"] = "Nahkampf",
["formation.name.line"] = "Linie",
["formation.name.circle"] = "Kreis",
["formation.name.chaos"] = "Chaos",
["formation.name.shield"] = "Schild",
["formation.name.far"] = "Entfernt",
["formation.name.unknown"] = "Unbekannt",
}

register("deDE", deDEValues)
Expand Down
20 changes: 20 additions & 0 deletions Locales/MultiBotAceLocale-enGB.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,26 @@ local enGBValues = {
["info.trainer.reason.NO_MATCHING_SPELL"] = "This spell is no longer available.",
["info.trainer.reason.TOO_EXPENSIVE"] = "The bot does not have enough available money.",
["tips.outfits.equip"] = "Left click: Equip\nRight click: Replace",
-- Chatless formation query
["formation.query.title"] = "Current formations",
["formation.query.unavailable"] = "Bridge unavailable.",
["formation.query.timeout"] = "Formation query timed out.",
["formation.query.empty"] = "No controllable bot found in the group or raid.",
["formation.query.mixed"] = "Formation state: mixed",
["formation.query.common"] = "Current formation: %s (%d bot(s))",
["formation.confirm.none"] = "Formation was not applied to any bot in the group or raid.",
["formation.confirm.partial"] = "Formation applied to %d bot(s); failed for %d bot(s).",
["formation.confirm.timeout"] = "Formation command timed out.",
["formation.name.arrow"] = "Arrow",
["formation.name.queue"] = "Queue",
["formation.name.near"] = "Near",
["formation.name.melee"] = "Melee",
["formation.name.line"] = "Line",
["formation.name.circle"] = "Circle",
["formation.name.chaos"] = "Chaos",
["formation.name.shield"] = "Shield",
["formation.name.far"] = "Far",
["formation.name.unknown"] = "Unknown",
}

register("enGB", enGBValues)
Expand Down
Loading
Loading