Jump to content

Module:UI

From The Perfect Tower II
Revision as of 23:01, 6 October 2026 by Speedybrain (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Draws Factory interfaces. It has one so far, the crafting grid, which Module:Recipe table calls for {{Crafting}} (through Module:Crafting). Inspired by Minecraft Wiki's Module:UI. The look is in Template:Crafting/styles.css (TemplateStyles), which the grid adds itself.

  • crafting(args): the 8×4 input grid, the output slot and the cost.
    • The inputs are A1 to H4 (columns A to H, rows 1 to 4) and the output is Output. Each takes an item stack as described in Module:Factory slot, which draws the slots. A slot's link is A1link and so on, Olink for the output.
    • The smallest rectangle that holds all inputs, at least 2×2, is drawn from the top-left corner as open slots; the rest of the 8×4 grid is drawn as locked slots.
    • cost is the cost in Factory resources, formatted by Module:FormatNumber, so it follows the reader's number format (scientific notation or named numbers). It defaults to 0, and negative values show as 0.
  • formatDoubleInteger(amount, nonNegative): an older formatter for counts (1.5E6, ∞). The grid only calls it when cost isn't a number, and then it returns a "NaN" error.



-- Used to render Factory UIs
-- Inspired by Minecraft Wiki's [[Module:UI]]
require("Module:No globals");
local checkTypeMulti = require("libraryUtil").checkTypeMulti;
--- @type fun(args: table): MwHtml
local slot = require("Module:Factory slot").slot;

local formatNumber = require("Module:FormatNumber").format;

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

local p = {};

-- Columns for Crafting grids
local COLUMNS = { "A", "B", "C", "D", "E", "F", "G", "H" }

--- Creates a Factory UI slot from the arguments named by `name`,
--- `(name or prefix) .. "link"`, and `(name or prefix) .. "title"`
---
--- @param args table
--- @param name string
--- @param prefix? string
--- @param class? string
--- @param default? string
local function createSlot(args, name, prefix, class, default)
	return slot{
		args[name],
		link	= args[(prefix or name) .. "link"],
		title	= args[(prefix or name) .. "title"],
		class	= class,
		default	= default,
	};
end

--- @param amount number Non-negative double precision float
local function implFormatDoubleInteger(amount)
	if (amount >= math.huge) then
		return "∞";
	end

	amount = math.floor(amount);
	if (amount < 1e6) then
		-- less than a million
		return tostring(amount);
	end

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

		if (amount < 1e6) then
			--- @type string, string
			local result, b = tostring(amount):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

--- Formats a non-negative double precision integer value
---
--- @param amount string|number
--- @param nonNegative? boolean
--- @return string|nil
function p.formatDoubleInteger(amount, nonNegative)
	checkTypeMulti('"Module:UI".formatDoubleInteger', 1, amount, { "string", "number" });
	amount = tonumber(amount);
	if (amount == nil or amount ~= amount) then
		return mw.text.tag("strong", { class="error" }, "NaN");
	end

	if (nonNegative and amount < 0) then
		amount = 0;
	end

	local result = implFormatDoubleInteger(math.abs(amount));
	if (amount < 0) then
		result = "-" .. result;
	end
	return result;
end


--- Crafting grid (size scales with Factory tier)
---
--- @param args table
function p.crafting(args)
	--- @type integer, integer
	local minRow, maxRow = math.huge, -math.huge;

	--- @type integer, integer
	local minCol, maxCol = math.huge, -math.huge;

	for row = 1, 4 do
		for col = 1, 8 do
			local name = COLUMNS[col] .. row;
			if ((args[name] or "") ~= "") then
				minRow = math.min(minRow, row);
				maxRow = math.max(maxRow, row);

				minCol = math.min(minCol, col);
				maxCol = math.max(maxCol, col);
			end
		end
	end

	local unlockedRows = maxRow - minRow + 1;
	local unlockedCols = maxCol - minCol + 1;

	-- Minimum crafting grid size is 2×2
	if (unlockedRows < 2) then
		unlockedRows = 2;
	end
	if (unlockedCols < 2) then
		unlockedCols = 2;
	end

	local root = mw.html.create("div")
		:addClass("factoryui factoryui-crafting")
		:wikitext(mw.getCurrentFrame():extensionTag("templatestyles", "", { src = STYLES }));

	-- Input grid
	do
		local input = root:tag("div")
			:addClass("factoryui-input");

		local rows = 0;
		for row = minRow, maxRow do
			rows = rows + 1;
			local rowHtml = input:tag("div")
				:addClass("factoryui-row");

			local cols = 0;
			for col = minCol, maxCol do
				cols = cols + 1;

				local name = COLUMNS[col] .. row;
				rowHtml:node(createSlot(args, name));
			end

			while cols < unlockedCols do
				cols = cols + 1;
				rowHtml:node(slot{});
			end

			while cols < 8 do
				cols = cols + 1;
				rowHtml:node(slot{ class = "locked" });
			end
		end

		while rows < unlockedRows do
			rows = rows + 1;

			local rowHtml = input:tag("div")
				:addClass("factoryui-row");

			for _ = 1, unlockedCols do
				rowHtml:node(slot{});
			end
			for _ = unlockedCols + 1, 8 do
				rowHtml:node(slot{ class = "locked" });
			end
		end

		while rows < 4 do
			rows = rows + 1;
			local rowHtml = input:tag("div")
				:addClass("factoryui-row locked");

			for _ = 1, 8 do
				rowHtml:node(slot{ class = "locked" });
			end
		end
	end

	-- Output slot and Resource cost
	do
		local output = root:tag("div")
			:addClass("factoryui-output");

		output:node(createSlot(args, "Output", "O", "factoryslot-large"));

		-- The cost follows the reader's number format (scientific or named), like {{Number}}
		local cost = tonumber(args.cost or 0);
		output:tag("span")
			:addClass("factoryui-cost")
			:wikitext(
				(cost and cost == cost) and formatNumber(math.max(cost, 0)) or p.formatDoubleInteger(args.cost),
				" [[File:Red resource.png|x25px|link=]]"
			)
	end

	return root;
end

return p;