Module:Sandbox/GameData

From Melvor Idle
Revision as of 12:53, 12 April 2023 by Auron956 (talk | contribs) (Update testing)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Documentation for this module may be created at Module:Sandbox/GameData/doc

-- This module is responsible for managing all game data, and provides various
-- functions so that other modules may interact with the data.

local p = {}

local GameData1 = mw.loadData('Module:Sandbox/GameData/data')
local GameData2 = mw.loadData('Module:Sandbox/GameData/data2')
-- Combine data into a single object
local GameData = {}
for _, data in ipairs({GameData1, GameData2}) do
	for entityType, entityData in pairs(data) do
		GameData[entityType] = entityData
	end
end

local indexCache = {}

-- Expose underlying data should other modules require it
p.rawData = GameData

-- Given a namespace & local ID, returns a namespaced ID
function p.getNamespacedID(namespace, localID)
	if string.find(localID, ':') == nil then
		return namespace .. ':' .. localID
	else
		-- ID already appears to be namespaced
		return localID
	end
end

-- Given a namespaced ID, returns both the namespace & local ID
function p.getLocalID(ID)
	local namespace, localID = nil, nil
	local sepIdx = string.find(ID, ':')
	if sepIdx == nil then
		-- Provided ID doesn't appear to be namespaced
		localID = ID
	else
		namespace = string.sub(ID, 1, sepIdx - 1)
		localID = string.sub(ID, sepIdx + 1, string.len(ID))
	end
	return namespace, localID
end

local function populateSkillData()
	local skillData = {}
	for i, skillObj in ipairs(GameData.skillData) do
		local _, localID = p.getLocalID(skillObj.skillID)
        if localID ~= nil then
            skillData[localID] = skillObj.data
        end
	end
    return skillData
end

-- Expose an easy way to reference skill data by skill local ID
p.skillData = populateSkillData()

function p.skillDataTest()
	for localID, data in pairs(p.skillData) do
		mw.log(localID)
	end
end

-- If the entity ID is within the cache for the given entity type, then return it.
-- Otherwise, returns nil
local function getCache(entityType, ID)
	if type(entityType) == 'string' and type(ID) == 'string' then
		local cacheCat = indexCache[entityType]
		if cacheCat ~= nil then
			return cacheCat[ID]
		end
	end
end

-- Sets the cache for entity ID within the given entity type to idx.
local function setCache(entityType, ID, idx)
	if type(entityType) == 'string' and type(ID) == 'string' and type(idx) == 'number' then
		if indexCache[entityType] == nil then
			indexCache[entityType] = {}
		end
		if indexCache[entityType][ID] == nil then
			indexCache[entityType][ID] = math.floor(idx)
		end
	end
end

function p.getSkillData(skillID)
	if type(skillID) ~= 'string' then
		error('Skill ID must be a string', 2)
	end
	for idx, skill in ipairs(GameData.skillData) do
		if skill.skillID == skillID then
			return skill.data
		end
	end
end

-- Takes an entity type (or entity list) & property name/value, returning an object representing the
-- entity with the given property (if found). If not found, then nil is returned.
function p.getEntityByProperty(entityType, propName, propValue)
	if type(entityType) ~= 'string' and type(entityType) ~= 'table' then
		error('Entity type name must be a string or table', 2)
	elseif type(propName) ~= 'string' then
		error('Property name must be a string', 2)
	end
    local entData, useCache = nil, false
    if type(entityType) == 'string' then
        entData = GameData[entityType]
        useCache = true
    else
        -- Function was passed a table of entities rather than a entity type
        entData = entityType
    end
	if entData == nil then
		error('No such entity type: ' .. entityType, 2)
	elseif type(entData) ~= 'table' then
		error('Entity data is not a table: ' .. entityType, 2)
	elseif type(entData[1]) ~= 'table' or entData[1][propName] == nil then
		error('Entity data is not composed of entities: ' .. entityType, 2)
	end
	-- Check if this ID is already cached
	if propName == 'id' and useCache then
		local cacheIdx = getCache(entityType, propValue)
		if cacheIdx ~= nil then
			-- Cache hit
			return entData[cacheIdx]
		end
	end
	-- Cache miss or property isn't ID, so scan the entity data sequentially
	for idx, entity in ipairs(entData) do
        if useCache then
		    setCache(entityType, entity.id, idx)
        end
		if entity[propName] == propValue then
			return entity
		end
	end
end

function p.getEntityByID(entityType, entityID)
	return p.getEntityByProperty(entityType, 'id', entityID)
end

function p.getEntityByName(entityType, entityName)
	return p.getEntityByProperty(entityType, 'name', entityName)
end

-- Takes an entity type and a function, returning a list of entities for which
-- the function evaluates to true.
-- The function must accept one parameter (the entity being checked), and must
-- return either true or false
function p.getEntities(entityType, checkFunc)
	local result = {}
	local entityCount = 0
	if type(entityType) ~= 'string' and type(entityType) ~= 'table' then
		error('Entity type name must be a string or table', 2)
	elseif type(checkFunc) ~= 'function' then
		error('Check function name must be a function', 2)
	end
	local entData, useCache = nil, false
    if type(entityType) == 'string' then
        entData = GameData[entityType]
        useCache = true
    else
        -- Function was passed a table of entities rather than a entity type
        entData = entityType
    end
	if entData == nil and type(entityType) == 'string' then
		error('No such entity type: ' .. entityType, 2)
	elseif type(entData) ~= 'table' then
		error('Entity data is not a table: ' .. entityType, 2)
	elseif type(entData[1]) ~= 'table' then
		error('Entity data is not composed of entities: ' .. entityType, 2)
	end
	for idx, entity in ipairs(entData) do
		if useCache then
			setCache(entityType, entity.id, idx)
		end
		if checkFunc(entity) then
			entityCount = entityCount + 1
			result[entityCount] = entity
		end
	end
	return result
end

-- Sorts the given dataTable by ID into the same order as orderTable
-- keepUnsorted specifies whether unsorted elements are included within the output. Default: true
--  unsorted elements will be at the end of the array, order is not guaranteed
-- idKey specifies the ID key to sort upon. Default: id
-- orderFunc specifies a custom order function if the default behaviour is not desired
-- Example - Sorts combat areas into the same order as displayed in game:
--      p.sortByOrderTable(p.rawData.combatAreas, p.rawData.combatAreaDisplayOrder)
function p.sortByOrderTable(dataTable, orderTable, keepUnsorted, idKey, orderFunc)
    -- Create index table from orderTable
    local orderIdx = {}
    for idx, v in ipairs(orderTable) do
        orderIdx[v] = idx
    end
    -- Determine if user-specified or default paramater values are to be used
    if type(keepUnsorted) ~= 'boolean' then
        keepUnsorted = true
    end
    if type(idKey) ~= 'string' then
        idKey = 'id'
    end
    if type(orderFunc) ~= 'function' then
        orderFunc = function(k1, k2)
            return orderIdx[k1[idKey]] < orderIdx[k2[idKey]]
        end
    end

    -- Build unsorted result table, removing unsorted elements if requested
    local resultTable = {}
    local resultItemCount = 0
    for idx, v in ipairs(dataTable) do
        local keyVal = v[idKey]
        if keyVal ~= nil then
            if keepUnsorted or orderIdx[keyVal] ~= nil then
                resultItemCount = resultItemCount + 1
                resultTable[resultItemCount] = v
            end
        end
    end
    -- Sort table
    table.sort(resultTable, orderFunc)
    return resultTable
end

return p