Refactored UPS monitor as clusterio plugin (#398)

* Refactor server ups

* Use catalogs

* Move to own plugin

* Use web config

* Remove External.get_server_ups

* Update workspace version requirement

* Remove need for storage

* Add locale

* Fix CI
This commit is contained in:
Cooldude2606
2025-08-08 16:36:22 +01:00
committed by GitHub
parent 69909e3202
commit ce80ae9021
34 changed files with 4620 additions and 167 deletions

30
exp_server_ups/index.ts Normal file
View File

@@ -0,0 +1,30 @@
import * as lib from "@clusterio/lib";
declare module "@clusterio/lib" {
export interface InstanceConfigFields {
"exp_server_ups.update_interval": number;
"exp_server_ups.average_interval": number;
}
}
export const plugin: lib.PluginDeclaration = {
name: "exp_server_ups",
title: "ExpGaming - Server UPS",
description: "Clusterio plugin providing in game server ups counter",
instanceEntrypoint: "./dist/node/instance",
instanceConfigFields: {
"exp_server_ups.update_interval": {
title: "Update Interval",
description: "Frequency at which updates are exchanged with factorio (ms)",
type: "number",
initialValue: 1000,
},
"exp_server_ups.average_interval": {
title: "Average Interval",
description: "Number of update intervals to average updates per second across",
type: "number",
initialValue: 60
},
},
};

View File

@@ -0,0 +1,48 @@
import * as lib from "@clusterio/lib";
import { BaseInstancePlugin } from "@clusterio/host";
export class InstancePlugin extends BaseInstancePlugin {
private updateInterval?: ReturnType<typeof setInterval>;
private gameTimes: number[] = [];
async onStart() {
this.updateInterval = setInterval(this.updateUps.bind(this), this.instance.config.get("exp_server_ups.update_interval"));
}
async onStop() {
if (this.updateInterval) {
clearInterval(this.updateInterval);
}
}
async onInstanceConfigFieldChanged(field: string, curr: unknown): Promise<void> {
if (field === "exp_server_ups.update_interval") {
await this.onStop();
await this.onStart();
} else if (field === "exp_server_ups.average_interval") {
this.gameTimes.splice(curr as number);
}
}
async updateUps() {
let ups = 0;
const collected = this.gameTimes.length - 1;
if (collected > 0) {
const minTick = this.gameTimes[0];
const maxTick = this.gameTimes[collected];
const interval = this.instance.config.get("exp_server_ups.update_interval") / 1000;
ups = (maxTick - minTick) / (collected * interval);
}
try {
const newGameTime = await this.sendRcon(`/_rcon return exp_server_ups.update(${ups})`);
this.gameTimes.push(Number(newGameTime));
} catch (error: any) {
this.logger.error(`Failed to receive new game time: ${error}`);
}
if (collected > this.instance.config.get("exp_server_ups.average_interval")) {
this.gameTimes.shift();
}
}
}

View File

@@ -0,0 +1,95 @@
--[[-- Gui - Server UPS
Adds a server ups counter in the top right corner and a command to toggle it
]]
local Gui = require("modules/exp_gui")
local ExpUtil = require("modules/exp_util")
local Commands = require("modules/exp_commands")
--- Label to show the server ups, drawn to screen on join
local server_ups = Gui.element("server_ups")
:track_all_elements()
:draw{
type = "label",
name = Gui.property_from_name,
}
:style{
font = "default-game",
}
:player_data(function(def, element)
local player = Gui.get_player(element)
local existing = def.data[player]
if not existing or not existing.valid then
return element -- Only set if no previous
end
end)
--- Update the caption for all online players
--- @param ups number The UPS to be displayed
local function update_server_ups(ups)
local caption = ("%.1f (%.1f%%)"):format(ups, ups * 5 / 3)
for _, element in server_ups:online_elements() do
element.caption = caption
end
end
--- Stores the visible state of server ups element for a player
local PlayerData = require("modules/exp_legacy/expcore/player_data")
local UsesServerUps = PlayerData.Settings:combine("UsesServerUps")
UsesServerUps:set_default(false)
UsesServerUps:set_metadata{
permission = "command/server-ups",
stringify = function(value) return value and "Visible" or "Hidden" end,
}
--- Change the visible state when your data loads
UsesServerUps:on_load(function(player_name, visible)
local player = assert(game.get_player(player_name))
server_ups.data[player].visible = visible or false
end)
--- Toggles if the server ups is visbile
Commands.new("server-ups", { "exp_server-ups.description" })
:add_aliases{ "sups", "ups" }
:register(function(player)
local visible = not UsesServerUps:get(player)
server_ups.data[player].visible = visible
UsesServerUps:set(player, visible)
end)
--- Add an interface which can be called from rcon
Commands.add_rcon_static("exp_server_ups", {
update = function(ups)
ExpUtil.assert_argument_type(ups, "number", 1, "ups")
update_server_ups(ups)
return game.tick
end
})
--- Set the location of the label
local function set_location(event)
local player = game.players[event.player_index]
local element = server_ups.data[player]
if not element then
element = server_ups(player.gui.screen)
element.visible = UsesServerUps:get(player)
end
local uis = player.display_scale
local res = player.display_resolution
element.location = { x = res.width - 363 * uis, y = 31 * uis } -- below ups and clock
end
local e = defines.events
return {
elements = {
server_ups = server_ups,
},
events = {
[e.on_player_created] = set_location,
[e.on_player_joined_game] = set_location,
[e.on_player_display_resolution_changed] = set_location,
[e.on_player_display_scale_changed] = set_location,
},
}

View File

@@ -0,0 +1,2 @@
[exp_server-ups]
description=Toggle the server UPS display.

View File

@@ -0,0 +1,2 @@
[exp_server-ups]
description=啟動 UPS 顯示

View File

@@ -0,0 +1,2 @@
[exp_server-ups]
description=啟動 UPS 顯示

View File

@@ -0,0 +1,14 @@
{
"name": "exp_server_ups",
"load": [
"control.lua"
],
"require": [
],
"dependencies": {
"clusterio": "*",
"exp_commands": "*",
"exp_util": "*",
"exp_gui": "*"
}
}

View File

@@ -0,0 +1,3 @@
-- Access the exports from other modules using require("modules/exp_server_ups")
return require("modules/exp_server_ups/control").elements.server_ups

View File

@@ -0,0 +1,40 @@
{
"name": "@expcluster/server_ups",
"version": "0.1.0",
"description": "Clusterio plugin providing in game server ups counter",
"author": "Cooldude2606 <https://github.com/Cooldude2606>",
"license": "MIT",
"repository": "explosivegaming/ExpCluster",
"main": "dist/node/index.js",
"scripts": {
"prepare": "tsc --build && webpack-cli --env production"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@clusterio/lib": "catalog:"
},
"devDependencies": {
"typescript": "catalog:",
"@types/node": "catalog:",
"@clusterio/lib": "catalog:",
"webpack": "catalog:",
"webpack-cli": "catalog:",
"webpack-merge": "catalog:"
},
"dependencies": {
"@sinclair/typebox": "catalog:",
"@expcluster/lib_util": "workspace:^",
"@expcluster/lib_gui": "workspace:^",
"@expcluster/lib_commands": "workspace:^"
},
"publishConfig": {
"access": "public"
},
"keywords": [
"clusterio",
"clusterio-plugin",
"factorio"
]
}

View File

@@ -0,0 +1,4 @@
{
"extends": "../tsconfig.browser.json",
"include": [ "web/**/*.tsx", "web/**/*.ts", "package.json" ],
}

View File

@@ -0,0 +1,6 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.node.json" }
]
}

View File

@@ -0,0 +1,5 @@
{
"extends": "../tsconfig.node.json",
"include": ["./**/*.ts"],
"exclude": ["test/*", "./dist/*"],
}

View File

View File

@@ -0,0 +1,29 @@
"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: "exp_server_ups",
library: { type: "window", name: "plugin_exp_server_ups" },
exposes: {
"./": "./index.ts",
"./package.json": "./package.json",
"./web": "./web/index.tsx",
},
shared: {
"@clusterio/lib": { import: false },
"@clusterio/web_ui": { import: false },
},
}),
],
});