Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Enno Rehling 2019-09-15 14:17:37 +02:00
commit e90b7acd4c
69 changed files with 1828 additions and 1163 deletions

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

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

@ -1707,6 +1707,9 @@ msgstr "Ring der Unsichtbarkeit"
msgid "caravel_a"
msgstr "eine Karavelle"
msgid "galleon_a"
msgstr "eine Galeone"
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 "Galeone"
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

@ -37,6 +37,18 @@ function setup()
eressea.settings.set("study.random_progress", "0")
end
function test_set_faction()
local r = region.create(0, 0, "plain")
local f1 = create_faction('elf')
local f2 = create_faction('elf')
local u = one_unit(r, f1)
u.group = 'Experten'
assert_equal('Experten', u.group)
u.faction = f2
assert_equal(f2, u.faction)
assert_nil(u.group)
end
function test_locales()
assert_equal(2, eressea.locale.direction("de", "Ost"))
assert_equal(5, eressea.locale.direction("de", "westen"))

View file

@ -12,6 +12,23 @@ function setup()
eressea.settings.set("rules.peasants.growth.factor", "0")
end
function test_give_unit()
local r = region.create(0, 0, "plain")
local f1 = faction.create('elf')
local f2 = faction.create('elf')
local u1 = unit.create(f1, r)
local u2 = unit.create(f2, r)
assert_equal(f1, u1.faction)
assert_equal(f2, u2.faction)
u2.group = 'Experten'
assert_equal('Experten', u2.group)
u1:add_order("HELFE " .. itoa36(f2.id) .. " GIB")
u2:add_order("GIB " .. itoa36(u1.id) .. " EINHEIT")
process_orders()
assert_equal(f1, u2.faction)
assert_nil(u2.group)
end
function test_study_auto()
local r = region.create(0, 0, "plain")
local f = faction.create("human")

View file

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

View file

@ -7,8 +7,8 @@ function test_quit_faction()
local f1 = faction.create("human")
f1.password = "steamedhams"
local f2 = faction.create("human")
local u1 = unit.create(f1, r, 10)
local u2 = unit.create(f2, r, 10)
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()

View file

@ -36,6 +36,20 @@ function teardown()
end
end
function test_new_faction_cannot_give_unit()
local r = region.create(0, 0, "plain")
local f1 = faction.create('elf')
local f2 = faction.create('elf')
local u1 = unit.create(f1, r)
local u2 = unit.create(f2, r)
assert_equal(f1, u1.faction)
assert_equal(f2, u2.faction)
u1:add_order("HELFE " .. itoa36(f2.id) .. " GIB")
u2:add_order("GIB " .. itoa36(u1.id) .. " EINHEIT")
process_orders()
assert_equal(f2, u2.faction)
end
function test_calendar()
assert_equal("winter", get_season(396))
end

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

@ -196,7 +196,6 @@ void register_attributes(void)
at_register(&at_group);
at_register(&at_building_generic_type);
at_register(&at_npcfaction);
/* connection-typen */
register_bordertype(&bt_noway);
@ -205,6 +204,7 @@ void register_attributes(void)
register_bordertype(&bt_illusionwall);
register_bordertype(&bt_road);
at_deprecate("npcfaction", a_readint);
at_deprecate("siege", a_readint);
at_deprecate("maxmagicians", a_readint); /* factions with differnt magician limits, probably unused */
at_deprecate("hurting", a_readint); /* an old arena attribute */

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 1024
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

@ -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

@ -259,10 +259,9 @@ static int tolua_dice_rand(lua_State * L)
static int tolua_get_season(lua_State * L)
{
int turnno = (int)tolua_tonumber(L, 1, 0);
gamedate gd;
get_gamedate(turnno, &gd);
tolua_pushstring(L, seasonnames[gd.season]);
int turn_no = (int)tolua_tonumber(L, 1, 0);
season_t season = calendar_season(turn_no);
tolua_pushstring(L, seasonnames[season]);
return 1;
}

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

