Module:Minion

local p = {}

local getArgs = require('Module:Arguments').getArgs local mergeArgsSyntax = require('Module:Arguments').mergeArgsSyntax local loader = require('Module:Loader')

local string, table, yesno, inventoryslot, bazaar, colorModule, currency, itemModule, ui, ctModule = loader.require('String', 'Table', 'Yesno', 'Inventory slot', 'Bazaar', 'Color', 'Currency', 'Item', 'UI', 'Crafting') local minionData, collectionData, minionAliases = loader.loadData('Minion/Data', 'Collection/Data', 'Minion/Aliases')

local _error = string._error

local slot = inventoryslot.slot -- not all minions have all 12 tiers; use #TIERS to cover all tiers, instead of using '12' local TIERS = { 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII', 'VIII', 'IX', 'X', 'XI', 'XII' } local infosymbol = 'ⓘ'

-- NPC text for p.minionUI local npctext = { ['Bulvar'] = {name = '&5Bulvar', loc = '&2Dwarven Mines'}, ['Terry'] = {name = '&5Terry', loc = '&bTrappers Den'}, }

-- merchant sell pricess for non-bazaar items, for profit calculation local nonBazaarMerchantSellPrices = { ['Poisonous Potato'] = 10, ['White Wool'] = 2, ['Clownfish'] = 20, ['Cactus Green'] = 1, ['White Wool'] = 2, ['Egg'] = 3, ['Poisonous Potato'] = 10, ['Iron Ore'] = 3, ['Gold Ore'] = 3, }

-- Produces that aren't recorded as items in Minion/Data -- Used in p._minionPerDayTableCombined (DUMC) and _getStatTableBaseCost local differentList = { ['dandelion'] = {'Flower'}, ['poppy'] = {'Flower'}, }

local function npcSellPrice(item) if string.trim(item) == '' then return 0 end local response = itemModule._api(item) if response then return response.npc_sell_price else error(('Item API search: Invalid item name %s. Aliases can be added Module:Item/ApiAliases'):format(item)) end end

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 function getMinion(name) return minionData[name] or formattedError("Minion %q does not exist in the database, consider adding it", 3, name) end

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

-- utility: peakStat -- not all minions have all 12 tiers; this function returns the values of --				the highest tier and the highest craftable tier of a minion local function peakStat(name) highestTier = table.length(getMinion(name).stats) for x = #TIERS, 1, -1 do		if minionData[name].stats[x] and minionData[name].stats[x].crafting then highestCraftable = x		end if highestCraftable then break end end return highestTier, highestCraftable end

p.peakStat = peakStat

