Module:Sandbox/GameData

From Melvor Idle

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