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.
319 lines
10 KiB
319 lines
10 KiB
-- Base Class Helpers
|
|
-- Contains necessary logic for defining, creating and working with all Class structures in a consistent manner
|
|
local appName, app = ...;
|
|
|
|
-- Global locals
|
|
local type, pairs, tonumber, setmetatable, rawget, tinsert
|
|
= type, pairs, tonumber, setmetatable, rawget, tinsert;
|
|
|
|
-- App locals
|
|
local GetRelativeValue = app.GetRelativeValue;
|
|
|
|
-- Lib Helpers
|
|
local constructor = function(id, t, typeID)
|
|
if t then
|
|
if not t.g and t[1] then
|
|
return { g=t, [typeID]=id };
|
|
else
|
|
t[typeID] = id;
|
|
return t;
|
|
end
|
|
else
|
|
return {[typeID] = id};
|
|
end
|
|
end
|
|
local returnZero = function() return 0; end;
|
|
|
|
-- Provides a Unique Counter value for the Key referenced on each reference
|
|
local uniques = setmetatable({}, { __index = function(t, key) return 0; end });
|
|
local UniqueCounter = setmetatable({}, {
|
|
__index = function(t, key)
|
|
local count = uniques[key] + 1;
|
|
-- app.PrintDebug("UniqueCounter",key,count)
|
|
uniques[key] = count;
|
|
return count;
|
|
end
|
|
});
|
|
app.UniqueCounter = UniqueCounter;
|
|
|
|
-- Proper unique hash for a Class Object is not as simple as ID..Value, there are many situations where that does not provide adequate uniqueness
|
|
local function CreateHash(t)
|
|
local key = t.key or t.text;
|
|
if key then
|
|
local hash = key .. (t[key] or "NOKEY");
|
|
if key == "criteriaID" and t.achievementID then
|
|
hash = hash .. ":" .. t.achievementID;
|
|
elseif key == "itemID" and t.modItemID and t.modItemID ~= t.itemID then
|
|
hash = key .. t.modItemID;
|
|
elseif key == "creatureID" then
|
|
if t.encounterID then hash = hash .. ":" .. t.encounterID; end
|
|
local difficultyID = GetRelativeValue(t, "difficultyID");
|
|
if difficultyID then hash = hash .. "-" .. difficultyID; end
|
|
elseif key == "encounterID" then
|
|
if t.creatureID then hash = hash .. ":" .. t.creatureID; end
|
|
local difficultyID = GetRelativeValue(t, "difficultyID");
|
|
if difficultyID then hash = hash .. "-" .. difficultyID; end
|
|
if t.crs then
|
|
local numCrs = #t.crs;
|
|
if numCrs == 1 then
|
|
hash = hash .. t.crs[1];
|
|
elseif numCrs == 2 then
|
|
hash = hash .. t.crs[1] .. t.crs[2];
|
|
elseif numCrs > 2 then
|
|
hash = hash .. t.crs[1] .. t.crs[2] .. t.crs[3];
|
|
end
|
|
end
|
|
elseif key == "difficultyID" then
|
|
local instanceID = GetRelativeValue(t, "instanceID") or GetRelativeValue(t, "headerID");
|
|
if instanceID then hash = hash .. "-" .. instanceID; end
|
|
elseif key == "headerID" then
|
|
-- for custom headers, they may be used in conjunction with other bits of data that we don't want to merge together (because it makes no sense)
|
|
-- Separate if using Class requirements
|
|
if t.c then
|
|
for _,class in pairs(t.c) do
|
|
hash = hash .. "C" .. class;
|
|
end
|
|
end
|
|
-- Separate if using Faction/Race requirements
|
|
if t.r then
|
|
hash = "F" .. t.r .. hash;
|
|
elseif t.races then
|
|
for _,race in pairs(t.races) do
|
|
hash = hash .. "R" .. race;
|
|
end
|
|
end
|
|
elseif key == "spellID" and t.itemID then
|
|
-- Some recipes teach the same spell, so need to differentiate by their itemID as well
|
|
hash = hash .. ":" .. t.itemID;
|
|
end
|
|
if t.rank then
|
|
hash = hash .. "." .. t.rank;
|
|
-- app.PrintDebug("hash.rank",hash)
|
|
end
|
|
if t.nomerge then
|
|
hash = hash.."__"..UniqueCounter["Hash"];
|
|
end
|
|
t.hash = hash;
|
|
return hash;
|
|
end
|
|
end
|
|
app.CreateHash = CreateHash;
|
|
|
|
-- Represents default field evaluation logic for all Classes unless defined within the Class
|
|
local DefaultFields = {
|
|
-- Cloned groups will not directly have a parent, but they will instead have a sourceParent, so fill in with that instead
|
|
["parent"] = function(t)
|
|
return t.sourceParent;
|
|
end,
|
|
-- A semi-unique string value that identifies this object based on its key, or text if it doesn't have one.
|
|
["hash"] = function(t)
|
|
return CreateHash(t);
|
|
end,
|
|
-- Default text should be a valid link or name
|
|
["text"] = function(t)
|
|
return t.link or t.name;
|
|
end,
|
|
-- Whether or not something is repeatable.
|
|
["repeatable"] = function(t)
|
|
return t.isDaily or t.isWeekly or t.isMonthly or t.isYearly or t.isWorldQuest;
|
|
end,
|
|
["costProgress"] = returnZero,
|
|
["costTotal"] = returnZero,
|
|
["progress"] = returnZero,
|
|
["total"] = returnZero,
|
|
};
|
|
|
|
-- Creates a Base Object Table which will evaluate the provided set of 'fields' (each field value being a keyed function)
|
|
local classDefinitions, _cache = {};
|
|
local BaseObjectFields = function(fields, className)
|
|
if not className then
|
|
print("A Class Name must be declared when using BaseObjectFields");
|
|
end
|
|
local class = { __type = function() return className; end };
|
|
if not classDefinitions[className] then
|
|
classDefinitions[className] = class;
|
|
else
|
|
print("A Class has already been defined with that name!", className);
|
|
end
|
|
if fields then
|
|
for key,method in pairs(fields) do
|
|
class[key] = method;
|
|
end
|
|
end
|
|
|
|
-- Inject the default fields into the class
|
|
for key,method in pairs(DefaultFields) do
|
|
if not rawget(class, key) then
|
|
class[key] = method;
|
|
end
|
|
end
|
|
return {
|
|
__index = function(t, key)
|
|
_cache = rawget(class, key);
|
|
if _cache then return _cache(t); end
|
|
end
|
|
};
|
|
end
|
|
app.BaseObjectFields = BaseObjectFields;
|
|
app.BaseClass = BaseObjectFields(nil, "BaseClass");
|
|
|
|
app.CreateClass = function(className, classKey, fields, ...)
|
|
-- Validate arguments
|
|
if not className then
|
|
print("A Class Name must be declared when using CreateClass");
|
|
end
|
|
if not classKey then
|
|
print("A Class Key must be declared when using CreateClass");
|
|
end
|
|
if not fields then
|
|
print("Fields must be declared when using CreateClass");
|
|
end
|
|
|
|
-- Ensure that a key field exists!
|
|
if not fields.key then
|
|
fields.key = function() return classKey; end;
|
|
end
|
|
|
|
-- If this object supports collectibleAsCost, that means it needs a way to fallback to a version of itself without any cost evaluations should it detect that it doesn't use it anywhere.
|
|
if fields.collectibleAsCost then
|
|
local simpleclass = {};
|
|
for key,method in pairs(fields) do
|
|
simpleclass[key] = method;
|
|
end
|
|
simpleclass.collectibleAsCost = app.ReturnFalse;
|
|
simpleclass.collectedAsCost = nil;
|
|
local simplemeta = BaseObjectFields(simpleclass, "Simple" .. className);
|
|
fields.simplemeta = function(t) return simplemeta; end;
|
|
end
|
|
|
|
local args = { ... };
|
|
local total = #args;
|
|
if total > 0 then
|
|
local conditionals = {};
|
|
for i=1,total,3 do
|
|
local class = args[i + 1];
|
|
tinsert(conditionals, args[i + 2]);
|
|
if class then
|
|
for key,method in pairs(fields) do
|
|
if not rawget(class, key) then
|
|
class[key] = method;
|
|
end
|
|
end
|
|
if class.collectibleAsCost then
|
|
local simpleclass = {};
|
|
for key,method in pairs(class) do
|
|
simpleclass[key] = method;
|
|
end
|
|
simpleclass.collectibleAsCost = app.ReturnFalse;
|
|
simpleclass.collectedAsCost = nil;
|
|
local simplemeta = BaseObjectFields(simpleclass, "Simple" .. className .. args[i]);
|
|
class.simplemeta = function(t) return simplemeta; end;
|
|
end
|
|
tinsert(conditionals, BaseObjectFields(class, className .. args[i]));
|
|
else
|
|
tinsert(conditionals, {});
|
|
end
|
|
end
|
|
total = #conditionals;
|
|
fields.conditionals = conditionals;
|
|
local Class = BaseObjectFields(fields, className);
|
|
return function(id, t)
|
|
t = constructor(id, t, classKey);
|
|
for i=1,total,2 do
|
|
if conditionals[i](t) then
|
|
return setmetatable(t, conditionals[i + 1]);
|
|
end
|
|
end
|
|
return setmetatable(t, Class);
|
|
end, Class;
|
|
else
|
|
local Class = BaseObjectFields(fields, className);
|
|
return function(id, t)
|
|
return setmetatable(constructor(id, t, classKey), Class);
|
|
end, Class;
|
|
end
|
|
end
|
|
app.ExtendClass = function(baseClassName, className, classKey, fields, ...)
|
|
local baseClass = classDefinitions[baseClassName];
|
|
if baseClass then
|
|
if not fields then fields = {}; end
|
|
for key,method in pairs(baseClass) do
|
|
if not fields[key] then
|
|
fields[key] = method;
|
|
end
|
|
end
|
|
fields.__type = nil;
|
|
fields.key = nil;
|
|
else
|
|
print("Could not find specified base class:", baseClassName);
|
|
end
|
|
return app.CreateClass(className, classKey, fields, ...);
|
|
end
|
|
|
|
-- Allows wrapping one Type Object with another Type Object. This allows for fall-through field logic
|
|
-- without requiring a full copied definition of identical field functions and raw Object content
|
|
app.WrapObject = function(object, baseObject)
|
|
if not object or not baseObject then
|
|
error("Tried to WrapObject with none provided!",object,baseObject)
|
|
end
|
|
-- need to preserve the existing object's meta AND return the object being wrapped while also allowing fallback to the base object
|
|
local objectMeta = getmetatable(object)
|
|
if not objectMeta then
|
|
error("Tried to WrapObject which has no metatable! (Wrapping not necessary)")
|
|
end
|
|
objectMeta = objectMeta.__index
|
|
if not objectMeta then
|
|
error("Tried to WrapObject which has no index!")
|
|
end
|
|
if type(objectMeta) == "function" then
|
|
return setmetatable(object, {
|
|
__index = function(t, key)
|
|
return objectMeta(t, key) or baseObject[key];
|
|
end
|
|
});
|
|
end
|
|
return setmetatable(object, {
|
|
__index = function(t, key)
|
|
return objectMeta[key] or baseObject[key];
|
|
end
|
|
});
|
|
end
|
|
|
|
--[[
|
|
-- Proof of Concept with Class Conditionals
|
|
local fields = {
|
|
["name"] = function(t)
|
|
return "Loki";
|
|
end,
|
|
["OnTest"] = function()
|
|
return function(t)
|
|
print(t.name .. " (" .. t.__type .. "): I'm a god!");
|
|
end
|
|
end,
|
|
};
|
|
local fieldsWithArgs = {
|
|
OnTest = function()
|
|
return function(t)
|
|
print(t.name .. " (" .. t.__type .. "): I'm a variant!");
|
|
end
|
|
end
|
|
};
|
|
local fieldsWithFeeling = {
|
|
OnTest = function()
|
|
return function(t)
|
|
print(t.name .. " (" .. t.__type .. "): I'm a variant... with feeling!");
|
|
end
|
|
end
|
|
};
|
|
app.CreateExample = app.CreateClass("Example", "exampleID", fields,
|
|
"WithArgs", fieldsWithArgs, (function(t) return t.args; end),
|
|
"WithFeeling", fieldsWithFeeling, (function(t) return t.feeling; end));
|
|
|
|
for i,instance in ipairs({
|
|
app.CreateExample(1),
|
|
app.CreateExample(2, { name = "Alligator Loki", args = "I'm a Crocodile!" }),
|
|
app.CreateExample(3, { name = "Sylvie", feeling = "Pretty Neat" }),
|
|
}) do
|
|
instance.OnTest(instance);
|
|
end
|
|
]]--
|