You've already forked ComfyFactorio
mirror of
https://github.com/ComfyFactory/ComfyFactorio.git
synced 2025-11-25 22:32:18 +02:00
Some techs are now locked behind islands. Text is printed out to chat when a new tech is unlocked. Basic loot is added to the ammo chest whenever the player progresses manually towards the next island. Fixed an issue with biters attacking an island too soon. Fixed an issue with terrain generation.
662 lines
21 KiB
Lua
662 lines
21 KiB
Lua
---@diagnostic disable: deprecated
|
|
--luacheck: ignore 561
|
|
local Global = require 'utils.global'
|
|
local Core = require 'utils.core'
|
|
local Session = require 'utils.datastore.session_data'
|
|
local Supporters = require 'utils.datastore.supporters'
|
|
local Task = require 'utils.task_token'
|
|
local Server = require 'utils.server'
|
|
|
|
---@class CommandData
|
|
---@field name string
|
|
---@field help string
|
|
---@field aliases table
|
|
---@field parameters table
|
|
---@field parameters_count number
|
|
---@field parameters_required number
|
|
---@field check_server boolean
|
|
---@field check_backend boolean
|
|
---@field check_admin boolean
|
|
---@field check_supporter boolean
|
|
---@field check_trusted boolean
|
|
---@field check_playtime number
|
|
---@field callback function
|
|
---@field validate_self boolean
|
|
---@field validated_command boolean
|
|
---@field validate_activated boolean
|
|
---@field command_activated boolean
|
|
|
|
local this =
|
|
{
|
|
commands = {}
|
|
}
|
|
local trace = debug.traceback
|
|
|
|
local output =
|
|
{
|
|
backend_is_required = 'No backend is currently available. Please try again later.',
|
|
server_is_required = 'This command requires to be run from the server.',
|
|
admin_is_required = 'This command requires admin permissions to run.',
|
|
supporter_is_required = 'This command requires supporter permissions to run.',
|
|
trusted_is_required = 'This command requires trusted permissions to run.',
|
|
playtime_is_required = 'This command requires a minimum playtime of %s to run.',
|
|
param_is_required = 'This command requires a parameter to run.',
|
|
command_failed = 'Command failed to run.',
|
|
command_success = 'Command ran successfully.',
|
|
command_needs_validation =
|
|
'This command requires validation to run. Please re-run the command if you wish to proceed.',
|
|
command_needs_custom_validation =
|
|
'This command requires validation to run. %s - please re-run the command if you wish to proceed.',
|
|
command_is_active = 'This command is already active.',
|
|
command_is_inactive = 'This command is already inactive.'
|
|
}
|
|
|
|
local validate_types =
|
|
{
|
|
['string'] = true,
|
|
['number'] = true,
|
|
['integer'] = true,
|
|
['boolean'] = true,
|
|
['player'] = true,
|
|
['player-online'] = true,
|
|
['player-admin'] = true,
|
|
['server'] = true,
|
|
['surface'] = true
|
|
}
|
|
|
|
local check_boolean =
|
|
{
|
|
['true'] = true,
|
|
['false'] = true
|
|
}
|
|
|
|
---@class MetaCommand
|
|
local Public = {}
|
|
|
|
Public.metatable = { __index = Public }
|
|
|
|
Global.register(
|
|
this,
|
|
function (tbl)
|
|
this = tbl
|
|
end
|
|
)
|
|
|
|
script.register_metatable('CommandData', Public.metatable)
|
|
|
|
local function conv(v)
|
|
if tonumber(v) then
|
|
return tonumber(v)
|
|
end
|
|
|
|
return v
|
|
end
|
|
|
|
--- Handles errors.
|
|
---@param message string
|
|
---@param notify_sound string
|
|
local function handle_error(message, notify_sound)
|
|
message = message or ''
|
|
Core.output_message('Command failed: ' .. message, 'warning')
|
|
if notify_sound then
|
|
notify_sound = notify_sound or 'utility/wire_pickup'
|
|
if game.player then
|
|
game.player.play_sound { path = notify_sound }
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Handles internal errors.
|
|
---@param has_run boolean
|
|
---@param name string
|
|
---@param message string
|
|
---@return boolean
|
|
local function internal_error(has_run, name, message)
|
|
if not has_run then
|
|
handle_error('Action has been logged!', 'utility/cannot_build')
|
|
if type(message) == 'string' then
|
|
Server.output_script_data('[ERROR] Command failed to run: ' .. name .. ' - ' .. message)
|
|
else
|
|
Server.output_script_data('[ERROR] Command failed to run: ' .. name)
|
|
end
|
|
end
|
|
return not has_run
|
|
end
|
|
|
|
---@param event EventData.on_console_command
|
|
local function execute(event)
|
|
local command_data = this.commands[event.name] --[[@as CommandData]]
|
|
|
|
local player
|
|
if event.player_index and event.player_index > 0 then
|
|
player = game.get_player(event.player_index)
|
|
else
|
|
player =
|
|
{
|
|
name = '<server>',
|
|
position = { x = 0, y = 0 },
|
|
surface = game.get_surface('nauvis'),
|
|
force = game.forces.player,
|
|
print = Server.output_script_data
|
|
}
|
|
end
|
|
|
|
local is_server = event.player_index == nil
|
|
|
|
local function reject(error_message)
|
|
error_message = error_message or ''
|
|
command_data.validated_command = false
|
|
return handle_error(error_message, 'utility/cannot_build')
|
|
end
|
|
|
|
-- Check if player and return
|
|
local check_server = command_data.check_server or false
|
|
if (check_server and not is_server) and player and player.valid then
|
|
reject(output.server_is_required)
|
|
return
|
|
end
|
|
|
|
-- Check if player and return
|
|
local check_backend = command_data.check_backend or false
|
|
if (check_backend and not is_server) and event.player_index then
|
|
if not Server.get_current_time() then
|
|
reject(output.backend_is_required)
|
|
return
|
|
end
|
|
end
|
|
|
|
-- Check if the player is an admin and if the command requires it
|
|
local check_admin = command_data.check_admin or false
|
|
if (check_admin and not is_server) and player and not player.admin then
|
|
reject(output.admin_is_required)
|
|
return
|
|
end
|
|
|
|
-- Check if the player is trusted and if the command requires it
|
|
local check_trusted = command_data.check_trusted or false
|
|
if (check_trusted and not is_server) and Core.validate_player(player) then
|
|
local is_trusted = Session.get_trusted_player(player)
|
|
if not is_trusted then
|
|
reject(output.trusted_is_required)
|
|
return
|
|
end
|
|
end
|
|
|
|
-- Check if the player is a supporter and if the command requires it
|
|
local check_supporter = command_data.check_supporter or false
|
|
if (check_supporter and not is_server) and Core.validate_player(player) then
|
|
local is_supporter = Supporters.is_supporter(player.name)
|
|
if not is_supporter then
|
|
reject(output.supporter_is_required)
|
|
return
|
|
end
|
|
end
|
|
|
|
-- Check if the player has the required playtime and if the command requires it
|
|
local check_playtime = command_data.check_playtime or false
|
|
if (check_playtime and not is_server) and Core.validate_player(player) then
|
|
local playtime = Session.get_session_player(player)
|
|
if not playtime then
|
|
reject(string.format(output.playtime_is_required, Core.get_formatted_playtime(check_playtime)))
|
|
return
|
|
end
|
|
|
|
if playtime < check_playtime then
|
|
reject(string.format(output.playtime_is_required, Core.get_formatted_playtime(check_playtime)))
|
|
return
|
|
end
|
|
end
|
|
|
|
-- Check for parameters
|
|
if command_data.parameters_required > 0 and not event.parameter then
|
|
reject(output.param_is_required)
|
|
return
|
|
end
|
|
|
|
local is_multiplayer = game.is_multiplayer()
|
|
|
|
-- Check if the command requires the player to validate the command
|
|
local validate_self = command_data.validate_self or false
|
|
if validate_self and not command_data.validated_command and is_multiplayer then
|
|
command_data.validated_command = true
|
|
if command_data.custom_message then
|
|
handle_error(string.format(output.command_needs_custom_validation, command_data.custom_message),
|
|
'utility/cannot_build')
|
|
else
|
|
handle_error(output.command_needs_validation, 'utility/cannot_build')
|
|
end
|
|
return
|
|
end
|
|
|
|
-- Extract quoted arguments
|
|
local input_text = event.parameter or ''
|
|
local quoted_segments = {}
|
|
|
|
local processed_input =
|
|
input_text:gsub(
|
|
'"([^"]-)"',
|
|
function (segment)
|
|
local no_spaces_segment = segment:gsub('%s', '%%s')
|
|
quoted_segments[no_spaces_segment] = segment
|
|
return ' ' .. no_spaces_segment .. ' '
|
|
end
|
|
)
|
|
|
|
-- Extract unquoted arguments
|
|
local parameters = {}
|
|
local current_index = 0
|
|
local parameter_count = 0
|
|
|
|
for word in processed_input:gmatch('%S+') do
|
|
parameter_count = parameter_count + 1
|
|
local quoted_word = quoted_segments[word]
|
|
local formatted_word = quoted_word and ('"' .. quoted_word .. '"') or word
|
|
|
|
if parameter_count > command_data.parameters_count then
|
|
parameters[current_index] = parameters[current_index] .. ' ' .. formatted_word
|
|
else
|
|
current_index = current_index + 1
|
|
parameters[current_index] = formatted_word
|
|
end
|
|
end
|
|
|
|
-- Check the param count
|
|
local parameters_count = #parameters
|
|
if parameters_count < command_data.parameters_required then
|
|
reject(output.param_is_required)
|
|
return
|
|
end
|
|
|
|
-- Parse the arguments
|
|
local index = 1
|
|
local handled_parameters = {}
|
|
for _, param_data in pairs(command_data.parameters) do
|
|
if param_data.as_type then
|
|
local param = conv(parameters[index])
|
|
if param_data.as_type == 'player' and param ~= nil then
|
|
local player_name = param
|
|
local player_data = game.get_player(player_name) --[[@type LuaPlayer]]
|
|
if not player_data then
|
|
return reject('Player was not found.')
|
|
end
|
|
handled_parameters[index] = player_data
|
|
index = index + 1
|
|
end
|
|
if param_data.as_type == 'surface' and param ~= nil then
|
|
local surface_name = param
|
|
if type(surface_name) ~= 'string' then
|
|
return reject('Inputted value is not of type string. Valid values are: "string"')
|
|
end
|
|
local surface_data = game.get_surface(surface_name) --[[@type LuaSurface]]
|
|
if not surface_data then
|
|
return reject('Surface was not found.')
|
|
end
|
|
handled_parameters[index] = surface_data
|
|
index = index + 1
|
|
end
|
|
if param_data.as_type == 'player-online' and param ~= nil then
|
|
local player_name = param
|
|
local player_data = game.get_player(player_name) --[[@type LuaPlayer]]
|
|
if not player_data or not player_data.valid then
|
|
return reject('Player was not found.')
|
|
end
|
|
if not player_data.connected then
|
|
return reject('Player is not online.')
|
|
end
|
|
handled_parameters[index] = player_data
|
|
index = index + 1
|
|
end
|
|
if param_data.as_type == 'player-admin' and param ~= nil then
|
|
local player_name = param
|
|
local player_data = game.get_player(player_name) --[[@type LuaPlayer]]
|
|
if not player_data or not player_data.valid then
|
|
return reject('Player was not found.')
|
|
end
|
|
if not player_data.admin then
|
|
return reject('Player is not an admin.')
|
|
end
|
|
handled_parameters[index] = player_data
|
|
index = index + 1
|
|
end
|
|
if param_data.as_type == 'server' and param ~= nil then
|
|
local player_name = param
|
|
local player_data = game.get_player(player_name) --[[@type LuaPlayer]]
|
|
if player_data and player_data.valid then
|
|
return reject('Not running from server.')
|
|
end
|
|
handled_parameters[index] = player_data
|
|
index = index + 1
|
|
end
|
|
if (param_data.as_type == 'number' or param_data.as_type == 'integer') and param ~= nil then
|
|
local num = tonumber(param)
|
|
if not num then
|
|
return reject('Inputted value is not of type number. Valid values are: 1, 2, 3, etc.')
|
|
end
|
|
handled_parameters[index] = num
|
|
index = index + 1
|
|
end
|
|
if param_data.as_type == 'string' and param ~= nil then
|
|
if type(param) ~= 'string' then
|
|
return reject('Inputted value is not of type string. Valid values are: "string"')
|
|
end
|
|
|
|
handled_parameters[index] = param
|
|
index = index + 1
|
|
end
|
|
if param_data.as_type == 'boolean' and param ~= nil then
|
|
if not check_boolean[param] then
|
|
return reject('Inputted value is not of type boolean. Valid values are: true, false.')
|
|
end
|
|
|
|
--[[ if command_data.command_activated and param == 'true' then
|
|
return handle_error(output.command_is_active, 'utility/cannot_build')
|
|
end
|
|
|
|
if not command_data.command_activated and param == 'false' then
|
|
return handle_error(output.command_is_inactive, 'utility/cannot_build')
|
|
end ]]
|
|
|
|
if param == 'true' then
|
|
param = true
|
|
else
|
|
param = false
|
|
end
|
|
|
|
handled_parameters[index] = param
|
|
index = index + 1
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Run the command callback if everything is validated
|
|
local callback = Task.get(command_data.callback)
|
|
local success, err = pcall(callback, player, unpack(handled_parameters))
|
|
if internal_error(success, command_data.name, err) then
|
|
return reject(output.command_failed)
|
|
end
|
|
|
|
-- Check if the command can only be run once
|
|
local validate_activated = command_data.validate_activated or false
|
|
if validate_activated then
|
|
if not command_data.command_activated then
|
|
command_data.command_activated = true
|
|
else
|
|
command_data.command_activated = false
|
|
end
|
|
end
|
|
|
|
command_data.validated_command = false
|
|
|
|
if err ~= nil then
|
|
if type(err) == 'boolean' then
|
|
if err == false then
|
|
Core.output_message(output.command_failed, 'warning')
|
|
else
|
|
Core.output_message(output.command_success, 'success')
|
|
end
|
|
else
|
|
Core.output_message(err)
|
|
end
|
|
else
|
|
Core.output_message(output.command_success, 'success')
|
|
end
|
|
end
|
|
|
|
--- Creates a new command.
|
|
---@param name string
|
|
---@param help string
|
|
---@return MetaCommand
|
|
function Public.new(name, help)
|
|
if this.commands[name] then
|
|
error('Command already exists: ' .. name, 2)
|
|
end
|
|
|
|
if game then error('Cannot run new() when game is initialized : ' .. name, 2) end
|
|
|
|
local command =
|
|
setmetatable(
|
|
{
|
|
name = name,
|
|
help = help,
|
|
aliases = {},
|
|
parameters = {},
|
|
parameters_count = 0,
|
|
parameters_required = 0,
|
|
check_admin = false,
|
|
check_server = false,
|
|
check_backend = false,
|
|
check_supporter = false,
|
|
check_trusted = false,
|
|
check_playtime = false,
|
|
validate_self = false,
|
|
validated_command = false
|
|
},
|
|
Public.metatable
|
|
)
|
|
|
|
this.commands[name] = command
|
|
|
|
return command
|
|
end
|
|
|
|
--- Requires the player to validate the command before running it.
|
|
---@param custom_message? string
|
|
---@return MetaCommand
|
|
function Public:require_validation(custom_message)
|
|
self.validate_self = true
|
|
if custom_message then
|
|
self.custom_message = custom_message
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
--- Requires the player to validate the command before running it.
|
|
---@return MetaCommand
|
|
function Public:is_activated()
|
|
self.validate_activated = true
|
|
return self
|
|
end
|
|
|
|
--- Requires the player to be an admin to run the command.
|
|
---@return MetaCommand
|
|
function Public:require_admin()
|
|
self.check_admin = true
|
|
return self
|
|
end
|
|
|
|
---@return MetaCommand
|
|
function Public:require_server()
|
|
self.check_server = true
|
|
return self
|
|
end
|
|
|
|
--- Requires that the server is connected to a backend
|
|
---@return MetaCommand
|
|
function Public:require_backend()
|
|
self.check_backend = true
|
|
return self
|
|
end
|
|
|
|
--- Requires the player to be a supporter to run the command.
|
|
---@return MetaCommand
|
|
function Public:require_supporter()
|
|
self.check_supporter = true
|
|
return self
|
|
end
|
|
|
|
--- Requires the player to be trusted to run the command.
|
|
---@return MetaCommand
|
|
function Public:require_trusted()
|
|
self.check_trusted = true
|
|
return self
|
|
end
|
|
|
|
--- Requires the player to have a minimum playtime to run the command.
|
|
---@param playtime integer|number
|
|
---@return MetaCommand
|
|
function Public:require_playtime(playtime)
|
|
self.check_playtime = playtime or nil
|
|
return self
|
|
end
|
|
|
|
---@alias ParamName
|
|
---| '"string"'
|
|
---| '"number"'
|
|
---| '"integer"'
|
|
---| '"boolean"'
|
|
---| '"player"'
|
|
---| '"player-online"'
|
|
---| '"player-admin"'
|
|
---| '"server"'
|
|
---| '"surface"'
|
|
|
|
--- Adds a parameter to the command.
|
|
---@param name string
|
|
---@param optional boolean
|
|
---@param as_type? ParamName
|
|
---@return MetaCommand
|
|
function Public:add_parameter(name, optional, as_type)
|
|
if not validate_types[as_type] then
|
|
error('Invalid type: ' .. as_type .. ' for parameter: ' .. name, 2)
|
|
end
|
|
|
|
if self.parameters[name] then
|
|
error('Parameter: ' .. name .. ' already exists for command: ' .. self.name, 2)
|
|
end
|
|
|
|
self.parameters[name] = { optional = optional, as_type = as_type }
|
|
self.parameters_count = self.parameters_count + 1
|
|
|
|
if not optional then
|
|
self.parameters_required = self.parameters_required + 1
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
--- Adds an alias to the command.
|
|
---@param name string
|
|
---@return MetaCommand
|
|
function Public:add_alias(name)
|
|
if self.aliases[name] then
|
|
error('Alias: ' .. name .. ' already exists for command: ' .. self.name, 2)
|
|
end
|
|
|
|
self.aliases[name] = name
|
|
|
|
return self
|
|
end
|
|
|
|
--- Sets the command as default if marking paramaters as optional.
|
|
---@param defaults any
|
|
---@return MetaCommand
|
|
function Public:set_default(defaults)
|
|
for name, value in pairs(defaults) do
|
|
if self.parameters[name] then
|
|
self.parameters[name].default = value
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Restores the command_activated state for each command
|
|
function Public.restore_states()
|
|
for _, command in pairs(this.commands) do
|
|
command.validated_command = false
|
|
command.command_activated = false
|
|
end
|
|
end
|
|
|
|
--- Registers the command to the game. Will return the player/server and the args as separate arguments.
|
|
---@param func function
|
|
function Public:callback(func)
|
|
-- Generates a description to be used
|
|
local description = ''
|
|
for param_name, param_details in pairs(self.parameters) do
|
|
if param_details.optional then
|
|
description = string.format('%s [%s]', description, param_name)
|
|
else
|
|
description = string.format('%s <%s>', description, param_name)
|
|
end
|
|
end
|
|
self.description = description
|
|
|
|
-- If command fails to run, notify the player/server
|
|
local function command_error(err)
|
|
internal_error(false, self.name, trace(err))
|
|
end
|
|
|
|
-- Registers the command as a token
|
|
local id = Task.register(func)
|
|
self.callback = id
|
|
|
|
-- Callback
|
|
local function command_callback(event)
|
|
event.name = self.name
|
|
xpcall(execute, command_error, event)
|
|
end
|
|
|
|
-- Lastly, adds the command to the game
|
|
local help = description .. ' - ' .. self.help
|
|
commands.add_command(self.name, help, command_callback)
|
|
|
|
-- Adds any aliases if any
|
|
for _, alias in pairs(self.aliases) do
|
|
if not commands.commands[alias] and not commands.game_commands[alias] then
|
|
commands.add_command(alias, help, command_callback)
|
|
end
|
|
end
|
|
end
|
|
|
|
local directions =
|
|
{
|
|
[0] = 'defines.direction.north',
|
|
[1] = 'defines.direction.northnortheast',
|
|
[2] = 'defines.direction.northeast',
|
|
[3] = 'defines.direction.eastnortheast',
|
|
[4] = 'defines.direction.east',
|
|
[5] = 'defines.direction.eastsoutheast',
|
|
[6] = 'defines.direction.southeast',
|
|
[7] = 'defines.direction.southsoutheast',
|
|
[8] = 'defines.direction.south',
|
|
[9] = 'defines.direction.southsouthwest',
|
|
[10] = 'defines.direction.southwest',
|
|
[11] = 'defines.direction.westsouthwest',
|
|
[12] = 'defines.direction.west',
|
|
[13] = 'defines.direction.westnorthwest',
|
|
[14] = 'defines.direction.northwest',
|
|
[15] = 'defines.direction.northnorthwest',
|
|
}
|
|
|
|
Public.new('get', 'Hover over an object to get its name.')
|
|
:require_admin()
|
|
:add_parameter('die', true, 'string')
|
|
:add_alias('entity')
|
|
:callback(
|
|
function (player, action)
|
|
local entity = player.selected
|
|
if not entity or not entity.valid then
|
|
return false
|
|
end
|
|
|
|
if action and action == 'die' then
|
|
entity.die()
|
|
return true
|
|
end
|
|
|
|
player.print('[color=orange]Name:[/color] ' .. entity.name)
|
|
player.print('[color=orange]Type:[/color] ' .. entity.type)
|
|
player.print('[color=orange]Force:[/color] ' .. entity.force.name)
|
|
player.print('[color=orange]Direction:[/color] ' .. entity.direction .. ' (' .. directions[entity.direction] .. ')')
|
|
player.print('[color=orange]Destructible:[/color] ' .. (entity.destructible and 'true' or 'false'))
|
|
player.print('[color=orange]Minable:[/color] ' .. (entity.minable and 'true' or 'false'))
|
|
player.print('[color=orange]Unit Number:[/color] ' .. (entity.unit_number or 'nil'))
|
|
player.print('[color=orange]Position:[/color] ' .. serpent.line(entity.position))
|
|
player.print('[color=orange]Active:[/color] ' .. (entity.active and 'true' or 'false'))
|
|
return true
|
|
end
|
|
)
|
|
|
|
return Public
|