Jump to content

Module:Factory slot: Difference between revisions

From The Perfect Tower II
Exe boss (talk | contribs)
Add `title` attribute to item count and disable inline styles
Stack counts accept an uppercase E
 
(4 intermediate revisions by 2 users not shown)
Line 6: Line 6:
local INFINITY = "∞";
local INFINITY = "∞";


-- Workaround until this wiki gets TemplateStyles
-- The look of the slots is in [[Template:Crafting/styles.css]] (TemplateStyles).
local styles = {
local STYLES = "Template:Crafting/styles.css";
[".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 = {};
local p = {};
Line 75: Line 43:
result = result .. "." .. b;
result = result .. "." .. b;
end
end
result = result .. "e" .. exp;
result = result .. "E" .. exp;
return result;
return result;
end
end
Line 95: Line 63:
:addClass("factoryslot-item")
:addClass("factoryslot-item")
:addClass(args.itemClass)
:addClass(args.itemClass)
-- :cssText(styles[".factoryslot-item"])
:cssText(args.itemStyle);
:cssText(args.itemStyle);


Line 104: Line 71:
end
end


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


local link = args.link;
local link = args.link;
Line 113: Line 82:
end
end


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


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


local count = stack.count;
local count = stack.count;
Line 131: Line 99:
local number = item:tag("span")
local number = item:tag("span")
:addClass("factoryslot-stacksize")
:addClass("factoryslot-stacksize")
-- :cssText(styles[".factoryslot-item .factoryslot-stacksize"])
:attr("title", name)
:attr("title", name)
:wikitext(count);
:wikitext((count:gsub("E", '<span class="tpt-num__e">E</span>')));


number:cssText(args.numStyle);
number:cssText(args.numStyle);
Line 145: Line 112:
end
end
p._makeItem = makeItem;
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
--- Parses an Item Stack Definition into an ItemStack table
Line 158: Line 136:
-- Simple stack definition with no parts or reserved characters
-- Simple stack definition with no parts or reserved characters
if (not string.match(itemStackDesc, "[:,]")) then
if (not string.match(itemStackDesc, "[:,]")) then
return { name = itemStackDesc };
return { name = fixTier(itemStackDesc) };
end
end


Line 164: 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;
Line 171: Line 149:
end
end
if (count) then
if (count) then
stack.name = mw.text.trim(name);
name = mw.text.trim(name);
if (count >= 1e18) then
if (count >= 1e18) then
count = math.huge;
count = math.huge;
Line 179: Line 157:
stack.count = count;
stack.count = count;
else
else
stack.name = mw.text.trim(itemStackDesc);
name = mw.text.trim(itemStackDesc);
end
end
stack.name = fixTier(name);


return stack;
return stack;
Line 217: Line 196:
end
end


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


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


return p;
return p;

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;