Module:BreedingCalculator

From Palworld Wiki
Jump to navigation Jump to search

Documentation for this module may be created at Module:BreedingCalculator/doc

local p = {}
local cargo = mw.ext.cargo

-- TODO: Pull this data from Cargo somehow
local ExclusiveChildren = {
    ["Relaxaurus Lux"]="Relaxaurus+Sparkit",
    ["Incineram Noct"]="Incineram+Maraith",
    ["Mau Cryst"]="Mau+Pengullet",
    ["Vanwyrm Cryst"]="Vanwyrm+Foxcicle",
    ["Eikthyrdeer Terra"]="Eikthyrdeer+Hangyu",
    ["Elphidran Aqua"]="Elphidran+Surfent",
    ["Pyrin Noct"]="Pyrin+Katress",
    ["Mammorest Cryst"]="Mammorest+Wumpo",
    ["Mossanda Lux"]="Mossanda+Grizzbolt",
    ["Dinossom Lux"]="Dinossom+Rayhound",
    ["Jolthog Cryst"]="Jolthog+Pengullet",
    ["Frostallion Noct"]="Frostallion+Helzephyr",
    ["Ice Kingpaca"]="Kingpaca+Reindrix",
    ["Lyleen Noct"]="Lyleen+Menasting",
    ["Leezpunk Ignis"]="Leezpunk+Flambelle",
    ["Blazehowl Noct"]="Blazehowl+Felbat",
    ["Robinquill Terra"]="Robinquill+Fuddler",
    ["Broncherry Aqua"]="Broncherry+Fuack",
    ["Surfent Terra"]="Surfent+Dumud",
    ["Gobfin Ignis"]="Gobfin+Rooby",
    ["Suzaku Aqua"]="Suzaku+Jormuntide",
    ["Ice Reptyro"]="Reptyro+Foxcicle",
    ["Hangyu Cryst"]="Hangyu+Swee",
    ["Lyleen"]="Mossanda+Petallia",
    ["Faleris"]="Vanwyrm+Anubis",
    ["Grizzbolt"]="Mossanda+Rayhound",
    ["Orserk"]="Grizzbolt+Relaxaurus",
    ["Shadowbeak"]="Kitsun+Astegon",
}

-- TODO: Pull this data from Cargo somehow
local ExclusiveParents = {
    ["Relaxaurus+Sparkit"]="Relaxaurus Lux",
    ["Sparkit+Relaxaurus"]="Relaxaurus Lux",
    ["Incineram+Maraith"]="Incineram Noct",
    ["Maraith+Incineram"]="Incineram Noct",
    ["Mau+Pengullet"]="Mau Cryst",
    ["Pengullet+Mau"]="Mau Cryst",
    ["Vanwyrm+Foxcicle"]="Vanwyrm Cryst",
    ["Foxcicle+Vanwyrm"]="Vanwyrm Cryst",
    ["Eikthyrdeer+Hangyu"]="Eikthyrdeer Terra",
    ["Hangyu+Eikthyrdeer"]="Eikthyrdeer Terra",
    ["Elphidran+Surfent"]="Elphidran Aqua",
    ["Surfent+Elphidran"]="Elphidran Aqua",
    ["Pyrin+Katress"]="Pyrin Noct",
    ["Katress+Pyrin"]="Pyrin Noct",
    ["Mammorest+Wumpo"]="Mammorest Cryst",
    ["Wumpo+Mammorest"]="Mammorest Cryst",
    ["Mossanda+Grizzbolt"]="Mossanda Lux",
    ["Grizzbolt+Mossanda"]="Mossanda Lux",
    ["Dinossom+Rayhound"]="Dinossom Lux",
    ["Rayhound+Dinossom"]="Dinossom Lux",
    ["Jolthog+Pengullet"]="Jolthog Cryst",
    ["Pengullet+Jolthog"]="Jolthog Cryst",
    ["Frostallion+Helzephyr"]="Frostallion Noct",
    ["Helzephyr+Frostallion"]="Frostallion Noct",
    ["Kingpaca+Reindrix"]="Ice Kingpaca",
    ["Reindrix+Kingpaca"]="Ice Kingpaca",
    ["Lyleen+Menasting"]="Lyleen Noct",
    ["Menasting+Lyleen"]="Lyleen Noct",
    ["Leezpunk+Flambelle"]="Leezpunk Ignis",
    ["Flambelle+Leezpunk"]="Leezpunk Ignis",
    ["Blazehowl+Felbat"]="Blazehowl Noct",
    ["Felbat+Blazehowl"]="Blazehowl Noct",
    ["Robinquill+Fuddler"]="Robinquill Terra",
    ["Fuddler+Robinquill"]="Robinquill Terra",
    ["Broncherry+Fuack"]="Broncherry Aqua",
    ["Fuack+Broncherry"]="Broncherry Aqua",
    ["Surfent+Dumud"]="Surfent Terra",
    ["Dumud+Surfent"]="Surfent Terra",
    ["Gobfin+Rooby"]="Gobfin Ignis",
    ["Rooby+Gobfin"]="Gobfin Ignis",
    ["Suzaku+Jormuntide"]="Suzaku Aqua",
    ["Jormuntide+Suzaku"]="Suzaku Aqua",
    ["Reptyro+Foxcicle"]="Ice Reptyro",
    ["Foxcicle+Reptyro"]="Ice Reptyro",
    ["Hangyu+Swee"]="Hangyu Cryst",
    ["Swee+Hangyu"]="Hangyu Cryst",
    ["Mossanda+Petallia"]="Lyleen",
    ["Petallia+Mossanda"]="Lyleen",
    ["Vanwyrm+Anubis"]="Faleris",
    ["Anubis+Vanwyrm"]="Faleris",
    ["Mossanda+Rayhound"]="Grizzbolt",
    ["Rayhound+Mossanda"]="Grizzbolt",
    ["Grizzbolt+Relaxaurus"]="Orserk",
    ["Relaxaurus+Grizzbolt"]="Orserk",
    ["Kitsun+Astegon"]="Shadowbeak",
    ["Astegon+Kitsun"]="Shadowbeak",
}

