Module:Item util

From Path of Exile 2 Wiki
Revision as of 20:53, 22 January 2022 by Vinifera7 (talk | contribs) (Fixed bug where item query was returning an error when it shouldn't.)
Jump to navigation Jump to search
Module documentation[view] [edit] [history] [purge]


This is a meta module.

This module is meant to be used only by other modules. It should not be invoked in wikitext.

Lua logo

This module depends on the following other modules:

This meta module provides utility functions for modules that deal with items.

Usage

This module should be loaded with require().

-------------------------------------------------------------------------------
-- 
--                              Module:Item util
-- 
-- This meta module contains utility functions for modules that deal with items
-------------------------------------------------------------------------------

local m_util = require('Module:Util')
local m_cargo -- Lazy load require('Module:Cargo')

-- Should we use the sandbox version of our submodules?
local use_sandbox = m_util.misc.maybe_sandbox('Item util')

-- The cfg table contains all localisable strings and configuration, to make it
-- easier to port this module to another wiki.
local cfg = use_sandbox and mw.loadData('Module:Item util/config/sandbox') or mw.loadData('Module:Item util/config')

local i18n = cfg.i18n

-- ----------------------------------------------------------------------------
-- Exported functions
-- ----------------------------------------------------------------------------

local m = {}

function m.format_value(tpl_args, value, options)
    -- value: table
    --  min:
    --  max:
    -- options: table
    --  func: Function to transform the value retrieved from the database
    --  fmt: Format string (or function that returns format string) to use for the value. Default: '%s'
    --  fmt_range: Format string to use for the value range. Default: '(%s-%s)'
    --  color: poe_color code to use for the value range. False for no color. Default: 'mod'
    --  class: Additional css class added to color tag
    --  inline: Format string to use for the output
    --  inline_color: poe_color code to use for the output. False for no color. Default: 'default'
    --  inline_class: Additional css class added to inline color tag
    --  no_color: (Deprecated; use color=false instead)
    --  return_color: (Deprecated; returns both value.out and value without this)
    if options.color ~= false and options.no_color == nil then
        local base = {
            min = value.base_min or value.base,
            max = value.base_max or value.base,
        }
        if options.color then
            value.color = options.color
        elseif value.min ~= base.min or value.max ~= base.max then
            value.color = 'mod'
        else
            value.color = 'value'
        end
    end
    if options.func then
        value.min = options.func(tpl_args, value.min)
        value.max = options.func(tpl_args, value.max)
    end
    options.fmt = options.fmt or '%s'
    if type(options.fmt) == 'function' then -- Function that returns the format string
        options.fmt = options.fmt(tpl_args, value)
    end
    if value.min == value.max then -- Static value
        value.out = string.format(options.fmt, value.min)
    else -- Range value
        options.fmt_range = options.fmt_range or i18n.range
        value.out = string.format(
            string.format(options.fmt_range, options.fmt, options.fmt),
            value.min,
            value.max
        )
    end
    if value.color then
        value.out = m_util.html.poe_color(value.color, value.out, options.class)
    end
    if type(options.inline) == 'function' then
        options.inline = options.inline(tpl_args, value)
    end
    if options.inline and options.inline ~= '' then
        value.out = string.format(options.inline, value.out)
        if options.inline_color ~= false then
            options.inline_color = options.inline_color or 'default'
            value.out = m_util.html.poe_color(options.inline_color, value.out, options.inline_class)
        end
    end
    local return_color = options.return_color and value.color or nil
    if return_color then
        return value.out, return_color
    end
    return value.out, value
end

function m.get_item_namespaces(args)
    -- Returns item namespaces from config as a table or as a comma-separated string
    args.format = args.format or 'table'
    if args.format == 'list' then
        return cfg.item_namespaces_list
    end
    return cfg.item_namespaces
end

function m.query_item(args, qargs)
    qargs = qargs or {}
    m_cargo = m_cargo or require('Module:Cargo')
    if not m_util.table.has_any_key(args, cfg.search_terms) then
        error(i18n.errors.missing_search_term)
    end
    local tables = {'items'}
    local fields = {
        'items._pageName=_pageName',
        'items.drop_enabled=drop_enabled',
        'items.class_id=class_id',
    }
    local query = {
        groupBy = 'items._pageID',
        orderBy = 'items.drop_enabled DESC',
    }
    local search_param
    local results
    if args.metadata_id then
        query.where = string.format(
            'items.metadata_id = "%s"',
            args.metadata_id
        )
        search_param = 'metadata_id'
    elseif args.page then
        -- Join with _pageData in order to check for page redirect
        tables[#tables+1] = '_pageData'
        query.join = 'items._pageName = _pageData._pageNameOrRedirect'
        query.where = string.format(
            '_pageData._pageName = "%s"',
            m_cargo.addslashes(args.page)
        )
        search_param = 'page'
    else
        tables[#tables+1] = 'maps'
        tables[#tables+1] = 'map_series'
        query.join = 'items._pageID = maps._pageID, maps.series = map_series.name'
        query.where = string.format(
            'items._pageNamespace IN (%s)',
            m.get_item_namespaces{format = 'list'}
        )
        query.orderBy = 'map_series.ordinal DESC, ' .. query.orderBy
        if args.item_name_exact then
            query.where = query.where .. string.format(
                ' AND items.name = "%s"',
                m_cargo.addslashes(args.item_name_exact)
            )
            search_param = 'item_name_exact'
        else
            query.where = query.where .. string.format(
                ' AND items.name_list HOLDS "%s"',
                m_cargo.addslashes(args.item_name)
            )
            search_param = 'item_name'
        end
    end
    -- Append additional tables and fields
    if type(qargs.tables) == 'table' and #qargs.tables > 0 then
        tables = m_util.table.merge(tables, qargs.tables)
    end
    if type(qargs.fields) == 'table' and #qargs.fields > 0 then
        fields = m_util.table.merge(fields, qargs.fields)
    end
    -- Append to join, where and orderBy clauses
    if qargs.join then
        query.join = table.concat({query.join, qargs.join}, ', ')
    end
    if qargs.where then
        query.where = table.concat({query.where, qargs.where}, ' AND ')
    end
    if qargs.orderBy then
        query.orderBy = table.concat({qargs.orderBy, query.orderBy}, ', ')
    end
    results = m_cargo.query(tables, fields, query)
    local err
    if #results == 0 then
        -- No results found
        err = m_util.misc.raise_error_or_return{
            raise_required = true,
            args = args,
            msg = string.format(
                i18n.errors.no_results_found,
                search_param,
                args[search_param]
            )
        }
    elseif #results > 1 then
        -- More than one result found
        -- 
        -- If results are all maps, use the one from the most recent map
        -- series, regardless of whether it's drop enabled. Otherwise, 
        -- if only one of the results is drop enabled then use that one.
        local map_count = 0
        local drop_enabled_count = 0
        for i, v in ipairs(results) do
            if v.class_id == 'Map' then
                map_count = map_count + 1
            end
            if m_util.cast.boolean(v.drop_enabled) then
                drop_enabled_count = drop_enabled_count + 1
            end
        end
        if (map_count == 0 or map_count ~= #results) and drop_enabled_count ~= 1 then
            err = m_util.misc.raise_error_or_return{
                raise_required = true,
                args = args,
                msg = string.format(
                    i18n.errors.many_results_found,
                    search_param,
                    args[search_param]
                )
            }
        end
    end
    if args.debug then
        mw.logObject(results)
    end
    if err ~= nil then
        if not m_util.cast.boolean(args.nocat) then
            err = err .. m_util.misc.add_category({args.error_category or i18n.categories.failed_query})
        end
        return {error = err}
    end
    return results[1] -- orderBy ensures that the first result is the one we want
end

return m