Skip to content

Commit 61d0b2e

Browse files
committed
feat: validate Raidus pool and restore player slot
1 parent c54277d commit 61d0b2e

17 files changed

Lines changed: 1327 additions & 76 deletions

Core/MultiBot.lua

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,27 @@ function MultiBot.SetGlobalBotEntry(name, value)
287287
return value
288288
end
289289

290+
function MultiBot.RemoveGlobalBotEntry(name)
291+
if type(name) ~= "string" or name == "" then
292+
return false
293+
end
294+
295+
local removed = false
296+
local store = MultiBot.Store and MultiBot.Store.GetBotsStore and MultiBot.Store.GetBotsStore()
297+
if store and store[name] ~= nil then
298+
store[name] = nil
299+
removed = true
300+
end
301+
302+
local legacyStore = getLegacyGlobalBotStore()
303+
if legacyStore[name] ~= nil and isGlobalBotRosterEntry(legacyStore[name]) then
304+
legacyStore[name] = nil
305+
removed = true
306+
end
307+
308+
return removed
309+
end
310+
290311
function MultiBot.ClearGlobalBotStore()
291312
local store = MultiBot.Store and MultiBot.Store.EnsureBotsStore and MultiBot.Store.EnsureBotsStore()
292313
if not store then
@@ -2509,6 +2530,10 @@ function MultiBot.BindUnitToggleHandlers(button, options)
25092530
return
25102531
end
25112532

2533+
if MultiBot.allowLegacyChatFallback ~= true then
2534+
return
2535+
end
2536+
25122537
SendChatMessage(".playerbot bot remove " .. unitButton.name, "SAY")
25132538
if MultiBot.SetBridgeBotOnlineState and unitButton.bridge ~= nil then
25142539
MultiBot.SetBridgeBotOnlineState(unitButton, false)
@@ -2565,6 +2590,10 @@ function MultiBot.BindUnitToggleHandlers(button, options)
25652590
return
25662591
end
25672592

2593+
if MultiBot.allowLegacyChatFallback ~= true then
2594+
return
2595+
end
2596+
25682597
SendChatMessage(".playerbot bot add " .. unitButton.name, "SAY")
25692598
unitButton.setEnable()
25702599

Core/MultiBotComm.lua

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ local GROUP_ROLL_MAX_ITEM_LINK_LENGTH = 160
106106
local STATE_TIMEOUT_SECONDS = 5.0
107107
local STATES_TIMEOUT_SECONDS = 15.0
108108
local STRATEGY_MUTATION_TIMEOUT_SECONDS = 5.0
109+
Comm._GROUP_ORDER_TIMEOUT_SECONDS = 5.0
110+
Comm._GROUP_ORDER_MAX_ACTIVE = 8
109111
local SELF_STRATEGY_MUTATION_TIMEOUT_SECONDS = 10.0
110112
local SELF_ACTION_TIMEOUT_SECONDS = 10.0
111113
local STRATEGY_MUTATION_MAX_ACTIVE = 32
@@ -1850,6 +1852,156 @@ local function validateStrategyMutationChanges(changes)
18501852
return table.concat(normalized, ",")
18511853
end
18521854

