From 3fa70bd55a1ee28419b929aabad6fcf5dc8e04f3 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Mon, 6 Apr 2026 20:57:19 +0200 Subject: [PATCH 01/39] update copyright date, version, changelog --- CHANGELOG.md | 13 +++++++++++++ LICENSE.md | 2 +- src/kernel/version.c | 2 +- src/main.c | 2 +- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d99e82eea..52b1710db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# 31.0 + - Es wurde ein Bug gefixt, der bei Orks das Lernen von + Kampftalenten verlangsamt hat, nicht nur das von allen + anderen. + - Wenn in einem Kampf zwei Heere gleich gute Taktiker haben, + dann ist die Chance, von einem Kämpfer aus dem jeweils + anderen Heer getroffen zu werden nach der neuen Taktikregel + Null. Das hat allerdings nur für Angriffe mit einer Waffe + gegolten, und z.B. nicht für magische Attacken. + - Die Schneekugel macht aus Feuerwänden keine Vulkane mehr. + - Fehler im Gesang der Versklavung, der bei besonders großen + Ziel-Einheiten deren Magieresistenz ausgeschaltet hat. + # 30.4.1 - Verzauberungen, die eigene Daten speichern, müssen Speicher reservieren, in den sie sie laden (null-pointer crash). diff --git a/LICENSE.md b/LICENSE.md index 44de6fdeb..6262bc013 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 1998-2022, Enno Rehling +Copyright (c) 1998-2026, Enno Rehling Eressea is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. diff --git a/src/kernel/version.c b/src/kernel/version.c index b017fdb12..9d860fc51 100644 --- a/src/kernel/version.c +++ b/src/kernel/version.c @@ -8,7 +8,7 @@ #ifndef ERESSEA_VERSION /* the version number, if it was not passed to make with -D */ -#define ERESSEA_VERSION "31.1.0" +#define ERESSEA_VERSION "31.2.0" #endif const char *eressea_version(void) { diff --git a/src/main.c b/src/main.c index a3de84ae9..3cb72a561 100644 --- a/src/main.c +++ b/src/main.c @@ -186,7 +186,7 @@ static int parse_args(int argc, char **argv) else if (argi[1] == '-') { /* long format */ if (strcmp(argi + 2, "version") == 0) { printf("Eressea version %s, " - "Copyright (C) 2023 Enno Rehling et al.\n", + "Copyright (C) 2026 Enno Rehling et al.\n", eressea_version()); return 1; #ifdef HAVE_CURSES From 86b26fbc71e00424310370f9cb2e217a545ccabb Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 11 Apr 2026 19:54:20 +0200 Subject: [PATCH 02/39] Docker build container (#1137) * Dockerfile, initial commit can build an image that - contains the server binary - contains all library dependencies - contains lua scripts - sets LUA_PATH TODO: - runs whatever, as root! * Docker USER (not root) * directory and locale start from correct directory (find eressea.ini) configure locale during build * Set the WORKDIR * better error messages * script to create a minimal game some funny experimental script magic * improve build times a bit fix reports.lua using dofile * create a default eressea.ini if none exists fix the config.lua script, set up paths. * explain how to create game * workaround for turn 0 strangeness --- .dockerignore | 2 ++ .env.example | 8 +++++ DOCKER.md | 74 ++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 65 +++++++++++++++++++++++++++++++++++++ compose.yaml | 16 +++++++++ conf/eressea.ini | 25 +++++++------- docker/start-container | 23 +++++++++++++ scripts/config.lua | 6 +--- scripts/newgame.lua | 11 +++++++ scripts/reports.lua | 2 +- src/kernel/calendar.c | 7 ++-- src/main.c | 2 +- 12 files changed, 220 insertions(+), 21 deletions(-) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 DOCKER.md create mode 100644 Dockerfile create mode 100644 compose.yaml create mode 100644 docker/start-container create mode 100644 scripts/newgame.lua diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..d436eaba1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.git + diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..80e7454a7 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +## local user and group ids +WWWGROUP=1337 +WWWUSER=1337 + +LOCALE=en_US + +## cmake build flags +CMAKE_BUILD_TYPE=Release diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 000000000..91c86edda --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,74 @@ +# Overview + +At this point, the container can mount a game directory from the host, and run the server with a given script like so: +```bash +docker run -v $HOME/eressea/game-1:/data eressea/server:v1 myscript.lua +``` + +This mounts the host's `game-1` directory as a data volume in the container, and the server operated on the files in there, i.e. game data and reports go here. +The script can either be on the data volume, or one of the pre-made scripts in the scripts directory, like newgame.lua, reports.lua, or run-turn.lua. These get copied to /usr/local/share/eressea/scripts during the build process. + +# Installation (using Docker) + +## Install Docker (with compose plugin) + +* Debian users, follow https://linuxize.com/post/how-to-install-docker-on-debian-13/ +* Ubuntu users, follow https://linuxiac.com/how-to-install-docker-on-ubuntu-24-04-lts/ + +## Clone the Repository + +```bash +$ git clone -b docker https://github.com/eressea/server.git ~/eressea/source +$ cd ~/eressea/source +$ git submodule update --init +``` + +## Configuration + +Copy .env.example to .env and modify it. The most importent bit here is using the +same uid and gid as your local user on the host, so files can be shared easily. Use +the id command to find your own values: + +```sh +$ id +uid=1000(enno) gid=1000(enno) groups=1000(enno),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),100(users),107(netdev),989(docker) +``` + +I am enno, my uid is 1000, and my default gid is also 1000. So my .env file looks like this: + +```ini +## local user and group ids +WWWGROUP=1000 +WWWUSER=1000 + +LOCALE=en_US + +## cmake build flags +CMAKE_BUILD_TYPE=Release +``` + +You can also change the locale, which will give you system errors in a different language. + +## Building the image + +Before we can run a container, we need to build the image first. Use +the command `docker compose build` to do that. When you're done, `docker image ls` should contain there eressea/server:v1 image. + +## Create a game directory and a tiny world. + +The following commands set up your first game: + +```bash +$ cd ~/eressea +$ mkdir game-1 +$ docker run -v $HOME/eressea/game-1:/data eressea/server:v1 newgame.lua +``` + +After this, your ~/eressea/game-1 directory contains some new files: + +`eressea.ini` contains a game configuration with sensible defaults for +a game with the E2 ruleset. Modify this with your own email address and +a different ruleset, if you want to. Only e2 and e3 currently supported, +custom rulesets TBD. + +`data/0.dat` is a game file that contains a single-hex island with some ocean around it. There are no players in this world (yet). diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..71e34daa3 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,65 @@ +FROM ubuntu:24.04 AS build + +RUN apt update && \ + apt install --no-install-recommends -y \ + software-properties-common build-essential gcc cmake \ + liblua5.2-dev libtolua-dev libncurses5-dev libsqlite3-dev \ + libcjson-dev libiniparser-dev libexpat1-dev libutf8proc-dev + +WORKDIR /workspace + +COPY CMakeLists.txt eressea/ +COPY cmake/ eressea/cmake/ +COPY src/ eressea/src/ +COPY include/ eressea/include/ +COPY storage/ eressea/storage/ +COPY clibs/ eressea/clibs/ +COPY crpat/ eressea/crpat/ +COPY tools/ eressea/tools/ +COPY process/CMakeLists.txt eressea/process/ + +ENV CMAKE_MODULE_PATH=/workspace/eressea/cmake/Modules + +RUN cd eressea && ls --recursive /workspace +RUN cd eressea && mkdir -p build +RUN cd eressea/build && cmake .. +RUN cd eressea/build && make + +FROM ubuntu:24.04 + +ARG WWWGROUP +ARG WWWUSER +ARG LOCALE + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt update && apt install --no-install-recommends -y \ + locales libcjson1 \ + liblua5.2-0 libsqlite3-0 \ + libiniparser1 libexpat1 libutf8proc3 +RUN echo "$LOCALE.UTF-8 UTF-8" >> /etc/locale.gen && locale-gen + +ENV LC_ALL=$LOCALE.UTF-8 +ENV LANG=$LOCALE.UTF-8 +ENV LANGUAGE=$LOCALE:en + +RUN userdel -r ubuntu +RUN groupadd --force -g $WWWGROUP eressea +RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u $WWWUSER eressea + +COPY --from=build /workspace/eressea/build/eressea /usr/local/bin/ +COPY scripts/ /usr/local/share/eressea/scripts/ +COPY conf/ /usr/local/share/eressea/conf/ +COPY res/ /usr/local/share/eressea/res/ +RUN chmod -R g+rx,o+rx /usr/local/share/eressea + +COPY docker/start-container /usr/local/bin/start-container +RUN chmod 755 /usr/local/bin/start-container + +USER $WWWUSER:$WWWGROUP + +WORKDIR /data + +VOLUME ["/data"] + +ENTRYPOINT ["/usr/local/bin/start-container"] diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 000000000..cd3dc7c21 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,16 @@ +services: + server: + build: + context: . + dockerfile: Dockerfile + args: + WWWGROUP: '${WWWGROUP:-1337}' + WWWUSER: '${WWWUSER:-1337}' + CMAKE_BUILD_TYPE: '${CMAKE_BUILD_TYPE}' + LOCALE: '${LOCALE:-en_US}' + image: eressea/server:v1 + hostname: eressea + environment: + WWWUSER: '${WWWUSER:-1337}' + WWWGROUP: '${WWWGROUP:-1337}' + CMAKE_BUILD_TYPE: '${CMAKE_BUILD_TYPE:-Release}' diff --git a/conf/eressea.ini b/conf/eressea.ini index 17547f8f9..4faf9892b 100644 --- a/conf/eressea.ini +++ b/conf/eressea.ini @@ -1,17 +1,18 @@ [game] -email = eressea-server@example.com -sender = Eressea Server -name = Eressea -report = reports -verbose = 0 -memcheck = 0 -locales = de,en +id = 1 +start = 0 +email = eressea-server@example.com +sender = Eressea Server +name = Eressea +report = reports +verbose = 0 +memcheck = 0 +locales = de,en [lua] -install = . -paths = lunit:scripts -maxnmrs = 20 +rules = e2 +install = /usr/local/share/eressea +maxnmrs = 5 [editor] -color = 1 - +color = 1 diff --git a/docker/start-container b/docker/start-container new file mode 100644 index 000000000..421778d7f --- /dev/null +++ b/docker/start-container @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +if [ ! -z "$WWWUSER" ]; then + usermod -u $WWWUSER sail +fi + +if [ ! -e eressea.ini ]; then + cp /usr/local/share/eressea/conf/eressea.ini . +fi + +export LUA_PATH="/usr/local/share/eressea/scripts/?.lua;/usr/local/share/eressea/scripts/?/init.lua;$LUA_PATH" +SCRIPT="$1" +if [ -e "$SCRIPT" ]; then + shift +elif [ -e "/usr/local/share/eressea/scripts/$SCRIPT" ]; then + SCRIPT="/usr/local/share/eressea/scripts/$SCRIPT" + shift +else + # TODO: debug mode, remove this: + exec "$@" + exit +fi +exec eressea "$@" "$SCRIPT" diff --git a/scripts/config.lua b/scripts/config.lua index 8b61bcad2..0ebd921c1 100644 --- a/scripts/config.lua +++ b/scripts/config.lua @@ -1,8 +1,4 @@ -local path = 'scripts' -if config.install then - path = config.install .. '/' .. path -end -package.path = package.path .. ';' .. path .. '/?.lua;' .. path .. '/?/init.lua' +require 'eressea.path' require 'eressea' require 'eressea.xmlconf' -- read xml data diff --git a/scripts/newgame.lua b/scripts/newgame.lua new file mode 100644 index 000000000..2cfcda02c --- /dev/null +++ b/scripts/newgame.lua @@ -0,0 +1,11 @@ +require 'config' + +region.create(0, 0, 'plain') +region.create(1, 0, 'ocean') +region.create(-1, 0, 'ocean') +region.create(-1, 1, 'ocean') +region.create(0, 1, 'ocean') +region.create(0, -1, 'ocean') +region.create(1, -1, 'ocean') + +eressea.write_game(get_turn() .. '.dat') diff --git a/scripts/reports.lua b/scripts/reports.lua index 9d313b9f1..d13b890a0 100644 --- a/scripts/reports.lua +++ b/scripts/reports.lua @@ -1,4 +1,4 @@ -dofile('config.lua') +require 'config' eressea.read_game(get_turn() .. '.dat') -- generate a new password and a message for new factions: init_reports() diff --git a/src/kernel/calendar.c b/src/kernel/calendar.c index 5d2fc6c4b..2a16b2370 100644 --- a/src/kernel/calendar.c +++ b/src/kernel/calendar.c @@ -50,10 +50,13 @@ const gamedate *get_gamedate(int turn_now, gamedate * gd) int t = turn_now - first_turn(); assert(gd); - if (t<0) { + if (t < 0) { + // FIXME: Why is this happening, especially for turn 0? t = 0; - log_error("current turn %d is before first %d", + if (turn_now > 0) { + log_error("current turn %d is before first %d", turn_now, first_turn()); + } } assert(t>=0); diff --git a/src/main.c b/src/main.c index 3cb72a561..7376bd8c4 100644 --- a/src/main.c +++ b/src/main.c @@ -338,7 +338,7 @@ int main(int argc, char **argv) bind_monsters(L); err = eressea_run(L, luafile); if (err) { - log_error("script %s failed with code %d\n", luafile, err); + log_error("script %s failed with code %d (%s)\n", luafile, err, strerror(err)); return err; } #else From b8ff005ead4747e8c4a1783c3e71de0c07ee4839 Mon Sep 17 00:00:00 2001 From: oddluminari Date: Thu, 16 Apr 2026 15:13:04 +0200 Subject: [PATCH 03/39] adding a lua binding for eressea's c side rng_init function as rng.seed(x) (#1138) Co-authored-by: martin --- src/bindings.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/bindings.c b/src/bindings.c index 1152021ad..1930633c5 100755 --- a/src/bindings.c +++ b/src/bindings.c @@ -169,6 +169,12 @@ static int tolua_translate(lua_State * L) return 0; } +static int tolua_rng_seed(lua_State * L) { + int seed = (int)tolua_tonumber(L, 1, 0); + rng_init(seed); + return 0; +} + static int tolua_random(lua_State * L) { lua_pushinteger(L, rng_int()); @@ -943,6 +949,7 @@ int tolua_bindings_open(lua_State * L, const dictionary *inifile) { tolua_function(L, "inject", lua_rng_default); tolua_function(L, "random", tolua_random); + tolua_function(L, "seed", tolua_rng_seed); } tolua_endmodule(L); tolua_function(L, "rng_int", tolua_random); From ef7caa4e22e4688806a14282984d247743f17861 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 18 Apr 2026 12:08:22 +0200 Subject: [PATCH 04/39] Custom config and rules (#1139) * custom rulesets and config * custom rulesets and config. the authorities seem a bit superfluous, but here we are. * update documentation * default CMAKE_BUILD_TYPE * documentation update --- DOCKER.md | 119 ++++++++++++++++++++++++++++++------ compose.yaml | 2 +- docker/start-container | 2 +- scripts/eressea/path.lua | 17 +++--- scripts/eressea/xmlconf.lua | 11 ++-- src/bind_config.c | 8 ++- src/bind_config.h | 1 + src/config.pkg | 1 + src/jsonconf.c | 31 ++++++++-- src/jsonconf.h | 18 ++---- src/kernel/config.c | 2 + 11 files changed, 163 insertions(+), 49 deletions(-) diff --git a/DOCKER.md b/DOCKER.md index 91c86edda..dc7678e68 100644 --- a/DOCKER.md +++ b/DOCKER.md @@ -2,28 +2,42 @@ At this point, the container can mount a game directory from the host, and run the server with a given script like so: ```bash -docker run -v $HOME/eressea/game-1:/data eressea/server:v1 myscript.lua +docker run -v $HOME/eressea/game:/data eressea/server:v1 myscript.lua ``` -This mounts the host's `game-1` directory as a data volume in the container, and the server operated on the files in there, i.e. game data and reports go here. +This mounts the host's `game` directory as a data volume in the container, and the server operated on the files in there, i.e. game data and reports go here. The script can either be on the data volume, or one of the pre-made scripts in the scripts directory, like newgame.lua, reports.lua, or run-turn.lua. These get copied to /usr/local/share/eressea/scripts during the build process. +## Official Image + +If you don't want to build the image yourself, use the latest official image from +the container registry: + +```bash +docker pull ghcr.io/eressea/server:latest +``` + # Installation (using Docker) +If you want to build the image yourself, because you're planning to make +changes to the source code, follow these steps. + ## Install Docker (with compose plugin) * Debian users, follow https://linuxize.com/post/how-to-install-docker-on-debian-13/ * Ubuntu users, follow https://linuxiac.com/how-to-install-docker-on-ubuntu-24-04-lts/ -## Clone the Repository +## Building the image from source + +### Clone the Repository ```bash -$ git clone -b docker https://github.com/eressea/server.git ~/eressea/source +$ git clone -b develop https://github.com/eressea/server.git ~/eressea/source $ cd ~/eressea/source $ git submodule update --init ``` -## Configuration +### Configuration Copy .env.example to .env and modify it. The most importent bit here is using the same uid and gid as your local user on the host, so files can be shared easily. Use @@ -31,10 +45,12 @@ the id command to find your own values: ```sh $ id -uid=1000(enno) gid=1000(enno) groups=1000(enno),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),100(users),107(netdev),989(docker) +uid=1000(enno) gid=1000(enno) groups=1000(enno),4(adm),27(sudo),100(users),107(netdev),989(docker) ``` -I am enno, my uid is 1000, and my default gid is also 1000. So my .env file looks like this: +I am enno, my uid is 1000, and my default gid is also 1000. The eressea user in the +container uses 1337 by default, but if I want it to use 1000, that can be achieved +by changing the .env file like this: ```ini ## local user and group ids @@ -49,26 +65,93 @@ CMAKE_BUILD_TYPE=Release You can also change the locale, which will give you system errors in a different language. -## Building the image +### Building the image + +Before we can run a container, we need to first build the image. Use +the command `docker compose build` to do that. When you're done, +`docker image ls` should contain the eressea/server:v1 image. -Before we can run a container, we need to build the image first. Use -the command `docker compose build` to do that. When you're done, `docker image ls` should contain there eressea/server:v1 image. ## Create a game directory and a tiny world. -The following commands set up your first game: +The following steps assume you're using the official image. +If you built your oen image, I recommend that you tag it to +follow along: + +```bash +docker image tag eressea/server:v1 eressea/server:latest +``` + +Create a directory for your first game: ```bash -$ cd ~/eressea -$ mkdir game-1 -$ docker run -v $HOME/eressea/game-1:/data eressea/server:v1 newgame.lua +cd ~/eressea +mkdir game +# for convenience, alias the "docker run" command +alias eressea='docker run -ti -e TERM -v $HOME/eressea/game:/data eressea/server:v1' +eressea newgame.lua ``` -After this, your ~/eressea/game-1 directory contains some new files: +After this, your ~/eressea/game directory contains some new files: + +`data/0.dat` is a game file that contains a single-hex island with some ocean around it. There are no players in this world (yet). `eressea.ini` contains a game configuration with sensible defaults for a game with the E2 ruleset. Modify this with your own email address and -a different ruleset, if you want to. Only e2 and e3 currently supported, -custom rulesets TBD. +a different ruleset, if you want to. The only built-in rulesets you can choose from are e2 and e3. + +## Let's Play +Now let's add a faction to our world and write initial reports. + +in the game directory, add a short Lua program named start.lua: +```lua +require 'config' +eressea.read_game('0.dat') +r = get_region(0, 0) +f = faction.create('orc', 'orcs@example.com', 'de') +u = unit.create(f, r) +init_reports() +write_reports() +eressea.write_game(get_turn() .. '.dat') +``` + +Execute this program: +``` +eressea start.lua +``` +You should now have reports for your initial faction in the game/reports +directory! + +If you would like to use the GM Mapper, you can start that with the built-in map.lua program, just run `eressea map.lua`. + +## Custom Rulesets + +You can choose to make your own rules, too! Just make the following modifications to your eressea.ini : +```ini +[lua] +;rules = e2 +config = rules.json +``` +This will instruct the game to not use the e2 ruleset, but a rules.json +file from your game directory. This file could look like this: +```json +{ + "settings": { + "game.name": "Discworld", + "game.start": 0 + }, + "include": [ + "config://conf/keywords.json", + "config://conf/calendar.json", + "config://conf/e2/locales.json", + "config://conf/e2/terrains.json", + "config://conf/e2/items.json", + "config://res/eressea/races.xml", + "config://custom/terrains.json", + "config://custom/strings.de.po" + ] +} +``` +Here, we are instructing the game to borrow some rules from the core game, +like the names of keywords, the calendar, and the races. We also borrow the translations, terrains and items from E2. In addition, we include our own terrains and translations from the game/custom directoory. For their strucuture, refer to the original files and make your own changes! -`data/0.dat` is a game file that contains a single-hex island with some ocean around it. There are no players in this world (yet). diff --git a/compose.yaml b/compose.yaml index cd3dc7c21..142bbf415 100644 --- a/compose.yaml +++ b/compose.yaml @@ -6,7 +6,7 @@ services: args: WWWGROUP: '${WWWGROUP:-1337}' WWWUSER: '${WWWUSER:-1337}' - CMAKE_BUILD_TYPE: '${CMAKE_BUILD_TYPE}' + CMAKE_BUILD_TYPE: '${CMAKE_BUILD_TYPE:-Release}' LOCALE: '${LOCALE:-en_US}' image: eressea/server:v1 hostname: eressea diff --git a/docker/start-container b/docker/start-container index 421778d7f..666b6affe 100644 --- a/docker/start-container +++ b/docker/start-container @@ -1,7 +1,7 @@ #!/usr/bin/env bash if [ ! -z "$WWWUSER" ]; then - usermod -u $WWWUSER sail + usermod -u $WWWUSER eressea fi if [ ! -e eressea.ini ]; then diff --git a/scripts/eressea/path.lua b/scripts/eressea/path.lua index 32ae75b81..ec1eb765d 100644 --- a/scripts/eressea/path.lua +++ b/scripts/eressea/path.lua @@ -1,9 +1,12 @@ -local path = '.' +root = os.getenv("ERESSEA_ROOT") +if root then + local path = root .. "/scripts" + package.path = path .. '/?.lua;' .. path .. '/?/init.lua;' .. package.path +end + if config.install then - path = config.install -else - path = os.getenv("ERESSEA_ROOT") or path - config.install = path + local path = config.install .. "/scripts" + package.path = path .. '/?.lua;' .. path .. '/?/init.lua;' .. package.path + eressea.config.add_authority("conf", config.install .. '/conf') + eressea.config.add_authority("res", config.install .. '/res') end -path = path .. "/scripts" -package.path = path .. '/?.lua;' .. path .. '/?/init.lua;' .. package.path diff --git a/scripts/eressea/xmlconf.lua b/scripts/eressea/xmlconf.lua index c0bce5717..a2a73a53b 100644 --- a/scripts/eressea/xmlconf.lua +++ b/scripts/eressea/xmlconf.lua @@ -1,7 +1,10 @@ -local rules = 'conf' - -if config.rules then - rules = rules .. '/' .. config.rules +if config.config then + -- custom config file + local conffile = config.config + assert(0 == eressea.config.read(conffile, ''), "could not read JSON data from " .. conffile) +elseif config.rules then + -- standard ruleset + rules = 'conf/' .. config.rules assert(0 == eressea.config.read(rules .. '/config.json', config.install), "could not read JSON data from " .. rules) end diff --git a/src/bind_config.c b/src/bind_config.c index d3a036aa8..94fe643dc 100644 --- a/src/bind_config.c +++ b/src/bind_config.c @@ -27,6 +27,11 @@ void bind_config_reset(void) { free_configuration(); } +void bind_config_add_authority(const char *key, const char *path) +{ + add_authority(key, path); +} + int bind_config_parse(const char *json) { cJSON * conf = cJSON_Parse(json); @@ -62,7 +67,7 @@ int bind_config_read(const char *filename, const char * relpath) FILE *F; json_relpath = relpath; - if (relpath) { + if (relpath && relpath[0]) { char name[PATH_MAX]; path_join(relpath, filename, name, sizeof(name)); F = fopen(name, "r"); @@ -82,6 +87,7 @@ int bind_config_read(const char *filename, const char * relpath) size_t sz = (size_t)size; data = malloc(sz+1); + if (!data) abort(); sz = fread(data, 1, sz, F); data[sz] = 0; fclose(F); diff --git a/src/bind_config.h b/src/bind_config.h index ac074d6a8..6c2db4568 100644 --- a/src/bind_config.h +++ b/src/bind_config.h @@ -1,6 +1,7 @@ #pragma once void bind_config_reset(void); +void bind_config_add_authority(const char *key, const char *path); int bind_config_parse(const char *json); int bind_config_read(const char *filename, const char * relpath); const char *bind_config_get(const char *key); diff --git a/src/config.pkg b/src/config.pkg index 2019e7ff2..f29ab922b 100644 --- a/src/config.pkg +++ b/src/config.pkg @@ -5,6 +5,7 @@ $#include "bind_config.h" module eressea { module config { void bind_config_reset @ reset(void); + void bind_config_add_authority @ add_authority(const char *key, const char *path); int bind_config_read @ read(const char *filename, const char *relpath); int bind_config_parse @ parse(const char *json); const char * bind_config_get @ get(const char *key); diff --git a/src/jsonconf.c b/src/jsonconf.c index 02837783b..942f6f209 100644 --- a/src/jsonconf.c +++ b/src/jsonconf.c @@ -1225,12 +1225,24 @@ static void json_races(cJSON *json) { const char * json_relpath; -/* TODO: much more configurable authority-to-file lookup */ -static const char * authority_to_path(const char *authority, char *name, size_t size) { +typedef struct authority_hash { + char *key; + char *value; +} authority_hash; + +static authority_hash *authorities = NULL; + +static const char * authority_to_path(const char *authority) { + ptrdiff_t i; /* source and destination cannot share the same buffer */ - assert(authority < name || authority > name + size); + if (authorities && 0 <= (i = stbds_shgeti(authorities, authority))) { + return authorities[i].value; + } + return authority; +} - return join_path(json_relpath, authority, name, size); +void add_authority(const char *key, const char *path) { + shput(authorities, str_strdup(key), str_strdup(path)); } static const char * uri_to_file(const char * uri, char *name, size_t size) { @@ -1255,7 +1267,7 @@ static const char * uri_to_file(const char * uri, char *name, size_t size) { memcpy(buffer, authority, alen); buffer[alen] = 0; - path = authority_to_path(buffer, name, size); + path = authority_to_path(buffer); path = path_join(path, pos + 1, name, size); } } @@ -1444,3 +1456,12 @@ void json_config(cJSON *json) { } } +void jsonconf_done(void) { + unsigned int i; + size_t sz = shlen(authorities); + for (i = 0; i != sz; ++i) { + free(authorities[i].key); + free(authorities[i].value); + } + stbds_shfree(authorities); +} diff --git a/src/jsonconf.h b/src/jsonconf.h index d5d0935d0..99db0cdb9 100644 --- a/src/jsonconf.h +++ b/src/jsonconf.h @@ -1,16 +1,10 @@ -#ifndef H_JSONCONF_H -#define H_JSONCONF_H -#ifdef __cplusplus -extern "C" { -#endif +#pragma once - struct cJSON; +struct cJSON; - extern const char * json_relpath; +extern const char * json_relpath; - void json_config(struct cJSON *str); +void add_authority(const char *key, const char *path); -#ifdef __cplusplus -} -#endif -#endif +void json_config(struct cJSON *str); +void jsonconf_done(void); diff --git a/src/kernel/config.c b/src/kernel/config.c index 0cd6bce07..e1485fae4 100644 --- a/src/kernel/config.c +++ b/src/kernel/config.c @@ -12,6 +12,7 @@ #include "faction.h" #include "group.h" #include "item.h" +#include "jsonconf.h" #include "messages.h" #include "move.h" #include "objtypes.h" @@ -207,6 +208,7 @@ void kernel_done(void) reports_done(); crmessage_done(); translation_done(); + jsonconf_done(); mt_clear(); } From ce978302cd8e727a8d62c41807f0693fe97850fb Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 19 Apr 2026 20:15:23 +0200 Subject: [PATCH 05/39] prevent crash in sp_stun --- src/spells/combatspells.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/spells/combatspells.c b/src/spells/combatspells.c index 9bafbadc1..62fe07a2e 100644 --- a/src/spells/combatspells.c +++ b/src/spells/combatspells.c @@ -223,12 +223,14 @@ int sp_stun(struct castorder * co) while (force && stunned < enemies) { troop dt = select_enemy(fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); fighter *df = dt.fighter; - unit *du = df->unit; + if (df) { + unit *du = df->unit; - --force; - if (!is_magic_resistant(mage, du, 0)) { - df->person[dt.index].flags |= FL_STUNNED; - ++stunned; + --force; + if (!is_magic_resistant(mage, du, 0)) { + df->person[dt.index].flags |= FL_STUNNED; + ++stunned; + } } } From 1f1229588cb793d8739ccce5997d56c1b0e01236 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 19 Apr 2026 20:19:28 +0200 Subject: [PATCH 06/39] simplify and add lightness strip a superfluous argument from count_enemies --- src/battle.c | 16 ++++++++-------- src/battle.h | 3 +-- src/items/weapons.c | 4 ++-- src/spells/combatspells.c | 26 +++++++++++++------------- 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/battle.c b/src/battle.c index 51058ed79..d50244224 100644 --- a/src/battle.c +++ b/src/battle.c @@ -1420,11 +1420,12 @@ count_allies(const side * as, int minrow, int maxrow, int select, int allytype) } static int -count_enemies_i(battle * b, const fighter * af, int minrow, int maxrow, +count_enemies_i(const fighter * af, int minrow, int maxrow, int select) { side *as = af->side; int i = 0; + battle *b = as->battle; size_t si, sl = arrlen(b->sides); for (si = 0; si != sl; ++si) { @@ -1444,11 +1445,10 @@ count_enemies_i(battle * b, const fighter * af, int minrow, int maxrow, } int -count_enemies(battle *b, const fighter *af, int minrow, int maxrow, - int select) +count_enemies(const fighter *af, int minrow, int maxrow, int select) { if (maxrow >= FIRST_ROW) { - return count_enemies_i(b, af, minrow, maxrow, select); + return count_enemies_i(af, minrow, maxrow, select); } return 0; } @@ -1469,7 +1469,7 @@ troop select_enemy(fighter * af, int minrow, int maxrow, int select) if (minrow < FIGHT_ROW) minrow = FIGHT_ROW; - enemies = count_enemies(b, af, minrow, maxrow, select); + enemies = count_enemies(af, minrow, maxrow, select); /* Niemand ist in der angegebenen Entfernung? */ if (enemies <= 0) @@ -2115,7 +2115,7 @@ static void attack(battle * b, troop ta, const att * a, int numattack) /* wenn der magier in die potenzielle Reichweite von Attacken des * Feindes kommt, beginnt er auch bei einem Status von KAEMPFE NICHT, * Kampfzauber zu schleudern: */ - if (count_enemies(b, af, melee_range[0], missile_range[1], + if (count_enemies(af, melee_range[0], missile_range[1], SELECT_ADVANCE | SELECT_DISTANCE | SELECT_FIND)) { do_combatspell(ta); } @@ -2130,7 +2130,7 @@ static void attack(battle * b, troop ta, const att * a, int numattack) const weapon* wp = ta.fighter->person[ta.index].missile; const weapon_type *wtype = NULL; bool missile = false; - if (count_enemies(b, af, melee_range[0], melee_range[1], + if (count_enemies(af, melee_range[0], melee_range[1], SELECT_ADVANCE | SELECT_DISTANCE | SELECT_FIND) > 0) { wp = preferred_weapon(ta, true); } @@ -2266,7 +2266,7 @@ static void do_attack(fighter * af) /* Wir suchen eine beliebige Feind-Einheit aus. An der koennen * wir feststellen, ob noch jemand da ist. */ int apr, attacks = attacks_per_round(ta); - if (!count_enemies(b, af, FIGHT_ROW, LAST_ROW, SELECT_FIND)) + if (!count_enemies(af, FIGHT_ROW, LAST_ROW, SELECT_FIND)) break; for (apr = 0; apr != attacks; ++apr) { diff --git a/src/battle.h b/src/battle.h index 3738f0074..5bd5d5662 100644 --- a/src/battle.h +++ b/src/battle.h @@ -199,8 +199,7 @@ troop select_ally(struct fighter* af, int minrow, int maxrow, int allytype); int get_tactics(const struct side* as, const struct side* ds); -int count_enemies(struct battle* b, const struct fighter* af, - int minrow, int maxrow, int select); +int count_enemies(const struct fighter* af, int minrow, int maxrow, int select); int natural_armor(struct unit* u); const struct armor_type* select_armor(struct troop t, bool shield); const struct weapon* select_weapon(const struct troop t, bool attacking, bool ismissile); diff --git a/src/items/weapons.c b/src/items/weapons.c index 6bd514a04..861644d33 100644 --- a/src/items/weapons.c +++ b/src/items/weapons.c @@ -50,7 +50,7 @@ int *casualties) const char *damage = "2d8"; int force = 1 + rng_int() % 10; int enemies = - count_enemies(fi->side->battle, fi, 0, 1, SELECT_ADVANCE | SELECT_DISTANCE); + count_enemies(fi, 0, 1, SELECT_ADVANCE | SELECT_DISTANCE); if (!enemies) { if (casualties) @@ -99,7 +99,7 @@ attack_catapult(const troop * at, const struct weapon_type *wtype, } } - enemies = count_enemies(b, af, FIGHT_ROW, FIGHT_ROW, SELECT_ADVANCE); + enemies = count_enemies(af, FIGHT_ROW, FIGHT_ROW, SELECT_ADVANCE); if (enemies > CATAPULT_ATTACKS) enemies = CATAPULT_ATTACKS; if (enemies == 0) { return false; diff --git a/src/spells/combatspells.c b/src/spells/combatspells.c index 62fe07a2e..a621b3833 100644 --- a/src/spells/combatspells.c +++ b/src/spells/combatspells.c @@ -132,7 +132,7 @@ int damage_spell(struct castorder * co, int dmg, int strength) at.fighter = fi; at.index = 0; - enemies = count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW - 1, SELECT_ADVANCE); + enemies = count_enemies(fi, FIGHT_ROW, BEHIND_ROW - 1, SELECT_ADVANCE); if (enemies == 0) { m = msg_message("spell_out_of_range", "mage spell", fi->unit, sp); message_all(b, m); @@ -166,7 +166,7 @@ int sp_petrify(struct castorder * co) force = lovar(get_force(power, 0)); - enemies = count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + enemies = count_enemies(fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); if (!enemies) { m = msg_message("spell_out_of_range", "mage spell", fi->unit, sp); message_all(b, m); @@ -211,7 +211,7 @@ int sp_stun(struct castorder * co) force = lovar(get_force(power, 1)); - enemies = count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + enemies = count_enemies(fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); if (!enemies) { m = msg_message("spell_out_of_range", "mage spell", fi->unit, sp); message_all(b, m); @@ -270,7 +270,7 @@ int sp_combatrosthauch(struct castorder * co) int force = lovar(power * 15); int k = 0; - if (!count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW - 1, + if (!count_enemies(fi, FIGHT_ROW, BEHIND_ROW - 1, SELECT_ADVANCE | SELECT_FIND)) { message *msg = msg_message("rust_effect_0", "mage", fi->unit); message_all(b, msg); @@ -358,7 +358,7 @@ int sp_sleep(struct castorder * co) /* Immer aus der ersten Reihe nehmen */ force = lovar(co->force * 25); - enemies = count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + enemies = count_enemies(fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); if (!enemies) { m = msg_message("spell_out_of_range", "mage spell", fi->unit, sp); @@ -480,7 +480,7 @@ int sp_mindblast(struct castorder * co) int k = 0, reset = 0, maxloss = (level + 2) / 3; message *m; int force = lovar(power * 25); - int enemies = count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + int enemies = count_enemies(fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); if (!enemies) { m = msg_message("spell_out_of_range", "mage spell", fi->unit, sp); @@ -547,7 +547,7 @@ int sp_dragonodem(struct castorder * co) /* Jungdrache 3->54, Drache 6->216, Wyrm 12->864 Treffer */ force = lovar(get_force(power, 6)); - enemies = count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW - 1, SELECT_ADVANCE); + enemies = count_enemies(fi, FIGHT_ROW, BEHIND_ROW - 1, SELECT_ADVANCE); if (!enemies) { struct message *m = @@ -593,7 +593,7 @@ int sp_immolation(struct castorder * co) /* Betrifft alle Gegner */ force = 99999; - if (!count_enemies(b, fi, FIGHT_ROW, AVOID_ROW, SELECT_ADVANCE | SELECT_FIND)) { + if (!count_enemies(fi, FIGHT_ROW, AVOID_ROW, SELECT_ADVANCE | SELECT_FIND)) { m = msg_message("spell_out_of_range", "mage spell", fi->unit, sp); message_all(b, m); msg_release(m); @@ -764,7 +764,7 @@ int sp_chaosrow(struct castorder * co) int k = 0; bool chaosrow = sp && (strcmp(sp->sname, "chaosrow") == 0); - if (!count_enemies(b, fi, FIGHT_ROW, NUMROWS, SELECT_ADVANCE | SELECT_FIND)) { + if (!count_enemies(fi, FIGHT_ROW, NUMROWS, SELECT_ADVANCE | SELECT_FIND)) { m = msg_message("spell_out_of_range", "mage spell", fi->unit, sp); message_all(b, m); msg_release(m); @@ -859,7 +859,7 @@ int flee_spell(struct castorder * co, int strength, bool pre_combat) int force; force = (int)get_force(power, strength); - if (force<=0 || !count_enemies(b, fi, FIGHT_ROW, AVOID_ROW, SELECT_ADVANCE | SELECT_FIND)) { + if (force<=0 || !count_enemies(fi, FIGHT_ROW, AVOID_ROW, SELECT_ADVANCE | SELECT_FIND)) { msg = msg_message("flee_effect_0", "mage spell", mage, sp); message_all(b, msg); msg_release(msg); @@ -1014,7 +1014,7 @@ int sp_frighten(struct castorder * co) df_malus = 2; force = (int)get_force(power, 2); - enemies = count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW - 1, SELECT_ADVANCE); + enemies = count_enemies(fi, FIGHT_ROW, BEHIND_ROW - 1, SELECT_ADVANCE); if (!enemies) { m = msg_message("spell_out_of_range", "mage spell", fi->unit, sp); message_all(b, m); @@ -1063,7 +1063,7 @@ int sp_tiredsoldiers(struct castorder * co) int force = (int)(power * power * 4); message *m; - if (!count_enemies(b, fi, FIGHT_ROW, BEHIND_ROW, + if (!count_enemies(fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE | SELECT_FIND)) { m = msg_message("spell_out_of_range", "mage spell", fi->unit, sp); message_all(b, m); @@ -1109,7 +1109,7 @@ int sp_windshield(struct castorder * co) force = (int)get_force(power, 4); at_malus = level / 4; - enemies = count_enemies(b, fi, BEHIND_ROW, BEHIND_ROW, SELECT_ADVANCE); + enemies = count_enemies(fi, BEHIND_ROW, BEHIND_ROW, SELECT_ADVANCE); if (!enemies) { m = msg_message("spell_out_of_range", "mage spell", fi->unit, sp); message_all(b, m); From fb2071a3e18390eb704ca3b0591589e872666d97 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 19 Apr 2026 20:47:33 +0200 Subject: [PATCH 07/39] fix CI build --- src/items/weapons.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/items/weapons.c b/src/items/weapons.c index 861644d33..47f51e1a1 100644 --- a/src/items/weapons.c +++ b/src/items/weapons.c @@ -78,7 +78,6 @@ attack_catapult(const troop * at, const struct weapon_type *wtype, { fighter *af = at->fighter; unit *au = af->unit; - battle *b = af->side->battle; troop dt; int shots = INT_MAX, d = 0, enemies; const resource_type *rtype; From 1d15bdd6a15b009cb39ff557a07bf4863734a42b Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Thu, 23 Apr 2026 20:39:15 +0200 Subject: [PATCH 08/39] no need to take address here. --- src/bindings.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bindings.c b/src/bindings.c index 1930633c5..161f44c91 100755 --- a/src/bindings.c +++ b/src/bindings.c @@ -1037,7 +1037,7 @@ int tolua_bindings_open(lua_State * L, const dictionary *inifile) tolua_function(L, "write_scores", tolua_write_scores); tolua_function(L, "update_owners", tolua_update_owners); tolua_function(L, "create_curse", tolua_create_curse); - tolua_function(L, "translate", &tolua_translate); + tolua_function(L, "translate", tolua_translate); tolua_function(L, "spells", tolua_get_spells); tolua_function(L, "equip_newunits", tolua_equip_newunits); } tolua_endmodule(L); From c5bbc9566c9bfa1eab9b1371db392c682cd6b37b Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 25 Apr 2026 11:16:01 +0200 Subject: [PATCH 09/39] setting config.install is not optional $ERESSEA_ROOT is not the replacement. --- scripts/eressea/path.lua | 6 ------ src/bindings.c | 4 +--- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/scripts/eressea/path.lua b/scripts/eressea/path.lua index ec1eb765d..ea614dbe3 100644 --- a/scripts/eressea/path.lua +++ b/scripts/eressea/path.lua @@ -1,9 +1,3 @@ -root = os.getenv("ERESSEA_ROOT") -if root then - local path = root .. "/scripts" - package.path = path .. '/?.lua;' .. path .. '/?/init.lua;' .. package.path -end - if config.install then local path = config.install .. "/scripts" package.path = path .. '/?.lua;' .. path .. '/?/init.lua;' .. package.path diff --git a/src/bindings.c b/src/bindings.c index 161f44c91..4be096277 100755 --- a/src/bindings.c +++ b/src/bindings.c @@ -1112,9 +1112,7 @@ int eressea_run(lua_State *L, const char *luafile) lua_getfield(L, -1, "traceback"); lua_remove(L, -2); - /* try to run configuration scripts: */ - err = run_script(L, "custom.lua"); - + /* run the main script */ if (luafile) { err = run_script(L, luafile); From 4be058983b3b84782fac98252df726db71735300 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 25 Apr 2026 14:38:32 +0200 Subject: [PATCH 10/39] docker sets the install directory in env --- Dockerfile | 1 + conf/eressea.ini | 1 - scripts/eressea/path.lua | 11 +++++++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 71e34daa3..a46a45704 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,6 +42,7 @@ RUN echo "$LOCALE.UTF-8 UTF-8" >> /etc/locale.gen && locale-gen ENV LC_ALL=$LOCALE.UTF-8 ENV LANG=$LOCALE.UTF-8 ENV LANGUAGE=$LOCALE:en +ENV ERESSEA_INSTALL=/usr/local/share/eressea RUN userdel -r ubuntu RUN groupadd --force -g $WWWGROUP eressea diff --git a/conf/eressea.ini b/conf/eressea.ini index 4faf9892b..74a3a660d 100644 --- a/conf/eressea.ini +++ b/conf/eressea.ini @@ -11,7 +11,6 @@ locales = de,en [lua] rules = e2 -install = /usr/local/share/eressea maxnmrs = 5 [editor] diff --git a/scripts/eressea/path.lua b/scripts/eressea/path.lua index ea614dbe3..0b4d8a7b8 100644 --- a/scripts/eressea/path.lua +++ b/scripts/eressea/path.lua @@ -1,6 +1,9 @@ -if config.install then - local path = config.install .. "/scripts" +local install = os.getenv("ERESSEA_INSTALL") or config.install +if install then + local path = install .. "/scripts" package.path = path .. '/?.lua;' .. path .. '/?/init.lua;' .. package.path - eressea.config.add_authority("conf", config.install .. '/conf') - eressea.config.add_authority("res", config.install .. '/res') + eressea.config.add_authority("conf", install .. '/conf') + eressea.config.add_authority("res", install .. '/res') +else + print("eressea.ini does not set an install directory") end From af59bb1dac59d937db73b96b6ce40e7e0b613f1b Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 25 Apr 2026 16:04:51 +0200 Subject: [PATCH 11/39] Default UID and GID Do not complain about custom rulesets No version label in image name --- Dockerfile | 6 +++--- compose.yaml | 2 +- scripts/run-turn.lua | 6 ++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index a46a45704..f4f81ac26 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,9 +27,9 @@ RUN cd eressea/build && make FROM ubuntu:24.04 -ARG WWWGROUP -ARG WWWUSER -ARG LOCALE +ARG WWWGROUP=1337 +ARG WWWUSER=1337 +ARG LOCALE=en_US ENV DEBIAN_FRONTEND=noninteractive diff --git a/compose.yaml b/compose.yaml index 142bbf415..cb68ca2a9 100644 --- a/compose.yaml +++ b/compose.yaml @@ -8,7 +8,7 @@ services: WWWUSER: '${WWWUSER:-1337}' CMAKE_BUILD_TYPE: '${CMAKE_BUILD_TYPE:-Release}' LOCALE: '${LOCALE:-en_US}' - image: eressea/server:v1 + image: eressea/server hostname: eressea environment: WWWUSER: '${WWWUSER:-1337}' diff --git a/scripts/run-turn.lua b/scripts/run-turn.lua index f4f2cc4aa..4c4fedb0b 100644 --- a/scripts/run-turn.lua +++ b/scripts/run-turn.lua @@ -177,7 +177,9 @@ function run_turn(rules) end orderfile = eressea.config.get("config.orderfile") or config.basepath .. '/orders.' .. turn print("reading orders from " .. orderfile) - eressea.log.debug("executing turn " .. get_turn() .. " with " .. orderfile .. " with rules=" .. config.rules) + local str = config.rules or 'unknown' + str = string.format("executing turn %d with %s and %s ruleset", get_turn(), orderfile, str) + eressea.log.debug(str) local result = process(rules, orderfile) return result end @@ -205,7 +207,7 @@ local rules = {} if config.rules then rules = require('eressea.' .. config.rules) eressea.log.info('loaded ' .. #rules .. ' modules for ' .. config.rules) -else +elseif not config.config then eressea.log.warning('no rule modules loaded, specify a game in eressea.ini or with -r') end run_turn(rules) From d40e427b7676a3e593b1d06ff7fb9ed9cbc65e23 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 25 Apr 2026 16:18:07 +0200 Subject: [PATCH 12/39] avoid password changes for new factions. --- src/kernel/faction.c | 2 +- src/kernel/faction.test.c | 2 +- src/reports.c | 2 +- src/reports.test.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/kernel/faction.c b/src/kernel/faction.c index 483dd2fb3..4a5a49de0 100755 --- a/src/kernel/faction.c +++ b/src/kernel/faction.c @@ -207,7 +207,7 @@ faction *addfaction(const char *email, const char *password, } f->alliance_joindate = turn; - f->lastorders = 0; + f->lastorders = -1; f->password_id = 0; faction_set_age(f, 0); f->race = frace; diff --git a/src/kernel/faction.test.c b/src/kernel/faction.test.c index 066431e04..1b719ab0c 100644 --- a/src/kernel/faction.test.c +++ b/src/kernel/faction.test.c @@ -268,7 +268,7 @@ static void test_addfaction(CuTest *tc) { CuAssertIntEquals(tc, 0, faction_age(f)); CuAssertTrue(tc, faction_alive(f)); CuAssertIntEquals(tc, M_GRAY, f->magiegebiet); - CuAssertIntEquals(tc, 0, f->lastorders); + CuAssertIntEquals(tc, -1, f->lastorders); CuAssertPtrEquals(tc, f, findfaction(f->no)); test_teardown(); } diff --git a/src/reports.c b/src/reports.c index 048cda992..e4ccccdd7 100644 --- a/src/reports.c +++ b/src/reports.c @@ -1607,7 +1607,7 @@ int init_reports(void) } for (f = factions; f; f = f->next) { if (f->email && !fval(f, FFL_NPC)) { - if (f->lastorders == 0 || faction_age(f) <= 1) { + if (f->lastorders < 0 || faction_age(f) <= 1) { attrib *a; /* neue Parteien, oder solche die noch NIE einen Zug gemacht haben, * kriegen ein neues Passwort: */ diff --git a/src/reports.test.c b/src/reports.test.c index 71b177fdc..26cc2c483 100644 --- a/src/reports.test.c +++ b/src/reports.test.c @@ -1174,7 +1174,7 @@ static void test_reports_genpassword(CuTest *tc) { test_setup(); mt_create_va(mt_new("changepasswd", NULL), "value:string", MT_NEW_END); f = test_create_faction(); - CuAssertIntEquals(tc, 0, f->lastorders); + CuAssertIntEquals(tc, -1, f->lastorders); CuAssertIntEquals(tc, 0, f->password_id); f->options = 0; /* writing the report does not change the password */ From d39382131769e353b235c598f95cd0f7418350a8 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 25 Apr 2026 17:46:52 +0200 Subject: [PATCH 13/39] solve initial password confusion --- src/reports.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reports.c b/src/reports.c index e4ccccdd7..bec2e4770 100644 --- a/src/reports.c +++ b/src/reports.c @@ -1607,7 +1607,7 @@ int init_reports(void) } for (f = factions; f; f = f->next) { if (f->email && !fval(f, FFL_NPC)) { - if (f->lastorders < 0 || faction_age(f) <= 1) { + if (f->lastorders < 0) { attrib *a; /* neue Parteien, oder solche die noch NIE einen Zug gemacht haben, * kriegen ein neues Passwort: */ From 70db00e192b13144d05409e4d27feeca3bc8a61e Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 25 Apr 2026 17:47:24 +0200 Subject: [PATCH 14/39] typo in comment --- src/helpers.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers.c b/src/helpers.c index 6690023c9..3656290e1 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -106,7 +106,7 @@ static void push_param(lua_State * L, char c, spellparameter* param) } } -/** callback to use lua functions isntead of equipment */ +/** callback to use lua functions instead of equipment */ static bool lua_equipunit(unit *u, const char *eqname, int mask) { lua_State *L = (lua_State *)global.vm_state; bool result = false; From 10bc585e2a61244c7d95e048916233a165caa98a Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 25 Apr 2026 17:57:47 +0200 Subject: [PATCH 15/39] remove junk from start-container script --- .gitignore | 2 +- docker/start-container | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 8ca35c107..58f1042a6 100644 --- a/.gitignore +++ b/.gitignore @@ -47,5 +47,5 @@ tests/config.lua *.mo /.vs /.idea/ -.env +.env* /tests/data/test.dat diff --git a/docker/start-container b/docker/start-container index 666b6affe..321aa3b83 100644 --- a/docker/start-container +++ b/docker/start-container @@ -1,9 +1,5 @@ #!/usr/bin/env bash -if [ ! -z "$WWWUSER" ]; then - usermod -u $WWWUSER eressea -fi - if [ ! -e eressea.ini ]; then cp /usr/local/share/eressea/conf/eressea.ini . fi From be664a040976b0388e66e978d2e551728479688e Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 25 Apr 2026 18:14:54 +0200 Subject: [PATCH 16/39] if script is missing, try falling back to built-ins --- src/bindings.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/bindings.c b/src/bindings.c index 4be096277..0a05b1005 100755 --- a/src/bindings.c +++ b/src/bindings.c @@ -1081,8 +1081,17 @@ static int run_script(lua_State *L, const char *luafile) { F = fopen(luafile, "r"); if (!F) { - log_debug("dofile('%s'): %s", luafile, strerror(errno)); - return errno; + /* try the scripts in the install directory */ + char scripts[PATH_MAX], filename[PATH_MAX]; + const char *install_dir = config_get("config.install"); + join_path(install_dir, "scripts", scripts, sizeof(scripts)); + join_path(scripts, luafile, filename, sizeof(filename)); + F = fopen(filename, "r"); + if (!F) { + log_debug("dofile('%s'): %s", luafile, strerror(errno)); + return errno; + } + luafile = filename; } fclose(F); From a9e8e2243d44b90d023f2d7fe0ce0ebdd056941c Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 25 Apr 2026 18:25:36 +0200 Subject: [PATCH 17/39] start-container sets ERESSEA_INSTALL run_script considers config.install --- docker/start-container | 14 ++------------ scripts/eressea/path.lua | 2 -- src/bindings.c | 14 +++++++++----- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/docker/start-container b/docker/start-container index 321aa3b83..287675062 100644 --- a/docker/start-container +++ b/docker/start-container @@ -5,15 +5,5 @@ if [ ! -e eressea.ini ]; then fi export LUA_PATH="/usr/local/share/eressea/scripts/?.lua;/usr/local/share/eressea/scripts/?/init.lua;$LUA_PATH" -SCRIPT="$1" -if [ -e "$SCRIPT" ]; then - shift -elif [ -e "/usr/local/share/eressea/scripts/$SCRIPT" ]; then - SCRIPT="/usr/local/share/eressea/scripts/$SCRIPT" - shift -else - # TODO: debug mode, remove this: - exec "$@" - exit -fi -exec eressea "$@" "$SCRIPT" +export ERESSEA_INSTALL=/usr/local/share/eressea +exec eressea -i "$ERESSEA_INSTALL" "$@" diff --git a/scripts/eressea/path.lua b/scripts/eressea/path.lua index 0b4d8a7b8..ad60f7d6f 100644 --- a/scripts/eressea/path.lua +++ b/scripts/eressea/path.lua @@ -1,7 +1,5 @@ local install = os.getenv("ERESSEA_INSTALL") or config.install if install then - local path = install .. "/scripts" - package.path = path .. '/?.lua;' .. path .. '/?/init.lua;' .. package.path eressea.config.add_authority("conf", install .. '/conf') eressea.config.add_authority("res", install .. '/res') else diff --git a/src/bindings.c b/src/bindings.c index 0a05b1005..0739557db 100755 --- a/src/bindings.c +++ b/src/bindings.c @@ -1082,16 +1082,20 @@ static int run_script(lua_State *L, const char *luafile) { F = fopen(luafile, "r"); if (!F) { /* try the scripts in the install directory */ - char scripts[PATH_MAX], filename[PATH_MAX]; const char *install_dir = config_get("config.install"); - join_path(install_dir, "scripts", scripts, sizeof(scripts)); - join_path(scripts, luafile, filename, sizeof(filename)); - F = fopen(filename, "r"); + if (install_dir) { + char scripts[PATH_MAX], filename[PATH_MAX]; + join_path(install_dir, "scripts", scripts, sizeof(scripts)); + join_path(scripts, luafile, filename, sizeof(filename)); + F = fopen(filename, "r"); + if (F) { + luafile = filename; + } + } if (!F) { log_debug("dofile('%s'): %s", luafile, strerror(errno)); return errno; } - luafile = filename; } fclose(F); From 067314a52f955ab5ec5df22c421e2318c79b0a60 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 25 Apr 2026 18:37:54 +0200 Subject: [PATCH 18/39] fix LUA_PATH for tests --- s/runtests | 2 +- tests/runtests.bat | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/s/runtests b/s/runtests index a171a702c..f9f22b91a 100755 --- a/s/runtests +++ b/s/runtests @@ -3,7 +3,7 @@ set -e eval "$(luarocks path)" ROOT=$(git rev-parse --show-toplevel) -export LUA_PATH="$ROOT/scripts/?.lua;$LUA_PATH" +export LUA_PATH="$ROOT/scripts/?.lua;$ROOT/scripts/?/init.lua;$LUA_PATH" BUILD="$ROOT/build" UNIT_TESTS="$BUILD/eressea/test_eressea" diff --git a/tests/runtests.bat b/tests/runtests.bat index ecf8974f6..27a7c0448 100644 --- a/tests/runtests.bat +++ b/tests/runtests.bat @@ -3,7 +3,7 @@ IF EXIST ..\build\x64-Debug\eressea.exe SET BUILD=..\build\x64-Debug SET SERVER=%BUILD%\eressea.exe %BUILD%\test_eressea.exe -SET LUA_PATH=..\scripts\?.lua;%LUA_PATH% +SET LUA_PATH=..\scripts\?.lua;..\scripts\?\init.lua;%LUA_PATH% %SERVER% -v1 ..\scripts\run-tests.lua if %ERRORLEVEL% NEQ 0 echo Error %ERRORLEVEL% From d2e580da4d02eb3013f43d5550565d57cc902b41 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 25 Apr 2026 22:13:43 +0200 Subject: [PATCH 19/39] set LUA_PATH for cmake tests --- src/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ca53d109d..c9068819d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -254,6 +254,12 @@ add_test(NAME lua-e3 WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests) set_property(TEST lua-e3 PROPERTY RUN_SERIAL) set_tests_properties(lua-core lua-e2 lua-e3 PROPERTIES LABELS "lua;ci-only") +set_tests_properties(lua-core lua-e2 lua-e3 PROPERTIES + ENVIRONMENT_MODIFICATION + "LUA_PATH=path_list_prepend:${CMAKE_SOURCE_DIR}/scripts/?/init.lua" + ENVIRONMENT_MODIFICATION + "LUA_PATH=path_list_prepend:${CMAKE_SOURCE_DIR}/scripts/?.lua" +) find_program(LUAROCKS_EXECUTABLE luarocks) if(LUAROCKS_EXECUTABLE) From f69501ac309682afca496b8e01a120baa58147bb Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 26 Apr 2026 12:18:19 +0200 Subject: [PATCH 20/39] debug the lua tests, fix CTest environment fix a debug test to inspect some stuff --- .github/workflows/main.yml | 1 + scripts/debug.lua | 20 ++++++++++---------- src/CMakeLists.txt | 28 ++++++++++++++++------------ 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index cc7f91c71..0730674c5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,6 +30,7 @@ jobs: run: | sudo apt install libutf8proc-dev libexpat1-dev liblua5.2-dev lua5.2 luarocks libtolua-dev libncurses5-dev libsqlite3-dev libiniparser-dev libcjson-dev libbsd-dev cppcheck shellcheck clang-tools iwyu luarocks install lunitx --local + luarocks path # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Checkout repository uses: actions/checkout@v4 diff --git a/scripts/debug.lua b/scripts/debug.lua index e6020a1a2..69e830a87 100644 --- a/scripts/debug.lua +++ b/scripts/debug.lua @@ -1,13 +1,13 @@ -require 'eressea.path' -require 'eressea' -require 'eressea.xmlconf' +print("LUA_PATH is " .. os.getenv('LUA_PATH')) +print("LUA_CPATH is " .. os.getenv('LUA_CPATH')) +print("PACKAGE.PATH is " .. package.path) +lunit = require('lunit') -function gmtool_on_keypressed(keycode) - local warp = require('eressea.warpgate') - if keycode == gmtool.KEY_F1 then - warp.gm_edit() - end +module('tests.example', lunit.testcase, package.seeall) +function test_example() + assert_true(true) end -eressea.read_game(get_turn() .. ".dat") -dump_unit("jiha") +result = lunit.main() +return result.errors + result.failed + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c9068819d..3134ad595 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -241,6 +241,10 @@ target_link_libraries(eressea ${LUA_LIBRARIES} ) +add_test(NAME lua-debug + COMMAND eressea -v1 ../scripts/debug.lua + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests) +set_property(TEST lua-debug PROPERTY RUN_SERIAL) add_test(NAME lua-core COMMAND eressea -v1 ../scripts/run-tests.lua WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests) @@ -253,21 +257,21 @@ add_test(NAME lua-e3 COMMAND eressea -v1 ../scripts/run-tests-e3.lua WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests) set_property(TEST lua-e3 PROPERTY RUN_SERIAL) -set_tests_properties(lua-core lua-e2 lua-e3 PROPERTIES LABELS "lua;ci-only") -set_tests_properties(lua-core lua-e2 lua-e3 PROPERTIES - ENVIRONMENT_MODIFICATION - "LUA_PATH=path_list_prepend:${CMAKE_SOURCE_DIR}/scripts/?/init.lua" - ENVIRONMENT_MODIFICATION - "LUA_PATH=path_list_prepend:${CMAKE_SOURCE_DIR}/scripts/?.lua" -) +set_tests_properties(lua-debug lua-core lua-e2 lua-e3 PROPERTIES LABELS "lua;ci-only") find_program(LUAROCKS_EXECUTABLE luarocks) if(LUAROCKS_EXECUTABLE) -execute_process(COMMAND ${LUAROCKS_EXECUTABLE} path --lr-path OUTPUT_VARIABLE LR_PATH) -execute_process(COMMAND ${LUAROCKS_EXECUTABLE} path --lr-cpath OUTPUT_VARIABLE LR_CPATH) -string(REPLACE ";" "\;" LUA_PATH "${CMAKE_SOURCE_DIR}/scripts/?.lua;${LR_PATH}") -string(REPLACE ";" "\;" LUA_CPATH "${LR_CPATH}") -set_tests_properties(lua-core lua-e2 lua-e3 PROPERTIES ENVIRONMENT "LUA_PATH=${LUA_PATH};LUA_CPATH=${LUA_CPATH}") +execute_process(COMMAND ${LUAROCKS_EXECUTABLE} path --lr-path + OUTPUT_VARIABLE LR_PATH) +execute_process(COMMAND ${LUAROCKS_EXECUTABLE} path --lr-cpath + OUTPUT_VARIABLE LR_CPATH) +string(REPLACE ";" "\;" LUA_PATH + "${CMAKE_SOURCE_DIR}/scripts/?.lua;${CMAKE_SOURCE_DIR}/scripts/?/init.lua;${LR_PATH}") +string(REPLACE ";" "\;" LUA_CPATH + "${LR_CPATH}") +set_tests_properties(lua-debug lua-core lua-e2 lua-e3 PROPERTIES + ENVIRONMENT "LUA_PATH=${LUA_PATH};LUA_CPATH=${LUA_CPATH}" +) endif(LUAROCKS_EXECUTABLE) endif (LUA_FOUND) From 544025d45b5b15243f706240ca568f06d7550d9a Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 26 Apr 2026 14:30:56 +0200 Subject: [PATCH 21/39] upgrade actions/checkout --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0730674c5..88ab34a28 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,7 +33,7 @@ jobs: luarocks path # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: true - name: Build and Test From 1641c6af87dbc4fab63344361ea99c6829d2fb8c Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 26 Apr 2026 14:55:24 +0200 Subject: [PATCH 22/39] Setting LUA_PATH in cron jobs --- process/cron/preview.cron | 2 +- process/cron/run-eressea.cron | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/process/cron/preview.cron b/process/cron/preview.cron index 922b5960d..580fcefc5 100755 --- a/process/cron/preview.cron +++ b/process/cron/preview.cron @@ -4,7 +4,7 @@ [ -z "${ERESSEA}" ] && ERESSEA="$HOME/eressea" eval "$(luarocks path)" -export LUA_PATH="${ERESSEA}/git/scripts/?.lua;$LUA_PATH" +export LUA_PATH="${ERESSEA}/git/scripts/?.lua;${ERESSEA}/git/scripts/?/init.lua;$LUA_PATH" branch="develop" if [ -e "${ERESSEA}/build/.preview" ]; then diff --git a/process/cron/run-eressea.cron b/process/cron/run-eressea.cron index 11993132d..477e90107 100755 --- a/process/cron/run-eressea.cron +++ b/process/cron/run-eressea.cron @@ -51,7 +51,7 @@ backup() export ERESSEA eval "$(luarocks path)" -export LUA_PATH="${ERESSEA}/server/scripts/?.lua;$LUA_PATH" +export LUA_PATH="${ERESSEA}/server/scripts/?.lua;${ERESSEA}/git/scripts/?/init.lua;$LUA_PATH" BIN="$ERESSEA/server/bin" TURN=$(cat "$ERESSEA/game-$GAME/turn") if [ ! -e "$ERESSEA/game-$GAME/data/$TURN.dat" ]; then From f6943a6c04074c1f78df692b410c8de12f4717e8 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 26 Apr 2026 14:58:18 +0200 Subject: [PATCH 23/39] include ?/init.lua in LUA_PATH --- README.md | 4 ++-- tests/run-turn.sh | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 79db0f3bb..998ebb0b0 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ in "args". On my own computer, the configuration looks like this: "currentDir": "${env.ERESSEA_ROOT}", "env": { "ERESSEA_ROOT": "${workspaceRoot}", - "LUA_PATH": "${workspaceRoot}\\scripts\\?.lua;${env.LUA_PATH}" + "LUA_PATH": "${workspaceRoot}\\scripts\\?.lua;${workspaceRoot}\\scripts\\?\\init.lua;${env.LUA_PATH}" }, "name": "eressea.exe" }, @@ -95,6 +95,6 @@ in "args". On my own computer, the configuration looks like this: Note that I have an environment variable for my game directory called ERESSEA_ROOT. My LUA_PATH also contains the luarocks installation. ``` -LUA_PATH=C:\Users\Enno\AppData\Roaming\Luarocks\share\lua\5.4\?.lua +LUA_PATH=C:\Users\Enno\AppData\Roaming\Luarocks\share\lua\5.4\?.lua;C:\Users\Enno\AppData\Roaming\Luarocks\share\lua\5.4\?\init.lua ERESSEA_ROOT C:\Users\Enno\Documents\Eressea\test ``` diff --git a/tests/run-turn.sh b/tests/run-turn.sh index bc631d39c..082eaf309 100755 --- a/tests/run-turn.sh +++ b/tests/run-turn.sh @@ -27,7 +27,7 @@ ROOT=`pwd` while [ ! -d $ROOT/.git ]; do ROOT=`dirname $ROOT` done -export LUA_PATH="$ROOT/scripts/?.lua;$LUA_PATH" +export LUA_PATH="$ROOT/scripts/?.lua;$ROOT/scripts/?/init.lua;$LUA_PATH" cd $ROOT/tests setup cleanup From d4d70a0997fef00b3eafe48e129b4d746a5d7958 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 26 Apr 2026 15:44:07 +0200 Subject: [PATCH 24/39] set LUA_PATH in the preview script --- process/cron/preview.cron | 3 --- s/preview | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/process/cron/preview.cron b/process/cron/preview.cron index 580fcefc5..63fe2ca76 100755 --- a/process/cron/preview.cron +++ b/process/cron/preview.cron @@ -3,9 +3,6 @@ [ "$PREVIEW" != "yes" ] && exit [ -z "${ERESSEA}" ] && ERESSEA="$HOME/eressea" -eval "$(luarocks path)" -export LUA_PATH="${ERESSEA}/git/scripts/?.lua;${ERESSEA}/git/scripts/?/init.lua;$LUA_PATH" - branch="develop" if [ -e "${ERESSEA}/build/.preview" ]; then branch=$(cat "${ERESSEA}/build/.preview") diff --git a/s/preview b/s/preview index cc61e28eb..86cfe947b 100755 --- a/s/preview +++ b/s/preview @@ -126,6 +126,8 @@ shift $((OPTIND-1)) [ -z "$1" ] && usage [ -z "$SOURCE" ] && SOURCE="$ERESSEA/git" [ -d "$SOURCE" ] || abort "missing source directory: $SOURCE" +eval $(luarocks path) +export LUA_PATH="$SOURCE/scripts/?.lua;$SOURCE/scripts/?/init.lua;$LUA_PATH" LIVE="$ERESSEA/game-$game" TESTROOT="$LIVE/test" From da940c05dc977730a38f1f68e5bf56cba038eb1f Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Tue, 28 Apr 2026 09:02:42 +0200 Subject: [PATCH 25/39] use @example.com for sample emails --- scripts/tools/build-e3.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/tools/build-e3.lua b/scripts/tools/build-e3.lua index b33927877..9dd233e7a 100644 --- a/scripts/tools/build-e3.lua +++ b/scripts/tools/build-e3.lua @@ -29,10 +29,10 @@ function give_item(email, id, uname, iname) end function give_items() - give_item("hongeldongel@web.de", "boss", "Drollitz", "rpg_item_1") - give_item("zangerl.helmut@chello.at", "holb", "Holbard", "rpg_item_2") - give_item("r.lang@chello.at", "brtL", "Bertl", "rpg_item_2") - give_item("schlaustauch@gmx.de", "bert", "Bertram", "rpg_item_3") + give_item("hodor@example.com", "boss", "Drollitz", "rpg_item_1") + give_item("foo@example.com", "holb", "Holbard", "rpg_item_2") + give_item("bar@example.com", "brtL", "Bertl", "rpg_item_2") + give_item("baz@example.com", "bert", "Bertram", "rpg_item_3") end function island(pl, x, y, r) From 9a587a7bbd64038bf959b31d11be77bd71cf54c6 Mon Sep 17 00:00:00 2001 From: oddluminari Date: Wed, 29 Apr 2026 12:19:08 +0200 Subject: [PATCH 26/39] gitignore clangd cache directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 58f1042a6..8038af7a7 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ tests/config.lua /.idea/ .env* /tests/data/test.dat +.cache/clangd From f204c16b8076b38d24cf573ca851968b7056a06d Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 1 May 2026 14:02:35 +0200 Subject: [PATCH 27/39] additional tests for sp_break_curse. fix reduce_curse sign error. --- src/spells.c | 4 +- src/spells.test.c | 105 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 101 insertions(+), 8 deletions(-) diff --git a/src/spells.c b/src/spells.c index b468a3b77..11c934e2e 100644 --- a/src/spells.c +++ b/src/spells.c @@ -5757,7 +5757,6 @@ int sp_break_curse(castorder * co) region *r = co_get_region(co); unit *mage = co_get_caster(co); int cast_level = co->level; - double force = co->force; spellparameter *params = co->a_params; void *curse_target = NULL; @@ -5779,6 +5778,7 @@ int sp_break_curse(castorder * co) "unit region command", mage, mage->region, co->order)); } else { + double force = co->force; const char *ts = NULL; attrib **ap; switch (obj) { @@ -5828,7 +5828,7 @@ int sp_break_curse(castorder * co) } /* curse aufloesen, wenn zauber staerker (force > vigour) */ - force = reduce_curse(c, cast_level, -force, curse_target); + reduce_curse(c, cast_level, force, curse_target); if (c->vigour <= 0.0) { remove_curse(ap, c); diff --git a/src/spells.test.c b/src/spells.test.c index 3a059855b..660de3270 100644 --- a/src/spells.test.c +++ b/src/spells.test.c @@ -24,6 +24,7 @@ #include "kernel/types.h" #include +#include "util/base36.h" #include "util/keyword.h" #include "util/language.h" #include "util/message.h" @@ -174,28 +175,117 @@ static void test_change_race(CuTest* tc) { static void test_break_curse(CuTest *tc) { struct region *r; struct faction *f; - unit *u1, *u2; + unit *u; + curse *c; castorder co; spellparameter param, *args = NULL; test_setup(); r = test_create_plain(0, 0); f = test_create_faction(); - u1 = test_create_unit(f, r); - u2 = test_create_unit(f, r); - scale_number(u2, 30); + u = test_create_unit(f, r); + c = create_curse(u, &r->attribs, &ct_astralblock, 5.0, 1, 0.0, 0); + + param.flag = TARGET_OK; + param.typ = SPP_REGION; + param.data.r = r; + arrput(args, param); + + param.typ = SPP_STRING; + param.data.xs = str_strdup(itoa36(c->no)); + arrput(args, param); + + test_create_castorder(&co, u, 5, 5., 0, args); + CuAssertIntEquals(tc, co.level, sp_break_curse(&co)); + CuAssertPtrEquals(tc, NULL, r->attribs); + CuAssertPtrNotNull(tc, test_find_faction_message(f, "destroy_curse_effect")); + test_teardown(); +} + +static void test_break_curse_low_level(CuTest *tc) { + struct region *r; + struct faction *f; + unit *u; + curse *c; + castorder co; + spellparameter param, *args = NULL; + + test_setup(); + r = test_create_plain(0, 0); + f = test_create_faction(); + u = test_create_unit(f, r); + c = create_curse(u, &r->attribs, &ct_astralblock, 5.0, 1, 0.0, 0); + + param.flag = TARGET_OK; + param.typ = SPP_REGION; + param.data.r = r; + arrput(args, param); + + param.typ = SPP_STRING; + param.data.xs = str_strdup(itoa36(c->no)); + arrput(args, param); + + test_create_castorder(&co, u, 4, 5., 0, args); + CuAssertIntEquals(tc, co.level, sp_break_curse(&co)); + CuAssertPtrNotNull(tc, r->attribs); + CuAssertDblEquals(tc, 2.5, c->vigour, 0.01); + CuAssertPtrNotNull(tc, test_find_faction_message(f, "destroy_curse_noeffect")); + test_teardown(); +} + +static void test_break_curse_too_weak(CuTest *tc) { + struct region *r; + struct faction *f; + unit *u; + curse *c; + castorder co; + spellparameter param, *args = NULL; + + test_setup(); + r = test_create_plain(0, 0); + f = test_create_faction(); + u = test_create_unit(f, r); + c = create_curse(u, &r->attribs, &ct_astralblock, 5.0, 1, 0.0, 0); + + param.flag = TARGET_OK; + param.typ = SPP_REGION; + param.data.r = r; + arrput(args, param); + + param.typ = SPP_STRING; + param.data.xs = str_strdup(itoa36(c->no)); + arrput(args, param); + + test_create_castorder(&co, u, 5, 4., 0, args); + CuAssertIntEquals(tc, co.level, sp_break_curse(&co)); + CuAssertPtrNotNull(tc, r->attribs); + CuAssertDblEquals(tc, 5.0, c->vigour, 0.01); + CuAssertPtrNotNull(tc, test_find_faction_message(f, "destroy_curse_noeffect")); + test_teardown(); +} + +static void test_break_curse_resists(CuTest *tc) { + struct region *r; + struct faction *f; + unit *u; + castorder co; + spellparameter param, *args = NULL; + + test_setup(); + r = test_create_plain(0, 0); + f = test_create_faction(); + u = test_create_unit(f, r); param.flag = TARGET_RESISTS; param.typ = SPP_BUILDING; param.data.b = NULL; arrput(args, param); - param.flag = TARGET_OK; param.typ = SPP_STRING; param.data.xs = NULL; arrput(args, param); - test_create_castorder(&co, u1, 3, 4., 0, args); + test_create_castorder(&co, u, 3, 4., 0, args); CuAssertIntEquals(tc, co.level, sp_break_curse(&co)); CuAssertIntEquals(tc, 0, test_count_messagetype(f->msgs, NULL)); test_teardown(); @@ -2482,6 +2572,9 @@ CuSuite *get_spells_suite(void) SUITE_ADD_TEST(suite, test_create_firewall); SUITE_ADD_TEST(suite, test_destroy_firewall); SUITE_ADD_TEST(suite, test_break_curse); + SUITE_ADD_TEST(suite, test_break_curse_low_level); + SUITE_ADD_TEST(suite, test_break_curse_too_weak); + SUITE_ADD_TEST(suite, test_break_curse_resists); SUITE_ADD_TEST(suite, test_magicrunes); return suite; From 2791facfe77fd8852ae14712c202e06d45c0cb7d Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 2 May 2026 09:34:55 +0200 Subject: [PATCH 28/39] bug 3098: remove charm effect when it is dispelled --- src/kernel/attrib.h | 14 ++++++------- src/kernel/curse.c | 8 ++++++- src/kernel/curse.h | 1 + src/spells.c | 4 ++-- src/spells/charming.c | 7 +++++++ src/spells/charming.test.c | 43 ++++++++++++++++++++++++++++++++++++-- 6 files changed, 65 insertions(+), 12 deletions(-) diff --git a/src/kernel/attrib.h b/src/kernel/attrib.h index f6eaf20a3..8c1ccc3d1 100644 --- a/src/kernel/attrib.h +++ b/src/kernel/attrib.h @@ -23,15 +23,15 @@ typedef struct attrib { typedef struct attrib_type { const char *name; - void(*initialize) (union variant *); - void(*finalize) (union variant *); - int(*age) (struct attrib *, void *owner); + void (*initialize) (union variant *); + void (*finalize) (union variant *); + int (*age) (struct attrib *, void *owner); /* age returns 0 if the attribute needs to be removed, !=0 otherwise */ - void(*write) (const union variant *, const void *owner, struct storage *); - int(*read) (union variant *, void *owner, struct gamedata *); /* return AT_READ_OK on success, AT_READ_FAIL if attrib needs removal */ - void(*upgrade) (struct attrib **alist, struct attrib *a); + void (*write) (const union variant *, const void *owner, struct storage *); + int (*read) (union variant *, void *owner, struct gamedata *); /* return AT_READ_OK on success, AT_READ_FAIL if attrib needs removal */ + void (*upgrade) (struct attrib **alist, struct attrib *a); unsigned int flags; - void(*dump) (const struct attrib *a); + void (*dump) (const struct attrib *a); } attrib_type; void at_register(attrib_type * at); diff --git a/src/kernel/curse.c b/src/kernel/curse.c index e876e5ac8..a2c113a8e 100644 --- a/src/kernel/curse.c +++ b/src/kernel/curse.c @@ -364,7 +364,13 @@ curse *findcurse(int i) bool remove_curse(attrib ** ap, const curse * c) { attrib *a = a_select(*ap, c, cmp_curse); - return a && a_remove(ap, a) == 1; + if (a) { + if (c->type->remove) { + c->type->remove(c); + } + return a_remove(ap, a) == 1; + } + return false; } /* gibt die allgemeine Staerke der Verzauberung zurueck. id2 wird wie diff --git a/src/kernel/curse.h b/src/kernel/curse.h index 9b2495c36..76d3d75fb 100644 --- a/src/kernel/curse.h +++ b/src/kernel/curse.h @@ -146,6 +146,7 @@ extern "C" { int(*cansee) (const struct faction *, const void *, objtype_t, const struct curse *, int); int(*age) (struct curse *, void *); + void(*remove) (const struct curse *); void(*construct) (struct curse *); void(*destroy) (struct curse *); } curse_type; diff --git a/src/spells.c b/src/spells.c index 11c934e2e..22778666a 100644 --- a/src/spells.c +++ b/src/spells.c @@ -354,7 +354,7 @@ static void magicanalyse_ship(ship * sh, unit * mage, double force) } -static int break_curse(attrib ** alist, int cast_level, double force, curse * c, void *curse_target) +static int break_curses(attrib ** alist, int cast_level, double force, curse * c, void *curse_target) { int succ = 0; /* attrib **a = a_find(*ap, &at_curse); */ @@ -736,7 +736,7 @@ int sp_destroy_magic(castorder * co) return 0; } - succ = break_curse(ap, co->level, force, c, curse_target); + succ = break_curses(ap, co->level, force, c, curse_target); if (succ > 0) { ADDMSG(&caster->faction->msgs, msg_message("destroy_magic_effect", diff --git a/src/spells/charming.c b/src/spells/charming.c index ae54f1e99..2e93f442c 100644 --- a/src/spells/charming.c +++ b/src/spells/charming.c @@ -108,6 +108,12 @@ static int slave_age(struct curse *c, void *owner) return 0; } +static void slave_remove(const struct curse *c) +{ + slave_data *sd = (slave_data *)c->data.v; + sd->self->faction = sd->faction; +} + const struct curse_type ct_slavery = { .cname = "slavery", .typ = CURSETYP_NORM, @@ -117,6 +123,7 @@ const struct curse_type ct_slavery = { .read = slave_read, .write = slave_write, .age = slave_age, + .remove = slave_remove, .construct = slave_init, .destroy = slave_done, }; diff --git a/src/spells/charming.test.c b/src/spells/charming.test.c index 6d5942a3a..6639cacbf 100644 --- a/src/spells/charming.test.c +++ b/src/spells/charming.test.c @@ -1,15 +1,20 @@ #include "charming.h" +#include "spells.h" +#include "laws.h" +#include "magic.h" + +#include #include #include #include #include +#include #include #include -#include -#include +#include #include #include @@ -44,6 +49,39 @@ static void test_charm_unit(CuTest * tc) test_teardown(); } +static void test_charm_unit_dispell(CuTest *tc) +{ + unit *u, *mage; + faction *f; + castorder co; + spellparameter param, *args = NULL; + curse *c; + + test_setup(); + u = test_create_unit(f = test_create_faction(), test_create_plain(0, 0)); + mage = test_create_unit(test_create_faction(), u->region); + charm_unit(u, mage, 3.0, 2); + CuAssertTrue(tc, unit_is_slaved(u)); + CuAssertPtrEquals(tc, mage->faction, u->faction); + CuAssertPtrNotNull(tc, c = get_curse(u->attribs, &ct_slavery)); + + param.flag = TARGET_OK; + param.typ = SPP_UNIT; + param.data.u = u; + arrput(args, param); + + param.typ = SPP_STRING; + param.data.xs = str_strdup(itoa36(c->no)); + arrput(args, param); + + test_create_castorder(&co, u, 5, 5., 0, args); + sp_break_curse(&co); + CuAssertTrue(tc, !unit_is_slaved(u)); + CuAssertPtrEquals(tc, f, u->faction); + + test_teardown(); +} + static void test_charmed_unit_original_faction_dies(CuTest *tc) { unit *u, *mage; @@ -177,6 +215,7 @@ CuSuite *get_charming_suite(void) CuSuite *suite = CuSuiteNew(); SUITE_ADD_TEST(suite, test_charm_unit); + SUITE_ADD_TEST(suite, test_charm_unit_dispell); SUITE_ADD_TEST(suite, test_charmingsong); SUITE_ADD_TEST(suite, test_charmed_unit_original_faction_dies); SUITE_ADD_TEST(suite, test_charmed_unit_new_faction_dies); From c4e12fb926c64d8ea19e07550a1aa7b8740e0cc6 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 2 May 2026 15:04:12 +0200 Subject: [PATCH 29/39] Additional testing for ct_firewall This curse doesn't use curse_type.remove, but a clever combination of change_vigour and age! --- src/spells.test.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/spells.test.c b/src/spells.test.c index 660de3270..4e860d9b2 100644 --- a/src/spells.test.c +++ b/src/spells.test.c @@ -2496,7 +2496,7 @@ static void test_destroy_firewall(CuTest *tc) { unit *u; region *r2, *r1; - curse *c; + curse *c1, *c2; double force; test_setup(); @@ -2504,10 +2504,21 @@ static void test_destroy_firewall(CuTest *tc) r2 = test_create_plain(1, 0); create_firewall(u, r1, D_EAST, force = 3.0, 2); - CuAssertPtrNotNull(tc, c = get_curse(r1->attribs, &ct_firewall)); - CuAssertDblEquals(tc, 0.0, reduce_curse(c, 4, c->vigour, r1), 0.001); - - CuAssertPtrEquals(tc, NULL, get_borders(u->region, r2)); + CuAssertPtrNotNull(tc, get_borders(r1, r2)); + CuAssertPtrNotNull(tc, get_borders(r2, r1)); + CuAssertPtrNotNull(tc, c1 = get_curse(r1->attribs, &ct_firewall)); + CuAssertPtrNotNull(tc, c2 = get_curse(r2->attribs, &ct_firewall)); + CuAssertDblEquals(tc, 3.0, c1->vigour, 0.001); + CuAssertDblEquals(tc, 3.0, c2->vigour, 0.001); + CuAssertDblEquals(tc, 0.0, reduce_curse(c1, 4, c1->vigour, r1), 0.001); + CuAssertPtrEquals(tc, c1, get_curse(r1->attribs, &ct_firewall)); + CuAssertDblEquals(tc, 0.0, c1->vigour, 0.001); + CuAssertPtrEquals(tc, c2, get_curse(r2->attribs, &ct_firewall)); + + /* firewall disappears right away, but curses age out: */ + CuAssertPtrEquals(tc, NULL, get_borders(r1, r2)); + CuAssertIntEquals(tc, 0, ct_firewall.age(c1, r1)); + CuAssertIntEquals(tc, 0, ct_firewall.age(c2, r2)); test_teardown(); } From 85f63fafe439c3aed891144aaa4fb0b1145eadf6 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Thu, 7 May 2026 19:27:10 +0200 Subject: [PATCH 30/39] make autoseed deal with different kinds of whitespace --- scripts/eressea/autoseed.lua | 8 ++++---- src/bind_faction.c | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/scripts/eressea/autoseed.lua b/scripts/eressea/autoseed.lua index fc658d1a1..b4ab4833d 100644 --- a/scripts/eressea/autoseed.lua +++ b/scripts/eressea/autoseed.lua @@ -2,10 +2,10 @@ if not config.autoseed or config.autoseed==0 then return nil end local autoseed = {} -- minimum required resources in the 7-hex neighborhood: -local peasants = 10000 -local trees = 800 +local peasants = 1000 +local trees = 400 -- minimum resources in the region itself: -local min_peasants = 2000 +local min_peasants = 1000 -- number of starters per region: local per_region = 2 @@ -48,7 +48,7 @@ local function read_players() local str = input:read("*line") while str do if str==nil then break end - local email, race, lang = str:match("([^ ]*) ([^ ]*) ([^ ]*)") + local email, race, lang = str:match("([^%s]*)%s+([^%s]*)%s+([^%s]*)") if email and string.char(string.byte(email, 1))~='#' then table.insert(players, { race = race, lang = lang, email = email }) end diff --git a/src/bind_faction.c b/src/bind_faction.c index 1f6eb05cd..c70b59a8b 100644 --- a/src/bind_faction.c +++ b/src/bind_faction.c @@ -364,9 +364,12 @@ static int tolua_faction_create(lua_State * L) const char *racename = tolua_tostring(L, 1, NULL); const char *email = tolua_tostring(L, 2, NULL); const char *lang = tolua_tostring(L, 3, NULL); - struct locale *loc = lang ? get_locale(lang) : default_locale; + struct locale *loc = lang ? get_locale(lang) : NULL; faction *f = NULL; const struct race *frace = rc_find(racename ? racename : "human"); + if (!loc) { + loc = default_locale; + } if (frace != NULL) { f = addfaction(email, NULL, frace, loc); } From 384a1ebcbbf3100b8b7ceee1b33be281bf141f90 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Thu, 7 May 2026 19:30:17 +0200 Subject: [PATCH 31/39] vscode devcontainer --- .devcontainer/devcontainer.json | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..fe218f261 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,29 @@ +// https://aka.ms/devcontainer.json +{ + "name": "Shard API", + "dockerComposeFile": [ + "../compose.yaml" + ], + "service": "shard-api", + "workspaceFolder": "/var/www/html", + "customizations": { + "vscode": { + "extensions": [ + "waderyan.gitblame", + "EditorConfig.EditorConfig", + "ms-vscode.cpptools", + "ms-vscode.cpp-devtools", + "ms-vscode.cpptools-extension-pack", + "ms-vscode.cmake-tools", + "fredericbonnet.cmake-test-adapter" + + ], + "settings": {} + } + }, + "remoteUser": "sail", + "postCreateCommand": "chown -R 1000:1000 /var/www/html 2>/dev/null || true" + // "forwardPorts": [], + // "runServices": [], + // "shutdownAction": "none", +} From f6614ad27e0b04d441d59975ae4d7aa03d60d8f0 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Thu, 7 May 2026 19:42:15 +0200 Subject: [PATCH 32/39] let autoseed rules be set externally add them to your by rules.json, if you like! --- scripts/eressea/autoseed.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/eressea/autoseed.lua b/scripts/eressea/autoseed.lua index b4ab4833d..f6d6c6b1b 100644 --- a/scripts/eressea/autoseed.lua +++ b/scripts/eressea/autoseed.lua @@ -1,11 +1,15 @@ if not config.autoseed or config.autoseed==0 then return nil end local autoseed = {} +local function config_num(str, def) + return tonumber(eressea.config.get(str)) or def +end + -- minimum required resources in the 7-hex neighborhood: -local peasants = 1000 -local trees = 400 +local peasants = config_num("autoseed.peasants", 10000) +local trees = config_num("autoseed.trees", 800) -- minimum resources in the region itself: -local min_peasants = 1000 +local min_peasants = config_num("autoseed.min_peasants", 1000) -- number of starters per region: local per_region = 2 From 8eff826a815da58277fa844a2849aacceb53e29b Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Thu, 7 May 2026 19:49:16 +0200 Subject: [PATCH 33/39] sample game demonstrate use of custom rules and scripts. --- sample/custom/strings.de.po | 18 +++++++++++ sample/custom/strings.en.po | 18 +++++++++++ sample/custom/terrains.json | 11 +++++++ sample/export.lua | 61 +++++++++++++++++++++++++++++++++++++ sample/newfactions | 7 +++++ sample/newgame.lua | 30 ++++++++++++++++++ sample/rules.json | 17 +++++++++++ 7 files changed, 162 insertions(+) create mode 100644 sample/custom/strings.de.po create mode 100644 sample/custom/strings.en.po create mode 100644 sample/custom/terrains.json create mode 100644 sample/export.lua create mode 100644 sample/newfactions create mode 100644 sample/newgame.lua create mode 100644 sample/rules.json diff --git a/sample/custom/strings.de.po b/sample/custom/strings.de.po new file mode 100644 index 000000000..722a53b10 --- /dev/null +++ b/sample/custom/strings.de.po @@ -0,0 +1,18 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: de\n" +"X-Generator: Poedit 2.0.7\n" + +msgid "hellfire" +msgstr "Höllenfeuer" + +msgid "hellfire_trail" +msgstr "das Höllenfeuer von %s" diff --git a/sample/custom/strings.en.po b/sample/custom/strings.en.po new file mode 100644 index 000000000..36c4b6790 --- /dev/null +++ b/sample/custom/strings.en.po @@ -0,0 +1,18 @@ +msgid "" +msgstr "" +"Project-Id-Version: \n" +"POT-Creation-Date: \n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: en\n" +"X-Generator: Poedit 2.0.7\n" + +msgid "hellfire" +msgstr "hellfire" + +msgid "hellfire_trail" +msgstr "the hellfires of %s" diff --git a/sample/custom/terrains.json b/sample/custom/terrains.json new file mode 100644 index 000000000..d933e8346 --- /dev/null +++ b/sample/custom/terrains.json @@ -0,0 +1,11 @@ +{ + "terrains": { + "grassland": { + "size": 5000, + "herbs": [ "h0", "h1", "h2", "h3", "h4", "h5" ], + "seed": 3, + "road": 50, + "flags": [ "forest", "cavalry", "land", "walk", "sail", "fly" ] + } + } +} diff --git a/sample/export.lua b/sample/export.lua new file mode 100644 index 000000000..33f51d738 --- /dev/null +++ b/sample/export.lua @@ -0,0 +1,61 @@ +require 'config' + +function export(filename) + local cr = io.open(filename, 'w') + cr:write('VERSION 69\n"UTF-8";charset\n"de";locale\n') + for f in factions() do + cr:write('PARTEI ' .. f.id) + cr:write(string.format('"%s";Parteiname\n', f.name)) + cr:write(string.format('"%s";email\n', f.email)) + end + for r in regions() do + if r.plane then + local x, y = r.plane:normalize(r.x, r.y) + cr:write(string.format('REGION %d %d %d\n', x, y, r.plane.id)) + else + cr:write(string.format('REGION %d %d\n', r.x, r.y)) + end + cr:write(string.format('%d;id\n', r.id)) + cr:write(string.format('"%s";Terrain\n', translate(r.terrain, 'de'))) + local s = r:get_resource('money') + if s > 0 then cr:write(string.format('%d;Silber\n', s)) end + local p = r.peasants + if p > 0 then + cr:write(string.format('%d;Bauern\n', p)) + cr:write(string.format('%d;Unterh\n', math.floor(p / 20))) + end + for x in r.buildings do + cr:write(string.format('BURG %d\n', x.id)) + cr:write(string.format('"%s";Name\n', x.name)) + local typename = x:get_typename() or x.type + cr:write(string.format('"%s";Typ\n', translate(typename, 'de'))) + cr:write(string.format('%d;Groesse\n', x.size)) + end + for x in r.ships do + cr:write(string.format('SCHIFF %d\n', x.id)) + cr:write(string.format('"%s";Name\n', x.name)) + cr:write(string.format('"%s";Typ\n', translate(x.type, 'de'))) + cr:write(string.format('%d;Groesse\n', x.size)) + local cap = x.units() + if cap then + cr:write(string.format('%d;Kapitaen\n', cap.id)) + end + end + for u in r.units do + cr:write(string.format('EINHEIT %d\n', u.id)) + cr:write(string.format('"%s";Typ\n', translate('race::' .. u.race, 'de'))) + cr:write(string.format('"%s";Name\n', u.name)) + cr:write(string.format('%d;Partei\n', u.faction.id)) + cr:write(string.format('%d;Anzahl\n', u.number)) + if u.building then + cr:write(string.format('%d;Burg\n', u.building.id)) + end + if u.ship then + cr:write(string.format('%d;Schiff\n', u.ship.id)) + end + end + end +end + +eressea.read_game(get_turn() .. '.dat') +export("export.cr") diff --git a/sample/newfactions b/sample/newfactions new file mode 100644 index 000000000..235e63eda --- /dev/null +++ b/sample/newfactions @@ -0,0 +1,7 @@ +troll@example.com troll de +orc@example.com orc de +human@example.com human de +elf@example.com elf de +aquarian@example.com aquarian de +dwarf@example.com dwarf de + diff --git a/sample/newgame.lua b/sample/newgame.lua new file mode 100644 index 000000000..63d7d1a7f --- /dev/null +++ b/sample/newgame.lua @@ -0,0 +1,30 @@ +require 'config' + +region.create(1, 0, 'desert').name = 'Armouth' +region.create(-1, 0, 'ocean') +region.create(-1, 1, 'ocean') +region.create(0, 1, 'ocean') +region.create(0, -1, 'ocean') +region.create(1, -1, 'hellfire').name = 'Dis' + +r = region.create(0, 0, 'plain') +r.name = 'Gont' +f = faction.create('orc', 'ged@example.com', 'de') +f.name = 'Wizards of Earthsea' +ship.create(r, 'boat').name = 'Shadow' +b = building.create(r, 'lighthouse') +b.name = 'Gont Port' +b = building.create(r, 'castle') +b.name = 'The Tombs of Atuan' +u = unit.create(f, r) +equip_unit(u, "seed_faction") +equip_unit(u, "seed_unit") +equip_unit(u, "seed_orc", 7) +u.name = 'Sparrowhawk' +u.ship = ship.create(r, 'caravel') +u.ship.name = 'Lookfar' +print(u) +print(r, r.terrain) +init_reports() +write_reports() +eressea.write_game(get_turn() .. '.dat') diff --git a/sample/rules.json b/sample/rules.json new file mode 100644 index 000000000..6f37059ff --- /dev/null +++ b/sample/rules.json @@ -0,0 +1,17 @@ +{ + "settings": { + "game.name": "Earthsea", + "game.start": 0, + "modules.astralspace": 0, + "autoseed.peasants": 1000, + "autoseed.trees": 400, + "autoseed.min_peasants": 1000, + "autoseed.per_region": 2 + }, + "include": [ + "config://conf/e2/config.json", + "config://custom/terrains.json", + "config://custom/strings.de.po", + "config://custom/strings.en.po" + ] +} From 5bfe394b3195b0ab0680910b235e095a595d9204 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Thu, 7 May 2026 19:58:23 +0200 Subject: [PATCH 34/39] devcontainer file didn't work remove it for now, fix one at a later time. --- .devcontainer/devcontainer.json | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index fe218f261..000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,29 +0,0 @@ -// https://aka.ms/devcontainer.json -{ - "name": "Shard API", - "dockerComposeFile": [ - "../compose.yaml" - ], - "service": "shard-api", - "workspaceFolder": "/var/www/html", - "customizations": { - "vscode": { - "extensions": [ - "waderyan.gitblame", - "EditorConfig.EditorConfig", - "ms-vscode.cpptools", - "ms-vscode.cpp-devtools", - "ms-vscode.cpptools-extension-pack", - "ms-vscode.cmake-tools", - "fredericbonnet.cmake-test-adapter" - - ], - "settings": {} - } - }, - "remoteUser": "sail", - "postCreateCommand": "chown -R 1000:1000 /var/www/html 2>/dev/null || true" - // "forwardPorts": [], - // "runServices": [], - // "shutdownAction": "none", -} From d5e11d8114a133c0738a8c91aeb2e06b98370da4 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Thu, 7 May 2026 20:34:43 +0200 Subject: [PATCH 35/39] README: add luarocks+lunit instructions --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 998ebb0b0..d6e303b21 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Eressea depends on a number of external libraries. On a recent Debian-based Linux system, this is the apt-get command to install all of them: - sudo apt-get install git cmake gcc make luarocks \ + sudo apt-get install git cmake gcc make \ liblua5.2-dev libtolua-dev libncurses5-dev libsqlite3-dev \ libcjson-dev libiniparser-dev libexpat1-dev libutf8proc-dev @@ -34,6 +34,21 @@ Here's how you clone and build the source on Linux or macOS: If you got this far and all went well, you have built the server, and it will have passed some basic functionality tests. +### Testing + +If you plan to do any development, there is also a whole suite of Lua tests +that require the [lunit](https://github.com/dcurrie/lunit) unit testing framework. +This is best installed using the luarocks package manager: +``` + sudo apt-get install lua5.2 luarocks + luarocks config lua_version 5.2 + luarocks config variables.LUA_INCDIR /usr/include/lua5.2 + luarocks install --local lunitx +``` + +You should now be able to run the `s/runtests` script, which will execute +all the tests. + * [![Static Analysis](https://scan.coverity.com/projects/6742/badge.svg?flat=1)](https://scan.coverity.com/projects/6742/) * [![Build Status](https://api.travis-ci.org/eressea/server.svg?branch=develop)](https://travis-ci.org/eressea/server) * [![License: CC BY-NC-SA 4.0](https://licensebuttons.net/l/by-nc-sa/4.0/80x15.png)](http://creativecommons.org/licenses/by-nc-sa/4.0/) From cc22bbd62f3fbdeee23b3ce55240f9941e68cdba Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 22 May 2026 14:10:19 +0200 Subject: [PATCH 36/39] log more errors --- src/jsonconf.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/jsonconf.c b/src/jsonconf.c index 942f6f209..3137a79cb 100644 --- a/src/jsonconf.c +++ b/src/jsonconf.c @@ -1285,7 +1285,9 @@ static int include_json(const char *uri) { int result = -1; F = fopen(filename, "r"); - if (F) { + if (!F) { + log_error("file not found: %s", filename); + } else { long pos; fseek(F, 0, SEEK_END); pos = ftell(F); From c7eebbce121e029f8b9fee705a17ba966f558528 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Mon, 25 May 2026 21:11:10 +0200 Subject: [PATCH 37/39] Bug 3105: Partei fehlt in CR und Adressliste (#1144) * Bug 0003105: Partei fehlt in Adressliste * fix new regression in get_addresses --- src/reports.c | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/reports.c b/src/reports.c index bec2e4770..619e9dfad 100644 --- a/src/reports.c +++ b/src/reports.c @@ -201,7 +201,7 @@ const char *hp_status(const unit * u) return NULL; } -bool see_schemes(const region *r, seen_mode mode) { +bool see_schemes(const region *r, enum seen_mode mode) { return mode >= seen_unit && is_astral(r) && !is_cursed(r->attribs, &ct_astralblock); } @@ -1164,13 +1164,20 @@ void get_addresses(report_context * ctx) const unit *u = r->units; while (u != NULL) { if (u->faction != ctx->f) { - faction *sf = visible_faction(ctx->f, u, get_otherfaction(u)); - if (sf) { - bool ballied = sf != ctx->f && sf != lastf - && !fval(u, UFL_ANON_FACTION) && cansee(ctx->f, r, u, stealthmod); - if (ballied || is_allied(ctx->f, sf)) { - add_seen_faction_i(&flist, sf); - lastf = sf; + if (cansee(ctx->f, r, u, stealthmod)) { + faction *sf = get_otherfaction(u); + // faction *sf = visible_faction(ctx->f, u, get_otherfaction(u)); + if (sf) { + bool ballied = sf != ctx->f && sf != lastf + && !fval(u, UFL_ANON_FACTION); + if (ballied || is_allied(ctx->f, sf)) { + add_seen_faction_i(&flist, sf); + lastf = sf; + } + } + else if (lastf != u->faction) { + add_seen_faction_i(&flist, u->faction); + lastf = u->faction; } } } From cbe7c335bc4b94f4d494b217b729642ed2778274 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Tue, 25 Aug 2026 08:26:27 +0200 Subject: [PATCH 38/39] prepare a release 3.1 --- src/kernel/version.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kernel/version.c b/src/kernel/version.c index 9d860fc51..b017fdb12 100644 --- a/src/kernel/version.c +++ b/src/kernel/version.c @@ -8,7 +8,7 @@ #ifndef ERESSEA_VERSION /* the version number, if it was not passed to make with -D */ -#define ERESSEA_VERSION "31.2.0" +#define ERESSEA_VERSION "31.1.0" #endif const char *eressea_version(void) { From dda9b0a5e69849b3596d7ec7256332ad02e1bde5 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Mon, 24 Aug 2026 14:16:16 +0200 Subject: [PATCH 39/39] license change, AI policy --- AGENTS.md | 20 ++++++++++++++++++++ CLAUDE.md | 22 ++++++++++++++++++++++ LICENSE.md | 20 ++++++++++++++++---- 3 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..7709baa11 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,20 @@ +AI must not be used to generate code for contributions to this project. + +"AI" in this case means a Large Language Model ("LLM"), such as ChatGPT, +Claude, Copilot, Grok, etc. + +AI-generated code is based upon sources of unknown origins and may not be +compatible with the Zlib license, or may introduce conflicting license terms +if they include code from other projects. + +AI can be used to identify issues with contributions to this project, but the +solutions to those issues should be authored by humans. + +We have found that AI will frequently hallucinate issues that are not actually +problems in practice, report incorrect information, and describe problems that +are actually not issues at all. If AI identifies a problem with this codebase, +please make sure you understand what it is saying and have independently +confirmed that the issue exists before submitting a bug report or pull request. + +Any pull request to this project will ask you to confirm that you are the +author and that you are contributing your changes under the Zlib license. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..c8408cf2c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,22 @@ +AI must not be used to generate code for contributions to this project. + +"AI" in this case means a Large Language Model ("LLM"), such as ChatGPT, +Claude, Copilot, Grok, etc. + +AI-generated code is based upon sources of unknown origins and may not be +compatible with the Zlib license, or may introduce conflicting license terms +if they include code from other projects. + +AI can be used to identify issues with contributions to this project, but the +solutions to those issues should be authored by humans. + +We have found that AI will frequently hallucinate issues that are not actually +problems in practice, report incorrect information, and describe problems that +are actually not issues at all. If AI identifies a problem with this codebase, +please make sure you understand what it is saying and have independently +confirmed that the issue exists before submitting a bug report or pull request. + +Any pull request to this project will ask you to confirm that you are the +author and that you are contributing your changes under the Zlib license. + + diff --git a/LICENSE.md b/LICENSE.md index 6262bc013..4784dd1ed 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,5 +1,17 @@ -Copyright (c) 1998-2026, Enno Rehling +Copyright (c) 1998-2026, Enno Rehling and others. + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. -Eressea is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. - -* [![License Text](https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png)](http://creativecommons.org/licenses/by-nc-sa/4.0/) +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. \ No newline at end of file