Module:Breeding

From Palworld Wiki
Jump to navigation Jump to search

This module is used in the Template:Breeding. It stores breeding data, and displays it on the page for the end user to see.

Cargo Store Information:

Subpages

local argsUtil  = require("Module:ArgsUtil")
local cargoUtil = require("Module:CargoUtil")
local html      = mw.html

local p = {}

local function isBlank(v)
	return v == nil or mw.text.trim(tostring(v)) == ""
end

local function trim(v)
	return mw.text.trim(tostring(v or ""))
end

local function toNumber(v)
	if isBlank(v) then return nil end
	return tonumber(trim(v))
end

local function split(str, sep)
	local out = {}
	str = tostring(str or "")
	sep = sep or ";"
	for part in mw.text.gsplit(str, sep, true) do
		part = trim(part)
		if part ~= "" then
			table.insert(out, part)
		end
	end
	return out
end

local function normalizeGender(g)
	g = trim(g):lower()
	if g == "" then return nil end
	if g == "m" or g == "male" or g == "♂" then return "M" end
	if g == "f" or g == "female" or g == "♀" then return "F" end
	return nil
end

local function formatGenderRatio(maleProbability, genderRatioText)
	local mp = toNumber(maleProbability)
	if mp ~= nil then
		if mp < 0 then mp = 0 end
		if mp > 100 then mp = 100 end
		local f = 100 - mp
		return string.format("%d:%d", f, mp)
	end

	genderRatioText = trim(genderRatioText)
	if genderRatioText ~= "" then
		return mw.text.nowiki(genderRatioText)
	end

	return ""
end

local function makeEggDisplay(eggName, eggLink)
	eggName = trim(eggName)
	eggLink = trim(eggLink)

	if eggName == "" then
		return ""
	end

	local file = string.format("%s_icon.png", eggName)
	local linkTarget = eggLink ~= "" and eggLink or eggName

	local img = string.format("[[File:%s|80px|link=%s]]", file, linkTarget)
	local label = string.format("'''[[%s|%s]]'''", linkTarget, mw.text.nowiki(eggName))

	return img .. "<br/>" .. label
end

local function titleCase(s)
	s = trim(s)
	if s == "" then return "" end
	return (mw.ustring.gsub(mw.ustring.lower(s), "(%a)([%w']*)", function(a, b)
		return mw.ustring.upper(a) .. b
	end))
end

local function getEggParts(eggName)
	eggName = titleCase(eggName)
	if eggName == "" then
		return nil, nil
	end

	local size = "Regular"

	if eggName:find("^Large%s+") then
		size = "Large"
		eggName = trim(eggName:gsub("^Large%s+", ""))
	elseif eggName:find("^Huge%s+") then
		size = "Huge"
		eggName = trim(eggName:gsub("^Huge%s+", ""))
	end

	local eggType = eggName:match("^(%w+)%s+Egg$")
	if not eggType then
		eggType = eggName:match("^(%w+)")
	end

	eggType = titleCase(eggType or "")
	if eggType == "" then
		eggType = nil
	end

	return eggType, size
end

local function buildEggCategoriesWikitext(eggName)
	local eggType, size = getEggParts(eggName)
	if not eggType and not size then
		return ""
	end

	local cats = {}

	if eggType then
		table.insert(cats,
			string.format("[[Category:Pals Hatched From %s Eggs]]", eggType)
		)
	end

	if size then
		table.insert(cats,
			string.format("[[Category:Pals Hatched From %s Eggs]]", size)
		)
	end

	return table.concat(cats)
end

local function buildTopTable(breedingRankText, genderRatioText, eggNameText, eggDisplay, combiDuplicatePriority)
	local breedingRank = trim(breedingRankText)
	if breedingRank == "" then breedingRank = "Unknown" end

	local genderRatio = trim(genderRatioText)
	if genderRatio == "" then genderRatio = "Unknown" end

	local dupPriority = trim(combiDuplicatePriority)
	if dupPriority == "" then dupPriority = "Unknown" end

	local eggName = trim(eggNameText)
	if eggName == "" then eggName = "Unknown" end

	local hasEgg = not isBlank(eggDisplay)
	local tbl = html.create("table"):addClass("wikitable")

	if hasEgg then
		local tr1 = tbl:tag("tr")
		tr1:tag("th")
			:attr("rowspan", "3")
			:css("text-align", "center")
			:css("vertical-align", "middle")
			:css("width", "140px")
			:wikitext(eggDisplay)

		tr1:tag("th"):wikitext("Breeding Rank")
		tr1:tag("td"):wikitext(mw.text.nowiki(breedingRank))

		local tr2 = tbl:tag("tr")
		tr2:tag("th"):wikitext("Gender Ratio (F:M)")
		tr2:tag("td"):wikitext(mw.text.nowiki(genderRatio))

		local tr3 = tbl:tag("tr")
		tr3:tag("th"):wikitext("Breeding Duplicate Priority")
		tr3:tag("td"):wikitext(mw.text.nowiki(dupPriority))
	else
		local tr1 = tbl:tag("tr")
		tr1:tag("th"):wikitext("Breeding Rank")
		tr1:tag("td"):wikitext(mw.text.nowiki(breedingRank))

		local tr2 = tbl:tag("tr")
		tr2:tag("th"):wikitext("Gender Ratio (F:M)")
		tr2:tag("td"):wikitext(mw.text.nowiki(genderRatio))

		local tr3 = tbl:tag("tr")
		tr3:tag("th"):wikitext("Breeding Duplicate Priority")
		tr3:tag("td"):wikitext(mw.text.nowiki(dupPriority))

		local tr4 = tbl:tag("tr")
		tr4:tag("th"):wikitext("Egg")
		tr4:tag("td"):wikitext("Unknown")
	end

	return tbl
