Add instance plugin

This commit is contained in:
Cooldude2606
2026-05-20 22:39:44 +01:00
parent 7ddfa8e735
commit dca5086ce3
3 changed files with 181 additions and 28 deletions
+148 -1
View File
@@ -1,6 +1,153 @@
import * as lib from "@clusterio/lib";
import { BaseInstancePlugin } from "@clusterio/host"; import { BaseInstancePlugin } from "@clusterio/host";
import * as lib from "@clusterio/lib";
import * as messages from "./messages";
export type IpcGroupUpdated = {
group_name: string,
group_id: number | undefined,
permissions: { is_blacklist: boolean, permissions: string[] },
};
export type IpcGroupDeleted = {
group_name: string,
group_id: number | undefined,
};
export type IpcPlayerAssignments = {
assignments: Record<string, number>,
};
export class InstancePlugin extends BaseInstancePlugin { export class InstancePlugin extends BaseInstancePlugin {
async init() {
this.instance.handle(messages.GroupUpdatedEvent, this.handleGroupUpdatedEvent.bind(this));
this.instance.handle(messages.AssignmentUpdatedEvent, this.handleAssignmentUpdatedEvent.bind(this));
this.instance.server.handle(`exp_group:group_updated`, this.handleGroupUpdatedIPC.bind(this))
this.instance.server.handle(`exp_group:group_deleted`, this.handleGroupDeletedIPC.bind(this))
this.instance.server.handle(`exp_group:player_assignments`, this.handlePlayerAssignmentsIPC.bind(this))
}
async onInstanceConfigFieldChanged(field: string, curr: unknown, prev: unknown) {
switch(field) {
case "exp_groups.sync_mode":
await this.luaSetEmitEvents(curr == "bidirectional")
break;
}
}
async onStart() {
await this.instance.sendTo("controller", new lib.SubscriptionRequest(
`exp_groups:${messages.GroupUpdatedEvent.name}`, true
));
const groups = await this.instance.sendTo("controller", new messages.GroupListRequest())
await this.luaSendInitialGroups(groups);
await this.luaSetEmitEvents(this.instance.config.get("exp_groups.sync_mode") == "bidirectional")
}
async onPlayerEvent(event: lib.PlayerEvent) {
switch(event.type) {
case "join":
this.handlePlayerJoin(event.name);
break;
case "leave":
this.handlePlayerLeave(event.name);
break;
}
}
async handlePlayerJoin(playerName: string) {
await this.subscribePlayerAssignment(playerName);
await this.requestPlayerAssignment(playerName);
}
async handlePlayerLeave(playerName: string) {
await this.unsubscribePlayerAssignment(playerName);
}
async handleGroupUpdatedEvent(event: messages.GroupUpdatedEvent) {
for (const group of event.updates) {
await this.luaSendGroupUpdate(group);
}
}
async handleAssignmentUpdatedEvent(event: messages.AssignmentUpdatedEvent) {
for (const assignment of event.updates) {
await this.luaSendAssignmentUpdate(assignment);
}
}
async handleGroupUpdatedIPC(event: IpcGroupUpdated) {
const permissions = new messages.GroupPermissions(
event.permissions.is_blacklist,
event.permissions.permissions,
);
if (event.group_id === undefined) {
await this.instance.sendTo("controller",
new messages.GroupCreateRequest(event.group_name, permissions),
);
} else {
await this.instance.sendTo("controller", new messages.GroupUpdateRequest(
new messages.GroupRecord(event.group_id, event.group_name, permissions),
));
}
}
async handleGroupDeletedIPC(event: IpcGroupDeleted) {
if (event.group_id === undefined) {
return;
}
await this.instance.sendTo("controller", new messages.GroupDeleteRequest(event.group_id));
}
async handlePlayerAssignmentsIPC(event: IpcPlayerAssignments) {
await Promise.all(
Object.entries(event.assignments).map(([playerName, groupId]) =>
this.instance.sendTo("controller",
new messages.AssignmentUpdateRequest(new messages.AssignmentRecord(playerName, groupId)),
)
)
);
}
async subscribePlayerAssignment(playerName: string) {
await this.instance.sendTo("controller", new lib.SubscriptionRequest(
`exp_groups:${messages.AssignmentUpdatedEvent.name}`, true, 0, playerName
));
}
async unsubscribePlayerAssignment(playerName: string) {
await this.instance.sendTo("controller", new lib.SubscriptionRequest(
`exp_groups:${messages.AssignmentUpdatedEvent.name}`, false, 0, playerName
));
}
async requestPlayerAssignment(playerName: string) {
const assignment = await this.instance.sendTo("controller", new messages.AssignmentGetRequest(playerName));
await this.luaSendAssignmentUpdate(assignment);
}
async luaSendInitialGroups(groups: messages.GroupRecord[]) {
if (this.instance.config.get("exp_groups.sync_mode") == "disabled") {
return;
}
await this.instance.sendRcon(`/sc exp_groups.initialise_groups(helpers.json_to_table${JSON.stringify(groups)})`)
}
async luaSendGroupUpdate(group: messages.GroupRecord) {
if (this.instance.config.get("exp_groups.sync_mode") == "disabled") {
return;
}
await this.instance.sendRcon(`/sc exp_groups.receive_group_update(helpers.json_to_table${JSON.stringify(group)})`)
}
async luaSendAssignmentUpdate(assignment: messages.AssignmentRecord) {
if (this.instance.config.get("exp_groups.sync_mode") == "disabled") {
return;
}
await this.instance.sendRcon(`/sc exp_groups.initialise_groups(helpers.receive_assignment_update${JSON.stringify(assignment)})`)
}
async luaSetEmitEvents(emitEvents: boolean) {
await this.instance.sendRcon(`/sc exp_groups.set_emit_events(${emitEvents})`)
}
} }
+24 -27
View File
@@ -1,4 +1,4 @@
--[[-- Exp Permission Groups --[[-- ExpGroups
Adds permission group syncing to clusterio Adds permission group syncing to clusterio
]] ]]
@@ -6,18 +6,18 @@ local clusterio_api = require("modules/clusterio/api")
local compat = require("modules/clusterio/compat") local compat = require("modules/clusterio/compat")
--- Top level module table, contains event handlers and public methods --- Top level module table, contains event handlers and public methods
--- @class ExpPermissionGroups --- @class ExpGroups
local ExpPermissionGroups = {} local ExpGroups = {}
--- @class ExpPermissionGroups.GroupPermissions --- @class ExpPermissionGroups.GroupPermissions
--- @field [1] boolean -- is_blacklist, true means action names is a backlist --- @field is_blacklist boolean
--- @field [2] string[] -- action_names, the actions to allow or disallow --- @field permissions string[]
--- @class ExpPermissionGroups.GroupRecord --- @class ExpPermissionGroups.GroupRecord
--- @field id number --- @field id number
--- @field name string --- @field name string
--- @field permissions ExpPermissionGroups.GroupPermissions? --- @field permissions ExpPermissionGroups.GroupPermissions?
--- @field isDeleted boolean --- @field is_deleted boolean
--- @class ExpPermissionGroups.AssignmentRecord --- @class ExpPermissionGroups.AssignmentRecord
--- @field name string --- @field name string
@@ -44,7 +44,7 @@ local function setup_script_data()
} }
end end
ExpPermissionGroups.on_load() ExpGroups.on_load()
end end
--[[ --[[
@@ -71,17 +71,15 @@ end
--- @param group LuaPermissionGroup --- @param group LuaPermissionGroup
--- @param permissions ExpPermissionGroups.GroupPermissions --- @param permissions ExpPermissionGroups.GroupPermissions
local function decode_group_permissions(group, permissions) 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 -- Construct a hash map for faster lookup
local action_map = {} local action_map = {}
for _, input_action_name in pairs(actions_names) do for _, input_action_name in pairs(permissions.permissions) do
action_map[input_action_name] = true action_map[input_action_name] = true
end end
-- Apply the whitelist / backlist to the group -- Apply the whitelist / backlist to the group
local is_blacklist = permissions.is_blacklist
local action_allowed = not is_blacklist
for input_action_name, input_action in pairs(defines.input_action) do for input_action_name, input_action in pairs(defines.input_action) do
if action_map[input_action_name] then if action_map[input_action_name] then
group.set_allows_action(input_action, action_allowed) group.set_allows_action(input_action, action_allowed)
@@ -114,11 +112,11 @@ local function encode_group_permissions(group)
-- Return the whitelist if it is smaller -- Return the whitelist if it is smaller
if blacklist_index > whitelist_index then if blacklist_index > whitelist_index then
return { false, whitelist } return { is_blacklist = false, permissions = whitelist }
end end
-- Otherwise return the blacklist as it is smaller -- Otherwise return the blacklist as it is smaller
return { true, blacklist } return { is_blacklist = true, permissions = blacklist }
end end
--[[ --[[
@@ -128,7 +126,7 @@ end
--- Update the factorio permission group --- Update the factorio permission group
--- @param group_record ExpPermissionGroups.GroupRecord --- @param group_record ExpPermissionGroups.GroupRecord
local function update_group(group_record) local function update_group(group_record)
assert(not group_record.isDeleted) assert(not group_record.is_deleted)
-- Try find the group by id and then then name -- Try find the group by id and then then name
local group = script_data.clusterio_id_to_group[group_record.id] local group = script_data.clusterio_id_to_group[group_record.id]
@@ -156,7 +154,7 @@ end
--- Delete the factorio permission group --- Delete the factorio permission group
--- @param group_record ExpPermissionGroups.GroupRecord --- @param group_record ExpPermissionGroups.GroupRecord
local function delete_group(group_record) local function delete_group(group_record)
assert(group_record.isDeleted) assert(group_record.is_deleted)
local default_group = get_default_group() local default_group = get_default_group()
local group = script_data.clusterio_id_to_group[group_record.id] local group = script_data.clusterio_id_to_group[group_record.id]
@@ -193,19 +191,19 @@ end
]] ]]
--- Restore local references to persistent script data after load --- Restore local references to persistent script data after load
function ExpPermissionGroups.on_load() function ExpGroups.on_load()
script_data = compat.script_data["exp_groups"] script_data = compat.script_data["exp_groups"]
end end
--- Enable or disable emitting lua changes back to the instance plugin --- Enable or disable emitting lua changes back to the instance plugin
--- @param enabled boolean? --- @param enabled boolean?
function ExpPermissionGroups.set_emit_events(enabled) function ExpGroups.set_emit_events(enabled)
script_data.emit_updates = enabled ~= false script_data.emit_updates = enabled ~= false
end end
--- Replace local state with expected controller state on startup --- Replace local state with expected controller state on startup
--- @param group_records ExpPermissionGroups.GroupRecord[] --- @param group_records ExpPermissionGroups.GroupRecord[]
function ExpPermissionGroups.initialise_groups(group_records) function ExpGroups.initialise_groups(group_records)
local _emit_events = script_data.emit_updates local _emit_events = script_data.emit_updates
script_data.emit_updates = false script_data.emit_updates = false
@@ -241,11 +239,11 @@ end
--- Receive an updated version of a group record --- Receive an updated version of a group record
--- @param group_record ExpPermissionGroups.GroupRecord --- @param group_record ExpPermissionGroups.GroupRecord
function ExpPermissionGroups.receive_group_update(group_record) function ExpGroups.receive_group_update(group_record)
local _emit_events = script_data.emit_updates local _emit_events = script_data.emit_updates
script_data.emit_updates = false script_data.emit_updates = false
if group_record.isDeleted then if group_record.is_deleted then
delete_group(group_record) delete_group(group_record)
else else
update_group(group_record) update_group(group_record)
@@ -256,7 +254,7 @@ end
--- Receive an updated version of a assignment record --- Receive an updated version of a assignment record
--- @param assignment_record ExpPermissionGroups.AssignmentRecord --- @param assignment_record ExpPermissionGroups.AssignmentRecord
function ExpPermissionGroups.receive_assignment_update(assignment_record) function ExpGroups.receive_assignment_update(assignment_record)
local _emit_events = script_data.emit_updates local _emit_events = script_data.emit_updates
script_data.emit_updates = false script_data.emit_updates = false
@@ -271,7 +269,7 @@ end
--- Get the current script data for debugging purposes --- Get the current script data for debugging purposes
--- @package --- @package
function ExpPermissionGroups._script_data() function ExpGroups._script_data()
return script_data return script_data
end end
@@ -332,7 +330,6 @@ local function flush_group_updates()
end end
-- Get all the groups and emit their updates -- Get all the groups and emit their updates
local get_group = game.permissions.get_group
for _, group in pairs(script_data.dirty_groups) do for _, group in pairs(script_data.dirty_groups) do
if group.valid then if group.valid then
emit_group_update(group) emit_group_update(group)
@@ -431,6 +428,6 @@ local on_nth_tick = {
[300] = on_nth_tick_flush, [300] = on_nth_tick_flush,
} }
ExpPermissionGroups.events = events --- @package ExpGroups.events = events --- @package
ExpPermissionGroups.on_nth_tick = on_nth_tick --- @package ExpGroups.on_nth_tick = on_nth_tick --- @package
return ExpPermissionGroups return ExpGroups
+9
View File
@@ -0,0 +1,9 @@
--[[
It is best practice to not expose any globals because all modules share a global environment
However, sometimes you need globals, for example to access functions within rcon commands
Therefore, we advise that this should be the only file in your module to expose globals
Typically this would be your control file as shown in the example below
]]
-- Access using `/sc exp_groups.foo()`
exp_groups = require("modules/exp_groups/control")