Difference between revisions of "Module:Database"

From Cassette Beasts
 
(43 intermediate revisions by the same user not shown)
Line 54: Line 54:
 
return i
 
return i
 
end
 
end
 +
end
 +
 +
function cmp_i(a, b)
 +
if type(a) == "string" then
 +
a = string.lower(a)
 +
end
 +
if type(b) == "string" then
 +
b = string.lower(b)
 +
end
 +
return a < b
 
end
 
end
  
Line 60: Line 70:
 
local bi = species_sort_index(b)
 
local bi = species_sort_index(b)
 
if ai == bi then
 
if ai == bi then
return a.name < b.name
+
return string.lower(a.name) < string.lower(b.name)
 
end
 
end
 
return ai < bi
 
return ai < bi
Line 66: Line 76:
  
 
function cmp_moves(a, b)
 
function cmp_moves(a, b)
return a.name < b.name
+
return string.lower(a.name) < string.lower(b.name)
 +
end
 +
 
 +
function cmp_status_effects(a, b)
 +
return string.lower(a.name) < string.lower(b.name)
 
end
 
end
  
Line 103: Line 117:
 
return db.types.by_name[string.lower(id)] or db.types.typeless
 
return db.types.by_name[string.lower(id)] or db.types.typeless
 
end
 
end
 +
 +
function get_status_effect(id)
 +
return db.status_effects.by_name[string.lower(id)] or db.status_effects.unknown
 +
end
 +
 +
local getters = {
 +
species = get_species,
 +
move = get_move,
 +
["type"] = get_type,
 +
status_effect = get_status_effect
 +
}
  
 
function subscript(value, path, path_start)
 
function subscript(value, path, path_start)
Line 130: Line 155:
  
 
function textify(frame, value)
 
function textify(frame, value)
 +
if frame.args.sorted and type(value) == "table" then
 +
value = table.values(value)
 +
table.sort(value, cmp_i)
 +
elseif frame.args.sort_by and type(value) == "table" then
 +
value = table.values(value)
 +
table.sort(value, function(a, b)
 +
return cmp_i(a[frame.args.sort_by], b[frame.args.sort_by])
 +
end)
 +
end
 
if frame.args["if_" .. tostring(value)] ~= nil then
 
if frame.args["if_" .. tostring(value)] ~= nil then
 
return frame.args["if_" .. tostring(value)]
 
return frame.args["if_" .. tostring(value)]
Line 136: Line 170:
 
elseif type(value) == "table" and frame.args.if_empty ~= nil and table.empty(value) then
 
elseif type(value) == "table" and frame.args.if_empty ~= nil and table.empty(value) then
 
return frame.args.if_empty
 
return frame.args.if_empty
elseif type(value) == "table" and (frame.args.delim or frame.args["foreach"] ~= nil) then
+
elseif type(value) == "table" and (frame.args.delim or frame.args["foreach"]) then
 
local delim = frame.args.delim or ""
 
local delim = frame.args.delim or ""
 
if delim == "," then delim = delim .. " " end
 
if delim == "," then delim = delim .. " " end
 
local result = ""
 
local result = ""
 
local i = 1
 
local i = 1
for key,elem in ipairs(value) do
+
local iter
 +
if value[1] then iter = ipairs else iter = pairs end
 +
for key,elem in iter(value) do
 
if i > 1 then
 
if i > 1 then
 
result = result .. delim
 
result = result .. delim
Line 148: Line 184:
 
elem = subscript(elem, string.split(frame.args.subscript, "."), 1)
 
elem = subscript(elem, string.split(frame.args.subscript, "."), 1)
 
end
 
end
if frame.args["foreach"] ~= nil then
+
if frame.args["foreach"] then
 
local args
 
local args
 
if type(elem) == "table" then
 
if type(elem) == "table" then
Line 155: Line 191:
 
args = { elem, first = i == 1 }
 
args = { elem, first = i == 1 }
 
end
 
end
if frame.args.key_param ~= nil then
+
if frame.args.key_param then
 
local s = key
 
local s = key
if frame.args.key_param_format ~= nil then
+
if frame.args.key_param_format then
 
s = string.format(frame.args.key_param_format, s)
 
s = string.format(frame.args.key_param_format, s)
 
end
 
end
 
args[frame.args.key_param] = s
 
args[frame.args.key_param] = s
 
end
 
end
if frame.args.pass_through ~= nil then
+
if frame.args.pass_through then
 
args[frame.args.pass_through] = frame.args[frame.args.pass_through]
 