end

local function parsePalWithGender(text)
	text = trim(text)
	if text == "" then
		return nil, nil
	end

	local name, gender = text:match("^(.-)%s*%((.-)%)%s*$")
	if name then
		name = trim(name)
		gender = normalizeGender(gender)
		if name ~= "" then
			return name, gender
		end
	end

	return text, nil
end

local function parseBreedingCombos(text)
	local combos = {}
	text = trim(text)
	if text == "" then
		return combos
	end

	for _, row in ipairs(split(text, ";")) do
		local left, childText = row:match("^(.-)%s*=%s*(.-)%s*$")
		if left and childText then
			local p1Text, p2Text = left:match("^(.-)%s*%+%s*(.-)$")
			if p1Text and p2Text then
				local p1Name, p1Gender = parsePalWithGender(p1Text)
				local p2Name, p2Gender = parsePalWithGender(p2Text)
				local childName, childGender = parsePalWithGender(childText)

				if p1Name and p2Name and childName then
					table.insert(combos, {
						parent1Name = p1Name,
						parent1Gender = p1Gender,
						parent2Name = p2Name,
						parent2Gender = p2Gender,
						childName = childName,
						childGender = childGender
					})
				end
			end
		end
	end

	return combos
end

local function renderPalCard(container, palName, gender, role)
	local card = container:tag("div")
		:addClass("module-card")
		:addClass("module-breeding-card")
		:addClass("module-breeding-card-" .. role)
		:attr("data-pal", tostring(palName):lower())

	card:tag("div")
		:addClass("module-card-image")
		:wikitext(string.format(
			"[[File:%s_icon.png|frameless|link=%s]]",
			palName,
			palName
		))

	local nameText = not isBlank(gender)
		and string.format("[[%s]] (%s)", palName, gender)
		or string.format("[[%s]]", palName)

	card:tag("div")
		:addClass("module-card-banner-top")
		:tag("span")
			:addClass("module-card-banner-top-text")
			:wikitext(nameText)

	return card
end

local function renderBreedingCombos(combos)
	if not combos or #combos == 0 then
		return nil
	end

	local root = html.create("div")
		:addClass("module-breeding-combos")
		:cssText(
			"display:flex;" ..
			"flex-direction:column;" ..
			"gap:12px;"
		)

	for _, c in ipairs(combos) do
		local wrap = root:tag("div")
			:addClass("module-breeding-combo-row")
			:cssText(
				"display:flex;" ..
				"align-items:center;" ..
				"gap:10px;" ..
				"flex-wrap:wrap;"
			)

		renderPalCard(wrap, c.parent1Name, c.parent1Gender, "parent1")

		wrap:tag("div")
			:addClass("module-breeding-combo-sep")
			:cssText(
				"font-size:32px;" ..
				"font-weight:800;" ..
				"line-height:1;" ..
				"min-width:22px;" ..
				"text-align:center;" ..
				"align-self:center;"
			)
			:wikitext("+")
			:done()

		renderPalCard(wrap, c.parent2Name, c.parent2Gender, "parent2")

		wrap:tag("div")
			:addClass("module-breeding-combo-sep")
			:cssText(
				"font-size:32px;" ..
				"font-weight:800;" ..
				"line-height:1;" ..
				"min-width:22px;" ..
				"text-align:center;" ..
				"align-self:center;"
			)
			:wikitext("=")
			:done()

		renderPalCard(wrap, c.childName, c.childGender, "child")
	end

	return root
end

function p.main(frame)
	local args = argsUtil.merge()
	local title = mw.title.getCurrentTitle()

	local palName = trim(args.palName)
	if palName == "" then
		palName = title.text
	end

	local breedingRankRaw = args.breedingRank or args.breeding_rank
	local breedingRankNum = toNumber(breedingRankRaw)
	local breedingRankText = trim(breedingRankRaw)

	local maleProbability = args.maleProbability or args.male_probability
	local genderRatioText = formatGenderRatio(maleProbability, args.genderRatio or args.gender_ratio)

	local eggName = args.palEgg or args.egg
	local eggDisplay = makeEggDisplay(eggName, args.eggLink or args.egg_link)

	local combiDuplicatePriority = args.combi_duplicate_priority
	local combos = parseBreedingCombos(args.uniqueCombos or args.unique_combos or "")
	local isUniqueCombo = (#combos > 0)

	local out = html.create("div")

	if title.namespace == 0 then
		cargoUtil.storeRow("PalBreeding", {
			palName = palName,
			breedingRank = breedingRankNum and tostring(breedingRankNum) or nil,
			maleProbability = toNumber(maleProbability) and tostring(toNumber(maleProbability)) or nil,
			isUniqueCombo = isUniqueCombo and "1" or "0",
			palEgg = (trim(eggName) ~= "" and trim(eggName) or nil),
			combiDuplicatePriority = (trim(combiDuplicatePriority) ~= "" and trim(combiDuplicatePriority) or nil),
		})

		out:wikitext(buildEggCategoriesWikitext(eggName))
	end

	out:node(buildTopTable(
		breedingRankText,
		genderRatioText,
		eggName,
		eggDisplay,
		combiDuplicatePriority
	))

	local combosNode = renderBreedingCombos(combos)
	if combosNode then
		out:tag("h3"):wikitext("Unique Breeding Combinations")
		out:node(combosNode)
	end

	return tostring(out)
end

return p