mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2026-07-26 18:36:23 +09:00
Add controller plugin
This commit is contained in:
+413
-1
@@ -1,6 +1,418 @@
|
|||||||
import * as lib from "@clusterio/lib";
|
|
||||||
import { BaseControllerPlugin } from "@clusterio/controller";
|
import { BaseControllerPlugin } from "@clusterio/controller";
|
||||||
|
import * as lib from "@clusterio/lib";
|
||||||
|
import * as messages from "./messages";
|
||||||
|
import * as path from "node:path";
|
||||||
|
|
||||||
export class ControllerPlugin extends BaseControllerPlugin {
|
export class ControllerPlugin extends BaseControllerPlugin {
|
||||||
|
groups!: lib.SubscribableDatastore<messages.GroupRecord>;
|
||||||
|
roleMappings!: lib.SubscribableDatastore<messages.RoleMappingRecord>;
|
||||||
|
manualAssignments!: lib.SubscribableDatastore<messages.AssignmentRecord>;
|
||||||
|
resolvedAssignments!: lib.SubscribableDatastore<messages.AssignmentRecord>;
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
const databaseDirectory = this.controller.config.get("controller.database_directory");
|
||||||
|
|
||||||
|
this.groups = new lib.SubscribableDatastore(
|
||||||
|
...await new lib.JsonIdDatastoreProvider(
|
||||||
|
path.join(databaseDirectory, "exp_groups", "groups.json"),
|
||||||
|
messages.GroupRecord.fromJSON.bind(messages.GroupRecord),
|
||||||
|
).bootstrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
this.roleMappings = new lib.SubscribableDatastore(
|
||||||
|
...await new lib.JsonIdDatastoreProvider(
|
||||||
|
path.join(databaseDirectory, "exp_groups", "role_mappings.json"),
|
||||||
|
messages.RoleMappingRecord.fromJSON.bind(messages.RoleMappingRecord),
|
||||||
|
).bootstrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
this.manualAssignments = new lib.SubscribableDatastore(
|
||||||
|
...await new lib.JsonIdDatastoreProvider(
|
||||||
|
path.join(databaseDirectory, "exp_groups", "assignments.json"),
|
||||||
|
messages.AssignmentRecord.fromJSON.bind(messages.AssignmentRecord),
|
||||||
|
).bootstrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
this.resolvedAssignments = new lib.SubscribableDatastore();
|
||||||
|
|
||||||
|
this.controller.subscriptions.handle(messages.GroupUpdatedEvent, this.handleGroupSubscription.bind(this));
|
||||||
|
this.controller.subscriptions.handle(messages.RoleMappingUpdatedEvent, this.handleRoleMappingSubscription.bind(this));
|
||||||
|
this.controller.subscriptions.handle(messages.ManualAssignmentUpdatedEvent, this.handleManualAssignmentSubscription.bind(this));
|
||||||
|
this.controller.subscriptions.handle(messages.ResolvedAssignmentUpdatedEvent, this.handleResolvedAssignmentSubscription.bind(this));
|
||||||
|
|
||||||
|
this.groups.on("update", this.groupsUpdated.bind(this));
|
||||||
|
this.roleMappings.on("update", this.roleMappingsUpdated.bind(this));
|
||||||
|
this.manualAssignments.on("update", this.manualAssignmentsUpdated.bind(this));
|
||||||
|
this.resolvedAssignments.on("update", this.resolvedAssignmentsUpdated.bind(this));
|
||||||
|
|
||||||
|
this.controller.handle(messages.GroupCreateRequest, this.handleGroupCreateRequest.bind(this));
|
||||||
|
this.controller.handle(messages.GroupUpdateRequest, this.handleGroupUpdateRequest.bind(this));
|
||||||
|
this.controller.handle(messages.GroupDeleteRequest, this.handleGroupDeleteRequest.bind(this));
|
||||||
|
this.controller.handle(messages.GroupGetRequest, this.handleGroupGetRequest.bind(this));
|
||||||
|
this.controller.handle(messages.GroupListRequest, this.handleGroupListRequest.bind(this));
|
||||||
|
|
||||||
|
this.controller.handle(messages.AssignmentCreateRequest, this.handleAssignmentCreateRequest.bind(this));
|
||||||
|
this.controller.handle(messages.AssignmentUpdateRequest, this.handleAssignmentUpdateRequest.bind(this));
|
||||||
|
this.controller.handle(messages.AssignmentDeleteRequest, this.handleAssignmentDeleteRequest.bind(this));
|
||||||
|
this.controller.handle(messages.AssignmentGetRequest, this.handleAssignmentGetRequest.bind(this));
|
||||||
|
this.controller.handle(messages.AssignmentListRequest, this.handleAssignmentListRequest.bind(this));
|
||||||
|
|
||||||
|
this.controller.handle(messages.RoleMappingCreateRequest, this.handleRoleMappingCreateRequest.bind(this));
|
||||||
|
this.controller.handle(messages.RoleMappingUpdateRequest, this.handleRoleMappingUpdateRequest.bind(this));
|
||||||
|
this.controller.handle(messages.RoleMappingDeleteRequest, this.handleRoleMappingDeleteRequest.bind(this));
|
||||||
|
this.controller.handle(messages.RoleMappingGetRequest, this.handleRoleMappingGetRequest.bind(this));
|
||||||
|
this.controller.handle(messages.RoleMappingListRequest, this.handleRoleMappingListRequest.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
async onShutdown() {
|
||||||
|
await Promise.all([
|
||||||
|
this.groups.save(),
|
||||||
|
this.manualAssignments.save(),
|
||||||
|
this.roleMappings.save(),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Subscriptions
|
||||||
|
*/
|
||||||
|
|
||||||
|
async groupsUpdated(groups: messages.GroupRecord[]) {
|
||||||
|
this.controller.subscriptions.broadcast(new messages.GroupUpdatedEvent(groups));
|
||||||
|
|
||||||
|
// We need to do extra work if the group was deleted
|
||||||
|
const deletedGroupIds = groups.filter(g => g.isDeleted).map(g => g.id);
|
||||||
|
if (!deletedGroupIds.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cascade the delete down to affected role mappings
|
||||||
|
const mappingsToDelete = [];
|
||||||
|
for (const mapping of this.roleMappings.values()) {
|
||||||
|
if (deletedGroupIds.includes(mapping.groupId)) {
|
||||||
|
mappingsToDelete.push(mapping);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mappingsToDelete.length) {
|
||||||
|
this.roleMappings.deleteMany(mappingsToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cascade the delete down to affected manual assignments
|
||||||
|
const affectedPlayers = new Set<string>();
|
||||||
|
const assignmentsToDelete = [];
|
||||||
|
for (const assignment of this.manualAssignments.values()) {
|
||||||
|
if (deletedGroupIds.includes(assignment.groupId)) {
|
||||||
|
assignmentsToDelete.push(assignment);
|
||||||
|
affectedPlayers.add(assignment.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (assignmentsToDelete.length) {
|
||||||
|
this.manualAssignments.deleteMany(assignmentsToDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find all the affected players who were assigned to this group
|
||||||
|
for (const resolved of this.resolvedAssignments.values()) {
|
||||||
|
if (deletedGroupIds.includes(resolved.groupId)) {
|
||||||
|
affectedPlayers.add(resolved.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (affectedPlayers.size) {
|
||||||
|
this.resolvedAssignments.setMany(await this.computeResolvedAssignments([...affectedPlayers]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleGroupSubscription(request: lib.SubscriptionRequest) {
|
||||||
|
const groups = [...this.groups.values()]
|
||||||
|
.filter(group => group.updatedAtMs > request.lastRequestTimeMs);
|
||||||
|
return groups.length ? new messages.GroupUpdatedEvent(groups) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async roleMappingsUpdated(roleMappings: messages.RoleMappingRecord[]) {
|
||||||
|
this.controller.subscriptions.broadcast(new messages.RoleMappingUpdatedEvent(roleMappings));
|
||||||
|
|
||||||
|
// Mappings pointing to a deleted group have already been handled
|
||||||
|
// But if any are active, then we still must recompute all assignments
|
||||||
|
let hasActiveGroup = false;
|
||||||
|
for (const roleMapping of roleMappings) {
|
||||||
|
const group = this.groups.get(roleMapping.groupId);
|
||||||
|
if (group && !group.isDeleted) {
|
||||||
|
hasActiveGroup = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasActiveGroup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Affected players are those without manual assignments
|
||||||
|
const affectedPlayers = [];
|
||||||
|
for (const resolved of this.resolvedAssignments.values()) {
|
||||||
|
if (!this.manualAssignments.has(resolved.name)) {
|
||||||
|
affectedPlayers.push(resolved.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (affectedPlayers.length) {
|
||||||
|
this.resolvedAssignments.setMany(await this.computeResolvedAssignments(affectedPlayers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleRoleMappingSubscription(request: lib.SubscriptionRequest) {
|
||||||
|
const mappings = [...this.roleMappings.values()]
|
||||||
|
.filter(mapping => mapping.updatedAtMs > request.lastRequestTimeMs);
|
||||||
|
return mappings.length ? new messages.RoleMappingUpdatedEvent(mappings) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async manualAssignmentsUpdated(assignments: messages.AssignmentRecord[]) {
|
||||||
|
this.controller.subscriptions.broadcast(new messages.ManualAssignmentUpdatedEvent(assignments));
|
||||||
|
|
||||||
|
// Assignments pointing to a deleted group have already been handled
|
||||||
|
const affectedPlayers: string[] = [];
|
||||||
|
for (const assignment of assignments) {
|
||||||
|
const group = this.groups.get(assignment.groupId);
|
||||||
|
if (!group || group.isDeleted) continue;
|
||||||
|
affectedPlayers.push(assignment.name);
|
||||||
|
}
|
||||||
|
if (affectedPlayers.length) {
|
||||||
|
this.resolvedAssignments.setMany(await this.computeResolvedAssignments(affectedPlayers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleManualAssignmentSubscription(request: lib.SubscriptionRequest) {
|
||||||
|
const assignments = [...this.resolvedAssignments.values()]
|
||||||
|
.filter(a => a.updatedAtMs > request.lastRequestTimeMs);
|
||||||
|
return assignments.length ? new messages.ManualAssignmentUpdatedEvent(assignments) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolvedAssignmentsUpdated(assignments: messages.AssignmentRecord[]) {
|
||||||
|
this.controller.subscriptions.broadcast(
|
||||||
|
new messages.ResolvedAssignmentUpdatedEvent(assignments),
|
||||||
|
assignments.map(assignment => assignment.name),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleResolvedAssignmentSubscription(request: lib.SubscriptionRequest) {
|
||||||
|
// Check for any missing assignments to be computed on demand
|
||||||
|
const filters = Array.isArray(request.filters) ? request.filters : [request.filters!];
|
||||||
|
if (request.filters && filters.length) {
|
||||||
|
const missing = filters.filter(name => !this.resolvedAssignments.has(name));
|
||||||
|
if (missing.length) {
|
||||||
|
this.resolvedAssignments.setMany(await this.computeResolvedAssignments(missing));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter the assignments
|
||||||
|
const assignments = (filters.length
|
||||||
|
? filters.map(name => this.resolvedAssignments.get(name)).filter(Boolean)
|
||||||
|
: [...this.resolvedAssignments.values()]
|
||||||
|
).filter(a => a.updatedAtMs > request.lastRequestTimeMs);
|
||||||
|
|
||||||
|
return assignments.length ? new messages.ResolvedAssignmentUpdatedEvent(assignments) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Groups
|
||||||
|
*/
|
||||||
|
|
||||||
|
async handleGroupListRequest() {
|
||||||
|
return [...this.groups.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleGroupCreateRequest(request: messages.GroupCreateRequest) {
|
||||||
|
if ([...this.groups.values()].some(g => g.name === request.name)) {
|
||||||
|
throw new lib.RequestError(`Group with name '${request.name}' already exists`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = Math.random() * 2**31 | 0;
|
||||||
|
while (this.groups.has(id)) {
|
||||||
|
id = Math.random() * 2**31 | 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const group = new messages.GroupRecord(id, request.name, request.permissions);
|
||||||
|
this.groups.set(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleGroupUpdateRequest(request: messages.GroupUpdateRequest) {
|
||||||
|
const group = request.group;
|
||||||
|
if (group.id === undefined || !this.groups.has(group.id)) {
|
||||||
|
throw new lib.RequestError(`Group with ID ${group.id} does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.groups.set(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleGroupDeleteRequest(request: messages.GroupDeleteRequest) {
|
||||||
|
const { groupId } = request;
|
||||||
|
|
||||||
|
const group = this.groups.getMutable(groupId);
|
||||||
|
if (!group) {
|
||||||
|
throw new lib.RequestError(`Group with ID ${groupId} does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.groups.delete(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleGroupGetRequest(request: messages.GroupGetRequest) {
|
||||||
|
const group = this.groups.get(request.groupId);
|
||||||
|
if (!group) {
|
||||||
|
throw new lib.RequestError(`Group with ID ${request.groupId} does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Groups
|
||||||
|
*/
|
||||||
|
|
||||||
|
async handleAssignmentListRequest() {
|
||||||
|
return [...this.manualAssignments.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleAssignmentCreateRequest(request: messages.AssignmentCreateRequest) {
|
||||||
|
const { name, groupId } = request;
|
||||||
|
if (this.manualAssignments.has(name)) {
|
||||||
|
throw new lib.RequestError(`Assignment for '${name}' already exists`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const assignment = new messages.AssignmentRecord(name, groupId);
|
||||||
|
this.manualAssignments.set(assignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleAssignmentUpdateRequest(request: messages.AssignmentUpdateRequest) {
|
||||||
|
const assignment = request.assignment;
|
||||||
|
if (!this.manualAssignments.has(assignment.name)) {
|
||||||
|
throw new lib.RequestError(`Assignment for '${assignment.name}' does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.manualAssignments.set(assignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleAssignmentDeleteRequest(request: messages.AssignmentDeleteRequest) {
|
||||||
|
const { name } = request;
|
||||||
|
|
||||||
|
const assignment = this.manualAssignments.getMutable(name);
|
||||||
|
if (!assignment) {
|
||||||
|
throw new lib.RequestError(`Assignment for '${name}' does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.manualAssignments.delete(assignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleAssignmentGetRequest(request: messages.AssignmentGetRequest) {
|
||||||
|
if (request.resolve) {
|
||||||
|
let assignment = this.resolvedAssignments.get(request.name);
|
||||||
|
if (!assignment) {
|
||||||
|
assignment = await this.computeResolvedAssignment(request.name);
|
||||||
|
this.resolvedAssignments.set(assignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
return assignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
const assignment = this.manualAssignments.get(request.name);
|
||||||
|
if (!assignment) {
|
||||||
|
throw new lib.RequestError(`Assignment for '${request.name}' does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return assignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Role mappings
|
||||||
|
*/
|
||||||
|
|
||||||
|
async handleRoleMappingListRequest() {
|
||||||
|
return [...this.roleMappings.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleRoleMappingCreateRequest(request: messages.RoleMappingCreateRequest) {
|
||||||
|
let id = Math.random() * 2**31 | 0;
|
||||||
|
while (this.roleMappings.has(id)) {
|
||||||
|
id = Math.random() * 2**31 | 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapping = new messages.RoleMappingRecord(
|
||||||
|
id,
|
||||||
|
new Set(request.roleIds),
|
||||||
|
request.groupId,
|
||||||
|
request.priority,
|
||||||
|
request.enabled,
|
||||||
|
);
|
||||||
|
|
||||||
|
this.roleMappings.set(mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleRoleMappingUpdateRequest(request: messages.RoleMappingUpdateRequest) {
|
||||||
|
const mapping = request.roleMapping;
|
||||||
|
if (mapping.id === undefined || !this.roleMappings.has(mapping.id)) {
|
||||||
|
throw new lib.RequestError(`Role mapping with ID ${mapping.id} does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.roleMappings.set(mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleRoleMappingDeleteRequest(request: messages.RoleMappingDeleteRequest) {
|
||||||
|
const { id } = request;
|
||||||
|
|
||||||
|
const mapping = this.roleMappings.getMutable(id);
|
||||||
|
if (!mapping) {
|
||||||
|
throw new lib.RequestError(`Role mapping with ID ${id} does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.roleMappings.delete(mapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleRoleMappingGetRequest(request: messages.RoleMappingGetRequest) {
|
||||||
|
const mapping = this.roleMappings.get(request.id);
|
||||||
|
if (!mapping) {
|
||||||
|
throw new lib.RequestError(`Role mapping with ID ${request.id} does not exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Calculating assignments
|
||||||
|
*/
|
||||||
|
|
||||||
|
async computeResolvedAssignment(playerName: string): Promise<messages.AssignmentRecord> {
|
||||||
|
// 1) Manual override
|
||||||
|
const manual = this.manualAssignments.get(playerName);
|
||||||
|
if (manual) {
|
||||||
|
return manual;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = this.controller.users.get(playerName);
|
||||||
|
const userRoles = user?.roleIds ?? new Set<number>();
|
||||||
|
|
||||||
|
// 2) Role mappings
|
||||||
|
let best: messages.RoleMappingRecord | null = null;
|
||||||
|
for (const mapping of this.roleMappings.values()) {
|
||||||
|
if (!mapping.enabled) continue;
|
||||||
|
|
||||||
|
let matches = true;
|
||||||
|
for (const roleId of mapping.roleIds) {
|
||||||
|
if (!userRoles.has(roleId)) {
|
||||||
|
matches = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matches) continue;
|
||||||
|
|
||||||
|
if (!best || mapping.priority > best.priority) {
|
||||||
|
best = mapping;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best) {
|
||||||
|
return new messages.AssignmentRecord(playerName, best.groupId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Default (deleted assignment, assigns to 'Default' in game)
|
||||||
|
return new messages.AssignmentRecord(playerName, 0, 0, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
async computeResolvedAssignments(playerNames: string[]): Promise<messages.AssignmentRecord[]> {
|
||||||
|
return Promise.all(playerNames.map(name => this.computeResolvedAssignment(name)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -133,7 +133,7 @@ export const plugin: lib.PluginDeclaration = {
|
|||||||
|
|
||||||
messages: [
|
messages: [
|
||||||
messages.GroupUpdatedEvent,
|
messages.GroupUpdatedEvent,
|
||||||
messages.AssignmentUpdatedEvent,
|
messages.ManualAssignmentUpdatedEvent,
|
||||||
messages.RoleMappingUpdatedEvent,
|
messages.RoleMappingUpdatedEvent,
|
||||||
|
|
||||||
messages.GroupCreateRequest,
|
messages.GroupCreateRequest,
|
||||||
|
|||||||
+28
-23
@@ -18,9 +18,15 @@ export type IpcPlayerAssignments = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class InstancePlugin extends BaseInstancePlugin {
|
export class InstancePlugin extends BaseInstancePlugin {
|
||||||
|
// Once only, don't send permissions for these groups
|
||||||
|
// This is used for groups created on this instance that only need the controller generated id
|
||||||
|
skipSendingPermissions = new Set<string>();
|
||||||
|
// Track known online players so that we only apply assignment updates for them
|
||||||
|
onlinePlayers = new Set<string>();
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
this.instance.handle(messages.GroupUpdatedEvent, this.handleGroupUpdatedEvent.bind(this));
|
this.instance.handle(messages.GroupUpdatedEvent, this.handleGroupUpdatedEvent.bind(this));
|
||||||
this.instance.handle(messages.AssignmentUpdatedEvent, this.handleAssignmentUpdatedEvent.bind(this));
|
this.instance.handle(messages.ResolvedAssignmentUpdatedEvent, this.handleResolvedAssignmentUpdatedEvent.bind(this));
|
||||||
this.instance.server.handle(`exp_group:group_updated`, this.handleGroupUpdatedIPC.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:group_deleted`, this.handleGroupDeletedIPC.bind(this))
|
||||||
this.instance.server.handle(`exp_group:player_assignments`, this.handlePlayerAssignmentsIPC.bind(this))
|
this.instance.server.handle(`exp_group:player_assignments`, this.handlePlayerAssignmentsIPC.bind(this))
|
||||||
@@ -35,8 +41,9 @@ export class InstancePlugin extends BaseInstancePlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onStart() {
|
async onStart() {
|
||||||
|
// We use Date.now() because we need to manually initialise the groups on the lua side
|
||||||
await this.instance.sendTo("controller", new lib.SubscriptionRequest(
|
await this.instance.sendTo("controller", new lib.SubscriptionRequest(
|
||||||
`exp_groups:${messages.GroupUpdatedEvent.name}`, true
|
`exp_groups:${messages.GroupUpdatedEvent.name}`, true, Date.now()
|
||||||
));
|
));
|
||||||
const groups = await this.instance.sendTo("controller", new messages.GroupListRequest())
|
const groups = await this.instance.sendTo("controller", new messages.GroupListRequest())
|
||||||
await this.luaSendInitialGroups(groups);
|
await this.luaSendInitialGroups(groups);
|
||||||
@@ -46,32 +53,27 @@ export class InstancePlugin extends BaseInstancePlugin {
|
|||||||
async onPlayerEvent(event: lib.PlayerEvent) {
|
async onPlayerEvent(event: lib.PlayerEvent) {
|
||||||
switch(event.type) {
|
switch(event.type) {
|
||||||
case "join":
|
case "join":
|
||||||
this.handlePlayerJoin(event.name);
|
this.onlinePlayers.add(event.name);
|
||||||
|
await this.subscribePlayerAssignment(event.name);
|
||||||
break;
|
break;
|
||||||
case "leave":
|
case "leave":
|
||||||
this.handlePlayerLeave(event.name);
|
this.onlinePlayers.delete(event.name);
|
||||||
|
await this.unsubscribePlayerAssignment(event.name);
|
||||||
break;
|
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) {
|
async handleGroupUpdatedEvent(event: messages.GroupUpdatedEvent) {
|
||||||
for (const group of event.updates) {
|
for (const group of event.updates) {
|
||||||
await this.luaSendGroupUpdate(group);
|
await this.luaSendGroupUpdate(group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleAssignmentUpdatedEvent(event: messages.AssignmentUpdatedEvent) {
|
async handleResolvedAssignmentUpdatedEvent(event: messages.ManualAssignmentUpdatedEvent) {
|
||||||
for (const assignment of event.updates) {
|
for (const assignment of event.updates) {
|
||||||
await this.luaSendAssignmentUpdate(assignment);
|
if (this.onlinePlayers.has(assignment.name)) {
|
||||||
|
await this.luaSendAssignmentUpdate(assignment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +84,7 @@ export class InstancePlugin extends BaseInstancePlugin {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (event.group_id === undefined) {
|
if (event.group_id === undefined) {
|
||||||
|
this.skipSendingPermissions.add(event.group_name);
|
||||||
await this.instance.sendTo("controller",
|
await this.instance.sendTo("controller",
|
||||||
new messages.GroupCreateRequest(event.group_name, permissions),
|
new messages.GroupCreateRequest(event.group_name, permissions),
|
||||||
);
|
);
|
||||||
@@ -111,21 +114,16 @@ export class InstancePlugin extends BaseInstancePlugin {
|
|||||||
|
|
||||||
async subscribePlayerAssignment(playerName: string) {
|
async subscribePlayerAssignment(playerName: string) {
|
||||||
await this.instance.sendTo("controller", new lib.SubscriptionRequest(
|
await this.instance.sendTo("controller", new lib.SubscriptionRequest(
|
||||||
`exp_groups:${messages.AssignmentUpdatedEvent.name}`, true, 0, playerName
|
`exp_groups:${messages.ResolvedAssignmentUpdatedEvent.name}`, true, 0, playerName
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
async unsubscribePlayerAssignment(playerName: string) {
|
async unsubscribePlayerAssignment(playerName: string) {
|
||||||
await this.instance.sendTo("controller", new lib.SubscriptionRequest(
|
await this.instance.sendTo("controller", new lib.SubscriptionRequest(
|
||||||
`exp_groups:${messages.AssignmentUpdatedEvent.name}`, false, 0, playerName
|
`exp_groups:${messages.ResolvedAssignmentUpdatedEvent.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[]) {
|
async luaSendInitialGroups(groups: messages.GroupRecord[]) {
|
||||||
if (this.instance.config.get("exp_groups.sync_mode") == "disabled") {
|
if (this.instance.config.get("exp_groups.sync_mode") == "disabled") {
|
||||||
return;
|
return;
|
||||||
@@ -137,7 +135,14 @@ export class InstancePlugin extends BaseInstancePlugin {
|
|||||||
if (this.instance.config.get("exp_groups.sync_mode") == "disabled") {
|
if (this.instance.config.get("exp_groups.sync_mode") == "disabled") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this.instance.sendRcon(`/sc exp_groups.receive_group_update(helpers.json_to_table${JSON.stringify(group)})`)
|
|
||||||
|
const json = group.toJSON();
|
||||||
|
if (this.skipSendingPermissions.has(group.name)) {
|
||||||
|
this.skipSendingPermissions.delete(group.name);
|
||||||
|
delete (json as any).permissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.instance.sendRcon(`/sc exp_groups.receive_group_update(helpers.json_to_table${JSON.stringify(json)})`)
|
||||||
}
|
}
|
||||||
|
|
||||||
async luaSendAssignmentUpdate(assignment: messages.AssignmentRecord) {
|
async luaSendAssignmentUpdate(assignment: messages.AssignmentRecord) {
|
||||||
|
|||||||
+35
-3
@@ -85,6 +85,10 @@ export class AssignmentRecord {
|
|||||||
public isDeleted: boolean = false,
|
public isDeleted: boolean = false,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
get id() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
static jsonSchema = Type.Object({
|
static jsonSchema = Type.Object({
|
||||||
name: Type.String(),
|
name: Type.String(),
|
||||||
group_id: Type.Integer(),
|
group_id: Type.Integer(),
|
||||||
@@ -202,8 +206,33 @@ export class GroupUpdatedEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AssignmentUpdatedEvent {
|
export class ManualAssignmentUpdatedEvent {
|
||||||
declare ["constructor"]: typeof AssignmentUpdatedEvent;
|
declare ["constructor"]: typeof ManualAssignmentUpdatedEvent;
|
||||||
|
static plugin = "exp_groups" as const;
|
||||||
|
static type = "event" as const;
|
||||||
|
static src = "controller" as const;
|
||||||
|
static dst = ["control", "instance"] as const;
|
||||||
|
static permission = "exp_groups.assignment.subscribe" as const;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public updates: AssignmentRecord[],
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static jsonSchema = Type.Object({
|
||||||
|
updates: Type.Array(AssignmentRecord.jsonSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
toJSON() {
|
||||||
|
return { updates: this.updates.map(assignment => assignment.toJSON()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
|
return new this(json.updates.map(assignment => AssignmentRecord.fromJSON(assignment)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ResolvedAssignmentUpdatedEvent {
|
||||||
|
declare ["constructor"]: typeof ResolvedAssignmentUpdatedEvent;
|
||||||
static plugin = "exp_groups" as const;
|
static plugin = "exp_groups" as const;
|
||||||
static type = "event" as const;
|
static type = "event" as const;
|
||||||
static src = "controller" as const;
|
static src = "controller" as const;
|
||||||
@@ -486,20 +515,23 @@ export class AssignmentGetRequest {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public name: string,
|
public name: string,
|
||||||
|
public resolve: boolean = false,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
static jsonSchema = Type.Object({
|
static jsonSchema = Type.Object({
|
||||||
name: Type.String(),
|
name: Type.String(),
|
||||||
|
resolve: Type.Boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
name: this.name,
|
name: this.name,
|
||||||
|
resolve: this.resolve,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||||
return new this(json.name);
|
return new this(json.name, json.resolve);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user