Module:SpellTiers

From Noita Wiki
Jump to navigation Jump to search

local p = {}
local framesPerSecond = 60

local function trim(str)
  return str:gsub("^%s*(.-)%s*$", "%1")
end

local function is_empty(str)
	return str == nil or str.len(trim(str)) == 0
end

local function append_table(tbl, data)
	tbl[#tbl + 1] = data
end

function formatNumeric(number)
	return ("%.5g"):format(tonumber(number))
end

function split(str, delimiter)
	local chunks = {}
	
	for c in (str..delimiter):gmatch("(.-)"..delimiter) do
	   table.insert(chunks, c)
	end
	
	return chunks
end

function subrange(t, first, last)
  local sub = {}
  
  for i = first, last do
    sub[#sub + 1] = t[i]
  end
  
  return sub
end

-- Values as of 22-01-2024
-- TODO - It would be ideal to generate & update this automatically
local sumOfSpellSpawnWeightsByTier = {
	-- same as 8th April release (will we get more beta...?)
	-- ['beta'] = { 44.55, 84.05001, 137.71, 129.96, 142.89, 125.21, 82.06, 0.6, 0, 0, 59.8 },
	-- current figures from 8th April release
	-- ['release'] = { 44.55, 84.05001, 137.71, 129.96, 142.89, 125.21, 82.06, 0.6, 0, 0, 59.8 },
	-- these are the figures for the pre 8th April release version 
	-- ['old'] = { 47.1, 86.65, 139.18, 130.2, 145.1, 127.35,88.3, 0.6, 0, 0, 51.7 }
	['beta'] = {
		[0] = 44.55,  [1] = 84.05,  [2] = 137.71, [3] = 129.96,
		[4] = 142.89, [5] = 125.21, [6] = 82.06,  [7] = 0.6,
		[8] = 0,      [9] = 0,     [10] = 59.8 },
	['release'] = {
		[0] = 44.55,  [1] = 84.05,  [2] = 137.71, [3] = 129.96,
		[4] = 142.89, [5] = 125.21, [6] = 82.06,  [7] = 0.6,
		[8] = 0,      [9] = 0,     [10] = 59.8 },
	['old'] = {
		[0] = 47.1,   [1] = 86.65,  [2] = 139.18, [3] = 130.2,
		[4] = 145.1,  [5] = 127.35, [6] = 88.3,   [7] = 0.6,
		[8] = 0,      [9] = 0,     [10] = 51.7 }
}

-- Converts spell tier + spawn weight into probability per tier
-- Uses 'tiers' and 'weights' from passed or parent frame context
--  tiers: tiers the spell spawns in (valid: 0-10, used: 0-7,10)
--  weights: matching list of spawn chance weightings
--  (both are strings of comma-seperated numbers) (whitespace ignored)
-- e.g.  tiers: 0,1,  2,  5,6
--     weights: 1,1,0.3,0.2,1
--
-- mw.logObject(p.getSpellSpawnInfo('0,1,2,3,4,5,6,7,10', '1,1,1,1,1,1,1,1,1', 'release'))
function getSpellSpawnInfo(tiers, weights, version)
	local spellTiers   = split(tiers   or '', ',')
	local spellWeights = split(weights or '', ',')
	local version      = trim(version) or 'release'

	local weightSums   = sumOfSpellSpawnWeightsByTier[version]

	local spellTierProbabilities = {}
	
	for i = 0, 10 do
		spellTierProbabilities[i + 1] = {
	    	['tier'] = string.format('%d', i),
        	['weight'] = -1,
        	['probability'] = 0,
        	['relative'] = 0,
        	['percentage'] = '0',
        	['version'] = '',
        }
	end
	local bestProbability = 0
	for spellTiersIdx, tier in ipairs(spellTiers) do
		local tierIdx = tonumber(tier, 10)
		local weight = tonumber(spellWeights[spellTiersIdx] or 0, 10)
		local weightSum = weightSums[tierIdx] or 0
		if (weight > 0 and weightSum > 0) then
			local probability = weight / weightSum
			spellTierProbabilities[tierIdx + 1]['weight'] = weight
			spellTierProbabilities[tierIdx + 1]['probability'] = probability
			spellTierProbabilities[tierIdx + 1]['percentage'] = string.format('%1.2f', probability * 100)
			spellTierProbabilities[tierIdx + 1]['version'] = version
			bestProbability = math.max(probability, bestProbability)
		end
	end
	for spellTiersIdx, tier in ipairs(spellTiers) do
		local tierIdx = tonumber(tier, 10)
		spellTierProbabilities[tierIdx + 1]['relative'] = 1 / bestProbability * spellTierProbabilities[tierIdx + 1]['probability']
	end

	return spellTierProbabilities
end

function binRarity(prob)
	local p = prob * 100
	if p <= 0 then
		return 'nospawn', 'Impossible'
	elseif p < 0.01 then
		return 'vrare', 'Very Rare'
	elseif p < 0.1 then
		return 'rare', 'Rare'
	elseif p < 1 then
		return 'uncommon', 'Uncommon'
	elseif p < 3 then
		return 'common', 'Common'
	end
	return 'vcommon', 'Very Common'
end

function binOdds(prob)
	local mc = require('Module:Common')
	local res = ''
	if ( prob ~= nil ) then
		local frac = mc.formatNum(1/prob)
		if (frac ~= nil) then
			local tmp = '{{fraction|1|'..frac..'}}'
			local percent = (string.sub(mc.string.parseFloat(prob * 100), 0, 5) or 'null')..'%'
			res = mw.getCurrentFrame():preprocess('{{abbr|'..tmp..'|'..percent..'}}')
		end
	end
	return res
end

-- /* FYI - strings in this function contain U+200A 'hair space' characters */
function binPercent(prob)
	local p = prob * 100
	if p <= 0 then
		return '0'
	elseif p < 0.01 then
		return '0.01'
	elseif p < 0.1 then
		return '0.1'
	elseif p < 1 then
		return '1'
	end
	return string.format('%1.0f', p)
end

function p.formatSpellSpawnInfo(frame)
	local args = frame.args
	local pArgs = frame:getParent().args
	
	local tiers = pArgs['tiers'] or args['tiers'] or ''
	local weights = pArgs['weights'] or args['weights'] or ''
	local version = pArgs['version'] or args['version'] or ''

	local spawnInfo = getSpellSpawnInfo(tiers, weights, version)
	
	-- Build output HTML
	local root = mw.html.create('div')
			:attr('class', 'spellTierInfo')
			:attr('data-version', version)

	for i = 0, 10 do
		local info = spawnInfo[i + 1]
		local rarity, rarityDisplay = binRarity(info['probability'])
	    local tierTag = root:tag('div'):attr('class', 'spellTierInfo-tier')
    		:attr('data-tier-id', info['tier'])
			:attr('data-tier-percentage', info['percentage'])
			:attr('data-tier-percentage-binned', binPercent(info['probability']))
			:attr('data-tier-rarity', rarity)
    		:attr('data-tier-weight', info['weight'])
    		:attr('data-tier-probability', info['probability'])
    		:attr('data-tier-relative', info['relative'])
			:attr('data-tier-version', info['version'])

			tierTag:tag('span'):attr('class', 'spellTierInfo-tiernum')
				:wikitext(string.format('%d', info['tier']))
			tierTag:tag('span'):attr('class', 'spellTierInfo-tierbinodds')
				:wikitext(binOdds(info['probability']))
			tierTag:tag('span'):attr('class', 'spellTierInfo-tierbinpercent')
				:wikitext(binPercent(info['probability']))
			tierTag:tag('span'):attr('class', 'spellTierInfo-tierpercent')
				:wikitext(info['percentage'])
	end

	root:allDone()
	return tostring(root)
end

return p