Improve config, message, and module templates (#641)

* Improve config, message, and module templates

* Update global handler in template module

* Add comments to empty instance template
This commit is contained in:
Cooldude2606 authored and GitHub committed 2024-06-08 13:53:23 +01:00
1 parent 51c450727d
commit 498638c0c9
17 files changed
+224 -56

No files matched your search

+1
View File
@@ -699,6 +699,7 @@ async function inquirerMissingArgs(args) {
{ name: "Command Line", value: "ctl" },
{ name: "Web UI", value: "web" },
{ name: "No Typescript", value: "js" },
{ name: "No Config", value: "no_config" },
],
},
{
+28 -5
View File
@@ -76,8 +76,15 @@ async function copyPluginTemplates(pluginName, templates) {
const files = new Map();
const prepare = [];
// Some basic flags for selecting files to include
const javascriptOnly = templates.includes("js");
const webpack = templates.includes("controller") || templates.includes("web");
const config = !templates.includes("no_config") && templates.some(value => (
["controller", "host", "instance", "ctl"].includes(value)
));
const webpack = config || templates.includes("controller") || templates.includes("web");
// A count of the number of isolated contexts the plugin runs under
let pluginContexts = 0;
// Get the file extension and path to the templates
const ext = javascriptOnly ? "js" : "ts";
@@ -88,7 +95,6 @@ async function copyPluginTemplates(pluginName, templates) {
files.set(".gitignore", path.join(commonPath, ".gitignore"));
files.set(".npmignore", path.join(commonPath, ".npmignore"));
files.set("package.json", path.join(commonPath, "package.json"));
files.set(`messages.${ext}`, path.join(templatePath, `messages.${ext}`));
files.set(`index.${ext}`, path.join(templatePath, `index.${ext}`));
// Files and dependencies to support typescript
@@ -105,24 +111,30 @@ async function copyPluginTemplates(pluginName, templates) {
if (webpack) {
prepare.push("webpack-cli --env production");
files.set("webpack.config.js", path.join(commonPath, "webpack.config.js"));
files.set(`web/index.${ext}x`, path.join(templatePath,
templates.includes("web") ? `web/plugin.${ext}x` : `web/no_plugin.${ext}x`
));
if (templates.includes("web")) {
files.set(`web/index.${ext}x`, path.join(templatePath, `web/plugin.${ext}x`));
pluginContexts += 1;
} else {
files.set(`web/index.${ext}x`, path.join(templatePath, `web/no_plugin.${ext}x`));
}
}
// Files for the controller
if (templates.includes("controller")) {
files.set(`controller.${ext}`, path.join(templatePath, `controller.${ext}`));
pluginContexts += 1;
}
// Files for hosts
if (templates.includes("host")) {
files.set(`host.${ext}`, path.join(templatePath, `host.${ext}`));
pluginContexts += 1;
}
// Files for instances
if (templates.includes("instance")) {
files.set(`instance.${ext}`, path.join(templatePath, `instance.${ext}`));
pluginContexts += 1;
}
// Files for lua modules
@@ -133,16 +145,26 @@ async function copyPluginTemplates(pluginName, templates) {
files.set("module/module_exports.lua", path.join(commonPath, "module/module_exports.lua"));
if (templates.includes("instance")) {
files.set("module/globals.lua", path.join(commonPath, "module/globals.lua"));
} else {
files.set(`instance.${ext}`, path.join(templatePath, `instance_empty.${ext}`));
pluginContexts += 1;
}
}
// Files for the control tool
if (templates.includes("ctl")) {
files.set(`ctl.${ext}`, path.join(templatePath, `ctl.${ext}`));
pluginContexts += 1;
}
// If there are more than one contexts then include the message file
if (pluginContexts > 1) {
files.set(`messages.${ext}`, path.join(templatePath, `messages.${ext}`));
}
// Properties that will control the replacements in the templates
const properties = {
config: config,
typescript: !javascriptOnly,
controller: templates.includes("controller"),
host: templates.includes("host"),
@@ -150,6 +172,7 @@ async function copyPluginTemplates(pluginName, templates) {
module: templates.includes("module"),
ctl: templates.includes("ctl"),
web: templates.includes("web"),
multi_context: pluginContexts > 1,
plugin_name: pluginName,
prepare: prepare.join(" && "),
ext: ext,
@@ -6,8 +6,10 @@ local MyModule = {
on_nth_tick = {},
}
--- global is 'synced' between players, you should use your plugin name to avoid conflicts
-- setupGlobalData should either be removed or called during clusterio_api.events.on_server_startup
--- global is 'synced' between players, you should use your plugin name to avoid name conflicts
-- The following helper function is desync safe and avoids name conflicts after being called during on_server_startup
-- There are edge cases in the following events: pre player left, player left, console command, and config changed
-- If you use any of these events and access globalData then make sure to call this function first
local globalData = {}
local function setupGlobalData()
if global["__plugin_name__"] == nil then
@@ -15,6 +17,12 @@ local function setupGlobalData()
-- starting values go here
}
end
MyModule.on_load()
end
--- The on_load function is called independently for each client when they first load the map
-- It should be used to restore global aliases and metatables not registered with script.register_metatable
function MyModule.on_load()
globalData = global["__plugin_name__"]
end
@@ -28,7 +36,7 @@ local function bar()
game.print("bar")
end
--- Clusterio provides a few custom events, on_server_startup is the most useful and should be used in place of on_load
--- Clusterio provides a few custom events, on_server_startup is the most useful and should be used in place of on_init
MyModule.events[clusterio_api.events.on_server_startup] = function(event)
setupGlobalData()
game.print(game.table_to_json(event))
@@ -1,13 +1,17 @@
"use strict";
const lib = require("@clusterio/lib");
const { BaseControllerPlugin } = require("@clusterio/controller");
//%if multi_context // Subscribing requires multi context
const {
PluginExampleEvent, PluginExampleRequest,
//%endif
//%if controller & web // Subscribing requires web content and the controller
ExampleSubscribableUpdate, ExampleSubscribableValue,
//%endif
//%if multi_context // Subscribing requires multi context
} = require("./messages");
//%endif
class ControllerPlugin extends BaseControllerPlugin {
//%if controller & web // Subscribing requires web content and the controller
@@ -15,14 +19,18 @@ class ControllerPlugin extends BaseControllerPlugin {
storageDirty = false;
//%endif
//%if multi_context // Subscribing requires multi context
async init() {
this.controller.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
this.controller.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
//%endif
//%if controller & web // Subscribing requires web content and the controller
this.controller.subscriptions.handle(ExampleSubscribableUpdate, this.handleExampleSubscription.bind(this));
this.exampleDatabase = new Map(); // If needed, replace with loading from database file
//%endif
//%if multi_context // Subscribing requires multi context
}
//%endif
async onControllerConfigFieldChanged(field, curr, prev) {
this.logger.info(`controller::onControllerConfigFieldChanged ${field}`);
@@ -45,6 +53,7 @@ class ControllerPlugin extends BaseControllerPlugin {
async onPlayerEvent(instance, event) {
this.logger.info(`controller::onPlayerEvent ${instance.id} ${JSON.stringify(event)}`);
}
//%if multi_context
async handlePluginExampleEvent(event) {
this.logger.info(JSON.stringify(event));
@@ -57,6 +66,7 @@ class ControllerPlugin extends BaseControllerPlugin {
myResponseNumbers: request.myNumberArray,
};
}
//%endif
//%if controller & web // Subscribing requires web content and the controller
async handleExampleSubscription(request) {
@@ -1,6 +1,7 @@
"use strict";
const { BaseCtlPlugin } = require("@clusterio/ctl");
const { CommandTree, Command } = require("@clusterio/lib");
//%// We do not check for multi context here because it doesn't make sense to have a ctl without messages
const { PluginExampleEvent, PluginExampleRequest } = require("./messages");
/* eslint-disable no-console */
@@ -1,13 +1,17 @@
"use strict";
const lib = require("@clusterio/lib");
const { BaseHostPlugin } = require("@clusterio/host");
//%if multi_context
const { PluginExampleEvent, PluginExampleRequest } = require("./messages");
//%endif
class HostPlugin extends BaseHostPlugin {
//%if multi_context
async init() {
this.host.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
this.host.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
}
//%endif
async onHostConfigFieldChanged(field, curr, prev) {
this.logger.info(`host::onInstanceConfigFieldChanged ${field}`);
@@ -16,6 +20,7 @@ class HostPlugin extends BaseHostPlugin {
async onShutdown() {
this.logger.info("host::onShutdown");
}
//%if multi_context
async handlePluginExampleEvent(event) {
this.logger.info(JSON.stringify(event));
@@ -28,6 +33,7 @@ class HostPlugin extends BaseHostPlugin {
myResponseNumbers: request.myNumberArray,
};
}
//%endif
}
module.exports = {
+30 -13
View File
@@ -1,5 +1,6 @@
"use strict";
const lib = require("@clusterio/lib");
//%if multi_context
const Messages = require("./messages");
lib.definePermission({
@@ -13,6 +14,7 @@ lib.definePermission({
title: "Example permission request",
description: "Example Description. Request. Change me in index.js",
});
//%endif
//%if controller & web // Subscribing requires web content and the controller
lib.definePermission({
@@ -34,9 +36,13 @@ const plugin = {
name: "__plugin_name__",
title: "__plugin_name__",
description: "Example Description. Plugin. Change me in index.js",
//%if controller
//%if controller | host & !config | instance & !config | ctl & !config // Blank line for formatting
controllerEntrypoint: "./controller",
//%endif
//%if controller
controllerEntrypoint: "./dist/node/controller",
//%endif
//%if controller & config
controllerConfigFields: {
"__plugin_name__.myControllerField": {
title: "My Controller Field",
@@ -46,9 +52,13 @@ const plugin = {
},
},
//%endif
//%if host
//%if host & config // Blank line for formatting
hostEntrypoint: "./host",
//%endif
//%if host
hostEntrypoint: "./dist/node/host",
//%endif
//%if host & config
hostConfigFields: {
"__plugin_name__.myHostField": {
title: "My Host Field",
@@ -58,9 +68,13 @@ const plugin = {
},
},
//%endif
//%if instance
//%if instance & config | module & config // Blank line for formatting
instanceEntrypoint: "./instance",
//%endif
//%if instance | module // Modules load an empty instance plugin
instanceEntrypoint: "./dist/node/instance",
//%endif
//%if instance & config
instanceConfigFields: {
"__plugin_name__.myInstanceField": {
title: "My Instance Field",
@@ -70,9 +84,13 @@ const plugin = {
},
},
//%endif
//%if ctl
//%if ctl & config // Blank line for formatting
ctlEntrypoint: "./ctl",
//%endif
//%if ctl
ctlEntrypoint: "./dist/node/ctl",
//%endif
//%if ctl & config
controlConfigFields: {
"__plugin_name__.myControlField": {
title: "My Control Field",
@@ -82,14 +100,18 @@ const plugin = {
},
},
//%endif
//%if multi_context // Subscribing requires multi context
messages: [
Messages.PluginExampleEvent,
Messages.PluginExampleRequest,
//%endif
//%if controller & web // Subscribing requires web content and the controller
Messages.ExampleSubscribableUpdate,
//%endif
//%if multi_context // Subscribing requires multi context
],
//%endif
//%if web // Web content template has an example route which is the plugin name
webEntrypoint: "./web",
@@ -97,11 +119,6 @@ const plugin = {
"/__plugin_name__",
],
//%endif
//%if controller & !web // The controller always includes web entry even if there is no content
webEntrypoint: "./web",
routes: [],
//%endif
};
module.exports = {
@@ -1,16 +1,23 @@
"use strict";
const lib = require("@clusterio/lib");
const { BaseInstancePlugin } = require("@clusterio/host");
//%if multi_context
const { PluginExampleEvent, PluginExampleRequest } = require("./messages");
//%endif
class InstancePlugin extends BaseInstancePlugin {
//%if multi_context | module
async init() {
//%if multi_context
this.instance.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
this.instance.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
//%endif
//%if module
this.instance.server.handle("__plugin_name__-plugin_example_ipc", this.handlePluginExampleIPC.bind(this));
//%endif
//%if multi_context | module
}
//%endif
async onInstanceConfigFieldChanged(field, curr, prev) {
this.logger.info(`instance::onInstanceConfigFieldChanged ${field}`);
@@ -26,13 +33,16 @@ class InstancePlugin extends BaseInstancePlugin {
async onPlayerEvent(event) {
this.logger.info(`onPlayerEvent::onPlayerEvent ${JSON.stringify(event)}`);
//%if module
if (this.instance.status === "running") {
this.sendRcon("/sc __plugin_name__.foo()");
}
//%endif
}
//%if multi_context
async handlePluginExampleEvent(event) {
this.logger.info(JSON.stringify(event));
//%if module
this.sendRcon("/sc __plugin_name__.foo()");
//%endif
}
async handlePluginExampleRequest(request) {
@@ -42,6 +52,7 @@ class InstancePlugin extends BaseInstancePlugin {
myResponseNumbers: request.myNumberArray,
};
}
//%endif
//%if module
async handlePluginExampleIPC(event) {
@@ -0,0 +1,11 @@
"use strict";
const { BaseInstancePlugin } = require("@clusterio/host");
class InstancePlugin extends BaseInstancePlugin {
// This class is empty because an instance plugin must be defined for a module to be injected
// This requirement may change in the future to allow for standalone modules
}
module.exports = {
InstancePlugin,
};
@@ -1,6 +1,6 @@
import React, {
useContext, useEffect, useState,
//%if controller & web // Subscribing requires web content and the controller
//%if controller // Subscribing requires web content and the controller
useCallback, useSyncExternalStore,
//%endif
} from "react";
@@ -12,33 +12,37 @@ import React, {
import {
BaseWebPlugin, PageLayout, Control, ControlContext, notifyErrorHandler,
} from "@clusterio/web_ui";
//%if multi_context // Subscribing requires multi context
import {
PluginExampleEvent, PluginExampleRequest,
//%if controller & web // Subscribing requires web content and the controller
ExampleSubscribableUpdate,
//%endif
//%if controller // Subscribing requires web content and the controller
ExampleSubscribableUpdate, ExampleSubscribableValue,
//%endif
//%if multi_context // Subscribing requires multi context
} from "../messages";
//%endif
import * as lib from "@clusterio/lib";
function MyTemplatePage() {
let control = useContext(ControlContext);
//%if controller & web // Subscribing requires web content and the controller
//%if controller // Subscribing requires web content and the controller
const plugin = control.plugins.get("__plugin_name__");
const [subscribableData, synced] = plugin.useSubscribableData();
//%endif
return <PageLayout nav={[{ name: "__plugin_name__" }]}>
<h2>__plugin_name__</h2>
//%if controller & web // Subscribing requires web content and the controller
//%if controller // Subscribing requires web content and the controller
Synced: {String(synced)} Data: {JSON.stringify([...subscribableData.values()])}
//%endif
</PageLayout>;
}
export class WebPlugin extends BaseWebPlugin {
//%if controller & web // Subscribing requires web content and the controller
//%if controller // Subscribing requires web content and the controller
subscribableData = new lib.EventSubscriber(ExampleSubscribableUpdate, this.control);
//%endif
@@ -47,15 +51,24 @@ export class WebPlugin extends BaseWebPlugin {
{
path: "/__plugin_name__",
sidebarName: "__plugin_name__",
permission: "__plugin_name__.page.view",
// This permission is client side only, so it must match the permission string of a resource request to be secure
// An undefined value means that the page will always be visible
//%if controller // Subscribing requires web content and the controller
permission: "__plugin_name__.example.permission.subscribe",
//%endif
//%if !controller
permission: undefined, // "__plugin_name__.example.permission.request",
//%endif
content: <MyTemplatePage/>,
},
];
//%if multi_context
this.control.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
this.control.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
//%endif
}
//%if controller & web // Subscribing requires web content and the controller
//%if controller// Subscribing requires web content and the controller
useSubscribableData() {
const control = useContext(ControlContext);
@@ -63,6 +76,7 @@ export class WebPlugin extends BaseWebPlugin {
return useSyncExternalStore(subscribe, () => this.subscribableData.getSnapshot());
}
//%endif
//%if multi_context
async handlePluginExampleEvent(event) {
this.logger.info(JSON.stringify(event));
@@ -75,4 +89,5 @@ export class WebPlugin extends BaseWebPlugin {
myResponseNumbers: request.myNumberArray,
};
}
//%endif
}
@@ -1,12 +1,16 @@
import * as lib from "@clusterio/lib";
import { BaseControllerPlugin, InstanceInfo } from "@clusterio/controller";
//%if multi_context // Subscribing requires multi context
import {
PluginExampleEvent, PluginExampleRequest,
//%endif
//%if controller & web // Subscribing requires web content and the controller
ExampleSubscribableUpdate, ExampleSubscribableValue,
//%endif
//%if multi_context // Subscribing requires multi context
} from "./messages";
//%endif
export class ControllerPlugin extends BaseControllerPlugin {
//%if controller & web // Subscribing requires web content and the controller
@@ -14,14 +18,18 @@ export class ControllerPlugin extends BaseControllerPlugin {
storageDirty = false;
//%endif
//%if multi_context // Subscribing requires multi context
async init() {
this.controller.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
this.controller.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
//%endif
//%if controller & web // Subscribing requires web content and the controller
this.controller.subscriptions.handle(ExampleSubscribableUpdate, this.handleExampleSubscription.bind(this));
this.exampleDatabase = new Map(); // If needed, replace with loading from database file
//%endif
//%if multi_context // Subscribing requires multi context
}
//%endif
async onControllerConfigFieldChanged(field: string, curr: unknown, prev: unknown) {
this.logger.info(`controller::onControllerConfigFieldChanged ${field}`);
@@ -44,6 +52,7 @@ export class ControllerPlugin extends BaseControllerPlugin {
async onPlayerEvent(instance: InstanceInfo, event: lib.PlayerEvent) {
this.logger.info(`controller::onPlayerEvent ${instance.id} ${JSON.stringify(event)}`);
}
//%if multi_context
async handlePluginExampleEvent(event: PluginExampleEvent) {
this.logger.info(JSON.stringify(event));
@@ -56,6 +65,7 @@ export class ControllerPlugin extends BaseControllerPlugin {
myResponseNumbers: request.myNumberArray,
};
}
//%endif
//%if controller & web // Subscribing requires web content and the controller
async handleExampleSubscription(request: lib.SubscriptionRequest) {
+2 -2
View File
@@ -1,6 +1,6 @@
type Control = any; // import type { Control } from "@clusterio/ctl";
import { BaseCtlPlugin } from "@clusterio/ctl";
import { BaseCtlPlugin, type Control } from "@clusterio/ctl";
import { CommandTree, Command } from "@clusterio/lib";
//%// We do not check for multi context here because it doesn't make sense to have a ctl without messages
import { PluginExampleEvent, PluginExampleRequest } from "./messages";
/* eslint-disable no-console */
@@ -1,12 +1,16 @@
import * as lib from "@clusterio/lib";
import { BaseHostPlugin } from "@clusterio/host";
//%if multi_context
import { PluginExampleEvent, PluginExampleRequest } from "./messages";
//%endif
export class HostPlugin extends BaseHostPlugin {
//%if multi_context
async init() {
this.host.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
this.host.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
}
//%endif
async onHostConfigFieldChanged(field: string, curr: unknown, prev: unknown) {
this.logger.info(`host::onInstanceConfigFieldChanged ${field}`);
@@ -15,6 +19,7 @@ export class HostPlugin extends BaseHostPlugin {
async onShutdown() {
this.logger.info("host::onShutdown");
}
//%if multi_context
async handlePluginExampleEvent(event: PluginExampleEvent) {
this.logger.info(JSON.stringify(event));
@@ -27,4 +32,5 @@ export class HostPlugin extends BaseHostPlugin {
myResponseNumbers: request.myNumberArray,
};
}
//%endif
}
+35 -13
View File
@@ -1,4 +1,5 @@
import * as lib from "@clusterio/lib";
//%if multi_context
import * as Messages from "./messages";
lib.definePermission({
@@ -12,6 +13,7 @@ lib.definePermission({
title: "Example permission request",
description: "Example Description. Request. Change me in index.ts",
});
//%endif
//%if controller & web // Subscribing requires web content and the controller
lib.definePermission({
@@ -28,37 +30,46 @@ lib.definePermission({
description: "Example Description. View. Change me in index.ts",
});
//%endif
//%if config
declare module "@clusterio/lib" {
//%if controller
//%endif
//%if controller & config
export interface ControllerConfigFields {
"__plugin_name__.myControllerField": string;
}
//%endif
//%if host
//%if host & config
export interface HostConfigFields {
"__plugin_name__.myHostField": string;
}
//%endif
//%if instance
//%if instance & config
export interface InstanceConfigFields {
"__plugin_name__.myInstanceField": string;
}
//%endif
//%if ctl
//%if ctl & config
export interface ControlConfigFields {
"__plugin_name__.myControlField": string;
}
//%endif
//%if config
}
//%endif
export const plugin: lib.PluginDeclaration = {
name: "__plugin_name__",
title: "__plugin_name__",
description: "Example Description. Plugin. Change me in index.ts",
//%if controller
//%// There are a lot of statements here to make the formatting look good with and without 'config'
//%if controller | host & !config | instance & !config | ctl & !config // Blank line for formatting
//%endif
//%if controller
controllerEntrypoint: "./dist/node/controller",
//%endif
//%if controller & config
controllerConfigFields: {
"__plugin_name__.myControllerField": {
title: "My Controller Field",
@@ -68,9 +79,13 @@ export const plugin: lib.PluginDeclaration = {
},
},
//%endif
//%if host
//%if host & config // Blank line for formatting
//%endif
//%if host
hostEntrypoint: "./dist/node/host",
//%endif
//%if host & config
hostConfigFields: {
"__plugin_name__.myHostField": {
title: "My Host Field",
@@ -80,9 +95,13 @@ export const plugin: lib.PluginDeclaration = {
},
},
//%endif
//%if instance
//%if instance & config | module & config // Blank line for formatting
//%endif
//%if instance | module // Modules load an empty instance plugin
instanceEntrypoint: "./dist/node/instance",
//%endif
//%if instance & config
instanceConfigFields: {
"__plugin_name__.myInstanceField": {
title: "My Instance Field",
@@ -92,9 +111,13 @@ export const plugin: lib.PluginDeclaration = {
},
},
//%endif
//%if ctl
//%if ctl & config // Blank line for formatting
//%endif
//%if ctl
ctlEntrypoint: "./dist/node/ctl",
//%endif
//%if ctl & config
controlConfigFields: {
"__plugin_name__.myControlField": {
title: "My Control Field",
@@ -104,14 +127,18 @@ export const plugin: lib.PluginDeclaration = {
},
},
//%endif
//%if multi_context // Subscribing requires multi context
messages: [
Messages.PluginExampleEvent,
Messages.PluginExampleRequest,
//%endif
//%if controller & web // Subscribing requires web content and the controller
Messages.ExampleSubscribableUpdate,
//%endif
//%if multi_context // Subscribing requires multi context
],
//%endif
//%if web // Web content template has an example route which is the plugin name
webEntrypoint: "./web",
@@ -119,9 +146,4 @@ export const plugin: lib.PluginDeclaration = {
"/__plugin_name__",
],
//%endif
//%if controller & !web // The controller always includes web entry even if there is no content
webEntrypoint: "./web",
routes: [],
//%endif
};
@@ -1,6 +1,8 @@
import * as lib from "@clusterio/lib";
import { BaseInstancePlugin } from "@clusterio/host";
//%if multi_context
import { PluginExampleEvent, PluginExampleRequest } from "./messages";
//%endif
//%if module
type PuginExampleIPC = {
@@ -10,13 +12,19 @@ type PuginExampleIPC = {
//%endif
export class InstancePlugin extends BaseInstancePlugin {
//%if multi_context | module
async init() {
//%endif
//%if multi_context
this.instance.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
this.instance.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
//%endif
//%if module
this.instance.server.handle("__plugin_name__-plugin_example_ipc", this.handlePluginExampleIPC.bind(this));
//%endif
//%if multi_context | module
}
//%endif
async onInstanceConfigFieldChanged(field: string, curr: unknown, prev: unknown) {
this.logger.info(`instance::onInstanceConfigFieldChanged ${field}`);
@@ -33,9 +41,12 @@ export class InstancePlugin extends BaseInstancePlugin {
async onPlayerEvent(event: lib.PlayerEvent) {
this.logger.info(`onPlayerEvent::onPlayerEvent ${JSON.stringify(event)}`);
//%if module
this.sendRcon("/sc __plugin_name__.foo()");
if (this.instance.status === "running") {
this.sendRcon("/sc __plugin_name__.foo()");
}
//%endif
}
//%if multi_context
async handlePluginExampleEvent(event: PluginExampleEvent) {
this.logger.info(JSON.stringify(event));
@@ -48,6 +59,7 @@ export class InstancePlugin extends BaseInstancePlugin {
myResponseNumbers: request.myNumberArray,
};
}
//%endif
//%if module
async handlePluginExampleIPC(event: PuginExampleIPC) {
@@ -0,0 +1,7 @@
import * as lib from "@clusterio/lib";
import { BaseInstancePlugin } from "@clusterio/host";
export class InstancePlugin extends BaseInstancePlugin {
// This class is empty because an instance plugin must be defined for a module to be injected
// This requirement may change in the future to allow for standalone modules
}
@@ -12,33 +12,37 @@ import React, {
import {
BaseWebPlugin, PageLayout, Control, ControlContext, notifyErrorHandler,
} from "@clusterio/web_ui";
//%if multi_context // Subscribing requires multi context
import {
PluginExampleEvent, PluginExampleRequest,
//%if controller & web // Subscribing requires web content and the controller
ExampleSubscribableUpdate,
//%endif
//%if controller & web // Subscribing requires web content and the controller
ExampleSubscribableUpdate, ExampleSubscribableValue,
//%endif
//%if multi_context // Subscribing requires multi context
} from "../messages";
//%endif
import * as lib from "@clusterio/lib";
function MyTemplatePage() {
const control = useContext(ControlContext);
//%if controller & web // Subscribing requires web content and the controller
//%if controller // Subscribing requires web content and the controller
const plugin = control.plugins.get("__plugin_name__") as WebPlugin;
const [subscribableData, synced] = plugin.useSubscribableData();
//%endif
return <PageLayout nav={[{ name: "__plugin_name__" }]}>
<h2>__plugin_name__</h2>
//%if controller & web // Subscribing requires web content and the controller
//%if controller // Subscribing requires web content and the controller
Synced: {String(synced)} Data: {JSON.stringify([...subscribableData.values()])}
//%endif
</PageLayout>;
}
export class WebPlugin extends BaseWebPlugin {
//%if controller & web // Subscribing requires web content and the controller
//%if controller // Subscribing requires web content and the controller
subscribableData = new lib.EventSubscriber(ExampleSubscribableUpdate, this.control);
//%endif
@@ -49,20 +53,22 @@ export class WebPlugin extends BaseWebPlugin {
sidebarName: "__plugin_name__",
// This permission is client side only, so it must match the permission string of a resource request to be secure
// An undefined value means that the page will always be visible
//%if controller & web // Subscribing requires web content and the controller
//%if controller // Subscribing requires web content and the controller
permission: "__plugin_name__.example.permission.subscribe",
//%endif
//%if !controller | !web
//%if !controller
permission: undefined, // "__plugin_name__.example.permission.request",
//%endif
content: <MyTemplatePage/>,
},
];
//%if multi_context
this.control.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
this.control.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
//%endif
}
//%if controller & web // Subscribing requires web content and the controller
//%if controller // Subscribing requires web content and the controller
useSubscribableData() {
const control = useContext(ControlContext);
@@ -70,6 +76,7 @@ export class WebPlugin extends BaseWebPlugin {
return useSyncExternalStore(subscribe, () => this.subscribableData.getSnapshot());
}
//%endif
//%if multi_context
async handlePluginExampleEvent(event: PluginExampleEvent) {
this.logger.info(JSON.stringify(event));
@@ -82,4 +89,5 @@ export class WebPlugin extends BaseWebPlugin {
myResponseNumbers: request.myNumberArray,
};
}
//%endif
}