Jump to content

Module:Factory slot: Difference between revisions

From The Perfect Tower II
No edit summary
Stack counts accept an uppercase E
 
Line 142: Line 142:
local name, count;
local name, count;


name, count = ustr.match(itemStackDesc, "^%s*(.*),%s*([" .. INFINITY .. "0-9%.e]+)%s*$");
name, count = ustr.match(itemStackDesc, "^%s*(.*),%s*([" .. INFINITY .. "0-9%.eE]+)%s*$");
if (count == INFINITY) then
if (count == INFINITY) then
count = math.huge;
count = math.huge;

Latest revision as of 01:41, 7 October 2026

Draws one slot of a Factory grid: an item icon with its stack count. Module:UI uses it for the crafting grids of {{Crafting}}, and Module:Recipe table uses its stack parser. Inspired by and partially based on Minecraft Wiki's Module:Inventory slot.

The look is in Template:Crafting/styles.css (TemplateStyles). main adds that stylesheet; when a module calls slot, the module adds it, as Module:UI does.

Item stacks

A stack is written Name or Name,count. A tier at the end, Circuit Plate T3 or Circuit Plate [T3], gives the name Circuit Plate [T3] and the icon File:Circuit Plate T3.png, shown at 32×32 pixels and pixelated. Counts above 1 show in the corner of the slot: in full below a million, then as 1.5E6 (the E with the brand kit class .tpt-num__e), and as ∞ from 1E18 or when written ∞. The slot count always uses this notation; it doesn't follow the reader's number format.

Counts can be written in full or in E notation, with either case: Plate,1500000, Plate,1.5e6 and Plate,1.5E6 are the same stack.

Functions

  • slot(args): the slot as an mw.html span. args[1] is a stack string or table; without it the slot is empty. link is the page the icon and count link to (no link when empty or no). class and style go on the slot, itemClass and itemStyle on the item, numStyle on the count.
  • main(frame): the same for {{Factory slot}}, which no page uses at the moment.
  • parseStack(text) and stringifyStack(stack): convert between stack strings and { name, count } tables.
  • formatCount(count): the count text, or nil for counts below 2.



-- Used to render Factory inventory slots
-- Inspired by and partially based on Minecraft Wiki's [[Module:Inventory slot]]
require("Module:No globals");
local checkType = require("libraryUtil").checkType;
local ustr = mw.ustring;
local INFINITY = "∞";

-- The look of the slots is in [[Template:Crafting/styles.css]] (TemplateStyles).
local STYLES = "Template:Crafting/styles.css";

local p = {};

--- Formats the item stack count
--- @param count integer
--- @return string|nil
function p.formatCount(count)
	checkType('"Module:Factory slot".formatCount', 1, count, "number");

	count = math.floor(count);
	if (count >= 1e18) then
		return INFINITY;
	end

	if (count < 2) then
		return nil;
	end

	if (count < 1e6) then
		-- less than a million
		return tostring(count);
	end

	local exp = 3;
	while true do
		exp = exp + 3;
		count = math.floor(count / 1e3 + 0.5);

		if (count < 1e6 or exp == 15) then
			--- @type string, string
			local result, b = tostring(count):match("^(%d-%d)(%d%d%d)$");
			b = b:gsub("0+$", "");
			if (b ~= "") then
				result = result .. "." .. b;
			end
			result = result .. "E" .. exp;
			return result;
		end
	end
end

--- @class ItemStack
--- @field name string
--- @field count? number

--- Creates the actual HTML node for a given item stack
---
--- @param stack ItemStack
--- @param args? table
local function makeItem(stack, args)
	args = args or {}

	local item = mw.html.create("span")
		:addClass("factoryslot-item")
		:addClass(args.itemClass)
		:cssText(args.itemStyle);

	local name = stack.name;
	if ((name or "") == "") then
		-- Empty stack
		return nil;
	end

	local escapedName = name:gsub("%[(T%d+)%]$", "&#91;%1&#93;");
	local pageName = name:gsub("%[(T%d+)%]$", "%1");
	local img = pageName .. ".png";

	local link = args.link;
	if ((link or "") == "") then
		link = nil;
	elseif (string.lower(link) == "no") then
		link = nil;
	end

	local altText = img .. ": The Perfect Tower 2 Factory inventory icon of " .. escapedName;
	if (link) then
		altText = altText .. " linking to " .. link;
	end

	item:addClass("pixel-image")
		:wikitext("[[File:", img, "|32x32px|link=", link or "", "|alt=", altText, "|", escapedName, "]]");

	local count = stack.count;
	if (count and count > 1) then
		count = p.formatCount(count);
		if (count ~= nil) then
			if (link) then
				item:wikitext("[[", link, "|");
			end
			local number = item:tag("span")
				:addClass("factoryslot-stacksize")
				:attr("title", name)
				:wikitext((count:gsub("E", '<span class="tpt-num__e">E</span>')));

			number:cssText(args.numStyle);
			if (link) then
				item:wikitext("]]");
			end
		end
	end

	return item;
end
p._makeItem = makeItem;

local function fixTier(name)
	local prefix, tier;
	prefix, tier = name:match("^%s*(.*) (T%d+)%s*$");
	if (tier) then
		name = prefix .. " [" .. tier .. "]";
	else
		name = mw.text.trim(name)
	end
	return name;
end

--- Parses an Item Stack Definition into an ItemStack table
---
--- Syntax:
--- ```text
--- <Name:string> ( ',' <Count:number> )?
--- ```
---
--- @param itemStackDesc string
--- @return ItemStack
function p.parseStack(itemStackDesc)
	-- Simple stack definition with no parts or reserved characters
	if (not string.match(itemStackDesc, "[:,]")) then
		return { name = fixTier(itemStackDesc) };
	end

	local stack = {};
	local name, count;

	name, count = ustr.match(itemStackDesc, "^%s*(.*),%s*([" .. INFINITY .. "0-9%.eE]+)%s*$");
	if (count == INFINITY) then
		count = math.huge;
	else
		count = count and tonumber(count);
	end
	if (count) then
		name = mw.text.trim(name);
		if (count >= 1e18) then
			count = math.huge;
		else
			count = math.floor(count);
		end
		stack.count = count;
	else
		name = mw.text.trim(itemStackDesc);
	end
	stack.name = fixTier(name);

	return stack;
end

--- Stringifies an ItemStack table into an Item Stack Definition string
---
--- @param itemStack ItemStack
--- @return string
function p.stringifyStack(itemStack)
	local name = itemStack.name;
	local count = itemStack.count;

	local result = name;
	if (count and count >= 0) then
		if (count >= 1e18) then
			count = INFINITY;
		else
			count = tostring(count);
		end

		result = result .. "," .. count;
	end

	return result;
end

--- Main entry point.
--- Builds the HTML for a Factory inventory slot.
---
--- @param args any
function p.slot(args)
	local stack = args[1];
	if (type(stack) == "string") then
		stack = p.parseStack(stack);
	end

	local class = args.class;
	local body = mw.html.create("span")
		:addClass("factoryslot")
		:addClass(class)
		:cssText(args.style);

	if (not stack) then
		return body;
	end

	body:node(makeItem(stack, args));

	return body;
end

-- Main template entry point.
function p.main(frame)
	return frame:extensionTag("templatestyles", "", { src = STYLES })
		.. tostring(p.slot(require("Module:Arguments").getArgs(frame, {
			frameOnly = false,
			wrappers = { "Template:Factory slot" },
		})));
end

return p;