Merge branch 'develop' of github.com:ennorehling/eressea into develop

This commit is contained in:
Enno Rehling 2019-08-21 15:35:59 +02:00
commit f2f7ece38c
145 changed files with 1838 additions and 1132 deletions

2
clibs

@ -1 +1 @@
Subproject commit 1854780fe3073e491775836c22f709668b1fff62
Subproject commit 6965050165efdae89305a13bff06283229f143f4

@ -1 +1 @@
Subproject commit 22741d9ce9d19bf7b5f5a219b6ed0925259a4d1b
Subproject commit e3533ac0a45e43e9716c40f6a874bc6f41ddc96d

View File

@ -2,7 +2,7 @@
if [ -z "$ERESSEA" ]; then
echo "You need to define the \$ERESSEA environment variable to run $0"
exit -2
exit 2
fi
GAME="$ERESSEA/game-$1"
@ -16,7 +16,7 @@ fi
if [ ! -d "$GAME/reports" ]; then
echo "cannot find reports directory in $GAME"
exit -1
exit 1
fi
cd "$GAME/reports" || exit

View File

@ -11,10 +11,5 @@ fi
for GAME in $*
do
if [ "$GAME" == "eressea" ]; then GAME=2 ; fi
if [ "$GAME" == "e3a" ]; then GAME=3 ; fi
if [ -e $HOME/eressea/game-$GAME/orders.queue ]
then
$HOME/bin/orders-process $GAME
fi
$HOME/eressea/orders-php/check-orders.sh $GAME
done

View File

@ -3,34 +3,9 @@
from string import split
from string import strip
from string import lower
import subprocess
import bcrypt
import sqlite3
def baseconvert(n, base):
"""convert positive decimal integer n to equivalent in another base (2-36)"""
digits = "0123456789abcdefghijkLmnopqrstuvwxyz"
try:
n = int(n)
base = int(base)
except:
return ""
if n < 0 or base < 2 or base > 36:
return ""
s = ""
while True:
r = n % base
s = digits[r] + s
n = n / base
if n == 0:
break
return s
class EPasswd:
def __init__(self):
self.data = {}
@ -47,7 +22,7 @@ class EPasswd:
c = conn.cursor()
for row in c.execute('SELECT `no`, `email`, `password` FROM `faction`'):
(no, email, passwd) = row
self.set_data(baseconvert(no, 36), email, passwd)
self.set_data(no, email, passwd)
conn.close()
def load_file(self, file):

View File

@ -2,6 +2,7 @@
# example: orders-accept 2 de < mail.txt
game="$1"
lang="$2"
[ -z "$ERESSEA" ] && ERESSEA="$HOME/eressea"
SCRIPT=$(readlink -f "$0")
@ -21,5 +22,5 @@ filename=$(basename "$ACCEPT_FILE")
email="$ACCEPT_MAIL"
if [ -d "$ERESSEA/orders-php" ]
then
php "$ERESSEA/orders-php/cli.php" insert "$filename" "$email"
php "$ERESSEA/orders-php/cli.php" insert "$filename" "$lang" "$email"
fi

View File

@ -5,7 +5,7 @@ if [ -z "$ERESSEA" ]; then
fi
if [ ! -f reports.txt ]; then
echo "need to run $0 from the report direcory"
exit -2
exit 2
fi
TEMPLATE=report-mail.txt

View File

@ -6,7 +6,7 @@ if [ -z "$ERESSEA" ]; then
fi
if [ ! -f reports.txt ]; then
echo "need to run $0 from the report direcory"
exit -2
exit 2
fi
PWD=$(pwd)
@ -33,7 +33,7 @@ fi
if [ ! -e "$TEMPLATE" ]; then
echo "no such email template: $TEMPLATE"
exit -3
exit 3
fi
while [ -e /tmp/.stopped ] ; do

View File

