Module:OrdersData

From Against the Storm Official Wiki
Revision as of 21:35, 2 August 2025 by Anteprefix (talk | contribs)

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

---@class OrdersData
local OrdersData = {}



--region Dependencies

local JsonUtils = require("Module:JsonUtils")

--endregion

--region Private member variables

local ordersData

--endregion
--region Public enums

OrdersData.TIER_I = "I"
OrdersData.TIER_II = "II"
OrdersData.TIER_III = "III"
OrdersData.TIER_IV = "IV"
OrdersData.NONE = "None"

OrdersData.DIFFICULTY_EASY = "Easy"
OrdersData.DIFFICULTY_MEDIUM = "Medium"
OrdersData.DIFFICULTY_HARD = "Hard"

--endregion



--region Private constants


local DATA_FILE = "Module:OrdersData/Orders.json"


local SWITCH_TIERS_CHECK = {
	[OrdersData.TIER_I] = true,
	[OrdersData.TIER_II] = true,
	[OrdersData.TIER_III] = true,
	[OrdersData.TIER_IV] = true,
}

local NORMALIZED_TEXT_TIERS = {
	["i"] = OrdersData.TIER_I,
	["ii"] = OrdersData.TIER_II,
	["iii"] = OrdersData.TIER_III,
	["iv"] = OrdersData.TIER_IV,
}

local TIERS_SORT_ORDER = {
	[OrdersData.TIER_I] = 1,
	[OrdersData.TIER_II] = 2,
	[OrdersData.TIER_III] = 3,
	[OrdersData.TIER_IV] = 4,
}

local SWITCH_DIFFICULTIES_CHECK = {
	[OrdersData.DIFFICULTY_EASY] = true,
	[OrdersData.DIFFICULTY_MEDIUM] = true,
	[OrdersData.DIFFICULTY_HARD] = true,
}

local NORMALIZED_TEXT_DIFFICULTIES = {
	["easy"] = OrdersData.DIFFICULTY_EASY,
	["medium"] = OrdersData.DIFFICULTY_MEDIUM,
	["hard"] = OrdersData.DIFFICULTY_HARD,
}
--region Private constants



local SCHEMA = {
    ID = "id",
    NAME = "displayName",
    TIERS = "tiers",
    TIMED_ORDER = "isTimed",
    TIME_LIMIT = "timeToFail",

    OBJECTIVES = "objectivesSets",
    DIFFICULTY = "difficulty",
    REQUIREMENTS = "logics",
    REQUIREMENT_TYPE = "type",
    REQUIREMENT_AMOUNT = "amount",
    REQUIREMENT_ICON = "icon",
    REQUIREMENT_NAME = "displayName",
    REQUIREMENT_DESCRIPTION = "description",

    REWARD_1 = "rewards1",
    REWARD_2 = "rewards2",
    REWARD_3 = "rewards3",
    
    REWARD_ID = "id",
    REWARD_NAME = "displayName",
    REWARD_DESCRIPTION = "description",
    
    IS_TIMED_TRUE = "timed",
    IS_TIMED_FALSE = "non-timed",

}

local ERROR_MESSAGE_INVALID_FUNCTION_CALL = "Invalid function call to OrdersData"
--endregion



--region Localization string constants

--endregion


--region Private methods

 local function loadData()

    if not ordersData then

        local newData = JsonUtils.convertJSONToLuaTable(DATA_FILE)

        --Promote the ID to be the key of the main data table.
        ordersData = {}
        for _, order in ipairs(newData) do
            ordersData[order[SCHEMA.ID]] = order
        end
    end
end

local function getOrderFieldByID(orderID, fieldName)

    loadData()
    local order = ordersData[orderID]
    if not order then
        return nil
    else
        return order[fieldName]
    end
end

local function getObjectivesArrayByID(orderID)

    loadData()
    local objectives = getOrderFieldByID(orderID, SCHEMA.OBJECTIVES)
    if not objectives then
        return nil
    else
        return objectives
    end
end

