Files
factorio-scenario-ExpCluster/exp_scenario/module/commands/search.lua
2024-11-19 22:36:52 +00:00

182 lines
6.6 KiB
Lua

--[[-- Commands - Inventory Search
Adds commands that will search all players inventories for an item
]]
local ExpUtil = require("modules/exp_util")
local Commands = require("modules/exp_commands")
local format_player_name = Commands.format_player_name_locale
local format_number = require("util").format_number
--- A player who is of a lower role than the executing player
--- @type Commands.InputParser
local function parse_item(input, player)
-- First Case - internal name is given
-- Second Case - rich text is given
local item_name = input:lower():gsub(" ", "-")
local item = prototypes.item[item_name] or prototypes.item[input:match("%[item=([0-9a-z-]+)%]")]
if item then
return Commands.status.success(item)
end
-- No item found, we do not attempt to search all prototypes as this will be expensive
return Commands.status.invalid_input{ "exp-commands_search.invalid-item", item_name }
end
--- @class SearchResult: { player: LuaPlayer, count: number, online_time: number }
--- Search all players for this item
--- @param players LuaPlayer[] Players to search
--- @param item LuaItemPrototype Item to find
--- @return SearchResult[]
local function search_players(players, item)
local found = {} --- @type SearchResult[]
local head = 1
-- Check the item count of all players
for _, player in pairs(players) do
local item_count = player.get_item_count(item.name)
if item_count > 0 then
-- Add the player to the array as they have the item
found[head] = { player = player, count = item_count, online_time = player.online_time }
head = head + 1
end
end
return found
end
--- @alias SortFunction fun(result: SearchResult): number
--- Custom sort function which only retains 5 greatest values
--- @param results SearchResult[] Players to sort
--- @param func SortFunction Function to calculate value, higher better
--- @return SearchResult[] # Top 5 results
local function sort_results(results, func)
local sorted = {}
local values = {}
local threshold = 0
-- Loop over all provided players
for index, result in ipairs(results) do
local value = func(result)
-- Check if the item will make the top 5 elements
if index <= 5 or value > threshold then
local inserted = false
values[result] = value
-- Find where in the top 5 to insert the element
for next_index, next_result in ipairs(sorted) do
if value > values[next_result] then
table.insert(sorted, next_index, result)
inserted = true
break
end
end
-- Update the threshold, clean up the tables, and insert if needed
if sorted[6] then
threshold = values[sorted[5]]
values[sorted[6]] = nil
sorted[6] = nil
elseif not inserted then
-- index <= 5 so insert at the end
sorted[#sorted + 1] = result
threshold = value
end
end
end
return sorted
end
local display_players_time_format = ExpUtil.format_time_factory_locale{ format = "short", hours = true, minutes = true }
--- Display to the player the top players which were found
--- @param results SearchResult[]
--- @param item LuaItemPrototype
--- @return LocalisedString
local function format_response(results, item)
if #results == 0 then
return { "exp-commands_search.no-results", item.name }
end
local response = { "", { "exp-commands_search.title", item.name } } --- @type LocalisedString
for index, data in ipairs(results) do
response[index + 2] = {
"exp-commands_search.result",
index,
format_player_name(data.player),
format_number(data.count, false),
display_players_time_format(data.online_time),
}
end
return response
end
--- Return the the amount of an item a player has divided by their playtime
local function combined_sort(data)
return data.count / data.online_time
end
--- Get a list of players sorted by quantity held and play time
Commands.new("search", { "exp-commands_search.description-search" })
:argument("item", { "exp-commands_search.arg-item" }, parse_item)
:enable_auto_concatenation()
:add_aliases{ "s" }
:register(function(player, item)
--- @cast item LuaItemPrototype
local results = search_players(game.players, item)
local sorted = sort_results(results, combined_sort)
return Commands.status.success(format_response(sorted, item))
end)
--- Get a list of online players sorted by quantity held and play time
Commands.new("search-online", { "exp-commands_search.description-online" })
:argument("item", { "exp-commands_search.arg-item" }, parse_item)
:enable_auto_concatenation()
:add_aliases{ "so" }
:register(function(player, item)
--- @cast item LuaItemPrototype
local results = search_players(game.connected_players, item)
local sorted = sort_results(results, combined_sort)
return Commands.status.success(format_response(sorted, item))
end)
--- Return the amount of an item a player has
--- @type SortFunction
local function sort_by_count(data)
return data.count
end
--- Get a list of players sorted by the quantity of an item in their inventory
Commands.new("search-amount", { "exp-commands_search.description-amount" })
:argument("item", { "exp-commands_search.arg-item" }, parse_item)
:enable_auto_concatenation()
:add_aliases{ "sa" } -- cant use /sc
:register(function(player, item)
--- @cast item LuaItemPrototype
local results = search_players(game.players, item)
local sorted = sort_results(results, sort_by_count)
return Commands.status.success(format_response(sorted, item))
end)
--- Return the index of the player, higher means they joined more recently
local function sort_by_recent(data)
return data.player.index
end
--- Get a list of players who have the given item, sorted by how recently they joined
Commands.new("search-recent", { "exp-commands_search.description-recent" })
:argument("item", { "exp-commands_search.arg-item" }, parse_item)
:enable_auto_concatenation()
:add_aliases{ "sr" } -- cant use /sc
:register(function(player, item)
--- @cast item LuaItemPrototype
local results = search_players(game.players, item)
local sorted = sort_results(results, sort_by_recent)
return Commands.status.success(format_response(sorted, item))
end)