Изменения

Все релизы amxts: фреймворк, CLI, официальные модули и расширение для редактора.
Готовится следующая версия
Её документация пишется вместе с ней: что она добавит и изменит, видно ещё до релиза.
amxts

v0.2.2

compare changes

Summary

A fix for a server that crashed at start on some AMX Mod X builds: the module registered its server commands with fewer arguments than AMX Mod X reads, and AMX Mod X read a command's description from leftover memory. Whether it crashed depended on the AMX Mod X build and the plugins loaded - most often with one or two plugins. The server suites run on AMX Mod X 1.9 and the newest 1.10 as well from now on.

⬆️ Upgrade guide

npx amxts upgrade in the project, with the server stopped: it moves @amxts/core to 0.2.2, builds the plugins again and puts the 0.2.2 module on the server named in .env. A server installed by hand takes the module (and amxts-compile, where it has one) from this release, and its plugins are built again.

🩹 Fixes

  • runtime: No crash at start on some AMX Mod X builds: every native the module calls - register_srvcmd, register_clcmd, set_task, RegisterHam - gets all its parameters (c02eb1e)

❤️ Contributors

📦 Files

  • amxts-server-windows-x64.zip, amxts-server-linux-x64.tar.gz - the server kit: the module, and the compiler for .ts plugins written on the server.
  • amxts_amxx.dll, amxts_amxx_i386.so - the module alone.
  • amxts-compile-*, wamrc-* - the on-server compiler and WAMR's AOT compiler, per system.
  • amxts-windows.json, amxts-linux.json - what each was built from, with every file's sha256.
amxts

v0.2.1

compare changes

Summary

Fixes for 0.2.0: a plugin's call could fail at random when a native wrote into a buffer near the end of its memory, a build after an upgrade could keep plugins of the old version that the new module refuses, amxts upgrade rewrote the module's own files when the project lives in the server's folder, and the editor marked error.message in a catch as an error.

⬆️ Upgrade guide

npx amxts upgrade in the project: it moves @amxts/core to 0.2.1, builds the plugins again and puts the 0.2.1 module on the server named in .env. A server installed by hand takes the module (and amxts-compile, where it has one) from this release; the plugins are built again, since the module loads only plugins of its own release.

🩹 Fixes

  • module: Start on a server with an older Visual C++ runtime: the network thread's lock crashed Windows servers whose msvcp140.dll predates Visual Studio 2022 17.10 (04dbe6b)
  • runtime: A native whose array size the include does not give - get_players, which server.players calls, among them - no longer fails the plugin's call when the array lies near the end of the plugin's memory (e5dee94)
  • editor: catch (error) reads error.message without a cast, as the compiler and the docs do (d2875cf)
  • build: A plugin is compiled again when the core's version changes: after an upgrade the build cache could hand back a plugin of the old version, which the new module refuses (7abae6c)
  • upgrade: The module's own files are left alone - the API it writes into addons/amxts/plugins/ - when the project lives in the server's folder (c400c24)
  • testing: The test server raises configsQueued and configsExecuted when the map starts, as AMX Mod X does (2be9123)

❤️ Contributors

📦 Files

  • amxts-server-windows-x64.zip, amxts-server-linux-x64.tar.gz - the server kit: the module, and the compiler for .ts plugins written on the server.
  • amxts_amxx.dll, amxts_amxx_i386.so - the module alone.
  • amxts-compile-*, wamrc-* - the on-server compiler and WAMR's AOT compiler, per system.
  • amxts-windows.json, amxts-linux.json - what each was built from, with every file's sha256.
amxts

v0.2.0

compare changes

Summary

0.2.0 makes a server one module to install and runs it on Valve's own HLDS as well as ReHLDS. Plugins get commands with typed arguments, menus, fetch, FTP and SFTP, bots, game messages by name, errors that point at their TypeScript, and names in the player's words. A 0.1.0 project moves over with amxts upgrade.

✨ Highlights

One module to install

A server needs the addons/ folder of the server kit and one line in modules.ini:

