This commit is contained in:
+15
@@ -74,6 +74,21 @@ RUN node /tmp/patch-custom-game.js /app
|
|||||||
COPY docker/patch-auto-verify-email.js /tmp/patch-auto-verify-email.js
|
COPY docker/patch-auto-verify-email.js /tmp/patch-auto-verify-email.js
|
||||||
RUN node /tmp/patch-auto-verify-email.js /app
|
RUN node /tmp/patch-auto-verify-email.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.
|
||||||
|
COPY docker/patch-tutorial-reward-auth.js /tmp/patch-tutorial-reward-auth.js
|
||||||
|
RUN node /tmp/patch-tutorial-reward-auth.js /app
|
||||||
|
|
||||||
|
# Local patch: make the merchant and non-merchant multi-account limits
|
||||||
|
# configurable (docker/patch-multi-account-limits.js). Upstream hard-codes one
|
||||||
|
# merchant per IP/owner and three non-merchant characters per IP/owner in the
|
||||||
|
# backend enforcement cron, while login uses options.ip_limit/character_limit.
|
||||||
|
# The patch makes both places use options.merchant_ip_limit,
|
||||||
|
# options.merchant_owner_limit, options.ip_limit and options.character_limit.
|
||||||
|
COPY docker/patch-multi-account-limits.js /tmp/patch-multi-account-limits.js
|
||||||
|
RUN node /tmp/patch-multi-account-limits.js /app
|
||||||
|
|
||||||
|
|
||||||
FROM ${NODE_IMAGE} AS runtime
|
FROM ${NODE_IMAGE} AS runtime
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,12 @@ und Game-Server. Bei späteren Starts überspringt sich der Seed selbst.
|
|||||||
[Zum Entwickeln]“** klicken, E-Mail + Passwort eingeben. Danach Charakter
|
[Zum Entwickeln]“** klicken, E-Mail + Passwort eingeben. Danach Charakter
|
||||||
erstellen und auf den Charakter klicken – man landet direkt im Spiel.
|
erstellen und auf den Charakter klicken – man landet direkt im Spiel.
|
||||||
|
|
||||||
|
**Charakter Limits anheben** Folgendes Kommando setzt für alle existierenden Spieler das Charakter Limit auf 18.
|
||||||
|
```sh
|
||||||
|
docker compose exec al-mongo mongosh adventureland --eval \
|
||||||
|
'db.user.updateMany({}, { $set: { "info.slots": 18 } })'
|
||||||
|
```
|
||||||
|
|
||||||
**Admin werden:** Die Admin-Seiten (`/admin/executor`, `/admin/renderer`) sind
|
**Admin werden:** Die Admin-Seiten (`/admin/executor`, `/admin/renderer`) sind
|
||||||
standardmäßig gesperrt. `is_admin` in `common/admin.js` prüft das Feld
|
standardmäßig gesperrt. `is_admin` in `common/admin.js` prüft das Feld
|
||||||
`user.admin`; setzen lässt es sich direkt in MongoDB:
|
`user.admin`; setzen lässt es sich direkt in MongoDB:
|
||||||
@@ -40,6 +46,37 @@ Alternativ in `config/options.js` `unsecure_admin: true` setzen – dann gilt
|
|||||||
**jeder** Besucher als Admin (`/admin/executor` erlaubt Code-Ausführung), daher
|
**jeder** Besucher als Admin (`/admin/executor` erlaubt Code-Ausführung), daher
|
||||||
nur bei rein lokalem Zugriff sinnvoll.
|
nur bei rein lokalem Zugriff 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:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose exec al-mongo mongosh adventureland --eval \
|
||||||
|
'db.character.updateOne(
|
||||||
|
{ name: "CharName" },
|
||||||
|
{ $set: { role: "gm", "info.p.role": "gm" } }
|
||||||
|
)'
|
||||||
|
```
|
||||||
|
|
||||||
|
Danach den Charakter neu einloggen. `role: "gm"` gibt die In-Game-GM-Rechte und
|
||||||
|
ist unabhängig von `user.admin` (Web-Admin). Wer **nur** von den Multi-Account-
|
||||||
|
Limits ausgenommen werden will, aber keine GM-Rechte braucht, setzt stattdessen
|
||||||
|
`info.p.free`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose exec al-mongo mongosh adventureland --eval \
|
||||||
|
'db.character.updateOne(
|
||||||
|
{ name: "CharName" },
|
||||||
|
{ $set: { "info.p.free": true } }
|
||||||
|
)'
|
||||||
|
```
|
||||||
|
|
||||||
|
| Feld | Wirkung |
|
||||||
|
| --- | --- |
|
||||||
|
| `user.admin: true` | Web-Admin: `/admin/executor`, `/admin/renderer`; keine In-Game-GM-Rechte |
|
||||||
|
| Charakter `role: "gm"` | In-Game-GM; wird außerdem von den Limits ausgenommen |
|
||||||
|
| Charakter `info.p.free: true` | nur Limit-Ausnahme; keine GM-Rechte |
|
||||||
|
|
||||||
## Aufbau
|
## Aufbau
|
||||||
|
|
||||||
| Service | Image | Aufgabe |
|
| Service | Image | Aufgabe |
|
||||||
@@ -61,7 +98,8 @@ ersetzt das Template aus dem Upstream-Repo:
|
|||||||
- `config/keys.js` – MongoDB-URI, Master-Keys, Stripe/Steam/SES (alles leer bzw.
|
- `config/keys.js` – MongoDB-URI, Master-Keys, Stripe/Steam/SES (alles leer bzw.
|
||||||
feste Entwicklungs-Defaults). Die Werte sind absichtlich stabil, damit
|
feste Entwicklungs-Defaults). Die Werte sind absichtlich stabil, damit
|
||||||
Sitzungen und Serverdatensätze Neustarts überleben.
|
Sitzungen und Serverdatensätze Neustarts überleben.
|
||||||
- `config/options.js` – `base_url`, Ports, Serverdefinition `local`.
|
- `config/options.js` – `base_url`, Ports, Serverdefinition `local`,
|
||||||
|
Multi-Account-Limits.
|
||||||
|
|
||||||
Beide Dateien sind normale Node-Module; nach Änderungen Container neu starten:
|
Beide Dateien sind normale Node-Module; nach Änderungen Container neu starten:
|
||||||
|
|
||||||
@@ -73,6 +111,39 @@ docker compose restart backend game
|
|||||||
> `docker-compose.yml` (`ports`), `config/options.js` (`port`, `local_port`,
|
> `docker-compose.yml` (`ports`), `config/options.js` (`port`, `local_port`,
|
||||||
> `address`) und `base_url` (inkl. Port).
|
> `address`) und `base_url` (inkl. Port).
|
||||||
|
|
||||||
|
### Multi-Account-Limits
|
||||||
|
|
||||||
|
`config/options.js` steuert, wie viele Charaktere gleichzeitig online sein
|
||||||
|
dürfen:
|
||||||
|
|
||||||
|
| Option | Bedeutung | aktuell |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `ip_limit` | Nicht-Händler pro IP (mal `ipx` einer IP-Ausnahme) | `9` |
|
||||||
|
| `character_limit` | Nicht-Händler pro Konto | `3` |
|
||||||
|
| `merchant_ip_limit` | Händler pro IP | `3` |
|
||||||
|
| `merchant_owner_limit` | Händler pro Konto | `1` |
|
||||||
|
|
||||||
|
Die Werte gelten beim Login (`is_player_allowed`) und im Backend-Cron
|
||||||
|
(`enforce_limitations`). Der Patch
|
||||||
|
`docker/patch-multi-account-limits.js` ersetzt dabei die früher fest codierten
|
||||||
|
Grenzen (3 Nicht-Händler, 1 Händler). Mit den aktuellen Werten können z. B. drei
|
||||||
|
Konten von derselben IP jeweils drei Nicht-Händler-Charaktere online haben
|
||||||
|
(9 insgesamt); ein zehntes Nicht-Händler-Charakter bzw. ein vierter Händler wird
|
||||||
|
abgewiesen. `character_limit` ist die Zahl der **gleichzeitig online** erlaubten
|
||||||
|
Charaktere, nicht die Zahl der Charakterplätze (`info.slots`); `ip_limit` ist
|
||||||
|
ebenfalls keine Slotzahl. Eine `IP_<ip>`-Ausnahme mit
|
||||||
|
`{ exception: true, info: { limit: N } }` multipliziert nur `ip_limit` für
|
||||||
|
Nicht-Händler, nicht die Händler-Limits. Nicht-freie Händler zählen; `p.free`
|
||||||
|
und GM-Charaktere fallen heraus. `merchant_owner_limit: 1` erlaubt einen Händler
|
||||||
|
pro Konto; Admin- und User-Händler auf getrennten Konten sind damit kein
|
||||||
|
Problem. Sollen beide auf **demselben** Konto laufen, `merchant_owner_limit` auf
|
||||||
|
`2` setzen.
|
||||||
|
|
||||||
|
> Hinter einem Reverse-Proxy (Caddy) muss die echte Client-IP als
|
||||||
|
> `X-Forwarded-For` ankommen. Sonst sieht der Server nur die Proxy-/Docker-IP
|
||||||
|
> und alle Spieler teilen sich dieselben Limits. Siehe
|
||||||
|
> `deploy/Caddyfile.example`.
|
||||||
|
|
||||||
### MongoDB-Zugriff
|
### MongoDB-Zugriff
|
||||||
|
|
||||||
Die Datenbank ist nur im Compose-Netz erreichbar. Für einen Blick hinein:
|
Die Datenbank ist nur im Compose-Netz erreichbar. Für einen Blick hinein:
|
||||||
@@ -145,6 +216,21 @@ Login als verifiziert und überspringt die Verifikationsmail. Ohne den Schalter
|
|||||||
bleibt das Upstream-Verhalten unverändert. Auch dieser Patch bricht den Build
|
bleibt das Upstream-Verhalten unverändert. Auch dieser Patch bricht den Build
|
||||||
ab, wenn ein Anker in `api.js` fehlt.
|
ab, wenn ein Anker in `api.js` fehlt.
|
||||||
|
|
||||||
|
`docker/patch-tutorial-reward-auth.js` macht die `auth_id`-Pflicht der
|
||||||
|
Tutorial- und Klassenbelohnungen abschaltbar. Bei
|
||||||
|
`options.tutorial_reward_requires_auth_id: false` können auch reine Web-Accounts
|
||||||
|
`c0` und Klassenbelohnungen abholen; fehlt die Option oder steht sie auf `true`,
|
||||||
|
gilt das Upstream-Verhalten. Fehlt einer der beiden Anker in `node/server.js`,
|
||||||
|
bricht der Build ab.
|
||||||
|
|
||||||
|
`docker/patch-multi-account-limits.js` macht die Händler- und Nicht-Händler-
|
||||||
|
Limits konfigurierbar. Upstream prüft beim Login `options.ip_limit` und
|
||||||
|
`options.character_limit`, würgt im Backend-Cron aber pauschal nach 3
|
||||||
|
Nicht-Händlern bzw. 1 Händler ab. Der Patch liest beide Stellen aus
|
||||||
|
`config/options.js` (`ip_limit`, `character_limit`, `merchant_ip_limit`,
|
||||||
|
`merchant_owner_limit`). Fehlt ein Anker in `node/server_functions.js` oder
|
||||||
|
`adventure_functions.js`, bricht der Build ab.
|
||||||
|
|
||||||
## Spieldaten anpassen (`files/custom/game.js`)
|
## Spieldaten anpassen (`files/custom/game.js`)
|
||||||
|
|
||||||
Werte bestehender Elemente und neue Elemente lassen sich ohne Quelltextänderung
|
Werte bestehender Elemente und neue Elemente lassen sich ohne Quelltextänderung
|
||||||
@@ -207,15 +293,17 @@ Custom-Datei zurückgesetzt werden; dann den Game-Server neu starten.
|
|||||||
auf `true` setzen (siehe Admin-Befehl oben). Die `/ev`-Route bleibt in beiden
|
auf `true` setzen (siehe Admin-Befehl oben). Die `/ev`-Route bleibt in beiden
|
||||||
Fällen funktionsfähig.
|
Fällen funktionsfähig.
|
||||||
|
|
||||||
- **Tutorial-Belohnung (`c0`):** Jedes Konto mit `auth_id` (Steam/MAS) kann nach
|
- **Tutorial-Belohnung (`c0`):** Jedes verifizierte Konto kann nach
|
||||||
abgeschlossenem Tutorial einmal `c0` abholen. Die Belohnung ist ein zufälliger
|
abgeschlossenem Tutorial einmal `c0` abholen. Die Belohnung ist ein zufälliger
|
||||||
kosmetischer Look aus dem `cosmo0`-Pool (über 100 Varianten). Der Game-Server
|
kosmetischer Look aus dem `cosmo0`-Pool (über 100 Varianten). Der Game-Server
|
||||||
prüft dafür `player.verified` **und** `player.auth_id`
|
prüft `player.verified` und normalerweise `player.auth_id` (Steam/MAS,
|
||||||
(`node/server.js:5272`). Bei reinen Web-Accounts ohne Steam/MAS bleibt
|
`node/server.js`, `ureward`/`creward`). Bei reinen Web-Accounts ist `auth_id`
|
||||||
`auth_id` leer, deshalb ist die Belohnung trotz Auto-Verifizierung weiterhin
|
leer; deshalb steht `options.tutorial_reward_requires_auth_id` hier auf
|
||||||
gesperrt. Die alten Klassen-Belohnungen (`creward`) sind in
|
`false`, damit auch sie die Belohnung erhalten. Auf `true` gestellt, gilt
|
||||||
`design/classes.js` auskommentiert und werden vom Client nicht angefragt;
|
wieder die Upstream-Sperre für Web-Accounts. Die alten Klassen-Belohnungen
|
||||||
Hardcore-Rewards gelten nur auf dem Hardcore-Server.
|
(`creward`) nutzen denselben Schalter, sind aber in `design/classes.js`
|
||||||
|
auskommentiert und werden vom Client nicht angefragt; Hardcore-Rewards gelten
|
||||||
|
nur auf dem Hardcore-Server.
|
||||||
|
|
||||||
- **„Authorization Failure“ (Gold/Glück −85 %, XP −20 %):** Das ist die
|
- **„Authorization Failure“ (Gold/Glück −85 %, XP −20 %):** Das ist die
|
||||||
DRM-Prüfung des Game-Servers. Sie verlangt für jedes Konto ab 2019 eine
|
DRM-Prüfung des Game-Servers. Sie verlangt für jedes Konto ab 2019 eine
|
||||||
|
|||||||
@@ -20,7 +20,12 @@ al.example.com {
|
|||||||
@game path /socket.io/* /socket.io-msgpack/* /server.api/*
|
@game path /socket.io/* /socket.io-msgpack/* /server.api/*
|
||||||
handle @game {
|
handle @game {
|
||||||
reverse_proxy 10.10.16.28:7192 {
|
reverse_proxy 10.10.16.28:7192 {
|
||||||
}
|
# Overwrite, not append: Adventure Land reads the first
|
||||||
|
# X-Forwarded-For entry for its IP limits. Without this a client
|
||||||
|
# could send a fake header and bypass the limits.
|
||||||
|
header_up X-Forwarded-For {remote_host}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# --- Web backend (container port 8090) ------------------------------
|
# --- Web backend (container port 8090) ------------------------------
|
||||||
# Everything else: HTML, JS, /api/*, static files.
|
# Everything else: HTML, JS, /api/*, static files.
|
||||||
@@ -30,6 +35,8 @@ al.example.com {
|
|||||||
# auth cookie domain from it.
|
# auth cookie domain from it.
|
||||||
header_up Host {host}
|
header_up Host {host}
|
||||||
# Overwrite the client IP; see the game handler above.
|
# Overwrite the client IP; see the game handler above.
|
||||||
|
header_up X-Forwarded-For {remote_host}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Internal-only TLS (no Let's Encrypt). Clients must trust Caddy's CA.
|
# Internal-only TLS (no Let's Encrypt). Clients must trust Caddy's CA.
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Local patch: make the multi-account limits configurable.
|
||||||
|
//
|
||||||
|
// Upstream hard-codes:
|
||||||
|
// - one merchant per IP and per owner,
|
||||||
|
// - three non-merchant characters per IP and per owner,
|
||||||
|
// and only checks some of those values at login.
|
||||||
|
//
|
||||||
|
// This patch reads the limits from options.js in both places:
|
||||||
|
// - is_player_allowed() (login, node/server_functions.js)
|
||||||
|
// - enforce_limitations() (backend cron, adventure_functions.js)
|
||||||
|
//
|
||||||
|
// New options (defaults match upstream):
|
||||||
|
// merchant_ip_limit: 1
|
||||||
|
// merchant_owner_limit: 1
|
||||||
|
// ip_limit: 3
|
||||||
|
// character_limit: 3
|
||||||
|
//
|
||||||
|
// The patch is idempotent and fails loudly if an anchor is missing.
|
||||||
|
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
|
||||||
|
const app = path.resolve(process.argv[2] || "/app");
|
||||||
|
const serverFunctionsFile = path.join(app, "node", "server_functions.js");
|
||||||
|
const adventureFunctionsFile = path.join(app, "adventure_functions.js");
|
||||||
|
|
||||||
|
function fail(message) {
|
||||||
|
console.error("patch-multi-account-limits: " + message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceOnce(source, from, to, label) {
|
||||||
|
const count = source.split(from).length - 1;
|
||||||
|
if (count !== 1) {
|
||||||
|
fail(label + ": expected 1 anchor, found " + count);
|
||||||
|
}
|
||||||
|
return source.replace(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchServerFunctions(source) {
|
||||||
|
const marker = "function get_multi_account_identity(player) {";
|
||||||
|
const oldMerchant =
|
||||||
|
'\tif (player.type == "merchant") {\n' +
|
||||||
|
"\t\tfor (var id in players) {\n" +
|
||||||
|
"\t\t\tif ((players[id].stones && 0) || id == player.socket.id || is_free(player)) {\n" +
|
||||||
|
"\t\t\t\tcontinue;\n" +
|
||||||
|
"\t\t\t}\n" +
|
||||||
|
'\t\t\tif (players[id].owner == player.owner && players[id].type == "merchant") {\n' +
|
||||||
|
"\t\t\t\treturn false;\n" +
|
||||||
|
"\t\t\t}\n" +
|
||||||
|
"\t\t}\n" +
|
||||||
|
"\t\treturn true;\n" +
|
||||||
|
"\t}";
|
||||||
|
|
||||||
|
if (source.includes(marker)) {
|
||||||
|
if (source.includes(oldMerchant)) {
|
||||||
|
fail("server_functions.js has a partial merchant patch");
|
||||||
|
}
|
||||||
|
console.log("patch-multi-account-limits: server_functions.js already patched");
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
source = replaceOnce(
|
||||||
|
source,
|
||||||
|
'function get_limit_identity(player) {\n\treturn player && player.mainframe === true ? "mainframe:" + player.owner : get_ip_server(player);\n}',
|
||||||
|
'function get_limit_identity(player) {\n\treturn player && player.mainframe === true ? "mainframe:" + player.owner : get_ip_server(player);\n}\n\n' +
|
||||||
|
"// Multi-account identity; mirrors the auth/temp_auth handling in enforce_limitations().\n" +
|
||||||
|
"function get_multi_account_identity(player) {\n" +
|
||||||
|
'\tif (player.temp_auth) return "owner:" + player.owner;\n' +
|
||||||
|
'\tif (player.auth_id) return "auth:" + player.auth_id;\n' +
|
||||||
|
"\treturn get_limit_identity(player);\n" +
|
||||||
|
"}",
|
||||||
|
"get_limit_identity",
|
||||||
|
);
|
||||||
|
|
||||||
|
const newMerchant =
|
||||||
|
"\t// Multi-account limits: configured in options.js; the backend cron uses the same values.\n" +
|
||||||
|
'\tif (player.type == "merchant") {\n' +
|
||||||
|
"\t\tvar merchant_limit = options.merchant_owner_limit === undefined ? 1 : options.merchant_owner_limit;\n" +
|
||||||
|
"\t\tvar merchant_ip_limit = options.merchant_ip_limit === undefined ? 1 : options.merchant_ip_limit;\n" +
|
||||||
|
"\t\tvar merchants = 0;\n" +
|
||||||
|
"\t\tvar merchant_ips = 0;\n" +
|
||||||
|
"\t\tvar merchant_identity = get_multi_account_identity(player);\n" +
|
||||||
|
"\t\tfor (var id in players) {\n" +
|
||||||
|
'\t\t\tif (id == player.socket.id || is_free(players[id]) || players[id].type != "merchant") {\n' +
|
||||||
|
"\t\t\t\tcontinue;\n" +
|
||||||
|
"\t\t\t}\n" +
|
||||||
|
"\t\t\tif (players[id].owner == player.owner) {\n" +
|
||||||
|
"\t\t\t\tmerchants++;\n" +
|
||||||
|
"\t\t\t}\n" +
|
||||||
|
"\t\t\tif (get_multi_account_identity(players[id]) == merchant_identity) {\n" +
|
||||||
|
"\t\t\t\tmerchant_ips++;\n" +
|
||||||
|
"\t\t\t}\n" +
|
||||||
|
"\t\t}\n" +
|
||||||
|
"\t\tif (merchants >= merchant_limit || merchant_ips >= merchant_ip_limit) {\n" +
|
||||||
|
"\t\t\treturn false;\n" +
|
||||||
|
"\t\t}\n" +
|
||||||
|
"\t\treturn true;\n" +
|
||||||
|
"\t}";
|
||||||
|
|
||||||
|
source = replaceOnce(source, oldMerchant, newMerchant, "merchant branch");
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchAdventureFunctions(source) {
|
||||||
|
const marker = "var merchant_ip_limit = options.merchant_ip_limit === undefined ? 1 : options.merchant_ip_limit;";
|
||||||
|
const oldMerchant =
|
||||||
|
'\t\t\t\tif (player.type === "merchant" && (mips[player.ip] > 1 || mowners[player.owner] > 1) && to_disconnect.indexOf(player.name) === -1) {';
|
||||||
|
const oldOther =
|
||||||
|
'\t\t\t\tif (player.type !== "merchant" && (ips[player.ip] > 3 * (ipx[player.ip] || 1) || owners[player.owner] > 3) && to_disconnect.indexOf(player.name) === -1) {';
|
||||||
|
|
||||||
|
if (source.includes(marker)) {
|
||||||
|
if (source.includes(oldMerchant) || source.includes(oldOther)) {
|
||||||
|
fail("adventure_functions.js has a partial limits patch");
|
||||||
|
}
|
||||||
|
console.log("patch-multi-account-limits: adventure_functions.js already patched");
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
source = replaceOnce(
|
||||||
|
source,
|
||||||
|
"\t\t\tvar to_disconnect = [];",
|
||||||
|
"\t\t\tvar to_disconnect = [];\n" +
|
||||||
|
"\t\t\tvar merchant_ip_limit = options.merchant_ip_limit === undefined ? 1 : options.merchant_ip_limit;\n" +
|
||||||
|
"\t\t\tvar merchant_owner_limit = options.merchant_owner_limit === undefined ? 1 : options.merchant_owner_limit;\n" +
|
||||||
|
"\t\t\tvar ip_limit = options.ip_limit === undefined ? 3 : options.ip_limit;\n" +
|
||||||
|
"\t\t\tvar character_limit = options.character_limit === undefined ? 3 : options.character_limit;",
|
||||||
|
"limit variables",
|
||||||
|
);
|
||||||
|
|
||||||
|
source = replaceOnce(
|
||||||
|
source,
|
||||||
|
oldMerchant,
|
||||||
|
"\t\t\t\tif (\n" +
|
||||||
|
'\t\t\t\t\tplayer.type === "merchant" &&\n' +
|
||||||
|
"\t\t\t\t\t(mips[player.ip] > merchant_ip_limit || mowners[player.owner] > merchant_owner_limit) &&\n" +
|
||||||
|
"\t\t\t\t\tto_disconnect.indexOf(player.name) === -1\n" +
|
||||||
|
"\t\t\t\t) {",
|
||||||
|
"merchant condition",
|
||||||
|
);
|
||||||
|
|
||||||
|
source = replaceOnce(
|
||||||
|
source,
|
||||||
|
oldOther,
|
||||||
|
"\t\t\t\tif (\n" +
|
||||||
|
'\t\t\t\t\tplayer.type !== "merchant" &&\n' +
|
||||||
|
"\t\t\t\t\t(ips[player.ip] > ip_limit * (ipx[player.ip] || 1) || owners[player.owner] > character_limit) &&\n" +
|
||||||
|
"\t\t\t\t\tto_disconnect.indexOf(player.name) === -1\n" +
|
||||||
|
"\t\t\t\t) {",
|
||||||
|
"non-merchant condition",
|
||||||
|
);
|
||||||
|
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
let serverFunctions = fs.readFileSync(serverFunctionsFile, "utf8");
|
||||||
|
serverFunctions = patchServerFunctions(serverFunctions);
|
||||||
|
fs.writeFileSync(serverFunctionsFile, serverFunctions);
|
||||||
|
|
||||||
|
let adventureFunctions = fs.readFileSync(adventureFunctionsFile, "utf8");
|
||||||
|
adventureFunctions = patchAdventureFunctions(adventureFunctions);
|
||||||
|
fs.writeFileSync(adventureFunctionsFile, adventureFunctions);
|
||||||
|
|
||||||
|
console.log("patch-multi-account-limits: wrote " + serverFunctionsFile + " and " + adventureFunctionsFile);
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Local patch: make the platform auth-id requirement for rewards optional.
|
||||||
|
//
|
||||||
|
// Adventure Land's tutorial (ureward) and class (creward) handlers require
|
||||||
|
// player.verified and player.auth_id. auth_id is only populated for Steam/MAS
|
||||||
|
// accounts; plain web accounts have an empty pid/auth_id and therefore can
|
||||||
|
// never claim these rewards. On a self-hosted server that requirement is
|
||||||
|
// usually unnecessary.
|
||||||
|
//
|
||||||
|
// options.tutorial_reward_requires_auth_id:
|
||||||
|
// true/absent -> upstream behaviour (auth_id required)
|
||||||
|
// false -> verified web accounts may claim the rewards too
|
||||||
|
//
|
||||||
|
// The patch is idempotent and fails loudly if the two anchors are gone.
|
||||||
|
|
||||||
|
const fs = require("node:fs");
|
||||||
|
const path = require("node:path");
|
||||||
|
|
||||||
|
const app = path.resolve(process.argv[2] || "/app");
|
||||||
|
const file = path.join(app, "node", "server.js");
|
||||||
|
let source = fs.readFileSync(file, "utf8");
|
||||||
|
|
||||||
|
const from = `\t\t\tif (!player.verified || !player.auth_id) {`;
|
||||||
|
const to = `\t\t\tif (
|
||||||
|
\t\t\t\t!player.verified ||
|
||||||
|
\t\t\t\t((typeof options === "undefined" || options.tutorial_reward_requires_auth_id !== false) && !player.auth_id)
|
||||||
|
\t\t\t) {`;
|
||||||
|
|
||||||
|
function fail(message) {
|
||||||
|
console.error("patch-tutorial-reward-auth: " + message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const unpatched = source.split(from).length - 1;
|
||||||
|
const patched = source.split(to).length - 1;
|
||||||
|
|
||||||
|
if (unpatched === 2) {
|
||||||
|
source = source.split(from).join(to);
|
||||||
|
console.log("patch-tutorial-reward-auth: patched ureward and creward gates");
|
||||||
|
} else if (unpatched === 0 && patched === 2) {
|
||||||
|
console.log("patch-tutorial-reward-auth: already patched");
|
||||||
|
} else {
|
||||||
|
fail("expected 2 reward gates, found " + unpatched + " unpatched and " + patched + " patched; refusing partial patch");
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(file, source);
|
||||||
|
console.log("patch-tutorial-reward-auth: wrote " + file);
|
||||||
+13
-1
@@ -49,12 +49,24 @@ module.exports = {
|
|||||||
merchant_map: "main",
|
merchant_map: "main",
|
||||||
port: 8090,
|
port: 8090,
|
||||||
close_timeout: 4000,
|
close_timeout: 4000,
|
||||||
ip_limit: 3,
|
// Max concurrent non-merchant characters per IP (multiplied by an IP
|
||||||
|
// exception's limit) and per account. Login and the backend cron both use
|
||||||
|
// these values; 9 here allows 3 accounts with 3 characters each from one IP.
|
||||||
|
ip_limit: 9,
|
||||||
character_limit: 3,
|
character_limit: 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.
|
||||||
|
merchant_ip_limit: 3,
|
||||||
|
merchant_owner_limit: 1,
|
||||||
// Without an email service, mark new accounts as verified at signup and
|
// Without an email service, mark new accounts as verified at signup and
|
||||||
// keep existing accounts verified on login. Set to false to restore the
|
// keep existing accounts verified on login. Set to false to restore the
|
||||||
// upstream verification flow (requires AWS SES).
|
// upstream verification flow (requires AWS SES).
|
||||||
auto_verify_email: true,
|
auto_verify_email: true,
|
||||||
|
// Tutorial/class rewards normally require a platform auth id (Steam/MAS)
|
||||||
|
// as an anti-alt measure. Web-only accounts have no auth id; set to false
|
||||||
|
// to let verified web accounts claim them too. Default is true.
|
||||||
|
tutorial_reward_requires_auth_id: false,
|
||||||
fast_sdk: 0,
|
fast_sdk: 0,
|
||||||
machines: machines,
|
machines: machines,
|
||||||
servers: servers,
|
servers: servers,
|
||||||
|
|||||||
Reference in New Issue
Block a user