-- 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 = getMinion(minionName) local aliases = minionAliases.minionDataAliases local data = aliases[data:lower] local tier = tonumber(string._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'], false) local fullwidth = yesno(args['fullwidth'] or args['noscroll'], false) return p._minionPerDayTable( minion, amount, item, split, fullwidth ) end

function p._minionPerDayTable( minion, amount, item, split, fullwidth ) if not minionData[minion] then error('Invalid minion ' .. minion) end local HALF_ROW = 6 local actionsPerDay, 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 centertxt table-fixed') if fullwidth and not split then _table:addClass('full-width-1') end _table:tag('caption'):wikitext(string.format( '%s to acquire %s using %s %s Minion (By tier, No fuel, No Minion Upgrades) If the player has multiple minions, divide the number of days by how many minions will be used. ',		string.makeTitle('Days' .. infosymbol, 'The &#34;Day&#34; used in here has a duration of 1 real-life day, or 72 SkyBlock days.'), itemModule._resourceDisplay(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 fullwidth and not split then row:tag('th'):wikitext(tier):done else row:tag('th'):wikitext(tier):addClass('article-minion-smallTabs'):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) days = days < 1 and math.floor(days * 100) / 100 or				days < 10 and math.floor(days * 10) / 10 or				math.floor(days) 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 = string.wrapHtml(tostring(_table), 'div', {		class = 'article-scrollable',	}) return fullwidth and tostring(_table) or scrollable end

- -- Template:Days Using Minions Combined -- Template:DUMC -- -- Displays Days Using Minions with tabbers -

function p.minionPerDayTableCombined( frame ) local args = getArgs(frame) local lang = mw.language.getContentLanguage local split = yesno(args['split'], false) local fullwidth = yesno(args['fullwidth'] or args['noscroll'], false) -- first combine both methods of entering data into one list local resources = mergeArgsSyntax(args) local items = {} for _, item in ipairs(resources) do		local numOrNil = tonumber(lang:parseFormattedNumber(item)) if numOrNil then items[#items+1] = numOrNil else local num, name = mw.ustring.match(item, '([%d,%.]+)x? %[?%[?([%a%s%d,\'-_]*)%|?.*%]?%]?') if not num then -- also test for format name, num = mw.ustring.match(item, '([%a%s,\'-_]*),([%d]+)') if not num then return _error('Invalid item format: '..item) end end items[#items+1] = { name, tonumber(lang:parseFormattedNumber(num)) } end end return p._minionPerDayTableCombined(items, split, fullwidth) end

function p._minionPerDayTableCombined(entries, split, fullwidth) local result = {} local function addtolist(key, val) local stor = differentList[key:lower] if stor then table.merge(stor, val) else differentList[key:lower] = { val } end end for mn, dt in pairs(minionData) do		-- Adds each item that is in Minion/Data into differentList table.each(dt.items, function(i)			i = i.item			if i ==  then return end			addtolist(i, mn)			if i:match('^Raw ') then				local foo = i:gsub('^Raw ',)				addtolist(foo, mn)			end		end) end for i, entry in pairs(entries) do		minionList = differentList[entry[1]:lower] if minionList then table.push(result, ('|-|%s='):format(entry[1])) table.each(minionList, function(_minion)				local frame = mw.getCurrentFrame				table.push(result, p._minionPerDayTable(_minion, entry[2], entry[1], split, fullwidth))			end) else local frame = mw.getCurrentFrame table.push(result, ('|-|%s='):format(entry[1])) table.push(result, p._minionPerDayTable(entry[1], entry[2], entry[1], split, fullwidth)) end end result = ' ' .. table.concat(result) .. ' '	local frame = mw.getCurrentFrame return frame:preprocess(result) 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

local recipeCache = {} function p._getRecipe( minionName, tier, mode ) local recipeOutput = minionName .. ' Minion ' .. TIERS[tier] recipeCache[recipeOutput] = recipeCache[recipeOutput] or {} local function getval(k) return recipeCache[recipeOutput][k] end local minion = getMinion(minionName) local crafting = minion.stats[tier].crafting if not crafting or crafting.info then return end local recipeBase = crafting.item .. (crafting.num and ',' .. crafting.num or '') local recipeMinion = crafting.base or minionName .. ' Minion ' .. TIERS[tier-1] local ret = getval('grid') or { A1 = crafting.A1 or recipeBase, B1 = crafting.B1 or recipeBase, C1 = crafting.C1 or recipeBase, A2 = crafting.A2 or recipeBase, B2 = crafting.B2 or recipeMinion, C2 = crafting.C2 or recipeBase, A3 = crafting.A3 or recipeBase, B3 = crafting.B3 or recipeBase, C3 = crafting.C3 or recipeBase, Output = recipeOutput }	recipeCache[recipeOutput].grid = table.deepCopy(ret, true) local mat = {} if mode == 'table' then -- return in a table format instead of crafting table format if getval('table') then return getval('table') end local ret2 = {} for k, v in pairs(ret) do			local item, n = v:match('^%s*(.-)%s*,%s*(%d+)%s*$') if tonumber(n) then ret2[k] = { item = item, num = tonumber(n) } else ret2[k] = { item = v, num = 1 } end end recipeCache[recipeOutput].table = table.deepCopy(ret2, true) return ret2 elseif mode == 'materials' then if getval('materials') then return getval('materials') end local ret3, mat = {}, {} for k, v in pairs(ret) do			local item, n = v:match('^%s*(.-)%s*,%s*(%d+)%s*$') if tonumber(n) then mat[item] = (mat[item] or 0) + tonumber(n) else mat[v] = (mat[v] or 0) + 1 end end mat[recipeOutput] = nil for k, v in pairs(mat) do			table.push(ret3, { item = k, num = v }) end recipeCache[recipeOutput].materials = table.deepCopy(ret3, true) return ret3 else return ret end end

function p._minionRecipeGallery( minionName ) local minion = getMinion(minionName) -- Get a list of the desired tiers local tiers = {} local start = minion.stats[1] and (minion.stats[1].crafting.info and 2 or 1) local _, nCraftable = peakStat(minionName) for i = start, nCraftable do tiers[#tiers+1] = i end -- Generate crafting tables local tables = {} for _,tier in ipairs(tiers) do		local grid = p._getRecipe( minionName, tier ) tables[#tables+1] = grid and ui.craftingTable(grid) or nil 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 mw.getCurrentFrame:preprocess(p._minionAnimatedCraftingTable( minion, item, from, to )) end

function p._minionAnimatedCraftingTable( minionName, item, from, to ) local minion = getMinion(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 return (''):format(table.concat(table.map(tiers, function(v)		return ('|%s Minion %s'):format(minionName, string._toRoman(v))	end))) 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 function _getStatTableBaseCost(minionName, item, num) -- Find the total number of the base minion items -- returns { {item, num} } local minion = getMinion(minionName) local recipes = minion.recipes if not recipes[item] then return (bazaar._getProduct(item) or not not differentList[item:lower]) and { {item, num} } or {} 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				table.push(items, value2) end end return items end local function _getStatTableSingleTier(minionName, tier) local minion = getMinion(minionName) local stats = minion.stats[tier] local materials = p._getRecipe( minionName, tier, 'materials' ) local items = materials or stats.trade or {} local cost = {} for _, itemData in ipairs(items) do			cost = table.merge(cost, _getStatTableBaseCost(minionName, itemData.item, itemData.num)) end return cost end local function _getStatTableCumulativeTotal(minionName, tier) -- Find total of base minion items for all tiers up to the one passed in		local totals = {} for i = 1, tier, 1 do			local cost = _getStatTableSingleTier(minionName, i)			for _, value in ipairs(cost) do				local item = value[1] totals[item] = (totals[item] or 0) + value[2] end end return totals end local minion = getMinion(minionName) local showBazaarCost = minionName:lower ~= 'flower' local totalsBazaarCost = 0 -- We want the cost of cumulative enchanted version local wikitable = mw.html.create('table'):addClass('wikitable article-margin-off article-msTable') local cumulativeDisplay = string.wrapHtml(string.makeTitle('CUMU','This result is cumulative.'),'sup') -- 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('Total Upgrade Cost'):done if showBazaarCost then row:tag('th'):wikitext('Bazaar Upgrade Cost'):done end row:tag('th'):attr({ rowspan = 2 }):wikitext('Recipe'):done -- Header row (second row) row = wikitable:tag('tr') row:tag('th'):wikitext('Total Cumulative Cost') if showBazaarCost then row:tag('th'):wikitext('Bazaar Cumulat. Cost') end -- Display non-header rows local baseCraftingItem = minion.stats[2].crafting.item local items = minion.items 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 recipe, materials = p._getRecipe(minionName, i), p._getRecipe( minionName, i, 'materials' ) local actionsPerDay = _actionsPerMinute(minionName, i) * 60 * 24 -- if need unique base material count, uncomment the following -- local unique = {} -- for j = i, 1, -1 do		-- 	local stats, crafting, mainItem = getInfo(j) -- 	unique[mainItem] = true -- end local uniqueAdded = true if i > 1 then local stats2, crafting2, mainItem2 = getInfo(i - 1) uniqueAdded = mainItem2 ~= mainItem end --		-- Calculate info for below --		-- Calculate Item Costs local tierCosts = {} if materials then tierCosts = table.map(materials, function (v, i)				return (not v.item:match(minionName .. ' Minion')) and ('%sx %s'):format(v.num, v.item) or nil			end) elseif stats.trade then for _, itemData in ipairs(stats.trade) do				if itemData.item:lower == 'coin' then table.push(tierCosts, itemData.num .. ' coins') elseif itemData.item:lower == 'pelt' then table.push(tierCosts, itemData.num .. ' pelts') else table.push(tierCosts, itemData.num .. 'x ' .. itemData.item .. '') end end end -- Find totals for items needed for all tiers up to this one local totals = {} local totalsAbbr = {} local totalsObj = _getStatTableCumulativeTotal(minionName, i)		for key, value in pairs(totalsObj) do table.push(totals, string._formatNum(math.ceil(value)) .. ' ' .. key) table.merge(totalsAbbr, ('%dx %s'):format(math.ceil(value), key)) end -- Calculate Bazaar Costs local abbr = {} local bazaarCost = 0 if showBazaarCost then -- Since crafting and trade data have different formats, list format is needed local basecosts = _getStatTableSingleTier(minionName, i)			local items = basecosts or stats.trade or {} for _, itemData in ipairs(items) do				local craftNum, craftItem = itemData.num or itemData[2], itemData.item or itemData[1] -- 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 = 0 if bazaar._bazaarable(craftItem) then calculatedPrice = bazaar._calcMaterialBuyPrices(, 'buy') table.merge(abbr, ('%dx %s'):format(craftNum, craftItem)) end bazaarCost = bazaarCost + (tonumber(calculatedPrice) or 0) end end -- Totals totalsBazaarCost = totalsBazaarCost + bazaarCost end --		-- First row for this tier --		local sepClass = uniqueAdded and 'table-section-separator thick' or '' local row = wikitable:tag('tr'):addClass('article-row-main'):attr('id', TIERS[i]) -- Tier + Icon row:tag('th'):attr({ rowspan=2 }):addClass(sepClass):wikitext(TIERS[i] .. ' ' .. slot{ minionName .. ' Minion ' .. TIERS[i] }):done -- Info row:tag('td') :attr({ rowspan=2 }):addClass(sepClass) :wikitext('Cooldown: '..colorModule._colorTemplates('Green', stats.tba .. 's')) :wikitext(' Storage: '..colorModule._colorTemplates('Yellow', stats.storage)) :done -- Cost row:tag('td'):addClass(sepClass):wikitext(table.length(tierCosts) > 0 and			 or ):done -- Bazaar Cost if showBazaarCost then row:tag('td'):addClass(sepClass):wikitext(string.makeTitle( '', 'Materials used: &#10;' .. table.concat(abbr, ', &#010;'))) 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 slots[#slots+1] = ui.slot{trd.num .. ' coins'} elseif trd.item:lower == 'pelt' then slots[#slots+1] = ui.slot{trd.num .. ' pelts'} else slots[#slots+1] = ui.slot{ trd.item .. ',' .. trd.num } end end row:tag('td'):attr({ rowspan = 2 }):addClass(sepClass .. 'centertxt') :wikitext(' Merchant ') :tag('div'):wikitext(' Required ' .. 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 }):addClass(sepClass):addClass('centertxt'):wikitext(crafting.info):done else -- Recipe exists local grid = recipe grid.Output = nil row:tag('td'):attr({ rowspan = 2 }):addClass(sepClass):addClass('centertxt article-msTable-crafting') :wikitext(ctModule.craftingGrid(grid)):done end --		-- Second row for this tier --		row = wikitable:tag('tr'):addClass('oddrow2 article-row-bound') -- Total cost row:tag('td') :tag('div'):addClass('article-msTable-cumulative'):wikitext(table.length(totals) > 0				and (' ' .. cumulativeDisplay) or '') -- Bazaar Total Cost if showBazaarCost then row:tag('td') :tag('div'):addClass('article-msTable-cumulative'):wikitext(string.makeTitle( '',					'Materials used: &#10;' .. table.concat(totalsAbbr, ', &#010;')) .. cumulativeDisplay				) end end return tostring(wikitable) 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'):addClass('txt-nowrap') :wikitext('Listed below are the profits of this minion when its items are sold to Bazaar. No Minion Fuel, Compactors, nor Diamond Spreading are used in the calculation.', ' ', '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 day'):attr ({ colspan = 3 }):done :done wikitable:tag('tr') :tag('th'):wikitext('Items'):done :tag('th'):wikitext('Bazaar Profit'):done :tag('th'):wikitext('NPC 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 -- 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 with ) 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 npcSellPrice(items[j].item) then bazaarStr[#bazaarStr+1] = '\n*' .. (p._producePerMinute(items[j].avg, tba) * 60) * npcSellPrice(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 nonBazaarMerchantSellPrices[items[j].item] then bazaarStr_24[#bazaarStr_24+1] = '\n*' .. (p._producePerMinute(items[j].avg, tba) * 60 * 24 * nonBazaarMerchantSellPrices[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 = string.roundNumber(60/(tba), 2) else hpm = string.roundNumber(60/(tba*2), 2) end -- NPC sell price local npcPrice, npcPriceSum = {}, 0 npcPrice[1] = (p._producePerMinute(items[1].avg, tba) * 60 * 24) * (npcSellPrice(items[1].item)) for j = 2, #items, 1 do			if items[j].item then npcPrice[j] = (p._producePerMinute(items[j].avg, tba) * 60 * 24) * (npcSellPrice(items[j].item)) end end for j = 1, #npcPrice do			npcPriceSum = npcPriceSum + npcPrice[j] end -- add the table cells and fill them with the data from above wikitable:tag('tr') :tag('th'):wikitext(string._toRoman(i), ' '):done :tag('td'):wikitext(' '):done :tag('td'):wikitext(''):done :tag('td'):wikitext(''):done :tag('td'):wikitext(npcPriceSum ~= 0 and ():format(npcPriceSum) or ):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 minion 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'] return p._minionProfit(minion, tier, {		diam_spread = yesno(args['diamond_spreading'] or args['ds'], false),		fuel = args['fuel'] or args['f'],		crystal = yesno(args['crystal'] or args['c']),		bonus = args['bonus'] or args['other'],		expander = args['expander'] or args['e'],		web = args['flycatcher'] or args['fly'] or args['web'],		sc3000 = yesno(args['sc3000'], false),		beacon = args['beacon'],		infusion = yesno(args['infusion'], false),		input_time = args['input_time'] or args['time'],		selltobazaar = yesno(args['selltobazaar'] or args['bazsell'], true),		coin = true,		productinfo = false,	}) end

function p._minionProfit(...) local minion, tier, options, diam_spread, fuel, crystal, bonus, expander, web, sc3000, beacon, infusion, input_time, selltobazaar, coin, productinfo if type(({...})[3]) ~= 'table' then minion, tier, diam_spread, fuel, crystal, bonus, expander, web, sc3000, beacon, infusion, input_time, selltobazaar, coin, productinfo = ... else minion, tier, options = ... diam_spread, fuel, crystal, bonus, expander, web, sc3000, beacon, infusion, input_time, selltobazaar, coin, productinfo = options.diam_spread, options.fuel, options.crystal, options.bonus, options.expander, options.web, options.sc3000, options.beacon, options.infusion, options.input_time, options.selltobazaar, options.coin, options.productinfo end local data = minionData[string.ucfirst(minion)] if not data then return _error('Invalid minion: '.. 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 = data.items -- Set time to 1h if not specified if not input_time then input_time = 1 end -- Set tier to max tier if not specified. convert tier to arabic local maxTier = table.length(data.stats) if tier then if tonumber(tier) then tier = tonumber(tier) if tier > maxTier then return nil end else tier = string._toArabic(tier) end else tier = maxTier end local fuels = minionAliases.fuelData if fuel and fuel:lower:match('none') then fuel = nil end if fuel then fuel = minionAliases.fuelAliases[fuel:lower] or error('Invalid fuel "' .. fuel .. '"') end -- '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) elseif produce[i] and produce[i].condition == 'Enchanted Egg' then items[i] = produce[i] end end local function getMultiplier(multiplier, boost) -- Note: 'boost' should be passed as a multiple, in other words, (1 + percentage increase) return boost == 0 and multiplier or multiplier * boost end -- Get minion tba local tba, multiplier = data.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 = minionAliases.crystalValid if table.find(validMinions, minion:lower) then multiplier = getMultiplier(multiplier, 1.1) end end if bonus then multiplier = getMultiplier(multiplier, bonus) end if beacon and yesno(beacon, true) ~= false and not beacon:lower:match('none') then local beaconLv = tonumber(string._toArabic(beacon)) if beaconLv > 0 and beaconLv < 6 then multiplier = getMultiplier(multiplier, 1 + .2 * beaconLv) end end if infusion then multiplier = getMultiplier(multiplier, 1.1) end -- Calculate final produce collected local totalProduce = {} local itemCount = 0 for i, item in pairs(items) do		itemCount = itemCount + item.avg 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(itemCount, 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 = table.merge({			['Enchanted Diamond']={ {'Diamond',160} },			['Enchanted Diamond Block']={ {'Enchanted Diamond',160} },		}, table.deepCopy(data.recipes, true)) local i = 1 while totalProduce[i] do			local item = totalProduce[i] -- Go down recipe list and see if this item can be upgraded for recipeName, recipeList 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 table.length(recipeList) == 1 and (recipeName ~= 'Silver Fang' and recipeName ~= 'Melon (block)') then local ingr = recipeList[1] if ingr[1] == item.name and item.num >= ingr[2] then -- Integer division, since we only want a whole number of craftable amount local numToBeCompacted = math.floor(item.num / ingr[2]) local numRemains = item.num % ingr[2] -- If item can be compacted then add new item to list and subtract count away from original recipe ingredient if numToBeCompacted > 0 then table.insert(totalProduce, i + 1, { name=recipeName, num=numToBeCompacted }) end totalProduce[i].num = numRemains end end end i = i + 1 end end -- Process all produces and combine duplicate items local temp, index = {}, 1 while totalProduce[index] do		local v = totalProduce[index] local j = table.findIndex(temp, v.name) if minion == 'Flower' then v.name = 'Any Flower Variant' end if v.num == 0 then table.remove(totalProduce, index) elseif j then totalProduce[j].num = totalProduce[j].num + v.num table.remove(totalProduce, index) else table.push(temp, v.name) index = index + 1 end end -- Make a string that can be used in 	local bazaarValues = {} for i, item in pairs(totalProduce) do		if yesno(selltobazaar, false) and bazaar._getProduct(item.name) then bazaarValues[#bazaarValues+1] = { item.name, item.num } else local price = (minion == 'Flower' and 1 or npcSellPrice(item.name)) if price ~= nil then bazaarValues[#bazaarValues+1] = item.num * price end end end -- Finish local finalprice = bazaar._calcMaterialBuyPrices(bazaarValues, 'sell', coin) if productinfo then return finalprice, totalProduce else return finalprice end end

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

function p._minionsPageProfitTable(...) local diam_spread, fuel, options, crystal, bonus, expander, web, sc3000, beacon, infusion, input_time if type(({...})[1]) ~= 'table' then diam_spread, fuel, crystal, bonus, expander, web, sc3000, beacon, infusion, input_time = ... options = { diam_spread = diam_spread, fuel = fuel, crystal = crystal, bonus = bonus, expander = expander, web = web, sc3000 = sc3000, beacon = beacon, infusion = infusion, input_time = input_time, }	else options = ... diam_spread, fuel, crystal, bonus, expander, web, sc3000, beacon, infusion, input_time = options.diam_spread, options.fuel, options.crystal, options.bonus, options.expander, options.web, options.sc3000, options.beacon, options.infusion, options.input_time 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 :tag('th'):attr( 'data-sort-type', 'number' ):wikitext('Tier '..#TIERS):done -- Store as a list first so we can sort it	local rowData = {} for minion,_ in pairs(minionData) do		local minionText = table.concat{ mw.getCurrentFrame:expandTemplate{ title = 'MinionName', args = {minion, short='y'} }, minion == 'Chicken' and 'ⓘ' or minion == 'Gravel' and 'ⓘ' or '', }		local function getPrice(minion, tier) local ret = {} ret[1], ret[2] = {}, {} ret[1], ret.products = p._minionProfit(minion, tier, table.merge({ selltobazaar = false, coin = false, productinfo = true }, options)) ret[2] = p._minionProfit(minion, tier, table.merge({ selltobazaar = true, coin = false, productinfo = true }, options)) return ret end rowData[#rowData+1] = { getPrice(minion, 1), getPrice(minion, 3), getPrice(minion, 5), getPrice(minion, 7), getPrice(minion, 9), getPrice(minion, 11), getPrice(minion, #TIERS), text = minionText } end -- Now sort minion from best to worst in terms of profit table.sort(rowData, function(a, b)		local _1, _2 = tonumber(a[6][1]), tonumber(b[6][1])		local _3, _4 = tonumber(a[6][2]), tonumber(b[6][2])		local _a = (_1 and _3) and (_1 > _3 and _1 or _3) or (_1 or _3)		local _b = (_2 and _4) and (_2 > _4 and _2 or _4) or (_2 or _4)		if _a == nil or _b == nil then			return false		else			return _a > _b		end	end) -- Now add rows to the table for _, data in ipairs(rowData) do		local row = wikitable:tag('tr') :tag('td'):wikitext(data.text):done for count = 1, 7 do			local npc = { val = data[count][1], text = string.makeTitle('NPC', 'Price when sold to a shop.'), }			local bz = { val = data[count][2], text = string.makeTitle('BZ', 'Price when sold to bazaar. Non-bazaar items sold to shop instead.'), }			local products = data[count].products row:node(p._createPriceCell(npc, bz, nil, products)) end end return tostring(wikitable) end

function p._createPriceCell(npc, bz, amount, products) local function makeCoin(num) if tonumber(num) == nil then return end return tostring(currency._coins(math.ceil(num))) end amount = amount or 1 local productInfo = table.concat(table.map(products or {}, function(prod) return ('%.0fx %s; '):format(tonumber(prod.num) or 0, prod.name) end), '&#10;') productInfo = productInfo ==  and  or string.makeTitle('[\'\'Produces\'\']', 'List of Produces: &#10;' .. productInfo) local cell = mw.html.create('td') npc.price = tonumber(npc.val) bz.price = tonumber(bz.val) npc.price = npc.price and npc.price * amount bz.price = bz.price and bz.price * amount npc.coin = makeCoin(npc.price) bz.coin = makeCoin(bz.price) if npc.coin and bz.coin then local shopfirst = npc.price > bz.price -- shop prize first or bazaar price first if shopfirst then cell:attr('data-sort-value', shopfirst and npc.price or bz.price):wikitext((' %s: %s %s: %s %s '):format( 'color-alburn', npc.text, npc.coin, 'color-blue_violet', bz.text, bz.coin, productInfo )):done else cell:attr('data-sort-value', shopfirst and npc.price or bz.price):wikitext((' %s: %s %s: %s %s '):format( 'color-blue_violet', bz.text, bz.coin, 'color-alburn', npc.text, npc.coin, productInfo )):done end elseif npc.coin and not bz.coin then cell:attr('data-sort-value', npc.price):wikitext((' %s: %s %s '):format( 'color-alburn', npc.text, npc.coin, productInfo )):done elseif bz.coin and not npc.coin then cell:attr('data-sort-value', bz.price):wikitext((' %s: %s %s '):format( 'color-alburn', bz.text, bz.coin, productInfo )):done else cell:wikitext(string.blankCell):done end return cell 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) minion = minion:lower minion = minion:match('(.+) minion') or minion local divideTwo = minion:lower == 'fishing' and true or false local dropData = getMinion(string._ucfirst(minion)).items local hasCondition = false for _,v in pairs(dropData) do		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({ colspan=2 }):wikitext('Harvest Amount'):done if hasCondition then row:tag('th'):attr({ rowspan=2 }):wikitext('Condition'):done end row:tag('th'):attr({ rowspan=2 }):wikitext('XP Per Item'):done row:tag('th'):attr({ colspan=2 }):wikitext('Sell Price'):done row:done row = wikitable:tag('tr') row:tag('th'):wikitext('Avg.'):done:tag('th'):wikitext('%'):done row:tag('th'):wikitext('Per Item'):done:tag('th'):wikitext('Stack'):done row:done for _, v in pairs(dropData) do		-- Items row = wikitable:tag('tr') row:tag('td'):wikitext(ui.slot{ v.item }):done row:tag('td'):wikitext(..v.item..):done -- Average Harvest / Chance row:tag('td'):wikitext(v.avg):done row:tag('td'):wikitext(v.avg < 1 and (v.avg / (divideTwo and 2 or 1) * 100) or 100):done -- Condition if v.condition then row:tag('td'):wikitext(itemModule._itemDisplay(v.condition)):done elseif hasCondition then row:tag('td'):wikitext(string.blankCell):done end -- XP Per Item row:tag('td'):wikitext(v.exp or '?'):done -- Sell Price local npc = { val = npcSellPrice(v.item), text = string.makeTitle('NPC', 'Price when sold to a shop.'), }		local bz = { val = bazaar._getProduct(v.item) and bazaar._calcMaterialBuyPrices(, 'sell') or nil, text = string.makeTitle('BZ', 'Price when sold to bazaar. Non-bazaar items sold to shop instead.'), }		row:node(p._createPriceCell(npc, bz, 1)):done row:node(p._createPriceCell(npc, bz, 64)):done row:done end return tostring(wikitable) end

-- Template:Days using minions collection -- -- Shows a table with days to acquire each tier of a collection with a minion

function p.minionCollectionDaysTable( frame ) --local parameters = frame.args local args = getArgs(frame) local coll = args['collection'] or args['coll'] or args[1] local minion = args['minion'] return p._minionCollectionDaysTable( coll, minion ) end

function p._minionCollectionDaysTable( coll, minion ) local function handleMinion local mins = {} for c,dt in pairs(collectionData) do			if dt.minion and dt.minion == (minion or coll) then table.push(mins, c)			end end if #mins == 0 then return else return mins end end if minion then local t = handleMinion if t then return table.concat(table.map(t, function(m) return p._minionCollectionDaysTable(m) end),'\n') end end coll = coll or minion if not collectionData[string.ucfirst(coll)] then error('Invalid collection or minion: '..coll) end local minion = collectionData[string.ucfirst(coll)].minion if not minion then error('Collection ' .. coll .. ' cannot be obtained by minions.') end local wikitable = mw.html.create('table'):addClass('article-table') wikitable:tag('caption'):wikitext(string.format( 'Time needed to acquire each tier of the %s Collection using a %s Minion (by tier, no fuel) If you have multiple minions, just divide the number of days by how many minions you have. ',		string.ucfirst(coll), minion)) local highestTier, _ = peakStat(minion) local row = wikitable:tag('th'):wikitext('Minion Tier'):done for i = 1,highestTier,1 do		row:tag('th'):addClass('article-minion-coolLabel'):wikitext(string._toRoman(i)):done end for i,ctier in ipairs(collectionData[coll]) do		local row = wikitable:tag('tr') row:tag('th'):wikitext(			string.wrapHtml(('%s %s'):format(string.ucfirst(coll), string._toRoman(i)),'span', { class = 'article-minion-coolLabel' })			..' '			..string.wrapHtml(('(%s)'):format(ctier.required),'span', { class = 'color-green' } )		):done for mtier = 1, highestTier, 1 do			local actPerHour = _actionsPerMinute(minion, mtier) * 60 local durationHr = ctier.required / actPerHour if durationHr < 24 then row:tag('td'):wikitext(('%.2f'):format(durationHr) .. 'h') else row:tag('td'):wikitext(('%.2f'):format(durationHr/24) .. 'd') end end end return tostring(wikitable) end

- -- Template: Minion Recipes UI -- -- Creates a minion recipes UI - function p.minionRecipesUI(frame) return p._minionRecipesUI(table.deepCopy(getArgs(frame))) end

- -- function: minionRewardsUI(args?: table|frame) -- -- Invoked by. - function p._minionRecipesUI(args) checkType(1, args, 'table') local col = (args['collection'] or args[1] or titleObj.rootText):gsub('%s[Mm]inion', '') return ui._collectionRewardsUI{ col, args['reward_tier'] or args[2] or 1, pipeline(		*%s Minion $n	, string.dedent, col, string.format, 11, string._repeat):gsub('(%d+)', function(m) return string._toRoman(m) end), hide=yesno(args['hide'], true), id=args['id'], return_id=args['return_id'] or args['return-id'], return_text=args['return_text'], } 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 rowLetter = { 'A', 'B', 'C', 'D', 'E', } for y = 1,size,1 do		slots[y] = {} for x = 1,size,1 do			slots[y][x] = args[rowLetter[x]..y]		end end return p.minionIdealLayout( minion, size, ideal, border, slots ) end

function p.minionIdealLayout( 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 margin-centered 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 = ('[%s]%s'):format(minion, minion) if customSlots[y][x] then img = ('%s;%s'):format(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

function p._minionUI( minion ) local name, tier = minion:match('(.-)%s?[Mm]?[Ii]?[Nn]?[Ii]?[Oo]?[Nn]?%s([%divxIVX]+)') if tier then tier = tonumber(tier) or string._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 slotno = 1, 15, 1 do -- Minions have 15 total slots for y = 1, 10, 1 do -- All slots unlocked at tier 10 local avail = minionData[name].stats[y].storage / 64 storageTier[slotno] = y			if avail >= slotno then break end end end local function available( num ) if tier >= storageTier[num] then return '' -- Empty slot else return ('White Stained Glass Pane, none, &eStorage unlocked at tier %s, none'):format(string._toRoman(storageTier[num])) end end local title = name..' Minion '..string._toRoman(tier) local titleLink = name..' Minion#'..string._toRoman(tier) local description = minionData[name].description or 'No Description' local upgradeMessage = '' local upgradeMessageView = '' local upgradeMessageUpgrade = '' 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 uitable = { title, arrow = 'none', close = 'none', dnl = true, ['1,4'] = 'Redstone Torch, none, &aIdeal Layout, &7View the most effecient spot for/this minion to be placed in.', ['1,5'] = ('%s, none;%s, &9%s, &7%s//&7Time Between Action: &a%ss/&7Max Storage: &e%s/&7Resources Generated: &b0'):format(			title, titleLink, title, description,			minionData[name].stats[tier].tba, minionData[name].stats[tier].storage		), ['1,6'] = 'Gold Ingot, none, &aNext Tier, '..upgradeMessageView, ['2,2'] = 'Lime Stained Glass Pane, none, &aMinion Skin Slot, &7You can insert a Minion Skin/here to change the appearance of/your minion.', ['3,2'] = 'Orange Stained Glass Pane, none, &aFuel, &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!', ['4,2'] = 'Blue Stained Glass Pane, none, &aAutomated Shipping, 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.', ['5,2'] = 'Yellow Stained Glass Pane, none, &aUpgrade Slot, &7You can improve your minion by/adding a minion upgrade item/here.', ['6,2'] = 'Yellow Stained Glass Pane, none, &aUpgrade Slot, &7You can improve your minion by/adding a minion upgrade item/here.', ['6,4'] = 'Chest, none, &aCollect All, &eClick to Collect all items!', ['6,6'] = 'Diamond, none, &aQuick-Upgrade Minion, &7Click here to upgrade your/minion to the next tier.//'..upgradeMessageUpgrade, ['6,9'] = 'Bedrock, none, &aPickup Minion, &eClick to pickup!', }	for i = 0, 14, 1 do		uitable[('%d,%d'):format(math.floor(i/5)+3, i%5+4)] = available(i+1) end return tostring(ui.ui(uitable)) end

-- Finish Module -- return p