Module:Sandbox/Lakelimbo/TypeEffectiveness

Charmander:
Something here on top...
Weaknesses
Immunities
None
Resistances
Bug½×
Steel½×
Fire½×
Grass½×
Ice½×
Fairy½×
Swampert:
Something here on top...
Weaknesses
Immunities
Resistances
Poison½×
Rock½×
Steel½×
Fire½×
Shedinja:
Something here on top...
Weaknesses
Immunities
Resistances
Poison½×
Ground½×
Bug½×
Grass½×
Tinkaton:
Something here on top...
Weaknesses
Immunities
Resistances
Normal½×
Flying½×
Rock½×
Bug¼×
Grass½×
Ice½×
Dark½×
Fairy½×
Dusknoir:
Something here on top...
Weaknesses
Dark
Immunities
Resistances
Poison½×
Bug½×
Ferrothorn:
Something here on top...
Weaknesses
Immunities
Resistances
Normal½×
Rock½×
Steel½×
Water½×
Grass¼×
Dragon½×
Fairy½×
Malamar:
Something here on top...
Weaknesses
Bug
Immunities
Resistances
None
Goodra (sap sipper):
Something here on top...
Weaknesses
Immunities
None
Resistances
Fire½×
Water½×
Grass½×

--[[
  This module calculates the type effectiveness of a Pokémon's types.
  It's meant to replace the current {{TypeEffectiveness}} template.

  EXPERIMENTAL! Do not use it yet!

  TO-DO:
    - Fix some design issues
    - Decide how abilities will impact the calculation
    - Decide how generations will impact the calculation
--]]

local type_effectiveness = {}
local lang = mw.language.getContentLanguage()
local types = require("Module:Sandbox/Lakelimbo/TypeEffectiveness/Types")
local utils = require("Module:Sandbox/Lakelimbo/Utils")

local container = mw.html.create("div"):addClass("roundy")

---The main part of the module, which takes the arguments and outputs the
---damage value
---@return string
function type_effectiveness.main(frame)
	---First type, mandatory
	---@type string
	local type_1 = utils.trim_lower(frame:getParent().args[1])
		or utils.trim_lower(frame:getParent().args["type1"])
		or "normal"

	---Second type, optional
	---@type string | nil
	local type_2 = utils.trim_lower(frame:getParent().args[2]) or utils.trim_lower(frame:getParent().args["type2"])

	-- If for whatever reason `type_2` is defined but not `type_1`
	if type_2 and not type_1 then
		container
			:wikitext("For some reason you have set the secondary type but not the first. Plase fix this.")
			:allDone()

		return tostring(container)
	end

	container
		:tag("div")
		:css({
			["font-weight"] = "bold",
			["padding"] = "0.5rem",
			["color"] = "#fff",
		})
		:wikitext("Something here on top...")
		:done()

	---Table for the resulting damage calculation
	local results = {
		["Weaknesses"] = {},
		["Resistances"] = {},
		["Immunities"] = {},
	}

	-- Loop for each type and push it to its respective
	-- place on `results`
	for i = 1, #types.types do
		---@type number
		local factor = calculate_damage(type_1, type_2, types.types[i])

		local group
		if factor > 1 then
			group = "Weaknesses"
		elseif factor == 0 then
			group = "Immunities"
		elseif factor < 1 then
			group = "Resistances"
		end

		if factor ~= 1 then
			table.insert(results[group], types.types[i])
		end
	end

	local groups = container:tag("div"):css({
		["display"] = "flex",
		["flex-wrap"] = "wrap",
		["gap"] = "1px",
	})

	-- Loop once again, now to create each an element
	-- for each table inside of `results`
	for group, values in pairs(results) do
		local subgroup = groups
			:tag("div")
			:css({
				["background-color"] = "#fff",
				["flex"] = "175px",
			})
			:tag("div")
			:css({
				["background-color"] = "#{{" .. type_1 .. " color dark}}",
				["color"] = "#fff",
				["padding"] = "0.333rem",
			})
			:wikitext(group)
			:done()
			:tag("div")
			:css({
				["padding"] = "0.25rem"
			})

		if #values > 0 then
			for _, value in ipairs(values) do
				local factor = calculate_damage(type_1, type_2, value)

				subgroup:node(type_label(value, factor, frame)):done()
			end
		else
			subgroup:node(type_label()):done()
		end

		subgroup:done()
	end

	-- At this point, if `type_2` is nil, it's better to just
	-- fallback to `type_1` instead of creating multiple other
	-- check conditions for the colors below
	if not type_2 then
		type_2 = type_1
	end

	container
		:css({
			["border"] = "2px solid #{{" .. type_2 .. " color}}",
			["background-color"] = "#{{" .. type_1 .. " color}}",
			["text-align"] = "center",
			["margin"] = "auto",
			["max-width"] = "600px",
			["overflow"] = "hidden",
		})
		:allDone()

	return tostring(container)
end

---This function will calculate the damage between the user's types
---and a specific opposing (attacking) type.
---@param user_type_1 string
---@param user_type_2 string | nil
---@param opposing_type string
---@return number
function calculate_damage(user_type_1, user_type_2, opposing_type)
	---@type number | nil
	local damage

	if user_type_2 then
		damage = types.effectiveness[opposing_type][user_type_1] * types.effectiveness[opposing_type][user_type_2]
	else
		damage = types.effectiveness[opposing_type][user_type_1]
	end

	return damage
end

---Creates a label with {{TypeIcon}} and also insert the
---damage factor on the side.
---@param name string | nil
---@param factor number | nil
---@return string
function type_label(name, factor, frame)
	local label = mw.html.create("div")

	if not name then
		return label
			:css({
				["background-color"] = "#000",
				["color"] = "#fff",
				["padding"] = "0.25rem",
				["border-radius"] = "50px",
				["font-weight"] = "bold",
				["width"] = "84px",
				["margin"] = "auto",
			})
			:wikitext("None")
			:allDone()
	end

	local factor_color = "#000"
	if factor == 4 then
		factor_color = "#ff0000"
	elseif factor == 0.25 then
		factor_color = "#0000ff"
	end

	name = lang:ucfirst(name)

	label:wikitext(frame:expandTemplate({title = "TypeIcon", args = { name }}))

	if factor ~= 0 then
		label:tag("span")
			:css({
				["display"] = "inline-block",
				["width"] = "25px",
				["padding"] = "3px 0",
				["background-color"] = factor_color,
				["color"] = "#fff",
				["border-radius"] = "100%",
				["font-weight"] = "bold",
			}):wikitext(utils.fraction(factor) .. "×")
	end

	label:allDone()

	return label
end

return type_effectiveness