Compare commits
2
Commits
793797dacb
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e98395b5d | ||
|
|
b70127a57b |
@@ -1,3 +1,4 @@
|
|||||||
|
ai/*
|
||||||
files/*
|
files/*
|
||||||
!files/config
|
!files/config
|
||||||
!files/config/**
|
!files/config/**
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user