From 534dc14221392d7485078acf5cc53141f2ddf053 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Sun, 21 Jan 2024 23:27:19 +0000 Subject: [PATCH 01/29] Add create command mode/args for plugin templates --- packages/create/create.js | 42 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/create/create.js b/packages/create/create.js index c222ab04..668a8179 100755 --- a/packages/create/create.js +++ b/packages/create/create.js @@ -501,10 +501,15 @@ WantedBy=multi-user.target } } +async function copyPluginTemplate(templates) { + logger.info(`Please wait, coping template for ${templates.join(", ")}`); +} + // eslint-disable-next-line complexity async function inquirerMissingArgs(args) { let answers = {}; if (args.mode) { answers.mode = args.mode; } + if (args["plugin-template"]) { answers.mode = "plugin-template"; } answers = await inquirer.prompt([ { type: "list", @@ -517,6 +522,7 @@ async function inquirerMissingArgs(args) { { name: "Host only", value: "host" }, { name: "Ctl only", value: "ctl" }, { name: "Plugins only", value: "plugins" }, + { name: "Plugin template", value: "plugin-template" }, ], }, ], answers); @@ -644,6 +650,28 @@ async function inquirerMissingArgs(args) { } } + if (answers.mode === "plugin-template") { + if (args["plugin-template"]) { + answers.pluginTemplate = args["plugin-template"]; + } + answers.plugins = []; + answers = await inquirer.prompt([ + { + type: "checkbox", + name: "pluginTemplate", + message: "Plugin templates to use", + choices: [ + { name: "Controller", value: "controller" }, + { name: "Host", value: "host" }, + { name: "Instance", value: "instance" }, + { name: "Lua Module", value: "module" }, + { name: "Command Line", value: "ctl" }, + { name: "Web UI", value: "web" }, + ], + }, + ], answers); + } + if (dev) { answers.plugins = []; } else if (args.plugins) { @@ -720,7 +748,7 @@ async function main() { }) .option("mode", { nargs: 1, describe: "Operating mode to install", - choices: ["standalone", "controller", "host", "ctl", "plugins"], + choices: ["standalone", "controller", "host", "ctl", "plugins", "plugin-template"], }) .option("migrate-rename", { nargs: 0, describe: "Migrate from before slave/master rename", default: false, type: "boolean", @@ -746,6 +774,9 @@ async function main() { .option("plugins", { array: true, describe: "Plugins to install", type: "string", }) + .option("plugin-template", { + array: true, describe: "Plugin template to use [plugin-template]", type: "string", + }) .strict() ; @@ -779,6 +810,13 @@ async function main() { } let answers = await inquirerMissingArgs(args); + logger.verbose(JSON.stringify(answers)); + + if (answers.pluginTemplate) { + await copyPluginTemplate(answers.pluginTemplate); + return; + } + if (answers.downloadHeadless) { if (answers.factorioDir !== "factorio") { throw new InstallError("--download-headless option requires --factorio-dir to be set to factorio"); @@ -786,8 +824,6 @@ async function main() { await downloadLinuxServer(); } - logger.verbose(JSON.stringify(answers)); - if (!dev) { await installClusterio(answers.mode, answers.plugins); } From c81d2b61a6ff0276da8b94762a893a49f73d8988 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Mon, 22 Jan 2024 02:47:34 +0000 Subject: [PATCH 02/29] Add template copying --- .vscode/settings.json | 3 + packages/create/create.js | 94 ++++++++++++++++++- .../create/templates/plugin-ts/.gitignore | 2 + .../create/templates/plugin-ts/.npmignore | 2 + .../create/templates/plugin-ts/controller.ts | 0 packages/create/templates/plugin-ts/ctl.ts | 0 packages/create/templates/plugin-ts/host.ts | 0 packages/create/templates/plugin-ts/index.ts | 27 ++++++ .../create/templates/plugin-ts/instance.ts | 0 .../create/templates/plugin-ts/messages.ts | 55 +++++++++++ .../templates/plugin-ts/module/control.lua | 0 .../templates/plugin-ts/module/module.json | 12 +++ .../plugin-ts/module/module_exports.lua | 3 + .../create/templates/plugin-ts/package.json | 31 ++++++ .../create/templates/plugin-ts/tsconfig.json | 11 +++ .../templates/plugin-ts/tsconfig.web.json | 11 +++ .../templates/plugin-ts/web/no_plugin.tsx | 0 .../create/templates/plugin-ts/web/plugin.tsx | 0 .../templates/plugin-ts/webpack.config.js | 28 ++++++ 19 files changed, 278 insertions(+), 1 deletion(-) create mode 100644 packages/create/templates/plugin-ts/.gitignore create mode 100644 packages/create/templates/plugin-ts/.npmignore create mode 100644 packages/create/templates/plugin-ts/controller.ts create mode 100644 packages/create/templates/plugin-ts/ctl.ts create mode 100644 packages/create/templates/plugin-ts/host.ts create mode 100644 packages/create/templates/plugin-ts/index.ts create mode 100644 packages/create/templates/plugin-ts/instance.ts create mode 100644 packages/create/templates/plugin-ts/messages.ts create mode 100644 packages/create/templates/plugin-ts/module/control.lua create mode 100644 packages/create/templates/plugin-ts/module/module.json create mode 100644 packages/create/templates/plugin-ts/module/module_exports.lua create mode 100644 packages/create/templates/plugin-ts/package.json create mode 100644 packages/create/templates/plugin-ts/tsconfig.json create mode 100644 packages/create/templates/plugin-ts/tsconfig.web.json create mode 100644 packages/create/templates/plugin-ts/web/no_plugin.tsx create mode 100644 packages/create/templates/plugin-ts/web/plugin.tsx create mode 100644 packages/create/templates/plugin-ts/webpack.config.js diff --git a/.vscode/settings.json b/.vscode/settings.json index 64e9720d..e800d22c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,4 +8,7 @@ "editor.indentSize": 4, "editor.insertSpaces": true, }, + "cSpell.words": [ + "npmignore" + ], } diff --git a/packages/create/create.js b/packages/create/create.js index 668a8179..9fb960ba 100755 --- a/packages/create/create.js +++ b/packages/create/create.js @@ -501,8 +501,99 @@ 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.replace(/\/\/ ([a-z_]*) \/\//g, (_, value) => { + if (!props.hasOwnProperty(value)) { + logger.warn(`Unknown property string (${value}) found in template file ${src}`); + return value; + } + + return props[value]; + }); + + await fs.outputFile(dst, outputData); +} + async function copyPluginTemplate(templates) { - logger.info(`Please wait, coping template for ${templates.join(", ")}`); + logger.info(`Please wait, coping templates for ${templates.join(", ")}`); + const noTypescript = templates.includes("js"); + const entryPoints = []; + const files = new Map(); + + let templatePath = "./templates/plugin-ts"; + if (noTypescript) { + templatePath = "./templates/plugin-js"; + logger.error("Javascript only is not currently supported"); + return; + } + + templatePath = path.resolve(__dirname, templatePath); + + // Files includes in all templates (package.json has its own logic) + files.set(".gitignore", path.join(templatePath, ".gitignore")); + files.set(".npmignore", path.join(templatePath, ".npmignore")); + files.set("tsconfig.json", path.join(templatePath, "tsconfig.json")); + files.set("messages.ts", path.join(templatePath, "messages.ts")); + files.set("index.ts", path.join(templatePath, "index.ts")); + + if (templates.includes("controller")) { + entryPoints.push("webEntrypoint: \"./web\","); + entryPoints.push("controllerEntrypoint: \"dist/plugin/controller\","); + files.set("controller.ts", path.join(templatePath, "controller.ts")); + files.set("webpack.config.js", path.join(templatePath, "webpack.config.js")); + files.set("tsconfig.web.json", path.join(templatePath, "tsconfig.web.json")); + files.set("web/index.tsx", path.join(templatePath, + templates.includes("web") ? "web/plugin.tsx" : "web/no_plugin.tsx" + )); + } else if (templates.includes("web")) { + entryPoints.push("webEntrypoint: \"./web\","); + files.set("web/index.tsx", path.join(templatePath, "web/plugin.tsx")); + files.set("webpack.config.js", path.join(templatePath, "webpack.config.js")); + files.set("tsconfig.web.json", path.join(templatePath, "tsconfig.web.json")); + } + + if (templates.includes("host")) { + entryPoints.push("hostEntrypoint: \"dist/plugin/host\","); + files.set("host.ts", path.join(templatePath, "host.ts")); + } + + if (templates.includes("instance")) { + entryPoints.push("instanceEntrypoint: \"dist/plugin/instance\","); + files.set("instance.ts", path.join(templatePath, "instance.ts")); + } + + if (templates.includes("module")) { + 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")) { + files.set("ctl.ts", path.join(templatePath, "ctl.ts")); + } + + // Props that will be replaced in the template files, found with "// prop_name //" + const props = { + plugin_name: "TBC", + entry_points: entryPoints.join("\n\t"), + }; + + const writes = []; + for (let [dst, src] of files) { + writes.push(copyPluginTemplateFile(src, dst, props)); + } + + // TODO package.json + + await Promise.all(writes); + logger.info("Successfully wrote all template files"); } // eslint-disable-next-line complexity @@ -667,6 +758,7 @@ async function inquirerMissingArgs(args) { { name: "Lua Module", value: "module" }, { name: "Command Line", value: "ctl" }, { name: "Web UI", value: "web" }, + { name: "No Typescript", value: "js" }, ], }, ], answers); diff --git a/packages/create/templates/plugin-ts/.gitignore b/packages/create/templates/plugin-ts/.gitignore new file mode 100644 index 00000000..1eae0cf6 --- /dev/null +++ b/packages/create/templates/plugin-ts/.gitignore @@ -0,0 +1,2 @@ +dist/ +node_modules/ diff --git a/packages/create/templates/plugin-ts/.npmignore b/packages/create/templates/plugin-ts/.npmignore new file mode 100644 index 00000000..ebd99907 --- /dev/null +++ b/packages/create/templates/plugin-ts/.npmignore @@ -0,0 +1,2 @@ +webpack.config.js +dist/web/build diff --git a/packages/create/templates/plugin-ts/controller.ts b/packages/create/templates/plugin-ts/controller.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/create/templates/plugin-ts/ctl.ts b/packages/create/templates/plugin-ts/ctl.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/create/templates/plugin-ts/host.ts b/packages/create/templates/plugin-ts/host.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/create/templates/plugin-ts/index.ts b/packages/create/templates/plugin-ts/index.ts new file mode 100644 index 00000000..2ecceb6a --- /dev/null +++ b/packages/create/templates/plugin-ts/index.ts @@ -0,0 +1,27 @@ +import * as lib from "@clusterio/lib"; + +import { PluginExampleEvent, PluginExampleRequest } from "./messages"; + +lib.definePermission({ + 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", + title: "Example permission request", + description: "My plugin's example permission that I forgot to remove", +}); + +export const plugin: lib.PluginDeclaration = { + name: "// plugin_name //", + title: "// plugin_name //", + description: "I didn't update my description", + // entry_points // + + messages: [ + PluginExampleEvent, + PluginExampleRequest, + ], +}; diff --git a/packages/create/templates/plugin-ts/instance.ts b/packages/create/templates/plugin-ts/instance.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/create/templates/plugin-ts/messages.ts b/packages/create/templates/plugin-ts/messages.ts new file mode 100644 index 00000000..f207d6a1 --- /dev/null +++ b/packages/create/templates/plugin-ts/messages.ts @@ -0,0 +1,55 @@ +import { plainJson, jsonArray, JsonBoolean, JsonNumber, JsonString, StringEnum } from "@clusterio/lib"; +import { Type, Static } from "@sinclair/typebox"; + +export class PluginExampleEvent { + declare ["constructor"]: typeof PluginExampleEvent; + static type = "event" as const; + static src = ["host"] as const; + static dst = "controller" as const; + static plugin = "// plugin_name //" as const; + static permission = "// plugin_name //.example.permission.event"; + + constructor( + public myString: string, + public myNumberArray: number[], + ) { + } + + static jsonSchema = Type.Object({ + "myString": Type.String(), + "myNumberArray": Type.Array(Type.Number()), + }); + + static fromJson(json: Static) { + return new PluginExampleEvent(json.myString, json.myNumberArray); + } +} + +export class PluginExampleRequest { + declare ["constructor"]: typeof PluginExampleEvent; + static type = "request" as const; + static src = ["host"] as const; + static dst = "controller" as const; + static plugin = "// plugin_name //" as const; + static permission = "// plugin_name //.example.permission.request"; + + constructor( + public myString: string, + public myNumberArray: number[], + ) { + } + + static jsonSchema = Type.Object({ + "myString": Type.String(), + "myNumberArray": Type.Array(Type.Number()), + }); + + static fromJson(json: Static) { + return new PluginExampleEvent(json.myString, json.myNumberArray); + } + + static Response = plainJson(Type.Object({ + "myResponseString": Type.String(), + "myResponseNumbers": Type.Array(Type.Number()), + })); +} diff --git a/packages/create/templates/plugin-ts/module/control.lua b/packages/create/templates/plugin-ts/module/control.lua new file mode 100644 index 00000000..e69de29b diff --git a/packages/create/templates/plugin-ts/module/module.json b/packages/create/templates/plugin-ts/module/module.json new file mode 100644 index 00000000..1edcac37 --- /dev/null +++ b/packages/create/templates/plugin-ts/module/module.json @@ -0,0 +1,12 @@ +{ + "name": "// plugin_name //", + "load": [ + "control.lua" + ], + "require": [ + + ], + "dependencies": [ + + ] +} diff --git a/packages/create/templates/plugin-ts/module/module_exports.lua b/packages/create/templates/plugin-ts/module/module_exports.lua new file mode 100644 index 00000000..29cbb493 --- /dev/null +++ b/packages/create/templates/plugin-ts/module/module_exports.lua @@ -0,0 +1,3 @@ +local Control = require("./control") + +return Control diff --git a/packages/create/templates/plugin-ts/package.json b/packages/create/templates/plugin-ts/package.json new file mode 100644 index 00000000..45b71e49 --- /dev/null +++ b/packages/create/templates/plugin-ts/package.json @@ -0,0 +1,31 @@ +{ + "name": "__plugin_name__", + "version": "0.1.0", + "description": "", + "main": "dist/plugin/index.js", + "scripts": { + "prepare": "tsc --build && webpack-cli --env production" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@clusterio/lib": "^2.0.0-alpha.0" + }, + "devDependencies": { + "@clusterio/lib": "^2.0.0-alpha.14", + "@clusterio/web_ui": "^2.0.0-alpha.14", + "@types/node": "^20.4.5", + "@types/react": "^18.2.21", + "typescript": "^5.1.6", + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4", + "webpack-merge": "^5.9.0" + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@sinclair/typebox": "^0.30.4" + } +} diff --git a/packages/create/templates/plugin-ts/tsconfig.json b/packages/create/templates/plugin-ts/tsconfig.json new file mode 100644 index 00000000..087576d6 --- /dev/null +++ b/packages/create/templates/plugin-ts/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.node.json", + "references": [ + { "path": "../../packages/lib" }, + ], + "include": ["./**/*.ts"], + "exclude": ["test/*", "./dist/*"], + "compilerOptions": { + "outDir": "dist/plugin", + }, +} diff --git a/packages/create/templates/plugin-ts/tsconfig.web.json b/packages/create/templates/plugin-ts/tsconfig.web.json new file mode 100644 index 00000000..c37f9141 --- /dev/null +++ b/packages/create/templates/plugin-ts/tsconfig.web.json @@ -0,0 +1,11 @@ +{ + "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", + } +} diff --git a/packages/create/templates/plugin-ts/web/no_plugin.tsx b/packages/create/templates/plugin-ts/web/no_plugin.tsx new file mode 100644 index 00000000..e69de29b diff --git a/packages/create/templates/plugin-ts/web/plugin.tsx b/packages/create/templates/plugin-ts/web/plugin.tsx new file mode 100644 index 00000000..e69de29b diff --git a/packages/create/templates/plugin-ts/webpack.config.js b/packages/create/templates/plugin-ts/webpack.config.js new file mode 100644 index 00000000..85da4267 --- /dev/null +++ b/packages/create/templates/plugin-ts/webpack.config.js @@ -0,0 +1,28 @@ +"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 }, + }, + }), + ], +}); From be8bba8ff81a8bf68e5309ba679d8bfc886604c0 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Mon, 22 Jan 2024 05:01:09 +0000 Subject: [PATCH 03/29] Complete template files --- packages/create/create.js | 19 ++++++-- .../create/templates/plugin-ts/controller.ts | 42 +++++++++++++++++ packages/create/templates/plugin-ts/ctl.ts | 26 +++++++++++ packages/create/templates/plugin-ts/host.ts | 30 +++++++++++++ .../create/templates/plugin-ts/instance.ts | 38 ++++++++++++++++ .../create/templates/plugin-ts/messages.ts | 10 ++--- .../templates/plugin-ts/module/control.lua | 24 ++++++++++ .../plugin-ts/module/module_exports.lua | 3 +- .../create/templates/plugin-ts/web/plugin.tsx | 45 +++++++++++++++++++ 9 files changed, 227 insertions(+), 10 deletions(-) diff --git a/packages/create/create.js b/packages/create/create.js index 9fb960ba..b8c328ed 100755 --- a/packages/create/create.js +++ b/packages/create/create.js @@ -521,7 +521,7 @@ async function copyPluginTemplateFile(src, dst, props) { await fs.outputFile(dst, outputData); } -async function copyPluginTemplate(templates) { +async function copyPluginTemplate(pluginName, templates) { logger.info(`Please wait, coping templates for ${templates.join(", ")}`); const noTypescript = templates.includes("js"); const entryPoints = []; @@ -581,7 +581,7 @@ async function copyPluginTemplate(templates) { // Props that will be replaced in the template files, found with "// prop_name //" const props = { - plugin_name: "TBC", + plugin_name: pluginName, entry_points: entryPoints.join("\n\t"), }; @@ -745,6 +745,9 @@ async function inquirerMissingArgs(args) { if (args["plugin-template"]) { answers.pluginTemplate = args["plugin-template"]; } + if (args["plugin-name"]) { + answers.pluginName = args["plugin-name"]; + } answers.plugins = []; answers = await inquirer.prompt([ { @@ -762,6 +765,13 @@ async function inquirerMissingArgs(args) { ], }, ], answers); + answers = await inquirer.prompt([ + { + type: "input", + name: "pluginName", + message: "Name of your new plugin", + }, + ], answers); } if (dev) { @@ -869,6 +879,9 @@ async function main() { .option("plugin-template", { array: true, describe: "Plugin template to use [plugin-template]", type: "string", }) + .option("plugin-name", { + nargs: 1, describe: "Name of your new plugin [plugin-template]", type: "string", + }) .strict() ; @@ -905,7 +918,7 @@ async function main() { logger.verbose(JSON.stringify(answers)); if (answers.pluginTemplate) { - await copyPluginTemplate(answers.pluginTemplate); + await copyPluginTemplate(answers.pluginName, answers.pluginTemplate); return; } diff --git a/packages/create/templates/plugin-ts/controller.ts b/packages/create/templates/plugin-ts/controller.ts index e69de29b..ee6d56b7 100644 --- a/packages/create/templates/plugin-ts/controller.ts +++ b/packages/create/templates/plugin-ts/controller.ts @@ -0,0 +1,42 @@ +import * as lib from "@clusterio/lib"; +import { BaseControllerPlugin, InstanceInfo } from "@clusterio/controller"; +import { PluginExampleEvent, PluginExampleRequest } from "./messages"; + +export class ControllerPlugin extends BaseControllerPlugin { + async init() { + this.controller.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this)); + this.controller.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this)); + } + + async onControllerConfigFieldChanged(field: string, curr: unknown, prev: unknown) { + this.logger.info(`controller::onControllerConfigFieldChanged ${field}`); + } + + async onInstanceConfigFieldChanged(instance: InstanceInfo, field: string, curr: unknown, prev: unknown) { + this.logger.info(`controller::onInstanceConfigFieldChanged ${instance.id} ${field}`); + } + + async onSaveData() { + this.logger.info("controller::onSaveData"); + } + + async onShutdown() { + this.logger.info("controller::onShutdown"); + } + + async onPlayerEvent(instance: InstanceInfo, event: lib.PlayerEvent) { + this.logger.info(`controller::onPlayerEvent ${instance.id} ${JSON.stringify(event)}`); + } + + async handlePluginExampleEvent(event: PluginExampleEvent) { + this.logger.info(JSON.stringify(event)); + } + + async handlePluginExampleRequest(request: PluginExampleRequest) { + this.logger.info(JSON.stringify(request)); + return { + myResponseString: request.myString, + myResponseNumbers: request.myNumberArray, + }; + } +} diff --git a/packages/create/templates/plugin-ts/ctl.ts b/packages/create/templates/plugin-ts/ctl.ts index e69de29b..adbc0d05 100644 --- a/packages/create/templates/plugin-ts/ctl.ts +++ b/packages/create/templates/plugin-ts/ctl.ts @@ -0,0 +1,26 @@ +type Control = any; // import type { Control } from "@clusterio/ctl"; +import { BaseCtlPlugin } from "@clusterio/ctl"; +import { CommandTree, Command } from "@clusterio/lib"; +import { PluginExampleEvent, PluginExampleRequest } from "./messages"; +/* eslint-disable no-console */ + +const pluginCommands = new CommandTree({ + name: "// plugin_name //", description: "The default description I forgot to change", +}); + +pluginCommands.add(new Command({ + definition: ["my-command ", "My command description that I really should change", (yargs) => { + yargs.positional("foo", { describe: "foo is foo!", type: "string" }); + }], + handler: async function(args: { foo: string }, control: Control) { + await control.sendTo("controller", new PluginExampleEvent(args.foo, [1, 2, 3])); + console.log(args); + }, +})); + +/* eslint-enable no-console */ +export class CtlPlugin extends BaseCtlPlugin { + async addCommands(rootCommand: CommandTree) { + rootCommand.add(pluginCommands); + } +} diff --git a/packages/create/templates/plugin-ts/host.ts b/packages/create/templates/plugin-ts/host.ts index e69de29b..bdfc05b5 100644 --- a/packages/create/templates/plugin-ts/host.ts +++ b/packages/create/templates/plugin-ts/host.ts @@ -0,0 +1,30 @@ +import * as lib from "@clusterio/lib"; +import { BaseHostPlugin } from "@clusterio/host"; +import { PluginExampleEvent, PluginExampleRequest } from "./messages"; + +export class HostPlugin extends BaseHostPlugin { + async init() { + this.host.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this)); + this.host.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this)); + } + + async onHostConfigFieldChanged(field: string, curr: unknown, prev: unknown) { + this.logger.info(`host::onInstanceConfigFieldChanged ${field}`); + } + + async onShutdown() { + this.logger.info("host::onShutdown"); + } + + async handlePluginExampleEvent(event: PluginExampleEvent) { + this.logger.info(JSON.stringify(event)); + } + + async handlePluginExampleRequest(request: PluginExampleRequest) { + this.logger.info(JSON.stringify(request)); + return { + myResponseString: request.myString, + myResponseNumbers: request.myNumberArray, + }; + } +} diff --git a/packages/create/templates/plugin-ts/instance.ts b/packages/create/templates/plugin-ts/instance.ts index e69de29b..f89429dd 100644 --- a/packages/create/templates/plugin-ts/instance.ts +++ b/packages/create/templates/plugin-ts/instance.ts @@ -0,0 +1,38 @@ +import * as lib from "@clusterio/lib"; +import { BaseInstancePlugin } from "@clusterio/host"; +import { PluginExampleEvent, PluginExampleRequest } from "./messages"; + +export class InstancePlugin extends BaseInstancePlugin { + async init() { + this.instance.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this)); + this.instance.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this)); + } + + async onInstanceConfigFieldChanged(field: string, curr: unknown, prev: unknown) { + this.logger.info(`instance::onInstanceConfigFieldChanged ${field}`); + } + + async onStart() { + this.logger.info("instance::onStart"); + } + + async onStop() { + this.logger.info("instance::onStop"); + } + + async onPlayerEvent(event: lib.PlayerEvent) { + this.logger.info(`onPlayerEvent::onPlayerEvent ${JSON.stringify(event)}`); + } + + async handlePluginExampleEvent(event: PluginExampleEvent) { + this.logger.info(JSON.stringify(event)); + } + + async handlePluginExampleRequest(request: PluginExampleRequest) { + this.logger.info(JSON.stringify(request)); + return { + myResponseString: request.myString, + myResponseNumbers: request.myNumberArray, + }; + } +} diff --git a/packages/create/templates/plugin-ts/messages.ts b/packages/create/templates/plugin-ts/messages.ts index f207d6a1..d357e31e 100644 --- a/packages/create/templates/plugin-ts/messages.ts +++ b/packages/create/templates/plugin-ts/messages.ts @@ -4,8 +4,8 @@ import { Type, Static } from "@sinclair/typebox"; export class PluginExampleEvent { declare ["constructor"]: typeof PluginExampleEvent; static type = "event" as const; - static src = ["host"] as const; - static dst = "controller" as const; + static src = ["host", "control"] as const; + static dst = ["controller", "host"] as const; static plugin = "// plugin_name //" as const; static permission = "// plugin_name //.example.permission.event"; @@ -26,10 +26,10 @@ export class PluginExampleEvent { } export class PluginExampleRequest { - declare ["constructor"]: typeof PluginExampleEvent; + declare ["constructor"]: typeof PluginExampleRequest; static type = "request" as const; - static src = ["host"] as const; - static dst = "controller" as const; + static src = ["host", "control"] as const; + static dst = ["controller", "host"] as const; static plugin = "// plugin_name //" as const; static permission = "// plugin_name //.example.permission.request"; diff --git a/packages/create/templates/plugin-ts/module/control.lua b/packages/create/templates/plugin-ts/module/control.lua index e69de29b..ab32acd6 100644 --- a/packages/create/templates/plugin-ts/module/control.lua +++ b/packages/create/templates/plugin-ts/module/control.lua @@ -0,0 +1,24 @@ +local clusterio_api = require("modules/clusterio/api") + +local MyModule = { + events = {}, + on_nth_tick = {}, +} + +function MyModule.foo() + game.print("foo") +end + +MyModule.events[clusterio_api.events.on_server_startup] = function(event) + game.print(game.table_to_json(event)) +end + +MyModule.events[defines.events.on_player_crafted_item] = function(event) + game.print(game.table_to_json(event)) +end + +MyModule.on_nth_tick[300] = function() + game.print(game.tick) +end + +return MyModule diff --git a/packages/create/templates/plugin-ts/module/module_exports.lua b/packages/create/templates/plugin-ts/module/module_exports.lua index 29cbb493..cc0c5632 100644 --- a/packages/create/templates/plugin-ts/module/module_exports.lua +++ b/packages/create/templates/plugin-ts/module/module_exports.lua @@ -1,3 +1,2 @@ -local Control = require("./control") -return Control +return require("./control") diff --git a/packages/create/templates/plugin-ts/web/plugin.tsx b/packages/create/templates/plugin-ts/web/plugin.tsx index e69de29b..3d329883 100644 --- a/packages/create/templates/plugin-ts/web/plugin.tsx +++ b/packages/create/templates/plugin-ts/web/plugin.tsx @@ -0,0 +1,45 @@ +import React, { useContext, useEffect, useState } from "react"; +// import { } from "antd"; + +import { + BaseWebPlugin, PageLayout, Control, ControlContext, notifyErrorHandler +} from "@clusterio/web_ui"; + +import * as lib from "@clusterio/lib"; +import { PluginExampleEvent, PluginExampleRequest } from "../messages"; + +function MyTemplatePage() { + let control = useContext(ControlContext); + + return +

// plugin_name //

