Module:Chem box

From Space Station 14 Wiki
Revision as of 04:22, 21 May 2025 by Aliser (talk | contribs) (unhardcoded colors and css class names from the manual chem box expand call)
Module documentation
View or edit this documentation (about module documentation)
Uses JSON data
This module uses JSON data pages:

Implements {{chem box}}.


local p = {} --p stands for package
local getArgs = require("Module:Arguments").getArgs
local yesNo = require("Module:Yesno")

local chem_data = mw.loadJsonData("Module:Chem box/chem data.json")

local current_frame = mw.getCurrentFrame()

-- ====================

local function numeric_table_length(t)
	local count = 0
	for _ in ipairs(t) do
		count = count + 1
	end
	return count
end

local function table_length(t)
	local count = 0
	for _ in pairs(t) do
		count = count + 1
	end
	return count
end

local function table_has_value(tab, val)
	for _, value in ipairs(tab) do
		if value == val then
			return true
		end
	end

	return false
end

local function assert_value_not_nil(value, error_message)
	if value == nil then
		if error_message == nil then
			error("value is nil")
		else
			error(error_message)
		end
	end
end

-- Makes the first letter uppercase.
-- Source: https://stackoverflow.com/a/2421746
local function capitalize(str)
	return (str:gsub("^%l", string.upper))
end

local function passthrough_assert_true(value, valueToReturnIfTrue, errorMessageOnFalse)
	if value then
		return valueToReturnIfTrue
	else
		error(errorMessageOnFalse)
	end
end

local function find_first_numeric_table_item_matching_condition(table, condition)
	for i, item in ipairs(table) do
		if condition(item, i, table) then
			return item
		end
	end
end

-- table concat function because ofcourse fucking table.concat doesn't work
local function concat_numberic_table(tbl, sep)
	local temp_table = {}
	for i = 1, numeric_table_length(tbl) do
		table.insert(temp_table, tbl[i])
	end

	return table.concat(temp_table, sep)
end

local function ternary_strict(valueToCheck, valueIfTrue, valueIfFalse)
	if valueToCheck == true then
		return valueIfTrue
	else
		return valueIfFalse
	end
end

-- ====================

-- Lookups reagent by ID or name.
--
-- Raises an error if no reagent was found.
-- Set `no_error` to `true` to return `nil` instead.
function p.lookup_reagent(query, no_error)
	local query_lower = string.lower(query)

	for _, reagent in ipairs(chem_data) do
		assert_value_not_nil(reagent.id)
		assert_value_not_nil(reagent.name)

		if reagent.id == query then
			return reagent
		end
		if string.lower(reagent.name) == query_lower then
			return reagent
		end
	end

	return passthrough_assert_true(no_error, nil, "failed to lookup reagent: no match was found")
end

-- ====================