@ -3,14 +3,14 @@
if [ -z "$ERESSEA" ]; then
echo "You have to define the \$ERESSEA environment variable to run $0"
exit -2
exit 2
fi
function abort() {
if [ $# -gt 0 ]; then
echo "$@"
fi
exit -1
exit 1
}
GAME=$1
@ -47,7 +47,7 @@ fi
bash "${FACTION}.sh" "$EMAIL" || reply "Unbekannte Partei $FACTION"
OWNER=$("$BIN/getfaction.py" "$PWFILE" "$FACTION")
if [ ! -z "$OWNER" ]; then
if [ -n "$OWNER" ]; then
echo "Der Report Deiner Partei wurde an ${EMAIL} gesandt." \
| mutt -s "Reportnachforderung Partei ${FACTION}" "$OWNER"
fi

View File

@ -5,10 +5,10 @@
if [ -z "$ERESSEA" ]; then
echo "You have to define the \$ERESSEA environment variable to run $0"
exit -2
exit 2
fi
if [ ! -z "$1" ]; then
if [ -n "$1" ]; then
GAME="$ERESSEA/game-$1"
else
GAME=$ERESSEA

View File

@ -4391,6 +4391,13 @@
<arg name="command" type="order"/>
</type>
</message>
<message name="error125" section="errors">
<type>
<arg name="unit" type="unit"/>
<arg name="region" type="region"/>
<arg name="command" type="order"/>
</type>
</message>
<message name="error84" section="errors">
<type>
<arg name="unit" type="unit"/>
@ -5196,6 +5203,12 @@
</type>
</message>
<message name="transfer_unit" section="economy">
<type>
<arg name="unit" type="unit"/>
</type>
</message>
<message name="receive_person" section="economy">
<type>
<arg name="unit" type="unit"/>

View File

@ -8,6 +8,13 @@
</construction>
</ship>
<ship name="galleon" range="5" storm="1.00" damage="1.00" cargo="2000000" cptskill="5" minskill="2" sumskill="250" opensea="yes">
<coast terrain="plain"/>
<construction skill="shipcraft" minskill="5" maxsize="2000">
<requirement type="log" quantity="1"/>
</construction>
</ship>
<ship name="caravel" range="5" storm="1.00" damage="1.00" cargo="300000" cptskill="3" minskill="1" sumskill="30" opensea="yes">
<coast terrain="plain"/>
<construction skill="shipcraft" minskill="3" maxsize="250">

View File

@ -38,6 +38,9 @@ msgstr "\"$unit($mage) verwandelt $unit($target) in $race($race,0).\""
msgid "give_person"
msgstr "\"$unit($unit) übergibt $int($amount) Person$if($eq($amount,1),\"\",\"en\") an $unit($target).\""
msgid "transfer_unit"
msgstr "\"$unit($unit) schließt sich unserer Partei an.\""
msgid "rust_effect_2"
msgstr "\"$unit($mage) ruft ein fürchterliches Unwetter über seine Feinde. Der magischen Regen lässt alles Eisen rosten.\""
@ -686,6 +689,9 @@ msgstr "\"$unit($unit) in $region($region): '$order($command)' - Die Einheit kan
msgid "error85"
msgstr "\"$unit($unit) in $region($region): '$order($command)' - Es wurde keine Emailadresse angegeben.\""
msgid "error125"
msgstr "\"$unit($unit) in $region($region): '$order($command)' - Es wurde kein Banner angegeben.\""
msgid "starvation"
msgstr "\"$unit($unit) verliert in $region($region) $int($dead) von $int($add($live,$dead)) Personen durch Unterernährung.\""

View File

@ -38,6 +38,9 @@ msgstr "\"$unit($mage) tranforms $unit($target) to $race($race,0).\""
msgid "give_person"
msgstr "\"$unit($unit) transfers $int($amount) person$if($eq($amount,1),\"\",\"s\") to $unit($target).\""
msgid "transfer_unit"
msgstr "\"$unit($unit) joins our faction.\""
msgid "rust_effect_2"
msgstr "\"$unit($mage) calls forth a terrible torment over the enemy. The magical rain makes all iron rusty.\""
@ -686,6 +689,9 @@ msgstr "\"$unit($unit) in $region($region): '$order($command)' - The unit cannot
msgid "error85"
msgstr "\"$unit($unit) in $region($region): '$order($command)' - No email address was supplied.\""
msgid "error125"
msgstr "\"$unit($unit) in $region($region): '$order($command)' - No banner text was supplied.\""
msgid "starvation"
msgstr "\"$unit($unit) loses $int($dead) of $int($add($live,$dead)) people due to starvation in $region($region).\""

View File

@ -1707,6 +1707,9 @@ msgstr "Ring der Unsichtbarkeit"
msgid "caravel_a"
msgstr "eine Karavelle"
msgid "galleon_a"
msgstr "eine Galleone"
msgctxt "keyword"
msgid "describe"
msgstr "BESCHREIBE"
@ -2064,6 +2067,9 @@ msgstr "Wir schreiben %s des Monats %s im Jahre %d %s."
msgid "caravel"
msgstr "Karavelle"
msgid "galleon"
msgstr "Galleone"
msgid "dragon_postfix_10"
msgstr "der Goldene"

View File

@ -1458,6 +1458,12 @@ msgstr "DESCRIBE"
msgid "roi"
msgstr "ring of invisibility"
msgid "galleon_a"
msgstr "a galleon"
msgid "galleon"
msgstr "galleon"
msgid "caravel_a"
msgstr "a caravel"

9
s/convert.sh Normal file
View File

@ -0,0 +1,9 @@
#!/bin/sh
while [ ! -z $1 ] ; do
tmpfile=$(mktemp eressea.XXX)
iconv -f latin1 -t utf-8 < $1 | \
perl -pe 's/ß/ss/; s/ä/ae/; s/ü/ue/; s/ö/oe/;' \
> $tmpfile && \mv $tmpfile $1
file $1
shift 1
done

View File

@ -37,6 +37,7 @@ function use_snowglobe(u, amount, token, ord)
local transform = {
ocean = "glacier",
firewall = "volcano",
activevolcano = "volcano",
volcano = "mountain",
desert = "plain"
}

View File

@ -95,11 +95,11 @@ local function write_htpasswd()
end
local function write_files(locales)
write_reports()
write_summary()
write_database()
write_passwords()
write_htpasswd()
write_reports()
write_summary()
end
local function write_scores()

View File

@ -1,3 +1,4 @@
require 'tests.e2.quit'
require 'tests.e2.movement'
require 'tests.e2.astral'
require 'tests.e2.spells'

21
scripts/tests/e2/quit.lua Normal file
View File

@ -0,0 +1,21 @@
require "lunit"
module("tests.e2.quit", package.seeall, lunit.testcase)
function test_quit_faction()
local r = region.create(47, 0, "plain")
local f1 = faction.create("human")
f1.password = "steamedhams"
local f2 = faction.create("human")
local u1 = unit.create(f1, r, 8)
local u2 = unit.create(f2, r, 9)
local u3 = unit.create(f1, r, 10)
u1:clear_orders()
u2:clear_orders()
u1:add_order("STIRB steamedhams PARTEI " .. itoa36(f2.id))
u2:add_order("KONTAKTIERE " .. itoa36(u1.id))
process_orders()
assert_equal(f2, u1.faction)
assert_equal(f2, u2.faction)
assert_equal(f2, u3.faction)
end

View File

@ -1,4 +1,5 @@
-- new tests 2014-06-11
require 'tests.laws'
require 'tests.faction'
require 'tests.locale'
require 'tests.movement'
@ -6,6 +7,5 @@ require 'tests.pool'
require 'tests.regions'
require 'tests.settings'
require 'tests.study'
require 'tests.laws'
require 'tests.bindings'
require 'tests.hunger'

View File

@ -70,15 +70,15 @@ end
function test_give_temp()
u.number = 2
u:add_order("GIB TEMP 123 1 PERSON")
u:add_order("MACHE TEMP 123 'Herpderp'")
u:add_order("MACHE TEMP 123 'Lorax'")
u:add_order("ENDE")
_G.process_orders()
assert_equal(1, u.number)
for x in f.units do
if x.name == 'Herpderp' then u=x end
if x.name == 'Lorax' then u=x end
end
assert_equal('Herpderp', u.name)
assert_equal('Lorax', u.name)
assert_equal(1, u.number)
end

View File

@ -94,7 +94,9 @@ function test_lighthouse()
eressea.free_game()
local r = region.create(0, 0, "mountain")
local f = faction.create("human", "human@example.com")
region.create(1, 0, "mountain")
local f2 = faction.create("dwarf")
local r2 = region.create(1, 0, "mountain")
unit.create(f2, r2, 1).name = 'The Babadook'
region.create(2, 0, "ocean")
region.create(0, 1, "firewall")
region.create(3, 0, "ocean")
@ -110,12 +112,13 @@ function test_lighthouse()
init_reports()
write_report(f)
assert_false(find_in_report(f, " %(1,0%) %(vom Turm erblickt%)"))
assert_false(find_in_report(f, "The Babadook"))
assert_true(find_in_report(f, " %(1,0%) %(vom Turm erblickt%)"))
assert_true(find_in_report(f, " %(2,0%) %(vom Turm erblickt%)"))
assert_true(find_in_report(f, " %(3,0%) %(vom Turm erblickt%)"))
assert_true(find_in_report(f, " %(0,1%) %(vom Turm erblickt%)"))
assert_false(find_in_report(f, " %(0,0%) %(vom Turm erblickt%)"))
assert_false(find_in_report(f, " %(0,1%) %(vom Turm erblickt%)"))
assert_false(find_in_report(f, " %(4,0%) %(vom Turm erblickt%)"))
remove_report(f)
end

View File

@ -101,6 +101,7 @@ set (ERESSEA_SRC
creport.c
direction.c
donations.c
recruit.c
economy.c
eressea.c
exparse.c

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,6 +1,5 @@
/*
Copyright (c) 1998-2018,
Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,6 @@
#include <platform.h>
#include "kernel/config.h"
#include "kernel/faction.h"
#include "kernel/messages.h"
#include "kernel/order.h"
@ -16,48 +17,57 @@
#include <stdlib.h>
#include <assert.h>
static int cmp_scholars(const void *lhs, const void *rhs)
{
static int cmp_scholars(const void *lhs, const void *rhs) {
const scholar *a = (const scholar *)lhs;
const scholar *b = (const scholar *)rhs;
if (a->sk == b->sk) {
/* sort by level, descending: */
return b->level - a->level;
}
/* order by skill */
return (int)a->sk - (int)b->sk;
return b->level - a->level;
}
int autostudy_init(scholar scholars[], int max_scholars, unit **units)
int autostudy_init(scholar scholars[], int max_scholars, unit **units, skill_t *o_skill)
{
unit *unext = NULL, *u = *units;
faction *f = u->faction;
int nscholars = 0;
skill_t skill = NOSKILL;
while (u) {
keyword_t kwd = init_order(u->thisorder, u->faction->locale);
if (kwd == K_AUTOSTUDY) {
if (long_order_allowed(u)) {
if (!fval(u, UFL_MARK)) {
keyword_t kwd = init_order(u->thisorder, u->faction->locale);
if (kwd == K_AUTOSTUDY) {
if (f == u->faction) {
scholar * st = scholars + nscholars;
skill_t sk = getskill(u->faction->locale);
if (check_student(u, u->thisorder, sk)) {
st->sk = sk;
st->level = effskill_study(u, st->sk);
st->learn = 0;
st->u = u;
if (++nscholars > max_scholars) {
log_fatal("you must increase MAXSCHOLARS");
unext = u->next;
if (long_order_allowed(u)) {
scholar * st = scholars + nscholars;
skill_t sk = getskill(u->faction->locale);
if (skill == NOSKILL && sk != NOSKILL) {
skill = sk;
if (o_skill) {
*o_skill = skill;
}
}
if (check_student(u, u->thisorder, sk)) {
if (sk == skill) {
fset(u, UFL_MARK);
st->level = (short)effskill_study(u, sk);
st->learn = 0;
st->u = u;
if (++nscholars >= max_scholars) {
log_warning("you must increase MAXSCHOLARS");
break;
}
}
}
else {
fset(u, UFL_MARK);
}
}
}
else if (!unext) {
unext = u;
}
}
}
u = u->next;
}
while (unext && unext->faction != f) {
unext = unext->next;
}
*units = unext;
if (nscholars > 0) {
qsort(scholars, nscholars, sizeof(scholar), cmp_scholars);
@ -81,26 +91,25 @@ void autostudy_run(scholar scholars[], int nscholars)
{
int ti = 0;
while (ti != nscholars) {
skill_t sk = scholars[ti].sk;
int t, se, ts = 0, tt = 0, si = ti;
for (se = ti; se != nscholars && scholars[se].sk == sk; ++se) {
for (se = ti; se != nscholars; ++se) {
int mint;
ts += scholars[se].u->number; /* count total scholars */
mint = (ts + 10) / 11; /* need a minimum of ceil(ts/11) teachers */
for (; mint > tt && si != nscholars && scholars[si].sk == sk; ++si) {
for (; mint > tt && si != nscholars; ++si) {
tt += scholars[si].u->number;
}
}
/* now si splits the teachers and students 1:10 */
/* first student must be 2 levels below first teacher: */
for (; si != se && scholars[si].sk == sk; ++si) {
for (; si != se; ++si) {
if (scholars[si].level + TEACHDIFFERENCE <= scholars[ti].level) {
break;
}
tt += scholars[si].u->number;
}
/* now si is the first unit we can teach, if we can teach any */
if (si == se || scholars[si].sk != sk) {
if (si == se) {
/* there are no students, so standard learning for everyone */
for (t = ti; t != se; ++t) {
learning(scholars + t, scholars[t].u->number);
@ -161,23 +170,38 @@ void autostudy_run(scholar scholars[], int nscholars)
}
}
#define MAXSCHOLARS 512
void do_autostudy(region *r)
{
static int config;
static int batchsize = MAXSCHOLARS;
static int max_scholars;
unit *units = r->units;
scholar scholars[MAXSCHOLARS];
while (units) {
int i, nscholars = autostudy_init(scholars, MAXSCHOLARS, &units);
if (nscholars > max_scholars) {
stats_count("automate.max_scholars", nscholars - max_scholars);
max_scholars = nscholars;
}
autostudy_run(scholars, nscholars);
for (i = 0; i != nscholars; ++i) {
int days = STUDYDAYS * scholars[i].learn;
learn_skill(scholars[i].u, scholars[i].sk, days);
unit *u;
if (config_changed(&config)) {
batchsize = config_get_int("automate.batchsize", MAXSCHOLARS);
assert(batchsize <= MAXSCHOLARS);
}
for (u = r->units; u; u = u->next) {
if (!fval(u, UFL_MARK)) {
unit *ulist = u;
int sum_scholars = 0;
while (ulist) {
skill_t skill = NOSKILL;
int i, nscholars = autostudy_init(scholars, batchsize, &ulist, &skill);
assert(ulist == NULL || ulist->faction == u->faction);
sum_scholars += nscholars;
if (sum_scholars > max_scholars) {
stats_count("automate.max_scholars", sum_scholars - max_scholars);
max_scholars = sum_scholars;
}
autostudy_run(scholars, nscholars);
for (i = 0; i != nscholars; ++i) {
int days = STUDYDAYS * scholars[i].learn;
learn_skill(scholars[i].u, skill, days);
}
}
}
freset(u, UFL_MARK);
}
}

View File

@ -28,16 +28,16 @@ struct unit;
typedef struct scholar {
struct unit *u;
skill_t sk;
int level;
int learn;
short level;
} scholar;
#define MAXSCHOLARS 128
#define STUDENTS_PER_TEACHER 10
void do_autostudy(struct region *r);
int autostudy_init(scholar scholars[], int max_scholars, struct unit **units);
int autostudy_init(scholar scholars[], int max_scholars, struct unit **units, skill_t *o_skill);
void autostudy_run(scholar scholars[], int nscholars);
#endif

View File

@ -4,6 +4,7 @@
#include "automate.h"
#include "kernel/config.h"
#include "kernel/faction.h"
#include "kernel/order.h"
#include "kernel/region.h"
@ -20,6 +21,8 @@ static void test_autostudy_init(CuTest *tc) {
unit *u1, *u2, *u3, *u4, *u5, *ulist;
faction *f;
region *r;
message *msg;
skill_t skill = NOSKILL;
test_setup();
mt_create_error(77);
@ -33,36 +36,36 @@ static void test_autostudy_init(CuTest *tc) {
u2 = test_create_unit(f, r);
u2->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_ENTERTAINMENT]);
set_level(u2, SK_ENTERTAINMENT, 2);
u3 = test_create_unit(f, r);
u3->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_PERCEPTION]);
u4 = test_create_unit(f, r);
u4->thisorder = create_order(K_AUTOSTUDY, f->locale, "Dudelidu");
u3 = test_create_unit(f, r);
u3->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_PERCEPTION]);
u5 = test_create_unit(test_create_faction(NULL), r);
u5->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_PERCEPTION]);
scholars[3].u = NULL;
scholars[2].u = NULL;
ulist = r->units;
CuAssertIntEquals(tc, 3, autostudy_init(scholars, 4, &ulist));
CuAssertPtrNotNull(tc, test_find_messagetype(u4->faction->msgs, "error77"));
CuAssertIntEquals(tc, 2, autostudy_init(scholars, 4, &ulist, &skill));
CuAssertIntEquals(tc, SK_ENTERTAINMENT, skill);
CuAssertPtrEquals(tc, u2, scholars[0].u);
CuAssertIntEquals(tc, 2, scholars[0].level);
CuAssertIntEquals(tc, 0, scholars[0].learn);
CuAssertIntEquals(tc, SK_ENTERTAINMENT, scholars[0].sk);
CuAssertPtrEquals(tc, u1, scholars[1].u);
CuAssertIntEquals(tc, 0, scholars[1].level);
CuAssertIntEquals(tc, 0, scholars[1].learn);
CuAssertIntEquals(tc, SK_ENTERTAINMENT, scholars[1].sk);
CuAssertPtrEquals(tc, u3, scholars[2].u);
CuAssertIntEquals(tc, 0, scholars[2].level);
CuAssertIntEquals(tc, 0, scholars[2].learn);
CuAssertIntEquals(tc, SK_PERCEPTION, scholars[2].sk);
CuAssertPtrEquals(tc, NULL, scholars[3].u);
CuAssertPtrEquals(tc, u5, ulist);
CuAssertIntEquals(tc, 1, autostudy_init(scholars, 4, &ulist));
CuAssertPtrEquals(tc, u5, scholars[0].u);
CuAssertPtrEquals(tc, NULL, scholars[2].u);
CuAssertPtrEquals(tc, NULL, ulist);
ulist = u3;
CuAssertIntEquals(tc, 1, autostudy_init(scholars, 4, &ulist, &skill));
CuAssertIntEquals(tc, SK_PERCEPTION, skill);
CuAssertPtrEquals(tc, u3, scholars[0].u);
CuAssertIntEquals(tc, 0, scholars[0].level);
CuAssertIntEquals(tc, 0, scholars[0].learn);
CuAssertIntEquals(tc, SK_PERCEPTION, scholars[0].sk);
CuAssertPtrEquals(tc, NULL, ulist);
CuAssertPtrNotNull(tc, msg = test_find_messagetype(f->msgs, "error77"));
CuAssertPtrEquals(tc, NULL, test_find_messagetype_ex(f->msgs, "error77", msg));
test_teardown();
}
@ -75,6 +78,7 @@ static void test_autostudy_run_twoteachers(CuTest *tc) {
unit *u1, *u2, *u3, *u4, *ulist;
faction *f;
region *r;
skill_t skill;
test_setup();
r = test_create_plain(0, 0);
@ -94,9 +98,10 @@ static void test_autostudy_run_twoteachers(CuTest *tc) {
set_number(u4, 12);
ulist = r->units;
CuAssertIntEquals(tc, 4, nscholars = autostudy_init(scholars, 4, &ulist));
CuAssertIntEquals(tc, 4, nscholars = autostudy_init(scholars, 4, &ulist, &skill));
CuAssertPtrEquals(tc, NULL, ulist);
autostudy_run(scholars, nscholars);
CuAssertIntEquals(tc, SK_ENTERTAINMENT, skill);
CuAssertIntEquals(tc, 0, scholars[0].learn);
CuAssertIntEquals(tc, 0, scholars[1].learn);
CuAssertIntEquals(tc, scholars[2].u->number * 2, scholars[2].learn);
@ -125,21 +130,34 @@ static void test_autostudy_run(CuTest *tc) {
u3 = test_create_unit(f, r);
u3->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_PERCEPTION]);
set_number(u3, 15);
scholars[3].u = NULL;
scholars[2].u = NULL;
ulist = r->units;
CuAssertIntEquals(tc, 3, nscholars = autostudy_init(scholars, 4, &ulist));
CuAssertIntEquals(tc, 2, nscholars = autostudy_init(scholars, 4, &ulist, NULL));
CuAssertIntEquals(tc, UFL_MARK, u1->flags & UFL_MARK);
CuAssertIntEquals(tc, UFL_MARK, u2->flags & UFL_MARK);
CuAssertIntEquals(tc, 0, u3->flags & UFL_MARK);
CuAssertPtrEquals(tc, NULL, ulist);
autostudy_run(scholars, nscholars);
CuAssertIntEquals(tc, 1, scholars[0].learn);
CuAssertIntEquals(tc, 20, scholars[1].learn);
CuAssertIntEquals(tc, 15, scholars[2].learn);
CuAssertPtrEquals(tc, NULL, scholars[2].u);
scholars[1].u = NULL;
ulist = u3;
CuAssertIntEquals(tc, 1, nscholars = autostudy_init(scholars, 4, &ulist, NULL));
CuAssertPtrEquals(tc, NULL, ulist);
autostudy_run(scholars, nscholars);
CuAssertIntEquals(tc, 15, scholars[0].learn);
CuAssertPtrEquals(tc, NULL, scholars[1].u);
test_teardown();
}
static void test_autostudy_run_noteachers(CuTest *tc) {
scholar scholars[4];
int nscholars;
unit *u1, *u2, *u3, *ulist;
unit *u1, *u2, *ulist;
faction *f;
region *r;
@ -147,23 +165,24 @@ static void test_autostudy_run_noteachers(CuTest *tc) {
r = test_create_plain(0, 0);
f = test_create_faction(NULL);
u1 = test_create_unit(f, r);
u1->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_LUMBERJACK]);
set_number(u1, 2);
u1->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_ENTERTAINMENT]);
set_number(u1, 5);
set_level(u1, SK_ENTERTAINMENT, 2);
u2 = test_create_unit(f, r);
u2->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_ENTERTAINMENT]);
set_number(u2, 10);
u3 = test_create_unit(f, r);
u3->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_PERCEPTION]);
set_number(u3, 15);
scholars[3].u = NULL;
set_number(u2, 7);
set_level(u2, SK_ENTERTAINMENT, 2);
scholars[2].u = NULL;
ulist = r->units;
CuAssertIntEquals(tc, 3, nscholars = autostudy_init(scholars, 4, &ulist));
CuAssertIntEquals(tc, 2, nscholars = autostudy_init(scholars, 4, &ulist, NULL));
CuAssertPtrEquals(tc, NULL, ulist);
autostudy_run(scholars, nscholars);
CuAssertIntEquals(tc, 2, scholars[0].learn);
CuAssertIntEquals(tc, 10, scholars[1].learn);
CuAssertIntEquals(tc, 15, scholars[2].learn);
/* stupid qsort is unstable: */
CuAssertIntEquals(tc, 12, scholars[0].learn + scholars[1].learn);
CuAssertIntEquals(tc, 35, scholars[0].learn * scholars[1].learn);
CuAssertPtrEquals(tc, NULL, scholars[2].u);
test_teardown();
}
@ -188,7 +207,7 @@ static void test_autostudy_run_teachers_learn(CuTest *tc) {
u2->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_ENTERTAINMENT]);
set_number(u2, 10);
ulist = r->units;
CuAssertIntEquals(tc, 2, nscholars = autostudy_init(scholars, 4, &ulist));
CuAssertIntEquals(tc, 2, nscholars = autostudy_init(scholars, 4, &ulist, NULL));
CuAssertPtrEquals(tc, NULL, ulist);
autostudy_run(scholars, nscholars);
CuAssertIntEquals(tc, 1, scholars[0].learn);
@ -222,7 +241,7 @@ static void test_autostudy_run_skilldiff(CuTest *tc) {
set_number(u3, 10);
scholars[3].u = NULL;
ulist = r->units;
CuAssertIntEquals(tc, 3, nscholars = autostudy_init(scholars, 4, &ulist));
CuAssertIntEquals(tc, 3, nscholars = autostudy_init(scholars, 4, &ulist, NULL));
CuAssertPtrEquals(tc, NULL, ulist);
autostudy_run(scholars, nscholars);
CuAssertIntEquals(tc, 0, scholars[0].learn);
@ -231,11 +250,78 @@ static void test_autostudy_run_skilldiff(CuTest *tc) {
test_teardown();
}
static void test_autostudy_batches(CuTest *tc) {
scholar scholars[2];
int nscholars;
unit *u1, *u2, *u3, *ulist;
faction *f;
region *r;
test_setup();
r = test_create_plain(0, 0);
f = test_create_faction(NULL);
u1 = test_create_unit(f, r);
u1->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_PERCEPTION]);
set_number(u1, 1);
set_level(u1, SK_PERCEPTION, 2);
u2 = test_create_unit(f, r);
u2->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_PERCEPTION]);
set_number(u2, 10);
u3 = test_create_unit(f, r);
u3->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_PERCEPTION]);
set_number(u3, 10);
scholars[1].u = NULL;
ulist = r->units;
config_set("automate.batchsize", "2");
CuAssertIntEquals(tc, 2, nscholars = autostudy_init(scholars, 2, &ulist, NULL));
CuAssertPtrEquals(tc, u3, ulist);
autostudy_run(scholars, nscholars);
CuAssertIntEquals(tc, 0, scholars[0].learn);
CuAssertIntEquals(tc, 20, scholars[1].learn);
CuAssertIntEquals(tc, 1, nscholars = autostudy_init(scholars, 2, &ulist, NULL));
autostudy_run(scholars, nscholars);
CuAssertIntEquals(tc, 10, scholars[0].learn);
test_teardown();
}
static void test_do_autostudy(CuTest *tc) {
unit *u1, *u2, *u3, *u4;
faction *f;
region *r;
test_setup();
r = test_create_plain(0, 0);
f = test_create_faction(NULL);
u1 = test_create_unit(f, r);
u1->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_PERCEPTION]);
set_number(u1, 1);
set_level(u1, SK_PERCEPTION, 2);
u2 = test_create_unit(f, r);
u2->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_PERCEPTION]);
set_number(u2, 10);
u3 = test_create_unit(f, r);
u3->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_ENTERTAINMENT]);
u4 = test_create_unit(test_create_faction(NULL), r);
u4->thisorder = create_order(K_AUTOSTUDY, f->locale, skillnames[SK_ENTERTAINMENT]);
do_autostudy(r);
CuAssertIntEquals(tc, 2, get_level(u1, SK_PERCEPTION));
/* impossible to say if u2 is T1 or T2 now */
CuAssertIntEquals(tc, 1, get_level(u3, SK_ENTERTAINMENT));
CuAssertIntEquals(tc, 1, get_level(u4, SK_ENTERTAINMENT));
CuAssertIntEquals(tc, 0, u1->flags & UFL_MARK);
CuAssertIntEquals(tc, 0, u2->flags & UFL_MARK);
CuAssertIntEquals(tc, 0, u3->flags & UFL_MARK);
CuAssertIntEquals(tc, 0, u4->flags & UFL_MARK);
test_teardown();
}
CuSuite *get_automate_suite(void)
{
CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_autostudy_init);
SUITE_ADD_TEST(suite, test_autostudy_run);
SUITE_ADD_TEST(suite, test_do_autostudy);
SUITE_ADD_TEST(suite, test_autostudy_batches);
SUITE_ADD_TEST(suite, test_autostudy_run_noteachers);
SUITE_ADD_TEST(suite, test_autostudy_run_teachers_learn);
SUITE_ADD_TEST(suite, test_autostudy_run_twoteachers);

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -527,7 +527,7 @@ static int tolua_region_get_peasants(lua_State * L)
region *self = (region *)tolua_tousertype(L, 1, NULL);
if (self) {
lua_pushinteger(L, self->land ? self->land->peasants : 0);
lua_pushinteger(L, rpeasants(self));
return 1;
}
return 0;
@ -538,7 +538,7 @@ static int tolua_region_set_peasants(lua_State * L)
region *self = (region *)tolua_tousertype(L, 1, NULL);
if (self && self->land) {
self->land->peasants = lua_tointeger(L, 2);
rsetpeasants(self, lua_tointeger(L, 2));
}
return 0;
}

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -115,13 +115,13 @@ int contact_cmd(unit * u, order * ord)
}
else {
/* old-style syntax, KONTAKTIERE foo */
unit *u2;
unit *u2 = NULL;
int n = 0;
if (p == P_TEMP) {
n = getid();
u2 = findnewunit(u->region, u->faction, n);
}
else {
else if (str) {
n = atoi36((const char *)str);
u2 = findunit(n);
}

View File

@ -74,11 +74,73 @@ static void test_contact_cmd(CuTest *tc) {
test_teardown();
}
static void test_contact_cmd_invalid(CuTest *tc) {
struct unit *u;
struct region *r;
const struct locale *lang;
struct order *ord;
test_setup();
r = test_create_plain(0, 0);
u = test_create_unit(test_create_faction(NULL), r);
lang = u->faction->locale;
/* KONTAKTIERE EINHEIT <not-found> */
ord = create_order(K_CONTACT, u->faction->locale, "%s %i",
LOC(lang, parameters[P_UNIT]), u->no + 1);
contact_cmd(u, ord);
CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "feedback_unit_not_found"));
free_order(ord);
test_clear_messages(u->faction);
/* KONTAKTIERE EINHEIT TEMP <not-found> */
ord = create_order(K_CONTACT, u->faction->locale, "%s %s %i",
LOC(lang, parameters[P_UNIT]), LOC(lang, parameters[P_TEMP]), u->no + 1);
contact_cmd(u, ord);
CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "feedback_unit_not_found"));
free_order(ord);
test_clear_messages(u->faction);
/* KONTAKTIERE EINHEIT TEMP */
ord = create_order(K_CONTACT, u->faction->locale, "%s %s",
LOC(lang, parameters[P_UNIT]), LOC(lang, parameters[P_TEMP]));
contact_cmd(u, ord);
CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "feedback_unit_not_found"));
free_order(ord);
test_clear_messages(u->faction);
/* KONTAKTIERE EINHEIT */
ord = create_order(K_CONTACT, u->faction->locale,
LOC(lang, parameters[P_UNIT]));
contact_cmd(u, ord);
CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "feedback_unit_not_found"));
free_order(ord);
test_clear_messages(u->faction);
/* KONTAKTIERE TEMP */
ord = create_order(K_CONTACT, u->faction->locale,
LOC(lang, parameters[P_TEMP]));
contact_cmd(u, ord);
CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "feedback_unit_not_found"));
free_order(ord);
test_clear_messages(u->faction);
/* KONTAKTIERE */
ord = create_order(K_CONTACT, u->faction->locale, NULL);
contact_cmd(u, ord);
CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "feedback_unit_not_found"));
free_order(ord);
test_clear_messages(u->faction);
test_teardown();
}
CuSuite *get_contact_suite(void)
{
CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_contact);
SUITE_ADD_TEST(suite, test_contact_cmd);
SUITE_ADD_TEST(suite, test_contact_cmd_invalid);
return suite;
}