args[frame.args.pass_through] = frame.args[frame.args.pass_through]
 
end
 
end
 
flatten_template_args(args)
 
flatten_template_args(args)
mw.logObject(args)
 
 
elem = frame:expandTemplate{title = frame.args["foreach"], args = args}
 
elem = frame:expandTemplate{title = frame.args["foreach"], args = args}
 
else
 
else
Line 223: Line 258:
 
local result_set = {}
 
local result_set = {}
 
local species = get_species(frame.args[1])
 
local species = get_species(frame.args[1])
for _,tag in ipairs(species.moves.tags) do
+
local tags = species.moves.tags
 +
if frame.args.always_compatible then
 +
tags = species.moves.core_tags
 +
end
 +
for _,tag in ipairs(tags) do
 
if db.moves.by_tag[tag] then
 
if db.moves.by_tag[tag] then
 
for _,move in ipairs(db.moves.by_tag[tag]) do
 
for _,move in ipairs(db.moves.by_tag[tag]) do
Line 233: Line 272:
 
table.sort(result, cmp_moves)
 
table.sort(result, cmp_moves)
 
return textify(frame, result)
 
return textify(frame, result)
 +
end
 +
 +
function p.get_tag_moves(frame)
 +
local result = {}
 +
local tag = frame.args[1]
 +
local for_bootleg = frame.args.for_bootleg
 +
if db.moves.by_tag[tag] then
 +
for _,move in ipairs(db.moves.by_tag[tag]) do
 +
if not for_bootleg or move.camouflage ~= tag then
 +
table.insert(result, move)
 +
end
 +
end
 +
end
 +
return textify(frame, result)
 +
end
 +
 +
function p.get_species_move_learn_method(frame)
 +
local species = get_species(frame.args[1])
 +
local move = get_move(frame.args[2])
 +
for _,m in ipairs(species.moves.initial) do
 +
if move.name == m then
 +
return textify(frame, "initial")
 +
end
 +
end
 +
for i,m in ipairs(species.moves.upgrades) do
 +
if move.name == m then
 +
return textify(frame, i)
 +
end
 +
end
 +
if string.lower(species.elemental_type) ~= move.camouflage then
 +
local tags = {}
 +
for _,tag in ipairs(species.moves.tags) do
 +
tags[tag] = true
 +
end
 +
for _,tag in ipairs(move.tags) do
 +
if tags[tag] then
 +
return textify(frame, "compatible")
 +
end
 +
end
 +
end
 +
return textify(frame, "incompatible")
 
end
 
end
  
Line 284: Line 364:
 
if db.species.by_tag[tag] then
 
if db.species.by_tag[tag] then
 
for _,species in ipairs(db.species.by_tag[tag]) do
 
for _,species in ipairs(db.species.by_tag[tag]) do
result_set[species.name] = true
+
if string.lower(species.elemental_type) ~= move.camouflage then
 +
result_set[species.name] = true
 +
end
 
end
 
end
 
end
 
end
Line 291: Line 373:
 
for species,_ in pairs(result_set) do
 
for species,_ in pairs(result_set) do
 
table.insert(result, species)
 
table.insert(result, species)
 +
end
 +
table.sort(result)
 +
return textify(frame, result)
 +
end
 +
 +
function p.get_move_type_tags(frame)
 +
local result = {}
 +
local move = get_move(frame.args[1])
 +
for _,tag in ipairs(move.tags) do
 +
if db.types.by_name[tag] and tag ~= move.camouflage then
 +
table.insert(result, db.types.by_name[tag].name)
 +
end
 
end
 
end
 
table.sort(result)
 
table.sort(result)
Line 313: Line 407:
 
function p.get_type(frame)
 
function p.get_type(frame)
 
local t = get_type(frame.args[1])
 
local t = get_type(frame.args[1])
return textify(frame, subscript(t, frame.args, 2))
+
local result = subscript(t, frame.args, 2)
 +
return textify(frame, result)
 
end
 
end
  
Line 325: Line 420:
 
local reaction = atype.inflicts[dtype.name]
 
local reaction = atype.inflicts[dtype.name]
 
return textify(frame, subscript(reaction, frame.args, 3))
 
return textify(frame, subscript(reaction, frame.args, 3))
 +
end
 +
 +
function p.get_status_effect(frame)
 +
local s = get_status_effect(frame.args[1])
 +
local result = subscript(s, frame.args, 2)
 +
return textify(frame, result)
 +
end
 +
 +
function p.get_status_effect_list(frame)
 +
