Module:Item: Difference between revisions
(adjusted the code for removing duplicate IDs from the "names" JSON) |
(renamed function for generating all items, removed markers for resolving name conflicts, instead replacing it with a conflict resolver based on a config, replaced nil checks with nil assertion function) |
||
Line 3: | Line 3: | ||
local p = {} --p stands for package | local p = {} --p stands for package | ||
local getArgs = require('Module:Arguments').getArgs | local getArgs = require('Module:Arguments').getArgs | ||
-- A table of items IDs mapped to their names. | -- A table of items IDs mapped to their names. | ||
Line 16: | Line 9: | ||
-- A table of items IDs mapped to their image file names. | -- A table of items IDs mapped to their image file names. | ||
local item_image_by_item_id = mw.loadJsonData("Module:Item/item image files by item id.json") | local item_image_by_item_id = mw.loadJsonData("Module:Item/item image files by item id.json") | ||
-- A config for resolving which item is returned from a name lookup when the query matches multiplte items. | |||
local item_name_lookup_conflicts_resolvers = mw.loadJsonData("Module:Item/item lookup conflicts resolvers.json") | |||
function numeric_table_length(t) | function numeric_table_length(t) | ||
Line 29: | Line 25: | ||
end | end | ||
-- Lookups the item's ID by its name or an alias. | function table_has_value (tab, val) | ||
-- Any casing is allowed. | for index, value in ipairs(tab) do | ||
function p. | if value == val then | ||
return true | |||
end | |||
end | |||
return false | |||
end | |||
-- Searches for a conflict resolver by item ID. | |||
-- If one is defined, returns it. Otherwise returns nil. | |||
function find_item_name_lookup_conflict_resolver_by_item_id(item_id) | |||
for _, resolver_config in ipairs(item_name_lookup_conflicts_resolvers) do | |||
if table_has_value(resolver_config.match, item_id) then | |||
return resolver_config | |||
end | |||
end | |||
end | |||
function assert_value_not_nil(value, error_message) | |||
if value == nil then | |||
if error_message == nil then | |||
error("value is nil") | |||
else | |||
error(error_message) | |||
end | |||
end | |||
end | |||
-- Resolved an item name conflict based on resolver provided. | |||
-- Takes in the item ID and amount. Amount can be nil. | |||
function resolve_item_name_conflict(item_id, items_amount, resolver_config) | |||
for _, resolver in ipairs(resolver_config.resolvers) do | |||
if table_has_value(resolver.match, item_id) then | |||
-- check if amount is set. if it's missing, then there's no need to resolve anything further | |||
-- since the only condition currnetly defined uses the amount to resolve. | |||
-- so, we just use the fallback item ID. | |||
if items_amount == nil then | |||
return resolver_config.fallbackItemId | |||
end | |||
-- get the single condition property currently used | |||
local condition_min_amount = resolver.conditions.min | |||
-- if it's not defined, then no conditions are defined for that resolver, | |||
-- so we can use it no problem | |||
if condition_min_amount == nil then | |||
return resolver.itemId | |||
end | |||
-- otherwise, check for amount | |||
if items_amount >= condition_min_amount then | |||
return resolver.itemId | |||
end | |||
end | |||
-- on all conditions exhausted, use the fallback item ID. | |||
return resolver_config.fallbackItemId | |||
end | |||
-- if no resolvers were found, return nil | |||
return nil | |||
end | |||
-- Lookups the item's ID by its name (main name, ID or an alias) and an optional amount. | |||
-- Any casing for the name is allowed, except when using an ID. | |||
function p.lookup_item_id_by_name_and_amount(frame) | |||
local args = getArgs(frame) | local args = getArgs(frame) | ||
local name_query = args[1] | local name_query = args[1] | ||
if | assert_value_not_nil(name_query, "item name was not provided") | ||
error(" | |||
-- optional items amount | |||
local items_amount = args[2] | |||
if type(items_amount) ~= "number" and type(items_amount) ~= "nil" then | |||
error("expected items amount to be number, got " .. type(items_amount) .. " for value: " .. items_amount) | |||
end | end | ||
Line 50: | Line 116: | ||
for _, name in ipairs(names) do | for _, name in ipairs(names) do | ||
if string.lower(name) == name_query_lower then | if string.lower(name) == name_query_lower then | ||
return item_id | -- on a match, check if there are conflict resolvers for this item ID | ||
local resolver_config = find_item_name_lookup_conflict_resolver_by_item_id(item_id) | |||
-- if no conflict resolver is defined, then return the current item ID | |||
if resolver_config == nil then | |||
return item_id | |||
end | |||
-- if resolver is defined - resolve. | |||
local resolved_item_id = resolve_item_name_conflict(item_id, items_amount, resolver_config) | |||
-- is resolved to an item ID, return it | |||
if resolved_item_id ~= nil then | |||
return resolved_item_id | |||
else | |||
-- otherwise return the item ID found initially | |||
return item_id | |||
end | |||
end | end | ||
end | end | ||
Line 63: | Line 146: | ||
local args = getArgs(frame) | local args = getArgs(frame) | ||
local id_query = args[1] | local id_query = args[1] | ||
assert_value_not_nil(id_query, "item ID was not provided") | |||
local match = item_image_by_item_id[id_query] | local match = item_image_by_item_id[id_query] | ||
assert_value_not_nil(match, "No item image found by ID: " .. id_query .. ". Note that new images must be added manually to the module") | |||
return match | return match | ||
Line 79: | Line 158: | ||
-- A shorthand method. | -- A shorthand method. | ||
function p.lookup_item_image_by_name(frame) | function p.lookup_item_image_by_name(frame) | ||
return p.lookup_item_image_by_id(p. | return p.lookup_item_image_by_id(p.lookup_item_id_by_name_and_amount(frame)) | ||
end | end | ||
Line 87: | Line 166: | ||
local args = getArgs(frame) | local args = getArgs(frame) | ||
local id_query = args[1] | local id_query = args[1] | ||
assert_value_not_nil(id_query, "item ID was not provided") | |||
local names = item_names_by_item_id[id_query] | local names = item_names_by_item_id[id_query] | ||
assert_value_not_nil(id_query, "Item with ID " .. id_query .. "doesn't exist") | |||
local names_length = numeric_table_length(names) | local names_length = numeric_table_length(names) | ||
Line 101: | Line 176: | ||
end | end | ||
return names[1] | |||
end | end | ||
function p. | |||
function p.generate_list_of_all_items_with_icons(frame) | |||
local args = getArgs(frame) | local args = getArgs(frame) | ||
local columns_count = args[1] | local columns_count = args[1] | ||
assert_value_not_nil(columns_count, "columns count was not provided") | |||
local container = mw.html.create("div") | local container = mw.html.create("div") |
Revision as of 21:14, 17 August 2024
Module documentation
|
---|
View or edit this documentation • (about module documentation) |
Implements {{Item}}.
Known items are synced regularly from the upstream, but things like icons and links must be defined manually. See #JSON files to see what data files there are, and see #FAQ on specific instructions.
JSON files
JSON files that are updated automatically, syncing with the upstream:
- Module:Item/item names by item ids.json - contains 1 to 1 mapping of item IDs to their names.
- Module:Item/item ids by item lowercase names.json - contains 1 to 1 mapping of item names (lowercase) to their IDs. If a name repeats for multiple items, only a single item ID will be defined here.
JSON files that are filled manually:
- Module:Item/item ids by item lowercase names overrides.json - a 1 to 1 mapping of item names (lowercase) to their IDs. This one has a higher priority than the other one, so it can be used to override existing name → ID mappings or create new ones.
- Module:Item/item image files by item id.json - a mapping for item IDs to image files for these items. Can be a simple 1 to 1 mapping of item name to file name, or can have a whole config that allows to define multiple images per single ID (see sections below for more info). Used to add icons to items.
- Module:Item/item page links by item ids.json - a 1 to 1 mapping of item IDs to page names. Used to turn items into links to their pages (or any other page on the wiki).
FAQ
How to add new item?
New items are added automatically. This doesn't include icons - for that, see #How to add icon to item?.
Where to get item ID?
From Module:Item/item_names_by_item_ids.json.
How to add icon to item?
If you want to add multiple textures per single item, see #Adding multiple icons to item
1. Upload new icon to the wiki.
2. Go to Module:Item/item_image_files_by_item_id.json.
3. Add a new line. Follow the format: "<item ID>": "<file name>"
"WeaponLaserCarbine": "laser rifle-East-35325.png"
4. Save the file. The icon should now appear when using {{item}}.
Adding multiple icons to item
Currently, the only supported use case if for items that have a different icon based on the amount of item.
1. Upload new icons to the wiki.
2. Go to Module:Item/item_image_files_by_item_id.json.
3. Add a new line. Follow the format:
"<item ID>": {
"default": "<default file name>",
"byCondition": [
{
"type": "amount",
"conditions": [
{
"file": "<file name 1>",
"min": <minimum amount 1>
},
{
"file": "<file name 2>",
"min": <minimum amount 2>
},
{
"file": "<file name 3>",
}
]
}
]
}
item ID
- item ID to add icons for.default file name
- icon to use when amount is not specified.file name 1/2/N
- icons to use with specified amounts."min": <amount 1/2/N>
- icon to use when there's at least this much of item.
Last condition entry (objects that have "file"
and "min"
fields) shouldn't have any condition in it (i.e. no "min"
specified), because it will be used in cases where other conditions do not satisfy.
Conditions are evaluated top to bottom, meaning the file from the first one that satisfies will be used.
4. Save the file. The icons should now appear when using {{item}} and differ based on the amount.
How to add custom names to item?
When using {{item}}, you probably don't want to use item IDs because that's internal game info which is a pain in the ass to write. Gladly, there's an existing set of item names defined in Module:Item/item_ids_by_item_lowercase_names.json, which are human-readable. But not all existing items will have their names in there, because some names do repeat (for instance, various bottles named bottle).
To add new cool names and have them not be erased on new update (which happens to the JSON file linked in previous paragraph), add them to Module:Item/item_ids_by_item_lowercase_names_overrides.json. These will have higher priority and will be used instead. You can define as much "aliases" for an item ID as you wish.
Step-by-step: 1. Go to Module:Item/item_ids_by_item_lowercase_names_overrides.json.
2. Add a new line. Follow format: "<item lowercase name>": "<item ID>"
. Please note, that all items names defined here must be lowercase.
"emag": "EmagUnlimited"
3. Save the file.
How to add a link to item?
To make {{item}} behave like a link all the time, a page link needs to be established in Module:Item/item page links by item ids.json. Please note, if you need a one-time link, use the link parameter in the {{item}} template.
1. Go to Module:Item/item_ids_by_item_lowercase_names_overrides.json.
2. Add a new line. Follow format: "<item ID>": "<page name>"
.
"Protolathe": "Research_and_Development#Protolathe"
3. Save the file.
TODO
- Ores are currently hardcoded into names overrides. Figure out a way to pull them from game resources. This is for Module:Item recipe.
-- Contains utilities for working with in-game items.
local p = {} --p stands for package
local getArgs = require('Module:Arguments').getArgs
-- A table of items IDs mapped to their names.
local item_names_by_item_id = mw.loadJsonData("Module:Item/item names by item id.json")
-- A table of items IDs mapped to their image file names.
local item_image_by_item_id = mw.loadJsonData("Module:Item/item image files by item id.json")
-- A config for resolving which item is returned from a name lookup when the query matches multiplte items.
local item_name_lookup_conflicts_resolvers = mw.loadJsonData("Module:Item/item lookup conflicts resolvers.json")
function numeric_table_length(t)
local count = 0
for _ in ipairs(t) do count = count + 1 end
return count
end
function table_length(t)
local count = 0
for _ in pairs(t) do count = count + 1 end
return count
end
function table_has_value (tab, val)
for index, value in ipairs(tab) do
if value == val then
return true
end
end
return false
end
-- Searches for a conflict resolver by item ID.
-- If one is defined, returns it. Otherwise returns nil.
function find_item_name_lookup_conflict_resolver_by_item_id(item_id)
for _, resolver_config in ipairs(item_name_lookup_conflicts_resolvers) do
if table_has_value(resolver_config.match, item_id) then
return resolver_config
end
end
end
function assert_value_not_nil(value, error_message)
if value == nil then
if error_message == nil then
error("value is nil")
else
error(error_message)
end
end
end
-- Resolved an item name conflict based on resolver provided.
-- Takes in the item ID and amount. Amount can be nil.
function resolve_item_name_conflict(item_id, items_amount, resolver_config)
for _, resolver in ipairs(resolver_config.resolvers) do
if table_has_value(resolver.match, item_id) then
-- check if amount is set. if it's missing, then there's no need to resolve anything further
-- since the only condition currnetly defined uses the amount to resolve.
-- so, we just use the fallback item ID.
if items_amount == nil then
return resolver_config.fallbackItemId
end
-- get the single condition property currently used
local condition_min_amount = resolver.conditions.min
-- if it's not defined, then no conditions are defined for that resolver,
-- so we can use it no problem
if condition_min_amount == nil then
return resolver.itemId
end
-- otherwise, check for amount
if items_amount >= condition_min_amount then
return resolver.itemId
end
end
-- on all conditions exhausted, use the fallback item ID.
return resolver_config.fallbackItemId
end
-- if no resolvers were found, return nil
return nil
end
-- Lookups the item's ID by its name (main name, ID or an alias) and an optional amount.
-- Any casing for the name is allowed, except when using an ID.
function p.lookup_item_id_by_name_and_amount(frame)
local args = getArgs(frame)
local name_query = args[1]
assert_value_not_nil(name_query, "item name was not provided")
-- optional items amount
local items_amount = args[2]
if type(items_amount) ~= "number" and type(items_amount) ~= "nil" then
error("expected items amount to be number, got " .. type(items_amount) .. " for value: " .. items_amount)
end
-- check if name is an item ID in disguise
local match_by_id = item_names_by_item_id[name_query]
if match_by_id ~= nil then
-- if so - return the name = item ID
return name_query
end
-- otherwise look through the names for each item there is
local name_query_lower = string.lower(name_query)
for item_id, names in pairs(item_names_by_item_id) do
for _, name in ipairs(names) do
if string.lower(name) == name_query_lower then
-- on a match, check if there are conflict resolvers for this item ID
local resolver_config = find_item_name_lookup_conflict_resolver_by_item_id(item_id)
-- if no conflict resolver is defined, then return the current item ID
if resolver_config == nil then
return item_id
end
-- if resolver is defined - resolve.
local resolved_item_id = resolve_item_name_conflict(item_id, items_amount, resolver_config)
-- is resolved to an item ID, return it
if resolved_item_id ~= nil then
return resolved_item_id
else
-- otherwise return the item ID found initially
return item_id
end
end
end
end
error("No item ID found for item with name: " .. name_query)
end
-- Lookups the item's image by its ID.
-- The case must match.
function p.lookup_item_image_by_id(frame)
local args = getArgs(frame)
local id_query = args[1]
assert_value_not_nil(id_query, "item ID was not provided")
local match = item_image_by_item_id[id_query]
assert_value_not_nil(match, "No item image found by ID: " .. id_query .. ". Note that new images must be added manually to the module")
return match
end
-- Lookups the item's image by its name or an alias.
-- Any casing is allowed.
-- A shorthand method.
function p.lookup_item_image_by_name(frame)
return p.lookup_item_image_by_id(p.lookup_item_id_by_name_and_amount(frame))
end
-- Lookups the item's name by its ID.
-- The case must match.
function p.lookup_item_name_by_id(frame)
local args = getArgs(frame)
local id_query = args[1]
assert_value_not_nil(id_query, "item ID was not provided")
local names = item_names_by_item_id[id_query]
assert_value_not_nil(id_query, "Item with ID " .. id_query .. "doesn't exist")
local names_length = numeric_table_length(names)
if names_length == 0 then
error("Expected item with ID'" .. id_query .. "' to have atleast one name")
end
return names[1]
end
function p.generate_list_of_all_items_with_icons(frame)
local args = getArgs(frame)
local columns_count = args[1]
assert_value_not_nil(columns_count, "columns count was not provided")
local container = mw.html.create("div")
:css("column-count", columns_count)
-- an array of item ids that have images
local item_ids_with_images = {}
for item_id, _ in pairs(item_image_by_item_id) do
table.insert(item_ids_with_images, item_id)
end
-- sort alphabetically
table.sort(item_ids_with_images, function (first, second)
return p.lookup_item_name_by_id({ [1] = first }) < p.lookup_item_name_by_id({ [1] = second })
end)
-- generate child elements from the template
for _, item_id in ipairs(item_ids_with_images) do
container:node(frame:preprocess("<div>{{item|" .. item_id .. "}}</div>"))
end
return container
:allDone()
end
return p