View File

@ -1503,33 +1503,34 @@ static void cr_output_region(FILE * F, report_context * ctx, region * r)
cr_output_messages(F, mlist, f);
}
}
/* buildings */
for (b = rbuildings(r); b; b = b->next) {
int fno = -1;
u = building_owner(b);
if (u && !fval(u, UFL_ANON_FACTION)) {
const faction *sf = visible_faction(f, u);
fno = sf->no;
}
cr_output_building_compat(F, b, u, fno, f);
}
/* ships */
for (sh = r->ships; sh; sh = sh->next) {
int fno = -1;
u = ship_owner(sh);
if (u && !fval(u, UFL_ANON_FACTION)) {
const faction *sf = visible_faction(f, u);
fno = sf->no;
if (r->seen.mode >= seen_lighthouse) {
/* buildings */
for (b = rbuildings(r); b; b = b->next) {
int fno = -1;
u = building_owner(b);
if (u && !fval(u, UFL_ANON_FACTION)) {
const faction *sf = visible_faction(f, u);
fno = sf->no;
}
cr_output_building_compat(F, b, u, fno, f);
}
cr_output_ship_compat(F, sh, u, fno, f, r);
}
/* ships */
for (sh = r->ships; sh; sh = sh->next) {
int fno = -1;
u = ship_owner(sh);
if (u && !fval(u, UFL_ANON_FACTION)) {
const faction *sf = visible_faction(f, u);
fno = sf->no;
}
/* visible units */
for (u = r->units; u; u = u->next) {
if (visible_unit(u, f, stealthmod, r->seen.mode)) {
cr_output_unit_compat(F, f, u, r->seen.mode);
cr_output_ship_compat(F, sh, u, fno, f, r);
}
/* visible units */
for (u = r->units; u; u = u->next) {
if (visible_unit(u, f, stealthmod, r->seen.mode)) {
cr_output_unit_compat(F, f, u, r->seen.mode);
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2014,
Copyright (c) 1998-2019,
Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>
@ -84,28 +84,28 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include <assert.h>
#include <limits.h>
static int working;
static econ_request entertainers[1024];
static econ_request *nextentertainer;
static int entertaining;
#define MAX_REQUESTS 1024
static struct econ_request econ_requests[MAX_REQUESTS];
static econ_request **g_requests; /* TODO: no need for this to be module-global */
#define RECRUIT_MERGE 1
static int rules_recruit = -1;
#define ENTERTAINFRACTION 20
static void recruit_init(void)
{
if (rules_recruit < 0) {
rules_recruit = 0;
if (config_get_int("recruit.allow_merge", 1)) {
rules_recruit |= RECRUIT_MERGE;
}
}
static void add_request(econ_request * req, enum econ_type type, unit *u, order *ord, int want) {
req->next = NULL;
req->unit = u;
req->qty = u->wants = want;
req->type = type;
}
#define ENTERTAINFRACTION 20
static bool rule_auto_taxation(void)
{
return config_get_int("rules.economy.taxation", 0) != 0;
}
static bool rule_autowork(void) {
return config_get_int("work.auto", 0) != 0;
}
int entertainmoney(const region * r)
{
@ -187,6 +187,10 @@ int expand_production(region * r, econ_request * requests, econ_request ***resul
return norders;
}
static int expandorders(region * r, econ_request * requests) {
return expand_production(r, requests, &g_requests);
}
static void free_requests(econ_request *requests) {
while (requests) {
econ_request *req = requests->next;
@ -195,383 +199,8 @@ static void free_requests(econ_request *requests) {
}
}
static int expandorders(region * r, econ_request * requests) {
return expand_production(r, requests, &g_requests);
}
/* ------------------------------------------------------------- */
typedef struct recruitment {
struct recruitment *next;
faction *f;
econ_request *requests;
int total, assigned;
} recruitment;
/** Creates a list of recruitment structs, one for each faction. Adds every quantifyable production
* to the faction's struct and to total.
*/
static recruitment *select_recruitment(econ_request ** rop,
int(*quantify) (const struct race *, int), int *total)
{
recruitment *recruits = NULL;
while (*rop) {
recruitment *rec = recruits;
econ_request *ro = *rop;
unit *u = ro->unit;
const race *rc = u_race(u);
int qty = quantify(rc, ro->qty);
if (qty < 0) {
rop = &ro->next; /* skip this one */
}
else {
*rop = ro->next; /* remove this one */
while (rec && rec->f != u->faction)
rec = rec->next;
if (rec == NULL) {
rec = (recruitment *)malloc(sizeof(recruitment));
if (!rec) abort();
rec->f = u->faction;
rec->total = 0;
rec->assigned = 0;
rec->requests = NULL;
rec->next = recruits;
recruits = rec;
}
*total += qty;
rec->total += qty;
ro->next = rec->requests;
rec->requests = ro;
}
}
return recruits;
}
void add_recruits(unit * u, int number, int wanted)
{
region *r = u->region;
assert(number <= wanted);
if (number > 0) {
unit *unew;
char equipment[64];
int len;
if (u->number == 0) {
set_number(u, number);
u->hp = number * unit_max_hp(u);
unew = u;
}
else {
unew = create_unit(r, u->faction, number, u_race(u), 0, NULL, u);
}
len = snprintf(equipment, sizeof(equipment), "new_%s", u_race(u)->_name);
if (len > 0 && (size_t)len < sizeof(equipment)) {
equip_unit(unew, equipment);
}
if (unew != u) {
transfermen(unew, u, unew->number);
remove_unit(&r->units, unew);
}
}
if (number < wanted) {
ADDMSG(&u->faction->msgs, msg_message("recruit",
"unit region amount want", u, r, number, wanted));
}
}
static int any_recruiters(const struct race *rc, int qty)
{
return (int)(qty * 2 * rc->recruit_multi);
}
static int do_recruiting(recruitment * recruits, int available)
{
recruitment *rec;
int recruited = 0;
/* try to assign recruits to factions fairly */
while (available > 0) {
int n = 0;
int rest, mintotal = INT_MAX;
/* find smallest production */
for (rec = recruits; rec != NULL; rec = rec->next) {
int want = rec->total - rec->assigned;
if (want > 0) {
if (mintotal > want)
mintotal = want;
++n;
}
}
if (n == 0)
break;
if (mintotal * n > available) {
mintotal = available / n;
}
rest = available - mintotal * n;
/* assign size of smallest production for everyone if possible; in the end roll dice to assign
* small rest */
for (rec = recruits; rec != NULL; rec = rec->next) {
int want = rec->total - rec->assigned;
if (want > 0) {
int get = mintotal;
if (want > mintotal && rest < n && (rng_int() % n) < rest) {
--rest;
++get;
}
assert(get <= want);
available -= get;
rec->assigned += get;
}
}
}
/* do actual recruiting */
for (rec = recruits; rec != NULL; rec = rec->next) {
econ_request *req;
int get = rec->assigned;
for (req = rec->requests; req; req = req->next) {
unit *u = req->unit;
const race *rc = u_race(u); /* race is set in recruit() */
int number;
double multi = 2.0 * rc->recruit_multi;
number = (int)(get / multi);
if (number > req->qty) number = req->qty;
if (rc->recruitcost) {
int afford = get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT,
number * rc->recruitcost) / rc->recruitcost;
if (number > afford) number = afford;
}
if (u->number + number > UNIT_MAXSIZE) {
ADDMSG(&u->faction->msgs, msg_feedback(u, req->type.recruit.ord, "error_unit_size",
"maxsize", UNIT_MAXSIZE));
number = UNIT_MAXSIZE - u->number;
assert(number >= 0);
}
if (rc->recruitcost) {
use_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT,
rc->recruitcost * number);
}
if (u->number == 0 && fval(u, UFL_DEAD)) {
/* unit is empty, dead, and cannot recruit */
number = 0;
}
add_recruits(u, number, req->qty);
if (number > 0) {
int dec = (int)(number * multi);
if ((rc->ec_flags & ECF_REC_ETHEREAL) == 0) {
recruited += dec;
}
get -= dec;
}
}
}
return recruited;
}
void free_recruitments(recruitment * recruits)
{
while (recruits) {
recruitment *rec = recruits;
recruits = rec->next;
while (rec->requests) {
econ_request *req = rec->requests;
rec->requests = req->next;
free(req);
}
free(rec);
}
}
/* Rekrutierung */
static void expandrecruit(region * r, econ_request * recruitorders)
{
recruitment *recruits;
int orc_total = 0;
/* peasant limited: */
recruits = select_recruitment(&recruitorders, any_recruiters, &orc_total);
if (recruits) {
int orc_recruited, orc_peasants = rpeasants(r) * 2;
int orc_frac = orc_peasants / RECRUITFRACTION; /* anzahl orks. 2 ork = 1 bauer */
if (orc_total < orc_frac)
orc_frac = orc_total;
orc_recruited = do_recruiting(recruits, orc_frac);
assert(orc_recruited <= orc_frac);
rsetpeasants(r, (orc_peasants - orc_recruited) / 2);
free_recruitments(recruits);
}
/* no limit: */
recruits = select_recruitment(&recruitorders, any_recruiters, &orc_total);
if (recruits) {
int recruited, peasants = rpeasants(r) * 2;
recruited = do_recruiting(recruits, INT_MAX);
if (recruited > 0) {
rsetpeasants(r, (peasants - recruited) / 2);
}
free_recruitments(recruits);
}
assert(recruitorders == NULL);
}
static int recruit_cost(const faction * f, const race * rc)
{
if (is_monsters(f) || valid_race(f, rc)) {
return rc->recruitcost;
}
return -1;
}
message *can_recruit(unit *u, const race *rc, order *ord, int now)
{
region *r = u->region;
/* this is a very special case because the recruiting unit may be empty
* at this point and we have to look at the creating unit instead. This
* is done in cansee, which is called indirectly by is_guarded(). */
if (is_guarded(r, u)) {
return msg_error(u, ord, 70);
}
if (rc == get_race(RC_INSECT)) {
gamedate date;
get_gamedate(now, &date);
if (date.season == SEASON_WINTER && r->terrain != newterrain(T_DESERT)) {
bool usepotion = false;
unit *u2;
for (u2 = r->units; u2; u2 = u2->next) {
if (fval(u2, UFL_WARMTH)) {
usepotion = true;
break;
}
}
if (!usepotion) {
return msg_error(u, ord, 98);
}
}
/* in Gletschern, Eisbergen gar nicht rekrutieren */
if (r_insectstalled(r)) {
return msg_error(u, ord, 97);
}
}
if (is_cursed(r->attribs, &ct_riotzone)) {
/* Die Region befindet sich in Aufruhr */
return msg_error(u, ord, 237);
}
if (rc && !playerrace(rc)) {
return msg_error(u, ord, 139);
}
if (fval(u, UFL_HERO)) {
return msg_feedback(u, ord, "error_herorecruit", "");
}
if (has_skill(u, SK_MAGIC)) {
/* error158;de;{unit} in {region}: '{command}' - Magier arbeiten
* grundsaetzlich nur alleine! */
return msg_error(u, ord, 158);
}
return NULL;
}
static void recruit(unit * u, struct order *ord, econ_request ** recruitorders)
{
region *r = u->region;
econ_request *o;
int recruitcost = -1;
const faction *f = u->faction;
const struct race *rc = u_race(u);
int n;
message *msg;
init_order_depr(ord);
n = getint();
if (n <= 0) {
syntax_error(u, ord);
return;
}
if (u->number == 0) {
char token[128];
const char *str;
str = gettoken(token, sizeof(token));
if (str && str[0]) {
/* Monsters can RECRUIT 15 DRACOID
* also: secondary race */
rc = findrace(str, f->locale);
if (rc != NULL) {
recruitcost = recruit_cost(f, rc);
}
}
}
if (recruitcost < 0) {
rc = u_race(u);
recruitcost = recruit_cost(f, rc);
if (recruitcost < 0) {
recruitcost = INT_MAX;
}
}
if (recruitcost > 0) {
int pool;
plane *pl = getplane(r);
if (pl && (pl->flags & PFL_NORECRUITS)) {
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_pflnorecruit", ""));
return;
}
pool = get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT, recruitcost * n);
if (pool < recruitcost) {
cmistake(u, ord, 142, MSG_EVENT);
return;
}
pool /= recruitcost;
if (n > pool) n = pool;
}
if (!n) {
cmistake(u, ord, 142, MSG_EVENT);
return;
}
if (has_skill(u, SK_ALCHEMY)) {
if (faction_count_skill(u->faction, SK_ALCHEMY) + n > faction_skill_limit(u->faction, SK_ALCHEMY)) {
cmistake(u, ord, 156, MSG_EVENT);
return;
}
}
assert(rc);
msg = can_recruit(u, rc, ord, turn);
if (msg) {
add_message(&u->faction->msgs, msg);
msg_release(msg);
return;
}
u_setrace(u, rc);
u->wants = n;
o = (econ_request *)calloc(1, sizeof(econ_request));
if (!o) abort();
o->qty = n;
o->unit = u;
o->type.recruit.ord = ord;
addlist(recruitorders, o);
}
static void friendly_takeover(region * r, faction * f)
{
region_set_owner(r, f, turn);
@ -761,7 +390,6 @@ void maintain_buildings(region * r)
void economics(region * r)
{
unit *u;
econ_request *recruitorders = NULL;
/* Geben vor Selbstmord (doquit)! Hier alle unmittelbaren Befehle.
* Rekrutieren vor allen Einnahmequellen. Bewachen JA vor Steuern
@ -784,28 +412,10 @@ void economics(region * r)
}
}
}
/* RECRUIT orders */
if (rules_recruit < 0)
recruit_init();
for (u = r->units; u; u = u->next) {
order *ord;
if ((rules_recruit & RECRUIT_MERGE) || u->number == 0) {
for (ord = u->orders; ord; ord = ord->next) {
if (getkeyword(ord) == K_RECRUIT) {
recruit(u, ord, &recruitorders);
break;
}
}
}
}
if (recruitorders) {
expandrecruit(r, recruitorders);
}
remove_empty_units_in_region(r);
}
void destroy(region *r) {
unit *u;
for (u = r->units; u; u = u->next) {
order *ord = u->thisorder;
keyword_t kwd = getkeyword(ord);
@ -1490,7 +1100,7 @@ static void expandbuying(region * r, econ_request * buyorders)
unsigned int j;
for (j = 0; j != norders; j++) {
int price, multi;
ltype = g_requests[j]->type.trade.ltype;
ltype = g_requests[j]->data.trade.ltype;
trade = trades;
while (trade->type && trade->type != ltype)
++trade;
@ -1671,10 +1281,11 @@ static void buy(unit * u, econ_request ** buyorders, struct order *ord)
}
o = (econ_request *)calloc(1, sizeof(econ_request));
if (!o) abort();
o->type.trade.ltype = ltype; /* sollte immer gleich sein */
o->data.trade.ltype = ltype; /* sollte immer gleich sein */
o->unit = u;
o->qty = n;
o->type = ECON_BUY;
addlist(buyorders, o);
}
@ -1774,7 +1385,7 @@ static void expandselling(region * r, econ_request * sellorders, int limit)
int j;
for (j = 0; j != norders; j++) {
const luxury_type *search = NULL;
const luxury_type *ltype = g_requests[j]->type.trade.ltype;
const luxury_type *ltype = g_requests[j]->data.trade.ltype;
int multi = r_demand(r, ltype);
int i, price;
int use = 0;
@ -1981,7 +1592,7 @@ static bool sell(unit * u, econ_request ** sellorders, struct order *ord)
/* Wenn andere Einheiten das selbe verkaufen, muss ihr Zeug abgezogen
* werden damit es nicht zweimal verkauft wird: */
for (o = *sellorders; o; o = o->next) {
if (o->type.trade.ltype == ltype && o->unit->faction == u->faction) {
if (o->data.trade.ltype == ltype && o->unit->faction == u->faction) {
int fpool =
o->qty - get_pooled(o->unit, itype->rtype, GET_RESERVE, INT_MAX);
if (fpool < 0) fpool = 0;
@ -2022,7 +1633,8 @@ static bool sell(unit * u, econ_request ** sellorders, struct order *ord)
if (!o) abort();
o->unit = u;
o->qty = n;
o->type.trade.ltype = ltype;
o->type = ECON_SELL;
o->data.trade.ltype = ltype;
addlist(sellorders, o);
return unlimited;
@ -2349,36 +1961,39 @@ static void research_cmd(unit * u, struct order *ord)
}
}
static void expandentertainment(region * r)
static void expandentertainment(region * r, econ_request *ecbegin, econ_request *ecend, long total)
{
int m = entertainmoney(r);
econ_request *o;
for (o = &entertainers[0]; o != nextentertainer; ++o) {
double part = m / (double)entertaining;
unit *u = o->unit;
for (o = ecbegin; o != ecend; ++o) {
if (o->type == ECON_ENTERTAIN) {
double part = m / (double)total;
unit *u = o->unit;
if (entertaining <= m)
u->n = o->qty;
else
u->n = (int)(o->qty * part);
change_money(u, u->n);
rsetmoney(r, rmoney(r) - u->n);
m -= u->n;
entertaining -= o->qty;
if (total <= m)
u->n = o->qty;
else
u->n = (int)(o->qty * part);
change_money(u, u->n);
rsetmoney(r, rmoney(r) - u->n);
m -= u->n;
total -= o->qty;
/* Nur soviel PRODUCEEXP wie auch tatsaechlich gemacht wurde */
produceexp(u, SK_ENTERTAINMENT, (u->n < u->number) ? u->n : u->number);
add_income(u, IC_ENTERTAIN, o->qty, u->n);
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
/* Nur soviel PRODUCEEXP wie auch tatsaechlich gemacht wurde */
produceexp(u, SK_ENTERTAINMENT, (u->n < u->number) ? u->n : u->number);
add_income(u, IC_ENTERTAIN, o->qty, u->n);
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
}
}
assert(total == 0);
}
void entertain_cmd(unit * u, struct order *ord)
static int entertain_cmd(unit * u, struct order *ord, econ_request **io_req)
{
region *r = u->region;
int max_e;
econ_request *o;
int wants, max_e;
econ_request *req = *io_req;
static int entertainbase = 0;
static int entertainperlevel = 0;
keyword_t kwd;
@ -2395,39 +2010,38 @@ void entertain_cmd(unit * u, struct order *ord)
}
if (fval(u, UFL_WERE)) {
cmistake(u, ord, 58, MSG_INCOME);
return;
return 0;
}
if (!effskill(u, SK_ENTERTAINMENT, NULL)) {
cmistake(u, ord, 58, MSG_INCOME);
return;
return 0;
}
if (u->ship && is_guarded(r, u)) {
cmistake(u, ord, 69, MSG_INCOME);
return;
return 0;
}
if (is_cursed(r->attribs, &ct_depression)) {
cmistake(u, ord, 28, MSG_INCOME);
return;
return 0;
}
u->wants = u->number * (entertainbase + effskill(u, SK_ENTERTAINMENT, NULL)
* entertainperlevel);
max_e = getuint();
if (max_e != 0) {
if (u->wants > max_e) u->wants = max_e;
wants = getuint();
max_e = u->number * (entertainbase + effskill(u, SK_ENTERTAINMENT, NULL) * entertainperlevel);
if (wants > 0 && wants < max_e) {
max_e = wants;
}
o = nextentertainer++;
o->unit = u;
o->qty = u->wants;
entertaining += o->qty;
if (max_e > 0) {
add_request(req++, ECON_ENTERTAIN, u, ord, max_e);
*io_req = req;
}
return max_e;
}
/**
* \return number of working spaces taken by players
*/
static void
expandwork(region * r, econ_request * work_begin, econ_request * work_end, int maxwork)
expandwork(region * r, econ_request * work_begin, econ_request * work_end, int maxwork, long total)
{
int earnings;
/* n: verbleibende Einnahmen */
@ -2435,37 +2049,41 @@ expandwork(region * r, econ_request * work_begin, econ_request * work_end, int m
int jobs = maxwork;
int p_wage = wage(r, NULL, NULL, turn);
int money = rmoney(r);
econ_request *o;
if (total > 0 && !rule_autowork()) {
econ_request *o;
for (o = work_begin; o != work_end; ++o) {
unit *u = o->unit;
int workers;
for (o = work_begin; o != work_end; ++o) {
if (o->type == ECON_WORK) {
unit *u = o->unit;
int workers;
if (u->number == 0)
continue;
if (u->number == 0)
continue;
if (jobs >= working)
workers = u->number;
else {
int req = (u->number * jobs) % working;
workers = u->number * jobs / working;
if (req > 0 && rng_int() % working < req)
workers++;
if (jobs >= total)
workers = u->number;
else {
int req = (u->number * jobs) % total;
workers = u->number * jobs / total;
if (req > 0 && rng_int() % total < req)
workers++;
}
assert(workers >= 0);
u->n = workers * wage(u->region, u->faction, u_race(u), turn);
jobs -= workers;
assert(jobs >= 0);
change_money(u, u->n);
total -= o->unit->number;
add_income(u, IC_WORK, o->qty, u->n);
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
}
}
assert(workers >= 0);
u->n = workers * wage(u->region, u->faction, u_race(u), turn);
jobs -= workers;
assert(jobs >= 0);
change_money(u, u->n);
working -= o->unit->number;
add_income(u, IC_WORK, o->qty, u->n);
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
assert(total == 0);
}
if (jobs > rpeasants(r)) {
jobs = rpeasants(r);
}
@ -2478,34 +2096,35 @@ expandwork(region * r, econ_request * work_begin, econ_request * work_end, int m
rsetmoney(r, money + earnings);
}
static int do_work(unit * u, order * ord, econ_request * o)
static int work_cmd(unit * u, order * ord, econ_request ** io_req)
{
if (playerrace(u_race(u))) {
econ_request *req = *io_req;
region *r = u->region;
int w;
if (fval(u, UFL_WERE)) {
if (ord)
if (ord) {
cmistake(u, ord, 313, MSG_INCOME);
return -1;
}
return 0;
}
if (u->ship && is_guarded(r, u)) {
if (ord)
if (ord) {
cmistake(u, ord, 69, MSG_INCOME);
return -1;
}
return 0;
}
w = wage(r, u->faction, u_race(u), turn);
u->wants = u->number * w;
o->unit = u;
o->qty = u->number * w;
working += u->number;
return 0;
add_request(req++, ECON_WORK, u, ord, w * u->number);
*io_req = req;
return u->number;
}
else if (ord && !is_monsters(u->faction)) {
ADDMSG(&u->faction->msgs,
msg_feedback(u, ord, "race_cantwork", "race", u_race(u)));
}
return -1;
return 0;
}
static void expandloot(region * r, econ_request * lootorders)
@ -2637,6 +2256,7 @@ void tax_cmd(unit * u, struct order *ord, econ_request ** taxorders)
o = (econ_request *)calloc(1, sizeof(econ_request));
if (!o) abort();
o->qty = u->wants / TAXFRACTION;
o->type = ECON_TAX;
o->unit = u;
addlist(taxorders, o);
return;
@ -2703,29 +2323,30 @@ void loot_cmd(unit * u, struct order *ord, econ_request ** lootorders)
o = (econ_request *)calloc(1, sizeof(econ_request));
if (!o) abort();
o->qty = u->wants / TAXFRACTION;
o->type = ECON_LOOT;
o->unit = u;
addlist(lootorders, o);
return;
}
#define MAX_WORKERS 512
void auto_work(region * r)
{
econ_request workers[MAX_WORKERS];
econ_request *nextworker = workers;
econ_request *nextrequest = econ_requests;
unit *u;
long total = 0;
for (u = r->units; u; u = u->next) {
if (!(u->flags & UFL_LONGACTION) && !is_monsters(u->faction)) {
if (do_work(u, NULL, nextworker) == 0) {
assert(nextworker - workers < MAX_WORKERS);
++nextworker;
int work = work_cmd(u, NULL, &nextrequest);
if (work) {
total += work;
assert(nextrequest - econ_requests <= MAX_REQUESTS);
}
}
}
if (nextworker != workers) {
expandwork(r, workers, nextworker, region_maxworkers(r));
if (nextrequest != econ_requests) {
expandwork(r, econ_requests, nextrequest, region_maxworkers(r), total);
}
}
@ -2770,22 +2391,13 @@ static void peasant_taxes(region * r)
}
}
static bool rule_auto_taxation(void)
{
return config_get_int("rules.economy.taxation", 0) != 0;
}
static bool rule_autowork(void) {
return config_get_int("work.auto", 0) != 0;
}
void produce(struct region *r)
{
econ_request workers[MAX_WORKERS];
econ_request *taxorders, *lootorders, *sellorders, *stealorders, *buyorders;
unit *u;
bool limited = true;
econ_request *nextworker = workers;
long entertaining = 0, working = 0;
econ_request *nextrequest = econ_requests;
static int bt_cache;
static const struct building_type *caravan_bt;
static int rc_cache;
@ -2820,9 +2432,6 @@ void produce(struct region *r)
buyorders = 0;
sellorders = 0;
working = 0;
nextentertainer = &entertainers[0];
entertaining = 0;
taxorders = 0;
lootorders = 0;
stealorders = 0;
@ -2879,13 +2488,17 @@ void produce(struct region *r)
switch (todo) {
case K_ENTERTAIN:
entertain_cmd(u, u->thisorder);
entertaining += entertain_cmd(u, u->thisorder, &nextrequest);
assert(nextrequest - econ_requests <= MAX_REQUESTS);
break;
case K_WORK:
if (!rule_autowork() && do_work(u, u->thisorder, nextworker) == 0) {
assert(nextworker - workers < MAX_WORKERS);
++nextworker;
if (!rule_autowork()) {
int work = work_cmd(u, u->thisorder, &nextrequest);
if (work != 0) {
working += work;
assert(nextrequest - econ_requests <= MAX_REQUESTS);
}
}
break;
@ -2927,11 +2540,11 @@ void produce(struct region *r)
* Befehlen, die den Bauern mehr Geld geben, damit man aus den Zahlen der
* letzten Runde berechnen kann, wieviel die Bauern fuer Unterhaltung
* auszugeben bereit sind. */
if (entertaining)
expandentertainment(r);
if (!rule_autowork()) {
expandwork(r, workers, nextworker, region_maxworkers(r));
if (entertaining > 0) {
expandentertainment(r, econ_requests, nextrequest, entertaining);
}
expandwork(r, econ_requests, nextrequest, region_maxworkers(r), working);
if (taxorders) {
expandtax(r, taxorders);
free_requests(taxorders);

View File

@ -55,10 +55,17 @@ extern "C" {
struct econ_request *next;
struct unit *unit;
int qty;
enum econ_type {
ECON_LIST,
ECON_ENTERTAIN,
ECON_WORK,
ECON_TAX,
ECON_LOOT,
ECON_BUY,
ECON_SELL,
ECON_STEAL,
} type;
union {
struct {
struct order *ord;
} recruit;
struct {
int no;
bool goblin; /* stealing */
@ -66,18 +73,19 @@ extern "C" {
struct {
const struct luxury_type *ltype; /* trading */
} trade;
} type;
} data;
} econ_request;
int expand_production(struct region * r, struct econ_request * requests, struct econ_request ***results);
int income(const struct unit *u);
int entertainmoney(const struct region *r);
void economics(struct region *r);
void destroy(struct region *r);
void produce(struct region *r);
void auto_work(struct region *r);
int expand_production(struct region * r, struct econ_request * requests, struct econ_request ***results);
typedef enum income_t { IC_WORK, IC_ENTERTAIN, IC_TAX, IC_TRADE, IC_TRADETAX, IC_STEAL, IC_MAGIC, IC_LOOT } income_t;
void add_income(struct unit * u, income_t type, int want, int qty);
@ -95,9 +103,6 @@ extern "C" {
void steal_cmd(struct unit * u, struct order *ord, struct econ_request ** stealorders);
void expandstealing(struct region * r, struct econ_request * stealorders);
struct message *can_recruit(struct unit *u, const struct race *rc, struct order *ord, int now);
void add_recruits(struct unit * u, int number, int wanted);
#ifdef __cplusplus
}
#endif

View File

@ -3,6 +3,7 @@
#endif
#include <kernel/config.h>
#include "economy.h"
#include "recruit.h"
#include <util/message.h>
#include <kernel/building.h>
@ -162,7 +163,7 @@ static void test_heroes_dont_recruit(CuTest * tc) {
fset(u, UFL_HERO);
unit_addorder(u, create_order(K_RECRUIT, default_locale, "1"));
economics(u->region);
recruit(u->region);
CuAssertIntEquals(tc, 1, u->number);
CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "error_herorecruit"));
@ -178,7 +179,7 @@ static void test_normals_recruit(CuTest * tc) {
u = create_recruiter();
unit_addorder(u, create_order(K_RECRUIT, default_locale, "1"));
economics(u->region);
recruit(u->region);
CuAssertIntEquals(tc, 2, u->number);

View File

@ -1,6 +1,5 @@
/*
Copyright (c) 1998-2015,
Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>
@ -216,6 +216,7 @@ faction *addfaction(const char *email, const char *password,
const struct race * frace, const struct locale * loc)
{
faction *f = calloc(1, sizeof(faction));
const char *fname;
char buf[128];
if (!f) abort();
@ -255,7 +256,8 @@ faction *addfaction(const char *email, const char *password,
addlist(&factions, f);
fhash(f);
slprintf(buf, sizeof(buf), "%s %s", LOC(loc, "factiondefault"), itoa36(f->no));
fname = LOC(loc, "factiondefault");
slprintf(buf, sizeof(buf), "%s %s", fname ? fname : "faction", itoa36(f->no));
f->name = str_strdup(buf);
if (!f->race) {
@ -300,7 +302,7 @@ unit *addplayer(region * r, faction * f)
} while (rc == NULL || urc == RC_DAEMON || !playerrace(rc));
u->irace = rc;
}
f->lastorders = 0;
f->lastorders = turn;
return u;
}

View File

@ -344,6 +344,7 @@ static void test_addplayer(CuTest *tc) {
CuAssertPtrNotNull(tc, u);
CuAssertPtrEquals(tc, r, u->region);
CuAssertPtrEquals(tc, f, u->faction);
CuAssertIntEquals(tc, turn, u->faction->lastorders);
CuAssertIntEquals(tc, i_get(u->items, itype), 10);
CuAssertPtrNotNull(tc, u->orders);
CuAssertIntEquals(tc, K_WORK, getkeyword(u->orders));

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>
@ -625,7 +625,11 @@ void rsetpeasants(region * r, int value)
assert(r->land || value==0);
assert(value >= 0);
if (r->land) {
r->land->peasants = value;
if (value > USHRT_MAX) {
log_warning("region %s cannot have %d peasants.", regionname(r, NULL), value);
value = USHRT_MAX;
}
r->land->peasants = (unsigned short)value;
}
}
@ -639,7 +643,11 @@ void rsethorses(const region * r, int value)
assert(r->land || value==0);
assert(value >= 0);
if (r->land) {
r->land->horses = value;
if (value > USHRT_MAX) {
log_warning("region %s cannot have %d horses.", regionname(r, NULL), value);
value = USHRT_MAX;
}
r->land->horses = (unsigned short)value;
}
}
@ -659,15 +667,18 @@ void rsetmoney(region * r, int value)
int rherbs(const region *r)
{
return r->land?r->land->herbs:0;
return r->land ? r->land->herbs : 0;
}
void rsetherbs(region *r, int value)
{
assert(r->land || value==0);
assert(value >= 0 && value<=SHRT_MAX);
if (r->land) {
r->land->herbs = value;
if (value > USHRT_MAX) {
log_warning("region %s cannot have %d herbs.", regionname(r, NULL), value);
value = USHRT_MAX;
}
r->land->herbs = (unsigned short)value;
}
}
@ -1478,7 +1489,7 @@ const char *region_getname(const region * r)
int region_get_morale(const region * r)
{
if (r->land) {
assert(r->land->morale >= 0 && r->land->morale <= MORALE_MAX);
assert(r->land->morale <= MORALE_MAX);
return r->land->morale;
}
return -1;
@ -1487,11 +1498,11 @@ int region_get_morale(const region * r)
void region_set_morale(region * r, int morale, int turn)
{
if (r->land) {
r->land->morale = morale;
r->land->morale = (unsigned short)morale;
if (turn >= 0 && r->land->ownership) {
r->land->ownership->morale_turn = turn;
}
assert(r->land->morale >= 0 && r->land->morale <= MORALE_MAX);
assert(r->land->morale <= MORALE_MAX);
}
}

View File

@ -97,12 +97,12 @@ extern "C" {
char *display;
demand *demands;
const struct item_type *herbtype;
int herbs;
int morale;
unsigned short horses;
unsigned short herbs;
unsigned short peasants;
unsigned short morale;
short newpeasants;
int trees[3]; /* 0 -> seeds, 1 -> shoots, 2 -> trees */
int horses;
int peasants;
int newpeasants;
int money;
struct region_owner *ownership;
} land_region;

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>
@ -318,7 +318,10 @@ int crew_skill(const ship *sh) {
for (u = sh->region->units; u; u = u->next) {
if (u->ship == sh) {
n += effskill(u, SK_SAILING, NULL) * u->number;
int es = effskill(u, SK_SAILING, NULL);
if (es >= sh->type->minskill) {
n += es * u->number;
}
}
}
return n;

View File

@ -384,27 +384,6 @@ static void test_stype_defaults(CuTest *tc) {
test_teardown();
}
static void test_crew_skill(CuTest *tc) {
ship *sh;
region *r;
struct faction *f;
int i;
test_setup();
test_create_world();
r = test_create_region(0, 0, NULL);
f = test_create_faction(NULL);
assert(r && f);
sh = test_create_ship(r, st_find("boat"));
for (i = 0; i != 4; ++i) {
unit * u = test_create_unit(f, r);
set_level(u, SK_SAILING, 5);
u->ship = sh;
}
CuAssertIntEquals(tc, 20, crew_skill(sh));
test_teardown();
}
static ship *setup_ship(void) {
region *r;
ship_type *stype;
@ -651,6 +630,35 @@ static void test_shipspeed_max_range(CuTest *tc) {
test_teardown();
}
static void test_crew_skill(CuTest *tc) {
ship_type *stype;
ship * sh;
unit *u;
region *r;
test_setup();
stype = test_create_shiptype("kayak");
CuAssertIntEquals(tc, 1, stype->minskill);
r = test_create_ocean(0, 0);
sh = test_create_ship(r, stype);
CuAssertIntEquals(tc, 0, crew_skill(sh));
u = test_create_unit(test_create_faction(NULL), r);
set_level(u, SK_SAILING, 1);
CuAssertIntEquals(tc, 0, crew_skill(sh));
u_set_ship(u, sh);
set_level(u, SK_SAILING, 1);
CuAssertIntEquals(tc, 1, crew_skill(sh));
set_number(u, 10);
CuAssertIntEquals(tc, 10, crew_skill(sh));
stype->minskill = 2;
CuAssertIntEquals(tc, 0, crew_skill(sh));
set_level(u, SK_SAILING, 2);
CuAssertIntEquals(tc, 20, crew_skill(sh));
set_level(u, SK_SAILING, 3);
CuAssertIntEquals(tc, 30, crew_skill(sh));
test_teardown();
}
CuSuite *get_ship_suite(void)
{
CuSuite *suite = CuSuiteNew();

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -59,6 +59,7 @@ struct weapon_type;
typedef enum {
seen_none,
seen_neighbour,
seen_lighthouse_land,
seen_lighthouse,
seen_travel,
seen_unit,

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,6 +1,5 @@
/*
Copyright (c) 1998-2014,
Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>
@ -36,6 +35,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include "monsters.h"
#include "move.h"
#include "randenc.h"
#include "recruit.h"
#include "renumber.h"
#include "spy.h"
#include "study.h"
@ -281,14 +281,13 @@ static void live(region * r)
#define MAX_EMIGRATION(p) ((p)/MAXDIRECTIONS)
#define MAX_IMMIGRATION(p) ((p)*2/3)
static void calculate_emigration(region * r)
void peasant_migration(region * r)
{
int i;
int maxp = region_maxworkers(r);
int rp = rpeasants(r);
int max_immigrants = MAX_IMMIGRATION(maxp - rp);
if (volcano_module()) {
static int terrain_cache;
static const terrain_type *t_volcano;
@ -314,8 +313,14 @@ static void calculate_emigration(region * r)
if (max_emigration > 0) {
if (max_emigration > max_immigrants) max_emigration = max_immigrants;
r->land->newpeasants += max_emigration;
rc->land->newpeasants -= max_emigration;
if (max_emigration + r->land->newpeasants > USHRT_MAX) {
max_emigration = USHRT_MAX - r->land->newpeasants;
}
if (max_emigration + rc->land->newpeasants > USHRT_MAX) {
max_emigration = USHRT_MAX - rc->land->newpeasants;
}
r->land->newpeasants += (short)max_emigration;
rc->land->newpeasants -= (short)max_emigration;
max_immigrants -= max_emigration;
}
}
@ -779,6 +784,7 @@ void immigration(void)
/* FIXME: kann ernsthaft abs(newpeasants) > rpeasants(r) sein? */
if (rp < 0) rp = 0;
rsetpeasants(r, rp);
r->land->newpeasants = 0;
}
/* Genereate some (0-6 depending on the income) peasants out of nothing */
/* if less than 50 are in the region and there is space and no monster or demon units in the region */
@ -878,7 +884,7 @@ void demographics(void)
/* Seuchen erst nachdem die Bauern sich vermehrt haben
* und gewandert sind */
calculate_emigration(r);
peasant_migration(r);
peasants(r, peasant_rules);
if (r->age > 20) {
@ -947,6 +953,8 @@ void transfer_faction(faction *fsrc, faction *fdst) {
int skill_count[MAXSKILLS];
int skill_limit[MAXSKILLS];
assert(fsrc != fdst);
for (sk = 0; sk != MAXSKILLS; ++sk) {
skill_limit[sk] = faction_skill_limit(fdst, sk);
}
@ -963,7 +971,10 @@ void transfer_faction(faction *fsrc, faction *fdst) {
}
}
for (u = fsrc->units; u != NULL; u = u->nextF) {
u = fsrc->units;
while (u) {
unit *unext = u->nextF;
if (u_race(u) == fdst->race) {
u->flags &= ~UFL_HERO;
if (give_unit_allowed(u) == 0) {
@ -978,12 +989,15 @@ void transfer_faction(faction *fsrc, faction *fdst) {
}
}
if (i != u->skill_size) {
u = u->nextF;
continue;
}
}
ADDMSG(&fdst->msgs, msg_message("transfer_unit", "unit", u));
u_setfaction(u, fdst);
}
}
u = unext;
}
}
@ -1003,6 +1017,7 @@ int quit_cmd(unit * u, struct order *ord)
param_t p;
p = getparam(f->locale);
if (p == P_FACTION) {
#ifdef QUIT_WITH_TRANSFER
faction *f2 = getfaction();
if (f2 == NULL) {
cmistake(u, ord, 66, MSG_EVENT);
@ -1015,17 +1030,23 @@ int quit_cmd(unit * u, struct order *ord)
else {
unit *u2;
for (u2 = u->region->units; u2; u2 = u2->next) {
if (u2->faction == f2 && ucontact(u2, u)) {
transfer_faction(u->faction, u2->faction);
break;
if (u2->faction == f2) {
if (ucontact(u2, u)) {
transfer_faction(u->faction, u2->faction);
break;
}
}
}
if (u2 == NULL) {
/* no target unit found */
cmistake(u, ord, 0, MSG_EVENT);
cmistake(u, ord, 40, MSG_EVENT);
flags = 0;
}
}
#else
log_error("faction %s: QUIT FACTION is disabled.", factionname(f));
flags = 0;
#endif
}
}
f->flags |= flags;
@ -2152,8 +2173,13 @@ int banner_cmd(unit * u, struct order *ord)
init_order_depr(ord);
s = getstrtoken();
faction_setbanner(u->faction, s);
ADDMSG(&u->faction->msgs, msg_message("changebanner", "value", s));
if (!s || !s[0]) {
cmistake(u, ord, 125, MSG_EVENT);
}
else {
faction_setbanner(u->faction, s);
ADDMSG(&u->faction->msgs, msg_message("changebanner", "value", s));
}
return 0;
}
@ -3872,7 +3898,6 @@ void init_processor(void)
add_proc_order(p, K_GROUP, group_cmd, 0, NULL);
p += 10;
add_proc_order(p, K_QUIT, quit_cmd, 0, NULL);
add_proc_order(p, K_URSPRUNG, origin_cmd, 0, NULL);
add_proc_order(p, K_ALLY, ally_cmd, 0, NULL);
add_proc_order(p, K_PREFIX, prefix_cmd, 0, NULL);
@ -3896,6 +3921,7 @@ void init_processor(void)
p += 10; /* all claims must be done before we can USE */
add_proc_region(p, enter_1, "Betreten (1. Versuch)"); /* for GIVE CONTROL */
add_proc_order(p, K_USE, use_cmd, 0, "Benutzen");
add_proc_order(p, K_QUIT, quit_cmd, 0, "Stirb");
p += 10; /* in case it has any effects on alliance victories */
add_proc_order(p, K_GIVE, give_control_cmd, 0, "GIB KOMMANDO");
@ -3923,11 +3949,14 @@ void init_processor(void)
if (rule_force_leave(FORCE_LEAVE_ALL)) {
add_proc_region(p, do_force_leave, "kick non-allies out of buildings/ships");
}
add_proc_region(p, economics, "Zerstoeren, Geben, Rekrutieren, Vergessen");
add_proc_region(p, economics, "Geben, Vergessen");
add_proc_region(p+1, recruit, "Rekrutieren");
add_proc_region(p+2, destroy, "Zerstoeren");
/* all recruitment must be finished before we can calculate
* promotion cost of ability */
p += 10;
add_proc_global(p, quit, "Sterben");
add_proc_order(p, K_PROMOTION, promotion_cmd, 0, "Heldenbefoerderung");
p += 10;
@ -3936,9 +3965,6 @@ void init_processor(void)
}
add_proc_postregion(p, maintain_buildings, "Gebaeudeunterhalt");
p += 10; /* QUIT fuer sich alleine */
add_proc_global(p, quit, "Sterben");
if (!keyword_disabled(K_CAST)) {
p += 10;
add_proc_global(p, magic, "Zaubern");

View File

@ -93,6 +93,8 @@ extern "C" {
int reserve_cmd(struct unit *u, struct order *ord);
int reserve_self(struct unit *u, struct order *ord);
int claim_cmd(struct unit *u, struct order *ord);
void transfer_faction(struct faction *fsrc, struct faction *fdst);
void peasant_migration(struct region * r);
void nmr_warnings(void);
bool nmr_death(const struct faction * f, int turn, int timeout);
@ -119,6 +121,8 @@ extern "C" {
enum param_t findparam_ex(const char *s, const struct locale * lang);
#define QUIT_WITH_TRANSFER
#ifdef __cplusplus
}
#endif

View File

@ -1248,6 +1248,68 @@ static void test_ally_cmd_errors(CuTest *tc) {
test_teardown();
}
static void test_banner_cmd(CuTest *tc) {
unit *u;
faction *f;
order *ord;
test_setup();
mt_create_error(125);
mt_create_va(mt_new("changebanner", NULL), "value:string", MT_NEW_END);
u = test_create_unit(f = test_create_faction(NULL), test_create_region(0, 0, NULL));
ord = create_order(K_BANNER, f->locale, "Hodor!");
banner_cmd(u, ord);
CuAssertStrEquals(tc, "Hodor!", faction_getbanner(f));
CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "changebanner"));
free_order(ord);
test_clear_messages(f);
ord = create_order(K_BANNER, f->locale, NULL);
banner_cmd(u, ord);
CuAssertStrEquals(tc, "Hodor!", faction_getbanner(f));
CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "error125"));
free_order(ord);
test_clear_messages(f);
test_teardown();
}
static void test_email_cmd(CuTest *tc) {
unit *u;
faction *f;
order *ord;
test_setup();
mt_create_error(85);
mt_create_va(mt_new("changemail", NULL), "value:string", MT_NEW_END);
mt_create_va(mt_new("changemail_invalid", NULL), "value:string", MT_NEW_END);
u = test_create_unit(f = test_create_faction(NULL), test_create_region(0, 0, NULL));
ord = create_order(K_EMAIL, f->locale, "hodor@example.com");
email_cmd(u, ord);
CuAssertStrEquals(tc, "hodor@example.com", f->email);
CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "changemail"));
free_order(ord);
test_clear_messages(f);
ord = create_order(K_EMAIL, f->locale, "example.com");
email_cmd(u, ord);
CuAssertStrEquals(tc, "hodor@example.com", f->email);
CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "changemail_invalid"));
free_order(ord);
test_clear_messages(f);
ord = create_order(K_EMAIL, f->locale, NULL);
email_cmd(u, ord);
CuAssertStrEquals(tc, "hodor@example.com", f->email);
CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "error85"));
free_order(ord);
test_clear_messages(f);
test_teardown();
}
static void test_name_cmd(CuTest *tc) {
unit *u;
faction *f;
@ -1869,6 +1931,53 @@ static void test_long_order_on_ocean(CuTest *tc) {
test_teardown();
}
static void test_peasant_migration(CuTest *tc) {
region *r1, *r2;
int rmax;
test_setup();
config_set("rules.economy.repopulate_maximum", "0");
r1 = test_create_plain(0, 0);
rsettrees(r1, 0, 0);
rsettrees(r1, 1, 0);
rsettrees(r1, 2, 0);
rmax = region_maxworkers(r1);
r2 = test_create_plain(0, 1);
rsettrees(r2, 0, 0);
rsettrees(r2, 1, 0);
rsettrees(r2, 2, 0);
rsetpeasants(r1, rmax - 90);
rsetpeasants(r2, rmax);
peasant_migration(r1);
immigration();
CuAssertIntEquals(tc, rmax - 90, rpeasants(r1));
CuAssertIntEquals(tc, rmax, rpeasants(r2));
rsetpeasants(r1, rmax - 90);
rsetpeasants(r2, rmax + 60);
peasant_migration(r1);
immigration();
CuAssertIntEquals(tc, rmax - 80, rpeasants(r1));
CuAssertIntEquals(tc, rmax + 50, rpeasants(r2));
rsetpeasants(r1, rmax - 6); /* max 4 immigrants. */
rsetpeasants(r2, rmax + 60); /* max 10 emigrants. */
peasant_migration(r1);
immigration(); /* 4 peasants will move */
CuAssertIntEquals(tc, rmax - 2, rpeasants(r1));
CuAssertIntEquals(tc, rmax + 56, rpeasants(r2));
rsetpeasants(r1, rmax - 6); /* max 4 immigrants. */
rsetpeasants(r2, rmax + 6); /* max 1 emigrant. */
peasant_migration(r1);
immigration(); /* 4 peasants will move */
CuAssertIntEquals(tc, rmax - 5, rpeasants(r1));
CuAssertIntEquals(tc, rmax + 5, rpeasants(r2));
test_teardown();
}
static void test_quit(CuTest *tc) {
faction *f;
unit *u;
@ -1891,6 +2000,7 @@ static void test_quit(CuTest *tc) {
test_teardown();
}
#ifdef QUIT_WITH_TRANSFER
/**
* Gifting units to another faction upon voluntary death (QUIT).
*/
@ -2012,6 +2122,30 @@ static void test_quit_transfer_hero(CuTest *tc) {
test_teardown();
}
static void test_transfer_faction(CuTest *tc) {
faction *f1, *f2;
unit *u1, *u2, *u3, *u4;
region *r;
test_setup();
r = test_create_plain(0, 0);
f1 = test_create_faction(NULL);
f2 = test_create_faction(NULL);
u1 = test_create_unit(f1, r);
u2 = test_create_unit(f1, r);
u_setrace(u2, test_create_race("smurf"));
u3 = test_create_unit(f2, r);
u4 = test_create_unit(f1, r);
transfer_faction(f1, f2);
CuAssertPtrEquals(tc, f2, u1->faction);
CuAssertPtrEquals(tc, f1, u2->faction);
CuAssertPtrEquals(tc, f2, u3->faction);
CuAssertPtrEquals(tc, f2, u4->faction);
test_teardown();
}
#endif
CuSuite *get_laws_suite(void)
{
CuSuite *suite = CuSuiteNew();
@ -2021,6 +2155,8 @@ CuSuite *get_laws_suite(void)
SUITE_ADD_TEST(suite, test_nmr_warnings);
SUITE_ADD_TEST(suite, test_ally_cmd);
SUITE_ADD_TEST(suite, test_name_cmd);
SUITE_ADD_TEST(suite, test_banner_cmd);
SUITE_ADD_TEST(suite, test_email_cmd);
SUITE_ADD_TEST(suite, test_name_cmd_2274);
SUITE_ADD_TEST(suite, test_name_unit);
SUITE_ADD_TEST(suite, test_name_region);
@ -2088,10 +2224,14 @@ CuSuite *get_laws_suite(void)
SUITE_ADD_TEST(suite, test_long_orders);
SUITE_ADD_TEST(suite, test_long_order_on_ocean);
SUITE_ADD_TEST(suite, test_quit);
SUITE_ADD_TEST(suite, test_peasant_migration);
#ifdef QUIT_WITH_TRANSFER
SUITE_ADD_TEST(suite, test_quit_transfer);
SUITE_ADD_TEST(suite, test_quit_transfer_limited);
SUITE_ADD_TEST(suite, test_quit_transfer_migrants);
SUITE_ADD_TEST(suite, test_quit_transfer_hero);
SUITE_ADD_TEST(suite, test_transfer_faction);
#endif
return suite;
}

View File

@ -1,6 +1,5 @@
/*
Copyright (c) 1998-2014,
Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2018, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>
@ -193,7 +193,7 @@ static int parse_args(int argc, char **argv)
else if (argi[1] == '-') { /* long format */
if (strcmp(argi + 2, "version") == 0) {
printf("Eressea version %s, "
"Copyright (C) 2018 Enno Rehling et al.\n",
"Copyright (C) 2019 Enno Rehling et al.\n",
eressea_version());
return 1;
#ifdef USE_CURSES

View File

@ -565,8 +565,8 @@ int autoseed(newfaction ** players, int nsize, int max_agediff)
nfp = &nextf->next;
while (*nfp) {
newfaction *nf = *nfp;
if (strcmp(nextf->email, nf->email) == 0) {
log_warning("Duplicate email %s\n", nf->email?nf->email:"");
if (nf->email && nextf->email && strcmp(nextf->email, nf->email) == 0) {
log_warning("Duplicate email %s\n", nf->email ? nf->email : "");
*nfp = nf->next;
free_newfaction(nf);
}

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
* Eressea PB(E)M host Copyright (C) 1998-2015
* Eressea PB(E)M host Copyright (C) 1998-2019
* Christian Schlittchen (corwin@amber.kn-bremen.de)
* Katja Zedel (katze@felidae.kn-bremen.de)
* Henning Peters (faroul@beyond.kn-bremen.de)
@ -730,6 +730,17 @@ static order *plan_dragon(unit * u)
return long_order;
}
static void monster_cannibalism(unit *u) {
unit *u2;
for (u2 = u->next; u2; u2 = u2->next) {
if (u2->_race == u->_race) {
stats_count("monsters.cannibalism", u2->number);
u2->number = 0;
}
}
}
void plan_monsters(faction * f)
{
region *r;
@ -748,14 +759,20 @@ void plan_monsters(faction * f)
bool can_move = true;
/* Ab hier nur noch Befehle fuer NPC-Einheiten. */
if (u->faction!=f)
if (u->faction != f || u->number <= 0) {
continue;
}
/* Parteitarnung von Monstern ist doof: */
if (fval(u, UFL_ANON_FACTION)) {
u->flags &= ~UFL_ANON_FACTION;
}
if (rc->splitsize < 10) {
/* hermit-type monsters eat each other */
monster_cannibalism(u);
}
if (skill_enabled(SK_PERCEPTION)) {
/* Monster bekommen jede Runde ein paar Tage Wahrnehmung dazu */
produceexp(u, SK_PERCEPTION, u->number);

View File

@ -1,6 +1,5 @@
/*
Copyright (c) 1998-2014,
Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,6 +1,5 @@
/*
Copyright (c) 1998-2018,
Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

View File

@ -1,7 +1,5 @@
/*
*
*
* Eressea PB(E)M host Copyright (C) 1998-2015
* Eressea PB(E)M host Copyright (C) 1998-2019
* Christian Schlittchen (corwin@amber.kn-bremen.de)
* Katja Zedel (katze@felidae.kn-bremen.de)
* Henning Peters (faroul@beyond.kn-bremen.de)

View File

@ -1,5 +1,5 @@
/*
* Eressea PB(E)M host Copyright (C) 1998-2015
* Eressea PB(E)M host Copyright (C) 1998-2019
* Christian Schlittchen (corwin@amber.kn-bremen.de)
* Katja Zedel (katze@felidae.kn-bremen.de)
* Henning Peters (faroul@beyond.kn-bremen.de)

View File

@ -1,5 +1,5 @@
/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>

506
src/recruit.c Normal file
View File

@ -0,0 +1,506 @@
/*
Copyright (c) 1998-2019,
Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**/
#ifdef _MSC_VER
#include <platform.h>
#endif
#include "recruit.h"
#include "alchemy.h"
#include "direction.h"
#include "donations.h"
#include "guard.h"
#include "give.h"
#include "laws.h"
#include "randenc.h"
#include "spy.h"
#include "study.h"
#include "move.h"
#include "monsters.h"
#include "morale.h"
#include "reports.h"
#include <attributes/reduceproduction.h>
#include <attributes/racename.h>
#include <spells/buildingcurse.h>
#include <spells/regioncurse.h>
#include <spells/unitcurse.h>
/* kernel includes */
#include "kernel/ally.h"
#include "kernel/attrib.h"
#include "kernel/building.h"
#include "kernel/calendar.h"
#include "kernel/config.h"
#include "kernel/curse.h"
#include "kernel/equipment.h"
#include "kernel/event.h"
#include "kernel/faction.h"
#include "kernel/item.h"
#include "kernel/messages.h"
#include "kernel/order.h"
#include "kernel/plane.h"
#include "kernel/pool.h"
#include "kernel/race.h"
#include "kernel/region.h"
#include "kernel/resources.h"
#include "kernel/ship.h"
#include "kernel/terrain.h"
#include "kernel/terrainid.h"
#include "kernel/unit.h"
/* util includes */
#include <util/base36.h>
#include <util/goodies.h>
#include <util/language.h>
#include <util/lists.h>
#include <util/log.h>
#include "util/param.h"
#include <util/parser.h>
#include <util/rng.h>
/* libs includes */
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <limits.h>
#define RECRUIT_MERGE 1
static int rules_recruit = -1;
typedef struct recruit_request {
struct recruit_request *next;
struct unit *unit;
struct order *ord;
int qty;
} recruit_request;
typedef struct recruitment {
struct recruitment *next;
faction *f;
recruit_request *requests;
int total, assigned;
} recruitment;
static void recruit_init(void)
{
if (rules_recruit < 0) {
rules_recruit = 0;
if (config_get_int("recruit.allow_merge", 1)) {
rules_recruit |= RECRUIT_MERGE;
}
}
}
static void free_requests(recruit_request *requests) {
while (requests) {
recruit_request *req = requests->next;
free(requests);
requests = req;
}
}
void free_recruitments(recruitment * recruits)
{
while (recruits) {
recruitment *rec = recruits;
recruits = rec->next;
free_requests(rec->requests);
free(rec);
}
}
/** Creates a list of recruitment structs, one for each faction. Adds every quantifyable production
* to the faction's struct and to total.
*/
static recruitment *select_recruitment(recruit_request ** rop,
int(*quantify) (const struct race *, int), int *total)
{
recruitment *recruits = NULL;
while (*rop) {
recruitment *rec = recruits;
recruit_request *ro = *rop;
unit *u = ro->unit;
const race *rc = u_race(u);
int qty = quantify(rc, ro->qty);
if (qty < 0) {
rop = &ro->next; /* skip this one */
}
else {
*rop = ro->next; /* remove this one */
while (rec && rec->f != u->faction)
rec = rec->next;
if (rec == NULL) {
rec = (recruitment *)malloc(sizeof(recruitment));
if (!rec) abort();
rec->f = u->faction;
rec->total = 0;
rec->assigned = 0;
rec->requests = NULL;
rec->next = recruits;
recruits = rec;
}
*total += qty;
rec->total += qty;
ro->next = rec->requests;
rec->requests = ro;
}
}
return recruits;
}
void add_recruits(unit * u, int number, int wanted)
{
region *r = u->region;
assert(number <= wanted);
if (number > 0) {
unit *unew;
char equipment[64];
int len;
if (u->number == 0) {
set_number(u, number);
u->hp = number * unit_max_hp(u);
unew = u;
}
else {
unew = create_unit(r, u->faction, number, u_race(u), 0, NULL, u);
}
len = snprintf(equipment, sizeof(equipment), "new_%s", u_race(u)->_name);
if (len > 0 && (size_t)len < sizeof(equipment)) {
equip_unit(unew, equipment);
}
if (unew != u) {
transfermen(unew, u, unew->number);
remove_unit(&r->units, unew);
}
}
if (number < wanted) {
ADDMSG(&u->faction->msgs, msg_message("recruit",
"unit region amount want", u, r, number, wanted));
}
}
static int any_recruiters(const struct race *rc, int qty)
{
return (int)(qty * 2 * rc->recruit_multi);
}
static int do_recruiting(recruitment * recruits, int available)
{
recruitment *rec;
int recruited = 0;
/* try to assign recruits to factions fairly */
while (available > 0) {
int n = 0;
int rest, mintotal = INT_MAX;
/* find smallest production */
for (rec = recruits; rec != NULL; rec = rec->next) {
int want = rec->total - rec->assigned;
if (want > 0) {
if (mintotal > want)
mintotal = want;
++n;
}
}
if (n == 0)
break;
if (mintotal * n > available) {
mintotal = available / n;
}
rest = available - mintotal * n;
/* assign size of smallest production for everyone if possible; in the end roll dice to assign
* small rest */
for (rec = recruits; rec != NULL; rec = rec->next) {
int want = rec->total - rec->assigned;
if (want > 0) {
int get = mintotal;
if (want > mintotal && rest < n && (rng_int() % n) < rest) {
--rest;
++get;
}
assert(get <= want);
available -= get;
rec->assigned += get;
}
}
}
/* do actual recruiting */
for (rec = recruits; rec != NULL; rec = rec->next) {
recruit_request *req;
int get = rec->assigned;
for (req = rec->requests; req; req = req->next) {
unit *u = req->unit;
const race *rc = u_race(u); /* race is set in recruit() */
int number;
double multi = 2.0 * rc->recruit_multi;
number = (int)(get / multi);
if (number > req->qty) number = req->qty;
if (rc->recruitcost) {
int afford = get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT,
number * rc->recruitcost) / rc->recruitcost;
if (number > afford) number = afford;
}
if (u->number + number > UNIT_MAXSIZE) {
ADDMSG(&u->faction->msgs, msg_feedback(u, req->ord, "error_unit_size",
"maxsize", UNIT_MAXSIZE));
number = UNIT_MAXSIZE - u->number;
assert(number >= 0);
}
if (rc->recruitcost) {
use_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT,
rc->recruitcost * number);
}
if (u->number == 0 && fval(u, UFL_DEAD)) {
/* unit is empty, dead, and cannot recruit */
number = 0;
}
add_recruits(u, number, req->qty);
if (number > 0) {
int dec = (int)(number * multi);
if ((rc->ec_flags & ECF_REC_ETHEREAL) == 0) {
recruited += dec;
}
get -= dec;
}
}
}
return recruited;
}
/* Rekrutierung */
static void expandrecruit(region * r, recruit_request * recruitorders)
{
recruitment *recruits;
int orc_total = 0;
/* peasant limited: */
recruits = select_recruitment(&recruitorders, any_recruiters, &orc_total);
if (recruits) {
int orc_recruited, orc_peasants = rpeasants(r) * 2;
int orc_frac = orc_peasants / RECRUITFRACTION; /* anzahl orks. 2 ork = 1 bauer */
if (orc_total < orc_frac)
orc_frac = orc_total;
orc_recruited = do_recruiting(recruits, orc_frac);
assert(orc_recruited <= orc_frac);
rsetpeasants(r, (orc_peasants - orc_recruited) / 2);
free_recruitments(recruits);
}
/* no limit: */
recruits = select_recruitment(&recruitorders, any_recruiters, &orc_total);
if (recruits) {
int recruited, peasants = rpeasants(r) * 2;
recruited = do_recruiting(recruits, INT_MAX);
if (recruited > 0) {
rsetpeasants(r, (peasants - recruited) / 2);
}
free_recruitments(recruits);
}
assert(recruitorders == NULL);
}
static int recruit_cost(const faction * f, const race * rc)
{
if (is_monsters(f) || valid_race(f, rc)) {
return rc->recruitcost;
}
return -1;
}
message *can_recruit(unit *u, const race *rc, order *ord, int now)
{
region *r = u->region;
/* this is a very special case because the recruiting unit may be empty
* at this point and we have to look at the creating unit instead. This
* is done in cansee, which is called indirectly by is_guarded(). */
if (is_guarded(r, u)) {
return msg_error(u, ord, 70);
}
if (rc == get_race(RC_INSECT)) {
gamedate date;
get_gamedate(now, &date);
if (date.season == SEASON_WINTER && r->terrain != newterrain(T_DESERT)) {
bool usepotion = false;
unit *u2;
for (u2 = r->units; u2; u2 = u2->next) {
if (fval(u2, UFL_WARMTH)) {
usepotion = true;
break;
}
}
if (!usepotion) {
return msg_error(u, ord, 98);
}
}
/* in Gletschern, Eisbergen gar nicht rekrutieren */
if (r_insectstalled(r)) {
return msg_error(u, ord, 97);
}
}
if (is_cursed(r->attribs, &ct_riotzone)) {
/* Die Region befindet sich in Aufruhr */
return msg_error(u, ord, 237);
}
if (rc && !playerrace(rc)) {
return msg_error(u, ord, 139);
}
if (fval(u, UFL_HERO)) {
return msg_feedback(u, ord, "error_herorecruit", "");
}
if (has_skill(u, SK_MAGIC)) {
/* error158;de;{unit} in {region}: '{command}' - Magier arbeiten
* grundsaetzlich nur alleine! */
return msg_error(u, ord, 158);
}
return NULL;
}
static void recruit_cmd(unit * u, struct order *ord, recruit_request ** recruitorders)
{
region *r = u->region;
recruit_request *o;
int recruitcost = -1;
const faction *f = u->faction;
const struct race *rc = u_race(u);
int n;
message *msg;
init_order_depr(ord);
n = getint();
if (n <= 0) {
syntax_error(u, ord);
return;
}
if (u->number == 0) {
char token[128];
const char *str;
str = gettoken(token, sizeof(token));
if (str && str[0]) {
/* Monsters can RECRUIT 15 DRACOID
* also: secondary race */
rc = findrace(str, f->locale);
if (rc != NULL) {
recruitcost = recruit_cost(f, rc);
}
}
}
if (recruitcost < 0) {
rc = u_race(u);
recruitcost = recruit_cost(f, rc);
if (recruitcost < 0) {
recruitcost = INT_MAX;
}
}
if (recruitcost > 0) {
int pool;
plane *pl = getplane(r);
if (pl && (pl->flags & PFL_NORECRUITS)) {
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_pflnorecruit", ""));
return;
}
pool = get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT, recruitcost * n);
if (pool < recruitcost) {
cmistake(u, ord, 142, MSG_EVENT);
return;
}
pool /= recruitcost;
if (n > pool) n = pool;
}
if (!n) {
cmistake(u, ord, 142, MSG_EVENT);
return;
}
if (has_skill(u, SK_ALCHEMY)) {
if (faction_count_skill(u->faction, SK_ALCHEMY) + n > faction_skill_limit(u->faction, SK_ALCHEMY)) {
cmistake(u, ord, 156, MSG_EVENT);
return;
}
}
assert(rc);
msg = can_recruit(u, rc, ord, turn);
if (msg) {
add_message(&u->faction->msgs, msg);
msg_release(msg);
return;
}
u_setrace(u, rc);
u->wants = n;
o = (recruit_request *)calloc(1, sizeof(recruit_request));
if (!o) abort();
o->qty = n;
o->unit = u;
o->ord = ord;
addlist(recruitorders, o);
}
void recruit(region * r)
{
unit *u;
recruit_request *recruitorders = NULL;
if (rules_recruit < 0)
recruit_init();
for (u = r->units; u; u = u->next) {
order *ord;
if ((rules_recruit & RECRUIT_MERGE) || u->number == 0) {
for (ord = u->orders; ord; ord = ord->next) {
if (getkeyword(ord) == K_RECRUIT) {
recruit_cmd(u, ord, &recruitorders);
break;
}
}
}
}
if (recruitorders) {
expandrecruit(r, recruitorders);
}
remove_empty_units_in_region(r);
}

Some files were not shown because too many files have changed in this diff Show More