<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.funkystation.org/index.php?action=history&amp;feed=atom&amp;title=Module%3AChemistry</id>
	<title>Module:Chemistry - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.funkystation.org/index.php?action=history&amp;feed=atom&amp;title=Module%3AChemistry"/>
	<link rel="alternate" type="text/html" href="https://wiki.funkystation.org/index.php?title=Module:Chemistry&amp;action=history"/>
	<updated>2026-06-04T23:54:41Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.42.3</generator>
	<entry>
		<id>https://wiki.funkystation.org/index.php?title=Module:Chemistry&amp;diff=1420&amp;oldid=prev</id>
		<title>Taydeo: Created page with &quot;local p = {} --p stands for package local getArgs = require(&quot;Module:Arguments&quot;).getArgs local yesNo = require(&quot;Module:Yesno&quot;) local Color = require(&quot;Module:Color&quot;) local VariablesLua = mw.ext.VariablesLua  -- local reagents_table = mw.loadJsonData(&quot;Module:Chemistry/data/auto/reagents.json&quot;); -- local reactions_table = mw.loadJsonData(&quot;Module:Chemistry/data/auto/reactions.json&quot;);  local reagent_card_templatestyles_src = &quot;Template:Reagent card/styles.css&quot;  -- =============...&quot;</title>
		<link rel="alternate" type="text/html" href="https://wiki.funkystation.org/index.php?title=Module:Chemistry&amp;diff=1420&amp;oldid=prev"/>
		<updated>2025-09-02T23:12:57Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;local p = {} --p stands for package local getArgs = require(&amp;quot;Module:Arguments&amp;quot;).getArgs local yesNo = require(&amp;quot;Module:Yesno&amp;quot;) local Color = require(&amp;quot;Module:Color&amp;quot;) local VariablesLua = mw.ext.VariablesLua  -- local reagents_table = mw.loadJsonData(&amp;quot;Module:Chemistry/data/auto/reagents.json&amp;quot;); -- local reactions_table = mw.loadJsonData(&amp;quot;Module:Chemistry/data/auto/reactions.json&amp;quot;);  local reagent_card_templatestyles_src = &amp;quot;Template:Reagent card/styles.css&amp;quot;  -- =============...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;local p = {} --p stands for package&lt;br /&gt;