function p.generate_chem_box(frame)
	local args = getArgs(frame)

	local query = args[1]
	assert_value_not_nil(query, "failed to generate chem box: query was not provided")

	-- ===================

	local reagent = p.lookup_reagent(query)
	assert_value_not_nil(reagent.color)
	assert_value_not_nil(reagent.name)
	assert_value_not_nil(reagent.recipes)
	assert_value_not_nil(reagent.effectStrings)
	assert_value_not_nil(reagent.description)
	assert_value_not_nil(reagent.physicalDescription)
	assert_value_not_nil(reagent.textColorTheme)

	local recipes_container_el = mw.html.create("div")
	if numeric_table_length(reagent.recipes) == 0 then
		local no_recipes_text_el = mw.html.create("span"):css("color", "gray"):node("No recipes")

		recipes_container_el:node(no_recipes_text_el)
	else
		for _, recipe in ipairs(reagent.recipes) do
			assert_value_not_nil(recipe.id)
			assert_value_not_nil(recipe.reactants)
			assert_value_not_nil(recipe.products)

			local recipe_box_template_args = {}
			recipe_box_template_args.name = recipe.id

			-- a list of catalist reactants.
			-- contains "id" and "amount"
			local catalyst_reactants = {}

			for reactant_i, reactant in ipairs(recipe.reactants) do
				local reactant_name = capitalize(reactant.name)
				local reactant_amount = reactant.count
				local reactant_is_catalyst = reactant.isCatalyst or false

				local label = reactant_name
				local link = reactant.id

				if reactant_is_catalyst then
					table.insert(catalyst_reactants, {
						name = reactant_name,
						id = reactant_id,
						amount = reactant_amount,
					})
				end

				local component_el = current_frame:expandTemplate({
					title = "Recipe Component",
					args = {
						item = label,
						amount = reactant_amount,
						itemlink = link
						-- TODO
						-- image = ""
					},
				})

				recipe_box_template_args["component-" .. reactant_i] = component_el
			end

			local temperature_args = {}
			if recipe.minTemp ~= nil or recipe.maxTemp ~= nil then
				local temperature_string = ""
				if recipe.minTemp ~= nil then
					temperature_string = tostring(recipe.minTemp) .. "k "
				end
				temperature_string = temperature_string .. "<"
				if recipe.maxTemp ~= nil then
					temperature_string = temperature_string .. " " .. tostring(recipe.maxTemp) .. "k"
				end
				temperature_args = {temperature = temperature_string}
			end
			local required_mixer = "Beaker"
			if recipe.requiredMixerCategories ~= nil then
				required_mixer = recipe.requiredMixerCategories[1]
			end
			recipe_box_template_args.transformer = current_frame:expandTemplate({
				title = required_mixer,
				args = temperature_args
			})

			for product_i, product in ipairs(recipe.products) do
				local component_el = current_frame:expandTemplate({
					title = "Recipe Component",
					args = {
						item = capitalize(product.name),
						amount = product.count,
						-- TODO
						-- image = ""
					},
				})

				recipe_box_template_args["result-" .. product_i] = component_el
			end

			-- add catalysts as products, if any
			for catalyst_product_i, catalyst_product_entry in ipairs(catalyst_reactants) do
				local product_name = catalyst_product_entry.name
				local product_amount = catalyst_product_entry.amount

				local label = current_frame:expandTemplate({
					title = "Tooltip",
					args = {
						'<sup class="quickbox-catalyst-note">+</sup>',
						'<span class="quickbox-catalyst-note-tooltip">This product is a catalyst reactant, remaining after the reaction.</span>',
					},
				}) .. product_name

				local component_el = current_frame:expandTemplate({
					title = "Recipe Component",
					args = {
						item = label,
						amount = product_amount,
						-- TODO
						-- image = ""
					},
				})

				local index = numeric_table_length(recipe.products) + catalyst_product_i
				recipe_box_template_args["result-" .. index] = component_el
			end

			local recipes_el = current_frame:expandTemplate({
				title = "Recipe Box",
				args = recipe_box_template_args,
			})

			recipes_container_el:node(recipes_el)
		end
	end

	local effects = concat_numberic_table(reagent.effectStrings, "")

	local text_color = ternary_strict(
		reagent.textColorTheme == "light",
		"light",
		"dark"
	)
	
	-- inverse of text color
	local text_outline_color = ternary_strict(
		reagent.textColorTheme == "light",
		"light",
		"dark"
	)

	return current_frame:expandTemplate({
		title = "Manual Chem Box",
		args = {
			id = reagent.id,
			color = reagent.color,
			textcolor = text_color,
			textcolor_outline = text_outline_color,
			name = capitalize(reagent.name),
			recipes = tostring(recipes_container_el:allDone()),
			metabolisms = effects,
			desc = reagent.description,
			physicalDesc = reagent.physicalDescription,
		},
	})
end

function p.generate_chem_boxes_for_all_reagents()
	local container_el = mw.html.create("div"):addClass("reagents-list")

	for _, reagent in ipairs(chem_data) do
		assert_value_not_nil(reagent.id)

		container_el:node(p.generate_chem_box({
			reagent.id,
		}))
	end

	return container_el:allDone()
end

function p.generate_chem_boxes_for_group(frame)
	local args = getArgs(frame)
	local query = args[1]
	assert_value_not_nil(query)
	query = string.lower(query)
	local container_el = mw.html.create("div"):addClass("reagents-list")

	for _, reagent in ipairs(chem_data) do
		assert_value_not_nil(reagent.id)
		assert_value_not_nil(reagent.group)
		if string.lower(reagent.group) == query then
			container_el:node(p.generate_chem_box({
				reagent.id,
			}))
		end
	end

	return container_el:allDone()
end

return p