@ -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;
@ -2146,12 +1758,8 @@ static void breedtrees(unit * u, int raw)
{
int n, i, skill, planted = 0;
const resource_type *rtype;
int current_season;
season_t current_season = calendar_season(turn);
region *r = u->region;
gamedate date;
get_gamedate(turn, &date);
current_season = date.season;
/* Baeume zuechten geht nur im Fruehling */
if (current_season != SEASON_SPRING) {
@ -2349,36 +1957,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 +2006,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 +2045,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 +2092,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 +2252,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,30 +2319,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 1024
static struct econ_request workers[MAX_WORKERS];
void auto_work(region * r)
{
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);
}
}
@ -2771,21 +2387,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 *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 +2428,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 +2484,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 +2536,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,11 +3,13 @@
#endif
#include <kernel/config.h>
#include "economy.h"
#include "recruit.h"
#include <util/message.h>
#include <kernel/attrib.h>
#include <kernel/building.h>
#include <kernel/item.h>
#include <kernel/calendar.h>
#include <kernel/faction.h>
#include <kernel/item.h>
#include <kernel/messages.h>
#include <kernel/order.h>
#include <kernel/pool.h>
@ -19,9 +21,9 @@
#include <kernel/terrainid.h>
#include <kernel/unit.h>
#include <kernel/attrib.h>
#include <util/language.h>
#include <util/macros.h>
#include <util/message.h>
#include <CuTest.h>
#include <tests.h>
@ -162,7 +164,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 +180,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);
@ -493,6 +495,7 @@ static void test_recruit_insect(CuTest *tc) {
u = test_create_unit(f, test_create_region(0, 0, NULL));
u->thisorder = create_order(K_RECRUIT, f->locale, "%d", 1);
CuAssertIntEquals(tc, SEASON_AUTUMN, calendar_season(1083));
msg = can_recruit(u, f->race, u->thisorder, 1083); /* Autumn */
CuAssertPtrEquals(tc, NULL, msg);

View file

@ -580,12 +580,49 @@ static void reset_region(region *r) {
}
}
static void reset_cursor(state *st) {
static region * state_region(state *st) {
int nx = st->cursor.x;
int ny = st->cursor.y;
region *r;
pnormalize(&nx, &ny, st->cursor.pl);
if ((r = findregion(nx, ny)) != NULL) {
return findregion(nx, ny);
}
static void reset_area_cb(void *arg) {
region *r = (region *)arg;
r->age = 0;
freset(r, RF_MARK);
}
static void reset_area(state *st) {
region * r = state_region(st);
if (r != NULL) {
selist * ql = NULL;
int qi = 0, qlen = 0;
fset(r, RF_MARK);
selist_insert(&ql, qlen++, r);
while (qi != qlen) {
int i;
region *adj[MAXDIRECTIONS];
r = selist_get(ql, qi++);
get_neighbours(r, adj);
for (i = 0; i != MAXDIRECTIONS; ++i) {
region *rn = adj[i];
if (rn && !fval(rn, RF_MARK)) {
if ((rn->terrain->flags & FORBIDDEN_REGION) == 0) {
fset(rn, RF_MARK);
selist_insert(&ql, qlen++, rn);
}
}
}
}
selist_foreach(ql, reset_area_cb);
selist_free(ql);
}
}
static void reset_cursor(state *st) {
region * r = state_region(st);
if (r != NULL) {
reset_region(r);
}
}
@ -1072,6 +1109,16 @@ static void seed_player(state *st, const newfaction *player) {
}
}
static bool confirm(WINDOW * win, const char *q) {
int ch;
werase(win);
mvwaddstr(win, 0, 0, (char *)q);
wmove(win, 0, (int)(strlen(q) + 1));
ch = wgetch(win);
return (ch == 'y') || (ch == 'Y');
}
static void handlekey(state * st, int c)
{
window *wnd;
@ -1157,6 +1204,13 @@ static void handlekey(state * st, int c)
st->wnd_status->update |= 1;
st->wnd_map->update |= 1;
break;
case 'A': /* clear/reset area */
if (confirm(st->wnd_status->handle, "Are you sure you want to reset this entire area?")) {
reset_area(st);
st->modified = 1;
st->wnd_map->update |= 1;
}
break;
case 'c': /* clear/reset */
reset_cursor(st);
st->modified = 1;

View file

@ -792,7 +792,7 @@ static void json_calendar(cJSON *json) {
for (i = 0, jmonth = child->child; jmonth; jmonth = jmonth->next, ++i) {
if (jmonth->type == cJSON_Object) {
storms[i] = cJSON_GetObjectItem(jmonth, "storm")->valueint;
month_season[i] = cJSON_GetObjectItem(jmonth, "season")->valueint;
month_season[i] = (season_t) cJSON_GetObjectItem(jmonth, "season")->valueint;
}
else {
log_error("calendar.months[%d] is not an object: %d", i, json->type);

View file

@ -160,8 +160,8 @@ static void test_calendar(CuTest * tc)
CuAssertIntEquals(tc, 99, storms[0]);
CuAssertIntEquals(tc, 22, storms[1]);
CuAssertPtrNotNull(tc, month_season);
CuAssertIntEquals(tc, 1, month_season[0]);
CuAssertIntEquals(tc, 2, month_season[1]);
CuAssertIntEquals(tc, SEASON_SPRING, month_season[0]);
CuAssertIntEquals(tc, SEASON_SUMMER, month_season[1]);
cJSON_Delete(json);
test_teardown();
}

View file

@ -238,22 +238,6 @@ autoalliance(const faction * sf, const faction * f2)
return 0;
}
static void init_npcfaction(variant *var)
{
var->i = 1;
}
attrib_type at_npcfaction = {
"npcfaction",
init_npcfaction,
NULL,
NULL,
a_writeint,
a_readint,
NULL,
ATF_UNIQUE
};
/** Limits the available help modes
* The bitfield returned by this function specifies the available help modes
* in this game (so you can, for example, disable HELP GIVE globally).
@ -307,12 +291,6 @@ int alliance_status(const faction *f, const faction *f2, int status) {
if (status > 0) {
int mask = AllianceRestricted();
if (mask) {
if (a_find(f->attribs, &at_npcfaction)) {
return status;
}
if (a_find(f2->attribs, &at_npcfaction)) {
return status;
}
if (f->alliance != f2->alliance) {
status &= ~mask;
}

View file

@ -18,7 +18,7 @@ int months_per_year = 12;
const char *seasonnames[CALENDAR_SEASONS] = { "winter", "spring", "summer", "fall" };
char **weeknames = NULL;
char **weeknames2 = NULL;
int *month_season = NULL;
season_t *month_season = NULL;
const char *calendar_month(int index)
{
@ -27,6 +27,13 @@ const char *calendar_month(int index)
return result;
}
season_t calendar_season(int now) {
gamedate date;
get_gamedate(now, &date);
return date.season;
}
const char *calendar_era(void)
{
static char result[20];

View file

@ -5,17 +5,17 @@
extern "C" {
#endif
enum {
typedef enum season_t {
SEASON_WINTER,
SEASON_SPRING,
SEASON_SUMMER,
SEASON_AUTUMN
};
} season_t;
#define CALENDAR_SEASONS 4
extern const char *seasonnames[CALENDAR_SEASONS];
extern int months_per_year;
extern int *month_season;
extern season_t *month_season;
extern int first_month;
extern int turn;
@ -26,16 +26,18 @@ extern "C" {
typedef struct gamedate {
int turn;
int year;
int season;
season_t season;
int month;
int week;
} gamedate;
const gamedate *get_gamedate(int turn, gamedate * gd);
void calendar_cleanup(void);
const char *calendar_month(int index);
const char *calendar_era(void);
int first_turn(void);
const gamedate *get_gamedate(int turn, gamedate * gd);
season_t calendar_season(int turn);
void calendar_cleanup(void);
const char *calendar_month(int index);
const char *calendar_era(void);
int first_turn(void);
#ifdef __cplusplus
}

View file

@ -65,26 +65,59 @@ static void test_calendar(CuTest * tc)
test_teardown();
}
static void setup_calendar(void) {
int i;
months_per_year = 4;
weeks_per_month = 2;
free(month_season);
month_season = calloc(months_per_year, sizeof(season_t));
for (i = 0; i != 4; ++i) {
month_season[i] = (season_t)i;
}
}
static void test_calendar_season(CuTest * tc)
{
test_setup();
setup_calendar();
CuAssertIntEquals(tc, SEASON_WINTER, calendar_season(0));
CuAssertIntEquals(tc, SEASON_WINTER, calendar_season(1));
CuAssertIntEquals(tc, SEASON_SPRING, calendar_season(2));
CuAssertIntEquals(tc, SEASON_SPRING, calendar_season(3));
CuAssertIntEquals(tc, SEASON_SUMMER, calendar_season(4));
CuAssertIntEquals(tc, SEASON_SUMMER, calendar_season(5));
CuAssertIntEquals(tc, SEASON_AUTUMN, calendar_season(6));
CuAssertIntEquals(tc, SEASON_AUTUMN, calendar_season(7));
CuAssertIntEquals(tc, SEASON_WINTER, calendar_season(8));
free(month_season);
month_season = NULL;
test_teardown();
}
static void test_gamedate(CuTest * tc)
{
gamedate gd;
test_setup();
month_season = calloc(months_per_year, sizeof(int));
setup_calendar();
get_gamedate(0, &gd);
CuAssertIntEquals(tc, 1, gd.year);
CuAssertIntEquals(tc, 0, gd.season);
CuAssertIntEquals(tc, SEASON_WINTER, gd.season);
CuAssertIntEquals(tc, 0, gd.month);
CuAssertIntEquals(tc, 0, gd.week);
month_season[1] = 1;
get_gamedate(weeks_per_month + 1, &gd);
CuAssertIntEquals(tc, 1, gd.year);
CuAssertIntEquals(tc, 1, gd.season);
CuAssertIntEquals(tc, SEASON_SPRING, gd.season);
CuAssertIntEquals(tc, 1, gd.month);
CuAssertIntEquals(tc, 1, gd.week);
free(month_season);
month_season = NULL;
test_teardown();
}
@ -94,5 +127,6 @@ CuSuite *get_calendar_suite(void)
SUITE_ADD_TEST(suite, test_calendar_config);
SUITE_ADD_TEST(suite, test_calendar);
SUITE_ADD_TEST(suite, test_calendar_season);
SUITE_ADD_TEST(suite, test_gamedate);
return suite;
}

View file

@ -302,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

@ -49,7 +49,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
static group *ghash[GMAXHASH];
static int maxgid;
group *new_group(faction * f, const char *name, int gid)
group *create_group(faction * f, const char *name, int gid)
{
group **gp = &f->groups;
int index = gid % GMAXHASH;
@ -63,6 +63,7 @@ group *new_group(faction * f, const char *name, int gid)
if (gid > maxgid) maxgid = gid;
g->name = str_strdup(name);
g->gid = gid;
g->f = f;
g->nexthash = ghash[index];
return ghash[index] = g;
@ -93,11 +94,15 @@ static int read_group(variant *var, void *owner, gamedata *data)
{
struct storage *store = data->store;
group *g;
unit * u = (unit *)owner;
int gid;
READ_INT(store, &gid);
var->v = g = find_group(gid);
if (g != 0) {
if (g != NULL) {
if (g->f != u->faction) {
return AT_READ_FAIL;
}
g->members++;
return AT_READ_OK;
}
@ -184,7 +189,7 @@ group *join_group(unit * u, const char *name)
if (name && name[0]) {
g = find_groupbyname(u->faction->groups, name);
if (g == NULL) {
g = new_group(u->faction, name, ++maxgid);
g = create_group(u->faction, name, ++maxgid);
init_group(u->faction, g);
}
}
@ -219,7 +224,7 @@ void read_groups(gamedata *data, faction * f)
if (gid == 0)
break;
READ_STR(store, buf, sizeof(buf));
g = new_group(f, buf, gid);
g = create_group(f, buf, gid);
read_allies(data, &g->allies);
read_attribs(data, &g->attribs, g);
}

View file

@ -40,7 +40,7 @@ extern "C" {
extern void set_group(struct unit *u, struct group *g);
extern struct group * get_group(const struct unit *u);
extern void free_group(struct group *g);
struct group *new_group(struct faction * f, const char *name, int gid);
struct group *create_group(struct faction * f, const char *name, int gid);
extern void write_groups(struct gamedata *data, const struct faction *f);
extern void read_groups(struct gamedata *data, struct faction *f);

View file

@ -80,8 +80,10 @@ static void test_group_readwrite(CuTest * tc)
mstream_init(&data.strm);
gamedata_init(&data, &store, RELEASE_VERSION);
f = test_create_faction(NULL);
new_group(f, "NW", 42);
g = new_group(f, "Egoisten", 43);
create_group(f, "NW", 42);
g = create_group(f, "Egoisten", 43);
CuAssertPtrEquals(tc, f, g->f);
CuAssertStrEquals(tc, "Egoisten", g->name);
key_set(&g->attribs, 44, 44);
ally_set(&g->allies, f, HELP_GIVE);
write_groups(&data, f);

View file

@ -141,11 +141,10 @@ static region **internal_path_find(region * handle_start, const region * target,
int maxlen, bool(*allowed) (const region *, const region *))
{
static region *path[MAXDEPTH + 2]; /* STATIC_RETURN: used for return, not across calls */
direction_t d;
direction_t d = MAXDIRECTIONS;
node *root = new_node(handle_start, 0, NULL);
node **handle_end = &root->next;
node *n = root;
bool found = false;
assert(maxlen <= MAXDEPTH);
fset(handle_start, RF_MARK);
@ -156,36 +155,33 @@ static region **internal_path_find(region * handle_start, const region * target,
break;
for (d = 0; d != MAXDIRECTIONS; ++d) {
region *rn = rconnect(r, d);
if (rn == NULL)
continue;
if (fval(rn, RF_MARK))
continue; /* already been there */
if (!allowed(r, rn))
continue; /* can't go there */
if (rn == target) {
int i = depth;
path[i + 1] = NULL;
path[i] = rn;
while (n) {
path[--i] = n->r;
n = n->prev;
if (rn && !fval(rn, RF_MARK) && allowed(r, rn)) {
if (rn == target) {
int i = depth;
path[i + 1] = NULL;
path[i] = rn;
while (n) {
path[--i] = n->r;
n = n->prev;
}
break;
}
else {
fset(rn, RF_MARK);
*handle_end = new_node(rn, depth, n);
handle_end = &(*handle_end)->next;
}
found = true;
break;
}
else {
fset(rn, RF_MARK);
*handle_end = new_node(rn, depth, n);
handle_end = &(*handle_end)->next;
}
}
if (found)
if (d != MAXDIRECTIONS) {
break;
}
n = n->next;
}
free_nodes(root);
if (found)
if (d != MAXDIRECTIONS) {
return path;
}
return NULL;
}

View file

@ -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,3 +1,4 @@
#include "save.h"
/*
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
@ -630,7 +631,6 @@ static void fix_resource_levels(region *r) {
}
}
}
}
}
@ -1474,61 +1474,15 @@ static void fix_familiars(void (*callback)(unit *)) {
}
}
int read_game(gamedata *data)
{
int p, nread;
faction *f, **fp;
region *r;
unit *u;
static void read_regions(gamedata *data) {
storage * store = data->store;
const struct building_type *bt_lighthouse = bt_find("lighthouse");
const struct race *rc_spell = rc_find("spell");
if (data->version >= SAVEGAMEID_VERSION) {
int gameid;
READ_INT(store, &gameid);
if (gameid != game_id()) {
log_warning("game mismatch: datafile contains game %d, but config is for %d", gameid, game_id());
}
}
else {
READ_STR(store, NULL, 0);
}
if (data->version < FIXATKEYS_VERSION) {
attrib *a = NULL;
read_attribs(data, &a, NULL);
a_removeall(&a, NULL);
}
READ_INT(store, &turn);
log_debug(" - reading turn %d", turn);
rng_init(turn + config_get_int("game.seed", 0));
READ_INT(store, NULL); /* max_unique_id = ignore */
READ_INT(store, &nextborder);
read_planes(data);
read_alliances(data);
READ_INT(store, &nread);
log_debug(" - Einzulesende Parteien: %d\n", nread);
fp = &factions;
while (*fp) {
fp = &(*fp)->next;
}
while (--nread >= 0) {
faction *f = read_faction(data);
*fp = f;
fp = &f->next;
}
*fp = 0;
/* Regionen */
region *r;
int nread;
READ_INT(store, &nread);
assert(nread < MAXREGIONS && nread >=0);
assert(nread < MAXREGIONS && nread >= 0);
log_debug(" - Einzulesende Regionen: %d", nread);
@ -1536,13 +1490,14 @@ int read_game(gamedata *data)
unit **up;
building **bp;
ship **shp;
int p;
r = read_region(data);
/* Burgen */
READ_INT(store, &p);
if (p > 0 && !r->land) {
log_debug("%s, uid=%d has %d %s", regionname(r, NULL), r->uid, p, (p==1) ? "building" : "buildings");
log_debug("%s, uid=%d has %d %s", regionname(r, NULL), r->uid, p, (p == 1) ? "building" : "buildings");
}
bp = &r->buildings;
@ -1596,7 +1551,6 @@ int read_game(gamedata *data)
}
}
}
read_borders(data);
log_debug("updating area information for lighthouses.");
for (r = regions; r; r = r->next) {
@ -1609,8 +1563,12 @@ int read_game(gamedata *data)
}
}
}
}
static void init_factions(int data_version)
{
log_debug("marking factions as alive.");
for (f = factions; f; f = f->next) {
for (faction *f = factions; f; f = f->next) {
if (f->flags & FFL_NPC) {
f->_alive = true;
f->magiegebiet = M_GRAY;
@ -1624,8 +1582,8 @@ int read_game(gamedata *data)
}
else {
assert(f->units);
for (u = f->units; u; u = u->nextF) {
if (data->version < SPELL_LEVEL_VERSION) {
for (unit *u = f->units; u; u = u->nextF) {
if (data_version < SPELL_LEVEL_VERSION) {
struct sc_mage *mage = get_mage(u);
if (mage) {
faction *f = u->faction;
@ -1642,16 +1600,76 @@ int read_game(gamedata *data)
}
if (u->number > 0) {
f->_alive = true;
if (data->version >= SPELL_LEVEL_VERSION) {
if (data_version >= SPELL_LEVEL_VERSION) {
break;
}
}
}
if (data->version < SPELL_LEVEL_VERSION && f->spellbook) {
if (data_version < SPELL_LEVEL_VERSION && f->spellbook) {
spellbook_foreach(f->spellbook, cb_sb_maxlevel, f);
}
}
}
}
static void read_factions(gamedata * data)
{
storage * store = data->store;
int nread;
faction **fp;
READ_INT(store, &nread);
log_debug(" - Einzulesende Parteien: %d\n", nread);
fp = &factions;
while (*fp) {
fp = &(*fp)->next;
}
while (--nread >= 0) {
faction *f = read_faction(data);
*fp = f;
fp = &f->next;
}
}
int read_game(gamedata *data)
{
storage * store = data->store;
if (data->version >= SAVEGAMEID_VERSION) {
int gameid;
READ_INT(store, &gameid);
if (gameid != game_id()) {
log_warning("game mismatch: datafile contains game %d, but config is for %d", gameid, game_id());
}
}
else {
READ_STR(store, NULL, 0);
}
if (data->version < FIXATKEYS_VERSION) {
attrib *a = NULL;
read_attribs(data, &a, NULL);
a_removeall(&a, NULL);
}
READ_INT(store, &turn);
log_debug(" - reading turn %d", turn);
rng_init(turn + config_get_int("game.seed", 0));
READ_INT(store, NULL); /* max_unique_id = ignore */
READ_INT(store, &nextborder);
read_planes(data);
read_alliances(data);
read_factions(data);
/* Regionen */
read_regions(data);
read_borders(data);
init_factions(data->version);
if (data->version < FIX_CLONES_VERSION) {
fix_clones();
}

View file

@ -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

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

View file

@ -913,7 +913,7 @@ void u_setfaction(unit * u, faction * f)
--u->faction->num_units;
u->faction->num_people -= u->number;
}
join_group(u, NULL);
set_group(u, NULL);
free_orders(&u->orders);
set_order(&u->thisorder, NULL);

View file

@ -1,4 +1,4 @@
#include <platform.h>
#include <kernel/ally.h>
#include <kernel/config.h>
#include <kernel/curse.h>
#include <kernel/item.h>
@ -652,6 +652,62 @@ static void test_get_modifier(CuTest *tc) {
test_teardown();
}
static void test_gift_items(CuTest *tc) {
unit *u, *u1, *u2;
region *r;
const resource_type *rtype;
test_setup();
init_resources();
r = test_create_plain(0, 0);
u = test_create_unit(test_create_faction(NULL), r);
rtype = get_resourcetype(R_SILVER);
region_setresource(r, rtype, 0);
i_change(&u->items, rtype->itype, 10);
gift_items(u, GIFT_FRIENDS | GIFT_PEASANTS | GIFT_SELF);
CuAssertIntEquals(tc, 10, region_getresource(r, rtype));
CuAssertIntEquals(tc, 0, i_get(u->items, rtype->itype));
region_setresource(r, get_resourcetype(R_HORSE), 0);
region_setresource(r, rtype, 0);
i_change(&u->items, rtype->itype, 10);
i_change(&u->items, get_resourcetype(R_HORSE)->itype, 20);
u1 = test_create_unit(test_create_faction(NULL), r);
u2 = test_create_unit(u1->faction, r);
gift_items(u, GIFT_FRIENDS | GIFT_PEASANTS | GIFT_SELF);
CuAssertIntEquals(tc, 20, region_getresource(r, get_resourcetype(R_HORSE)));
CuAssertIntEquals(tc, 10, region_getresource(r, rtype));
CuAssertIntEquals(tc, 0, i_get(u->items, rtype->itype));
CuAssertIntEquals(tc, 0, i_get(u1->items, rtype->itype));
CuAssertIntEquals(tc, 0, i_get(u2->items, rtype->itype));
region_setresource(r, rtype, 0);
i_change(&u->items, rtype->itype, 10);
ally_set(&u->faction->allies, u1->faction, HELP_MONEY);
ally_set(&u1->faction->allies, u->faction, HELP_GIVE);
gift_items(u, GIFT_FRIENDS | GIFT_PEASANTS | GIFT_SELF);
CuAssertIntEquals(tc, 0, region_getresource(r, rtype));
CuAssertIntEquals(tc, 0, i_get(u->items, rtype->itype));
CuAssertIntEquals(tc, 10, i_get(u1->items, rtype->itype));
CuAssertIntEquals(tc, 0, i_get(u2->items, rtype->itype));
i_change(&u1->items, rtype->itype, -10);
set_number(u1, 2);
u_setfaction(u2, test_create_faction(NULL));
ally_set(&u->faction->allies, u2->faction, HELP_MONEY);
ally_set(&u2->faction->allies, u->faction, HELP_GIVE);
region_setresource(r, rtype, 0);
i_change(&u->items, rtype->itype, 15);
ally_set(&u->faction->allies, u1->faction, HELP_MONEY);
ally_set(&u1->faction->allies, u->faction, HELP_GIVE);
gift_items(u, GIFT_FRIENDS | GIFT_PEASANTS | GIFT_SELF);
CuAssertIntEquals(tc, 0, region_getresource(r, rtype));
CuAssertIntEquals(tc, 0, i_get(u->items, rtype->itype));
CuAssertIntEquals(tc, 10, i_get(u1->items, rtype->itype));
CuAssertIntEquals(tc, 5, i_get(u2->items, rtype->itype));
test_teardown();
}
CuSuite *get_unit_suite(void)
{
CuSuite *suite = CuSuiteNew();
@ -683,5 +739,6 @@ CuSuite *get_unit_suite(void)
SUITE_ADD_TEST(suite, test_name_unit);
SUITE_ADD_TEST(suite, test_heal_factor);
SUITE_ADD_TEST(suite, test_get_modifier);
SUITE_ADD_TEST(suite, test_gift_items);
return suite;
}

View file

@ -8,7 +8,7 @@
#ifndef ERESSEA_VERSION
/* the version number, if it was not passed to make with -D */
#define ERESSEA_VERSION "3.20.0"
#define ERESSEA_VERSION "3.21.0"
#endif
const char *eressea_version(void) {

View file

@ -35,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"
@ -280,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;
@ -313,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;
}
}
@ -607,7 +613,7 @@ growing_trees_e3(region * r, const int current_season,
}
static void
growing_trees(region * r, const int current_season, const int last_weeks_season)
growing_trees(region * r, const season_t current_season, const season_t last_weeks_season)
{
int grownup_trees, i, seeds, sprout;
attrib *a;
@ -749,7 +755,7 @@ growing_trees(region * r, const int current_season, const int last_weeks_season)
}
static void
growing_herbs(region * r, const int current_season, const int last_weeks_season)
growing_herbs(region * r, const int current_season, const season_t last_weeks_season)
{
/* Jetzt die Kraeutervermehrung. Vermehrt wird logistisch:
*
@ -778,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 */
@ -842,20 +849,12 @@ void nmr_warnings(void)
void demographics(void)
{
region *r;
static int last_weeks_season = -1;
static int current_season = -1;
int plant_rules = config_get_int("rules.grow.formula", 2);
int horse_rules = config_get_int("rules.horses.growth", 1);
int peasant_rules = config_get_int("rules.peasants.growth", 1);
const struct building_type *bt_harbour = bt_find("harbour");
if (current_season < 0) {
gamedate date;
get_gamedate(turn, &date);
current_season = date.season;
get_gamedate(turn - 1, &date);
last_weeks_season = date.season;
}
season_t current_season = calendar_season(turn);
season_t last_weeks_season = calendar_season(turn - 1);
for (r = regions; r; r = r->next) {
++r->age; /* also oceans. no idea why we didn't always do that */
@ -877,7 +876,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) {
@ -1703,7 +1702,7 @@ static int rename_cmd(unit * u, order * ord, char **s, const char *s2)
}
static bool try_rename(unit *u, building *b, order *ord) {
unit *owner = b ? building_owner(b) : 0;
unit *owner = b ? building_owner(b) : NULL;
bool foreign = !(owner && owner->faction == u->faction);
if (!b) {
@ -1733,12 +1732,12 @@ static bool try_rename(unit *u, building *b, order *ord) {
msg_message("renamed_building_notseen",
"building region", b, u->region));
}
if (owner != u) {
cmistake(u, ord, 148, MSG_PRODUCE);
return false;
}
}
}
if (owner && owner->faction != u->faction) {
cmistake(u, ord, 148, MSG_PRODUCE);
return false;
}
return true;
}
@ -3891,7 +3890,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);
@ -3915,6 +3913,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");
@ -3942,11 +3941,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;
@ -3955,9 +3957,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

@ -94,6 +94,7 @@ extern "C" {
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);
@ -120,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

@ -1355,6 +1355,24 @@ static void test_name_cmd(CuTest *tc) {
test_teardown();
}
static void test_name_foreign_cmd(CuTest *tc) {
building *b;
faction *f;
region *r;
unit *u;
test_setup();
u = test_create_unit(f = test_create_faction(NULL), r = test_create_region(0, 0, NULL));
b = test_create_building(u->region, NULL);
u->thisorder = create_order(K_NAME, f->locale, "%s %s %s Hodor",
LOC(f->locale, parameters[P_FOREIGN]),
LOC(f->locale, parameters[P_BUILDING]),
itoa36(b->no));
name_cmd(u, u->thisorder);
CuAssertStrEquals(tc, "Hodor", b->name);
test_teardown();
}
static void test_name_cmd_2274(CuTest *tc) {
unit *u1, *u2, *u3;
faction *f;
@ -1931,6 +1949,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;
@ -2108,6 +2173,7 @@ 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_name_foreign_cmd);
SUITE_ADD_TEST(suite, test_banner_cmd);
SUITE_ADD_TEST(suite, test_email_cmd);
SUITE_ADD_TEST(suite, test_name_cmd_2274);
@ -2177,6 +2243,7 @@ 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);

View file

@ -1335,8 +1335,8 @@ static void do_fumble(castorder * co)
static int rc_cache;
fumble_f fun;
ADDMSG(&mage->faction->msgs,
msg_message("patzer", "unit region spell", mage, r, sp));
ADDMSG(&caster->faction->msgs,
msg_message("patzer", "unit region spell", caster, r, sp));
switch (rng_int() % 10) {
case 0:
/* wenn vorhanden spezieller Patzer, ansonsten nix */
@ -1385,14 +1385,14 @@ static void do_fumble(castorder * co)
c = create_curse(mage, &mage->attribs, &ct_skillmod, level,
duration, effect, 1);
c->data.i = SK_MAGIC;
ADDMSG(&mage->faction->msgs, msg_message("patzer2", "unit region", mage, r));
ADDMSG(&caster->faction->msgs, msg_message("patzer2", "unit region", caster, r));
break;
case 3:
case 4:
/* Spruch schlaegt fehl, alle Magiepunkte weg */
set_spellpoints(mage, 0);
ADDMSG(&mage->faction->msgs, msg_message("patzer3", "unit region spell",
mage, r, sp));
ADDMSG(&caster->faction->msgs, msg_message("patzer3", "unit region spell",
caster, r, sp));
break;
case 5:
@ -1400,8 +1400,8 @@ static void do_fumble(castorder * co)
/* Spruch gelingt, aber alle Magiepunkte weg */
co->level = cast_spell(co);
set_spellpoints(mage, 0);
ADDMSG(&mage->faction->msgs, msg_message("patzer4", "unit region spell",
mage, r, sp));
ADDMSG(&caster->faction->msgs, msg_message("patzer4", "unit region spell",
caster, r, sp));
break;
case 7:
@ -1410,8 +1410,8 @@ static void do_fumble(castorder * co)
default:
/* Spruch gelingt, alle nachfolgenden Sprueche werden 2^4 so teuer */
co->level = cast_spell(co);
ADDMSG(&mage->faction->msgs, msg_message("patzer5", "unit region spell",
mage, r, sp));
ADDMSG(&caster->faction->msgs, msg_message("patzer5", "unit region spell",
caster, r, sp));
countspells(caster, 3);
}
}
@ -1626,7 +1626,7 @@ verify_unit(region * r, unit * mage, const spell * sp, spllprm * spobj,
static void
verify_targets(castorder * co, int *invalid, int *resist, int *success)
{
unit *mage = co_get_magician(co);
unit *caster = co_get_caster(co);
const spell *sp = co->sp;
region *target_r = co_get_region(co);
spellparameter *sa = co->par;
@ -1647,15 +1647,15 @@ verify_targets(castorder * co, int *invalid, int *resist, int *success)
switch (spobj->typ) {
case SPP_TEMP:
case SPP_UNIT:
if (!verify_unit(target_r, mage, sp, spobj, co->order))
if (!verify_unit(target_r, caster, sp, spobj, co->order))
++ * invalid;
break;
case SPP_BUILDING:
if (!verify_building(target_r, mage, sp, spobj, co->order))
if (!verify_building(target_r, caster, sp, spobj, co->order))
++ * invalid;
break;
case SPP_SHIP:
if (!verify_ship(target_r, mage, sp, spobj, co->order))
if (!verify_ship(target_r, caster, sp, spobj, co->order))
++ * invalid;
break;
default:
@ -1679,13 +1679,13 @@ verify_targets(castorder * co, int *invalid, int *resist, int *success)
u = spobj->data.u;
if ((sp->sptyp & TESTRESISTANCE)
&& target_resists_magic(mage, u, TYP_UNIT, 0)) {
&& target_resists_magic(caster, u, TYP_UNIT, 0)) {
/* Fehlermeldung */
spobj->data.i = u->no;
spobj->flag = TARGET_RESISTS;
++*resist;
ADDMSG(&mage->faction->msgs, msg_message("spellunitresists",
"unit region command target", mage, mage->region, co->order, u));
ADDMSG(&caster->faction->msgs, msg_message("spellunitresists",
"unit region command target", caster, caster->region, co->order, u));
break;
}
@ -1696,13 +1696,13 @@ verify_targets(castorder * co, int *invalid, int *resist, int *success)
b = spobj->data.b;
if ((sp->sptyp & TESTRESISTANCE)
&& target_resists_magic(mage, b, TYP_BUILDING, 0)) { /* Fehlermeldung */
&& target_resists_magic(caster, b, TYP_BUILDING, 0)) { /* Fehlermeldung */
spobj->data.i = b->no;
spobj->flag = TARGET_RESISTS;
++*resist;
ADDMSG(&mage->faction->msgs, msg_message("spellbuildingresists",
ADDMSG(&caster->faction->msgs, msg_message("spellbuildingresists",
"unit region command id",
mage, mage->region, co->order, spobj->data.i));
caster, caster->region, co->order, spobj->data.i));
break;
}
++*success;
@ -1711,11 +1711,11 @@ verify_targets(castorder * co, int *invalid, int *resist, int *success)
sh = spobj->data.sh;
if ((sp->sptyp & TESTRESISTANCE)
&& target_resists_magic(mage, sh, TYP_SHIP, 0)) { /* Fehlermeldung */
&& target_resists_magic(caster, sh, TYP_SHIP, 0)) { /* Fehlermeldung */
spobj->data.i = sh->no;
spobj->flag = TARGET_RESISTS;
++*resist;
ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order,
ADDMSG(&caster->faction->msgs, msg_feedback(caster, co->order,
"spellshipresists", "ship", sh));
break;
}
@ -1728,11 +1728,11 @@ verify_targets(castorder * co, int *invalid, int *resist, int *success)
tr = spobj->data.r;
if ((sp->sptyp & TESTRESISTANCE)
&& target_resists_magic(mage, tr, TYP_REGION, 0)) { /* Fehlermeldung */
&& target_resists_magic(caster, tr, TYP_REGION, 0)) { /* Fehlermeldung */
spobj->flag = TARGET_RESISTS;
++*resist;
ADDMSG(&mage->faction->msgs, msg_message("spellregionresists",
"unit region command", mage, mage->region, co->order));
ADDMSG(&caster->faction->msgs, msg_message("spellregionresists",
"unit region command", caster, caster->region, co->order));
break;
}
++*success;
@ -1767,10 +1767,10 @@ verify_targets(castorder * co, int *invalid, int *resist, int *success)
co->par = sa;
if ((sp->sptyp & TESTRESISTANCE)) {
if (target_resists_magic(mage, target_r, TYP_REGION, 0)) {
if (target_resists_magic(caster, target_r, TYP_REGION, 0)) {
/* Fehlermeldung */
ADDMSG(&mage->faction->msgs, msg_message("spellregionresists",
"unit region command", mage, mage->region, co->order));
ADDMSG(&caster->faction->msgs, msg_message("spellregionresists",
"unit region command", caster, caster->region, co->order));
spobj->flag = TARGET_RESISTS;
++*resist;
}
@ -2848,8 +2848,8 @@ void magic(void)
/* Sprueche mit Fixkosten werden immer auf Stufe des Spruchs
* gezaubert, co->level ist aber defaultmaessig Stufe des Magiers */
if (spl_costtyp(sp) != SPC_FIX) {
ADDMSG(&mage->faction->msgs, msg_message("missing_components",
"unit spell level", mage, sp, cast_level));
ADDMSG(&caster->faction->msgs, msg_message("missing_components",
"unit spell level", caster, sp, cast_level));
}
}
@ -2864,8 +2864,8 @@ void magic(void)
/* die Staerke kann durch Antimagie auf 0 sinken */
if (co->force <= 0) {
co->force = 0;
ADDMSG(&mage->faction->msgs, msg_message("missing_force",
"unit spell level", mage, sp, co->level));
ADDMSG(&caster->faction->msgs, msg_message("missing_force",
"unit spell level", caster, sp, co->level));
}
/* Ziele auf Existenz pruefen und Magieresistenz feststellen. Wurde
@ -2885,8 +2885,8 @@ void magic(void)
co->force = 0;
/* zwar wurde mindestens ein Ziel gefunden, das widerstand
* jedoch dem Zauber. Kosten abziehen und abbrechen. */
ADDMSG(&mage->faction->msgs, msg_message("spell_resist",
"unit region spell", mage, r, sp));
ADDMSG(&caster->faction->msgs, msg_message("spell_resist",
"unit region spell", caster, r, sp));
}
}

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);
}

507
src/recruit.c Normal file
View file

@ -0,0 +1,507 @@
/*
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)) {
/* in Gletschern, Eisbergen gar nicht rekrutieren */
if (r_insectstalled(r)) {
return msg_error(u, ord, 97);
}
/* in Wüsten ganzjährig rekrutieren, sonst im Winter nur mit Trank */
if (r->terrain != newterrain(T_DESERT) && calendar_season(now) == SEASON_WINTER) {
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);
}
}
}
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);
}

