Module:Factory slot

From The Perfect Tower II
Jump to navigation Jump to search

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

-- 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 = "∞";

-- Workaround until this wiki gets TemplateStyles
local styles = {
[".factoryslot"] = [[
	display: inline-block;
	border: 1px solid black;
	width: 32px;
	height: 32px;
	padding: 8px;
	line-height: 1;
]],

[".factoryslot-item"] = [[
	display: block;
	position: relative;
	width: 32px;
	height: 32px;
	margin: -4px;
	padding: 4px;
]],

[".pixel-image"] = [[
	image-rendering: crisp-edges;
	image-rendering: pixelated;
]],

[".factoryslot-item .factoryslot-stacksize"] = [[
	position: absolute;
	bottom: 0;
	right: 0;
	white-space: nowrap;
	font-family: sans-serif !important;
	font-style: normal !important;
]],
};

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(styles[".factoryslot-item"])
		:cssText(args.itemStyle);

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

	local img = name .. ".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 " .. name
	if (link) then
		altText = altText .. " linking to " .. link
	end

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

	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")
				-- :cssText(styles[".factoryslot-item .factoryslot-stacksize"])
				:attr("title", name)
				:wikitext(count);

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

	return item;
end
p._makeItem = makeItem;

--- 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 = itemStackDesc };
	end

	local stack = {};
	local name, count;

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

	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 body = mw.html.create("span")
		:addClass("factoryslot")
		:addClass(args.class)
		-- :cssText(styles[".factoryslot"])
		: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 p.slot(require("Module:Arguments").getArgs(frame, {
		frameOnly = false,
		wrappers = { "Template:Factory slot" },
	}));
end

return p;