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")