diff --git a/res/e3a/races.xml b/res/e3a/races.xml
index bb70fa3df..00d34e58a 100644
--- a/res/e3a/races.xml
+++ b/res/e3a/races.xml
@@ -8,7 +8,7 @@
-
+
@@ -192,7 +192,7 @@
-
+
@@ -218,7 +218,7 @@
-
+
@@ -239,7 +239,7 @@
-
+
@@ -260,7 +260,7 @@
-
+
@@ -283,7 +283,7 @@
-
+
@@ -306,7 +306,7 @@
-
+
@@ -328,7 +328,7 @@
-
+
@@ -354,7 +354,7 @@
-
+
@@ -376,7 +376,7 @@
-
+
@@ -400,7 +400,7 @@
-
+
@@ -425,7 +425,7 @@
-
+
@@ -448,7 +448,7 @@
-
+
@@ -471,7 +471,7 @@
-
+
@@ -493,7 +493,7 @@
-
+
@@ -520,7 +520,7 @@
-
+
@@ -543,7 +543,7 @@
-
+
@@ -565,7 +565,7 @@
-
+
@@ -589,15 +589,15 @@
-
+
-
+
-
+
@@ -651,28 +651,28 @@
-
+
-
+
-
+
-
+
-
+
@@ -687,14 +687,14 @@
-
+
-
+
@@ -703,14 +703,14 @@
-
+
@@ -736,7 +736,7 @@
-
+
@@ -754,7 +754,7 @@
-
+
@@ -771,7 +771,7 @@
-
+
@@ -787,7 +787,7 @@
-
+
@@ -801,7 +801,7 @@
-
+
@@ -816,7 +816,7 @@
-
+
@@ -830,12 +830,12 @@
-
+
-
+
diff --git a/res/eressea.dtd b/res/eressea.dtd
new file mode 100644
index 000000000..c58ea5f33
--- /dev/null
+++ b/res/eressea.dtd
@@ -0,0 +1,190 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/eressea/races.xml b/res/eressea/races.xml
index 21ff5b028..eb85b6900 100644
--- a/res/eressea/races.xml
+++ b/res/eressea/races.xml
@@ -11,9 +11,8 @@
-
+
-
@@ -27,7 +26,7 @@
-
+
@@ -60,7 +59,7 @@
-
+
@@ -88,7 +87,7 @@
-
+
@@ -115,7 +114,7 @@
-
+
@@ -144,7 +143,7 @@
-
+
@@ -173,7 +172,7 @@
-
+
@@ -203,7 +202,7 @@
-
+
@@ -237,7 +236,7 @@
-
+
@@ -267,7 +266,7 @@
-
+
@@ -299,7 +298,7 @@
-
+
@@ -329,7 +328,7 @@
-
+
@@ -358,7 +357,7 @@
-
+
@@ -388,7 +387,7 @@
-
+
@@ -417,7 +416,7 @@
-
+
@@ -448,7 +447,7 @@
-
+
@@ -479,7 +478,7 @@
-
+
@@ -508,7 +507,7 @@
-
+
@@ -540,7 +539,7 @@
-
+
@@ -570,16 +569,16 @@
-
+
-
+
+ regaura="1.000000" weight="100" capacity="540" speed="1.000000" hp="20" damage="0d0" unarmedattack="0" unarmeddefense="0" attackmodifier="6" defensemodifier="10" scarepeasants="yes" fly="yes" walk="yes" canteach="no" invinciblenonmagic="yes">
@@ -649,28 +648,28 @@
-
+
-
+
-
+
-
+
-
+
@@ -684,25 +683,25 @@
-
+
-
+
-
+
-
+
@@ -712,7 +711,7 @@
-
+
@@ -722,7 +721,7 @@
-
+
@@ -733,7 +732,7 @@
-
+
@@ -783,7 +782,7 @@
-
+
@@ -925,7 +924,7 @@
-
+
@@ -962,7 +961,7 @@
-
+
@@ -970,7 +969,7 @@
-
+
@@ -984,7 +983,7 @@
-
+
@@ -1001,7 +1000,7 @@
-
+
@@ -1017,7 +1016,7 @@
-
+
@@ -1032,7 +1031,7 @@
-
+
@@ -1045,7 +1044,7 @@
-
+
@@ -1059,7 +1058,7 @@
-
+
@@ -1072,11 +1071,11 @@
-
+
-
+
@@ -1203,7 +1202,7 @@
-
+
diff --git a/res/races/dragon.xml b/res/races/dragon.xml
index 7e0da2056..c9692cd9b 100644
--- a/res/races/dragon.xml
+++ b/res/races/dragon.xml
@@ -3,7 +3,7 @@
diff --git a/res/races/goblin-3.xml b/res/races/goblin-3.xml
index 636f8cc29..9eb7ea835 100644
--- a/res/races/goblin-3.xml
+++ b/res/races/goblin-3.xml
@@ -7,7 +7,7 @@ speed="1.0" hp="16" damage="1d5" unarmedattack="-2" unarmeddefense="0"
playerrace="yes" walk="yes" giveperson="yes" giveunit="yes"
getitem="yes" equipment="yes" healing="2.0">
-
+
diff --git a/res/races/halfling.xml b/res/races/halfling.xml
index 3628d2b35..1f326056f 100644
--- a/res/races/halfling.xml
+++ b/res/races/halfling.xml
@@ -1,7 +1,7 @@
-
+
diff --git a/src/exparse.c b/src/exparse.c
index bc7218383..44455888a 100644
--- a/src/exparse.c
+++ b/src/exparse.c
@@ -128,7 +128,19 @@ static void handle_bad_input(parseinfo *pi, const XML_Char *el, const XML_Char *
static bool handle_flag(int *flags, const XML_Char **pair, const char *names[]) {
int i;
for (i = 0; names[i]; ++i) {
- if (xml_strcmp(pair[0], names[i]) == 0) {
+ const char * name = names[i];
+ if (name[0] == '!') {
+ if (xml_strcmp(pair[0], name+1) == 0) {
+ if (xml_bool(pair[1])) {
+ *flags &= ~(1 << i);
+ }
+ else {
+ *flags |= (1 << i);
+ }
+ return true;
+ }
+ }
+ else if (xml_strcmp(pair[0], name) == 0) {
if (xml_bool(pair[1])) {
*flags |= (1 << i);
}
@@ -865,6 +877,173 @@ static void XMLCALL start_ships(parseinfo *pi, const XML_Char *el, const XML_Cha
}
}
+static void XMLCALL start_races(parseinfo *pi, const XML_Char *el, const XML_Char **attr) {
+ race *rc = (race *)pi->object;
+ const char *flag_names[] = {
+ "!playerrace", "killpeasants", "scarepeasants", "!cansteal",
+ "moverandom", "cannotmove", "learn", "fly", "swim", "walk",
+ "!canlearn", "!canteach", "horse", "desert", "illusionary",
+ "absorbpeasants", "noheal", "noweapons", "shapeshift",
+ "shapeshiftany", "undead", "dragon", "coastal", "unarmedguard",
+ "cansail", "invisible", "shipspeed", "moveattack", "migrants", NULL };
+ const char *bflag_names[] = {
+ "equipment", "noblock", "resistpierce", "resistcut", "resistbash",
+ "invinciblenonmagic", "noattack", NULL };
+ const char *eflag_names[] = {
+ "giveperson", "giveunit", "getitem", "recruitethereal",
+ "recruitunlimited", "stonegolem", "irongolem", NULL };
+
+ if (xml_strcmp(el, "attack") == 0) {
+ assert(rc);
+ }
+ else if (xml_strcmp(el, "familiar") == 0) {
+ assert(rc);
+ }
+ else if (xml_strcmp(el, "skill") == 0) {
+ const XML_Char *name = NULL;
+ int i, speed = 0, mod = 0;
+
+ for (i = 0; attr[i]; i += 2) {
+ const XML_Char *key = attr[i], *val = attr[i + 1];
+ if (xml_strcmp(key, "name") == 0) {
+ name = val;
+ }
+ else if (xml_strcmp(key, "modifier") == 0) {
+ mod = xml_int(val);
+ }
+ else if (xml_strcmp(key, "speed") == 0) {
+ speed = xml_int(val);
+ }
+ else {
+ handle_bad_input(pi, el, key);
+ }
+ }
+ if (name) {
+ skill_t sk = findskill(name);
+ if (sk != NOSKILL) {
+ rc->bonus[sk] = (char)mod;
+ if (speed != 0) {
+ set_study_speed(rc, sk, speed);
+ }
+ }
+ }
+ }
+ else if (xml_strcmp(el, "param") == 0) {
+ const XML_Char *key = attr_get(attr, "name"), *val = attr_get(attr, "value");
+ if (key && val) {
+ rc_set_param(rc, key, val);
+ }
+ }
+ else if (xml_strcmp(el, "ai") == 0) {
+ /* AI flags are cumulative to race flags. XML format is dumb */
+ int i, flags = 0;
+ assert(rc);
+ for (i = 0; attr[i]; i += 2) {
+ const XML_Char *key = attr[i], *val = attr[i + 1];
+ if (xml_strcmp(key, "splitsize") == 0) {
+ rc->splitsize = xml_int(val);
+ }
+ else if (xml_strcmp(key, "scare") == 0) {
+ rc_set_param(rc, "scare", val);
+ }
+ else if (!handle_flag(&flags, attr + i, flag_names)) {
+ handle_bad_input(pi, el, key);
+ }
+ }
+ rc->flags |= flags;
+ }
+ else if (xml_strcmp(el, "race") == 0) {
+ const XML_Char *name;
+
+ name = attr_get(attr, "name");
+ if (name) {
+ assert(!rc);
+ pi->object = rc = rc_get_or_create(name);
+ int i;
+
+ for (i = 0; attr[i]; i += 2) {
+ const XML_Char *key = attr[i], *val = attr[i + 1];
+ if (xml_strcmp(key, "maxaura") == 0) {
+ rc->maxaura = (int)(100 * xml_float(val));
+ }
+ else if (xml_strcmp(key, "magres") == 0) {
+ /* specified in percent: */
+ rc->magres = frac_make(xml_int(val), 100);
+ }
+ else if (xml_strcmp(key, "healing") == 0) {
+ rc->healing = (int)(xml_float(val) * 100);
+ }
+ else if (xml_strcmp(key, "regaura") == 0) {
+ rc->regaura = xml_float(val);
+ }
+ else if (xml_strcmp(key, "recruitcost") == 0) {
+ rc->recruitcost = xml_int(val);
+ }
+ else if (xml_strcmp(key, "maintenance") == 0) {
+ rc->maintenance = xml_int(val);
+ }
+ else if (xml_strcmp(key, "income") == 0) {
+ rc->income = xml_int(val);
+ }
+ else if (xml_strcmp(key, "weight") == 0) {
+ rc->weight = xml_int(val);
+ }
+ else if (xml_strcmp(key, "capacity") == 0) {
+ rc->capacity = xml_int(val);
+ }
+ else if (xml_strcmp(key, "speed") == 0) {
+ rc->speed = xml_float(val);
+ }
+ else if (xml_strcmp(key, "hp") == 0) {
+ rc->hitpoints = xml_int(val);
+ }
+ else if (xml_strcmp(key, "ac") == 0) {
+ rc->armor = xml_int(val);
+ }
+ else if (xml_strcmp(key, "damage") == 0) {
+ rc->def_damage = str_strdup(val);
+ }
+ else if (xml_strcmp(key, "unarmedattack") == 0) {
+ rc->at_default = xml_int(val);
+ }
+ else if (xml_strcmp(key, "unarmeddefense") == 0) {
+ rc->df_default = xml_int(val);
+ }
+ else if (xml_strcmp(key, "attackmodifier") == 0) {
+ rc->at_bonus = xml_int(val);
+ }
+ else if (xml_strcmp(key, "defensemodifier") == 0) {
+ rc->df_bonus = xml_int(val);
+ }
+ else if (xml_strcmp(key, "studyspeed") == 0) {
+ int study_speed = xml_int(val);
+ if (study_speed != 0) {
+ skill_t sk;
+ for (sk = 0; sk < MAXSKILLS; ++sk) {
+ set_study_speed(rc, sk, study_speed);
+ }
+ }
+
+ }
+ else if (!handle_flag(&rc->flags, attr + i, flag_names)) {
+ if (!handle_flag(&rc->battle_flags, attr + i, bflag_names)) {
+ if (!handle_flag(&rc->ec_flags, attr + i, eflag_names)) {
+ /* we already handled the name earlier: */
+ if (xml_strcmp(key, "name") != 0) {
+ handle_bad_input(pi, el, attr[i]);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ else {
+ assert(rc);
+ handle_bad_input(pi, el, NULL);
+ }
+}
+
static void XMLCALL start_buildings(parseinfo *pi, const XML_Char *el, const XML_Char **attr) {
const char *flag_names[] = { "nodestroy", "nobuild", "unique", "decay", "magic", "namechange", "fort", "oneperturn", NULL };
if (xml_strcmp(el, "building") == 0) {
@@ -979,6 +1158,9 @@ static void XMLCALL handle_start(void *data, const XML_Char *el, const XML_Char
}
else {
switch (pi->type) {
+ case EXP_RACES:
+ start_races(pi, el, attr);
+ break;
case EXP_BUILDINGS:
start_buildings(pi, el, attr);
break;
@@ -1063,6 +1245,15 @@ static void end_resources(parseinfo *pi, const XML_Char *el) {
}
}
+static void end_races(parseinfo *pi, const XML_Char *el) {
+ if (xml_strcmp(el, "race") == 0) {
+ pi->object = NULL;
+ }
+ else if (xml_strcmp(el, "races") == 0) {
+ pi->type = EXP_UNKNOWN;
+ }
+}
+
static void end_ships(parseinfo *pi, const XML_Char *el) {
ship_type *stype = (ship_type *)pi->object;
if (xml_strcmp(el, "construction") == 0) {
@@ -1135,6 +1326,9 @@ static void XMLCALL handle_end(void *data, const XML_Char *el) {
parseinfo *pi = (parseinfo *)data;
switch (pi->type) {
+ case EXP_RACES:
+ end_races(pi, el);
+ break;
case EXP_SHIPS:
end_ships(pi, el);
break;
diff --git a/src/kernel/race.c b/src/kernel/race.c
index ac81f7f78..596f1527f 100644
--- a/src/kernel/race.c
+++ b/src/kernel/race.c
@@ -451,6 +451,12 @@ int rc_herb_trade(const struct race *rc)
return 500;
}
+void set_study_speed(race *rc, skill_t sk, int modifier) {
+ if (!rc->study_speed)
+ rc->study_speed = calloc(1, MAXSKILLS);
+ rc->study_speed[sk] = (char)modifier;
+}
+
const race *rc_otherrace(const race *rc)
{
variant *v = rc_getoption(rc, RCO_OTHER);
@@ -466,18 +472,13 @@ void rc_set_param(struct race *rc, const char *key, const char *value) {
if (strcmp(key, "recruit_multi") == 0) {
rc->recruit_multi = atof(value);
}
- else if (strcmp(key, "migrants.formula") == 0) {
- if (value[0] == '1') {
- rc->flags |= RCF_MIGRANTS;
- }
- }
else if (strcmp(key, "other_race")==0) {
rc_setoption(rc, RCO_OTHER, value);
}
- else if (strcmp(key, "ai.scare")==0) {
+ else if (strcmp(key, "scare")==0) {
rc_setoption(rc, RCO_SCARE, value);
}
- else if (strcmp(key, "hunger.damage")==0) {
+ else if (strcmp(key, "hunger_damage")==0) {
rc_setoption(rc, RCO_HUNGER, value);
}
else if (strcmp(key, "armor.stamina")==0) {
diff --git a/src/kernel/race.h b/src/kernel/race.h
index 7c38eec57..51c99e0f0 100644
--- a/src/kernel/race.h
+++ b/src/kernel/race.h
@@ -129,7 +129,7 @@ extern "C" {
int weight;
int capacity;
int income;
- float speed;
+ double speed;
int hitpoints;
char *def_damage;
int armor;
@@ -229,9 +229,11 @@ extern "C" {
#define RCF_CANSAIL (1<<24) /* Einheit darf Schiffe betreten */
#define RCF_INVISIBLE (1<<25) /* not visible in any report */
#define RCF_SHIPSPEED (1<<26) /* race gets +1 on shipspeed */
-#define RCF_MIGRANTS (1<<27) /* may have migrant units (human bonus) */
-#define RCF_FAMILIAR (1<<28) /* may be a familiar */
-#define RCF_ATTACK_MOVED (1<<29) /* may attack if it has moved */
+#define RCF_ATTACK_MOVED (1<<27) /* may attack if it has moved */
+#define RCF_MIGRANTS (1<<28) /* may have migrant units (human bonus) */
+#define RCF_FAMILIAR (1<<29) /* may be a familiar */
+
+#define RCF_DEFAULT (RCF_NOSTEAL|RCF_CANSAIL|RCF_NOLEARN)
/* Economic flags */
#define ECF_GIVEPERSON (1<<2) /* �bergibt Personen */
@@ -271,6 +273,7 @@ extern "C" {
const char *raceprefix(const struct unit *u);
void register_race_function(race_func, const char *);
+ void set_study_speed(struct race *rc, skill_t sk, int modifier);
#ifdef __cplusplus
}
#endif
diff --git a/src/kernel/race.test.c b/src/kernel/race.test.c
index dcbe26e0e..9a118bc74 100644
--- a/src/kernel/race.test.c
+++ b/src/kernel/race.test.c
@@ -113,9 +113,9 @@ static void test_rc_set_param(CuTest *tc) {
rc_set_param(rc, "migrants.formula", "1");
CuAssertIntEquals(tc, RCF_MIGRANTS, rc->flags&RCF_MIGRANTS);
CuAssertIntEquals(tc, MIGRANTS_LOG10, rc_migrants_formula(rc));
- rc_set_param(rc, "ai.scare", "400");
+ rc_set_param(rc, "scare", "400");
CuAssertIntEquals(tc, 400, rc_scare(rc));
- rc_set_param(rc, "hunger.damage", "1d10+12");
+ rc_set_param(rc, "hunger_damage", "1d10+12");
CuAssertStrEquals(tc, "1d10+12", rc_hungerdamage(rc));
test_teardown();
}
diff --git a/src/xmlreader.c b/src/xmlreader.c
index 3fd6c1b7b..b26bff66c 100644
--- a/src/xmlreader.c
+++ b/src/xmlreader.c
@@ -1256,7 +1256,7 @@ static void parse_ai(race * rc, xmlNodePtr node)
propValue = xmlGetProp(node, BAD_CAST "scare");
if (propValue) {
- rc_set_param(rc, "ai.scare", (const char *)propValue);
+ rc_set_param(rc, "scare", (const char *)propValue);
xmlFree(propValue);
}
rc->splitsize = xml_ivalue(node, "splitsize", 0);
@@ -1270,12 +1270,6 @@ static void parse_ai(race * rc, xmlNodePtr node)
rc->flags |= RCF_ATTACK_MOVED;
}
-static void set_study_speed(race *rc, skill_t sk, int modifier) {
- if (!rc->study_speed)
- rc->study_speed = calloc(1, MAXSKILLS);
- rc->study_speed[sk] = (char)modifier;
-}
-
static int parse_races(xmlDocPtr doc)
{
xmlXPathContextPtr xpath = xmlXPathNewContext(doc);
@@ -1319,7 +1313,7 @@ static int parse_races(xmlDocPtr doc)
rc->income = xml_ivalue(node, "income", rc->income);
rc->speed = (float)xml_fvalue(node, "speed", rc->speed);
rc->hitpoints = xml_ivalue(node, "hp", rc->hitpoints);
- rc->armor = (char)xml_ivalue(node, "ac", rc->armor);
+ rc->armor = xml_ivalue(node, "ac", rc->armor);
study_speed_base = xml_ivalue(node, "studyspeed", 0);
if (study_speed_base != 0) {
for (sk = 0; sk < MAXSKILLS; ++sk) {
@@ -1332,14 +1326,18 @@ static int parse_races(xmlDocPtr doc)
rc->at_bonus = (char)xml_ivalue(node, "attackmodifier", rc->at_bonus);
rc->df_bonus = (char)xml_ivalue(node, "defensemodifier", rc->df_bonus);
+ if (!xml_bvalue(node, "canteach", true))
+ rc->flags |= RCF_NOTEACH;
+ if (!xml_bvalue(node, "cansteal", true))
+ rc->flags |= RCF_NOSTEAL;
+ if (!xml_bvalue(node, "canlearn", true))
+ rc->flags |= RCF_NOLEARN;
if (!xml_bvalue(node, "playerrace", false)) {
assert(rc->recruitcost == 0);
rc->flags |= RCF_NPC;
}
if (xml_bvalue(node, "scarepeasants", false))
rc->flags |= RCF_SCAREPEASANTS;
- if (!xml_bvalue(node, "cansteal", true))
- rc->flags |= RCF_NOSTEAL;
if (xml_bvalue(node, "cansail", true))
rc->flags |= RCF_CANSAIL;
if (xml_bvalue(node, "cannotmove", false))
@@ -1356,10 +1354,6 @@ static int parse_races(xmlDocPtr doc)
rc->flags |= RCF_SWIM;
if (xml_bvalue(node, "walk", false))
rc->flags |= RCF_WALK;
- if (!xml_bvalue(node, "canlearn", true))
- rc->flags |= RCF_NOLEARN;
- if (!xml_bvalue(node, "canteach", true))
- rc->flags |= RCF_NOTEACH;
if (xml_bvalue(node, "horse", false))
rc->flags |= RCF_HORSE;
if (xml_bvalue(node, "desert", false))