Module:Minion

-- local p = {}

local getArgs = require('Module:Arguments').getArgs local slot = require( Module:Inventory slot ).slot local craftingTable = require( Module:UI ).craftingTable local formatResList = require('Module:List')._resourceList local string = require(Module:String) local formatNum = string._formatNum local ucfirst = string._ucfirst local toRoman = string._toRoman local toArabic = string._toArabic local roundNumber = string.roundNumber local _error = string._error local table = require(Module:Table) local colorText = require('Module:Color')._colorTemplates local yesno = require(Module:Yesno) local bazaar = require(Module:Bazaar) local formatCoins = require('Module:Currency')._coins local processResource = require('Module:Item')._resourceDisplay local processItem = require('Module:Item')._itemDisplay

local minionData = mw.loadData('Module:Minion/Data')

local TIERS = { "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII" }

local function _actionsPerMinute(minion, tier) -- TBA*2 = SECONDS_PER_FULL_ACTIONS : Each action either picks up or places, so 2 actions are needed for every collection. -- 60/SECONDS_PER_FULL_ACTIONS = ACTIONS_PER_MINION : This gives us a number that can be used as a multiplier to find all other action/time related questions. return 60/(minionData[minion].stats[tier].tba*2) end

local lang = mw.getContentLanguage local title = mw.title.getCurrentTitle.text

-- utility: peakStat local function peakStat(name) for x = #TIERS, 1, -1 do   	if not highestTier and minionData[name].stats[x] then highestTier = x   	end if highestTier and minionData[name].stats[x].crafting then highestCraftable = x		end if highestTier and highestCraftable then break end end return highestTier, highestCraftable end

-- Template:GetMinionData -- -- Returns a specified peice of data from the /Data module based on inputs

function p.getMinionData(frame) local args = getArgs(frame) local minion = args[1] or args["minion"] or args["m"] local tier = args["tier"] or args["t"] local data = args[2] or args["data"] or args["d"] local item = args[3] or args["i"] or args["item"] if not minion then return _error('Inputs expected, got none') end if not data then return _error('Invalid Argument to #2: paramater "data" is required.') end if not tier and minion:match('(.+) ([%dIVXivx]+)') then minion, tier = minion:match('(.+) ([%dIVXivx]+)') else tier = tier minion = minion end return p._getMinionData(minion, data, tier, item) end

function p._getMinionData(minionName, data, tier, item) local minionData = minionData[minionName] local aliases = { ['s']='storage'; ['str']='storage'; ['store']='storage'; ['storage']='storage'; ['tba']='time between actions'; ['time b a']='time between actions'; ['time betw a']='time between actions'; ['time between a']='time between actions'; ['time b act']='time between actions'; ['time b actions']='time between actions'; ['time betw act']='time between actions'; ['time between act']='time between actions'; ['time between actions']='time between actions'; ['time between action']='time between actions'; ['ci']='crafting item'; ['c item']='crafting item'; ['cra i']='crafting item'; ['craft i']='crafting item'; ['cra item']='crafting item'; ['craft item']='crafting item'; ['crafting item']='crafting item'; ['cn']='crafting number'; ['cra n']='crafting number'; ['craft n']='crafting number'; ['crafting n']='crafting number'; ['cra n']='crafting number'; ['cra num']='crafting number'; ['cra number']='crafting number'; ['c n']='crafting number'; ['c num']='crafting number'; ['c number']='crafting number'; ['craft num']='crafting number'; ['crafting num']='crafting number'; ['crafting number']='crafting number'; ['desc']='description'; ['d']='description'; ['descr']='description'; ['descrip']='description'; ['description']='description'; ['ai']='average item'; ['avg i']='average item'; ['avrg i']='average item'; ['averag i']='average item'; ['average i']='average item'; ['avg item']='average item'; ['avrg item']='average item'; ['averag item']='average item'; ['average item']='average item'; ['avg i']='average item'; ['a i']='average item'; ['a item']='average item'; ['an']='average number'; ['avg n']='average number'; ['avrg n']='average number'; ['averag n']='average number'; ['average n']='average number'; ['avg num']='average number'; ['avrg num']='average number'; ['averag num']='average number'; ['average num']='average number'; ['avg number']='average number'; ['avrg number']='average number'; ['averag number']='average number'; ['average number']='average number'; }   local data = aliases[data:lower] local tier = tonumber(toArabic(tier or 0)) local item = tonumber(item) if data == "storage" then ret = minionData.stats[tier].storage elseif data == "time between actions" then ret = minionData.stats[tier].tba elseif data == "crafting item" then ret = minionData.stats[tier].crafting.item elseif data == "crafting number" then ret = minionData.stats[tier].crafting.num elseif data == "description" then ret = minionData.description elseif data == "average item" then ret = minionData.items[item].item elseif data == "average number" then ret = minionData.items[item].avg else ret = _error("Invalid Argument to \"data\": unknown data type (help)") end return ret end

- -- Template:Days using minions -- -- Makes a table with how long it would take a single minion to output a certain amount of items based on it's tier - function p.minionPerDayTable( frame ) --local parameters = frame.args local args = getArgs(frame) local minion = args["minion"] or args[1] or title local amount = args["amount"] or args[2] local item = args["item"] or (amount == args[2] and args[3] or args[2]) local split = yesno(args["split"], true) local noscroll = yesno(args["noscroll"], false) return p._minionPerDayTable( minion, amount, item, split, noscroll ) end function p._minionPerDayTable( minion, amount, item, split, noscroll ) local HALF_ROW = 6 local actionsPerDay local days local itemIndex = 1 if item then for key,value in ipairs(minionData[minion].items) do           if value['item'] == item then itemIndex = key end end end local table = mw.html.create('table'):addClass('wikitable'):css({['text-align']="center", ['table-layout']="fixed"}) if noscroll and not split then table:css({['min-width']="100%"}) end table:tag('caption'):wikitext(string.format( 'Days to acquire %s using %s %s Minion (by tier, no fuel) If the player has multiple minions, divide the number of days by how many minions will be used. ',		processResource(amount..' '..minionData[minion].items[itemIndex].item, true), (minion:lower:match('^([aeiouy])') and 'an' or 'a'), minion )	)	local row, row_data, row2, row_data2 = table:tag("tr"), table:tag("tr"), table:tag("tr"), table:tag("tr") for i, tier in ipairs(TIERS) do		if minionData[minion].stats[i] then if split and i > HALF_ROW then row2:tag("th"):wikitext(tier):done else if noscroll and not split then row:tag("th"):wikitext(tier):done else row:tag("th"):wikitext(tier):css({["min-width"]="3.8em"}):done end end else if split then row2:tag("td"):wikitext(string.blankCell):attr({rowspan=2}) end end end for i, tier in ipairs(TIERS) do		if minionData[minion].stats[i] then actionsPerDay = _actionsPerMinute(minion, i)*60*24; days = amount/actionsPerDay/minionData[minion].items[itemIndex].avg; -- Show appropriate number of decimals (smaller the number = more) if days < 1 then days = math.floor(days*100)/100 elseif days < 10 then days = math.floor(days*10)/10 else days = math.floor(days) end if split and i > HALF_ROW then row_data2:tag("td"):wikitext( days ):done else row_data:tag("td"):wikitext( days ):done end end end row:done row2:done row_data:done row_data2:done table:done scrollable = " "..tostring(table).." " return noscroll and tostring(table) or scrollable end