40
src/recruit.h Normal file
View file

@ -0,0 +1,40 @@
/*
Copyright (c) 1998-2015, 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.
**/
#pragma once
#ifndef H_GC_RECRUIT
#define H_GC_RECRUIT
#ifdef __cplusplus
extern "C" {
#endif
struct message;
struct order;
struct race;
struct region;
struct unit;
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);
void recruit(struct region * r);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -750,7 +750,7 @@ static void append_message(sbstring *sbp, message *m, const faction * f) {
sbp->end += size;
}
static void prices(struct stream *out, const region * r, const faction * f)
static void report_prices(struct stream *out, const region * r, const faction * f)
{
const luxury_type *sale = NULL;
struct demand *dmd;
@ -771,6 +771,7 @@ static void prices(struct stream *out, const region * r, const faction * f)
m = msg_message("nr_market_sale", "product price",
sale->itype->rtype, sale->price);
newline(out);
nr_render(m, f->locale, buf, sizeof(buf), f);
msg_release(m);
sbs_adopt(&sbs, buf, sizeof(buf));
@ -867,7 +868,8 @@ static void report_region_description(struct stream *out, const region * r, fact
sbs_strcat(&sbs, LOC(f->locale, "see_neighbour"));
sbs_strcat(&sbs, ")");
}
else if (r->seen.mode == seen_lighthouse) {
else if ((r->seen.mode == seen_lighthouse)
|| (r->seen.mode == seen_lighthouse_land)) {
sbs_strcat(&sbs, " (");
sbs_strcat(&sbs, LOC(f->locale, "see_lighthouse"));
sbs_strcat(&sbs, ")");
@ -1154,17 +1156,22 @@ void report_region(struct stream *out, const region * r, faction * f)
}
report_region_description(out, r, f, see);
report_region_schemes(out, r, f);
report_region_edges(out, r, f, edges, ne);
if (r->seen.mode >= seen_unit) {
report_region_schemes(out, r, f);
}
if (r->seen.mode >= seen_lighthouse) {
report_region_edges(out, r, f, edges, ne);
}
}
static void statistics(struct stream *out, const region * r, const faction * f)
static void report_statistics(struct stream *out, const region * r, const faction * f)
{
int p = rpeasants(r);
message *m;
char buf[4096];
/* print */
newline(out);
m = msg_message("nr_stat_header", "region", r);
nr_render(m, f->locale, buf, sizeof(buf), f);
msg_release(m);
@ -1592,6 +1599,8 @@ static void allies(struct stream *out, const faction * f)
const group *g = f->groups;
char prefix[64];
rpline(out);
newline(out);
centre(out, LOC(f->locale, "nr_alliances"), false);
newline(out);
@ -1609,7 +1618,7 @@ static void allies(struct stream *out, const faction * f)
}
}
static void guards(struct stream *out, const region * r, const faction * see)
static void report_guards(struct stream *out, const region * r, const faction * see)
{
/* die Partei see sieht dies; wegen
* "unbekannte Partei", wenn man es selbst ist... */
@ -1678,6 +1687,8 @@ static void list_address(struct stream *out, const faction * uf, selist * seenfa
int qi = 0;
selist *flist = seenfactions;
rpline(out);
newline(out);
centre(out, LOC(uf->locale, "nr_addresses"), false);
newline(out);
@ -1806,12 +1817,10 @@ nr_building(struct stream *out, const region *r, const building *b, const factio
}
paragraph(out, buffer, 2, 0, 0);
if (r->seen.mode >= seen_lighthouse) {
nr_curses(out, 4, f, TYP_BUILDING, b);
}
nr_curses(out, 4, f, TYP_BUILDING, b);
}
static void nr_paragraph(struct stream *out, message * m, faction * f)
static void nr_paragraph(struct stream *out, message * m, const faction * f)
{
char buf[4096];
@ -1897,26 +1906,44 @@ static void cb_write_travelthru(region *r, unit *u, void *cbdata) {
void report_travelthru(struct stream *out, region *r, const faction *f)
{
int maxtravel;
assert(r);
assert(f);
if (!fval(r, RF_TRAVELUNIT)) {
return;
if (fval(r, RF_TRAVELUNIT)) {
int maxtravel = count_travelthru(r, f);
if (maxtravel > 0) {
cb_data cbdata;
char buf[8192];
newline(out);
init_cb(&cbdata, out, buf, sizeof(buf), f);
cbdata.maxtravel = maxtravel;
cbdata.writep +=
str_strlcpy(buf, LOC(f->locale, "travelthru_header"), sizeof(buf));
travelthru_map(r, cb_write_travelthru, &cbdata);
return;
}
}
}
/* How many are we listing? For grammar. */
maxtravel = count_travelthru(r, f);
if (maxtravel > 0) {
cb_data cbdata;
char buf[8192];
static void report_market(stream * out, const region *r, const faction *f) {
const item_type *lux = r_luxury(r);
const item_type *herb = r->land->herbtype;
message * m = NULL;
init_cb(&cbdata, out, buf, sizeof(buf), f);
cbdata.maxtravel = maxtravel;
cbdata.writep +=
str_strlcpy(buf, LOC(f->locale, "travelthru_header"), sizeof(buf));
travelthru_map(r, cb_write_travelthru, &cbdata);
return;
if (herb && lux) {
m = msg_message("nr_market_info_p", "p1 p2",
lux->rtype, herb->rtype);
}
else if (lux) {
m = msg_message("nr_market_info_s", "p1", lux->rtype);
}
else if (herb) {
m = msg_message("nr_market_info_s", "p1", herb->rtype);
}
if (m) {
newline(out);
nr_paragraph(out, m, f);
}
}
@ -2120,121 +2147,92 @@ report_plaintext(const char *filename, report_context * ctx,
for (r = ctx->first; r != ctx->last; r = r->next) {
int stealthmod = stealth_modifier(r, f, r->seen.mode);
building *b = r->buildings;
ship *sh = r->ships;
if (r->seen.mode < seen_lighthouse)
continue;
/* Beschreibung */
if (r->seen.mode >= seen_lighthouse_land) {
rpline(out);
newline(out);
report_region(out, r, f);
}
rpline(out);
newline(out);
if (r->seen.mode >= seen_unit) {
anyunits = 1;
report_region(out, r, f);
if (markets_module() && r->land) {
const item_type *lux = r_luxury(r);
const item_type *herb = r->land->herbtype;
m = NULL;
if (herb && lux) {
m = msg_message("nr_market_info_p", "p1 p2",
lux->rtype, herb->rtype);
}
else if (lux) {
m = msg_message("nr_market_info_s", "p1",lux->rtype);
}
else if (herb) {
m = msg_message("nr_market_info_s", "p1", herb->rtype);
}
if (m) {
newline(out);
nr_paragraph(out, m, f);
}
report_market(out, r, f);
}
else {
if (!fval(r->terrain, SEA_REGION) && rpeasants(r) / TRADE_FRACTION > 0) {
newline(out);
prices(out, r, f);
}
else if (!fval(r->terrain, SEA_REGION) && rpeasants(r) / TRADE_FRACTION > 0) {
report_prices(out, r, f);
}
guards(out, r, f);
newline(out);
report_guards(out, r, f);
report_travelthru(out, r, f);
}
else {
report_region(out, r, f);
newline(out);
report_travelthru(out, r, f);
}
if (wants_stats && r->seen.mode >= seen_travel) {
if (r->land || r->seen.mode >= seen_unit) {
newline(out);
statistics(out, r, f);
if (wants_stats) {
report_statistics(out, r, f);
}
}
else if (r->seen.mode >= seen_lighthouse) {
report_travelthru(out, r, f);
}
/* Nachrichten an REGION in der Region */
if (r->seen.mode >= seen_travel) {
if (r->seen.mode >= seen_lighthouse) {
message_list *mlist = r_getmessages(r, f);
newline(out);
if (mlist) {
struct mlist **split = merge_messages(mlist, r->msgs);
newline(out);
rp_messages(out, mlist, f, 0, false);
split_messages(mlist, split);
}
else {
else if (r->msgs) {
newline(out);
rp_messages(out, r->msgs, f, 0, false);
}
}
/* report all units. they are pre-sorted in an efficient manner */
u = r->units;
while (b) {
while (b && (!u || u->building != b)) {
nr_building(out, r, b, f);
b = b->next;
}
if (b) {
nr_building(out, r, b, f);
while (u && u->building == b) {
if (visible_unit(u, f, stealthmod, r->seen.mode)) {
nr_unit(out, f, u, 6, r->seen.mode);
/* report all units. they are pre-sorted in an efficient manner */
u = r->units;
if (r->seen.mode >= seen_travel) {
building *b = r->buildings;
while (b) {
while (b && (!u || u->building != b)) {
nr_building(out, r, b, f);
b = b->next;
}
u = u->next;
}
b = b->next;
}
}
while (u && !u->ship) {
if (visible_unit(u, f, stealthmod, r->seen.mode)) {
nr_unit(out, f, u, 4, r->seen.mode);
}
assert(!u->building);
u = u->next;
}
while (sh) {
while (sh && (!u || u->ship != sh)) {
nr_ship(out, r, sh, f, NULL);
sh = sh->next;
}
if (sh) {
nr_ship(out, r, sh, f, u);
while (u && u->ship == sh) {
if (visible_unit(u, f, stealthmod, r->seen.mode)) {
nr_unit(out, f, u, 6, r->seen.mode);
if (b) {
nr_building(out, r, b, f);
while (u && u->building == b) {
if (visible_unit(u, f, stealthmod, r->seen.mode)) {
nr_unit(out, f, u, 6, r->seen.mode);
}
u = u->next;
}
b = b->next;
}
u = u->next;
}
sh = sh->next;
}
while (u && !u->ship) {
if (visible_unit(u, f, stealthmod, r->seen.mode)) {
nr_unit(out, f, u, 4, r->seen.mode);
}
assert(!u->building);
u = u->next;
}
while (sh) {
while (sh && (!u || u->ship != sh)) {
nr_ship(out, r, sh, f, NULL);
sh = sh->next;
}
if (sh) {
nr_ship(out, r, sh, f, u);
while (u && u->ship == sh) {
if (visible_unit(u, f, stealthmod, r->seen.mode)) {
nr_unit(out, f, u, 6, r->seen.mode);
}
u = u->next;
}
sh = sh->next;
}
}
assert(!u);
}
assert(!u);
newline(out);
ERRNO_CHECK();
}
if (!is_monsters(f)) {

View file

@ -260,7 +260,7 @@ static void test_report_travelthru(CuTest *tc) {
out.api->rewind(out.handle);
len = out.api->read(out.handle, buf, sizeof(buf));
buf[len] = '\0';
CuAssertStrEquals_Msg(tc, "list one unit", "Durchreise: Hodor (1).\n", buf);
CuAssertStrEquals_Msg(tc, "list one unit", "\nDurchreise: Hodor (1).\n", buf);
mstream_done(&out);
mstream_init(&out);

View file

@ -102,6 +102,7 @@ const char *visibility[] = {
"none",
"neighbour",
"lighthouse",
"lighthouse",
"travel",
"far",
"unit",
@ -1115,38 +1116,40 @@ void get_addresses(report_context * ctx)
}
for (; r != NULL; r = r->next) {
int stealthmod = stealth_modifier(r, ctx->f, r->seen.mode);
if (r->seen.mode == seen_lighthouse) {
unit *u = r->units;
for (; u; u = u->next) {
faction *sf = visible_faction(ctx->f, u);
if (lastf != sf) {
if (u->building || u->ship || (stealthmod > INT_MIN
&& cansee(ctx->f, r, u, stealthmod))) {
add_seen_faction_i(&flist, sf);
lastf = sf;
if (r->seen.mode >= seen_lighthouse) {
int stealthmod = stealth_modifier(r, ctx->f, r->seen.mode);
if (r->seen.mode == seen_lighthouse) {
unit *u = r->units;
for (; u; u = u->next) {
faction *sf = visible_faction(ctx->f, u);
if (lastf != sf) {
if (u->building || u->ship || (stealthmod > INT_MIN
&& cansee(ctx->f, r, u, stealthmod))) {
add_seen_faction_i(&flist, sf);
lastf = sf;
}
}
}
}
}
else if (r->seen.mode == seen_travel) {
/* when we travel through a region, then we must add
* the factions of any units we saw */
add_travelthru_addresses(r, ctx->f, &flist, stealthmod);
}
else if (r->seen.mode > seen_travel) {
const unit *u = r->units;
while (u != NULL) {
if (u->faction != ctx->f) {
faction *sf = visible_faction(ctx->f, u);
bool ballied = sf && sf != ctx->f && sf != lastf
&& !fval(u, UFL_ANON_FACTION) && cansee(ctx->f, r, u, stealthmod);
if (ballied || is_allied(ctx->f, sf)) {
add_seen_faction_i(&flist, sf);
lastf = sf;
else if (r->seen.mode == seen_travel) {
/* when we travel through a region, then we must add
* the factions of any units we saw */
add_travelthru_addresses(r, ctx->f, &flist, stealthmod);
}
else if (r->seen.mode > seen_travel) {
const unit *u = r->units;
while (u != NULL) {
if (u->faction != ctx->f) {
faction *sf = visible_faction(ctx->f, u);
bool ballied = sf && sf != ctx->f && sf != lastf
&& !fval(u, UFL_ANON_FACTION) && cansee(ctx->f, r, u, stealthmod);
if (ballied || is_allied(ctx->f, sf)) {
add_seen_faction_i(&flist, sf);
lastf = sf;
}
}
u = u->next;
}
u = u->next;
}
}
}
@ -1293,6 +1296,9 @@ static void add_seen_lighthouse(region *r, faction *f)
if (r->terrain->flags & SEA_REGION) {
add_seen_nb(f, r, seen_lighthouse);
}
else {
add_seen_nb(f, r, seen_lighthouse_land);
}
}
/** mark all regions seen by the lighthouse.
@ -1460,14 +1466,14 @@ void report_warnings(faction *f, int now)
}
if (f->race == get_race(RC_INSECT)) {
gamedate date;
get_gamedate(now + 1, &date);
season_t season = calendar_season(now + 1);
if (date.season == SEASON_WINTER) {
if (season == SEASON_WINTER) {
ADDMSG(&f->msgs, msg_message("nr_insectwinter", ""));
}
else if (date.season == SEASON_AUTUMN) {
if (get_gamedate(now + 2 + 2, &date)->season == SEASON_WINTER) {
else if (season == SEASON_AUTUMN) {
/* warning: next turn is the last week of autumn */
if (calendar_season(now + 2) == SEASON_WINTER) {
ADDMSG(&f->msgs, msg_message("nr_insectfall", ""));
}
}

View file

@ -573,7 +573,7 @@ static void test_prepare_lighthouse(CuTest *tc) {
CuAssertIntEquals(tc, seen_unit, r1->seen.mode);
CuAssertIntEquals(tc, seen_lighthouse, r2->seen.mode);
CuAssertIntEquals(tc, seen_neighbour, r3->seen.mode);
CuAssertIntEquals(tc, seen_neighbour, r4->seen.mode);
CuAssertIntEquals(tc, seen_lighthouse_land, r4->seen.mode);
finish_reports(&ctx);
test_teardown();
}
@ -868,6 +868,7 @@ static void test_visible_unit(CuTest *tc) {
CuAssertTrue(tc, !visible_unit(u, f, 0, seen_travel));
CuAssertTrue(tc, !visible_unit(u, f, 0, seen_none));
CuAssertTrue(tc, !visible_unit(u, f, 0, seen_neighbour));
CuAssertTrue(tc, !visible_unit(u, f, 0, seen_lighthouse_land));
CuAssertTrue(tc, visible_unit(u, f, 0, seen_lighthouse));
CuAssertTrue(tc, !visible_unit(u, f, -2, seen_lighthouse));

View file

@ -70,7 +70,7 @@ void expandstealing(region * r, econ_request * stealorders)
break;
}
u = findunit(requests[j]->type.steal.no);
u = findunit(requests[j]->data.steal.no);
if (u && u->region == r) {
n = get_pooled(u, rsilver, GET_ALL, INT_MAX);
@ -233,8 +233,9 @@ void steal_cmd(unit * u, struct order *ord, econ_request ** stealorders)
if (!o) abort();
o->unit = u;
o->qty = 1; /* Betrag steht in u->wants */
o->type.steal.no = u2->no;
o->type.steal.goblin = goblin; /* Merken, wenn Goblin-Spezialklau */
o->type = ECON_STEAL;
o->data.steal.no = u2->no;
o->data.steal.goblin = goblin; /* Merken, wenn Goblin-Spezialklau */
o->next = *stealorders;
*stealorders = o;

View file

@ -860,8 +860,9 @@ void reduce_skill_days(unit *u, skill_t sk, int days) {
}
}
/** Talente von Daemonen verschieben sich.
*/
/**
* Talente von Daemonen verschieben sich.
*/
void demon_skillchange(unit *u)
{
skill *sv = u->skills;

View file

@ -177,11 +177,11 @@ static int count_umlaut(const char *s)
int result = 0;
const char *cp;
for (cp = s; *cp; ++cp) {
ucs4_t ucs = *cp;
if (ucs & 0x80) {
wint_t wc = *cp;
if (wc & 0x80) {
size_t size;
int err;
err = unicode_utf8_to_ucs4(&ucs, cp, &size);
err = unicode_utf8_decode(&wc, cp, &size);
if (err != 0) {
log_error("illegal utf8 encoding %s at %s", s, cp);
return result;

View file

@ -277,7 +277,7 @@ static void test_reset(void) {
void test_create_calendar(void) {
config_set_int("game.start", 184);
months_per_year = 9;
month_season = malloc(sizeof(int) * months_per_year);
month_season = malloc(sizeof(season_t) * months_per_year);
if (!month_season) abort();
month_season[0] = SEASON_SUMMER;
month_season[1] = SEASON_AUTUMN;
@ -576,15 +576,11 @@ struct message * test_find_messagetype_ex(struct message_list *msgs, const char
struct mlist *ml;
if (!msgs) return 0;
for (ml = msgs->begin; ml; ml = ml->next) {
if (strcmp(name, test_get_messagetype(ml->msg)) == 0) {
if (prev) {
if (ml->msg == prev) {
prev = NULL;
}
}
else {
return ml->msg;
}
if (prev && ml->msg == prev) {
prev = NULL;
}
else if (strcmp(name, test_get_messagetype(ml->msg)) == 0) {
return ml->msg;
}
}
return 0;

View file

@ -26,12 +26,12 @@ static int eatwhite(const char *ptr, size_t * total_size)
*total_size = 0;
while (*ptr) {
ucs4_t ucs;
wint_t wc;
size_t size = 0;
ret = unicode_utf8_to_ucs4(&ucs, ptr, &size);
ret = unicode_utf8_decode(&wc, ptr, &size);
if (ret != 0)
break;
if (!iswspace((wint_t)ucs))
if (!iswspace(wc))
break;
*total_size += size;
ptr += size;
@ -86,7 +86,7 @@ static const char *getbuf_utf8(FILE * F)
}
cont = false;
while (*bp && cp < fbuf + MAXLINE) {
ucs4_t ucs;
wint_t wc;
size_t size;
int ret;
@ -119,14 +119,14 @@ static const char *getbuf_utf8(FILE * F)
}
}
ret = unicode_utf8_to_ucs4(&ucs, bp, &size);
ret = unicode_utf8_decode(&wc, bp, &size);
if (ret != 0) {
unicode_warning(bp);
break;
}
if (iswspace((wint_t)ucs)) {
if (iswspace(wc)) {
if (!quote) {
bp += size;
ret = eatwhite(bp, &size);
@ -151,7 +151,7 @@ static const char *getbuf_utf8(FILE * F)
bp += size;
}
}
else if (iswcntrl((wint_t)ucs)) {
else if (iswcntrl(wc)) {
if (!comment && cp < fbuf + MAXLINE) {
*cp++ = '?';
}

View file

@ -27,7 +27,7 @@ static parse_state *states;
static int eatwhitespace_c(const char **str_p)
{
int ret = 0;
ucs4_t ucs;
wint_t wc;
size_t len;
const char *str = *str_p;
@ -40,12 +40,12 @@ static int eatwhitespace_c(const char **str_p)
++str;
}
else {
ret = unicode_utf8_to_ucs4(&ucs, str, &len);
ret = unicode_utf8_decode(&wc, str, &len);
if (ret != 0) {
log_warning("illegal character sequence in UTF8 string: %s\n", str);
break;
}
if (!iswspace((wint_t)ucs))
if (!iswspace(wc))
break;
str += len;
}
@ -106,16 +106,16 @@ void skip_token(void)
eatwhitespace_c(&states->current_token);
while (*states->current_token) {
ucs4_t ucs;
wint_t wc;
size_t len;
unsigned char utf8_character = (unsigned char)states->current_token[0];
if (~utf8_character & 0x80) {
ucs = utf8_character;
wc = utf8_character;
++states->current_token;
}
else {
int ret = unicode_utf8_to_ucs4(&ucs, states->current_token, &len);
int ret = unicode_utf8_decode(&wc, states->current_token, &len);
if (ret == 0) {
states->current_token += len;
}
@ -123,7 +123,7 @@ void skip_token(void)
log_warning("illegal character sequence in UTF8 string: %s\n", states->current_token);
}
}
if (iswspace((wint_t)ucs) && quotechar == 0) {
if (iswspace(wc) && quotechar == 0) {
return;
}
else {
@ -160,17 +160,17 @@ char *parse_token(const char **str, char *lbuf, size_t buflen)
return 0;
}
while (*ctoken) {
ucs4_t ucs;
wint_t wc;
size_t len;
bool copy = false;
unsigned char utf8_character = *(unsigned char *)ctoken;
if (~utf8_character & 0x80) {
ucs = utf8_character;
wc = utf8_character;
len = 1;
}
else {
int ret = unicode_utf8_to_ucs4(&ucs, ctoken, &len);
int ret = unicode_utf8_decode(&wc, ctoken, &len);
if (ret != 0) {
log_warning("illegal character sequence in UTF8 string: %s\n", ctoken);
break;
@ -180,7 +180,7 @@ char *parse_token(const char **str, char *lbuf, size_t buflen)
copy = true;
escape = false;
}
else if (iswspace((wint_t)ucs)) {
else if (iswspace(wc)) {
if (quotechar == 0)
break;
copy = true;

View file

@ -32,7 +32,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
typedef struct tref {
struct tref *nexthash;
ucs4_t ucs;
wint_t wc;
struct tnode *node;
} tref;
@ -99,8 +99,8 @@ char * transliterate(char * out, size_t size, const char * in)
size -= advance;
}
else {
ucs4_t ucs;
int ret = unicode_utf8_to_ucs4(&ucs, src, &len);
wint_t wc;
int ret = unicode_utf8_decode(&wc, src, &len);
if (ret != 0) {
/* encoding is broken. yikes */
log_error("transliterate | encoding error in '%s'\n", src);
@ -127,7 +127,7 @@ void addtoken(tnode ** root, const char *str, variant id)
{
tnode * tk;
static const struct replace {
ucs4_t ucs;
wint_t wc;
const char str[3];
} replace[] = {
/* match lower-case (!) umlauts and others to transcriptions */
@ -150,10 +150,10 @@ void addtoken(tnode ** root, const char *str, variant id)
else {
tref *next;
int ret, index, i = 0;
ucs4_t ucs, lcs;
wint_t ucs, lcs;
size_t len;
ret = unicode_utf8_to_ucs4(&ucs, str, &len);
ret = unicode_utf8_decode(&ucs, str, &len);
assert(ret == 0 || !"invalid utf8 string");
lcs = ucs;
@ -166,7 +166,7 @@ void addtoken(tnode ** root, const char *str, variant id)
next = tk->next[index];
if (!(tk->flags & LEAF))
tk->id = id;
while (next && next->ucs != ucs)
while (next && next->wc != ucs)
next = next->nexthash;
if (!next) {
tref *ref;
@ -181,7 +181,7 @@ void addtoken(tnode ** root, const char *str, variant id)
ref = (tref *)malloc(sizeof(tref));
if (!ref) abort();
ref->ucs = ucs;
ref->wc = ucs;
ref->node = node;
ref->nexthash = tk->next[index];
tk->next[index] = ref;
@ -195,7 +195,7 @@ void addtoken(tnode ** root, const char *str, variant id)
#endif
ref = (tref *)malloc(sizeof(tref));
assert_alloc(ref);
ref->ucs = lcs;
ref->wc = lcs;
ref->node = node;
++node->refcount;
ref->nexthash = tk->next[index];
@ -211,7 +211,7 @@ void addtoken(tnode ** root, const char *str, variant id)
}
addtoken(&next->node, str + len, id);
while (replace[i].str[0]) {
if (lcs == replace[i].ucs) {
if (lcs == replace[i].wc) {
char zText[1024];
memcpy(zText, replace[i].str, 3);
str_strlcpy(zText + 2, (const char *)str + len, sizeof(zText)-2);
@ -255,9 +255,9 @@ int findtoken(const void * root, const char *key, variant * result)
do {
int index;
const tref *ref;
ucs4_t ucs;
wint_t wc;
size_t len;
int ret = unicode_utf8_to_ucs4(&ucs, str, &len);
int ret = unicode_utf8_decode(&wc, str, &len);
if (ret != 0) {
/* encoding is broken. youch */
@ -265,12 +265,12 @@ int findtoken(const void * root, const char *key, variant * result)
return E_TOK_NOMATCH;
}
#if NODEHASHSIZE == 8
index = ucs & 7;
index = wc & 7;
#else
index = ucs % NODEHASHSIZE;
index = wc % NODEHASHSIZE;
#endif
ref = tk->next[index];
while (ref && ref->ucs != ucs)
while (ref && ref->wc != wc)
ref = ref->nexthash;
str += len;
if (!ref) {

View file

@ -13,6 +13,7 @@
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <string.h>
#include <wctype.h>
#include <ctype.h>
@ -33,18 +34,26 @@
#define B00000011 0x03
#define B00000001 0x01
int unicode_utf8_trim(utf8_t *buf)
static bool char_trimmed(wint_t wc) {
if (wc >= 0x2000 && wc <= 0x200f) {
/* only weird stuff here */
return true;
}
return iswspace(wc) || iswcntrl(wc);
}
size_t unicode_utf8_trim(char *buf)
{
int result = 0, ts = 0;
utf8_t *op = buf, *ip = buf, *lc = buf;
char *op = buf, *ip = buf, *lc = buf;
assert(buf);
while (*ip) {
size_t size = 1;
wint_t wc = *ip;
wint_t wc = *(unsigned char *)ip;
if (wc & 0x80) {
ucs4_t ucs = 0;
wint_t ucs = 0;
if (ip[1]) {
int ret = unicode_utf8_to_ucs4(&ucs, ip, &size);
int ret = unicode_utf8_decode(&ucs, ip, &size);
if (ret != 0) {
return ret;
}
@ -56,22 +65,24 @@ int unicode_utf8_trim(utf8_t *buf)
++result;
}
}
if (op == buf && iswspace(wc)) {
++result;
if (op == buf && char_trimmed(wc)) {
result += size;
}
else if (wc>255 || !iscntrl(wc)) {
else if (wc>255 || !iswcntrl(wc)) {
if (op != ip) {
memmove(op, ip, size);
}
op += size;
if (iswspace(wc)) ++ts;
if (char_trimmed(wc)) {
ts += size;
}
else {
lc = op;
ts = 0;
}
}
else {
++result;
result += size;
}
ip += size;
}
@ -79,15 +90,15 @@ int unicode_utf8_trim(utf8_t *buf)
return result + ts;
}
int unicode_utf8_tolower(utf8_t * op, size_t outlen, const utf8_t * ip)
int unicode_utf8_tolower(char * op, size_t outlen, const char * ip)
{
while (*ip) {
ucs4_t ucs = *ip;
ucs4_t low;
wint_t ucs = *ip;
wint_t low;
size_t size = 1;
if (ucs & 0x80) {
int ret = unicode_utf8_to_ucs4(&ucs, ip, &size);
int ret = unicode_utf8_decode(&ucs, ip, &size);
if (ret != 0) {
return ret;
}
@ -104,7 +115,7 @@ int unicode_utf8_tolower(utf8_t * op, size_t outlen, const utf8_t * ip)
}
else {
ip += size;
unicode_ucs4_to_utf8(op, &size, low);
unicode_utf8_encode(op, &size, low);
op += size;
outlen -= size;
}
@ -114,7 +125,7 @@ int unicode_utf8_tolower(utf8_t * op, size_t outlen, const utf8_t * ip)
}
int
unicode_latin1_to_utf8(utf8_t * dst, size_t * outlen, const char *in,
unicode_latin1_to_utf8(char * dst, size_t * outlen, const char *in,
size_t * inlen)
{
int is = (int)*inlen;
@ -148,15 +159,15 @@ unicode_latin1_to_utf8(utf8_t * dst, size_t * outlen, const char *in,
return (int)*outlen;
}
int unicode_utf8_strcasecmp(const utf8_t * a, const utf8_t *b)
int unicode_utf8_strcasecmp(const char * a, const char *b)
{
while (*a && *b) {
int ret;
size_t size;
ucs4_t ucsa = *a, ucsb = *b;
wint_t ucsa = *a, ucsb = *b;
if (ucsa & 0x80) {
ret = unicode_utf8_to_ucs4(&ucsa, a, &size);
ret = unicode_utf8_decode(&ucsa, a, &size);
if (ret != 0)
return -1;
a += size;
@ -164,7 +175,7 @@ int unicode_utf8_strcasecmp(const utf8_t * a, const utf8_t *b)
else
++a;
if (ucsb & 0x80) {
ret = unicode_utf8_to_ucs4(&ucsb, b, &size);
ret = unicode_utf8_decode(&ucsb, b, &size);
if (ret != 0)
return -1;
b += size;
@ -188,10 +199,10 @@ int unicode_utf8_strcasecmp(const utf8_t * a, const utf8_t *b)
return 0;
}
/* Convert a UCS-4 character to UTF-8. */
/* Convert a wide character to UTF-8. */
int
unicode_ucs4_to_utf8(utf8_t * utf8_character, size_t * size,
ucs4_t ucs4_character)
unicode_utf8_encode(char * utf8_character, size_t * size,
wint_t ucs4_character)
{
int utf8_bytes;
@ -213,6 +224,7 @@ unicode_ucs4_to_utf8(utf8_t * utf8_character, size_t * size,
utf8_character[1] = (char)(((ucs4_character >> 6) & B00111111) | B10000000);
utf8_character[2] = (char)((ucs4_character & B00111111) | B10000000);
}
#if 0
else if (ucs4_character <= 0x001FFFFF) {
/* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
utf8_bytes = 4;
@ -246,6 +258,7 @@ unicode_ucs4_to_utf8(utf8_t * utf8_character, size_t * size,
utf8_character[4] = (char)(((ucs4_character >> 6) & B00111111) | B10000000);
utf8_character[5] = (char)((ucs4_character & B00111111) | B10000000);
}
#endif
else {
return EILSEQ;
}
@ -257,10 +270,10 @@ unicode_ucs4_to_utf8(utf8_t * utf8_character, size_t * size,
/* Convert a UTF-8 encoded character to UCS-4. */
int
unicode_utf8_to_ucs4(ucs4_t * ucs4_character, const utf8_t * utf8_string,
unicode_utf8_decode(wint_t * ucs4_character, const char * utf8_string,
size_t * length)
{
utf8_t utf8_character = utf8_string[0];
char utf8_character = utf8_string[0];
/* Is the character in the ASCII range? If so, just copy it to the
output. */
@ -361,13 +374,13 @@ unicode_utf8_to_ucs4(ucs4_t * ucs4_character, const utf8_t * utf8_string,
/** Convert a UTF-8 encoded character to CP437. */
int
unicode_utf8_to_cp437(unsigned char *cp_character, const utf8_t * utf8_string,
unicode_utf8_to_cp437(unsigned char *cp_character, const char * utf8_string,
size_t * length)
{
ucs4_t ucs4_character;
wint_t ucs4_character;
int result;
result = unicode_utf8_to_ucs4(&ucs4_character, utf8_string, length);
result = unicode_utf8_decode(&ucs4_character, utf8_string, length);
if (result != 0) {
/* pass decoding characters upstream */
return result;
@ -378,7 +391,7 @@ unicode_utf8_to_cp437(unsigned char *cp_character, const utf8_t * utf8_string,
}
else {
struct {
ucs4_t ucs4;
wint_t ucs4;
unsigned char cp437;
} xref[160] = {
{ 0x00A0, 255 },
@ -566,7 +579,7 @@ unicode_utf8_to_cp437(unsigned char *cp_character, const utf8_t * utf8_string,
}
/** Convert a UTF-8 encoded character to ASCII, with '?' replacements. */
int unicode_utf8_to_ascii(unsigned char *cp_character, const utf8_t * utf8_string,
int unicode_utf8_to_ascii(unsigned char *cp_character, const char * utf8_string,
size_t *length)
{
int result = unicode_utf8_to_cp437(cp_character, utf8_string, length);
@ -579,13 +592,13 @@ int unicode_utf8_to_ascii(unsigned char *cp_character, const utf8_t * utf8_strin
}
/** Convert a UTF-8 encoded character to CP1252. */
int unicode_utf8_to_cp1252(unsigned char *cp_character, const utf8_t * utf8_string,
int unicode_utf8_to_cp1252(unsigned char *cp_character, const char * utf8_string,
size_t * length)
{
ucs4_t ucs4_character;
wint_t ucs4_character;
int result;
result = unicode_utf8_to_ucs4(&ucs4_character, utf8_string, length);
result = unicode_utf8_decode(&ucs4_character, utf8_string, length);
if (result != 0) {
/* pass decoding characters upstream */
return result;
@ -596,7 +609,7 @@ int unicode_utf8_to_cp1252(unsigned char *cp_character, const utf8_t * utf8_stri
}
else {
struct {
ucs4_t ucs4;
wint_t ucs4;
unsigned char cp;
} xref[] = {
{ 0x0081, 0x81 },

View file

@ -19,30 +19,29 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#ifndef _UNICODE_H
#define _UNICODE_H
#include <stddef.h>
#include <wchar.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <wchar.h>
#define USE_UNICODE
typedef long ucs4_t;
typedef char utf8_t;
int unicode_utf8_to_cp437(unsigned char *result, const utf8_t * utf8_string,
int unicode_utf8_to_cp437(unsigned char *result, const char * utf8_string,
size_t * length);
int unicode_utf8_to_cp1252(unsigned char *result, const utf8_t * utf8_string,
int unicode_utf8_to_cp1252(unsigned char *result, const char * utf8_string,
size_t * length);
int unicode_utf8_to_ucs4(ucs4_t * result, const utf8_t * utf8_string,
int unicode_utf8_decode(wint_t * result, const char * utf8_string,
size_t * length);
int unicode_ucs4_to_utf8(utf8_t * result, size_t * size,
ucs4_t ucs4_character);
int unicode_utf8_to_ascii(unsigned char *cp_character, const utf8_t * utf8_string,
int unicode_utf8_encode(char * result, size_t * size,
wint_t ucs4_character);
int unicode_utf8_to_ascii(unsigned char *cp_character, const char * utf8_string,
size_t *length);
int unicode_utf8_strcasecmp(const utf8_t * a, const utf8_t * b);
int unicode_latin1_to_utf8(utf8_t * out, size_t * outlen,
int unicode_utf8_strcasecmp(const char * a, const char * b);
int unicode_latin1_to_utf8(char * out, size_t * outlen,
const char *in, size_t * inlen);
int unicode_utf8_tolower(utf8_t *op, size_t outlen, const utf8_t *ip);
int unicode_utf8_trim(utf8_t *ip);
int unicode_utf8_tolower(char *op, size_t outlen, const char *ip);
size_t unicode_utf8_trim(char *ip);
#ifdef __cplusplus
}

View file

@ -1,6 +1,12 @@
#ifdef _MSC_VER
#include <platform.h>
#include <CuTest.h>
#endif
#include "unicode.h"
#include <CuTest.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
@ -9,9 +15,33 @@ static void test_unicode_trim(CuTest * tc)
{
char buffer[32];
strcpy(buffer, "Hello Word");
strcpy(buffer, "Hello World");
CuAssertIntEquals(tc, 0, unicode_utf8_trim(buffer));
CuAssertStrEquals(tc, "Hello Word", buffer);
CuAssertStrEquals(tc, "Hello World", buffer);
strcpy(buffer, " Hello World");
CuAssertIntEquals(tc, 2, unicode_utf8_trim(buffer));
CuAssertStrEquals(tc, "Hello World", buffer);
strcpy(buffer, "Hello World ");
CuAssertIntEquals(tc, 2, unicode_utf8_trim(buffer));
CuAssertStrEquals(tc, "Hello World", buffer);
strcpy(buffer, " Hello World ");
CuAssertIntEquals(tc, 2, unicode_utf8_trim(buffer));
CuAssertStrEquals(tc, "Hello World", buffer);
strcpy(buffer, "Hello\t\r\nWorld");
CuAssertIntEquals(tc, 3, unicode_utf8_trim(buffer));
CuAssertStrEquals(tc, "HelloWorld", buffer);
strcpy(buffer, "LTR");
buffer[3] = -30;
buffer[4] = -128;
buffer[5] = -114;
buffer[6] = 0;
CuAssertIntEquals(tc, 3, unicode_utf8_trim(buffer));
CuAssertStrEquals(tc, "LTR", buffer);
strcpy(buffer, " Hello Word ");
CuAssertIntEquals(tc, 4, unicode_utf8_trim(buffer));
@ -48,7 +78,7 @@ static void test_unicode_tolower(CuTest * tc)
static void test_unicode_utf8_to_other(CuTest *tc)
{
const unsigned char uchar_str[] = { 0xc3, 0x98, 0xc5, 0xb8, 0xc2, 0x9d, 'l', 0 }; /* &Oslash;&Yuml;&#157;l */
utf8_t *utf8_str = (utf8_t *)uchar_str;
char *utf8_str = (char *)uchar_str;
unsigned char ch;
size_t sz;
CuAssertIntEquals(tc, 0, unicode_utf8_to_cp437(&ch, utf8_str, &sz));
@ -92,27 +122,27 @@ static void test_unicode_utf8_to_other(CuTest *tc)
}
static void test_unicode_utf8_to_ucs(CuTest *tc) {
ucs4_t ucs;
wint_t wc;
size_t sz;
CuAssertIntEquals(tc, 0, unicode_utf8_to_ucs4(&ucs, "a", &sz));
CuAssertIntEquals(tc, 'a', ucs);
CuAssertIntEquals(tc, 0, unicode_utf8_decode(&wc, "a", &sz));
CuAssertIntEquals(tc, 'a', wc);
CuAssertIntEquals(tc, 1, sz);
}
static void test_unicode_bug2262(CuTest *tc) {
char name[7];
ucs4_t ucs;
wint_t wc;
size_t sz;
strcpy(name, "utende");
CuAssertIntEquals(tc, 0, unicode_utf8_to_ucs4(&ucs, name, &sz));
CuAssertIntEquals(tc, 0, unicode_utf8_decode(&wc, name, &sz));
CuAssertIntEquals(tc, 1, sz);
CuAssertIntEquals(tc, 'u', ucs);
CuAssertIntEquals(tc, 'u', wc);
CuAssertIntEquals(tc, 0, unicode_utf8_trim(name));
name[0] = -4; /* latin1: &uuml; should fail to decode */
CuAssertIntEquals(tc, EILSEQ, unicode_utf8_to_ucs4(&ucs, name, &sz));
CuAssertIntEquals(tc, EILSEQ, unicode_utf8_decode(&wc, name, &sz));
CuAssertIntEquals(tc, EILSEQ, unicode_utf8_trim(name));
}
@ -123,26 +153,47 @@ static void test_unicode_compare(CuTest *tc)
CuAssertIntEquals(tc, 1, unicode_utf8_strcasecmp("bacdefg123", "ABCDEFG123"));
}
static void test_unicode_farsi_nzwj(CuTest *tc) {
const char str[] = { 0xe2, 0x80, 0x8c, 0xd8, 0xa7, 0xd9, 0x84, 0xd8, 0xaf,
0xdb, 0x8c, 0xd9, 0x86, 0x20, 0xd9, 0x85, 0xd8, 0xad, 0xd9, 0x85, 0xd8,
0xaf, 0x20, 0xd8, 0xb1, 0xd9, 0x88, 0xd9, 0x85, 0xdb, 0x8c, 0xe2, 0x80,
0x8e, 0xe2, 0x80, 0x8e, 0x00 };
static void test_unicode_trim_zwnj(CuTest *tc) {
const char zwnj[] = { 0xe2, 0x80, 0x8c, 0x00 };
char name[64];
strcpy(name, str);
char expect[64];
snprintf(name, sizeof(name), "%sA%sB%s ", zwnj, zwnj, zwnj);
snprintf(expect, sizeof(expect), "A%sB", zwnj);
CuAssertIntEquals(tc, 8, unicode_utf8_trim(name));
CuAssertStrEquals(tc, expect, name);
}
static void test_unicode_trim_ltrm(CuTest *tc) {
const char ltrm[] = { 0xe2, 0x80, 0x8e, 0x00 };
char name[64];
char expect[64];
snprintf(name, sizeof(name), "%sBrot%szeit%s ", ltrm, ltrm, ltrm);
snprintf(expect, sizeof(expect), "Brot%szeit", ltrm);
CuAssertIntEquals(tc, 8, unicode_utf8_trim(name));
CuAssertStrEquals(tc, expect, name);
}
static void test_unicode_trim_emoji(CuTest *tc) {
const char clock[] = { 0xE2, 0x8F, 0xB0, 0x00 };
char name[64];
char expect[64];
snprintf(name, sizeof(name), "%s Alarm%sClock %s", clock, clock, clock);
strcpy(expect, name);
CuAssertIntEquals(tc, 0, unicode_utf8_trim(name));
CuAssertStrEquals(tc, str, name);
CuAssertStrEquals(tc, expect, name);
}
CuSuite *get_unicode_suite(void)
{
CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_unicode_bug2262);
SUITE_ADD_TEST(suite, test_unicode_tolower);
SUITE_ADD_TEST(suite, test_unicode_trim);
SUITE_ADD_TEST(suite, test_unicode_trim_zwnj);
SUITE_ADD_TEST(suite, test_unicode_trim_ltrm);
SUITE_ADD_TEST(suite, test_unicode_trim_emoji);
SUITE_ADD_TEST(suite, test_unicode_utf8_to_other);
SUITE_ADD_TEST(suite, test_unicode_utf8_to_ucs);
SUITE_ADD_TEST(suite, test_unicode_compare);
SUITE_ADD_TEST(suite, test_unicode_farsi_nzwj);
SUITE_ADD_TEST(suite, test_unicode_bug2262);
SUITE_ADD_TEST(suite, test_unicode_tolower);
return suite;
}

View file

@ -40,8 +40,10 @@
static bool good_region(const region * r)
{
return (!fval(r, RF_CHAOTIC) && r->age > 30 && rplane(r) == NULL
&& r->units != NULL && r->land != NULL);
if (fval(r, RF_CHAOTIC) || r->age <= 100 || !r->units || !r->land || rplane(r)) {
return false;
}
return true;
}
static int cmp_age(const void *v1, const void *v2)

View file

@ -54,11 +54,11 @@ assert_grep_count reports/$CRFILE '^EINHEIT' 2
assert_grep_count reports/$CRFILE '^GEGENSTAENDE' 2
assert_grep_count reports/185-heg.cr '185;Runde' 1
assert_grep_count reports/185-heg.cr ';Baeume' 2
assert_grep_count reports/185-heg.cr '"B.ume";type' 2
assert_grep_count reports/185-heg.cr '"Pferde";type' 2
assert_grep_count reports/185-heg.nr 'erblickt' 2
assert_grep_count reports/185-heg.cr '"lighthouse";visibility' 2
assert_grep_count reports/185-heg.cr ';Baeume' 4
assert_grep_count reports/185-heg.cr '"B.ume";type' 4
assert_grep_count reports/185-heg.cr '"Pferde";type' 6
assert_grep_count reports/185-heg.nr 'erblickt' 6
assert_grep_count reports/185-heg.cr '"lighthouse";visibility' 6
assert_grep_count reports/185-heg.cr '"neighbour";visibility' 11
assert_grep_count reports/185-6rLo.cr '^EINHEIT' 2
assert_grep_count reports/185-6rLo.cr '^REGION' 13