------------------------------------------------------------------------------------------------------------------- -- This include library allows use of specially-designed tables for tracking -- certain types of modes and state. -- -- Usage: include('Modes.lua') -- -- Construction syntax: -- -- 1) Create a new list of mode values. The first value will always be the default. -- MeleeMode = M{'Normal', 'Acc', 'Att'} -- Construct as a basic table, using braces. -- MeleeMode = M('Normal', 'Acc', 'Att') -- Pass in a list of strings, using parentheses. -- -- Optional: If constructed as a basic table, and the table contains a key value -- of 'description', that description will be saved for future reference. -- If a simple list of strings is passed in, no description will be set. -- -- MeleeMode = M{['description']='Melee Mode', 'Normal', 'Acc', 'Att'} -- -- -- 2) Create a boolean mode with a specified default value (note parentheses): -- UseLuzafRing = M(true) -- UseLuzafRing = M(false) -- -- Optional: A string may be provided that will be used as the mode description: -- UseLuzafRing = M(false, 'description') -- UseLuzafRing = M(true, 'description') -- -- -- 3) Create a string mode with a specified default value. Construct with a table: -- CombatWeapon = M{['description']='Melee Mode', ['string']='Dagger'} -- CombatWeapon = M{['description']='Melee Mode', ['string']=''} -- -- -- -- Public information fields (all are case-insensitive): -- -- 1) m.description -- Get a text description of the mode table, if it's been set. -- 2) m.current -- Gets the current mode text value. Booleans will return the strings "on" or "off". -- 3) m.value -- Gets the current mode value. Booleans will return the boolean values of true or false. -- 3) m.index -- Gets the current index value, or true/false for booleans. -- -- Typically use m.current when using the mode to create a set name, and m.value when -- testing the mode value in conditional rules. -- -- Public class functions: -- -- 1) m:describe(str) -- Sets the description for the mode table to the provided string value. -- 2) m:options(options) -- Redefine the options for a list mode table. Cannot be used on a boolean table. -- -- -- Public mode manipulation functions: -- -- 1) m:cycle() -- Cycles through the list going forwards. Acts as a toggle on boolean mode vars. -- 2) m:cycleback() -- Cycles through the list going backwards. Acts as a toggle on boolean mode vars. -- 3) m:toggle() -- Toggles a boolean Mode between true and false. -- 4) m:set(n) -- Sets the current mode value to n. -- Note: If m is boolean, n can be boolean true/false, or string of on/off/true/false. -- Note: If m is boolean and no n is given, this forces m to true. -- 5) m:unset() -- Sets a boolean mode var to false. -- 6) m:reset() -- Returns the mode var to its default state. -- 7) m:default() -- Same as reset() -- -- All public functions return the current value after completion. -- -- -- Example usage: -- -- sets.MeleeMode.Normal = {} -- sets.MeleeMode.Acc = {} -- sets.MeleeMode.Att = {} -- -- MeleeMode = M{'Normal', 'Acc', 'Att', ['description']='Melee Mode'} -- MeleeMode:cycle() -- equip(sets.engaged[MeleeMode.current]) -- MeleeMode:options('Normal', 'LowAcc', 'HighAcc') -- >> Changes the option list, but the description stays 'Melee Mode' -- -- -- sets.LuzafRing.on = {ring2="Luzaf's Ring"} -- sets.LuzafRing.off = {} -- -- UseLuzafRing = M(false) -- UseLuzafRing:toggle() -- equip(sets.precast['Phantom Roll'], sets.LuzafRing[UseLuzafRing.value]) ------------------------------------------------------------------------------------------------------------------- _meta = _meta or {} _meta.M = {} _meta.M.__class = 'mode' _meta.M.__methods = {} -- Default constructor for mode tables -- M{'a', 'b', etc, ['description']='a'} -- defines a mode list, description 'a' -- M('a', 'b', etc) -- defines a mode list, no description -- M('a') -- defines a mode list, default 'a' -- M{['description']='a'} -- defines a mode list, default 'Normal', description 'a' -- M{} -- defines a mode list, default 'Normal', no description -- M(false) -- defines a mode boolean, default false, no description -- M(true) -- defines a mode boolean, default true, no description -- M(false, 'a') -- defines a mode boolean, default false, description 'a' -- M(true, 'a') -- defines a mode boolean, default true, description 'a' -- M() -- defines a mode boolean, default false, no description function M(t, ...) local m = {} m._track = {} m._track._class = 'mode' -- If we're passed a list of strings (that is, the first element is a string), -- convert them to a table local args = {...} if type(t) == 'string' then t = {[1] = t} for ind, val in ipairs(args) do t[ind+1] = val end end -- Construct the table that we'll be adding the metadata to -- If we have a table of values, it's either a list or a string if type(t) == 'table' then -- Save the description, if provided if t['description'] then m._track._description = t['description'] end -- If we were given an explicit 'string' field, construct a string mode class. if t.string and type(t.string) == 'string' then m._track._type = 'string' m._track._count = 1 m._track._default = 'defaultstring' if t.string then m['string'] = t.string m['defaultstring'] = t.string end -- Otherwise put together a standard list mode class. else m._track._type = 'list' m._track._invert = {} m._track._count = 0 m._track._default = 1 -- Only copy numerically indexed values for ind, val in ipairs(t) do m[ind] = val m._track._invert[val] = ind m._track._count = ind end if m._track._count == 0 then m[1] = 'Normal' m._track._invert['Normal'] = 1 m._track._count = 1 end end -- If the first argument is a bool, construct a boolean mode class. elseif type(t) == 'boolean' or t == nil then m._track._type = 'boolean' m._track._count = 2 m._track._default = t or false m._track._description = args[1] -- Text lookups for bool values m[true] = 'on' m[false] = 'off' else -- Construction failure error("Unable to construct a mode table with the provided parameters.", 2) end -- Initialize current value to the default. m._track._current = m._track._default return setmetatable(m, _meta.M) end -------------------------------------------------------------------------- -- Metamethods -- Functions that will be used as metamethods for the class -------------------------------------------------------------------------- -- Handler for __index when trying to access the current mode value. -- Handles indexing 'current' or 'value' keys. _meta.M.__index = function(m, k) if type(k) == 'string' then local lk = k:lower() if lk == 'current' then return m[m._track._current] elseif lk == 'value' then if m._track._type == 'boolean' then return m._track._current else return m[m._track._current] end elseif lk == 'has_value' then return _meta.M.__methods.f_has_value(m) elseif lk == 'default' then if m._track._type == 'boolean' then return m._track._default else return m[m._track._default] end elseif lk == 'description' then return m._track._description elseif lk == 'index' then return m._track._current elseif m._track[lk] then return m._track[lk] elseif m._track['_'..lk] then return m._track['_'..lk] else return _meta.M.__methods[lk] end end end -- Tostring handler for printing out the table and its current state. _meta.M.__tostring = function(m) local res = '' if m._track._description then res = res .. m._track._description .. ': ' end if m._track._type == 'list' then res = res .. '{' for k,v in ipairs(m) do res = res..tostring(v) if m[k+1] ~= nil then res = res..', ' end end res = res..'}' elseif m._track._type == 'string' then res = res .. 'String' else res = res .. 'Boolean' end res = res .. ' ('..tostring(m.Current).. ')' -- Debug addition --res = res .. ' [' .. m._track._type .. '/' .. tostring(m._track._current) .. ']' return res end -- Length handler for the # value lookup. _meta.M.__len = function(m) return m._track._count end -------------------------------------------------------------------------- -- Public methods -- Functions that can be used as public methods for manipulating the class. -------------------------------------------------------------------------- -- Function to set the table's description. _meta.M.__methods['describe'] = function(m, str) if type(str) == 'string' then m._track._description = str else error("Invalid argument type: " .. type(str), 2) end end -- Function to change the list of options available. -- Leaves the description intact. -- Cannot be used on boolean classes. _meta.M.__methods['options'] = function(m, ...) if m._track._type ~= 'list' then error("Can only revise the options list for a list mode class.", 2) end local options = {...} -- Always include a default option if nothing else is given. if #options == 0 then options = {'Normal'} end -- Zero-out existing values and clear the tracked inverted list -- and member count. for key,val in ipairs(m) do m[key] = nil end m._track._invert = {} m._track._count = 0 -- Insert in new data. for key,val in ipairs(options) do m[key] = val m._track._invert[val] = key m._track._count = key end m._track._current = m._track._default end -- Function to test whether the list table contains the specified value. _meta.M.__methods['contains'] = function(m, str) if m._track._invert then if type(str) == 'string' then return (m._track._invert[str] ~= nil) else error("Invalid argument type: " .. type(str), 2) end else error("Cannot test for containment on a " .. m._track._type .. " mode class.", 2) end end -------------------------------------------------------------------------- -- Public methods -- Functions that will be used as public methods for manipulating state. -------------------------------------------------------------------------- -- Cycle forwards through the list _meta.M.__methods['cycle'] = function(m) if m._track._type == 'list' then m._track._current = (m._track._current % m._track._count) + 1 elseif m._track._type == 'boolean' then m:toggle() end return m.Current end -- Cycle backwards through the list _meta.M.__methods['cycleback'] = function(m) if m._track._type == 'list' then m._track._current = m._track._current - 1 if m._track._current < 1 then m._track._current = m._track._count end elseif m._track._type == 'boolean' then m:toggle() end return m.Current end -- Toggle a boolean value _meta.M.__methods['toggle'] = function(m) if m._track._type == 'boolean' then m._track._current = not m._track._current else error("Can only toggle a boolean mode.", 2) end return m.Current end -- Set the current value _meta.M.__methods['set'] = function(m, val) if m._track._type == 'boolean' then if val == nil then m._track._current = true elseif type(val) == 'boolean' then m._track._current = val elseif type(val) == 'string' then val = val:lower() if val == 'on' or val == 'true' then m._track._current = true elseif val == 'off' or val == 'false' then m._track._current = false else error("Unrecognized value: "..tostring(val), 2) end else error("Unrecognized value type: "..type(val), 2) end elseif m._track._type == 'list' then if not val then error("List variable cannot be set to nil.", 2) end if m._track._invert[val] then m._track._current = m._track._invert[val] else local found = false for v, ind in pairs(m._track._invert) do if val:lower() == v:lower() then m._track._current = ind found = true break end end if not found then error("Unknown mode value: " .. tostring(val), 2) end end elseif m._track._type == 'string' then if type(val) == 'string' then m._track._current = 'string' m.string = val else error("Unrecognized value type: "..type(val), 2) end end return m.Current end -- Forces a boolean mode to false, or a string to an empty string. _meta.M.__methods['unset'] = function(m) if m._track._type == 'boolean' then m._track._current = false elseif m._track._type == 'string' then m._track._current = 'string' m.string = '' else error("Cannot unset a list mode class.", 2) end return m.Current end -- Reset to the default value _meta.M.__methods['reset'] = function(m) m._track._current = m._track._default return m.Current end -- Check to be sure that the mode var has a valid (for its type) value. -- String vars must be non-empty. -- List vars must not be empty strings, or the word 'none'. -- Boolean are always considered valid (can only be true or false). _meta.M.__methods['f_has_value'] = function(m) local cur = m.value if m._track._type == 'string' then if cur and cur ~= '' then return true else return false end elseif m._track._type == 'boolean' then return true else if not cur or cur == '' or cur:lower() == 'none' then return false else return true end end end