local list = table.values(db.status_effects.by_name)
 +
table.sort(list, cmp_status_effects)
 +
return textify(frame, list)
 +
end
 +
 +
function p.get_link(frame)
 +
local table_name = frame.args[1]
 +
local getter = getters[table_name]
 +
if getter == nil then
 +
return frame:preprocess("[[INVALID]]")
 +
end
 +
local id = frame.args[2]
 +
local object = getter(id)
 +
for k,g in pairs(getters) do
 +
if k ~= table_name then
 +
local unknown = g("Unknown")
 +
local o = g(id)
 +
if o ~= unknown then
 +
return frame:preprocess("[[" .. object.name .. " (" .. table_name ..")|" .. object.name .. "]]")
 +
end
 +
end
 +
end
 +
return frame:preprocess("[[" .. object.name .. "]]")
 +
end
 +
 +
function p.exists(frame)
 +
local getter = getters[frame.args[1]]
 +
local object = getter(frame.args[2])
 +
local unknown = getter("Unknown")
 +
return textify(frame, object ~= unknown)
 
end
 
end
  
 
return p
 
return p

Latest revision as of 16:04, 13 July 2023

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

local p = {}

local db = mw.loadData("Module:Database/data")

-- Private helpers

function table.empty(t)
	for _,_ in pairs(t) do
		return false
	end
	return true
end

function table.clone(t)
	local result = {}
	for k,v in pairs(t) do
		result[k] = v
	end
	return result
end

function table.values(t)
	local result = {}
	for _,v in pairs(t) do
		table.insert(result, v)
	end
	return result
end

function string.split(s, delim)
	local result = {}
	if s == "" then
		return result
	end
	local current = ""
	for i = 1, string.len(s) do
		if string.sub(s, i, i + string.len(delim) - 1) == delim then
			i = i + string.len(delim) - 1
			table.insert(result, current)
			current = ""
		else
			current = current .. string.sub(s, i, i)
		end
	end
	table.insert(result, current)
	return result
end

function species_sort_index(species)
	local i = species.bestiary_index
	if i == nil or i < 0 then
		return 99999
	else
		return i
	end
end

function cmp_i(a, b)
	if type(a) == "string" then
		a = string.lower(a)
	end
	if type(b) == "string" then
		b = string.lower(b)
	end
	return a < b
end

function cmp_species(a, b)
	local ai = species_sort_index(a)
	local bi = species_sort_index(b)
	if ai == bi then
		return string.lower(a.name) < string.lower(b.name)
	end
	return ai < bi
end

function cmp_moves(a, b)
	return string.lower(a.name) < string.lower(b.name)
end

function cmp_status_effects(a, b)
	return string.lower(a.name) < string.lower(b.name)
end

function get_species(id)
	if tonumber(id) ~= nil then
		id = tonumber(id)
	end
	if type(id) == "number" then
		return db.species.by_index[id] or db.species.unknown
	else
		return db.species.by_name[string.lower(id)] or db.species.unknown
	end
end

function describe_evolution_branch(evo)
	if evo.specialization ~= "" then
		return evo.specialization
	elseif evo.required_type_override ~= "" then
		return "[[" .. evo.required_type_override .. "]] Bootleg"
	elseif evo.required_move ~= "" then
		return "[[" ..evo.required_move .. "]] Sticker"
	elseif evo.min_hour == 18 and evo.max_hour == 6 then
		return "Night"
	elseif evo.min_hour == 6 and evo.max_hour == 18 then
		return "Day"
	else
		return "Default"
	end
end

function get_move(id)
	return db.moves.by_name[string.lower(id)] or db.moves.unknown
end

function get_type(id)
	return db.types.by_name[string.lower(id)] or db.types.typeless
end

function get_status_effect(id)
	return db.status_effects.by_name[string.lower(id)] or db.status_effects.unknown
end

local getters = {
	species = get_species,
	move = get_move,
	["type"] = get_type,
	status_effect = get_status_effect
}

function subscript(value, path, path_start)
	while value ~= nil and path[path_start] ~= nil do
		if type(value) ~= "table" then
			return nil
		end
		local key = path[path_start]
		if tonumber(key) ~= nil then
			value = value[tonumber(key)]
		else
			value = value[key]
		end
		path_start = path_start + 1
	end
	return value
end

function flatten_template_args(args)
	for k,v in pairs(args) do
		if type(v) == "table" and v.name then
			args[k] = v.name
		end
		args[k] = tostring(v)
	end
end