function Round(value)
    return math.floor(value + 0.5)
end

function Split(inputstr, sep)
    if sep == nil then
       sep = "%s"
    end
    local t={}
    for str in string.gmatch(inputstr, "([^"..sep.."]+)") do
       table.insert(t, str)
    end
    return t
 end

function CalculateBreedingRank (palBreedingRanks, parent1, parent2)
    local parent1Rank = palBreedingRanks[parent1]
    local parent2Rank = palBreedingRanks[parent2]

    if (parent1Rank == nil) or (parent2Rank == nil) then
        return 0
    end

    if parent1Rank == parent2Rank then
        return parent1Rank
    end

    -- Get the average of the two parents, rounded up.
    local childRankRaw = math.floor((parent1Rank + parent2Rank) / 2)

    return childRankRaw
end

function ScanBreedingRank (breedingRankPals, childRank)
    local childName = breedingRankPals[childRank]
    if childName == nil then
        childRank = Round((childRank * 5 / 5))
        childName = breedingRankPals[childRank]

        if childName == nil then
            childName = 'Unknown'
        end
    end

    return childRank, childName
end

function BruteForceParents (palBreedingRanks, breedingRankPals, target)
    local parents = {}

    for parent1, rank in pairs(palBreedingRanks) do
        for parent2, rank2 in pairs(palBreedingRanks) do
            local child = GetChild(palBreedingRanks, breedingRankPals, parent1, parent2)
            mw.log('BruteForceParents(' .. target .. ', ' .. parent1 .. ', ' .. parent2 .. ') = ' .. child)

            if child == target then
                table.insert(parents, {parent1, parent2})
            end
        end
    end

    return parents
end

function GetParents ( palBreedingRanks, breedingRankPals, child )
    if ExclusiveChildren[child] ~= nil then
        local parents = Split(ExclusiveChildren[child], '+')
        mw.log('GetParents(' .. child .. ') = EXCLUSIVE(' .. parents[0] .. ', ' .. parents[1] .. ')')
        return {
            {parents[0], parents[1]},
            {child, child}
        }
    end

    mw.log('GetParents(' .. child .. ')')

    local output = ''
    local parents = BruteForceParents(palBreedingRanks, breedingRankPals, child)

    for key, ppair in pairs(parents) do
        output = output .. '\n\n' .. ppair[0] .. ' + ' .. ppair[1]
    end

    return output
end

