mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-07-26 18:36:23 +09:00
Add lua module code
This commit is contained in:
@@ -0,0 +1,436 @@
|
||||
--[[-- Exp Permission Groups
|
||||
Adds permission group syncing to clusterio
|
||||
]]
|
||||
|
||||
local clusterio_api = require("modules/clusterio/api")
|
||||
local compat = require("modules/clusterio/compat")
|
||||
|
||||
--- Top level module table, contains event handlers and public methods
|
||||
--- @class ExpPermissionGroups
|
||||
local ExpPermissionGroups = {}
|
||||
|
||||
--- @class ExpPermissionGroups.GroupPermissions
|
||||
--- @field [1] boolean -- is_blacklist, true means action names is a backlist
|
||||
--- @field [2] string[] -- action_names, the actions to allow or disallow
|
||||
|
||||
--- @class ExpPermissionGroups.GroupRecord
|
||||
--- @field id number
|
||||
--- @field name string
|
||||
--- @field permissions ExpPermissionGroups.GroupPermissions?
|
||||
--- @field isDeleted boolean
|
||||
|
||||
--- @class ExpPermissionGroups.AssignmentRecord
|
||||
--- @field name string
|
||||
--- @field groupId number
|
||||
--- @field isDeleted boolean
|
||||
|
||||
--- @class ExpPermissionGroups.ScriptData
|
||||
--- @field factorio_to_clusterio_id table<number, number?>
|
||||
--- @field clusterio_id_to_group table<number, LuaPermissionGroup?>
|
||||
--- @field dirty_groups table<number, LuaPermissionGroup>
|
||||
--- @field dirty_players table<string, LuaPlayer>
|
||||
--- @field emit_updates boolean
|
||||
local script_data = {}
|
||||
|
||||
local function setup_script_data()
|
||||
if compat.script_data["exp_groups"] == nil then
|
||||
--- @type ExpPermissionGroups.ScriptData
|
||||
compat.script_data["exp_groups"] = {
|
||||
factorio_to_clusterio_id = {},
|
||||
clusterio_id_to_group = {},
|
||||
dirty_groups = {},
|
||||
dirty_players = {},
|
||||
emit_updates = false,
|
||||
}
|
||||
end
|
||||
|
||||
ExpPermissionGroups.on_load()
|
||||
end
|
||||
|
||||
--[[
|
||||
Helper methods
|
||||
]]
|
||||
|
||||
--- Get the default factorio permission group
|
||||
--- @return LuaPermissionGroup
|
||||
local function get_default_group()
|
||||
return assert(game.permissions.get_group("Default"))
|
||||
end
|
||||
|
||||
--- Move all players from one group to another
|
||||
--- @param group_src LuaPermissionGroup
|
||||
--- @param group_dst LuaPermissionGroup
|
||||
local function move_players(group_src, group_dst)
|
||||
local add_player = group_dst.add_player
|
||||
for _, player in pairs(group_src.players) do
|
||||
add_player(player)
|
||||
end
|
||||
end
|
||||
|
||||
--- Apply an encoded permission definition to a group
|
||||
--- @param group LuaPermissionGroup
|
||||
--- @param permissions ExpPermissionGroups.GroupPermissions
|
||||
local function decode_group_permissions(group, permissions)
|
||||
local is_blacklist = permissions[1]
|
||||
local actions_names = permissions[2]
|
||||
local action_allowed = not is_blacklist
|
||||
|
||||
-- Construct a hash map for faster lookup
|
||||
local action_map = {}
|
||||
for _, input_action_name in pairs(actions_names) do
|
||||
action_map[input_action_name] = true
|
||||
end
|
||||
|
||||
-- Apply the whitelist / backlist to the group
|
||||
for input_action_name, input_action in pairs(defines.input_action) do
|
||||
if action_map[input_action_name] then
|
||||
group.set_allows_action(input_action, action_allowed)
|
||||
else
|
||||
group.set_allows_action(input_action, is_blacklist)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Encode the permissions of a group in a shorthand format
|
||||
--- @param group LuaPermissionGroup
|
||||
--- @return ExpPermissionGroups.GroupPermissions
|
||||
local function encode_group_permissions(group)
|
||||
local whitelist = {} --- @type string[]
|
||||
local blacklist = {} --- @type string[]
|
||||
local whitelist_index = 0
|
||||
local blacklist_index = 0
|
||||
|
||||
-- Construct the whitelist and blacklist
|
||||
local allows_action = group.allows_action
|
||||
for input_action_name, input_action in pairs(defines.input_action) do
|
||||
if allows_action(input_action) then
|
||||
whitelist[whitelist_index] = input_action_name
|
||||
whitelist_index = whitelist_index + 1
|
||||
else
|
||||
blacklist[blacklist_index] = input_action_name
|
||||
blacklist_index = blacklist_index + 1
|
||||
end
|
||||
end
|
||||
|
||||
-- Return the whitelist if it is smaller
|
||||
if blacklist_index > whitelist_index then
|
||||
return { false, whitelist }
|
||||
end
|
||||
|
||||
-- Otherwise return the blacklist as it is smaller
|
||||
return { true, blacklist }
|
||||
end
|
||||
|
||||
--[[
|
||||
State handlers
|
||||
]]
|
||||
|
||||
--- Update the factorio permission group
|
||||
--- @param group_record ExpPermissionGroups.GroupRecord
|
||||
local function update_group(group_record)
|
||||
assert(not group_record.isDeleted)
|
||||
|
||||
-- Try find the group by id and then then name
|
||||
local group = script_data.clusterio_id_to_group[group_record.id]
|
||||
if not group then
|
||||
group = game.permissions.get_group(group_record.name)
|
||||
end
|
||||
|
||||
-- Create a new group or update the found group
|
||||
if not group or not group.valid then
|
||||
group = assert(game.permissions.create_group(group_record.name))
|
||||
else
|
||||
group.name = group_record.name
|
||||
end
|
||||
|
||||
-- Update the permissions for the group
|
||||
if group_record.permissions then
|
||||
decode_group_permissions(group, group_record.permissions)
|
||||
end
|
||||
|
||||
-- Update the script data
|
||||
script_data.factorio_to_clusterio_id[group.group_id] = group_record.id
|
||||
script_data.clusterio_id_to_group[group_record.id] = group
|
||||
end
|
||||
|
||||
--- Delete the factorio permission group
|
||||
--- @param group_record ExpPermissionGroups.GroupRecord
|
||||
local function delete_group(group_record)
|
||||
assert(group_record.isDeleted)
|
||||
|
||||
local default_group = get_default_group()
|
||||
local group = script_data.clusterio_id_to_group[group_record.id]
|
||||
if group then
|
||||
move_players(group, default_group)
|
||||
group.destroy()
|
||||
end
|
||||
end
|
||||
|
||||
--- Update an assignment by moving the player to their new group
|
||||
--- @param assignment_record ExpPermissionGroups.AssignmentRecord
|
||||
local function update_assignment(assignment_record)
|
||||
assert(not assignment_record.isDeleted)
|
||||
|
||||
local group = script_data.clusterio_id_to_group[assignment_record.groupId]
|
||||
local player = assert(game.get_player(assignment_record.name))
|
||||
if group then
|
||||
group.add_player(player)
|
||||
end
|
||||
end
|
||||
|
||||
--- Clear an assignment by moving the player to the default group
|
||||
--- @param assignment_record ExpPermissionGroups.AssignmentRecord
|
||||
local function delete_assignment(assignment_record)
|
||||
assert(assignment_record.isDeleted)
|
||||
|
||||
local default_group = get_default_group()
|
||||
local player = assert(game.get_player(assignment_record.name))
|
||||
default_group.add_player(player)
|
||||
end
|
||||
|
||||
--[[
|
||||
Public methods
|
||||
]]
|
||||
|
||||
--- Restore local references to persistent script data after load
|
||||
function ExpPermissionGroups.on_load()
|
||||
script_data = compat.script_data["exp_groups"]
|
||||
end
|
||||
|
||||
--- Enable or disable emitting lua changes back to the instance plugin
|
||||
--- @param enabled boolean?
|
||||
function ExpPermissionGroups.set_emit_events(enabled)
|
||||
script_data.emit_updates = enabled ~= false
|
||||
end
|
||||
|
||||
--- Replace local state with expected controller state on startup
|
||||
--- @param group_records ExpPermissionGroups.GroupRecord[]
|
||||
function ExpPermissionGroups.initialise_groups(group_records)
|
||||
local _emit_events = script_data.emit_updates
|
||||
script_data.emit_updates = false
|
||||
|
||||
-- Update all the received groups
|
||||
local seen_clusterio_ids = {} --- @type table<number, boolean>
|
||||
for _, group_record in pairs(group_records) do
|
||||
update_group(group_record)
|
||||
seen_clusterio_ids[group_record.id] = true
|
||||
end
|
||||
|
||||
-- Cleanup the script data, removing stale group ids
|
||||
local factorio_to_clusterio_id = script_data.factorio_to_clusterio_id
|
||||
local clusterio_id_to_group = script_data.clusterio_id_to_group
|
||||
for factorio_id, clusterio_id in pairs(factorio_to_clusterio_id) do
|
||||
if not seen_clusterio_ids[clusterio_id] then
|
||||
factorio_to_clusterio_id[factorio_id] = nil
|
||||
clusterio_id_to_group[clusterio_id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Remove all other groups
|
||||
local default_group = get_default_group()
|
||||
local default_group_id = default_group.group_id
|
||||
for _, group in pairs(game.permissions.groups) do
|
||||
if not factorio_to_clusterio_id[group.group_id] and group.group_id ~= default_group_id then
|
||||
move_players(group, default_group)
|
||||
group.destroy()
|
||||
end
|
||||
end
|
||||
|
||||
script_data.emit_updates = _emit_events
|
||||
end
|
||||
|
||||
--- Receive an updated version of a group record
|
||||
--- @param group_record ExpPermissionGroups.GroupRecord
|
||||
function ExpPermissionGroups.receive_group_update(group_record)
|
||||
local _emit_events = script_data.emit_updates
|
||||
script_data.emit_updates = false
|
||||
|
||||
if group_record.isDeleted then
|
||||
delete_group(group_record)
|
||||
else
|
||||
update_group(group_record)
|
||||
end
|
||||
|
||||
script_data.emit_updates = _emit_events
|
||||
end
|
||||
|
||||
--- Receive an updated version of a assignment record
|
||||
--- @param assignment_record ExpPermissionGroups.AssignmentRecord
|
||||
function ExpPermissionGroups.receive_assignment_update(assignment_record)
|
||||
local _emit_events = script_data.emit_updates
|
||||
script_data.emit_updates = false
|
||||
|
||||
if assignment_record.isDeleted then
|
||||
delete_assignment(assignment_record)
|
||||
else
|
||||
update_assignment(assignment_record)
|
||||
end
|
||||
|
||||
script_data.emit_updates = _emit_events
|
||||
end
|
||||
|
||||
--- Get the current script data for debugging purposes
|
||||
--- @package
|
||||
function ExpPermissionGroups._script_data()
|
||||
return script_data
|
||||
end
|
||||
|
||||
--[[
|
||||
IPC events
|
||||
]]
|
||||
|
||||
--- Emit a group update to the instance plugin
|
||||
--- @param group LuaPermissionGroup
|
||||
local function emit_group_update(group)
|
||||
clusterio_api.send_json("exp_group:group_updated", {
|
||||
group_name = group.name,
|
||||
group_id = script_data.factorio_to_clusterio_id[group.group_id],
|
||||
permissions = encode_group_permissions(group),
|
||||
})
|
||||
end
|
||||
|
||||
--- Emit a group deletion to the instance plugin
|
||||
--- @param group_id number
|
||||
--- @param group_name string
|
||||
local function emit_group_delete(group_id, group_name)
|
||||
clusterio_api.send_json("exp_group:group_deleted", {
|
||||
group_name = group_name,
|
||||
group_id = script_data.factorio_to_clusterio_id[group_id],
|
||||
})
|
||||
end
|
||||
|
||||
--- Emit a player assignment to the instance plugin
|
||||
--- @param assignments table<string, number>
|
||||
local function emit_player_assignments(assignments)
|
||||
clusterio_api.send_json("exp_group:player_assignments", {
|
||||
assignments = assignments
|
||||
})
|
||||
end
|
||||
|
||||
--[[
|
||||
IPC event queuing
|
||||
]]
|
||||
|
||||
--- Mark a group as changed
|
||||
--- @param group LuaPermissionGroup
|
||||
local function mark_group_dirty(group)
|
||||
script_data.dirty_groups[group.group_id] = group
|
||||
end
|
||||
|
||||
--- Mark a player as changed
|
||||
--- @param player LuaPlayer
|
||||
local function mark_player_dirty(player)
|
||||
script_data.dirty_players[player.name] = player
|
||||
end
|
||||
|
||||
--- Flush queued group updates to the instance plugin
|
||||
local function flush_group_updates()
|
||||
-- Check if updates should be updated
|
||||
if not script_data.emit_updates then
|
||||
script_data.dirty_groups = {}
|
||||
return
|
||||
end
|
||||
|
||||
-- Get all the groups and emit their updates
|
||||
local get_group = game.permissions.get_group
|
||||
for _, group in pairs(script_data.dirty_groups) do
|
||||
if group.valid then
|
||||
emit_group_update(group)
|
||||
end
|
||||
end
|
||||
|
||||
script_data.dirty_groups = {}
|
||||
end
|
||||
|
||||
--- Flush queued player updates to the instance plugin
|
||||
local function flush_player_updates()
|
||||
-- Check if updates should be updated
|
||||
if not script_data.emit_updates then
|
||||
script_data.dirty_players = {}
|
||||
return
|
||||
end
|
||||
|
||||
-- Construct the update payload
|
||||
local assignments = {} --- @type table<string, number>
|
||||
for player_name, player in pairs(script_data.dirty_players) do
|
||||
local group = player.valid and player.permission_group
|
||||
if group then
|
||||
assignments[player_name] = script_data.factorio_to_clusterio_id[group.group_id]
|
||||
end
|
||||
end
|
||||
|
||||
emit_player_assignments(assignments)
|
||||
|
||||
script_data.dirty_players = {}
|
||||
end
|
||||
|
||||
--[[
|
||||
Factorio events
|
||||
]]
|
||||
|
||||
--- Handle clusterio server startup
|
||||
local function on_server_startup()
|
||||
setup_script_data()
|
||||
end
|
||||
|
||||
--- Handle creation of permission groups
|
||||
--- @param event EventData.on_permission_group_added
|
||||
local function on_permission_group_added(event)
|
||||
-- Check if updates should be updated
|
||||
if not script_data.emit_updates then
|
||||
return
|
||||
end
|
||||
|
||||
emit_group_update(event.group)
|
||||
end
|
||||
|
||||
--- Handle deletion of permission groups
|
||||
--- @param event EventData.on_permission_group_deleted
|
||||
local function on_permission_group_deleted(event)
|
||||
-- Check if updates should be updated
|
||||
if not script_data.emit_updates then
|
||||
return
|
||||
end
|
||||
|
||||
emit_group_delete(event.id, event.group_name)
|
||||
end
|
||||
|
||||
--- Handle edits make to permission groups
|
||||
--- @param event EventData.on_permission_group_edited
|
||||
local function on_permission_group_edited(event)
|
||||
-- Check if updates should be updated
|
||||
if not script_data.emit_updates then
|
||||
return
|
||||
end
|
||||
|
||||
-- Check if this is a player or group event
|
||||
if event.type == "add-player" or event.type == "remove-player" then
|
||||
local player = assert(game.get_player(event.other_player_index))
|
||||
mark_player_dirty(player)
|
||||
else
|
||||
mark_group_dirty(event.group)
|
||||
end
|
||||
end
|
||||
|
||||
--- Periodically flush queued changes
|
||||
local function on_nth_tick_flush()
|
||||
flush_group_updates()
|
||||
flush_player_updates()
|
||||
end
|
||||
|
||||
local e = defines.events
|
||||
|
||||
local events = {
|
||||
[clusterio_api.events.on_server_startup] = on_server_startup,
|
||||
[e.on_permission_group_added] = on_permission_group_added,
|
||||
[e.on_permission_group_deleted] = on_permission_group_deleted,
|
||||
[e.on_permission_group_edited] = on_permission_group_edited,
|
||||
}
|
||||
|
||||
local on_nth_tick = {
|
||||
[300] = on_nth_tick_flush,
|
||||
}
|
||||
|
||||
ExpPermissionGroups.events = events --- @package
|
||||
ExpPermissionGroups.on_nth_tick = on_nth_tick --- @package
|
||||
return ExpPermissionGroups
|
||||
Reference in New Issue
Block a user