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

This commit is contained in:
Enno Rehling 2018-09-09 18:17:22 +02:00
commit 8fdfe9fab4
49 changed files with 994 additions and 439 deletions

View File

@ -28,6 +28,9 @@ if [ -e orders.$TURN ]; then
files="$files orders.$TURN"
fi
echo "backup turn $TURN, game $GAME, files: $files"
if [ -d reports ] ; then
tar cjf backup/$TURN-reports.tar.bz2 reports
fi
tar cjf backup/$TURN.tar.bz2 $files
echo "uploading game-$GAME/$TURN.tar.bz2"
curl -s -n -T backup/$TURN.tar.bz2 https://dav.box.com/dav/Eressea/game-$GAME/$TURN.tar.bz2

View File

@ -25,6 +25,6 @@ addr=$1
subj=$2
shift 2
mutt -F "$ERESSEA/etc/muttrc" -s "$subj" -a "$@" -- "$addr" \
mutt -s "$subj" -a "$@" -- "$addr" \
< "$ERESSEA/server/etc/$TEMPLATE"

View File

@ -44,6 +44,6 @@ addr=$1
subject=$2
shift 2
mutt -F "$ERESSEA/etc/muttrc" -s "$subject" -a "$@" -- "$addr" \
mutt -s "$subject" -a "$@" -- "$addr" \
< "$TEMPLATE" || echo "Sending failed for email/report: $2/$3"

View File

@ -30,7 +30,7 @@
</type>
</message>
<message name="target_region_invalid">
<message name="target_region_invalid" section="errors">
<type>
<arg name="unit" type="unit"/>
<arg name="region" type="region"/>
@ -38,7 +38,7 @@
</type>
</message>
<message name="missing_direction">
<message name="missing_direction" section="errors">
<type>
<arg name="unit" type="unit"/>
<arg name="region" type="region"/>
@ -46,7 +46,7 @@
</type>
</message>
<message name="target_region_not_empty">
<message name="target_region_not_empty" section="errors">
<type>
<arg name="unit" type="unit"/>
<arg name="region" type="region"/>

View File

@ -2864,6 +2864,9 @@ msgstr "der Schatten"
msgid "ALLES"
msgstr "ALLES"
msgid "AUTO"
msgstr "AUTO"
msgid "undead_postfix_2"
msgstr "der Finsternis"
@ -5958,6 +5961,10 @@ msgctxt "keyword"
msgid "maketemp"
msgstr "MACHE TEMP"
msgctxt "keyword"
msgid "autostudy"
msgstr "LERNE AUTO"
msgctxt "spell"
msgid "reanimate"
msgstr "Wiederbelebung"

View File

@ -2510,6 +2510,9 @@ msgstr "halfling foot"
msgid "ALLES"
msgstr "ALL"
msgid "AUTO"
msgstr "AUTO"
msgctxt "race"
msgid "songdragon_d"
msgstr "song dragons"
@ -5268,7 +5271,11 @@ msgstr "berserkers blood potions"
msgctxt "keyword"
msgid "maketemp"
msgstr "MAKETEMP"
msgstr "MAKE TEMP"
msgctxt "keyword"
msgid "autostudy"
msgstr "LEARN AUTO"
msgctxt "spell"
msgid "reanimate"

View File

@ -93,7 +93,7 @@ email=$(grep "faction=$1:" reports.txt | cut -d: -f2 | sed 's/email=//')
echo "sending reports to $1 / $email"
info=/dev/null
[ -e ../email.txt ] && info=../email.txt
cat $info | mutt -F $ERESSEA/etc/muttrc -s "Testauswertung Spiel $game Partei $1" -a $zip -- $email
cat $info | mutt -s "Testauswertung Spiel $game Partei $1" -a $zip -- $email
}
game=0

View File

@ -12,6 +12,30 @@ function setup()
eressea.settings.set("rules.peasants.growth.factor", "0")
end
function test_study_auto()
local r = region.create(0, 0, "plain")
local f = faction.create("human")
local u = unit.create(f, r, 1)
u:add_order("LERN AUT Waffenbau")
assert_equal("LERNE AUTO Waffenbau", u:get_order(0))
process_orders()
assert_equal(1, u:get_skill("weaponsmithing"))
end
function test_study_auto_expensive()
local r = region.create(0, 0, "plain")
local f = faction.create("human")
local u = unit.create(f, r, 1)
u:add_order("LERNE AUTO Magie")
assert_equal("LERNE Magie", u:get_order(0))
u:clear_orders()
u:add_order("LERN AUT Taktik")
assert_equal("LERNE Taktik", u:get_order(0))
u:clear_orders()
u:add_order("LERN AUT Waffenbau")
assert_equal("LERNE AUTO Waffenbau", u:get_order(0))
end
function test_calendar()
assert_equal("winter", get_season(1011))
assert_equal("spring", get_season(1012))

View File

@ -42,7 +42,8 @@ function test_build_watch()
process_orders()
assert_not_nil(u.building)
if 5 ~= u.building.size then
for k,v in f.messages do
-- debug logging to find intermittent errors
for k,v in ipairs(f.messages) do
print(v)
end
end

View File

@ -24,7 +24,7 @@ end
function test_study()
local r = region.create(0, 0, "plain")
local f = faction.create("human", "test@example.com", "de")
local f = faction.create("human")
local u = unit.create(f, r, 1)
u:add_order("LERNEN Armbrust")
process_orders()
@ -33,7 +33,7 @@ end
function test_study_expensive()
local r = region.create(0, 0, "plain")
local f = faction.create("human", "test@example.com", "de")
local f = faction.create("human")
local u = unit.create(f, r, 1)
eressea.settings.set("skills.cost.alchemy", "50")
u:add_order("LERNEN Alchemie")
@ -45,7 +45,7 @@ end
function test_unit_spells()
local r = region.create(0, 0, "plain")
local f = faction.create("human", "test@example.com", "de")
local f = faction.create("human")
local u = unit.create(f, r, 1)
u.magic = "gray"
u:set_skill("magic", 1)
@ -75,7 +75,7 @@ end
function test_study_no_teacher()
local r = region.create(0, 0, "plain")
local f = faction.create("human", "test@example.com", "de")
local f = faction.create("human")
local u1 = make_student(f, r, 1)
u1:set_skill("crossbow", 1)
process_orders()
@ -84,7 +84,7 @@ end
function test_study_with_teacher()
local r = region.create(0, 0, "plain")
local f = faction.create("human", "test@example.com", "de")
local f = faction.create("human")
local u1 = make_student(f, r, 1)
make_teacher(u1)
@ -95,7 +95,7 @@ end
function test_study_too_many_students()
local r = region.create(0, 0, "plain")
local f = faction.create("human", "test@example.com", "de")
local f = faction.create("human")
local u1 = make_student(f, r, 20, "Taktik")
u1.name = "Student"
u1:add_item("money", 201*u1.number)
@ -106,7 +106,7 @@ end
function test_study_multiple_teachers()
local r = region.create(0, 0, "plain")
local f = faction.create("human", "test@example.com", "de")
local f = faction.create("human")
local u1 = make_student(f, r, 20, "Taktik")
u1.name = "Student"
u1:add_item("money", 201*u1.number)

