diff --git a/.gitignore b/.gitignore index 0d59349f5..a894eecff 100644 --- a/.gitignore +++ b/.gitignore @@ -38,10 +38,7 @@ Thumbs.db *.cfg *.cmd tmp/ -tests/orders.txt tests/config.lua -/tests/reports -/tests/data /quicklist /cutest /critbit diff --git a/CMakeLists.txt b/CMakeLists.txt index de1b08fa0..02c2fcd9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,12 +79,10 @@ add_subdirectory (clibs) add_subdirectory (process) add_subdirectory (src eressea) -install(DIRECTORY etc DESTINATION ${CMAKE_INSTALL_PREFIX} FILES_MATCHING PATTERN "*.txt") -install(DIRECTORY res conf DESTINATION ${CMAKE_INSTALL_PREFIX} FILES_MATCHING PATTERN "*.po") -install(DIRECTORY res conf DESTINATION ${CMAKE_INSTALL_PREFIX} FILES_MATCHING PATTERN "*.xml") -install(DIRECTORY res conf DESTINATION ${CMAKE_INSTALL_PREFIX} FILES_MATCHING PATTERN "*.json") -install(DIRECTORY scripts DESTINATION ${CMAKE_INSTALL_PREFIX} PATTERN "tests" EXCLUDE) -install(DIRECTORY lunit DESTINATION ${CMAKE_INSTALL_PREFIX} FILES_MATCHING PATTERN "*.lua") -install(DIRECTORY share DESTINATION ${CMAKE_INSTALL_PREFIX}) - +install(DIRECTORY etc DESTINATION "." FILES_MATCHING PATTERN "*.txt") +install(DIRECTORY res conf DESTINATION "." FILES_MATCHING PATTERN "*.po") +install(DIRECTORY res conf DESTINATION "." FILES_MATCHING PATTERN "*.xml") +install(DIRECTORY res conf DESTINATION "." FILES_MATCHING PATTERN "*.json") +install(DIRECTORY scripts DESTINATION "." PATTERN "tests" EXCLUDE) +install(DIRECTORY share DESTINATION ".") diff --git a/process/accept-orders.py b/process/accept-orders.py index 9a2987530..767d3b3e2 100755 --- a/process/accept-orders.py +++ b/process/accept-orders.py @@ -1,23 +1,20 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import os.path -import ConfigParser +import io +from configparser import ConfigParser import string import logging import sys import subprocess import time import socket -import rfc822 from stat import ST_MTIME -from email.Utils import parseaddr -from email.Parser import Parser +from email.utils import parseaddr, parsedate_tz, mktime_tz +from email.parser import Parser -if sys.version_info[0] > 2: - print("this script has not yet been converted to work with python 3") - sys.exit(2) if 'ERESSEA' in os.environ: dir = os.environ['ERESSEA'] elif 'HOME' in os.environ: @@ -39,7 +36,7 @@ if not os.path.exists(inifile): print("no such file: " . inifile) else: - config = ConfigParser.ConfigParser() + config = ConfigParser() config.read(inifile) if config.has_option('game', 'email'): frommail = config.get('game', 'email') @@ -63,57 +60,57 @@ messages = { "multipart-en" : - "ERROR: The orders you sent contain no plaintext. " \ - "The Eressea server cannot process orders containing HTML " \ - "or invalid attachments, which are the reasons why this " \ - "usually happens. Please change the settings of your mail " \ - "software and re-send the orders.", + u"ERROR: The orders you sent contain no plaintext. " \ + u"The Eressea server cannot process orders containing HTML " \ + u"or invalid attachments, which are the reasons why this " \ + u"usually happens. Please change the settings of your mail " \ + u"software and re-send the orders.", "multipart-de" : - "FEHLER: Die von dir eingeschickte Mail enthält keinen " \ - "Text. Evtl. hast Du den Zug als HTML oder als anderweitig " \ - "ungültig formatierte Mail ingeschickt. Wir können ihn " \ - "deshalb nicht berücksichtigen. Schicke den Zug nochmals " \ - "als reinen Text ohne Formatierungen ein.", + u"FEHLER: Die von dir eingeschickte Mail enthält keinen " \ + u"Text. Evtl. hast Du den Zug als HTML oder als anderweitig " \ + u"ungültig formatierte Mail ingeschickt. Wir können ihn " \ + u"deshalb nicht berücksichtigen. Schicke den Zug nochmals " \ + u"als reinen Text ohne Formatierungen ein.", "maildate-de": - "Es erreichte uns bereits ein Zug mit einem späteren " \ - "Absendedatum (%s > %s). Entweder ist deine " \ - "Systemzeit verstellt, oder ein Zug hat einen anderen Zug von " \ - "dir auf dem Transportweg überholt. Entscheidend für die " \ - "Auswertungsreihenfolge ist das Absendedatum, d.h. der Date:-Header " \ - "deiner Mail.", + u"Es erreichte uns bereits ein Zug mit einem späteren " \ + u"Absendedatum (%s > %s). Entweder ist deine " \ + u"Systemzeit verstellt, oder ein Zug hat einen anderen Zug von " \ + u"dir auf dem Transportweg überholt. Entscheidend für die " \ + u"Auswertungsreihenfolge ist das Absendedatum, d.h. der Date:-Header " \ + u"deiner Mail.", "maildate-en": - "The server already received an order file that was sent at a later " \ - "date (%s > %s). Either your system clock is wrong, or two messages have " \ - "overtaken each other on the way to the server. The order of " \ - "execution on the server is always according to the Date: header in " \ - "your mail.", + u"The server already received an order file that was sent at a later " \ + u"date (%s > %s). Either your system clock is wrong, or two messages have " \ + u"overtaken each other on the way to the server. The order of " \ + u"execution on the server is always according to the Date: header in " \ + u"your mail.", "nodate-en": - "Your message did not contain a valid Date: header in accordance with RFC2822.", + u"Your message did not contain a valid Date: header in accordance with RFC2822.", "nodate-de": - "Deine Nachricht enthielt keinen gueltigen Date: header nach RFC2822.", + u"Deine Nachricht enthielt keinen gueltigen Date: header nach RFC2822.", "error-de": - "Fehler", + u"Fehler", "error-en": - "Error", + u"Error", "warning-de": - "Warnung", + u"Warnung", "warning-en": - "Warning", + u"Warning", "subject-de": - "Befehle angekommen", + u"Befehle angekommen", "subject-en": - "orders received" + u"orders received" } # return 1 if addr is a valid email address @@ -178,7 +175,7 @@ def available_file(dirname, basename): return maxdate, filename def formatpar(string, l=76, indent=2): - words = string.split(string) + words = string.split() res = "" ll = 0 first = 1 @@ -190,7 +187,7 @@ def formatpar(string, l=76, indent=2): ll = len(word) else: if ll + len(word) > l: - res = res + "\n"+" "*indent+word + res = res + u"\n"+" "*indent+word ll = len(word) + indent else: res = res+" "+word @@ -199,7 +196,7 @@ def formatpar(string, l=76, indent=2): return res+"\n" def store_message(message, filename): - outfile = open(filename, "w") + outfile = io.open(filename, "wb") outfile.write(message.as_string()) outfile.close() return @@ -225,7 +222,7 @@ def write_part(outfile, part, sender): except: outfile.write(msg) return False - outfile.write("\n"); + outfile.write("\n".encode('ascii')); return True def copy_orders(message, filename, sender, mtime): @@ -234,13 +231,13 @@ def copy_orders(message, filename, sender, mtime): if writeheaders: header_dir = dirname + '/headers' if not os.path.exists(header_dir): os.mkdir(header_dir) - outfile = open(header_dir + '/' + basename, "w") + outfile = io.open(header_dir + '/' + basename, "wb") for name, value in message.items(): - outfile.write(name + ": " + value + "\n") + outfile.write((name + ": " + value + "\n").encode('utf8', 'ignore')) outfile.close() found = False - outfile = open(filename, "w") + outfile = io.open(filename, "wb") if message.is_multipart(): for part in message.get_payload(): if write_part(outfile, part, sender): @@ -284,7 +281,7 @@ def accept(game, locale, stream, extend=None): if maildate is None: turndate = time.time() else: - turndate = rfc822.mktime_tz(rfc822.parsedate_tz(maildate)) + turndate = mktime_tz(parsedate_tz(maildate)) text_ok = copy_orders(message, filename, email, turndate) @@ -292,7 +289,7 @@ def accept(game, locale, stream, extend=None): if not maildate is None: os.utime(filename, (turndate, turndate)) logger.debug("mail date is '%s' (%d)" % (maildate, turndate)) - if False and turndate < maxdate: + if turndate < maxdate: logger.warning("inconsistent message date " + email) warning = " (" + messages["warning-" + locale] + ")" msg = msg + formatpar(messages["maildate-" + locale] % (time.ctime(maxdate), time.ctime(turndate)), 76, 2) + "\n" @@ -312,14 +309,19 @@ def accept(game, locale, stream, extend=None): savedir = savedir + "/rejected" if not os.path.exists(savedir): os.mkdir(savedir) maxdate, filename = available_file(savedir, prefix + email) - store_message(message, filename) + if filename is None: + logger.error("too many failed attempts") + else: + store_message(message, filename) fail = True if sendmail and warning is not None: logger.warning(warning) subject = gamename + " " + messages["subject-"+locale] + warning - ps = subprocess.Popen(['mutt', '-s', subject, email], stdin=subprocess.PIPE) - ps.communicate(msg) + ps = subprocess.Popen(['mutt', '-s', subject, email], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = ps.communicate(msg.encode("utf8", "ignore")) + if output[0] != '': + logger.warning(output[0]) if not sendmail: print(text_ok, fail, email) @@ -341,7 +343,7 @@ def accept(game, locale, stream, extend=None): locale = sys.argv[2] infile = sys.stdin if len(sys.argv)>3: - infile = open(sys.argv[3], "r") + infile = io.open(sys.argv[3], "rt") retval = accept(game, locale, infile, delay) if infile!=sys.stdin: infile.close() diff --git a/process/checkpasswd.py b/process/checkpasswd.py index ac58e7c73..fdc8613d0 100755 --- a/process/checkpasswd.py +++ b/process/checkpasswd.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys, re from epasswd import EPasswd diff --git a/process/compress.py b/process/compress.py index f87c6a069..ade1a6c0d 100755 --- a/process/compress.py +++ b/process/compress.py @@ -1,7 +1,8 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from sys import argv, exit import os +import io import os.path gamename='Eressea' @@ -9,7 +10,7 @@ if(len(argv) >= 3): gamename=argv[2] -template="""#!/bin/bash +template=u"""#!/bin/bash #PATH=$PATH:$HOME/bin addr=%(email)s @@ -19,7 +20,7 @@ turn = argv[1] try: - infile = open("reports.txt", "rt") + infile = io.open("reports.txt", "rt") except: print("%s: reports.txt file does not exist" % (argv[0], )) exit(0) @@ -78,7 +79,7 @@ if os.path.isfile(extra): files = files + [extra] options["files"] = ' '.join(files) - batch = open("%s.sh" % options["faction"], "wt") + batch = io.open("%s.sh" % options["faction"], "wt") batch.write(template % options) batch.close() infile.close() diff --git a/process/epasswd.py b/process/epasswd.py index 913b2ac93..0ddafd8bc 100755 --- a/process/epasswd.py +++ b/process/epasswd.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 import bcrypt import sqlite3 diff --git a/process/getemail.py b/process/getemail.py index d9951bcb0..de648508e 100755 --- a/process/getemail.py +++ b/process/getemail.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys, re from epasswd import EPasswd diff --git a/process/getfaction.py b/process/getfaction.py index 11350ec8a..ad00e2b51 100755 --- a/process/getfaction.py +++ b/process/getfaction.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys, re from epasswd import EPasswd diff --git a/process/orders-accept b/process/orders-accept index c5db0d38a..8058eb023 100755 --- a/process/orders-accept +++ b/process/orders-accept @@ -11,7 +11,7 @@ BIN=$(dirname "$SCRIPT") cd "$ERESSEA/game-$game" mkdir -p orders.dir cd orders.dir -eval "$(python2.7 "$BIN/accept-orders.py" "$@")" +eval "$("$BIN/accept-orders.py" "$@")" if [ -e "$ACCEPT_FILE" ] then rm -f "$LOCKFILE" diff --git a/res/core/common/items.xml b/res/core/common/items.xml index 67f2c1119..d9b01680b 100644 --- a/res/core/common/items.xml +++ b/res/core/common/items.xml @@ -35,7 +35,7 @@ - + diff --git a/res/eressea/buildings.xml b/res/eressea/buildings.xml index d239907f1..8d5a0e26f 100644 --- a/res/eressea/buildings.xml +++ b/res/eressea/buildings.xml @@ -1,9 +1,9 @@ - - - - + + + + diff --git a/res/eressea/items.xml b/res/eressea/items.xml index ea6ab047d..5bec46908 100644 --- a/res/eressea/items.xml +++ b/res/eressea/items.xml @@ -24,7 +24,7 @@ - + @@ -74,7 +74,7 @@ - + diff --git a/res/translations/strings.de.po b/res/translations/strings.de.po index 52fe24c90..0f9090a21 100644 --- a/res/translations/strings.de.po +++ b/res/translations/strings.de.po @@ -4690,7 +4690,7 @@ msgid "unit_guards" msgstr "bewacht die Region" msgid "pavilion" -msgstr "Pavillion" +msgstr "Pavillon" msgctxt "race" msgid "spell_d" diff --git a/s/build b/s/build index f47ff2d95..3e0a52ffe 100755 --- a/s/build +++ b/s/build @@ -2,11 +2,7 @@ ROOT=$(git rev-parse --show-toplevel) BUILD=build if [ -z "$JOBS" ] ; then - if [ -e /usr/sbin/sysctl ]; then - JOBS=`sysctl -n hw.ncpu` - else - JOBS=`nproc` - fi + JOBS=`nproc` fi DISTCC=`which distcc` if [ ! -z "$DISTCC" ] ; then diff --git a/s/cmake-init b/s/cmake-init index e416a560a..33391505d 100755 --- a/s/cmake-init +++ b/s/cmake-init @@ -98,7 +98,6 @@ cat >| build/config.cmake < 0 then for name, count in pairs(items) do u:add_item(name, count * u.number) end end local skills = set['skills'] - if skills then + if skills and bit32.band(flags, EQUIP_SKILLS) > 0 then for name, level in pairs(skills) do u:set_skill(name, level) end end local spells = set['spells'] - if spells then + if spells and bit32.band(flags, EQUIP_SPELLS) > 0 then for name, level in pairs(spells) do u:add_spell(name, level) end end local callback = set['callback'] - if callback and type(callback) == 'function' then + if callback and bit32.band(flags, EQUIP_SPECIAL) > 0 and type(callback) == 'function' then callback(u, flags) end + + u.hp = u.hp_max * u.number return true end return false diff --git a/scripts/tests/e2/e2features.lua b/scripts/tests/e2/e2features.lua index d4878bd39..7f703d788 100644 --- a/scripts/tests/e2/e2features.lua +++ b/scripts/tests/e2/e2features.lua @@ -636,3 +636,21 @@ function test_seacast() process_orders() assert_equal(8, u2.region.x) end + +-- Solthar: long orders prevent trading +function test_bug_2978() + local r = region.create(0, 0, 'plain') + r.peasants = 10000 + r.luxury = 'balm' + local f = faction.create('human') + local u = unit.create(f, r, 1) + u:add_item("jewel", 1) + assert_equal(1, u:get_item("jewel")) + u:add_item("stone", 2) + u:add_order("MACHE 2 Burg") + u:add_order("VERKAUFE ALLES Juwel") + u:set_skill("trade", 10) + u:set_skill("building", 10) + process_orders() + assert_equal(1, u:get_item("jewel")) +end diff --git a/scripts/tests/e2/recruit.lua b/scripts/tests/e2/recruit.lua index 74304519e..2766f195b 100644 --- a/scripts/tests/e2/recruit.lua +++ b/scripts/tests/e2/recruit.lua @@ -57,7 +57,6 @@ function test_guarded_empty_units_cannot_recruit() u2:set_skill("melee", 1) u2.guard = true - u1.name='Xolthar' u1:add_item("money", 100) u1:add_order("GIB 0 ALLES PERSONEN") u1:add_order("REKRUTIERE 1") diff --git a/scripts/tests/e2/report.lua b/scripts/tests/e2/report.lua index 00a3e0628..e4ece2012 100644 --- a/scripts/tests/e2/report.lua +++ b/scripts/tests/e2/report.lua @@ -39,6 +39,7 @@ function test_coordinates_unnamed_plane() local f = faction.create("human", "unnamed@eressea.de", "de") local u = unit.create(f, r, 1) init_reports() + f.id = 1 write_report(f) assert_true(find_in_report(f, r.name .. " %(0,0%), Berg")) remove_report(f) @@ -49,6 +50,7 @@ function test_coordinates_no_plane() local f = faction.create("human", "noplane@eressea.de", "de") local u = unit.create(f, r, 1) init_reports() + f.id = 2 write_report(f) assert_true(find_in_report(f, r.name .. " %(0,0%), Berg")) remove_report(f) @@ -63,6 +65,7 @@ function test_show_shadowmaster_attacks() u:add_order("ZEIGE Schattenmeister") process_orders() init_reports() + f.id = 3 write_report(f) assert_false(find_in_report(f, ", ,")) remove_report(f) @@ -74,6 +77,7 @@ function test_coordinates_named_plane() local f = faction.create("human", "noreply@eressea.de", "de") local u = unit.create(f, r, 1) init_reports() + f.id = 4 write_report(f) assert_true(find_in_report(f, r.name .. " %(0,0,Hell%), Berg")) remove_report(f) @@ -85,6 +89,7 @@ function test_coordinates_noname_plane() local f = faction.create("human", "noreply@eressea.de", "de") local u = unit.create(f, r, 1) init_reports() + f.id = 5 write_report(f) assert_true(find_in_report(f, r.name .. " %(0,0%), Berg")) remove_report(f) @@ -110,6 +115,7 @@ function test_lighthouse() assert_not_nil(b) init_reports() + f.id = 6 write_report(f) assert_false(find_in_report(f, "The Babadook")) assert_true(find_in_report(f, " %(1,0%) %(vom Turm erblickt%)")) diff --git a/scripts/tests/e3/items.lua b/scripts/tests/e3/items.lua index e1a20430b..08c9199a7 100644 --- a/scripts/tests/e3/items.lua +++ b/scripts/tests/e3/items.lua @@ -26,6 +26,7 @@ function test_water_of_life() trees = r:get_resource('tree')-trees if trees~=5 then init_reports() + f.id = atoi36('tree') write_report(f) print(f, get_turn()) end diff --git a/scripts/tests/eressea/equipment.lua b/scripts/tests/eressea/equipment.lua new file mode 100644 index 000000000..5e941abab --- /dev/null +++ b/scripts/tests/eressea/equipment.lua @@ -0,0 +1,152 @@ +local tcname = 'tests.eressea.equipment' +local lunit = require('lunit') +if _VERSION >= 'Lua 5.2' then + _ENV = module(tcname, 'seeall') +else + module(tcname, lunit.testcase, package.seeall) +end + +local equipment + +function setup() + equipment = require 'eressea.equipment' + eressea.free_game() + eressea.game.reset() + eressea.config.reset(); + eressea.config.parse([[ +{ + "races": { + "human": { "hp" : 20 }, + "demon": { "hp" : 25 }, + "goblin": { "hp" : 30 } + }, + "terrains": { + "plain" : {} + }, + "items" : { + "stone" : {}, + "roi" : {} + }, + "spells" : { "bug" : {} } +} + ]]) +end + +function test_start() + assert_equal(2500, equipment.get('first_unit')['items']['money']) + equipment.add('test_start', { ['hodor'] = 'Hodor!'}) + assert_equal('Hodor!', equipment.get('test_start')['hodor']) +end + +function add_test_equipment(u, flags) + u:add_item("money", flags) + u.number = 2 +end + +function test_noflags() + local r = region.create(0,0,'plain') + local f = faction.create('human') + local u = unit.create(f, r) + u.magic = 'illaun' + + equipment.add('test_equip', { + ['items'] = { ['stone'] = 10, }, + ['skills'] = { ['magic'] = 1, }, + ['spells'] = { ['bug'] = 2}, + ['callback'] = add_test_equipment + }) + + assert_equal(0, u:get_item('stone')) + + equip_unit(u, 'test_equip') + assert_equal(10, u:get_item('stone')) + assert_equal(1, u:get_skill('magic')) + for sp in u.spells do + assert_equal("bug", sp.name) + assert_equal(2, sp.level) + end + assert_equal(255, u:get_item('money')) + assert_equal(2, u.number) + assert_equal(u.hp_max * u.number, u.hp) +end + +function test_equip_unit() + local r = region.create(0,0,'plain') + local f = faction.create('human') + local u = unit.create(f, r) + u.magic = 'illaun' + + equipment.add('test_equip', { + ['items'] = { ['stone'] = 10, }, + ['skills'] = { ['magic'] = 1, }, + ['spells'] = { ['bug'] = 2}, + ['callback'] = add_test_equipment + }) + equip_unit(u, 'test_equip', 0) + assert_equal(0, u:get_item('stone')) + local flags = EQUIP_ITEMS + equip_unit(u, 'test_equip', flags) + assert_equal(10, u:get_item('stone')) + assert_equal(0, u:get_skill('magic')) + flags = EQUIP_ITEMS + EQUIP_SKILLS + equip_unit(u, 'test_equip', flags) + assert_equal(20, u:get_item('stone')) + assert_equal(1, u:get_skill('magic')) + assert_equal(nil, u.spells) + flags = EQUIP_ITEMS + EQUIP_SKILLS + EQUIP_SPELLS + equip_unit(u, 'test_equip', flags) + assert_equal(30, u:get_item('stone')) + assert_equal(1, u:get_skill('magic')) + for sp in u.spells do + assert_equal("bug", sp.name) + assert_equal(2, sp.level) + end + assert_equal(0, u:get_item('money')) + flags = EQUIP_SKILLS + EQUIP_SPECIAL + equip_unit(u, 'test_equip', flags) + assert_equal(30, u:get_item('stone')) + assert_equal(1, u:get_skill('magic')) + for sp in u.spells do + assert_equal("bug", sp.name) + assert_equal(2, sp.level) + end + assert_equal(flags, u:get_item('money')) + assert_equal(2, u.number) + assert_equal(u.hp_max * u.number, u.hp) +end + +function test_equip_hp() + local r = region.create(0,0,'plain') + local f = faction.create('human') + local u = unit.create(f, r) + + equipment.add('test_equip', { + ['skills'] = { ['stamina'] = 1, }, + ['callback'] = add_test_equipment + }) + equip_unit(u, 'test_equip', EQUIP_SKILLS + EQUIP_SPECIAL) + assert_equal(1, u:get_skill('stamina')) + assert_equal(math.floor(2*20*1.07), u.hp) +end + +function test_equip_demon() + local r = region.create(0, 0, 'plain') + local f = faction.create('demon') + local u = unit.create(f, r) + + equip_unit(u, 'seed_demon', EQUIP_SKILLS + EQUIP_SPECIAL) + assert_equal(15, u:get_skill('stamina')) + assert_equal(u.hp_max, u.hp) + +end + +function test_equip_goblin() + local r = region.create(0, 0, 'plain') + local f = faction.create('goblin') + local u = unit.create(f, r) + + equip_unit(u, 'seed_goblin', EQUIP_ALL) + assert_equal(10, u.number) + assert_equal(u.hp_max * u.number, u.hp) + +end \ No newline at end of file diff --git a/scripts/tests/init.lua b/scripts/tests/init.lua index ca68a3f20..f382e50b0 100644 --- a/scripts/tests/init.lua +++ b/scripts/tests/init.lua @@ -13,3 +13,4 @@ require 'tests.shiplanding' require 'tests.harbor' require 'tests.economy' require 'tests.skills' +require 'tests.eressea.equipment' diff --git a/scripts/tests/pool.lua b/scripts/tests/pool.lua index c5331c8ad..6a70f8185 100644 --- a/scripts/tests/pool.lua +++ b/scripts/tests/pool.lua @@ -12,7 +12,7 @@ function setup() eressea.settings.set("rules.magic.playerschools", "") conf = [[{ "races": { - "human" : { "flags" : [ "giveitem", "getitem" ] } + "human" : { "flags" : [ "getitem" ] } }, "terrains" : { "plain": { "flags" : [ "land" ] } @@ -35,7 +35,7 @@ end function test_give_nopool() local r = region.create(1, 1, "plain") - local f = faction.create("human", "test@example.com", "de") + local f = faction.create("human") local u1 = unit.create(f, r, 1) local u2 = unit.create(f, r, 1) u1:add_item("money", 100) @@ -47,7 +47,7 @@ end function test_give_from_faction() local r = region.create(1, 1, "plain") - local f = faction.create("human", "test@example.com", "de") + local f = faction.create("human") local u1 = unit.create(f, r, 1) local u2 = unit.create(f, r, 1) local u3 = unit.create(f, r, 1) @@ -64,8 +64,8 @@ function test_give_divisor() eressea.settings.set("rules.items.give_divisor", 2) eressea.settings.set("GiveRestriction", 0) local r = region.create(1, 1, "plain") - local f1 = faction.create("human", "test@example.com", "de") - local f2 = faction.create("human", "test@example.com", "de") + local f1 = faction.create("human") + local f2 = faction.create("human") local u1 = unit.create(f1, r, 1) local u2 = unit.create(f2, r, 1) u2:add_order("KONTAKTIERE " .. itoa36(u1.id)) diff --git a/scripts/tools/build-e3.lua b/scripts/tools/build-e3.lua index b33927877..5955604e8 100644 --- a/scripts/tools/build-e3.lua +++ b/scripts/tools/build-e3.lua @@ -146,6 +146,7 @@ function seed() b.name = "Home" end b.size = 10 + u.hp = u.hp * 10 u.building = b end end diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index aa6afecfc..92d5fcd41 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -300,6 +300,7 @@ set(TESTS_SRC spells/flyingship.test.c spells/regioncurse.test.c triggers/shock.test.c + races/races.test.c ${ATTRIBUTES_TESTS} ${UTIL_TESTS} ${KERNEL_TESTS} diff --git a/src/battle.c b/src/battle.c index 43cf12f71..0eed565cf 100644 --- a/src/battle.c +++ b/src/battle.c @@ -11,6 +11,13 @@ #include "study.h" #include "spy.h" +#include "reports.h" + +/* attributes includes */ +#include "attributes/key.h" +#include "attributes/racename.h" +#include "attributes/otherfaction.h" + #include "spells/buildingcurse.h" #include "spells/regioncurse.h" #include "spells/unitcurse.h" @@ -19,6 +26,7 @@ #include "kernel/alliance.h" #include "kernel/build.h" #include "kernel/building.h" +#include "kernel/callbacks.h" #include "kernel/config.h" #include "kernel/curse.h" #include "kernel/equipment.h" @@ -36,13 +44,6 @@ #include "kernel/unit.h" #include "kernel/spell.h" -#include "reports.h" - -/* attributes includes */ -#include "attributes/key.h" -#include "attributes/racename.h" -#include "attributes/otherfaction.h" - /* util includes */ #include "kernel/attrib.h" #include "util/base36.h" @@ -68,6 +69,8 @@ #include #include +#include + #define TACTICS_BONUS 1 /* when undefined, we have a tactics round. else this is the bonus tactics give */ #define TACTICS_MODIFIER 1 /* modifier for generals in the front/rear */ @@ -510,40 +513,48 @@ static bool is_riding(const troop t) return false; } -static weapon *preferred_weapon(const troop t, bool attacking) +static const weapon *preferred_weapon(const troop t, bool attacking) { - weapon *missile = t.fighter->person[t.index].missile; - weapon *melee = t.fighter->person[t.index].melee; - if (attacking) { - if (melee == NULL || (missile && missile->attackskill > melee->attackskill)) { - return missile; - } + const weapon *missile = t.fighter->person[t.index].missile; + const weapon *melee = t.fighter->person[t.index].melee; + if (!melee) { + return missile; } - else { - if (melee == NULL || (missile - && missile->defenseskill > melee->defenseskill)) { - return missile; + if (missile) { + if (attacking) { + if (missile && missile->attackskill > melee->attackskill) { + return missile; + } + } + else { + if (missile && missile->defenseskill > melee->defenseskill) { + return missile; + } } } return melee; } -weapon *select_weapon(const troop t, bool attacking, bool ismissile) +const struct weapon *select_weapon(const troop t, bool attacking, bool ismissile) /* select the primary weapon for this trooper */ { + const weapon *w = NULL; if (attacking) { if (ismissile) { /* from the back rows, have to use your missile weapon */ - return t.fighter->person[t.index].missile; + w = t.fighter->person[t.index].missile; } } else { if (!ismissile) { /* have to use your melee weapon if it's melee */ - return t.fighter->person[t.index].melee; + w = t.fighter->person[t.index].melee; } } - return preferred_weapon(t, attacking); + if (!w) { + w = preferred_weapon(t, attacking); + } + return w; } static bool i_canuse(const unit * u, const item_type * itype) @@ -655,9 +666,9 @@ weapon_effskill(troop t, troop enemy, const weapon * w, fighter *tf = t.fighter; unit *tu = t.fighter->unit; /* Alle Modifier berechnen, die fig durch die Waffen bekommt. */ - if (w) { + if (w && w->item.type) { int skill = 0; - const weapon_type *wtype = w->type; + const weapon_type *wtype = resource2weapon(w->item.type->rtype); if (attacking) { skill = w->attackskill; @@ -817,7 +828,7 @@ bool meffect_blocked(battle * b, meffect * s, side * as) /* rmfighter wird schon im PRAECOMBAT gebraucht, da gibt es noch keine * troops */ -void rmfighter(fighter * df, int i) +void reduce_fighter(fighter * df, int i) { side *ds = df->side; @@ -843,12 +854,22 @@ void rmfighter(fighter * df, int i) df->alive -= i; } +void flee_all(fighter *fig) +{ + unit *u = fig->unit; + fig->run.hp = u->hp; + fig->run.number = u->number; + fig->side->flee += u->number; + setguard(u, false); + reduce_fighter(fig, u->number); +} + static void rmtroop(troop dt) { fighter *df = dt.fighter; /* troop ist immer eine einzelne Person */ - rmfighter(df, 1); + reduce_fighter(df, 1); assert(dt.index >= 0 && dt.index < df->unit->number); if (dt.index != df->alive - df->removed) { @@ -1052,7 +1073,7 @@ int calculate_armor(troop dt, const weapon_type *dwtype, const weapon_type *awty int apply_resistance(int damage, troop dt, const weapon_type *dwtype, const armor_type *armor, const armor_type *shield, bool magic) { const fighter *df = dt.fighter; unit *du = df->unit; - + if (!magic) return damage; @@ -1140,7 +1161,7 @@ static void destroy_items(troop dt) { for (pitm = &du->items; *pitm;) { item *itm = *pitm; const item_type *itype = itm->type; - if (!(itype->flags & (ITF_CURSED | ITF_NOTLOST)) && dt.index < itm->number) { + if (!(itype->flags & ITF_NOTLOST) && dt.index < itm->number) { /* 25% Grundchance, dass ein Item kaputtgeht. */ if (rng_int() % 4 < 1) { i_change(pitm, itype, -1); @@ -1159,7 +1180,7 @@ static void calculate_defense_type(troop at, troop dt, int type, bool missile, weapon = select_weapon(dt, false, true); /* missile=true to get the unmodified best weapon she has */ *defskill = weapon_effskill(dt, at, weapon, false, false); if (weapon != NULL) - *dwtype = weapon->type; + *dwtype = WEAPON_TYPE(weapon); } static void calculate_attack_type(troop at, troop dt, int type, bool missile, @@ -1171,7 +1192,7 @@ static void calculate_attack_type(troop at, troop dt, int type, bool missile, weapon = select_weapon(at, true, missile); *attskill = weapon_effskill(at, dt, weapon, true, missile); if (weapon) - *awtype = weapon->type; + *awtype = WEAPON_TYPE(weapon); if (*awtype && fval(*awtype, WTF_MAGICAL)) *magic = true; break; @@ -1441,24 +1462,18 @@ troop select_enemy(fighter * af, int minrow, int maxrow, int select) for (si = 0; as->enemies[si]; ++si) { side *ds = as->enemies[si]; fighter *df; - int unitrow[NUMROWS]; + int unitrow[NUMROWS] = { 0 }; int offset = 0; if (select & SELECT_DISTANCE) offset = get_unitrow(af, ds) - FIGHT_ROW; - if (select & SELECT_ADVANCE) { - int ui; - for (ui = 0; ui != NUMROWS; ++ui) - unitrow[ui] = -1; - } - for (df = ds->fighters; df; df = df->next) { int dr; dr = statusrow(df->status); if (select & SELECT_ADVANCE) { - if (unitrow[dr] < 0) { + if (unitrow[dr] == 0) { unitrow[dr] = get_unitrow(df, as); } dr = unitrow[dr]; @@ -1612,9 +1627,7 @@ static bool select_row(const side *vs, const fighter *fig, void *cbdata) selist *fighters(battle * b, const side * vs, int minrow, int maxrow, int mask) { - struct selector sel; - sel.maxrow = maxrow; - sel.minrow = minrow; + struct selector sel = { .maxrow = maxrow, .minrow = minrow }; return select_fighters(b, vs, mask, select_row, &sel); } @@ -1853,9 +1866,10 @@ int skilldiff(troop at, troop dt, int dist) fighter *af = at.fighter, *df = dt.fighter; unit *au = af->unit, *du = df->unit; int is_protected = 0, skdiff = 0; - weapon *awp = select_weapon(at, true, dist > 1); + const weapon *awp = select_weapon(at, true, dist > 1); static int rc_cache; static const race *rc_halfling, *rc_goblin; + const weapon_type *wtype; if (rc_changed(&rc_cache)) { rc_halfling = get_race(RC_HALFLING); @@ -1903,21 +1917,22 @@ int skilldiff(troop at, troop dt, int dist) } /* Effekte der Waffen */ skdiff += weapon_effskill(at, dt, awp, true, dist > 1); - if (awp && fval(awp->type, WTF_MISSILE)) { + wtype = awp ? WEAPON_TYPE(awp) : NULL; + if (wtype && fval(wtype, WTF_MISSILE)) { skdiff -= is_protected; - if (awp->type->modifiers) { + if (wtype->modifiers) { int w; - for (w = 0; awp->type->modifiers[w].value != 0; ++w) { - if (awp->type->modifiers[w].flags & WMF_MISSILE_TARGET) { + for (w = 0; wtype->modifiers[w].value != 0; ++w) { + if (wtype->modifiers[w].flags & WMF_MISSILE_TARGET) { /* skill decreases by targeting difficulty (bow -2, catapult -4) */ - skdiff -= awp->type->modifiers[w].value; + skdiff -= wtype->modifiers[w].value; break; } } } } if (skill_formula == FORMULA_ORIG) { - weapon *dwp = select_weapon(dt, false, dist > 1); + const weapon *dwp = select_weapon(dt, false, dist > 1); skdiff -= weapon_effskill(dt, at, dwp, false, dist > 1); } return skdiff; @@ -1926,10 +1941,13 @@ int skilldiff(troop at, troop dt, int dist) static int setreload(troop at) { fighter *af = at.fighter; - const weapon_type *wtype = af->person[at.index].missile->type; - if (wtype->reload == 0) - return 0; - return af->person[at.index].reload = wtype->reload; + const weapon_type *wtype = WEAPON_TYPE(af->person[at.index].missile); + if (wtype) { + if (wtype->reload != 0) { + return af->person[at.index].reload = wtype->reload; + } + } + return 0; } int getreload(troop at) @@ -1937,20 +1955,20 @@ int getreload(troop at) return at.fighter->person[at.index].reload; } -bool hits(troop at, troop dt, weapon * awp) +bool hits(troop at, troop dt, const weapon_type *awp) { fighter *af = at.fighter, *df = dt.fighter; const armor_type *armor, *shield = NULL; int skdiff = 0; const int dist = get_unitrow(af, df->side) + get_unitrow(df, af->side) - 1; const bool missiles_only = dist > 1; - weapon *dwp = select_weapon(dt, false, missiles_only); + const weapon_type *dwp; if (!df->alive) return false; if (getreload(at)) return false; - if (missiles_only && (awp == NULL || !fval(awp->type, WTF_MISSILE))) + if (missiles_only && (awp == NULL || !fval(awp, WTF_MISSILE))) return false; /* mark this person as hit. */ @@ -1965,17 +1983,18 @@ bool hits(troop at, troop dt, weapon * awp) return false; /* effect of sp_reeling_arrows combatspell */ - if (af->side->battle->reelarrow && awp && fval(awp->type, WTF_MISSILE) + if (af->side->battle->reelarrow && awp && fval(awp, WTF_MISSILE) && rng_double() < 0.5) { return false; } - skdiff = skilldiff(at, dt, dist); /* Verteidiger bekommt eine Ruestung */ - armor = select_armor(dt, true); - if (dwp == NULL || (dwp->type->flags & WTF_USESHIELD)) { + dwp = WEAPON_TYPE(select_weapon(dt, false, missiles_only)); + if (dwp == NULL || (dwp->flags & WTF_USESHIELD)) { shield = select_armor(dt, false); } + armor = select_armor(dt, true); + skdiff = skilldiff(at, dt, dist); if (!contest(skdiff, dt, armor, shield)) { return false; } @@ -2104,15 +2123,20 @@ static void attack(battle * b, troop ta, const att * a, int numattack) ta.fighter->person[ta.index].reload--; } else { - weapon* wp = ta.fighter->person[ta.index].missile; + 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], SELECT_ADVANCE | SELECT_DISTANCE | SELECT_FIND) > 0) { wp = preferred_weapon(ta, true); } - if (wp && fval(wp->type, WTF_MISSILE)) - missile = true; + if (wp) { + wtype = WEAPON_TYPE(wp); + if (fval(wtype, WTF_MISSILE)) { + missile = true; + } + } if (missile) { td = select_opponent(b, ta, missile_range[0], missile_range[1]); } @@ -2124,29 +2148,30 @@ static void attack(battle * b, troop ta, const att * a, int numattack) if (ta.fighter->person[ta.index].last_action < b->turn) { ta.fighter->person[ta.index].last_action = b->turn; } - if (hits(ta, td, wp)) { + if (hits(ta, td, wtype)) { const char* d; - if (wp == NULL) + if (wtype == NULL) d = u_race(au)->def_damage; else if (is_riding(ta)) - d = wp->type->damage[1]; + d = wtype->damage[1]; else - d = wp->type->damage[0]; + d = wtype->damage[0]; terminate(td, ta, a->type, d, missile); } /* spezialattacken der waffe nur, wenn erste attacke in der runde. * sonst helden mit feuerschwertern zu maechtig */ - if (numattack == 0 && wp && wp->type->attack) { + if (numattack == 0 && wtype && wtype->attack) { int dead = 0; - if (wp->type->attack(&ta, wp->type, &dead)) { - af->catmsg += dead; + if (wtype->attack(&ta, wtype, &dead)) { + ++af->special.attacks; + af->special.kills += dead; if (af->person[ta.index].last_action < b->turn) { af->person[ta.index].last_action = b->turn; } } } - if (wp && wp->type->reload && !getreload(ta)) { + if (wtype && wtype->reload && !getreload(ta)) { setreload(ta); } } @@ -2221,12 +2246,10 @@ static void attack(battle * b, troop ta, const att * a, int numattack) static void do_attack(fighter * af) { - troop ta; unit *au = af->unit; side *side = af->side; battle *b = side->battle; - - ta.fighter = af; + troop ta = { .fighter = af, .index = af->fighting }; assert(au && au->number); /* Da das Zuschlagen auf Einheiten und nicht auf den einzelnen @@ -2234,7 +2257,6 @@ static void do_attack(fighter * af) * Rolle spielen, Das tut sie nur dann, wenn jeder, der am Anfang der * Runde lebte, auch zuschlagen darf. Ansonsten ist der, der zufaellig * mit einer grossen Einheit zuerst drankommt, extrem bevorteilt. */ - ta.index = af->fighting; while (ta.index--) { /* Wir suchen eine beliebige Feind-Einheit aus. An der koennen @@ -2253,8 +2275,9 @@ static void do_attack(fighter * af) if (u_race(au)->attack[a].type != AT_STANDARD) continue; else { - weapon *wp = preferred_weapon(ta, true); - if (wp != NULL && wp->type->reload) + const weapon *wp = preferred_weapon(ta, true); + const weapon_type *wtype = WEAPON_TYPE(wp); + if (wp != NULL && wtype->reload) continue; } } @@ -2264,12 +2287,30 @@ static void do_attack(fighter * af) } /* Der letzte Katapultschuetze setzt die * Ladezeit neu und generiert die Meldung. */ - if (af->catmsg >= 0) { - struct message *m = - msg_message("killed_battle", "unit dead", au, af->catmsg); + if (af->special.attacks > 0) { + struct message *m; + if (callbacks.report_special_attacks) { + const weapon_type *wtype = NULL; + for (ta.index = 0; ta.index != af->fighting; ++ta.index) { + const weapon *wp = preferred_weapon(ta, true); + wtype = WEAPON_TYPE(wp); + if (wtype && wtype->attack) { + break; + } + else { + wtype = NULL; + } + + } + if (wtype && wtype->attack) { + callbacks.report_special_attacks(af, wtype->itype); + } + } + m = msg_message("killed_battle", "unit dead", au, af->special.kills); message_all(b, m); msg_release(m); - af->catmsg = -1; + af->special.kills = 0; + af->special.attacks = 0; } } @@ -2400,10 +2441,7 @@ troop select_ally(fighter * af, int minrow, int maxrow, int allytype) int dr = get_unitrow(df, NULL); if (dr >= minrow && dr <= maxrow) { if (df->alive - df->removed > allies) { - troop dt; - assert(allies >= 0); - dt.index = allies; - dt.fighter = df; + troop dt = { .fighter = df, .index = allies }; return dt; } allies -= (df->alive - df->removed); @@ -2419,6 +2457,9 @@ static int loot_quota(const unit * src, const unit * dst, const item_type * type, int n) { UNUSED_ARG(type); + if (loot_divisor <= 0) { + return 0; + } if (dst && src && src->faction != dst->faction) { assert(loot_divisor <= 0 || loot_divisor >= 1); if (loot_divisor > 1) { @@ -2461,7 +2502,7 @@ void loot_items(fighter * corpse) int looting = 0; int maxrow = 0; /* mustloot: we absolutely, positively must have somebody loot this thing */ - bool mustloot = 0 != (itm->type->flags & (ITF_CURSED | ITF_NOTLOST)); + bool mustloot = (loot_divisor == 1) || (0 != (itm->type->flags & (ITF_CURSED|ITF_NOTLOST))); item_add(itm, -loot); maxloot -= loot; @@ -2551,7 +2592,7 @@ static void reorder_fleeing(region * r) unit **usrc = &r->units; unit **udst = &r->units; unit *ufirst = NULL; - unit *u; + unit *u = NULL; for (; *udst; udst = &u->next) { u = *udst; @@ -2902,7 +2943,8 @@ static void print_stats(battle * b) bfaction *bf; for (bf = b->factions; bf; bf = bf->next) { - char buf[1024], *bufp = buf; + static char buf[1024]; + char *bufp = buf; size_t rsize, size = sizeof(buf); int komma = 0; faction *f = bf->faction; @@ -2931,6 +2973,7 @@ static void print_stats(battle * b) fbattlerecord(b, f, buf); bufp = buf; + buf[0] = 0; size = sizeof(buf); komma = 0; header = LOC(f->locale, "battle_helpers"); @@ -3011,14 +3054,6 @@ static void print_stats(battle * b) } } -static int weapon_weight(const weapon * w, bool missile) -{ - if (missile == !!(fval(w->type, WTF_MISSILE))) { - return w->attackskill + w->defenseskill; - } - return 0; -} - side * get_side(battle * b, const struct unit * u) { side * s; @@ -3074,94 +3109,101 @@ static int tactics_bonus(int num) { return bonus; } +static int cmp_weapon(const void *lhs, const void *rhs) +{ + const weapon *a = (const weapon *)lhs; + const weapon *b = (const weapon *)rhs; + int diff = b->attackskill - a->attackskill + b->defenseskill - a->defenseskill; + if (diff == 0) { + if (a->item.ref->type->rtype->wtype->attack) return 1; + if (b->item.ref->type->rtype->wtype->attack) return -1; + } + return diff; +} + /* Fuer alle Waffengattungen wird bestimmt, wie viele der Personen mit * ihr kaempfen koennten, und was ihr Wert darin ist. */ static void equip_weapons(fighter* fig) { -#define WMAX 20 item* itm; unit* u = fig->unit; - weapon weapons[WMAX]; - int owp[WMAX]; - int dwp[WMAX]; - int wcount[WMAX]; - int wused[WMAX]; - int i, oi = 0, di = 0, w = 0; - - for (itm = u->items; itm && w != WMAX; itm = itm->next) { - const weapon_type* wtype = resource2weapon(itm->type->rtype); - if (wtype == NULL || itm->number == 0) + int wpless = weapon_skill(NULL, u, true); + size_t w, i; + int p_melee = 0, p_missile = 0; + + fig->weapons = NULL; + for (itm = u->items; itm; itm = itm->next) { + weapon * wp; + const weapon_type *wtype = resource2weapon(itm->type->rtype); + if (wtype == NULL || itm->number == 0) { + continue; + } + wp = arraddnptr(fig->weapons, 1); + wp->attackskill = weapon_skill(wtype, u, true); + wp->defenseskill = weapon_skill(wtype, u, false); + wp->item.ref = itm; + } + w = arrlen(fig->weapons); + qsort(fig->weapons, w, sizeof(weapon), cmp_weapon); + + /* now fig->weapons[0].item is the unit's best weapon */ + + /* hand out weapons: */ + for (i = 0; i != w; ++i) { + weapon *wp = fig->weapons + i; + const item *itm = wp->item.ref; + const weapon_type *wtype = resource2weapon(item2resource(itm->type)); + bool is_missile; + assert(wtype); + wp->item.type = itm->type; + is_missile = (wtype->flags & WTF_MISSILE); + if (!is_missile && wpless > wp->attackskill + wp->defenseskill) { + /* we fight better with bare hands than this melee weapon */ continue; - weapons[w].attackskill = weapon_skill(wtype, u, true); - weapons[w].defenseskill = weapon_skill(wtype, u, false); - if (weapons[w].attackskill >= 0 || weapons[w].defenseskill >= 0) { - weapons[w].type = wtype; - wused[w] = 0; - wcount[w] = itm->number; - ++w; - } - assert(w != WMAX); - } - fig->weapons = malloc((1 + (size_t)w) * sizeof(weapon)); - if (fig->weapons) { - memcpy(fig->weapons, weapons, w * sizeof(weapon)); - fig->weapons[w].type = NULL; - for (i = 0; i != w; ++i) { - int j, o = 0, d = 0; - for (j = 0; j != i; ++j) { - if (weapon_weight(fig->weapons + j, - true) >= weapon_weight(fig->weapons + i, true)) - ++d; - if (weapon_weight(fig->weapons + j, - false) >= weapon_weight(fig->weapons + i, false)) - ++o; - } - for (j = i + 1; j != w; ++j) { - if (weapon_weight(fig->weapons + j, - true) > weapon_weight(fig->weapons + i, true)) - ++d; - if (weapon_weight(fig->weapons + j, - false) > weapon_weight(fig->weapons + i, false)) - ++o; - } - owp[o] = i; - dwp[d] = i; - } - } - /* jetzt enthalten owp und dwp eine absteigend schlechter werdende Liste der Waffen - * oi and di are the current index to the sorted owp/dwp arrays - * owp, dwp contain indices to the figther::weapons array */ - - /* hand out melee weapons: */ - for (i = 0; i != fig->alive; ++i) { - int wpless = weapon_skill(NULL, u, true); - while (oi != w - && (wused[owp[oi]] == wcount[owp[oi]] - || fval(fig->weapons[owp[oi]].type, WTF_MISSILE))) { - ++oi; - } - if (oi == w) - break; /* no more weapons available */ - if (weapon_weight(fig->weapons + owp[oi], false) <= wpless) { - continue; /* we fight better with bare hands */ - } - fig->person[i].melee = &fig->weapons[owp[oi]]; - ++wused[owp[oi]]; - } - /* hand out missile weapons (from back to front, in case of mixed troops). */ - for (di = 0, i = fig->alive; i-- != 0;) { - while (di != w && (wused[dwp[di]] == wcount[dwp[di]] - || !fval(fig->weapons[dwp[di]].type, WTF_MISSILE))) { - ++di; - } - if (di == w) - break; /* no more weapons available */ - if (weapon_weight(fig->weapons + dwp[di], true) > 0) { - fig->person[i].missile = &fig->weapons[dwp[di]]; - ++wused[dwp[di]]; + } + if (is_missile) { + if (p_missile == fig->alive) { + /* everyone already has a missile weapon */ + continue; + } + } + else { + if (p_melee == fig->alive) { + /* everyone already has a melee weapon */ + continue; + } + } + if (wp->attackskill >= 0 || wp->defenseskill >= 0) + { + int count = itm->number; + while (count > 0 && (p_missile < fig->alive || p_melee < fig->alive)) { + if (is_missile) { + if (p_missile < fig->alive) { + struct person *p = fig->person + fig->alive - ++p_missile; + p->missile = wp; + --count; + } + else { + /* everyone already has a missile weapon */ + break; + } + } + else { + if (p_melee < fig->alive) { + struct person *p = fig->person + p_melee++; + p->melee = wp; + --count; + } + else { + /* everyone already has a melee weapon */ + break; + } + } + } } } } + static void equip_armor(fighter* fig) { item* itm; @@ -3246,7 +3288,8 @@ fighter *make_fighter(battle * b, unit * u, side * s1, bool attack) fig->side = s1; fig->alive = u->number; fig->side->alive += u->number; - fig->catmsg = -1; + fig->special.kills = 0; + fig->special.attacks = 0; /* Freigeben nicht vergessen! */ assert(fig->alive > 0); @@ -3451,7 +3494,7 @@ static void free_fighter(fighter * fig) } i_freeall(&fig->loot); free(fig->person); - free(fig->weapons); + arrfree(fig->weapons); } @@ -3822,11 +3865,7 @@ static bool start_battle(region * r, battle ** bp) int effect = get_effect(u2, it_mistletoe); if (effect >= u->number) { change_effect(u2, it_mistletoe, -u2->number); - c2->run.hp = u2->hp; - c2->run.number = u2->number; - c2->side->flee += u2->number; - setguard(u2, false); - rmfighter(c2, u2->number); + flee_all(c2); } } } @@ -3910,7 +3949,7 @@ static void battle_flee(battle * b) fighter *fig; for (fig = s->fighters; fig; fig = fig->next) { unit *u = fig->unit; - troop dt; + troop dt = { .fighter = fig, .index = fig->alive - fig->removed }; /* Flucht nicht bei mehr als 600 HP. Damit Wyrme toetbar bleiben. */ int runhp = (int)(0.9 + unit_max_hp(u) * hpflee(u->status)); if (runhp > 600) runhp = 600; @@ -3920,8 +3959,6 @@ static void battle_flee(battle * b) continue; } - dt.fighter = fig; - dt.index = fig->alive - fig->removed; while (s->size[SUM_ROW] && dt.index != 0) { --dt.index; assert(dt.index >= 0 && dt.index < fig->unit->number); diff --git a/src/battle.h b/src/battle.h index 6bfb3c2b7..dbacc3605 100644 --- a/src/battle.h +++ b/src/battle.h @@ -6,6 +6,7 @@ struct message; struct selist; +struct weapon_type; union variant; /** more defines **/ @@ -95,11 +96,16 @@ typedef struct battle { } battle; typedef struct weapon { - const struct weapon_type* type; + union { + struct item *ref; + const struct item_type *type; + } item; int attackskill; int defenseskill; } weapon; +#define WEAPON_TYPE(wp) ((wp && (wp)->item.type) ? (wp)->item.type->rtype->wtype : NULL) + typedef struct troop { struct fighter* fighter; int index; @@ -114,6 +120,7 @@ typedef struct armor { /*** fighter::flags ***/ #define FIG_ATTACKER 1<<0 #define FIG_NOLOOT 1<<1 + typedef struct fighter { struct fighter* next; struct side* side; @@ -132,7 +139,10 @@ typedef struct fighter { int horses; /* Anzahl brauchbarer Pferde der Einheit */ int elvenhorses; /* Anzahl brauchbarer Elfenpferde der Einheit */ struct item* loot; - int catmsg; /* Merkt sich, ob Katapultmessage schon generiert. */ + struct { + int attacks; + int kills; + } special; struct person { int hp; /* Trefferpunkte der Personen */ int flags; @@ -142,8 +152,8 @@ typedef struct fighter { unsigned char speed; unsigned char reload; unsigned char last_action; - struct weapon* missile; /* missile weapon */ - struct weapon* melee; /* melee weapon */ + const struct weapon* missile; /* missile weapon */ + const struct weapon* melee; /* melee weapon */ } *person; unsigned int flags; struct { @@ -195,13 +205,13 @@ int count_enemies(struct battle* b, 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); -struct weapon* select_weapon(const struct troop t, bool attacking, bool ismissile); +const struct weapon* select_weapon(const struct troop t, bool attacking, bool ismissile); int calculate_armor(troop dt, const struct weapon_type* dwtype, const struct weapon_type* awtype, const struct armor_type* armor, const struct armor_type* shield, bool magic); int apply_resistance(int damage, struct troop dt, const struct weapon_type* dwtype, const struct armor_type* armor, const struct armor_type* shield, bool magic); bool terminate(troop dt, troop at, int type, const char* damage, bool missile); void message_all(battle* b, struct message* m); -bool hits(troop at, troop dt, weapon* awp); +bool hits(troop at, troop dt, const struct weapon_type *awp); void damage_building(struct battle* b, struct building* bldg, int damage_abs); @@ -209,11 +219,12 @@ typedef bool(*select_fun)(const struct side* vs, const struct fighter* fig, void struct selist* select_fighters(struct battle* b, const struct side* vs, int mask, select_fun cb, void* cbdata); struct selist* fighters(struct battle* b, const struct side* vs, int minrow, int maxrow, int mask); +void flee_all(struct fighter *fig); int count_allies(const struct side* as, int minrow, int maxrow, int select, int allytype); bool helping(const struct side* as, const struct side* ds); -void rmfighter(fighter* df, int i); +void reduce_fighter(fighter* df, int i); struct fighter* select_corpse(struct battle* b, struct fighter* af); int statusrow(int status); void drain_exp(struct unit* u, int d); diff --git a/src/battle.test.c b/src/battle.test.c index 0e398e4b5..d949ec728 100644 --- a/src/battle.test.c +++ b/src/battle.test.c @@ -34,6 +34,8 @@ #include +#include + #include #include @@ -98,9 +100,42 @@ static void test_make_fighter(CuTest * tc) test_teardown(); } +static void test_select_weapon(CuTest *tc) { + item_type *it_missile, *it_axe, *it_sword; + unit *au; + fighter *af; + battle *b; + + test_setup(); + au = test_create_unit(test_create_faction(), test_create_plain(0, 0)); + set_number(au, 3); + set_level(au, SK_MELEE, 1); + it_axe = test_create_itemtype("axe"); + new_weapontype(it_axe, 0, frac_zero, NULL, 1, 0, 0, SK_MELEE); + i_change(&au->items, it_axe, 1); + it_sword = test_create_itemtype("sword"); + new_weapontype(it_sword, 0, frac_zero, NULL, 0, 0, 0, SK_MELEE); + i_change(&au->items, it_sword, 1); + it_missile = test_create_itemtype("crossbow"); + new_weapontype(it_missile, WTF_MISSILE, frac_zero, NULL, 0, 0, 0, SK_CROSSBOW); + i_change(&au->items, it_missile, 2); + + b = make_battle(au->region); + af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); + CuAssertIntEquals(tc, 3, (int)arrlen(af->weapons)); + CuAssertPtrEquals(tc, it_axe, (item_type *)af->person[0].melee->item.type); + CuAssertPtrEquals(tc, NULL, (weapon *)af->person[0].missile); + CuAssertPtrEquals(tc, it_sword, (item_type *)af->person[1].melee->item.type); + CuAssertPtrEquals(tc, it_missile, (item_type *)af->person[1].missile->item.type); + CuAssertPtrEquals(tc, NULL, (weapon *)af->person[2].melee); + CuAssertPtrEquals(tc, it_missile, (item_type *)af->person[2].missile->item.type); + free_battle(b); + + test_teardown(); +} + static void test_select_weapon_restricted(CuTest *tc) { item_type *itype; - weapon_type * wtype; unit *au; fighter *af; battle *b; @@ -109,49 +144,48 @@ static void test_select_weapon_restricted(CuTest *tc) { test_setup(); au = test_create_unit(test_create_faction(), test_create_plain(0, 0)); itype = test_create_itemtype("halberd"); - wtype = new_weapontype(itype, 0, frac_zero, NULL, 0, 0, 0, SK_MELEE); + new_weapontype(itype, 0, frac_zero, NULL, 0, 0, 0, SK_MELEE); i_change(&au->items, itype, 1); rc = test_create_race("smurf"); CuAssertIntEquals(tc, 0, rc->mask_item & au->_race->mask_item); + /* melee weapon, can be used by any race: */ b = make_battle(au->region); af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); - CuAssertPtrNotNull(tc, af->weapons); - CuAssertPtrEquals(tc, wtype, (void *)af->weapons[0].type); - CuAssertPtrEquals(tc, NULL, (void *)af->weapons[1].type); + CuAssertIntEquals(tc, 1, (int)arrlen(af->weapons)); + CuAssertPtrEquals(tc, (item_type *)au->items->type, (item_type *)af->weapons[0].item.type); + CuAssertPtrEquals(tc, af->weapons, (void *)af->person[0].melee); free_battle(b); + /* weapon is for denied to our race: */ itype->mask_deny = rc_mask(au->_race); b = make_battle(au->region); af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); - CuAssertPtrNotNull(tc, af->weapons); - CuAssertPtrEquals(tc, NULL, (void *)af->weapons[0].type); + CuAssertIntEquals(tc, 1, (int)arrlen(af->weapons)); + CuAssertPtrEquals(tc, (item_type *)au->items->type, (item_type *)af->weapons[0].item.type); + CuAssertPtrNotNull(tc, af->person); + CuAssertPtrEquals(tc, NULL, (void *)af->person[0].melee); free_battle(b); + /* weapon is for exclusive use by our race: */ itype->mask_deny = 0; itype->mask_allow = rc_mask(au->_race); b = make_battle(au->region); af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); CuAssertPtrNotNull(tc, af->weapons); - CuAssertPtrEquals(tc, wtype, (void *)af->weapons[0].type); - CuAssertPtrEquals(tc, NULL, (void *)af->weapons[1].type); + CuAssertIntEquals(tc, 1, (int)arrlen(af->weapons)); + CuAssertPtrEquals(tc, (item_type *)au->items->type, (item_type *)af->weapons[0].item.type); + CuAssertPtrEquals(tc, af->weapons, (void *)af->person[0].melee); free_battle(b); + /* weapon is for exclusive use by another race: */ itype->mask_deny = 0; itype->mask_allow = rc_mask(rc); b = make_battle(au->region); af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); - CuAssertPtrNotNull(tc, af->weapons); - CuAssertPtrEquals(tc, NULL, (void *)af->weapons[0].type); - free_battle(b); - - itype->mask_deny = 0; - itype->mask_allow = rc_mask(au->_race); - b = make_battle(au->region); - af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); - CuAssertPtrNotNull(tc, af->weapons); - CuAssertPtrEquals(tc, wtype, (void *)af->weapons[0].type); - CuAssertPtrEquals(tc, NULL, (void *)af->weapons[1].type); + CuAssertIntEquals(tc, 1, (int)arrlen(af->weapons)); + CuAssertPtrEquals(tc, (item_type *)au->items->type, (void *)af->weapons[0].item.type); + CuAssertPtrEquals(tc, NULL, (void *)af->person[0].melee); free_battle(b); test_teardown(); @@ -368,10 +402,12 @@ static int test_armor(troop dt, weapon_type *awtype, bool magic) { return calculate_armor(dt, 0, awtype, select_armor(dt, false), select_armor(dt, true), magic); } -static int test_resistance(troop dt) { - return apply_resistance(1000, dt, - select_weapon(dt, false, true) ? select_weapon(dt, false, true)->type : 0, - select_armor(dt, false), select_armor(dt, true), true); +static int test_resistance(troop dt, bool magic) { + const weapon *dw = select_weapon(dt, false, true); + return apply_resistance(1000, dt, + dw ? WEAPON_TYPE(dw) : NULL, + select_armor(dt, false), + select_armor(dt, true), magic); } static void test_calculate_armor(CuTest * tc) @@ -401,7 +437,7 @@ static void test_calculate_armor(CuTest * tc) dt.fighter = setup_fighter(&b, du); CuAssertIntEquals_Msg(tc, "default ac", 0, test_armor(dt, 0, false)); - CuAssertIntEquals_Msg(tc, "magres unmodified", 1000, test_resistance(dt)); + CuAssertIntEquals_Msg(tc, "magres unmodified", 1000, test_resistance(dt, true)); free_battle(b); b = NULL; @@ -437,13 +473,13 @@ static void test_calculate_armor(CuTest * tc) CuAssertIntEquals_Msg(tc, "magical attack", 3, test_armor(dt, wtype, true)); CuAssertIntEquals_Msg(tc, "magres unmodified", 1000, - test_resistance(dt)); + test_resistance(dt, true)); ashield->flags |= ATF_LAEN; achain->flags |= ATF_LAEN; CuAssertIntEquals_Msg(tc, "laen armor", 3, test_armor(dt, wtype, true)); - CuAssertIntEquals_Msg(tc, "laen magres bonus", 250, test_resistance(dt)); + CuAssertIntEquals_Msg(tc, "laen magres bonus", 250, test_resistance(dt, true)); free_battle(b); test_teardown(); } @@ -497,17 +533,15 @@ static void test_magic_resistance(CuTest *tc) i_change(&du->items, ishield, 1); dt.fighter = setup_fighter(&b, du); - CuAssertIntEquals_Msg(tc, "no magres reduction", 1000, test_resistance(dt)); + CuAssertIntEquals_Msg(tc, "no magres reduction", 1000, test_resistance(dt, true)); magres = magic_resistance(du); CuAssertIntEquals_Msg(tc, "no magres reduction", 0, magres.sa[0]); ashield->flags |= ATF_LAEN; ashield->magres = v10p; - CuAssertIntEquals_Msg(tc, "laen reduction => 10%%", 900, test_resistance(dt)); + CuAssertIntEquals_Msg(tc, "laen reduction => 10%%", 900, test_resistance(dt, true)); CuAssertIntEquals_Msg(tc, "no magic, no resistance", 1000, - apply_resistance(1000, dt, - select_weapon(dt, false, true) ? select_weapon(dt, false, true)->type : 0, - select_armor(dt, false), select_armor(dt, true), false)); + test_resistance(dt, false)); free_battle(b); b = NULL; @@ -517,7 +551,7 @@ static void test_magic_resistance(CuTest *tc) ashield->flags |= ATF_LAEN; ashield->magres = v10p; dt.fighter = setup_fighter(&b, du); - CuAssertIntEquals_Msg(tc, "2x laen reduction => 81%%", 810, test_resistance(dt)); + CuAssertIntEquals_Msg(tc, "2x laen reduction => 81%%", 810, test_resistance(dt, true)); free_battle(b); b = NULL; @@ -525,18 +559,18 @@ static void test_magic_resistance(CuTest *tc) i_change(&du->items, ichain, -1); set_level(du, SK_MAGIC, 2); dt.fighter = setup_fighter(&b, du); - CuAssertIntEquals_Msg(tc, "skill reduction => 90%%", 900, test_resistance(dt)); + CuAssertIntEquals_Msg(tc, "skill reduction => 90%%", 900, test_resistance(dt, true)); magres = magic_resistance(du); CuAssert(tc, "skill reduction", frac_equal(magres, v10p)); rc->magres = v50p; /* percentage, gets added to skill bonus */ - CuAssertIntEquals_Msg(tc, "race reduction => 40%%", 400, test_resistance(dt)); + CuAssertIntEquals_Msg(tc, "race reduction => 40%%", 400, test_resistance(dt, true)); magres = magic_resistance(du); CuAssert(tc, "race bonus => 60%%", frac_equal(magres, frac_make(60, 100))); rc->magres = frac_make(15, 10); /* 150% resistance should not cause negative damage multiplier */ magres = magic_resistance(du); CuAssert(tc, "magic resistance is never > 0.9", frac_equal(magres, frac_make(9, 10))); - CuAssertIntEquals_Msg(tc, "damage reduction is never < 0.1", 100, test_resistance(dt)); + CuAssertIntEquals_Msg(tc, "damage reduction is never < 0.1", 100, test_resistance(dt, true)); free_battle(b); test_teardown(); @@ -615,6 +649,44 @@ static void test_battle_skilldiff(CuTest *tc) } static void test_loot_items(CuTest* tc) +{ + troop ta, td; + region* r; + faction *f; + unit* ua, * ud; + battle* b = NULL; + const resource_type* rtype; + race* rc; + + test_setup(); + config_set_int("rules.items.loot_divisor", 1); // everything is looted 100% + test_create_horse(); + rc = test_create_race("ghost"); + rc->flags |= RCF_FLY; /* bug 2887 */ + + r = test_create_plain(0, 0); + ud = test_create_unit(f = test_create_faction(), r); + ud->status = ST_FLEE; /* bug 2887 */ + ua = test_create_unit(f, r); + u_setrace(ua, rc); + td.fighter = setup_fighter(&b, ud); + td.index = 0; + ta.fighter = setup_fighter(&b, ua); + ta.index = 0; + + ta.fighter->alive = 0; + + rtype = get_resourcetype(R_HORSE); + i_change(&ua->items, rtype->itype, 1); + loot_items(ta.fighter); + CuAssertIntEquals(tc, 1, i_get(td.fighter->loot, rtype->itype)); + CuAssertIntEquals(tc, 0, i_get(ua->items, rtype->itype)); + + free_battle(b); + test_teardown(); +} + +static void test_loot_notlost_items(CuTest* tc) { troop ta, td; region* r; @@ -624,6 +696,7 @@ static void test_loot_items(CuTest* tc) race* rc; test_setup(); + config_set_int("rules.items.loot_divisor", 0); // nothing is looted test_create_horse(); rc = test_create_race("ghost"); rc->flags |= RCF_FLY; /* bug 2887 */ @@ -657,6 +730,127 @@ static void test_loot_items(CuTest* tc) test_teardown(); } +static void test_loot_cursed_items_self(CuTest* tc) +{ + troop ta, td; + region* r; + faction *f; + unit* ua, * ud; + battle* b = NULL; + const resource_type* rtype; + race* rc; + + test_setup(); + config_set_int("rules.items.loot_divisor", 1); // everything is looted + test_create_horse(); + rc = test_create_race("ghost"); + rc->flags |= RCF_FLY; /* bug 2887 */ + + r = test_create_plain(0, 0); + ud = test_create_unit(f = test_create_faction(), r); + ud->status = ST_FLEE; /* bug 2887 */ + ua = test_create_unit(f, r); + u_setrace(ua, rc); + td.fighter = setup_fighter(&b, ud); + td.index = 0; + ta.fighter = setup_fighter(&b, ua); + ta.index = 0; + + ta.fighter->alive = 0; + + rtype = get_resourcetype(R_HORSE); + rtype->itype->flags |= ITF_CURSED; /* must be looted by own faction */ + i_change(&ua->items, rtype->itype, 1); + loot_items(ta.fighter); + CuAssertIntEquals(tc, 1, i_get(td.fighter->loot, rtype->itype)); + CuAssertIntEquals(tc, 0, i_get(ua->items, rtype->itype)); + + free_battle(b); + test_teardown(); +} + +static void test_loot_cursed_items_other(CuTest* tc) +{ + troop ta, td; + region* r; + unit* ua, * ud; + battle* b = NULL; + const resource_type* rtype; + race* rc; + + test_setup(); + config_set_int("rules.items.loot_divisor", 1); // everything is looted + test_create_horse(); + rc = test_create_race("ghost"); + rc->flags |= RCF_FLY; /* bug 2887 */ + + r = test_create_plain(0, 0); + ud = test_create_unit(test_create_faction(), r); + ud->status = ST_FLEE; /* bug 2887 */ + ua = test_create_unit(test_create_faction(), r); + u_setrace(ua, rc); + td.fighter = setup_fighter(&b, ud); + td.index = 0; + ta.fighter = setup_fighter(&b, ua); + ta.index = 0; + + ta.fighter->alive = 0; + + rtype = get_resourcetype(R_HORSE); + rtype->itype->flags |= ITF_CURSED; /* must not be looted */ + i_change(&ua->items, rtype->itype, 1); + loot_items(ta.fighter); + CuAssertIntEquals(tc, 0, i_get(td.fighter->loot, rtype->itype)); + CuAssertIntEquals(tc, 0, i_get(ua->items, rtype->itype)); + + free_battle(b); + test_teardown(); +} + +static void test_no_loot_from_fleeing(CuTest* tc) +{ + troop ta, td; + region* r; + unit* ua, * ud; + battle* b = NULL; + const resource_type* rtype; + race* rc; + + test_setup(); + test_create_horse(); + rc = test_create_race("ghost"); + rc->flags |= RCF_FLY; /* bug 2887 */ + + r = test_create_plain(0, 0); + ud = test_create_unit(test_create_faction(), r); + ud->status = ST_FLEE; /* bug 2887 */ + ua = test_create_unit(test_create_faction(), r); + u_setrace(ua, rc); + td.fighter = setup_fighter(&b, ud); + td.index = 0; + ta.fighter = setup_fighter(&b, ua); + ta.index = 0; + + ta.fighter->side->relations[td.fighter->side->index] |= E_ENEMY; + ta.fighter->side->enemies[0] = td.fighter->side; + ta.fighter->side->enemies[1] = NULL; + td.fighter->side->relations[ta.fighter->side->index] |= E_ENEMY; + td.fighter->side->enemies[0] = ta.fighter->side; + td.fighter->side->enemies[1] = NULL; + + flee_all(ta.fighter); + + rtype = get_resourcetype(R_HORSE); + rtype->itype->flags |= ITF_NOTLOST; /* must always be looted */ + i_change(&ua->items, rtype->itype, 1); + loot_items(ta.fighter); + CuAssertIntEquals(tc, 0, i_get(td.fighter->loot, rtype->itype)); + CuAssertIntEquals(tc, 1, i_get(ua->items, rtype->itype)); + + free_battle(b); + test_teardown(); +} + static void test_terminate(CuTest * tc) { troop at, dt; @@ -969,6 +1163,7 @@ CuSuite *get_battle_suite(void) { CuSuite *suite = CuSuiteNew(); SUITE_ADD_TEST(suite, test_make_fighter); + SUITE_ADD_TEST(suite, test_select_weapon); SUITE_ADD_TEST(suite, test_select_weapon_restricted); SUITE_ADD_TEST(suite, test_select_armor); SUITE_ADD_TEST(suite, test_battle_fleeing); @@ -990,6 +1185,10 @@ CuSuite *get_battle_suite(void) SUITE_ADD_TEST(suite, test_tactics_chance); SUITE_ADD_TEST(suite, test_terminate); SUITE_ADD_TEST(suite, test_loot_items); + SUITE_ADD_TEST(suite, test_loot_notlost_items); + SUITE_ADD_TEST(suite, test_loot_cursed_items_self); + SUITE_ADD_TEST(suite, test_loot_cursed_items_other); + SUITE_ADD_TEST(suite, test_no_loot_from_fleeing); DISABLE_TEST(suite, test_drain_exp); return suite; } diff --git a/src/defaults.test.c b/src/defaults.test.c index a69008133..8fac689ee 100644 --- a/src/defaults.test.c +++ b/src/defaults.test.c @@ -165,8 +165,8 @@ static void test_long_order_multi_long(CuTest* tc) { unit_addorder(u, create_order(K_MOVE, u->faction->locale, NULL)); unit_addorder(u, create_order(K_DESTROY, u->faction->locale, NULL)); update_long_orders(); - CuAssertPtrNotNull(tc, u->thisorder); CuAssertPtrNotNull(tc, u->orders); + CuAssertPtrNotNull(tc, u->thisorder); CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "error52")); test_teardown(); } @@ -180,8 +180,23 @@ static void test_long_order_multi_buy(CuTest* tc) { unit_addorder(u, create_order(K_BUY, u->faction->locale, 0)); unit_addorder(u, create_order(K_BUY, u->faction->locale, 0)); update_long_orders(); + CuAssertPtrNotNull(tc, u->orders); CuAssertPtrEquals(tc, NULL, u->thisorder); + CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "error52")); + test_teardown(); +} + +static void test_long_order_trade_and_other(CuTest *tc) { + + unit *u; + test_setup(); + mt_create_error(52); + u = test_create_unit(test_create_faction(), test_create_plain(0, 0)); + unit_addorder(u, create_order(K_WORK, u->faction->locale, 0)); + unit_addorder(u, create_order(K_BUY, u->faction->locale, 0)); + update_long_orders(); CuAssertPtrNotNull(tc, u->orders); + CuAssertIntEquals(tc, K_WORK, getkeyword(u->thisorder)); CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "error52")); test_teardown(); } @@ -362,6 +377,7 @@ CuSuite *get_defaults_suite(void) SUITE_ADD_TEST(suite, test_long_order_cast); SUITE_ADD_TEST(suite, test_long_order_attack); SUITE_ADD_TEST(suite, test_long_order_buy_sell); + SUITE_ADD_TEST(suite, test_long_order_trade_and_other); SUITE_ADD_TEST(suite, test_long_order_multi_long); SUITE_ADD_TEST(suite, test_long_order_multi_buy); SUITE_ADD_TEST(suite, test_long_order_multi_sell); diff --git a/src/economy.c b/src/economy.c index 4e6aa5f85..a3ad3682a 100644 --- a/src/economy.c +++ b/src/economy.c @@ -1385,6 +1385,11 @@ static void buy(unit * u, econ_request ** buyorders, struct order *ord) const luxury_type *ltype = NULL; const char *s; + if (fval(u, UFL_LONGACTION)) { + cmistake(u, ord, 52, MSG_PRODUCE); + return; + } + if (u->ship && is_guarded(r, u)) { cmistake(u, ord, 69, MSG_INCOME); return; @@ -1674,9 +1679,9 @@ static bool sell(unit * u, econ_request ** sellorders, struct order *ord) static int bt_cache; static const struct building_type *castle_bt, *caravan_bt; - if (bt_changed(&bt_cache)) { - castle_bt = bt_find("castle"); - caravan_bt = bt_find("caravan"); + if (fval(u, UFL_LONGACTION)) { + cmistake(u, ord, 52, MSG_PRODUCE); + return false; } if (u->ship && is_guarded(r, u)) { @@ -1686,6 +1691,11 @@ static bool sell(unit * u, econ_request ** sellorders, struct order *ord) /* sellorders sind KEIN array, weil fuer alle items DIE SELBE resource * (das geld der region) aufgebraucht wird. */ + if (bt_changed(&bt_cache)) { + castle_bt = bt_find("castle"); + caravan_bt = bt_find("caravan"); + } + init_order(ord, NULL); s = gettoken(token, sizeof(token)); @@ -2472,9 +2482,9 @@ static void peasant_taxes(region * r) void produce(struct region *r) { - econ_request *taxorders, *lootorders, *sellorders, *stealorders, *buyorders; - unit *u; bool limited = true; + econ_request *taxorders = NULL, *lootorders = NULL, *sellorders = NULL, *stealorders = NULL, *buyorders = NULL; + unit *u; long entertaining = 0, working = 0; econ_request *nextrequest = econ_requests; static int bt_cache; @@ -2509,15 +2519,8 @@ void produce(struct region *r) peasant_taxes(r); } - buyorders = 0; - sellorders = 0; - taxorders = 0; - lootorders = 0; - stealorders = 0; - for (u = r->units; u; u = u->next) { order *ord; - bool trader = false; keyword_t todo; if (!long_order_allowed(u, false)) continue; @@ -2534,28 +2537,29 @@ void produce(struct region *r) continue; } - for (ord = u->orders; ord; ord = ord->next) { - keyword_t kwd = getkeyword(ord); - if (kwd == K_BUY) { - buy(u, &buyorders, ord); - trader = true; - } - else if (kwd == K_SELL) { - /* sell returns true if the sale is not limited - * by the region limit */ - limited &= !sell(u, &sellorders, ord); - trader = true; + if (u->thisorder == NULL) { + bool trader = false; + for (ord = u->orders; ord; ord = ord->next) { + keyword_t kwd = getkeyword(ord); + if (kwd == K_BUY) { + buy(u, &buyorders, ord); + trader = true; + } + else if (kwd == K_SELL) { + /* sell returns true if the sale is not limited + * by the region limit */ + limited &= !sell(u, &sellorders, ord); + trader = true; + } } - } - if (trader) { - attrib *a = a_find(u->attribs, &at_trades); - if (a && a->data.i) { - produceexp(u, SK_TRADE, u->number); + if (trader) { + attrib *a = a_find(u->attribs, &at_trades); + if (a && a->data.i) { + produceexp(u, SK_TRADE, u->number); + } + fset(u, UFL_LONGACTION | UFL_NOTMOVING); } - fset(u, UFL_LONGACTION | UFL_NOTMOVING); - continue; } - todo = getkeyword(u->thisorder); if (todo == NOKEYWORD) continue; diff --git a/src/economy.test.c b/src/economy.test.c index 03959e800..46dbba2a4 100644 --- a/src/economy.test.c +++ b/src/economy.test.c @@ -617,6 +617,42 @@ static void test_buy_prices_rising(CuTest* tc) { test_teardown(); } +static void test_trade_is_long_action(CuTest *tc) { + unit *u; + region *r; + const item_type *it_luxury, *it_sold; + const resource_type *rt_silver; + test_setup(); + setup_production(); + r = setup_trade_region(tc, NULL); + test_create_building(r, test_create_buildingtype("castle"))->size = 2; + rt_silver = get_resourcetype(R_SILVER); + CuAssertPtrNotNull(tc, rt_silver); + CuAssertPtrNotNull(tc, rt_silver->itype); + it_luxury = r_luxury(r); + it_sold = it_find("balm"); + CuAssertTrue(tc, it_sold != it_luxury); + CuAssertPtrNotNull(tc, it_luxury); + u = test_create_unit(test_create_faction(), r); + test_set_item(u, it_sold, 100); + test_set_item(u, rt_silver->itype, 1000); + set_level(u, SK_TRADE, 1); + unit_addorder(u, create_order(K_BUY, u->faction->locale, "1 %s", LOC(u->faction->locale, resourcename(it_luxury->rtype, 0)))); + unit_addorder(u, create_order(K_SELL, u->faction->locale, "1 %s", LOC(u->faction->locale, resourcename(it_sold->rtype, 0)))); + produce(u->region); + CuAssertPtrEquals(tc, NULL, test_find_faction_message(u->faction, "error52")); + CuAssertIntEquals(tc, UFL_NOTMOVING | UFL_LONGACTION, u->flags & (UFL_NOTMOVING | UFL_LONGACTION)); + CuAssertIntEquals(tc, 1, i_get(u->items, it_luxury)); + CuAssertIntEquals(tc, 99, i_get(u->items, it_sold)); + + produce(u->region); + // error, but message is created in update_long_order! + CuAssertPtrEquals(tc, NULL, test_find_faction_message(u->faction, "error52")); + CuAssertIntEquals(tc, 1, i_get(u->items, it_luxury)); + CuAssertIntEquals(tc, 99, i_get(u->items, it_sold)); + test_teardown(); +} + static void test_buy_cmd(CuTest *tc) { region * r; unit *u; @@ -1459,6 +1495,7 @@ CuSuite *get_economy_suite(void) SUITE_ADD_TEST(suite, test_normals_recruit); SUITE_ADD_TEST(suite, test_heroes_dont_recruit); SUITE_ADD_TEST(suite, test_tax_cmd); + SUITE_ADD_TEST(suite, test_trade_is_long_action); SUITE_ADD_TEST(suite, test_buy_cmd); SUITE_ADD_TEST(suite, test_buy_twice); SUITE_ADD_TEST(suite, test_buy_prices); diff --git a/src/gmtool.c b/src/gmtool.c index d49f549f7..5c4cdf649 100644 --- a/src/gmtool.c +++ b/src/gmtool.c @@ -1089,6 +1089,132 @@ static int exec_key_binding(int keycode) return -1; } +static void show_help(void) +{ + WINDOW *wn, *pad; + int line, col, ch=0; + int height, width; + const char* const help[] = { + "", + "GETTING AROUND", + "", + "arrow keys, position keys: move cursor", + "g: go to coordinate", + "/: search for ... r: region by name, u: unit by id, f: faction by id, F: faction from list", + "n: find next element according to last search", + "SPACE: select / unselect current region", + "TAB: jump to next selected region (by space or t)", + "p: jump between planes", + "a: jump to corresponding astral region / real region", + "", + "", + "DISPLAY", + "", + "I: show/hide info about ... f: factions, u: units, s: ships, b: buildings", + "d: map mode ... t: show terrains, l: show luxurues", + "", + "", + "MANIPULATING DATA", + "", + "O: open data file", + "S: save data file", + "f, Ctrl-t: terraform region at cursor", + "CTRL+b: fill block with oceans", + "B: build island E3 style", + "s: seed next player from newfactions at current region", + "A: reset area (set region age to 0) for whole contiguos region", + "c: clear (reset resources) region under cursor", + "C: clear rectangle under cursor (2 regions up and to the right)" + "", + "h: mark regions ... n: none, i: island under cursor, t: terrain type, s: with ships,", + " u: with units, p: with player units, m: with monsters, f: with units of a faction,", + " c: chaos regions, v: new regions with age 0", + "H: unmark regions (as above)", + "t: select regions (for batch commands, as above)", + "T: un-select regions (as above)", + ";: run batch command for selected regions ... 'r': reset region, 't': terraform', 'f': fix (very special)", + "", + "", + "OTHER", + "", + "Ctrl+L: redraw", + "L: open lua prompt (exit/execute with enter)", + "Q: quit", + "" }; + int lines = sizeof help / sizeof *help, cols = 0; + const char* title = "HELP (exit with q)"; + const int BORDERX = 2, BORDERY = 2; + bool exit = FALSE; + + for (line = 0; line < lines; ++line) { + if (cols < (int) strlen(help[line])) cols = (int) strlen(help[line]); + } + + getmaxyx(stdscr, height, width); + + wn = newwin(height - 2 * BORDERY, width - 2 * BORDERY, 2, 2); + pad = newpad(lines, cols); + box(wn, 0, 0); + mvwprintw(wn, 0, 2, "[ %s ]", title); + for (line=0;line lines - 1) line = lines - 1; + if (col > cols - width / 2 + BORDERX * 2) col = cols - width / 2 + BORDERX * 2; + if (col < 0) col = 0; + + box(wn, 0, 0); + mvwprintw(wn, 0, 2, "[ %s %d/%d %d/%d]", title, line+1, lines, col+1, cols); + wnoutrefresh(wn); + wrefresh(wn); + prefresh(pad, line, col, BORDERY + 1, BORDERX + 2, height - BORDERY * 2, width - BORDERX * 2); + + if (!exit) ch = getch(); + } +} + static void handlekey(state * st, int c) { window *wnd; @@ -1145,9 +1271,17 @@ static void handlekey(state * st, int c) case KEY_SAVE: savedata(st); break; + case 'O': case KEY_OPEN: loaddata(st); break; + case '?': /* help */ + show_help(); + st->modified = 1; + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + st->wnd_map->update |= 3; + break; case 'B': cnormalize(&st->cursor, &nx, &ny); minpop = config_get_int("editor.island.min", 8); diff --git a/src/items/weapons.c b/src/items/weapons.c index 96b9cd3ed..6bd514a04 100644 --- a/src/items/weapons.c +++ b/src/items/weapons.c @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -16,10 +17,29 @@ /* libc includes */ #include +#include #include /* damage types */ +static void report_special_attacks(const fighter *af, const item_type *itype) +{ + battle *b = af->side->battle; + unit *au = af->unit; + message *msg; + int k = af->special.attacks; + const weapon_type *wtype = resource2weapon(itype->rtype); + + if (wtype->skill == SK_CATAPULT) { + msg = msg_message("usecatapult", "amount unit", k, au); + } + else { + msg = msg_message("useflamingsword", "amount unit", k, au); + } + message_all(b, msg); + msg_release(msg); +} + static bool attack_firesword(const troop * at, const struct weapon_type *wtype, int *casualties) @@ -35,21 +55,7 @@ int *casualties) if (!enemies) { if (casualties) *casualties = 0; - return true; /* if no enemy found, no use doing standarad attack */ - } - - if (fi->catmsg == -1) { - int i, k = 0; - message *msg; - for (i = 0; i <= at->index; ++i) { - struct weapon *wp = fi->person[i].melee; - if (wp != NULL && wp->type == wtype) - ++k; - } - msg = msg_message("useflamingsword", "amount unit", k, fi->unit); - message_all(fi->side->battle, msg); - msg_release(msg); - fi->catmsg = 0; + return false; /* if no enemy found, no use doing standarad attack */ } do { @@ -68,14 +74,13 @@ int *casualties) static bool attack_catapult(const troop * at, const struct weapon_type *wtype, -int *casualties) + int *casualties) { fighter *af = at->fighter; unit *au = af->unit; battle *b = af->side->battle; troop dt; - int d = 0, enemies; - weapon *wp; + int shots = INT_MAX, d = 0, enemies; const resource_type *rtype; if (au->status >= ST_AVOID) { @@ -83,13 +88,13 @@ int *casualties) return false; } - wp = af->person[at->index].missile; - assert(wp->type == wtype); + assert(wtype && wtype == WEAPON_TYPE(af->person[at->index].missile)); assert(af->person[at->index].reload == 0); rtype = rt_find("catapultammo"); if (rtype) { - if (get_pooled(au, rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, 1) <= 0) { + shots = get_pooled(au, rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, 1); + if (shots <= 0) { return false; } } @@ -100,34 +105,20 @@ int *casualties) return false; } - if (af->catmsg == -1) { - int i, k = 0; - message *msg; - - for (i = 0; i <= at->index; ++i) { - if (af->person[i].reload == 0 && af->person[i].missile == wp) - ++k; - } - msg = msg_message("usecatapult", "amount unit", k, au); - message_all(b, msg); - msg_release(msg); - af->catmsg = 0; - } - if (rtype) { use_pooled(au, rtype, GET_DEFAULT, 1); } - while (--enemies >= 0) { + while (enemies-- > 0) { /* Select defender */ dt = select_enemy(af, FIGHT_ROW, FIGHT_ROW, SELECT_ADVANCE); if (!dt.fighter) break; /* If battle succeeds */ - if (hits(*at, dt, wp)) { + if (hits(*at, dt, wtype)) { int chance_pct = config_get_int("rules.catapult.damage.chance_percent", 5); - d += terminate(dt, *at, AT_STANDARD, wp->type->damage[0], true); + d += terminate(dt, *at, AT_STANDARD, wtype->damage[0], true); structural_damage(dt, 0, chance_pct); } } @@ -140,6 +131,7 @@ int *casualties) void register_weapons(void) { + callbacks.report_special_attacks = report_special_attacks; register_function((pf_generic)attack_catapult, "attack_catapult"); register_function((pf_generic)attack_firesword, "attack_firesword"); } diff --git a/src/items/weapons.h b/src/items/weapons.h index aa150fa13..5821a9c21 100644 --- a/src/items/weapons.h +++ b/src/items/weapons.h @@ -1,7 +1,6 @@ #pragma once -#ifndef H_ITM_WEAPONS -#define H_ITM_WEAPONS -void register_weapons(void); +struct fighter; +struct weapon_type; -#endif +void register_weapons(void); diff --git a/src/kernel/build.c b/src/kernel/build.c index 532a184a7..fba7e9380 100644 --- a/src/kernel/build.c +++ b/src/kernel/build.c @@ -586,11 +586,6 @@ build_building(unit * u, const building_type * btype, int id, int want, order * btype = b->type; } - if (fval(btype, BTF_UNIQUE) && buildingtype_exists(r, btype, false)) { - /* only one of these per region */ - cmistake(u, ord, 93, MSG_PRODUCE); - return 0; - } if (btype->flags & BTF_NOBUILD) { /* special building, cannot be built */ cmistake(u, ord, 221, MSG_PRODUCE); @@ -601,6 +596,11 @@ build_building(unit * u, const building_type * btype, int id, int want, order * cmistake(u, ord, 221, MSG_PRODUCE); return 0; } + if (fval(btype, BTF_UNIQUE) && buildingtype_exists(r, btype, false)) { + /* only one of these per region */ + cmistake(u, ord, 93, MSG_PRODUCE); + return 0; + } if (btype->flags & BTF_ONEPERTURN) { if (b && fval(b, BLD_EXPANDED)) { cmistake(u, ord, 318, MSG_PRODUCE); diff --git a/src/kernel/build.test.c b/src/kernel/build.test.c index 8de422591..d8d748f00 100644 --- a/src/kernel/build.test.c +++ b/src/kernel/build.test.c @@ -341,7 +341,7 @@ static void test_build_building_no_materials(CuTest *tc) { u = setup_build(&bf); btype = bf.btype; set_level(u, SK_BUILDING, 1); - u->orders = create_order(K_MAKE, u->faction->locale, 0); + u->orders = create_order(K_MAKE, u->faction->locale, NULL); CuAssertIntEquals(tc, ENOMATERIALS, build_building(u, btype, 0, 4, u->orders)); CuAssertPtrEquals(tc, NULL, u->region->buildings); CuAssertPtrEquals(tc, NULL, u->building); @@ -358,7 +358,7 @@ static void test_build_building_with_golem(CuTest *tc) { btype = bf.btype; set_level(bf.u, SK_BUILDING, 1); - u->orders = create_order(K_MAKE, u->faction->locale, 0); + u->orders = create_order(K_MAKE, u->faction->locale, NULL); CuAssertIntEquals(tc, 1, build_building(u, btype, 0, 1, u->orders)); CuAssertPtrNotNull(tc, u->region->buildings); CuAssertIntEquals(tc, 1, u->region->buildings->size); @@ -379,9 +379,9 @@ static void test_build_building_success(CuTest *tc) { assert(btype && rtype && rtype->itype); assert(!u->region->buildings); - i_change(&bf.u->items, rtype->itype, 1); + i_change(&u->items, rtype->itype, 1); set_level(u, SK_BUILDING, 1); - u->orders = create_order(K_MAKE, u->faction->locale, 0); + u->orders = create_order(K_MAKE, u->faction->locale, NULL); CuAssertIntEquals(tc, 1, build_building(u, btype, 0, 4, u->orders)); CuAssertPtrNotNull(tc, u->region->buildings); CuAssertPtrEquals(tc, u->region->buildings, u->building); @@ -398,6 +398,66 @@ static void test_build_roqf_factor(CuTest *tc) { test_teardown(); } +static void test_build_building_nobuild_fail(CuTest *tc) { + unit *u; + build_fixture bf = { 0 }; + building_type *btype; + const resource_type *rtype; + + u = setup_build(&bf); + + rtype = get_resourcetype(R_STONE); + btype = bf.btype; + assert(btype && rtype && rtype->itype); + assert(!u->region->buildings); + btype->flags |= BTF_NOBUILD; + + i_change(&u->items, rtype->itype, 1); + set_level(u, SK_BUILDING, 1); + u->orders = create_order(K_MAKE, u->faction->locale, NULL); + CuAssertIntEquals(tc, 0, build_building(u, btype, 0, 4, u->orders)); + CuAssertPtrNotNull(tc, test_find_faction_message(u->faction, "error221")); + CuAssertPtrEquals(tc, NULL, u->region->buildings); + CuAssertPtrEquals(tc, NULL, u->building); + CuAssertIntEquals(tc, 1, i_get(u->items, rtype->itype)); + teardown_build(&bf); +} + +static void test_build_building_unique(CuTest *tc) { + unit *u; + build_fixture bf = { 0 }; + building_type *btype; + const resource_type *rtype; + + u = setup_build(&bf); + + rtype = get_resourcetype(R_STONE); + btype = bf.btype; + assert(btype && rtype && rtype->itype); + i_change(&u->items, rtype->itype, 2); + set_level(u, SK_BUILDING, 1); + u->orders = create_order(K_MAKE, u->faction->locale, NULL); + + btype->flags |= BTF_UNIQUE; + CuAssertIntEquals(tc, 1, build_building(u, btype, 0, 4, u->orders)); + CuAssertPtrEquals(tc, NULL, test_find_faction_message(u->faction, "error93")); + CuAssertIntEquals(tc, 1, i_get(u->items, rtype->itype)); + CuAssertPtrNotNull(tc, u->building); + leave_building(u); + CuAssertIntEquals(tc, 0, build_building(u, btype, 0, 4, u->orders)); + CuAssertPtrNotNull(tc, test_find_faction_message(u->faction, "error93")); + CuAssertIntEquals(tc, 1, i_get(u->items, rtype->itype)); + test_clear_messages(u->faction); + + /* NOBUILD before UNIQUE*/ + btype->flags |= BTF_UNIQUE|BTF_NOBUILD; + CuAssertIntEquals(tc, 0, build_building(u, btype, 0, 4, u->orders)); + CuAssertPtrNotNull(tc, test_find_faction_message(u->faction, "error221")); + CuAssertPtrEquals(tc, NULL, test_find_faction_message(u->faction, "error93")); + + teardown_build(&bf); +} + CuSuite *get_build_suite(void) { CuSuite *suite = CuSuiteNew(); @@ -416,6 +476,8 @@ CuSuite *get_build_suite(void) SUITE_ADD_TEST(suite, test_build_building_stage_continue); SUITE_ADD_TEST(suite, test_build_building_with_golem); SUITE_ADD_TEST(suite, test_build_building_no_materials); + SUITE_ADD_TEST(suite, test_build_building_nobuild_fail); + SUITE_ADD_TEST(suite, test_build_building_unique); return suite; } diff --git a/src/kernel/callbacks.h b/src/kernel/callbacks.h index f42707f45..b861aef05 100644 --- a/src/kernel/callbacks.h +++ b/src/kernel/callbacks.h @@ -1,31 +1,23 @@ -#ifndef H_KRNL_CALLBACKS_H -#define H_KRNL_CALLBACKS_H +#pragma once #include -#ifdef __cplusplus -extern "C" { -#endif +struct castorder; +struct order; +struct unit; +struct region; +struct fighter; +struct item_type; +struct resource_type; - struct castorder; - struct order; - struct unit; - struct region; - struct item_type; - struct resource_type; - - struct callback_struct { - bool (*equip_unit)(struct unit *u, const char *eqname, int mask); - int (*cast_spell)(struct castorder *co, const char *fname); - int (*use_item)(struct unit *u, const struct item_type *itype, - int amount, struct order *ord); - void(*produce_resource)(struct region *, const struct resource_type *, int); - int(*limit_resource)(const struct region *, const struct resource_type *); - }; - - extern struct callback_struct callbacks; -#ifdef __cplusplus -} -#endif -#endif /* H_KRNL_CALLBACKS_H */ +struct callback_struct { + bool (*equip_unit)(struct unit *u, const char *eqname, int mask); + int (*cast_spell)(struct castorder *co, const char *fname); + int (*use_item)(struct unit *u, const struct item_type *itype, + int amount, struct order *ord); + void(*produce_resource)(struct region *, const struct resource_type *, int); + int(*limit_resource)(const struct region *, const struct resource_type *); + void (*report_special_attacks)(const struct fighter *fig, const struct item_type *itype); +}; +extern struct callback_struct callbacks; diff --git a/src/kernel/faction.c b/src/kernel/faction.c index e2f364867..6a54f56aa 100755 --- a/src/kernel/faction.c +++ b/src/kernel/faction.c @@ -424,21 +424,21 @@ void destroyfaction(faction ** fp) } while (u) { + region *r = u->region; /* give away your stuff, to ghosts if you cannot (quest items) */ if (u->items) { - region *r = u->region; int result = gift_items(u, GIFT_FRIENDS | GIFT_PEASANTS); if (result != 0) { save_special_items(u); } - if (r->land && playerrace(u_race(u))) { - const race *rc = u_race(u); - /* Personen gehen nur an die Bauern, wenn sie auch von dort stammen */ - if ((rc->ec_flags & ECF_REC_ETHEREAL) == 0) { - int p = rpeasants(u->region); - p += (int)(u->number / rc->recruit_multi); - rsetpeasants(r, p); - } + } + if (r->land && playerrace(u_race(u))) { + const race *rc = u_race(u); + /* Personen gehen nur an die Bauern, wenn sie auch von dort stammen */ + if ((rc->ec_flags & ECF_REC_ETHEREAL) == 0) { + int p = rpeasants(u->region); + p += (int)(u->number / rc->recruit_multi); + rsetpeasants(r, p); } } set_number(u, 0); diff --git a/src/kernel/faction.test.c b/src/kernel/faction.test.c index 728ceac8a..066431e04 100644 --- a/src/kernel/faction.test.c +++ b/src/kernel/faction.test.c @@ -42,20 +42,38 @@ static void test_destroyfaction(CuTest *tc) { init_resources(); r = test_create_plain(0, 0); - rsethorses(r, 10); rsetpeasants(r, 100); + f = test_create_faction(); + u = test_create_unit(f, r); + scale_number(u, 100); + CuAssertPtrEquals(tc, f, factions); + CuAssertPtrEquals(tc, NULL, f->next); + destroyfaction(&factions); + CuAssertPtrEquals(tc, NULL, factions); + CuAssertIntEquals(tc, 200, rpeasants(r)); + test_teardown(); +} + +static void test_destroyfaction_items(CuTest *tc) { + faction *f; + region *r; + unit* u; + + test_setup(); + init_resources(); + + r = test_create_plain(0, 0); + rsethorses(r, 10); rsetmoney(r, 1000); f = test_create_faction(); u = test_create_unit(f, r); i_change(&u->items, it_find("horse"), 10); i_change(&u->items, it_find("money"), 1000); - scale_number(u, 100); CuAssertPtrEquals(tc, f, factions); CuAssertPtrEquals(tc, NULL, f->next); destroyfaction(&factions); CuAssertPtrEquals(tc, NULL, factions); CuAssertIntEquals(tc, 20, rhorses(r)); - CuAssertIntEquals(tc, 200, rpeasants(r)); CuAssertIntEquals(tc, 2000, rmoney(r)); test_teardown(); } @@ -563,6 +581,7 @@ CuSuite *get_faction_suite(void) SUITE_ADD_TEST(suite, test_addfaction); SUITE_ADD_TEST(suite, test_remove_empty_factions); SUITE_ADD_TEST(suite, test_destroyfaction); + SUITE_ADD_TEST(suite, test_destroyfaction_items); SUITE_ADD_TEST(suite, test_destroyfaction_undead); SUITE_ADD_TEST(suite, test_destroyfaction_demon); SUITE_ADD_TEST(suite, test_destroyfaction_orc); diff --git a/src/kernel/version.c b/src/kernel/version.c index beac576bb..f6f22a88e 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 "28.3.0" +#define ERESSEA_VERSION "28.4.0" #endif const char *eressea_version(void) { diff --git a/src/races/races.c b/src/races/races.c index c9a5b9538..22bec241b 100644 --- a/src/races/races.c +++ b/src/races/races.c @@ -35,7 +35,8 @@ void equip_newunits(struct unit *u) case RC_GOBLIN: rtype = rt_find("roi"); set_show_item(u->faction, rtype->itype); - set_number(u, 10); + scale_number(u, 10); + break; case RC_HUMAN: if (u->building == NULL) { diff --git a/src/races/races.test.c b/src/races/races.test.c new file mode 100644 index 000000000..f936a66ae --- /dev/null +++ b/src/races/races.test.c @@ -0,0 +1,42 @@ +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "races.h" + +#include "kernel/item.h" +#include "kernel/unit.h" +#include "kernel/race.h" + +#include + +#include + +#include "tests.h" + +static void test_equip_newgoblin(CuTest *tc) { + unit *u; + race *rc; + + test_setup(); + + it_get_or_create(rt_get_or_create("roi")); + rc = test_create_race("goblin"); + u = test_create_unit(test_create_faction_ex(rc, NULL), test_create_plain(0, 0)); + + CuAssertIntEquals(tc, 1, u->number); + equip_newunits(u); + CuAssertIntEquals(tc, 10, u->number); + CuAssertIntEquals(tc, 20, unit_max_hp(u)); + CuAssertIntEquals(tc, 20*10, u->hp); + + test_teardown(); +} + + +CuSuite *get_races_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_equip_newgoblin); + return suite; +} diff --git a/src/reports.c b/src/reports.c index ef6833921..1b5872260 100644 --- a/src/reports.c +++ b/src/reports.c @@ -1542,7 +1542,7 @@ int write_reports(faction * f, int options, const char *password) } if (errno) { error = errno; - log_fatal("error %d during %s report for faction %s: %s", errno, rtype->extension, factionname(f), strerror(error)); + log_fatal("error %d writing report %s for faction %s: %s", errno, path, factionname(f), strerror(error)); errno = 0; } } while (error); diff --git a/src/spells.c b/src/spells.c index 7f539ea9f..859d46dd2 100644 --- a/src/spells.c +++ b/src/spells.c @@ -2593,11 +2593,11 @@ static int sp_firewall(castorder * co) int cast_level = co->level; double force = co->force; spellparameter *pa = co->par; - direction_t dir; + int dir; region *r2; - dir = get_direction(pa->param[0]->data.xs, caster->faction->locale); - if (dir < MAXDIRECTIONS && dir != NODIRECTION) { + dir = (int) get_direction(pa->param[0]->data.xs, caster->faction->locale); + if (dir >= 0) { r2 = rconnect(r, dir); } else { @@ -3587,16 +3587,16 @@ static int sp_song_susceptmagic(castorder * co) static int sp_rallypeasantmob(castorder * co) { - unit *u, *un; int erfolg = 0; region *r = co_get_region(co); unit *mage = co_get_caster(co); int cast_level = co->level; + unit *u = r->units; message *msg; curse *c; - for (u = r->units; u; u = un) { - un = u->next; + while (u) { + unit *un = u->next; if (is_monsters(u->faction) && u_race(u) == get_race(RC_PEASANT)) { rsetpeasants(r, rpeasants(r) + u->number); rsetmoney(r, rmoney(r) + get_money(u)); @@ -3605,6 +3605,7 @@ static int sp_rallypeasantmob(castorder * co) set_number(u, 0); erfolg = cast_level; } + u = un; } c = get_curse(r->attribs, &ct_riotzone); diff --git a/src/spells/combatspells.c b/src/spells/combatspells.c index 83c1bca2d..7bfc49df9 100644 --- a/src/spells/combatspells.c +++ b/src/spells/combatspells.c @@ -281,15 +281,17 @@ int sp_combatrosthauch(struct castorder * co) for (qi = 0, ql = fgs; force>0 && ql; selist_advance(&ql, &qi, 1)) { fighter *df = (fighter *)selist_get(ql, qi); - int w; + unsigned int w; + size_t len = arrlen(df->weapons); - for (w = 0; df->weapons[w].type != NULL; ++w) { + for (w = 0; w != len; ++w) { weapon *wp = df->weapons; if (df->unit->items && force > 0) { - item ** itp = i_find(&df->unit->items, wp->type->itype); + const item_type *itype = wp->item.type; + item ** itp = i_find(&df->unit->items, itype); if (*itp) { item *it = *itp; - requirement *mat = wp->type->itype->construction->materials; + requirement *mat = itype->construction->materials; int n = force; if (it->number < n) n = it->number; @@ -298,7 +300,7 @@ int sp_combatrosthauch(struct castorder * co) int p; force -= n; k += n; - i_change(itp, wp->type->itype, -n); + i_change(itp, itype, -n); for (p = 0; n && p != df->unit->number; ++p) { if (df->person[p].melee == wp) { df->person[p].melee = NULL; @@ -1195,7 +1197,7 @@ int sp_appeasement(struct castorder * co) fi->run.hp = mage->hp; fi->run.number = mage->number; /* fighter leeren */ - rmfighter(fi, mage->number); + reduce_fighter(fi, mage->number); m = msg_message("cast_escape_effect", "mage spell", fi->unit, sp); message_all(b, m); diff --git a/src/test_eressea.c b/src/test_eressea.c index 04d5480af..4f9e8fac3 100644 --- a/src/test_eressea.c +++ b/src/test_eressea.c @@ -147,6 +147,7 @@ int RunAllTests(int argc, char *argv[]) ADD_SUITE(otherfaction); ADD_SUITE(piracy); ADD_SUITE(prefix); + ADD_SUITE(races); ADD_SUITE(recruit); ADD_SUITE(renumber); ADD_SUITE(report); diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 040216bfd..bb7a17ae5 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -12,3 +12,5 @@ add_executable(atoi36 ${CMAKE_SOURCE_DIR}/src/util/base36.c ) target_include_directories(atoi36 PRIVATE ${CMAKE_SOURCE_DIR}/src/util) + +install(TARGETS inifile DESTINATION "bin")