local function getDifficultiesArrayByID(orderID)

    loadData()
    local objectives = getObjectivesArrayByID(orderID)
    if not objectives then
        return nil
    else
    	local difficultyList = {}
        for i, obj in pairs(objectives) do
        	for j, value in pairs(objectives[i]) do
        		if j == SCHEMA.DIFFICULTY then
        			table.insert(difficultyList,value)
        		end
        	end
        end
        return difficultyList
    end
end

local function getRequirementsArrayByID(orderID)

    loadData()
    local objectives = getObjectivesArrayByID(orderID)
    if not objectives then
        return nil
    else
    	local requirementsList = {}
        for i, obj in pairs(objectives) do
        	for j, value in pairs(objectives[i]) do
        		if j == SCHEMA.REQUIREMENTS then
        			table.insert(requirementsList,value)
        		end
        	end
        end
        return requirementsList
    end
end

local function getRequirementByIDAndRow(orderID, rowNum)

    local requirements = getRequirementsArrayByID(orderID)
    local requirement = requirements[rowNum]
    if not requirement then
        return nil
    else
        return requirement
    end
end

local function getSpecificRequirementByIDAndRowAndSubrow(orderID, rowNum, subrow)
	local specific = getRequirementByIDAndRow(orderID, rowNum)[subrow]
	if not specific then
		return nil
	else
		return specific
	end
end

local function getSpecificRequirementFieldByIDAndRowAndSubrowAndField(orderID, rowNum, subrow, field)
	local specificField = getSpecificRequirementByIDAndRowAndSubrow(orderID, rowNum, subrow)[field]
	if not specificField then
		return nil
	else
		return specificField
	end
end

local function makeFormattedRequirementByIDAndRowNumAndSubrow(orderID, rowNum, subrow)
	local requirementType = getSpecificRequirementFieldByIDAndRowAndSubrowAndField(orderID, rowNum, subrow, SCHEMA.REQUIREMENT_TYPE)
	if not requirementType then
		return nil
	end
	---It's better if the following parts are reworked to not be hardcoded in the future
	---For Good Delivery requirements, use the name instead of description
	if requirementType == "Good" then
		local requirementString = getSpecificRequirementFieldByIDAndRowAndSubrowAndField(orderID, rowNum, subrow, SCHEMA.REQUIREMENT_NAME)
		requirementString = requirementString .. ". "
		return requirementString
	end
	---For owning at least X buildings of type Y, use "Own at least [X] [Y](s)."
	if requirementType == "BuildingOwning" then
		local requirementString = "Own at least "
		local requirementAmount = getSpecificRequirementFieldByIDAndRowAndSubrowAndField(orderID, rowNum, subrow, SCHEMA.REQUIREMENT_AMOUNT)
		local requirementBuildingName = getSpecificRequirementFieldByIDAndRowAndSubrowAndField(orderID, rowNum, subrow, SCHEMA.REQUIREMENT_NAME)
		
		if tonumber(requirementAmount) > 1 then
			requirementString = requirementString .. requirementAmount .. " " .. requirementBuildingName .. "s."
		else
			requirementString = requirementString .. requirementAmount .. " " .. requirementBuildingName .. ". "
		end
		return requirementString
	end
	---For producing at least X goods, use "Produce [X] Goods in any capable building."
	if requirementType == "GoodProduced" then
		local requirementString = getSpecificRequirementFieldByIDAndRowAndSubrowAndField(orderID, rowNum, subrow, SCHEMA.REQUIREMENT_DESCRIPTION)
		local requirementAmount = getSpecificRequirementFieldByIDAndRowAndSubrowAndField(orderID, rowNum, subrow, SCHEMA.REQUIREMENT_AMOUNT)
		requirementString = requirementString:sub(1, 7) .. " " .. requirementAmount .. requirementString:sub(8)
		return requirementString
	end
	---In any other case, the description is already properly formatted
	return getSpecificRequirementFieldByIDAndRowAndSubrowAndField(orderID, rowNum, subrow, SCHEMA.REQUIREMENT_DESCRIPTION)
end