BIN
src/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -86,6 +86,7 @@ ENDIF()
set (ERESSEA_SRC
vortex.c
automate.c
move.c
piracy.c
spells.c
@ -187,6 +188,7 @@ set(TESTS_SRC
tests.c
academy.test.c
alchemy.test.c
automate.test.c
battle.test.c
creport.test.c
direction.test.c

View File

@ -33,13 +33,13 @@ void academy_teaching_bonus(struct unit *u, skill_t sk, int students) {
}
}
bool academy_can_teach(unit *teacher, unit *student, skill_t sk) {
bool academy_can_teach(unit *teacher, unit *scholar, skill_t sk) {
const struct building_type *btype = bt_find("academy");
if (active_building(teacher, btype) && active_building(student, btype)) {
int j = study_cost(student, sk) * 2;
if (active_building(teacher, btype) && active_building(scholar, btype)) {
int j = study_cost(scholar, sk) * 2;
if (j < 50) j = 50;
/* kann Einheit das zahlen? */
return get_pooled(student, get_resourcetype(R_SILVER), GET_DEFAULT, j) >= j;
return get_pooled(scholar, get_resourcetype(R_SILVER), GET_DEFAULT, j) >= j;
/* sonst nehmen sie nicht am Unterricht teil */
}
return false;

View File

@ -9,7 +9,7 @@ extern "C" {
struct unit;
void academy_teaching_bonus(struct unit *u, skill_t sk, int academy);
bool academy_can_teach(struct unit *teacher, struct unit *student, skill_t sk);
bool academy_can_teach(struct unit *teacher, struct unit *scholar, skill_t sk);
#ifdef __cplusplus
}
#endif

161
src/automate.c Normal file
View File

@ -0,0 +1,161 @@
#include <platform.h>
#include "kernel/faction.h"
#include "kernel/messages.h"
#include "kernel/order.h"
#include "kernel/region.h"
#include "kernel/unit.h"
#include "util/log.h"
#include "automate.h"
#include "keyword.h"
#include "laws.h"
#include "study.h"
#include <stdlib.h>
#include <assert.h>
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;
}
int autostudy_init(scholar scholars[], int max_scholars, region *r)
{
unit *u;
int nscholars = 0;
for (u = r->units; u; u = u->next) {
keyword_t kwd = getkeyword(u->thisorder);
if (kwd == K_AUTOSTUDY) {
if (long_order_allowed(u) && unit_can_study(u)) {
scholar * st = scholars + nscholars;
init_order(u->thisorder, u->faction->locale);
st->sk = getskill(u->faction->locale);
st->level = effskill_study(u, st->sk);
st->learn = 0;
st->u = u;
if (++nscholars == max_scholars) {
log_fatal("you must increase MAXSCHOLARS");
}
}
else {
ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder, "error_race_nolearn", "race",
u_race(u)));
}
}
}
if (nscholars > 0) {
qsort(scholars, nscholars, sizeof(scholar), cmp_scholars);
}
return nscholars;
}
static void teaching(scholar *s, int n) {
assert(n <= s->u->number);
s->learn += n;
s->u->flags |= UFL_LONGACTION;
}
static void learning(scholar *s, int n) {
assert(n <= s->u->number);
s->learn += n;
s->u->flags |= UFL_LONGACTION;
}
void autostudy_run(scholar scholars[], int nscholars)
{
int ti = 0;
while (ti != nscholars) {
skill_t sk = scholars[ti].sk;
int t, s, se, ts = 0, tt = 0, si = ti;
for (se = ti; se != nscholars && scholars[se].sk == sk; ++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; ++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[ti].level - TEACHDIFFERENCE > scholars[si].level; ++si) {
tt += scholars[si].u->number;
}
if (si == se) {
/* there are no students, so standard learning only */
for (t = ti; t != se; ++t) {
learning(scholars + t, scholars[t].u->number);
}
}
else {
/* invariant: unit ti can still teach i students */
int i = scholars[ti].u->number * STUDENTS_PER_TEACHER;
/* invariant: unit si has n students that can still be taught */
int n = scholars[si].u->number;
for (t = ti, s = si; t != si && s != se; ) {
if (i > n) {
/* t has more than enough teaching capacity for s */
i -= n;
teaching(scholars + s, n);
learning(scholars + s, scholars[s].u->number);
/* next student, please: */
if (++s == se) {
continue;
}
n = scholars[s].u->number;
}
else {
/* s gets partial credit and we need a new teacher */
teaching(scholars + s, i);
/* we are done with this teacher. any remaining people are regular learners: */
if (scholars[t].u->number > 1) {
/* remain = number - ceil(taught/10); */
int remain = (STUDENTS_PER_TEACHER * scholars[t].u->number - i + STUDENTS_PER_TEACHER - 1) / STUDENTS_PER_TEACHER;
learning(scholars + t, remain);
}
/* we want a new teacher for s. if any exists, it's next in the sequence. */
if (++t == si) {
continue;
}
if (scholars[t].level - TEACHDIFFERENCE < scholars[s].level) {
/* next teacher cannot teach, we must skip students. */
do {
learning(scholars + s, (n - i));
i = 0;
if (++s == se) {
continue;
}
n = scholars[s].u->number;
} while (scholars[t].level - TEACHDIFFERENCE < scholars[s].level);
}
i = scholars[t].u->number * STUDENTS_PER_TEACHER;
}
}
}
ti = se;
}
}
#define MAXSCHOLARS 128
void do_autostudy(region *r)
{
scholar scholars[MAXSCHOLARS];
int i, nscholars = autostudy_init(scholars, MAXSCHOLARS, r);
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);
}
}

43
src/automate.h Normal file
View File

@ -0,0 +1,43 @@
/*
Copyright (c) 1998-2018, 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_AUTOMATE
#define H_GC_AUTOMATE
#include "skill.h"
struct region;
struct unit;
typedef struct scholar {
struct unit *u;
skill_t sk;
int level;
int learn;
} scholar;
#define STUDENTS_PER_TEACHER 10
void do_autostudy(struct region *r);
int autostudy_init(scholar scholars[], int max_scholars, struct region *r);
void autostudy_run(scholar scholars[], int nscholars);
#endif

114
src/automate.test.c Normal file
View File

@ -0,0 +1,114 @@
#ifdef _MSC_VER
#include <platform.h>
#endif
#include "automate.h"
#include "kernel/faction.h"
#include "kernel/order.h"
#include "kernel/region.h"
#include "kernel/unit.h"
#include "tests.h"
#include <CuTest.h>
static void test_autostudy_init(CuTest *tc) {
scholar scholars[4];
unit *u1, *u2, *u3;
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_ENTERTAINMENT]);
test_create_unit(f, r);
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]);
scholars[3].u = NULL;
CuAssertIntEquals(tc, 3, autostudy_init(scholars, 4, r));
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);
test_teardown();
}
static void test_autostudy_run(CuTest *tc) {
scholar scholars[4];
unit *u1, *u2, *u3;
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_ENTERTAINMENT]);
set_number(u1, 2);
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;
CuAssertIntEquals(tc, 3, autostudy_init(scholars, 4, r));
autostudy_run(scholars, 3);
CuAssertIntEquals(tc, 0, scholars[0].learn);
CuAssertIntEquals(tc, 20, scholars[1].learn);
CuAssertIntEquals(tc, 15, scholars[2].learn);
test_teardown();
}
static void test_autostudy_run_noteachers(CuTest *tc) {
scholar scholars[4];
unit *u1, *u2, *u3;
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_LUMBERJACK]);
set_number(u1, 2);
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;
CuAssertIntEquals(tc, 3, autostudy_init(scholars, 4, r));
autostudy_run(scholars, 3);
CuAssertIntEquals(tc, 2, scholars[0].learn);
CuAssertIntEquals(tc, 10, scholars[1].learn);
CuAssertIntEquals(tc, 15, scholars[2].learn);
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_autostudy_run_noteachers);
return suite;
}

View File

@ -100,9 +100,7 @@ typedef enum combatmagic {
#define MINSPELLRANGE 1
#define MAXSPELLRANGE 7
#ifndef ROW_FACTOR
# define ROW_FACTOR 10
#endif
#define ROW_FACTOR 3 /* factor for combat row advancement rule */
#define EFFECT_PANIC_SPELL 0.25
#define TROLL_REGENERATION 0.10
@ -161,7 +159,7 @@ static void init_rules(void)
skill_formula = config_get_int("rules.combat.skill_formula",
FORMULA_ORIG);
/* maximum number of combat turns */
max_turns = config_get_int("rules.combat.turns", COMBAT_TURNS);
max_turns = config_get_int("rules.combat.turns", 5);
/* damage calculation */
if (config_get_int("rules.combat.critical", 1)) {
rule_damage |= DAMAGE_CRITICAL;

View File

@ -645,6 +645,7 @@ static void cr_output_building(struct stream *out, building *b,
const unit *owner, int fno, faction *f)
{
const char *bname, *billusion;
int i;
stream_printf(out, "BURG %d\n", b->no);
@ -673,9 +674,12 @@ static void cr_output_building(struct stream *out, building *b,
if (fno >= 0) {
stream_printf(out, "%d;Partei\n", fno);
}
if (b->besieged) {
stream_printf(out, "%d;Belagerer\n", b->besieged);
i = building_get_siege(b);
if (i) {
stream_printf(out, "%d;Belagerer\n", i);
}
cr_output_curses(out, f, b, TYP_BUILDING);
}

View File

@ -103,6 +103,8 @@ static void recruit_init(void)
}
}
#define ENTERTAINFRACTION 20
int entertainmoney(const region * r)
{
double n;
@ -1666,7 +1668,7 @@ static void buy(unit * u, econ_request ** buyorders, struct order *ord)
return;
}
}
if (r_demand(r, ltype)) {
if (!r->land || r_demand(r, ltype)) {
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "luxury_notsold", ""));
return;
}
@ -1708,6 +1710,7 @@ static void expandselling(region * r, econ_request * sellorders, int limit)
static int bt_cache;
static const struct building_type *castle_bt, *harbour_bt, *caravan_bt;
assert(r->land);
if (bt_changed(&bt_cache)) {
castle_bt = bt_find("castle");
harbour_bt = bt_find("harbour");
@ -1977,7 +1980,7 @@ static bool sell(unit * u, econ_request ** sellorders, struct order *ord)
econ_request *o;
int k, available;
if (!r_demand(r, ltype)) {
if (!r->land || !r_demand(r, ltype)) {
cmistake(u, ord, 263, MSG_COMMERCE);
return false;
}

File diff suppressed because it is too large Load Diff

View File

@ -870,3 +870,14 @@ int cmp_current_owner(const building * b, const building * a)
}
return 0;
}
int building_get_siege(const struct building *b)
{
return b->_besieged;
}
int building_add_siege(struct building *b, int delta)
{
b->_besieged += delta;
return b->_besieged;
}

