Add Clusterio Plugins

This commit is contained in:
Cooldude2606
2024-10-20 17:11:02 +01:00
parent 16813dca40
commit 446e87b610
53 changed files with 4938 additions and 823 deletions

View File

@@ -0,0 +1,179 @@
--[[-- Command Module - Default data types
The default data types that are available to all commands
@usage Adds parsers for:
boolean
string-options - options: array of strings
string-key - map: table of string keys and any values
string-max-length - maximum: number
number
integer
number-range - minimum: number, maximum: number
integer-range - minimum: number, maximum: number
player
player-online
player-alive
force
surface
color
]]
local ExpUtil = require("modules/exp_util")
local Commands = require("modules/exp_commands")
local add, parse = Commands.add_data_type, Commands.parse_data_type
local valid, invalid = Commands.status.success, Commands.status.invalid_input
--- A boolean value where true is one of: yes, y, true, 1
add("boolean", function(input)
input = input:lower()
if input == "yes"
or input == "y"
or input == "true"
or input == "1" then
return valid(true)
else
return valid(false)
end
end)
--- A string, validation does nothing but it is a requirement
add("string", function(input)
return valid(input)
end)
--- A string from a set of options, takes one argument which is an array of options
add("string-options", function(input, _, options)
local option = ExpUtil.auto_complete(options, input)
if option == nil then
return invalid{"exp-commands-parse.string-options", table.concat(options, ", ")}
else
return valid(option)
end
end)
--- A string which is the key of a table, takes one argument which is an map of string keys to values
add("string-key", function(input, _, map)
local option = ExpUtil.auto_complete(map, input, true)
if option == nil then
return invalid{"exp-commands-parse.string-options", table.concat(table.get_keys(map), ", ")}
else
return valid(option)
end
end)
--- A string with a maximum length, takes one argument which is the maximum length of a string
add("string-max-length", function(input, _, maximum)
if input:len() > maximum then
return invalid{"exp-commands-parse.string-max-length", maximum}
else
return valid(input)
end
end)
--- A number
add("number", function(input)
local number = tonumber(input)
if number == nil then
return invalid{"exp-commands-parse.number"}
else
return valid(number)
end
end)
--- An integer, number which has been floored
add("integer", function(input)
local number = tonumber(input)
if number == nil then
return invalid{"exp-commands-parse.number"}
else
return valid(math.floor(number))
end
end)
--- A number in a given inclusive range
add("number-range", function(input, _, minimum, maximum)
local success, status, number = parse("number", input)
if not success then
return status, number
elseif number < minimum or number > maximum then
return invalid{"exp-commands-parse.number-range", minimum, maximum}
else
return valid(number)
end
end)
--- An integer in a given inclusive range
add("integer-range", function(input, _, minimum, maximum)
local success, status, number = parse("integer", input)
if not success then
return status, number
elseif number < minimum or number > maximum then
return invalid{"exp-commands-parse.number-range", minimum, maximum}
else
return valid(number)
end
end)
--- A player who has joined the game at least once
add("player", function(input)
local player = game.get_player(input)
if player == nil then
return invalid{"exp-commands-parse.player", input}
else
return valid(player)
end
end)
--- A player who is online
add("player-online", function(input)
local success, status, player = parse("player", input)
if not success then
return status, player
elseif player.connected == false then
return invalid{"exp-commands-parse.player-online"}
else
return valid(player)
end
end)
--- A player who is online and alive
add("player-alive", function(input)
local success, status, player = parse("player-online", input)
if not success then
return status, player
elseif player.character == nil or player.character.health <= 0 then
return invalid{"exp-commands-parse.player-alive"}
else
return valid(player)
end
end)
--- A force within the game
add("force", function(input)
local force = game.forces[input]
if force == nil then
return invalid{"exp-commands-parse.force"}
else
return valid(force)
end
end)
--- A surface within the game
add("surface", function(input)
local surface = game.surfaces[input]
if surface == nil then
return invalid{"exp-commands-parse.surface"}
else
return valid(surface)
end
end)
--- A name of a color from the predefined list, too many colours to use string-key
add("color", function(input)
local color = ExpUtil.auto_complete(Commands.color, input, true)
if color == nil then
return invalid{"exp-commands-parse.color"}
else
return valid(color)
end
end)

View File

