<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.runerealm.org/index.php?action=history&amp;feed=atom&amp;title=Module%3AZMICalculator</id>
	<title>Module:ZMICalculator - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.runerealm.org/index.php?action=history&amp;feed=atom&amp;title=Module%3AZMICalculator"/>
	<link rel="alternate" type="text/html" href="https://wiki.runerealm.org/index.php?title=Module:ZMICalculator&amp;action=history"/>
	<updated>2026-05-03T18:47:14Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.41.1</generator>
	<entry>
		<id>https://wiki.runerealm.org/index.php?title=Module:ZMICalculator&amp;diff=35230&amp;oldid=prev</id>
		<title>Alex: Created page with &quot;local p = {}  -- For calculating experience local helpers = require(&#039;Module:Skill_calc/Helpers&#039;) local experience = require(&#039;Module:Experience&#039;)  -- For rendering the tables local commas = require(&#039;Module:Addcommas&#039;)._add local coins = require(&#039;Module:Coins&#039;)._amount local gePrices = mw.loadJsonData(&#039;Module:GEPrices/data.json&#039;) local yesno = require(&#039;Module:Yesno&#039;) function nocoins(num) 	frame = mw.getCurrentFrame() 	return frame:preprocess(&#039;{{Nocoins|&#039; .. num .. &#039;}}&#039;) e...&quot;</title>
		<link rel="alternate" type="text/html" href="https://wiki.runerealm.org/index.php?title=Module:ZMICalculator&amp;diff=35230&amp;oldid=prev"/>
		<updated>2024-10-16T23:13:25Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;local p = {}  -- For calculating experience local helpers = require(&amp;#039;Module:Skill_calc/Helpers&amp;#039;) local experience = require(&amp;#039;Module:Experience&amp;#039;)  -- For rendering the tables local commas = require(&amp;#039;Module:Addcommas&amp;#039;)._add local coins = require(&amp;#039;Module:Coins&amp;#039;)._amount local gePrices = mw.loadJsonData(&amp;#039;Module:GEPrices/data.json&amp;#039;) local yesno = require(&amp;#039;Module:Yesno&amp;#039;) function nocoins(num) 	frame = mw.getCurrentFrame() 	return frame:preprocess(&amp;#039;{{Nocoins|&amp;#039; .. num .. &amp;#039;}}&amp;#039;) e...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;local p = {}&lt;br /&gt;
&lt;br /&gt;
-- For calculating experience&lt;br /&gt;
local helpers = require(&amp;#039;Module:Skill_calc/Helpers&amp;#039;)&lt;br /&gt;
local experience = require(&amp;#039;Module:Experience&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
-- For rendering the tables&lt;br /&gt;
local commas = require(&amp;#039;Module:Addcommas&amp;#039;)._add&lt;br /&gt;
local coins = require(&amp;#039;Module:Coins&amp;#039;)._amount&lt;br /&gt;
local gePrices = mw.loadJsonData(&amp;#039;Module:GEPrices/data.json&amp;#039;)&lt;br /&gt;
local yesno = require(&amp;#039;Module:Yesno&amp;#039;)&lt;br /&gt;
function nocoins(num)&lt;br /&gt;
	frame = mw.getCurrentFrame()&lt;br /&gt;
	return frame:preprocess(&amp;#039;{{Nocoins|&amp;#039; .. num .. &amp;#039;}}&amp;#039;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local data = {&lt;br /&gt;
	-- base xp, air, mind, water, earth, fire, body, cosmic, chaos, astral, nature, law, death, blood, soul&lt;br /&gt;
	{9.37, 0.5, 0.25, 0.12, 0.06, 0.03, 0.015, 0.0085, 0.006, 0.0045, 0.003, 0.0015, 0.0008, 0.0005, 0.0002},&lt;br /&gt;
	{10.5, 0.15, 0.18, 0.21, 0.24, 0.12, 0.06, 0.0175, 0.008, 0.006, 0.004, 0.0024, 0.0012, 0.0006, 0.0003},&lt;br /&gt;
	{11.32, 0.12, 0.13, 0.135, 0.14, 0.15, 0.16, 0.08, 0.042, 0.021, 0.011, 0.0055, 0.0032, 0.0015, 0.0008},&lt;br /&gt;
	{12.24, 0.07, 0.08, 0.09, 0.11, 0.12, 0.13, 0.2, 0.1, 0.05, 0.025, 0.013, 0.006, 0.004, 0.002},&lt;br /&gt;
	{12.87, 0.06, 0.065, 0.07, 0.075, 0.08, 0.1, 0.15, 0.2, 0.1, 0.05, 0.026, 0.012, 0.008, 0.004},&lt;br /&gt;
	{13.44, 0.05, 0.055, 0.06, 0.065, 0.07, 0.075, 0.1, 0.11, 0.15, 0.135, 0.07, 0.035, 0.017, 0.008},&lt;br /&gt;
	{13.6, 0.045, 0.05, 0.055, 0.06, 0.07, 0.075, 0.095, 0.105, 0.14, 0.155, 0.08, 0.04, 0.02, 0.01},&lt;br /&gt;
	{14.56, 0.03, 0.03, 0.03, 0.04, 0.04, 0.05, 0.07, 0.09, 0.12, 0.15, 0.18, 0.1, 0.05, 0.02},&lt;br /&gt;
	{14.79, 0.02, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.08, 0.105, 0.135, 0.145, 0.145, 0.06, 0.04},&lt;br /&gt;
	{15.32, 0.01, 0.01, 0.02, 0.03, 0.04, 0.05, 0.06, 0.07, 0.1, 0.135, 0.145, 0.165, 0.1, 0.065},&lt;br /&gt;
	{15.55, 0.01, 0.01, 0.02, 0.03, 0.03, 0.04, 0.05, 0.06, 0.095, 0.135, 0.145, 0.155, 0.13, 0.09},&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
-- Capacity using rune pouch and all possible runecraft pouches&lt;br /&gt;
local pouches = {&lt;br /&gt;
	-- level, cumulative capacity, size&lt;br /&gt;
	{1, 26 + 3, &amp;#039;Small&amp;#039;},&lt;br /&gt;
	{25, 25 + 9, &amp;#039;Medium&amp;#039;},&lt;br /&gt;
	{50, 24 + 18, &amp;#039;Large&amp;#039;},&lt;br /&gt;
	{75, 23 + 30, &amp;#039;Giant&amp;#039;},&lt;br /&gt;
	{85, 26 + 40, &amp;#039;Colossal&amp;#039;},&lt;br /&gt;
	{999, 1, &amp;#039;na&amp;#039;},&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
local runes = {&lt;br /&gt;
	-- rune, xp, diary boost in %&lt;br /&gt;
	{ &amp;#039;Air rune&amp;#039;, 5, 25 },&lt;br /&gt;
	{ &amp;#039;Mind rune&amp;#039;, 5.5, 25 },&lt;br /&gt;
	{ &amp;#039;Water rune&amp;#039;, 6, 25 },&lt;br /&gt;
	{ &amp;#039;Earth rune&amp;#039;, 6.5, 25 },&lt;br /&gt;
	{ &amp;#039;Fire rune&amp;#039;, 7, 25 },&lt;br /&gt;
	{ &amp;#039;Body rune&amp;#039;, 7.5, 25 },&lt;br /&gt;
	{ &amp;#039;Cosmic rune&amp;#039;, 8, 25 },&lt;br /&gt;
	{ &amp;#039;Chaos rune&amp;#039;, 8.5, 25 },&lt;br /&gt;
	{ &amp;#039;Astral rune&amp;#039;, 8.7, 25 },&lt;br /&gt;
	{ &amp;#039;Nature rune&amp;#039;, 9, 22.5 },&lt;br /&gt;
	{ &amp;#039;Law rune&amp;#039;, 9.5, 20 },&lt;br /&gt;
	{ &amp;#039;Death rune&amp;#039;, 10, 17.5 },&lt;br /&gt;
	{ &amp;#039;Blood rune&amp;#039;, 23.8, 15 },&lt;br /&gt;
	{ &amp;#039;Soul rune&amp;#039;, 29.7, 10 },&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
	-- Parse the args&lt;br /&gt;
	local args = frame:getParent().args&lt;br /&gt;
	&lt;br /&gt;
	local current = tonumber(args.current)&lt;br /&gt;
	local currentType = args.currentType&lt;br /&gt;
	local target = tonumber(args.target)&lt;br /&gt;
	local targetType = args.targetType&lt;br /&gt;
	local tripSeconds = tonumber(args.tripSeconds)&lt;br /&gt;
	local blockedStorage = tonumber(args.blockedStorage)&lt;br /&gt;
	local diary = yesno(args.diary)&lt;br /&gt;
	local raiments = tonumber(args.raiments)&lt;br /&gt;
	local colossal = yesno(args.colossal)&lt;br /&gt;
	&lt;br /&gt;
	-- Not using a rune pouch means keeping the astral, law, and payment runes&lt;br /&gt;
	-- for &lt;br /&gt;
	if args.blockedStorage ~= &amp;#039;true&amp;#039; then&lt;br /&gt;
		runePouch = true&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- If we&amp;#039;re targeting a level, we have a hard cap at 99&lt;br /&gt;
	if targetType == &amp;#039;Level&amp;#039; then&lt;br /&gt;
		target = math.min(target, 99)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Update the pouch capacities if we have manually blocked storage (e.g., no rune pouch)&lt;br /&gt;
	if blockedStorage &amp;gt; 0 then&lt;br /&gt;
		for i=1, 5, 1 do&lt;br /&gt;
			pouches[i][2] = pouches[i][2] - blockedStorage&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Update the xp rates if we&amp;#039;re using Daeyalt essence&lt;br /&gt;
	local essenceType = &amp;#039;Pure essence&amp;#039;&lt;br /&gt;
	if args.daeyalt == &amp;#039;true&amp;#039; then&lt;br /&gt;
		essenceType = &amp;#039;Daeyalt essence&amp;#039;&lt;br /&gt;
		for i=1, 11, 1 do&lt;br /&gt;
			data[i][1] = data[i][1] * 1.5&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Get the starting and target XP&lt;br /&gt;
	local bulkLevelInformation = helpers.calculateCurrentGoalInformation(current, currentType, target, targetType)&lt;br /&gt;
	local currentXP = bulkLevelInformation.currentExperience&lt;br /&gt;
	local goalXP = bulkLevelInformation.goalExperience&lt;br /&gt;
	&lt;br /&gt;
	-- Get the total number of actions at each block to achieve the goal&lt;br /&gt;
	local actions = getActions(currentXP, goalXP)&lt;br /&gt;
	&lt;br /&gt;
	-- Get the total number of trips at each pouch block to achieve the goal&lt;br /&gt;
	local trips = getTrips(currentXP, goalXP, colossal)&lt;br /&gt;
	&lt;br /&gt;
	-- This calculates gp and renders the runes crafted table&lt;br /&gt;
	local rcData = renderRunesCrafted(actions,diary,raiments)&lt;br /&gt;
	&lt;br /&gt;
	-- Render the components&lt;br /&gt;
	local summary = renderSummary(essenceType, actions, trips, tripSeconds, rcData[&amp;#039;totalGP&amp;#039;])&lt;br /&gt;
	local arTable = renderActionsRequired(actions,diary,raiments)&lt;br /&gt;
	local ptTable = renderPouchTrips(trips, tripSeconds, essenceType)&lt;br /&gt;
	local rcTable = rcData[&amp;#039;hTable&amp;#039;]&lt;br /&gt;
	&lt;br /&gt;
	-- Combine the tables&lt;br /&gt;
	return finalRender(summary, arTable, ptTable, rcTable)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Gets the total number of actions at each block level to achieve the goal&lt;br /&gt;
function getActions(currentXP, targetXP)&lt;br /&gt;
	-- Track the total amount of XP gained&lt;br /&gt;
	local totalXP = 0&lt;br /&gt;
&lt;br /&gt;
	-- The number of actions at each block to achieve our goal&lt;br /&gt;
	local totalActions = 0&lt;br /&gt;
	local actions = {&lt;br /&gt;
		0,&lt;br /&gt;
		0,&lt;br /&gt;
		0,&lt;br /&gt;
		0,&lt;br /&gt;
		0,&lt;br /&gt;
		0,&lt;br /&gt;
		0,&lt;br /&gt;
		0,&lt;br /&gt;
		0,&lt;br /&gt;
		0,&lt;br /&gt;
		0,&lt;br /&gt;
	}&lt;br /&gt;
	&lt;br /&gt;
	-- Make sure we&amp;#039;re positive on XP&lt;br /&gt;
	local blockXP = getBlockXP(currentXP, targetXP)&lt;br /&gt;
	if blockXP[&amp;#039;xp&amp;#039;] &amp;lt; 0 then&lt;br /&gt;
		return {&lt;br /&gt;
			xp = 0,&lt;br /&gt;
			actions = actions,&lt;br /&gt;
			totalActions = 0&lt;br /&gt;
		}&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Get the number of actions at each block to gain the goal XP&lt;br /&gt;
	local currentBlock = blockXP[&amp;#039;currentBlock&amp;#039;]&lt;br /&gt;
	local xpRate = 0&lt;br /&gt;
	for i=currentBlock, 10, 1 do&lt;br /&gt;
		-- Limit the amount of XP needed to the original goal&lt;br /&gt;
		local xp = math.min(targetXP - currentXP, blockXP[&amp;#039;xp&amp;#039;])&lt;br /&gt;
		&lt;br /&gt;
		-- We have some XP left, so update the actions for this block&lt;br /&gt;
		if xp &amp;gt; 0 then&lt;br /&gt;
			xpRate = data[currentBlock][1]&lt;br /&gt;
			local blockActions = math.ceil(xp / xpRate)&lt;br /&gt;
			actions[currentBlock] = actions[currentBlock] + blockActions&lt;br /&gt;
			totalActions = totalActions + blockActions&lt;br /&gt;
			local xpGain = math.floor(blockActions * xpRate)&lt;br /&gt;
			totalXP = totalXP + xpGain&lt;br /&gt;
			currentXP = currentXP + xpGain&lt;br /&gt;
		else&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		-- We have more XP to grab&lt;br /&gt;
		if currentXP &amp;lt; targetXP then&lt;br /&gt;
			blockXP = getBlockXP(currentXP, targetXP)&lt;br /&gt;
			currentBlock = blockXP[&amp;#039;currentBlock&amp;#039;]&lt;br /&gt;
			xpRate = data[currentBlock][1]&lt;br /&gt;
		else&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Get the actions remaining in the current block to achieve the goal (for 99+)&lt;br /&gt;
	local remainingXP = math.max(targetXP - currentXP, 0)&lt;br /&gt;
	if remainingXP &amp;gt; 0 then&lt;br /&gt;
		xpRate = data[currentBlock][1]&lt;br /&gt;
		local blockActions = math.ceil(remainingXP / xpRate)&lt;br /&gt;
		local xpGain = math.floor(blockActions * xpRate)&lt;br /&gt;
		totalXP = totalXP + xpGain&lt;br /&gt;
		currentXP = currentXP + xpGain&lt;br /&gt;
		actions[currentBlock] = actions[currentBlock] + blockActions&lt;br /&gt;
		totalActions = totalActions + blockActions&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	return {&lt;br /&gt;
		xp = totalXP,&lt;br /&gt;
		actions = actions,&lt;br /&gt;
		totalActions = totalActions&lt;br /&gt;
	}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Gets the XP required to get to the next ZMI block (every 10 levels, or 99), or the target XP&lt;br /&gt;
function getBlockXP(startingXP, targetXP)&lt;br /&gt;
	if targetXP &amp;lt;= startingXP then&lt;br /&gt;
		return { xp = -1 }&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local currentLevel = experience.level_at_xp_unr({args = {startingXP}})&lt;br /&gt;
	local targetLevel = experience.level_at_xp_unr({args = {targetXP}})&lt;br /&gt;
	&lt;br /&gt;
	-- Get the amount of XP to the end of the current block. Level 99 is last block&lt;br /&gt;
	local currentBlock = 10 -- Level 99&lt;br /&gt;
	local blockLevel = 0&lt;br /&gt;
	if currentLevel &amp;lt; 99 then&lt;br /&gt;
		currentBlock = math.floor(currentLevel / 10)&lt;br /&gt;
		blockLevel = math.min((currentBlock + 1) * 10, 99)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- There is still XP remaining to get to the next block&lt;br /&gt;
	local xp = 0&lt;br /&gt;
	if blockLevel &amp;gt; currentLevel then&lt;br /&gt;
		xp = experience.xp_at_level_unr({args = {blockLevel}}) - startingXP&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- There is no amount of XP that will get us to the next block&lt;br /&gt;
	return { currentBlock = currentBlock + 1, xp = xp }&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Gets the total number of trips at each pouch level to get the target XP&lt;br /&gt;
function getTrips(startingXP, targetXP, colossal)&lt;br /&gt;
	if targetXP &amp;lt;= startingXP then&lt;br /&gt;
		return {&lt;br /&gt;
			totalTrips = 0,&lt;br /&gt;
			trips = {&lt;br /&gt;
				0,&lt;br /&gt;
				0,&lt;br /&gt;
				0,&lt;br /&gt;
				0,&lt;br /&gt;
				0,&lt;br /&gt;
			}&lt;br /&gt;
		}&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Track the trips&lt;br /&gt;
	local totalTrips = 0&lt;br /&gt;
	local trips = {&lt;br /&gt;
		0,&lt;br /&gt;
		0,&lt;br /&gt;
		0,&lt;br /&gt;
		0,&lt;br /&gt;
		0,&lt;br /&gt;
	}&lt;br /&gt;
	&lt;br /&gt;
	-- Get the lowest level pouch that can be used&lt;br /&gt;
	local minimumPouchLevel = experience.level_at_xp({args = {startingXP}})&lt;br /&gt;
	minimumPouchLevel = math.floor(minimumPouchLevel / 25) + 1&lt;br /&gt;
	if experience.level_at_xp({args = {startingXP}}) &amp;gt; 84 and colossal then&lt;br /&gt;
		minimumPouchLevel = 5&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- 4 if the colossal pouch is turned of, 5 otherwise&lt;br /&gt;
	local highestPouchLevel = 4 + (colossal and 1 or 0)&lt;br /&gt;
	&lt;br /&gt;
	-- Run through each pouch level, excluding the colossal/giant pouch&lt;br /&gt;
	local currentXP = startingXP&lt;br /&gt;
	for i=minimumPouchLevel, highestPouchLevel, 1 do&lt;br /&gt;
		local pouchTrips = getPouchTrips(currentXP, targetXP, colossal)&lt;br /&gt;
		trips[i] = trips[i] + pouchTrips[&amp;#039;trips&amp;#039;]&lt;br /&gt;
		totalTrips = totalTrips + pouchTrips[&amp;#039;trips&amp;#039;]&lt;br /&gt;
		currentXP = pouchTrips[&amp;#039;xp&amp;#039;]&lt;br /&gt;
		&lt;br /&gt;
		if currentXP == targetXP then&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	if currentXP &amp;lt; targetXP then&lt;br /&gt;
		if 1 == 1 then&lt;br /&gt;
			return &amp;#039;ERORR&amp;#039;&lt;br /&gt;
		end&lt;br /&gt;
		local pouchTrips = getPouchTrips(currentXP, targetXP, colossal)&lt;br /&gt;
		trips[highestPouchLevel] = trips[highestPouchLevel] + pouchTrips[&amp;#039;trips&amp;#039;]&lt;br /&gt;
		totalTrips = totalTrips + pouchTrips[&amp;#039;trips&amp;#039;]&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	return {&lt;br /&gt;
		trips = trips,&lt;br /&gt;
		totalTrips = totalTrips,&lt;br /&gt;
	}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Gets the trips required to get to the next pouch. This will slightly overestimate&lt;br /&gt;
-- the number of trips, due to the fact that it does not track XP on a per-essence&lt;br /&gt;
-- basis, but rather treats all actions within a given trip at the same XP rate.&lt;br /&gt;
-- In the worst case, this will miscalculate by 28 - 53 XP (depending on level).&lt;br /&gt;
-- However, this is minor (&amp;lt;1%) for levels 20+ and negligable (&amp;lt;0.1%) for 40+. &lt;br /&gt;
function getPouchTrips(startingXP, targetXP, colossal)&lt;br /&gt;
	local currentLevel = experience.level_at_xp({args = {startingXP}})&lt;br /&gt;
	&lt;br /&gt;
	-- Get the current and next pouch details&lt;br /&gt;
	local pouchLevel = math.floor(currentLevel / 25) + 1&lt;br /&gt;
	if experience.level_at_xp({args = {startingXP}}) &amp;gt; 84 and colossal then&lt;br /&gt;
		pouchLevel = 5&lt;br /&gt;
	end&lt;br /&gt;
	local pouch = pouches[pouchLevel]&lt;br /&gt;
	local currentCapacity = pouch[2]&lt;br /&gt;
	local nextPouchLevel = pouches[pouchLevel + 1][1]&lt;br /&gt;
	if pouchLevel == 4 and not colossal then&lt;br /&gt;
		nextPouchLevel = pouches[6][1]&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Track the trips and xp needed&lt;br /&gt;
	local trips = 0&lt;br /&gt;
	&lt;br /&gt;
	-- Calculate the number of actions for each level&lt;br /&gt;
	local remainingXP = targetXP - startingXP&lt;br /&gt;
	local currentXP = startingXP&lt;br /&gt;
	while currentLevel &amp;lt; nextPouchLevel do&lt;br /&gt;
		-- Get the block xp for the current level&lt;br /&gt;
		local currentBlock = math.floor(currentLevel / 10) + 1&lt;br /&gt;
		if currentLevel == 99 then&lt;br /&gt;
			currentBlock = 11&lt;br /&gt;
		end&lt;br /&gt;
		local xpRate = data[currentBlock][1]&lt;br /&gt;
		&lt;br /&gt;
		-- Get the xp needed to progress&lt;br /&gt;
		local nextLevelXP = experience.xp_at_level_unr({args = {currentLevel + 1}})&lt;br /&gt;
		-- For 99+, xp to next level no longer matters&lt;br /&gt;
		if currentLevel == 99 then&lt;br /&gt;
			nextLevelXP = targetXP&lt;br /&gt;
		end&lt;br /&gt;
		local nextXP = math.min(nextLevelXP, targetXP) - currentXP&lt;br /&gt;
		&lt;br /&gt;
		-- Get the trips needed to progress&lt;br /&gt;
		local currentTrips = math.ceil(nextXP / (xpRate * currentCapacity))&lt;br /&gt;
		trips = trips + currentTrips&lt;br /&gt;
		&lt;br /&gt;
		-- Update current and remaining XP&lt;br /&gt;
		local xp = math.ceil(currentTrips * currentCapacity * xpRate)&lt;br /&gt;
		remainingXP = remainingXP - xp&lt;br /&gt;
		remainingXP = math.max(remainingXP, 0)&lt;br /&gt;
		currentXP = currentXP + xp&lt;br /&gt;
		currentXP = math.min(currentXP, targetXP)&lt;br /&gt;
		&lt;br /&gt;
		-- Update current level&lt;br /&gt;
		local nextCurrentLevel = experience.level_at_xp_unr({args = {currentXP}})&lt;br /&gt;
		if nextCurrentLevel &amp;lt; currentLevel then&lt;br /&gt;
			return &amp;#039;ERROR: Broken trip level predicate&amp;#039;&lt;br /&gt;
		elseif nextCurrentLevel == currentLevel then&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
		currentLevel = nextCurrentLevel&lt;br /&gt;
		&lt;br /&gt;
		if remainingXP == 0 then&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	return {&lt;br /&gt;
		trips = trips,&lt;br /&gt;
		xp = currentXP,&lt;br /&gt;
	}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Formats the number of seconds into a useful time string&lt;br /&gt;
function formatTime(seconds, asText)&lt;br /&gt;
	local hours = math.floor(seconds / 3600)&lt;br /&gt;
	local minutes = math.floor(seconds / 60) % 60&lt;br /&gt;
	local fSeconds = seconds % 60&lt;br /&gt;
	&lt;br /&gt;
	if asText == true then&lt;br /&gt;
		if hours &amp;gt; 1 then&lt;br /&gt;
			return &amp;#039;about &amp;#039; .. commas(hours) .. &amp;#039; hours&amp;#039;&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		minutes = minutes + (hours * 60)&lt;br /&gt;
		if minutes &amp;gt; 1 then&lt;br /&gt;
			return &amp;#039;about &amp;#039; .. commas(minutes) .. &amp;#039; minutes&amp;#039;&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		if seconds == 1 then&lt;br /&gt;
			return &amp;#039;1 second&amp;#039;&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		return commas(seconds) .. &amp;#039; seconds&amp;#039;&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	return string.format(&amp;#039;%02d:%02d:%02d&amp;#039;, hours, minutes, fSeconds)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Renders the summary paragraph&lt;br /&gt;
function renderSummary(essenceType, actions, trips, tripSeconds, totalGP)&lt;br /&gt;
	local totalDuration = tripSeconds * trips[&amp;#039;totalTrips&amp;#039;]&lt;br /&gt;
	local formattedTime = formatTime(totalDuration, true)&lt;br /&gt;
	&lt;br /&gt;
	-- To prevent NAN due to dividing by zero&lt;br /&gt;
	if totalDuration == 0 then&lt;br /&gt;
		totalDuration = 1&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Calculate gp and xp per hour&lt;br /&gt;
	local hours = totalDuration / 3600&lt;br /&gt;
	local hourlyGP = math.floor(totalGP / hours)&lt;br /&gt;
	local hourlyXP = math.floor(actions[&amp;#039;xp&amp;#039;] / hours)&lt;br /&gt;
	&lt;br /&gt;
	local summary = mw.html.create(&amp;#039;p&amp;#039;)&lt;br /&gt;
	summary&lt;br /&gt;
		:wikitext(&lt;br /&gt;
			   &amp;#039;It will take &amp;#039;&lt;br /&gt;
			.. commas(actions[&amp;#039;totalActions&amp;#039;])&lt;br /&gt;
			.. &amp;#039; &amp;#039;&lt;br /&gt;
			.. essenceType:lower()&lt;br /&gt;
			.. &amp;#039; across &amp;#039;&lt;br /&gt;
			.. commas(trips[&amp;#039;totalTrips&amp;#039;])&lt;br /&gt;
			.. &amp;#039; trips to reach your goal. This will take &amp;#039;&lt;br /&gt;
			.. formattedTime&lt;br /&gt;
			.. &amp;#039;. This will earn an average of &amp;#039;&lt;br /&gt;
		)&lt;br /&gt;
		:wikitext(coins(round(hourlyGP)))&lt;br /&gt;
		:wikitext(&amp;#039; per hour and &amp;#039;)&lt;br /&gt;
		:wikitext(round(hourlyXP))&lt;br /&gt;
		:wikitext(&amp;#039; XP per hour.&amp;#039;)&lt;br /&gt;
		:done()&lt;br /&gt;
	&lt;br /&gt;
	return summary&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Renders the Actions Required table&lt;br /&gt;
function renderActionsRequired(actions,diary,raiments)&lt;br /&gt;
	-- Create the table&lt;br /&gt;
	local hTable = mw.html.create(&amp;#039;table&amp;#039;)&lt;br /&gt;
		:addClass(&amp;#039;wikitable sortable sticky-header alternating-rows align-left-1&amp;#039;)&lt;br /&gt;
		:attr(&amp;#039;style&amp;#039;, &amp;#039;text-align:right;float:left;margin-right:1.5em&amp;#039;)&lt;br /&gt;
	&lt;br /&gt;
	-- Add the headers&lt;br /&gt;
	hTable&lt;br /&gt;
		:tag(&amp;#039;caption&amp;#039;):wikitext(&amp;#039;Actions Required&amp;#039;)&lt;br /&gt;
		:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
			:tag(&amp;#039;th&amp;#039;):wikitext(&amp;#039;Level&amp;#039;)&lt;br /&gt;
			:tag(&amp;#039;th&amp;#039;):wikitext(&amp;#039;Actions&amp;#039;)&lt;br /&gt;
			:tag(&amp;#039;th&amp;#039;):wikitext(&amp;#039;XP&amp;#039;)&lt;br /&gt;
			:tag(&amp;#039;th&amp;#039;):wikitext(&amp;#039;GP&amp;#039;)&lt;br /&gt;
		:done()&lt;br /&gt;
		&lt;br /&gt;
	-- Track the total gp&lt;br /&gt;
	local totalGP = 0&lt;br /&gt;
		&lt;br /&gt;
	-- Add the rows&lt;br /&gt;
	for i=1, 11, 1 do&lt;br /&gt;
		-- Calculate the block range&lt;br /&gt;
		local range = &amp;#039;99&amp;#039;&lt;br /&gt;
		if i &amp;lt; 11 then&lt;br /&gt;
			range = ((i - 1) * 10) .. &amp;#039; - &amp;#039; .. ((i * 10) - 1)&lt;br /&gt;
		end&lt;br /&gt;
		&lt;br /&gt;
		-- Calculate the values&lt;br /&gt;
		local blockActions = actions[&amp;#039;actions&amp;#039;][i]&lt;br /&gt;
		local xpRate = data[i][1]&lt;br /&gt;
		local xp = math.floor(blockActions * xpRate)&lt;br /&gt;
		&lt;br /&gt;
		-- Calculate the gp&lt;br /&gt;
		local rowGP = 0&lt;br /&gt;
		local rowDiaryGP = 0&lt;br /&gt;
		for r=1, 14, 1 do&lt;br /&gt;
			local crafted = blockActions * data[i][r + 1]&lt;br /&gt;
			local gp = crafted * gePrices[runes[r][1]] * (1+(diary and runes[r][3]/100 or 0)+raiments/10+((raiments == 4) and 0.2 or 0))&lt;br /&gt;
			rowGP = math.floor(rowGP + gp)&lt;br /&gt;
		end&lt;br /&gt;
		totalGP = totalGP + rowGP&lt;br /&gt;
		&lt;br /&gt;
		-- Add the row&lt;br /&gt;
		if blockActions &amp;gt; 0 then&lt;br /&gt;
			hTable:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
				:tag(&amp;#039;td&amp;#039;):wikitext(range)&lt;br /&gt;
				:tag(&amp;#039;td&amp;#039;):wikitext(commas(blockActions))&lt;br /&gt;
				:tag(&amp;#039;td&amp;#039;):wikitext(commas(xp))&lt;br /&gt;
				:tag(&amp;#039;td&amp;#039;):wikitext(nocoins(rowGP))&lt;br /&gt;
				:done()&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Add the footer&lt;br /&gt;
	hTable:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
		:tag(&amp;#039;th&amp;#039;)&lt;br /&gt;
			:attr(&amp;#039;style&amp;#039;, &amp;#039;text-align:left&amp;#039;)&lt;br /&gt;
			:wikitext(&amp;#039;Total&amp;#039;)&lt;br /&gt;
		:tag(&amp;#039;th&amp;#039;)&lt;br /&gt;
			:attr(&amp;#039;style&amp;#039;, &amp;#039;text-align:right&amp;#039;)&lt;br /&gt;
			:wikitext(commas(actions[&amp;#039;totalActions&amp;#039;]))&lt;br /&gt;
		:tag(&amp;#039;th&amp;#039;)&lt;br /&gt;
			:attr(&amp;#039;style&amp;#039;, &amp;#039;text-align:right&amp;#039;)&lt;br /&gt;
			:wikitext(commas(actions[&amp;#039;xp&amp;#039;]))&lt;br /&gt;
		:tag(&amp;#039;th&amp;#039;)&lt;br /&gt;
			:attr(&amp;#039;style&amp;#039;, &amp;#039;text-align:right&amp;#039;)&lt;br /&gt;
			:wikitext(coins(round(totalGP)))&lt;br /&gt;
		:done()&lt;br /&gt;
	&lt;br /&gt;
	return hTable&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Renders the Pouch Trips table&lt;br /&gt;
function renderPouchTrips(trips, tripSeconds, essenceType)&lt;br /&gt;
	frame = mw.getCurrentFrame()&lt;br /&gt;
	&lt;br /&gt;
	-- Create the table&lt;br /&gt;
	local hTable = mw.html.create(&amp;#039;table&amp;#039;)&lt;br /&gt;
		:addClass(&amp;#039;wikitable sortable sticky-header alternating-rows align-center-1 align-left-2&amp;#039;)&lt;br /&gt;
		:attr(&amp;#039;style&amp;#039;, &amp;#039;text-align:right&amp;#039;)&lt;br /&gt;
	&lt;br /&gt;
	-- Add the headers&lt;br /&gt;
	hTable&lt;br /&gt;
		:tag(&amp;#039;caption&amp;#039;):wikitext(&amp;#039;Trips&amp;#039;)&lt;br /&gt;
		:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
			:tag(&amp;#039;th&amp;#039;):attr(&amp;#039;colspan&amp;#039;, 2):wikitext(&amp;#039;Pouch&amp;#039;)&lt;br /&gt;
			:tag(&amp;#039;th&amp;#039;):wikitext(&amp;#039;[[File:Runecraft_icon.png|link=Runecraft|x21px]]&amp;#039;):done()&lt;br /&gt;
			:tag(&amp;#039;th&amp;#039;):wikitext(frame:preprocess(&amp;#039;[[File:&amp;#039; .. essenceType .. &amp;#039;.png|link=&amp;#039; .. essenceType .. &amp;#039;|x21px]] &amp;lt;ref&amp;gt;Total essence capacity for a single trip.&amp;lt;/ref&amp;gt;&amp;#039;))&lt;br /&gt;
			:tag(&amp;#039;th&amp;#039;):wikitext(&amp;#039;Trips&amp;#039;)&lt;br /&gt;
			:tag(&amp;#039;th&amp;#039;):wikitext(&amp;#039;Time&amp;#039;)&lt;br /&gt;
		:done()&lt;br /&gt;
		&lt;br /&gt;
	-- Add the rows&lt;br /&gt;
	for i=1, 5, 1 do&lt;br /&gt;
		local pouch = pouches[i]&lt;br /&gt;
		local rowTrips = trips[&amp;#039;trips&amp;#039;][i]&lt;br /&gt;
		local formattedTime = formatTime(tripSeconds * rowTrips, false)&lt;br /&gt;
			&lt;br /&gt;
		-- Add the row&lt;br /&gt;
		if rowTrips&amp;gt;0 then&lt;br /&gt;
			hTable:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
				:tag(&amp;#039;td&amp;#039;):wikitext(&amp;#039;[[File:&amp;#039; .. pouch[3] .. &amp;#039; pouch.png|link=&amp;#039; .. pouch[3] .. &amp;#039; pouch|x21px]]&amp;#039;):done()&lt;br /&gt;
				:tag(&amp;#039;td&amp;#039;):wikitext(pouch[3])&lt;br /&gt;
				:tag(&amp;#039;td&amp;#039;):wikitext(pouch[1])&lt;br /&gt;
				:tag(&amp;#039;td&amp;#039;):wikitext(pouch[2])&lt;br /&gt;
				:tag(&amp;#039;td&amp;#039;):wikitext(round(rowTrips))&lt;br /&gt;
				:tag(&amp;#039;td&amp;#039;):wikitext(formattedTime)&lt;br /&gt;
				:done()&lt;br /&gt;
			end&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local totalTime = formatTime(tripSeconds * trips[&amp;#039;totalTrips&amp;#039;], false)&lt;br /&gt;
	&lt;br /&gt;
	-- Add the footer&lt;br /&gt;
	hTable:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
		:tag(&amp;#039;th&amp;#039;)&lt;br /&gt;
			:attr(&amp;#039;colspan&amp;#039;, 4)&lt;br /&gt;
			:attr(&amp;#039;style&amp;#039;, &amp;#039;text-align:left&amp;#039;)&lt;br /&gt;
			:wikitext(&amp;#039;Total&amp;#039;)&lt;br /&gt;
		:tag(&amp;#039;th&amp;#039;)&lt;br /&gt;
			:attr(&amp;#039;style&amp;#039;, &amp;#039;text-align:right&amp;#039;)&lt;br /&gt;
			:wikitext(round(trips[&amp;#039;totalTrips&amp;#039;]))&lt;br /&gt;
		:tag(&amp;#039;th&amp;#039;)&lt;br /&gt;
			:attr(&amp;#039;style&amp;#039;, &amp;#039;text-align:right&amp;#039;)&lt;br /&gt;
			:wikitext(totalTime)&lt;br /&gt;
		:done()&lt;br /&gt;
		&lt;br /&gt;
	-- Wrap the table in a floated div so we can have the reflist underneath it&lt;br /&gt;
	local div = mw.html.create(&amp;#039;div&amp;#039;):attr(&amp;#039;style&amp;#039;, &amp;#039;float:left&amp;#039;)&lt;br /&gt;
		:node(hTable)&lt;br /&gt;
		:wikitext(frame:preprocess(&amp;#039;{{Reflist}}&amp;#039;))&lt;br /&gt;
		:done()&lt;br /&gt;
		&lt;br /&gt;
	return div&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Renders the Runes Crafted table&lt;br /&gt;
function renderRunesCrafted(actions,diary,raiments)&lt;br /&gt;
	frame = mw.getCurrentFrame()&lt;br /&gt;
	&lt;br /&gt;
	-- Create the table&lt;br /&gt;
	local hTable = mw.html.create(&amp;#039;table&amp;#039;)&lt;br /&gt;
		:addClass(&amp;#039;wikitable sortable sticky-header alternating-rows align-center-1 align-left-2&amp;#039;)&lt;br /&gt;
		:attr(&amp;#039;style&amp;#039;, &amp;#039;text-align:right&amp;#039;)&lt;br /&gt;
	&lt;br /&gt;
	-- Add the headers&lt;br /&gt;
	hTable&lt;br /&gt;
		:tag(&amp;#039;caption&amp;#039;):wikitext(&amp;#039;Runes Crafted&amp;#039;)&lt;br /&gt;
		:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
			:tag(&amp;#039;th&amp;#039;):attr(&amp;#039;colspan&amp;#039;, 2):wikitext(&amp;#039;Rune&amp;#039;)&lt;br /&gt;
			:tag(&amp;#039;th&amp;#039;):wikitext(&amp;#039;XP&amp;#039;)&lt;br /&gt;
			:tag(&amp;#039;th&amp;#039;):wikitext(&amp;#039;%&amp;#039;)&lt;br /&gt;
			:tag(&amp;#039;th&amp;#039;):wikitext(&amp;#039;Crafted&amp;#039;)&lt;br /&gt;
			:tag(&amp;#039;th&amp;#039;):wikitext(&amp;#039;GP&amp;#039;)&lt;br /&gt;
		:done()&lt;br /&gt;
		&lt;br /&gt;
	-- We will be adding cells to each rune row showing the relative percent&lt;br /&gt;
	-- of all runes crafted for that rune. As this relies on knowing the total&lt;br /&gt;
	-- crafted, which can&amp;#039;t be known at this time, we will be saving a reference&lt;br /&gt;
	-- to them so we can update later.&lt;br /&gt;
	local runeRatios = {}&lt;br /&gt;
		&lt;br /&gt;
	-- Add the rows&lt;br /&gt;
	local totalCrafted = 0&lt;br /&gt;
	local totalGP = 0&lt;br /&gt;
	for r=1, 14, 1 do&lt;br /&gt;
		local rune = runes[r]&lt;br /&gt;
	&lt;br /&gt;
		-- Calculate the crafted runes&lt;br /&gt;
		local crafted = 0&lt;br /&gt;
		for i=1, 11, 1 do&lt;br /&gt;
			local blockActions = actions[&amp;#039;actions&amp;#039;][i]&lt;br /&gt;
			crafted = crafted + (blockActions * data[i][r + 1]) * (1+(diary and runes[r][3]/100 or 0)+raiments/10+((raiments == 4) and 0.2 or 0))&lt;br /&gt;
		end&lt;br /&gt;
		totalCrafted = totalCrafted + crafted&lt;br /&gt;
		&lt;br /&gt;
		-- Get the prices&lt;br /&gt;
		local gp = crafted * gePrices[rune[1]]&lt;br /&gt;
		totalGP = totalGP + gp&lt;br /&gt;
		&lt;br /&gt;
		-- Keep a reference to the ratio cells so we can update it later&lt;br /&gt;
		local ratioCell = mw.html.create(&amp;#039;td&amp;#039;)&lt;br /&gt;
		table.insert(runeRatios, { ratioCell, crafted })&lt;br /&gt;
			&lt;br /&gt;
		-- Add the row&lt;br /&gt;
		hTable:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
			:tag(&amp;#039;td&amp;#039;):wikitext(&amp;#039;[[File:&amp;#039; .. rune[1] .. &amp;#039;.png|link=&amp;#039; .. rune[1] .. &amp;#039;|x21px]]&amp;#039;):done()&lt;br /&gt;
			:tag(&amp;#039;td&amp;#039;):wikitext(rune[1])&lt;br /&gt;
			:tag(&amp;#039;td&amp;#039;):wikitext(round(crafted * rune[2]))&lt;br /&gt;
			:node(ratioCell)&lt;br /&gt;
			:tag(&amp;#039;td&amp;#039;):wikitext(round(crafted))&lt;br /&gt;
			:tag(&amp;#039;td&amp;#039;):wikitext(nocoins(round(gp)))&lt;br /&gt;
			:done()&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Update the ratios&lt;br /&gt;
	for i=1, 14, 1 do&lt;br /&gt;
		local cell = runeRatios[i][1]&lt;br /&gt;
		local ratio = (runeRatios[i][2] / totalCrafted) * 100&lt;br /&gt;
		&lt;br /&gt;
		cell:wikitext(string.format(&amp;#039;%0.2f%%&amp;#039;, ratio))&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Add the footer&lt;br /&gt;
	hTable:tag(&amp;#039;tr&amp;#039;)&lt;br /&gt;
		:tag(&amp;#039;th&amp;#039;)&lt;br /&gt;
			:attr(&amp;#039;colspan&amp;#039;, 4)&lt;br /&gt;
			:attr(&amp;#039;style&amp;#039;, &amp;#039;text-align:left&amp;#039;)&lt;br /&gt;
			:wikitext(&amp;#039;Total&amp;#039;)&lt;br /&gt;
		:tag(&amp;#039;th&amp;#039;)&lt;br /&gt;
			:attr(&amp;#039;style&amp;#039;, &amp;#039;text-align:right&amp;#039;)&lt;br /&gt;
			:wikitext(round(totalCrafted))&lt;br /&gt;
		:tag(&amp;#039;th&amp;#039;)&lt;br /&gt;
			:attr(&amp;#039;style&amp;#039;, &amp;#039;text-align:right&amp;#039;)&lt;br /&gt;
			:wikitext(coins(round(totalGP)))&lt;br /&gt;
		:done()&lt;br /&gt;
	&lt;br /&gt;
	return {&lt;br /&gt;
		hTable = hTable,&lt;br /&gt;
		totalGP = totalGP,&lt;br /&gt;
	}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function round(num)&lt;br /&gt;
	return commas(string.format(&amp;#039;%.2f&amp;#039;, num))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function finalRender(summary, arTable, ptTable, rcTable)&lt;br /&gt;
	local div = mw.html.create(&amp;#039;div&amp;#039;)&lt;br /&gt;
	&lt;br /&gt;
	div&lt;br /&gt;
		:node(summary)&lt;br /&gt;
		:node(arTable)&lt;br /&gt;
		:node(ptTable)&lt;br /&gt;
		:node(mw.html.create(&amp;#039;br&amp;#039;):attr(&amp;#039;style&amp;#039;, &amp;#039;clear:both&amp;#039;):done())&lt;br /&gt;
		:node(rcTable)&lt;br /&gt;
		:done()&lt;br /&gt;
	&lt;br /&gt;
	return tostring(div)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Alex</name></author>
	</entry>
</feed>