mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-11-11 23:54:52 +09:00
- Feature.register returns the config as a second value, so files write `local feature, config = Feature.register(...)`. - The lib is ExpScenario.Features and the instance class with its fields sits on Feature._prototype, which is the __index, the same shape as exp_commands. - update_config only looks at values whose override was added, removed, or replaced. _apply_field sets one value and returns it, so the comparison happens while applying and the second loop only raises. Overrides dropped from the previous set count as candidates because the value reverts to its default. - The `@setting name` tags are gone from the config comments. - Protection is guarded as a whole instead of checking config.enabled in one handler. The handlers also forget mined entities, so get_entities and is_entity_protected now drop invalid references, which also covers entities destroyed without an event. The list getters are gone, the selection overlay builds its lists from Protection.config. - Chat and damage popups are one feature in control/popups.lua. - ignore_admins and command_admin_only are removed, roles cover them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
104 lines
3.8 KiB
Lua
104 lines
3.8 KiB
Lua
--[[-- Control -- AFK Kick
|
|
Kicks players when all players on the server are afk
|
|
]]
|
|
|
|
local Async = require("modules/exp_util/async")
|
|
local Feature = require("modules/exp_scenario/features")
|
|
local Roles = require("modules/exp_roles")
|
|
local Storage = require("modules/exp_util/storage")
|
|
local feature, config = Feature.register("afk_kick", {
|
|
admin_as_active = true, -- When true admins will be treated as active regardless of afk time
|
|
trust_as_active = true, -- When true trusted players (by playtime) will be treated as active regardless of afk time
|
|
active_role_id = Feature.optional("number"), -- Players with this role or higher are treated as active regardless of afk time
|
|
afk_minutes = 10, -- The time in minutes that must pass for a player to be considered afk
|
|
kick_minutes = 30, -- The time in minutes that must pass without any active players for all players to be kicked
|
|
trust_minutes = 600, -- The time in minutes that a player must be online for to count as trusted
|
|
})
|
|
|
|
local ticks_per_minute = 3600
|
|
|
|
--- @type { last_active: number }
|
|
local script_data = { last_active = 0 }
|
|
Storage.register(script_data, function(tbl)
|
|
script_data = tbl
|
|
end)
|
|
|
|
--- Kicks an afk player, used to add a delay so the gui has time to appear
|
|
local afk_kick_player_async =
|
|
Async.register(function(player)
|
|
if game.tick - script_data.last_active < config.kick_minutes * ticks_per_minute then return end
|
|
game.kick_player(player, "AFK while no active players on the server")
|
|
end)
|
|
|
|
--- Check if a player has a role which counts as active regardless of afk time
|
|
--- @param player LuaPlayer
|
|
--- @return boolean
|
|
local function is_active_role(player)
|
|
local role = config.active_role_id and Roles.get_role(config.active_role_id)
|
|
return role ~= nil and not Roles.get_player_highest_role(player):is_lower_than(role)
|
|
end
|
|
|
|
--- Check if there is an active player
|
|
local function has_active_player()
|
|
for _, player in ipairs(game.connected_players) do
|
|
if player.afk_time < config.afk_minutes * ticks_per_minute
|
|
or config.admin_as_active and player.admin
|
|
or config.trust_as_active and player.online_time > config.trust_minutes * ticks_per_minute
|
|
or is_active_role(player) then
|
|
script_data.last_active = game.tick
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
--- Check for an active player every update_time number of ticks
|
|
local function check_afk_players()
|
|
-- Check for active players
|
|
if has_active_player() then return end
|
|
|
|
-- Check if players should be kicked
|
|
if game.tick - script_data.last_active < config.kick_minutes * ticks_per_minute then return end
|
|
|
|
-- Kick time exceeded, kick all players
|
|
for _, player in ipairs(game.connected_players) do
|
|
-- Add a frame to say why the player was kicked
|
|
local frame = player.gui.screen.add{
|
|
type = "frame",
|
|
name = "afk-kick",
|
|
caption = { "exp_afk-kick.kick-message" },
|
|
}
|
|
|
|
local uis = player.display_scale
|
|
local res = player.display_resolution
|
|
frame.location = {
|
|
x = res.width * (0.5 - 0.11 * uis),
|
|
y = res.height * (0.5 - 0.14 * uis),
|
|
}
|
|
|
|
-- Kick the player, some delay needed allow the gui to show
|
|
afk_kick_player_async:start_after(60, player)
|
|
end
|
|
end
|
|
|
|
--- Remove the screen gui if it is present
|
|
--- @param event EventData.on_player_joined_game
|
|
local function on_player_joined_game(event)
|
|
local player = assert(game.get_player(event.player_index))
|
|
local frame = player.gui.screen["afk-kick"]
|
|
if frame and frame.valid then frame.destroy() end
|
|
end
|
|
|
|
local e = defines.events
|
|
|
|
return feature:guard{
|
|
events = {
|
|
[e.on_player_joined_game] = on_player_joined_game,
|
|
},
|
|
on_nth_tick = {
|
|
[60 * 60] = check_afk_players,
|
|
},
|
|
has_active_player = has_active_player,
|
|
}
|