Module:Sandbox/User:Artoire/1: Difference between revisions

From Brighter Shores Wiki
Jump to navigation Jump to search
Content added Content deleted
mNo edit summary
mNo edit summary
 
(12 intermediate revisions by the same user not shown)
Line 3: Line 3:
current_xp = 24,
current_xp = 24,
target_xp = 37,
target_xp = 37,
ingot = 'Maloic Adathril Ingot',
ingot = 'Coarse Deathstone (Etched)',
profession = 'Stonemason', -- 'Bonewright'/'Stonemason'/'Blacksmith'
ore_buy = 'true',
pole_buy = 'none', -- 'none'/'logs'/'pole'
})
})
]]
]]
Line 10: Line 13:
require('Module:Mw.html extension')
require('Module:Mw.html extension')
local xp = require('Module:Experience')
local xp = require('Module:Experience')
local cache = require('Module:Break Isolation').get_module_store('Module:Sandbox/User:Artoire/1')
local currency = require('Module:Currency')
local currency = require('Module:Currency')
local lang = mw.language.getContentLanguage()
local lang = mw.language.getContentLanguage()


local function lookup_ingots()
local _ingots = {}
local function lookup_ingots(profession)
if cache.ingots then return cache.ingots end
if _ingots[profession] then
local ingots = {
return _ingots[profession]
end
_ingots[profession] = {
recipes = {},
recipes = {},
order = {}
order = {}
}
}
cache.ingots = ingots
for _, result in ipairs(mw.smw.ask{
for _, result in ipairs(mw.smw.ask{
({
'[[Uses facility::Goblin Smelter||Gnome Smelter]]',
Blacksmith = '[[Uses facility::Goblin Smelter||Gnome Smelter]]',
Stonemason = '[[Uses facility::T.E.A. Machine]]',
Bonewright = '[[Uses facility::B.R.E.W.S. Vat]]'
})[profession],
['?Recipe JSON'] = '',
['?Recipe JSON'] = '',
mainlabel = '-',
mainlabel = '-',
Line 30: Line 38:
result = mw.text.jsonDecode(result[1])
result = mw.text.jsonDecode(result[1])
local ingot = result.output[1].name
local ingot = result.output[1].name
ingots.recipes[ingot] = {
_ingots[profession].recipes[ingot] = {
xp = result.xp,
xp = result.xp,
facility = result.facility,
facility = result.facility,
Line 37: Line 45:
level = result.level
level = result.level
}
}
table.insert(ingots.order, ingot)
table.insert(_ingots[profession].order, ingot)
end
end
return ingots
return _ingots[profession]
end
end


local function lookup_weapons()
local function lookup_weapons(profession)
local ingots = lookup_ingots(profession)
if cache.weapons then return cache.weapons end
local ingots = lookup_ingots()
local weapons = {}
local weapons = {}
cache.weapons = weapons
for _, result in ipairs(mw.smw.ask{
for _, result in ipairs(mw.smw.ask{
({
'[[Uses facility::Goblin Forge||Gnome Forge]]',
Blacksmith = '[[Uses facility::Goblin Forge||Gnome Forge]]',
Stonemason = '[[Category:Stonemason]][[Category:Pages with recipes]][[Uses facility::!T.E.A. Machine]]',
Bonewright = '[[Category:Bonewright]][[Category:Pages with recipes]][[Uses facility::!B.R.E.W.S. Vat]]'
})[profession],
['?Recipe JSON'] = '',
['?Recipe JSON'] = '',
mainlabel = '-',
mainlabel = '-',
Line 56: Line 66:
local lvl = result['Profession Level A']
local lvl = result['Profession Level A']
result = mw.text.jsonDecode(result[1])
result = mw.text.jsonDecode(result[1])
if not result.passive and result.profession == profession then
local ingot
local pole
local ingot
local pole
for _, material in ipairs(result.materials) do
if ingots.recipes[material.name] then
for _, material in ipairs(result.materials) do
if ingots.recipes[material.name] then
assert(not ingot)
ingot = material
assert(not ingot)
ingot = material
else
else
assert(not pole)
pole = material
assert(not pole)
pole = material
end
end
end
weapons[ingot.name] = weapons[ingot.name] or {}
table.insert(weapons[ingot.name], {
facility = result.facility,
level = result.level,
ingot = ingot,
pole = pole,
name = result.output[1].name,
xp = result.xp
})
end
end
weapons[ingot.name] = weapons[ingot.name] or {}
table.insert(weapons[ingot.name], {
facility = result.facility,
level = result.level,
ingot = ingot,
pole = pole,
name = result.output[1].name,
xp = result.xp
})
end
end
return weapons
return weapons
Line 81: Line 93:


local function _lookup_price_uncached(item)
local function _lookup_price_uncached(item)
local result = tonumber(mw.smw.ask{
local result = mw.smw.ask{
('[[Sold item::%s]]'):format(item),
('[[Sold item::%s]]'):format(item),
['?Shop buy price'] = '',
['?Shop buy price'] = 'buy',
['?Shop sell price'] = 'sell',
mainlabel = '-'
mainlabel = '-'
}[1][1])
}[1]
return result or -1
return {
buy = tonumber(result.buy),
sell = tonumber(result.sell)
}
end
end


local _lookup_price_cache = {}
cache.lookup_price = cache.lookup_price or {}
local function lookup_price(item)
local function lookup_price(item)
cache.lookup_price[item] = cache.lookup_price[item] or _lookup_price_uncached(item)
_lookup_price_cache[item] = _lookup_price_cache[item] or _lookup_price_uncached(item)
return cache.lookup_price[item]
return _lookup_price_cache[item]
end
end


Line 115: Line 131:
end
end


local _lookup_pole_cache = {}
cache.lookup_pole = cache.lookup_pole or {}
local function lookup_pole(pole)
local function lookup_pole(pole)
cache.lookup_pole[pole] = cache.lookup_pole[pole] or _lookup_pole_uncached(pole)
_lookup_pole_cache[pole] = _lookup_pole_cache[pole] or _lookup_pole_uncached(pole)
return cache.lookup_pole[pole]
return _lookup_pole_cache[pole]
end
end


Line 132: Line 148:
end
end


local _lookup_ore_cache = {}
cache.lookup_ore = cache.lookup_ore or {}
local function lookup_ore(ore)
local function lookup_ore(ore)
cache.lookup_ore[ore] = cache.lookup_ore[ore] or _lookup_ore_uncached(ore)
_lookup_ore_cache[ore] = _lookup_ore_cache[ore] or _lookup_ore_uncached(ore)
return cache.lookup_ore[ore]
return _lookup_ore_cache[ore]
end
end


local function _lookup_log_uncached(log)
local function _lookup_log_uncached(log)
local data = mw.smw.ask{
local data = mw.smw.ask{
('[[%s]]'):format(ore),
('[[%s]]'):format(log),
['?Profession Level A'] = '',
['?Profession Level A'] = '',
mainlabel = '-'
mainlabel = '-'
Line 149: Line 165:
end
end


local _lookup_log_cache = {}
cache.lookup_log = cache.lookup_log or {}
local function lookup_log(log)
local function lookup_log(log)
cache.lookup_log[log] = cache.lookup_log[log] or _lookup_log_uncached(log)
_lookup_log_cache[log] = _lookup_log_cache[log] or _lookup_log_uncached(log)
return cache.lookup_log[log]
return _lookup_log_cache[log]
end
end


Line 159: Line 175:
local p = {}
local p = {}


function p.ingots()
function p.ingots(frame)
return table.concat(lookup_ingots().order, ',')
return table.concat(lookup_ingots(frame.args.profession).order, ',')
end
end


Line 166: Line 182:
return p._main(frame:getParent().args)
return p._main(frame:getParent().args)
end
end

p.w = lookup_weapons


function p._main(args)
function p._main(args)
local profession = args.profession

local current_xp = tonumber(args.current_xp) or 0
local current_xp = tonumber(args.current_xp) or 0
local current_lvl
local current_lvl
Line 189: Line 205:


local remaining_xp = target_xp - current_xp
local remaining_xp = target_xp - current_xp

local buying_ores = args.ore_buy ~= 'false'


local buying_poles = args.pole_buy
local buying_poles = args.pole_buy
Line 195: Line 213:
buying_poles = not (buying_logs or chopping_logs)
buying_poles = not (buying_logs or chopping_logs)


local ingot = lookup_ingots().recipes[args.ingot]
local ingot = lookup_ingots(profession).recipes[args.ingot]


local result = mw.html.create()
local result = mw.html.create()
Line 210: Line 228:
local colspan = 23
local colspan = 23
if chopping_logs then
if chopping_logs then
colspan = colspan + 4
colspan = colspan + 1
end
end
if chopping_logs or buying_logs then
if chopping_logs or buying_logs then
colspan = colspan + 1
colspan = colspan + 3
end
end


tbl
tbl
:tag('tr')
:tag('tr')
:th(skillclickpic('Miner')):done()
:th(skillclickpic(({
Blacksmith = 'Miner',
:th{ 'Ore', attr = { colspan = '3' } }:done()
Stonemason = 'Miner',
:th{ 'Ingot', attr = { colspan = '2' } }:done()
Bonewright = 'Gatherer'
})[profession])):done()
:th{ ({
Blacksmith = 'Ore',
Stonemason = 'Rock',
Bonewright = 'Bone'
})[profession], attr = { colspan = '3' } }:done()
:th{ ({
Blacksmith = 'Ingot',
Stonemason = 'Etched',
Bonewright = 'Brewed'
})[profession], attr = { colspan = '2' } }:done()
:IF(chopping_logs)
:IF(chopping_logs)
:th(skillclickpic('Woodcutter')):done()
:th(skillclickpic('Woodcutter')):done()
:th{ 'Log', attr = { colspan = '3' } }:done()
:END()
:END()
:IF(chopping_logs or buying_logs)
:IF(chopping_logs or buying_logs)
:th{ 'Log', attr = { colspan = '3' } }:done()
:th(skillclickpic('Carpenter')):done()
:th(skillclickpic('Carpenter')):done()
:END()
:END()
:th{ 'Pole', attr = { colspan = '3' } }:done()
:th{ 'Pole', attr = { colspan = '3' } }:done()
:th(skillclickpic('Blacksmith')):done()
:th(skillclickpic(profession)):done()
:th{ 'Product', attr = { colspan = '3' } }:done()
:th{ 'Product', attr = { colspan = '3' } }:done()
:th{ 'Cost', attr = { colspan = '10' } }:done()
:th{ 'Cost', attr = { colspan = '10' } }:done()
:done()
:done()


for _, weapon in ipairs(lookup_weapons()[ingot.ingot]) do
for _, weapon in ipairs(lookup_weapons(profession)[ingot.ingot]) do
if not ingot.xp then
if not ingot.xp then
tbl
tbl
:tr()
:tr()
:td{
:td{
'Missing XP for smelting [[' .. ingot.ingot .. ']]; Please [' .. mw.uri.fullUrl(ingot.ingot, 'action=edit&section=1') .. ' edit the page] to add the experience earned (after |exp =)',
'Missing XP for preparing [[' .. ingot.ingot .. ']]; Please [' .. tostring(mw.uri.fullUrl(ingot.ingot, 'action=edit&section=1')) .. ' edit the page] to add the experience earned (after |exp =)',
attr = { colspan = colspan }
attr = { colspan = colspan }
}
}
Line 249: Line 279:
:tr()
:tr()
:td{
:td{
'Missing XP for creating [[' .. weapon.name .. ']]; Please ['.. mw.uri.fullUrl(weapon.name, 'action=edit&section=1') .. ' edit the page] to add the experience earned (after |exp =)',
'Missing XP for creating [[' .. weapon.name .. ']]; Please ['.. tostring(mw.uri.fullUrl(weapon.name, 'action=edit&section=1')) .. ' edit the page] to add the experience earned (after |exp =)',
attr = { colspan = colspan }
attr = { colspan = colspan }
}
}
:done()
:done()
break
else
local xp_per = weapon.xp + ingot.xp * weapon.ingot.quantity
end
local needed = math.ceil(remaining_xp / xp_per)
local xp_per = weapon.xp + ingot.xp * weapon.ingot.quantity
local needed = math.ceil(remaining_xp / xp_per)


local needed_ores = needed * weapon.ingot.quantity
local needed_ores = needed * weapon.ingot.quantity
local row = tbl
local row = tbl
:tag('tr')
:tag('tr')
:td(lookup_ore(ingot.ore).level):done()
:td(lookup_ore(ingot.ore).level):done()
:td{ needed_ores .. '×', css = { ['border-right'] = '0', ['padding-right'] = '0', ['text-align'] = 'right', ['data-sort-value'] = needed_ores } }:done()
:td{ needed_ores .. '×', css = { ['border-right'] = '0', ['padding-right'] = '0', ['text-align'] = 'right', ['data-sort-value'] = needed_ores } }:done()
:td{ ('[[File:%s.png|link=%s|30px]]'):format(ingot.ore, ingot.ore), css = { ['border-left'] = '0', ['padding-left'] = '0' }, addClass = 'plinkt-image no-border' }:done()
:td{ ('[[File:%s.png|link=%s|30px]]'):format(ingot.ore, ingot.ore), css = { ['border-left'] = '0', ['padding-left'] = '0' }, addClass = 'plinkt-image no-border' }:done()
:td{ '[[' .. ingot.ore .. ']]', addClass = 'plinkt-link no-border' }:done()
:td{ '[[' .. ingot.ore .. ']]', addClass = 'plinkt-link no-border' }:done()
:td{ ('[[File:%s.png|link=%s|30px]]'):format(ingot.ingot, ingot.ingot), css = { ['border-left'] = '0', ['padding-left'] = '0' }, addClass = 'plinkt-image no-border', ['data-sort-value'] = needed_ores }:done()
:td{ ('[[File:%s.png|link=%s|30px]]'):format(ingot.ingot, ingot.ingot), css = { ['border-left'] = '0', ['padding-left'] = '0' }, addClass = 'plinkt-image no-border', ['data-sort-value'] = needed_ores }:done()
:td{ '[[' .. ingot.ingot .. ']]', addClass = 'plinkt-link no-border' }:done()
:td{ '[[' .. ingot.ingot .. ']]', addClass = 'plinkt-link no-border' }:done()


local pole_info
local pole_info
local needed_logs, needed_poles
local needed_logs, needed_poles

if weapon.pole then
needed_poles = needed * weapon.pole.quantity
if chopping_logs or buying_logs then
pole_info = lookup_pole(weapon.pole.name)
needed_logs = math.ceil(needed_poles / 2)
end
if chopping_logs then
row
:td(lookup_log(pole_info.log).level):done()
end
if chopping_logs or buying_logs then
row
:td{ needed_logs .. '×', css = { ['border-right'] = '0', ['padding-right'] = '0', ['text-align'] = 'right' }, attr = { ['data-sort-value'] = needed_logs } }:done()
:td{ ('[[File:%s.png|link=%s|30px]]'):format(pole_info.log, pole_info.log), css = { ['border-left'] = '0', ['padding-left'] = '0' }, addClass = 'plinkt-image no-border' }:done()
:td{ '[[' .. pole_info.log .. ']]', addClass = 'plinkt-link no-border' }:done()
:td(pole_info.level):done()
end


if weapon.pole then
needed_poles = needed * weapon.pole.quantity
if chopping_logs then
pole_info = lookup_pole(weapon.pole.name)
needed_logs = math.ceil(needed_poles / 2)
row
row
:td{ needed_poles .. '×', css = { ['border-right'] = '0', ['padding-right'] = '0', ['text-align'] = 'right' }, attr = { ['data-sort-value'] = needed_poles } }:done()
:td(lookup_log(pole_info.log).level):done()
:td{ needed_logs .. '×', css = { ['border-right'] = '0', ['padding-right'] = '0', ['text-align'] = 'right' }, attr = { ['data-sort-value'] = needed_logs } }:done()
:td{ ('[[File:%s.png|link=%s|30px]]'):format(weapon.pole.name, weapon.pole.name), css = { ['border-left'] = '0', ['padding-left'] = '0' }, addClass = 'plinkt-image no-border' }:done()
:td{ ('[[File:%s.png|link=%s|30px]]'):format(pole_info.log, pole.log), css = { ['border-left'] = '0', ['padding-left'] = '0' }, addClass = 'plinkt-image no-border' }:done()
:td{ '[[' .. weapon.pole.name .. ']]', addClass = 'plinkt-link no-border' }:done()
else
:td{ '[[' .. pole_info.log .. ']]', addClass = 'plinkt-link no-border' }:done()
if chopping_logs then
end
row
if chopping_logs or buying_logs then
:na()
pole_info = pole_info or lookup_pole(weapon.pole.name)
end
if chopping_logs or buying_logs then
row
:td{ '<small>N/A</small>', addClass = 'table-na', attr = { ['data-sort-value'] = '0', colspan = '3' } }:done()
:na()
end
row
row
:td{ '<small>N/A</small>', addClass = 'table-na', attr = { ['data-sort-value'] = '0', colspan = '3' } }:done()
:td(pole_info.level):done()
end
end


row
row
:td(weapon.level)
:td{ needed_poles .. '&times;', css = { ['border-right'] = '0', ['padding-right'] = '0', ['text-align'] = 'right' }, attr = { ['data-sort-value'] = needed_poles } }:done()
:td{ ('[[File:%s.png|link=%s|30px]]'):format(weapon.pole.name, weapon.pole.name), css = { ['border-left'] = '0', ['padding-left'] = '0' }, addClass = 'plinkt-image no-border' }:done()
:td{ needed .. '&times;', css = { ['border-right'] = '0', ['padding-right'] = '0', ['text-align'] = 'right' }, attr = { ['data-sort-value'] = needed } }:done()
:td{ '[[' .. weapon.pole.name .. ']]', addClass = 'plinkt-link no-border' }:done()
:td{ ('[[File:%s.png|link=%s|30px]]'):format(weapon.name, weapon.name), css = { ['border-left'] = '0', ['padding-left'] = '0' }, addClass = 'plinkt-image no-border' }:done()
:td{ '[[' .. weapon.name .. ']]', addClass = 'plinkt-link no-border' }
else

local cost = needed_ores * lookup_price(ingot.ore)[buying_ores and 'buy' or 'sell']
if chopping_logs then
if chopping_logs then
if needed_logs then
row
cost = cost + needed_logs * lookup_price(pole_info.log).sell
:na()
end
:td{ '<small>N/A</small>', addClass = 'table-na', attr = { ['data-sort-value'] = '0', colspan = '3' } }:done()
elseif buying_logs then
if needed_logs then
cost = cost + needed_logs * lookup_price(pole_info.log).buy
end
else
if weapon.pole then
cost = cost + needed_poles * lookup_price(weapon.pole.name).buy
end
end
end
if chopping_logs or buying_logs then
row
:na()
end
row
:td{ '<small>N/A</small>', addClass = 'table-na', attr = { ['data-sort-value'] = '0', colspan = '3' } }:done()
end


row:node(currency._cell(cost, { html = 'yes' }))
row
:td(weapon.level)
:td{ needed .. '&times;', css = { ['border-right'] = '0', ['padding-right'] = '0', ['text-align'] = 'right' }, attr = { ['data-sort-value'] = needed } }:done()
:td{ ('[[File:%s.png|link=%s|30px]]'):format(weapon.name, weapon.name), css = { ['border-left'] = '0', ['padding-left'] = '0' }, addClass = 'plinkt-image no-border' }:done()
:td{ '[[' .. weapon.name .. ']]', addClass = 'plinkt-link no-border' }

local cost = needed_ores * lookup_price(ingot.ore)
if chopping_logs then
-- No extra cost
elseif buying_logs then
if needed_logs then
cost = cost + needed_logs * lookup_price(pole_info.log)
end
else
if weapon.pole then
cost = cost + needed_poles * lookup_price(weapon.pole.name)
end
end
end

row:node(currency._cell(cost, { html = 'yes' }))
end
end



Latest revision as of 20:30, 21 December 2024

Documentation for this module may be created at Module:Sandbox/User:Artoire/1/doc

--[[
mw.log(p._main{
	current_xp = 24,
	target_xp = 37,
	ingot = 'Coarse Deathstone (Etched)',
	profession = 'Stonemason',  -- 'Bonewright'/'Stonemason'/'Blacksmith'
	ore_buy = 'true',
	pole_buy = 'none',  -- 'none'/'logs'/'pole'
})
]]

require('strict')
require('Module:Mw.html extension')
local xp = require('Module:Experience')
local currency = require('Module:Currency')
local lang = mw.language.getContentLanguage()

local _ingots = {}
local function lookup_ingots(profession)
	if _ingots[profession] then
		return _ingots[profession]
	end
	_ingots[profession] = {
		recipes = {},
		order = {}
	}
	for _, result in ipairs(mw.smw.ask{
		({
			Blacksmith = '[[Uses facility::Goblin Smelter||Gnome Smelter]]',
			Stonemason = '[[Uses facility::T.E.A. Machine]]',
			Bonewright = '[[Uses facility::B.R.E.W.S. Vat]]'
		})[profession],
		['?Recipe JSON'] = '',
		mainlabel = '-',
		sort = 'Profession Level A',
		limit = '500'
	}) do
		result = mw.text.jsonDecode(result[1])
		local ingot = result.output[1].name
		_ingots[profession].recipes[ingot] = {
			xp = result.xp,
			facility = result.facility,
			ingot = result.output[1].name,
			ore = result.materials[1].name,
			level = result.level
		}
		table.insert(_ingots[profession].order, ingot)
	end
	return _ingots[profession]
end

local function lookup_weapons(profession)
	local ingots = lookup_ingots(profession)
	local weapons = {}
	for _, result in ipairs(mw.smw.ask{
		({
			Blacksmith = '[[Uses facility::Goblin Forge||Gnome Forge]]',
			Stonemason = '[[Category:Stonemason]][[Category:Pages with recipes]][[Uses facility::!T.E.A. Machine]]',
			Bonewright = '[[Category:Bonewright]][[Category:Pages with recipes]][[Uses facility::!B.R.E.W.S. Vat]]'
		})[profession],
		['?Recipe JSON'] = '',
		mainlabel = '-',
		sort = 'Profession Level A',
		limit = '500'
	}) do
		local lvl = result['Profession Level A']
		result = mw.text.jsonDecode(result[1])
		if not result.passive and result.profession == profession then
			local ingot
			local pole
			for _, material in ipairs(result.materials) do
				if ingots.recipes[material.name] then
					assert(not ingot)
					ingot = material
				else
					assert(not pole)
					pole = material
				end
			end
			weapons[ingot.name] = weapons[ingot.name] or {}
			table.insert(weapons[ingot.name], {
				facility = result.facility,
				level = result.level,
				ingot = ingot,
				pole = pole,
				name = result.output[1].name,
				xp = result.xp
			})
		end
	end
	return weapons
end

local function _lookup_price_uncached(item)
	local result = mw.smw.ask{
		('[[Sold item::%s]]'):format(item),
		['?Shop buy price'] = 'buy',
		['?Shop sell price'] = 'sell',
		mainlabel = '-'
	}[1]
	return {
		buy = tonumber(result.buy),
		sell = tonumber(result.sell)
	}
end

local _lookup_price_cache = {}
local function lookup_price(item)
	_lookup_price_cache[item] = _lookup_price_cache[item] or _lookup_price_uncached(item)
	return _lookup_price_cache[item]
end

local function _lookup_pole_uncached(pole)
	local pole_data = mw.text.jsonDecode(mw.smw.ask{
		('[[%s]]'):format(pole),
		['?Recipe JSON'] = '',
		mainlabel = '-'
	}[1][1])
	local post = pole_data.materials[1].name
	local post_data = mw.text.jsonDecode(mw.smw.ask{
		('[[%s]]'):format(post),
		['?Recipe JSON'] = '',
		mainlabel = '-'
	}[1][1])
	return {
		level = pole_data.level,
		pole = pole,
		post = post,
		log = post_data.materials[1].name
	}
end

local _lookup_pole_cache = {}
local function lookup_pole(pole)
	_lookup_pole_cache[pole] = _lookup_pole_cache[pole] or _lookup_pole_uncached(pole)
	return _lookup_pole_cache[pole]
end

local function _lookup_ore_uncached(ore)
	local data = mw.smw.ask{
		('[[%s]]'):format(ore),
		['?Profession Level A'] = '',
		mainlabel = '-'
	}[1]
	return {
		level = tonumber(data[1])
	}
end

local _lookup_ore_cache = {}
local function lookup_ore(ore)
	_lookup_ore_cache[ore] = _lookup_ore_cache[ore] or _lookup_ore_uncached(ore)
	return _lookup_ore_cache[ore]
end

local function _lookup_log_uncached(log)
	local data = mw.smw.ask{
		('[[%s]]'):format(log),
		['?Profession Level A'] = '',
		mainlabel = '-'
	}[1]
	return {
		level = tonumber(data[1])
	}
end

local _lookup_log_cache = {}
local function lookup_log(log)
	_lookup_log_cache[log] = _lookup_log_cache[log] or _lookup_log_uncached(log)
	return _lookup_log_cache[log]
end

local p = {}

local p = {}

function p.ingots(frame)
	return table.concat(lookup_ingots(frame.args.profession).order, ',')
end

function p.main(frame)
	return p._main(frame:getParent().args)
end

function p._main(args)
	local profession = args.profession

	local current_xp = tonumber(args.current_xp) or 0
	local current_lvl
	if current_xp <= 500 then
		current_lvl = current_xp
		current_xp = xp._total_xp(current_lvl)
	else
		current_lvl = xp._level_at(current_xp)
	end

	local target_xp = tonumber(args.target_xp) or 0	
	local target_lvl
	if target_xp <= 500 then
		target_lvl = target_xp
		target_xp = xp._total_xp(target_lvl)
	else
		target_lvl = xp._level_at(target_xp)
	end

	local remaining_xp = target_xp - current_xp

	local buying_ores = args.ore_buy ~= 'false'

	local buying_poles = args.pole_buy
	local chopping_logs = buying_poles == 'none'
	local buying_logs = buying_poles == 'logs'
	buying_poles = not (buying_logs or chopping_logs)

	local ingot = lookup_ingots(profession).recipes[args.ingot]

	local result = mw.html.create()

	result:wikitext(('To get from %s xp (level %s) to %s xp (level %s) requires %s experience'):format(lang:formatNum(current_xp), current_lvl, lang:formatNum(target_xp), target_lvl, lang:formatNum(remaining_xp)))

	local tbl = result:tag('table')
		:addClass('wikitable sortable')

	local function skillclickpic(profession)
		return ('[[File:%s small icon.png|15px|link=%s|%s level]]'):format(profession, profession, profession)
	end
	
	local colspan = 23
	if chopping_logs then
		colspan = colspan + 1
	end
	if chopping_logs or buying_logs then
		colspan = colspan + 3
	end

	tbl
		:tag('tr')
			:th(skillclickpic(({
				Blacksmith = 'Miner',
				Stonemason = 'Miner',
				Bonewright = 'Gatherer'
			})[profession])):done()
			:th{ ({
				Blacksmith = 'Ore',
				Stonemason = 'Rock',
				Bonewright = 'Bone'
			})[profession], attr = { colspan = '3' } }:done()
			:th{ ({
				Blacksmith = 'Ingot',
				Stonemason = 'Etched',
				Bonewright = 'Brewed'
			})[profession], attr = { colspan = '2' } }:done()
			:IF(chopping_logs)
				:th(skillclickpic('Woodcutter')):done()
			:END()
			:IF(chopping_logs or buying_logs)
				:th{ 'Log', attr = { colspan = '3' } }:done()
				:th(skillclickpic('Carpenter')):done()
			:END()
			:th{ 'Pole', attr = { colspan = '3' } }:done()
			:th(skillclickpic(profession)):done()
			:th{ 'Product', attr = { colspan = '3' } }:done()
			:th{ 'Cost', attr = { colspan = '10' } }:done()
		:done()

	for _, weapon in ipairs(lookup_weapons(profession)[ingot.ingot]) do
		if not ingot.xp then
			tbl
				:tr()
					:td{
						'Missing XP for preparing [[' .. ingot.ingot .. ']]; Please [' .. tostring(mw.uri.fullUrl(ingot.ingot, 'action=edit&section=1')) .. ' edit the page] to add the experience earned (after |exp =)',
						attr = { colspan = colspan }
					}
				:done()
			break
		end
		if not weapon.xp then
			tbl
				:tr()
					:td{
						'Missing XP for creating [[' .. weapon.name .. ']]; Please ['.. tostring(mw.uri.fullUrl(weapon.name, 'action=edit&section=1')) .. ' edit the page] to add the experience earned (after |exp =)',
						attr = { colspan = colspan }
					}
				:done()
		else
			local xp_per = weapon.xp + ingot.xp * weapon.ingot.quantity
			local needed = math.ceil(remaining_xp / xp_per)

			local needed_ores = needed * weapon.ingot.quantity
			local row = tbl
				:tag('tr')
					:td(lookup_ore(ingot.ore).level):done()
					:td{ needed_ores .. '&times;', css = { ['border-right'] = '0', ['padding-right'] = '0', ['text-align'] = 'right', ['data-sort-value'] = needed_ores } }:done()
					:td{ ('[[File:%s.png|link=%s|30px]]'):format(ingot.ore, ingot.ore), css = { ['border-left'] = '0', ['padding-left'] = '0' }, addClass = 'plinkt-image no-border' }:done()
					:td{ '[[' .. ingot.ore .. ']]', addClass = 'plinkt-link no-border' }:done()
					:td{ ('[[File:%s.png|link=%s|30px]]'):format(ingot.ingot, ingot.ingot), css = { ['border-left'] = '0', ['padding-left'] = '0' }, addClass = 'plinkt-image no-border', ['data-sort-value'] = needed_ores }:done()
					:td{ '[[' .. ingot.ingot .. ']]', addClass = 'plinkt-link no-border' }:done()

			local pole_info
			local needed_logs, needed_poles

			if weapon.pole then
				needed_poles = needed * weapon.pole.quantity
				if chopping_logs or buying_logs then
					pole_info = lookup_pole(weapon.pole.name)
					needed_logs = math.ceil(needed_poles / 2)
				end
				if chopping_logs then
					row
						:td(lookup_log(pole_info.log).level):done()
				end
				if chopping_logs or buying_logs then
					row
						:td{ needed_logs .. '&times;', css = { ['border-right'] = '0', ['padding-right'] = '0', ['text-align'] = 'right' }, attr = { ['data-sort-value'] = needed_logs } }:done()
						:td{ ('[[File:%s.png|link=%s|30px]]'):format(pole_info.log, pole_info.log), css = { ['border-left'] = '0', ['padding-left'] = '0' }, addClass = 'plinkt-image no-border' }:done()
						:td{ '[[' .. pole_info.log .. ']]', addClass = 'plinkt-link no-border' }:done()
						:td(pole_info.level):done()
				end

				row
					:td{ needed_poles .. '&times;', css = { ['border-right'] = '0', ['padding-right'] = '0', ['text-align'] = 'right' }, attr = { ['data-sort-value'] = needed_poles } }:done()
					:td{ ('[[File:%s.png|link=%s|30px]]'):format(weapon.pole.name, weapon.pole.name), css = { ['border-left'] = '0', ['padding-left'] = '0' }, addClass = 'plinkt-image no-border' }:done()
					:td{ '[[' .. weapon.pole.name .. ']]', addClass = 'plinkt-link no-border' }:done()
			else
				if chopping_logs then
					row
						:na()
				end
				if chopping_logs or buying_logs then
					row
						:td{ '<small>N/A</small>', addClass = 'table-na', attr = { ['data-sort-value'] = '0', colspan = '3' } }:done()
						:na()
				end
				row
					:td{ '<small>N/A</small>', addClass = 'table-na', attr = { ['data-sort-value'] = '0', colspan = '3' } }:done()
			end

			row
				:td(weapon.level)
				:td{ needed .. '&times;', css = { ['border-right'] = '0', ['padding-right'] = '0', ['text-align'] = 'right' }, attr = { ['data-sort-value'] = needed } }:done()
				:td{ ('[[File:%s.png|link=%s|30px]]'):format(weapon.name, weapon.name), css = { ['border-left'] = '0', ['padding-left'] = '0' }, addClass = 'plinkt-image no-border' }:done()
				:td{ '[[' .. weapon.name .. ']]', addClass = 'plinkt-link no-border' }

			local cost = needed_ores * lookup_price(ingot.ore)[buying_ores and 'buy' or 'sell']
			if chopping_logs then
				if needed_logs then
					cost = cost + needed_logs * lookup_price(pole_info.log).sell
				end
			elseif buying_logs then
				if needed_logs then
					cost = cost + needed_logs * lookup_price(pole_info.log).buy
				end
			else
				if weapon.pole then
					cost = cost + needed_poles * lookup_price(weapon.pole.name).buy
				end
			end

			row:node(currency._cell(cost, { html = 'yes' }))
		end
	end

	return result
end

return p