function textify(frame, value)
	if frame.args.sorted and type(value) == "table" then
		value = table.values(value)
		table.sort(value, cmp_i)
	elseif frame.args.sort_by and type(value) == "table" then
		value = table.values(value)
		table.sort(value, function(a, b)
			return cmp_i(a[frame.args.sort_by], b[frame.args.sort_by])
		end)
	end
	if frame.args["if_" .. tostring(value)] ~= nil then
		return frame.args["if_" .. tostring(value)]
	elseif type(value) == "number" and frame.args.if_number ~= nil then
		return string.format(frame.args.if_number, value)
	elseif type(value) == "table" and frame.args.if_empty ~= nil and table.empty(value) then
		return frame.args.if_empty
	elseif type(value) == "table" and (frame.args.delim or frame.args["foreach"]) then
		local delim = frame.args.delim or ""
		if delim == "," then delim = delim .. " " end
		local result = ""
		local i = 1
		local iter
		if value[1] then iter = ipairs else iter = pairs end
		for key,elem in iter(value) do
			if i > 1 then
				result = result .. delim
			end
			if frame.args.subscript then
				elem = subscript(elem, string.split(frame.args.subscript, "."), 1)
			end
			if frame.args["foreach"] then
				local args
				if type(elem) == "table" then
					args = table.clone(elem)
				else
					args = { elem, first = i == 1 }
				end
				if frame.args.key_param then
					local s = key
					if frame.args.key_param_format then
						s = string.format(frame.args.key_param_format, s)
					end
					args[frame.args.key_param] = s
				end
				if frame.args.pass_through then
					args[frame.args.pass_through] = frame.args[frame.args.pass_through]
				end
				flatten_template_args(args)
				elem = frame:expandTemplate{title = frame.args["foreach"], args = args}
			else
				elem = textify(frame, elem)
			end
			result = result .. elem
			i = i + 1
		end
		return result
	end
	if type(frame.args.format) == "string" then
		value = string.format(frame.args.format, value)
	end
	if type(value) == "table" and value.name ~= nil then
		value = value.name
	end

	if value == nil or value == false then
		return ""
	elseif type(value) == "string" or type(value) == "number" or type(value) == "boolean" then
		return tostring(value)
	end
	return "&lt;" .. tostring(value) .. "&gt;"
end

-- Public functions for use with #invoke

function p.get_species(frame)
	local species = get_species(frame.args[1])
	return textify(frame, subscript(species, frame.args, 2))
end

function p.get_prev_species(frame)
	local species = get_species(frame.args[1])
	if species.bestiary_index ~= nil then
		species = get_species(species.bestiary_index - 1)
	end
	return textify(frame, subscript(species, frame.args, 2))
end

function p.get_next_species(frame)
	local species = get_species(frame.args[1])
	if species.bestiary_index ~= nil then
		species = get_species(species.bestiary_index + 1)
	end
	return textify(frame, subscript(species, frame.args, 2))
end

function p.species_has_family(frame)
	local species = get_species(frame.args[1])
	local result = not table.empty(species.evolves_from) or not table.empty(species.evolves_to)
	return textify(frame, subscript(result, frame.args, 2))
end

function p.get_species_compatible_moves(frame)
	local result_set = {}
	local species = get_species(frame.args[1])
	local tags = species.moves.tags
	if frame.args.always_compatible then
		tags = species.moves.core_tags
	end
	for _,tag in ipairs(tags) do
		if db.moves.by_tag[tag] then
			for _,move in ipairs(db.moves.by_tag[tag]) do
				result_set[move.name] = move
			end
		end
	end
	local result = table.values(result_set)
	table.sort(result, cmp_moves)
	return textify(frame, result)
end

function p.get_tag_moves(frame)
	local result = {}
	local tag = frame.args[1]
	local for_bootleg = frame.args.for_bootleg
	if db.moves.by_tag[tag] then
		for _,move in ipairs(db.moves.by_tag[tag]) do
			if not for_bootleg or move.camouflage ~= tag then
				table.insert(result, move)
			end
		end
	end
	return textify(frame, result)
end

function p.get_species_move_learn_method(frame)
	local species = get_species(frame.args[1])
	local move = get_move(frame.args[2])
	for _,m in ipairs(species.moves.initial) do
		if move.name == m then
			return textify(frame, "initial")
		end
	end
	for i,m in ipairs(species.moves.upgrades) do
		if move.name == m then
			return textify(frame, i)
		end
	end
	if string.lower(species.elemental_type) ~= move.camouflage then
		local tags = {}
		for _,tag in ipairs(species.moves.tags) do
			tags[tag] = true
		end
		for _,tag in ipairs(move.tags) do
			if tags[tag] then
				return textify(frame, "compatible")
			end
		end
	end
	return textify(frame, "incompatible")