local getArgs = require(&amp;quot;Module:Arguments&amp;quot;).getArgs&lt;br /&gt;
local yesNo = require(&amp;quot;Module:Yesno&amp;quot;)&lt;br /&gt;
local Color = require(&amp;quot;Module:Color&amp;quot;)&lt;br /&gt;
local VariablesLua = mw.ext.VariablesLua&lt;br /&gt;
&lt;br /&gt;
-- local reagents_table = mw.loadJsonData(&amp;quot;Module:Chemistry/data/auto/reagents.json&amp;quot;);&lt;br /&gt;
-- local reactions_table = mw.loadJsonData(&amp;quot;Module:Chemistry/data/auto/reactions.json&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
local reagent_card_templatestyles_src = &amp;quot;Template:Reagent card/styles.css&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- ==============================&lt;br /&gt;
&lt;br /&gt;
local are_template_styles_loaded = false&lt;br /&gt;
&lt;br /&gt;
-- ==============================&lt;br /&gt;
&lt;br /&gt;
local function assert_not_nil(value, error_message)&lt;br /&gt;
    if value == nil then&lt;br /&gt;
        if error_message == nil then&lt;br /&gt;
            error(&amp;quot;value is nil&amp;quot;)&lt;br /&gt;
        else&lt;br /&gt;
            error(error_message)&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function starts_with(str, substr)&lt;br /&gt;
    return string.sub(str, 1, string.len(substr)) == substr&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ends_with(str, substr)&lt;br /&gt;
    local substr_length = string.len(substr)&lt;br /&gt;
    return string.sub(str, string.len(str) - substr_length + 1, string.len(str)) == substr&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function starts_with_insensitive(str, substr)&lt;br /&gt;
    return starts_with(string.lower(str), string.lower(substr))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ends_with_insensitive(str, substr)&lt;br /&gt;
    return ends_with(string.lower(str), string.lower(substr))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function table_filter(tbl, filterFn)&lt;br /&gt;
    local out = {}&lt;br /&gt;
&lt;br /&gt;
    for k, v in pairs(tbl) do&lt;br /&gt;
        if filterFn(v, k, tbl) then out[k] = v end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return out&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function num_table_reduce(tbl, reduceFn, initial_value)&lt;br /&gt;
    local out = initial_value&lt;br /&gt;
&lt;br /&gt;
    for i, v in ipairs(tbl) do&lt;br /&gt;
        out = reduceFn(out, v, i, tbl)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return out&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function table_find(tbl, findFn)&lt;br /&gt;
    for k, v in pairs(tbl) do&lt;br /&gt;
        if findFn(v, k, tbl) then return v end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function table_contains(tbl, findFn)&lt;br /&gt;
    return table_find(tbl, findFn) ~= nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function table_contains_value(tbl, value)&lt;br /&gt;
    return table_find(tbl, function(iter_value)&lt;br /&gt;
        return iter_value == value&lt;br /&gt;
    end) ~= nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function table_find_key(tbl, findFn)&lt;br /&gt;
    for k, v in pairs(tbl) do&lt;br /&gt;
        if findFn(v, k, tbl) then return k end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function table_find_index(tbl, findFn)&lt;br /&gt;
    for i, v in ipairs(tbl) do&lt;br /&gt;
        if findFn(v, i, tbl) then return i end&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function table_map(tbl, mapFn)&lt;br /&gt;
    local res = {}&lt;br /&gt;
    for k, v in pairs(tbl) do&lt;br /&gt;
        table.insert(res, mapFn(v, k, tbl))&lt;br /&gt;
    end&lt;br /&gt;
    return res&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function numeric_table_length(t)&lt;br /&gt;
    local count = 0&lt;br /&gt;
    for _ in ipairs(t) do count = count + 1 end&lt;br /&gt;
    return count&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function table_length(t)&lt;br /&gt;
    local count = 0&lt;br /&gt;
    for _ in pairs(t) do count = count + 1 end&lt;br /&gt;
    return count&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- Returns keys of a table.&lt;br /&gt;
local function table_keys(tbl)&lt;br /&gt;
    local arr = {}&lt;br /&gt;
    for key, _ in pairs(tbl) do&lt;br /&gt;
        table.insert(arr, key)&lt;br /&gt;
    end&lt;br /&gt;
    return arr&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Sorts a table into a new table.&lt;br /&gt;
-- An alternative to cases such as when table.sort is not working,&lt;br /&gt;
-- like when trying to sort JSON tables (thanks fuckass lua).&lt;br /&gt;
local function table_to_sorted(tbl, sortfn)&lt;br /&gt;
    local keys = {}&lt;br /&gt;
&lt;br /&gt;
    for key, _ in pairs(tbl) do&lt;br /&gt;
        table.insert(keys, key)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    table.sort(keys, function(keyA, keyB) return sortfn(tbl[keyA], tbl[keyB]) end)&lt;br /&gt;
&lt;br /&gt;
    local t2 = {}&lt;br /&gt;
&lt;br /&gt;
    for _, key in ipairs(keys) do&lt;br /&gt;
        table.insert(t2, tbl[key])&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return t2&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Given a table and a property key, attempts to retrieve said property.&lt;br /&gt;
-- If property does not exists, creates it with by assigning a value from `create_value_fn`.&lt;br /&gt;
-- The created value is then returned.&lt;br /&gt;
function get_table_prop_or_create(tbl, prop_key, create_value_fn)&lt;br /&gt;
    local value = tbl[prop_key]&lt;br /&gt;
    if value == nil then&lt;br /&gt;
        value = create_value_fn()&lt;br /&gt;
        tbl[prop_key] = value&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return value;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- ==============================&lt;br /&gt;
&lt;br /&gt;
-- Creates a template styles node, loading styles from `src`.&lt;br /&gt;
local function create_template_styles_node(frame, src)&lt;br /&gt;
    return frame:extensionTag(&amp;quot;templatestyles&amp;quot;, &amp;quot;&amp;quot;, { src = src })&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function ensure_frame(maybe_frame)&lt;br /&gt;
    if type(maybe_frame) == &amp;quot;table&amp;quot; and maybe_frame.expandTemplate then&lt;br /&gt;
        return maybe_frame -- valid frame&lt;br /&gt;
    else&lt;br /&gt;
        return mw.getCurrentFrame()&lt;br /&gt;
    end &lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- ==============================&lt;br /&gt;
&lt;br /&gt;
local collapsible_config_uniq_counter_var_name = &amp;quot;module-collapsible-config-uniq-counter&amp;quot;&lt;br /&gt;
&lt;br /&gt;
-- Generates a config for custom collapsible elements using a unique ID.&lt;br /&gt;
-- The ID must be unique for the current page.&lt;br /&gt;
-- &lt;br /&gt;
-- Returns 2 values: a class for a toggle element and an ID for a collapsible element.&lt;br /&gt;
local function generate_collapsible_config(unique_id)&lt;br /&gt;
    -- Ensures that in case of duplicates of collapsibles there would be basically no collisions.&lt;br /&gt;
    local unique_counter = VariablesLua.var(collapsible_config_uniq_counter_var_name, 0)&lt;br /&gt;
    unique_counter = VariablesLua.vardefineecho(collapsible_config_uniq_counter_var_name, unique_counter + 1)&lt;br /&gt;
&lt;br /&gt;
    return (&amp;quot;mw-customtoggle-&amp;quot; .. unique_id .. &amp;quot;-&amp;quot; .. unique_counter), &lt;br /&gt;
    (&amp;quot;mw-customcollapsible-&amp;quot; .. unique_id .. &amp;quot;-&amp;quot; .. unique_counter)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Calculates text color of a reagent header based on its background color.&lt;br /&gt;
-- Returns either `black` or `white`.&lt;br /&gt;
-- Uses the same algorithm the game uses.&lt;br /&gt;
local function calculate_reagent_header_text_color(col_obj)&lt;br /&gt;
    local r, g, b = col_obj:rgba()&lt;br /&gt;
    return 0.2126 * r + 0.7152 * g + 0.0722 * b &amp;gt; 0.5&lt;br /&gt;
        and &amp;quot;black&amp;quot;&lt;br /&gt;
        or &amp;quot;white&amp;quot;&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Creates a collapsible section to use in reagent card.&lt;br /&gt;
-- @param label Label to use as a &amp;quot;header&amp;quot; for the section, as well as a collapse/show button.&lt;br /&gt;
-- @param parent_el Element to use as parent for node creation.&lt;br /&gt;
-- @param collapsible_id A unique ID to use to bind collapsible elements.&lt;br /&gt;
local function reagent_card_collapsible_section(label, parent_el, collapsible_id)&lt;br /&gt;
    local toggle_class, collapsible_id = generate_collapsible_config(collapsible_id)&lt;br /&gt;
    local label_el = parent_el:tag(&amp;quot;p&amp;quot;)&lt;br /&gt;
        :wikitext(label)&lt;br /&gt;
        :addClass(&amp;quot;reagent-card-spoiler-section-label &amp;quot; .. toggle_class)&lt;br /&gt;
&lt;br /&gt;
    local content_el = parent_el:tag(&amp;quot;div&amp;quot;)&lt;br /&gt;
        :attr(&amp;quot;id&amp;quot;, collapsible_id)&lt;br /&gt;
        :addClass(&amp;quot;reagent-card-spoiler-section mw-collapsible mw-collapsed&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    return content_el&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Creates a text section to use in reagent card.&lt;br /&gt;
-- @param parent_el Element to use as parent for node creation.&lt;br /&gt;
local function reagent_card_text_section(parent_el)&lt;br /&gt;
    return parent_el:tag(&amp;quot;p&amp;quot;)&lt;br /&gt;
        :addClass(&amp;quot;reagent-card-text-section&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function auto_reagent_card(frame, args)&lt;br /&gt;
    error(&amp;quot;not impl&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function manual_reagent_card(frame, args)&lt;br /&gt;
    local product_name = args.name&lt;br /&gt;
    assert_not_nil(product_name, &amp;quot;failed to generate manual reagent card: product name not provided&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    local product_id = args.id&lt;br /&gt;
    local color = args.color&lt;br /&gt;
    local recipes = args.recipe or args.recipes&lt;br /&gt;
    local description = args.desc&lt;br /&gt;
    local physical_description = args.physicalDesc&lt;br /&gt;
    local effects = args.effect or args.effects or args.metabolisms&lt;br /&gt;
    local plant_effects = args.plantMetabolism or args.plantMetabolisms -- todo; label spelled &amp;quot;Plant Metabolism&amp;quot;&lt;br /&gt;
    local sources = args.source or args.sources -- todo; label spelled &amp;quot;Sources&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    -- ===============&lt;br /&gt;
&lt;br /&gt;
    local card_container_el = mw.html.create(&amp;quot;div&amp;quot;)&lt;br /&gt;
        :addClass(&amp;quot;reagent-card&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    if product_id then&lt;br /&gt;
        card_container_el:attr(&amp;quot;id&amp;quot;, &amp;quot;chem_&amp;quot; .. product_id)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local header_el = card_container_el:tag(&amp;quot;p&amp;quot;)&lt;br /&gt;
        :addClass(&amp;quot;reagent-card-header&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    local header_label_el = header_el:tag(&amp;quot;span&amp;quot;)&lt;br /&gt;
        :wikitext(product_name)&lt;br /&gt;
&lt;br /&gt;
    if color then&lt;br /&gt;
        local success, col_obj = Color.tryColor(color)&lt;br /&gt;
        if success then&lt;br /&gt;
            header_el:css(&amp;quot;background-color&amp;quot;, color);&lt;br /&gt;
            header_label_el:css(&amp;quot;color&amp;quot;, calculate_reagent_header_text_color(col_obj))&lt;br /&gt;
        end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    local collapsible_id_base = product_id or product_name&lt;br /&gt;
&lt;br /&gt;
    if sources and #sources &amp;gt; 0 then&lt;br /&gt;
        local section = reagent_card_collapsible_section(&amp;quot;Sources&amp;quot;, card_container_el, collapsible_id_base .. &amp;quot;-sources&amp;quot;)&lt;br /&gt;
        if type(sources) == &amp;quot;string&amp;quot; then section:wikitext(sources) else section:node(sources) end&lt;br /&gt;
    end&lt;br /&gt;
    &lt;br /&gt;
    if recipes and #recipes &amp;gt; 0 then&lt;br /&gt;
        local section = reagent_card_collapsible_section(&amp;quot;Recipe&amp;quot;, card_container_el, collapsible_id_base .. &amp;quot;-recipes&amp;quot;)&lt;br /&gt;
        if type(recipes) == &amp;quot;string&amp;quot; then section:wikitext(recipes) else section:node(recipes) end&lt;br /&gt;
    end&lt;br /&gt;
    &lt;br /&gt;
    if effects and #effects &amp;gt; 0 then&lt;br /&gt;
        local section = reagent_card_collapsible_section(&amp;quot;Effects&amp;quot;, card_container_el, collapsible_id_base .. &amp;quot;-effects&amp;quot;)&lt;br /&gt;
        if type(effects) == &amp;quot;string&amp;quot; then section:wikitext(effects) else section:node(effects) end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if plant_effects and #plant_effects &amp;gt; 0 then&lt;br /&gt;
        local section = reagent_card_collapsible_section(&amp;quot;Plant Metabolism&amp;quot;, card_container_el, collapsible_id_base .. &amp;quot;-plant-effects&amp;quot;)&lt;br /&gt;
        if type(plant_effects) == &amp;quot;string&amp;quot; then section:wikitext(plant_effects) else section:node(plant_effects) end&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if description then&lt;br /&gt;
        local section = reagent_card_text_section(card_container_el)&lt;br /&gt;
            :wikitext(description)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if physical_description then&lt;br /&gt;
        local section = reagent_card_text_section(card_container_el)&lt;br /&gt;
			:addClass(&amp;quot;cursive&amp;quot;)&lt;br /&gt;
            :wikitext(&amp;quot;Seems to be &amp;quot; .. physical_description .. &amp;quot;.&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    if not are_template_styles_loaded then&lt;br /&gt;
        card_container_el:node(create_template_styles_node(frame, reagent_card_templatestyles_src))&lt;br /&gt;
        are_template_styles_loaded = true&lt;br /&gt;
    end&lt;br /&gt;
&lt;br /&gt;
    return card_container_el&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function auto_reaction_card(frame, args)&lt;br /&gt;
    error(&amp;quot;not impl&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
local function manual_reaction_card(frame, args)&lt;br /&gt;
    error(&amp;quot;not impl&amp;quot;)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
-- ==============================&lt;br /&gt;
&lt;br /&gt;
-- Generates a reagent card element, containing&lt;br /&gt;
-- info about a reagent such as description, recipes, effects, etc.&lt;br /&gt;
-- &lt;br /&gt;
-- Has 2 modes depending on what is passed:&lt;br /&gt;
-- - Automatic - when first argument is a reagent ID. This triggers automatic data lookup.&lt;br /&gt;
-- - Manual - when there are only named arguments. This allows to fill every parameter manually.&lt;br /&gt;
function p.reagent_card(frame)&lt;br /&gt;
    local args = getArgs(frame)&lt;br /&gt;
    frame = ensure_frame(frame)&lt;br /&gt;
&lt;br /&gt;
    if args[1] == nil then&lt;br /&gt;
        return manual_reagent_card(frame, args)&lt;br /&gt;
    else&lt;br /&gt;
        return auto_reagent_card(frame, args)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Generates a reaction card element, containing&lt;br /&gt;
-- info about a chemical reaction.&lt;br /&gt;
-- &lt;br /&gt;
-- Has 2 modes depending on what is passed:&lt;br /&gt;
-- - Automatic - when first argument is a reagent ID. This triggers automatic data lookup.&lt;br /&gt;
-- - Manual - when there are only named arguments. This allows to fill every parameter manually.&lt;br /&gt;
function p.reaction_card(frame)&lt;br /&gt;
    local args = getArgs(frame)&lt;br /&gt;
    frame = ensure_frame(frame)&lt;br /&gt;
&lt;br /&gt;
    if args[1] == nil then&lt;br /&gt;
        return manual_reaction_card(frame, args)&lt;br /&gt;
    else&lt;br /&gt;
        return auto_reaction_card(frame, args)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>Taydeo</name></author>
	</entry>
</feed>