Module:UI

From The Perfect Tower II
Jump to navigation Jump to search

Documentation for this module may be created at Module:UI/doc

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

local ustr = mw.ustring;
--- @type fun(args: table): MwHtml
local slot = require("Module:Factory slot").slot;

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 pixel-image");

	-- 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"));

		output:tag("span")
			:addClass("factoryui-cost")
			:wikitext(
				"[[File:Red resource.png|x25px|link=]] ",
				p.formatDouble(args.cost or 0)
			)
	end

	return root;
end

return p;