end

function p.get_species_list(frame)
	-- If frame.args includes either 'type' as a key, it returns only the
	-- species with that type. Otherwise it returns all species.
	-- Use this with foreach= and/or delim= in frame.args.
	local result = {}
	if frame.args["type"] then
		local t = get_type(frame.args["type"])
		result = table.values(db.species.by_type[string.lower(t.name)] or {})
		table.sort(result, cmp_species)
	else
		for i = db.species.min_index, db.species.max_index do
			if db.species.by_index[i] then
				table.insert(result, db.species.by_index[i])
			else
				table.insert(result, db.species.unknown)
			end
		end
		for _,s in ipairs(db.species.unnumbered) do
			table.insert(result, s)
		end
	end
	return textify(frame, result)
end

function p.describe_evolution_branch(frame)
	local from_species = get_species(frame.args[1])
	local to_species = get_species(frame.args[2])
	if from_species == db.species.unknown or to_species == db.species.unknown then
		return nil
	end
	for _,evo in ipairs(from_species.evolves_to) do
		if evo.evolved_form == to_species.name then
			return describe_evolution_branch(evo)
		end
	end
	return nil
end

function p.get_move(frame)
	local move = get_move(frame.args[1])
	return textify(frame, subscript(move, frame.args, 2))
end

function p.get_move_compatible_species(frame)
	local result_set = {}
	local move = get_move(frame.args[1])
	for _,tag in ipairs(move.tags) do
		if db.species.by_tag[tag] then
			for _,species in ipairs(db.species.by_tag[tag]) do
				if string.lower(species.elemental_type) ~= move.camouflage then
					result_set[species.name] = true
				end
			end
		end
	end
	local result = {}
	for species,_ in pairs(result_set) do
		table.insert(result, species)
	end
	table.sort(result)
	return textify(frame, result)
end

function p.get_move_type_tags(frame)
	local result = {}
	local move = get_move(frame.args[1])
	for _,tag in ipairs(move.tags) do
		if db.types.by_name[tag] and tag ~= move.camouflage then
			table.insert(result, db.types.by_name[tag].name)
		end
	end
	table.sort(result)
	return textify(frame, result)
end

function p.get_move_list(frame)
	-- If frame.args includes either 'type' as a key, it returns only the
	-- moves with that type. Otherwise it returns all moves.
	-- Use this with foreach= and/or delim= in frame.args.
	local result = {}
	if frame.args["type"] then
		local t = get_type(frame.args["type"])
		result = table.values(db.moves.by_type[string.lower(t.name)] or {})
	else
		result = table.values(db.moves.by_name)
	end
	table.sort(result, cmp_moves)
	return textify(frame, result)
end

function p.get_type(frame)
	local t = get_type(frame.args[1])
	local result = subscript(t, frame.args, 2)
	return textify(frame, result)
end

function p.get_type_list(frame)
	return textify(frame, db.types.ordered)
end

function p.get_reaction(frame)
	local atype = get_type(frame.args[1])
	local dtype = get_type(frame.args[2])
	local reaction = atype.inflicts[dtype.name]
	return textify(frame, subscript(reaction, frame.args, 3))
end

function p.get_status_effect(frame)
	local s = get_status_effect(frame.args[1])
	local result = subscript(s, frame.args, 2)
	return textify(frame, result)
end

function p.get_status_effect_list(frame)
	local list = table.values(db.status_effects.by_name)
	table.sort(list, cmp_status_effects)
	return textify(frame, list)
end

function p.get_link(frame)
	local table_name = frame.args[1]
	local getter = getters[table_name]
	if getter == nil then
		return frame:preprocess("[[INVALID]]")
	end
	local id = frame.args[2]
	local object = getter(id)
	for k,g in pairs(getters) do
		if k ~= table_name then
			local unknown = g("Unknown")
			local o = g(id)
			if o ~= unknown then
				return frame:preprocess("[[" .. object.name .. " (" .. table_name ..")|" .. object.name .. "]]")
			end
		end
	end
	return frame:preprocess("[[" .. object.name .. "]]")
end

function p.exists(frame)
	local getter = getters[frame.args[1]]
	local object = getter(frame.args[2])
	local unknown = getter("Unknown")
	return textify(frame, object ~= unknown)
end

return p