@@ -0,0 +1,81 @@
--[[-- Command Module - Help
Game command to list and search all registered commands in a nice format
@commands _system-ipc
@usage-- Get all messages related to banning a player
/commands ban
-- Get the second page of results
/commands ban 2
]]
local Global = require("modules/exp_util/global")
local Commands = require("modules/exp_commands")
local PAGE_SIZE = 5
local search_cache = {}
Global.register(search_cache, function(tbl)
search_cache = tbl
end)
--- Format commands into a strings across multiple pages
local function format_as_pages(commands, page_size)
local pages = { {} }
local page_length = 0
local current_page = 1
local total = 0
for _, command in pairs(commands) do
total = total + 1
page_length = page_length + 1
if page_length > page_size then
current_page = current_page + 1
pages[current_page] = {}
page_length = 1
end
local aliases = #command.aliases > 0 and {"exp-commands-help.aliases", table.concat(command.aliases, ", ")} or ""
pages[current_page][page_length] = { "exp-commands-help.format", command.name, command.description, command.help, aliases }
end
return pages, total
end
Commands.new("commands", "List and search all commands for a keyword")
:add_aliases{ "chelp", "helpp" }
:argument("keyword", "string")
:optional("page", "integer")
:defaults{ page = 1 }
:register(function(player, keyword, page)
keyword = keyword:lower()
local pages, found
local cache = search_cache[player.index]
if cache and cache.keyword == keyword then
-- Cached value found, no search is needed
pages = cache.pages
found = cache.found
else
-- No cached value, so a search needs to be done
local commands = Commands.search_for_player(keyword, player)
pages, found = format_as_pages(commands, PAGE_SIZE)
search_cache[player.index] = { keyword = keyword, pages = pages, found = found }
end
-- Error if no pages found
if found == 0 then
return Commands.status.success{ "exp-commands-help.no-results" }
end
local page_data = pages[page]
if page_data == nil then
-- Page number was out of range for this search
return Commands.status.invalid_input{"exp-commands-help.out-of-range", page, #pages }
end
-- Print selected page to the player
Commands.print{ "exp-commands-help.header", keyword == '' and '<all>' or keyword }
for _, command in pairs(page_data) do
Commands.print(command)
end
return Commands.status.success{ "exp-commands-help.footer", found, page, #pages }
end)

View File

@@ -0,0 +1,25 @@
--[[-- Command Module - IPC
System command which sends an object to the clustorio api, should be used for debugging / echo commands
@commands _system-ipc
@usage-- Send a message on your custom channel, message is a json string
/_ipc myChannel { "myProperty": "foo", "playerName": "Cooldude2606" }
]]
local Commands = require("modules/exp_commands")
local Clustorio = require("modules/clusterio/api")
Commands.new("_ipc", "Send an IPC message on the selected channel")
:add_flags{ "system_only" }
:enable_auto_concatenation()
:argument("channel", "string")
:argument("message", "string")
:register(function(_, channel, message)
local tbl = game.json_to_table(message)
if tbl == nil then
return Commands.status.invalid_input("Invalid json string")
else
Clustorio.send_json(channel, tbl)
return Commands.status.success()
end
end)

View File

@@ -0,0 +1,91 @@
--[[-- Command Module - Default permission authorities
The default permission authorities controlled by the flags: admin_only, system_only, no_rcon, disabled
@usage-- Unlock system commands for debugging purposes
/c require("modules/exp-commands").unlock_system_commands(game.player)
@usage-- Disable a command for all players because it is not functioning correctly
/c require("modules/exp-commands").disable("my-command")
]]
local Global = require("modules/exp_util/global")
local Commands = require("modules/exp_commands")
local add, allow, deny = Commands.add_permission_authority, Commands.status.success, Commands.status.unauthorised
local permission_authorities = {}
local system_players = {}
local disabled_commands = {}
Global.register({
system_players,
disabled_commands,
}, function(tbl)
system_players = tbl[1]
disabled_commands = tbl[2]
end)
--- Allow a player access to system commands, use for debug purposes only
-- @tparam[opt] string player_name The name of the player to give access to, default is the current player
function Commands.unlock_system_commands(player_name)
system_players[player_name or game.player.name] = true
end
--- Remove access from system commands for a player, use for debug purposes only
-- @tparam[opt] string player_name The name of the player to give access to, default is the current player
function Commands.lock_system_commands(player_name)
system_players[player_name or game.player.name] = nil
end
--- Get a list of all players who have system commands unlocked
function Commands.get_system_command_players()
return table.get_keys(system_players)
end
--- Stops a command from be used by any one
-- @tparam string command_name The name of the command to disable
function Commands.disable(command_name)
disabled_commands[command_name] = true
end
--- Allows a command to be used again after disable was used
-- @tparam string command_name The name of the command to enable
function Commands.enable(command_name)
disabled_commands[command_name] = nil
end
--- Get a list of all players who have system commands unlocked
function Commands.get_disabled_commands()
return table.get_keys(disabled_commands)
end
--- If a command has the flag "admin_only" then only admins can use the command#
permission_authorities.admin_only =
add(function(player, command)
if command.flags.admin_only and not player.admin then
return deny{"exp-commands-permissions.admin-only"}
else
return allow()
end
end)
--- If a command has the flag "system_only" then only rcon connections can use the command
permission_authorities.system_only =
add(function(player, command)
if command.flags.system_only and not system_players[player.name] then
return deny{"exp-commands-permissions.system-only"}
else
return allow()
end
end)
--- If Commands.disable was called then no one can use the command
permission_authorities.disabled =
add(function(_, command)
if disabled_commands[command.name] then
return deny{"exp-commands-permissions.disabled"}
else
return allow()
end
end)
return permission_authorities

View File

@@ -0,0 +1,82 @@
--[[-- Command Module - Rcon
System command which runs arbitrary code within a custom (not sandboxed) environment
@commands _system-rcon
@usage-- Get the names of all online players, using rcon
/_system-rcon local names = {}; for index, player in pairs(game.connected_player) do names[index] = player.name end; return names;
@usage-- Get the names of all online players, using clustorio ipcs
/_system-rcon local names = {}; for index, player in pairs(game.connected_player) do names[index] = player.name end; ipc("online-players", names);
]]
local ExpUtil = require("modules/exp_util")
local Async = require("modules/exp_util/async")
local Global = require("modules/exp_util/global")
local Commands = require("modules/exp_commands")
local Clustorio = require("modules/clusterio/api")
local rcon_env = {}
local rcon_statics = {}
local rcon_callbacks = {}
setmetatable(rcon_statics, { __index = _G })
setmetatable(rcon_env, { __index = rcon_statics })
--- Some common static values which can be added now
rcon_statics.Async = Async
rcon_statics.ExpUtil = ExpUtil
rcon_statics.Commands = Commands
rcon_statics.Clustorio = Clustorio
rcon_statics.output = Commands.print
rcon_statics.ipc = Clustorio.send_json
--- Some common callback values which are useful when a player uses the command
function rcon_callbacks.player(player) return player end
function rcon_callbacks.surface(player) return player and player.surface end
function rcon_callbacks.force(player) return player and player.force end
function rcon_callbacks.position(player) return player and player.position end
function rcon_callbacks.entity(player) return player and player.selected end
function rcon_callbacks.tile(player) return player and player.surface.get_tile(player.position) end
--- The rcon env is saved between command runs to prevent desyncs
Global.register(rcon_env, function(tbl)
rcon_env = setmetatable(tbl, { __index = rcon_statics })
end)
--- Static values can be added to the rcon env which are not stored in global such as modules
function Commands.add_rcon_static(name, value)
ExpUtil.assert_not_runtime()
rcon_statics[name] = value
end
--- Callback values can be added to the rcon env, these are called on each invocation and should return one value
function Commands.add_rcon_callback(name, callback)
ExpUtil.assert_not_runtime()
rcon_callbacks[name] = callback
end
Commands.new("_rcon", "Execute arbitrary code within a custom environment")
:add_flags{ "system_only" }
:enable_auto_concatenation()
:argument("invocation", "string")
:register(function(player, invocation_string)
-- Construct the environment the command will run within
local env = setmetatable({}, { __index = rcon_env, __newindex = rcon_env })
for name, callback in pairs(rcon_callbacks) do
local _, rtn = pcall(callback, player.index > 0 and player or nil)
rawset(env, name, rtn)
end
-- Compile and run the invocation string
local invocation, compile_error = load(invocation_string, "rcon-invocation", "t", env)
if compile_error then
return Commands.status.invalid_input(compile_error)
else
local success, rtn = xpcall(invocation, debug.traceback)
if success == false then
local err = rtn:gsub('%.%.%..-/temp/currently%-playing/', '')
return Commands.status.error(err)
else
return Commands.status.success(rtn)
end
end
end)

View File

@@ -0,0 +1,25 @@
--[[-- Command Module - Sudo
System command to execute a command as another player using their permissions (except for permissions group actions)
@commands _system-sudo
@usage-- Run the example command as another player
-- As Cooldude2606: /repeat 5
/_system-sudo Cooldude2606 repeat 5
]]
local Commands = require("modules/exp_commands")
Commands.new("_sudo", "Run a command as another player")
:add_flags{ "system_only" }
:enable_auto_concatenation()
:argument("player", "player")
:argument("command", "string-key", Commands.registered_commands)
:argument("arguments", "string")
:register(function(_, player, command, parameter)
return Commands._event_handler{
name = command.name,
tick = game.tick,
player_index = player.index,
parameter = parameter
}
end)