local function getRewardsByID(orderID)
	local reward1 = getOrderFieldByID(orderID, SCHEMA.REWARD_1)
	local reward2 = getOrderFieldByID(orderID, SCHEMA.REWARD_2)
	local reward3 = getOrderFieldByID(orderID, SCHEMA.REWARD_3)
	return {reward1, reward2, reward3}
end
--endregion


--region Public methods

---@public
---Gets an array of all IDs of orders in the data.
---
---@return table an array of IDs
function OrdersData.getAllOrderIDs()

    loadData()

    local orderIDs = {}
    for id, _ in pairs(ordersData) do
        table.insert(orderIDs, id)
    end
    return orderIDs
end


---@public
-----Gets the name of the specified order.
-----
-----@param orderID string
-----@return string the display name
function OrdersData.getNameByID(orderID)
    return getOrderFieldByID(orderID, SCHEMA.NAME)
end

---@public
-----Gets the tiers of the specified order.
-----
-----@param orderID string
-----@return array the list of valid tiers
function OrdersData.getTiers(orderID)
    return getOrderFieldByID(orderID, SCHEMA.TIERS)
end

---@public
-----Gets the timed status of the specified order.
-----
-----@param orderID string
-----@return boolean the timed status
function OrdersData.getTimed(orderID)
    return getOrderFieldByID(orderID, SCHEMA.TIMED_ORDER)
end

---@public
-----Gets the time limit of the specified order.
-----
-----@param orderID string
-----@return number the time limit
function OrdersData.getTimeLimit(orderID)
    return getOrderFieldByID(orderID, SCHEMA.TIME_LIMIT)
end

---@public
-----Gets the specified difficulty of the specified order.
-----
-----@param orderID string
-----@param rowNum integer
-----@return difficultyList[rowNum] the difficulty listed in that row
function OrdersData.getDifficultyByIDAndRowNum(orderID, rowNum)
    local difficultyList = getDifficultiesArrayByID(orderID)
    return difficultyList[rowNum]
end

---@public
-----Gets the specified requirements of the specified order and returns it after formatting it for display.
-----
-----@param orderID string
-----@param rowNum integer
-----@return string the appropriately formatted requirement description
function OrdersData.getFormattedRequirementsByIDAndRowNum(orderID, rowNum)
    requirements = getRequirementByIDAndRow(orderID, rowNum)
    local requirementsAmount = #requirements
    local formattedRequirements = ""
    for i = 1, requirementsAmount do
    	if i > 1 then
    		formattedRequirements = formattedRequirements:newline()
    	end
    	formattedRequirements = formattedRequirements .. makeFormattedRequirementByIDAndRowNumAndSubrow(orderID, rowNum, i)
    end

    return formattedRequirements
end

---@public
-----Gets the specified rewards of the specified order and returns it after formatting it for display.
-----
-----@param orderID string
-----@return string the appropriately formatted rewards description
function OrdersData.getRewardsFormattedByID(orderID)
	local rewards = getRewardsByID(orderID)
    local rewardsAmount = #rewards
    local formattedRewards = ""
    for i = 1, rewardsAmount do
    	if i > 1 then
    		formattedRewards = formattedRewards .. "\n"
    	end
    	local reward = rewards[i][1]
    	if reward then
    		formattedRewards = formattedRewards .. reward[SCHEMA.REWARD_DESCRIPTION]
    	end
    end
    return formattedRewards
end

---@public
-----Gets the number of unique difficulties for the order.
-----
-----@param orderID string
-----@return difficultyList[rowNum] the difficulty listed in that row
function OrdersData.getNumberOfDifficulties(orderID)
    return #getDifficultiesArrayByID(orderID)
end

---@public
-----Gets the valid difficulties of the specified order.
-----
-----@param orderID string
-----@return array the list of valid difficulties
function OrdersData.getDifficulties(orderID)
    return getDifficultiesArrayByID(orderID)
end

--- Standardizes the display of a string name of one of Orders' tier.
---
--- If the provided tier is nil or not an accepted term (for example misspelled), this will return nil.
---
---@param tierString string the descriptive name of the tier of an order
---@return string that same tier, normalized to title case
function OrdersData.normalizeTierText(tierString)

	if not tierString or "" == tierString then
		return nil
	end

	local tier = string.lower(tierString)
	if not tier or SWITCH_TIERS_CHECK[tier] then
		return nil
	end

	return NORMALIZED_TEXT_TIERS[tier]
