Modulo:Learnlist

da Pokémon Central Wiki, l'enciclopedia Pokémon in italiano.
Vai alla navigazione Vai alla ricerca
Questo modulo non ha ancora un manuale. Creane uno!
--[[

Creates the list of moves learned by a certain Pokémon.

Its main WikiCode interface are "level", "tm", "breed", "tutor", "preevo" and
"event", used as

{{#invoke: learnlist | level | <pokemon name> | gen = <gen> | form = <abbr> }}

where "gen" is optional and defaults to the latest generation, and "form" is
optional and defaults to base form.

Also the Pokémon name is optional and defaults to the page's name.
A typical usage is

{{#invoke: learnlist | level }}

in a Pokémon's page in order to build the list of moves it learns in the latest
generation.

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

Some useful informations for developers:
    - notes are needed only for breed and preevo
    - except for level and tm, other methods has exactly one line per move.
      A move can be learned at multiple levels, and a tm or hm with a move can
      change within a generation (es: spaccaroccia)

--]]

local l = {}

local txt = require('Modulo:Wikilib/strings')      -- luacheck: no unused
local tab = require('Modulo:Wikilib/tables')       -- luacheck: no unused
local lib = require('Modulo:Wikilib/learnlists')
local genlib = require('Modulo:Wikilib/gens')
local multigen = require('Modulo:Wikilib/multigen')
local wlib = require('Modulo:Wikilib')
local links = require('Modulo:Links')
local ms = require('Modulo:MiniSprite')
local hf = require('Modulo:Learnlist/hf')
local sup = mw.loadData('Modulo:Sup/data')
local pokes = mw.loadData('Modulo:Poké/data')
local moves = mw.loadData('Modulo:Move/data')
local gendata = mw.loadData('Modulo:Gens/data')
local mtdata = mw.loadData('Modulo:Machines/data')

-- ============================ Wikilib-learnlists ============================
-- Here are functions previously in Wikilib-learnlists that uses PokéMoves-data
-- Moved here to remove dependency on the data module of pages that doesn't use
-- it specifically
local pokemoves = mw.loadData('Modulo:PokéMoves/data')

--[[

Given something, compute breed notes, ie. "breed chain", "the parent should
have learned the move in a previous gen" or "no parent can learn the move".
Arguments:
	- gen: the generation of this entry
	- move: the name of the move
	- parent: any parent listed in the data module
	- basenotes (optional): notes from the data module
	                        (ie. pokemoves[poke][kind][gen][move].notes)

--]]
l.breednotes = function(gen, move, parent, basenotes)
	local notes = { basenotes }
	-- To compute notes it checks only one parent because they should all be
	-- the same for this. Otherwise the different one would be the only one
	-- (for instance: parents that need a chain aren't listed if there are
	-- some that doesn't)

	if parent and not l.canLearn(move, parent, gen, {"breed"}) then
		if l.learnKind(move, parent, gen, "breed") then
			-- Parent can learn by breed but not in any other way: chain
			table.insert(notes, 1, "catena di accoppiamenti")
		-- In theory this second check is useless because a parent wouldn't
		-- be listed if it doesn't learn the move, so if it doesn't in this
		-- gen it should in a past one
		-- elseif l.learnPreviousGen(move, parent1, gen) then
		else
			table.insert(notes, 1, "il padre deve aver imparato la mossa in una generazione precedente")
		end
	elseif not parent then
		table.insert(notes, 1, "nessun genitore può apprendere la mossa")
	end

	return table.concat(notes, ", ")
end

-- ====================== "Decompress" PokéMoves entries ======================
-- Decompress a level entry. A level entry is the "table" obtained picking a
-- pokemon, generation and move from pokemoves-data
-- (ie: pokemoves[poke].level[gen][move])
l.decompressLevelEntry = function(entry, gen)
	local res
	if type(entry) == 'table' then
		res = table.copy(entry)
	else
		res = { { entry } }
	end
	-- if type(res[1]) ~= 'table' then
	-- 	res[1] = {res[1]}
	-- end
	if #res == 1 then
		res = table.map(lib.games.level[gen], function()
			return table.copy(res[1])
		end)
	end
	return res
end

-- Get a decompressed level entry
l.getLevelEntry = function(move, ndex, gen)
	local pmkind = pokemoves[ndex].level
	if not pmkind or not pmkind[gen] or not pmkind[gen][move] then
		return nil
	end
	return l.decompressLevelEntry(pmkind[gen][move], gen)
end

-- ========================== Check learn functions ==========================
--[[

Given a move, an ndex, a gen and a kind check whether that Pokémon can learn
that move in that generation in that kind. Return a true value if it can, a
false otherwise.
Arguments:
	- move: name of the move
	- ndex: name or ndex of the Pokémon
	- gen: generation (a string)
	- kind: kind of learnlist ("level", "tm", ...)

--]]
l.learnKind = function(move, ndex, gen, kind)
	local pmkind = pokemoves[ndex][kind]
	if not pmkind or not pmkind[gen] then
		return false
	end
	local mdata = pmkind[gen]
	if kind == "tm" then
		local mlist = mdata.all and tmdata[gen] or mdata
		-- Extra parentheses to force a single return value
		return (table.deepSearch(mlist, move))
	else
		return mdata[move]
	end
end

--[[

Given a move and an ndex check whether that Pokémon can learn the given move
in a given generation. Return a true value if it can, a false otherwise. It is
also possible to give an array of kind that aren't considered when determining
whether it can learn the move or not.
Arguments:
	- move: name of the move
	- ndex: name or ndex of the Pokémon
	- gen: generation
	- excludekinds: (optional) array of kinds to exclude

--]]
l.canLearn = function(move, ndex, gen, excludekinds)
	excludekinds = excludekinds or {}
	return table.any(pokemoves[ndex], function(_, kind)
		if table.search(excludekinds, kind) then
			return false
		end
		return l.learnKind(move, ndex, gen, kind)
	end)
end

--[[

Check whether a a Pokémon can learn a move in a generation previous than the
given one. If it can't returns false, otherwise the highest generation in which
it can  learn it.
Arguments:
	- move: name of the move
	- ndex: name or ndex of the Pokémon
	- gen: the gen considered: the function controls any generation strictly
	       lower than this.
	- firstgen: (optional) the lowest gen to check. Defaults to 1

--]]
l.learnPreviousGen = function(move, ndex, gen, firstgen)
	for g = gen - 1, firstgen or 1, -1 do
		if table.any(pokemoves[ndex], function(_, kind)
			return l.learnKind(move, ndex, g, kind)
		end) then
			return g
		end
	end
	return false
end

-- ========================== End Wikilib-learnlists ==========================


local STRINGS = {
    evolevelstr = 'Evo<span class="hidden-xs">luzione</span>',
    tmcell = [=[
| class="black-text" style="padding: 0.1em 0.3em;" | <span class="hidden-xs">[[File:${img} ${tipo} VIII Sprite Zaino.png]]</span>[[${p1}]]]=],
    breedcell = [=[
| style="padding: 0.1em 0.3em;" | ${p}]=],
}

--[[

TODO: select entry building function depending on gen (eg: gen 1 -> no category,
gen 4 -> contest entry)

Build the string corresponding to the last part of an entry, that is the part
without the learning method (levels for level entry, parents for breed entry,
etc.).

Arguments:
    - poke: Pokémon name or ndex
    - mossa: move name
    - notes: any note that should be added to this entry
    - gen: (optional) gen of the entry. Defaults to latest

--]]
l.entrytail = function(poke, mossa, notes, gen)
    local data = multigen.getgen(moves[mossa], gen)
    local stab = lib.computeSTAB(poke, mossa, nil, gen)
    return lib.categoryentry(stab, data.name, notes, string.fu(data.type),
                             string.fu(data.category), data.power,
                             data.accuracy, data.pp)
end

--[[

Add header and footer for a learnlist table.
Arguments:
    - str: body of the learnlist, to enclose between header and footer
    - poke: Pokémon name or ndex
    - gen: the generation of this entry
    - kind: kind of entry. Either "level", "tm", "breed", "tutor", "preevo" and
            "event".

--]]
l.addhf = function(str, poke, gen, kind)
    local pokedata = multigen.getGen(pokes[poke], gen)
    local hfargs = { pokedata.name, pokedata.type1, pokedata.type2, gen,
                     genlib.getGen.ndex(pokedata.ndex) }
    return table.concat({
        hf[kind .. "h"]{ args = hfargs },
        str,
        hf[kind .. "f"]{ args = hfargs },
    }, "\n")
end

--[[

General function to build an entry. Requires some functions in funcDict to
determine details of the entry.
Oop isn't used because there's no inheritance.

Arguments:
    - poke: Pokémon name or ndex
    - gen: the generation of this entry
    - kind: kind of entry. Either "level", "tm", "breed", "tutor", "preevo" and
            "event". Also used to select functions (picks the funcDict)

This function makes use of a dictionary of function, retrieved using "kind", to
implement details of the entry. It should contain the following functions:
    - processData: given a single value of pokemoves[poke][kind][gen]
                   transform it in a format more suitable for sorting and
                   printing. It is mapped over that table with dataMap.
                   Takes four arguments:
                    - poke: Pokémon name or ndex
                    - gen: the generation of this entry
                    - value: the value of pokemoves[poke][kind][gen][key]
                    - key: the key of that value
                   It should return an array of elements that will be sorted
                   and printed using other functions in the dict.
    - dataMap: the kind of map used to map processData over the collection.
               Often table.flatMapToNum or table.mapToNum
    - lt: given two elements produced by processData, compares them
          Takes two arguments, the two elements to compare.
    - makeEntry: create the string of an entry from an element produced by
                 processData.
                 Takes three arguments:
                    - poke: Pokémon name or ndex
                    - gen: the generation of this entry
                    - entry: the element produced by processData

--]]
l.entryLua = function(poke, gen, kind)
    local funcDict = l.dicts[kind]

    local res = {}
    local pmkind = pokemoves[poke][kind]
    if pmkind and pmkind[gen] then
        res = funcDict.dataMap(pmkind[gen],
            function(v, k) return funcDict.processData(poke, gen, v, k) end)
    end
    local resstr
    if #res == 0 then
        resstr = lib.entrynull(kind, "100")
    else
        table.sort(res, funcDict.lt)
        resstr = wlib.mapAndConcat(res, "\n", function(val)
            return funcDict.makeEntry(poke, gen, val)
        end)
    end

    return l.addhf(resstr, poke, gen, kind)
end

l.dicts = {}

--[[

Given a frame for learnlist returns the two parameters poke and gen, in this
order.

--]]
l.getParams = function(frame)
    local p = lib.sanitize(mw.clone(frame.args))
    local gen = tonumber(p.gen) or gendata.latest
    local form = p.form or ""
    local poke = mw.text.decode(p[1] or mw.title.getCurrentTitle().baseText):lower()
    return poke .. form, gen
end

--[[

Adds main lua and WikiCode interfaces for a kind of entries.
Arguments:
    - kind: the kind of entry

Lua interfaces take two arguments, poke and gen.
WikiCode interfaces are described in the initial comment.

--]]
local addInterfaces = function(kind)
    l[kind .. "Lua"] = function(poke, gen)
        return l.entryLua(poke, gen, kind)
    end

    l[kind] = function(frame)
        local poke, gen = l.getParams(frame)
        return l.entryLua(poke, gen, kind)
    end
    l[string.fu(kind)] = l[kind]
end


-- ================================== Level ==================================
--[[

This table hold texts used when a Pokémon can't learn a move in a game. It is
guaranteed to have the very same structure as pokemoves.games.level

--]]
l.nogameText = {
    [1] = {
        "Disponibile solo in Giallo",
        "Disponibile solo in Rosso e Blu"
    },
    [2] = {
        "Disponibile solo in Cristallo",
        "Disponibile solo in Oro e Argento"
    },
    [3] = {
        "Non disponibile in Rubino e Zaffiro",
        "Non disponibile in Rosso Fuoco e Verde Foglia",
        "Non disponibile in Smeraldo"
    },
    [4] = {
        "Non disponibile in Diamante e Perla",
        "Non disponibile in Platino",
        "Non disponibile in Oro HeartGold e Argento SoulSilver"
    },
    [5] = {
        "Disponibile solo in Nero 2 e Bianco 2",
        "Disponibile solo in Nero e Bianco"
    },
    [6] = {
        "Disponibile solo in Rubino Omega e Zaffiro Alpha",
        "Disponibile solo in X e Y"
    },
    [7] = {
        "Disponibile solo in Ultrasole e Ultraluna",
        "Disponibile solo in Sole e Luna"
    },
    [8] = {
        "",
    }
}

--[[

Creates and entry of a level learnlist.

Arguments:
    - poke: Pokémon name or ndex
    - gen: set of game for this entry. Should be an index of
           pokemoves.games.level
    - move: move name
    - levels: the set of levels to print. It should be in the following format:
        an array, of the same length ad pokemoves.games.level[gen], with each
        element being either a level (a number, "inizio" or "evo") or false,
        meaning that the Pokémon can't learn the move in that game

--]]
l.levelEntry = function(poke, gen, move, levels)
    levels = table.map(l.nogameText[gen], function(text, idx)
        local lvl = levels[idx]
        if not lvl then
            return links.tt('&mdash;', text)
        elseif lvl == "evo" then
            return STRINGS.evolevelstr
        else
            return string.fu(lvl)
        end
    end)
    return table.concat{
        '|-\n',
        lib.gameslevel(unpack(levels)),
        l.entrytail(poke, move, '', gen),
    }
end

-- Compares two levels (a number, "inizio", "evo" or nil). The order is
-- "inizio" < "evo" < numbers < nil
l.ltLevel = function(a, b)
    if b == "inizio" then
        return false
    elseif b == "evo" and a ~= "inizio" then
        return false
    elseif b == nil then
        return a ~= nil
    elseif (type(a) == "number" and a >= b) or a == nil then -- b is a number
        return false
    end
    return true
end

--[[

Comparator for levels. Given two levels tables, that are two arrays of the same
length with each element being either a level (a number, "inizio" or "evo") or
false, it returns true iff a <= b.

Sorting is lexicographic on levels, replacing false with the first subsequent
non-false value.

TODO: make this function more efficient

--]]
l.ltLevelarr = function(a, b)
    for k, v in ipairs(a[2]) do
        local aval, k1 = v, k
        -- Can't use (not aval) because that is true also for nil
        while aval == false do
            k1 = k1 + 1
            aval = a[2][k1]
        end
        local bval, k2 = b[2][k], k
        while bval == false do
            k2 = k2 + 1
            bval = b[2][k2]
        end
        if l.ltLevel(aval, bval) then
            return true
        elseif aval ~= bval then -- if they aren't equale then bval > aval
            return false
        end
    end
    -- here aval == bval at any iteration => equality => check name
    return a[1] < b[1]
end

l.dicts.level = {
    processData = function(_, gen, levels, move)
        levels = l.decompressLevelEntry(levels, gen)
        -- levels = { {"inizio"}, {"inizio", "evo"} },
        local alllevels = table.unique(table.flatten(levels))
        return table.map(alllevels, function(lvl)
            return { move, table.map(levels, function(t)
                return table.search(t, lvl) and lvl or false
            end) }
        end)
    end,
    dataMap = table.flatMapToNum,
    -- elements of res are like
    -- {
    --     <movename>,
    --    { <level of first game or false>, <level of second game or false>, ... },
    -- }
    lt = l.ltLevelarr,
    makeEntry = function(poke, gen, val)
        return l.levelEntry(poke, gen, unpack(val))
    end,
}

addInterfaces("level")

-- ==================================== Tm ====================================
--[[

Creates an entries of a tm learnlist.

Arguments:
    - poke: Pokémon name or ndex
    - gen: generation for this entry
    - move: move name, all lowercase
    - tmnum: tm or hm number. Is a pair { "M[TN]", "<number>" }
    - games: (optional) the abbr of a game, that is put as a note in sup

--]]
l.tmEntry = function(poke, gen, move, tmnum, games)
    local tmcell = string.interp(STRINGS.tmcell, {
        img = tmnum[1],
        p1 = table.concat(tmnum),
        tipo = string.fu(multigen.getGenValue(moves[move].type, gen))
               or 'Sconosciuto'
    })
    return table.concat{
        '|-\n',
        tmcell,
        l.entrytail(poke, move, sup[games:upper()] or "", gen),
    }
end

--[[

Comparator for two tm/hm pairs.

--]]
l.ltTm = function(a, b)
    return a[1] > b[1] or (a[1] == b[1] and tonumber(a[2]) < tonumber(b[2]))
end

l.dicts.tm = {
    processData = function(_, gen, move)
        return table.mapToNum(mtdata[gen], function(tmdata, tmkind)
            local tmnum, i = table.deepSearch(tmdata, move)
            if tmnum then
                local tmnums = string.nFigures(tmnum, 2)
                if i then
                    return { move, { tmkind, tmnums }, tmdata[tmnum][i][1] }
                else
                    return { move, { tmkind, tmnums }, "" }
                end
            end
            -- Needed because otherwise the function returns 0 values and
            -- table.insert complains (there's no automatic addition of nils)
            return nil
        end)
    end,
    dataMap = table.flatMapToNum,
    -- elements of res are like
    -- { <movename>, { "M[TN]", "12"}, <games abbr> }
    lt = function(a, b)
        return l.ltTm(a[2], b[2])
    end,
    makeEntry = function(poke, gen, val)
        return l.tmEntry(poke, gen, unpack(val))
    end,
}

-- Added by hand to handle the special case with all tms
l.tmLua = function(poke, gen)
    if pokemoves[poke].tm[gen].all then
        return l.addhf(hf.alltm{args={poke, gen, "tm"}}, poke, gen, "tm")
    end
    return l.entryLua(poke, gen, "tm")
end

l.tm = function(frame)
    return l.tmLua(l.getParams(frame))
end
l.Tm = l.tm
-- addInterfaces("tm")

-- ================================== Breed ==================================
l.dicts.breed = {
    processData = function(_, gen, movedata, move)
        --Bulba style: in a Pokémon page it prints parents for the latest game
        local parents = lib.moveParentsGame(movedata,
                        lib.games.breed[gen][#lib.games.breed[gen]])

        local notes = movedata.notes or l.breednotes(gen, move, parents[1])
        local res = { move, parents[1] and parents or { 000 },
                      notes == "" and "" or links.tt("*", string.fu(notes))
                    }
        if movedata.games then
            res[3] = wlib.mapAndConcat(movedata.games,
                                       function(s) return sup[s] end)
                     .. res[3]
        end
        return res
    end,
    dataMap = table.mapToNum,
    -- elements of res are like
    -- { <movename>, { <array of parents> }, <notes> }
    lt = function(a, b)
        return a[1] < b[1]
    end,
    makeEntry = function(poke, gen, val)
        local firstcell = string.interp(STRINGS.breedcell, {
            p = lib.msarrayToModal(val[2], gen, nil, 6),
        })
        return table.concat{
            '|-\n',
            firstcell,
            l.entrytail(poke, val[1], val[3], gen),
        }
    end,
}

addInterfaces("breed")

-- ================================== Tutor ==================================
l.dicts.tutor = {
    processData = function(_, gen, games, move)
        return {
            move,
            table.zip(lib.games.tutor[gen], games, function(a, b)
                return { a, b and "Yes" or "No" }
            end)
        }
    end,
    dataMap = table.mapToNum,
    -- elements of res are like
    -- { <movename>, { <array of games pairs { <abbr>, "Yes"/"No" }> } }
    lt = function(a, b)
        for k, v in ipairs(a[2]) do
            if v[2] ~= b[2][k][2] then
                -- Equivalent to v[2] < b[2][k][2]
                return v[2] == "Yes"
            end
        end
        return a[1] < b[1]
    end,
    makeEntry = function(poke, _, val)
        return table.concat{
            '|-\n',
            lib.tutorgames(val[2]),
            l.entrytail(poke, val[1], "", gen),
        }
    end,
}

addInterfaces("tutor")

-- ================================== Preevo ==================================
l.dicts.preevo = {
    processData = function(_, _, preevos, move)
        return { move, table.map(preevos, function(ndex)
            return { ndex, "" }
        end, ipairs), games = preevos.games }
    end,
    dataMap = table.mapToNum,
    -- elements of res are like
    -- { <movename>, { <array of preevo pairs: { ndex, notes }> } }
    lt = function(a, b)
        return a[1] < b[1]
    end,
    makeEntry = function(poke, gen, val)
        local firstcell = wlib.mapAndConcat(val[2], function(pair)
            return ms.staticLua(string.tf(pair[1] or "000"), gen or "")
                   .. (lib.preevott[pair[2]] or "")
        end)
        local notes = val.games
                      and wlib.mapAndConcat(val.games, function(s) return sup[s] end)
                      or ""
        return table.concat{
            "|-\n| ",
            firstcell,
            l.entrytail(poke, val[1], notes, gen),
        }
    end,
}

addInterfaces("preevo")

-- ================================== Event ==================================
l.dicts.event = {
    processData = function(_, _, occasion, move)
        return { move, occasion }
    end,
    dataMap = table.mapToNum,
    -- elements of res are like
    -- { <movename>, <occasion> }
    lt = function(a, b)
        return a[1] < b[1]
    end,
    makeEntry = function(poke, gen, val)
        local firstcell = string.interp(STRINGS.breedcell, {
            p = val[2],
        })
        return table.concat{
            '|-\n',
            firstcell,
            l.entrytail(poke, val[1], "", gen),
        }
    end,
}

addInterfaces("event")

-- ============================================================================
return l