- -- Template:Minion ideal layout -- -- Makes a table of to form the in-game ideal layout for a minion - function p.minionIdealLayoutTable( frame ) --local parameters = frame.args local args = getArgs(frame) local minion = args["minion"] or args[1] or title local ideal = args["ideal"] local border = args["border"] local size = 5; local slots = {} local abc = { "A", "B", "C", "D", "E", "F", "G", "H", "I" } for y = 1,size,1 do	   slots[y] = {} for x = 1,size,1 do   	   slots[y][x] = args[abc[x]..y]    	end end return p._minionIdealLayoutTable( minion, size, ideal, border, slots ) end function p._minionIdealLayoutTable( minion, size, ideal, border, customSlots ) if border then size = size + 2 end local table = mw.html.create('table'):addClass('mcui mcui-Crafting_Table pixel-image'):css({['margin']='0 auto', ['display']='table'}) for y = 1,size,1 do	   local row = table:tag("tr"):addClass('mcui-row') for x = 1,size,1 do   	    local img if border and (y==1 or y==size or x==1 or x==size) then img = border elseif x==math.ceil(size/2) and y==math.ceil(size/2) then img = "["..minion.."]"..minion.." I"               if customSlots[y][x] then img = img..";"..customSlots[y][x] end else img = border and (customSlots[y-1][x-1] or ideal) or (customSlots[y][x] or ideal) end row:tag("td"):wikitext( slot{ img } ):done end row:done end table:done return tostring(table) end

- -- Template:Minion UI -- -- Makes a table of to form the in-game UI of a minion - function p.minionUI( frame ) --local parameters = frame.args local args = getArgs(frame) local minion = args["minion"] or args[1] or title return p._minionUI( minion ) end

local NPCtext = { ["Bulvar"] = {name="&5Bulvar", loc="&2Dwarven Mines"}, ["Terry"] = {name="&5Terry", loc="&bTrappers Den"}, }