amxts_amxx

Nothing goes into plugins.ini: the module brings its host plugin itself, writes it next to AMX Mod X's plugins when it starts, and removes it when it stops. An install from 0.1.0 can drop its amxts_host.amxx line; the console says so once.

Plain HLDS

amxts runs on Valve's own HLDS as well as on ReHLDS. The same plugin works on both, unchanged:

  • every property of an entity and a player;
  • the game events Ham Sandwich shares with ReAPI - takeDamage, spawn, killed, jump and more;
  • rounds (newRound, roundStart, roundEnd), spawns, purchases, money and the bomb, heard through the game's own log lines and messages - after the fact, so preventDefault() on them says once that it cannot stop them.

What plain HLDS cannot give at all - ReGameDLL's own functions - prints one line in the console when a plugin listens for it, and a project with target: "hlds" does not build with such a listener. Each event's page says how it behaves on plain HLDS.

Faster properties

entity.origin, player.health, player.money and every other field are read and written where the game keeps them, on both servers, without a native call. A field the player sees on the HUD - money, the armor type, the flashlight's battery, night vision, the defuse kit - is sent to the player as the game sends it.

Commands with typed arguments

The usage is the command's help and its parser at once: <name> is required, [name] optional, and the interface you name says what each one is.

interface KickArgs {
    target: Player;
    reason?: string;
}

server.addCommand<KickArgs>("/kick <target> [reason]", ({ player, target, reason }) => {
    target.kick(reason ?? `Kicked by ${player.name}`);
}, { access: "Kick", description: "Kick a player" });
  • a Player is found by #userid, by the whole name or by a part of it;
  • a number is parsed, a union of words is one of them, the last text argument takes the rest of the line;
  • a player nobody's name matches, several whose do, or a word too many answers the one who typed it with the usage, and the handler does not run;
  • without the type argument every argument is text.

player is always the one who typed the command. server.commands lists every command with its usage, description and access, for a /help of your own.

Menu is AMX Mod X's own menu - number keys, pages with Back and More, Exit - written as objects. Each item has a title, and may say when it is shown (visible) and when it can be chosen (enabled, drawn grey otherwise); both are asked again at every show, for the player it is shown to. Choosing an item runs its onSelect and closes the menu.

const shop = new Menu("Shop");

shop.addItem({
    title: "Armor - $1000",
    enabled: ({ player }) => player.money >= 1000,
    onSelect: ({ player }) => {
        player.armor = 100;
        player.money -= 1000;
    },
});

shop.addItem({
    title: ({ player }) => `Heal (${player.health} HP)`,
    visible: ({ player }) => player.isAlive,
    onSelect: ({ player }) => {
        player.health = 100;
    },
});

server.addCommand("/shop", ({ player }) => shop.show(player));

A menu of the players on the server is a loop over server.players, one item for each. The official @amxts/menu-core takes the same shape in code - menus.create(...), addItem({ title, visible, enabled, onSelect }) with ({ player }) - and adds menus read from INI, YAML or JSON files, named conditions and actions, and lists with a row per player (({ player, target })).

fetch and useFetch, as in the browser

HTTPS, redirects, timeouts and retries, on a thread of their own, so the game never waits. useFetch never throws: it gives back data (the JSON, read as the type you name), error and status.

interface Weather {
    temperature: number;
}

server.addCommand("/weather", async ({ player }) => {
    const { data } = await useFetch<Weather>("https://api.example.com/weather", {
        query: { city: "Riga" },
        timeout: 5000,
        retry: 2,
    });

    if (data) print(player, `${data.temperature}°`);
    else print(player, "The weather service is not answering");
});

fetch is the browser's own beneath it, for a raw Response, headers and streams.

@amxts/ftp

A new official module: FTP, FTPS and SFTP, since game hosts give access to a server by those. Every call is a promise on the same network thread as fetch; files go between the game folder and the other server whole, binary included (maps, demos), without passing through the plugin.

import { ftp } from "@amxts/ftp";