1855+
-- MB_FOLLOW_STAY_ORDER_V1_TX_BEGIN
1856+
function Comm._FinishGroupOrderCommand(token, result)
1857+
local state = ensureBridgeState()
1858+
state.groupOrderCommands = state.groupOrderCommands or {}
1859+
1860+
local pending = state.groupOrderCommands[token]
1861+
if type(pending) ~= "table" then
1862+
return false
1863+
end
1864+
1865+
state.groupOrderCommands[token] = nil
1866+
result = type(result) == "table" and result or {}
1867+
result.token = token
1868+
result.order = result.order or pending.order
1869+
1870+
if type(pending.callback) == "function" then
1871+
pending.callback(result)
1872+
end
1873+
1874+
if MultiBot.OnGroupOrderApplied then
1875+
MultiBot.OnGroupOrderApplied(result.order, result)
1876+
end
1877+
1878+
return true
1879+
end
1880+
1881+
function Comm.RunGroupOrderCommand(order, callback)
1882+
local state = ensureBridgeState()
1883+
if not state.connected then
1884+
state.lastError = "GROUP_ORDER_NOT_CONNECTED"
1885+
return false
1886+
end
1887+
1888+
order = string.upper(trim(order or ""))
1889+
if order ~= "FOLLOW" and order ~= "STAY" then
1890+
state.lastError = "GROUP_ORDER_INVALID"
1891+
return false
1892+
end
1893+
1894+
state.groupOrderCommands = state.groupOrderCommands or {}
1895+
local active = 0
1896+
for _ in pairs(state.groupOrderCommands) do
1897+
active = active + 1
1898+
end
1899+
if active >= Comm._GROUP_ORDER_MAX_ACTIVE then
1900+
state.lastError = "GROUP_ORDER_BUSY"
1901+
return false
1902+
end
1903+
1904+
state.groupOrderSeq = (tonumber(state.groupOrderSeq) or 0) + 1
1905+
local token = tostring(math.floor(safeNow() * 1000)) .. "-group-order-" .. tostring(state.groupOrderSeq)
1906+
state.groupOrderCommands[token] = {
1907+
order = order,
1908+
callback = type(callback) == "function" and callback or nil,
1909+
startedAt = safeNow(),
1910+
}
1911+
1912+
if not Comm.Send("RUN", order .. "_ORDER~" .. token) then
1913+
state.groupOrderCommands[token] = nil
1914+
state.lastError = "GROUP_ORDER_SEND_FAILED"
1915+
return false
1916+
end
1917+
1918+
safeDelay(Comm._GROUP_ORDER_TIMEOUT_SECONDS, function()
1919+
local bridge = ensureBridgeState()
1920+
bridge.groupOrderCommands = bridge.groupOrderCommands or {}
1921+
if not bridge.groupOrderCommands[token] then
1922+
return
1923+
end
1924+
1925+
bridge.lastError = "GROUP_ORDER_TIMEOUT~" .. token
1926+
Comm._FinishGroupOrderCommand(token, {
1927+
status = "timeout",
1928+
matched = 0,
1929+
succeeded = 0,
1930+
failed = 0,
1931+
reason = "TIMEOUT",
1932+
})
1933+
end)
1934+
1935+
return token
1936+
end
1937+
-- MB_FOLLOW_STAY_ORDER_V1_TX_END
1938+
1939+
-- MB_ATTACK_ORDER_V1_TX_BEGIN
1940+
function Comm.RunAttackOrderCommand(audience, callback)
1941+
local state = ensureBridgeState()
1942+
if not state.connected then
1943+
state.lastError = "ATTACK_ORDER_NOT_CONNECTED"
1944+
return false
1945+
end
1946+
1947+
audience = string.upper(trim(audience or ""))
1948+
if audience ~= "ALL"
1949+
and audience ~= "TANK"
1950+
and audience ~= "HEALER"
1951+
and audience ~= "DPS"
1952+
and audience ~= "MELEE"
1953+
and audience ~= "RANGED" then
1954+
state.lastError = "ATTACK_ORDER_BAD_AUDIENCE"
1955+
return false
1956+
end
1957+
1958+
state.groupOrderCommands = state.groupOrderCommands or {}
1959+
local active = 0
1960+
for _ in pairs(state.groupOrderCommands) do
1961+
active = active + 1
1962+
end
1963+
if active >= Comm._GROUP_ORDER_MAX_ACTIVE then
1964+
state.lastError = "ATTACK_ORDER_BUSY"
1965+
return false
1966+
end
1967+
1968+
state.groupOrderSeq = (tonumber(state.groupOrderSeq) or 0) + 1
1969+
local token = tostring(math.floor(safeNow() * 1000)) .. "-attack-order-" .. tostring(state.groupOrderSeq)
1970+
state.groupOrderCommands[token] = {
1971+
order = "ATTACK",
1972+
audience = audience,
1973+
callback = type(callback) == "function" and callback or nil,
1974+
startedAt = safeNow(),
1975+
}
1976+
1977+
if not Comm.Send("RUN", "ATTACK_ORDER~" .. token .. "~" .. audience) then
1978+
state.groupOrderCommands[token] = nil
1979+
state.lastError = "ATTACK_ORDER_SEND_FAILED"
1980+
return false
1981+
end
1982+
1983+
safeDelay(Comm._GROUP_ORDER_TIMEOUT_SECONDS, function()
1984+
local bridge = ensureBridgeState()
1985+
bridge.groupOrderCommands = bridge.groupOrderCommands or {}
1986+
if not bridge.groupOrderCommands[token] then
1987+
return
1988+
end
1989+
1990+
bridge.lastError = "ATTACK_ORDER_TIMEOUT~" .. token
1991+
Comm._FinishGroupOrderCommand(token, {
1992+
status = "timeout",
1993+
audience = audience,
1994+
matched = 0,
1995+
succeeded = 0,
1996+
failed = 0,
1997+
reason = "TIMEOUT",
1998+
})
1999+
end)
2000+
2001+
return token
2002+
end
2003+
-- MB_ATTACK_ORDER_V1_TX_END
2004+
18532005
local function finishStrategyMutationCommand(token, result)
18542006
local state = ensureBridgeState()
18552007
local pending = state.strategyMutationCommands[token]
@@ -9764,6 +9916,146 @@ function Comm.HandleAddonMessage(prefix, message, distribution, sender)
97649916
return true
97659917
end
97669918