function p._minionUI( minion ) local name, tier = minion:match('(.-)%s?[Mm]?[Ii]?[Nn]?[Ii]?[Oo]?[Nn]?%s([%divxIVX]+)') if tier then tier = tonumber(tier) and tonumber(tier) or toArabic(tier) else name = minion:gsub('%s*[Mm][Ii][Nn][Ii][Oo][Nn]', '') tier = 1 end if not minionData[name] then error("Minion name "..name.." is not valid") end if not minionData[name].stats[tier] then error(name.."Minion has no such tier (Tier "..tier..")") end local highestTier, highestCraftable = peakStat(name) local storageTier = {} for x = 1,15,1 do -- Minions have 15 total slots for y = 1,10,1 do -- All slots unlocked at tier 10 if minionData[name].stats[y].storage / 64 >= x then storageTier[x] = y               break end end end local function available( num ) if tier >= storageTier[num] then return slot{ '' } else return slot{ 'White Stained Glass Pane', link='none', title='&eStorage unlocked at tier '..toRoman(storageTier[num]), text='none' } end end local title = name..' Minion '..toRoman(tier) local description = minionData[name].description or 'No Description' local upgradeMessage = '' local upgradeMessageView = '' local upgradeMessageUpgrade = '' local blank = slot{ 'Black Stained Glass Pane', link='none', title='none', text='none' } if tier == highestTier then upgradeMessage = '&aHighest tier has been reached!' upgradeMessageView = 'The highest tier of this minion/has been reached//'..upgradeMessage upgradeMessageUpgrade = "&cThis minion has reached the/maximum tier." elseif tier == highestCraftable then local tradenpc = minionData[name].stats[tier+1].tradeNpc upgradeMessage = '&aHighest craftable tier has been/reached!' upgradeMessageView = 'The highest craftable tier of/this minion has been reached/Talk to '..NPCtext[tradenpc] and NPCtext[tradenpc].name or tradenpc..' &rin the/'..NPCtext[tradenpc] and NPCtext[tradenpc].loc or '(unknown)'..' &rto unlock the/next tier!//'..upgradeMessage upgradeMessageUpgrade = "&cThis minion has reached the/maximum tier." elseif minionData[name].stats[tier + 1].storage > minionData[name].stats[tier].storage then upgradeMessage = '&7Time Between Actions: &e'..minionData[name].stats[tier].tba..'s/&7Max Storage: &8'..minionData[name].stats[tier].storage..' ➜ &a'..minionData[name].stats[tier + 1].storage upgradeMessageView = '&7View the items required to/upgrade this minion to the/next tier.//'..upgradeMessage..'//&eClick to view!' upgradeMessageUpgrade = upgradeMessage..'//&eClick to upgrade!' else upgradeMessage = '&7Time Between Actions: &8'..minionData[name].stats[tier].tba..'s ➜ &a'..minionData[name].stats[tier + 1].tba..'s/&7Max Storage: &e'..minionData[name].stats[tier].storage upgradeMessageView = '&7View the items required to/upgrade this minion to the/next tier.//'..upgradeMessage..'//&eClick to view!' upgradeMessageUpgrade = upgradeMessage..'//&eClick to upgrade!' end local table = mw.html.create('table'):addClass('mcui mcui-Crafting_Table pixel-image'):css({['margin']='0 auto', ['display']='table'}) table:tag("tr"):attr({style="position:absolute;top:2px;"}):tag("td"):attr({colspan=9,style="font-family:minecraft;color:black;font-size:16px;"}):wikitext( title ):done:done table:tag("tr"):addClass('mcui-row'):attr({style="margin-top: 24px;"}) :tag("td"):wikitext( blank ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( slot{ 'Redstone Torch', link='none', title='&aIdeal Layout', text='&7View the most effecient spot for/this minion to be placed in.' } ):done :tag("td"):wikitext( slot{ title, title='&9'..title, text='&7'..description..'//&7Time Between Action: &a'..minionData[name].stats[tier].tba..'s/&7Max Storage: &e'..minionData[name].stats[tier].storage..'/&7Resources Generated: &b0' } ):done :tag("td"):wikitext( slot{ 'Gold Ingot', link='none', title='&aNext Tier', text=upgradeMessageView } ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( blank ):done :done table:tag("tr"):addClass('mcui-row') :tag("td"):wikitext( blank ):done :tag("td"):wikitext( slot{ 'Lime Stained Glass Pane', link='none', title='&aMinion Skin Slot', text='&7You can insert a Minion Skin/here to change the appearance of/your minion.' } ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( blank ):done :done table:tag("tr"):addClass('mcui-row') :tag("td"):wikitext( blank ):done :tag("td"):wikitext( slot{ 'Orange Stained Glass Pane', link='none', title='&aFuel', text='&7Increase the speed of your/minion by adding minion fuel/items here.//&cNote: &7You can\'t take/fuel back out after you/place it here!' } ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( available( 1 ) ):done :tag("td"):wikitext( available( 2 ) ):done :tag("td"):wikitext( available( 3 ) ):done :tag("td"):wikitext( available( 4 ) ):done :tag("td"):wikitext( available( 5 ) ):done :tag("td"):wikitext( blank ):done :done table:tag("tr"):addClass('mcui-row') :tag("td"):wikitext( blank ):done :tag("td"):wikitext( slot{ 'Blue Stained Glass Pane', link='none', title='&aAutomated Shipping', text='Add a &bBudget Hopper&7,/&bEnchanted Hopper&7 or a/&bPerfect Hopper&7 here to make/your minion automatically sell/generated items after its/inventory is full.' } ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( available( 6 ) ):done :tag("td"):wikitext( available( 7 ) ):done :tag("td"):wikitext( available( 8 ) ):done :tag("td"):wikitext( available( 9 ) ):done :tag("td"):wikitext( available( 10 ) ):done :tag("td"):wikitext( blank ):done :done table:tag("tr"):addClass('mcui-row') :tag("td"):wikitext( blank ):done :tag("td"):wikitext( slot{ 'Yellow Stained Glass Pane', link='none', title='&aUpgrade Slot', text='&7You can improve your minion by/adding a minion upgrade item/here.' } ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( available( 11 ) ):done :tag("td"):wikitext( available( 12 ) ):done :tag("td"):wikitext( available( 13 ) ):done :tag("td"):wikitext( available( 14 ) ):done :tag("td"):wikitext( available( 15 ) ):done :tag("td"):wikitext( blank ):done :done table:tag("tr"):addClass('mcui-row') :tag("td"):wikitext( blank ):done :tag("td"):wikitext( slot{ 'Yellow Stained Glass Pane', link='none', title='&aUpgrade Slot', text='&7You can improve your minion by/adding a minion upgrade item/here.' } ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( slot{ 'Chest', link='none', title='&aCollect All', text='&eClick to Collect all items!' } ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( slot{ 'Diamond', link='none', title='&aQuick-Upgrade Minion', text='&7Click here to upgrade your/minion to the next tier.//'..upgradeMessageUpgrade } ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( blank ):done :tag("td"):wikitext( slot{ 'Bedrock', link='none', title='&aPickup Minion', text='&eClick to pickup!' } ):done :done table:done return tostring(table) end

- -- Template:Minion recipe gallery -- -- Makes a table of to form the in-game ideal layout for a minion - function p.minionRecipeGallery( frame ) --local parameters = frame.args local args = getArgs(frame) local minion = args["minion"] or args[1] if not minion then minion = title end return p._minionRecipeGallery( minion ) end function p._minionRecipeGallery( minionName ) local minion = minionData[minionName] -- Get a list of the desired tiers local tiers = {} local start = minion.stats[1].crafting.info and 2 or 1 for i=start,11 do tiers[#tiers+1] = i end -- Generate crafting tables local tables = {} for key,tier in pairs(tiers) do       local crafting = minion.stats[tier].crafting local recipeBase = crafting.item..","..crafting.num local recipeB3 = crafting.B3 or crafting.item..","..crafting.num local recipeMinion = crafting.base or minionName.." Minion "..TIERS[tier-1] local recipeOutput = minionName.." Minion "..TIERS[tier] tables[#tables+1] = craftingTable{ A1 = recipeBase, B1 = recipeBase,  C1 = recipeBase, A2 = recipeBase, B2 = recipeMinion, C2 = recipeBase, A3 = recipeBase, B3 = recipeB3,    C3 = recipeBase, Output = recipeOutput }   end -- Display all crafting tables in a grid local grid = mw.html.create('table') local row for i, ct in pairs(tables) do	   if (i-1)%3 == 0 then if row then row:done end row = grid:tag("tr") end row:tag("td"):wikitext(ct):done end row:done grid:done return tostring(grid) end

- -- Template:Minion animated crafting table -- -- Make an animated of all potential minion crafting recipes - function p.minionAnimatedCraftingTable( frame ) --local parameters = frame.args local args = getArgs(frame) local minion = args["minion"] or args[1] or title local item = args["item"] local from = args["from"] local to = args["to"] return p._minionAnimatedCraftingTable( minion, item, from, to ) end function p._minionAnimatedCraftingTable( minionName, item, from, to ) local minion = minionData[minionName] local highestTier, highestCraftable = peakStat(minionName) -- Get a list of the desired tiers local tiers = {} if item then for i=1,highestCraftable,1 do           local crafting = minion.stats[i].crafting if crafting.item == item and not crafting.info then tiers[#tiers+1] = i           end end --ignore .info elseif from and to then if not tonumber(from) or not tonumber(to) then error("Range is not correct") end from, to = tonumber(from), tonumber(to) if not minion.stats[from] or not minion.stats[to] then error("Range is not correct") end if from > to then from, to = to, from end for i=from,to,1 do tiers[#tiers+1] = i end else local start = minion.stats[1].crafting.info and 2 or 1 for i=start,highestCraftable,1 do tiers[#tiers+1] = i end end -- Use tier list to generate the animated crafting table local recipeBase = {} local recipeB3 = {} local recipeMinion = {} local recipeOutput = {} for key,tier in pairs(tiers) do       local crafting = minion.stats[tier].crafting recipeBase[#recipeBase+1] = crafting.item..","..crafting.num recipeB3[#recipeB3+1] = crafting.B3 or crafting.item..","..crafting.num recipeMinion[#recipeMinion+1] = crafting.base or minionName.." Minion "..TIERS[tier-1] recipeOutput[#recipeOutput+1] = minionName.." Minion "..TIERS[tier] -- special case for mushroom, since all elements must have 2 states, since the "Mushroom" item cycles between 2 states if minionName == "Mushroom" then recipeMinion[#recipeBase] = recipeMinion[#recipeBase]..";"..recipeMinion[#recipeBase] recipeOutput[#recipeBase] = recipeOutput[#recipeBase]..";"..recipeOutput[#recipeBase] end end -- now return the crafting table recipeBase = table.concat(recipeBase,";") recipeB3 = table.concat(recipeB3,";") recipeMinion = table.concat(recipeMinion,";") recipeOutput = table.concat(recipeOutput,";") return craftingTable{ A1 = recipeBase, B1 = recipeBase, C1 = recipeBase, A2 = recipeBase, B2 = recipeMinion, C2 = recipeBase, A3 = recipeBase, B3 = recipeB3, C3 = recipeBase, Output = recipeOutput } end

- -- Template:Minion stats table -- -- Takes all the data from /Data and displays it nicely in a table on the minion page. - function p.minionStatsTable( frame ) --local parameters = frame.args local args = getArgs(frame) local minion = args["minion"] or args[1] or title return frame:preprocess(p._minionStatsTable( minion )) end

function p._minionStatsTable( minionName ) local minion = minionData[minionName] -- B3 has non-standard info for slayer minions local B3Exists = minion.stats[2].crafting.B3   local showBazaarCost = minionName:lower ~= "flower" local totalsBazaarCost = 0 -- We want the cost of cumulative enchanted version local wikitable = mw.html.create('table'):addClass('wikitable') -- Header row local row = wikitable:tag("tr") :tag("th"):attr({ rowspan=2 }):wikitext("Tier"):done :tag("th"):attr({ rowspan=2 }):wikitext("Info"):done :tag("th"):wikitext("Upgrade Cost"):done if showBazaarCost then row:tag("th"):wikitext("Bazaar Upgrade Cost"):done end row:tag("th"):attr({ colspan=B3Exists and 3 or 2 }):wikitext("Recipe"):done -- Header row (second row) row = wikitable:tag("tr") row:tag("th"):css({ ["background-color"]="#29181a" }):wikitext("Total Cost") if showBazaarCost then row:tag("th"):wikitext("Bazaar Cumulative Cost") end row:tag("th"):wikitext(" Base ") if B3Exists then row:tag("th"):wikitext(" Item (x7) ") row:tag("th"):wikitext(" Extra ") else row:tag("th"):wikitext(" Item (x8) ") end -- Display non-header rows local baseCraftingItem = minion.stats[2].crafting.item local items = minion.items local background = { -- {rowBG CSS Table, rowBG2 CSS Table} {{}, { background="rgba(255, 255, 255, 0.3)" }}, -- phase 1 {{ background="rgba(110, 17, 17, 0.15)" }, { background="rgba(255, 255, 255, 0.15)" }}, -- phase 2 {{ background="rgba(110, 17, 17, 0.5)" },{ background="rgba(90, 75, 75, 0.34)" }}, -- etc.   	{{ background="rgba(110, 17, 17, 0.9)" },{ background="rgba(90, 75, 75, 0.03)" }}, }	local function getInfo(i) local stats = minion.stats[i] local crafting = stats.crafting local mainItem = crafting and crafting.item or stats.trade[1].item return stats, crafting, mainItem end for i = 1,#TIERS,1 do		if not minion.stats[i] then break end local stats, crafting, mainItem = getInfo(i) local multi8 = crafting and crafting.B3 and 7 or 8 local actionsPerDay = _actionsPerMinute(minionName, i)*60*24 local unique = {} for j = i,1,-1 do       	local stats, crafting, mainItem = getInfo(j) unique[mainItem] = true end -- Row color for recipes using enchanted items local rowBG = background[table.length(unique)][1] local rowBG2 = background[table.length(unique)][2] local cellTopBrdr = { ["border-top-width"]="2px" } --       -- Calculate info for below --       --Calculate Item Costs local tierCosts = {} if crafting then tierCosts[#tierCosts+1] = (crafting.num*multi8).." "..crafting.item.."" if crafting.base then tierCosts[#tierCosts+1] = "1x "..crafting.base.."" end if crafting.B3 then tierCosts[#tierCosts+1] = "1x "..crafting.B3.."" end elseif stats.trade then for _,itemData in ipairs(stats.trade) do       		if itemData.item:lower == "coin" then tierCosts[#tierCosts+1] = itemData.num.." coins" elseif itemData.item:lower == "pelt" then tierCosts[#tierCosts+1] = itemData.num.." pelts" else tierCosts[#tierCosts+1] = itemData.num.."x "..itemData.item.."" end end end -- Find totals for items needed for all tiers up to this one local totals = {} local totalsObj = _getStatTableCumulativeTotal(minionName, i)       for key,value in pairs(totalsObj) do           totals[#totals+1] = formatNum(math.ceil(value)).." "..key end --Calculate Bazaar Costs local bazaarCost = 0 if showBazaarCost then -- Since crafting and trade data have different formats, list format is needed local items = {} if crafting then items[#items+1] = { item=crafting.item, num=crafting.num*multi8 } elseif stats.trade then items = stats.trade end for _,itemData in ipairs(items) do           	local craftNum, craftItem = itemData.num, itemData.item -- Hardcoded workaround for Silver Fang not being purchasable on Bazaar if craftItem == "Silver Fang" then craftNum = craftNum*25 craftItem = "Enchanted Ghast Tear" elseif craftItem == "Melon (block)" then craftNum = craftNum*9 craftItem = "Melon" end -- Handle special items if craftItem:lower == "coin" then -- just increment cost by coin amount bazaarCost = bazaarCost + craftNum elseif craftItem:lower == "pelt" then -- skip else local calculatedPrice = bazaar._calcMaterialBuyPrices({ { craftItem, tonumber(craftNum) } }) if type(calculatedPrice) ~= "number" then return calculatedPrice end bazaarCost = bazaarCost + calculatedPrice end end --Totals totalsBazaarCost = totalsBazaarCost + bazaarCost end --       -- First row for this tier --       local row = wikitable:tag("tr"):attr('id', TIERS[i]):css(rowBG) -- Tier + Icon

row:tag('th'):attr({ rowspan=2 }):css(cellTopBrdr):wikitext(TIERS[i]..' '..slot{ minionName..' Minion '..TIERS[i] }):done -- Info row:tag("td") :attr({ rowspan=2 }) :css(cellTopBrdr) :wikitext("Cooldown: "..colorText("Green", stats.tba.."s")) :wikitext(" Storage: "..colorText("Yellow", stats.storage)) :done -- Cost row:tag("td"):css(cellTopBrdr):wikitext(""):done -- Bazaar Cost if showBazaarCost then row:tag("td"):css(cellTopBrdr):wikitext("") end -- Recipe if stats.trade then -- If bought via trade, list data local slots,list = { slot{ minionName.." Minion "..TIERS[i-1] } },{} for _,trd in ipairs(stats.trade) do       		if trd.item:lower == "coin" then list[#list+1] = "" elseif trd.item:lower == "pelt" then list[#list+1] = "Pelts" else slots[#slots+1] = slot{ trd.item..","..trd.num } end end row:tag("td"):attr({ rowspan=2, colspan=B3Exists and 3 or 2 }):css(cellTopBrdr) :wikitext("Merchant: "..stats.tradeNpc.."") :tag("div"):wikitext(table.concat(slots, "")):done :tag("ul"):addClass('lowmargin'):wikitext(""..table.concat(list, "")..""):done elseif crafting.info then -- If there is no crafting recipe, show the details on why row:tag("td"):attr({ rowspan=2, colspan=B3Exists and 3 or 2 }):css(cellTopBrdr):wikitext(crafting.info):done else -- Recipe exists row:tag("td"):attr({ rowspan=2 }):css(cellTopBrdr):wikitext(crafting.base and slot{ crafting.base } or slot{ minionName.." Minion "..TIERS[i-1] }):done local itemColspan = B3Exists and not crafting.B3 and 2 or 1 row:tag("td"):attr({ rowspan=2, colspan=itemColspan }):css(cellTopBrdr):wikitext(slot{ crafting.item..","..crafting.num }):wikitext(itemColspan==2 and " x8" or ""):done if crafting.B3 then row:tag("td"):attr({ rowspan=2 }):css(cellTopBrdr):wikitext(slot{ crafting.B3 }):done end end --       -- Second row for this tier --       row = wikitable:tag("tr") -- Total cost row:tag("td"):css(rowBG2):wikitext("") -- Bazaar Total Cost if showBazaarCost then row:tag("td"):css(rowBG):wikitext("") end end return tostring(wikitable) end

-- Find the total number of the base minion items -- returns { {item,num} } function _getStatTableBaseCost(minionName, item, num) local minion = minionData[minionName] local recipes = minion.recipes if not recipes[item] then return { {item, num} } end local items = {} for key,value in ipairs(recipes[item]) do       local subitems = _getStatTableBaseCost(minionName, value[1], value[2]*num) for key2,value2 in ipairs(subitems) do           items[#items+1]=value2 end end return items end

-- Find total of base minion items for all tiers up to the one passed in function _getStatTableCumulativeTotal(minionName, tier) local minion = minionData[minionName] local totals = {} for i = 1,tier,1 do   	local stats = minion.stats[i] local items = {} if stats.crafting then local crafting = stats.crafting local multi8 = crafting.B3 and 7 or 8 items = { { item=crafting.item, num=crafting.num*multi8} } elseif stats.trade then items = stats.trade end local cost = {} for _,itemData in ipairs(items) do	   	cost = table.merge(cost, _getStatTableBaseCost(minionName, itemData.item, itemData.num)) end for key,value in ipairs(cost) do           local item = value[1] totals[item] = (totals[item] or 0) + value[2] end end return totals end

- -- Template:MinionProfitTable -- -- Shows a table with resources collected by the minion alongside their bazaar sell prices - function p.minionProfitTable(frame) local args = getArgs(frame) local minion = args[1] or args["minion"] or title if not minion then minion = mw.title.getCurrentTitle.text end minion = minion:gsub('%s*[Mm]inion', '') return frame:preprocess(p._minionProfitTable(minion)) end

function p._minionProfitTable(minion, isOffline) local produce = minionData[minion].items local tba = minionData[minion].stats local items = {} --get drops produced by the minion for i=1,25,1 do --25 because some minions like flower minion have very large amount of different drops if produce[i] and produce[i].condition == nil then items[i] = produce[i] elseif produce[i] and produce[i].condition == "Auto Smelter" and produce[i].item ~= "Cactus Green" then items[i] = produce[i] table.remove(items, i-1) elseif produce[i] and produce[i].condition == "Flint Shovel" then items[i] = produce[i] table.remove(items, i-1) end end --compose the header of the table local wikitable = mw.html.create('table'):addClass('wikitable') wikitable:tag('caption'):wikitext("Listed below are the profits of this minion when its items are sold to Bazaar. No fuel and no diamond spreading are used.", ' ', 'Values are shown when the player is online.') wikitable:tag('tr') :tag('th'):wikitext('Tier'):attr({ rowspan=2 }):done :tag('th'):wikitext('Harvests per minute '):attr({ rowspan=2 }):done :tag('th'):wikitext('Per hour'):attr ({ colspan=2 }):done :tag('th'):wikitext('Per day'):attr ({ colspan=2 }):done :done wikitable:tag('tr') :tag('th'):wikitext('Items'):done :tag('th'):wikitext('Bazaar Profit'):done :tag('th'):wikitext('Items'):done :tag('th'):wikitext('Bazaar Profit'):done :done -- add minion produce and profits to the table for i=1,#TIERS,1 do    	if not minionData[minion].stats[i] then return tostring(wikitable) end local tba = minionData[minion].stats[i].tba --list items not possible to sell at bazaar local non_bazaar_items = { ["Poisonous Potato"] = 10, ['White Wool'] = 2, ['Clownfish'] = 20, }       --create the string displayed in the "produce" column (compatible with ) local resourceStr = {} --get first item resourceStr[1] = "*"..math.floor(p._producePerMinute(items[1].avg, tba)*60).." "..items[1].item --get rest of items, if applicable for j=2,#items,1 do           resourceStr[#resourceStr+1] = "\n*"..math.floor(p._producePerMinute(items[j].avg, tba)*60).." "..items[j].item end resourceStr = table.concat(resourceStr) --create string for profit calculation (compatible woth ) local bazaarStr = {} --get first item bazaarStr[1] = "*"..(p._producePerMinute(items[1].avg, tba)*60).." "..items[1].item --get rest of items, if applicable for j=2,#items,1 do           if non_bazaar_items[items[j].item] then bazaarStr[#bazaarStr+1] = "\n*"..(p._producePerMinute(items[j].avg, tba)*60)*non_bazaar_items[items[j].item] else bazaarStr[#bazaarStr+1] = "\n*"..(p._producePerMinute(items[j].avg, tba)*60).." "..items[j].item end end bazaarStr = table.concat(bazaarStr) --same thing as above but for the 24 hour section local resourceStr_24 = {} resourceStr_24[1] = "*"..math.floor(p._producePerMinute(items[1].avg, tba)*60*24).." "..items[1].item for j=2,#items,1 do           resourceStr_24[#resourceStr_24+1] = "\n*"..math.floor(p._producePerMinute(items[j].avg, tba)*60*24).." "..items[j].item end resourceStr_24 = table.concat(resourceStr_24) local bazaarStr_24 = {} bazaarStr_24[1] = "*"..(p._producePerMinute(items[1].avg, tba)*60*24).." "..items[1].item for j=2,#items,1 do           if non_bazaar_items[items[j].item] then bazaarStr_24[#bazaarStr_24+1] = "\n*"..(p._producePerMinute(items[j].avg, tba)*60*24)*non_bazaar_items[items[j].item] else bazaarStr_24[#bazaarStr_24+1] = "\n*"..(p._producePerMinute(items[j].avg, tba)*60*24).." "..items[j].item end end bazaarStr_24 = table.concat(bazaarStr_24) --get harvests per minute local hpm if minion == "Pumpkin" or minion == "Melon" or minion == "Fishing" then hpm = roundNumber(60/(tba), 2) else hpm = roundNumber(60/(tba*2), 2) end --add the table cells and fill them with the data from above wikitable:tag('tr') :tag('th'):wikitext(toRoman(i)..' '..slot{ minion..' Minion '..toRoman(i) }):done :tag('td'):wikitext(' "):done            :tag('td'):wikitext(""):done            :tag('td'):wikitext(""):done            :tag('td'):wikitext(""):done            :tag('td'):wikitext(""):done        :done    end    return tostring(wikitable) end function p._producePerMinute(amount, tba)    -- ACTIONS_PER_MINUTE = 60/TBA*2    -- PRODUCE_PER_MINUTE = ACTIONS_PER_MINUTE * AVG_AMOUNT    return 60/(tba*2)*amount end

- -- Template:Minion Profit -- -- Shows the average bazaar sell price for a specific minino setup - function p.minionProfit(frame) local args = getArgs(frame) local minion = args[1] or args["minion"] or title local tier = args["tier"] or args["t"] local diam_spread = yesno(args["diamond_spreading"] or args["ds"], false) local fuel = args["fuel"] or args["f"] local crystal = yesno(args["crystal"] or args["c"]) local bonus = args["bonus"] or args["other"] local expander = args["expander"] or args["e"] local web = args["flycatcher"] or args["fly"] or args["web"] local sc3000 = yesno(args["sc3000"], false) local input_time = args["input_time"] or args["time"] return p._minionProfit(minion, tier, diam_spread, fuel, crystal, bonus, expander, web, sc3000, input_time, true) end

function p._minionProfit(minion, tier, diam_spread, fuel, crystal, bonus, expander, web, sc3000, input_time, coin) local minionData = require('Module:Minion/Data') if not minionData[minion] then return _error('Invalid minion name', minion) end --remove "Minion" from minion name if minion then minion = minion:gsub("(%w)(%w+)", function(t1, t2) return t1:upper..t2:lower end) minion = minion:gsub("%s+?[Mm]inion", "") end local produce = minionData[minion].items local tba = minionData[minion].stats --set time to 1h if not specified if not input_time then input_time = 1 end --set tier to 11 if not specified. convert tier to arabic if tier then if tonumber(tier) then tier = tonumber(tier) else tier = toArabic(tier) end else tier = 11 end -- get fuel name local aliases = { ["coal"] = "coal", ["block of coal"] = "block of coal", ["enchanted bread"] = "enchanted bread", ["ench bread"] = "enchanted bread", ["enchanted coal"] = "enchanted bread", ["ench coal"] = "enchanted coal", ["enchanted charcoal"] = "enchanted charcoal", ["ench charcoal"] = "enchanted charcoal", ["solar panel"] = "solar panel", ["solar"] = "solar panel", ["enchanted lava bucket"] = "enchanted lava bucket", ["enchanted lava"] = "enchanted lava bucket", ["ench lava"] = "enchanted lava bucket", ["lava"] = "enchanted lava bucket", ["magma bucket"] = "magma bucket", ["magma"] = "magma bucket", ["plasma bucket"] = "plasma bucket", ["plasma"] = "plasma bucket", ["hamster wheel"] = "hamster wheel", ["hw"] = "hamster wheel", ["foul flesh"] = "foul flesh", ["ff"] = "foul flesh", ["tasty cheese"] = "tasty cheese", ["cheese"] = "tasty cheese", ["catalyst"] = "catalyst", ["cat"] = "catalyst", ["c"] = "catalyst", ["hyper catalyst"] = "hyper catalyst", ["hc"] = "hyper catalyst", }   local fuels = { -- note: fuel multipliers should be written as a multiple, in other words, (1+increase%) ["coal"] = 1.05, ["block of coal"] = 1.05, ["enchanted bread"] = 1.05, ["enchanted coal"] = 1.1, ["enchanted charcoal"] = 1.2, ["solar panel"] = 1.25, ["enchanted lava bucket"] = 1.25, ["magma bucket"] = 1.3, ["plasma bucket"] = 1.35, ["hamster wheel"] = 1.5, ["foul flesh"] = 1.9, ["tasty cheese"] = 2, ["catalyst"] = 3, ["hyper catalyst"] = 4, }   -- local fuelTypes = { --    ["catalyst"] = "catalyst", --    ["hyper catalyst"] = "catalyst", -- }   if fuel then fuel = aliases[fuel:lower] or error('Invalid fuel "' .. fuel .. '"') end -- local fuelType = fuel and (fuelTypes[fuel] and fuelTypes[fuel] or "normal") or "normal" -- 'fuel' gets the fuel multiplier fuel = fuels[fuel] or 0 --check if some other params exist; if not set to default if not expander then expander = 0 else expander = tonumber(expander) end if not web then web = 0 else web = tonumber(web) end if not bonus then bonus = 0 else bonus = tonumber(bonus) end -- if not crystal then crystal = 0 else crystal = 0.1 end if input_time then input_time = tonumber(input_time) or error('Invalid time "'..input_time..'"') end

--get items produced by minion local items = {} for i=1,25,1 do --25 because some minions like flower minion have very large amount of different drops if produce[i] and produce[i].condition == nil then items[i] = produce[i] elseif produce[i] and produce[i].condition == "Auto Smelter" and produce[i].item ~= "Cactus Green" then items[i] = produce[i] table.remove(items, i-1) elseif produce[i] and produce[i].condition == "Flint Shovel" then items[i] = produce[i] table.remove(items, i-1) end end local function getMultiplier(multiplier, boost) -- note: 'boost' should be passed as a multiple, in other words, (1+increase%) return boost == 0 and multiplier or multiplier * boost end --get minion tba local tba, multiplier = minionData[minion].stats[tier].tba, 1 --calculate new tba multiplier = getMultiplier(multiplier, fuel) for i = 1, expander>2 and 2 or expander<0 and 0 or expander, 1 do   	multiplier = getMultiplier(multiplier, 1.1) end for i = 1, web>2 and 2 or web<0 and 0 or web, 1 do   	multiplier = getMultiplier(multiplier, 1.1) end if crystal then -- check if minion can use crystal local validMinions = { -- Woodcutting Crystal ["oak"]=true, ["spruce"]=true, ["birch"]=true, ["dark oak"]=true, ["acacia"]=true, ["jungle"]=true, -- Farm Crystal ["wheat"]=true, ["carrot"]=true, ["potato"]=true, ["pumpkin"]=true, ["melon"]=true, ["mushroom"]=true, ["cocoa beans"]=true, ["cactus"]=true, ["sugar cane"]=true, ["nether wart"]=true, -- Mithril Crystal ["coal"]=true, ["iron"]=true, ["gold"]=true, ["diamond"]=true, ["lapis"]=true, ["emerald"]=true, ["redstone"]=true, ["quartz"]=true, ["mithril"]=true, }       if validMinions[minion:lower] then multiplier = getMultiplier(multiplier, 1.1) end end if bonus then multiplier = getMultiplier(multiplier, bonus) end --calculate final produce collected local totalProduce = {} for i,item in pairs(items) do       totalProduce[#totalProduce+1] = { name = item.item, num = p._producePerMinute(item.avg, tba/multiplier)*60*input_time }   end --add diamond spreading diamonds if necessary if diam_spread then totalProduce[#totalProduce+1] = { name = "Diamond", num = p._producePerMinute(1, tba/multiplier)*60*input_time/10 }   end -- If a sc3000 is being used, use enchanted forms for more accurate price if sc3000 then local recipes = minionData[minion].recipes local ingr, numToBeCompacted for i,item in pairs(totalProduce) do           -- Go down recipe list and see if this item can be upgraded for rName,rIngrList in pairs(recipes) do               -- If recipe has more than one ingredient sc3000 doesn't work on it                    -- Some items (such as Silver Fang) are ignored due to not having Bazaar support if #rIngrList == 1 and (rName ~= "Silver Fang" and rName ~= "Melon (block)") then ingr = rIngrList[1] if ingr[1] == item.name and item.num >= ingr[2] then -- integer division, since we only want a whole number of craftable amount numToBeCompacted = math.floor(item.num / ingr[2]) -- if item can be compacted then add new item to list and subtract count away from original recipe ingredient local n = #totalProduce+1 totalProduce[n] = { name=rName, num=numToBeCompacted } item.num = item.num % ingr[2] -- update list with the remainder item = totalProduce[n] end end end end end --get merchant sell pricess for non-bazaar items local non_bazaar_items = { ["Poisonous Potato"] = 10, ['White Wool'] = 2, ['Clownfish'] = 20, }   if minion == "Flower" then non_bazaar_items[""] = 1 end --make a string that can be used in    local bazaarValues = {} for i,item in pairs(totalProduce) do       if non_bazaar_items[item.name] then -- If not in the bazaar calculate the shop sell price bazaarValues[#bazaarValues+1] = item.num*non_bazaar_items[item.name] else bazaarValues[#bazaarValues+1] = {item.name, item.num} end end --finish return bazaar._calcMaterialBuyPrices(bazaarValues, "buy", coin); end

- -- invoked directly -- -- more or less p.minionProfit, just in table for each minion - function p.minionsPageProfitTable(frame) local args = getArgs(frame) local diam_spread = yesno(args["diamond_spreading"] or args["ds"], false) local fuel = args["fuel"] or args["f"] local crystal = yesno(args["crystal"] or args["c"]) local bonus = args["bonus"] or args["other"] local expander = args["expander"] or args["e"] local web = args["flycatcher"] or args["fly"] or args["web"] -- These two have defaults different from normal profit code due to minion page local sc3000 = yesno(args["sc3000"], true) local input_time = args["input_time"] or args["time"] or 24 return p._minionsPageProfitTable(diam_spread, fuel, crystal, bonus, expander, web, sc3000, input_time) end

function p._minionsPageProfitTable(diam_spread, fuel, crystal, bonus, expander, web, sc3000, input_time) -- Needed due to calculator if fuel == "[none]" or fuel == "none" then fuel = nil end local wikitable = mw.html.create('table'):addClass('wikitable sortable searchable') wikitable:tag("caption"):wikitext("Profit per "..input_time.." hours") wikitable:tag("tr") :tag("th"):wikitext("Minion"):done :tag("th"):attr( 'data-sort-type', 'number' ):wikitext("Tier 1"):done :tag("th"):attr( 'data-sort-type', 'number' ):wikitext("Tier 3"):done :tag("th"):attr( 'data-sort-type', 'number' ):wikitext("Tier 5"):done :tag("th"):attr( 'data-sort-type', 'number' ):wikitext("Tier 7"):done :tag("th"):attr( 'data-sort-type', 'number' ):wikitext("Tier 9"):done :tag("th"):attr( 'data-sort-type', 'number' ):wikitext("Tier 11"):done local function getPrice(minion, tier) return p._minionProfit(minion, tier, diam_spread, fuel, crystal, bonus, expander, web, sc3000, input_time, false) end -- Store as a list first so we can sort it   local rowData = {} for minion,_ in pairs(minionData) do       local minionText = minion if minion=="Chicken" then minionText = minionText..'ⓘ' end if minion=="Gravel" then minionText = minionText..'ⓘ' end rowData[#rowData+1] = { minionText, getPrice(minion, 1), getPrice(minion, 3), getPrice(minion, 5), getPrice(minion, 7), getPrice(minion, 9), getPrice(minion, 11) } end -- Now sort minion from best to worst in terms of profit table.sort(rowData, function(a,b)    	if type(a[6]) == 'string' or type(b[6]) == 'string' then    		return false    	else    		return a[6] > b[6]    	end    end) local function makeCoin(num) if type(num) == 'string' then return string.blankCell end return tostring(formatCoins(math.ceil(num))) end -- now add rows to the table for _,data in ipairs(rowData) do       wikitable:tag("tr") :tag("td"):wikitext(data[1]):done :tag("td"):wikitext(makeCoin(data[2])):done :tag("td"):wikitext(makeCoin(data[3])):done :tag("td"):wikitext(makeCoin(data[4])):done :tag("td"):wikitext(makeCoin(data[5])):done :tag("td"):wikitext(makeCoin(data[6])):done :tag("td"):wikitext(makeCoin(data[7])):done end return tostring(wikitable) end

- -- Template: Minion Drops Table -- -- Returns a table with minion drops - function p.minionDropsTable(frame) local args = getArgs(frame) local minion = args.minion or args.m or args[1] or title return p._minionDropsTable(minion) end

function p._minionDropsTable(minion) local notBazaarItems = { ['Cactus Green'] = 1, ['White Wool'] = 2, ['Egg'] = 3, ['Poisonous Potato'] = 10, ['Iron Ore'] = 3, ['Gold Ore'] = 3 }	minion = minion:lower minion = minion:match('(.+) minion') or minion local needsToBeDividedByTwo = minion == "fishing" and true or false local dropData = minionData[ucfirst(minion)].items local hasNotBazaarItem, hasCondition = {false, false} for _,v in pairs(dropData) do		if notBazaarItems[v.item] then hasNotBazaarItem = true end if v.condition           then hasCondition = true     end end local wikitable = mw.html.create('table'):addClass('wikitable') local row = wikitable:tag('tr') row:tag('th'):attr({rowspan=2, colspan=2}):wikitext('Items'):done row:tag('th'):attr({rowspan=2}):wikitext('Chance'):done row:tag('th'):attr({rowspan=2}):wikitext('Average per harvest'):done if hasCondition then row:tag('th'):attr({rowspan=2}):wikitext('Condition'):done end row:tag('th'):attr({rowspan=2}):wikitext('Experience per Item'):done row:tag('th'):attr({colspan=2}):wikitext(hasNotBazaarItem and 'Bazaar/Merchant Sell Price' or 'Bazaar Sell Price'):done row:done row = wikitable:tag('tr') row:tag('th'):wikitext('1 Item'):done row:tag('th'):wikitext('Stack'):done row:done for _,v in pairs(dropData) do		row = wikitable:tag('tr') row:tag('td'):wikitext(slot{ v.item }):done row:tag('td'):wikitext(..v.item..):done local chance = '100%' if v.avg < 1 then if needsToBeDividedByTwo then chance = (v.avg/2*100)..'%' else chance = (v.avg*100)..'%' end end row:tag('td'):wikitext(chance):done row:tag('td'):wikitext(v.avg):done if v.condition then row:tag('td'):wikitext(processItem(v.condition)):done elseif hasCondition then row:tag('td'):wikitext(string.blankCell):done end if v.exp then row:tag('td'):wikitext(v.exp):done else row:tag('td'):wikitext('?'):done end if notBazaarItems[v.item] then row:tag('td'):wikitext(tostring(formatCoins(notBazaarItems[v.item]))):done row:tag('td'):wikitext(tostring(formatCoins(notBazaarItems[v.item]*64))):done else row:tag('td'):wikitext(tostring(bazaar._calcMaterialBuyPrices(, 'buy', true))):done row:tag('td'):wikitext(tostring(bazaar._calcMaterialBuyPrices(, 'buy', true))):done end row:done end return tostring(wikitable) end

-- Finish Module -- return p --''