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/.gitignore b/.gitignore index 8ca35c107..8038af7a7 100644 --- a/.gitignore +++ b/.gitignore @@ -47,5 +47,6 @@ tests/config.lua *.mo /.vs /.idea/ -.env +.env* /tests/data/test.dat +.cache/clangd 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/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/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/DOCKER.md b/DOCKER.md new file mode 100644 index 000000000..dc7678e68 --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,157 @@ +# 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:/data eressea/server:v1 myscript.lua +``` + +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/ + +## Building the image from source + +### Clone the Repository + +```bash +$ git clone -b develop 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),27(sudo),100(users),107(netdev),989(docker) +``` + +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 +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 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. + + +## Create a game directory and a tiny world. + +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 +# 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 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. 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! + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..f4f81ac26 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,66 @@ +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=1337 +ARG WWWUSER=1337 +ARG LOCALE=en_US + +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 +ENV ERESSEA_INSTALL=/usr/local/share/eressea + +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/LICENSE.md b/LICENSE.md index 44de6fdeb..4784dd1ed 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,5 +1,17 @@ -Copyright (c) 1998-2022, 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 diff --git a/README.md b/README.md index 79db0f3bb..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/) @@ -78,7 +93,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 +110,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/compose.yaml b/compose.yaml new file mode 100644 index 000000000..cb68ca2a9 --- /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:-Release}' + LOCALE: '${LOCALE:-en_US}' + image: eressea/server + 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..74a3a660d 100644 --- a/conf/eressea.ini +++ b/conf/eressea.ini @@ -1,17 +1,17 @@ [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 +maxnmrs = 5 [editor] -color = 1 - +color = 1 diff --git a/docker/start-container b/docker/start-container new file mode 100644 index 000000000..287675062 --- /dev/null +++ b/docker/start-container @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +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" +export ERESSEA_INSTALL=/usr/local/share/eressea +exec eressea -i "$ERESSEA_INSTALL" "$@" diff --git a/process/cron/run-eressea.cron b/process/cron/run-eressea.cron index 88157b845..5a241ddfb 100755 --- a/process/cron/run-eressea.cron +++ b/process/cron/run-eressea.cron @@ -62,7 +62,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 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" 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/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" + ] +} 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/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/scripts/eressea/autoseed.lua b/scripts/eressea/autoseed.lua index fc658d1a1..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 = 10000 -local trees = 800 +local peasants = config_num("autoseed.peasants", 10000) +local trees = config_num("autoseed.trees", 800) -- minimum resources in the region itself: -local min_peasants = 2000 +local min_peasants = config_num("autoseed.min_peasants", 1000) -- number of starters per region: local per_region = 2 @@ -48,7 +52,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/scripts/eressea/path.lua b/scripts/eressea/path.lua index 32ae75b81..ad60f7d6f 100644 --- a/scripts/eressea/path.lua +++ b/scripts/eressea/path.lua @@ -1,9 +1,7 @@ -local path = '.' -if config.install then - path = config.install +local install = os.getenv("ERESSEA_INSTALL") or config.install +if install then + eressea.config.add_authority("conf", install .. '/conf') + eressea.config.add_authority("res", install .. '/res') else - path = os.getenv("ERESSEA_ROOT") or path - config.install = path + print("eressea.ini does not set an install directory") 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/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/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) 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) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ca53d109d..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,15 +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-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) diff --git a/src/battle.c b/src/battle.c index 71cb5004b..0b58ff8be 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; } @@ -1491,7 +1491,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) @@ -2126,7 +2126,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); } @@ -2141,7 +2141,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); } @@ -2277,7 +2277,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 fc1e452bb..b1fad11eb 100644 --- a/src/battle.h +++ b/src/battle.h @@ -193,8 +193,6 @@ enum { ALLY_SELF, ALLY_ANY }; int get_unitrow(const fighter* af, const side* vs); -int count_enemies(struct battle *b, const struct fighter *af, - int minrow, int maxrow, int select); bool escapes_tactics(const fighter *af, const troop dt); troop select_enemy(struct fighter* af, int minrow, int maxrow, int select); @@ -202,6 +200,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(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/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/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); } diff --git a/src/bindings.c b/src/bindings.c index 1152021ad..0739557db 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); @@ -1030,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); @@ -1074,8 +1081,21 @@ 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 */ + const char *install_dir = config_get("config.install"); + 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; + } } fclose(F); @@ -1105,9 +1125,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); 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/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; diff --git a/src/items/weapons.c b/src/items/weapons.c index 6bd514a04..47f51e1a1 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) @@ -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; @@ -99,7 +98,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/jsonconf.c b/src/jsonconf.c index 02837783b..3137a79cb 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); } } @@ -1273,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); @@ -1444,3 +1458,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/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/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/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(); } 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/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/kernel/messages.c b/src/kernel/messages.c index 1aa424cc0..c3ded0b16 100644 --- a/src/kernel/messages.c +++ b/src/kernel/messages.c @@ -155,7 +155,7 @@ struct message *msg_feedback(const struct unit *u, struct order *ord, msg = msg_create(mtype, args); } if (ord && is_silent(ord)) { - msg->is_silent = 1; + msg->is_silent = -1; } return msg; diff --git a/src/main.c b/src/main.c index a3de84ae9..7376bd8c4 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 @@ -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 diff --git a/src/reports.c b/src/reports.c index 048cda992..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; } } } @@ -1607,7 +1614,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: */ 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 */ diff --git a/src/spells.c b/src/spells.c index b468a3b77..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", @@ -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..4e860d9b2 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(); @@ -2406,7 +2496,7 @@ static void test_destroy_firewall(CuTest *tc) { unit *u; region *r2, *r1; - curse *c; + curse *c1, *c2; double force; test_setup(); @@ -2414,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(); } @@ -2482,6 +2583,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; 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); diff --git a/src/spells/combatspells.c b/src/spells/combatspells.c index 23e2f31fb..647ccdead 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); @@ -212,7 +212,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); @@ -223,10 +223,13 @@ int sp_stun(struct castorder * co) stunned = 0; while (force-- && stunned < enemies) { troop dt = select_enemy(fi, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); - if (dt.fighter) { - unit *du = dt.fighter->unit; + fighter *df = dt.fighter; + if (df) { + unit *du = df->unit; + + --force; if (!is_magic_resistant(mage, du, 0)) { - dt.fighter->person[dt.index].flags |= FL_STUNNED; + df->person[dt.index].flags |= FL_STUNNED; ++stunned; } } @@ -268,7 +271,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); @@ -356,7 +359,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); @@ -477,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); @@ -544,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 = @@ -590,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); @@ -761,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); @@ -856,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); @@ -1011,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); @@ -1059,7 +1062,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); @@ -1104,7 +1107,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); 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 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%