9919+
-- MB_FOLLOW_STAY_ORDER_V1_RX_BEGIN
9920+
if opcode == "FOLLOW_ORDER_ACK" or opcode == "STAY_ORDER_ACK" then
9921+
local fields = splitFields(payload or "")
9922+
if #fields ~= 5 then
9923+
state.lastError = "GROUP_ORDER_ACK_BAD_FIELD_COUNT"
9924+
return true
9925+
end
9926+
9927+
local token = fields[1]
9928+
local matched = tonumber(fields[2])
9929+
local succeeded = tonumber(fields[3])
9930+
local failed = tonumber(fields[4])
9931+
local reason = fields[5]
9932+
local order = opcode == "FOLLOW_ORDER_ACK" and "FOLLOW" or "STAY"
9933+
state.groupOrderCommands = state.groupOrderCommands or {}
9934+
local pending = state.groupOrderCommands[token]
9935+
9936+
local countsValid = matched and succeeded and failed
9937+
and matched >= 0 and matched <= 40 and math.floor(matched) == matched
9938+
and succeeded >= 0 and succeeded <= matched and math.floor(succeeded) == succeeded
9939+
and failed >= 0 and failed <= matched and math.floor(failed) == failed
9940+
and succeeded + failed == matched
9941+
9942+
if not isValidStateToken(token)
9943+
or not countsValid
9944+
or type(reason) ~= "string"
9945+
or reason == ""
9946+
or string.len(reason) > 32
9947+
or not string.match(reason, "^[A-Z0-9_]+$")
9948+
or type(pending) ~= "table"
9949+
or pending.order ~= order then
9950+
state.lastError = "GROUP_ORDER_ACK_INVALID"
9951+
return true
9952+
end
9953+
9954+
state.connected = true
9955+
state.lastError = reason == "OK" and nil or ("GROUP_ORDER_" .. reason)
9956+
debugPrint("ADDON:RX", opcode, payload or "")
9957+
9958+
local status = "failed"
9959+
if matched == 0 then
9960+
status = "empty"
9961+
elseif failed == 0 and succeeded == matched then
9962+
status = "ok"
9963+
elseif succeeded > 0 then
9964+
status = "partial"
9965+
end
9966+
9967+
Comm._FinishGroupOrderCommand(token, {
9968+
status = status,
9969+
matched = matched,
9970+
succeeded = succeeded,
9971+
failed = failed,
9972+
reason = reason,
9973+
})
9974+
return true
9975+
end
9976+
-- MB_FOLLOW_STAY_ORDER_V1_RX_END
9977+
9978+
-- MB_ATTACK_ORDER_V1_RX_BEGIN
9979+
if opcode == "ATTACK_ORDER_ACK" then
9980+
local fields = splitFields(payload or "")
9981+
if #fields ~= 6 then
9982+
state.lastError = "ATTACK_ORDER_ACK_BAD_FIELD_COUNT"
9983+
return true
9984+
end
9985+
9986+
local token = fields[1]
9987+
local audience = fields[2]
9988+
local matched = tonumber(fields[3])
9989+
local succeeded = tonumber(fields[4])
9990+
local failed = tonumber(fields[5])
9991+
local reason = fields[6]
9992+
state.groupOrderCommands = state.groupOrderCommands or {}
9993+
local pending = state.groupOrderCommands[token]
9994+
9995+
local audienceValid = audience == "ALL"
9996+
or audience == "TANK"
9997+
or audience == "HEALER"
9998+
or audience == "DPS"
9999+
or audience == "MELEE"
10000+
or audience == "RANGED"
10001+
10002+
local countsValid = matched and succeeded and failed
10003+
and matched >= 0 and matched <= 40 and math.floor(matched) == matched
10004+
and succeeded >= 0 and succeeded <= matched and math.floor(succeeded) == succeeded
10005+
and failed >= 0 and failed <= matched and math.floor(failed) == failed
10006+
and succeeded + failed == matched
10007+
10008+
local reasonValid = reason == "OK"
10009+
or reason == "BAD_AUDIENCE"
10010+
or reason == "RATE_LIMIT"
10011+
or reason == "REPLAY"
10012+
or reason == "NO_GROUP"
10013+
or reason == "NO_TARGET"
10014+
or reason == "NO_BOTS"
10015+
or reason == "BOT_LIMIT"
10016+
or reason == "PARTIAL"
10017+
or reason == "FAILED"
10018+
10019+
if not isValidStateToken(token)
10020+
or not audienceValid
10021+
or not countsValid
10022+
or not reasonValid
10023+
or type(pending) ~= "table"
10024+
or pending.order ~= "ATTACK"
10025+
or pending.audience ~= audience then
10026+
state.lastError = "ATTACK_ORDER_ACK_INVALID"
10027+
return true
10028+
end
10029+
10030+
state.connected = true
10031+
if reason == "OK" then
10032+
state.lastError = nil
10033+
else
10034+
state.lastError = "ATTACK_ORDER_" .. reason
10035+
end
10036+
debugPrint("ADDON:RX", opcode, payload or "")
10037+
10038+
local status = "failed"
10039+
if reason == "OK" and matched > 0 and failed == 0 and succeeded == matched then
10040+
status = "ok"
10041+
elseif reason == "NO_BOTS" and matched == 0 then
10042+
status = "empty"
10043+
elseif succeeded > 0 then
10044+
status = "partial"
10045+
end
10046+
10047+
Comm._FinishGroupOrderCommand(token, {
10048+
status = status,
10049+
audience = audience,
10050+
matched = matched,
10051+
succeeded = succeeded,
10052+
failed = failed,
10053+
reason = reason,
10054+
})
10055+
return true
10056+
end
10057+
-- MB_ATTACK_ORDER_V1_RX_END
10058+
976710059
if opcode == "RTI_ACK" then
976810060
state.connected = true
976910061
state.lastError = nil

0 commit comments

Comments
 (0)