function GetChild ( palBreedingRanks, breedingRankPals, parent1, parent2 )
    if parent1 == parent2 then
        return parent1
    end

    if ExclusiveParents[parent1..'+'..parent2] ~= nil then
        return ExclusiveParents[parent1..'+'..parent2]
    end

    local childRankRaw = CalculateBreedingRank(palBreedingRanks, parent1, parent2)
    local childRank, childName = ScanBreedingRank(breedingRankPals, childRankRaw)

    --mw.log('GetChild(' .. parent1 .. ', ' .. parent2 .. ') = ' .. childName .. ' (' .. childRank .. ')')
    local output = '' .. ('GetChild(' .. parent1 .. ', ' .. parent2 .. ') = ' .. childName .. ' (' .. childRank .. ')')

    return output .. '\n\n' .. childName
end

function p.BuildTables () 
    local output = ''

    local tables = 'Pals'
    local fields = 'Pal, Number, BreedingRank'
    -- optional parameters are grouped in one table
    -- you can omit any or all of them, except join if you use more than one table
    local args = {
        where = 'Pals.Number > 0 AND Pals.Pal IS NOT NULL AND Pals.BreedingRank > 0 AND Pals.BreedingRank <= 1500',
        -- Sorting by Paldeck number is IMPORTANT for later!
        orderBy = 'Pals.Number',
        offset = 0,
    }
    local results = cargo.query( tables, fields, args )
    
    -- Check if there were zero results.
    if #results == 0 then
        mw.log(output)
        return 'No results found'
    end

    -- Loop through the results, building a BreedingRank -> Pal table and a Pal -> BreedingRank table
    local breedingRankPals = {}
    local palBreedingRanks = {}
    local paldeck = {}
    local maxBreedingRank = 0
    for r = 1, #results do
        local result = results[r]
        local name = result.Pal or 'Unknown'

        if ExclusiveChildren[name] == nil then
            -- Only add a Breeding Rank if it is not an exclusive child.
            local breedingRank = result.BreedingRank or '0'
            local currentBreedingRank = math.floor(tonumber(breedingRank) or 0)
            if currentBreedingRank > maxBreedingRank then
                maxBreedingRank = currentBreedingRank
            end
    
            palBreedingRanks[name] = breedingRank
            local paldeckNumber = result.Number:gsub('B', '')
            paldeck[name] = tonumber(paldeckNumber)
            if breedingRankPals[currentBreedingRank] == nil then
                -- If two Pals have the same rank, use the first result (the one with the lowest Paldeck number).
                breedingRankPals[currentBreedingRank] = name
            end
        end
    end

    local prevBreedingRank = 1
    for r = 1, maxBreedingRank do
        local needsValue = not breedingRankPals[r]
        if needsValue then
            -- Calculate which Pal would be used.
            local prevPal = breedingRankPals[prevBreedingRank]
            if prevPal == nil or prevPal == 'Unknown' then
                mw.log('Unknown BreedingRank (' .. r .. ')')
                breedingRankPals[r] = 'Unknown'
            else
                local nextBreedingRank = r + 1
                local nextPal = breedingRankPals[nextBreedingRank]
                while nextPal == nil do
                    nextBreedingRank = nextBreedingRank + 1
                    nextPal = breedingRankPals[nextBreedingRank]
                end

                local prevDiff = r - prevBreedingRank
                local nextDiff = nextBreedingRank - r

                -- Use whichever Pal is closer
                if prevDiff < nextDiff then
                    breedingRankPals[r] = prevPal
                elseif prevDiff > nextDiff then
                    breedingRankPals[r] = nextPal
                else
                    local prevDeck = paldeck[prevPal]
                    local nextDeck = paldeck[nextPal]
                    if prevDeck > nextDeck then
                        breedingRankPals[r] = prevPal
                    else
                        breedingRankPals[r] = nextPal
                    end
                end
                -- 
            end
            mw.log('Calculated BreedingRank (' .. r .. ') = ' .. breedingRankPals[r])
        else
            mw.log('Got BreedingRank (' .. r .. ') = ' .. breedingRankPals[r])
            prevBreedingRank = r
        end
    end

    return output, breedingRankPals, palBreedingRanks
end

function p.Main( frame )
    local function_args = frame.args

    local operation = function_args.operation

    if not operation then
        return 'No operation specified'
    end
	
    local output, breedingRankPals, palBreedingRanks = p.BuildTables()

    -- Call a different function based on the operation
    if operation == 'GetParents' then
        local child = function_args.child
        output = output .. GetParents(palBreedingRanks, breedingRankPals, child)
    elseif operation == 'GetChild' then
        local parent1 = function_args.parent1
        local parent2 = function_args.parent2
        output = output .. GetChild(palBreedingRanks, breedingRankPals, parent1, parent2)
    else
        output = output .. 'Invalid operation'
    end

    return output
end

return p