const client = await ftp.connect("sftp://backup@example.com", { password: settings.password });
await client.upload("addons/amxmodx/logs/today.log", "logs/today.log");
await client.download("/maps/de_dust2.bsp", "maps/de_dust2.bsp");
const entries = await client.list("/configs");
await client.close();

SFTP takes a password or an RSA key, and can check the host's key. Keep the password in a config, not in the code.

Bots

A fake client is a player like any other: it is in server.players, it joins a team, it can be kicked. It has no AI of its own; move drives it, one frame at a time.

const bot = server.addBot("Spectator");   // Player | null when the server is full
bot?.joinTeam("SPECTATOR");

const runner = server.addBot("Runner");
server.addEventListener("frame", () => runner?.move({ forward: 250, buttons: ["jump"], angles: [0, 90, 0] }));

The natives with a ... tail - engfunc, dllfunc, pev, ExecuteHam and the rest - take text, floats and vectors as well as numbers, and what a native writes back is read through Ref:

const id = engfunc(EngFunc_CreateFakeClient, "Dummy");
const reason = new Ref("");
dllfunc(DLLFunc_ClientConnect, id, "Dummy", "127.0.0.1", reason);

Game messages by name

A message the game sends a player is heard by its name, in the player's words, with typed fields:

server.addMessageListener("death", (event) => {
    if (event.headshot) print(0, `${event.killer?.name} - headshot - ${event.victim?.name}`);
});

server.addMessageListener("progressBar", (event) => {
    console.log(`a bar for ${event.seconds} s, from ${event.startPercent}%`);
});

Every message has a description, with the game's own name (DeathMsg, BarTime) beside it, so a Pawn author finds it. Where the game sends one thing in several messages, one name hears them all: progressBar (from zero or from a percent), spectatedHealth (the health of the player a spectator watches), hint (the hint box, with or without texts put into it). player.screen.progressBar(5, { startPercent: 40 }) shows one.

Errors that point at your code

When a call fails - a null read with !, an error nobody catches, a stack overflow - the console prints the TypeScript stack, file, line and column, with the source line itself:

[amxts] shop.aot: TypeError: Unexpected 'null'
    at price (plugins/shop/shop.ts:26:9)
      26 |   return item!.price;
    at onSelect (plugins/shop/shop.ts:30:15)

console.error(error) and error.stack give the same stack. The map from the plugin's code to its TypeScript travels inside each .aot; the source line appears where the .ts is next to the plugin, as in amxts dev.

Names in the player's words

The API speaks the way players and plugin authors do, not the way the game's source does:

0.1.00.2.0
Player.all()server.players
player.accountplayer.money
restartRound, onRoundFreezeEndnewRound, roundStart
flPlayerFallDamage, fPlayerCanTakeDamagefallDamage, canTakeDamage
fireBullets3shoot
game.c4Guy, game.numCtWinsgame.bomber, game.ctWins
server.addEventListener("message:DeathMsg", ...)server.addMessageListener("death", ...)
buttons: ["Jump"], access "Kick"buttons: ["jump"], access "kick"

Game messages have named, typed fields too: event.killer, event.victim, event.headshot on DeathMsg. Fields that mean nothing to a plugin - the AI members of a player, Condition Zero's career - are left to @amxts/core/natives.

amxts upgrade rewrites a 0.1.0 project: imports, command handlers, Player.all and every renamed field and event. What it cannot be sure of, it lists.