+
; +} + +export class WebPlugin extends BaseWebPlugin { + async init() { + this.pages = [ + { + path: "/// plugin_name //", + sidebarName: "// plugin_name //", + permission: "// plugin_name //.page.view", + content: , + }, + ]; + + this.control.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this)); + this.control.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this)); + } + + async handlePluginExampleEvent(event: PluginExampleEvent) { + this.logger.info(JSON.stringify(event)); + } + + async handlePluginExampleRequest(request: PluginExampleRequest) { + this.logger.info(JSON.stringify(request)); + return { + myResponseString: request.myString, + myResponseNumbers: request.myNumberArray, + }; + } +} From c3b2c846b7cd76a5e183622db1e32b87c98cfc28 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:42:37 +0000 Subject: [PATCH 04/29] Add package.json template --- packages/create/create.js | 53 +++++++++++++------ .../create/templates/plugin-ts/package.json | 18 +++---- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/packages/create/create.js b/packages/create/create.js index b8c328ed..610134dc 100755 --- a/packages/create/create.js +++ b/packages/create/create.js @@ -524,7 +524,10 @@ async function copyPluginTemplateFile(src, dst, props) { async function copyPluginTemplate(pluginName, templates) { logger.info(`Please wait, coping templates for ${templates.join(", ")}`); const noTypescript = templates.includes("js"); - const entryPoints = []; + const devDependencies = new Map(); + const dependencies = new Map(); + const entryPoints = new Map(); + const prepare = []; const files = new Map(); let templatePath = "./templates/plugin-ts"; @@ -536,36 +539,46 @@ async function copyPluginTemplate(pluginName, templates) { templatePath = path.resolve(__dirname, templatePath); - // Files includes in all templates (package.json has its own logic) + // Files includes 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("tsconfig.json", path.join(templatePath, "tsconfig.json")); files.set("messages.ts", path.join(templatePath, "messages.ts")); files.set("index.ts", path.join(templatePath, "index.ts")); + devDependencies.set("@types/node", "^20.4.5"); + devDependencies.set("@types/node", "^20.4.5"); + devDependencies.set("typescript", "^5.1.6"); + dependencies.set("@sinclair/typebox", "^0.30.4"); + prepare.push("tsc --build"); - if (templates.includes("controller")) { - entryPoints.push("webEntrypoint: \"./web\","); - entryPoints.push("controllerEntrypoint: \"dist/plugin/controller\","); - files.set("controller.ts", path.join(templatePath, "controller.ts")); + if (templates.includes("controller") || templates.includes("web")) { + devDependencies.set("@clusterio/web_ui", "^2.0.0-alpha.14"); + devDependencies.set("@types/react", "^18.2.21"); + devDependencies.set("webpack", "^5.88.2"); + devDependencies.set("webpack-cli", "^5.1.4"); + devDependencies.set("webpack-merge", "^5.9.0"); + entryPoints.set("webEntrypoint", "./web"); + prepare.push("webpack-cli --env production"); files.set("webpack.config.js", path.join(templatePath, "webpack.config.js")); files.set("tsconfig.web.json", path.join(templatePath, "tsconfig.web.json")); files.set("web/index.tsx", path.join(templatePath, templates.includes("web") ? "web/plugin.tsx" : "web/no_plugin.tsx" )); - } else if (templates.includes("web")) { - entryPoints.push("webEntrypoint: \"./web\","); - files.set("web/index.tsx", path.join(templatePath, "web/plugin.tsx")); - files.set("webpack.config.js", path.join(templatePath, "webpack.config.js")); - files.set("tsconfig.web.json", path.join(templatePath, "tsconfig.web.json")); + } + + if (templates.includes("controller")) { + entryPoints.set("controllerEntrypoint", "dist/plugin/controller"); + files.set("controller.ts", path.join(templatePath, "controller.ts")); } if (templates.includes("host")) { - entryPoints.push("hostEntrypoint: \"dist/plugin/host\","); + entryPoints.set("hostEntrypoint", "dist/plugin/host"); files.set("host.ts", path.join(templatePath, "host.ts")); } if (templates.includes("instance")) { - entryPoints.push("instanceEntrypoint: \"dist/plugin/instance\","); + entryPoints.set("instanceEntrypoint", "dist/plugin/instance"); files.set("instance.ts", path.join(templatePath, "instance.ts")); } @@ -582,7 +595,17 @@ async function copyPluginTemplate(pluginName, templates) { // Props that will be replaced in the template files, found with "// prop_name //" const props = { plugin_name: pluginName, - entry_points: entryPoints.join("\n\t"), + prepare: prepare + .join(" && "), + entry_points: [...entryPoints.entries()] + .map((entry) => `${entry[0]}: "${entry[1]}",`) + .join("\n\t"), + dependencies: [...dependencies.entries()] + .map((entry) => `"${entry[0]}": "${entry[1]}"`) + .join(",\n\t\t"), + dev_dependencies: [...devDependencies.entries()] + .map((entry) => `"${entry[0]}": "${entry[1]}",`) + .join("\n\t\t"), }; const writes = []; @@ -590,8 +613,6 @@ async function copyPluginTemplate(pluginName, templates) { writes.push(copyPluginTemplateFile(src, dst, props)); } - // TODO package.json - await Promise.all(writes); logger.info("Successfully wrote all template files"); } diff --git a/packages/create/templates/plugin-ts/package.json b/packages/create/templates/plugin-ts/package.json index 45b71e49..9f542eb8 100644 --- a/packages/create/templates/plugin-ts/package.json +++ b/packages/create/templates/plugin-ts/package.json @@ -1,31 +1,25 @@ { - "name": "__plugin_name__", + "name": "// plugin_name //", "version": "0.1.0", "description": "", "main": "dist/plugin/index.js", "scripts": { - "prepare": "tsc --build && webpack-cli --env production" + "prepare": "// prepare //" }, "engines": { "node": ">=18" }, "peerDependencies": { - "@clusterio/lib": "^2.0.0-alpha.0" + "@clusterio/lib": "^2.0.0-alpha.14" }, "devDependencies": { - "@clusterio/lib": "^2.0.0-alpha.14", - "@clusterio/web_ui": "^2.0.0-alpha.14", - "@types/node": "^20.4.5", - "@types/react": "^18.2.21", - "typescript": "^5.1.6", - "webpack": "^5.88.2", - "webpack-cli": "^5.1.4", - "webpack-merge": "^5.9.0" + // dev_dependencies // + "@clusterio/lib": "^2.0.0-alpha.14" }, "publishConfig": { "access": "public" }, "dependencies": { - "@sinclair/typebox": "^0.30.4" + // dependencies // } } From 396e2c59c7488ec130e45c008fd4400bd90325cb Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:53:01 +0000 Subject: [PATCH 05/29] Revert accidental edit to vscode settings --- .vscode/settings.json | 3 --- packages/create/create.js | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index e800d22c..64e9720d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,4 @@ "editor.indentSize": 4, "editor.insertSpaces": true, }, - "cSpell.words": [ - "npmignore" - ], } diff --git a/packages/create/create.js b/packages/create/create.js index 610134dc..9bb12b52 100755 --- a/packages/create/create.js +++ b/packages/create/create.js @@ -533,7 +533,7 @@ async function copyPluginTemplate(pluginName, templates) { let templatePath = "./templates/plugin-ts"; if (noTypescript) { templatePath = "./templates/plugin-js"; - logger.error("Javascript only is not currently supported"); + logger.error("Javascript only templates are not currently supported"); return; } From de00a043c34426dd08688a5b22386b9672eddde9 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Mon, 22 Jan 2024 19:09:48 +0000 Subject: [PATCH 06/29] Update lint rules for templates --- .eslintrc.js | 10 ++++++++++ packages/create/templates/plugin-ts/web/plugin.tsx | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 3cf36d34..fd8becce 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -93,6 +93,16 @@ module.exports = { "node/shebang": "off", }, }, + { + "files": [ + "packages/create/templates/**/*.ts?(x)", + "packages/create/templates/**/*.js?(x)", + ], + "rules": { + "node/no-missing-require": "off", + "node/no-missing-import": "off", + }, + }, ], "rules": { diff --git a/packages/create/templates/plugin-ts/web/plugin.tsx b/packages/create/templates/plugin-ts/web/plugin.tsx index 3d329883..1d623443 100644 --- a/packages/create/templates/plugin-ts/web/plugin.tsx +++ b/packages/create/templates/plugin-ts/web/plugin.tsx @@ -2,7 +2,7 @@ import React, { useContext, useEffect, useState } from "react"; // import { } from "antd"; import { - BaseWebPlugin, PageLayout, Control, ControlContext, notifyErrorHandler + BaseWebPlugin, PageLayout, Control, ControlContext, notifyErrorHandler, } from "@clusterio/web_ui"; import * as lib from "@clusterio/lib"; From 9b0293501fe8a462f43382773b9ef712fe44bc94 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Tue, 23 Jan 2024 00:14:08 +0000 Subject: [PATCH 07/29] Add js templates and config templates --- packages/create/create.js | 97 +++++++++++-------- .../create/templates/plugin-js/.gitignore | 3 + .../create/templates/plugin-js/.npmignore | 2 + .../create/templates/plugin-js/controller.js | 47 +++++++++ packages/create/templates/plugin-js/ctl.js | 31 ++++++ packages/create/templates/plugin-js/host.js | 35 +++++++ packages/create/templates/plugin-js/index.js | 65 +++++++++++++ .../create/templates/plugin-js/instance.js | 43 ++++++++ .../create/templates/plugin-js/messages.js | 57 +++++++++++ .../templates/plugin-js/module/control.lua | 24 +++++ .../templates/plugin-js/module/module.json | 12 +++ .../plugin-js/module/module_exports.lua | 2 + .../create/templates/plugin-js/package.json | 29 ++++++ .../templates/plugin-js/web/no_plugin.jsx | 0 .../create/templates/plugin-js/web/plugin.jsx | 49 ++++++++++ .../templates/plugin-js/webpack.config.js | 28 ++++++ .../create/templates/plugin-ts/.gitignore | 1 + .../create/templates/plugin-ts/controller.ts | 4 +- packages/create/templates/plugin-ts/ctl.ts | 3 +- packages/create/templates/plugin-ts/index.ts | 45 ++++++++- .../create/templates/plugin-ts/messages.ts | 4 +- .../create/templates/plugin-ts/package.json | 10 +- .../templates/plugin-ts/webpack.config.js | 2 +- 23 files changed, 543 insertions(+), 50 deletions(-) create mode 100644 packages/create/templates/plugin-js/.gitignore create mode 100644 packages/create/templates/plugin-js/.npmignore create mode 100644 packages/create/templates/plugin-js/controller.js create mode 100644 packages/create/templates/plugin-js/ctl.js create mode 100644 packages/create/templates/plugin-js/host.js create mode 100644 packages/create/templates/plugin-js/index.js create mode 100644 packages/create/templates/plugin-js/instance.js create mode 100644 packages/create/templates/plugin-js/messages.js create mode 100644 packages/create/templates/plugin-js/module/control.lua create mode 100644 packages/create/templates/plugin-js/module/module.json create mode 100644 packages/create/templates/plugin-js/module/module_exports.lua create mode 100644 packages/create/templates/plugin-js/package.json create mode 100644 packages/create/templates/plugin-js/web/no_plugin.jsx create mode 100644 packages/create/templates/plugin-js/web/plugin.jsx create mode 100644 packages/create/templates/plugin-js/webpack.config.js diff --git a/packages/create/create.js b/packages/create/create.js index 9bb12b52..44f8c892 100755 --- a/packages/create/create.js +++ b/packages/create/create.js @@ -509,77 +509,89 @@ async function copyPluginTemplateFile(src, dst, props) { } const templateData = await fs.readFile(src, "utf8"); - const outputData = templateData.replace(/\/\/ ([a-z_]*) \/\//g, (_, value) => { - if (!props.hasOwnProperty(value)) { - logger.warn(`Unknown property string (${value}) found in template file ${src}`); - return value; - } + const outputData = templateData + .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; + } - return props[value]; - }); + return Boolean(props[prop]) ? content : ""; + }) + .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 noTypescript = templates.includes("js"); const devDependencies = new Map(); - const dependencies = new Map(); const entryPoints = new Map(); - const prepare = []; const files = new Map(); + const prepare = []; - let templatePath = "./templates/plugin-ts"; - if (noTypescript) { - templatePath = "./templates/plugin-js"; - logger.error("Javascript only templates are not currently supported"); - return; - } + const javascriptOnly = templates.includes("js"); + const webpack = templates.includes("controller") || templates.includes("web"); - templatePath = path.resolve(__dirname, templatePath); + // 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 includes in all templates + // 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("tsconfig.json", path.join(templatePath, "tsconfig.json")); - files.set("messages.ts", path.join(templatePath, "messages.ts")); - files.set("index.ts", path.join(templatePath, "index.ts")); - devDependencies.set("@types/node", "^20.4.5"); - devDependencies.set("@types/node", "^20.4.5"); - devDependencies.set("typescript", "^5.1.6"); - dependencies.set("@sinclair/typebox", "^0.30.4"); - prepare.push("tsc --build"); + files.set(`messages.${ext}`, path.join(templatePath, `messages.${ext}`)); + files.set(`index.${ext}`, path.join(templatePath, `index.${ext}`)); - if (templates.includes("controller") || templates.includes("web")) { - devDependencies.set("@clusterio/web_ui", "^2.0.0-alpha.14"); - devDependencies.set("@types/react", "^18.2.21"); + // 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("tsconfig.web.json", path.join(templatePath, "tsconfig.web.json")); - files.set("web/index.tsx", path.join(templatePath, - templates.includes("web") ? "web/plugin.tsx" : "web/no_plugin.tsx" + 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.ts", path.join(templatePath, "controller.ts")); + files.set(`controller.${ext}`, path.join(templatePath, `controller.${ext}`)); } if (templates.includes("host")) { entryPoints.set("hostEntrypoint", "dist/plugin/host"); - files.set("host.ts", path.join(templatePath, "host.ts")); + files.set(`host.${ext}`, path.join(templatePath, `host.${ext}`)); } if (templates.includes("instance")) { entryPoints.set("instanceEntrypoint", "dist/plugin/instance"); - files.set("instance.ts", path.join(templatePath, "instance.ts")); + files.set(`instance.${ext}`, path.join(templatePath, `instance.${ext}`)); } if (templates.includes("module")) { @@ -589,25 +601,30 @@ async function copyPluginTemplate(pluginName, templates) { } if (templates.includes("ctl")) { - files.set("ctl.ts", path.join(templatePath, "ctl.ts")); + entryPoints.set("ctlEntrypoint", "dist/plugin/ctl"); + files.set(`ctl.${ext}`, path.join(templatePath, `ctl.${ext}`)); } - // Props that will be replaced in the template files, found with "// prop_name //" + // Properties that will control the replacements in the templates const props = { + 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(" && "), entry_points: [...entryPoints.entries()] .map((entry) => `${entry[0]}: "${entry[1]}",`) .join("\n\t"), - dependencies: [...dependencies.entries()] - .map((entry) => `"${entry[0]}": "${entry[1]}"`) - .join(",\n\t\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)); diff --git a/packages/create/templates/plugin-js/.gitignore b/packages/create/templates/plugin-js/.gitignore new file mode 100644 index 00000000..1a993212 --- /dev/null +++ b/packages/create/templates/plugin-js/.gitignore @@ -0,0 +1,3 @@ +dist/ +node_modules/ +package-lock.json diff --git a/packages/create/templates/plugin-js/.npmignore b/packages/create/templates/plugin-js/.npmignore new file mode 100644 index 00000000..ebd99907 --- /dev/null +++ b/packages/create/templates/plugin-js/.npmignore @@ -0,0 +1,2 @@ +webpack.config.js +dist/web/build diff --git a/packages/create/templates/plugin-js/controller.js b/packages/create/templates/plugin-js/controller.js new file mode 100644 index 00000000..a18eda3b --- /dev/null +++ b/packages/create/templates/plugin-js/controller.js @@ -0,0 +1,47 @@ +"use strict"; +const lib = require("@clusterio/lib"); +const { BaseControllerPlugin } = require("@clusterio/controller"); +const { PluginExampleEvent, PluginExampleRequest } = require("./messages"); + +class ControllerPlugin extends BaseControllerPlugin { + async init() { + this.controller.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this)); + this.controller.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this)); + } + + async onControllerConfigFieldChanged(field, curr, prev) { + this.logger.info(`controller::onControllerConfigFieldChanged ${field}`); + }// [instance] // + + async onInstanceConfigFieldChanged(instance, field, curr, prev) { + this.logger.info(`controller::onInstanceConfigFieldChanged ${instance.id} ${field}`); + }// [] // + + async onSaveData() { + this.logger.info("controller::onSaveData"); + } + + async onShutdown() { + this.logger.info("controller::onShutdown"); + } + + async onPlayerEvent(instance, event) { + this.logger.info(`controller::onPlayerEvent ${instance.id} ${JSON.stringify(event)}`); + } + + async handlePluginExampleEvent(event) { + this.logger.info(JSON.stringify(event)); + } + + async handlePluginExampleRequest(request) { + this.logger.info(JSON.stringify(request)); + return { + myResponseString: request.myString, + myResponseNumbers: request.myNumberArray, + }; + } +} + +module.exports = { + ControllerPlugin, +}; diff --git a/packages/create/templates/plugin-js/ctl.js b/packages/create/templates/plugin-js/ctl.js new file mode 100644 index 00000000..5140697c --- /dev/null +++ b/packages/create/templates/plugin-js/ctl.js @@ -0,0 +1,31 @@ +"use strict"; +const { BaseCtlPlugin } = require("@clusterio/ctl"); +const { CommandTree, Command } = require("@clusterio/lib"); +const { PluginExampleEvent, PluginExampleRequest } = require("./messages"); +/* eslint-disable no-console */ + +const pluginCommands = new CommandTree({ + name: "// plugin_name //", description: "The default description I forgot to change", +}); + +pluginCommands.add(new Command({ + definition: ["my-command ", "My command description that I really should change", (yargs) => { + yargs.positional("foo", { describe: "foo is foo!", type: "string" }); + }], + handler: async function(args, control) { + const response = await control.sendTo("controller", new PluginExampleRequest(args.foo, [1, 2, 3])); + console.log(response); + console.log(args); + }, +})); + +/* eslint-enable no-console */ +class CtlPlugin extends BaseCtlPlugin { + async addCommands(rootCommand) { + rootCommand.add(pluginCommands); + } +} + +module.exports = { + CtlPlugin, +}; diff --git a/packages/create/templates/plugin-js/host.js b/packages/create/templates/plugin-js/host.js new file mode 100644 index 00000000..2aed38b9 --- /dev/null +++ b/packages/create/templates/plugin-js/host.js @@ -0,0 +1,35 @@ +"use strict"; +const lib = require("@clusterio/lib"); +const { BaseHostPlugin } = require("@clusterio/host"); +const { PluginExampleEvent, PluginExampleRequest } = require("./messages"); + +class HostPlugin extends BaseHostPlugin { + async init() { + this.host.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this)); + this.host.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this)); + } + + async onHostConfigFieldChanged(field, curr, prev) { + this.logger.info(`host::onInstanceConfigFieldChanged ${field}`); + } + + async onShutdown() { + this.logger.info("host::onShutdown"); + } + + async handlePluginExampleEvent(event) { + this.logger.info(JSON.stringify(event)); + } + + async handlePluginExampleRequest(request) { + this.logger.info(JSON.stringify(request)); + return { + myResponseString: request.myString, + myResponseNumbers: request.myNumberArray, + }; + } +} + +module.exports = { + HostPlugin, +}; diff --git a/packages/create/templates/plugin-js/index.js b/packages/create/templates/plugin-js/index.js new file mode 100644 index 00000000..32507df4 --- /dev/null +++ b/packages/create/templates/plugin-js/index.js @@ -0,0 +1,65 @@ +"use strict"; +const lib = require("@clusterio/lib"); + +const { PluginExampleEvent, PluginExampleRequest } = require("./messages"); + +lib.definePermission({ + 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", + title: "Example permission request", + description: "My plugin's example permission that I forgot to remove", +});// [web] // + +lib.definePermission({ + name: "// plugin_name //.page.view", + title: "Example page view permission", + description: "My plugin's example page permission that I forgot to remove", +});// [] // + +const plugin = { + name: "// plugin_name //", + title: "// plugin_name //", + description: "I didn't update my description", + // entry_points // + // [controller] // + controlConfigFields: { + "// plugin_name //.myControllerField": { + title: "My Controller Field", + description: "This should be removed", + type: "string", + initialValue: "Remove Me", + }, + },// [] //// [host] // + hostConfigFields: { + "// plugin_name //.myHostField": { + title: "My Host Field", + description: "This should be removed", + type: "string", + initialValue: "Remove Me", + }, + },// [] //// [instance] // + instanceConfigFields: { + "// plugin_name //.myInstanceField": { + title: "My Instance Field", + description: "This should be removed", + type: "string", + initialValue: "Remove Me", + }, + },// [] // + + messages: [ + PluginExampleEvent, + PluginExampleRequest, + ], +}; + +module.exports = { + plugin, + PluginExampleEvent, + PluginExampleRequest, +}; diff --git a/packages/create/templates/plugin-js/instance.js b/packages/create/templates/plugin-js/instance.js new file mode 100644 index 00000000..2a7020ef --- /dev/null +++ b/packages/create/templates/plugin-js/instance.js @@ -0,0 +1,43 @@ +"use strict"; +const lib = require("@clusterio/lib"); +const { BaseInstancePlugin } = require("@clusterio/host"); +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)); + } + + async onInstanceConfigFieldChanged(field, curr, prev) { + this.logger.info(`instance::onInstanceConfigFieldChanged ${field}`); + } + + async onStart() { + this.logger.info("instance::onStart"); + } + + async onStop() { + this.logger.info("instance::onStop"); + } + + async onPlayerEvent(event) { + this.logger.info(`onPlayerEvent::onPlayerEvent ${JSON.stringify(event)}`); + } + + async handlePluginExampleEvent(event) { + this.logger.info(JSON.stringify(event)); + } + + async handlePluginExampleRequest(request) { + this.logger.info(JSON.stringify(request)); + return { + myResponseString: request.myString, + myResponseNumbers: request.myNumberArray, + }; + } +} + +module.exports = { + InstancePlugin, +}; diff --git a/packages/create/templates/plugin-js/messages.js b/packages/create/templates/plugin-js/messages.js new file mode 100644 index 00000000..126934e5 --- /dev/null +++ b/packages/create/templates/plugin-js/messages.js @@ -0,0 +1,57 @@ +"use strict"; +const { plainJson, jsonArray, JsonBoolean, JsonNumber, JsonString, StringEnum } = require("@clusterio/lib"); +const { Type, Static } = require("@sinclair/typebox"); + +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"; + + constructor(myString, myNumberArray) { + this.myString = myString; + this.myNumberArray = myNumberArray; + } + + static jsonSchema = Type.Object({ + "myString": Type.String(), + "myNumberArray": Type.Array(Type.Number()), + }); + + static fromJson(json) { + return new PluginExampleEvent(json.myString, json.myNumberArray); + } +} + +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"; + + constructor(myString, myNumberArray) { + this.myString = myString; + this.myNumberArray = myNumberArray; + } + + static jsonSchema = Type.Object({ + "myString": Type.String(), + "myNumberArray": Type.Array(Type.Number()), + }); + + static fromJson(json) { + return new PluginExampleEvent(json.myString, json.myNumberArray); + } + + static Response = plainJson(Type.Object({ + "myResponseString": Type.String(), + "myResponseNumbers": Type.Array(Type.Number()), + })); +} + +module.exports = { + PluginExampleEvent, + PluginExampleRequest, +}; diff --git a/packages/create/templates/plugin-js/module/control.lua b/packages/create/templates/plugin-js/module/control.lua new file mode 100644 index 00000000..ab32acd6 --- /dev/null +++ b/packages/create/templates/plugin-js/module/control.lua @@ -0,0 +1,24 @@ +local clusterio_api = require("modules/clusterio/api") + +local MyModule = { + events = {}, + on_nth_tick = {}, +} + +function MyModule.foo() + game.print("foo") +end + +MyModule.events[clusterio_api.events.on_server_startup] = function(event) + game.print(game.table_to_json(event)) +end + +MyModule.events[defines.events.on_player_crafted_item] = function(event) + game.print(game.table_to_json(event)) +end + +MyModule.on_nth_tick[300] = function() + game.print(game.tick) +end + +return MyModule diff --git a/packages/create/templates/plugin-js/module/module.json b/packages/create/templates/plugin-js/module/module.json new file mode 100644 index 00000000..1edcac37 --- /dev/null +++ b/packages/create/templates/plugin-js/module/module.json @@ -0,0 +1,12 @@ +{ + "name": "// plugin_name //", + "load": [ + "control.lua" + ], + "require": [ + + ], + "dependencies": [ + + ] +} diff --git a/packages/create/templates/plugin-js/module/module_exports.lua b/packages/create/templates/plugin-js/module/module_exports.lua new file mode 100644 index 00000000..cc0c5632 --- /dev/null +++ b/packages/create/templates/plugin-js/module/module_exports.lua @@ -0,0 +1,2 @@ + +return require("./control") diff --git a/packages/create/templates/plugin-js/package.json b/packages/create/templates/plugin-js/package.json new file mode 100644 index 00000000..627f1431 --- /dev/null +++ b/packages/create/templates/plugin-js/package.json @@ -0,0 +1,29 @@ +{ + "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" + ] +} diff --git a/packages/create/templates/plugin-js/web/no_plugin.jsx b/packages/create/templates/plugin-js/web/no_plugin.jsx new file mode 100644 index 00000000..e69de29b diff --git a/packages/create/templates/plugin-js/web/plugin.jsx b/packages/create/templates/plugin-js/web/plugin.jsx new file mode 100644 index 00000000..dd0c2acd --- /dev/null +++ b/packages/create/templates/plugin-js/web/plugin.jsx @@ -0,0 +1,49 @@ +const { useContext, useEffect, useState } = require("react"); +// const { } = require("antd"); + +const { + BaseWebPlugin, PageLayout, Control, ControlContext, notifyErrorHandler, +} = require("@clusterio/web_ui"); + +const lib = require("@clusterio/lib"); +const { PluginExampleEvent, PluginExampleRequest } = require("../messages"); + +function MyTemplatePage() { + let control = useContext(ControlContext); + + return +

// plugin_name //

+
; +} + +class WebPlugin extends BaseWebPlugin { + async init() { + this.pages = [ + { + path: "/// plugin_name //", + sidebarName: "// plugin_name //", + permission: "// plugin_name //.page.view", + content: , + }, + ]; + + this.control.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this)); + this.control.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this)); + } + + async handlePluginExampleEvent(event) { + this.logger.info(JSON.stringify(event)); + } + + async handlePluginExampleRequest(request) { + this.logger.info(JSON.stringify(request)); + return { + myResponseString: request.myString, + myResponseNumbers: request.myNumberArray, + }; + } +} + +module.exports = { + WebPlugin, +} diff --git a/packages/create/templates/plugin-js/webpack.config.js b/packages/create/templates/plugin-js/webpack.config.js new file mode 100644 index 00000000..3d43cfbd --- /dev/null +++ b/packages/create/templates/plugin-js/webpack.config.js @@ -0,0 +1,28 @@ +"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.jsx", + output: { + path: path.resolve(__dirname, "dist", "web"), + }, + plugins: [ + new webpack.container.ModuleFederationPlugin({ + name: "// plugin_name //", + library: { type: "var", name: "plugin_// plugin_name //" }, + exposes: { + "./": "./info.js", + "./package.json": "./package.json", + }, + shared: { + "@clusterio/lib": { import: false }, + "@clusterio/web_ui": { import: false }, + }, + }), + ], +}); diff --git a/packages/create/templates/plugin-ts/.gitignore b/packages/create/templates/plugin-ts/.gitignore index 1eae0cf6..1a993212 100644 --- a/packages/create/templates/plugin-ts/.gitignore +++ b/packages/create/templates/plugin-ts/.gitignore @@ -1,2 +1,3 @@ dist/ node_modules/ +package-lock.json diff --git a/packages/create/templates/plugin-ts/controller.ts b/packages/create/templates/plugin-ts/controller.ts index ee6d56b7..e24dfca5 100644 --- a/packages/create/templates/plugin-ts/controller.ts +++ b/packages/create/templates/plugin-ts/controller.ts @@ -10,11 +10,11 @@ export class ControllerPlugin extends BaseControllerPlugin { async onControllerConfigFieldChanged(field: string, curr: unknown, prev: unknown) { this.logger.info(`controller::onControllerConfigFieldChanged ${field}`); - } + }// [instance] // async onInstanceConfigFieldChanged(instance: InstanceInfo, field: string, curr: unknown, prev: unknown) { this.logger.info(`controller::onInstanceConfigFieldChanged ${instance.id} ${field}`); - } + }// [] // async onSaveData() { this.logger.info("controller::onSaveData"); diff --git a/packages/create/templates/plugin-ts/ctl.ts b/packages/create/templates/plugin-ts/ctl.ts index adbc0d05..db6b9c1b 100644 --- a/packages/create/templates/plugin-ts/ctl.ts +++ b/packages/create/templates/plugin-ts/ctl.ts @@ -13,7 +13,8 @@ pluginCommands.add(new Command({ yargs.positional("foo", { describe: "foo is foo!", type: "string" }); }], handler: async function(args: { foo: string }, control: Control) { - await control.sendTo("controller", new PluginExampleEvent(args.foo, [1, 2, 3])); + const response = await control.sendTo("controller", new PluginExampleRequest(args.foo, [1, 2, 3])); + console.log(response); console.log(args); }, })); diff --git a/packages/create/templates/plugin-ts/index.ts b/packages/create/templates/plugin-ts/index.ts index 2ecceb6a..48371429 100644 --- a/packages/create/templates/plugin-ts/index.ts +++ b/packages/create/templates/plugin-ts/index.ts @@ -12,13 +12,56 @@ lib.definePermission({ name: "// plugin_name //.example.permission.request", title: "Example permission request", description: "My plugin's example permission that I forgot to remove", -}); +});// [web] // + +lib.definePermission({ + name: "// plugin_name //.page.view", + title: "Example page view permission", + description: "My plugin's example page permission that I forgot to remove", +});// [] // + +declare module "@clusterio/lib" {// [controller] // + export interface ControllerConfigFields { + "// plugin_name //.myControllerField": string; + }// [] //// [host] // + export interface HostConfigFields { + "// plugin_name //.myHostField": string; + }// [] //// [instance] // + export interface InstanceConfigFields { + "// plugin_name //.myInstanceField": string; + }// [] // +} export const plugin: lib.PluginDeclaration = { name: "// plugin_name //", title: "// plugin_name //", description: "I didn't update my description", // entry_points // + // [controller] // + controlConfigFields: { + "// plugin_name //.myControllerField": { + title: "My Controller Field", + description: "This should be removed", + type: "string", + initialValue: "Remove Me", + }, + },// [] //// [host] // + hostConfigFields: { + "// plugin_name //.myHostField": { + title: "My Host Field", + description: "This should be removed", + type: "string", + initialValue: "Remove Me", + }, + },// [] //// [instance] // + instanceConfigFields: { + "// plugin_name //.myInstanceField": { + title: "My Instance Field", + description: "This should be removed", + type: "string", + initialValue: "Remove Me", + }, + },// [] // messages: [ PluginExampleEvent, diff --git a/packages/create/templates/plugin-ts/messages.ts b/packages/create/templates/plugin-ts/messages.ts index d357e31e..2dec80e6 100644 --- a/packages/create/templates/plugin-ts/messages.ts +++ b/packages/create/templates/plugin-ts/messages.ts @@ -5,7 +5,7 @@ export class PluginExampleEvent { declare ["constructor"]: typeof PluginExampleEvent; static type = "event" as const; static src = ["host", "control"] as const; - static dst = ["controller", "host"] as const; + static dst = ["controller", "host", "instance"] as const; static plugin = "// plugin_name //" as const; static permission = "// plugin_name //.example.permission.event"; @@ -29,7 +29,7 @@ export class PluginExampleRequest { declare ["constructor"]: typeof PluginExampleRequest; static type = "request" as const; static src = ["host", "control"] as const; - static dst = ["controller", "host"] as const; + static dst = ["controller", "host", "instance"] as const; static plugin = "// plugin_name //" as const; static permission = "// plugin_name //.example.permission.request"; diff --git a/packages/create/templates/plugin-ts/package.json b/packages/create/templates/plugin-ts/package.json index 9f542eb8..f1a25265 100644 --- a/packages/create/templates/plugin-ts/package.json +++ b/packages/create/templates/plugin-ts/package.json @@ -16,10 +16,14 @@ // dev_dependencies // "@clusterio/lib": "^2.0.0-alpha.14" }, + "dependencies": { + "@sinclair/typebox": "^0.30.4" + }, "publishConfig": { "access": "public" }, - "dependencies": { - // dependencies // - } + "keywords": [ + "clusterio", + "factorio" + ] } diff --git a/packages/create/templates/plugin-ts/webpack.config.js b/packages/create/templates/plugin-ts/webpack.config.js index 85da4267..81e247eb 100644 --- a/packages/create/templates/plugin-ts/webpack.config.js +++ b/packages/create/templates/plugin-ts/webpack.config.js @@ -14,7 +14,7 @@ module.exports = (env = {}) => merge(common(env), { plugins: [ new webpack.container.ModuleFederationPlugin({ name: "// plugin_name //", - library: { type: "var", name: "plugin___plugin_name__" }, + library: { type: "var", name: "plugin_// plugin_name //" }, exposes: { "./": "./index.ts", "./package.json": "./package.json", From b222f6dcb1da3a03f6cf72da2b4132fde63786b6 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Tue, 23 Jan 2024 00:24:54 +0000 Subject: [PATCH 08/29] Update plugin creation docs --- docs/writing-plugins.md | 3 ++- packages/create/create.js | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/writing-plugins.md b/docs/writing-plugins.md index 2b7e6566..ead6f6e5 100644 --- a/docs/writing-plugins.md +++ b/docs/writing-plugins.md @@ -41,6 +41,7 @@ The usual guides for creating such packages apply. At minimum the `package.json` file must contain a version entry. A possible workflow for developing plugins is to place the plugin in a sub-directory of where clusterio has been installed, and rely on Node.js searching up the folder heirarchy for it to find `@clusterio/lib`. +After creating a directory for your plugin, the boilerplate can be generated automaticlly from a set of tempaltes using `npm exec @clusterio/create -y -- --plugin-template` after which you will be asked which areas your plugin will interact with and your plugin's name. To add it to `plugin-list.json` so that it gets loaded use the `plugin add ` sub-command to either clusteriocontroller, clusteriohost or clusterioctl. Note that it's important that the path starts with ./ or ../ (use .\ or ..\ on Windows). @@ -617,4 +618,4 @@ class CtlPlugin extends BaseCtlPlugin { module.exports = { CtlPlugin, } -``` \ No newline at end of file +``` diff --git a/packages/create/create.js b/packages/create/create.js index 44f8c892..797f5374 100755 --- a/packages/create/create.js +++ b/packages/create/create.js @@ -780,7 +780,7 @@ async function inquirerMissingArgs(args) { } if (answers.mode === "plugin-template") { - if (args["plugin-template"]) { + if (args["plugin-template"] && args["plugin-template"].length > 0) { answers.pluginTemplate = args["plugin-template"]; } if (args["plugin-name"]) { From 224a451bbd42d7c5203c3d74f4c5156bb5cdb7c5 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Tue, 23 Jan 2024 00:34:46 +0000 Subject: [PATCH 09/29] Fix lint for jsx template --- .eslintrc.js | 5 +++-- packages/create/templates/plugin-js/controller.js | 2 +- packages/create/templates/plugin-js/web/plugin.jsx | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index fd8becce..fd0847b7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -43,6 +43,7 @@ module.exports = { "files": [ "packages/web_ui/src/**/*.ts?(x)", "packages/lib/browser.ts", + "packages/create/templates/*/web/**/*.{t,j}s?(x)", "{packages,plugins}/*/web/**/*.ts?(x)", ], "env": { @@ -95,12 +96,12 @@ module.exports = { }, { "files": [ - "packages/create/templates/**/*.ts?(x)", - "packages/create/templates/**/*.js?(x)", + "packages/create/templates/**/*.{t,j}s?(x)", ], "rules": { "node/no-missing-require": "off", "node/no-missing-import": "off", + "comma-spacing": "off", }, }, ], diff --git a/packages/create/templates/plugin-js/controller.js b/packages/create/templates/plugin-js/controller.js index a18eda3b..9fbc057a 100644 --- a/packages/create/templates/plugin-js/controller.js +++ b/packages/create/templates/plugin-js/controller.js @@ -1,6 +1,6 @@ "use strict"; const lib = require("@clusterio/lib"); -const { BaseControllerPlugin } = require("@clusterio/controller"); +const { BaseControllerPlugin } = require("@clusterio/controller"); const { PluginExampleEvent, PluginExampleRequest } = require("./messages"); class ControllerPlugin extends BaseControllerPlugin { diff --git a/packages/create/templates/plugin-js/web/plugin.jsx b/packages/create/templates/plugin-js/web/plugin.jsx index dd0c2acd..9933fdb6 100644 --- a/packages/create/templates/plugin-js/web/plugin.jsx +++ b/packages/create/templates/plugin-js/web/plugin.jsx @@ -46,4 +46,4 @@ class WebPlugin extends BaseWebPlugin { module.exports = { WebPlugin, -} +}; From b9a2d6b7bda278c5cd55e0668b8e9602c2019488 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Tue, 23 Jan 2024 01:00:04 +0000 Subject: [PATCH 10/29] Add current folder as default for plugin name --- packages/create/create.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/create/create.js b/packages/create/create.js index 797f5374..5d3432c2 100755 --- a/packages/create/create.js +++ b/packages/create/create.js @@ -802,12 +802,11 @@ async function inquirerMissingArgs(args) { { name: "No Typescript", value: "js" }, ], }, - ], answers); - answers = await inquirer.prompt([ { type: "input", name: "pluginName", message: "Name of your new plugin", + default: path.basename(path.resolve()), }, ], answers); } From 2916dcc58c46591c5ee3a4700c874f60d2be4c3f Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Tue, 23 Jan 2024 02:47:10 +0000 Subject: [PATCH 11/29] Fix hang from rl.close on windows --- packages/create/create.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/create/create.js b/packages/create/create.js index 5d3432c2..599ffbd0 100755 --- a/packages/create/create.js +++ b/packages/create/create.js @@ -16,6 +16,18 @@ let dev = false; const scriptExt = process.platform === "win32" ? ".cmd" : ""; const finished = util.promisify(stream.finished); +// I hate this, there is a bug with rl.close on windows that was meant to have been fixed in Node14.4 +// See nodejs/node#21771 and nodejs/node#30701 for the apparent fix that was applied +// One option is to not call rl.close: SBoudrias/Inquirer.js#767 +// But this comes with its own issues: SBoudrias/Inquirer.js#899 +// The solution below was adapted from aws-amplify/amplify-cli#12347 +if (process.platform === "win32") { + const _inquirer_close = inquirer.ui.Prompt.prototype.close; + inquirer.ui.Prompt.prototype.close = function() { + this.rl.terminal = false; + _inquirer_close.call(this); + }; +} const availablePlugins = [ { name: "Global Chat", value: "@clusterio/plugin-global_chat" }, From 58231cc02cb7d36724c49cac4853c4531ed477b4 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Wed, 24 Jan 2024 00:40:22 +0000 Subject: [PATCH 12/29] Add IPC to templates and handle helper function server --- packages/create/templates/plugin-js/index.js | 14 +++++++++++--- .../create/templates/plugin-js/instance.js | 12 +++++++++--- .../templates/plugin-js/module/control.lua | 5 ++++- .../templates/plugin-js/module/module.json | 2 +- .../plugin-js/module/module_exports.lua | 7 ++++++- packages/create/templates/plugin-ts/index.ts | 17 ++++++++++++++--- .../create/templates/plugin-ts/instance.ts | 19 +++++++++++++++---- .../templates/plugin-ts/module/control.lua | 5 ++++- .../templates/plugin-ts/module/module.json | 2 +- .../plugin-ts/module/module_exports.lua | 7 ++++++- packages/host/src/server.ts | 7 +++++++ 11 files changed, 78 insertions(+), 19 deletions(-) diff --git a/packages/create/templates/plugin-js/index.js b/packages/create/templates/plugin-js/index.js index 32507df4..abb30dcd 100644 --- a/packages/create/templates/plugin-js/index.js +++ b/packages/create/templates/plugin-js/index.js @@ -24,10 +24,10 @@ lib.definePermission({ const plugin = { name: "// plugin_name //", title: "// plugin_name //", - description: "I didn't update my description", + description: "I didn't update my description",// [entry_points] // // entry_points // - // [controller] // - controlConfigFields: { + // [] //// [controller] // + controllerConfigFields: { "// plugin_name //.myControllerField": { title: "My Controller Field", description: "This should be removed", @@ -50,6 +50,14 @@ const plugin = { type: "string", initialValue: "Remove Me", }, + },// [] //// [ctl] // + controlConfigFields: { + "// plugin_name //.myControlField": { + title: "My Control Field", + description: "This should be removed", + type: "string", + initialValue: "Remove Me", + }, },// [] // messages: [ diff --git a/packages/create/templates/plugin-js/instance.js b/packages/create/templates/plugin-js/instance.js index 2a7020ef..99a6f19a 100644 --- a/packages/create/templates/plugin-js/instance.js +++ b/packages/create/templates/plugin-js/instance.js @@ -6,7 +6,8 @@ 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)); + this.instance.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));// [module] // + this.instance.server.handle("ipc-// plugin_name //-plugin_example_ipc", this.handlePluginExampleIPC.bind(this));// [] // } async onInstanceConfigFieldChanged(field, curr, prev) { @@ -26,7 +27,8 @@ class InstancePlugin extends BaseInstancePlugin { } async handlePluginExampleEvent(event) { - this.logger.info(JSON.stringify(event)); + this.logger.info(JSON.stringify(event));// [module] // + this.sendRcon("/sc ipc_// plugin_name //.foo()");// [] // } async handlePluginExampleRequest(request) { @@ -35,7 +37,11 @@ class InstancePlugin extends BaseInstancePlugin { myResponseString: request.myString, myResponseNumbers: request.myNumberArray, }; - } + }// [module] // + + async handlePluginExampleIPC(event) { + this.logger.info(JSON.stringify(event)); + }// [] // } module.exports = { diff --git a/packages/create/templates/plugin-js/module/control.lua b/packages/create/templates/plugin-js/module/control.lua index ab32acd6..5e6a2e2a 100644 --- a/packages/create/templates/plugin-js/module/control.lua +++ b/packages/create/templates/plugin-js/module/control.lua @@ -14,7 +14,10 @@ MyModule.events[clusterio_api.events.on_server_startup] = function(event) end MyModule.events[defines.events.on_player_crafted_item] = function(event) - game.print(game.table_to_json(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 MyModule.on_nth_tick[300] = function() diff --git a/packages/create/templates/plugin-js/module/module.json b/packages/create/templates/plugin-js/module/module.json index 1edcac37..33915ac9 100644 --- a/packages/create/templates/plugin-js/module/module.json +++ b/packages/create/templates/plugin-js/module/module.json @@ -4,7 +4,7 @@ "control.lua" ], "require": [ - + // [instance] //"module_exports.lua"// [] // ], "dependencies": [ diff --git a/packages/create/templates/plugin-js/module/module_exports.lua b/packages/create/templates/plugin-js/module/module_exports.lua index cc0c5632..4399db98 100644 --- a/packages/create/templates/plugin-js/module/module_exports.lua +++ b/packages/create/templates/plugin-js/module/module_exports.lua @@ -1,2 +1,7 @@ +local MyModule = require("./control")// [instance] // -return require("./control") +-- Can contain anything you plan to use with send_rcon from functions, modules, or data +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 diff --git a/packages/create/templates/plugin-ts/index.ts b/packages/create/templates/plugin-ts/index.ts index 48371429..92a7548f 100644 --- a/packages/create/templates/plugin-ts/index.ts +++ b/packages/create/templates/plugin-ts/index.ts @@ -29,16 +29,19 @@ declare module "@clusterio/lib" {// [controller] // }// [] //// [instance] // export interface InstanceConfigFields { "// plugin_name //.myInstanceField": string; + }// [] //// [ctl] // + export interface ControlConfigFields { + "// plugin_name //.myControlField": string; }// [] // } export const plugin: lib.PluginDeclaration = { name: "// plugin_name //", title: "// plugin_name //", - description: "I didn't update my description", + description: "I didn't update my description",// [entry_points] // // entry_points // - // [controller] // - controlConfigFields: { + // [] //// [controller] // + controllerConfigFields: { "// plugin_name //.myControllerField": { title: "My Controller Field", description: "This should be removed", @@ -61,6 +64,14 @@ export const plugin: lib.PluginDeclaration = { type: "string", initialValue: "Remove Me", }, + },// [] //// [ctl] // + controlConfigFields: { + "// plugin_name //.myControlField": { + title: "My Control Field", + description: "This should be removed", + type: "string", + initialValue: "Remove Me", + }, },// [] // messages: [ diff --git a/packages/create/templates/plugin-ts/instance.ts b/packages/create/templates/plugin-ts/instance.ts index f89429dd..02fce382 100644 --- a/packages/create/templates/plugin-ts/instance.ts +++ b/packages/create/templates/plugin-ts/instance.ts @@ -1,11 +1,17 @@ import * as lib from "@clusterio/lib"; import { BaseInstancePlugin } from "@clusterio/host"; -import { PluginExampleEvent, PluginExampleRequest } from "./messages"; +import { PluginExampleEvent, PluginExampleRequest } from "./messages";// [module] // + +type PuginExampleIPC = { + tick: number, + player_name: string, +};// [] // export class InstancePlugin extends BaseInstancePlugin { async init() { this.instance.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this)); - this.instance.handle(PluginExampleRequest, this.handlePluginExampleRequest.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));// [] // } async onInstanceConfigFieldChanged(field: string, curr: unknown, prev: unknown) { @@ -21,7 +27,8 @@ export class InstancePlugin extends BaseInstancePlugin { } async onPlayerEvent(event: lib.PlayerEvent) { - this.logger.info(`onPlayerEvent::onPlayerEvent ${JSON.stringify(event)}`); + this.logger.info(`onPlayerEvent::onPlayerEvent ${JSON.stringify(event)}`);// [module] // + this.sendRcon("/sc ipc_// plugin_name //.foo()");// [] // } async handlePluginExampleEvent(event: PluginExampleEvent) { @@ -34,5 +41,9 @@ export class InstancePlugin extends BaseInstancePlugin { myResponseString: request.myString, myResponseNumbers: request.myNumberArray, }; - } + }// [module] // + + async handlePluginExampleIPC(event: PuginExampleIPC) { + this.logger.info(JSON.stringify(event)); + }// [] // } diff --git a/packages/create/templates/plugin-ts/module/control.lua b/packages/create/templates/plugin-ts/module/control.lua index ab32acd6..5e6a2e2a 100644 --- a/packages/create/templates/plugin-ts/module/control.lua +++ b/packages/create/templates/plugin-ts/module/control.lua @@ -14,7 +14,10 @@ MyModule.events[clusterio_api.events.on_server_startup] = function(event) end MyModule.events[defines.events.on_player_crafted_item] = function(event) - game.print(game.table_to_json(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 MyModule.on_nth_tick[300] = function() diff --git a/packages/create/templates/plugin-ts/module/module.json b/packages/create/templates/plugin-ts/module/module.json index 1edcac37..33915ac9 100644 --- a/packages/create/templates/plugin-ts/module/module.json +++ b/packages/create/templates/plugin-ts/module/module.json @@ -4,7 +4,7 @@ "control.lua" ], "require": [ - + // [instance] //"module_exports.lua"// [] // ], "dependencies": [ diff --git a/packages/create/templates/plugin-ts/module/module_exports.lua b/packages/create/templates/plugin-ts/module/module_exports.lua index cc0c5632..4399db98 100644 --- a/packages/create/templates/plugin-ts/module/module_exports.lua +++ b/packages/create/templates/plugin-ts/module/module_exports.lua @@ -1,2 +1,7 @@ +local MyModule = require("./control")// [instance] // -return require("./control") +-- Can contain anything you plan to use with send_rcon from functions, modules, or data +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 diff --git a/packages/host/src/server.ts b/packages/host/src/server.ts index b34af3b9..2510903e 100644 --- a/packages/host/src/server.ts +++ b/packages/host/src/server.ts @@ -562,6 +562,13 @@ export class FactorioServer extends events.EventEmitter { } } + // TODO at the moment this is just a helper function, it has the potential for schema checking + handle(eventName: string, handler: (event: Event) => Promise) { + this.on(eventName, (event) => handler(event).catch(err => { + this._logger.error(`Error handling ipc event:\n${err.stack}`); + })); + } + async _handleIpc(line: Buffer) { let channelEnd = line.indexOf("?"); if (channelEnd === -1) { From f392b258f7df9c0f0127c9f365d312229dbb042a Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Sat, 17 Feb 2024 03:11:54 +0000 Subject: [PATCH 13/29] Add subscriptions and luacheck --- packages/create/create.js | 7 +- .../create/templates/plugin-js/.luacheckrc | 1705 +++++++++++++++++ .../create/templates/plugin-js/controller.js | 20 +- packages/create/templates/plugin-js/index.js | 18 +- .../create/templates/plugin-js/messages.js | 44 +- .../templates/plugin-js/module/control.lua | 25 + .../plugin-js/module/module_exports.lua | 5 +- .../create/templates/plugin-js/web/plugin.jsx | 23 +- .../create/templates/plugin-ts/.luacheckrc | 1705 +++++++++++++++++ .../create/templates/plugin-ts/controller.ts | 20 +- packages/create/templates/plugin-ts/index.ts | 18 +- .../create/templates/plugin-ts/messages.ts | 40 + .../templates/plugin-ts/module/control.lua | 25 + .../plugin-ts/module/module_exports.lua | 5 +- .../create/templates/plugin-ts/web/plugin.tsx | 23 +- 15 files changed, 3644 insertions(+), 39 deletions(-) create mode 100644 packages/create/templates/plugin-js/.luacheckrc create mode 100644 packages/create/templates/plugin-ts/.luacheckrc diff --git a/packages/create/create.js b/packages/create/create.js index 599ffbd0..ebf161e1 100755 --- a/packages/create/create.js +++ b/packages/create/create.js @@ -522,15 +522,18 @@ async function copyPluginTemplateFile(src, dst, props) { const templateData = await fs.readFile(src, "utf8"); const outputData = templateData - .replace(/\/\/ \[([a-z_]*)\] \/\/(.*?)\/\/ \[\] \/\//gs, (_, prop, content) => { + // 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)) { @@ -607,6 +610,7 @@ async function copyPluginTemplate(pluginName, templates) { } 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")); @@ -620,6 +624,7 @@ async function copyPluginTemplate(pluginName, templates) { // 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"), diff --git a/packages/create/templates/plugin-js/.luacheckrc b/packages/create/templates/plugin-js/.luacheckrc new file mode 100644 index 00000000..cacf598d --- /dev/null +++ b/packages/create/templates/plugin-js/.luacheckrc @@ -0,0 +1,1705 @@ +--[[---------------------------------------------------------------------------- + LICENSE + .luacheckrc for Factorio Version 0.18.8, luacheck version 0.23.0 + This file is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this file, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this file dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE FILE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +------------------------------------------------------------------------------]] +local LINE_LENGTH = false + +local IGNORE = {'21./%w+_$', '21./^_%w+$', '213/[ijk]', '213/index', '213/key'} + +-- These globals are not available to the factorio API +local NOT_GLOBALS = {'coroutine', 'io', 'socket', 'dofile', 'loadfile'} + +local STD_CONTROL = 'lua52+factorio+factorio_control+stdlib+factorio_defines' +local STD_DATA = 'lua52+factorio+factorio_data+stdlib+stdlib_data+factorio_defines' + +-- For Base and Core Mods +local STD_BASE_DATA = 'lua52+factorio+factorio_data+factorio_defines+factorio_base_data' +local STD_BASE_CONTROL = 'lua52+factorio+factorio_control+factorio_defines+factorio_base_control' + +do -- Assume Factorio Control Stage as Default + std = STD_CONTROL + --cache = true + not_globals = NOT_GLOBALS + ignore = IGNORE + quiet = 1 -- pass -q option + max_cyclomatic_complexity = false + codes = true + max_line_length = LINE_LENGTH + max_code_line_length = LINE_LENGTH + max_string_line_length = LINE_LENGTH + max_comment_line_length = LINE_LENGTH + + --List of files and directories to exclude + exclude_files = { + --Ignore special folders + '**/.trash/', + '**/.history/', + '**/stdlib/vendor/', + + --Ignore development mods + '**/combat-tester/', + '**/test-maker/', + '**/trailer/', + + --Ignore love Includes + '**/love/includes/' + } +end + +do -- RedMew and ExpGaming overrides + globals = { + 'math', 'table', 'package', + 'print', 'require', 'unpack', 'inspect', 'loadstring', 'ServerCommands', 'Debug', + '_C', '_DEBUG', '_CHEATS', '_DUMP_ENV', '_LIFECYCLE', '_STAGE', + } +end + +do -- Set default prototype files + files['**/data.lua'].std = STD_DATA + files['**/data-updates.lua'].std = STD_DATA + files['**/data-final-fixes.lua'].std = STD_DATA + files['**/settings.lua'].std = STD_DATA + files['**/settings-updates.lua'].std = STD_DATA + files['**/settings-final-fixes.lua'].std = STD_DATA + files['**/prototypes/'].std = STD_DATA + files['**/settings/'].std = STD_DATA +end + +do -- Base and Core mod files + local base_scenarios = { + std = STD_BASE_CONTROL .. '+factorio_base_scenarios+factorio_base_story', + --ignore = {'212/event', '111', '112', '113', '211', '212', '213', '311', '411', '412', '421', '422', '423', '431', '432', '512'} + ignore = {'...'} + } + files['**/base/scenarios/'] = base_scenarios + files['**/base/tutorials/'] = base_scenarios + files['**/base/campaigns/'] = base_scenarios + files['**/wip-scenario/'] = base_scenarios + + files['**/base/migrations/'] = {std = STD_BASE_CONTROL} + + files['**/core/lualib/'] = {std = STD_BASE_CONTROL} + files['**/core/lualib/util.lua'] = {globals = {'util', 'table'}, ignore = {'432/object'}} + files['**/core/lualib/silo-script.lua'] = {globals = {'silo_script'}, ignore = {'4../player'}} + files['**/core/lualib/production-score.lua'] = {globals = {'production_score', 'get_price_recursive'}, ignore = {'4../player'}} + files['**/core/lualib/story*'] = {std = '+factorio_base_story', ignore = {'42./k', '42./filter'}} + files['**/core/lualib/mod-gui.lua'] = {globals = {'mod_gui'}} + files['**/core/lualib/camera.lua'] = {globals = {'camera'}} + files['**/core/lualib/builder.lua'] = {globals = {'Builder', 'builder', 'action', 'down', 'right'}} + + files['**/core/lualib/bonus-gui-ordering/'] = {std = STD_BASE_DATA} + files['**/core/lualib/dataloader.lua'] = {globals = {'data'}} + files['**/core/lualib/circuit-connector-*'] = {std = STD_BASE_DATA..'+factorio_circuit_connector_generated'} + files['**/core/lualib/bonus-gui-ordering.lua'] = {globals = {'bonus_gui_ordering'}} + + files['**/base/prototypes/'] = {std = STD_BASE_DATA} + files['**/core/prototypes/'] = {std = STD_BASE_DATA} + files['**/core/prototypes/noise-programs.lua'] = {ignore = {'212/x', '212/y', '212/tile', '212/map'}} +end + +do -- Stdlib Files + local stdlib_control = { + std = 'lua52+factorio+factorio_control+stdlib+factorio_defines', + max_line_length = LINE_LENGTH + } + + local stdlib_data = { + std = 'lua52+factorio+factorio_data+stdlib+factorio_defines', + max_line_length = LINE_LENGTH + } + + -- Assume control stage for stdlib + files['**/stdlib/'] = stdlib_control + + -- Assume generic lua for stdlib utils + files['**/stdlib/utils/**'].std = 'lua52+stdlib' + + -- STDLIB data stage files + files['**/stdlib/data/'] = stdlib_data + + -- STDLIB Busted Spec + files['**/spec/**'] = { + globals = {'serpent', 'log', 'package.remove_stdlib'}, + std = 'lua52c+busted+factorio_defines+factorio_control+stdlib' + } + + -- Love + files['**/love/'].std = 'luajit+love+love_extra+stdlib+stdlib_data' +end + +do -- Factorio STDs-- + stds.factorio = { + --Set the read only variables + read_globals = { + -- @log@: Gives writing access to Factorio's logger instance. + "log", + -- @serpent@: Lua serializer and pretty printer. (https://github.com/pkulchenko/serpent). + "serpent", + -- @table_size@: Returns the number of elements inside an LUA table. + "table_size", + -- @lldebugger@: Provided by lua local debugger vscode extension. + lldebugger = { + fields = {'requestBreak'}, + other_fields = true, + read_only = false, + }, + -- @__DebugAdapter@: Provided by Factorio Mod Debug vscode extension. + __DebugAdapter = { + fields = {'print', 'stepIgnoreAll', 'stepIgnore', 'breakpoint'}, + other_fields = true, + read_only = false + }, + util = { + fields = { + "by_pixel", "distance", "findfirstentity", "positiontostr", "formattime", "moveposition", "oppositedirection", + "ismoduleavailable", "multiplystripes", "format_number", "increment", "color", "conditional_return", + "add_shift", "merge", "premul_color", "encode", "decode", "insert_safe", + table = { + fields = { + "compare", "deepcopy" + }, + }, + }, + }, + table = { + fields = { + "compare", "deepcopy" + }, + }, + }, + } + + stds.factorio_control = { + read_globals = { + + -- @commands@: + commands = { + fields = { + "add_command", "commands", "game_commands", "remove_command" + }, + }, + + -- @settings@: + settings = { + fields = { + "get_player_settings", + startup = {other_fields = true}, + global = {other_fields = true}, + player = {other_fields = true}, + get = {read_only = false}, -- stdlib added + get_startup = {read_only = false} -- stdlib added + }, + }, + + -- @script@: Provides an interface for registering event handlers. + -- (http://lua-api.factorio.com/latest/LuaBootstrap.html) + script = { + fields = { + "on_event", "on_nth_tick", "on_configuration_changed", "on_init", "on_load", "generate_event_name", + "raise_event", "get_event_handler", "mod_name", "get_event_order", + "is_game_in_debug_mode", "object_name", "set_event_filter", "get_event_filter", "register_metatable", + active_mods = {read_only = true, other_fields = true}, + }, + other_fields = false, + }, + + -- @remote@: Allows inter-mod communication by providing a repository of interfaces that is shared by all mods. + -- (http://lua-api.factorio.com/latest/LuaRemote.html) + remote = { + fields = { + interfaces = {read_only = false, other_fields = true}, + "add_interface", "remove_interface", "call" + }, + read_only = true, + other_fields = false, + }, + + rcon = { + fields = {'print'} + }, + + rendering = { + other_fields = false, + read_only = true, + fields = { + 'draw_animation', + 'draw_line', + 'draw_text', + 'draw_circle', + 'draw_rectangle', + 'draw_arc', + 'draw_polygon', + 'draw_sprite', + 'draw_light', + 'destroy', + 'is_font_valid', + 'is_valid', + 'get_all_ids', + 'clear', + 'get_type', + 'get_surface', + 'get_time_to_live', + 'set_time_to_live', + 'get_forces', + 'set_forces', + 'get_players', + 'set_players', + 'get_color', + 'set_color', + 'get_width', + 'set_width', + 'get_from', + 'set_from', + 'get_to', + 'set_to', + 'get_gap_amount', + 'set_gap_amount', + 'get_gap_length', + 'set_gap_length', + 'get_target', + 'set_target', + 'get_orientation', + 'set_orientation', + 'get_scale', + 'set_scale', + 'get_text', + 'set_text', + 'get_font', + 'set_font', + 'get_alignment', + 'set_alignment', + 'get_scale_with_zoom', + 'set_scale_with_zoom', + 'get_filled', + 'set_filled', + 'get_radius', + 'set_radius', + 'get_left_top', + 'set_left_top', + 'get_right_bottom', + 'set_right_bottom', + 'get_max_radius', + 'set_max_radius', + 'get_min_radius', + 'set_min_radius', + 'get_start_angle', + 'set_start_angle', + 'get_angle', + 'set_angle', + 'get_vertices', + 'set_vertices', + 'get_sprite', + 'set_sprite', + 'get_x_scale', + 'set_x_scale', + 'get_y_scale', + 'set_y_scale', + 'get_render_layer', + 'set_render_layer', + 'get_orientation_target', + 'set_orientation_target', + 'get_oriented_offset', + 'set_oriented_offset', + 'get_intensity', + 'set_intensity', + 'get_minimum_darkness', + 'set_minimum_darkness' + } + }, + + -- @game@: Main object through which most of the API is accessed. + -- It is, however, not available inside handlers registered with @script.on_load@. + -- (http://lua-api.factorio.com/latest/LuaGameScript.html) + game ={ + other_fields = false, + read_only = false, + fields = { + 'auto_save', + 'ban_player', + 'check_consistency', + 'check_prototype_translations', + 'count_pipe_groups', + 'create_force', + 'create_profiler', + 'create_random_generator', + 'create_surface', + 'decode_string', + 'delete_surface', + 'direction_to_string', + 'disable_replay', + 'disable_tips_and_tricks', + 'disable_tutorial_triggers', + 'encode_string', + 'evaluate_expression', + 'force_crc', + 'get_active_entities_count', + 'get_entity_by_tag', + 'get_filtered_achievement_prototypes', + 'get_filtered_decorative_prototypes', + 'get_filtered_entity_prototypes', + 'get_filtered_equipment_prototypes', + 'get_filtered_fluid_prototypes', + 'get_filtered_item_prototypes', + 'get_filtered_mod_setting_prototypes', + 'get_filtered_recipe_prototypes', + 'get_filtered_technology_prototypes', + 'get_filtered_tile_prototypes', + 'get_map_exchange_string', + 'get_player', + 'get_surface', + 'get_train_stops', + 'help', + 'is_demo', + 'is_multiplayer', + 'is_valid_sound_path', + 'is_valid_sprite_path', + 'json_to_table', + 'kick_player', + 'log_recipe_locale', + 'merge_forces', + 'mute_player', + 'parse_map_exchange_string', + 'play_sound', + 'print', + 'print_stack_size', + 'purge_player', + 'regenerate_entity', + 'reload_mods', + 'reload_script', + 'remove_offline_players', + 'remove_path', + 'reset_time_played', + 'save_atlas', + 'server_save', + 'set_game_state', + 'set_wait_for_screenshots_to_finish', + 'show_message_dialog', + 'table_to_json', + 'take_screenshot', + 'take_technology_screenshot', + 'unban_player', + 'unmute_player', + 'write_file', + + achievement_prototypes = {read_only = true, other_fields = true}, + active_mods = {read_only = true, other_fields = true}, + ammo_category_prototypes = {read_only = true, other_fields = true}, + autoplace_control_prototypes = {read_only = true, other_fields = true}, + backer_names = {read_only = true, other_fields = true}, + connected_players = {read_only = true, other_fields = true}, + custom_input_prototypes = {read_only = true, other_fields = true}, + damage_prototypes = {read_only = true, other_fields = true}, + decorative_prototypes = {read_only = true, other_fields = true}, + default_map_gen_settings = {read_only = true, other_fields = true}, + difficulty = {read_only = true, other_fields = true}, + difficulty_settings = {read_only = true, other_fields = true}, + entity_prototypes = {read_only = true, other_fields = true}, + equipment_category_prototypes = {read_only = true, other_fields = true}, + equipment_grid_prototypes = {read_only = true, other_fields = true}, + equipment_prototypes = {read_only = true, other_fields = true}, + finished = {read_only = true, other_fields = true}, + fluid_prototypes = {read_only = true, other_fields = true}, + forces = {read_only = true, other_fields = true}, + fuel_category_prototypes = {read_only = true, other_fields = true}, + item_group_prototypes = {read_only = true, other_fields = true}, + item_prototypes = {read_only = true, other_fields = true}, + item_subgroup_prototypes = {read_only = true, other_fields = true}, + map_settings = {read_only = true, other_fields = true}, + mod_setting_prototypes = {read_only = true, other_fields = true}, + module_category_prototypes = {read_only = true, other_fields = true}, + named_noise_expressions = {read_only = true, other_fields = true}, + noise_layer_prototypes = {read_only = true, other_fields = true}, + object_name = {read_only = true, other_fields = true}, + particle_prototypes = {read_only = true, other_fields = true}, + permissions = {read_only = true, other_fields = true}, + player = {read_only = true, other_fields = true}, + players = {read_only = true, other_fields = true}, + pollution_statistics = {read_only = true, other_fields = true}, + recipe_category_prototypes = {read_only = true, other_fields = true}, + recipe_prototypes = {read_only = true, other_fields = true}, + resource_category_prototypes = {read_only = true, other_fields = true}, + shortcut_prototypes = {read_only = true, other_fields = true}, + styles = {read_only = true, other_fields = true}, + surfaces = {read_only = true, other_fields = true}, + technology_prototypes = {read_only = true, other_fields = true}, + tick = {read_only = true, other_fields = true}, + ticks_played = {read_only = true, other_fields = true}, + tile_prototypes = {read_only = true, other_fields = true}, + trivial_smoke_prototypes = {read_only = true, other_fields = true}, + virtual_signal_prototypes = {read_only = true, other_fields = true}, + + autosave_enabled = {read_only = false, other_fields = false}, + draw_resource_selection = {read_only = false, other_fields = false}, + enemy_has_vision_on_land_mines = {read_only = false, other_fields = false}, + speed = {read_only = false, other_fields = false}, + tick_paused = {read_only = false, other_fields = false}, + ticks_to_run = {read_only = false, other_fields = false} + }, + }, + }, + + globals = { + -- @global@: The global dictionary, useful for storing data persistent across a save-load cycle. + -- Writing access is given to the mod-id field (for mod-wise saved data). + -- (http://lua-api.factorio.com/latest/Global.html) + "global", + + -- @MOD@: Keep it organized, use this variable for anything that "NEEDS" to be global for some reason. + "MOD" + }, + } + + stds.factorio_data = { + + read_globals = { + data = { + fields = { + raw = { + other_fields = true, + read_only = false + }, + "extend", "is_demo" + }, + }, + + settings = { + fields = { + "startup", "global", "player", "get", "get_startup" + }, + }, + + mods = { + other_fields = true + }, + + -- Popular mods that think they need globals! + angelsmods = { + other_fields = true + }, + + -- Popular mods that think they need globals! + bobmods = { + other_fields = true + }, + + + } + } +end + +do -- Factorio Base/Core STDs-- + stds.factorio_base_control = { + read_globals = {"silo_script", "mod_gui", "camera"} + } + + stds.factorio_base_scenarios = { + globals = { + "check_automate_science_packs_advice", "check_research_hints", "check_supplies", "manage_attacks", "all_dead", + "on_win", "difficulty_number", "init_attack_data", "handle_attacks", "count_items_in_container", "progress", "scanned", + "check_light", "check_machine_gun", "level", "story_table", + + "tightspot_prices", "tightspot_make_offer", "tightspot_init", "tightspot_get_required_balance", + "tightspot_init_level", "tightspot_init_spending_frame", "tightspot_init_progress_frame", "tightspot_update_progress", "tightspot_update_spending", + "tightspot_get_missing_to_win", "tightspot_sell_back", "tightspot_start_level", "tightspot_show_level_description", "tightspot_update_speed_label", + "map_ignore", "tightspot_check_level", "land_price", + + "transport_belt_madness_init", "transport_belt_madness_init_level", "transport_belt_madness_create_chests", "transport_belt_madness_fill_chests", + "transport_belt_madness_start_level", "map_ignore", "map_clear", "map_load", "map_save", "transport_belt_madness_show_level_description", + "transport_belt_madness_check_level", "transport_belt_madness_next_level", "transport_belt_madness_clear_level", "transport_belt_madness_contains_next_level", + + "restricted", "check_built_items", "result", "disable_combat_technologies", "apply_character_modifiers", "apply_combat_modifiers", "apply_balance", + "load_config", "starting_area_constant", "create_next_surface", "end_round", "prepare_next_round", "silo_died","choose_joining_gui", + "destroy_joining_guis", "create_random_join_gui", "create_auto_assign_gui", "create_pick_join_gui", "create_config_gui", "make_config_table", "default", + "make_team_gui", "make_team_gui_config", "add_team_button_press", "trash_team_button_press", "remove_team_from_team_table", "add_team_to_team_table", + "set_teams_from_gui", "on_team_button_press", "make_color_dropdown", "create_balance_option", "create_disable_frame", "disable_frame", "parse_disabled_items", + "set_balance_settings", "config_confirm", "parse_config_from_gui", "get_color", "roll_starting_area", "delete_roll_surfaces", "auto_assign", + "destroy_config_for_all", "prepare_map", "set_evolution_factor", "update_players_on_team_count", "random_join", "init_player_gui", + "destroy_player_gui", "objective_button_press", "admin_button_press", "admin_frame_button_press", "diplomacy_button_press", "update_diplomacy_frame", + "diplomacy_frame_button_press", "team_changed_diplomacy", "diplomacy_check_press", "get_stance", "give_inventory", "setup_teams", "disable_items_for_all", + "set_random_team", "set_diplomacy", "create_spawn_positions", "set_spawn_position", "set_team_together_spawns", "chart_starting_area_for_force_spawns", + "check_starting_area_chunks_are_generated", "check_player_color", "check_round_start", "clear_starting_area_enemies", "check_no_rush_end", "check_no_rush_players", + "finish_setup", "chart_area_for_force", "setup_start_area_copy", "update_copy_progress", "update_progress_bar", "copy_paste_starting_area_tiles", + "copy_paste_starting_area_entities", "create_silo_for_force", "setup_research", "on_chunk_generated", "get_distance_to_nearest_spawn", + "create_wall_for_force", "fpn", "give_items", "create_item_frame", "create_technologies_frame", "create_cheat_frame", "create_day_frame", + "time_modifier", "points_per_second_start", "points_per_second_level_subtract", "levels", "update_info", "get_time_left", "update_time_left", + "on_joined", "make_frame", "update_frame", "update_table", "calculate_task_item_multiplayer", "setup_config", "select_from_probability_table", + "select_inventory", "select_equipment", "select_challange_type", "save_round_statistics", "start_challenge", "create_teams", "set_areas", + "decide_player_team", "set_teams", "refresh_leaderboard", "set_player", "generate_technology_list", "generate_research_task","setup_unlocks", + "check_technology_progress", "generate_production_task", "generate_shopping_list_task", "set_gui_flow_table", "create_visibility_button", + "check_item_lists", "update_task_gui", "check_end_of_round", "end_round_gui_update", "try_to_check_victory", "update_gui", "check_start_round", + "check_start_set_areas", "check_start_setting_entities", "check_set_areas", "check_clear_areas", "check_chests", "check_chests_shopping_list", + "check_chests_production", "check_input_chests", "fill_input_chests", "check_victory", "shopping_task_finished", "calculate_force_points", + "update_research_task_table", "update_production_task_table", "update_shopping_list_task_table", "create_joined_game_gui", "pre_ending_round", + "player_ending_prompt", "update_end_timer", "update_begin_timer", "team_finished", "save_points_list", "give_force_players_points", + "update_winners_list", "set_spectator", "set_character", "give_starting_inventory", "give_equipment", "shuffle_table", "format_time", + "spairs", "fill_leaderboard", "create_grid", "simple_entities", "save_map_data", "clear_map", "create_tiles", "recreate_entities", + "map_sets", "give_points", "init_forces", "init_globals", "init_unit_settings", "check_next_wave", "next_wave", "calculate_wave_power", + "wave_end", "make_next_spawn_tick", "check_spawn_units", "get_wave_units", "spawn_units", "randomize_ore", "set_command", "command_straglers", + "unit_config", "make_next_wave_tick", "time_to_next_wave", "time_to_wave_end", "rocket_died", "unit_died", "get_bounty_price", "setup_waypoints", + "insert_items", "give_starting_equipment", "give_spawn_equipment", "next_round_button_visible", "gui_init", "create_wave_frame", "create_money_frame", + "create_upgrade_gui", "update_upgrade_listing", "upgrade_research", "get_upgrades", "get_money", "update_connected_players", "update_round_number", + "set_research", "set_recipes", "check_deconstruction", "check_blueprint_placement", "loop_entities", "experiment_items", + "setup", "story_gui_click", "clear_surface", "add_run_trains_button", "puzzle_condition", "basic_signals", + "loop_trains", "Y_offset", "ghosts_1", "ghosts_2", "required_path", "through_wall_path", "count", "check_built_real_rail", + "current_ghosts_count", "other", "rails", "set_rails", "straight_section", "late_entities", "entities", "stop", + "get_spawn_coordinate", + + --tutorials + "intermission", "create_entities_on_tick", "on_player_created", "required_count", "non_player_entities", "clear_rails", + "chest", "damage", "furnace", "init_prototypes", "build_infi_table", "junk", "update_player_tags", "time_left", "team_production", + "create_task_frame", "create_visibilty_buttons", "update_leaderboard", "in_in_area" + } + } + + stds.factorio_base_data = { + globals = { + --Style + "make_cursor_box", "make_full_cursor_box", + "default_container_padding", "default_orange_color", "default_light_orange_color", "warning_red_color", + "achievement_green_color", "achievement_tan_color", "orangebuttongraphcialset", "bluebuttongraphcialset", + "bonus_gui_ordering", "trivial_smoke", "technology_slot_base_width", "technology_slot_base_height", "default_frame_font_vertical_compensation", + + --Belts + "transport_belt_connector_frame_sprites", "transport_belt_circuit_wire_connection_point", "transport_belt_circuit_wire_max_distance", + "transport_belt_circuit_connector_sprites", "ending_patch_prototype", "basic_belt_horizontal", "basic_belt_vertical", + "basic_belt_ending_top", "basic_belt_ending_bottom", "basic_belt_ending_side", "basic_belt_starting_top", "basic_belt_starting_bottom", + "basic_belt_starting_side", "fast_belt_horizontal", "fast_belt_vertical", "fast_belt_ending_top", "fast_belt_ending_bottom", + "fast_belt_ending_side", "fast_belt_starting_top", "fast_belt_starting_bottom", "fast_belt_starting_side", "express_belt_horizontal", + "express_belt_vertical", "express_belt_ending_top", "express_belt_ending_bottom", "express_belt_ending_side", "express_belt_starting_top", + "express_belt_starting_bottom", "express_belt_starting_side", + + --Circuit Connectors + "circuit_connector_definitions", "default_circuit_wire_max_distance", "inserter_circuit_wire_max_distance", + "universal_connector_template", "belt_connector_template", "belt_frame_connector_template", "inserter_connector_template", + + --Inserter Circuit Connectors + "inserter_circuit_wire_max_distance", "inserter_default_stack_control_input_signal", + + --Sounds/beams + "make_heavy_gunshot_sounds", "make_light_gunshot_sounds", "make_laser_sounds", + + --Gun/Laser + "gun_turret_extension", "gun_turret_extension_shadow", "gun_turret_extension_mask", "gun_turret_attack", + "laser_turret_extension", "laser_turret_extension_shadow", "laser_turret_extension_mask", + + --Pipes + "pipecoverspictures", "pipepictures", "assembler2pipepictures", "assembler3pipepictures", "make_heat_pipe_pictures", + + --Combinators + "generate_arithmetic_combinator", "generate_decider_combinator", "generate_constant_combinator", + + --Rail + "destroyed_rail_pictures", "rail_pictures", "rail_pictures_internal", "standard_train_wheels", "drive_over_tie", + "rolling_stock_back_light", "rolling_stock_stand_by_light", + + --Enemies + "make_enemy_autoplace", "make_enemy_spawner_autoplace", "make_enemy_worm_autoplace", + "make_spitter_attack_animation", "make_spitter_run_animation", "make_spitter_dying_animation", + "make_spitter_attack_parameters", "make_spitter_roars", "make_spitter_dying_sounds", + "make_spawner_idle_animation", "make_spawner_die_animation", + "make_biter_run_animation", "make_biter_attack_animation", "make_biter_die_animation", + "make_biter_roars", "make_biter_dying_sounds", "make_biter_calls", + "make_worm_roars", "make_worm_dying_sounds", "make_worm_folded_animation", "make_worm_preparing_animation", + "make_worm_prepared_animation", "make_worm_attack_animation", "make_worm_die_animation", + + --Other + "tile_variations_template", "make_water_autoplace_settings", + "make_unit_melee_ammo_type", "make_trivial_smoke", "make_4way_animation_from_spritesheet", "flying_robot_sounds", + "productivitymodulelimitation", "crash_trigger", "capsule_smoke", "make_beam", "playeranimations", + "make_blood_tint", "make_shadow_tint", + + --tiles + "water_transition_template", "make_water_transition_template", "water_autoplace_settings", "water_tile_type_names", + "patch_for_inner_corner_of_transition_between_transition", + } + } + + stds.factorio_base_story = { + globals = { + "story_init_helpers", "story_update_table", "story_init", "story_update", "story_on_tick", "story_add_update", + "story_remove_update", "story_jump_to", "story_elapsed", "story_elapsed_check", "story_show_message_dialog", + "set_goal", "player_set_goal", "on_player_joined", "flash_goal", "set_info", "player_set_info", "export_entities", + "list", "recreate_entities", "entity_to_connect", "limit_camera", "find_gui_recursive", "enable_entity_export", + "add_button", "on_gui_click", "set_continue_button_style", "add_message_log", "story_add_message_log", + "player_add_message_log", "message_log_frame", "message_log_scrollpane", "message_log_close_button", + "message_log_table", "toggle_message_log_button", "toggle_objective_button", "message_log_init", + "add_gui_recursive", "add_toggle_message_log_button", "add_toggle_objective_button", "mod_gui", + "flash_message_log_button", "flash_message_log_on_tick", "story_gui_click", "story_points_by_name", "story_branches", + "player", "surface", "deconstruct_on_tick", "recreate_entities_on_tick", "flying_congrats", "story_table" + } + } + + stds.factorio_circuit_connector_generated = { + globals = { + 'default_circuit_wire_max_distance', 'circuit_connector_definitions', 'universal_connector_template', + 'belt_connector_template', 'belt_frame_connector_template', 'inserter_connector_template', 'inserter_connector_template', + 'inserter_circuit_wire_max_distance', 'inserter_default_stack_control_input_signal', 'transport_belt_connector_frame_sprites', + 'transport_belt_circuit_wire_max_distance', + } + } +end + +do -- STDLIB STDs-- + stds.stdlib = { + read_globals = {}, + globals = { + 'STDLIB', + 'prequire', + 'rawtostring', + 'traceback', + 'data_traceback', + 'inspect', + 'serpent', + 'inline_if', + 'install', + 'log', + 'concat', + 'GAME', + 'AREA', + 'POSITION', + 'TILE', + 'SURFACE', + 'CHUNK', + 'COLOR', + 'ENTITY', + 'INVENTORY', + 'RESOURCE', + 'CONFIG', + 'LOGGER', + 'QUEUE', + 'EVENT', + 'GUI', + 'PLAYER', + 'FORCE', + 'MATH', + 'STRING', + 'TABLE' + } + } + + stds.stdlib_control = {} + + stds.stdlib_data = { + globals = { + 'DATA', + 'RECIPE', + 'ITEM', + 'FLUID', + 'ENTITY', + 'TECHNOLOGY', + 'CATEGORY' + } + } +end + +do -- Love STDs + stds.love_extra = { + read_globals = { + love = { + fields = { + arg = { + fields = { + 'parseGameArguments', + 'parseOption', + 'getLow', + 'optionIndices', + 'options' + } + } + } + }, + 'coroutine', + 'io', + 'socket', + 'dofile', + 'loadfile' + }, + globals = { + love = { + fields = { + 'handlers' + } + } + } + } +end + +do -- Factorio Defines STDs-- + stds.factorio_defines = { + read_globals = { + defines = { + fields = { + alert_type = { + fields = { + 'entity_destroyed', + 'entity_under_attack', + 'not_enough_construction_robots', + 'no_material_for_construction', + 'not_enough_repair_packs', + 'turret_fire', + 'custom', + 'no_storage', + 'train_out_of_fuel', + 'fluid_mixing' + } + }, + behavior_result = { + fields = { + 'in_progress', + 'fail', + 'success', + 'deleted' + } + }, + build_check_type = { + fields = { + 'script', + 'manual', + 'ghost_place', + 'ghost_revive' + } + }, + chain_signal_state = { + fields = { + 'all_open', + 'partially_open', + 'none_open' + } + }, + chunk_generated_status = { + fields = { + 'nothing', + 'custom_tiles', + 'basic_tiles', + 'corrected_tiles', + 'tiles', + 'entities' + } + }, + circuit_condition_index = { + fields = { + 'inserter_circuit', + 'inserter_logistic', + 'lamp', + 'arithmetic_combinator', + 'decider_combinator', + 'constant_combinator', + 'offshore_pump', + 'pump' + } + }, + circuit_connector_id = { + fields = { + 'accumulator', + 'constant_combinator', + 'container', + 'programmable_speaker', + 'rail_signal', + 'rail_chain_signal', + 'roboport', + 'storage_tank', + 'wall', + 'electric_pole', + 'inserter', + 'lamp', + 'combinator_input', + 'combinator_output', + 'offshore_pump', + 'pump' + } + }, + command = { + fields = { + 'attack', + 'go_to_location', + 'compound', + 'group', + 'attack_area', + 'wander', + 'flee', + 'stop', + 'build_base' + } + }, + compound_command = { + fields = { + 'logical_and', + 'logical_or', + 'return_last' + } + }, + control_behavior = { + fields = { + inserter = { + fields = { + circuit_mode_of_operation = { + fields = { + 'enable_disable', + 'set_filters', + 'read_hand_contents', + 'set_stack_size' + } + }, + hand_read_mode = { + fields = { + 'hold', + 'pulse' + } + } + } + }, + lamp = { + fields = { + circuit_mode_of_operation = { + fields = { + 'use_colors' + } + } + } + }, + logistic_container = { + fields = { + circuit_mode_of_operation = { + fields = { + 'send_contents', + 'set_requests' + } + } + } + }, + mining_drill = { + fields = { + resource_read_mode = { + fields = { + 'this_miner', + 'entire_patch' + } + } + } + }, + roboport = { + fields = { + circuit_mode_of_operation = { + fields = { + 'read_logistics', + 'read_robot_stats' + } + } + } + }, + train_stop = { + fields = { + circuit_mode_of_operation = { + fields = { + 'enable_disable', + 'send_to_train', + 'read_from_train', + 'read_stopped_train' + } + } + } + }, + transport_belt = { + fields = { + content_read_mode = { + fields = { + 'pulse', + 'hold' + } + } + } + }, + type = { + fields = { + 'container', + 'generic_on_off', + 'inserter', + 'lamp', + 'logistic_container', + 'roboport', + 'storage_tank', + 'train_stop', + 'decider_combinator', + 'arithmetic_combinator', + 'constant_combinator', + 'transport_belt', + 'accumulator', + 'rail_signal', + 'rail_chain_signal', + 'wall', + 'mining_drill', + 'programmable_speaker' + } + } + } + }, + controllers = { + fields = { + 'ghost', + 'character', + 'god', + 'editor', + 'cutscene', + 'spectator' + } + }, + deconstruction_item = { + fields = { + entity_filter_mode = { + fields = { + 'whitelist', + 'blacklist' + } + }, + tile_filter_mode = { + fields = { + 'whitelist', + 'blacklist' + } + }, + tile_selection_mode = { + fields = { + 'normal', + 'always', + 'never', + 'only' + } + } + } + }, + difficulty = { + fields = { + 'easy', + 'normal', + 'hard' + } + }, + difficulty_settings = { + fields = { + recipe_difficulty = { + fields = { + 'normal', + 'expensive' + } + }, + technology_difficulty = { + fields = { + 'normal', + 'expensive' + } + } + } + }, + direction = { + fields = { + 'north', + 'northeast', + 'east', + 'southeast', + 'south', + 'southwest', + 'west', + 'northwest' + } + }, + distraction = { + fields = { + 'by_enemy', + 'by_anything', + 'by_damage' + } + }, + entity_status = { + fields = { + 'working', + 'no_power', + 'no_fuel', + 'no_recipe', + 'no_input_fluid', + 'no_research_in_progress', + 'no_minable_resources', + 'low_input_fluid', + 'low_power', + 'disabled_by_control_behavior', + 'disabled_by_script', + 'fluid_ingredient_shortage', + 'fluid_production_overload', + 'item_ingredient_shortage', + 'item_production_overload', + 'marked_for_deconstruction', + 'missing_required_fluid', + 'missing_science_packs', + 'waiting_for_source_items', + 'waiting_for_space_in_destination', + 'waiting_to_launch_rocket' + } + }, + events = { + fields = { + 'on_tick', + 'on_gui_click', + 'on_gui_confirmed', + 'on_gui_text_changed', + 'on_gui_checked_state_changed', + 'on_entity_died', + 'on_post_entity_died', + 'on_entity_damaged', + 'on_picked_up_item', + 'on_built_entity', + 'on_sector_scanned', + 'on_player_mined_item', + 'on_put_item', + 'on_rocket_launched', + 'on_pre_player_mined_item', + 'on_chunk_generated', + 'on_player_crafted_item', + 'on_robot_built_entity', + 'on_robot_pre_mined', + 'on_robot_mined', + 'on_research_started', + 'on_research_finished', + 'on_player_rotated_entity', + 'on_player_set_quickbar_slot', + 'on_marked_for_deconstruction', + 'on_cancelled_deconstruction', + 'on_trigger_created_entity', + 'on_trigger_fired_artillery', + 'on_train_changed_state', + 'on_player_created', + 'on_resource_depleted', + 'on_player_driving_changed_state', + 'on_force_created', + 'on_forces_merging', + 'on_player_cursor_stack_changed', + 'on_pre_entity_settings_pasted', + 'on_entity_settings_pasted', + 'on_player_main_inventory_changed', + 'on_player_armor_inventory_changed', + 'on_player_ammo_inventory_changed', + 'on_player_gun_inventory_changed', + 'on_player_placed_equipment', + 'on_player_removed_equipment', + 'on_pre_player_died', + 'on_player_died', + 'on_player_respawned', + 'on_player_joined_game', + 'on_player_left_game', + 'on_player_built_tile', + 'on_player_mined_tile', + 'on_robot_built_tile', + 'on_robot_mined_tile', + 'on_player_selected_area', + 'on_player_alt_selected_area', + 'on_player_changed_surface', + 'on_selected_entity_changed', + 'on_market_item_purchased', + 'on_player_dropped_item', + 'on_biter_base_built', + 'on_player_changed_force', + 'on_entity_renamed', + 'on_gui_selection_state_changed', + 'on_runtime_mod_setting_changed', + 'on_difficulty_settings_changed', + 'on_surface_created', + 'on_surface_deleted', + 'on_pre_surface_deleted', + 'on_player_mined_entity', + 'on_robot_mined_entity', + 'on_train_created', + 'on_gui_elem_changed', + 'on_player_setup_blueprint', + 'on_player_deconstructed_area', + 'on_player_configured_blueprint', + 'on_console_chat', + 'on_console_command', + 'on_player_removed', + 'on_pre_player_removed', + 'on_player_used_capsule', + 'script_raised_built', + 'script_raised_destroy', + 'script_raised_revive', + 'on_player_promoted', + 'on_player_demoted', + 'on_combat_robot_expired', + 'on_player_changed_position', + 'on_mod_item_opened', + 'on_gui_opened', + 'on_gui_closed', + 'on_gui_value_changed', + 'on_player_muted', + 'on_player_unmuted', + 'on_player_cheat_mode_enabled', + 'on_player_cheat_mode_disabled', + 'on_character_corpse_expired', + 'on_pre_ghost_deconstructed', + 'on_player_pipette', + 'on_player_display_resolution_changed', + 'on_player_display_scale_changed', + 'on_pre_player_crafted_item', + 'on_player_cancelled_crafting', + 'on_chunk_charted', + 'on_technology_effects_reset', + 'on_land_mine_armed', + 'on_forces_merged', + 'on_player_trash_inventory_changed', + 'on_pre_player_left_game', + 'on_pre_surface_cleared', + 'on_surface_cleared', + 'on_chunk_deleted', + 'on_pre_chunk_deleted', + 'on_train_schedule_changed', + 'on_player_banned', + 'on_player_kicked', + 'on_player_unbanned', + 'on_rocket_launch_ordered', + 'on_script_path_request_finished', + 'on_ai_command_completed', + 'on_marked_for_upgrade', + 'on_cancelled_upgrade', + 'on_player_toggled_map_editor', + 'on_entity_cloned', + 'on_area_cloned', + 'on_brush_cloned', + 'on_game_created_from_scenario', + 'on_surface_imported', + 'on_surface_renamed', + 'on_player_toggled_alt_mode', + 'on_player_repaired_entity', + 'on_player_fast_transferred', + 'on_pre_robot_exploded_cliff', + 'on_robot_exploded_cliff', + 'on_entity_spawned', + 'on_cutscene_waypoint_reached', + 'on_unit_group_created', + 'on_unit_added_to_group', + 'on_unit_removed_from_group', + 'on_unit_group_finished_gathering', + 'on_build_base_arrived', + 'on_chart_tag_added', + 'on_chart_tag_modified', + 'on_chart_tag_removed', + 'on_lua_shortcut', + 'on_gui_location_changed', + 'on_gui_selected_tab_changed', + 'on_gui_switch_state_changed', + 'on_force_cease_fire_changed', + 'on_force_friends_changed', + 'on_string_translated', + 'on_script_trigger_effect' + } + }, + flow_precision_index = { + fields = { + 'one_second', + 'one_minute', + 'ten_minutes', + 'one_hour', + 'ten_hours', + 'fifty_hours', + 'two_hundred_fifty_hours', + 'one_thousand_hours' + } + }, + group_state = { + fields = { + 'gathering', + 'moving', + 'attacking_distraction', + 'attacking_target', + 'finished', + 'pathfinding', + 'wander_in_group' + } + }, + gui_type = { + fields = { + 'entity', + 'research', + 'controller', + 'production', + 'item', + 'bonus', + 'trains', + 'achievement', + 'blueprint_library', + 'equipment', + 'logistic', + 'other_player', + 'kills', + 'permissions', + 'tutorials', + 'custom', + 'server_management', + 'player_management', + 'tile' + } + }, + input_action = { + fields = { + 'activate_copy', + 'activate_cut', + 'activate_paste', + 'add_permission_group', + 'add_train_station', + 'admin_action', + 'alt_select_area', + 'alt_select_blueprint_entities', + 'alternative_copy', + 'begin_mining', + 'begin_mining_terrain', + 'build_item', + 'build_rail', + 'build_terrain', + 'cancel_craft', + 'cancel_deconstruct', + 'cancel_new_blueprint', + 'cancel_research', + 'cancel_upgrade', + 'change_active_item_group_for_crafting', + 'change_active_item_group_for_filters', + 'change_active_quick_bar', + 'change_arithmetic_combinator_parameters', + 'change_blueprint_book_record_label', + 'change_decider_combinator_parameters', + 'change_item_label', + 'change_multiplayer_config', + 'change_picking_state', + 'change_programmable_speaker_alert_parameters', + 'change_programmable_speaker_circuit_parameters', + 'change_programmable_speaker_parameters', + 'change_riding_state', + 'change_shooting_state', + 'change_single_blueprint_record_label', + 'change_train_stop_station', + 'change_train_wait_condition', + 'change_train_wait_condition_data', + 'clean_cursor_stack', + 'clear_selected_blueprint', + 'clear_selected_deconstruction_item', + 'clear_selected_upgrade_item', + 'connect_rolling_stock', + 'copy', + 'copy_entity_settings', + 'craft', + 'create_blueprint_like', + 'cursor_split', + 'cursor_transfer', + 'custom_input', + 'cycle_blueprint_book_backwards', + 'cycle_blueprint_book_forwards', + 'deconstruct', + 'delete_blueprint_library', + 'delete_blueprint_record', + 'delete_custom_tag', + 'delete_permission_group', + 'destroy_opened_item', + 'disconnect_rolling_stock', + 'drag_train_schedule', + 'drag_train_wait_condition', + 'drop_blueprint_record', + 'drop_item', + 'drop_to_blueprint_book', + 'edit_custom_tag', + 'edit_permission_group', + 'export_blueprint', + 'fast_entity_split', + 'fast_entity_transfer', + 'go_to_train_station', + 'grab_blueprint_record', + 'gui_checked_state_changed', + 'gui_click', + 'gui_confirmed', + 'gui_elem_changed', + 'gui_location_changed', + 'gui_selected_tab_changed', + 'gui_selection_state_changed', + 'gui_switch_state_changed', + 'gui_text_changed', + 'gui_value_changed', + 'import_blueprint', + 'import_blueprint_string', + 'import_permissions_string', + 'inventory_split', + 'inventory_transfer', + 'launch_rocket', + 'lua_shortcut', + 'map_editor_action', + 'market_offer', + 'mod_settings_changed', + 'open_achievements_gui', + 'open_blueprint_library_gui', + 'open_blueprint_record', + 'open_bonus_gui', + 'open_character_gui', + 'open_equipment', + 'open_gui', + 'open_item', + 'open_kills_gui', + 'open_logistic_gui', + 'open_mod_item', + 'open_production_gui', + 'open_technology_gui', + 'open_train_gui', + 'open_train_station_gui', + 'open_trains_gui', + 'open_tutorials_gui', + 'paste_entity_settings', + 'place_equipment', + 'quick_bar_pick_slot', + 'quick_bar_set_selected_page', + 'quick_bar_set_slot', + 'remove_cables', + 'remove_train_station', + 'reset_assembling_machine', + 'rotate_entity', + 'select_area', + 'select_blueprint_entities', + 'select_entity_slot', + 'select_item', + 'select_mapper_slot', + 'select_next_valid_gun', + 'select_tile_slot', + 'set_auto_launch_rocket', + 'set_autosort_inventory', + 'set_behavior_mode', + 'set_car_weapons_control', + 'set_circuit_condition', + 'set_circuit_mode_of_operation', + 'set_deconstruction_item_tile_selection_mode', + 'set_deconstruction_item_trees_and_rocks_only', + 'set_entity_color', + 'set_entity_energy_property', + 'set_filter', + 'set_heat_interface_mode', + 'set_heat_interface_temperature', + 'set_infinity_container_filter_item', + 'set_infinity_container_remove_unfiltered_items', + 'set_infinity_pipe_filter', + 'set_inserter_max_stack_size', + 'set_inventory_bar', + 'set_logistic_filter_item', + 'set_logistic_filter_signal', + 'set_logistic_trash_filter_item', + 'set_request_from_buffers', + 'set_research_finished_stops_game', + 'set_signal', + 'set_single_blueprint_record_icon', + 'set_splitter_priority', + 'set_train_stopped', + 'setup_assembling_machine', + 'setup_blueprint', + 'setup_single_blueprint_record', + 'smart_pipette', + 'stack_split', + 'stack_transfer', + 'start_repair', + 'start_research', + 'start_walking', + 'stop_building_by_moving', + 'switch_connect_to_logistic_network', + 'switch_constant_combinator_state', + 'switch_inserter_filter_mode_state', + 'switch_power_switch_state', + 'switch_to_rename_stop_gui', + 'take_equipment', + 'toggle_deconstruction_item_entity_filter_mode', + 'toggle_deconstruction_item_tile_filter_mode', + 'toggle_driving', + 'toggle_enable_vehicle_logistics_while_moving', + 'toggle_equipment_movement_bonus', + 'toggle_map_editor', + 'toggle_personal_roboport', + 'toggle_show_entity_info', + 'translate_string', + 'undo', + 'upgrade', + 'upgrade_opened_blueprint', + 'use_artillery_remote', + 'use_item', + 'wire_dragging', + 'write_to_console' + } + }, + inventory = { + fields = { + 'fuel', + 'burnt_result', + 'chest', + 'furnace_source', + 'furnace_result', + 'furnace_modules', + 'character_main', + 'character_guns', + 'character_ammo', + 'character_armor', + 'character_vehicle', + 'character_trash', + 'god_main', + 'editor_main', + 'editor_guns', + 'editor_ammo', + 'editor_armor', + 'roboport_robot', + 'roboport_material', + 'robot_cargo', + 'robot_repair', + 'assembling_machine_input', + 'assembling_machine_output', + 'assembling_machine_modules', + 'lab_input', + 'lab_modules', + 'mining_drill_modules', + 'item_main', + 'rocket_silo_rocket', + 'rocket_silo_result', + 'rocket', + 'car_trunk', + 'car_ammo', + 'cargo_wagon', + 'turret_ammo', + 'beacon_modules', + 'character_corpse', + 'artillery_turret_ammo', + 'artillery_wagon_ammo' + } + }, + logistic_member_index = { + fields = { + 'logistic_container', + 'vehicle_storage', + 'character_requester', + 'character_storage', + 'character_provider', + 'generic_on_off_behavior' + } + }, + logistic_mode = { + fields = { + 'active_provider', + 'storage', + 'requester', + 'passive_provider', + 'buffer' + } + }, + mouse_button_type = { + fields = { + 'left', + 'right', + 'middle' + } + }, + rail_connection_direction = { + fields = { + 'left', + 'straight', + 'right' + } + }, + rail_direction = { + fields = { + 'front', + 'back' + } + }, + render_mode = { + fields = { + 'game', + 'chart', + 'chart_zoomed_in' + } + }, + rich_text_setting = { + fields = { + 'enabled', + 'disabled', + 'highlight' + } + }, + riding = { + fields = { + acceleration = { + fields = { + 'nothing', + 'accelerating', + 'braking', + 'reversing' + } + }, + direction = { + fields = { + 'left', + 'straight', + 'right' + } + } + } + }, + shooting = { + fields = { + 'not_shooting', + 'shooting_enemies', + 'shooting_selected' + } + }, + signal_state = { + fields = { + 'open', + 'closed', + 'reserved', + 'reserved_by_circuit_network' + } + }, + train_state = { + fields = { + 'on_the_path', + 'path_lost', + 'no_schedule', + 'no_path', + 'arrive_signal', + 'wait_signal', + 'arrive_station', + 'wait_station', + 'manual_control_stop', + 'manual_control' + } + }, + transport_line = { + fields = { + 'left_line', + 'right_line', + 'left_underground_line', + 'right_underground_line', + 'secondary_left_line', + 'secondary_right_line', + 'left_split_line', + 'right_split_line', + 'secondary_left_split_line', + 'secondary_right_split_line' + } + }, + wire_connection_id = { + fields = { + 'electric_pole', + 'power_switch_left', + 'power_switch_right' + } + }, + wire_type = { + fields = { + 'red', + 'green', + 'copper' + } + }, + -- Defines additional modules + color = { + other_fields = true + }, + anticolor = { + other_fields = true + }, + lightcolor = { + other_fields = true + }, + time = { + fields = { + 'second', + 'minute', + 'hour', + 'day', + 'week', + 'month', + 'year' + } + } + } + } + } + } +end + +--[[ Options + "ignore", "std", "globals", "unused_args", "self", "compat", "global", "unused", "redefined", + "unused_secondaries", "allow_defined", "allow_defined_top", "module", + "read_globals", "new_globals", "new_read_globals", "enable", "only", "not_globals", + "max_line_length", "max_code_line_length", "max_string_line_length", "max_comment_line_length", + "max_cyclomatic_complexity" +--]] + +--[[ Warnings list + -- 011 A syntax error. + -- 021 An invalid inline option. + -- 022 An unpaired inline push directive. + -- 023 An unpaired inline pop directive. + -- 111 Setting an undefined global variable. + -- 112 Mutating an undefined global variable. + -- 113 Accessing an undefined global variable. + -- 121 Setting a read-only global variable. + -- 122 Setting a read-only field of a global variable. + -- 131 Unused implicitly defined global variable. + -- 142 Setting an undefined field of a global variable. + -- 143 Accessing an undefined field of a global variable. + -- 211 Unused local variable. + -- 212 Unused argument. + -- 213 Unused loop variable. + -- 221 Local variable is accessed but never set. + -- 231 Local variable is set but never accessed. + -- 232 An argument is set but never accessed. + -- 233 Loop variable is set but never accessed. + -- 241 Local variable is mutated but never accessed. + -- 311 Value assigned to a local variable is unused. + -- 312 Value of an argument is unused. + -- 313 Value of a loop variable is unused. + -- 314 Value of a field in a table literal is unused. + -- 321 Accessing uninitialized local variable. + -- 331 Value assigned to a local variable is mutated but never accessed. + -- 341 Mutating uninitialized local variable. + -- 411 Redefining a local variable. + -- 412 Redefining an argument. + -- 413 Redefining a loop variable. + -- 421 Shadowing a local variable. + -- 422 Shadowing an argument. + -- 423 Shadowing a loop variable. + -- 431 Shadowing an upvalue. + -- 432 Shadowing an upvalue argument. + -- 433 Shadowing an upvalue loop variable. + -- 511 Unreachable code. + -- 512 Loop can be executed at most once. + -- 521 Unused label. + -- 531 Left-hand side of an assignment is too short. + -- 532 Left-hand side of an assignment is too long. + -- 541 An empty do end block. + -- 542 An empty if branch. + -- 551 An empty statement. + -- 611 A line consists of nothing but whitespace. + -- 612 A line contains trailing whitespace. + -- 613 Trailing whitespace in a string. + -- 614 Trailing whitespace in a comment. + -- 621 Inconsistent indentation (SPACE followed by TAB). + -- 631 Line is too long. +--]] diff --git a/packages/create/templates/plugin-js/controller.js b/packages/create/templates/plugin-js/controller.js index 9fbc057a..7d566479 100644 --- a/packages/create/templates/plugin-js/controller.js +++ b/packages/create/templates/plugin-js/controller.js @@ -1,12 +1,17 @@ "use strict"; const lib = require("@clusterio/lib"); const { BaseControllerPlugin } = require("@clusterio/controller"); -const { PluginExampleEvent, PluginExampleRequest } = require("./messages"); +const { PluginExampleEvent, PluginExampleRequest,/*/ [subscribable] /*/ ExampleSubscribableUpdate, ExampleSubscribableValue/*/ [] /*/ } = require("./messages"); -class ControllerPlugin extends BaseControllerPlugin { +class ControllerPlugin extends BaseControllerPlugin {// [subscribable] // + exampleDatabase; + storageDirty = false; + // [] // async init() { this.controller.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this)); - this.controller.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this)); + this.controller.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));// [subscribable] // + this.controller.subscriptions.handle(ExampleSubscribableUpdate, this.handleExampleSubscription.bind(this)); + this.exampleDatabase = new Map(); // If needed, replace with loading from database file // [] // } async onControllerConfigFieldChanged(field, curr, prev) { @@ -39,7 +44,14 @@ class ControllerPlugin extends BaseControllerPlugin { myResponseString: request.myString, myResponseNumbers: request.myNumberArray, }; - } + }// [subscribable] // + + async handleExampleSubscription(request) { + const values = [...this.exampleDatabase.values()].filter( + value => value.updatedAtMs > request.lastRequestTimeMs, + ); + return values.length ? new ExampleSubscribableUpdate(values) : null; + }// [] // } module.exports = { diff --git a/packages/create/templates/plugin-js/index.js b/packages/create/templates/plugin-js/index.js index abb30dcd..32ca9d87 100644 --- a/packages/create/templates/plugin-js/index.js +++ b/packages/create/templates/plugin-js/index.js @@ -1,7 +1,6 @@ "use strict"; const lib = require("@clusterio/lib"); - -const { PluginExampleEvent, PluginExampleRequest } = require("./messages"); +const messages = require("./messages"); lib.definePermission({ name: "// plugin_name //.example.permission.event", @@ -13,7 +12,13 @@ lib.definePermission({ name: "// plugin_name //.example.permission.request", title: "Example permission request", description: "My plugin's example permission that I forgot to remove", -});// [web] // +});// [subscribable] // + +lib.definePermission({ + name: "// plugin_name //.example.permission.subscribe", + title: "Example permission subscribe", + description: "My plugin's example permission that I forgot to remove", +});// [] //// [web] // lib.definePermission({ name: "// plugin_name //.page.view", @@ -26,7 +31,7 @@ const plugin = { title: "// plugin_name //", description: "I didn't update my description",// [entry_points] // // entry_points // - // [] //// [controller] // +// [] //// [controller] // controllerConfigFields: { "// plugin_name //.myControllerField": { title: "My Controller Field", @@ -61,8 +66,9 @@ const plugin = { },// [] // messages: [ - PluginExampleEvent, - PluginExampleRequest, + messages.PluginExampleEvent, + messages.PluginExampleRequest,// [subscribable] // + messages.ExampleSubscribableUpdate,// [] // ], }; diff --git a/packages/create/templates/plugin-js/messages.js b/packages/create/templates/plugin-js/messages.js index 126934e5..b6659ce4 100644 --- a/packages/create/templates/plugin-js/messages.js +++ b/packages/create/templates/plugin-js/messages.js @@ -1,6 +1,6 @@ "use strict"; const { plainJson, jsonArray, JsonBoolean, JsonNumber, JsonString, StringEnum } = require("@clusterio/lib"); -const { Type, Static } = require("@sinclair/typebox"); +const { Type } = require("@sinclair/typebox"); class PluginExampleEvent { static type = "event"; @@ -49,9 +49,49 @@ class PluginExampleRequest { "myResponseString": Type.String(), "myResponseNumbers": Type.Array(Type.Number()), })); +}// [subscribable] // + +class ExampleSubscribableValue { + constructor(id, updatedAtMs, isDeleted) { + this.id = id; + this.updatedAtMs = updatedAtMs; + this.isDeleted = isDeleted; + } + + static jsonSchema = Type.Object({ + id: Type.String(), + updatedAtMs: Type.Number(), + isDeleted: Type.Boolean(), + }); + + static fromJSON(json) { + return new this(json.id, json.updatedAtMs, json.isDeleted); + } } +class ExampleSubscribableUpdate { + static type = "event"; + static src = "controller"; + static dst = "control"; + static plugin = "// plugin_name //"; + static permission = "// plugin_name //.example.permission.subscribe"; + + constructor(updates) { + this.updates = updates; + } + + static jsonSchema = Type.Object({ + "updates": Type.Array(ExampleSubscribableValue.jsonSchema), + }); + + static fromJSON(json) { + return new this(json.updates.map(update => ExampleSubscribableValue.fromJSON(update))); + } +}// [] // + module.exports = { PluginExampleEvent, - PluginExampleRequest, + PluginExampleRequest,// [subscribable] // + ExampleSubscribableValue, + ExampleSubscribableUpdate,// [] // }; diff --git a/packages/create/templates/plugin-js/module/control.lua b/packages/create/templates/plugin-js/module/control.lua index 5e6a2e2a..c472aadd 100644 --- a/packages/create/templates/plugin-js/module/control.lua +++ b/packages/create/templates/plugin-js/module/control.lua @@ -1,18 +1,40 @@ 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", { @@ -20,8 +42,11 @@ MyModule.events[defines.events.on_player_crafted_item] = function(event) })// [] // 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 diff --git a/packages/create/templates/plugin-js/module/module_exports.lua b/packages/create/templates/plugin-js/module/module_exports.lua index 4399db98..ecce6291 100644 --- a/packages/create/templates/plugin-js/module/module_exports.lua +++ b/packages/create/templates/plugin-js/module/module_exports.lua @@ -1,7 +1,8 @@ local MyModule = require("./control")// [instance] // --- Can contain anything you plan to use with send_rcon from functions, modules, or data +--- 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 +--- Can contain anything you want to allow other plugins to have access to, this example exposes the whole module return MyModule diff --git a/packages/create/templates/plugin-js/web/plugin.jsx b/packages/create/templates/plugin-js/web/plugin.jsx index 9933fdb6..ea582e32 100644 --- a/packages/create/templates/plugin-js/web/plugin.jsx +++ b/packages/create/templates/plugin-js/web/plugin.jsx @@ -1,4 +1,4 @@ -const { useContext, useEffect, useState } = require("react"); +const { useContext, useEffect, useState,/*/ [subscribable] /*/ useCallback, useSyncExternalStore,/*/ [] /*/ } = require("react"); // const { } = require("antd"); const { @@ -6,17 +6,22 @@ const { } = require("@clusterio/web_ui"); const lib = require("@clusterio/lib"); -const { PluginExampleEvent, PluginExampleRequest } = require("../messages"); +const { PluginExampleEvent, PluginExampleRequest,/*/ [subscribable] /*/ ExampleSubscribableUpdate,/*/ [] /*/ } = require("../messages"); function MyTemplatePage() { - let control = useContext(ControlContext); + let control = useContext(ControlContext);// [subscribable] // + const plugin = control.plugins.get("// plugin_name //"); + const [subscribableData, synced] = plugin.useSubscribableData();// [] // return -

