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

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