Module:Sandbox/GameData: Difference between revisions

From Melvor Idle
(Update testing)
 
(sortByOrderTable: Protect against errors from unsorted elements)
 
Line 47: Line 47:
for i, skillObj in ipairs(GameData.skillData) do
for i, skillObj in ipairs(GameData.skillData) do
local _, localID = p.getLocalID(skillObj.skillID)
local _, localID = p.getLocalID(skillObj.skillID)
        if localID ~= nil then
if localID ~= nil then
            skillData[localID] = skillObj.data
skillData[localID] = skillObj.data
        end
end
end
end
    return skillData
return skillData
end
end


Line 105: Line 105:
error('Property name must be a string', 2)
error('Property name must be a string', 2)
end
end
    local entData, useCache = nil, false
local entData, useCache = nil, false
    if type(entityType) == 'string' then
if type(entityType) == 'string' then
        entData = GameData[entityType]
entData = GameData[entityType]
        useCache = true
useCache = true
    else
else
        -- Function was passed a table of entities rather than a entity type
-- Function was passed a table of entities rather than a entity type
        entData = entityType
entData = entityType
    end
end
if entData == nil then
if entData == nil then
error('No such entity type: ' .. entityType, 2)
error('No such entity type: ' .. entityType, 2)
Line 130: Line 130:
-- Cache miss or property isn't ID, so scan the entity data sequentially
-- Cache miss or property isn't ID, so scan the entity data sequentially
for idx, entity in ipairs(entData) do
for idx, entity in ipairs(entData) do
        if useCache then
if useCache then
    setCache(entityType, entity.id, idx)
setCache(entityType, entity.id, idx)
        end
end
if entity[propName] == propValue then
if entity[propName] == propValue then
return entity
return entity
Line 160: Line 160:
end
end
local entData, useCache = nil, false
local entData, useCache = nil, false
    if type(entityType) == 'string' then
if type(entityType) == 'string' then
        entData = GameData[entityType]
entData = GameData[entityType]
        useCache = true
useCache = true
    else
else
        -- Function was passed a table of entities rather than a entity type
-- Function was passed a table of entities rather than a entity type
        entData = entityType
entData = entityType
    end
end
if entData == nil and type(entityType) == 'string' then
if entData == nil and type(entityType) == 'string' then
error('No such entity type: ' .. entityType, 2)
error('No such entity type: ' .. entityType, 2)
Line 192: Line 192:
-- orderFunc specifies a custom order function if the default behaviour is not desired
-- 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:
-- Example - Sorts combat areas into the same order as displayed in game:
--     p.sortByOrderTable(p.rawData.combatAreas, p.rawData.combatAreaDisplayOrder)
--   p.sortByOrderTable(p.rawData.combatAreas, p.rawData.combatAreaDisplayOrder)
function p.sortByOrderTable(dataTable, orderTable, keepUnsorted, idKey, orderFunc)
function p.sortByOrderTable(dataTable, orderTable, keepUnsorted, idKey, orderFunc)
    -- Create index table from orderTable
-- Create index table from orderTable
    local orderIdx = {}
local orderIdx = {}
    for idx, v in ipairs(orderTable) do
for idx, v in ipairs(orderTable) do
        orderIdx[v] = idx
orderIdx[v] = idx
    end
end
    -- Determine if user-specified or default paramater values are to be used
-- Determine if user-specified or default paramater values are to be used
    if type(keepUnsorted) ~= 'boolean' then
if type(keepUnsorted) ~= 'boolean' then
        keepUnsorted = true
keepUnsorted = true
    end
end
    if type(idKey) ~= 'string' then
if type(idKey) ~= 'string' then
        idKey = 'id'
idKey = 'id'
    end
end
    if type(orderFunc) ~= 'function' then
if type(orderFunc) ~= 'function' then
        orderFunc = function(k1, k2)
orderFunc = function(k1, k2)
            return orderIdx[k1[idKey]] < orderIdx[k2[idKey]]
local o1, o2 = orderIdx[k1[idKey]], orderIdx[k2[idKey]]
        end
if o1 == nil or o2 == nil then
    end
return false
else
return orderIdx[k1[idKey]] < orderIdx[k2[idKey]]
end
end
end


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


return p
return p

Latest revision as of 18:15, 12 April 2023

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)
			local o1, o2 = orderIdx[k1[idKey]], orderIdx[k2[idKey]]
			if o1 == nil or o2 == nil then
	return false
			else
	return orderIdx[k1[idKey]] < orderIdx[k2[idKey]]
			end
		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