Compare commits
4
Commits
644b860145
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e98395b5d | ||
|
|
b70127a57b | ||
|
|
793797dacb | ||
|
|
4efb7dcf21 |
+2
-1
@@ -1,5 +1,6 @@
|
||||
ai/*
|
||||
files/*
|
||||
!files/config
|
||||
!files/config/**
|
||||
!files/custom
|
||||
!files/custom/**
|
||||
!files/custom/**
|
||||
@@ -74,6 +74,14 @@ RUN node /tmp/patch-custom-game.js /app
|
||||
COPY docker/patch-auto-verify-email.js /tmp/patch-auto-verify-email.js
|
||||
RUN node /tmp/patch-auto-verify-email.js /app
|
||||
|
||||
# Local patch: make the minimum length for newly created character names
|
||||
# configurable (docker/patch-character-name-length.js). Upstream rejects names
|
||||
# shorter than 4 at creation, while rename/chat already accept 1-12; options
|
||||
# .character_name_min_length defaults to 4 and can be set to 3 in
|
||||
# files/config/options.js to allow 3-character names at creation.
|
||||
COPY docker/patch-character-name-length.js /tmp/patch-character-name-length.js
|
||||
RUN node /tmp/patch-character-name-length.js /app
|
||||
|
||||
# Local patch: make the Steam/MAS auth-id requirement for tutorial and class
|
||||
# rewards optional (docker/patch-tutorial-reward-auth.js). Controlled at runtime
|
||||
# by options.tutorial_reward_requires_auth_id.
|
||||
|
||||
@@ -36,6 +36,32 @@ docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.user.updateMany({}, { $set: { "info.slots": 18 } })'
|
||||
```
|
||||
|
||||
Der Wert wird bei jeder Anfrage neu aus MongoDB gelesen; ein Neustart ist nicht
|
||||
nötig. Maximal sind 18 Plätze möglich. Prüfen, ob der richtige Account getroffen
|
||||
wurde:
|
||||
|
||||
```sh
|
||||
docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.user.findOne(
|
||||
{ "info.email": "du@example.com" },
|
||||
{ _id: 1, name: 1, "info.slots": 1, "info.characters": 1, pid: 1 }
|
||||
)'
|
||||
```
|
||||
|
||||
**Charakternamen mit 3 Zeichen:** Beim Anlegen verlangt Upstream mindestens 4
|
||||
Zeichen; kürzere Namen sind nur über die kostenpflichtige Umbenennung möglich
|
||||
(3 Zeichen: 24.000 Muscheln). `files/config/options.js` setzt deshalb
|
||||
`character_name_min_length: 3`. Der Build-Patch
|
||||
`docker/patch-character-name-length.js` liest den Wert in `is_name_allowed`;
|
||||
die übrigen Regeln bleiben unverändert: 1–12 Zeichen im Validator,
|
||||
`[A-Za-z0-9_]`, keine reinen Zahlen und keine Prototype-Schlüssel. Mit dem hier
|
||||
gesetzten Wert 3 bleiben 1–2 Zeichen weiterhin der Umbenennung vorbehalten.
|
||||
Bestehende kurze Namen sind nicht betroffen. Zum Zurückstellen den Wert auf `4`
|
||||
setzen und `al-backend` sowie `al-game` neu starten; ein Rebuild ist nur nötig,
|
||||
wenn der Patch selbst fehlt. Der Patch ändert nur die Erstellung:
|
||||
3-Zeichen-Namen sind damit kostenlos, obwohl eine Umbenennung auf 3 Zeichen
|
||||
weiterhin 24.000 Muscheln kostet.
|
||||
|
||||
**Admin werden:** Die Admin-Seiten (`/admin/executor`, `/admin/renderer`) sind
|
||||
standardmäßig gesperrt. `is_admin` in `common/admin.js` prüft das Feld
|
||||
`user.admin`; setzen lässt es sich direkt in MongoDB:
|
||||
@@ -52,12 +78,27 @@ sinnvoll.
|
||||
|
||||
**GM werden (Game):** Der Web-Admin ist **kein** Game-GM. Die GM-Rolle hängt am
|
||||
Charakter, nicht am Benutzerkonto: Der Game-Server prüft `player.role` bzw.
|
||||
`player.p.role` auf `"gm"`. Charakter vorher ausloggen, dann in MongoDB setzen:
|
||||
`player.p.role` auf `"gm"`. Charakter vorher ausloggen, dann in MongoDB setzen.
|
||||
|
||||
Wichtig: Das Top-Level-Feld `name` wird beim Anlegen kleingeschrieben
|
||||
(`simplify_name`), das Anzeige-Feld `info.name` behält die Schreibweise. Eine
|
||||
Abfrage auf `{ name: "CharName" }` findet deshalb nichts. Nutze den
|
||||
kleingeschriebenen Namen oder `info.name`:
|
||||
|
||||
```sh
|
||||
docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.character.updateOne(
|
||||
{ name: "CharName" },
|
||||
{ name: "charname" },
|
||||
{ $set: { role: "gm", "info.p.role": "gm" } }
|
||||
)'
|
||||
```
|
||||
|
||||
Alternativ, wenn die Groß-/Kleinschreibung im Spiel erhalten bleiben soll:
|
||||
|
||||
```sh
|
||||
docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.character.updateOne(
|
||||
{ "info.name": "CharName" },
|
||||
{ $set: { role: "gm", "info.p.role": "gm" } }
|
||||
)'
|
||||
```
|
||||
@@ -249,6 +290,12 @@ Nicht-Händlern bzw. 1 Händler ab. Der Patch liest beide Stellen aus
|
||||
`merchant_owner_limit`). Fehlt ein Anker in `node/server_functions.js` oder
|
||||
`adventure_functions.js`, bricht der Build ab.
|
||||
|
||||
`docker/patch-character-name-length.js` macht die Mindestlänge neuer
|
||||
Charakternamen konfigurierbar. `options.character_name_min_length` (Standard 4)
|
||||
ersetzt die feste 4 in `is_name_allowed`; die Umbenennung (`is_name_xallowed`)
|
||||
und ihre Preislogik bleiben unverändert. Fehlt der Anker in `api.js`, bricht der
|
||||
Build ab.
|
||||
|
||||
## Spieldaten anpassen (`files/custom/game.js`)
|
||||
|
||||
Werte bestehender Elemente und neue Elemente lassen sich ohne Quelltextänderung
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env node
|
||||
// Local patch: make the minimum length for newly created character names
|
||||
// configurable.
|
||||
//
|
||||
// Upstream's is_name_allowed() rejects names shorter than 4 characters, while
|
||||
// is_name_xallowed() already accepts 1-12 for rename, chat and login. Three
|
||||
// character names are therefore fully supported; the 4-character gate only
|
||||
// applies to character creation and makes them a paid rename target (24,000
|
||||
// shells). This patch reads options.character_name_min_length (default 4) so a
|
||||
// local server can allow 3-character names at creation without changing the
|
||||
// rename rules or prices.
|
||||
//
|
||||
// The patch is idempotent and fails loudly if the anchor is gone.
|
||||
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
|
||||
const app = path.resolve(process.argv[2] || "/app");
|
||||
const file = path.join(app, "api.js");
|
||||
let source = fs.readFileSync(file, "utf8");
|
||||
|
||||
const oldLine = "\tif (name.length < 4) return false;";
|
||||
const newLines = [
|
||||
"\tvar min_length =",
|
||||
'\t\ttypeof options === "undefined" || !options || options.character_name_min_length === undefined',
|
||||
"\t\t\t? 4",
|
||||
"\t\t\t: Number(options.character_name_min_length);",
|
||||
"\tif (!Number.isSafeInteger(min_length) || min_length < 1 || min_length > 12) min_length = 4;",
|
||||
"\tif (name.length < min_length) return false;",
|
||||
].join("\n");
|
||||
|
||||
function fail(message) {
|
||||
console.error("patch-character-name-length: " + message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const marker = "options.character_name_min_length";
|
||||
const oldCount = source.split(oldLine).length - 1;
|
||||
const patchedCount = source.split("if (name.length < min_length) return false;").length - 1;
|
||||
|
||||
if (oldCount === 1 && patchedCount === 0) {
|
||||
source = source.replace(oldLine, newLines);
|
||||
fs.writeFileSync(file, source);
|
||||
console.log("patch-character-name-length: patched " + file);
|
||||
} else if (oldCount === 0 && patchedCount === 1 && source.includes(marker)) {
|
||||
console.log("patch-character-name-length: already patched");
|
||||
} else {
|
||||
fail(
|
||||
"expected 1 unpatched creation length check, found " +
|
||||
oldCount +
|
||||
" unpatched and " +
|
||||
patchedCount +
|
||||
" patched; refusing partial patch",
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
# Game-Server-API (`/server.api/...`)
|
||||
|
||||
Der Game-Server läuft separat vom Backend und lauscht lokal auf Port `7192`.
|
||||
Seine Admin-Endpunkte liegen unter `/server.api/` und sind **nicht** die
|
||||
öffentliche JSON-API des Backends (`/api`).
|
||||
|
||||
## Endpunkte
|
||||
|
||||
| Methode | Endpunkt | Zweck |
|
||||
|---|---|---|
|
||||
| `POST` | `/server.api/eval` | JavaScript im Game-Server ausführen |
|
||||
| `POST` | `/server.api/cupdate` | Shells eines online Characters setzen |
|
||||
| `POST` | `/server.api/shutdown` | Game-Server herunterfahren |
|
||||
| `POST` | `/server.api/new_friend` | intern: Freund hinzufügen |
|
||||
| `POST` | `/server.api/lost_friend` | intern: Freund entfernen |
|
||||
|
||||
Alle diese Endpunkte verlangen `spass` = `keys.ACCESS_MASTER`. Der Key erlaubt
|
||||
beliebige Codeausführung im Game-Server und darf niemals öffentlich werden.
|
||||
|
||||
## `ACCESS_MASTER` lesen
|
||||
|
||||
```sh
|
||||
cd /data/ai/gaming/adventureland
|
||||
|
||||
ACCESS_MASTER="$(docker compose exec -T al-game node -e \
|
||||
'process.stdout.write(require("/app/secretsandconfig/keys").ACCESS_MASTER)')"
|
||||
```
|
||||
|
||||
Den Wert nicht in Chat, Logs oder Commits schreiben.
|
||||
|
||||
## `/server.api/eval`
|
||||
|
||||
Felder:
|
||||
|
||||
- `spass` – `ACCESS_MASTER`
|
||||
- `code` – JavaScript, wird im Game-Server ausgeführt
|
||||
- `data` – JSON-String, im Code als `data` verfügbar
|
||||
|
||||
Der Code läuft im Modul-Scope des Game-Servers. Verfügbar sind unter anderem
|
||||
`players`, `name_to_id`, `get_player(name)`, `add_item`, `resend`,
|
||||
`calculate_player_stats` und `output`.
|
||||
|
||||
### Gold geben (live)
|
||||
|
||||
```sh
|
||||
curl -sS -X POST http://127.0.0.1:7192/server.api/eval \
|
||||
--data-urlencode "spass=$ACCESS_MASTER" \
|
||||
--data-urlencode 'code=var p=get_player("CharName"); if(p){ p.gold += 100000; resend(p,"reopen+u+cid"); output={ok:true,gold:p.gold}; } else { output={ok:false,reason:"offline"}; }' \
|
||||
--data-urlencode 'data={}'
|
||||
```
|
||||
|
||||
### Item geben (live)
|
||||
|
||||
```sh
|
||||
curl -sS -X POST http://127.0.0.1:7192/server.api/eval \
|
||||
--data-urlencode "spass=$ACCESS_MASTER" \
|
||||
--data-urlencode 'code=var p=get_player("CharName"); if(p){ var n=add_item(p,{name:"hpot0",q:100},{announce:false}); resend(p,"reopen+u+cid"); output={ok:true,slot:n}; } else { output={ok:false,reason:"offline"}; }' \
|
||||
--data-urlencode 'data={}'
|
||||
```
|
||||
|
||||
`add_item` prüft Inventarplatz und stapelt stackbare Items. `announce:false`
|
||||
verhindert eine globale Fundmeldung.
|
||||
|
||||
### XP geben (live)
|
||||
|
||||
```sh
|
||||
curl -sS -X POST http://127.0.0.1:7192/server.api/eval \
|
||||
--data-urlencode "spass=$ACCESS_MASTER" \
|
||||
--data-urlencode 'code=var p=get_player("CharName"); if(p){ p.xp += 100000; resend(p,"reopen"); output={ok:true,level:p.level,xp:p.xp}; } else { output={ok:false,reason:"offline"}; }' \
|
||||
--data-urlencode 'data={}'
|
||||
```
|
||||
|
||||
`resend(p,"reopen")` ruft `calculate_player_stats` auf und führt Level-Ups aus.
|
||||
|
||||
## `/server.api/cupdate`
|
||||
|
||||
Felder:
|
||||
|
||||
- `spass` – `ACCESS_MASTER`
|
||||
- `id` – Character-Name (`player.id` ist der Name, nicht `CH_...`)
|
||||
- `cash` – neuer Gesamtstand der Shells
|
||||
- `ncash` – Betrag für die „received shells“-Meldung; wird **nicht** addiert
|
||||
|
||||
`cupdate` setzt `player.cash` auf `cash`. Es ist kein Inkrement. Deshalb zuerst
|
||||
den neuen Gesamtstand in MongoDB schreiben und dann `cupdate` aufrufen.
|
||||
|
||||
```sh
|
||||
# 1. Account-Cash erhöhen
|
||||
docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.user.updateOne({"info.email":"du@example.com"}, { $inc: { cash: 1000 } })'
|
||||
|
||||
# 2. Neuen Gesamtstand lesen
|
||||
NEW_TOTAL="$(docker compose exec -T al-mongo mongosh adventureland --quiet --eval \
|
||||
'db.user.findOne({"info.email":"du@example.com"},{cash:1}).cash')"
|
||||
|
||||
# 3. Online-Character aktualisieren
|
||||
curl -sS -X POST http://127.0.0.1:7192/server.api/cupdate \
|
||||
--data-urlencode "spass=$ACCESS_MASTER" \
|
||||
--data-urlencode 'id=CharName' \
|
||||
--data-urlencode "cash=$NEW_TOTAL" \
|
||||
--data-urlencode 'ncash=1000'
|
||||
```
|
||||
|
||||
Ohne online Character reicht der MongoDB-Wert; beim nächsten Login wird
|
||||
`user.cash` geladen.
|
||||
|
||||
## Wichtige Hinweise
|
||||
|
||||
- **Shells** sind accountweit (`user.cash`). Ein `/cupdate` ändert nur die
|
||||
laufende Kopie im Game-Server; die DB muss separat aktualisiert werden.
|
||||
- **Gold** ist pro Character (`character.info.gold` / `player.gold`) oder
|
||||
accountweit in der Bank (`user.info.gold`).
|
||||
- `eval` ist mächtig und ungefiltert. Nur lokal oder hinter einer
|
||||
vertrauenswürdigen Admin-Schicht betreiben.
|
||||
- `aadd_item` nicht verwenden: Es ruft `reopen()` auf, das im Node-Port nicht
|
||||
definiert ist. Stattdessen `add_item` + `resend`.
|
||||
- Der Game-Server speichert live geänderte Character-Daten regelmäßig. Für
|
||||
dauerhafte Änderungen an Offline-Characters die MongoDB-Variante verwenden;
|
||||
für laufende Characters `eval`.
|
||||
- `ACCESS_MASTER` rotieren, bevor der Port öffentlich erreichbar ist. Der
|
||||
Default steht in `files/config/keys.js`.
|
||||
|
||||
## Quellen
|
||||
|
||||
- `source-repos/adventureland_mongodb/node/server.js` – `server_api`-Router,
|
||||
`/eval`, `/cupdate`, `/shutdown`; `add_item`, `resend`
|
||||
- `source-repos/adventureland_mongodb/adventure_functions.js` –
|
||||
`server_url`, `server_eval`, `server_eval_safe`, `character_eval`
|
||||
- `files/config/options.js` – `api_path: "/server.api/"`, `local_port: 7192`
|
||||
- `source-repos/adventureland_mongodb/README.md` – Admin-/Executor-Hinweise
|
||||
@@ -0,0 +1,249 @@
|
||||
# Docker- und MongoDB-Admin
|
||||
|
||||
Diese Variante arbeitet direkt auf den Containern und der MongoDB. Sie ist
|
||||
nützlich für Offline-Änderungen, Recovery und Prüfungen. Für laufende
|
||||
Characters ist die Game-Server-API (`docs/admin/api.md`) oder der
|
||||
Admin-Executor (`docs/admin/executor.md`) meist besser geeignet.
|
||||
|
||||
## Stack
|
||||
|
||||
| Service | Aufgabe | Port |
|
||||
|---|---|---|
|
||||
| `al-mongo` | MongoDB mit Replica-Set `rs0` | intern `27017` |
|
||||
| `al-backend` | Express-Backend, Web-Client, `/api` | `8090` |
|
||||
| `al-game` | Socket.IO-Game-Server, `/server.api` | `7192` |
|
||||
|
||||
MongoDB ist im Compose-Setup **nicht** auf den Host veröffentlicht. Der Zugriff
|
||||
läuft über `docker compose exec`.
|
||||
|
||||
## MongoDB-Shell
|
||||
|
||||
```sh
|
||||
cd /data/ai/gaming/adventureland
|
||||
|
||||
docker compose exec al-mongo mongosh adventureland
|
||||
```
|
||||
|
||||
Einzelnes Kommando:
|
||||
|
||||
```sh
|
||||
docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.user.findOne({ "info.email": "du@example.com" }, { _id: 1, name: 1, cash: 1, "info.slots": 1 })'
|
||||
```
|
||||
|
||||
## Account und Character finden
|
||||
|
||||
```sh
|
||||
docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.user.findOne(
|
||||
{ "info.email": "du@example.com" },
|
||||
{ _id: 1, name: 1, cash: 1, "info.gold": 1, "info.slots": 1, "info.characters": 1 }
|
||||
)'
|
||||
```
|
||||
|
||||
```sh
|
||||
docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.character.findOne(
|
||||
{ "info.name": "CharName" },
|
||||
{ _id: 1, owner: 1, level: 1, xp: 1, online: 1, server: 1, "info.gold": 1, "info.items": 1 }
|
||||
)'
|
||||
```
|
||||
|
||||
## Shells geben
|
||||
|
||||
Shells sind accountweit und liegen in `user.cash`.
|
||||
|
||||
```sh
|
||||
docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.user.updateOne(
|
||||
{ "info.email": "du@example.com" },
|
||||
{ $inc: { cash: 1000 } }
|
||||
)'
|
||||
```
|
||||
|
||||
Danach neu einloggen oder den neuen Stand per `/server.api/cupdate` an online
|
||||
Characters schicken (siehe `docs/admin/api.md`).
|
||||
|
||||
## Bank-Gold geben
|
||||
|
||||
Bank-Gold ist accountweit und liegt in `user.info.gold`.
|
||||
|
||||
```sh
|
||||
docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.user.updateOne(
|
||||
{ "info.email": "du@example.com" },
|
||||
{ $inc: { "info.gold": 100000 } }
|
||||
)'
|
||||
```
|
||||
|
||||
Neu einloggen bzw. Bank betreten. Ist der Character bereits in der Bank, wird
|
||||
`player.user.gold` live nicht automatisch aktualisiert.
|
||||
|
||||
## Character-Gold geben
|
||||
|
||||
Character-Gold liegt in `character.info.gold`.
|
||||
|
||||
> Nur bearbeiten, solange der Character **offline** ist (`online: false`,
|
||||
> `server: ""`). Sonst überschreibt der Game-Server den Wert beim nächsten Sync.
|
||||
|
||||
```sh
|
||||
docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.character.updateOne(
|
||||
{ "info.name": "CharName" },
|
||||
{ $inc: { "info.gold": 100000 } }
|
||||
)'
|
||||
```
|
||||
|
||||
## Item ins Character-Inventar
|
||||
|
||||
Item-Namen stehen in `design/items.js` (`G.items`), zum Beispiel `hpot0`,
|
||||
`mpot0`, `helmet`, `shoes`. Das Inventar fasst maximal 42 Einträge.
|
||||
|
||||
```sh
|
||||
docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.character.updateOne(
|
||||
{ "info.name": "CharName" },
|
||||
{ $push: { "info.items": { name: "hpot0", q: 100 } } }
|
||||
)'
|
||||
```
|
||||
|
||||
Das ist ein roher Push: kein Stacking, keine Inventarprüfung. Für laufende
|
||||
Characters `add_item` per Game-Server-API verwenden.
|
||||
|
||||
## Item in die Bank
|
||||
|
||||
Bank-Packs liegen in `user.info.items0` bis `user.info.items47`.
|
||||
|
||||
```sh
|
||||
docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.user.updateOne(
|
||||
{ "info.email": "du@example.com" },
|
||||
{ $push: { "info.items0": { name: "hpot0", q: 100 } } }
|
||||
)'
|
||||
```
|
||||
|
||||
Pack-Größe beachten (bis 42). Neu einloggen bzw. Bank betreten.
|
||||
|
||||
## Charakterplätze setzen
|
||||
|
||||
```sh
|
||||
docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.user.updateOne(
|
||||
{ "info.email": "du@example.com" },
|
||||
{ $set: { "info.slots": 18 } }
|
||||
)'
|
||||
```
|
||||
|
||||
Maximum 18. Der Wert wird bei jeder Anfrage gelesen; kein Neustart nötig.
|
||||
|
||||
## Admin setzen
|
||||
|
||||
```sh
|
||||
docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.user.updateOne(
|
||||
{ "info.email": "du@example.com" },
|
||||
{ $set: { admin: true } }
|
||||
)'
|
||||
```
|
||||
|
||||
Danach neu einloggen. Der Admin-Executor ist dann unter
|
||||
`http://localhost:8090/admin/executor` erreichbar.
|
||||
|
||||
## GM setzen
|
||||
|
||||
GM ist eine Character-Rolle, kein Web-Admin.
|
||||
|
||||
```sh
|
||||
docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.character.updateOne(
|
||||
{ "info.name": "CharName" },
|
||||
{ $set: { role: "gm", "info.p.role": "gm" } }
|
||||
)'
|
||||
```
|
||||
|
||||
Danach den Character neu einloggen.
|
||||
|
||||
## Keys lesen
|
||||
|
||||
`ACCESS_MASTER`, `BOT_MASTER` und `SERVER_MASTER` stehen in
|
||||
`files/config/keys.js` und sind im Container unter
|
||||
`/app/secretsandconfig/keys.js` gemountet.
|
||||
|
||||
```sh
|
||||
docker compose exec -T al-game node -e \
|
||||
'console.log(require("/app/secretsandconfig/keys").ACCESS_MASTER)'
|
||||
```
|
||||
|
||||
Den Wert nicht in Chat, Logs oder Commits schreiben. Vor öffentlichem Betrieb
|
||||
rotieren und `al-backend` sowie `al-game` neu starten.
|
||||
|
||||
## Backup und Restore
|
||||
|
||||
Backup:
|
||||
|
||||
```sh
|
||||
docker compose exec -T al-mongo mongodump \
|
||||
--db adventureland --archive=/tmp/al-backup.gz
|
||||
|
||||
docker compose cp al-mongo:/tmp/al-backup.gz ./al-backup.gz
|
||||
```
|
||||
|
||||
Restore (destruktiv, überschreibt die aktuelle DB):
|
||||
|
||||
```sh
|
||||
docker compose cp ./al-backup.gz al-mongo:/tmp/al-backup.gz
|
||||
|
||||
docker compose exec -T al-mongo mongorestore \
|
||||
--archive=/tmp/al-backup.gz --drop
|
||||
```
|
||||
|
||||
Vor jedem Restore den aktuellen Stand sichern. `--drop` entfernt vorhandene
|
||||
Collections.
|
||||
|
||||
## Logs und Status
|
||||
|
||||
```sh
|
||||
docker compose ps
|
||||
docker compose logs -f al-backend
|
||||
docker compose logs -f al-game
|
||||
docker compose logs -f al-mongo
|
||||
```
|
||||
|
||||
## Neustart und Rebuild
|
||||
|
||||
Nach Änderungen an `files/config/`:
|
||||
|
||||
```sh
|
||||
docker compose restart al-backend al-game
|
||||
```
|
||||
|
||||
Nach Änderungen am Image oder an Build-Patches:
|
||||
|
||||
```sh
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Nach Änderungen an `files/custom/game.js` reicht in der Regel ein Neustart der
|
||||
beiden App-Container.
|
||||
|
||||
## Sicherheit
|
||||
|
||||
- MongoDB ist nicht auf dem Host veröffentlicht; nicht zusätzlich exposen.
|
||||
- `ACCESS_MASTER` erlaubt Codeausführung im Game-Server. Nicht öffentlich
|
||||
erreichbar machen.
|
||||
- `al-backend` und `al-game` sind im Compose-Setup an alle Host-Interfaces
|
||||
gebunden. Für öffentlichen Betrieb Firewall/Caddy davorsetzen.
|
||||
- Default-Keys aus `files/config/keys.js` vor öffentlichem Zugriff ersetzen.
|
||||
- Offline-Characters vor DB-Änderungen ausloggen; sonst überschreibt der
|
||||
Game-Server die Werte.
|
||||
|
||||
## Quellen
|
||||
|
||||
- `docker-compose.yml` – Services, Ports, Volumes
|
||||
- `Dockerfile` – Build-Patches und Runtime
|
||||
- `files/config/keys.js` – Admin-Keys
|
||||
- `files/config/options.js` – Ports, Serverdefinition, Limits
|
||||
- `source-repos/adventureland_mongodb/adventure_functions.js` –
|
||||
`character_to_info`, `update_character`, `update_user_data`, `user_to_server`
|
||||
- `source-repos/adventureland_mongodb/api.js` – `create_character_api`
|
||||
@@ -0,0 +1,256 @@
|
||||
# Admin-Executor (`/admin/executor`)
|
||||
|
||||
Der Admin-Executor ist die bequemste Variante für administrative Änderungen.
|
||||
Er führt **Backend-JavaScript** aus – nicht Game-Server-JavaScript – und hat
|
||||
Zugriff auf die Backend-Funktionen und die MongoDB.
|
||||
|
||||
## Zugang
|
||||
|
||||
Voraussetzung ist `user.admin = true` oder `Local: true` zusammen mit
|
||||
`unsecure_admin: true` in `files/config/options.js`.
|
||||
|
||||
`user.admin` direkt setzen:
|
||||
|
||||
```sh
|
||||
cd /data/ai/gaming/adventureland
|
||||
|
||||
docker compose exec al-mongo mongosh adventureland --eval \
|
||||
'db.user.updateOne({"info.email": "du@example.com"}, { $set: { admin: true } })'
|
||||
```
|
||||
|
||||
Danach im Browser neu einloggen und öffnen:
|
||||
|
||||
```text
|
||||
http://localhost:8090/admin/executor
|
||||
```
|
||||
|
||||
Der frühere Weg `/admin/make/user/admin` ist im Code deaktiviert
|
||||
(`common_engine/admin.js` enthält ein frühes `return;`).
|
||||
|
||||
> **Sicherheit:** Der Executor erlaubt beliebige Codeausführung im Backend.
|
||||
> `unsecure_admin: true` macht jeden lokalen Besucher zum Admin. Niemals
|
||||
> zusammen mit öffentlichem Zugriff verwenden.
|
||||
|
||||
## Was im Executor verfügbar ist
|
||||
|
||||
Neben normalem JavaScript stehen unter anderem zur Verfügung:
|
||||
|
||||
- `req` – der Express-Request des Executor-Aufrufs
|
||||
- `output` – wird als `[OUTPUT]` an die Oberfläche zurückgegeben
|
||||
- `inspect` – wird als JSON zurückgegeben
|
||||
- `console.log()` / `console.error()` – landen in `[LOGS]`
|
||||
- `db` – MongoDB-Handle
|
||||
- `get_user_by_email(email)`, `get_character(name)`, `get(id)`
|
||||
- `save(entity)`, `tx()`, `tx_get()`, `tx_save()`
|
||||
- `update_characters(user, reason, name, shells)`
|
||||
- `character_eval(character, code, data)`
|
||||
- `server_eval(server, code, data)`, `server_eval_safe(server, code, data)`
|
||||
- `keys` – die Server-Keys, inklusive `ACCESS_MASTER`
|
||||
|
||||
`character_eval` führt Code auf dem Game-Server aus. Dort heißt der laufende
|
||||
Character `player`. Ist der Character offline, passiert nichts.
|
||||
|
||||
## Beispiele
|
||||
|
||||
### Shells geben (accountweit)
|
||||
|
||||
Shells liegen in `user.cash`. `update_characters` schickt den neuen Stand an
|
||||
alle online Characters.
|
||||
|
||||
```js
|
||||
var u = await get_user_by_email("du@example.com");
|
||||
if (!u) throw new Error("user not found");
|
||||
|
||||
var amount = 1000;
|
||||
u.cash = (u.cash || 0) + amount;
|
||||
await save(u);
|
||||
|
||||
await update_characters(u, "", "", amount);
|
||||
|
||||
output = { email: u.info.email, cash: u.cash };
|
||||
```
|
||||
|
||||
### Bank-Gold geben (accountweit)
|
||||
|
||||
Bank-Gold liegt in `user.info.gold`.
|
||||
|
||||
```js
|
||||
var u = await get_user_by_email("du@example.com");
|
||||
if (!u) throw new Error("user not found");
|
||||
|
||||
var amount = 100000;
|
||||
u.info.gold = (u.info.gold || 0) + amount;
|
||||
await save(u);
|
||||
|
||||
output = { bank_gold: u.info.gold };
|
||||
```
|
||||
|
||||
Danach neu einloggen bzw. die Bank betreten. Steht der Character bereits in der
|
||||
Bank, ist `player.user.gold` live veraltet; dann zusätzlich per
|
||||
`character_eval` aktualisieren oder neu einloggen.
|
||||
|
||||
### Character-Gold geben
|
||||
|
||||
Character-Gold liegt in `character.info.gold`.
|
||||
|
||||
```js
|
||||
var ch = await get_character("CharName");
|
||||
if (!ch) throw new Error("character not found");
|
||||
|
||||
var amount = 100000;
|
||||
|
||||
if (ch.online || ch.server) {
|
||||
await character_eval(
|
||||
ch,
|
||||
"player.gold += " + amount + "; resend(player, 'reopen+u+cid');"
|
||||
);
|
||||
output = { character: ch.info.name, mode: "live" };
|
||||
} else {
|
||||
ch.info.gold = (ch.info.gold || 0) + amount;
|
||||
await save(ch);
|
||||
output = { character: ch.info.name, gold: ch.info.gold, mode: "db" };
|
||||
}
|
||||
```
|
||||
|
||||
### Item geben
|
||||
|
||||
Live nutzt `add_item` Stacking und Inventarprüfung. Offline wird direkt in
|
||||
`character.info.items` geschrieben; dort gibt es keine Prüfung.
|
||||
|
||||
```js
|
||||
var ch = await get_character("CharName");
|
||||
if (!ch) throw new Error("character not found");
|
||||
|
||||
var item = { name: "hpot0", q: 100 };
|
||||
|
||||
if (ch.online || ch.server) {
|
||||
await character_eval(
|
||||
ch,
|
||||
"add_item(player, " + JSON.stringify(item) + ", {announce:false});" +
|
||||
"resend(player, 'reopen+u+cid');"
|
||||
);
|
||||
output = { character: ch.info.name, item: item, mode: "live" };
|
||||
} else {
|
||||
ch.info.items = ch.info.items || [];
|
||||
ch.info.items.push(item);
|
||||
await save(ch);
|
||||
output = { character: ch.info.name, item: item, mode: "db" };
|
||||
}
|
||||
```
|
||||
|
||||
Item-Namen stehen in `design/items.js` (`G.items`), zum Beispiel `hpot0`,
|
||||
`mpot0`, `helmet`, `shoes`. Das Inventar fasst maximal 42 Einträge.
|
||||
|
||||
### XP geben
|
||||
|
||||
```js
|
||||
var ch = await get_character("CharName");
|
||||
if (!ch) throw new Error("character not found");
|
||||
|
||||
var amount = 100000;
|
||||
|
||||
if (ch.online || ch.server) {
|
||||
await character_eval(
|
||||
ch,
|
||||
"player.xp += " + amount + "; resend(player, 'reopen');"
|
||||
);
|
||||
output = { character: ch.info.name, mode: "live" };
|
||||
} else {
|
||||
ch.xp = (ch.xp || 0) + amount;
|
||||
ch.info.xp = ch.xp;
|
||||
await save(ch);
|
||||
output = { character: ch.info.name, xp: ch.xp, mode: "db" };
|
||||
}
|
||||
```
|
||||
|
||||
`resend(player, "reopen")` ruft `calculate_player_stats` auf und führt
|
||||
Level-Ups aus. Offline passiert das beim nächsten Login.
|
||||
|
||||
### Item in die Bank legen
|
||||
|
||||
Bank-Packs liegen in `user.info.items0` bis `user.info.items47`.
|
||||
|
||||
```js
|
||||
var u = await get_user_by_email("du@example.com");
|
||||
if (!u) throw new Error("user not found");
|
||||
|
||||
u.info.items0 = u.info.items0 || [];
|
||||
u.info.items0.push({ name: "hpot0", q: 100 });
|
||||
await save(u);
|
||||
|
||||
output = { pack: "items0", count: u.info.items0.length };
|
||||
```
|
||||
|
||||
Danach neu einloggen bzw. die Bank betreten. Pack-Größe beachten (bis 42).
|
||||
|
||||
### Charakterplätze setzen
|
||||
|
||||
```js
|
||||
var u = await get_user_by_email("du@example.com");
|
||||
if (!u) throw new Error("user not found");
|
||||
|
||||
u.info.slots = 18;
|
||||
await save(u);
|
||||
|
||||
output = { slots: u.info.slots };
|
||||
```
|
||||
|
||||
`info.slots` wird bei jeder Anfrage gelesen; ein Neustart ist nicht nötig.
|
||||
Maximum sind 18.
|
||||
|
||||
### Account verifizieren
|
||||
|
||||
```js
|
||||
var u = await get_user_by_email("du@example.com");
|
||||
if (!u) throw new Error("user not found");
|
||||
|
||||
u.info.verified = true;
|
||||
await save(u);
|
||||
|
||||
output = { verified: true };
|
||||
```
|
||||
|
||||
### GM setzen
|
||||
|
||||
GM ist eine Character-Rolle, kein Web-Admin.
|
||||
|
||||
```js
|
||||
var ch = await get_character("CharName");
|
||||
if (!ch) throw new Error("character not found");
|
||||
|
||||
ch.role = "gm";
|
||||
ch.info.p = ch.info.p || {};
|
||||
ch.info.p.role = "gm";
|
||||
await save(ch);
|
||||
|
||||
output = { character: ch.info.name, role: "gm" };
|
||||
```
|
||||
|
||||
Danach den Character neu einloggen.
|
||||
|
||||
## Grenzen
|
||||
|
||||
- Der Executor ist **kein** Satz benannter Befehle wie `/give shells`.
|
||||
Es ist eine Codebox.
|
||||
- Er läuft im Backend. Für Funktionen, die es nur im Game-Server gibt
|
||||
(`players`, `add_item`, `resend`), `character_eval` bzw. `server_eval`
|
||||
verwenden.
|
||||
- Offline-Änderungen an `character.info.*` werden beim nächsten Sync des
|
||||
Game-Servers überschrieben, wenn der Character online ist. Dann live
|
||||
arbeiten oder den Character vorher ausloggen.
|
||||
- `aadd_item` aus `node/server.js` nicht verwenden: Es ruft eine nicht
|
||||
definierte `reopen()`-Funktion auf. Stattdessen `add_item(...)` +
|
||||
`resend(...)`.
|
||||
|
||||
## Quellen
|
||||
|
||||
- `source-repos/common_engine/admin.js` – `/admin/executor`, `execute_api`,
|
||||
`is_admin`
|
||||
- `source-repos/common_engine/htmls/admin_executor.html` – Oberfläche
|
||||
- `source-repos/adventureland_mongodb/adventure_functions.js` –
|
||||
`get_user_by_email`, `get_character`, `update_characters`,
|
||||
`character_eval`, `server_eval`, `update_character`, `update_user_data`,
|
||||
`user_to_server`
|
||||
- `source-repos/adventureland_mongodb/node/server.js` – `add_item`,
|
||||
`calculate_player_stats`, `resend`
|
||||
- `source-repos/adventureland_mongodb/design/items.js` – Item-Definitionen
|
||||
@@ -54,6 +54,11 @@ module.exports = {
|
||||
// these values; 9 here allows 3 accounts with 3 characters each from one IP.
|
||||
ip_limit: 9,
|
||||
character_limit: 3,
|
||||
// Minimum length for newly created character names. Upstream is 4; 3 lets
|
||||
// short names be created for free instead of only via the paid rename
|
||||
// (3 characters costs 24,000 shells). Valid 1-12; invalid values fall back
|
||||
// to 4. Rename/chat/login already accept short names either way.
|
||||
character_name_min_length: 3,
|
||||
// Max concurrent merchant characters per IP and per account. Upstream is
|
||||
// hard-coded to 1/1; this local build reads these options in login and in
|
||||
// the backend cron. 3 here means three merchant accounts may share one IP.
|
||||
|
||||
Reference in New Issue
Block a user