end

--- Standardizes the display of a string name of one of Orders' timed status.
---
--- If the provided tier is nil or not an accepted term (for example misspelled), this will return nil.
---
---@param timedString string the descriptive name of the timed status of an order
---@return string that same timed status, normalized to title case
function OrdersData.normalizeTimedText(timedString)

	--if not timedString or "" == timedString then
		--return nil
	--end

	--local timed = string.lower(timedString)
	--if not timed or SWITCH_TIMED_CHECK[timed] then
		--return nil
	--end

	--return NORMALIZED_TEXT_TIMED[timed]
	return timedString
end

--- Standardizes the display of a string name of one of Orders' difficulty.
---
--- If the provided tier is nil or not an accepted term (for example misspelled), this will return nil.
---
---@param rarityString string the descriptive name of the difficulty of an order
---@return string that same tier, normalized to title case
function OrdersData.normalizeDifficultyText(difficultyString)

	if not difficultyString or "" == difficultyString then
		return nil
	end

	local difficulty = string.lower(difficultyString)
	if not difficulty or SWITCH_DIFFICULTIES_CHECK[difficulty] then
		return nil
	end

	return NORMALIZED_TEXT_DIFFICULTIES[difficulty]
end

---isOrderIDValid
---@param orderID string the unique identifier of an Order
---@return boolean true if there is a record with the specified ID
function OrdersData.isOrderIDValid(orderID)

	if not orderID or "" == orderID then
		return false
	else
		local record = OrdersData.getNameByID(orderID)
		return record ~= nil
	end
end
--endregion

---isTierMatchByID
---@param orderID string the unique identifier of an Order
---@param tierToMatch string the tier to check against
---@return boolean true if the tier matches
function OrdersData.isTierMatchByID(orderID, tierToMatch)
	local record = OrdersData.getTiers(orderID)
	if record == tierToMatch then
		return true
	else
		return false
	end
end

---isTimedMatchByID
---@param orderID string the unique identifier of an Order
---@param timedToMatch boolean the timed status to check against
---@return boolean true if the status matches
function OrdersData.isTimedMatchByID(orderID, timedToMatch)
	local record = OrdersData.getTimed(orderID)
	if record then
		if timedToMatch == SCHEMA.IS_TIMED_TRUE then
			return true
		end
		if timedToMatch == SCHEMA.IS_TIMED_FALSE then
			return false
		end
	else
		if timedToMatch == SCHEMA.IS_TIMED_TRUE then
			return false
		end
		if timedToMatch == SCHEMA.IS_TIMED_FALSE then
			return true
		end
	end
	error(ERROR_MESSAGE_INVALID_FUNCTION_CALL .. " isTimedMatchByID was called but could not return a boolean.")
end

---isDifficultyMatchByID
---@param orderID string the unique identifier of an Order
---@param difficultyToMatch string the order difficulty to check against
---@return boolean true if the difficulty matches a substring in the array
function OrdersData.isDifficultyMatchByID(orderID, difficultyToMatch)
	local difficulties = OrdersData.getDifficulties(orderID)
	
	for i, diff in ipairs(difficulties) do
		if string.find(diff, difficultyToMatch) then
			return true
		end
	end
	return false
end
---getAllOrdersIDsWhereName
--- Loop through all orders and return those whose names contain the
--- specified search term.
---
---@param searchTerm string terms to search for
---@return table list of orderIDs with a match in their name
function OrdersData.getAllOrdersIDsWhereName(searchTerm)

	if not searchTerm then
		return {}
	end

	if not ordersData then
		loadData()
	end

	local list = {}
	for _, order in pairs(ordersData) do

		if string.lower(order[SCHEMA.NAME]):find(string.lower(searchTerm)) then
			table.insert(list, order[SCHEMA.ID])
		end
	end

	return list
end
--endregion

return OrdersData