// plugin_name //

+

// plugin_name //

// [subscribable] // + Synced: {String(synced)} Data: {JSON.stringify([...subscribableData.values()])}// [] //
; } -class WebPlugin extends BaseWebPlugin { +class WebPlugin extends BaseWebPlugin {// [subscribable] // + subscribableData = new lib.EventSubscriber(ExampleSubscribableUpdate, this.control); + // [] // async init() { this.pages = [ { @@ -29,7 +34,13 @@ class WebPlugin extends BaseWebPlugin { this.control.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this)); this.control.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this)); - } + }// [subscribable] // + + useSubscribableData() { + const control = useContext(ControlContext); + const subscribe = useCallback((callback) => this.subscribableData.subscribe(callback), [control]); + return useSyncExternalStore(subscribe, () => this.subscribableData.getSnapshot()); + }// [] // async handlePluginExampleEvent(event) { this.logger.info(JSON.stringify(event)); diff --git a/packages/create/templates/plugin-ts/.luacheckrc b/packages/create/templates/plugin-ts/.luacheckrc new file mode 100644 index 00000000..cacf598d --- /dev/null +++ b/packages/create/templates/plugin-ts/.luacheckrc @@ -0,0 +1,1705 @@ +--[[---------------------------------------------------------------------------- + LICENSE + .luacheckrc for Factorio Version 0.18.8, luacheck version 0.23.0 + This file is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this file, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this file dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + THE FILE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + For more information, please refer to +------------------------------------------------------------------------------]] +local LINE_LENGTH = false + +local IGNORE = {'21./%w+_$', '21./^_%w+$', '213/[ijk]', '213/index', '213/key'} + +-- These globals are not available to the factorio API +local NOT_GLOBALS = {'coroutine', 'io', 'socket', 'dofile', 'loadfile'} + +local STD_CONTROL = 'lua52+factorio+factorio_control+stdlib+factorio_defines' +local STD_DATA = 'lua52+factorio+factorio_data+stdlib+stdlib_data+factorio_defines' + +-- For Base and Core Mods +local STD_BASE_DATA = 'lua52+factorio+factorio_data+factorio_defines+factorio_base_data' +local STD_BASE_CONTROL = 'lua52+factorio+factorio_control+factorio_defines+factorio_base_control' + +do -- Assume Factorio Control Stage as Default + std = STD_CONTROL + --cache = true + not_globals = NOT_GLOBALS + ignore = IGNORE + quiet = 1 -- pass -q option + max_cyclomatic_complexity = false + codes = true + max_line_length = LINE_LENGTH + max_code_line_length = LINE_LENGTH + max_string_line_length = LINE_LENGTH + max_comment_line_length = LINE_LENGTH + + --List of files and directories to exclude + exclude_files = { + --Ignore special folders + '**/.trash/', + '**/.history/', + '**/stdlib/vendor/', + + --Ignore development mods + '**/combat-tester/', + '**/test-maker/', + '**/trailer/', + + --Ignore love Includes + '**/love/includes/' + } +end + +do -- RedMew and ExpGaming overrides + globals = { + 'math', 'table', 'package', + 'print', 'require', 'unpack', 'inspect', 'loadstring', 'ServerCommands', 'Debug', + '_C', '_DEBUG', '_CHEATS', '_DUMP_ENV', '_LIFECYCLE', '_STAGE', + } +end + +do -- Set default prototype files + files['**/data.lua'].std = STD_DATA + files['**/data-updates.lua'].std = STD_DATA + files['**/data-final-fixes.lua'].std = STD_DATA + files['**/settings.lua'].std = STD_DATA + files['**/settings-updates.lua'].std = STD_DATA + files['**/settings-final-fixes.lua'].std = STD_DATA + files['**/prototypes/'].std = STD_DATA + files['**/settings/'].std = STD_DATA +end + +do -- Base and Core mod files + local base_scenarios = { + std = STD_BASE_CONTROL .. '+factorio_base_scenarios+factorio_base_story', + --ignore = {'212/event', '111', '112', '113', '211', '212', '213', '311', '411', '412', '421', '422', '423', '431', '432', '512'} + ignore = {'...'} + } + files['**/base/scenarios/'] = base_scenarios + files['**/base/tutorials/'] = base_scenarios + files['**/base/campaigns/'] = base_scenarios + files['**/wip-scenario/'] = base_scenarios + + files['**/base/migrations/'] = {std = STD_BASE_CONTROL} + + files['**/core/lualib/'] = {std = STD_BASE_CONTROL} + files['**/core/lualib/util.lua'] = {globals = {'util', 'table'}, ignore = {'432/object'}} + files['**/core/lualib/silo-script.lua'] = {globals = {'silo_script'}, ignore = {'4../player'}} + files['**/core/lualib/production-score.lua'] = {globals = {'production_score', 'get_price_recursive'}, ignore = {'4../player'}} + files['**/core/lualib/story*'] = {std = '+factorio_base_story', ignore = {'42./k', '42./filter'}} + files['**/core/lualib/mod-gui.lua'] = {globals = {'mod_gui'}} + files['**/core/lualib/camera.lua'] = {globals = {'camera'}} + files['**/core/lualib/builder.lua'] = {globals = {'Builder', 'builder', 'action', 'down', 'right'}} + + files['**/core/lualib/bonus-gui-ordering/'] = {std = STD_BASE_DATA} + files['**/core/lualib/dataloader.lua'] = {globals = {'data'}} + files['**/core/lualib/circuit-connector-*'] = {std = STD_BASE_DATA..'+factorio_circuit_connector_generated'} + files['**/core/lualib/bonus-gui-ordering.lua'] = {globals = {'bonus_gui_ordering'}} + + files['**/base/prototypes/'] = {std = STD_BASE_DATA} + files['**/core/prototypes/'] = {std = STD_BASE_DATA} + files['**/core/prototypes/noise-programs.lua'] = {ignore = {'212/x', '212/y', '212/tile', '212/map'}} +end + +do -- Stdlib Files + local stdlib_control = { + std = 'lua52+factorio+factorio_control+stdlib+factorio_defines', + max_line_length = LINE_LENGTH + } + + local stdlib_data = { + std = 'lua52+factorio+factorio_data+stdlib+factorio_defines', + max_line_length = LINE_LENGTH + } + + -- Assume control stage for stdlib + files['**/stdlib/'] = stdlib_control + + -- Assume generic lua for stdlib utils + files['**/stdlib/utils/**'].std = 'lua52+stdlib' + + -- STDLIB data stage files + files['**/stdlib/data/'] = stdlib_data + + -- STDLIB Busted Spec + files['**/spec/**'] = { + globals = {'serpent', 'log', 'package.remove_stdlib'}, + std = 'lua52c+busted+factorio_defines+factorio_control+stdlib' + } + + -- Love + files['**/love/'].std = 'luajit+love+love_extra+stdlib+stdlib_data' +end + +do -- Factorio STDs-- + stds.factorio = { + --Set the read only variables + read_globals = { + -- @log@: Gives writing access to Factorio's logger instance. + "log", + -- @serpent@: Lua serializer and pretty printer. (https://github.com/pkulchenko/serpent). + "serpent", + -- @table_size@: Returns the number of elements inside an LUA table. + "table_size", + -- @lldebugger@: Provided by lua local debugger vscode extension. + lldebugger = { + fields = {'requestBreak'}, + other_fields = true, + read_only = false, + }, + -- @__DebugAdapter@: Provided by Factorio Mod Debug vscode extension. + __DebugAdapter = { + fields = {'print', 'stepIgnoreAll', 'stepIgnore', 'breakpoint'}, + other_fields = true, + read_only = false + }, + util = { + fields = { + "by_pixel", "distance", "findfirstentity", "positiontostr", "formattime", "moveposition", "oppositedirection", + "ismoduleavailable", "multiplystripes", "format_number", "increment", "color", "conditional_return", + "add_shift", "merge", "premul_color", "encode", "decode", "insert_safe", + table = { + fields = { + "compare", "deepcopy" + }, + }, + }, + }, + table = { + fields = { + "compare", "deepcopy" + }, + }, + }, + } + + stds.factorio_control = { + read_globals = { + + -- @commands@: + commands = { + fields = { + "add_command", "commands", "game_commands", "remove_command" + }, + }, + + -- @settings@: + settings = { + fields = { + "get_player_settings", + startup = {other_fields = true}, + global = {other_fields = true}, + player = {other_fields = true}, + get = {read_only = false}, -- stdlib added + get_startup = {read_only = false} -- stdlib added + }, + }, + + -- @script@: Provides an interface for registering event handlers. + -- (http://lua-api.factorio.com/latest/LuaBootstrap.html) + script = { + fields = { + "on_event", "on_nth_tick", "on_configuration_changed", "on_init", "on_load", "generate_event_name", + "raise_event", "get_event_handler", "mod_name", "get_event_order", + "is_game_in_debug_mode", "object_name", "set_event_filter", "get_event_filter", "register_metatable", + active_mods = {read_only = true, other_fields = true}, + }, + other_fields = false, + }, + + -- @remote@: Allows inter-mod communication by providing a repository of interfaces that is shared by all mods. + -- (http://lua-api.factorio.com/latest/LuaRemote.html) + remote = { + fields = { + interfaces = {read_only = false, other_fields = true}, + "add_interface", "remove_interface", "call" + }, + read_only = true, + other_fields = false, + }, + + rcon = { + fields = {'print'} + }, + + rendering = { + other_fields = false, + read_only = true, + fields = { + 'draw_animation', + 'draw_line', + 'draw_text', + 'draw_circle', + 'draw_rectangle', + 'draw_arc', + 'draw_polygon', + 'draw_sprite', + 'draw_light', + 'destroy', + 'is_font_valid', + 'is_valid', + 'get_all_ids', + 'clear', + 'get_type', + 'get_surface', + 'get_time_to_live', + 'set_time_to_live', + 'get_forces', + 'set_forces', + 'get_players', + 'set_players', + 'get_color', + 'set_color', + 'get_width', + 'set_width', + 'get_from', + 'set_from', + 'get_to', + 'set_to', + 'get_gap_amount', + 'set_gap_amount', + 'get_gap_length', + 'set_gap_length', + 'get_target', + 'set_target', + 'get_orientation', + 'set_orientation', + 'get_scale', + 'set_scale', + 'get_text', + 'set_text', + 'get_font', + 'set_font', + 'get_alignment', + 'set_alignment', + 'get_scale_with_zoom', + 'set_scale_with_zoom', + 'get_filled', + 'set_filled', + 'get_radius', + 'set_radius', + 'get_left_top', + 'set_left_top', + 'get_right_bottom', + 'set_right_bottom', + 'get_max_radius', + 'set_max_radius', + 'get_min_radius', + 'set_min_radius', + 'get_start_angle', + 'set_start_angle', + 'get_angle', + 'set_angle', + 'get_vertices', + 'set_vertices', + 'get_sprite', + 'set_sprite', + 'get_x_scale', + 'set_x_scale', + 'get_y_scale', + 'set_y_scale', + 'get_render_layer', + 'set_render_layer', + 'get_orientation_target', + 'set_orientation_target', + 'get_oriented_offset', + 'set_oriented_offset', + 'get_intensity', + 'set_intensity', + 'get_minimum_darkness', + 'set_minimum_darkness' + } + }, + + -- @game@: Main object through which most of the API is accessed. + -- It is, however, not available inside handlers registered with @script.on_load@. + -- (http://lua-api.factorio.com/latest/LuaGameScript.html) + game ={ + other_fields = false, + read_only = false, + fields = { + 'auto_save', + 'ban_player', + 'check_consistency', + 'check_prototype_translations', + 'count_pipe_groups', + 'create_force', + 'create_profiler', + 'create_random_generator', + 'create_surface', + 'decode_string', + 'delete_surface', + 'direction_to_string', + 'disable_replay', + 'disable_tips_and_tricks', + 'disable_tutorial_triggers', + 'encode_string', + 'evaluate_expression', + 'force_crc', + 'get_active_entities_count', + 'get_entity_by_tag', + 'get_filtered_achievement_prototypes', + 'get_filtered_decorative_prototypes', + 'get_filtered_entity_prototypes', + 'get_filtered_equipment_prototypes', + 'get_filtered_fluid_prototypes', + 'get_filtered_item_prototypes', + 'get_filtered_mod_setting_prototypes', + 'get_filtered_recipe_prototypes', + 'get_filtered_technology_prototypes', + 'get_filtered_tile_prototypes', + 'get_map_exchange_string', + 'get_player', + 'get_surface', + 'get_train_stops', + 'help', + 'is_demo', + 'is_multiplayer', + 'is_valid_sound_path', + 'is_valid_sprite_path', + 'json_to_table', + 'kick_player', + 'log_recipe_locale', + 'merge_forces', + 'mute_player', + 'parse_map_exchange_string', + 'play_sound', + 'print', + 'print_stack_size', + 'purge_player', + 'regenerate_entity', + 'reload_mods', + 'reload_script', + 'remove_offline_players', + 'remove_path', + 'reset_time_played', + 'save_atlas', + 'server_save', + 'set_game_state', + 'set_wait_for_screenshots_to_finish', + 'show_message_dialog', + 'table_to_json', + 'take_screenshot', + 'take_technology_screenshot', + 'unban_player', + 'unmute_player', + 'write_file', + + achievement_prototypes = {read_only = true, other_fields = true}, + active_mods = {read_only = true, other_fields = true}, + ammo_category_prototypes = {read_only = true, other_fields = true}, + autoplace_control_prototypes = {read_only = true, other_fields = true}, + backer_names = {read_only = true, other_fields = true}, + connected_players = {read_only = true, other_fields = true}, + custom_input_prototypes = {read_only = true, other_fields = true}, + damage_prototypes = {read_only = true, other_fields = true}, + decorative_prototypes = {read_only = true, other_fields = true}, + default_map_gen_settings = {read_only = true, other_fields = true}, + difficulty = {read_only = true, other_fields = true}, + difficulty_settings = {read_only = true, other_fields = true}, + entity_prototypes = {read_only = true, other_fields = true}, + equipment_category_prototypes = {read_only = true, other_fields = true}, + equipment_grid_prototypes = {read_only = true, other_fields = true}, + equipment_prototypes = {read_only = true, other_fields = true}, + finished = {read_only = true, other_fields = true}, + fluid_prototypes = {read_only = true, other_fields = true}, + forces = {read_only = true, other_fields = true}, + fuel_category_prototypes = {read_only = true, other_fields = true}, + item_group_prototypes = {read_only = true, other_fields = true}, + item_prototypes = {read_only = true, other_fields = true}, + item_subgroup_prototypes = {read_only = true, other_fields = true}, + map_settings = {read_only = true, other_fields = true}, + mod_setting_prototypes = {read_only = true, other_fields = true}, + module_category_prototypes = {read_only = true, other_fields = true}, + named_noise_expressions = {read_only = true, other_fields = true}, + noise_layer_prototypes = {read_only = true, other_fields = true}, + object_name = {read_only = true, other_fields = true}, + particle_prototypes = {read_only = true, other_fields = true}, + permissions = {read_only = true, other_fields = true}, + player = {read_only = true, other_fields = true}, + players = {read_only = true, other_fields = true}, + pollution_statistics = {read_only = true, other_fields = true}, + recipe_category_prototypes = {read_only = true, other_fields = true}, + recipe_prototypes = {read_only = true, other_fields = true}, + resource_category_prototypes = {read_only = true, other_fields = true}, + shortcut_prototypes = {read_only = true, other_fields = true}, + styles = {read_only = true, other_fields = true}, + surfaces = {read_only = true, other_fields = true}, + technology_prototypes = {read_only = true, other_fields = true}, + tick = {read_only = true, other_fields = true}, + ticks_played = {read_only = true, other_fields = true}, + tile_prototypes = {read_only = true, other_fields = true}, + trivial_smoke_prototypes = {read_only = true, other_fields = true}, + virtual_signal_prototypes = {read_only = true, other_fields = true}, + + autosave_enabled = {read_only = false, other_fields = false}, + draw_resource_selection = {read_only = false, other_fields = false}, + enemy_has_vision_on_land_mines = {read_only = false, other_fields = false}, + speed = {read_only = false, other_fields = false}, + tick_paused = {read_only = false, other_fields = false}, + ticks_to_run = {read_only = false, other_fields = false} + }, + }, + }, + + globals = { + -- @global@: The global dictionary, useful for storing data persistent across a save-load cycle. + -- Writing access is given to the mod-id field (for mod-wise saved data). + -- (http://lua-api.factorio.com/latest/Global.html) + "global", + + -- @MOD@: Keep it organized, use this variable for anything that "NEEDS" to be global for some reason. + "MOD" + }, + } + + stds.factorio_data = { + + read_globals = { + data = { + fields = { + raw = { + other_fields = true, + read_only = false + }, + "extend", "is_demo" + }, + }, + + settings = { + fields = { + "startup", "global", "player", "get", "get_startup" + }, + }, + + mods = { + other_fields = true + }, + + -- Popular mods that think they need globals! + angelsmods = { + other_fields = true + }, + + -- Popular mods that think they need globals! + bobmods = { + other_fields = true + }, + + + } + } +end + +do -- Factorio Base/Core STDs-- + stds.factorio_base_control = { + read_globals = {"silo_script", "mod_gui", "camera"} + } + + stds.factorio_base_scenarios = { + globals = { + "check_automate_science_packs_advice", "check_research_hints", "check_supplies", "manage_attacks", "all_dead", + "on_win", "difficulty_number", "init_attack_data", "handle_attacks", "count_items_in_container", "progress", "scanned", + "check_light", "check_machine_gun", "level", "story_table", + + "tightspot_prices", "tightspot_make_offer", "tightspot_init", "tightspot_get_required_balance", + "tightspot_init_level", "tightspot_init_spending_frame", "tightspot_init_progress_frame", "tightspot_update_progress", "tightspot_update_spending", + "tightspot_get_missing_to_win", "tightspot_sell_back", "tightspot_start_level", "tightspot_show_level_description", "tightspot_update_speed_label", + "map_ignore", "tightspot_check_level", "land_price", + + "transport_belt_madness_init", "transport_belt_madness_init_level", "transport_belt_madness_create_chests", "transport_belt_madness_fill_chests", + "transport_belt_madness_start_level", "map_ignore", "map_clear", "map_load", "map_save", "transport_belt_madness_show_level_description", + "transport_belt_madness_check_level", "transport_belt_madness_next_level", "transport_belt_madness_clear_level", "transport_belt_madness_contains_next_level", + + "restricted", "check_built_items", "result", "disable_combat_technologies", "apply_character_modifiers", "apply_combat_modifiers", "apply_balance", + "load_config", "starting_area_constant", "create_next_surface", "end_round", "prepare_next_round", "silo_died","choose_joining_gui", + "destroy_joining_guis", "create_random_join_gui", "create_auto_assign_gui", "create_pick_join_gui", "create_config_gui", "make_config_table", "default", + "make_team_gui", "make_team_gui_config", "add_team_button_press", "trash_team_button_press", "remove_team_from_team_table", "add_team_to_team_table", + "set_teams_from_gui", "on_team_button_press", "make_color_dropdown", "create_balance_option", "create_disable_frame", "disable_frame", "parse_disabled_items", + "set_balance_settings", "config_confirm", "parse_config_from_gui", "get_color", "roll_starting_area", "delete_roll_surfaces", "auto_assign", + "destroy_config_for_all", "prepare_map", "set_evolution_factor", "update_players_on_team_count", "random_join", "init_player_gui", + "destroy_player_gui", "objective_button_press", "admin_button_press", "admin_frame_button_press", "diplomacy_button_press", "update_diplomacy_frame", + "diplomacy_frame_button_press", "team_changed_diplomacy", "diplomacy_check_press", "get_stance", "give_inventory", "setup_teams", "disable_items_for_all", + "set_random_team", "set_diplomacy", "create_spawn_positions", "set_spawn_position", "set_team_together_spawns", "chart_starting_area_for_force_spawns", + "check_starting_area_chunks_are_generated", "check_player_color", "check_round_start", "clear_starting_area_enemies", "check_no_rush_end", "check_no_rush_players", + "finish_setup", "chart_area_for_force", "setup_start_area_copy", "update_copy_progress", "update_progress_bar", "copy_paste_starting_area_tiles", + "copy_paste_starting_area_entities", "create_silo_for_force", "setup_research", "on_chunk_generated", "get_distance_to_nearest_spawn", + "create_wall_for_force", "fpn", "give_items", "create_item_frame", "create_technologies_frame", "create_cheat_frame", "create_day_frame", + "time_modifier", "points_per_second_start", "points_per_second_level_subtract", "levels", "update_info", "get_time_left", "update_time_left", + "on_joined", "make_frame", "update_frame", "update_table", "calculate_task_item_multiplayer", "setup_config", "select_from_probability_table", + "select_inventory", "select_equipment", "select_challange_type", "save_round_statistics", "start_challenge", "create_teams", "set_areas", + "decide_player_team", "set_teams", "refresh_leaderboard", "set_player", "generate_technology_list", "generate_research_task","setup_unlocks", + "check_technology_progress", "generate_production_task", "generate_shopping_list_task", "set_gui_flow_table", "create_visibility_button", + "check_item_lists", "update_task_gui", "check_end_of_round", "end_round_gui_update", "try_to_check_victory", "update_gui", "check_start_round", + "check_start_set_areas", "check_start_setting_entities", "check_set_areas", "check_clear_areas", "check_chests", "check_chests_shopping_list", + "check_chests_production", "check_input_chests", "fill_input_chests", "check_victory", "shopping_task_finished", "calculate_force_points", + "update_research_task_table", "update_production_task_table", "update_shopping_list_task_table", "create_joined_game_gui", "pre_ending_round", + "player_ending_prompt", "update_end_timer", "update_begin_timer", "team_finished", "save_points_list", "give_force_players_points", + "update_winners_list", "set_spectator", "set_character", "give_starting_inventory", "give_equipment", "shuffle_table", "format_time", + "spairs", "fill_leaderboard", "create_grid", "simple_entities", "save_map_data", "clear_map", "create_tiles", "recreate_entities", + "map_sets", "give_points", "init_forces", "init_globals", "init_unit_settings", "check_next_wave", "next_wave", "calculate_wave_power", + "wave_end", "make_next_spawn_tick", "check_spawn_units", "get_wave_units", "spawn_units", "randomize_ore", "set_command", "command_straglers", + "unit_config", "make_next_wave_tick", "time_to_next_wave", "time_to_wave_end", "rocket_died", "unit_died", "get_bounty_price", "setup_waypoints", + "insert_items", "give_starting_equipment", "give_spawn_equipment", "next_round_button_visible", "gui_init", "create_wave_frame", "create_money_frame", + "create_upgrade_gui", "update_upgrade_listing", "upgrade_research", "get_upgrades", "get_money", "update_connected_players", "update_round_number", + "set_research", "set_recipes", "check_deconstruction", "check_blueprint_placement", "loop_entities", "experiment_items", + "setup", "story_gui_click", "clear_surface", "add_run_trains_button", "puzzle_condition", "basic_signals", + "loop_trains", "Y_offset", "ghosts_1", "ghosts_2", "required_path", "through_wall_path", "count", "check_built_real_rail", + "current_ghosts_count", "other", "rails", "set_rails", "straight_section", "late_entities", "entities", "stop", + "get_spawn_coordinate", + + --tutorials + "intermission", "create_entities_on_tick", "on_player_created", "required_count", "non_player_entities", "clear_rails", + "chest", "damage", "furnace", "init_prototypes", "build_infi_table", "junk", "update_player_tags", "time_left", "team_production", + "create_task_frame", "create_visibilty_buttons", "update_leaderboard", "in_in_area" + } + } + + stds.factorio_base_data = { + globals = { + --Style + "make_cursor_box", "make_full_cursor_box", + "default_container_padding", "default_orange_color", "default_light_orange_color", "warning_red_color", + "achievement_green_color", "achievement_tan_color", "orangebuttongraphcialset", "bluebuttongraphcialset", + "bonus_gui_ordering", "trivial_smoke", "technology_slot_base_width", "technology_slot_base_height", "default_frame_font_vertical_compensation", + + --Belts + "transport_belt_connector_frame_sprites", "transport_belt_circuit_wire_connection_point", "transport_belt_circuit_wire_max_distance", + "transport_belt_circuit_connector_sprites", "ending_patch_prototype", "basic_belt_horizontal", "basic_belt_vertical", + "basic_belt_ending_top", "basic_belt_ending_bottom", "basic_belt_ending_side", "basic_belt_starting_top", "basic_belt_starting_bottom", + "basic_belt_starting_side", "fast_belt_horizontal", "fast_belt_vertical", "fast_belt_ending_top", "fast_belt_ending_bottom", + "fast_belt_ending_side", "fast_belt_starting_top", "fast_belt_starting_bottom", "fast_belt_starting_side", "express_belt_horizontal", + "express_belt_vertical", "express_belt_ending_top", "express_belt_ending_bottom", "express_belt_ending_side", "express_belt_starting_top", + "express_belt_starting_bottom", "express_belt_starting_side", + + --Circuit Connectors + "circuit_connector_definitions", "default_circuit_wire_max_distance", "inserter_circuit_wire_max_distance", + "universal_connector_template", "belt_connector_template", "belt_frame_connector_template", "inserter_connector_template", + + --Inserter Circuit Connectors + "inserter_circuit_wire_max_distance", "inserter_default_stack_control_input_signal", + + --Sounds/beams + "make_heavy_gunshot_sounds", "make_light_gunshot_sounds", "make_laser_sounds", + + --Gun/Laser + "gun_turret_extension", "gun_turret_extension_shadow", "gun_turret_extension_mask", "gun_turret_attack", + "laser_turret_extension", "laser_turret_extension_shadow", "laser_turret_extension_mask", + + --Pipes + "pipecoverspictures", "pipepictures", "assembler2pipepictures", "assembler3pipepictures", "make_heat_pipe_pictures", + + --Combinators + "generate_arithmetic_combinator", "generate_decider_combinator", "generate_constant_combinator", + + --Rail + "destroyed_rail_pictures", "rail_pictures", "rail_pictures_internal", "standard_train_wheels", "drive_over_tie", + "rolling_stock_back_light", "rolling_stock_stand_by_light", + + --Enemies + "make_enemy_autoplace", "make_enemy_spawner_autoplace", "make_enemy_worm_autoplace", + "make_spitter_attack_animation", "make_spitter_run_animation", "make_spitter_dying_animation", + "make_spitter_attack_parameters", "make_spitter_roars", "make_spitter_dying_sounds", + "make_spawner_idle_animation", "make_spawner_die_animation", + "make_biter_run_animation", "make_biter_attack_animation", "make_biter_die_animation", + "make_biter_roars", "make_biter_dying_sounds", "make_biter_calls", + "make_worm_roars", "make_worm_dying_sounds", "make_worm_folded_animation", "make_worm_preparing_animation", + "make_worm_prepared_animation", "make_worm_attack_animation", "make_worm_die_animation", + + --Other + "tile_variations_template", "make_water_autoplace_settings", + "make_unit_melee_ammo_type", "make_trivial_smoke", "make_4way_animation_from_spritesheet", "flying_robot_sounds", + "productivitymodulelimitation", "crash_trigger", "capsule_smoke", "make_beam", "playeranimations", + "make_blood_tint", "make_shadow_tint", + + --tiles + "water_transition_template", "make_water_transition_template", "water_autoplace_settings", "water_tile_type_names", + "patch_for_inner_corner_of_transition_between_transition", + } + } + + stds.factorio_base_story = { + globals = { + "story_init_helpers", "story_update_table", "story_init", "story_update", "story_on_tick", "story_add_update", + "story_remove_update", "story_jump_to", "story_elapsed", "story_elapsed_check", "story_show_message_dialog", + "set_goal", "player_set_goal", "on_player_joined", "flash_goal", "set_info", "player_set_info", "export_entities", + "list", "recreate_entities", "entity_to_connect", "limit_camera", "find_gui_recursive", "enable_entity_export", + "add_button", "on_gui_click", "set_continue_button_style", "add_message_log", "story_add_message_log", + "player_add_message_log", "message_log_frame", "message_log_scrollpane", "message_log_close_button", + "message_log_table", "toggle_message_log_button", "toggle_objective_button", "message_log_init", + "add_gui_recursive", "add_toggle_message_log_button", "add_toggle_objective_button", "mod_gui", + "flash_message_log_button", "flash_message_log_on_tick", "story_gui_click", "story_points_by_name", "story_branches", + "player", "surface", "deconstruct_on_tick", "recreate_entities_on_tick", "flying_congrats", "story_table" + } + } + + stds.factorio_circuit_connector_generated = { + globals = { + 'default_circuit_wire_max_distance', 'circuit_connector_definitions', 'universal_connector_template', + 'belt_connector_template', 'belt_frame_connector_template', 'inserter_connector_template', 'inserter_connector_template', + 'inserter_circuit_wire_max_distance', 'inserter_default_stack_control_input_signal', 'transport_belt_connector_frame_sprites', + 'transport_belt_circuit_wire_max_distance', + } + } +end + +do -- STDLIB STDs-- + stds.stdlib = { + read_globals = {}, + globals = { + 'STDLIB', + 'prequire', + 'rawtostring', + 'traceback', + 'data_traceback', + 'inspect', + 'serpent', + 'inline_if', + 'install', + 'log', + 'concat', + 'GAME', + 'AREA', + 'POSITION', + 'TILE', + 'SURFACE', + 'CHUNK', + 'COLOR', + 'ENTITY', + 'INVENTORY', + 'RESOURCE', + 'CONFIG', + 'LOGGER', + 'QUEUE', + 'EVENT', + 'GUI', + 'PLAYER', + 'FORCE', + 'MATH', + 'STRING', + 'TABLE' + } + } + + stds.stdlib_control = {} + + stds.stdlib_data = { + globals = { + 'DATA', + 'RECIPE', + 'ITEM', + 'FLUID', + 'ENTITY', + 'TECHNOLOGY', + 'CATEGORY' + } + } +end + +do -- Love STDs + stds.love_extra = { + read_globals = { + love = { + fields = { + arg = { + fields = { + 'parseGameArguments', + 'parseOption', + 'getLow', + 'optionIndices', + 'options' + } + } + } + }, + 'coroutine', + 'io', + 'socket', + 'dofile', + 'loadfile' + }, + globals = { + love = { + fields = { + 'handlers' + } + } + } + } +end + +do -- Factorio Defines STDs-- + stds.factorio_defines = { + read_globals = { + defines = { + fields = { + alert_type = { + fields = { + 'entity_destroyed', + 'entity_under_attack', + 'not_enough_construction_robots', + 'no_material_for_construction', + 'not_enough_repair_packs', + 'turret_fire', + 'custom', + 'no_storage', + 'train_out_of_fuel', + 'fluid_mixing' + } + }, + behavior_result = { + fields = { + 'in_progress', + 'fail', + 'success', + 'deleted' + } + }, + build_check_type = { + fields = { + 'script', + 'manual', + 'ghost_place', + 'ghost_revive' + } + }, + chain_signal_state = { + fields = { + 'all_open', + 'partially_open', + 'none_open' + } + }, + chunk_generated_status = { + fields = { + 'nothing', + 'custom_tiles', + 'basic_tiles', + 'corrected_tiles', + 'tiles', + 'entities' + } + }, + circuit_condition_index = { + fields = { + 'inserter_circuit', + 'inserter_logistic', + 'lamp', + 'arithmetic_combinator', + 'decider_combinator', + 'constant_combinator', + 'offshore_pump', + 'pump' + } + }, + circuit_connector_id = { + fields = { + 'accumulator', + 'constant_combinator', + 'container', + 'programmable_speaker', + 'rail_signal', + 'rail_chain_signal', + 'roboport', + 'storage_tank', + 'wall', + 'electric_pole', + 'inserter', + 'lamp', + 'combinator_input', + 'combinator_output', + 'offshore_pump', + 'pump' + } + }, + command = { + fields = { + 'attack', + 'go_to_location', + 'compound', + 'group', + 'attack_area', + 'wander', + 'flee', + 'stop', + 'build_base' + } + }, + compound_command = { + fields = { + 'logical_and', + 'logical_or', + 'return_last' + } + }, + control_behavior = { + fields = { + inserter = { + fields = { + circuit_mode_of_operation = { + fields = { + 'enable_disable', + 'set_filters', + 'read_hand_contents', + 'set_stack_size' + } + }, + hand_read_mode = { + fields = { + 'hold', + 'pulse' + } + } + } + }, + lamp = { + fields = { + circuit_mode_of_operation = { + fields = { + 'use_colors' + } + } + } + }, + logistic_container = { + fields = { + circuit_mode_of_operation = { + fields = { + 'send_contents', + 'set_requests' + } + } + } + }, + mining_drill = { + fields = { + resource_read_mode = { + fields = { + 'this_miner', + 'entire_patch' + } + } + } + }, + roboport = { + fields = { + circuit_mode_of_operation = { + fields = { + 'read_logistics', + 'read_robot_stats' + } + } + } + }, + train_stop = { + fields = { + circuit_mode_of_operation = { + fields = { + 'enable_disable', + 'send_to_train', + 'read_from_train', + 'read_stopped_train' + } + } + } + }, + transport_belt = { + fields = { + content_read_mode = { + fields = { + 'pulse', + 'hold' + } + } + } + }, + type = { + fields = { + 'container', + 'generic_on_off', + 'inserter', + 'lamp', + 'logistic_container', + 'roboport', + 'storage_tank', + 'train_stop', + 'decider_combinator', + 'arithmetic_combinator', + 'constant_combinator', + 'transport_belt', + 'accumulator', + 'rail_signal', + 'rail_chain_signal', + 'wall', + 'mining_drill', + 'programmable_speaker' + } + } + } + }, + controllers = { + fields = { + 'ghost', + 'character', + 'god', + 'editor', + 'cutscene', + 'spectator' + } + }, + deconstruction_item = { + fields = { + entity_filter_mode = { + fields = { + 'whitelist', + 'blacklist' + } + }, + tile_filter_mode = { + fields = { + 'whitelist', + 'blacklist' + } + }, + tile_selection_mode = { + fields = { + 'normal', + 'always', + 'never', + 'only' + } + } + } + }, + difficulty = { + fields = { + 'easy', + 'normal', + 'hard' + } + }, + difficulty_settings = { + fields = { + recipe_difficulty = { + fields = { + 'normal', + 'expensive' + } + }, + technology_difficulty = { + fields = { + 'normal', + 'expensive' + } + } + } + }, + direction = { + fields = { + 'north', + 'northeast', + 'east', + 'southeast', + 'south', + 'southwest', + 'west', + 'northwest' + } + }, + distraction = { + fields = { + 'by_enemy', + 'by_anything', + 'by_damage' + } + }, + entity_status = { + fields = { + 'working', + 'no_power', + 'no_fuel', + 'no_recipe', + 'no_input_fluid', + 'no_research_in_progress', + 'no_minable_resources', + 'low_input_fluid', + 'low_power', + 'disabled_by_control_behavior', + 'disabled_by_script', + 'fluid_ingredient_shortage', + 'fluid_production_overload', + 'item_ingredient_shortage', + 'item_production_overload', + 'marked_for_deconstruction', + 'missing_required_fluid', + 'missing_science_packs', + 'waiting_for_source_items', + 'waiting_for_space_in_destination', + 'waiting_to_launch_rocket' + } + }, + events = { + fields = { + 'on_tick', + 'on_gui_click', + 'on_gui_confirmed', + 'on_gui_text_changed', + 'on_gui_checked_state_changed', + 'on_entity_died', + 'on_post_entity_died', + 'on_entity_damaged', + 'on_picked_up_item', + 'on_built_entity', + 'on_sector_scanned', + 'on_player_mined_item', + 'on_put_item', + 'on_rocket_launched', + 'on_pre_player_mined_item', + 'on_chunk_generated', + 'on_player_crafted_item', + 'on_robot_built_entity', + 'on_robot_pre_mined', + 'on_robot_mined', + 'on_research_started', + 'on_research_finished', + 'on_player_rotated_entity', + 'on_player_set_quickbar_slot', + 'on_marked_for_deconstruction', + 'on_cancelled_deconstruction', + 'on_trigger_created_entity', + 'on_trigger_fired_artillery', + 'on_train_changed_state', + 'on_player_created', + 'on_resource_depleted', + 'on_player_driving_changed_state', + 'on_force_created', + 'on_forces_merging', + 'on_player_cursor_stack_changed', + 'on_pre_entity_settings_pasted', + 'on_entity_settings_pasted', + 'on_player_main_inventory_changed', + 'on_player_armor_inventory_changed', + 'on_player_ammo_inventory_changed', + 'on_player_gun_inventory_changed', + 'on_player_placed_equipment', + 'on_player_removed_equipment', + 'on_pre_player_died', + 'on_player_died', + 'on_player_respawned', + 'on_player_joined_game', + 'on_player_left_game', + 'on_player_built_tile', + 'on_player_mined_tile', + 'on_robot_built_tile', + 'on_robot_mined_tile', + 'on_player_selected_area', + 'on_player_alt_selected_area', + 'on_player_changed_surface', + 'on_selected_entity_changed', + 'on_market_item_purchased', + 'on_player_dropped_item', + 'on_biter_base_built', + 'on_player_changed_force', + 'on_entity_renamed', + 'on_gui_selection_state_changed', + 'on_runtime_mod_setting_changed', + 'on_difficulty_settings_changed', + 'on_surface_created', + 'on_surface_deleted', + 'on_pre_surface_deleted', + 'on_player_mined_entity', + 'on_robot_mined_entity', + 'on_train_created', + 'on_gui_elem_changed', + 'on_player_setup_blueprint', + 'on_player_deconstructed_area', + 'on_player_configured_blueprint', + 'on_console_chat', + 'on_console_command', + 'on_player_removed', + 'on_pre_player_removed', + 'on_player_used_capsule', + 'script_raised_built', + 'script_raised_destroy', + 'script_raised_revive', + 'on_player_promoted', + 'on_player_demoted', + 'on_combat_robot_expired', + 'on_player_changed_position', + 'on_mod_item_opened', + 'on_gui_opened', + 'on_gui_closed', + 'on_gui_value_changed', + 'on_player_muted', + 'on_player_unmuted', + 'on_player_cheat_mode_enabled', + 'on_player_cheat_mode_disabled', + 'on_character_corpse_expired', + 'on_pre_ghost_deconstructed', + 'on_player_pipette', + 'on_player_display_resolution_changed', + 'on_player_display_scale_changed', + 'on_pre_player_crafted_item', + 'on_player_cancelled_crafting', + 'on_chunk_charted', + 'on_technology_effects_reset', + 'on_land_mine_armed', + 'on_forces_merged', + 'on_player_trash_inventory_changed', + 'on_pre_player_left_game', + 'on_pre_surface_cleared', + 'on_surface_cleared', + 'on_chunk_deleted', + 'on_pre_chunk_deleted', + 'on_train_schedule_changed', + 'on_player_banned', + 'on_player_kicked', + 'on_player_unbanned', + 'on_rocket_launch_ordered', + 'on_script_path_request_finished', + 'on_ai_command_completed', + 'on_marked_for_upgrade', + 'on_cancelled_upgrade', + 'on_player_toggled_map_editor', + 'on_entity_cloned', + 'on_area_cloned', + 'on_brush_cloned', + 'on_game_created_from_scenario', + 'on_surface_imported', + 'on_surface_renamed', + 'on_player_toggled_alt_mode', + 'on_player_repaired_entity', + 'on_player_fast_transferred', + 'on_pre_robot_exploded_cliff', + 'on_robot_exploded_cliff', + 'on_entity_spawned', + 'on_cutscene_waypoint_reached', + 'on_unit_group_created', + 'on_unit_added_to_group', + 'on_unit_removed_from_group', + 'on_unit_group_finished_gathering', + 'on_build_base_arrived', + 'on_chart_tag_added', + 'on_chart_tag_modified', + 'on_chart_tag_removed', + 'on_lua_shortcut', + 'on_gui_location_changed', + 'on_gui_selected_tab_changed', + 'on_gui_switch_state_changed', + 'on_force_cease_fire_changed', + 'on_force_friends_changed', + 'on_string_translated', + 'on_script_trigger_effect' + } + }, + flow_precision_index = { + fields = { + 'one_second', + 'one_minute', + 'ten_minutes', + 'one_hour', + 'ten_hours', + 'fifty_hours', + 'two_hundred_fifty_hours', + 'one_thousand_hours' + } + }, + group_state = { + fields = { + 'gathering', + 'moving', + 'attacking_distraction', + 'attacking_target', + 'finished', + 'pathfinding', + 'wander_in_group' + } + }, + gui_type = { + fields = { + 'entity', + 'research', + 'controller', + 'production', + 'item', + 'bonus', + 'trains', + 'achievement', + 'blueprint_library', + 'equipment', + 'logistic', + 'other_player', + 'kills', + 'permissions', + 'tutorials', + 'custom', + 'server_management', + 'player_management', + 'tile' + } + }, + input_action = { + fields = { + 'activate_copy', + 'activate_cut', + 'activate_paste', + 'add_permission_group', + 'add_train_station', + 'admin_action', + 'alt_select_area', + 'alt_select_blueprint_entities', + 'alternative_copy', + 'begin_mining', + 'begin_mining_terrain', + 'build_item', + 'build_rail', + 'build_terrain', + 'cancel_craft', + 'cancel_deconstruct', + 'cancel_new_blueprint', + 'cancel_research', + 'cancel_upgrade', + 'change_active_item_group_for_crafting', + 'change_active_item_group_for_filters', + 'change_active_quick_bar', + 'change_arithmetic_combinator_parameters', + 'change_blueprint_book_record_label', + 'change_decider_combinator_parameters', + 'change_item_label', + 'change_multiplayer_config', + 'change_picking_state', + 'change_programmable_speaker_alert_parameters', + 'change_programmable_speaker_circuit_parameters', + 'change_programmable_speaker_parameters', + 'change_riding_state', + 'change_shooting_state', + 'change_single_blueprint_record_label', + 'change_train_stop_station', + 'change_train_wait_condition', + 'change_train_wait_condition_data', + 'clean_cursor_stack', + 'clear_selected_blueprint', + 'clear_selected_deconstruction_item', + 'clear_selected_upgrade_item', + 'connect_rolling_stock', + 'copy', + 'copy_entity_settings', + 'craft', + 'create_blueprint_like', + 'cursor_split', + 'cursor_transfer', + 'custom_input', + 'cycle_blueprint_book_backwards', + 'cycle_blueprint_book_forwards', + 'deconstruct', + 'delete_blueprint_library', + 'delete_blueprint_record', + 'delete_custom_tag', + 'delete_permission_group', + 'destroy_opened_item', + 'disconnect_rolling_stock', + 'drag_train_schedule', + 'drag_train_wait_condition', + 'drop_blueprint_record', + 'drop_item', + 'drop_to_blueprint_book', + 'edit_custom_tag', + 'edit_permission_group', + 'export_blueprint', + 'fast_entity_split', + 'fast_entity_transfer', + 'go_to_train_station', + 'grab_blueprint_record', + 'gui_checked_state_changed', + 'gui_click', + 'gui_confirmed', + 'gui_elem_changed', + 'gui_location_changed', + 'gui_selected_tab_changed', + 'gui_selection_state_changed', + 'gui_switch_state_changed', + 'gui_text_changed', + 'gui_value_changed', + 'import_blueprint', + 'import_blueprint_string', + 'import_permissions_string', + 'inventory_split', + 'inventory_transfer', + 'launch_rocket', + 'lua_shortcut', + 'map_editor_action', + 'market_offer', + 'mod_settings_changed', + 'open_achievements_gui', + 'open_blueprint_library_gui', + 'open_blueprint_record', + 'open_bonus_gui', + 'open_character_gui', + 'open_equipment', + 'open_gui', + 'open_item', + 'open_kills_gui', + 'open_logistic_gui', + 'open_mod_item', + 'open_production_gui', + 'open_technology_gui', + 'open_train_gui', + 'open_train_station_gui', + 'open_trains_gui', + 'open_tutorials_gui', + 'paste_entity_settings', + 'place_equipment', + 'quick_bar_pick_slot', + 'quick_bar_set_selected_page', + 'quick_bar_set_slot', + 'remove_cables', + 'remove_train_station', + 'reset_assembling_machine', + 'rotate_entity', + 'select_area', + 'select_blueprint_entities', + 'select_entity_slot', + 'select_item', + 'select_mapper_slot', + 'select_next_valid_gun', + 'select_tile_slot', + 'set_auto_launch_rocket', + 'set_autosort_inventory', + 'set_behavior_mode', + 'set_car_weapons_control', + 'set_circuit_condition', + 'set_circuit_mode_of_operation', + 'set_deconstruction_item_tile_selection_mode', + 'set_deconstruction_item_trees_and_rocks_only', + 'set_entity_color', + 'set_entity_energy_property', + 'set_filter', + 'set_heat_interface_mode', + 'set_heat_interface_temperature', + 'set_infinity_container_filter_item', + 'set_infinity_container_remove_unfiltered_items', + 'set_infinity_pipe_filter', + 'set_inserter_max_stack_size', + 'set_inventory_bar', + 'set_logistic_filter_item', + 'set_logistic_filter_signal', + 'set_logistic_trash_filter_item', + 'set_request_from_buffers', + 'set_research_finished_stops_game', + 'set_signal', + 'set_single_blueprint_record_icon', + 'set_splitter_priority', + 'set_train_stopped', + 'setup_assembling_machine', + 'setup_blueprint', + 'setup_single_blueprint_record', + 'smart_pipette', + 'stack_split', + 'stack_transfer', + 'start_repair', + 'start_research', + 'start_walking', + 'stop_building_by_moving', + 'switch_connect_to_logistic_network', + 'switch_constant_combinator_state', + 'switch_inserter_filter_mode_state', + 'switch_power_switch_state', + 'switch_to_rename_stop_gui', + 'take_equipment', + 'toggle_deconstruction_item_entity_filter_mode', + 'toggle_deconstruction_item_tile_filter_mode', + 'toggle_driving', + 'toggle_enable_vehicle_logistics_while_moving', + 'toggle_equipment_movement_bonus', + 'toggle_map_editor', + 'toggle_personal_roboport', + 'toggle_show_entity_info', + 'translate_string', + 'undo', + 'upgrade', + 'upgrade_opened_blueprint', + 'use_artillery_remote', + 'use_item', + 'wire_dragging', + 'write_to_console' + } + }, + inventory = { + fields = { + 'fuel', + 'burnt_result', + 'chest', + 'furnace_source', + 'furnace_result', + 'furnace_modules', + 'character_main', + 'character_guns', + 'character_ammo', + 'character_armor', + 'character_vehicle', + 'character_trash', + 'god_main', + 'editor_main', + 'editor_guns', + 'editor_ammo', + 'editor_armor', + 'roboport_robot', + 'roboport_material', + 'robot_cargo', + 'robot_repair', + 'assembling_machine_input', + 'assembling_machine_output', + 'assembling_machine_modules', + 'lab_input', + 'lab_modules', + 'mining_drill_modules', + 'item_main', + 'rocket_silo_rocket', + 'rocket_silo_result', + 'rocket', + 'car_trunk', + 'car_ammo', + 'cargo_wagon', + 'turret_ammo', + 'beacon_modules', + 'character_corpse', + 'artillery_turret_ammo', + 'artillery_wagon_ammo' + } + }, + logistic_member_index = { + fields = { + 'logistic_container', + 'vehicle_storage', + 'character_requester', + 'character_storage', + 'character_provider', + 'generic_on_off_behavior' + } + }, + logistic_mode = { + fields = { + 'active_provider', + 'storage', + 'requester', + 'passive_provider', + 'buffer' + } + }, + mouse_button_type = { + fields = { + 'left', + 'right', + 'middle' + } + }, + rail_connection_direction = { + fields = { + 'left', + 'straight', + 'right' + } + }, + rail_direction = { + fields = { + 'front', + 'back' + } + }, + render_mode = { + fields = { + 'game', + 'chart', + 'chart_zoomed_in' + } + }, + rich_text_setting = { + fields = { + 'enabled', + 'disabled', + 'highlight' + } + }, + riding = { + fields = { + acceleration = { + fields = { + 'nothing', + 'accelerating', + 'braking', + 'reversing' + } + }, + direction = { + fields = { + 'left', + 'straight', + 'right' + } + } + } + }, + shooting = { + fields = { + 'not_shooting', + 'shooting_enemies', + 'shooting_selected' + } + }, + signal_state = { + fields = { + 'open', + 'closed', + 'reserved', + 'reserved_by_circuit_network' + } + }, + train_state = { + fields = { + 'on_the_path', + 'path_lost', + 'no_schedule', + 'no_path', + 'arrive_signal', + 'wait_signal', + 'arrive_station', + 'wait_station', + 'manual_control_stop', + 'manual_control' + } + }, + transport_line = { + fields = { + 'left_line', + 'right_line', + 'left_underground_line', + 'right_underground_line', + 'secondary_left_line', + 'secondary_right_line', + 'left_split_line', + 'right_split_line', + 'secondary_left_split_line', + 'secondary_right_split_line' + } + }, + wire_connection_id = { + fields = { + 'electric_pole', + 'power_switch_left', + 'power_switch_right' + } + }, + wire_type = { + fields = { + 'red', + 'green', + 'copper' + } + }, + -- Defines additional modules + color = { + other_fields = true + }, + anticolor = { + other_fields = true + }, + lightcolor = { + other_fields = true + }, + time = { + fields = { + 'second', + 'minute', + 'hour', + 'day', + 'week', + 'month', + 'year' + } + } + } + } + } + } +end + +--[[ Options + "ignore", "std", "globals", "unused_args", "self", "compat", "global", "unused", "redefined", + "unused_secondaries", "allow_defined", "allow_defined_top", "module", + "read_globals", "new_globals", "new_read_globals", "enable", "only", "not_globals", + "max_line_length", "max_code_line_length", "max_string_line_length", "max_comment_line_length", + "max_cyclomatic_complexity" +--]] + +--[[ Warnings list + -- 011 A syntax error. + -- 021 An invalid inline option. + -- 022 An unpaired inline push directive. + -- 023 An unpaired inline pop directive. + -- 111 Setting an undefined global variable. + -- 112 Mutating an undefined global variable. + -- 113 Accessing an undefined global variable. + -- 121 Setting a read-only global variable. + -- 122 Setting a read-only field of a global variable. + -- 131 Unused implicitly defined global variable. + -- 142 Setting an undefined field of a global variable. + -- 143 Accessing an undefined field of a global variable. + -- 211 Unused local variable. + -- 212 Unused argument. + -- 213 Unused loop variable. + -- 221 Local variable is accessed but never set. + -- 231 Local variable is set but never accessed. + -- 232 An argument is set but never accessed. + -- 233 Loop variable is set but never accessed. + -- 241 Local variable is mutated but never accessed. + -- 311 Value assigned to a local variable is unused. + -- 312 Value of an argument is unused. + -- 313 Value of a loop variable is unused. + -- 314 Value of a field in a table literal is unused. + -- 321 Accessing uninitialized local variable. + -- 331 Value assigned to a local variable is mutated but never accessed. + -- 341 Mutating uninitialized local variable. + -- 411 Redefining a local variable. + -- 412 Redefining an argument. + -- 413 Redefining a loop variable. + -- 421 Shadowing a local variable. + -- 422 Shadowing an argument. + -- 423 Shadowing a loop variable. + -- 431 Shadowing an upvalue. + -- 432 Shadowing an upvalue argument. + -- 433 Shadowing an upvalue loop variable. + -- 511 Unreachable code. + -- 512 Loop can be executed at most once. + -- 521 Unused label. + -- 531 Left-hand side of an assignment is too short. + -- 532 Left-hand side of an assignment is too long. + -- 541 An empty do end block. + -- 542 An empty if branch. + -- 551 An empty statement. + -- 611 A line consists of nothing but whitespace. + -- 612 A line contains trailing whitespace. + -- 613 Trailing whitespace in a string. + -- 614 Trailing whitespace in a comment. + -- 621 Inconsistent indentation (SPACE followed by TAB). + -- 631 Line is too long. +--]] diff --git a/packages/create/templates/plugin-ts/controller.ts b/packages/create/templates/plugin-ts/controller.ts index e24dfca5..b833cc50 100644 --- a/packages/create/templates/plugin-ts/controller.ts +++ b/packages/create/templates/plugin-ts/controller.ts @@ -1,11 +1,16 @@ import * as lib from "@clusterio/lib"; import { BaseControllerPlugin, InstanceInfo } from "@clusterio/controller"; -import { PluginExampleEvent, PluginExampleRequest } from "./messages"; +import { PluginExampleEvent, PluginExampleRequest,/*/ [subscribable] /*/ ExampleSubscribableUpdate, ExampleSubscribableValue/*/ [] /*/ } from "./messages"; -export class ControllerPlugin extends BaseControllerPlugin { +export class ControllerPlugin extends BaseControllerPlugin {// [subscribable] // + exampleDatabase!: Map; + storageDirty = false; + // [] // async init() { this.controller.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this)); - this.controller.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this)); + this.controller.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this));// [subscribable] // + this.controller.subscriptions.handle(ExampleSubscribableUpdate, this.handleExampleSubscription.bind(this)); + this.exampleDatabase = new Map();// [] // } async onControllerConfigFieldChanged(field: string, curr: unknown, prev: unknown) { @@ -38,5 +43,12 @@ export class ControllerPlugin extends BaseControllerPlugin { myResponseString: request.myString, myResponseNumbers: request.myNumberArray, }; - } + }// [subscribable] // + + async handleExampleSubscription(request: lib.SubscriptionRequest) { + const values = [...this.exampleDatabase.values()].filter( + value => value.updatedAtMs > request.lastRequestTimeMs, + ); + return values.length ? new ExampleSubscribableUpdate(values) : null; + }// [] // } diff --git a/packages/create/templates/plugin-ts/index.ts b/packages/create/templates/plugin-ts/index.ts index 92a7548f..6db9367c 100644 --- a/packages/create/templates/plugin-ts/index.ts +++ b/packages/create/templates/plugin-ts/index.ts @@ -1,6 +1,5 @@ import * as lib from "@clusterio/lib"; - -import { PluginExampleEvent, PluginExampleRequest } from "./messages"; +import * as Messages from "./messages"; lib.definePermission({ name: "// plugin_name //.example.permission.event", @@ -12,7 +11,13 @@ lib.definePermission({ name: "// plugin_name //.example.permission.request", title: "Example permission request", description: "My plugin's example permission that I forgot to remove", -});// [web] // +});// [subscribable] // + +lib.definePermission({ + name: "// plugin_name //.example.permission.subscribe", + title: "Example permission subscribe", + description: "My plugin's example permission that I forgot to remove", +});// [] //// [web] // lib.definePermission({ name: "// plugin_name //.page.view", @@ -40,7 +45,7 @@ export const plugin: lib.PluginDeclaration = { title: "// plugin_name //", description: "I didn't update my description",// [entry_points] // // entry_points // - // [] //// [controller] // +// [] //// [controller] // controllerConfigFields: { "// plugin_name //.myControllerField": { title: "My Controller Field", @@ -75,7 +80,8 @@ export const plugin: lib.PluginDeclaration = { },// [] // messages: [ - PluginExampleEvent, - PluginExampleRequest, + Messages.PluginExampleEvent, + Messages.PluginExampleRequest,// [subscribable] // + Messages.ExampleSubscribableUpdate,// [] // ], }; diff --git a/packages/create/templates/plugin-ts/messages.ts b/packages/create/templates/plugin-ts/messages.ts index 2dec80e6..5d148bdc 100644 --- a/packages/create/templates/plugin-ts/messages.ts +++ b/packages/create/templates/plugin-ts/messages.ts @@ -52,4 +52,44 @@ export class PluginExampleRequest { "myResponseString": Type.String(), "myResponseNumbers": Type.Array(Type.Number()), })); +}// [subscribable] // + +export class ExampleSubscribableValue { + constructor( + public id: string, + public updatedAtMs: number, + public isDeleted: boolean, + ) { + } + + static jsonSchema = Type.Object({ + id: Type.String(), + updatedAtMs: Type.Number(), + isDeleted: Type.Boolean(), + }); + + static fromJSON(json: Static) { + return new this(json.id, json.updatedAtMs, json.isDeleted); + } } + +export class ExampleSubscribableUpdate { + declare ["constructor"]: typeof 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"; + + constructor( + public updates: ExampleSubscribableValue[], + ) { } + + static jsonSchema = Type.Object({ + "updates": Type.Array(ExampleSubscribableValue.jsonSchema), + }); + + static fromJSON(json: Static) { + return new this(json.updates.map(update => ExampleSubscribableValue.fromJSON(update))); + } +}// [] //// [] // diff --git a/packages/create/templates/plugin-ts/module/control.lua b/packages/create/templates/plugin-ts/module/control.lua index 5e6a2e2a..c472aadd 100644 --- a/packages/create/templates/plugin-ts/module/control.lua +++ b/packages/create/templates/plugin-ts/module/control.lua @@ -1,18 +1,40 @@ 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", { @@ -20,8 +42,11 @@ MyModule.events[defines.events.on_player_crafted_item] = function(event) })// [] // 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 diff --git a/packages/create/templates/plugin-ts/module/module_exports.lua b/packages/create/templates/plugin-ts/module/module_exports.lua index 4399db98..ecce6291 100644 --- a/packages/create/templates/plugin-ts/module/module_exports.lua +++ b/packages/create/templates/plugin-ts/module/module_exports.lua @@ -1,7 +1,8 @@ local MyModule = require("./control")// [instance] // --- Can contain anything you plan to use with send_rcon from functions, modules, or data +--- 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 +--- Can contain anything you want to allow other plugins to have access to, this example exposes the whole module return MyModule diff --git a/packages/create/templates/plugin-ts/web/plugin.tsx b/packages/create/templates/plugin-ts/web/plugin.tsx index 1d623443..d2e1d336 100644 --- a/packages/create/templates/plugin-ts/web/plugin.tsx +++ b/packages/create/templates/plugin-ts/web/plugin.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from "react"; +import React, { useContext, useEffect, useState,/*/ [subscribable] /*/ useCallback, useSyncExternalStore,/*/ [] /*/ } from "react"; // import { } from "antd"; import { @@ -6,17 +6,22 @@ import { } from "@clusterio/web_ui"; import * as lib from "@clusterio/lib"; -import { PluginExampleEvent, PluginExampleRequest } from "../messages"; +import { PluginExampleEvent, PluginExampleRequest,/*/ [subscribable] /*/ ExampleSubscribableUpdate,/*/ [] /*/ } from "../messages"; function MyTemplatePage() { - let control = useContext(ControlContext); + const control = useContext(ControlContext);// [subscribable] // + const plugin = control.plugins.get("// plugin_name //") as WebPlugin; + const [subscribableData, synced] = plugin.useSubscribableData();// [] // return -

// plugin_name //

+

// plugin_name //

// [subscribable] // + Synced: {String(synced)} Data: {JSON.stringify([...subscribableData.values()])}// [] //
; } -export class WebPlugin extends BaseWebPlugin { +export class WebPlugin extends BaseWebPlugin {// [subscribable] // + subscribableData = new lib.EventSubscriber(ExampleSubscribableUpdate, this.control); + // [] // async init() { this.pages = [ { @@ -29,7 +34,13 @@ export class WebPlugin extends BaseWebPlugin { this.control.handle(PluginExampleEvent, this.handlePluginExampleEvent.bind(this)); this.control.handle(PluginExampleRequest, this.handlePluginExampleRequest.bind(this)); - } + }// [subscribable] // + + useSubscribableData() { + const control = useContext(ControlContext); + const subscribe = useCallback((callback: () => void) => this.subscribableData.subscribe(callback), [control]); + return useSyncExternalStore(subscribe, () => this.subscribableData.getSnapshot()); + }// [] // async handlePluginExampleEvent(event: PluginExampleEvent) { this.logger.info(JSON.stringify(event)); From 46309ce5b8b6cf36c8043af25ea5ecd6b1da3964 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Sun, 18 Feb 2024 17:58:16 +0000 Subject: [PATCH 14/29] Lint Fixes --- .eslintrc.js | 3 +++ packages/create/templates/plugin-js/web/plugin.jsx | 4 ++-- packages/create/templates/plugin-ts/web/plugin.tsx | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index fd0847b7..eefb4bc1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -101,7 +101,10 @@ module.exports = { "rules": { "node/no-missing-require": "off", "node/no-missing-import": "off", + "spaced-comment": "off", "comma-spacing": "off", + "max-len": "off", + "indent": "off", }, }, ], diff --git a/packages/create/templates/plugin-js/web/plugin.jsx b/packages/create/templates/plugin-js/web/plugin.jsx index ea582e32..5c6784ba 100644 --- a/packages/create/templates/plugin-js/web/plugin.jsx +++ b/packages/create/templates/plugin-js/web/plugin.jsx @@ -1,4 +1,4 @@ -const { useContext, useEffect, useState,/*/ [subscribable] /*/ useCallback, useSyncExternalStore,/*/ [] /*/ } = require("react"); +const { useContext, useEffect, useState/*/ [subscribable] /*/, useCallback, useSyncExternalStore/*/ [] /*/ } = require("react"); // const { } = require("antd"); const { @@ -6,7 +6,7 @@ const { } = require("@clusterio/web_ui"); const lib = require("@clusterio/lib"); -const { PluginExampleEvent, PluginExampleRequest,/*/ [subscribable] /*/ ExampleSubscribableUpdate,/*/ [] /*/ } = require("../messages"); +const { PluginExampleEvent, PluginExampleRequest/*/ [subscribable] /*/, ExampleSubscribableUpdate/*/ [] /*/ } = require("../messages"); function MyTemplatePage() { let control = useContext(ControlContext);// [subscribable] // diff --git a/packages/create/templates/plugin-ts/web/plugin.tsx b/packages/create/templates/plugin-ts/web/plugin.tsx index d2e1d336..f1a8c229 100644 --- a/packages/create/templates/plugin-ts/web/plugin.tsx +++ b/packages/create/templates/plugin-ts/web/plugin.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState,/*/ [subscribable] /*/ useCallback, useSyncExternalStore,/*/ [] /*/ } from "react"; +import React, { useContext, useEffect, useState/*/ [subscribable] /*/, useCallback, useSyncExternalStore/*/ [] /*/ } from "react"; // import { } from "antd"; import { @@ -6,7 +6,7 @@ import { } from "@clusterio/web_ui"; import * as lib from "@clusterio/lib"; -import { PluginExampleEvent, PluginExampleRequest,/*/ [subscribable] /*/ ExampleSubscribableUpdate,/*/ [] /*/ } from "../messages"; +import { PluginExampleEvent, PluginExampleRequest/*/ [subscribable] /*/, ExampleSubscribableUpdate/*/ [] /*/ } from "../messages"; function MyTemplatePage() { const control = useContext(ControlContext);// [subscribable] // From a68bb9c0a8a07b464b65af8adb9c59bf0ef86360 Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 25 Feb 2024 16:50:14 -0600 Subject: [PATCH 15/29] Increase libLink event emitter leak warning limit (#600) --- packages/lib/src/link/link.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/lib/src/link/link.ts b/packages/lib/src/link/link.ts index 0f8f2d32..1d3d3381 100644 --- a/packages/lib/src/link/link.ts +++ b/packages/lib/src/link/link.ts @@ -107,6 +107,9 @@ export class Link { ) { this.handle(libData.PingRequest, async () => {}); + // Prevent warnings about possible memory leak due to large number of event listeners + connector.setMaxListeners(20); + // Process messages received by the connector connector.on("message", payload => { try { From e5d82dcfdedd6f9e708eeb3e8e8faa36733d866f Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Mon, 26 Feb 2024 22:34:41 -0600 Subject: [PATCH 16/29] Don't fail CI due to project level codecov regressions --- codecov.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..8f46d2de --- /dev/null +++ b/codecov.yml @@ -0,0 +1,5 @@ +coverage: + status: + project: + default: + if_ci_failed: success From b7fec4c700a5e8d45d213b5fb57c7eb13d6fe83e Mon Sep 17 00:00:00 2001 From: Danielv123 Date: Tue, 27 Feb 2024 12:09:15 -0600 Subject: [PATCH 17/29] Set codecov to informational --- codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/codecov.yml b/codecov.yml index 8f46d2de..a8501f0d 100644 --- a/codecov.yml +++ b/codecov.yml @@ -3,3 +3,4 @@ coverage: project: default: if_ci_failed: success + informational: true From 62abfa903b62cf629149472378aa31cdddf05279 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Tue, 27 Feb 2024 23:07:38 +0000 Subject: [PATCH 18/29] Change to new preprocessor style --- packages/create/create.js | 141 +- packages/create/template.js | 165 ++ .../{plugin-js => common}/.gitignore | 0 .../{plugin-js => common}/.luacheckrc | 0 .../{plugin-js => common}/.npmignore | 1 + .../{plugin-ts => common}/module/control.lua | 14 +- .../templates/common/module/module.json | 14 + .../module/module_exports.lua | 8 +- packages/create/templates/common/package.json | 53 + .../{plugin-js => common}/webpack.config.js | 16 +- .../create/templates/plugin-js/controller.js | 32 +- packages/create/templates/plugin-js/ctl.js | 2 +- packages/create/templates/plugin-js/index.js | 71 +- .../create/templates/plugin-js/instance.js | 18 +- .../create/templates/plugin-js/messages.js | 24 +- .../templates/plugin-js/module/control.lua | 52 - .../templates/plugin-js/module/module.json | 12 - .../plugin-js/module/module_exports.lua | 8 - .../create/templates/plugin-js/package.json | 29 - .../create/templates/plugin-js/web/plugin.jsx | 60 +- .../create/templates/plugin-ts/.gitignore | 3 - .../create/templates/plugin-ts/.luacheckrc | 1705 ----------------- .../create/templates/plugin-ts/.npmignore | 2 - .../create/templates/plugin-ts/controller.ts | 32 +- packages/create/templates/plugin-ts/ctl.ts | 2 +- packages/create/templates/plugin-ts/index.ts | 95 +- .../create/templates/plugin-ts/instance.ts | 24 +- .../create/templates/plugin-ts/messages.ts | 18 +- .../templates/plugin-ts/module/module.json | 12 - .../create/templates/plugin-ts/package.json | 29 - .../templates/plugin-ts/tsconfig.browser.json | 11 + .../create/templates/plugin-ts/tsconfig.json | 14 +- .../templates/plugin-ts/tsconfig.node.json | 11 + .../templates/plugin-ts/tsconfig.web.json | 11 - .../create/templates/plugin-ts/web/plugin.tsx | 54 +- .../templates/plugin-ts/webpack.config.js | 28 - packages/create/templates/readmd.md | 114 ++ 37 files changed, 692 insertions(+), 2193 deletions(-) create mode 100644 packages/create/template.js rename packages/create/templates/{plugin-js => common}/.gitignore (100%) rename packages/create/templates/{plugin-js => common}/.luacheckrc (100%) rename packages/create/templates/{plugin-js => common}/.npmignore (71%) rename packages/create/templates/{plugin-ts => common}/module/control.lua (86%) create mode 100644 packages/create/templates/common/module/module.json rename packages/create/templates/{plugin-ts => common}/module/module_exports.lua (62%) create mode 100644 packages/create/templates/common/package.json rename packages/create/templates/{plugin-js => common}/webpack.config.js (63%) delete mode 100644 packages/create/templates/plugin-js/module/control.lua delete mode 100644 packages/create/templates/plugin-js/module/module.json delete mode 100644 packages/create/templates/plugin-js/module/module_exports.lua delete mode 100644 packages/create/templates/plugin-js/package.json delete mode 100644 packages/create/templates/plugin-ts/.gitignore delete mode 100644 packages/create/templates/plugin-ts/.luacheckrc delete mode 100644 packages/create/templates/plugin-ts/.npmignore delete mode 100644 packages/create/templates/plugin-ts/module/module.json delete mode 100644 packages/create/templates/plugin-ts/package.json create mode 100644 packages/create/templates/plugin-ts/tsconfig.browser.json create mode 100644 packages/create/templates/plugin-ts/tsconfig.node.json delete mode 100644 packages/create/templates/plugin-ts/tsconfig.web.json delete mode 100644 packages/create/templates/plugin-ts/webpack.config.js create mode 100644 packages/create/templates/readmd.md diff --git a/packages/create/create.js b/packages/create/create.js index ebf161e1..66e4084a 100755 --- a/packages/create/create.js +++ b/packages/create/create.js @@ -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; } diff --git a/packages/create/template.js b/packages/create/template.js new file mode 100644 index 00000000..8a998169 --- /dev/null +++ b/packages/create/template.js @@ -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 [// comment]\n \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, +}; diff --git a/packages/create/templates/plugin-js/.gitignore b/packages/create/templates/common/.gitignore similarity index 100% rename from packages/create/templates/plugin-js/.gitignore rename to packages/create/templates/common/.gitignore diff --git a/packages/create/templates/plugin-js/.luacheckrc b/packages/create/templates/common/.luacheckrc similarity index 100% rename from packages/create/templates/plugin-js/.luacheckrc rename to packages/create/templates/common/.luacheckrc diff --git a/packages/create/templates/plugin-js/.npmignore b/packages/create/templates/common/.npmignore similarity index 71% rename from packages/create/templates/plugin-js/.npmignore rename to packages/create/templates/common/.npmignore index ebd99907..fbac3cb2 100644 --- a/packages/create/templates/plugin-js/.npmignore +++ b/packages/create/templates/common/.npmignore @@ -1,2 +1,3 @@ webpack.config.js dist/web/build +dist/browser diff --git a/packages/create/templates/plugin-ts/module/control.lua b/packages/create/templates/common/module/control.lua similarity index 86% rename from packages/create/templates/plugin-ts/module/control.lua rename to packages/create/templates/common/module/control.lua index c472aadd..2747b83b 100644 --- a/packages/create/templates/plugin-ts/module/control.lua +++ b/packages/create/templates/common/module/control.lua @@ -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 diff --git a/packages/create/templates/common/module/module.json b/packages/create/templates/common/module/module.json new file mode 100644 index 00000000..60a1ddbe --- /dev/null +++ b/packages/create/templates/common/module/module.json @@ -0,0 +1,14 @@ +{ + "name": "__plugin_name__", + "load": [ + "control.lua" + ], + "require": [ +//%if instance + "module_exports.lua" +//%endif + ], + "dependencies": { + "clusterio": "*" + } +} diff --git a/packages/create/templates/plugin-ts/module/module_exports.lua b/packages/create/templates/common/module/module_exports.lua similarity index 62% rename from packages/create/templates/plugin-ts/module/module_exports.lua rename to packages/create/templates/common/module/module_exports.lua index ecce6291..6235edab 100644 --- a/packages/create/templates/plugin-ts/module/module_exports.lua +++ b/packages/create/templates/common/module/module_exports.lua @@ -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 diff --git a/packages/create/templates/common/package.json b/packages/create/templates/common/package.json new file mode 100644 index 00000000..fc2ee57e --- /dev/null +++ b/packages/create/templates/common/package.json @@ -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" + ] +} diff --git a/packages/create/templates/plugin-js/webpack.config.js b/packages/create/templates/common/webpack.config.js similarity index 63% rename from packages/create/templates/plugin-js/webpack.config.js rename to packages/create/templates/common/webpack.config.js index 3d43cfbd..bda4c382 100644 --- a/packages/create/templates/plugin-js/webpack.config.js +++ b/packages/create/templates/common/webpack.config.js @@ -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 }, }), ], diff --git a/packages/create/templates/plugin-js/controller.js b/packages/create/templates/plugin-js/controller.js index 7d566479..266e06fa 100644 --- a/packages/create/templates/plugin-js/controller.js +++ b/packages/create/templates/plugin-js/controller.js @@ -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 = { diff --git a/packages/create/templates/plugin-js/ctl.js b/packages/create/templates/plugin-js/ctl.js index 5140697c..ed1d1a32 100644 --- a/packages/create/templates/plugin-js/ctl.js +++ b/packages/create/templates/plugin-js/ctl.js @@ -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({ diff --git a/packages/create/templates/plugin-js/index.js b/packages/create/templates/plugin-js/index.js index 32ca9d87..035988ce 100644 --- a/packages/create/templates/plugin-js/index.js +++ b/packages/create/templates/plugin-js/index.js @@ -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, }; diff --git a/packages/create/templates/plugin-js/instance.js b/packages/create/templates/plugin-js/instance.js index 99a6f19a..a62f5520 100644 --- a/packages/create/templates/plugin-js/instance.js +++ b/packages/create/templates/plugin-js/instance.js @@ -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 = { diff --git a/packages/create/templates/plugin-js/messages.js b/packages/create/templates/plugin-js/messages.js index b6659ce4..ac982267 100644 --- a/packages/create/templates/plugin-js/messages.js +++ b/packages/create/templates/plugin-js/messages.js @@ -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 }; diff --git a/packages/create/templates/plugin-js/module/control.lua b/packages/create/templates/plugin-js/module/control.lua deleted file mode 100644 index c472aadd..00000000 --- a/packages/create/templates/plugin-js/module/control.lua +++ /dev/null @@ -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 diff --git a/packages/create/templates/plugin-js/module/module.json b/packages/create/templates/plugin-js/module/module.json deleted file mode 100644 index 33915ac9..00000000 --- a/packages/create/templates/plugin-js/module/module.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "// plugin_name //", - "load": [ - "control.lua" - ], - "require": [ - // [instance] //"module_exports.lua"// [] // - ], - "dependencies": [ - - ] -} diff --git a/packages/create/templates/plugin-js/module/module_exports.lua b/packages/create/templates/plugin-js/module/module_exports.lua deleted file mode 100644 index ecce6291..00000000 --- a/packages/create/templates/plugin-js/module/module_exports.lua +++ /dev/null @@ -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 diff --git a/packages/create/templates/plugin-js/package.json b/packages/create/templates/plugin-js/package.json deleted file mode 100644 index 627f1431..00000000 --- a/packages/create/templates/plugin-js/package.json +++ /dev/null @@ -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" - ] -} diff --git a/packages/create/templates/plugin-js/web/plugin.jsx b/packages/create/templates/plugin-js/web/plugin.jsx index 5c6784ba..db095219 100644 --- a/packages/create/templates/plugin-js/web/plugin.jsx +++ b/packages/create/templates/plugin-js/web/plugin.jsx @@ -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 -

// plugin_name //

// [subscribable] // - Synced: {String(synced)} Data: {JSON.stringify([...subscribableData.values()])}// [] // + return +

__plugin_name__

+//%if controller & web // Subscribing requires web content and the controller + Synced: {String(synced)} Data: {JSON.stringify([...subscribableData.values()])} +//%endif
; } -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: , }, ]; 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)); diff --git a/packages/create/templates/plugin-ts/.gitignore b/packages/create/templates/plugin-ts/.gitignore deleted file mode 100644 index 1a993212..00000000 --- a/packages/create/templates/plugin-ts/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -dist/ -node_modules/ -package-lock.json diff --git a/packages/create/templates/plugin-ts/.luacheckrc b/packages/create/templates/plugin-ts/.luacheckrc deleted file mode 100644 index cacf598d..00000000 --- a/packages/create/templates/plugin-ts/.luacheckrc +++ /dev/null @@ -1,1705 +0,0 @@ ---[[---------------------------------------------------------------------------- - LICENSE - .luacheckrc for Factorio Version 0.18.8, luacheck version 0.23.0 - This file is free and unencumbered software released into the public domain. - - Anyone is free to copy, modify, publish, use, compile, sell, or - distribute this file, either in source code form or as a compiled - binary, for any purpose, commercial or non-commercial, and by any - means. - - In jurisdictions that recognize copyright laws, the author or authors - of this file dedicate any and all copyright interest in the - software to the public domain. We make this dedication for the benefit - of the public at large and to the detriment of our heirs and - successors. We intend this dedication to be an overt act of - relinquishment in perpetuity of all present and future rights to this - software under copyright law. - - THE FILE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR - OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. - - For more information, please refer to -------------------------------------------------------------------------------]] -local LINE_LENGTH = false - -local IGNORE = {'21./%w+_$', '21./^_%w+$', '213/[ijk]', '213/index', '213/key'} - --- These globals are not available to the factorio API -local NOT_GLOBALS = {'coroutine', 'io', 'socket', 'dofile', 'loadfile'} - -local STD_CONTROL = 'lua52+factorio+factorio_control+stdlib+factorio_defines' -local STD_DATA = 'lua52+factorio+factorio_data+stdlib+stdlib_data+factorio_defines' - --- For Base and Core Mods -local STD_BASE_DATA = 'lua52+factorio+factorio_data+factorio_defines+factorio_base_data' -local STD_BASE_CONTROL = 'lua52+factorio+factorio_control+factorio_defines+factorio_base_control' - -do -- Assume Factorio Control Stage as Default - std = STD_CONTROL - --cache = true - not_globals = NOT_GLOBALS - ignore = IGNORE - quiet = 1 -- pass -q option - max_cyclomatic_complexity = false - codes = true - max_line_length = LINE_LENGTH - max_code_line_length = LINE_LENGTH - max_string_line_length = LINE_LENGTH - max_comment_line_length = LINE_LENGTH - - --List of files and directories to exclude - exclude_files = { - --Ignore special folders - '**/.trash/', - '**/.history/', - '**/stdlib/vendor/', - - --Ignore development mods - '**/combat-tester/', - '**/test-maker/', - '**/trailer/', - - --Ignore love Includes - '**/love/includes/' - } -end - -do -- RedMew and ExpGaming overrides - globals = { - 'math', 'table', 'package', - 'print', 'require', 'unpack', 'inspect', 'loadstring', 'ServerCommands', 'Debug', - '_C', '_DEBUG', '_CHEATS', '_DUMP_ENV', '_LIFECYCLE', '_STAGE', - } -end - -do -- Set default prototype files - files['**/data.lua'].std = STD_DATA - files['**/data-updates.lua'].std = STD_DATA - files['**/data-final-fixes.lua'].std = STD_DATA - files['**/settings.lua'].std = STD_DATA - files['**/settings-updates.lua'].std = STD_DATA - files['**/settings-final-fixes.lua'].std = STD_DATA - files['**/prototypes/'].std = STD_DATA - files['**/settings/'].std = STD_DATA -end - -do -- Base and Core mod files - local base_scenarios = { - std = STD_BASE_CONTROL .. '+factorio_base_scenarios+factorio_base_story', - --ignore = {'212/event', '111', '112', '113', '211', '212', '213', '311', '411', '412', '421', '422', '423', '431', '432', '512'} - ignore = {'...'} - } - files['**/base/scenarios/'] = base_scenarios - files['**/base/tutorials/'] = base_scenarios - files['**/base/campaigns/'] = base_scenarios - files['**/wip-scenario/'] = base_scenarios - - files['**/base/migrations/'] = {std = STD_BASE_CONTROL} - - files['**/core/lualib/'] = {std = STD_BASE_CONTROL} - files['**/core/lualib/util.lua'] = {globals = {'util', 'table'}, ignore = {'432/object'}} - files['**/core/lualib/silo-script.lua'] = {globals = {'silo_script'}, ignore = {'4../player'}} - files['**/core/lualib/production-score.lua'] = {globals = {'production_score', 'get_price_recursive'}, ignore = {'4../player'}} - files['**/core/lualib/story*'] = {std = '+factorio_base_story', ignore = {'42./k', '42./filter'}} - files['**/core/lualib/mod-gui.lua'] = {globals = {'mod_gui'}} - files['**/core/lualib/camera.lua'] = {globals = {'camera'}} - files['**/core/lualib/builder.lua'] = {globals = {'Builder', 'builder', 'action', 'down', 'right'}} - - files['**/core/lualib/bonus-gui-ordering/'] = {std = STD_BASE_DATA} - files['**/core/lualib/dataloader.lua'] = {globals = {'data'}} - files['**/core/lualib/circuit-connector-*'] = {std = STD_BASE_DATA..'+factorio_circuit_connector_generated'} - files['**/core/lualib/bonus-gui-ordering.lua'] = {globals = {'bonus_gui_ordering'}} - - files['**/base/prototypes/'] = {std = STD_BASE_DATA} - files['**/core/prototypes/'] = {std = STD_BASE_DATA} - files['**/core/prototypes/noise-programs.lua'] = {ignore = {'212/x', '212/y', '212/tile', '212/map'}} -end - -do -- Stdlib Files - local stdlib_control = { - std = 'lua52+factorio+factorio_control+stdlib+factorio_defines', - max_line_length = LINE_LENGTH - } - - local stdlib_data = { - std = 'lua52+factorio+factorio_data+stdlib+factorio_defines', - max_line_length = LINE_LENGTH - } - - -- Assume control stage for stdlib - files['**/stdlib/'] = stdlib_control - - -- Assume generic lua for stdlib utils - files['**/stdlib/utils/**'].std = 'lua52+stdlib' - - -- STDLIB data stage files - files['**/stdlib/data/'] = stdlib_data - - -- STDLIB Busted Spec - files['**/spec/**'] = { - globals = {'serpent', 'log', 'package.remove_stdlib'}, - std = 'lua52c+busted+factorio_defines+factorio_control+stdlib' - } - - -- Love - files['**/love/'].std = 'luajit+love+love_extra+stdlib+stdlib_data' -end - -do -- Factorio STDs-- - stds.factorio = { - --Set the read only variables - read_globals = { - -- @log@: Gives writing access to Factorio's logger instance. - "log", - -- @serpent@: Lua serializer and pretty printer. (https://github.com/pkulchenko/serpent). - "serpent", - -- @table_size@: Returns the number of elements inside an LUA table. - "table_size", - -- @lldebugger@: Provided by lua local debugger vscode extension. - lldebugger = { - fields = {'requestBreak'}, - other_fields = true, - read_only = false, - }, - -- @__DebugAdapter@: Provided by Factorio Mod Debug vscode extension. - __DebugAdapter = { - fields = {'print', 'stepIgnoreAll', 'stepIgnore', 'breakpoint'}, - other_fields = true, - read_only = false - }, - util = { - fields = { - "by_pixel", "distance", "findfirstentity", "positiontostr", "formattime", "moveposition", "oppositedirection", - "ismoduleavailable", "multiplystripes", "format_number", "increment", "color", "conditional_return", - "add_shift", "merge", "premul_color", "encode", "decode", "insert_safe", - table = { - fields = { - "compare", "deepcopy" - }, - }, - }, - }, - table = { - fields = { - "compare", "deepcopy" - }, - }, - }, - } - - stds.factorio_control = { - read_globals = { - - -- @commands@: - commands = { - fields = { - "add_command", "commands", "game_commands", "remove_command" - }, - }, - - -- @settings@: - settings = { - fields = { - "get_player_settings", - startup = {other_fields = true}, - global = {other_fields = true}, - player = {other_fields = true}, - get = {read_only = false}, -- stdlib added - get_startup = {read_only = false} -- stdlib added - }, - }, - - -- @script@: Provides an interface for registering event handlers. - -- (http://lua-api.factorio.com/latest/LuaBootstrap.html) - script = { - fields = { - "on_event", "on_nth_tick", "on_configuration_changed", "on_init", "on_load", "generate_event_name", - "raise_event", "get_event_handler", "mod_name", "get_event_order", - "is_game_in_debug_mode", "object_name", "set_event_filter", "get_event_filter", "register_metatable", - active_mods = {read_only = true, other_fields = true}, - }, - other_fields = false, - }, - - -- @remote@: Allows inter-mod communication by providing a repository of interfaces that is shared by all mods. - -- (http://lua-api.factorio.com/latest/LuaRemote.html) - remote = { - fields = { - interfaces = {read_only = false, other_fields = true}, - "add_interface", "remove_interface", "call" - }, - read_only = true, - other_fields = false, - }, - - rcon = { - fields = {'print'} - }, - - rendering = { - other_fields = false, - read_only = true, - fields = { - 'draw_animation', - 'draw_line', - 'draw_text', - 'draw_circle', - 'draw_rectangle', - 'draw_arc', - 'draw_polygon', - 'draw_sprite', - 'draw_light', - 'destroy', - 'is_font_valid', - 'is_valid', - 'get_all_ids', - 'clear', - 'get_type', - 'get_surface', - 'get_time_to_live', - 'set_time_to_live', - 'get_forces', - 'set_forces', - 'get_players', - 'set_players', - 'get_color', - 'set_color', - 'get_width', - 'set_width', - 'get_from', - 'set_from', - 'get_to', - 'set_to', - 'get_gap_amount', - 'set_gap_amount', - 'get_gap_length', - 'set_gap_length', - 'get_target', - 'set_target', - 'get_orientation', - 'set_orientation', - 'get_scale', - 'set_scale', - 'get_text', - 'set_text', - 'get_font', - 'set_font', - 'get_alignment', - 'set_alignment', - 'get_scale_with_zoom', - 'set_scale_with_zoom', - 'get_filled', - 'set_filled', - 'get_radius', - 'set_radius', - 'get_left_top', - 'set_left_top', - 'get_right_bottom', - 'set_right_bottom', - 'get_max_radius', - 'set_max_radius', - 'get_min_radius', - 'set_min_radius', - 'get_start_angle', - 'set_start_angle', - 'get_angle', - 'set_angle', - 'get_vertices', - 'set_vertices', - 'get_sprite', - 'set_sprite', - 'get_x_scale', - 'set_x_scale', - 'get_y_scale', - 'set_y_scale', - 'get_render_layer', - 'set_render_layer', - 'get_orientation_target', - 'set_orientation_target', - 'get_oriented_offset', - 'set_oriented_offset', - 'get_intensity', - 'set_intensity', - 'get_minimum_darkness', - 'set_minimum_darkness' - } - }, - - -- @game@: Main object through which most of the API is accessed. - -- It is, however, not available inside handlers registered with @script.on_load@. - -- (http://lua-api.factorio.com/latest/LuaGameScript.html) - game ={ - other_fields = false, - read_only = false, - fields = { - 'auto_save', - 'ban_player', - 'check_consistency', - 'check_prototype_translations', - 'count_pipe_groups', - 'create_force', - 'create_profiler', - 'create_random_generator', - 'create_surface', - 'decode_string', - 'delete_surface', - 'direction_to_string', - 'disable_replay', - 'disable_tips_and_tricks', - 'disable_tutorial_triggers', - 'encode_string', - 'evaluate_expression', - 'force_crc', - 'get_active_entities_count', - 'get_entity_by_tag', - 'get_filtered_achievement_prototypes', - 'get_filtered_decorative_prototypes', - 'get_filtered_entity_prototypes', - 'get_filtered_equipment_prototypes', - 'get_filtered_fluid_prototypes', - 'get_filtered_item_prototypes', - 'get_filtered_mod_setting_prototypes', - 'get_filtered_recipe_prototypes', - 'get_filtered_technology_prototypes', - 'get_filtered_tile_prototypes', - 'get_map_exchange_string', - 'get_player', - 'get_surface', - 'get_train_stops', - 'help', - 'is_demo', - 'is_multiplayer', - 'is_valid_sound_path', - 'is_valid_sprite_path', - 'json_to_table', - 'kick_player', - 'log_recipe_locale', - 'merge_forces', - 'mute_player', - 'parse_map_exchange_string', - 'play_sound', - 'print', - 'print_stack_size', - 'purge_player', - 'regenerate_entity', - 'reload_mods', - 'reload_script', - 'remove_offline_players', - 'remove_path', - 'reset_time_played', - 'save_atlas', - 'server_save', - 'set_game_state', - 'set_wait_for_screenshots_to_finish', - 'show_message_dialog', - 'table_to_json', - 'take_screenshot', - 'take_technology_screenshot', - 'unban_player', - 'unmute_player', - 'write_file', - - achievement_prototypes = {read_only = true, other_fields = true}, - active_mods = {read_only = true, other_fields = true}, - ammo_category_prototypes = {read_only = true, other_fields = true}, - autoplace_control_prototypes = {read_only = true, other_fields = true}, - backer_names = {read_only = true, other_fields = true}, - connected_players = {read_only = true, other_fields = true}, - custom_input_prototypes = {read_only = true, other_fields = true}, - damage_prototypes = {read_only = true, other_fields = true}, - decorative_prototypes = {read_only = true, other_fields = true}, - default_map_gen_settings = {read_only = true, other_fields = true}, - difficulty = {read_only = true, other_fields = true}, - difficulty_settings = {read_only = true, other_fields = true}, - entity_prototypes = {read_only = true, other_fields = true}, - equipment_category_prototypes = {read_only = true, other_fields = true}, - equipment_grid_prototypes = {read_only = true, other_fields = true}, - equipment_prototypes = {read_only = true, other_fields = true}, - finished = {read_only = true, other_fields = true}, - fluid_prototypes = {read_only = true, other_fields = true}, - forces = {read_only = true, other_fields = true}, - fuel_category_prototypes = {read_only = true, other_fields = true}, - item_group_prototypes = {read_only = true, other_fields = true}, - item_prototypes = {read_only = true, other_fields = true}, - item_subgroup_prototypes = {read_only = true, other_fields = true}, - map_settings = {read_only = true, other_fields = true}, - mod_setting_prototypes = {read_only = true, other_fields = true}, - module_category_prototypes = {read_only = true, other_fields = true}, - named_noise_expressions = {read_only = true, other_fields = true}, - noise_layer_prototypes = {read_only = true, other_fields = true}, - object_name = {read_only = true, other_fields = true}, - particle_prototypes = {read_only = true, other_fields = true}, - permissions = {read_only = true, other_fields = true}, - player = {read_only = true, other_fields = true}, - players = {read_only = true, other_fields = true}, - pollution_statistics = {read_only = true, other_fields = true}, - recipe_category_prototypes = {read_only = true, other_fields = true}, - recipe_prototypes = {read_only = true, other_fields = true}, - resource_category_prototypes = {read_only = true, other_fields = true}, - shortcut_prototypes = {read_only = true, other_fields = true}, - styles = {read_only = true, other_fields = true}, - surfaces = {read_only = true, other_fields = true}, - technology_prototypes = {read_only = true, other_fields = true}, - tick = {read_only = true, other_fields = true}, - ticks_played = {read_only = true, other_fields = true}, - tile_prototypes = {read_only = true, other_fields = true}, - trivial_smoke_prototypes = {read_only = true, other_fields = true}, - virtual_signal_prototypes = {read_only = true, other_fields = true}, - - autosave_enabled = {read_only = false, other_fields = false}, - draw_resource_selection = {read_only = false, other_fields = false}, - enemy_has_vision_on_land_mines = {read_only = false, other_fields = false}, - speed = {read_only = false, other_fields = false}, - tick_paused = {read_only = false, other_fields = false}, - ticks_to_run = {read_only = false, other_fields = false} - }, - }, - }, - - globals = { - -- @global@: The global dictionary, useful for storing data persistent across a save-load cycle. - -- Writing access is given to the mod-id field (for mod-wise saved data). - -- (http://lua-api.factorio.com/latest/Global.html) - "global", - - -- @MOD@: Keep it organized, use this variable for anything that "NEEDS" to be global for some reason. - "MOD" - }, - } - - stds.factorio_data = { - - read_globals = { - data = { - fields = { - raw = { - other_fields = true, - read_only = false - }, - "extend", "is_demo" - }, - }, - - settings = { - fields = { - "startup", "global", "player", "get", "get_startup" - }, - }, - - mods = { - other_fields = true - }, - - -- Popular mods that think they need globals! - angelsmods = { - other_fields = true - }, - - -- Popular mods that think they need globals! - bobmods = { - other_fields = true - }, - - - } - } -end - -do -- Factorio Base/Core STDs-- - stds.factorio_base_control = { - read_globals = {"silo_script", "mod_gui", "camera"} - } - - stds.factorio_base_scenarios = { - globals = { - "check_automate_science_packs_advice", "check_research_hints", "check_supplies", "manage_attacks", "all_dead", - "on_win", "difficulty_number", "init_attack_data", "handle_attacks", "count_items_in_container", "progress", "scanned", - "check_light", "check_machine_gun", "level", "story_table", - - "tightspot_prices", "tightspot_make_offer", "tightspot_init", "tightspot_get_required_balance", - "tightspot_init_level", "tightspot_init_spending_frame", "tightspot_init_progress_frame", "tightspot_update_progress", "tightspot_update_spending", - "tightspot_get_missing_to_win", "tightspot_sell_back", "tightspot_start_level", "tightspot_show_level_description", "tightspot_update_speed_label", - "map_ignore", "tightspot_check_level", "land_price", - - "transport_belt_madness_init", "transport_belt_madness_init_level", "transport_belt_madness_create_chests", "transport_belt_madness_fill_chests", - "transport_belt_madness_start_level", "map_ignore", "map_clear", "map_load", "map_save", "transport_belt_madness_show_level_description", - "transport_belt_madness_check_level", "transport_belt_madness_next_level", "transport_belt_madness_clear_level", "transport_belt_madness_contains_next_level", - - "restricted", "check_built_items", "result", "disable_combat_technologies", "apply_character_modifiers", "apply_combat_modifiers", "apply_balance", - "load_config", "starting_area_constant", "create_next_surface", "end_round", "prepare_next_round", "silo_died","choose_joining_gui", - "destroy_joining_guis", "create_random_join_gui", "create_auto_assign_gui", "create_pick_join_gui", "create_config_gui", "make_config_table", "default", - "make_team_gui", "make_team_gui_config", "add_team_button_press", "trash_team_button_press", "remove_team_from_team_table", "add_team_to_team_table", - "set_teams_from_gui", "on_team_button_press", "make_color_dropdown", "create_balance_option", "create_disable_frame", "disable_frame", "parse_disabled_items", - "set_balance_settings", "config_confirm", "parse_config_from_gui", "get_color", "roll_starting_area", "delete_roll_surfaces", "auto_assign", - "destroy_config_for_all", "prepare_map", "set_evolution_factor", "update_players_on_team_count", "random_join", "init_player_gui", - "destroy_player_gui", "objective_button_press", "admin_button_press", "admin_frame_button_press", "diplomacy_button_press", "update_diplomacy_frame", - "diplomacy_frame_button_press", "team_changed_diplomacy", "diplomacy_check_press", "get_stance", "give_inventory", "setup_teams", "disable_items_for_all", - "set_random_team", "set_diplomacy", "create_spawn_positions", "set_spawn_position", "set_team_together_spawns", "chart_starting_area_for_force_spawns", - "check_starting_area_chunks_are_generated", "check_player_color", "check_round_start", "clear_starting_area_enemies", "check_no_rush_end", "check_no_rush_players", - "finish_setup", "chart_area_for_force", "setup_start_area_copy", "update_copy_progress", "update_progress_bar", "copy_paste_starting_area_tiles", - "copy_paste_starting_area_entities", "create_silo_for_force", "setup_research", "on_chunk_generated", "get_distance_to_nearest_spawn", - "create_wall_for_force", "fpn", "give_items", "create_item_frame", "create_technologies_frame", "create_cheat_frame", "create_day_frame", - "time_modifier", "points_per_second_start", "points_per_second_level_subtract", "levels", "update_info", "get_time_left", "update_time_left", - "on_joined", "make_frame", "update_frame", "update_table", "calculate_task_item_multiplayer", "setup_config", "select_from_probability_table", - "select_inventory", "select_equipment", "select_challange_type", "save_round_statistics", "start_challenge", "create_teams", "set_areas", - "decide_player_team", "set_teams", "refresh_leaderboard", "set_player", "generate_technology_list", "generate_research_task","setup_unlocks", - "check_technology_progress", "generate_production_task", "generate_shopping_list_task", "set_gui_flow_table", "create_visibility_button", - "check_item_lists", "update_task_gui", "check_end_of_round", "end_round_gui_update", "try_to_check_victory", "update_gui", "check_start_round", - "check_start_set_areas", "check_start_setting_entities", "check_set_areas", "check_clear_areas", "check_chests", "check_chests_shopping_list", - "check_chests_production", "check_input_chests", "fill_input_chests", "check_victory", "shopping_task_finished", "calculate_force_points", - "update_research_task_table", "update_production_task_table", "update_shopping_list_task_table", "create_joined_game_gui", "pre_ending_round", - "player_ending_prompt", "update_end_timer", "update_begin_timer", "team_finished", "save_points_list", "give_force_players_points", - "update_winners_list", "set_spectator", "set_character", "give_starting_inventory", "give_equipment", "shuffle_table", "format_time", - "spairs", "fill_leaderboard", "create_grid", "simple_entities", "save_map_data", "clear_map", "create_tiles", "recreate_entities", - "map_sets", "give_points", "init_forces", "init_globals", "init_unit_settings", "check_next_wave", "next_wave", "calculate_wave_power", - "wave_end", "make_next_spawn_tick", "check_spawn_units", "get_wave_units", "spawn_units", "randomize_ore", "set_command", "command_straglers", - "unit_config", "make_next_wave_tick", "time_to_next_wave", "time_to_wave_end", "rocket_died", "unit_died", "get_bounty_price", "setup_waypoints", - "insert_items", "give_starting_equipment", "give_spawn_equipment", "next_round_button_visible", "gui_init", "create_wave_frame", "create_money_frame", - "create_upgrade_gui", "update_upgrade_listing", "upgrade_research", "get_upgrades", "get_money", "update_connected_players", "update_round_number", - "set_research", "set_recipes", "check_deconstruction", "check_blueprint_placement", "loop_entities", "experiment_items", - "setup", "story_gui_click", "clear_surface", "add_run_trains_button", "puzzle_condition", "basic_signals", - "loop_trains", "Y_offset", "ghosts_1", "ghosts_2", "required_path", "through_wall_path", "count", "check_built_real_rail", - "current_ghosts_count", "other", "rails", "set_rails", "straight_section", "late_entities", "entities", "stop", - "get_spawn_coordinate", - - --tutorials - "intermission", "create_entities_on_tick", "on_player_created", "required_count", "non_player_entities", "clear_rails", - "chest", "damage", "furnace", "init_prototypes", "build_infi_table", "junk", "update_player_tags", "time_left", "team_production", - "create_task_frame", "create_visibilty_buttons", "update_leaderboard", "in_in_area" - } - } - - stds.factorio_base_data = { - globals = { - --Style - "make_cursor_box", "make_full_cursor_box", - "default_container_padding", "default_orange_color", "default_light_orange_color", "warning_red_color", - "achievement_green_color", "achievement_tan_color", "orangebuttongraphcialset", "bluebuttongraphcialset", - "bonus_gui_ordering", "trivial_smoke", "technology_slot_base_width", "technology_slot_base_height", "default_frame_font_vertical_compensation", - - --Belts - "transport_belt_connector_frame_sprites", "transport_belt_circuit_wire_connection_point", "transport_belt_circuit_wire_max_distance", - "transport_belt_circuit_connector_sprites", "ending_patch_prototype", "basic_belt_horizontal", "basic_belt_vertical", - "basic_belt_ending_top", "basic_belt_ending_bottom", "basic_belt_ending_side", "basic_belt_starting_top", "basic_belt_starting_bottom", - "basic_belt_starting_side", "fast_belt_horizontal", "fast_belt_vertical", "fast_belt_ending_top", "fast_belt_ending_bottom", - "fast_belt_ending_side", "fast_belt_starting_top", "fast_belt_starting_bottom", "fast_belt_starting_side", "express_belt_horizontal", - "express_belt_vertical", "express_belt_ending_top", "express_belt_ending_bottom", "express_belt_ending_side", "express_belt_starting_top", - "express_belt_starting_bottom", "express_belt_starting_side", - - --Circuit Connectors - "circuit_connector_definitions", "default_circuit_wire_max_distance", "inserter_circuit_wire_max_distance", - "universal_connector_template", "belt_connector_template", "belt_frame_connector_template", "inserter_connector_template", - - --Inserter Circuit Connectors - "inserter_circuit_wire_max_distance", "inserter_default_stack_control_input_signal", - - --Sounds/beams - "make_heavy_gunshot_sounds", "make_light_gunshot_sounds", "make_laser_sounds", - - --Gun/Laser - "gun_turret_extension", "gun_turret_extension_shadow", "gun_turret_extension_mask", "gun_turret_attack", - "laser_turret_extension", "laser_turret_extension_shadow", "laser_turret_extension_mask", - - --Pipes - "pipecoverspictures", "pipepictures", "assembler2pipepictures", "assembler3pipepictures", "make_heat_pipe_pictures", - - --Combinators - "generate_arithmetic_combinator", "generate_decider_combinator", "generate_constant_combinator", - - --Rail - "destroyed_rail_pictures", "rail_pictures", "rail_pictures_internal", "standard_train_wheels", "drive_over_tie", - "rolling_stock_back_light", "rolling_stock_stand_by_light", - - --Enemies - "make_enemy_autoplace", "make_enemy_spawner_autoplace", "make_enemy_worm_autoplace", - "make_spitter_attack_animation", "make_spitter_run_animation", "make_spitter_dying_animation", - "make_spitter_attack_parameters", "make_spitter_roars", "make_spitter_dying_sounds", - "make_spawner_idle_animation", "make_spawner_die_animation", - "make_biter_run_animation", "make_biter_attack_animation", "make_biter_die_animation", - "make_biter_roars", "make_biter_dying_sounds", "make_biter_calls", - "make_worm_roars", "make_worm_dying_sounds", "make_worm_folded_animation", "make_worm_preparing_animation", - "make_worm_prepared_animation", "make_worm_attack_animation", "make_worm_die_animation", - - --Other - "tile_variations_template", "make_water_autoplace_settings", - "make_unit_melee_ammo_type", "make_trivial_smoke", "make_4way_animation_from_spritesheet", "flying_robot_sounds", - "productivitymodulelimitation", "crash_trigger", "capsule_smoke", "make_beam", "playeranimations", - "make_blood_tint", "make_shadow_tint", - - --tiles - "water_transition_template", "make_water_transition_template", "water_autoplace_settings", "water_tile_type_names", - "patch_for_inner_corner_of_transition_between_transition", - } - } - - stds.factorio_base_story = { - globals = { - "story_init_helpers", "story_update_table", "story_init", "story_update", "story_on_tick", "story_add_update", - "story_remove_update", "story_jump_to", "story_elapsed", "story_elapsed_check", "story_show_message_dialog", - "set_goal", "player_set_goal", "on_player_joined", "flash_goal", "set_info", "player_set_info", "export_entities", - "list", "recreate_entities", "entity_to_connect", "limit_camera", "find_gui_recursive", "enable_entity_export", - "add_button", "on_gui_click", "set_continue_button_style", "add_message_log", "story_add_message_log", - "player_add_message_log", "message_log_frame", "message_log_scrollpane", "message_log_close_button", - "message_log_table", "toggle_message_log_button", "toggle_objective_button", "message_log_init", - "add_gui_recursive", "add_toggle_message_log_button", "add_toggle_objective_button", "mod_gui", - "flash_message_log_button", "flash_message_log_on_tick", "story_gui_click", "story_points_by_name", "story_branches", - "player", "surface", "deconstruct_on_tick", "recreate_entities_on_tick", "flying_congrats", "story_table" - } - } - - stds.factorio_circuit_connector_generated = { - globals = { - 'default_circuit_wire_max_distance', 'circuit_connector_definitions', 'universal_connector_template', - 'belt_connector_template', 'belt_frame_connector_template', 'inserter_connector_template', 'inserter_connector_template', - 'inserter_circuit_wire_max_distance', 'inserter_default_stack_control_input_signal', 'transport_belt_connector_frame_sprites', - 'transport_belt_circuit_wire_max_distance', - } - } -end - -do -- STDLIB STDs-- - stds.stdlib = { - read_globals = {}, - globals = { - 'STDLIB', - 'prequire', - 'rawtostring', - 'traceback', - 'data_traceback', - 'inspect', - 'serpent', - 'inline_if', - 'install', - 'log', - 'concat', - 'GAME', - 'AREA', - 'POSITION', - 'TILE', - 'SURFACE', - 'CHUNK', - 'COLOR', - 'ENTITY', - 'INVENTORY', - 'RESOURCE', - 'CONFIG', - 'LOGGER', - 'QUEUE', - 'EVENT', - 'GUI', - 'PLAYER', - 'FORCE', - 'MATH', - 'STRING', - 'TABLE' - } - } - - stds.stdlib_control = {} - - stds.stdlib_data = { - globals = { - 'DATA', - 'RECIPE', - 'ITEM', - 'FLUID', - 'ENTITY', - 'TECHNOLOGY', - 'CATEGORY' - } - } -end - -do -- Love STDs - stds.love_extra = { - read_globals = { - love = { - fields = { - arg = { - fields = { - 'parseGameArguments', - 'parseOption', - 'getLow', - 'optionIndices', - 'options' - } - } - } - }, - 'coroutine', - 'io', - 'socket', - 'dofile', - 'loadfile' - }, - globals = { - love = { - fields = { - 'handlers' - } - } - } - } -end - -do -- Factorio Defines STDs-- - stds.factorio_defines = { - read_globals = { - defines = { - fields = { - alert_type = { - fields = { - 'entity_destroyed', - 'entity_under_attack', - 'not_enough_construction_robots', - 'no_material_for_construction', - 'not_enough_repair_packs', - 'turret_fire', - 'custom', - 'no_storage', - 'train_out_of_fuel', - 'fluid_mixing' - } - }, - behavior_result = { - fields = { - 'in_progress', - 'fail', - 'success', - 'deleted' - } - }, - build_check_type = { - fields = { - 'script', - 'manual', - 'ghost_place', - 'ghost_revive' - } - }, - chain_signal_state = { - fields = { - 'all_open', - 'partially_open', - 'none_open' - } - }, - chunk_generated_status = { - fields = { - 'nothing', - 'custom_tiles', - 'basic_tiles', - 'corrected_tiles', - 'tiles', - 'entities' - } - }, - circuit_condition_index = { - fields = { - 'inserter_circuit', - 'inserter_logistic', - 'lamp', - 'arithmetic_combinator', - 'decider_combinator', - 'constant_combinator', - 'offshore_pump', - 'pump' - } - }, - circuit_connector_id = { - fields = { - 'accumulator', - 'constant_combinator', - 'container', - 'programmable_speaker', - 'rail_signal', - 'rail_chain_signal', - 'roboport', - 'storage_tank', - 'wall', - 'electric_pole', - 'inserter', - 'lamp', - 'combinator_input', - 'combinator_output', - 'offshore_pump', - 'pump' - } - }, - command = { - fields = { - 'attack', - 'go_to_location', - 'compound', - 'group', - 'attack_area', - 'wander', - 'flee', - 'stop', - 'build_base' - } - }, - compound_command = { - fields = { - 'logical_and', - 'logical_or', - 'return_last' - } - }, - control_behavior = { - fields = { - inserter = { - fields = { - circuit_mode_of_operation = { - fields = { - 'enable_disable', - 'set_filters', - 'read_hand_contents', - 'set_stack_size' - } - }, - hand_read_mode = { - fields = { - 'hold', - 'pulse' - } - } - } - }, - lamp = { - fields = { - circuit_mode_of_operation = { - fields = { - 'use_colors' - } - } - } - }, - logistic_container = { - fields = { - circuit_mode_of_operation = { - fields = { - 'send_contents', - 'set_requests' - } - } - } - }, - mining_drill = { - fields = { - resource_read_mode = { - fields = { - 'this_miner', - 'entire_patch' - } - } - } - }, - roboport = { - fields = { - circuit_mode_of_operation = { - fields = { - 'read_logistics', - 'read_robot_stats' - } - } - } - }, - train_stop = { - fields = { - circuit_mode_of_operation = { - fields = { - 'enable_disable', - 'send_to_train', - 'read_from_train', - 'read_stopped_train' - } - } - } - }, - transport_belt = { - fields = { - content_read_mode = { - fields = { - 'pulse', - 'hold' - } - } - } - }, - type = { - fields = { - 'container', - 'generic_on_off', - 'inserter', - 'lamp', - 'logistic_container', - 'roboport', - 'storage_tank', - 'train_stop', - 'decider_combinator', - 'arithmetic_combinator', - 'constant_combinator', - 'transport_belt', - 'accumulator', - 'rail_signal', - 'rail_chain_signal', - 'wall', - 'mining_drill', - 'programmable_speaker' - } - } - } - }, - controllers = { - fields = { - 'ghost', - 'character', - 'god', - 'editor', - 'cutscene', - 'spectator' - } - }, - deconstruction_item = { - fields = { - entity_filter_mode = { - fields = { - 'whitelist', - 'blacklist' - } - }, - tile_filter_mode = { - fields = { - 'whitelist', - 'blacklist' - } - }, - tile_selection_mode = { - fields = { - 'normal', - 'always', - 'never', - 'only' - } - } - } - }, - difficulty = { - fields = { - 'easy', - 'normal', - 'hard' - } - }, - difficulty_settings = { - fields = { - recipe_difficulty = { - fields = { - 'normal', - 'expensive' - } - }, - technology_difficulty = { - fields = { - 'normal', - 'expensive' - } - } - } - }, - direction = { - fields = { - 'north', - 'northeast', - 'east', - 'southeast', - 'south', - 'southwest', - 'west', - 'northwest' - } - }, - distraction = { - fields = { - 'by_enemy', - 'by_anything', - 'by_damage' - } - }, - entity_status = { - fields = { - 'working', - 'no_power', - 'no_fuel', - 'no_recipe', - 'no_input_fluid', - 'no_research_in_progress', - 'no_minable_resources', - 'low_input_fluid', - 'low_power', - 'disabled_by_control_behavior', - 'disabled_by_script', - 'fluid_ingredient_shortage', - 'fluid_production_overload', - 'item_ingredient_shortage', - 'item_production_overload', - 'marked_for_deconstruction', - 'missing_required_fluid', - 'missing_science_packs', - 'waiting_for_source_items', - 'waiting_for_space_in_destination', - 'waiting_to_launch_rocket' - } - }, - events = { - fields = { - 'on_tick', - 'on_gui_click', - 'on_gui_confirmed', - 'on_gui_text_changed', - 'on_gui_checked_state_changed', - 'on_entity_died', - 'on_post_entity_died', - 'on_entity_damaged', - 'on_picked_up_item', - 'on_built_entity', - 'on_sector_scanned', - 'on_player_mined_item', - 'on_put_item', - 'on_rocket_launched', - 'on_pre_player_mined_item', - 'on_chunk_generated', - 'on_player_crafted_item', - 'on_robot_built_entity', - 'on_robot_pre_mined', - 'on_robot_mined', - 'on_research_started', - 'on_research_finished', - 'on_player_rotated_entity', - 'on_player_set_quickbar_slot', - 'on_marked_for_deconstruction', - 'on_cancelled_deconstruction', - 'on_trigger_created_entity', - 'on_trigger_fired_artillery', - 'on_train_changed_state', - 'on_player_created', - 'on_resource_depleted', - 'on_player_driving_changed_state', - 'on_force_created', - 'on_forces_merging', - 'on_player_cursor_stack_changed', - 'on_pre_entity_settings_pasted', - 'on_entity_settings_pasted', - 'on_player_main_inventory_changed', - 'on_player_armor_inventory_changed', - 'on_player_ammo_inventory_changed', - 'on_player_gun_inventory_changed', - 'on_player_placed_equipment', - 'on_player_removed_equipment', - 'on_pre_player_died', - 'on_player_died', - 'on_player_respawned', - 'on_player_joined_game', - 'on_player_left_game', - 'on_player_built_tile', - 'on_player_mined_tile', - 'on_robot_built_tile', - 'on_robot_mined_tile', - 'on_player_selected_area', - 'on_player_alt_selected_area', - 'on_player_changed_surface', - 'on_selected_entity_changed', - 'on_market_item_purchased', - 'on_player_dropped_item', - 'on_biter_base_built', - 'on_player_changed_force', - 'on_entity_renamed', - 'on_gui_selection_state_changed', - 'on_runtime_mod_setting_changed', - 'on_difficulty_settings_changed', - 'on_surface_created', - 'on_surface_deleted', - 'on_pre_surface_deleted', - 'on_player_mined_entity', - 'on_robot_mined_entity', - 'on_train_created', - 'on_gui_elem_changed', - 'on_player_setup_blueprint', - 'on_player_deconstructed_area', - 'on_player_configured_blueprint', - 'on_console_chat', - 'on_console_command', - 'on_player_removed', - 'on_pre_player_removed', - 'on_player_used_capsule', - 'script_raised_built', - 'script_raised_destroy', - 'script_raised_revive', - 'on_player_promoted', - 'on_player_demoted', - 'on_combat_robot_expired', - 'on_player_changed_position', - 'on_mod_item_opened', - 'on_gui_opened', - 'on_gui_closed', - 'on_gui_value_changed', - 'on_player_muted', - 'on_player_unmuted', - 'on_player_cheat_mode_enabled', - 'on_player_cheat_mode_disabled', - 'on_character_corpse_expired', - 'on_pre_ghost_deconstructed', - 'on_player_pipette', - 'on_player_display_resolution_changed', - 'on_player_display_scale_changed', - 'on_pre_player_crafted_item', - 'on_player_cancelled_crafting', - 'on_chunk_charted', - 'on_technology_effects_reset', - 'on_land_mine_armed', - 'on_forces_merged', - 'on_player_trash_inventory_changed', - 'on_pre_player_left_game', - 'on_pre_surface_cleared', - 'on_surface_cleared', - 'on_chunk_deleted', - 'on_pre_chunk_deleted', - 'on_train_schedule_changed', - 'on_player_banned', - 'on_player_kicked', - 'on_player_unbanned', - 'on_rocket_launch_ordered', - 'on_script_path_request_finished', - 'on_ai_command_completed', - 'on_marked_for_upgrade', - 'on_cancelled_upgrade', - 'on_player_toggled_map_editor', - 'on_entity_cloned', - 'on_area_cloned', - 'on_brush_cloned', - 'on_game_created_from_scenario', - 'on_surface_imported', - 'on_surface_renamed', - 'on_player_toggled_alt_mode', - 'on_player_repaired_entity', - 'on_player_fast_transferred', - 'on_pre_robot_exploded_cliff', - 'on_robot_exploded_cliff', - 'on_entity_spawned', - 'on_cutscene_waypoint_reached', - 'on_unit_group_created', - 'on_unit_added_to_group', - 'on_unit_removed_from_group', - 'on_unit_group_finished_gathering', - 'on_build_base_arrived', - 'on_chart_tag_added', - 'on_chart_tag_modified', - 'on_chart_tag_removed', - 'on_lua_shortcut', - 'on_gui_location_changed', - 'on_gui_selected_tab_changed', - 'on_gui_switch_state_changed', - 'on_force_cease_fire_changed', - 'on_force_friends_changed', - 'on_string_translated', - 'on_script_trigger_effect' - } - }, - flow_precision_index = { - fields = { - 'one_second', - 'one_minute', - 'ten_minutes', - 'one_hour', - 'ten_hours', - 'fifty_hours', - 'two_hundred_fifty_hours', - 'one_thousand_hours' - } - }, - group_state = { - fields = { - 'gathering', - 'moving', - 'attacking_distraction', - 'attacking_target', - 'finished', - 'pathfinding', - 'wander_in_group' - } - }, - gui_type = { - fields = { - 'entity', - 'research', - 'controller', - 'production', - 'item', - 'bonus', - 'trains', - 'achievement', - 'blueprint_library', - 'equipment', - 'logistic', - 'other_player', - 'kills', - 'permissions', - 'tutorials', - 'custom', - 'server_management', - 'player_management', - 'tile' - } - }, - input_action = { - fields = { - 'activate_copy', - 'activate_cut', - 'activate_paste', - 'add_permission_group', - 'add_train_station', - 'admin_action', - 'alt_select_area', - 'alt_select_blueprint_entities', - 'alternative_copy', - 'begin_mining', - 'begin_mining_terrain', - 'build_item', - 'build_rail', - 'build_terrain', - 'cancel_craft', - 'cancel_deconstruct', - 'cancel_new_blueprint', - 'cancel_research', - 'cancel_upgrade', - 'change_active_item_group_for_crafting', - 'change_active_item_group_for_filters', - 'change_active_quick_bar', - 'change_arithmetic_combinator_parameters', - 'change_blueprint_book_record_label', - 'change_decider_combinator_parameters', - 'change_item_label', - 'change_multiplayer_config', - 'change_picking_state', - 'change_programmable_speaker_alert_parameters', - 'change_programmable_speaker_circuit_parameters', - 'change_programmable_speaker_parameters', - 'change_riding_state', - 'change_shooting_state', - 'change_single_blueprint_record_label', - 'change_train_stop_station', - 'change_train_wait_condition', - 'change_train_wait_condition_data', - 'clean_cursor_stack', - 'clear_selected_blueprint', - 'clear_selected_deconstruction_item', - 'clear_selected_upgrade_item', - 'connect_rolling_stock', - 'copy', - 'copy_entity_settings', - 'craft', - 'create_blueprint_like', - 'cursor_split', - 'cursor_transfer', - 'custom_input', - 'cycle_blueprint_book_backwards', - 'cycle_blueprint_book_forwards', - 'deconstruct', - 'delete_blueprint_library', - 'delete_blueprint_record', - 'delete_custom_tag', - 'delete_permission_group', - 'destroy_opened_item', - 'disconnect_rolling_stock', - 'drag_train_schedule', - 'drag_train_wait_condition', - 'drop_blueprint_record', - 'drop_item', - 'drop_to_blueprint_book', - 'edit_custom_tag', - 'edit_permission_group', - 'export_blueprint', - 'fast_entity_split', - 'fast_entity_transfer', - 'go_to_train_station', - 'grab_blueprint_record', - 'gui_checked_state_changed', - 'gui_click', - 'gui_confirmed', - 'gui_elem_changed', - 'gui_location_changed', - 'gui_selected_tab_changed', - 'gui_selection_state_changed', - 'gui_switch_state_changed', - 'gui_text_changed', - 'gui_value_changed', - 'import_blueprint', - 'import_blueprint_string', - 'import_permissions_string', - 'inventory_split', - 'inventory_transfer', - 'launch_rocket', - 'lua_shortcut', - 'map_editor_action', - 'market_offer', - 'mod_settings_changed', - 'open_achievements_gui', - 'open_blueprint_library_gui', - 'open_blueprint_record', - 'open_bonus_gui', - 'open_character_gui', - 'open_equipment', - 'open_gui', - 'open_item', - 'open_kills_gui', - 'open_logistic_gui', - 'open_mod_item', - 'open_production_gui', - 'open_technology_gui', - 'open_train_gui', - 'open_train_station_gui', - 'open_trains_gui', - 'open_tutorials_gui', - 'paste_entity_settings', - 'place_equipment', - 'quick_bar_pick_slot', - 'quick_bar_set_selected_page', - 'quick_bar_set_slot', - 'remove_cables', - 'remove_train_station', - 'reset_assembling_machine', - 'rotate_entity', - 'select_area', - 'select_blueprint_entities', - 'select_entity_slot', - 'select_item', - 'select_mapper_slot', - 'select_next_valid_gun', - 'select_tile_slot', - 'set_auto_launch_rocket', - 'set_autosort_inventory', - 'set_behavior_mode', - 'set_car_weapons_control', - 'set_circuit_condition', - 'set_circuit_mode_of_operation', - 'set_deconstruction_item_tile_selection_mode', - 'set_deconstruction_item_trees_and_rocks_only', - 'set_entity_color', - 'set_entity_energy_property', - 'set_filter', - 'set_heat_interface_mode', - 'set_heat_interface_temperature', - 'set_infinity_container_filter_item', - 'set_infinity_container_remove_unfiltered_items', - 'set_infinity_pipe_filter', - 'set_inserter_max_stack_size', - 'set_inventory_bar', - 'set_logistic_filter_item', - 'set_logistic_filter_signal', - 'set_logistic_trash_filter_item', - 'set_request_from_buffers', - 'set_research_finished_stops_game', - 'set_signal', - 'set_single_blueprint_record_icon', - 'set_splitter_priority', - 'set_train_stopped', - 'setup_assembling_machine', - 'setup_blueprint', - 'setup_single_blueprint_record', - 'smart_pipette', - 'stack_split', - 'stack_transfer', - 'start_repair', - 'start_research', - 'start_walking', - 'stop_building_by_moving', - 'switch_connect_to_logistic_network', - 'switch_constant_combinator_state', - 'switch_inserter_filter_mode_state', - 'switch_power_switch_state', - 'switch_to_rename_stop_gui', - 'take_equipment', - 'toggle_deconstruction_item_entity_filter_mode', - 'toggle_deconstruction_item_tile_filter_mode', - 'toggle_driving', - 'toggle_enable_vehicle_logistics_while_moving', - 'toggle_equipment_movement_bonus', - 'toggle_map_editor', - 'toggle_personal_roboport', - 'toggle_show_entity_info', - 'translate_string', - 'undo', - 'upgrade', - 'upgrade_opened_blueprint', - 'use_artillery_remote', - 'use_item', - 'wire_dragging', - 'write_to_console' - } - }, - inventory = { - fields = { - 'fuel', - 'burnt_result', - 'chest', - 'furnace_source', - 'furnace_result', - 'furnace_modules', - 'character_main', - 'character_guns', - 'character_ammo', - 'character_armor', - 'character_vehicle', - 'character_trash', - 'god_main', - 'editor_main', - 'editor_guns', - 'editor_ammo', - 'editor_armor', - 'roboport_robot', - 'roboport_material', - 'robot_cargo', - 'robot_repair', - 'assembling_machine_input', - 'assembling_machine_output', - 'assembling_machine_modules', - 'lab_input', - 'lab_modules', - 'mining_drill_modules', - 'item_main', - 'rocket_silo_rocket', - 'rocket_silo_result', - 'rocket', - 'car_trunk', - 'car_ammo', - 'cargo_wagon', - 'turret_ammo', - 'beacon_modules', - 'character_corpse', - 'artillery_turret_ammo', - 'artillery_wagon_ammo' - } - }, - logistic_member_index = { - fields = { - 'logistic_container', - 'vehicle_storage', - 'character_requester', - 'character_storage', - 'character_provider', - 'generic_on_off_behavior' - } - }, - logistic_mode = { - fields = { - 'active_provider', - 'storage', - 'requester', - 'passive_provider', - 'buffer' - } - }, - mouse_button_type = { - fields = { - 'left', - 'right', - 'middle' - } - }, - rail_connection_direction = { - fields = { - 'left', - 'straight', - 'right' - } - }, - rail_direction = { - fields = { - 'front', - 'back' - } - }, - render_mode = { - fields = { - 'game', - 'chart', - 'chart_zoomed_in' - } - }, - rich_text_setting = { - fields = { - 'enabled', - 'disabled', - 'highlight' - } - }, - riding = { - fields = { - acceleration = { - fields = { - 'nothing', - 'accelerating', - 'braking', - 'reversing' - } - }, - direction = { - fields = { - 'left', - 'straight', - 'right' - } - } - } - }, - shooting = { - fields = { - 'not_shooting', - 'shooting_enemies', - 'shooting_selected' - } - }, - signal_state = { - fields = { - 'open', - 'closed', - 'reserved', - 'reserved_by_circuit_network' - } - }, - train_state = { - fields = { - 'on_the_path', - 'path_lost', - 'no_schedule', - 'no_path', - 'arrive_signal', - 'wait_signal', - 'arrive_station', - 'wait_station', - 'manual_control_stop', - 'manual_control' - } - }, - transport_line = { - fields = { - 'left_line', - 'right_line', - 'left_underground_line', - 'right_underground_line', - 'secondary_left_line', - 'secondary_right_line', - 'left_split_line', - 'right_split_line', - 'secondary_left_split_line', - 'secondary_right_split_line' - } - }, - wire_connection_id = { - fields = { - 'electric_pole', - 'power_switch_left', - 'power_switch_right' - } - }, - wire_type = { - fields = { - 'red', - 'green', - 'copper' - } - }, - -- Defines additional modules - color = { - other_fields = true - }, - anticolor = { - other_fields = true - }, - lightcolor = { - other_fields = true - }, - time = { - fields = { - 'second', - 'minute', - 'hour', - 'day', - 'week', - 'month', - 'year' - } - } - } - } - } - } -end - ---[[ Options - "ignore", "std", "globals", "unused_args", "self", "compat", "global", "unused", "redefined", - "unused_secondaries", "allow_defined", "allow_defined_top", "module", - "read_globals", "new_globals", "new_read_globals", "enable", "only", "not_globals", - "max_line_length", "max_code_line_length", "max_string_line_length", "max_comment_line_length", - "max_cyclomatic_complexity" ---]] - ---[[ Warnings list - -- 011 A syntax error. - -- 021 An invalid inline option. - -- 022 An unpaired inline push directive. - -- 023 An unpaired inline pop directive. - -- 111 Setting an undefined global variable. - -- 112 Mutating an undefined global variable. - -- 113 Accessing an undefined global variable. - -- 121 Setting a read-only global variable. - -- 122 Setting a read-only field of a global variable. - -- 131 Unused implicitly defined global variable. - -- 142 Setting an undefined field of a global variable. - -- 143 Accessing an undefined field of a global variable. - -- 211 Unused local variable. - -- 212 Unused argument. - -- 213 Unused loop variable. - -- 221 Local variable is accessed but never set. - -- 231 Local variable is set but never accessed. - -- 232 An argument is set but never accessed. - -- 233 Loop variable is set but never accessed. - -- 241 Local variable is mutated but never accessed. - -- 311 Value assigned to a local variable is unused. - -- 312 Value of an argument is unused. - -- 313 Value of a loop variable is unused. - -- 314 Value of a field in a table literal is unused. - -- 321 Accessing uninitialized local variable. - -- 331 Value assigned to a local variable is mutated but never accessed. - -- 341 Mutating uninitialized local variable. - -- 411 Redefining a local variable. - -- 412 Redefining an argument. - -- 413 Redefining a loop variable. - -- 421 Shadowing a local variable. - -- 422 Shadowing an argument. - -- 423 Shadowing a loop variable. - -- 431 Shadowing an upvalue. - -- 432 Shadowing an upvalue argument. - -- 433 Shadowing an upvalue loop variable. - -- 511 Unreachable code. - -- 512 Loop can be executed at most once. - -- 521 Unused label. - -- 531 Left-hand side of an assignment is too short. - -- 532 Left-hand side of an assignment is too long. - -- 541 An empty do end block. - -- 542 An empty if branch. - -- 551 An empty statement. - -- 611 A line consists of nothing but whitespace. - -- 612 A line contains trailing whitespace. - -- 613 Trailing whitespace in a string. - -- 614 Trailing whitespace in a comment. - -- 621 Inconsistent indentation (SPACE followed by TAB). - -- 631 Line is too long. ---]] diff --git a/packages/create/templates/plugin-ts/.npmignore b/packages/create/templates/plugin-ts/.npmignore deleted file mode 100644 index ebd99907..00000000 --- a/packages/create/templates/plugin-ts/.npmignore +++ /dev/null @@ -1,2 +0,0 @@ -webpack.config.js -dist/web/build diff --git a/packages/create/templates/plugin-ts/controller.ts b/packages/create/templates/plugin-ts/controller.ts index b833cc50..cab2d3c0 100644 --- a/packages/create/templates/plugin-ts/controller.ts +++ b/packages/create/templates/plugin-ts/controller.ts @@ -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; 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 } diff --git a/packages/create/templates/plugin-ts/ctl.ts b/packages/create/templates/plugin-ts/ctl.ts index db6b9c1b..75c3a1a4 100644 --- a/packages/create/templates/plugin-ts/ctl.ts +++ b/packages/create/templates/plugin-ts/ctl.ts @@ -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({ diff --git a/packages/create/templates/plugin-ts/index.ts b/packages/create/templates/plugin-ts/index.ts index 6db9367c..029a01a7 100644 --- a/packages/create/templates/plugin-ts/index.ts +++ b/packages/create/templates/plugin-ts/index.ts @@ -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 }; diff --git a/packages/create/templates/plugin-ts/instance.ts b/packages/create/templates/plugin-ts/instance.ts index 02fce382..d3b8b763 100644 --- a/packages/create/templates/plugin-ts/instance.ts +++ b/packages/create/templates/plugin-ts/instance.ts @@ -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 } diff --git a/packages/create/templates/plugin-ts/messages.ts b/packages/create/templates/plugin-ts/messages.ts index 5d148bdc..f7efef57 100644 --- a/packages/create/templates/plugin-ts/messages.ts +++ b/packages/create/templates/plugin-ts/messages.ts @@ -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) { return new this(json.updates.map(update => ExampleSubscribableValue.fromJSON(update))); } -}// [] //// [] // +} +//%endif diff --git a/packages/create/templates/plugin-ts/module/module.json b/packages/create/templates/plugin-ts/module/module.json deleted file mode 100644 index 33915ac9..00000000 --- a/packages/create/templates/plugin-ts/module/module.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "// plugin_name //", - "load": [ - "control.lua" - ], - "require": [ - // [instance] //"module_exports.lua"// [] // - ], - "dependencies": [ - - ] -} diff --git a/packages/create/templates/plugin-ts/package.json b/packages/create/templates/plugin-ts/package.json deleted file mode 100644 index f1a25265..00000000 --- a/packages/create/templates/plugin-ts/package.json +++ /dev/null @@ -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" - ] -} diff --git a/packages/create/templates/plugin-ts/tsconfig.browser.json b/packages/create/templates/plugin-ts/tsconfig.browser.json new file mode 100644 index 00000000..e1b39d0f --- /dev/null +++ b/packages/create/templates/plugin-ts/tsconfig.browser.json @@ -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", + } +} diff --git a/packages/create/templates/plugin-ts/tsconfig.json b/packages/create/templates/plugin-ts/tsconfig.json index 087576d6..136e8ab6 100644 --- a/packages/create/templates/plugin-ts/tsconfig.json +++ b/packages/create/templates/plugin-ts/tsconfig.json @@ -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" } + ] } diff --git a/packages/create/templates/plugin-ts/tsconfig.node.json b/packages/create/templates/plugin-ts/tsconfig.node.json new file mode 100644 index 00000000..b6197500 --- /dev/null +++ b/packages/create/templates/plugin-ts/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", + }, +} diff --git a/packages/create/templates/plugin-ts/tsconfig.web.json b/packages/create/templates/plugin-ts/tsconfig.web.json deleted file mode 100644 index c37f9141..00000000 --- a/packages/create/templates/plugin-ts/tsconfig.web.json +++ /dev/null @@ -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", - } -} diff --git a/packages/create/templates/plugin-ts/web/plugin.tsx b/packages/create/templates/plugin-ts/web/plugin.tsx index f1a8c229..ff0b6393 100644 --- a/packages/create/templates/plugin-ts/web/plugin.tsx +++ b/packages/create/templates/plugin-ts/web/plugin.tsx @@ -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 -

// plugin_name //

// [subscribable] // - Synced: {String(synced)} Data: {JSON.stringify([...subscribableData.values()])}// [] // + return +

__plugin_name__

+//%if controller & web // Subscribing requires web content and the controller + Synced: {String(synced)} Data: {JSON.stringify([...subscribableData.values()])} +//%endif
; } -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: , }, ]; 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)); diff --git a/packages/create/templates/plugin-ts/webpack.config.js b/packages/create/templates/plugin-ts/webpack.config.js deleted file mode 100644 index 81e247eb..00000000 --- a/packages/create/templates/plugin-ts/webpack.config.js +++ /dev/null @@ -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 }, - }, - }), - ], -}); diff --git a/packages/create/templates/readmd.md b/packages/create/templates/readmd.md new file mode 100644 index 00000000..7a47444c --- /dev/null +++ b/packages/create/templates/readmd.md @@ -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 | From 283aba70dfcf1ff4fbad3bd11505ecc96f076d5a Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Tue, 27 Feb 2024 23:53:20 +0000 Subject: [PATCH 19/29] Update descriptions --- packages/create/templates/common/package.json | 2 +- packages/create/templates/plugin-js/index.js | 10 +++++----- packages/create/templates/plugin-ts/index.ts | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/create/templates/common/package.json b/packages/create/templates/common/package.json index fc2ee57e..66b20070 100644 --- a/packages/create/templates/common/package.json +++ b/packages/create/templates/common/package.json @@ -1,7 +1,7 @@ { "name": "__plugin_name__", "version": "0.1.0", - "description": "", + "description": "Example Description. Package. Change me in package.json", //%if typescript "main": "dist/node/index.js", //%endif diff --git a/packages/create/templates/plugin-js/index.js b/packages/create/templates/plugin-js/index.js index 035988ce..1a6fb969 100644 --- a/packages/create/templates/plugin-js/index.js +++ b/packages/create/templates/plugin-js/index.js @@ -5,20 +5,20 @@ const Messages = require("./messages"); lib.definePermission({ name: "__plugin_name__.example.permission.event", title: "Example permission event", - description: "My plugin's example permission that I forgot to remove", + description: "Example Description. Event. Change me in index.js", }); lib.definePermission({ name: "__plugin_name__.example.permission.request", title: "Example permission request", - description: "My plugin's example permission that I forgot to remove", + description: "Example Description. Request. Change me in index.js", }); //%if controller & web // Subscribing requires web content and the controller lib.definePermission({ name: "__plugin_name__.example.permission.subscribe", title: "Example permission subscribe", - description: "My plugin's example permission that I forgot to remove", + description: "Example Description. Subscribe. Change me in index.js", }); //%endif //%if web @@ -26,14 +26,14 @@ lib.definePermission({ lib.definePermission({ name: "__plugin_name__.page.view", title: "Example page view permission", - description: "My plugin's example page permission that I forgot to remove", + description: "Example Description. View. Change me in index.js", }); //%endif const plugin = { name: "// plugin_name //", title: "// plugin_name //", - description: "I didn't update my description", + description: "Example Description. Plugin. Change me in index.js", //%if controller controllerEntrypoint: "./dist/node/controller", diff --git a/packages/create/templates/plugin-ts/index.ts b/packages/create/templates/plugin-ts/index.ts index 029a01a7..8a74fa20 100644 --- a/packages/create/templates/plugin-ts/index.ts +++ b/packages/create/templates/plugin-ts/index.ts @@ -4,20 +4,20 @@ import * as Messages from "./messages"; lib.definePermission({ name: "__plugin_name__.example.permission.event", title: "Example permission event", - description: "My plugin's example permission that I forgot to remove", + description: "Example Description. Event. Change me in index.ts", }); lib.definePermission({ name: "__plugin_name__.example.permission.request", title: "Example permission request", - description: "My plugin's example permission that I forgot to remove", + description: "Example Description. Request. Change me in index.ts", }); //%if controller & web // Subscribing requires web content and the controller lib.definePermission({ name: "__plugin_name__.example.permission.subscribe", title: "Example permission subscribe", - description: "My plugin's example permission that I forgot to remove", + description: "Example Description. Subscribe. Change me in index.ts", }); //%endif //%if web @@ -25,7 +25,7 @@ lib.definePermission({ lib.definePermission({ name: "__plugin_name__.page.view", title: "Example page view permission", - description: "My plugin's example page permission that I forgot to remove", + description: "Example Description. View. Change me in index.ts", }); //%endif @@ -55,7 +55,7 @@ declare module "@clusterio/lib" { export const plugin: lib.PluginDeclaration = { name: "__plugin_name__", title: "__plugin_name__", - description: "I didn't update my description", + description: "Example Description. Plugin. Change me in index.ts", //%if controller controllerEntrypoint: "./dist/node/controller", From a168f7db5ee5d58600cb1a5805697a22e0e786d5 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Tue, 27 Feb 2024 23:57:13 +0000 Subject: [PATCH 20/29] Add call to action for config desc --- packages/create/templates/plugin-js/index.js | 8 ++++---- packages/create/templates/plugin-ts/index.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/create/templates/plugin-js/index.js b/packages/create/templates/plugin-js/index.js index 1a6fb969..53392577 100644 --- a/packages/create/templates/plugin-js/index.js +++ b/packages/create/templates/plugin-js/index.js @@ -40,7 +40,7 @@ const plugin = { controllerConfigFields: { "__plugin_name__.myControllerField": { title: "My Controller Field", - description: "This should be removed", + description: "This should be removed from index.js", type: "string", initialValue: "Remove Me", }, @@ -52,7 +52,7 @@ const plugin = { hostConfigFields: { "__plugin_name__.myHostField": { title: "My Host Field", - description: "This should be removed", + description: "This should be removed from index.js", type: "string", initialValue: "Remove Me", }, @@ -64,7 +64,7 @@ const plugin = { instanceConfigFields: { "__plugin_name__.myInstanceField": { title: "My Instance Field", - description: "This should be removed", + description: "This should be removed from index.js", type: "string", initialValue: "Remove Me", }, @@ -76,7 +76,7 @@ const plugin = { controlConfigFields: { "__plugin_name__.myControlField": { title: "My Control Field", - description: "This should be removed", + description: "This should be removed from index.js", type: "string", initialValue: "Remove Me", }, diff --git a/packages/create/templates/plugin-ts/index.ts b/packages/create/templates/plugin-ts/index.ts index 8a74fa20..dc913106 100644 --- a/packages/create/templates/plugin-ts/index.ts +++ b/packages/create/templates/plugin-ts/index.ts @@ -62,7 +62,7 @@ export const plugin: lib.PluginDeclaration = { controllerConfigFields: { "__plugin_name__.myControllerField": { title: "My Controller Field", - description: "This should be removed", + description: "This should be removed from index.js", type: "string", initialValue: "Remove Me", }, @@ -74,7 +74,7 @@ export const plugin: lib.PluginDeclaration = { hostConfigFields: { "__plugin_name__.myHostField": { title: "My Host Field", - description: "This should be removed", + description: "This should be removed from index.js", type: "string", initialValue: "Remove Me", }, @@ -86,7 +86,7 @@ export const plugin: lib.PluginDeclaration = { instanceConfigFields: { "__plugin_name__.myInstanceField": { title: "My Instance Field", - description: "This should be removed", + description: "This should be removed from index.js", type: "string", initialValue: "Remove Me", }, @@ -98,7 +98,7 @@ export const plugin: lib.PluginDeclaration = { controlConfigFields: { "__plugin_name__.myControlField": { title: "My Control Field", - description: "This should be removed", + description: "This should be removed from index.js", type: "string", initialValue: "Remove Me", }, From 8dcb2197d6ea5c9a9d7a5283bee3111739f68ba5 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Wed, 28 Feb 2024 15:25:08 +0000 Subject: [PATCH 21/29] Use try catch for output file existing --- packages/create/template.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/create/template.js b/packages/create/template.js index 8a998169..51c462ce 100644 --- a/packages/create/template.js +++ b/packages/create/template.js @@ -6,12 +6,6 @@ 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)) { @@ -21,7 +15,7 @@ async function copyTemplateFile(src, dst, properties) { return properties[property]; } - // Get the boolean conjunction of a set of properties + // Closure, Get the boolean conjunction of a set of properties function propertyConjunction(props) { for (const property of props) { if (property.startsWith("!")) { @@ -43,8 +37,8 @@ async function copyTemplateFile(src, dst, properties) { // 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 [// comment]\n \n //%endif [comment]\n + .replace(/__([a-zA-Z][a-zA-Z0-9_]*?[a-zA-Z0-9]?)__/gm, (_, property) => getProperty(property)) + // match preprocessor conditionals: //%if [// COMMENT]\n \n//%endif [COMMENT]\n .replace(/^\/\/%if ([^\n\/]*)(?:\/\/.*?)?\n((?:.|\n)*?)\/\/%endif.*\n/gm, (_, condition, content) => { let include = false; const conjunctions = condition.split("|"); @@ -65,7 +59,16 @@ async function copyTemplateFile(src, dst, properties) { return include ? content : ""; }); - await fs.outputFile(dst, outputData); + // Attempt to write the output file, warn if it already exists + try { + await fs.outputFile(dst, outputData); + } catch (err) { + if (err.code === "EEXIST") { + logger.warn(`Could not create file ${dst} because it already exists`); + } else { + throw err; + } + } } async function copyPluginTemplates(pluginName, templates) { From 9a4241e4cde61a5dfb93825106790da862139f2c Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Wed, 28 Feb 2024 16:26:53 +0000 Subject: [PATCH 22/29] Update flags for output file Co-authored-by: Hornwitser --- packages/create/template.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create/template.js b/packages/create/template.js index 51c462ce..8719fc27 100644 --- a/packages/create/template.js +++ b/packages/create/template.js @@ -61,7 +61,7 @@ async function copyTemplateFile(src, dst, properties) { // Attempt to write the output file, warn if it already exists try { - await fs.outputFile(dst, outputData); + await fs.outputFile(dst, outputData, { flag: "wx" }); } catch (err) { if (err.code === "EEXIST") { logger.warn(`Could not create file ${dst} because it already exists`); From 3adfcf0a290adabc31a20c2609573070403ac986 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Wed, 28 Feb 2024 16:36:35 +0000 Subject: [PATCH 23/29] Updated web content permission --- packages/create/templates/plugin-ts/web/plugin.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/create/templates/plugin-ts/web/plugin.tsx b/packages/create/templates/plugin-ts/web/plugin.tsx index ff0b6393..df32127d 100644 --- a/packages/create/templates/plugin-ts/web/plugin.tsx +++ b/packages/create/templates/plugin-ts/web/plugin.tsx @@ -47,7 +47,14 @@ 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 & web // Subscribing requires web content and the controller + permission: "__plugin_name__.example.permission.subscribe", +//%endif +//%if !controller | !web + permission: undefined, // "__plugin_name__.example.permission.request", +//%endif content: , }, ]; From a66b1af454345ff2ddd00780a179ba0455d163bb Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Tue, 5 Mar 2024 00:56:01 +0000 Subject: [PATCH 24/29] Extra touch ups --- packages/create/template.js | 3 +++ packages/create/templates/common/.luacheckrc | 8 -------- packages/create/templates/common/module/globals.lua | 3 +++ packages/create/templates/common/module/module.json | 2 +- .../templates/common/module/module_exports.lua | 13 +++---------- packages/create/templates/plugin-js/instance.js | 2 +- packages/create/templates/plugin-ts/instance.ts | 2 +- 7 files changed, 12 insertions(+), 21 deletions(-) create mode 100644 packages/create/templates/common/module/globals.lua diff --git a/packages/create/template.js b/packages/create/template.js index 8719fc27..71ce6734 100644 --- a/packages/create/template.js +++ b/packages/create/template.js @@ -131,6 +131,9 @@ async function copyPluginTemplates(pluginName, templates) { 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")); + if (templates.includes("instance")) { + files.set("module/globals.lua", path.join(commonPath, "module/globals.lua")); + } } // Files for the control tool diff --git a/packages/create/templates/common/.luacheckrc b/packages/create/templates/common/.luacheckrc index cacf598d..9b043265 100644 --- a/packages/create/templates/common/.luacheckrc +++ b/packages/create/templates/common/.luacheckrc @@ -70,14 +70,6 @@ do -- Assume Factorio Control Stage as Default } end -do -- RedMew and ExpGaming overrides - globals = { - 'math', 'table', 'package', - 'print', 'require', 'unpack', 'inspect', 'loadstring', 'ServerCommands', 'Debug', - '_C', '_DEBUG', '_CHEATS', '_DUMP_ENV', '_LIFECYCLE', '_STAGE', - } -end - do -- Set default prototype files files['**/data.lua'].std = STD_DATA files['**/data-updates.lua'].std = STD_DATA diff --git a/packages/create/templates/common/module/globals.lua b/packages/create/templates/common/module/globals.lua new file mode 100644 index 00000000..b4d0bea0 --- /dev/null +++ b/packages/create/templates/common/module/globals.lua @@ -0,0 +1,3 @@ +--- Expose any globals here, typically just your control file if you want to access it through rcon or commands +-- luacheck: globals __plugin_name__ +__plugin_name__ = require("modules/__plugin_name__/control") diff --git a/packages/create/templates/common/module/module.json b/packages/create/templates/common/module/module.json index 60a1ddbe..9df0e849 100644 --- a/packages/create/templates/common/module/module.json +++ b/packages/create/templates/common/module/module.json @@ -5,7 +5,7 @@ ], "require": [ //%if instance - "module_exports.lua" + "globals.lua" //%endif ], "dependencies": { diff --git a/packages/create/templates/common/module/module_exports.lua b/packages/create/templates/common/module/module_exports.lua index 6235edab..d8dd0ae4 100644 --- a/packages/create/templates/common/module/module_exports.lua +++ b/packages/create/templates/common/module/module_exports.lua @@ -1,10 +1,3 @@ -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 -//%endif - ---- Can contain anything you want to allow other plugins to have access to, this example exposes the whole module -return MyModule +--- Can contain anything you want to allow other plugins to have access to, this example exposes the control file +-- Access the exports from other modules using require("modules/__plugin_name__") +return require("modules/__plugin_name__/control") diff --git a/packages/create/templates/plugin-js/instance.js b/packages/create/templates/plugin-js/instance.js index a62f5520..92fff909 100644 --- a/packages/create/templates/plugin-js/instance.js +++ b/packages/create/templates/plugin-js/instance.js @@ -31,7 +31,7 @@ class InstancePlugin extends BaseInstancePlugin { async handlePluginExampleEvent(event) { this.logger.info(JSON.stringify(event)); //%if module - this.sendRcon("/sc ipc___plugin_name__.foo()"); + this.sendRcon("/sc __plugin_name__.foo()"); //%endif } diff --git a/packages/create/templates/plugin-ts/instance.ts b/packages/create/templates/plugin-ts/instance.ts index d3b8b763..335304ac 100644 --- a/packages/create/templates/plugin-ts/instance.ts +++ b/packages/create/templates/plugin-ts/instance.ts @@ -33,7 +33,7 @@ export class InstancePlugin extends BaseInstancePlugin { async onPlayerEvent(event: lib.PlayerEvent) { this.logger.info(`onPlayerEvent::onPlayerEvent ${JSON.stringify(event)}`); //%if module - this.sendRcon("/sc ipc___plugin_name__.foo()"); + this.sendRcon("/sc __plugin_name__.foo()"); //%endif } From 5dcc66034bdd7bdf7fed226b9cc96f6fc9788976 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:11:45 +0000 Subject: [PATCH 25/29] Fix possible undefined title error (#605) --- packages/web_ui/src/components/SavesList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web_ui/src/components/SavesList.tsx b/packages/web_ui/src/components/SavesList.tsx index 92ce287c..2d6f2c69 100644 --- a/packages/web_ui/src/components/SavesList.tsx +++ b/packages/web_ui/src/components/SavesList.tsx @@ -150,7 +150,7 @@ function TransferModal(props: ModalProps) { autoFocus showSearch filterOption={(input, option) => ( - (option?.title.toLowerCase().indexOf(input.toLowerCase())??-1) >= 0 + (option?.title!.toLowerCase().indexOf(input.toLowerCase())??-1) >= 0 )} > {[...instances.values()].filter( From 7625fd82e353e6cce420b625ebdbc0d67786ee43 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Wed, 6 Mar 2024 18:30:45 +0100 Subject: [PATCH 26/29] Cache ModInfo to reduce startup time (#606) Store and use a cache of the loaded ModInfo data to mod-info-cache.json in the mods directory during startup. For simplicity this does not cache mods added to the controller or host after startup, these get added to the cache on the next startup. --- packages/lib/src/ModStore.ts | 36 ++++++++++++++++++++++++++++++++ packages/lib/src/data/ModInfo.ts | 9 ++++++++ test/integration/clusterio.js | 1 + test/lib/data/ModInfo.js | 3 +++ 4 files changed, 49 insertions(+) diff --git a/packages/lib/src/ModStore.ts b/packages/lib/src/ModStore.ts index 1819e645..e53fdb39 100644 --- a/packages/lib/src/ModStore.ts +++ b/packages/lib/src/ModStore.ts @@ -1,8 +1,10 @@ import fs from "fs-extra"; import path from "path"; +import { Static } from "@sinclair/typebox"; import { logger } from "./logging"; import { ModInfo } from "./data"; import TypedEventEmitter from "./TypedEventEmitter"; +import { safeOutputFile } from "./file_ops"; export interface ModStoreEvents { /** A stored mod was created, updated or deleted */ @@ -74,6 +76,21 @@ export default class ModStore extends TypedEventEmitter[]; + const cacheFilename = path.join(modsDirectory, "mod-info-cache.json"); + let cache = new Map(); + try { + const content = await fs.readFile(cacheFilename, "utf8"); + cache = new Map((JSON.parse(content) as ModInfoCache).map(item => { + const mod = ModInfo.fromJSON(item); + return [mod.filename, mod]; + })); + } catch (err: any) { + if (err.code !== "ENOENT") { + logger.warn(`Error loading ${cacheFilename}:\n${err.stack}`); + } + } + const files = new Map(); for (let entry of await fs.readdir(modsDirectory, { withFileTypes: true })) { if (entry.isDirectory()) { @@ -81,10 +98,23 @@ export default class ModStore extends TypedEventEmitter mod.toJSON()); + await safeOutputFile(cacheFilename, JSON.stringify(content)); + } + return new this(modsDirectory, files); } } diff --git a/packages/lib/src/data/ModInfo.ts b/packages/lib/src/data/ModInfo.ts index b555f0bb..e29db8d3 100644 --- a/packages/lib/src/data/ModInfo.ts +++ b/packages/lib/src/data/ModInfo.ts @@ -116,6 +116,11 @@ export default class ModInfo { */ size = 0; + /** + * Last modification time of the file. + */ + mtimeMs = 0; + /** * SHA1 hash of this mod */ @@ -147,6 +152,7 @@ export default class ModInfo { static jsonSchema = Type.Object({ ...this.infoJsonSchema.properties, "size": Type.Optional(Type.Integer()), + "mtime_ms": Type.Optional(Type.Number()), "sha1": Type.Optional(Type.String()), "updated_at_ms": Type.Optional(Type.Number()), "is_deleted": Type.Optional(Type.Boolean()), @@ -170,6 +176,7 @@ export default class ModInfo { // Additional data if (json.size) { modInfo.size = json.size; } + if (json.mtime_ms) { modInfo.mtimeMs = json.mtime_ms; } if (json.sha1) { modInfo.sha1 = json.sha1; } if (json.updated_at_ms) { modInfo.updatedAtMs = json.updated_at_ms; } if (json.is_deleted) { modInfo.isDeleted = json.is_deleted; } @@ -192,6 +199,7 @@ export default class ModInfo { json.dependencies = this.dependencies; } if (this.size) { json.size = this.size; } + if (this.mtimeMs) { json.mtime_ms = this.mtimeMs; } if (this.sha1) { json.sha1 = this.sha1; } if (this.updatedAtMs) { json.updated_at_ms = this.updatedAtMs; } if (this.isDeleted) { json.is_deleted = this.isDeleted; } @@ -229,6 +237,7 @@ export default class ModInfo { size: stat.size, sha1: await libHash.hashFile(modPath), + mtime_ms: stat.mtimeMs, updated_at_ms: stat.mtimeMs, is_deleted: false, }); diff --git a/test/integration/clusterio.js b/test/integration/clusterio.js index 4fe1f626..acf69b94 100644 --- a/test/integration/clusterio.js +++ b/test/integration/clusterio.js @@ -1054,6 +1054,7 @@ describe("Integration of Clusterio", function() { "factorioVersion: 1.1\n" + "dependencies:\n" + `size: ${stat.size}\n` + + `mtimeMs: ${stat.mtimeMs}\n` + `sha1: ${hash}\n` + `updatedAtMs: ${stat.mtimeMs}\n` + "isDeleted: false\n", diff --git a/test/lib/data/ModInfo.js b/test/lib/data/ModInfo.js index 50e104db..9e8d6c65 100644 --- a/test/lib/data/ModInfo.js +++ b/test/lib/data/ModInfo.js @@ -33,6 +33,7 @@ describe("lib/data/ModInfo", function() { check(ModInfo.fromJSON({ dependencies: ["UltraMod", "SuperLib >= 1.00", "! bad-mod"] })); check(ModInfo.fromJSON({ filename: "MyMod_1.0.0.zip" })); check(ModInfo.fromJSON({ size: 1024 })); + check(ModInfo.fromJSON({ mtime_ms: new Date(2024, 0, 1).getTime() })); check(ModInfo.fromJSON({ sha1: "verified-as-MyMod" })); check(ModInfo.fromJSON({ is_deleted: true })); @@ -49,6 +50,7 @@ describe("lib/data/ModInfo", function() { dependencies: ["UltraMod", "SuperLib >= 1.00", "! bad-mod"], filename: "MyMod_1.0.0.zip", size: 1024, + mtime_ms: new Date(2024, 0, 1).getTime(), sha1: "verified-as-MyMod", is_deleted: true, })); @@ -92,6 +94,7 @@ describe("lib/data/ModInfo", function() { dependencies: [], filename: "empty_mod_1.0.0.zip", size: stat.size, + mtime_ms: stat.mtimeMs, updated_at_ms: stat.mtimeMs, sha1: hash, }) From 3423e0a30c1840f07bbae75af7d7dae16a0759d6 Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 8 Mar 2024 20:08:16 +0000 Subject: [PATCH 27/29] Apply suggestions from code review - Copy paste errors. - Incorrect capitalisation. - Incorrect exports. - Missed find and replace. Co-authored-by: Hornwitser --- packages/create/templates/common/webpack.config.js | 2 +- packages/create/templates/plugin-js/index.js | 12 ++++++------ packages/create/templates/plugin-js/messages.js | 6 +++--- packages/create/templates/plugin-js/web/plugin.jsx | 6 +----- packages/create/templates/plugin-ts/messages.ts | 6 +++--- 5 files changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/create/templates/common/webpack.config.js b/packages/create/templates/common/webpack.config.js index bda4c382..bf153d04 100644 --- a/packages/create/templates/common/webpack.config.js +++ b/packages/create/templates/common/webpack.config.js @@ -14,7 +14,7 @@ module.exports = (env = {}) => merge(common(env), { plugins: [ new webpack.container.ModuleFederationPlugin({ name: "__plugin_name__", - library: { type: "var", name: "plugin___plugin_name__" }, + library: { type: "window", name: "plugin___plugin_name__" }, exposes: { "./": "./index.__ext__", "./package.json": "./package.json", diff --git a/packages/create/templates/plugin-js/index.js b/packages/create/templates/plugin-js/index.js index 53392577..ff4710df 100644 --- a/packages/create/templates/plugin-js/index.js +++ b/packages/create/templates/plugin-js/index.js @@ -31,12 +31,12 @@ lib.definePermission({ //%endif const plugin = { - name: "// plugin_name //", - title: "// plugin_name //", + name: "__plugin_name__", + title: "__plugin_name__", description: "Example Description. Plugin. Change me in index.js", //%if controller - controllerEntrypoint: "./dist/node/controller", + controllerEntrypoint: "./controller", controllerConfigFields: { "__plugin_name__.myControllerField": { title: "My Controller Field", @@ -48,7 +48,7 @@ const plugin = { //%endif //%if host - hostEntrypoint: "./dist/node/host", + hostEntrypoint: "./host", hostConfigFields: { "__plugin_name__.myHostField": { title: "My Host Field", @@ -60,7 +60,7 @@ const plugin = { //%endif //%if instance - instanceEntrypoint: "./dist/node/instance", + instanceEntrypoint: "./instance", instanceConfigFields: { "__plugin_name__.myInstanceField": { title: "My Instance Field", @@ -72,7 +72,7 @@ const plugin = { //%endif //%if ctl - ctlEntrypoint: "./dist/node/ctl", + ctlEntrypoint: "./ctl", controlConfigFields: { "__plugin_name__.myControlField": { title: "My Control Field", diff --git a/packages/create/templates/plugin-js/messages.js b/packages/create/templates/plugin-js/messages.js index ac982267..aaffd9f6 100644 --- a/packages/create/templates/plugin-js/messages.js +++ b/packages/create/templates/plugin-js/messages.js @@ -19,7 +19,7 @@ class PluginExampleEvent { "myNumberArray": Type.Array(Type.Number()), }); - static fromJson(json) { + static fromJSON(json) { return new PluginExampleEvent(json.myString, json.myNumberArray); } } @@ -41,8 +41,8 @@ class PluginExampleRequest { "myNumberArray": Type.Array(Type.Number()), }); - static fromJson(json) { - return new PluginExampleEvent(json.myString, json.myNumberArray); + static fromJSON(json) { + return new PluginExampleRequest(json.myString, json.myNumberArray); } static Response = plainJson(Type.Object({ diff --git a/packages/create/templates/plugin-js/web/plugin.jsx b/packages/create/templates/plugin-js/web/plugin.jsx index db095219..9cc5942e 100644 --- a/packages/create/templates/plugin-js/web/plugin.jsx +++ b/packages/create/templates/plugin-js/web/plugin.jsx @@ -37,7 +37,7 @@ function MyTemplatePage() {
; } -class WebPlugin extends BaseWebPlugin { +export class WebPlugin extends BaseWebPlugin { //%if controller & web // Subscribing requires web content and the controller subscribableData = new lib.EventSubscriber(ExampleSubscribableUpdate, this.control); @@ -76,7 +76,3 @@ class WebPlugin extends BaseWebPlugin { }; } } - -module.exports = { - WebPlugin, -}; diff --git a/packages/create/templates/plugin-ts/messages.ts b/packages/create/templates/plugin-ts/messages.ts index f7efef57..c0514e33 100644 --- a/packages/create/templates/plugin-ts/messages.ts +++ b/packages/create/templates/plugin-ts/messages.ts @@ -20,7 +20,7 @@ export class PluginExampleEvent { "myNumberArray": Type.Array(Type.Number()), }); - static fromJson(json: Static) { + static fromJSON(json: Static) { return new PluginExampleEvent(json.myString, json.myNumberArray); } } @@ -44,8 +44,8 @@ export class PluginExampleRequest { "myNumberArray": Type.Array(Type.Number()), }); - static fromJson(json: Static) { - return new PluginExampleEvent(json.myString, json.myNumberArray); + static fromJSON(json: Static) { + return new PluginExampleRequest(json.myString, json.myNumberArray); } static Response = plainJson(Type.Object({ From 6f81216e7c04ef7d26685fe3949534f8fa29d9bd Mon Sep 17 00:00:00 2001 From: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com> Date: Fri, 8 Mar 2024 22:00:38 +0000 Subject: [PATCH 28/29] Addressed remaining review comments --- docs/writing-plugins.md | 14 ++++++++++++++ packages/create/templates/plugin-js/index.js | 9 ++++++++- packages/create/templates/plugin-js/instance.js | 2 +- packages/create/templates/plugin-ts/index.ts | 9 ++++++++- packages/create/templates/plugin-ts/instance.ts | 2 +- packages/host/src/server.ts | 2 +- 6 files changed, 33 insertions(+), 5 deletions(-) diff --git a/docs/writing-plugins.md b/docs/writing-plugins.md index dd9e97a6..faf88a5f 100644 --- a/docs/writing-plugins.md +++ b/docs/writing-plugins.md @@ -301,6 +301,20 @@ local clusterio_api = require("modules/clusterio/api") clusterio_api.send_json("my_plugin_foo", { data = 123 }) ``` +For convience a helper function is exposed on server specifically for handling IPC events. + +```js +async init() { + this.instance.server.handle("my_plugin_foo", this.handleFoo.bind(this)); +} + +async handleFoo(content) { + // Do stuff with content +} +``` + +**Note:** This only handles IPC events, all other events must use `on`. You should not include the `ipc-` prefix when using this method. Error logging is handled for you, if you want custom error handling use `on`. + It's recommended to either use the plugin name as the channel name or to prefix the channel name with the name of the plugin if you need multiple channels. It's also important to catch any errors that might occur as they will otherwise be propogated to the instance code and kill the server. diff --git a/packages/create/templates/plugin-js/index.js b/packages/create/templates/plugin-js/index.js index 53392577..765ded4f 100644 --- a/packages/create/templates/plugin-js/index.js +++ b/packages/create/templates/plugin-js/index.js @@ -90,7 +90,14 @@ const plugin = { Messages.ExampleSubscribableUpdate, //%endif ], -//%if controller | web // The controller always includes web entry even if there is no content +//%if web // Web content template has an example route which is the plugin name + + webEntrypoint: "./web", + routes: [ + "/__plugin_name__", + ], +//%endif +//%if controller & !web // The controller always includes web entry even if there is no content webEntrypoint: "./web", routes: [], diff --git a/packages/create/templates/plugin-js/instance.js b/packages/create/templates/plugin-js/instance.js index 92fff909..d3bfa189 100644 --- a/packages/create/templates/plugin-js/instance.js +++ b/packages/create/templates/plugin-js/instance.js @@ -8,7 +8,7 @@ class InstancePlugin extends BaseInstancePlugin { this.instance.handle(PluginExampleEvent, this.handlePluginExampleEvent.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)); + this.instance.server.handle("__plugin_name__-plugin_example_ipc", this.handlePluginExampleIPC.bind(this)); //%endif } diff --git a/packages/create/templates/plugin-ts/index.ts b/packages/create/templates/plugin-ts/index.ts index dc913106..d90f22be 100644 --- a/packages/create/templates/plugin-ts/index.ts +++ b/packages/create/templates/plugin-ts/index.ts @@ -112,7 +112,14 @@ export const plugin: lib.PluginDeclaration = { Messages.ExampleSubscribableUpdate, //%endif ], -//%if controller | web // The controller always includes web entry even if there is no content +//%if web // Web content template has an example route which is the plugin name + + webEntrypoint: "./web", + routes: [ + "/__plugin_name__", + ], +//%endif +//%if controller & !web // The controller always includes web entry even if there is no content webEntrypoint: "./web", routes: [], diff --git a/packages/create/templates/plugin-ts/instance.ts b/packages/create/templates/plugin-ts/instance.ts index 335304ac..3cb7d18a 100644 --- a/packages/create/templates/plugin-ts/instance.ts +++ b/packages/create/templates/plugin-ts/instance.ts @@ -14,7 +14,7 @@ export class InstancePlugin extends BaseInstancePlugin { this.instance.handle(PluginExampleEvent, this.handlePluginExampleEvent.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)); + this.instance.server.handle("__plugin_name__-plugin_example_ipc", this.handlePluginExampleIPC.bind(this)); //%endif } diff --git a/packages/host/src/server.ts b/packages/host/src/server.ts index b2476ca8..e61f91c0 100644 --- a/packages/host/src/server.ts +++ b/packages/host/src/server.ts @@ -564,7 +564,7 @@ export class FactorioServer extends events.EventEmitter { // TODO at the moment this is just a helper function, it has the potential for schema checking handle(eventName: string, handler: (event: Event) => Promise) { - this.on(eventName, (event) => handler(event).catch(err => { + this.on(`ipc-${eventName}`, (event) => handler(event).catch(err => { this._logger.error(`Error handling ipc event:\n${err.stack}`); })); } From 79c1a2dc99da6eac79f54b3d55715d9e4f354c15 Mon Sep 17 00:00:00 2001 From: Hornwitser Date: Sun, 10 Mar 2024 00:43:38 +0100 Subject: [PATCH 29/29] Fix plugin hooks called before host removal (#607) Due a bad order of operations when a host connection was closed the event handler listening on "close" to remove the host from the hostConnections mapping was added after the HostConnection's event handler listening on "close" and invoking plugin hooks in response to the host going offline. This in turn causes plugins that send broadcasts in repsonse to thos hooks to throw an error due to the broadcast sending code assuming connections that exist are valid target to send messages to. Fix by ordering the listener for the removal of the host connection before the listener invoking plugin events. --- CHANGELOG.md | 1 + packages/controller/src/WsServer.ts | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a15e75b4..31908653 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Many thanks to the following for contributing to this release: ### Changes - Fixed Inventory Sync reporting a negative database size [ยค598](https://github.com/clusterio/clusterio/pull/598). +- Fixed plugin events being invoked while a host connection was in an invalid state. Many thanks to the following for contributing to this release: [@Cooldude2606](https://github.com/Cooldude2606) diff --git a/packages/controller/src/WsServer.ts b/packages/controller/src/WsServer.ts index 50c37d9c..05576336 100644 --- a/packages/controller/src/WsServer.ts +++ b/packages/controller/src/WsServer.ts @@ -284,7 +284,9 @@ ${err.stack}` ); } - connection = new HostConnection(data, connector, this.controller, this.remoteAddr(req)); + // Handle close before HostConnection does. This avoids a host + // connection event happening between the connection breaking and + // it being removed from the host connections list. connector.on("close", () => { if (this.hostConnections.get(data.id) === connection) { this.hostConnections.delete(data.id); @@ -292,6 +294,8 @@ ${err.stack}` logger.warn("Unlisted HostConnection closed"); } }); + + connection = new HostConnection(data, connector, this.controller, this.remoteAddr(req)); this.hostConnections.set(data.id, connection); let src = new lib.Address(lib.Address.host, data.id); connector.ready(socket, src, sessionToken, undefined); @@ -331,10 +335,10 @@ ${err.stack}` let [connector, sessionToken] = this.createSession(new lib.Address(lib.Address.control, id)); logger.verbose(`WsServer | registered control ${id} from ${this.remoteAddr(req)}`); - let connection = new ControlConnection(data, connector, this.controller, user, id); connector.on("close", () => { this.controlConnections.delete(id); }); + let connection = new ControlConnection(data, connector, this.controller, user, id); this.controlConnections.set(id, connection); let account = new lib.AccountDetails( user.name,