⚠️ Breaking changes

  • Player.all(filter) is server.players; filter it with .filter(...).
  • Renamed fields and events: player.account is player.money; restartRound, onRoundFreezeEnd and addAccount are newRound, roundStart and addMoney; fireBullets3 is shoot; about a hundred more follow the same rule (the full list is in amxts upgrade's output). Internal fields left the API and are reached through @amxts/core/natives.
  • A command's handler takes one object: ({ player }) => ..., not (player, args) => ....
  • @amxts/core/http is gone: fetch and useFetch are globals. easy_http is no longer needed on the server.
  • The core's API is imported by its package name (@amxts/core/natives, /fs, ...); ~/ is the project's own files only.
  • Game messages are heard with server.addMessageListener(name, ...), by their new names; the message: prefix of addEventListener is gone.
  • The names of every flag set are lowerCamelCase: "jump", "muzzleFlash", "kick".
  • @amxts/menu-core's code API takes one options object and one context: addItem({ title, onSelect: ({ player }) => ... }).
  • The module and amxts-compile must come from the same release: the module refuses a compiler of another build.
  • A plugin's id is steamId, not authid; server events are named as authors say them: putInServer, configsExecuted, pluginsLoaded, changeLevel.
  • A plugin built by 0.1.0 is not loaded: the console names it and says to build it again.

⬆️ Upgrade guide

From 0.1.0, update the command first, with the package manager the project uses: npm install @amxts/cli@latest, bun add @amxts/cli@latest, pnpm add @amxts/cli@latest or yarn add @amxts/cli@latest. Then run npx amxts upgrade in the project: it updates the packages, rewrites the code, builds, and updates the server named in .env.

🧭 Known issues

  • On plain HLDS, rounds, purchases, money and the bomb are heard after the fact; preventDefault() on them says once that it cannot stop them. ReGameDLL's own functions are not heard at all.
  • A stack shows the source line only where the plugin's .ts is next to it; in a full build, small functions are inlined and a frame may show its caller's name.
  • SFTP keys are RSA in PEM; Ed25519 and ECDSA keys are not read.

🚀 Enhancements

  • One module to install: the host plugin comes inside amxts_amxx
  • Plain HLDS: fields read in memory, a player's events through Ham Sandwich, rounds, spawns, purchases, money and the bomb through the game's own logs and messages
  • target: "hlds" stops a build that listens for what plain HLDS cannot hear
  • commands: Typed arguments, and server.commands for a /help
  • menus: Menu over AMX Mod X's menus
  • fetch: fetch and useFetch as globals, over libcurl and mbedTLS inside the module
  • kit: request over HTTP, FTP, FTPS and SFTP, for modules
  • modules: Library packages, compiled into each plugin that imports them
  • @amxts/ftp among the official modules
  • messages: Every known game message's arguments typed and named
  • events: Enum arguments of game events as names; a game event's vector arguments writable
  • players: server.players replaces Player.all
  • api: Fields and events in the player's words; internal ones left to @amxts/core/natives
  • runtime: A failed call prints the TypeScript stack, from a map carried in each plugin
  • module: A reload removes the old plugin's timers and hooks
  • module: A compiler of another build is refused, with one clear line
  • module: A plugin built for another amxts is refused, not run
  • players: server.addBot and bot.move; a fake client joins the spectators on both servers
  • natives: A native's ... tail takes text, floats, vectors and players; Ref reads what it writes back
  • messages: server.addMessageListener by the player's names, with a description for each
  • entities: A field the HUD shows is sent to the player when written
  • build: AMXTS_SERVER may name the server's root or its cstrike folder
  • server: amxts_load, amxts_unload and amxts_reload <plugin> for one plugin; amxts_plugins shows each one's state
  • dev: Saving a plugin reloads only the plugins that changed, and a menu it made stays open through the reload
  • natives: Reunion, VTC, ReChecker and GeoIP natives; player.authType, protocol and authKey from Reunion
  • modules: A shared module hears when a plugin that uses it stops (onPluginStop), and drops what it gave
  • menu-core: The code API in the same shape as Menu
  • The core's API by its package name: @amxts/core/natives, /constants, /fs, /os, /kit, /check
  • upgrade: amxts upgrade rewrites imports, command handlers, Player.all and every renamed field and event
  • docker: The server image is ghcr.io/amxts/server

🔥 Performance

  • build: Modules from npm come with their analysis, not compiled again
  • build: Plugins compile in parallel
  • build: Each file is read once per build

🩹 Fixes

  • module: The API files are written only when they changed (#1)
  • compiler: boolean | function fields, has ? get : fallback, a caught error read off a return, new Map(entries) and new Set(values), narrowing on assignment
  • commands: Typed arguments go where their interface does
  • editor: Date.UTC with fewer arguments
  • host: A missing native answers 0 and says so once
  • events: Removing the last listener switches its hook off
  • runtime: A function a plugin hands a module at its top level is called back, and a reloaded plugin's old functions are never called
  • runtime: A plugin loaded while another one runs no longer stalls that one's await, and a reload of all loads each plugin once

❤️ Contributors

📦 Files

  • amxts-server-windows-x64.zip, amxts-server-linux-x64.tar.gz - the server kit: the module, and the compiler for .ts plugins written on the server.
  • amxts_amxx.dll, amxts_amxx_i386.so - the module alone.
  • amxts-compile-*, wamrc-* - the on-server compiler and WAMR's AOT compiler, per system.
  • amxts-windows.json, amxts-linux.json - what each was built from, with every file's sha256.
ftp

v0.1.0

The first release: FTP, FTPS and SFTP for amxts plugins. Upload, download and list files on another server; every call is a promise on amxts's network thread, so the game never waits, and files go between the game folder and the other server whole, binary included.

import { ftp } from "@amxts/ftp";

const client = await ftp.connect("sftp://backup@example.com", { password: settings.password });
await client.upload("addons/amxmodx/logs/today.log", "logs/today.log");
await client.close();

SFTP takes a password or an RSA key in PEM, and can check the host's key.

❤️ Contributors

menu-core

v0.2.0

compare changes

Summary

menu-core for amxts 0.2.0: every menu function takes the menu's context, items take one options object as the core's Menu does, and a menu stays open while its plugin reloads.

✨ Highlights

The menu's context

A title, visible, enabled and onSelect take one object, { player, target, row, menu }:

const shop = menus.create("SHOP", { title: ({ player }) => `Shop - $${player.money}` });

shop.addItem({
    title: ({ player }) => `Heal (${player.health} HP)`,
    visible: ({ player }) => player.health < 100,
    onSelect: ({ player }) => {
        player.health = 100;
    },
});

In a list of players, target is the player of the row.

A menu kept through a reload

When the plugin that made a menu reloads - a save under amxts dev, amxts_reload - a player looking at the menu keeps it, on the same page, drawn from the new code. When the plugin is unloaded, its menus, items, placeholders and conditions go with it.

⚠️ Breaking changes

  • Every function a menu takes gets the context object: ({ player }) => ..., not (player) => ....
  • addItem takes one options object: addItem({ title, onSelect }), not addItem("Title", { onSelect }).
  • Conditions registered by name keep their (player, viewer, name) arguments.

⬆️ Upgrade guide

npx amxts upgrade rewrites both: the functions to the context and addItem to its options. Files of menus (INI, YAML, JSON) do not change.

📦 Dependencies

PackageFromTo
@amxts/core^0.1.0^0.2.0
@amxts/config-core^0.1.0^0.1.1

🚀 Enhancements

  • ⚠️ Every menu function takes the menu's context (496431b)
  • Keep a menu open through its plugin's reload (1a3ab54)

🩹 Fixes

  • The plugin's version is the package's (a646c2c)
  • Drop what a stopped plugin gave the menus (fa68648)

💅 Refactors

  • Import the core's API by its package name (d838de0)
  • Command handlers take one object (c6aa029)
  • server.players over Player.all (0a8c748)
  • Admin right by its lowerCamelCase name (13b2645)
  • Hear putInServer by its new name (ac6243e)

📖 Documentation

  • The player's money is player.money (0e0f3ca)
  • The Pawn natives without a plugin the reader never met (d320b06)
  • The menu's context in the README (ffe1920)
  • The README without a status note (ee841d7)

⚠️ Breaking Changes

  • ⚠️ Every menu function takes the menu's context (496431b)

❤️ Contributors

resemiclip

v0.1.1

compare changes

Summary

resemiclip for amxts 0.2.0. Its API is unchanged; the rule a plugin sets goes when that plugin stops, so ReSemiclip gets its own rules back.

⚠️ Breaking changes

None in resemiclip itself; it needs @amxts/core 0.2.

⬆️ Upgrade guide

npx amxts upgrade in the project updates it with the core.

📦 Dependencies

PackageFromTo
@amxts/core^0.1.0^0.2.0

🩹 Fixes

  • Give the rules back when the plugin that set the rule stops (62e06a8)

💅 Refactors

  • Import the core's API by its package name (4024ec5)
  • Command handlers take one object (16b55d1)
  • server.players over Player.all (ab12133)
  • Listen to the team message by its new name (c8a31d9)
  • Hear putInServer and playerChange by their new names (9b407b7)

📖 Documentation

  • The rule goes with the plugin that set it (7a4ce5b)

❤️ Contributors

config-core

v0.1.1

compare changes

Summary

config-core for amxts 0.2.0. Its API is unchanged: a plugin that reads its configs needs nothing new.

⚠️ Breaking changes

None in config-core itself; it needs @amxts/core 0.2.

⬆️ Upgrade guide

npx amxts upgrade in the project updates it with the core.

📦 Dependencies

PackageFromTo
@amxts/core^0.1.0^0.2.0

🩹 Fixes

  • The plugin's version is the package's (c53a7ee)

💅 Refactors

  • Import the core's API by its package name (7e12991)
  • Command handlers take one object (4240f17)
  • Admin right by its lowerCamelCase name (74c8ddf)

📖 Documentation

  • The Pawn natives without a plugin the reader never met (7de1140)
  • The natives' limits without comparing to Pawn (cedf727)

❤️ Contributors

amxts-cli

v0.2.0

compare changes

Summary

The command for amxts 0.2.0. amxts upgrade moves a 0.1.0 project over in one step, and a new project starts from 0.2.0's API.

✨ Highlights

amxts upgrade

One command updates the packages, rewrites the code for the renamed API, builds, and puts the release's module, compiler and wamrc on the server named in .env, with a backup of each file it replaces. --dry-run shows what it would do; --no-server and --server-only do half of it.

A new project

hello.ts has a typed config and a menu that greets the player by name; with @amxts/menu-core, its starter menu takes the menu's context.

The server's folder

AMXTS_SERVER may name the server's root or its cstrike folder.

⚠️ Breaking changes

  • The command drives @amxts/core 0.2.x only. A 0.1.0 project runs amxts upgrade first.

⬆️ Upgrade guide

Update the command with the package manager the project uses - npm install @amxts/cli@latest, bun add @amxts/cli@latest, pnpm add @amxts/cli@latest or yarn add @amxts/cli@latest - then run npx amxts upgrade.

🧭 Known issues

  • The server step of amxts upgrade downloads the release from GitHub; without network access run it with --no-server and install the server kit by hand.

📦 Dependencies

PackageFromTo
@amxts/core^0.1.0^0.2.0

🚀 Enhancements

  • server: Take the server's folder or its cstrike (f41a1d7)
  • init: Typed config and the core's Menu in hello.ts (50a7343)
  • init: The starter menu greets by name in its title (f38a850)
  • init: The menu-core starter menu takes its context (3e590ab)
  • upgrade: One command moves a project to a release (00d3974)
  • upgrade: Each package to its own version for the core (426f2a9)
  • New modules at their version for the core (a2be088)

🩹 Fixes

  • init: knip 6 in a new project (868f2e3)
  • init: Keep a linked core's own .bin on yarn (1262f55)
  • templates: The starter hears putInServer (e20acbe)

💅 Refactors

  • Command handlers take one object (5bd680c)

📖 Documentation

  • The packages are on npm (9770340)
  • amxts upgrade in the READMEs and CONTRIBUTING (7c69105)

❤️ Contributors

amxts-vscode

v0.1.0

The first release. Install from the .vsix below: VS Code → Extensions → ··· → Install from VSIX. On Open VSX (Cursor, VSCodium, Windsurf): search for amxts.

amxts

v0.1.0

amxts 0.1.0