You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
516 lines
16 KiB
516 lines
16 KiB
local ADDON_NAME, Internal = ...
|
|
local External = _G[ADDON_NAME]
|
|
|
|
local function tMap(tbl, func)
|
|
local result = {}
|
|
for k,v in pairs(tbl) do
|
|
result[k] = func(k, v, tbl)
|
|
end
|
|
return result
|
|
end
|
|
|
|
-- Base Mixin for State
|
|
local StateMixin = {}
|
|
function StateMixin:Init(id)
|
|
self.id = id;
|
|
end
|
|
function StateMixin:GetID()
|
|
return self.id;
|
|
end
|
|
function StateMixin:GetDisplayName(supportsCallback)
|
|
|
|
end
|
|
function StateMixin:GetUniqueKey()
|
|
-- Return a unique key for the states table so advanced scripts can access by key instead of by index
|
|
-- built in states use the [StateProvider id]:[State id] for example quest:63819
|
|
end
|
|
function StateMixin:SetCharacter(character)
|
|
self.character = character;
|
|
end
|
|
function StateMixin:GetCharacter()
|
|
if self.character then
|
|
return self.character
|
|
elseif self.driver then
|
|
return self.driver:GetCharacter()
|
|
else
|
|
return Internal.GetPlayer()
|
|
end
|
|
end
|
|
function StateMixin:SetDriver(driver)
|
|
self.driver = driver
|
|
end
|
|
-- Override this to register events to trigger updating completed and text
|
|
-- target might be a state driver or a ui element
|
|
-- isPlayer == true: Register events for updating the character that is online
|
|
-- isPlayer == true: Register events for updating for characters not online
|
|
-- isPlayer == nil: Register events for updating anyone
|
|
function StateMixin:RegisterEventsFor(target, isPlayer)
|
|
-- target:RegisterEvents("PLAYER_ENTERING_WORLD", "MY_CUSTOM_EVENT")
|
|
end
|
|
External.StateMixin = StateMixin
|
|
|
|
-- Base Mixin for State Providers
|
|
local StateProviderMixin = {}
|
|
function StateProviderMixin:Init(id, name, mixin)
|
|
self.id = id
|
|
self.name = name
|
|
self.mixin = mixin
|
|
end
|
|
function StateProviderMixin:GetID()
|
|
return self.id
|
|
end
|
|
function StateProviderMixin:GetName()
|
|
return self.name
|
|
end
|
|
function StateProviderMixin:RequiresID()
|
|
return true
|
|
end
|
|
-- Returns the title and optional description used for the config panel when adding a new state
|
|
function StateProviderMixin:GetAddTitle()
|
|
return string.format(BTWTODO_ADD_ITEM, self:GetName())
|
|
end
|
|
function StateProviderMixin:Acquire(...)
|
|
return CreateAndInitFromMixin(self.mixin, ...)
|
|
end
|
|
function StateProviderMixin:Supported(...)
|
|
return true
|
|
end
|
|
-- Returns data describing the possible basic functions
|
|
function StateProviderMixin:GetFunctions()
|
|
return {}
|
|
end
|
|
-- Returns the default functions for completed and text functions used for basic options
|
|
function StateProviderMixin:GetDefaults()
|
|
return nil, nil
|
|
end
|
|
function StateProviderMixin:ParseInput(input)
|
|
-- return true plus one or more values that can be passed to Aquire based on input
|
|
end
|
|
function StateProviderMixin:FillAutoComplete(tbl, text, offset, length)
|
|
-- Add items to tbl that are filted by text for auto completing adding an item, values can be passed to ParseInput
|
|
end
|
|
External.StateProviderMixin = StateProviderMixin
|
|
function External.CreateBasicStateProvider(id, name, mixin)
|
|
assert(type(id) == "string", "Usage: CreateBasicStateProvider(id, name, mixin): expected id to be string")
|
|
assert(type(name) == "string", "Usage: CreateBasicStateProvider(id, name, mixin): expected name to be string")
|
|
assert(type(mixin) == "table", "Usage: CreateBasicStateProvider(id, name, mixin): expected mixin to be table")
|
|
|
|
return CreateAndInitFromMixin(StateProviderMixin, id, name, mixin)
|
|
end
|
|
|
|
local stateProviders = {}
|
|
function External.RegisterStateProvider(provider)
|
|
stateProviders[provider:GetID()] = provider
|
|
Internal.TriggerEvent("REGISTER_STATE_PROVIDER")
|
|
end
|
|
local internalStateProviders = {}
|
|
function Internal.RegisterStateProvider(provider)
|
|
internalStateProviders[provider:GetID()] = provider
|
|
end
|
|
function Internal.GetStateProvider(provider)
|
|
assert(type(provider) == "string", "Usage: GetStateProvider(provider): expected provider to be string")
|
|
return internalStateProviders[provider] or stateProviders[provider]
|
|
end
|
|
function Internal.CreateState(provider, id, ...)
|
|
assert(type(provider) == "string", "Usage: CreateState(provider, id, ...): expected provider to be string")
|
|
|
|
local state
|
|
if internalStateProviders[provider] then
|
|
state = internalStateProviders[provider]:Acquire(id, ...)
|
|
elseif stateProviders[provider] then
|
|
state = stateProviders[provider]:Acquire(id, ...)
|
|
else
|
|
error("Usage: CreateState(provider, id, ...): provider " .. tostring(provider) .. " has not been registered")
|
|
end
|
|
|
|
return state
|
|
end
|
|
function Internal.IterateStateProviders()
|
|
local tbl = Mixin({}, stateProviders, internalStateProviders)
|
|
return next, tbl, nil
|
|
end
|
|
|
|
local CustomStateFunctions = {}
|
|
function Internal.RegisterCustomStateFunction(name, callback)
|
|
if type(name) ~= "string" then
|
|
error("Usage: RegisterCustomStateFunction(name, callback): name must be a string")
|
|
end
|
|
if CustomStateFunctions[name] ~= nil then
|
|
error("Usage: RegisterCustomStateFunction(name, callback): function with name \"" .. name .. "\" already registered")
|
|
end
|
|
|
|
CustomStateFunctions[name] = callback
|
|
end
|
|
local Colors = {
|
|
COMPLETE = CreateColor(0,1,0,1),
|
|
STALLED = CreateColor(1,1,0,1),
|
|
STARTED = ARTIFACT_GOLD_COLOR,
|
|
|
|
COMMON = COMMON_GRAY_COLOR,
|
|
UNCOMMON = UNCOMMON_GREEN_COLOR,
|
|
RARE = RARE_BLUE_COLOR,
|
|
EPIC = EPIC_PURPLE_COLOR,
|
|
LEGENDARY = LEGENDARY_ORANGE_COLOR,
|
|
ARTIFACT = ARTIFACT_GOLD_COLOR,
|
|
HEIRLOOM = HEIRLOOM_BLUE_COLOR,
|
|
WOWTOKEN = HEIRLOOM_BLUE_COLOR,
|
|
}
|
|
local Images = {
|
|
PADDING = [[|T982414:0|t]],
|
|
COMPLETE = "|A:achievementcompare-GreenCheckmark:0:0|a",
|
|
STALLED = "|A:achievementcompare-YellowCheckmark:0:0|a",
|
|
QUEST_PICKUP = "|A:QuestNormal:0:0|a",
|
|
QUEST_TURN_IN = "|A:QuestTurnin:0:0|a",
|
|
}
|
|
Internal.Images = Images
|
|
local EnvironmentMixin = {
|
|
print = print,
|
|
format = format,
|
|
ipairs = ipairs,
|
|
concat = table.concat,
|
|
select = select,
|
|
math = math,
|
|
tCount = function (tbl, func, from, to, every, ...)
|
|
from = from or 1
|
|
to = to or #tbl
|
|
every = every or 1
|
|
|
|
local result = 0
|
|
for i=from,to,every do
|
|
local item = tbl[i]
|
|
if item[func](item, ...) then
|
|
result = result + 1
|
|
end
|
|
end
|
|
return result
|
|
end,
|
|
tFirst = function (tbl, func, from, to, every, ...)
|
|
from = from or 1
|
|
to = to or #tbl
|
|
every = every or 1
|
|
|
|
for i=from,to,every do
|
|
local item = tbl[i]
|
|
if item[func](item, ...) then
|
|
return true, item
|
|
end
|
|
end
|
|
return false
|
|
end,
|
|
Custom = CustomStateFunctions,
|
|
Colors = Colors,
|
|
Images = Images,
|
|
table = table,
|
|
tFilter = tFilter,
|
|
tInvert = tInvert,
|
|
tMap = tMap,
|
|
|
|
GetMoneyString = GetMoneyString,
|
|
SecondsToTime = SecondsToTime,
|
|
|
|
IsAltKeyDown = IsAltKeyDown,
|
|
IsShiftKeyDown = IsShiftKeyDown,
|
|
IsControlKeyDown = IsControlKeyDown,
|
|
IsModifierKeyDown = IsModifierKeyDown,
|
|
IsLeftShiftKeyDown = IsLeftShiftKeyDown,
|
|
IsRightShiftKeyDown = IsRightShiftKeyDown,
|
|
}
|
|
local function CreateStateDriverFunction(driver, type, source, required, args)
|
|
if not required and not source then
|
|
return
|
|
end
|
|
|
|
local func, err = loadstring('local self, character, states' .. (args ~= nil and (', ' .. args) or '') .. ' = ...;' .. source, '[' .. driver:GetName() .. ':' .. type .. ']')
|
|
if not func then
|
|
return false, err
|
|
end
|
|
|
|
setfenv(func, CreateFromMixins(EnvironmentMixin))
|
|
|
|
return func
|
|
end
|
|
Internal.CreateStateDriverFunction = CreateStateDriverFunction
|
|
|
|
-- {3, "IsWeeklyCapped", 1, 2.3, "test"}
|
|
local function GenerateFunctionCall(tbl)
|
|
local values = {}
|
|
for i=3,#tbl do
|
|
local value = tbl[i]
|
|
if type(value) == "string" then
|
|
value = string.format("%q", value)
|
|
end
|
|
values[i] = value
|
|
end
|
|
return "states[" .. tbl[1] .. "]:" .. tbl[2] .. "(" .. table.concat(values, ", ") .. ")"
|
|
end
|
|
local function GenerateTextFunctionCalls(tbl)
|
|
local merger = "strjoin"
|
|
local arg = ", "
|
|
local index = 1
|
|
|
|
if type(tbl[index]) == "string" then
|
|
merger = tbl[index]
|
|
arg = tbl[index+1]
|
|
|
|
index = index + 2
|
|
end
|
|
|
|
local values = {}
|
|
for i=index,#tbl do
|
|
local value = tbl[i]
|
|
if type(value[1]) == "number" then
|
|
value = GenerateFunctionCall(value)
|
|
else
|
|
value = GenerateTextFunctionCalls(value)
|
|
end
|
|
values[#values+1] = value
|
|
end
|
|
|
|
return format("%s(%q, %s)", merger, arg, table.concat(values, ", "))
|
|
end
|
|
--[[
|
|
{
|
|
"and",
|
|
{1, "IsCapped"},
|
|
{2, "IsCapped"},
|
|
{
|
|
"or",
|
|
{3, "IsWeeklyCapped"},
|
|
{3, "IsCapped"},
|
|
}
|
|
}
|
|
]]
|
|
local function GenerateCompletedFunctionCalls(tbl)
|
|
local merger = "and"
|
|
local index = 1
|
|
|
|
if type(tbl[index]) == "string" then
|
|
merger = tbl[index]
|
|
index = index + 1
|
|
end
|
|
|
|
local values = {}
|
|
for i=index,#tbl do
|
|
local value = tbl[i]
|
|
if type(value[1]) == "number" then
|
|
value = GenerateFunctionCall(value)
|
|
else
|
|
value = "(" .. GenerateCompletedFunctionCalls(value) .. ")"
|
|
end
|
|
values[#values+1] = value
|
|
end
|
|
|
|
return table.concat(values, " " .. merger .. " ")
|
|
end
|
|
function Internal.GenerateFunctionFromTable(mode, tbl)
|
|
if mode == "completed" then
|
|
return "return " .. GenerateCompletedFunctionCalls(tbl)
|
|
else
|
|
return "return " .. GenerateTextFunctionCalls(tbl)
|
|
end
|
|
end
|
|
|
|
-- print(Internal.GenerateFunctionFromTable("completed", {
|
|
-- "and",
|
|
-- {1, "IsCapped"},
|
|
-- {2, "IsCapped"},
|
|
-- {
|
|
-- "or",
|
|
-- {3, "IsWeeklyCapped"},
|
|
-- {3, "IsCapped"},
|
|
-- }
|
|
-- }))
|
|
-- print(Internal.GenerateFunctionFromTable("text", {
|
|
-- "strjoin", ", ",
|
|
-- {1, "GetLevel", 1},
|
|
-- }))
|
|
|
|
local StateDriverMixin = CreateFromMixins(Internal.ScriptHandlerMixin)
|
|
function StateDriverMixin:Init(id, name, states, completed, text, click, tooltip)
|
|
Internal.ScriptHandlerMixin.OnLoad(self)
|
|
self:RegisterSupportedScriptHandlers("OnEvent")
|
|
|
|
self.id = id
|
|
self.name = name
|
|
self.states = states
|
|
|
|
for _,state in ipairs(states) do
|
|
state:SetDriver(self)
|
|
end
|
|
|
|
local err
|
|
self.completed, err = CreateStateDriverFunction(self, "Completed", completed, true)
|
|
if not self.completed then
|
|
error(err)
|
|
end
|
|
|
|
self.text, err = CreateStateDriverFunction(self, "Text", text, true, 'L')
|
|
if not self.text then
|
|
error(err)
|
|
end
|
|
|
|
self.click, err = CreateStateDriverFunction(self, "Click", click, false, 'button')
|
|
if self.click == false then
|
|
error(err)
|
|
end
|
|
|
|
self.tooltip, err = CreateStateDriverFunction(self, "Tooltip", tooltip, false, 'L, tooltip')
|
|
if self.tooltip == false then
|
|
error(err)
|
|
end
|
|
end
|
|
function StateDriverMixin:Deinit()
|
|
Internal.UnregisterEventsFor(self)
|
|
end
|
|
function StateDriverMixin:GetID()
|
|
return self.id
|
|
end
|
|
function StateDriverMixin:GetName()
|
|
return self.name
|
|
end
|
|
function StateDriverMixin:SetCharacter(character)
|
|
self.character = character
|
|
end
|
|
function StateDriverMixin:GetCharacter()
|
|
return self.character
|
|
end
|
|
function StateDriverMixin:SetFlaggedCompleted(value)
|
|
value = value and true or false
|
|
|
|
local character = self:GetCharacter()
|
|
assert(character ~= nil, "Call driver:SetCharacter before calling SetFlaggedCompleted")
|
|
|
|
character:SetData("todoFlaggedCompleted", self:GetID(), value and true or nil)
|
|
|
|
External.TriggerEvent("TODO_FLAGGED_COMPLETED", self:GetID(), value)
|
|
self:OnEvent("TODO_FLAGGED_COMPLETED", value) -- We dont actually register TODO_FLAGGED_COMPLETED so we will just trigger it manually here
|
|
end
|
|
function StateDriverMixin:IsFlaggedCompleted() -- Has the todo been clicked on
|
|
local character = self:GetCharacter()
|
|
assert(character ~= nil, "Call driver:SetCharacter before calling IsFlaggedCompleted")
|
|
return character:GetData("todoFlaggedCompleted", self:GetID())
|
|
end
|
|
function StateDriverMixin:IsCompleted()
|
|
local success, result = xpcall(function ()
|
|
return self.completed({
|
|
GetName = function ()
|
|
return self:GetName()
|
|
end,
|
|
IsFlaggedCompleted = function ()
|
|
return self:IsFlaggedCompleted()
|
|
end,
|
|
}, self:GetCharacter(), self.states) == true
|
|
end, geterrorhandler())
|
|
if success then
|
|
return result
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
local SUCCESS_TEXT_WRAPPER = "|cff00ff00%s|r"
|
|
function StateDriverMixin:GetText()
|
|
local success, result = xpcall(function ()
|
|
local result = self.text({
|
|
GetName = function ()
|
|
return self:GetName()
|
|
end,
|
|
IsCompleted = function ()
|
|
return self:IsCompleted()
|
|
end,
|
|
IsFlaggedCompleted = function ()
|
|
return self:IsFlaggedCompleted()
|
|
end,
|
|
}, self:GetCharacter(), self.states, Internal.L)
|
|
|
|
if self:IsCompleted() then
|
|
return string.format(SUCCESS_TEXT_WRAPPER, result or "")
|
|
else
|
|
return result or ""
|
|
end
|
|
end, geterrorhandler())
|
|
if success then
|
|
return result
|
|
else
|
|
return ""
|
|
end
|
|
end
|
|
function StateDriverMixin:SupportsTooltip()
|
|
return self.tooltip ~= nil
|
|
end
|
|
function StateDriverMixin:UpdateTooltip(tooltip)
|
|
local success, result = xpcall(function ()
|
|
return self.tooltip({
|
|
GetName = function ()
|
|
return self:GetName()
|
|
end,
|
|
IsCompleted = function ()
|
|
return self:IsCompleted()
|
|
end,
|
|
IsFlaggedCompleted = function ()
|
|
return self:IsFlaggedCompleted()
|
|
end,
|
|
}, self:GetCharacter(), self.states, Internal.L, tooltip)
|
|
end, geterrorhandler())
|
|
if success then
|
|
return result
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
function StateDriverMixin:SupportsClick()
|
|
return self.click ~= nil
|
|
end
|
|
function StateDriverMixin:Click(button)
|
|
xpcall(function ()
|
|
self.click({
|
|
GetName = function ()
|
|
return self:GetName()
|
|
end,
|
|
IsCompleted = function ()
|
|
return self:IsCompleted()
|
|
end,
|
|
SetFlaggedCompleted = function (_, ...)
|
|
return self:SetFlaggedCompleted(...)
|
|
end,
|
|
IsFlaggedCompleted = function ()
|
|
return self:IsFlaggedCompleted()
|
|
end,
|
|
}, self:GetCharacter(), self.states, button)
|
|
end, geterrorhandler())
|
|
end
|
|
function StateDriverMixin:OnEvent(...)
|
|
self:RunScript("OnEvent", ...)
|
|
end
|
|
function StateDriverMixin:RegisterEvents(...)
|
|
for i=1,select('#', ...) do
|
|
Internal.RegisterEvent(self, (select(i, ...)))
|
|
end
|
|
end
|
|
function StateDriverMixin:ClearEvents()
|
|
Internal.UnregisterEventsFor(self)
|
|
end
|
|
function StateDriverMixin:RegisterEventsFor(target, isPlayer)
|
|
for _,state in ipairs(self.states) do
|
|
state:RegisterEventsFor(target, isPlayer)
|
|
end
|
|
end
|
|
|
|
function Internal.CreateStateDriver(id, name, states, completed, text, click, tooltip)
|
|
local buildStates = {}
|
|
for index, source in ipairs(states) do
|
|
local state
|
|
if source.values then
|
|
state = Internal.CreateState(source.type, source.id, unpack(source.values))
|
|
else
|
|
state = Internal.CreateState(source.type, source.id)
|
|
end
|
|
local key = state:GetUniqueKey()
|
|
|
|
buildStates[index] = state
|
|
if key and not buildStates[key] then
|
|
buildStates[key] = state
|
|
end
|
|
end
|
|
|
|
return CreateAndInitFromMixin(StateDriverMixin, id, name, buildStates, completed, text, click, tooltip), buildStates
|
|
end
|
|
|