Module:Sandbox/User:Alsang: Difference between revisions
(rather than making a bunch of different modules under my name, I'll just use this space) |
No edit summary |
||
(56 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
require('strict') |
require('strict') |
||
require('Module:Mw.html extension') |
require('Module:Mw.html extension') |
||
local recipe = require('Module:Infobox Recipe') -- to make use of its extensive material searching function |
|||
local param = require( 'Module:Paramtest' ) |
local param = require( 'Module:Paramtest' ) |
||
local currency = require('Module:Currency') |
local currency = require('Module:Currency') |
||
local discount = require('Module:MerchantHideDiscount') |
|||
local lang = mw.getContentLanguage() |
local lang = mw.getContentLanguage() |
||
local xpdata = mw.loadData('Module:Experience/data') |
|||
local p = {} |
local p = {} |
||
Line 11: | Line 10: | ||
-- non dynamic module, no inputs |
-- non dynamic module, no inputs |
||
function p.main() |
function p.main() |
||
-- returns only directly needed parameter needed for the row, |
-- returns only directly needed parameter needed for the row, |
||
-- other parameters are determined by subqueries of chained pages |
-- other parameters are determined by subqueries of chained pages |
||
-- this is in testing, uncomment out one line to test that type of query |
|||
-- requires pages to be made and have recipes on them or it tends to get an error |
|||
-- query types that include non-recipe passive activities behave weird and need to be fixed |
|||
local query = { |
local query = { |
||
'[[Activity JSON::~*]]', |
|||
--'[[Uses facility::One Handed Range (bonewright)||One Handed Melee Workbench (bonewright)||Two Handed Range Workbench (bonewright)||Two Handed Melee Workbench (bonewright)||Shield Vice (bonewright)]]', -- bonewright active |
|||
'? = name', |
|||
--'[[Uses facility::Knickknacks workbench]] OR [[Variant of::Divination||Sabertooth Cat Skeleton||Wooly Mammoth Skeleton||Triceratops Skeleton||T-Rex Skeleton||Wendigo Skeleton||Bonemeal Cement]]', -- bonewright passive |
|||
'?Activity JSON = activityJSON', |
|||
--'[[Uses facility::One Handed Range (stonemason)||One Handed Melee Workbench (stonemason)||Two Handed Range Workbench (stonemason)||Two Handed Melee Workbench (stonemason)||Shield Vice (stonemason)]]', -- stonemason active |
|||
'limit = 1500' |
|||
--'[[Uses facility::Stoneware bench]] OR [[Variant of::Statue Sculpture||Ornamental Scuplture||Column Sculpture||Bust Sculpture]]', -- stonemason passive |
|||
--'[[Uses facility::Goblin Forge||Gnome Forge (skill node)]]', -- blacksmith active |
|||
--'[[Uses facility::Jewelry Bench]]', -- blacksmith passive, no non-recipe |
|||
'[[-Sold item.Sold by::Leather stall]][[Recipe JSON::~*]]', -- leatherworker active, the stall sells scraps and we need to ignore those |
|||
--'[[-Sold item.Sold by::Leather Goods]] OR [[Variant of::Upholstery]]', -- leatherworker passive, but the Upholstery activities dont have pages yet |
|||
'?Uses facility #- = facility', |
|||
'?Profession Level A = lvl', |
|||
'? #- = name', |
|||
'?Recipe JSON = recipeJSON', |
|||
'?Activity XP = XP', |
|||
'?Activity duration = duration', |
|||
'?Value = sell', |
|||
'?Activity coins = coins', |
|||
'sort = Profession Level A', |
|||
'limit = 500' |
|||
} |
} |
||
local results = mw.smw.ask(query) |
local results = mw.smw.ask(query) |
||
results = p.screenResults(results) |
|||
-- criterion for if an entry is a recipe, or a passive activity |
|||
for _, item in ipairs(results) do |
|||
item.passiveActivity = false -- can delete this entire FOR statement unless there are non-recipe passive activities on the table |
|||
--item.passiveActivity = not(item.facility=='Upholstery station') -- leatherworker has not got upholstery implemented yet, do not use |
|||
end |
|||
results = p.formatResults(results) |
|||
return p.displayTable(results) |
return p.displayTable(results) |
||
Line 54: | Line 30: | ||
end |
end |
||
-- goes through a list of results and culls the ones which are not needed for this table |
|||
-- makes the html for the cells containing currency directly |
|||
function p.screenResults(results) |
|||
-- Replaces nil with an "unknown" cell |
|||
local resultsScreened = {} |
|||
local function currency_cell(amount) |
|||
if not amount then |
|||
return mw.html.create('td') |
|||
:addClass('table-bg-gray') |
|||
:css{ ['text-align'] = 'center' } |
|||
:attr{ colspan = '10' } |
|||
:wikitext("''unknown''") |
|||
:done() |
|||
end |
|||
return currency._cell(amount, { html = 'yes' }) |
|||
end |
|||
-- do calculations and determine strings to go in cells |
|||
function p.formatResults(results) |
|||
local discountList = discount.main() |
|||
-- iterate through products |
-- iterate through products |
||
for _, item in ipairs(results) do |
for _, item in ipairs(results) do |
||
-- if theres only one activity, wrap in table |
|||
-- recipe in a workable format |
|||
if item. |
if type(item.activityJSON)=='string' then |
||
item.activityJSON = { item.activityJSON } |
|||
end |
|||
for j,json in ipairs(item.activityJSON) do |
|||
local activityJSON = mw.text.jsonDecode(json) |
|||
if activityJSON.duration and tonumber(activityJSON.duration)==20 then |
|||
-- if it is a passive activity that is not a recipe |
|||
local itemNew = {} |
|||
-- some fields are easy to work out |
|||
itemNew.name = item.name |
|||
item.outputQuantity = 1 |
|||
itemNew.XP = activityJSON.xp |
|||
item.materials = {} |
|||
itemNew.lvl = activityJSON.level |
|||
item.intermediates = {} |
|||
itemNew.profession = activityJSON.profession |
|||
item.buy = 0 |
|||
itemNew.duration = activityJSON.duration |
|||
item.sell = item.coins |
|||
itemNew.product = activityJSON.output[1].name |
|||
else |
|||
local unpackJSON = mw.text.jsonDecode(item.recipeJSON) |
|||
item.outputQuantity = unpackJSON.output[1].quantity |
|||
-- call the module:infobox recipe to extensively search for |
|||
-- all raw materials (to add to prices) |
|||
-- all intermediate materials (for intermediate XP and duration) |
|||
local Materials = recipe._getTrueRawMaterials(unpackJSON.materials) |
|||
if next(Materials) ~= nil then |
|||
item.materials = Materials.rawMaterials |
|||
item.intermediates = Materials.intermediateMaterials |
|||
end |
|||
-- iterate through materials, adding buy price to running total (individuals not needed) |
|||
item.buy = 0 |
|||
item.buyDiscount = 0 |
|||
for _, material in ipairs(item.materials) do |
|||
--shamelessley lifted from Module:Products |
|||
local shopPriceQuery = '[[:+]][[Sold item::' .. material.name .. ']]|?Shop buy price|mainlabel=' .. material.name |
|||
local shopPriceResult = mw.smw.ask(shopPriceQuery) or {} |
|||
local shopPrice = 0 |
|||
itemNew.XPperHour = itemNew.XP and tonumber(itemNew.XP) and itemNew.duration and tonumber(itemNew.duration) and tonumber(itemNew.XP) * 3600 / tonumber(itemNew.duration) |
|||
if shopPriceResult[1] and shopPriceResult[1]["Shop buy price"] then |
|||
shopPrice = tonumber(shopPriceResult[1]["Shop buy price"]) or 0 |
|||
else |
|||
item.buy = nil |
|||
end |
|||
item.buy = item.buy and item.buy + shopPrice * material.quantity |
|||
--with the merchant discount, buy price is sell price |
|||
local shopPriceQuery = '[[:+]][[Sold item::' .. material.name .. ']]|?Shop sell price|mainlabel=' .. material.name |
|||
local shopPriceResult = mw.smw.ask(shopPriceQuery) or {} |
|||
local shopPrice = 0 |
|||
itemNew.fitXP = xpdata.knowledge[itemNew.lvl]*4*0.0005 |
|||
if shopPriceResult[1] and shopPriceResult[1]["Shop sell price"] then |
|||
if itemNew.lvl>=200 then |
|||
shopPrice = tonumber(shopPriceResult[1]["Shop sell price"]) or 0 |
|||
itemNew.fitXP = itemNew.fitXP * 4 |
|||
else |
|||
item.buyDiscount = nil |
|||
end |
end |
||
itemNew.fitXP = math.floor(itemNew.fitXP * 100 ) / 100 |
|||
item.buyDiscount = item.buyDiscount and item.buyDiscount + shopPrice * material.quantity |
|||
end |
|||
-- iterate through intermediaries to add to XP and duration parameters |
|||
for _, intermediate in ipairs(item.intermediates) do |
|||
local query = mw.smw.ask('[[Sold item::' .. itemNew.product .. ']][[Shop sell price::!~N/A]]|?Shop sell price=data|mainlabel=-') |
|||
-- look up activity XP and add it to running total |
|||
if type(query)=='table' then |
|||
local XPQuery = '[[Recipe output::' .. intermediate.name .. ']]|?Activity XP|mainlabel=' .. intermediate.name |
|||
-- price is returned as a number |
|||
local XPResult = mw.smw.ask(XPQuery) or {} |
|||
itemNew.productSellPrice = tonumber(query[1].data)*0.005 |
|||
local XPIncrease = 0 |
|||
if XPResult[1] and XPResult[1]["Activity XP"] then |
|||
XPIncrease = tonumber(XPResult[1]["Activity XP"]) or 0 |
|||
else |
else |
||
-- will return nil if the item is not in a shop, or if it is in a shop but can only be sold to the shop |
|||
XPIncrease = nil |
|||
itemNew.productSellPrice = nil |
|||
end |
end |
||
table.insert(resultsScreened,itemNew) |
|||
-- need to look up both the amount of the item used in this recipe and created in its own recipe to know how much of this xp to use |
|||
local QTYused = intermediate.quantity |
|||
local QTYQuery = '[[Recipe output::' .. intermediate.name .. ']]|?Recipe JSON|mainlabel=' .. intermediate.name |
|||
local QTYResult = mw.smw.ask(QTYQuery) or {} |
|||
local QTYmade = 0 |
|||
if QTYResult[1] and QTYResult[1]["Recipe JSON"] then |
|||
QTYmade = mw.text.jsonDecode(QTYResult[1]["Recipe JSON"]) or {} |
|||
end |
|||
QTYmade = QTYmade.output[1].quantity |
|||
item.XP = item.XP and XPIncrease and item.XP + XPIncrease * QTYused / QTYmade; |
|||
-- look up activity duration and add it to running total |
|||
local durationQuery = '[[Recipe output::' .. intermediate.name .. ']]|?Activity duration|mainlabel=' .. intermediate.name |
|||
local durationResult = mw.smw.ask(durationQuery) or {} |
|||
local durationIncrease = 0 |
|||
if durationResult[1] and durationResult[1]["Activity duration"] then |
|||
durationIncrease = tonumber(durationResult[1]["Activity duration"]) or 0 |
|||
else |
|||
durationIncrease = nil |
|||
end |
|||
item.duration = item.duration and durationIncrease and item.duration + durationIncrease * intermediate.quantity |
|||
end |
end |
||
end |
end |
||
end |
|||
-- direct values |
|||
-- sort the results by recipe level |
|||
item.sell = item.sell and item.outputQuantity and item.sell * item.outputQuantity |
|||
table.sort(resultsScreened, function(item1, item2) |
|||
item.profit = item.sell and item.buy and item.sell - item.buy |
|||
local lvl1 = item1.lvl |
|||
item.profitDiscount = item.sell and item.buyDiscount and item.sell - item.buyDiscount |
|||
local lvl2 = item2.lvl |
|||
item.profitPerXP = item.profit and item.XP and math.floor(item.profit / item.XP * 100) / 100 |
|||
if (lvl1 == nil) ~= (lvl2 == nil) then --one of two are empty |
|||
item.profitPerXPDiscount = item.profitDiscount and item.XP and math.floor(item.profitDiscount / item.XP * 100) / 100 |
|||
return lvl2 == nil -- true if lvl2 is nil but not lvl1, false if lvl1 is nil but not lvl2 |
|||
local hideName = string.gsub(item.name,'Leather','Hide') |
|||
end |
|||
item.discountLevel = discountList[hideName] |
|||
if lvl1 == nil then |
|||
return false -- Both empty, equivalent |
|||
end |
|||
return lvl1 < lvl2 -- normal comparison |
|||
end) |
|||
return resultsScreened |
|||
-- a lot of downtime in leatherworker |
|||
-- 1min 40s to go from hide stall to tannery, run round the tannery, run to leather stall, then back to hide stall |
|||
local batchSize = 24 |
|||
local downtime = 100 |
|||
item.duration = item.duration and item.duration + downtime/batchSize |
|||
item.productPerHour = item.duration and 1 / item.duration * 3600 |
|||
-- properties per hour |
|||
item.XPPerHour = item.XP and item.productPerHour and math.floor(item.XP * item.productPerHour) |
|||
item.profitPerHour = item.profit and item.productPerHour and math.floor(item.profit * item.productPerHour) |
|||
item.profitPerHourDiscount = item.profitDiscount and item.productPerHour and math.floor(item.profitDiscount * item.productPerHour) |
|||
end |
|||
return results |
|||
end |
end |
||
Line 206: | Line 100: | ||
:tag('tr') |
:tag('tr') |
||
:tag('th') |
:tag('th') |
||
:wikitext(' |
:wikitext('Page') |
||
:done() |
:done() |
||
:tag('th') |
:tag('th') |
||
:attr{ colspan = '3' } |
|||
:wikitext('Product') |
:wikitext('Product') |
||
:done() |
:done() |
||
:tag('th') |
:tag('th') |
||
:wikitext(' |
:wikitext('Profession') |
||
:done() |
:done() |
||
-- :tag('th') |
|||
-- :attr{ colspan = '10' } |
|||
-- :wikitext('Buy Value') |
|||
-- :done() |
|||
-- :tag('th') |
|||
-- :attr{ colspan = '10' } |
|||
-- :wikitext('Sell Value') |
|||
-- :done() |
|||
:tag('th') |
:tag('th') |
||
: |
:wikitext('Level') |
||
:wikitext('Profit/item') |
|||
:done() |
:done() |
||
:tag('th') |
:tag('th') |
||
: |
:wikitext('XP') |
||
:wikitext('Profit/hr') |
|||
:done() |
:done() |
||
:tag('th') |
:tag('th') |
||
:wikitext(' |
:wikitext('0.0005 of levelup XP') |
||
:done() |
:done() |
||
:tag('th') |
:tag('th') |
||
:wikitext('sell price<br>per action') |
|||
:attr{ colspan = '10' } |
|||
:wikitext('Profit/hr with [[File:Merchant_small_icon.png|18px]] discount') |
|||
:done() |
:done() |
||
:tag('th') |
-- :tag('th') |
||
:wikitext(' |
-- :wikitext('Duration') |
||
:done() |
-- :done() |
||
:tag('th') |
-- :tag('th') |
||
:wikitext('XP/hr') |
-- :wikitext('XP/hr') |
||
:done() |
-- :done() |
||
-- :tag('th') |
|||
-- :attr{ colspan = '10' } |
|||
-- :wikitext('Coins/XP') |
|||
-- :done() |
|||
-- :tag('th') |
|||
-- :attr{ colspan = '10' } |
|||
-- :wikitext('Coins/XP with [[File:Merchant_small_icon.png|18px]] discount') |
|||
-- :done() |
|||
:done() |
:done() |
||
local unknown_value_cell = mw.html.create('td') |
|||
:addClass('table-bg-gray') |
|||
:css{ ['text-align'] = 'center' } |
|||
:wikitext("''unknown''") |
|||
for i, item in ipairs(results) do |
for i, item in ipairs(results) do |
||
local row = out:tag('tr') |
local row = out:tag('tr') |
||
:IF(item.lvl) |
|||
--level |
|||
:css{ ['text-align'] = 'center' } |
|||
:wikitext(item.lvl) |
|||
:done() |
|||
:ELSE() |
|||
:node(unknown_value_cell) |
|||
:END() |
|||
:tag('td') |
:tag('td') |
||
:wikitext(item.name) |
|||
:css{ ['border-right'] = '0', ['padding-right'] = '0', ['text-align'] = 'right' } |
|||
:attr{ ['data-sort-value'] = item.name } |
|||
:wikitext(item.outputQuantity .. ' ×') |
|||
:done() |
:done() |
||
:tag('td') |
:tag('td') |
||
:wikitext('[[' .. item.product .. ']]') |
|||
:addClass('plinkt-image no-border') |
|||
:css{ ['border-left'] = '0', ['padding-left'] = '0' } |
|||
:wikitext('[[File:' .. item.name .. '.png|link=' .. item.name .. '|30px]]') |
|||
:done() |
:done() |
||
:tag('td') |
:tag('td') |
||
:wikitext('[[' .. item.profession .. ']]') |
|||
:addClass('plinkt-link no-border') |
|||
:wikitext('[[' .. item.name .. ']]') |
|||
:done() |
:done() |
||
:IF(not(item.passiveActivity)) |
|||
local materialCell = row:tag('td') |
|||
for i, _ in ipairs(item.materials) do |
|||
materialCell:wikitext(item.materials[i].quantity .. '× [[File:' .. item.materials[i].name .. '.png|30px|link=' .. item.materials[i].name .. ']]' .. '[[' .. item.materials[i].name .. ']]<br>') |
|||
end |
|||
row |
|||
-- :node(currency_cell(item.buy)) |
|||
-- :node(currency_cell(item.sell)) |
|||
:ELSE() |
|||
:tag('td') |
:tag('td') |
||
: |
:wikitext(item.lvl) |
||
:attr{ colspan = '21' } |
|||
:wikitext('N/A') |
|||
:done() |
:done() |
||
:END() |
|||
:node(currency_cell(item.profit)) |
|||
:node(currency_cell(item.profitPerHour)) |
|||
:tag('td') |
:tag('td') |
||
:wikitext(item.XP) |
|||
:css{ ['text-align'] = 'center' } |
|||
:wikitext(item.discountLevel) |
|||
:done() |
:done() |
||
:tag('td') |
|||
:node(currency_cell(item.profitPerHourDiscount)) |
|||
:wikitext(item.fitXP) |
|||
: |
:done() |
||
:tag('td') |
|||
:wikitext(item.productSellPrice) |
|||
:done() |
|||
: |
-- :tag('td') |
||
-- :wikitext(item.duration) |
|||
:node(unknown_value_cell) |
|||
: |
-- :done() |
||
-- :tag('td') |
|||
: |
-- :wikitext(item.XPperHour) |
||
-- :done() |
|||
:wikitext(item.XPPerHour and lang:formatNum(tonumber(item.XPPerHour))) |
|||
:done() |
|||
:ELSE() |
|||
:node(unknown_value_cell) |
|||
:END() |
|||
-- :node(currency_cell(item.profitPerXP)) |
|||
-- :node(currency_cell(item.profitPerXPDiscount)) |
|||
:done() |
:done() |
Latest revision as of 00:51, 18 December 2024
Module documentation
This documentation is transcluded from Module:Sandbox/User:Alsang/doc. [edit] [history] [purge]
This module does not have any documentation. Please consider adding documentation at Module:Sandbox/User:Alsang/doc. [edit]
Module:Sandbox/User:Alsang's function main is invoked by Template:Sandbox/User:Alsang.
Module:Sandbox/User:Alsang requires Module:Currency.
Module:Sandbox/User:Alsang requires Module:Mw.html extension.
Module:Sandbox/User:Alsang requires Module:Paramtest.
Module:Sandbox/User:Alsang requires strict.
Module:Sandbox/User:Alsang loads data from Module:Experience/data.
require('strict')
require('Module:Mw.html extension')
local param = require( 'Module:Paramtest' )
local currency = require('Module:Currency')
local lang = mw.getContentLanguage()
local xpdata = mw.loadData('Module:Experience/data')
local p = {}
-- non dynamic module, no inputs
function p.main()
-- returns only directly needed parameter needed for the row,
-- other parameters are determined by subqueries of chained pages
local query = {
'[[Activity JSON::~*]]',
'? = name',
'?Activity JSON = activityJSON',
'limit = 1500'
}
local results = mw.smw.ask(query)
results = p.screenResults(results)
return p.displayTable(results)
--for debugging
--return '<pre>'..mw.text.jsonEncode(results, mw.text.JSON_PRETTY)..'</pre>'
end
-- goes through a list of results and culls the ones which are not needed for this table
function p.screenResults(results)
local resultsScreened = {}
-- iterate through products
for _, item in ipairs(results) do
-- if theres only one activity, wrap in table
if type(item.activityJSON)=='string' then
item.activityJSON = { item.activityJSON }
end
for j,json in ipairs(item.activityJSON) do
local activityJSON = mw.text.jsonDecode(json)
if activityJSON.duration and tonumber(activityJSON.duration)==20 then
local itemNew = {}
itemNew.name = item.name
itemNew.XP = activityJSON.xp
itemNew.lvl = activityJSON.level
itemNew.profession = activityJSON.profession
itemNew.duration = activityJSON.duration
itemNew.product = activityJSON.output[1].name
itemNew.XPperHour = itemNew.XP and tonumber(itemNew.XP) and itemNew.duration and tonumber(itemNew.duration) and tonumber(itemNew.XP) * 3600 / tonumber(itemNew.duration)
itemNew.fitXP = xpdata.knowledge[itemNew.lvl]*4*0.0005
if itemNew.lvl>=200 then
itemNew.fitXP = itemNew.fitXP * 4
end
itemNew.fitXP = math.floor(itemNew.fitXP * 100 ) / 100
local query = mw.smw.ask('[[Sold item::' .. itemNew.product .. ']][[Shop sell price::!~N/A]]|?Shop sell price=data|mainlabel=-')
if type(query)=='table' then
-- price is returned as a number
itemNew.productSellPrice = tonumber(query[1].data)*0.005
else
-- will return nil if the item is not in a shop, or if it is in a shop but can only be sold to the shop
itemNew.productSellPrice = nil
end
table.insert(resultsScreened,itemNew)
end
end
end
-- sort the results by recipe level
table.sort(resultsScreened, function(item1, item2)
local lvl1 = item1.lvl
local lvl2 = item2.lvl
if (lvl1 == nil) ~= (lvl2 == nil) then --one of two are empty
return lvl2 == nil -- true if lvl2 is nil but not lvl1, false if lvl1 is nil but not lvl2
end
if lvl1 == nil then
return false -- Both empty, equivalent
end
return lvl1 < lvl2 -- normal comparison
end)
return resultsScreened
end
-- make the table
function p.displayTable(results)
local out = mw.html.create('table')
:addClass('wikitable sortable')
:tag('tr')
:tag('th')
:wikitext('Page')
:done()
:tag('th')
:wikitext('Product')
:done()
:tag('th')
:wikitext('Profession')
:done()
:tag('th')
:wikitext('Level')
:done()
:tag('th')
:wikitext('XP')
:done()
:tag('th')
:wikitext('0.0005 of levelup XP')
:done()
:tag('th')
:wikitext('sell price<br>per action')
:done()
-- :tag('th')
-- :wikitext('Duration')
-- :done()
-- :tag('th')
-- :wikitext('XP/hr')
-- :done()
:done()
for i, item in ipairs(results) do
local row = out:tag('tr')
--level
:tag('td')
:wikitext(item.name)
:done()
:tag('td')
:wikitext('[[' .. item.product .. ']]')
:done()
:tag('td')
:wikitext('[[' .. item.profession .. ']]')
:done()
:tag('td')
:wikitext(item.lvl)
:done()
:tag('td')
:wikitext(item.XP)
:done()
:tag('td')
:wikitext(item.fitXP)
:done()
:tag('td')
:wikitext(item.productSellPrice)
:done()
-- :tag('td')
-- :wikitext(item.duration)
-- :done()
-- :tag('td')
-- :wikitext(item.XPperHour)
-- :done()
:done()
end
return out
end
return p