View File

@ -118,7 +118,7 @@ extern "C" {
int no;
int size;
int sizeleft; /* is only used during battle. should be a temporary attribute */
int besieged; /* should be an attribute */
int _besieged; /* should be an attribute */
int flags;
} building;
@ -134,6 +134,9 @@ extern "C" {
int id, int size, struct order *ord);
bool building_finished(const struct building *b);
int building_get_siege(const struct building *b);
int building_add_siege(struct building *b, int delta);
int wage(const struct region *r, const struct faction *f,
const struct race *rc, int in_turn);

View File

@ -135,7 +135,8 @@ const char *parameters[MAXPARAMS] = {
"GRUPPE",
"PARTEITARNUNG",
"BAEUME",
"ALLIANZ"
"ALLIANZ",
"AUTO"
};
int findoption(const char *s, const struct locale *lang)

View File

@ -84,7 +84,7 @@ char* get_command(const order *ord, const struct locale *lang, char *sbuffer, si
sbs_strcat(&sbs, str);
if (ord->id < 0) {
skill_t sk = (skill_t)(100+ord->id);
assert(kwd == K_STUDY && sk != SK_MAGIC && sk < MAXSKILLS);
assert((kwd == K_STUDY || kwd == K_AUTOSTUDY) && sk != SK_MAGIC && sk < MAXSKILLS);
str = skillname(sk, lang);
if (str) {
if (strchr(str, ' ') == NULL) {
@ -141,7 +141,8 @@ int stream_order(struct stream *out, const struct order *ord, const struct local
if (ord->id < 0) {
skill_t sk = (skill_t)(100 + ord->id);
assert(kwd == K_STUDY && sk != SK_MAGIC && sk < MAXSKILLS);
assert(kwd == K_AUTOSTUDY || kwd == K_STUDY);
assert(sk != SK_MAGIC && sk < MAXSKILLS);
text = skillname(sk, lang);
if (strchr(text, ' ') != NULL) {
swrite(" '", 1, 2, out);
@ -214,12 +215,12 @@ static int create_data(keyword_t kwd, const char *s,
order_data *data;
int id;
assert(kwd!=NOKEYWORD);
assert(kwd != NOKEYWORD);
if (!s || *s == 0) {
return 0;
}
if (kwd==K_STUDY) {
if (kwd == K_STUDY || kwd == K_AUTOSTUDY) {
const char * sptr = s;
skill_t sk = get_skill(parse_token_depr(&sptr), lang);
if (sk != SK_MAGIC && sk != NOSKILL) {
@ -310,12 +311,13 @@ order *parse_order(const char *s, const struct locale * lang)
assert(lang);
assert(s);
if (*s != 0) {
char token[32];
keyword_t kwd = NOKEYWORD;
const char *sptr = s;
bool persistent = false, noerror = false;
const char * p;
char * p;
p = *sptr ? parse_token_depr(&sptr) : 0;
p = parse_token(&sptr, token, sizeof(token));
if (p) {
while (*p == '!' || *p == '@') {
if (*p == '!') noerror = true;
@ -326,12 +328,25 @@ order *parse_order(const char *s, const struct locale * lang)
}
if (kwd == K_MAKE) {
const char *sp = sptr;
p = parse_token_depr(&sp);
p = parse_token(&sp, token, sizeof(token));
if (p && isparam(p, lang, P_TEMP)) {
kwd = K_MAKETEMP;
sptr = sp;
}
}
else if (kwd == K_STUDY) {
const char *sp = sptr;
p = parse_token(&sp, token, sizeof(token));
if (p && isparam(p, lang, P_AUTO)) {
skill_t sk;
sptr = sp;
p = parse_token(&sp, token, sizeof(token));
sk = get_skill(p, lang);
if (!expensive_skill(sk)) {
kwd = K_AUTOSTUDY;
}
}
}
if (kwd != NOKEYWORD) {
order *ord = (order *)malloc(sizeof(order));
create_order_i(ord, kwd, sptr, persistent, noerror, lang);
@ -366,6 +381,7 @@ bool is_repeated(keyword_t kwd)
case K_STEAL:
case K_SABOTAGE:
case K_STUDY:
case K_AUTOSTUDY:
case K_TEACH:
case K_GROW:
case K_PLANT:
@ -406,6 +422,7 @@ bool is_exclusive(const order * ord)
case K_STEAL:
case K_SABOTAGE:
case K_STUDY:
case K_AUTOSTUDY:
case K_TEACH:
case K_GROW:
case K_PLANT:
@ -447,6 +464,7 @@ bool is_long(keyword_t kwd)
case K_STEAL:
case K_SABOTAGE:
case K_STUDY:
case K_AUTOSTUDY:
case K_TEACH:
case K_GROW:
case K_PLANT:
@ -541,7 +559,7 @@ keyword_t init_order(const struct order *ord, const struct locale *lang)
assert(sk < MAXSKILLS);
assert(lang);
assert(kwd == K_STUDY);
assert(kwd == K_STUDY || kwd == K_AUTOSTUDY);
str = skillname(sk, lang);
if (strchr(str, ' ') == NULL) {
init_tokens_str(str);
@ -575,7 +593,7 @@ keyword_t init_order_depr(const struct order *ord)
{
if (ord) {
keyword_t kwd = ORD_KEYWORD(ord);
assert(kwd != K_STUDY);
assert(kwd != K_STUDY && kwd != K_AUTOSTUDY);
}
return init_order(ord, NULL);
}

View File

@ -113,6 +113,43 @@ static void test_parse_make(CuTest *tc) {
test_teardown();
}
static void test_parse_autostudy(CuTest *tc) {
char cmd[32];
order *ord;
struct locale * lang;
test_setup();
lang = get_or_create_locale("en");
locale_setstring(lang, mkname("skill", skillnames[SK_ENTERTAINMENT]), "Entertainment");
locale_setstring(lang, mkname("skill", skillnames[SK_MAGIC]), "Magic");
locale_setstring(lang, mkname("skill", skillnames[SK_TACTICS]), "Tactics");
locale_setstring(lang, keyword(K_STUDY), "STUDY");
locale_setstring(lang, keyword(K_AUTOSTUDY), "AUTOSTUDY");
locale_setstring(lang, parameters[P_AUTO], "AUTO");
init_locale(lang);
ord = parse_order("STUDY AUTO Entertainment", lang);
CuAssertPtrNotNull(tc, ord);
CuAssertIntEquals(tc, K_AUTOSTUDY, getkeyword(ord));
CuAssertStrEquals(tc, "AUTOSTUDY Entertainment", get_command(ord, lang, cmd, sizeof(cmd)));
CuAssertIntEquals(tc, K_AUTOSTUDY, init_order(ord, lang));
CuAssertStrEquals(tc, "Entertainment", getstrtoken());
free_order(ord);
ord = parse_order("STUDY AUTO Magic", lang);
CuAssertIntEquals(tc, K_STUDY, getkeyword(ord));
CuAssertStrEquals(tc, "STUDY Magic", get_command(ord, lang, cmd, sizeof(cmd)));
free_order(ord);
ord = parse_order("STUDY AUTO Tactics", lang);
CuAssertIntEquals(tc, K_STUDY, getkeyword(ord));
CuAssertStrEquals(tc, "STUDY Tactics", get_command(ord, lang, cmd, sizeof(cmd)));
free_order(ord);
test_teardown();
}
static void test_parse_make_temp(CuTest *tc) {
char cmd[32];
order *ord;
@ -130,7 +167,7 @@ static void test_parse_make_temp(CuTest *tc) {
CuAssertIntEquals(tc, K_MAKETEMP, getkeyword(ord));
CuAssertStrEquals(tc, "MAKETEMP herp", get_command(ord, lang, cmd, sizeof(cmd)));
CuAssertIntEquals(tc, K_MAKETEMP, init_order_depr(ord));
CuAssertIntEquals(tc, K_MAKETEMP, init_order(ord, lang));
CuAssertStrEquals(tc, "herp", getstrtoken());
free_order(ord);
test_teardown();
@ -153,7 +190,7 @@ static void test_parse_maketemp(CuTest *tc) {
CuAssertPtrNotNull(tc, ord);
CuAssertStrEquals(tc, "MAKETEMP herp", get_command(ord, lang, cmd, sizeof(cmd)));
CuAssertIntEquals(tc, K_MAKETEMP, getkeyword(ord));
CuAssertIntEquals(tc, K_MAKETEMP, init_order_depr(ord));
CuAssertIntEquals(tc, K_MAKETEMP, init_order(ord, lang));
CuAssertStrEquals(tc, "herp", getstrtoken());
free_order(ord);
test_teardown();
@ -167,7 +204,7 @@ static void test_init_order(CuTest *tc) {
lang = get_or_create_locale("en");
ord = create_order(K_MAKETEMP, lang, "hurr durr");
CuAssertIntEquals(tc, K_MAKETEMP, init_order_depr(ord));
CuAssertIntEquals(tc, K_MAKETEMP, init_order(ord, lang));
CuAssertStrEquals(tc, "hurr", getstrtoken());
CuAssertStrEquals(tc, "durr", getstrtoken());
free_order(ord);
@ -548,6 +585,7 @@ CuSuite *get_order_suite(void)
SUITE_ADD_TEST(suite, test_study_order_quoted);
SUITE_ADD_TEST(suite, test_parse_order);
SUITE_ADD_TEST(suite, test_parse_make);
SUITE_ADD_TEST(suite, test_parse_autostudy);
SUITE_ADD_TEST(suite, test_parse_make_temp);
SUITE_ADD_TEST(suite, test_parse_maketemp);
SUITE_ADD_TEST(suite, test_init_order);

View File

@ -716,7 +716,10 @@ const item_type *r_luxury(const region * r)
int r_demand(const region * r, const luxury_type * ltype)
{
struct demand *d = r->land->demands;
struct demand *d;
assert(r && r->land);
d = r->land->demands;
while (d && d->type != ltype)
d = d->next;
if (!d)

View File

@ -130,6 +130,7 @@ typedef enum {
P_FACTIONSTEALTH,
P_TREES,
P_ALLIANCE,
P_AUTO,
MAXPARAMS,
NOPARAM
} param_t;

View File

@ -1310,13 +1310,12 @@ int eff_skill(const unit * u, const skill *sv, const region *r)
return 0;
}
int effskill_study(const unit * u, skill_t sk, const region * r)
int effskill_study(const unit * u, skill_t sk)
{
skill *sv = unit_skill(u, sk);
if (sv && sv->level > 0) {
int mlevel = sv->level;
if (!r) r = u->region;
mlevel += get_modifier(u, sv->id, sv->level, r, true);
mlevel += get_modifier(u, sv->id, sv->level, u->region, true);
if (mlevel > 0)
return mlevel;
}
@ -1918,9 +1917,11 @@ int getunit(const region * r, const faction * f, unit **uresult)
int besieged(const unit * u)
{
/* belagert kann man in schiffen und burgen werden */
return (u && !keyword_disabled(K_BESIEGE)
&& u->building && u->building->besieged
&& u->building->besieged >= u->building->size * SIEGEFACTOR);
if (u && !keyword_disabled(K_BESIEGE) && u->building) {
building * b = u->building;
return building_get_siege(b) >= b->size * SIEGEFACTOR;
}
return false;
}
bool has_horses(const unit * u)

View File

@ -163,7 +163,7 @@ extern "C" {
void clone_men(const struct unit *src, struct unit *dst, int n); /* like transfer, but do not subtract from src */
int eff_skill(const struct unit *u, const struct skill *sv, const struct region *r);
int effskill_study(const struct unit *u, skill_t sk, const struct region *r);
int effskill_study(const struct unit *u, skill_t sk);
int get_modifier(const struct unit *u, skill_t sk, int level,
const struct region *r, bool noitem);

View File

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

View File

@ -149,5 +149,6 @@ const char *keywords[MAXKEYWORDS] = {
"promote",
"pay",
"loot",
"autostudy",
};

View File

@ -72,6 +72,7 @@ extern "C"
K_PROMOTION,
K_PAY,
K_LOOT,
K_AUTOSTUDY,
MAXKEYWORDS,
NOKEYWORD
} keyword_t;

View File

@ -26,6 +26,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#include <modules/gmcmd.h>
#include "alchemy.h"
#include "automate.h"
#include "battle.h"
#include "economy.h"
#include "keyword.h"
@ -305,6 +306,11 @@ static void calculate_emigration(region * r)
}
}
/* Vermehrungsrate Bauern in 1/10000.
* TODO: Evt. Berechnungsfehler, reale Vermehrungsraten scheinen hoeher. */
#define PEASANTGROWTH 10
#define PEASANTLUCK 10
#define PEASANTFORCE 0.75 /* Chance einer Vermehrung trotz 90% Auslastung */
static double peasant_growth_factor(void)
{
@ -907,7 +913,7 @@ static int slipthru(const region * r, const unit * u, const building * b)
int n, o;
/* b ist die burg, in die man hinein oder aus der man heraus will. */
if (b == NULL || b->besieged < b->size * SIEGEFACTOR) {
if (b == NULL || building_get_siege(b) < b->size * SIEGEFACTOR) {
return 1;
}
@ -3636,6 +3642,24 @@ void add_proc_unit(int priority, void(*process) (unit *), const char *name)
}
}
bool long_order_allowed(const unit *u)
{
const region *r = u->region;
if (fval(u, UFL_LONGACTION)) {
/* this message was already given in laws.update_long_order
cmistake(u, ord, 52, MSG_PRODUCE);
*/
return false;
}
else if (fval(r->terrain, SEA_REGION)
&& u_race(u) != get_race(RC_AQUARIAN)
&& !(u_race(u)->flags & RCF_SWIM)) {
/* error message disabled by popular demand */
return false;
}
return true;
}
/* per priority, execute processors in order from PR_GLOBAL down to PR_ORDER */
void process(void)
{
@ -3705,16 +3729,7 @@ void process(void)
cmistake(u, ord, 224, MSG_MAGIC);
ord = NULL;
}
else if (fval(u, UFL_LONGACTION)) {
/* this message was already given in laws.update_long_order
cmistake(u, ord, 52, MSG_PRODUCE);
*/
ord = NULL;
}
else if (fval(r->terrain, SEA_REGION)
&& u_race(u) != get_race(RC_AQUARIAN)
&& !(u_race(u)->flags & RCF_SWIM)) {
/* error message disabled by popular demand */
else if (!long_order_allowed(u)) {
ord = NULL;
}
}
@ -3847,7 +3862,7 @@ int siege_cmd(unit * u, order * ord)
usetsiege(u, b);
if (katapultiere < bewaffnete) katapultiere = bewaffnete;
b->besieged += katapultiere;
building_add_siege(b, katapultiere);
/* definitiver schaden eingeschraenkt */
if (d > b->size - 1) d = b->size - 1;
@ -4001,6 +4016,7 @@ void init_processor(void)
}
p += 10;
add_proc_region(p, do_autostudy, "study automation");
add_proc_order(p, K_TEACH, teach_cmd, PROC_THISORDER | PROC_LONGORDER,
"Lehren");
p += 10;
@ -4155,7 +4171,7 @@ void update_subscriptions(void)
/** determine if unit can be seen by faction
* @param f -- the observiong faction
* @param u -- the unit that is observed
* @param r -- the region that u is obesrved in (see below)
* @param r -- the region that u is obesrved from (see below)
* @param m -- terrain modifier to stealth
*
* r kann != u->region sein, wenn es um Durchreisen geht,

View File

@ -66,6 +66,7 @@ extern "C" {
void update_long_order(struct unit *u);
void sinkships(struct region * r);
void do_enter(struct region *r, bool is_final_attempt);
bool long_order_allowed(const struct unit *u);
int password_cmd(struct unit *u, struct order *ord);
int banner_cmd(struct unit *u, struct order *ord);

View File

@ -113,7 +113,8 @@ static void test_contact(CuTest * tc)
u3 = test_create_unit(test_create_faction(NULL), r);
set_level(u3, SK_PERCEPTION, 2);
usetsiege(u3, b);
b->besieged = 1;
building_add_siege(b, 1);
CuAssertIntEquals(tc, 1, building_get_siege(b));
CuAssertIntEquals(tc, 1, can_contact(r, u1, u2));
u_set_building(u1, b);
@ -1759,6 +1760,34 @@ static void test_nmr_timeout(CuTest *tc) {
test_teardown();
}
static void test_long_orders(CuTest *tc) {
unit *u;
test_setup();
u = test_create_unit(test_create_faction(NULL), test_create_plain(0, 0));
CuAssertTrue(tc, long_order_allowed(u));
u->flags |= UFL_LONGACTION;
CuAssertTrue(tc, !long_order_allowed(u));
test_teardown();
}
static void test_long_order_on_ocean(CuTest *tc) {
unit *u;
race * rc;
test_setup();
rc = test_create_race("pikachu");
u = test_create_unit(test_create_faction(rc), test_create_ocean(0, 0));
CuAssertTrue(tc, !long_order_allowed(u));
rc->flags |= RCF_SWIM;
CuAssertTrue(tc, long_order_allowed(u));
rc = test_create_race("aquarian");
u = test_create_unit(test_create_faction(rc), u->region);
CuAssertTrue(tc, long_order_allowed(u));
test_teardown();
}
CuSuite *get_laws_suite(void)
{
CuSuite *suite = CuSuiteNew();
@ -1831,6 +1860,8 @@ CuSuite *get_laws_suite(void)
SUITE_ADD_TEST(suite, test_cansee_ring);
SUITE_ADD_TEST(suite, test_cansee_sphere);
SUITE_ADD_TEST(suite, test_nmr_timeout);
SUITE_ADD_TEST(suite, test_long_orders);
SUITE_ADD_TEST(suite, test_long_order_on_ocean);
return suite;
}

View File

@ -1016,37 +1016,42 @@ static void eaten_by_monster(unit * u)
{
/* adjustment for smaller worlds */
double multi = RESOURCE_QUANTITY * newterrain(T_PLAIN)->size / 10000.0;
int n = 0;
int horse = -1;
const resource_type *rhorse = get_resourcetype(R_HORSE);
const race *rc = u_race(u);
int scare;
int p = rpeasants(u->region);
scare = rc_scare(rc);
if (scare>0) {
n = rng_int() % scare * u->number;
} else {
n = rng_int() % (u->number / 20 + 1);
horse = 0;
}
horse = horse ? i_get(u->items, rhorse->itype) : 0;
if (p > 0) {
int horse = -1;
int scare = rc_scare(rc);
int n = 0;
n = (int)(n * multi);
if (n > 0) {
n = lovar(n);
if (n > 0) {
int p = rpeasants(u->region);
if (p < n) n = p;
deathcounts(u->region, n);
rsetpeasants(u->region, rpeasants(u->region) - n);
ADDMSG(&u->region->msgs, msg_message("eatpeasants", "unit amount", u, n));
if (scare > 0) {
n = rng_int() % scare * u->number;
}
else {
n = rng_int() % (u->number / 20 + 1);
horse = 0;
}
horse = horse ? i_get(u->items, rhorse->itype) : 0;
if (horse > 0) {
i_change(&u->items, rhorse->itype, -horse);
ADDMSG(&u->region->msgs, msg_message("eathorse", "unit amount", u, horse));
}
n = (int)(n * multi);
if (n > 0) {
n = lovar(n);
if (p < n) n = p;
if (n > 0) {
if (n > 0) {
deathcounts(u->region, n);
rsetpeasants(u->region, rpeasants(u->region) - n);
ADDMSG(&u->region->msgs, msg_message("eatpeasants", "unit amount", u, n));
}
}
}
}
if (horse > 0) {
i_change(&u->items, rhorse->itype, -horse);
ADDMSG(&u->region->msgs, msg_message("eathorse", "unit amount", u, horse));
}
}
@ -1059,10 +1064,12 @@ static void absorbed_by_monster(unit * u)
if (n > 0) {
int p = rpeasants(u->region);
if (p < n) n = p;
rsetpeasants(u->region, rpeasants(u->region) - n);
scale_number(u, u->number + n);
ADDMSG(&u->region->msgs, msg_message("absorbpeasants",
"unit race amount", u, u_race(u), n));
if (n > 0) {
rsetpeasants(u->region, rpeasants(u->region) - n);
scale_number(u, u->number + n);
ADDMSG(&u->region->msgs, msg_message("absorbpeasants",
"unit race amount", u, u_race(u), n));
}
}
}
}

View File

@ -1853,14 +1853,16 @@ nr_building(struct stream *out, const region *r, const building *b, const factio
sbs_strcat(&sbs, LOC(lang, "nr_building_inprogress"));
}
if (b->besieged > 0 && r->seen.mode >= seen_lighthouse) {
msg = msg_message("nr_building_besieged", "soldiers diff", b->besieged,
b->besieged - b->size * SIEGEFACTOR);
if (!keyword_disabled(K_BESIEGE) && r->seen.mode >= seen_lighthouse) {
int s = building_get_siege(b);
if (s > 0) {
msg = msg_message("nr_building_besieged", "soldiers diff", s,
s - b->size * SIEGEFACTOR);
size = nr_render(msg, lang, sbs.end, sbs.size - (sbs.end - sbs.begin), f);
sbs.end += size;
size = nr_render(msg, lang, sbs.end, sbs.size - (sbs.end - sbs.begin), f);
sbs.end += size;
msg_release(msg);
msg_release(msg);
}
}
i = 0;
if (b->display && b->display[0]) {

View File

@ -10,23 +10,11 @@
without prior permission by the authors of Eressea.
*/
#define ENTERTAINFRACTION 20
#define TEACHDIFFERENCE 2
#define RESOURCE_QUANTITY 0.5
#define RECRUITFRACTION 40 /* 100/RECRUITFRACTION% */
#define COMBAT_TURNS 5
#undef NEWATSROI
/* Vermehrungsrate Bauern in 1/10000.
* TODO: Evt. Berechnungsfehler, reale Vermehrungsraten scheinen hoeher. */
#define PEASANTGROWTH 10
#define PEASANTLUCK 10
#define ROW_FACTOR 3 /* factor for combat row advancement rule */
/* TODO: move these settings to settings.h or into configuration files */
#define TREESIZE (8) /* space used by trees (in #peasants) */
#define PEASANTFORCE 0.75 /* Chance einer Vermehrung trotz 90% Auslastung */
/* Gebaeudegroesse = Minimalbelagerer */
#define SIEGEFACTOR 2

View File

@ -113,3 +113,38 @@ skill_t get_skill(const char *s, const struct locale * lang)
return result;
}
int skill_cost(skill_t sk) {
static int config;
static int costs[MAXSKILLS];
int cost;
switch (sk) {
case SK_SPY:
cost = 100;
break;
case SK_TACTICS:
case SK_HERBALISM:
case SK_ALCHEMY:
cost = 200;
break;
default:
cost = -1;
}
if (config_changed(&config)) {
memset(costs, 0, sizeof(costs));
}
if (costs[sk] == 0) {
char buffer[256];
sprintf(buffer, "skills.cost.%s", skillnames[sk]);
costs[sk] = config_get_int(buffer, cost);
}
if (costs[sk] >= 0) {
return costs[sk];
}
return (cost > 0) ? cost : 0;
}
bool expensive_skill(skill_t sk) {
return (sk == SK_MAGIC) || skill_cost(sk) > 0;
}

View File

@ -49,5 +49,7 @@ void init_skills(const struct locale *lang);
void init_skill(const struct locale *lang, skill_t kwd, const char *str);
void enable_skill(skill_t sk, bool enabled);
bool skill_enabled(skill_t sk);
int skill_cost(skill_t sk);
bool expensive_skill(skill_t sk);
#endif

View File

@ -1,6 +1,8 @@
#include <platform.h>
#include "skill.h"
#include "kernel/config.h"
#include "util/language.h"
#include "skill.h"
#include "tests.h"
#include <CuTest.h>
@ -38,12 +40,34 @@ static void test_get_skill(CuTest *tc) {
test_teardown();
}
static void test_skill_cost(CuTest *tc) {
test_setup();
CuAssertTrue(tc, expensive_skill(SK_MAGIC));
CuAssertTrue(tc, expensive_skill(SK_TACTICS));
CuAssertTrue(tc, expensive_skill(SK_SPY));
CuAssertTrue(tc, expensive_skill(SK_ALCHEMY));
CuAssertTrue(tc, expensive_skill(SK_HERBALISM));
CuAssertTrue(tc, !expensive_skill(SK_CROSSBOW));
CuAssertIntEquals(tc, 100, skill_cost(SK_SPY));
CuAssertIntEquals(tc, 200, skill_cost(SK_TACTICS));
CuAssertIntEquals(tc, 200, skill_cost(SK_ALCHEMY));
CuAssertIntEquals(tc, 200, skill_cost(SK_HERBALISM));
CuAssertIntEquals(tc, 0, skill_cost(SK_CROSSBOW));
config_set_int("skills.cost.crossbow", 300);
CuAssertIntEquals(tc, 300, skill_cost(SK_CROSSBOW));
CuAssertTrue(tc, expensive_skill(SK_CROSSBOW));
test_teardown();
}
CuSuite *get_skill_suite(void)
{
CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_init_skill);
SUITE_ADD_TEST(suite, test_init_skills);
SUITE_ADD_TEST(suite, test_get_skill);
SUITE_ADD_TEST(suite, test_skill_cost);
return suite;
}

View File

@ -69,7 +69,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#define TEACH_ALL 1
#define TEACH_FRIENDS
static skill_t getskill(const struct locale *lang)
skill_t getskill(const struct locale *lang)
{
char token[128];
const char * s = gettoken(token, sizeof(token));
@ -130,47 +130,23 @@ bool magic_lowskill(unit * u)
return u_race(u) == toad_rc;
}
/* ------------------------------------------------------------- */
int study_cost(struct unit *u, skill_t sk)
{
static int config;
static int costs[MAXSKILLS];
int cost = -1;
if (sk == SK_MAGIC) {
int next_level = 1 + (u ? get_level(u, sk) : 0);
static int config;
static int cost;
/* Die Magiekosten betragen 50+Summe(50*Stufe) */
/* 'Stufe' ist dabei die naechste zu erreichende Stufe */
cost = config_get_int("skills.cost.magic", 50);
return cost * (1 + ((next_level + next_level * next_level) / 2));
if (config_changed(&config)) {
cost = config_get_int("skills.cost.magic", 50);
}
if (cost > 0) {
int next_level = 1 + (u ? get_level(u, sk) : 0);
return cost * (1 + ((next_level + next_level * next_level) / 2));
}
return cost;
}
else switch (sk) {
case SK_SPY:
cost = 100;
break;
case SK_TACTICS:
case SK_HERBALISM:
case SK_ALCHEMY:
cost = 200;
break;
default:
cost = -1;
}
if (config_changed(&config)) {
memset(costs, 0, sizeof(costs));
}
if (costs[sk] == 0) {
char buffer[256];
sprintf(buffer, "skills.cost.%s", skillnames[sk]);
costs[sk] = config_get_int(buffer, cost);
}
if (costs[sk] >= 0) {
return costs[sk];
}
return (cost > 0) ? cost : 0;
return skill_cost(sk);
}
/* ------------------------------------------------------------- */
@ -195,37 +171,37 @@ const attrib_type at_learning = {
#define EXPERIENCEDAYS 10
static int study_days(unit * student, skill_t sk)
static int study_days(unit * scholar, skill_t sk)
{
int speed = STUDYDAYS;
if (u_race(student)->study_speed) {
speed += u_race(student)->study_speed[sk];
if (u_race(scholar)->study_speed) {
speed += u_race(scholar)->study_speed[sk];
if (speed < STUDYDAYS) {
skill *sv = unit_skill(student, sk);
skill *sv = unit_skill(scholar, sk);
if (sv == 0) {
speed = STUDYDAYS;
}
}
}
return student->number * speed;
return scholar->number * speed;
}
static int
teach_unit(unit * teacher, unit * student, int nteaching, skill_t sk,
teach_unit(unit * teacher, unit * scholar, int nteaching, skill_t sk,
bool report, int *academy_students)
{
teaching_info *teach = NULL;
attrib *a;
int students;
if (magic_lowskill(student)) {
if (magic_lowskill(scholar)) {
cmistake(teacher, teacher->thisorder, 292, MSG_EVENT);
return 0;
}
students = student->number;
students = scholar->number;
/* subtract already taught students */
a = a_find(student->attribs, &at_learning);
a = a_find(scholar->attribs, &at_learning);
if (a != NULL) {
teach = (teaching_info *)a->data.v;
students -= teach->students;
@ -235,18 +211,18 @@ teach_unit(unit * teacher, unit * student, int nteaching, skill_t sk,
if (students > 0) {
if (teach == NULL) {
a = a_add(&student->attribs, a_new(&at_learning));
a = a_add(&scholar->attribs, a_new(&at_learning));
teach = (teaching_info *)a->data.v;
}
selist_push(&teach->teachers, teacher);
teach->days += students * STUDYDAYS;
teach->students += students;
if (student->building && teacher->building == student->building) {
if (scholar->building && teacher->building == scholar->building) {
/* Solange Akademien groessenbeschraenkt sind, sollte Lehrer und
* Student auch in unterschiedlichen Gebaeuden stehen duerfen */
/* FIXME comment contradicts implementation */
if (academy_can_teach(teacher, student, sk)) {
if (academy_can_teach(teacher, scholar, sk)) {
/* Jeder Schueler zusaetzlich +10 Tage wenn in Uni. */
teach->days += students * EXPERIENCEDAYS; /* learning erhoehen */
/* Lehrer zusaetzlich +1 Tag pro Schueler. */
@ -304,7 +280,7 @@ int teach_cmd(unit * teacher, struct order *ord)
#if TEACH_ALL
if (getparam(teacher->faction->locale) == P_ANY) {
skill_t sk;
unit *student;
unit *scholar;
skill_t teachskill[MAXSKILLS];
int t = 0;
@ -313,15 +289,15 @@ int teach_cmd(unit * teacher, struct order *ord)
teachskill[t] = getskill(teacher->faction->locale);
} while (sk != NOSKILL);
for (student = r->units; teaching > 0 && student; student = student->next) {
if (LongHunger(student)) {
for (scholar = r->units; teaching > 0 && scholar; scholar = scholar->next) {
if (LongHunger(scholar)) {
continue;
}
else if (student->faction == teacher->faction) {
if (getkeyword(student->thisorder) == K_STUDY) {
else if (scholar->faction == teacher->faction) {
if (getkeyword(scholar->thisorder) == K_STUDY) {
/* Input ist nun von student->thisorder !! */
init_order(student->thisorder, student->faction->locale);
sk = getskill(student->faction->locale);
init_order(scholar->thisorder, scholar->faction->locale);
sk = getskill(scholar->faction->locale);
if (sk != NOSKILL && teachskill[0] != NOSKILL) {
for (t = 0; teachskill[t] != NOSKILL; ++t) {
if (sk == teachskill[t]) {
@ -331,20 +307,20 @@ int teach_cmd(unit * teacher, struct order *ord)
sk = teachskill[t];
}
if (sk != NOSKILL
&& effskill_study(teacher, sk, 0) - TEACHDIFFERENCE > effskill_study(student, sk, 0)) {
teaching -= teach_unit(teacher, student, teaching, sk, true, &academy_students);
&& effskill_study(teacher, sk) - TEACHDIFFERENCE > effskill_study(scholar, sk)) {
teaching -= teach_unit(teacher, scholar, teaching, sk, true, &academy_students);
}
}
}
#ifdef TEACH_FRIENDS
else if (alliedunit(teacher, student->faction, HELP_GUARD)) {
if (getkeyword(student->thisorder) == K_STUDY) {
else if (alliedunit(teacher, scholar->faction, HELP_GUARD)) {
if (getkeyword(scholar->thisorder) == K_STUDY) {
/* Input ist nun von student->thisorder !! */
init_order(student->thisorder, student->faction->locale);
sk = getskill(student->faction->locale);
init_order(scholar->thisorder, scholar->faction->locale);
sk = getskill(scholar->faction->locale);
if (sk != NOSKILL
&& effskill_study(teacher, sk, 0) - TEACHDIFFERENCE >= effskill(student, sk, 0)) {
teaching -= teach_unit(teacher, student, teaching, sk, true, &academy_students);
&& effskill_study(teacher, sk) - TEACHDIFFERENCE >= effskill(scholar, sk, NULL)) {
teaching -= teach_unit(teacher, scholar, teaching, sk, true, &academy_students);
}
}
}
@ -363,15 +339,15 @@ int teach_cmd(unit * teacher, struct order *ord)
while (!parser_end()) {
skill_t sk;
unit *student;
unit *scholar;
bool feedback;
getunit(r, teacher->faction, &student);
getunit(r, teacher->faction, &scholar);
++count;
/* Falls die Unit nicht gefunden wird, Fehler melden */
if (!student) {
if (!scholar) {
char tbuf[20];
const char *uid;
const char *token;
@ -403,8 +379,8 @@ int teach_cmd(unit * teacher, struct order *ord)
continue;
}
feedback = teacher->faction == student->faction
|| alliedunit(student, teacher->faction, HELP_GUARD);
feedback = teacher->faction == scholar->faction
|| alliedunit(scholar, teacher->faction, HELP_GUARD);
/* Neuen Befehl zusammenbauen. TEMP-Einheiten werden automatisch in
* ihre neuen Nummern uebersetzt. */
@ -412,31 +388,31 @@ int teach_cmd(unit * teacher, struct order *ord)
strncat(zOrder, " ", sz - 1);
--sz;
}
sz -= str_strlcpy(zOrder + 4096 - sz, itoa36(student->no), sz);
sz -= str_strlcpy(zOrder + 4096 - sz, itoa36(scholar->no), sz);
if (getkeyword(student->thisorder) != K_STUDY) {
if (getkeyword(scholar->thisorder) != K_STUDY) {
ADDMSG(&teacher->faction->msgs,
msg_feedback(teacher, ord, "teach_nolearn", "student", student));
msg_feedback(teacher, ord, "teach_nolearn", "student", scholar));
continue;
}
/* Input ist nun von student->thisorder !! */
parser_pushstate();
init_order(student->thisorder, student->faction->locale);
sk = getskill(student->faction->locale);
init_order(scholar->thisorder, scholar->faction->locale);
sk = getskill(scholar->faction->locale);
parser_popstate();
if (sk == NOSKILL) {
ADDMSG(&teacher->faction->msgs,
msg_feedback(teacher, ord, "teach_nolearn", "student", student));
msg_feedback(teacher, ord, "teach_nolearn", "student", scholar));
continue;
}
if (effskill_study(student, sk, 0) > effskill_study(teacher, sk, 0)
if (effskill_study(scholar, sk) > effskill_study(teacher, sk)
- TEACHDIFFERENCE) {
if (feedback) {
ADDMSG(&teacher->faction->msgs, msg_feedback(teacher, ord, "teach_asgood",
"student", student));
"student", scholar));
}
continue;
}
@ -444,18 +420,18 @@ int teach_cmd(unit * teacher, struct order *ord)
/* ist der Magier schon spezialisiert, so versteht er nur noch
* Lehrer seines Gebietes */
sc_mage *mage1 = get_mage_depr(teacher);
sc_mage *mage2 = get_mage_depr(student);
sc_mage *mage2 = get_mage_depr(scholar);
if (mage2 && mage1 && mage2->magietyp != M_GRAY
&& mage1->magietyp != mage2->magietyp) {
if (feedback) {
ADDMSG(&teacher->faction->msgs, msg_feedback(teacher, ord,
"error_different_magic", "target", student));
"error_different_magic", "target", scholar));
}
continue;
}
}
sk_academy = sk;
teaching -= teach_unit(teacher, student, teaching, sk, false, &academy_students);
teaching -= teach_unit(teacher, scholar, teaching, sk, false, &academy_students);
}
new_order = create_order(K_TEACH, teacher->faction->locale, "%s", zOrder);
replace_order(&teacher->orders, ord, new_order);
@ -766,9 +742,6 @@ int study_cmd(unit * u, order * ord)
days *= 2;
}
if (fval(u, UFL_HUNGER))
days /= 2;
learn_skill(u, sk, days);
if (a != NULL) {
if (teach->teachers) {
@ -832,6 +805,9 @@ void learn_skill(unit *u, skill_t sk, int days) {
int leveldays = STUDYDAYS * u->number;
int weeks = 0;
if (fval(u, UFL_HUNGER)) {
days /= 2;
}
assert(sk >= 0 && sk < MAXSKILLS);
if (inject_learn_fun) {
inject_learn_fun(u, sk, days);

View File

@ -29,25 +29,10 @@ extern "C" {
struct unit;
struct selist;
int teach_cmd(struct unit *u, struct order *ord);
int study_cmd(struct unit *u, struct order *ord);
magic_t getmagicskill(const struct locale *lang);
bool is_migrant(struct unit *u);
int study_cost(struct unit *u, skill_t talent);
typedef void(*learn_fun)(struct unit *u, skill_t sk, int days);
#define STUDYDAYS 30
void learn_skill(struct unit *u, skill_t sk, int days);
void reduce_skill_days(struct unit *u, skill_t sk, int days);
void produceexp(struct unit *u, skill_t sk, int n);
void produceexp_ex(struct unit *u, skill_t sk, int n, learn_fun learn);
void demon_skillchange(struct unit *u);
#define TEACHNUMBER 10
#define TEACHDIFFERENCE 2
typedef struct teaching_info {
struct selist *teachers;
int students;
@ -56,6 +41,24 @@ extern "C" {
extern const struct attrib_type at_learning;
int teach_cmd(struct unit *u, struct order *ord);
int study_cmd(struct unit *u, struct order *ord);
magic_t getmagicskill(const struct locale *lang);
skill_t getskill(const struct locale *lang);
bool is_migrant(struct unit *u);
int study_cost(struct unit *u, skill_t sk);
typedef void(*learn_fun)(struct unit *u, skill_t sk, int days);
void learn_skill(struct unit *u, skill_t sk, int days);
void reduce_skill_days(struct unit *u, skill_t sk, int days);
void produceexp(struct unit *u, skill_t sk, int n);
void produceexp_ex(struct unit *u, skill_t sk, int n, learn_fun learn);
void demon_skillchange(struct unit *u);
void inject_learn(learn_fun fun);
#ifdef __cplusplus

View File

@ -89,15 +89,15 @@ static void setup_teacher(study_fixture *fix, skill_t sk) {
setup_locale(lang);
fix->u = test_create_unit(f, r);
assert(fix->u);
fix->u->thisorder = create_order(K_STUDY, f->locale, "%s", skillnames[sk]);
fix->u->thisorder = create_order(K_STUDY, f->locale, skillnames[sk]);
fix->teachers[0] = test_create_unit(f, r);
assert(fix->teachers[0]);
fix->teachers[0]->thisorder = create_order(K_TEACH, f->locale, "%s", itoa36(fix->u->no));
fix->teachers[0]->thisorder = create_order(K_TEACH, f->locale, itoa36(fix->u->no));
fix->teachers[1] = test_create_unit(f, r);
assert(fix->teachers[1]);
fix->teachers[1]->thisorder = create_order(K_TEACH, f->locale, "%s", itoa36(fix->u->no));
fix->teachers[1]->thisorder = create_order(K_TEACH, f->locale, itoa36(fix->u->no));
test_clear_messages(f);
}
@ -110,7 +110,7 @@ static void test_study_no_teacher(CuTest *tc) {
CuAssertPtrNotNull(tc, sv = unit_skill(fix.u, SK_CROSSBOW));
CuAssertIntEquals(tc, 1, sv->level);
CuAssertIntEquals(tc, 2, sv->weeks);
CuAssertPtrEquals(tc, 0, test_get_last_message(fix.u->faction->msgs));
CuAssertPtrEquals(tc, NULL, test_get_last_message(fix.u->faction->msgs));
test_teardown();
}
@ -121,7 +121,7 @@ static void test_study_with_teacher(CuTest *tc) {
setup_teacher(&fix, SK_CROSSBOW);
set_level(fix.teachers[0], SK_CROSSBOW, TEACHDIFFERENCE);
teach_cmd(fix.teachers[0], fix.teachers[0]->thisorder);
CuAssertPtrEquals(tc, 0, test_get_last_message(fix.u->faction->msgs));
CuAssertPtrEquals(tc, NULL, test_get_last_message(fix.u->faction->msgs));
study_cmd(fix.u, fix.u->thisorder);
CuAssertPtrNotNull(tc, sv = unit_skill(fix.u, SK_CROSSBOW));
CuAssertIntEquals(tc, 1, sv->level);
@ -288,7 +288,7 @@ static void test_academy_bonus(CuTest *tc) {
u1 = test_create_unit(u->faction, u->region);
u3 = test_create_unit(u->faction, u->region);
u0->thisorder = create_order(K_TEACH, loc, "%s %s", itoa36(u3->no), itoa36(u1->no));
u->thisorder = create_order(K_TEACH, loc, "%s", itoa36(u1->no));
u->thisorder = create_order(K_TEACH, loc, itoa36(u1->no));
u1->thisorder = create_order(K_STUDY, loc, skillnames[SK_CROSSBOW]);
u3->thisorder = create_order(K_STUDY, loc, skillnames[SK_CROSSBOW]);
@ -405,7 +405,7 @@ static void test_study_magic(CuTest *tc) {
f = test_create_faction(NULL);
lang = f->locale;
u = test_create_unit(f, test_create_region(0, 0, NULL));
u->thisorder = create_order(K_STUDY, lang, "%s", skillnames[SK_MAGIC]);
u->thisorder = create_order(K_STUDY, lang, skillnames[SK_MAGIC]);
itype = test_create_silver();
CuAssertIntEquals(tc, -1, study_cmd(u, u->thisorder));
@ -423,7 +423,7 @@ static void test_study_magic(CuTest *tc) {
CuAssertIntEquals(tc, M_GWYRRD, f->magiegebiet);
CuAssertIntEquals(tc, 0, i_get(u->items, itype));
CuAssertPtrNotNull(tc, get_mage_depr(u));
CuAssertPtrEquals(tc, 0, test_find_messagetype(f->msgs, "error65"));
CuAssertPtrEquals(tc, NULL, test_find_messagetype(f->msgs, "error65"));
CuAssertIntEquals(tc, M_GWYRRD, get_mage_depr(u)->magietyp);
test_teardown();
@ -491,12 +491,12 @@ static void test_teach_magic(CuTest *tc) {
f = test_create_faction(NULL);
f->magiegebiet = M_GWYRRD;
u = test_create_unit(f, test_create_region(0, 0, NULL));
u->thisorder = create_order(K_STUDY, f->locale, "%s", skillnames[SK_MAGIC]);
u->thisorder = create_order(K_STUDY, f->locale, skillnames[SK_MAGIC]);
i_change(&u->items, itype, study_cost(u, SK_MAGIC));
ut = test_create_unit(f, u->region);
set_level(ut, SK_MAGIC, TEACHDIFFERENCE);
create_mage(ut, M_GWYRRD);
ut->thisorder = create_order(K_TEACH, f->locale, "%s", itoa36(u->no));
ut->thisorder = create_order(K_TEACH, f->locale, itoa36(u->no));
learn_inject();
teach_cmd(ut, ut->thisorder);
study_cmd(u, u->thisorder);

View File

@ -93,7 +93,6 @@ int RunAllTests(int argc, char *argv[])
ADD_SUITE(xerewards);
/* kernel */
ADD_SUITE(academy);
ADD_SUITE(alchemy);
ADD_SUITE(alliance);
ADD_SUITE(ally);
ADD_SUITE(building);
@ -121,6 +120,8 @@ int RunAllTests(int argc, char *argv[])
ADD_SUITE(spells);
ADD_SUITE(unit);
/* gamecode */
ADD_SUITE(alchemy);
ADD_SUITE(automate);
ADD_SUITE(battle);
ADD_SUITE(calendar);
ADD_SUITE(creport);

BIN
src/util/.DS_Store vendored Normal file

Binary file not shown.

View File

@ -66,6 +66,7 @@ int pofile_read(const char *filename, int (*callback)(const char *msgid, const c
if (!F) {
log_error("could not open %s", filename);
return -1;
}
msgctxt[0] = 0;
@ -106,10 +107,10 @@ int pofile_read(const char *filename, int (*callback)(const char *msgid, const c
line = read_line(F);
}
}
if (ferror(F)) {
log_error("read error in %s:%d.", filename, po_lineno);
return -1;
}
err = ferror(F);
fclose(F);
if (err) {
log_error("read error %d in %s:%d.", err, filename, po_lineno);
}
return err;
}

View File

@ -12,7 +12,6 @@
#include <platform.h>
#include <kernel/config.h>
#include "settings.h"
#include "wormhole.h"