mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 23:14:53 +09:00
Change to new preprocessor style
This commit is contained in:
37 files changed
+692
-2193
No files matched your search
+2
-139
@@ -12,6 +12,7 @@ const util = require("util");
|
||||
const yargs = require("yargs");
|
||||
|
||||
const { levels, logger, setLogLevel } = require("./logging");
|
||||
const { copyPluginTemplates } = require("./template");
|
||||
let dev = false;
|
||||
const scriptExt = process.platform === "win32" ? ".cmd" : "";
|
||||
const finished = util.promisify(stream.finished);
|
||||
@@ -513,144 +514,6 @@ WantedBy=multi-user.target
|
||||
}
|
||||
}
|
||||
|
||||
async function copyPluginTemplateFile(src, dst, props) {
|
||||
logger.verbose(`Writing ${dst} from template ${src}`);
|
||||
if (await fs.exists(dst)) {
|
||||
logger.warn(`Could not create file ${dst} because it already exists`);
|
||||
return;
|
||||
}
|
||||
|
||||
const templateData = await fs.readFile(src, "utf8");
|
||||
const outputData = templateData
|
||||
// matches: // [prop] // anything // [] // or /*/ [prop] /*/ anything /*/ [] /*/
|
||||
.replace(/\/\*?\/ \[([a-z_]*)\] \/\*?\/(.*?)\/\*?\/ \[\] \/\*?\//gs, (_, prop, content) => {
|
||||
// Find and replace // [prop] //content// [] //
|
||||
if (!props.hasOwnProperty(prop)) {
|
||||
logger.warn(`Unknown property clause (${prop}) found in template file ${src}`);
|
||||
return content;
|
||||
}
|
||||
|
||||
// Would be nice to support + (and) | (or) for prop clauses
|
||||
return Boolean(props[prop]) ? content : "";
|
||||
})
|
||||
// matches: // prop //
|
||||
.replace(/\/\/ ([a-z_]*) \/\//g, (_, prop) => {
|
||||
// Find and replace // prop //
|
||||
if (!props.hasOwnProperty(prop)) {
|
||||
logger.warn(`Unknown property string (${prop}) found in template file ${src}`);
|
||||
return prop;
|
||||
}
|
||||
|
||||
return props[prop];
|
||||
});
|
||||
|
||||
await fs.outputFile(dst, outputData);
|
||||
}
|
||||
|
||||
async function copyPluginTemplate(pluginName, templates) {
|
||||
logger.info(`Please wait, coping templates for ${templates.join(", ")}`);
|
||||
const devDependencies = new Map();
|
||||
const entryPoints = new Map();
|
||||
const files = new Map();
|
||||
const prepare = [];
|
||||
|
||||
const javascriptOnly = templates.includes("js");
|
||||
const webpack = templates.includes("controller") || templates.includes("web");
|
||||
|
||||
// Get the file extension and path to the templates
|
||||
const ext = javascriptOnly ? "js" : "ts";
|
||||
const templatePath = path.resolve(__dirname, javascriptOnly ? "./templates/plugin-js" : "./templates/plugin-ts");
|
||||
|
||||
// Files included in all templates
|
||||
files.set(".gitignore", path.join(templatePath, ".gitignore"));
|
||||
files.set(".npmignore", path.join(templatePath, ".npmignore"));
|
||||
files.set("package.json", path.join(templatePath, "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
|
||||
if (!javascriptOnly) {
|
||||
if (webpack) {
|
||||
devDependencies.set("@types/react", "^18.2.21");
|
||||
files.set("tsconfig.web.json", path.join(templatePath, "tsconfig.web.json"));
|
||||
}
|
||||
devDependencies.set("@types/node", "^20.4.5");
|
||||
devDependencies.set("typescript", "^5.1.6");
|
||||
files.set("tsconfig.json", path.join(templatePath, "tsconfig.json"));
|
||||
prepare.push("tsc --build");
|
||||
}
|
||||
|
||||
// Files and dependences to support webpack
|
||||
if (webpack) {
|
||||
devDependencies.set("webpack", "^5.88.2");
|
||||
devDependencies.set("webpack-cli", "^5.1.4");
|
||||
devDependencies.set("webpack-merge", "^5.9.0");
|
||||
devDependencies.set("@clusterio/web_ui", "^2.0.0-alpha.14");
|
||||
entryPoints.set("webEntrypoint", "./web");
|
||||
prepare.push("webpack-cli --env production");
|
||||
files.set("webpack.config.js", path.join(templatePath, "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("controller")) {
|
||||
entryPoints.set("controllerEntrypoint", "dist/plugin/controller");
|
||||
files.set(`controller.${ext}`, path.join(templatePath, `controller.${ext}`));
|
||||
}
|
||||
|
||||
if (templates.includes("host")) {
|
||||
entryPoints.set("hostEntrypoint", "dist/plugin/host");
|
||||
files.set(`host.${ext}`, path.join(templatePath, `host.${ext}`));
|
||||
}
|
||||
|
||||
if (templates.includes("instance")) {
|
||||
entryPoints.set("instanceEntrypoint", "dist/plugin/instance");
|
||||
files.set(`instance.${ext}`, path.join(templatePath, `instance.${ext}`));
|
||||
}
|
||||
|
||||
if (templates.includes("module")) {
|
||||
files.set(".luacheckrc", path.join(templatePath, ".luacheckrc"));
|
||||
files.set("module/module.json", path.join(templatePath, "module/module.json"));
|
||||
files.set("module/control.lua", path.join(templatePath, "module/control.lua"));
|
||||
files.set("module/module_exports.lua", path.join(templatePath, "module/module_exports.lua"));
|
||||
}
|
||||
|
||||
if (templates.includes("ctl")) {
|
||||
entryPoints.set("ctlEntrypoint", "dist/plugin/ctl");
|
||||
files.set(`ctl.${ext}`, path.join(templatePath, `ctl.${ext}`));
|
||||
}
|
||||
|
||||
// Properties that will control the replacements in the templates
|
||||
const props = {
|
||||
controller: templates.includes("controller"),
|
||||
subscribable: templates.includes("controller") && templates.includes("web"),
|
||||
host: templates.includes("host"),
|
||||
instance: templates.includes("instance"),
|
||||
module: templates.includes("module"),
|
||||
ctl: templates.includes("ctl"),
|
||||
web: templates.includes("web"),
|
||||
plugin_name: pluginName,
|
||||
prepare: prepare
|
||||
.join(" && "),
|
||||
entry_points: [...entryPoints.entries()]
|
||||
.map((entry) => `${entry[0]}: "${entry[1]}",`)
|
||||
.join("\n\t"),
|
||||
dev_dependencies: [...devDependencies.entries()]
|
||||
.map((entry) => `"${entry[0]}": "${entry[1]}",`)
|
||||
.join("\n\t\t"),
|
||||
};
|
||||
|
||||
// Write all the template files
|
||||
const writes = [];
|
||||
for (let [dst, src] of files) {
|
||||
writes.push(copyPluginTemplateFile(src, dst, props));
|
||||
}
|
||||
|
||||
await Promise.all(writes);
|
||||
logger.info("Successfully wrote all template files");
|
||||
}
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
async function inquirerMissingArgs(args) {
|
||||
let answers = {};
|
||||
@@ -972,7 +835,7 @@ async function main() {
|
||||
logger.verbose(JSON.stringify(answers));
|
||||
|
||||
if (answers.pluginTemplate) {
|
||||
await copyPluginTemplate(answers.pluginName, answers.pluginTemplate);
|
||||
await copyPluginTemplates(answers.pluginName, answers.pluginTemplate);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
"use strict";
|
||||
const { logger } = require("./logging");
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
|
||||
async function copyTemplateFile(src, dst, properties) {
|
||||
logger.verbose(`Writing ${dst} from template ${src}`);
|
||||
|
||||
// Check if the file already exists to prevent regex calculations
|
||||
if (await fs.exists(dst)) {
|
||||
logger.warn(`Could not create file ${dst} because it already exists`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Closure, get the property value or throw
|
||||
function getProperty(property) {
|
||||
if (!properties.hasOwnProperty(property)) {
|
||||
throw new Error(`Unknown property (${property}) found in template file ${src}`);
|
||||
}
|
||||
|
||||
return properties[property];
|
||||
}
|
||||
|
||||
// Get the boolean conjunction of a set of properties
|
||||
function propertyConjunction(props) {
|
||||
for (const property of props) {
|
||||
if (property.startsWith("!")) {
|
||||
if (Boolean(getProperty(property.slice(1)))) {
|
||||
return false;
|
||||
}
|
||||
} else if (!Boolean(getProperty(property))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Process the the template file
|
||||
const badChars = /[\(\)-+/*<>=]/;
|
||||
const templateData = await fs.readFile(src, "utf8");
|
||||
const outputData = templateData
|
||||
// match preprocessor comments: //%//
|
||||
.replace(/^\/\/%\/\/.*\n/gm, "")
|
||||
// match preprocessor insert: __property__
|
||||
.replace(/__([a-zA-Z][a-zA-Z_]*?[a-zA-Z]?)__/gm, (_, property) => getProperty(property))
|
||||
// match preprocessor conditionals: //%if <condition> [// comment]\n <content> \n //%endif [comment]\n
|
||||
.replace(/^\/\/%if ([^\n\/]*)(?:\/\/.*?)?\n((?:.|\n)*?)\/\/%endif.*\n/gm, (_, condition, content) => {
|
||||
let include = false;
|
||||
const conjunctions = condition.split("|");
|
||||
for (const conjunct of conjunctions) {
|
||||
if (badChars.test(conjunct)) {
|
||||
throw new Error("Preprocessor condition can only contain logical operators" +
|
||||
"such as & | or ! it can not have arithmetic, comparisons or parenthesis"
|
||||
);
|
||||
}
|
||||
|
||||
const property_names = conjunct.split("&").map(s => s.trim());
|
||||
if (propertyConjunction(property_names)) {
|
||||
include = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return include ? content : "";
|
||||
});
|
||||
|
||||
await fs.outputFile(dst, outputData);
|
||||
}
|
||||
|
||||
async function copyPluginTemplates(pluginName, templates) {
|
||||
logger.info(`Please wait, coping templates for ${templates.join(", ")}`);
|
||||
const files = new Map();
|
||||
const prepare = [];
|
||||
|
||||
const javascriptOnly = templates.includes("js");
|
||||
const webpack = templates.includes("controller") || templates.includes("web");
|
||||
|
||||
// Get the file extension and path to the templates
|
||||
const ext = javascriptOnly ? "js" : "ts";
|
||||
const templatePath = path.resolve(__dirname, javascriptOnly ? "./templates/plugin-js" : "./templates/plugin-ts");
|
||||
const commonPath = path.resolve(__dirname, "./templates/common");
|
||||
|
||||
// Files included in all 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
|
||||
if (!javascriptOnly) {
|
||||
prepare.push("tsc --build");
|
||||
files.set("tsconfig.json", path.join(templatePath, "tsconfig.json"));
|
||||
files.set("tsconfig.node.json", path.join(templatePath, "tsconfig.node.json"));
|
||||
if (webpack) {
|
||||
files.set("tsconfig.browser.json", path.join(templatePath, "tsconfig.browser.json"));
|
||||
}
|
||||
}
|
||||
|
||||
// Files and dependences to support webpack
|
||||
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`
|
||||
));
|
||||
}
|
||||
|
||||
// Files for the controller
|
||||
if (templates.includes("controller")) {
|
||||
files.set(`controller.${ext}`, path.join(templatePath, `controller.${ext}`));
|
||||
}
|
||||
|
||||
// Files for hosts
|
||||
if (templates.includes("host")) {
|
||||
files.set(`host.${ext}`, path.join(templatePath, `host.${ext}`));
|
||||
}
|
||||
|
||||
// Files for instances
|
||||
if (templates.includes("instance")) {
|
||||
files.set(`instance.${ext}`, path.join(templatePath, `instance.${ext}`));
|
||||
}
|
||||
|
||||
// Files for lua modules
|
||||
if (templates.includes("module")) {
|
||||
files.set(".luacheckrc", path.join(commonPath, ".luacheckrc"));
|
||||
files.set("module/module.json", path.join(commonPath, "module/module.json"));
|
||||
files.set("module/control.lua", path.join(commonPath, "module/control.lua"));
|
||||
files.set("module/module_exports.lua", path.join(commonPath, "module/module_exports.lua"));
|
||||
}
|
||||
|
||||
// Files for the control tool
|
||||
if (templates.includes("ctl")) {
|
||||
files.set(`ctl.${ext}`, path.join(templatePath, `ctl.${ext}`));
|
||||
}
|
||||
|
||||
// Properties that will control the replacements in the templates
|
||||
const properties = {
|
||||
typescript: !javascriptOnly,
|
||||
controller: templates.includes("controller"),
|
||||
host: templates.includes("host"),
|
||||
instance: templates.includes("instance"),
|
||||
module: templates.includes("module"),
|
||||
ctl: templates.includes("ctl"),
|
||||
web: templates.includes("web"),
|
||||
plugin_name: pluginName,
|
||||
prepare: prepare.join(" && "),
|
||||
ext: ext,
|
||||
};
|
||||
|
||||
// Write all the template files
|
||||
const writes = [];
|
||||
for (let [dst, src] of files) {
|
||||
writes.push(copyTemplateFile(src, dst, properties));
|
||||
}
|
||||
|
||||
// Wait for writes to complete
|
||||
await Promise.all(writes);
|
||||
logger.info("Successfully wrote all template files");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
copyPluginTemplates,
|
||||
};
|
||||
File renamed without changes.
File renamed without changes.
+1
@@ -1,2 +1,3 @@
|
||||
webpack.config.js
|
||||
dist/web/build
|
||||
dist/browser
|
||||
+8
-6
@@ -10,12 +10,12 @@ local MyModule = {
|
||||
-- setupGlobalData should either be removed or called during clusterio_api.events.on_server_startup
|
||||
local globalData = {}
|
||||
local function setupGlobalData()
|
||||
if global["// plugin_name //"] == nil then
|
||||
global["// plugin_name //"] = {
|
||||
if global["__plugin_name__"] == nil then
|
||||
global["__plugin_name__"] = {
|
||||
-- starting values go here
|
||||
}
|
||||
end
|
||||
globalData = global["// plugin_name //"]
|
||||
globalData = global["__plugin_name__"]
|
||||
end
|
||||
|
||||
--- Public methods should be available though your top level module table
|
||||
@@ -36,10 +36,12 @@ end
|
||||
|
||||
--- Factorio events are accessible through defines.events, you can have one handler per event per module
|
||||
MyModule.events[defines.events.on_player_crafted_item] = function(event)
|
||||
game.print(game.table_to_json(event))// [instance] //
|
||||
clusterio_api.send_json("// plugin_name //-plugin_example_ipc", {
|
||||
game.print(game.table_to_json(event))
|
||||
//%if instance
|
||||
clusterio_api.send_json("__plugin_name__-plugin_example_ipc", {
|
||||
tick = game.tick, player_name = game.get_player(event.player_index).name
|
||||
})// [] //
|
||||
})
|
||||
//%endif
|
||||
end
|
||||
|
||||
--- Nth tick is a special case that requires its own table, the index represents the time period between calls in ticks
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "__plugin_name__",
|
||||
"load": [
|
||||
"control.lua"
|
||||
],
|
||||
"require": [
|
||||
//%if instance
|
||||
"module_exports.lua"
|
||||
//%endif
|
||||
],
|
||||
"dependencies": {
|
||||
"clusterio": "*"
|
||||
}
|
||||
}
|
||||
+5
-3
@@ -1,8 +1,10 @@
|
||||
local MyModule = require("./control")// [instance] //
|
||||
local MyModule = require("./control")
|
||||
//%if instance
|
||||
|
||||
--- Can contain anything you plan to use with send_rcon including functions, modules, or data
|
||||
-- luacheck: globals ipc_// plugin_name //
|
||||
ipc_// plugin_name // = MyModule// [] //
|
||||
-- luacheck: globals ipc___plugin_name__
|
||||
ipc___plugin_name__ = MyModule
|
||||
//%endif
|
||||
|
||||
--- Can contain anything you want to allow other plugins to have access to, this example exposes the whole module
|
||||
return MyModule
|
||||
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "__plugin_name__",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
//%if typescript
|
||||
"main": "dist/node/index.js",
|
||||
//%endif
|
||||
//%if !typescript
|
||||
"main": "index.js",
|
||||
//%endif
|
||||
"scripts": {
|
||||
"prepare": "__prepare__"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@clusterio/lib": "^2.0.0-alpha.17"
|
||||
},
|
||||
"devDependencies": {
|
||||
//%// Typescript and type declarations
|
||||
//%if typescript
|
||||
"typescript": "^5.1.6",
|
||||
"@types/node": "^20.4.5",
|
||||
//%endif
|
||||
//%if typescript & web
|
||||
"@types/react": "^18.2.21",
|
||||
//%endif
|
||||
//%// Everything else
|
||||
//%if web
|
||||
"antd": "^5.13.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
//%endif
|
||||
//%if web | controller // The controller uses webpack even when there is no plugin code
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-merge": "^5.9.0",
|
||||
"@clusterio/web_ui": "^2.0.0-alpha.17",
|
||||
//%endif
|
||||
"@clusterio/lib": "^2.0.0-alpha.17"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sinclair/typebox": "^0.30.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"keywords": [
|
||||
"clusterio",
|
||||
"factorio"
|
||||
]
|
||||
}
|
||||
+12
-4
@@ -7,21 +7,29 @@ const common = require("@clusterio/web_ui/webpack.common");
|
||||
|
||||
module.exports = (env = {}) => merge(common(env), {
|
||||
context: __dirname,
|
||||
entry: "./web/index.jsx",
|
||||
entry: "./web/index.__ext__x",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist", "web"),
|
||||
},
|
||||
plugins: [
|
||||
new webpack.container.ModuleFederationPlugin({
|
||||
name: "// plugin_name //",
|
||||
library: { type: "var", name: "plugin_// plugin_name //" },
|
||||
name: "__plugin_name__",
|
||||
library: { type: "var", name: "plugin___plugin_name__" },
|
||||
exposes: {
|
||||
"./": "./info.js",
|
||||
"./": "./index.__ext__",
|
||||
"./package.json": "./package.json",
|
||||
//%if web
|
||||
"./web": "./web/index.__ext__x",
|
||||
//%endif
|
||||
},
|
||||
shared: {
|
||||
"@clusterio/lib": { import: false },
|
||||
"@clusterio/web_ui": { import: false },
|
||||
//%if web
|
||||
"antd": { import: false },
|
||||
"react": { import: false },
|
||||
"react-dom": { import: false },
|
||||
//%endif
|
||||
},
|
||||
}),
|
||||
],
|
||||
@@ -1,26 +1,38 @@
|
||||
"use strict";
|
||||
const lib = require("@clusterio/lib");
|
||||
const { BaseControllerPlugin } = require("@clusterio/controller");
|
||||
const { PluginExampleEvent, PluginExampleRequest,/*/ [subscribable] /*/ ExampleSubscribableUpdate, ExampleSubscribableValue/*/ [] /*/ } = require("./messages");
|
||||
|
||||
class ControllerPlugin extends BaseControllerPlugin {// [subscribable] //
|
||||
const {
|
||||
PluginExampleEvent, PluginExampleRequest,
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
ExampleSubscribableUpdate, ExampleSubscribableValue,
|
||||
//%endif
|
||||
} = require("./messages");
|
||||
|
||||
class ControllerPlugin extends BaseControllerPlugin {
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
exampleDatabase;
|
||||
storageDirty = false;
|
||||
// [] //
|
||||
|
||||
//%endif
|
||||
async init() {
|
||||
this.controller.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
|
||||
this.controller.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));// [subscribable] //
|
||||
this.controller.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
|
||||
//%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 // [] //
|
||||
this.exampleDatabase = new Map(); // If needed, replace with loading from database file
|
||||
//%endif
|
||||
}
|
||||
|
||||
async onControllerConfigFieldChanged(field, curr, prev) {
|
||||
this.logger.info(`controller::onControllerConfigFieldChanged ${field}`);
|
||||
}// [instance] //
|
||||
}
|
||||
//%if instance
|
||||
|
||||
async onInstanceConfigFieldChanged(instance, field, curr, prev) {
|
||||
this.logger.info(`controller::onInstanceConfigFieldChanged ${instance.id} ${field}`);
|
||||
}// [] //
|
||||
}
|
||||
//%endif
|
||||
|
||||
async onSaveData() {
|
||||
this.logger.info("controller::onSaveData");
|
||||
@@ -44,14 +56,16 @@ class ControllerPlugin extends BaseControllerPlugin {// [subscribable] //
|
||||
myResponseString: request.myString,
|
||||
myResponseNumbers: request.myNumberArray,
|
||||
};
|
||||
}// [subscribable] //
|
||||
}
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
|
||||
async handleExampleSubscription(request) {
|
||||
const values = [...this.exampleDatabase.values()].filter(
|
||||
value => value.updatedAtMs > request.lastRequestTimeMs,
|
||||
);
|
||||
return values.length ? new ExampleSubscribableUpdate(values) : null;
|
||||
}// [] //
|
||||
}
|
||||
//%endif
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -5,7 +5,7 @@ const { PluginExampleEvent, PluginExampleRequest } = require("./messages");
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const pluginCommands = new CommandTree({
|
||||
name: "// plugin_name //", description: "The default description I forgot to change",
|
||||
name: "__plugin_name__", description: "The default description I forgot to change",
|
||||
});
|
||||
|
||||
pluginCommands.add(new Command({
|
||||
|
||||
@@ -1,79 +1,102 @@
|
||||
"use strict";
|
||||
const lib = require("@clusterio/lib");
|
||||
const messages = require("./messages");
|
||||
const Messages = require("./messages");
|
||||
|
||||
lib.definePermission({
|
||||
name: "// plugin_name //.example.permission.event",
|
||||
name: "__plugin_name__.example.permission.event",
|
||||
title: "Example permission event",
|
||||
description: "My plugin's example permission that I forgot to remove",
|
||||
});
|
||||
|
||||
lib.definePermission({
|
||||
name: "// plugin_name //.example.permission.request",
|
||||
name: "__plugin_name__.example.permission.request",
|
||||
title: "Example permission request",
|
||||
description: "My plugin's example permission that I forgot to remove",
|
||||
});// [subscribable] //
|
||||
});
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
|
||||
lib.definePermission({
|
||||
name: "// plugin_name //.example.permission.subscribe",
|
||||
name: "__plugin_name__.example.permission.subscribe",
|
||||
title: "Example permission subscribe",
|
||||
description: "My plugin's example permission that I forgot to remove",
|
||||
});// [] //// [web] //
|
||||
});
|
||||
//%endif
|
||||
//%if web
|
||||
|
||||
lib.definePermission({
|
||||
name: "// plugin_name //.page.view",
|
||||
name: "__plugin_name__.page.view",
|
||||
title: "Example page view permission",
|
||||
description: "My plugin's example page permission that I forgot to remove",
|
||||
});// [] //
|
||||
});
|
||||
//%endif
|
||||
|
||||
const plugin = {
|
||||
name: "// plugin_name //",
|
||||
title: "// plugin_name //",
|
||||
description: "I didn't update my description",// [entry_points] //
|
||||
// entry_points //
|
||||
// [] //// [controller] //
|
||||
description: "I didn't update my description",
|
||||
//%if controller
|
||||
|
||||
controllerEntrypoint: "./dist/node/controller",
|
||||
controllerConfigFields: {
|
||||
"// plugin_name //.myControllerField": {
|
||||
"__plugin_name__.myControllerField": {
|
||||
title: "My Controller Field",
|
||||
description: "This should be removed",
|
||||
type: "string",
|
||||
initialValue: "Remove Me",
|
||||
},
|
||||
},// [] //// [host] //
|
||||
},
|
||||
//%endif
|
||||
//%if host
|
||||
|
||||
hostEntrypoint: "./dist/node/host",
|
||||
hostConfigFields: {
|
||||
"// plugin_name //.myHostField": {
|
||||
"__plugin_name__.myHostField": {
|
||||
title: "My Host Field",
|
||||
description: "This should be removed",
|
||||
type: "string",
|
||||
initialValue: "Remove Me",
|
||||
},
|
||||
},// [] //// [instance] //
|
||||
},
|
||||
//%endif
|
||||
//%if instance
|
||||
|
||||
instanceEntrypoint: "./dist/node/instance",
|
||||
instanceConfigFields: {
|
||||
"// plugin_name //.myInstanceField": {
|
||||
"__plugin_name__.myInstanceField": {
|
||||
title: "My Instance Field",
|
||||
description: "This should be removed",
|
||||
type: "string",
|
||||
initialValue: "Remove Me",
|
||||
},
|
||||
},// [] //// [ctl] //
|
||||
},
|
||||
//%endif
|
||||
//%if ctl
|
||||
|
||||
ctlEntrypoint: "./dist/node/ctl",
|
||||
controlConfigFields: {
|
||||
"// plugin_name //.myControlField": {
|
||||
"__plugin_name__.myControlField": {
|
||||
title: "My Control Field",
|
||||
description: "This should be removed",
|
||||
type: "string",
|
||||
initialValue: "Remove Me",
|
||||
},
|
||||
},// [] //
|
||||
},
|
||||
//%endif
|
||||
|
||||
messages: [
|
||||
messages.PluginExampleEvent,
|
||||
messages.PluginExampleRequest,// [subscribable] //
|
||||
messages.ExampleSubscribableUpdate,// [] //
|
||||
Messages.PluginExampleEvent,
|
||||
Messages.PluginExampleRequest,
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
Messages.ExampleSubscribableUpdate,
|
||||
//%endif
|
||||
],
|
||||
//%if controller | web // The controller always includes web entry even if there is no content
|
||||
|
||||
webEntrypoint: "./web",
|
||||
routes: [],
|
||||
//%endif
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
plugin,
|
||||
PluginExampleEvent,
|
||||
PluginExampleRequest,
|
||||
};
|
||||
@@ -6,8 +6,10 @@ const { PluginExampleEvent, PluginExampleRequest } = require("./messages");
|
||||
class InstancePlugin extends BaseInstancePlugin {
|
||||
async init() {
|
||||
this.instance.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
|
||||
this.instance.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));// [module] //
|
||||
this.instance.server.handle("ipc-// plugin_name //-plugin_example_ipc", this.handlePluginExampleIPC.bind(this));// [] //
|
||||
this.instance.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
|
||||
//%if module
|
||||
this.instance.server.handle("ipc-__plugin_name__-plugin_example_ipc", this.handlePluginExampleIPC.bind(this));
|
||||
//%endif
|
||||
}
|
||||
|
||||
async onInstanceConfigFieldChanged(field, curr, prev) {
|
||||
@@ -27,8 +29,10 @@ class InstancePlugin extends BaseInstancePlugin {
|
||||
}
|
||||
|
||||
async handlePluginExampleEvent(event) {
|
||||
this.logger.info(JSON.stringify(event));// [module] //
|
||||
this.sendRcon("/sc ipc_// plugin_name //.foo()");// [] //
|
||||
this.logger.info(JSON.stringify(event));
|
||||
//%if module
|
||||
this.sendRcon("/sc ipc___plugin_name__.foo()");
|
||||
//%endif
|
||||
}
|
||||
|
||||
async handlePluginExampleRequest(request) {
|
||||
@@ -37,11 +41,13 @@ class InstancePlugin extends BaseInstancePlugin {
|
||||
myResponseString: request.myString,
|
||||
myResponseNumbers: request.myNumberArray,
|
||||
};
|
||||
}// [module] //
|
||||
}
|
||||
//%if module
|
||||
|
||||
async handlePluginExampleIPC(event) {
|
||||
this.logger.info(JSON.stringify(event));
|
||||
}// [] //
|
||||
}
|
||||
//%endif
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -6,8 +6,8 @@ class PluginExampleEvent {
|
||||
static type = "event";
|
||||
static src = ["host", "control"];
|
||||
static dst = ["controller", "host", "instance"];
|
||||
static plugin = "// plugin_name //";
|
||||
static permission = "// plugin_name //.example.permission.event";
|
||||
static plugin = "__plugin_name__";
|
||||
static permission = "__plugin_name__.example.permission.event";
|
||||
|
||||
constructor(myString, myNumberArray) {
|
||||
this.myString = myString;
|
||||
@@ -28,8 +28,8 @@ class PluginExampleRequest {
|
||||
static type = "request";
|
||||
static src = ["host", "control"];
|
||||
static dst = ["controller", "host", "instance"];
|
||||
static plugin = "// plugin_name //";
|
||||
static permission = "// plugin_name //.example.permission.request";
|
||||
static plugin = "__plugin_name__";
|
||||
static permission = "__plugin_name__.example.permission.request";
|
||||
|
||||
constructor(myString, myNumberArray) {
|
||||
this.myString = myString;
|
||||
@@ -49,7 +49,8 @@ class PluginExampleRequest {
|
||||
"myResponseString": Type.String(),
|
||||
"myResponseNumbers": Type.Array(Type.Number()),
|
||||
}));
|
||||
}// [subscribable] //
|
||||
}
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
|
||||
class ExampleSubscribableValue {
|
||||
constructor(id, updatedAtMs, isDeleted) {
|
||||
@@ -73,8 +74,8 @@ class ExampleSubscribableUpdate {
|
||||
static type = "event";
|
||||
static src = "controller";
|
||||
static dst = "control";
|
||||
static plugin = "// plugin_name //";
|
||||
static permission = "// plugin_name //.example.permission.subscribe";
|
||||
static plugin = "__plugin_name__";
|
||||
static permission = "__plugin_name__.example.permission.subscribe";
|
||||
|
||||
constructor(updates) {
|
||||
this.updates = updates;
|
||||
@@ -87,11 +88,14 @@ class ExampleSubscribableUpdate {
|
||||
static fromJSON(json) {
|
||||
return new this(json.updates.map(update => ExampleSubscribableValue.fromJSON(update)));
|
||||
}
|
||||
}// [] //
|
||||
}
|
||||
//%endif
|
||||
|
||||
module.exports = {
|
||||
PluginExampleEvent,
|
||||
PluginExampleRequest,// [subscribable] //
|
||||
PluginExampleRequest,
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
ExampleSubscribableValue,
|
||||
ExampleSubscribableUpdate,// [] //
|
||||
ExampleSubscribableUpdate,
|
||||
//%endif
|
||||
};
|
||||
@@ -1,52 +0,0 @@
|
||||
local clusterio_api = require("modules/clusterio/api")
|
||||
|
||||
--- Top level module table, contains event handlers and public methods
|
||||
local MyModule = {
|
||||
events = {},
|
||||
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
|
||||
local globalData = {}
|
||||
local function setupGlobalData()
|
||||
if global["// plugin_name //"] == nil then
|
||||
global["// plugin_name //"] = {
|
||||
-- starting values go here
|
||||
}
|
||||
end
|
||||
globalData = global["// plugin_name //"]
|
||||
end
|
||||
|
||||
--- Public methods should be available though your top level module table
|
||||
function MyModule.foo()
|
||||
game.print("foo")
|
||||
end
|
||||
|
||||
--- Private methods should be local to the file, this will prevent others from calling it
|
||||
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
|
||||
MyModule.events[clusterio_api.events.on_server_startup] = function(event)
|
||||
setupGlobalData()
|
||||
game.print(game.table_to_json(event))
|
||||
end
|
||||
|
||||
--- Factorio events are accessible through defines.events, you can have one handler per event per module
|
||||
MyModule.events[defines.events.on_player_crafted_item] = function(event)
|
||||
game.print(game.table_to_json(event))// [instance] //
|
||||
clusterio_api.send_json("// plugin_name //-plugin_example_ipc", {
|
||||
tick = game.tick, player_name = game.get_player(event.player_index).name
|
||||
})// [] //
|
||||
end
|
||||
|
||||
--- Nth tick is a special case that requires its own table, the index represents the time period between calls in ticks
|
||||
MyModule.on_nth_tick[300] = function()
|
||||
game.print(game.tick)
|
||||
bar()
|
||||
end
|
||||
|
||||
--- Always return the top level module table from control, this is how clusterio will access your event handlers
|
||||
return MyModule
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "// plugin_name //",
|
||||
"load": [
|
||||
"control.lua"
|
||||
],
|
||||
"require": [
|
||||
// [instance] //"module_exports.lua"// [] //
|
||||
],
|
||||
"dependencies": [
|
||||
|
||||
]
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
local MyModule = require("./control")// [instance] //
|
||||
|
||||
--- Can contain anything you plan to use with send_rcon including functions, modules, or data
|
||||
-- luacheck: globals ipc_// plugin_name //
|
||||
ipc_// plugin_name // = MyModule// [] //
|
||||
|
||||
--- Can contain anything you want to allow other plugins to have access to, this example exposes the whole module
|
||||
return MyModule
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "// plugin_name //",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"prepare": "// prepare //"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@clusterio/lib": "^2.0.0-alpha.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
// dev_dependencies //
|
||||
"@clusterio/lib": "^2.0.0-alpha.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sinclair/typebox": "^0.30.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"keywords": [
|
||||
"clusterio",
|
||||
"factorio"
|
||||
]
|
||||
}
|
||||
@@ -1,46 +1,68 @@
|
||||
const { useContext, useEffect, useState/*/ [subscribable] /*/, useCallback, useSyncExternalStore/*/ [] /*/ } = require("react");
|
||||
// const { } = require("antd");
|
||||
import React, {
|
||||
useContext, useEffect, useState,
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
useCallback, useSyncExternalStore,
|
||||
//%endif
|
||||
} from "react";
|
||||
|
||||
const {
|
||||
// import {
|
||||
//
|
||||
// } from "antd";
|
||||
|
||||
import {
|
||||
BaseWebPlugin, PageLayout, Control, ControlContext, notifyErrorHandler,
|
||||
} = require("@clusterio/web_ui");
|
||||
} from "@clusterio/web_ui";
|
||||
|
||||
const lib = require("@clusterio/lib");
|
||||
const { PluginExampleEvent, PluginExampleRequest/*/ [subscribable] /*/, ExampleSubscribableUpdate/*/ [] /*/ } = require("../messages");
|
||||
import {
|
||||
PluginExampleEvent, PluginExampleRequest,
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
ExampleSubscribableUpdate,
|
||||
//%endif
|
||||
} from "../messages";
|
||||
|
||||
import * as lib from "@clusterio/lib";
|
||||
|
||||
function MyTemplatePage() {
|
||||
let control = useContext(ControlContext);// [subscribable] //
|
||||
const plugin = control.plugins.get("// plugin_name //");
|
||||
const [subscribableData, synced] = plugin.useSubscribableData();// [] //
|
||||
let control = useContext(ControlContext);
|
||||
//%if controller & web // 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>// [subscribable] //
|
||||
Synced: {String(synced)} Data: {JSON.stringify([...subscribableData.values()])}// [] //
|
||||
return <PageLayout nav={[{ name: "__plugin_name__" }]}>
|
||||
<h2>__plugin_name__</h2>
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
Synced: {String(synced)} Data: {JSON.stringify([...subscribableData.values()])}
|
||||
//%endif
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
class WebPlugin extends BaseWebPlugin {// [subscribable] //
|
||||
class WebPlugin extends BaseWebPlugin {
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
subscribableData = new lib.EventSubscriber(ExampleSubscribableUpdate, this.control);
|
||||
// [] //
|
||||
|
||||
//%endif
|
||||
async init() {
|
||||
this.pages = [
|
||||
{
|
||||
path: "/// plugin_name //",
|
||||
sidebarName: "// plugin_name //",
|
||||
permission: "// plugin_name //.page.view",
|
||||
path: "/__plugin_name__",
|
||||
sidebarName: "__plugin_name__",
|
||||
permission: "__plugin_name__.page.view",
|
||||
content: <MyTemplatePage/>,
|
||||
},
|
||||
];
|
||||
|
||||
this.control.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
|
||||
this.control.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
|
||||
}// [subscribable] //
|
||||
}
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
|
||||
useSubscribableData() {
|
||||
const control = useContext(ControlContext);
|
||||
const subscribe = useCallback((callback) => this.subscribableData.subscribe(callback), [control]);
|
||||
return useSyncExternalStore(subscribe, () => this.subscribableData.getSnapshot());
|
||||
}// [] //
|
||||
}
|
||||
//%endif
|
||||
|
||||
async handlePluginExampleEvent(event) {
|
||||
this.logger.info(JSON.stringify(event));
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
dist/
|
||||
node_modules/
|
||||
package-lock.json
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,2 +0,0 @@
|
||||
webpack.config.js
|
||||
dist/web/build
|
||||
@@ -1,25 +1,37 @@
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { BaseControllerPlugin, InstanceInfo } from "@clusterio/controller";
|
||||
import { PluginExampleEvent, PluginExampleRequest,/*/ [subscribable] /*/ ExampleSubscribableUpdate, ExampleSubscribableValue/*/ [] /*/ } from "./messages";
|
||||
|
||||
export class ControllerPlugin extends BaseControllerPlugin {// [subscribable] //
|
||||
import {
|
||||
PluginExampleEvent, PluginExampleRequest,
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
ExampleSubscribableUpdate, ExampleSubscribableValue,
|
||||
//%endif
|
||||
} from "./messages";
|
||||
|
||||
export class ControllerPlugin extends BaseControllerPlugin {
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
exampleDatabase!: Map<string, ExampleSubscribableValue>;
|
||||
storageDirty = false;
|
||||
// [] //
|
||||
|
||||
//%endif
|
||||
async init() {
|
||||
this.controller.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
|
||||
this.controller.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));// [subscribable] //
|
||||
this.controller.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
this.controller.subscriptions.handle(ExampleSubscribableUpdate, this.handleExampleSubscription.bind(this));
|
||||
this.exampleDatabase = new Map();// [] //
|
||||
this.exampleDatabase = new Map(); // If needed, replace with loading from database file
|
||||
//%endif
|
||||
}
|
||||
|
||||
async onControllerConfigFieldChanged(field: string, curr: unknown, prev: unknown) {
|
||||
this.logger.info(`controller::onControllerConfigFieldChanged ${field}`);
|
||||
}// [instance] //
|
||||
}
|
||||
//%if instance
|
||||
|
||||
async onInstanceConfigFieldChanged(instance: InstanceInfo, field: string, curr: unknown, prev: unknown) {
|
||||
this.logger.info(`controller::onInstanceConfigFieldChanged ${instance.id} ${field}`);
|
||||
}// [] //
|
||||
}
|
||||
//%endif
|
||||
|
||||
async onSaveData() {
|
||||
this.logger.info("controller::onSaveData");
|
||||
@@ -43,12 +55,14 @@ export class ControllerPlugin extends BaseControllerPlugin {// [subscribable] //
|
||||
myResponseString: request.myString,
|
||||
myResponseNumbers: request.myNumberArray,
|
||||
};
|
||||
}// [subscribable] //
|
||||
}
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
|
||||
async handleExampleSubscription(request: lib.SubscriptionRequest) {
|
||||
const values = [...this.exampleDatabase.values()].filter(
|
||||
value => value.updatedAtMs > request.lastRequestTimeMs,
|
||||
);
|
||||
return values.length ? new ExampleSubscribableUpdate(values) : null;
|
||||
}// [] //
|
||||
}
|
||||
//%endif
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import { PluginExampleEvent, PluginExampleRequest } from "./messages";
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const pluginCommands = new CommandTree({
|
||||
name: "// plugin_name //", description: "The default description I forgot to change",
|
||||
name: "__plugin_name__", description: "The default description I forgot to change",
|
||||
});
|
||||
|
||||
pluginCommands.add(new Command({
|
||||
|
||||
@@ -2,86 +2,119 @@ import * as lib from "@clusterio/lib";
|
||||
import * as Messages from "./messages";
|
||||
|
||||
lib.definePermission({
|
||||
name: "// plugin_name //.example.permission.event",
|
||||
name: "__plugin_name__.example.permission.event",
|
||||
title: "Example permission event",
|
||||
description: "My plugin's example permission that I forgot to remove",
|
||||
});
|
||||
|
||||
lib.definePermission({
|
||||
name: "// plugin_name //.example.permission.request",
|
||||
name: "__plugin_name__.example.permission.request",
|
||||
title: "Example permission request",
|
||||
description: "My plugin's example permission that I forgot to remove",
|
||||
});// [subscribable] //
|
||||
});
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
|
||||
lib.definePermission({
|
||||
name: "// plugin_name //.example.permission.subscribe",
|
||||
name: "__plugin_name__.example.permission.subscribe",
|
||||
title: "Example permission subscribe",
|
||||
description: "My plugin's example permission that I forgot to remove",
|
||||
});// [] //// [web] //
|
||||
});
|
||||
//%endif
|
||||
//%if web
|
||||
|
||||
lib.definePermission({
|
||||
name: "// plugin_name //.page.view",
|
||||
name: "__plugin_name__.page.view",
|
||||
title: "Example page view permission",
|
||||
description: "My plugin's example page permission that I forgot to remove",
|
||||
});// [] //
|
||||
});
|
||||
//%endif
|
||||
|
||||
declare module "@clusterio/lib" {// [controller] //
|
||||
declare module "@clusterio/lib" {
|
||||
//%if controller
|
||||
export interface ControllerConfigFields {
|
||||
"// plugin_name //.myControllerField": string;
|
||||
}// [] //// [host] //
|
||||
"__plugin_name__.myControllerField": string;
|
||||
}
|
||||
//%endif
|
||||
//%if host
|
||||
export interface HostConfigFields {
|
||||
"// plugin_name //.myHostField": string;
|
||||
}// [] //// [instance] //
|
||||
"__plugin_name__.myHostField": string;
|
||||
}
|
||||
//%endif
|
||||
//%if instance
|
||||
export interface InstanceConfigFields {
|
||||
"// plugin_name //.myInstanceField": string;
|
||||
}// [] //// [ctl] //
|
||||
"__plugin_name__.myInstanceField": string;
|
||||
}
|
||||
//%endif
|
||||
//%if ctl
|
||||
export interface ControlConfigFields {
|
||||
"// plugin_name //.myControlField": string;
|
||||
}// [] //
|
||||
"__plugin_name__.myControlField": string;
|
||||
}
|
||||
//%endif
|
||||
}
|
||||
|
||||
export const plugin: lib.PluginDeclaration = {
|
||||
name: "// plugin_name //",
|
||||
title: "// plugin_name //",
|
||||
description: "I didn't update my description",// [entry_points] //
|
||||
// entry_points //
|
||||
// [] //// [controller] //
|
||||
name: "__plugin_name__",
|
||||
title: "__plugin_name__",
|
||||
description: "I didn't update my description",
|
||||
//%if controller
|
||||
|
||||
controllerEntrypoint: "./dist/node/controller",
|
||||
controllerConfigFields: {
|
||||
"// plugin_name //.myControllerField": {
|
||||
"__plugin_name__.myControllerField": {
|
||||
title: "My Controller Field",
|
||||
description: "This should be removed",
|
||||
type: "string",
|
||||
initialValue: "Remove Me",
|
||||
},
|
||||
},// [] //// [host] //
|
||||
},
|
||||
//%endif
|
||||
//%if host
|
||||
|
||||
hostEntrypoint: "./dist/node/host",
|
||||
hostConfigFields: {
|
||||
"// plugin_name //.myHostField": {
|
||||
"__plugin_name__.myHostField": {
|
||||
title: "My Host Field",
|
||||
description: "This should be removed",
|
||||
type: "string",
|
||||
initialValue: "Remove Me",
|
||||
},
|
||||
},// [] //// [instance] //
|
||||
},
|
||||
//%endif
|
||||
//%if instance
|
||||
|
||||
instanceEntrypoint: "./dist/node/instance",
|
||||
instanceConfigFields: {
|
||||
"// plugin_name //.myInstanceField": {
|
||||
"__plugin_name__.myInstanceField": {
|
||||
title: "My Instance Field",
|
||||
description: "This should be removed",
|
||||
type: "string",
|
||||
initialValue: "Remove Me",
|
||||
},
|
||||
},// [] //// [ctl] //
|
||||
},
|
||||
//%endif
|
||||
//%if ctl
|
||||
|
||||
ctlEntrypoint: "./dist/node/ctl",
|
||||
controlConfigFields: {
|
||||
"// plugin_name //.myControlField": {
|
||||
"__plugin_name__.myControlField": {
|
||||
title: "My Control Field",
|
||||
description: "This should be removed",
|
||||
type: "string",
|
||||
initialValue: "Remove Me",
|
||||
},
|
||||
},// [] //
|
||||
},
|
||||
//%endif
|
||||
|
||||
messages: [
|
||||
Messages.PluginExampleEvent,
|
||||
Messages.PluginExampleRequest,// [subscribable] //
|
||||
Messages.ExampleSubscribableUpdate,// [] //
|
||||
Messages.PluginExampleRequest,
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
Messages.ExampleSubscribableUpdate,
|
||||
//%endif
|
||||
],
|
||||
//%if controller | web // The controller always includes web entry even if there is no content
|
||||
|
||||
webEntrypoint: "./web",
|
||||
routes: [],
|
||||
//%endif
|
||||
};
|
||||
@@ -1,17 +1,21 @@
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { BaseInstancePlugin } from "@clusterio/host";
|
||||
import { PluginExampleEvent, PluginExampleRequest } from "./messages";// [module] //
|
||||
import { PluginExampleEvent, PluginExampleRequest } from "./messages";
|
||||
//%if module
|
||||
|
||||
type PuginExampleIPC = {
|
||||
tick: number,
|
||||
player_name: string,
|
||||
};// [] //
|
||||
};
|
||||
//%endif
|
||||
|
||||
export class InstancePlugin extends BaseInstancePlugin {
|
||||
async init() {
|
||||
this.instance.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
|
||||
this.instance.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));// [module] //
|
||||
this.instance.server.handle("ipc-// plugin_name //-plugin_example_ipc", this.handlePluginExampleIPC.bind(this));// [] //
|
||||
this.instance.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
|
||||
//%if module
|
||||
this.instance.server.handle("ipc-__plugin_name__-plugin_example_ipc", this.handlePluginExampleIPC.bind(this));
|
||||
//%endif
|
||||
}
|
||||
|
||||
async onInstanceConfigFieldChanged(field: string, curr: unknown, prev: unknown) {
|
||||
@@ -27,8 +31,10 @@ export class InstancePlugin extends BaseInstancePlugin {
|
||||
}
|
||||
|
||||
async onPlayerEvent(event: lib.PlayerEvent) {
|
||||
this.logger.info(`onPlayerEvent::onPlayerEvent ${JSON.stringify(event)}`);// [module] //
|
||||
this.sendRcon("/sc ipc_// plugin_name //.foo()");// [] //
|
||||
this.logger.info(`onPlayerEvent::onPlayerEvent ${JSON.stringify(event)}`);
|
||||
//%if module
|
||||
this.sendRcon("/sc ipc___plugin_name__.foo()");
|
||||
//%endif
|
||||
}
|
||||
|
||||
async handlePluginExampleEvent(event: PluginExampleEvent) {
|
||||
@@ -41,9 +47,11 @@ export class InstancePlugin extends BaseInstancePlugin {
|
||||
myResponseString: request.myString,
|
||||
myResponseNumbers: request.myNumberArray,
|
||||
};
|
||||
}// [module] //
|
||||
}
|
||||
//%if module
|
||||
|
||||
async handlePluginExampleIPC(event: PuginExampleIPC) {
|
||||
this.logger.info(JSON.stringify(event));
|
||||
}// [] //
|
||||
}
|
||||
//%endif
|
||||
}
|
||||
@@ -6,8 +6,8 @@ export class PluginExampleEvent {
|
||||
static type = "event" as const;
|
||||
static src = ["host", "control"] as const;
|
||||
static dst = ["controller", "host", "instance"] as const;
|
||||
static plugin = "// plugin_name //" as const;
|
||||
static permission = "// plugin_name //.example.permission.event";
|
||||
static plugin = "__plugin_name__" as const;
|
||||
static permission = "__plugin_name__.example.permission.event";
|
||||
|
||||
constructor(
|
||||
public myString: string,
|
||||
@@ -30,8 +30,8 @@ export class PluginExampleRequest {
|
||||
static type = "request" as const;
|
||||
static src = ["host", "control"] as const;
|
||||
static dst = ["controller", "host", "instance"] as const;
|
||||
static plugin = "// plugin_name //" as const;
|
||||
static permission = "// plugin_name //.example.permission.request";
|
||||
static plugin = "__plugin_name__" as const;
|
||||
static permission = "__plugin_name__.example.permission.request";
|
||||
|
||||
constructor(
|
||||
public myString: string,
|
||||
@@ -52,7 +52,8 @@ export class PluginExampleRequest {
|
||||
"myResponseString": Type.String(),
|
||||
"myResponseNumbers": Type.Array(Type.Number()),
|
||||
}));
|
||||
}// [subscribable] //
|
||||
}
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
|
||||
export class ExampleSubscribableValue {
|
||||
constructor(
|
||||
@@ -78,8 +79,8 @@ export class ExampleSubscribableUpdate {
|
||||
static type = "event" as const;
|
||||
static src = "controller" as const;
|
||||
static dst = "control" as const;
|
||||
static plugin = "// plugin_name //" as const;
|
||||
static permission = "// plugin_name //.example.permission.subscribe";
|
||||
static plugin = "__plugin_name__" as const;
|
||||
static permission = "__plugin_name__.example.permission.subscribe";
|
||||
|
||||
constructor(
|
||||
public updates: ExampleSubscribableValue[],
|
||||
@@ -92,4 +93,5 @@ export class ExampleSubscribableUpdate {
|
||||
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
||||
return new this(json.updates.map(update => ExampleSubscribableValue.fromJSON(update)));
|
||||
}
|
||||
}// [] //// [] //
|
||||
}
|
||||
//%endif
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "// plugin_name //",
|
||||
"load": [
|
||||
"control.lua"
|
||||
],
|
||||
"require": [
|
||||
// [instance] //"module_exports.lua"// [] //
|
||||
],
|
||||
"dependencies": [
|
||||
|
||||
]
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"name": "// plugin_name //",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"main": "dist/plugin/index.js",
|
||||
"scripts": {
|
||||
"prepare": "// prepare //"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@clusterio/lib": "^2.0.0-alpha.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
// dev_dependencies //
|
||||
"@clusterio/lib": "^2.0.0-alpha.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sinclair/typebox": "^0.30.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"keywords": [
|
||||
"clusterio",
|
||||
"factorio"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.browser.json",
|
||||
"references": [
|
||||
{ "path": "../../packages/lib/tsconfig.browser.json" },
|
||||
{ "path": "../../packages/web_ui/tsconfig.browser.json" },
|
||||
],
|
||||
"include": [ "web/**/*.tsx", "web/**/*.ts", "messages.ts", "package.json" ],
|
||||
"compilerOptions": {
|
||||
"outDir": "dist/browser",
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.node.json",
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "../../packages/lib" },
|
||||
],
|
||||
"include": ["./**/*.ts"],
|
||||
"exclude": ["test/*", "./dist/*"],
|
||||
"compilerOptions": {
|
||||
"outDir": "dist/plugin",
|
||||
},
|
||||
//%if web
|
||||
{ "path": "./tsconfig.browser.json" },
|
||||
//%endif
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.node.json",
|
||||
"references": [
|
||||
{ "path": "../../packages/lib/tsconfig.node.json" },
|
||||
],
|
||||
"include": ["./**/*.ts"],
|
||||
"exclude": ["test/*", "./dist/*"],
|
||||
"compilerOptions": {
|
||||
"outDir": "dist/node",
|
||||
},
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.web.json",
|
||||
"references": [
|
||||
{ "path": "../../packages/lib/tsconfig.web.json" },
|
||||
{ "path": "../../packages/web_ui/tsconfig.web.json" },
|
||||
],
|
||||
"include": [ "web/**/*.tsx", "web/**/*.ts", "messages.ts", "package.json" ],
|
||||
"compilerOptions": {
|
||||
"outDir": "dist/web/build",
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,68 @@
|
||||
import React, { useContext, useEffect, useState/*/ [subscribable] /*/, useCallback, useSyncExternalStore/*/ [] /*/ } from "react";
|
||||
// import { } from "antd";
|
||||
import React, {
|
||||
useContext, useEffect, useState,
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
useCallback, useSyncExternalStore,
|
||||
//%endif
|
||||
} from "react";
|
||||
|
||||
// import {
|
||||
//
|
||||
// } from "antd";
|
||||
|
||||
import {
|
||||
BaseWebPlugin, PageLayout, Control, ControlContext, notifyErrorHandler,
|
||||
} from "@clusterio/web_ui";
|
||||
|
||||
import {
|
||||
PluginExampleEvent, PluginExampleRequest,
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
ExampleSubscribableUpdate,
|
||||
//%endif
|
||||
} from "../messages";
|
||||
|
||||
import * as lib from "@clusterio/lib";
|
||||
import { PluginExampleEvent, PluginExampleRequest/*/ [subscribable] /*/, ExampleSubscribableUpdate/*/ [] /*/ } from "../messages";
|
||||
|
||||
function MyTemplatePage() {
|
||||
const control = useContext(ControlContext);// [subscribable] //
|
||||
const plugin = control.plugins.get("// plugin_name //") as WebPlugin;
|
||||
const [subscribableData, synced] = plugin.useSubscribableData();// [] //
|
||||
const control = useContext(ControlContext);
|
||||
//%if controller & web // 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>// [subscribable] //
|
||||
Synced: {String(synced)} Data: {JSON.stringify([...subscribableData.values()])}// [] //
|
||||
return <PageLayout nav={[{ name: "__plugin_name__" }]}>
|
||||
<h2>__plugin_name__</h2>
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
Synced: {String(synced)} Data: {JSON.stringify([...subscribableData.values()])}
|
||||
//%endif
|
||||
</PageLayout>;
|
||||
}
|
||||
|
||||
export class WebPlugin extends BaseWebPlugin {// [subscribable] //
|
||||
export class WebPlugin extends BaseWebPlugin {
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
subscribableData = new lib.EventSubscriber(ExampleSubscribableUpdate, this.control);
|
||||
// [] //
|
||||
|
||||
//%endif
|
||||
async init() {
|
||||
this.pages = [
|
||||
{
|
||||
path: "/// plugin_name //",
|
||||
sidebarName: "// plugin_name //",
|
||||
permission: "// plugin_name //.page.view",
|
||||
path: "/__plugin_name__",
|
||||
sidebarName: "__plugin_name__",
|
||||
permission: "__plugin_name__.page.view",
|
||||
content: <MyTemplatePage/>,
|
||||
},
|
||||
];
|
||||
|
||||
this.control.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this));
|
||||
this.control.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));
|
||||
}// [subscribable] //
|
||||
}
|
||||
//%if controller & web // Subscribing requires web content and the controller
|
||||
|
||||
useSubscribableData() {
|
||||
const control = useContext(ControlContext);
|
||||
const subscribe = useCallback((callback: () => void) => this.subscribableData.subscribe(callback), [control]);
|
||||
return useSyncExternalStore(subscribe, () => this.subscribableData.getSnapshot());
|
||||
}// [] //
|
||||
}
|
||||
//%endif
|
||||
|
||||
async handlePluginExampleEvent(event: PluginExampleEvent) {
|
||||
this.logger.info(JSON.stringify(event));
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
"use strict";
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const { merge } = require("webpack-merge");
|
||||
|
||||
const common = require("@clusterio/web_ui/webpack.common");
|
||||
|
||||
module.exports = (env = {}) => merge(common(env), {
|
||||
context: __dirname,
|
||||
entry: "./web/index.tsx",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist", "web"),
|
||||
},
|
||||
plugins: [
|
||||
new webpack.container.ModuleFederationPlugin({
|
||||
name: "// plugin_name //",
|
||||
library: { type: "var", name: "plugin_// plugin_name //" },
|
||||
exposes: {
|
||||
"./": "./index.ts",
|
||||
"./package.json": "./package.json",
|
||||
},
|
||||
shared: {
|
||||
"@clusterio/lib": { import: false },
|
||||
"@clusterio/web_ui": { import: false },
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
# Templates
|
||||
|
||||
Create has the ability to create new plugins from templates to avoid needing to manually copy lots of boiler plate. This folder contains all those templates for both Javascript and Typescript plugins. These templates have custom preprocessor instructions that can be used to alter a template based on developer input, this syntax is described below.
|
||||
|
||||
- [The Basics](#the-basics)
|
||||
- [Insert Instruction](#insert-instruction) (`__variable__`)
|
||||
- [Conditional Instruction](#conditional-instruction) (`//%if` `//%endif`)
|
||||
- [Available Variables](#available-variables)
|
||||
|
||||
## The Basics
|
||||
|
||||
All files can contain preprocessor instructions whether json, js, lua, or anything else. All instructions use the same syntax although it has been designed to work well with js and ts files.
|
||||
|
||||
Except for the insert instruction, all preprocessors use a `//%` prefix and must be on their own line. These lines are never included in the output. Parsing is stopped when `//` is encountered to allow for comments on preprocessors. Eg. `//%if foo // this is a comment`.
|
||||
|
||||
Empty instructions, aka just comments, can be inserted into templates using `//%//` and are not included in the output file.
|
||||
|
||||
`templates/plugin-js` contains all the files which will be used by a javascript only plugin.
|
||||
`templates/plugin-ts` contains all the files which will be used by a plugin that supports typescript.
|
||||
`templates/common` contains files used by both types of plugins, this is mostly lua code or root files like `package.json`
|
||||
|
||||
Files are not copied via wildcards and instead are explicitly listed within `template.js` so any new templates will need to be added there.
|
||||
|
||||
## Insert Instruction
|
||||
|
||||
There are a number of template variables available to be inserted into templates. All variables are raw string values, that is they are inserted directly into the output file without any modifications. To signify a place to insert a variable you need only surround its identifier with double underscores, examples below.
|
||||
|
||||
Example Variables:
|
||||
|
||||
```sh
|
||||
my_number=1
|
||||
my_string=foo
|
||||
my_property=bar: "baz"
|
||||
```
|
||||
|
||||
Example Template:
|
||||
|
||||
```ts
|
||||
const __my_string__: number = __my_number__;
|
||||
console.log(__my_string__);
|
||||
|
||||
console.log({
|
||||
foo: "Hello, __my_string__!",
|
||||
__my_property__,
|
||||
my_property: __my_string__,
|
||||
});
|
||||
```
|
||||
|
||||
Example Output:
|
||||
|
||||
```ts
|
||||
const foo: number = 1;
|
||||
console.log(foo);
|
||||
|
||||
console.log({
|
||||
foo: "Hello, foo!",
|
||||
bar: "baz",
|
||||
my_property: foo,
|
||||
});
|
||||
```
|
||||
|
||||
## Conditional Instruction
|
||||
|
||||
Conditionals allow for code to be included/omitted based on the value of template variables. Currently only boolean logic in [disjunctive normal form](https://en.wikipedia.org/wiki/Disjunctive_normal_form) is supported and any variables that are not booleans are evaluated with the [Boolean constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean).
|
||||
|
||||
Variables used in condition statements do not require double underscores. Their values can be inverted using `!`. Arithmetic and comparison operations are not supported. Parentheses are also not supported, conditions must be in DNF.
|
||||
|
||||
Conversion to DNF:
|
||||
`A & (B | !C)` -> `A & B | A & !C` evaluated as `(A and B) or (A and not C)`
|
||||
|
||||
Example Variables:
|
||||
|
||||
```sh
|
||||
my_string=foo
|
||||
say_hello=true
|
||||
say_done=true
|
||||
verbose=false
|
||||
```
|
||||
|
||||
Example Template:
|
||||
|
||||
```ts
|
||||
//%if verbose
|
||||
console.log("Starting...");
|
||||
//%endif
|
||||
//%if say_hello & my_string
|
||||
console.log("Hello, __my_string__!");
|
||||
//%endif
|
||||
//%if say_done | verbose
|
||||
console.log("Done.");
|
||||
//%endif
|
||||
```
|
||||
|
||||
Example Output:
|
||||
|
||||
```ts
|
||||
console.log("Hello, foo!");
|
||||
console.log("Done.");
|
||||
```
|
||||
|
||||
## Available Variables
|
||||
|
||||
| Identifier | Example | Description |
|
||||
| - | - | - |
|
||||
| typescript | `true` / `false` | `true` if typescript templates are included |
|
||||
| controller | `true` / `false` | `true` if controller templates are included |
|
||||
| host | `true` / `false` | `true` if host templates are included |
|
||||
| instance | `true` / `false` | `true` if instance templates are included |
|
||||
| module | `true` / `false` | `true` if module templates are included |
|
||||
| ctl | `true` / `false` | `true` if control templates are included |
|
||||
| web | `true` / `false` | `true` if web templates are included |
|
||||
| plugin_name | `my_plugin` | name of the plugin |
|
||||
| prepare | `tsc --build` | commands to run for the prepare script |
|
||||
| ext | `ts` / `js` | file extension of source files |
|
||||
Reference in new issue
Block a user