From 498638c0c92c4cd42b3964a336d77d91818283cc Mon Sep 17 00:00:00 2001
From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
Date: Sat, 8 Jun 2024 13:53:23 +0100
Subject: [PATCH] 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
---
packages/create/create.js | 1 +
packages/create/template.js | 33 +++++++++++--
.../templates/common/module/control.lua | 14 ++++--
.../create/templates/plugin-js/controller.js | 10 ++++
packages/create/templates/plugin-js/ctl.js | 1 +
packages/create/templates/plugin-js/host.js | 6 +++
packages/create/templates/plugin-js/index.js | 43 ++++++++++++-----
.../create/templates/plugin-js/instance.js | 17 +++++--
.../templates/plugin-js/instance_empty.js | 11 +++++
.../create/templates/plugin-js/web/plugin.jsx | 31 ++++++++----
.../create/templates/plugin-ts/controller.ts | 10 ++++
packages/create/templates/plugin-ts/ctl.ts | 4 +-
packages/create/templates/plugin-ts/host.ts | 6 +++
packages/create/templates/plugin-ts/index.ts | 48 ++++++++++++++-----
.../create/templates/plugin-ts/instance.ts | 14 +++++-
.../templates/plugin-ts/instance_empty.ts | 7 +++
.../create/templates/plugin-ts/web/plugin.tsx | 24 ++++++----
17 files changed, 224 insertions(+), 56 deletions(-)
create mode 100644 packages/create/templates/plugin-js/instance_empty.js
create mode 100644 packages/create/templates/plugin-ts/instance_empty.ts
diff --git a/packages/create/create.js b/packages/create/create.js
index c6c0d094..eda9364e 100755
--- a/packages/create/create.js
+++ b/packages/create/create.js
@@ -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" },
],
},
{
diff --git a/packages/create/template.js b/packages/create/template.js
index 71ce6734..02549f7d 100644
--- a/packages/create/template.js
+++ b/packages/create/template.js
@@ -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,
diff --git a/packages/create/templates/common/module/control.lua b/packages/create/templates/common/module/control.lua
index 2747b83b..458f5cf3 100644
--- a/packages/create/templates/common/module/control.lua
+++ b/packages/create/templates/common/module/control.lua
@@ -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))
diff --git a/packages/create/templates/plugin-js/controller.js b/packages/create/templates/plugin-js/controller.js
index 266e06fa..b578d248 100644
--- a/packages/create/templates/plugin-js/controller.js
+++ b/packages/create/templates/plugin-js/controller.js
@@ -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) {
diff --git a/packages/create/templates/plugin-js/ctl.js b/packages/create/templates/plugin-js/ctl.js
index ed1d1a32..95e886b9 100644
--- a/packages/create/templates/plugin-js/ctl.js
+++ b/packages/create/templates/plugin-js/ctl.js
@@ -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 */
diff --git a/packages/create/templates/plugin-js/host.js b/packages/create/templates/plugin-js/host.js
index 2aed38b9..c1e1468d 100644
--- a/packages/create/templates/plugin-js/host.js
+++ b/packages/create/templates/plugin-js/host.js
@@ -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 = {
diff --git a/packages/create/templates/plugin-js/index.js b/packages/create/templates/plugin-js/index.js
index 67d5df0d..26c5387d 100644
--- a/packages/create/templates/plugin-js/index.js
+++ b/packages/create/templates/plugin-js/index.js
@@ -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 = {
diff --git a/packages/create/templates/plugin-js/instance.js b/packages/create/templates/plugin-js/instance.js
index d3bfa189..6993aa1d 100644
--- a/packages/create/templates/plugin-js/instance.js
+++ b/packages/create/templates/plugin-js/instance.js
@@ -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) {
diff --git a/packages/create/templates/plugin-js/instance_empty.js b/packages/create/templates/plugin-js/instance_empty.js
new file mode 100644
index 00000000..177f9952
--- /dev/null
+++ b/packages/create/templates/plugin-js/instance_empty.js
@@ -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,
+};
diff --git a/packages/create/templates/plugin-js/web/plugin.jsx b/packages/create/templates/plugin-js/web/plugin.jsx
index 9cc5942e..2f94158a 100644
--- a/packages/create/templates/plugin-js/web/plugin.jsx
+++ b/packages/create/templates/plugin-js/web/plugin.jsx
@@ -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
__plugin_name__
-//%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
;
}
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: ,
},
];
+//%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
}
diff --git a/packages/create/templates/plugin-ts/controller.ts b/packages/create/templates/plugin-ts/controller.ts
index cab2d3c0..cca31956 100644
--- a/packages/create/templates/plugin-ts/controller.ts
+++ b/packages/create/templates/plugin-ts/controller.ts
@@ -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) {
diff --git a/packages/create/templates/plugin-ts/ctl.ts b/packages/create/templates/plugin-ts/ctl.ts
index 75c3a1a4..dc0fa01b 100644
--- a/packages/create/templates/plugin-ts/ctl.ts
+++ b/packages/create/templates/plugin-ts/ctl.ts
@@ -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 */
diff --git a/packages/create/templates/plugin-ts/host.ts b/packages/create/templates/plugin-ts/host.ts
index bdfc05b5..9a395988 100644
--- a/packages/create/templates/plugin-ts/host.ts
+++ b/packages/create/templates/plugin-ts/host.ts
@@ -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
}
diff --git a/packages/create/templates/plugin-ts/index.ts b/packages/create/templates/plugin-ts/index.ts
index d90f22be..8ef46604 100644
--- a/packages/create/templates/plugin-ts/index.ts
+++ b/packages/create/templates/plugin-ts/index.ts
@@ -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
};
diff --git a/packages/create/templates/plugin-ts/instance.ts b/packages/create/templates/plugin-ts/instance.ts
index 3cb7d18a..21af1189 100644
--- a/packages/create/templates/plugin-ts/instance.ts
+++ b/packages/create/templates/plugin-ts/instance.ts
@@ -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) {
diff --git a/packages/create/templates/plugin-ts/instance_empty.ts b/packages/create/templates/plugin-ts/instance_empty.ts
new file mode 100644
index 00000000..e8fa7eeb
--- /dev/null
+++ b/packages/create/templates/plugin-ts/instance_empty.ts
@@ -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
+}
diff --git a/packages/create/templates/plugin-ts/web/plugin.tsx b/packages/create/templates/plugin-ts/web/plugin.tsx
index df32127d..06a7542a 100644
--- a/packages/create/templates/plugin-ts/web/plugin.tsx
+++ b/packages/create/templates/plugin-ts/web/plugin.tsx
@@ -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
__plugin_name__
-//%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
;
}
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: ,
},
];
+//%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
}