From 63b762a62741aad7c0387b7209519867b44b7977 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 17 Feb 2019 15:34:14 +0100 Subject: [PATCH 1/6] missing nul-termination triggers valgrind. --- src/json.test.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/json.test.c b/src/json.test.c index 30d4e072e..ec814eec6 100644 --- a/src/json.test.c +++ b/src/json.test.c @@ -58,6 +58,7 @@ static cJSON *export_a_region(CuTest * tc, const struct terrain_type *terrain, r int err; region *r; cJSON *json, *attr, *result, *regs; + size_t sz; r = test_create_region(0, 0, terrain); @@ -65,7 +66,8 @@ static cJSON *export_a_region(CuTest * tc, const struct terrain_type *terrain, r err = json_export(&out, EXPORT_REGIONS); CuAssertIntEquals(tc, 0, err); out.api->rewind(out.handle); - out.api->read(out.handle, buf, sizeof(buf)); + sz = out.api->read(out.handle, buf, sizeof(buf)); + buf[sz] = '\0'; mstream_done(&out); json = cJSON_Parse(buf); From 7c64ec5612e643d9a3b7fc9e2f3897d91362f1d8 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 24 Mar 2019 18:05:40 +0100 Subject: [PATCH 2/6] test debugging --- scripts/tests/e2/undead.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/tests/e2/undead.lua b/scripts/tests/e2/undead.lua index c6122657f..b836b17f8 100644 --- a/scripts/tests/e2/undead.lua +++ b/scripts/tests/e2/undead.lua @@ -32,6 +32,10 @@ function test_undead_reserve_other() process_orders() assert_equal(0, u1:get_item("log")) + if 2 ~= u2:get_item("log") then + -- try to catch that intermittent bug: + print(u2:show()) + end assert_equal(2, u2:get_item("log")) end From 9168a25bf2166160e639793eaca5554849701561 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 24 Mar 2019 18:05:40 +0100 Subject: [PATCH 3/6] test debugging --- scripts/tests/e2/undead.lua | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/tests/e2/undead.lua b/scripts/tests/e2/undead.lua index 92d8502e4..f7586ee4b 100644 --- a/scripts/tests/e2/undead.lua +++ b/scripts/tests/e2/undead.lua @@ -31,8 +31,15 @@ function test_undead_reserve_other() u1.name = 'Xolgrim' process_orders() - -- Intermittent Failure: expected 0 but was 2 - -- assert_equal(0, u1:get_item("log")) + if 0 ~= u1:get_item("log") then + -- try to catch that intermittent bug: + print(u1:show()) + end + assert_equal(0, u1:get_item("log")) + if 2 ~= u2:get_item("log") then + -- try to catch that intermittent bug: + print(u2:show()) + end assert_equal(2, u2:get_item("log")) end From fc6bb36ee41413c8fc6f6dcd329b90b39f17e5fe Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 24 Mar 2019 21:27:43 +0100 Subject: [PATCH 4/6] BUG 2566 mountains with no low-level resources. NB: new data version --- src/kernel/gamedata.h | 3 ++- src/kernel/save.c | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/kernel/gamedata.h b/src/kernel/gamedata.h index e14c0a551..986b13d1d 100644 --- a/src/kernel/gamedata.h +++ b/src/kernel/gamedata.h @@ -41,8 +41,9 @@ #define CRYPT_VERSION 363 /* passwords are encrypted */ #define FAMILIAR_FIXMAGE_VERSION 364 /* familiar links are fixed */ #define FAMILIAR_FIXSPELLBOOK_VERSION 365 /* familiar spells are fixed */ +#define FIX_STARTLEVEL_VERSION 366 /* fixing resource startlevels */ -#define RELEASE_VERSION FAMILIAR_FIXSPELLBOOK_VERSION /* current datafile */ +#define RELEASE_VERSION FIX_STARTLEVEL_VERSION /* current datafile */ #define MIN_VERSION UIDHASH_VERSION /* minimal datafile we support */ #define MAX_VERSION RELEASE_VERSION /* change this if we can need to read the future datafile, and we can do so */ diff --git a/src/kernel/save.c b/src/kernel/save.c index 45da22a7c..0b3ade593 100644 --- a/src/kernel/save.c +++ b/src/kernel/save.c @@ -613,6 +613,27 @@ static void read_regioninfo(gamedata *data, const region *r, char *info, size_t } } +static void fix_baselevel(region *r) { + struct terrain_production *p; + for (p = r->terrain->production; p->type; ++p) { + char *end; + long start = (int)strtol(p->startlevel, &end, 10); + if (*end == '\0') { + rawmaterial *res; + for (res = r->resources; res; res = res->next) { + if (p->type == res->rtype) { + if (start != res->startlevel) { + log_debug("setting resource start level for %s in %s to %d", + res->rtype->_name, regionname(r, NULL), start); + res->startlevel = start; + } + } + } + } + + } +} + static region *readregion(gamedata *data, int x, int y) { region *r; @@ -781,6 +802,11 @@ static region *readregion(gamedata *data, int x, int y) } } read_attribs(data, &r->attribs, r); + + if (r->resources && data->version < FIX_STARTLEVEL_VERSION) { + /* we had some badly made rawmaterials before this */ + fix_baselevel(r); + } return r; } From 88688558373a72d36dbf7ad83b5c5c54ca164236 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 31 Mar 2019 18:14:37 +0200 Subject: [PATCH 5/6] allow setting password to null --- src/bind_faction.c | 3 ++- src/kernel/faction.c | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/bind_faction.c b/src/bind_faction.c index 9cc6bdd97..518dddc27 100644 --- a/src/bind_faction.c +++ b/src/bind_faction.c @@ -383,7 +383,8 @@ static int tolua_faction_set_password(lua_State * L) { faction *self = (faction *)tolua_tousertype(L, 1, NULL); const char * passw = tolua_tostring(L, 2, NULL); - faction_setpassword(self, password_hash(passw, PASSWORD_DEFAULT)); + faction_setpassword(self, + passw ? password_hash(passw, PASSWORD_DEFAULT) : NULL); return 0; } diff --git a/src/kernel/faction.c b/src/kernel/faction.c index fc1de186a..238ba834b 100755 --- a/src/kernel/faction.c +++ b/src/kernel/faction.c @@ -564,8 +564,12 @@ const char *faction_getpassword(const faction *f) { void faction_setpassword(faction * f, const char *pwhash) { - assert(pwhash); - f->password_id = dbstring_save(pwhash); + if (pwhash) { + f->password_id = dbstring_save(pwhash); + } + else { + f->password_id = 0; + } } bool valid_race(const struct faction *f, const struct race *rc) From 17484e220fa6b245cd0bf48eb61a66b5fe1128f6 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 31 Mar 2019 21:03:31 +0200 Subject: [PATCH 6/6] Bug 2569: Rostregen macht zu viele Waffen kaputt. --- src/battle.c | 5601 +++++++++++++++++++------------------ src/battle.h | 1 - src/battle.test.c | 20 +- src/spells/combatspells.c | 56 +- 4 files changed, 2840 insertions(+), 2838 deletions(-) diff --git a/src/battle.c b/src/battle.c index 311cda7e3..132783ecb 100644 --- a/src/battle.c +++ b/src/battle.c @@ -506,7 +506,7 @@ contest_classic(int skilldiff, const armor_type * ar, const armor_type * sh) */ static int contest_new(int skilldiff, const troop dt, const armor_type * ar, -const armor_type * sh) + const armor_type * sh) { double tohit = 0.5 + skilldiff * 0.1; @@ -524,7 +524,7 @@ const armor_type * sh) static int contest(int skdiff, const troop dt, const armor_type * ar, -const armor_type * sh) + const armor_type * sh) { if (skill_formula == FORMULA_ORIG) { return contest_classic(skdiff, ar, sh); @@ -562,7 +562,7 @@ static weapon *preferred_weapon(const troop t, bool attacking) } weapon *select_weapon(const troop t, bool attacking, bool ismissile) - /* select the primary weapon for this trooper */ +/* select the primary weapon for this trooper */ { if (attacking) { if (ismissile) { @@ -864,7 +864,7 @@ static void rmtroop(troop dt) rmfighter(df, 1); assert(dt.index >= 0 && dt.index < df->unit->number); - if (dt.index!=df->alive-df->removed) { + if (dt.index != df->alive - df->removed) { df->person[dt.index] = df->person[df->alive - df->removed]; } if (df->removed) { @@ -930,32 +930,32 @@ void drain_exp(struct unit *u, int n) static void vampirism(troop at, int damage) { - const unit *au = at.fighter->unit; + const unit *au = at.fighter->unit; - if (u_race(au) == get_race(RC_DAEMON)) { - if (rule_vampire > 0) { - int gain = damage / rule_vampire; - int chance = damage - rule_vampire * gain; - if (chance > 0 && (rng_int() % rule_vampire < chance)) - ++gain; - if (gain > 0) { - int maxhp = unit_max_hp(at.fighter->unit); + if (u_race(au) == get_race(RC_DAEMON)) { + if (rule_vampire > 0) { + int gain = damage / rule_vampire; + int chance = damage - rule_vampire * gain; + if (chance > 0 && (rng_int() % rule_vampire < chance)) + ++gain; + if (gain > 0) { + int maxhp = unit_max_hp(at.fighter->unit); - gain += at.fighter->person[at.index].hp; - if (maxhp > gain) maxhp = gain; - at.fighter->person[at.index].hp = maxhp; + gain += at.fighter->person[at.index].hp; + if (maxhp > gain) maxhp = gain; + at.fighter->person[at.index].hp = maxhp; + } } } - } } static void ship_damage(int turn, unit *du) { - if (turn>1) { - /* someone on the ship got damaged, damage the ship */ - ship *sh = du->ship ? du->ship : leftship(du); - if (sh) - fset(sh, SF_DAMAGED); - } + if (turn > 1) { + /* someone on the ship got damaged, damage the ship */ + ship *sh = du->ship ? du->ship : leftship(du); + if (sh) + fset(sh, SF_DAMAGED); + } } #define MAXRACES 128 @@ -1006,7 +1006,7 @@ static int rc_specialdamage(const unit *au, const unit *du, const struct weapon_ } int calculate_armor(troop dt, const weapon_type *dwtype, const weapon_type *awtype, - const armor_type *armor, const armor_type *shield, bool magic) { + const armor_type *armor, const armor_type *shield, bool magic) { const fighter *df = dt.fighter; unit *du = df->unit; @@ -1027,8 +1027,8 @@ int calculate_armor(troop dt, const weapon_type *dwtype, const weapon_type *awty } if (magic) { - /* gegen Magie wirkt nur natuerliche und magische Ruestung */ - total_armor = 0; + /* gegen Magie wirkt nur natuerliche und magische Ruestung */ + total_armor = 0; } /* natuerliche Ruestung */ @@ -1065,33 +1065,33 @@ int calculate_armor(troop dt, const weapon_type *dwtype, const weapon_type *awty } int apply_resistance(int damage, troop dt, const weapon_type *dwtype, const armor_type *armor, const armor_type *shield, bool magic) { - const fighter *df = dt.fighter; - unit *du = df->unit; + const fighter *df = dt.fighter; + unit *du = df->unit; - if (!magic) - return damage; + if (!magic) + return damage; - /* calculate damage multiplier for magical damage */ - variant resistance_factor = frac_sub(frac_one, magic_resistance(du)); + /* calculate damage multiplier for magical damage */ + variant resistance_factor = frac_sub(frac_one, magic_resistance(du)); - if (u_race(du)->battle_flags & BF_EQUIPMENT) { - /* der Effekt von Laen steigt nicht linear */ - if (armor && fval(armor, ATF_LAEN)) { - resistance_factor = frac_mul(resistance_factor, frac_sub(frac_one, armor->magres)); + if (u_race(du)->battle_flags & BF_EQUIPMENT) { + /* der Effekt von Laen steigt nicht linear */ + if (armor && fval(armor, ATF_LAEN)) { + resistance_factor = frac_mul(resistance_factor, frac_sub(frac_one, armor->magres)); + } + if (shield && fval(shield, ATF_LAEN)) { + resistance_factor = frac_mul(resistance_factor, frac_sub(frac_one, shield->magres)); + } + if (dwtype) { + resistance_factor = frac_mul(resistance_factor, frac_sub(frac_one, dwtype->magres)); + } } - if (shield && fval(shield, ATF_LAEN)) { - resistance_factor = frac_mul(resistance_factor, frac_sub(frac_one, shield->magres)); + if (resistance_factor.sa[0] <= 0) { + return 0; } - if (dwtype) { - resistance_factor = frac_mul(resistance_factor, frac_sub(frac_one, dwtype->magres)); - } - } - if (resistance_factor.sa[0] <= 0) { - return 0; - } - variant reduced_damage = frac_mul(frac_make(damage, 1), resistance_factor); - return reduced_damage.sa[0] / reduced_damage.sa[1]; + variant reduced_damage = frac_mul(frac_make(damage, 1), resistance_factor); + return reduced_damage.sa[0] / reduced_damage.sa[1]; } @@ -1148,2937 +1148,2940 @@ static bool survives(fighter *af, troop dt, battle *b) { } static void destroy_items(troop dt) { - unit *du = dt.fighter->unit; + unit *du = dt.fighter->unit; - item **pitm; - - for (pitm = &du->items; *pitm;) { - item *itm = *pitm; - const item_type *itype = itm->type; - if (!(itype->flags & ITF_CURSED) && dt.index < itm->number) { - /* 25% Grundchance, das ein Item kaputtgeht. */ - if (rng_int() % 4 < 1) { - i_change(pitm, itype, -1); - } - } - if (*pitm == itm) { - pitm = &itm->next; - } - } + item **pitm; + for (pitm = &du->items; *pitm;) { + item *itm = *pitm; + const item_type *itype = itm->type; + if (!(itype->flags & ITF_CURSED) && dt.index < itm->number) { + /* 25% Grundchance, das ein Item kaputtgeht. */ + if (rng_int() % 4 < 1) { + i_change(pitm, itype, -1); + } + if (*pitm == itm) { + pitm = &itm->next; + } + } + else { + pitm = &itm->next; + } + } } static void calculate_defense_type(troop at, troop dt, int type, bool missile, - const weapon_type **dwtype, int *defskill) { - const weapon *weapon; - weapon = select_weapon(dt, false, true); /* missile=true to get the unmodified best weapon she has */ - *defskill = weapon_effskill(dt, at, weapon, false, false); - if (weapon != NULL) - *dwtype = weapon->type; -} + const weapon_type **dwtype, int *defskill) { + const weapon *weapon; + weapon = select_weapon(dt, false, true); /* missile=true to get the unmodified best weapon she has */ + *defskill = weapon_effskill(dt, at, weapon, false, false); + if (weapon != NULL) + *dwtype = weapon->type; + } -static void calculate_attack_type(troop at, troop dt, int type, bool missile, - const weapon_type **awtype, int *attskill, bool *magic) { - const weapon *weapon; + static void calculate_attack_type(troop at, troop dt, int type, bool missile, + const weapon_type **awtype, int *attskill, bool *magic) { + const weapon *weapon; - switch (type) { - case AT_STANDARD: - weapon = select_weapon(at, true, missile); - *attskill = weapon_effskill(at, dt, weapon, true, missile); - if (weapon) - *awtype = weapon->type; - if (*awtype && fval(*awtype, WTF_MAGICAL)) - *magic = true; - break; - case AT_NATURAL: - *attskill = weapon_effskill(at, dt, NULL, true, missile); - break; - case AT_SPELL: - case AT_COMBATSPELL: - *magic = true; - break; - default: - break; - } -} - -static int crit_damage(int attskill, int defskill, const char *damage_formula) { - int damage = 0; - if (rule_damage & DAMAGE_CRITICAL) { - double kritchance = ((double)attskill * 3.0 - (double)defskill) / 200.0; - int maxk = 4; - - kritchance = fmax(kritchance, 0.005); - kritchance = fmin(0.9, kritchance); - - while (maxk-- && chance(kritchance)) { - damage += dice_rand(damage_formula); - } - } - return damage; -} - -static int apply_race_resistance(int reduced_damage, fighter *df, - const weapon_type *awtype, bool magic) { - unit *du = df->unit; - - if ((u_race(du)->battle_flags & BF_INV_NONMAGIC) && !magic) - reduced_damage = 0; - else { - unsigned int i = 0; - - if (u_race(du)->battle_flags & BF_RES_PIERCE) - i |= WTF_PIERCE; - if (u_race(du)->battle_flags & BF_RES_CUT) - i |= WTF_CUT; - if (u_race(du)->battle_flags & BF_RES_BASH) - i |= WTF_BLUNT; - - if (i && awtype && fval(awtype, i)) - reduced_damage /= 2; - } - return reduced_damage; -} - -static int apply_magicshield(int reduced_damage, fighter *df, - const weapon_type *awtype, battle *b, bool magic) { - side *ds = df->side; - selist *ql; - int qi; - - if (reduced_damage <= 0) - return 0; - - /* Schilde */ - for (qi = 0, ql = b->meffects; ql; selist_advance(&ql, &qi, 1)) { - meffect *me = (meffect *)selist_get(ql, qi); - if (meffect_protection(b, me, ds) != 0) { - assert(0 <= reduced_damage); /* rda sollte hier immer mindestens 0 sein */ - /* jeder Schaden wird um effect% reduziert bis der Schild duration - * Trefferpunkte aufgefangen hat */ - if (me->typ == SHIELD_REDUCE) { - int hp = reduced_damage * (me->effect / 100); - reduced_damage -= hp; - me->duration -= hp; - } - /* gibt Ruestung +effect fuer duration Treffer */ - if (me->typ == SHIELD_ARMOR) { - reduced_damage -= me->effect; - if (reduced_damage < 0) reduced_damage = 0; - me->duration--; - } + switch (type) { + case AT_STANDARD: + weapon = select_weapon(at, true, missile); + *attskill = weapon_effskill(at, dt, weapon, true, missile); + if (weapon) + *awtype = weapon->type; + if (*awtype && fval(*awtype, WTF_MAGICAL)) + *magic = true; + break; + case AT_NATURAL: + *attskill = weapon_effskill(at, dt, NULL, true, missile); + break; + case AT_SPELL: + case AT_COMBATSPELL: + *magic = true; + break; + default: + break; } } - return reduced_damage; -} + static int crit_damage(int attskill, int defskill, const char *damage_formula) { + int damage = 0; + if (rule_damage & DAMAGE_CRITICAL) { + double kritchance = ((double)attskill * 3.0 - (double)defskill) / 200.0; + int maxk = 4; -bool -terminate(troop dt, troop at, int type, const char *damage_formula, bool missile) -{ - fighter *df = dt.fighter; - fighter *af = at.fighter; - unit *au = af->unit; - unit *du = df->unit; - battle *b = df->side->battle; + kritchance = fmax(kritchance, 0.005); + kritchance = fmin(0.9, kritchance); - int armor_value; - - const weapon_type *dwtype = NULL; - const weapon_type *awtype = NULL; - const armor_type *armor = NULL; - const armor_type *shield = NULL; - - int reduced_damage, attskill = 0, defskill = 0; - bool magic = false; - - int damage = dice_rand(damage_formula); - - assert(du->number > 0); - ++at.fighter->hits; - - calculate_attack_type(at, dt, type, missile, &awtype, &attskill, &magic); - calculate_defense_type(at, dt, type, missile, &dwtype, &defskill); - - if (is_riding(at) && (awtype == NULL || (fval(awtype, WTF_HORSEBONUS) - && !fval(awtype, WTF_MISSILE)))) { - damage += CavalryBonus(au, dt, BONUS_DAMAGE); - } - - armor = select_armor(dt, false); - shield = select_armor(dt, true); - - armor_value = calculate_armor(dt, dwtype, awtype, armor, shield, magic); - if (armor_value < 0) { - return false; - } - - damage = apply_resistance(damage, dt, dwtype, armor, shield, magic); - - if (type != AT_COMBATSPELL && type != AT_SPELL) { - damage += crit_damage(attskill, defskill, damage_formula); - - damage += rc_specialdamage(au, du, awtype); - - if (awtype == NULL || !fval(awtype, WTF_MISSILE)) { - /* melee bonus */ - if (rule_damage & DAMAGE_MELEE_BONUS) { - damage += af->person[at.index].damage; + while (maxk-- && chance(kritchance)) { + damage += dice_rand(damage_formula); } } + return damage; + } - /* Skilldifferenzbonus */ - if (rule_damage & DAMAGE_SKILL_BONUS) { - int b = (attskill - defskill) / DAMAGE_QUOTIENT; - if (b > 0) damage += b; + static int apply_race_resistance(int reduced_damage, fighter *df, + const weapon_type *awtype, bool magic) { + unit *du = df->unit; + + if ((u_race(du)->battle_flags & BF_INV_NONMAGIC) && !magic) + reduced_damage = 0; + else { + unsigned int i = 0; + + if (u_race(du)->battle_flags & BF_RES_PIERCE) + i |= WTF_PIERCE; + if (u_race(du)->battle_flags & BF_RES_CUT) + i |= WTF_CUT; + if (u_race(du)->battle_flags & BF_RES_BASH) + i |= WTF_BLUNT; + + if (i && awtype && fval(awtype, i)) + reduced_damage /= 2; } + return reduced_damage; } - reduced_damage = damage - armor_value; - if (reduced_damage < 0) reduced_damage = 0; + static int apply_magicshield(int reduced_damage, fighter *df, + const weapon_type *awtype, battle *b, bool magic) { + side *ds = df->side; + selist *ql; + int qi; - reduced_damage = apply_race_resistance(reduced_damage, df, awtype, magic); - reduced_damage = apply_magicshield(reduced_damage, df, awtype, b, magic); + if (reduced_damage <= 0) + return 0; - assert(dt.index >= 0 && dt.index < du->number); - if (reduced_damage > 0) { - df->person[dt.index].hp -= reduced_damage; - - vampirism(at, reduced_damage); - - ship_damage(b->turn, du); - } - - if (survives(af, dt, b)) - return false; - - ++at.fighter->kills; - - destroy_items(dt); - - kill_troop(dt); - - return true; -} - -static int -count_side(const side * s, const side * vs, int minrow, int maxrow, int select) -{ - fighter *fig; - int people = 0; - int unitrow[NUMROWS]; - - if (maxrow < FIGHT_ROW) - return 0; - if (select & SELECT_ADVANCE) { - memset(unitrow, -1, sizeof(unitrow)); - } - - for (fig = s->fighters; fig; fig = fig->next) { - if (fig->alive - fig->removed > 0) { - int row = statusrow(fig->status); - if (select & SELECT_ADVANCE) { - if (unitrow[row] == -1) { - unitrow[row] = get_unitrow(fig, vs); + /* Schilde */ + for (qi = 0, ql = b->meffects; ql; selist_advance(&ql, &qi, 1)) { + meffect *me = (meffect *)selist_get(ql, qi); + if (meffect_protection(b, me, ds) != 0) { + assert(0 <= reduced_damage); /* rda sollte hier immer mindestens 0 sein */ + /* jeder Schaden wird um effect% reduziert bis der Schild duration + * Trefferpunkte aufgefangen hat */ + if (me->typ == SHIELD_REDUCE) { + int hp = reduced_damage * (me->effect / 100); + reduced_damage -= hp; + me->duration -= hp; + } + /* gibt Ruestung +effect fuer duration Treffer */ + if (me->typ == SHIELD_ARMOR) { + reduced_damage -= me->effect; + if (reduced_damage < 0) reduced_damage = 0; + me->duration--; } - row = unitrow[row]; } - if (row >= minrow && row <= maxrow) { - people += fig->alive - fig->removed; - if (people > 0 && (select & SELECT_FIND)) + } + + return reduced_damage; + } + + bool + terminate(troop dt, troop at, int type, const char *damage_formula, bool missile) + { + fighter *df = dt.fighter; + fighter *af = at.fighter; + unit *au = af->unit; + unit *du = df->unit; + battle *b = df->side->battle; + + int armor_value; + + const weapon_type *dwtype = NULL; + const weapon_type *awtype = NULL; + const armor_type *armor = NULL; + const armor_type *shield = NULL; + + int reduced_damage, attskill = 0, defskill = 0; + bool magic = false; + + int damage = dice_rand(damage_formula); + + assert(du->number > 0); + ++at.fighter->hits; + + calculate_attack_type(at, dt, type, missile, &awtype, &attskill, &magic); + calculate_defense_type(at, dt, type, missile, &dwtype, &defskill); + + if (is_riding(at) && (awtype == NULL || (fval(awtype, WTF_HORSEBONUS) + && !fval(awtype, WTF_MISSILE)))) { + damage += CavalryBonus(au, dt, BONUS_DAMAGE); + } + + armor = select_armor(dt, false); + shield = select_armor(dt, true); + + armor_value = calculate_armor(dt, dwtype, awtype, armor, shield, magic); + if (armor_value < 0) { + return false; + } + + damage = apply_resistance(damage, dt, dwtype, armor, shield, magic); + + if (type != AT_COMBATSPELL && type != AT_SPELL) { + damage += crit_damage(attskill, defskill, damage_formula); + + damage += rc_specialdamage(au, du, awtype); + + if (awtype == NULL || !fval(awtype, WTF_MISSILE)) { + /* melee bonus */ + if (rule_damage & DAMAGE_MELEE_BONUS) { + damage += af->person[at.index].damage; + } + } + + /* Skilldifferenzbonus */ + if (rule_damage & DAMAGE_SKILL_BONUS) { + int b = (attskill - defskill) / DAMAGE_QUOTIENT; + if (b > 0) damage += b; + } + } + + reduced_damage = damage - armor_value; + if (reduced_damage < 0) reduced_damage = 0; + + reduced_damage = apply_race_resistance(reduced_damage, df, awtype, magic); + reduced_damage = apply_magicshield(reduced_damage, df, awtype, b, magic); + + assert(dt.index >= 0 && dt.index < du->number); + if (reduced_damage > 0) { + df->person[dt.index].hp -= reduced_damage; + + vampirism(at, reduced_damage); + + ship_damage(b->turn, du); + } + + if (survives(af, dt, b)) + return false; + + ++at.fighter->kills; + + destroy_items(dt); + + kill_troop(dt); + + return true; + } + + static int + count_side(const side * s, const side * vs, int minrow, int maxrow, int select) + { + fighter *fig; + int people = 0; + int unitrow[NUMROWS]; + + if (maxrow < FIGHT_ROW) + return 0; + if (select & SELECT_ADVANCE) { + memset(unitrow, -1, sizeof(unitrow)); + } + + for (fig = s->fighters; fig; fig = fig->next) { + if (fig->alive - fig->removed > 0) { + int row = statusrow(fig->status); + if (select & SELECT_ADVANCE) { + if (unitrow[row] == -1) { + unitrow[row] = get_unitrow(fig, vs); + } + row = unitrow[row]; + } + if (row >= minrow && row <= maxrow) { + people += fig->alive - fig->removed; + if (people > 0 && (select & SELECT_FIND)) + break; + } + } + } + return people; + } + + /* return the number of live allies warning: this function only considers + * troops that are still alive, not those that are still fighting although + * dead. */ + int + count_allies(const side * as, int minrow, int maxrow, int select, int allytype) + { + battle *b = as->battle; + side *ds; + int count = 0; + + for (ds = b->sides; ds != b->sides + b->nsides; ++ds) { + if ((allytype == ALLY_ANY && helping(as, ds)) || (allytype == ALLY_SELF + && as->faction == ds->faction)) { + count += count_side(ds, NULL, minrow, maxrow, select); + if (count > 0 && (select & SELECT_FIND)) break; } } + return count; } - return people; -} -/* return the number of live allies warning: this function only considers -* troops that are still alive, not those that are still fighting although -* dead. */ -int -count_allies(const side * as, int minrow, int maxrow, int select, int allytype) -{ - battle *b = as->battle; - side *ds; - int count = 0; + static int + count_enemies_i(battle * b, const fighter * af, int minrow, int maxrow, + int select) + { + side *es, *as = af->side; + int i = 0; - for (ds = b->sides; ds != b->sides + b->nsides; ++ds) { - if ((allytype == ALLY_ANY && helping(as, ds)) || (allytype == ALLY_SELF - && as->faction == ds->faction)) { - count += count_side(ds, NULL, minrow, maxrow, select); - if (count > 0 && (select & SELECT_FIND)) - break; - } - } - return count; -} - -static int -count_enemies_i(battle * b, const fighter * af, int minrow, int maxrow, -int select) -{ - side *es, *as = af->side; - int i = 0; - - for (es = b->sides; es != b->sides + b->nsides; ++es) { - if (as == NULL || enemy(es, as)) { - int offset = 0; - if (select & SELECT_DISTANCE) { - offset = get_unitrow(af, es) - FIGHT_ROW; - } - i += count_side(es, as, minrow - offset, maxrow - offset, select); - if (i > 0 && (select & SELECT_FIND)) - break; - } - } - return i; -} - -int -count_enemies(battle * b, const fighter * af, int minrow, int maxrow, -int select) -{ - int sr = statusrow(af->status); - side *as = af->side; - - if (b->alive == b->fast.alive && as == b->fast.side && sr == b->fast.status - && minrow == b->fast.minrow && maxrow == b->fast.maxrow) { - if (b->fast.enemies[select] >= 0) { - return b->fast.enemies[select]; - } - else if (select & SELECT_FIND) { - if (b->fast.enemies[select - SELECT_FIND] >= 0) { - return b->fast.enemies[select - SELECT_FIND]; + for (es = b->sides; es != b->sides + b->nsides; ++es) { + if (as == NULL || enemy(es, as)) { + int offset = 0; + if (select & SELECT_DISTANCE) { + offset = get_unitrow(af, es) - FIGHT_ROW; + } + i += count_side(es, as, minrow - offset, maxrow - offset, select); + if (i > 0 && (select & SELECT_FIND)) + break; } } - } - else if (select != SELECT_FIND || b->alive != b->fast.alive) { - b->fast.side = as; - b->fast.status = sr; - b->fast.minrow = minrow; - b->fast.alive = b->alive; - b->fast.maxrow = maxrow; - memset(b->fast.enemies, -1, sizeof(b->fast.enemies)); - } - if (maxrow >= FIRST_ROW) { - int i = count_enemies_i(b, af, minrow, maxrow, select); - b->fast.enemies[select] = i; return i; } - return 0; -} -troop select_enemy(fighter * af, int minrow, int maxrow, int select) -{ - side *as = af->side; - battle *b = as->battle; - int si, selected; - int enemies; -#ifdef DEBUG_SELECT - troop result = no_troop; -#endif - if (u_race(af->unit)->flags & RCF_FLY) { - /* flying races ignore min- and maxrow and can attack anyone fighting - * them */ - minrow = FIGHT_ROW; - maxrow = BEHIND_ROW; + int + count_enemies(battle * b, const fighter * af, int minrow, int maxrow, + int select) + { + int sr = statusrow(af->status); + side *as = af->side; + + if (b->alive == b->fast.alive && as == b->fast.side && sr == b->fast.status + && minrow == b->fast.minrow && maxrow == b->fast.maxrow) { + if (b->fast.enemies[select] >= 0) { + return b->fast.enemies[select]; + } + else if (select & SELECT_FIND) { + if (b->fast.enemies[select - SELECT_FIND] >= 0) { + return b->fast.enemies[select - SELECT_FIND]; + } + } + } + else if (select != SELECT_FIND || b->alive != b->fast.alive) { + b->fast.side = as; + b->fast.status = sr; + b->fast.minrow = minrow; + b->fast.alive = b->alive; + b->fast.maxrow = maxrow; + memset(b->fast.enemies, -1, sizeof(b->fast.enemies)); + } + if (maxrow >= FIRST_ROW) { + int i = count_enemies_i(b, af, minrow, maxrow, select); + b->fast.enemies[select] = i; + return i; + } + return 0; } - if (minrow < FIGHT_ROW) minrow = FIGHT_ROW; - - enemies = count_enemies(b, af, minrow, maxrow, select); - - /* Niemand ist in der angegebenen Entfernung? */ - if (enemies <= 0) - return no_troop; - - selected = (int)(rng_int() % enemies); - for (si = 0; as->enemies[si]; ++si) { - side *ds = as->enemies[si]; - fighter *df; - int unitrow[NUMROWS]; - int offset = 0; - - if (select & SELECT_DISTANCE) - offset = get_unitrow(af, ds) - FIGHT_ROW; - - if (select & SELECT_ADVANCE) { - int ui; - for (ui = 0; ui != NUMROWS; ++ui) - unitrow[ui] = -1; + troop select_enemy(fighter * af, int minrow, int maxrow, int select) + { + side *as = af->side; + battle *b = as->battle; + int si, selected; + int enemies; +#ifdef DEBUG_SELECT + troop result = no_troop; +#endif + if (u_race(af->unit)->flags & RCF_FLY) { + /* flying races ignore min- and maxrow and can attack anyone fighting + * them */ + minrow = FIGHT_ROW; + maxrow = BEHIND_ROW; } - for (df = ds->fighters; df; df = df->next) { - int dr; + if (minrow < FIGHT_ROW) minrow = FIGHT_ROW; - dr = statusrow(df->status); - if (select & SELECT_ADVANCE) { - if (unitrow[dr] < 0) { - unitrow[dr] = get_unitrow(df, as); - } - dr = unitrow[dr]; - } + enemies = count_enemies(b, af, minrow, maxrow, select); + + /* Niemand ist in der angegebenen Entfernung? */ + if (enemies <= 0) + return no_troop; + + selected = (int)(rng_int() % enemies); + for (si = 0; as->enemies[si]; ++si) { + side *ds = as->enemies[si]; + fighter *df; + int unitrow[NUMROWS]; + int offset = 0; if (select & SELECT_DISTANCE) - dr += offset; - if (dr < minrow || dr > maxrow) - continue; - if (df->alive - df->removed > selected) { -#ifdef DEBUG_SELECT - if (result.fighter == NULL) { - result.index = selected; - result.fighter = df; + offset = get_unitrow(af, ds) - FIGHT_ROW; + + if (select & SELECT_ADVANCE) { + int ui; + for (ui = 0; ui != NUMROWS; ++ui) + unitrow[ui] = -1; + } + + for (df = ds->fighters; df; df = df->next) { + int dr; + + dr = statusrow(df->status); + if (select & SELECT_ADVANCE) { + if (unitrow[dr] < 0) { + unitrow[dr] = get_unitrow(df, as); + } + dr = unitrow[dr]; } -#else - troop dt; - dt.index = selected; - dt.fighter = df; - return dt; -#endif - } - selected -= (df->alive - df->removed); - enemies -= (df->alive - df->removed); - } - } - if (enemies != 0) { - log_error("select_enemies has a bug.\n"); - } + + if (select & SELECT_DISTANCE) + dr += offset; + if (dr < minrow || dr > maxrow) + continue; + if (df->alive - df->removed > selected) { #ifdef DEBUG_SELECT - return result; + if (result.fighter == NULL) { + result.index = selected; + result.fighter = df; + } #else - assert(!selected); - return no_troop; + troop dt; + dt.index = selected; + dt.fighter = df; + return dt; #endif -} - -static int get_tactics(const side * as, const side * ds) -{ - battle *b = as->battle; - side *stac; - int result = 0; - int defense = 0; - - if (b->max_tactics > 0) { - for (stac = b->sides; stac != b->sides + b->nsides; ++stac) { - if (stac->leader.value > result && helping(stac, as)) { - assert(ds == NULL || !helping(stac, ds)); - result = stac->leader.value; - } - if (ds && stac->leader.value > defense && helping(stac, ds)) { - assert(!helping(stac, as)); - defense = stac->leader.value; + } + selected -= (df->alive - df->removed); + enemies -= (df->alive - df->removed); } } - } - return result - defense; -} - -double tactics_chance(const unit *u, int skilldiff) { - double tacch = 0.1 * skilldiff; - if (fval(u->region->terrain, SEA_REGION)) { - const ship *sh = u->ship; - if (sh) { - tacch *= sh->type->tac_bonus; + if (enemies != 0) { + log_error("select_enemies has a bug.\n"); } - } - return tacch; -} - -static troop select_opponent(battle * b, troop at, int mindist, int maxdist) -{ - fighter *af = at.fighter; - troop dt; - - if (u_race(af->unit)->flags & RCF_FLY) { - /* flying races ignore min- and maxrow and can attack anyone fighting - * them */ - dt = select_enemy(at.fighter, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); - } - else { - if (mindist < FIGHT_ROW) mindist = FIGHT_ROW; - dt = select_enemy(at.fighter, mindist, maxdist, SELECT_ADVANCE); +#ifdef DEBUG_SELECT + return result; +#else + assert(!selected); + return no_troop; +#endif } - if (b->turn == 0 && dt.fighter) { - if (rule_tactics_formula == 1) { - int tactics = get_tactics(at.fighter->side, dt.fighter->side); + static int get_tactics(const side * as, const side * ds) + { + battle *b = as->battle; + side *stac; + int result = 0; + int defense = 0; - /* percentage chance to get this attack */ - if (tactics > 0) { - double tacch = tactics_chance(af->unit, tactics); - if (!chance(tacch)) { + if (b->max_tactics > 0) { + for (stac = b->sides; stac != b->sides + b->nsides; ++stac) { + if (stac->leader.value > result && helping(stac, as)) { + assert(ds == NULL || !helping(stac, ds)); + result = stac->leader.value; + } + if (ds && stac->leader.value > defense && helping(stac, ds)) { + assert(!helping(stac, as)); + defense = stac->leader.value; + } + } + } + return result - defense; + } + + double tactics_chance(const unit *u, int skilldiff) { + double tacch = 0.1 * skilldiff; + if (fval(u->region->terrain, SEA_REGION)) { + const ship *sh = u->ship; + if (sh) { + tacch *= sh->type->tac_bonus; + } + } + return tacch; + } + + static troop select_opponent(battle * b, troop at, int mindist, int maxdist) + { + fighter *af = at.fighter; + troop dt; + + if (u_race(af->unit)->flags & RCF_FLY) { + /* flying races ignore min- and maxrow and can attack anyone fighting + * them */ + dt = select_enemy(at.fighter, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + } + else { + if (mindist < FIGHT_ROW) mindist = FIGHT_ROW; + dt = select_enemy(at.fighter, mindist, maxdist, SELECT_ADVANCE); + } + + if (b->turn == 0 && dt.fighter) { + if (rule_tactics_formula == 1) { + int tactics = get_tactics(at.fighter->side, dt.fighter->side); + + /* percentage chance to get this attack */ + if (tactics > 0) { + double tacch = tactics_chance(af->unit, tactics); + if (!chance(tacch)) { + dt.fighter = NULL; + } + } + else { dt.fighter = NULL; } } - else { - dt.fighter = NULL; - } } + + return dt; } - return dt; -} - -selist *select_fighters(battle * b, const side * vs, int mask, select_fun cb, void *cbdata) -{ - side *s; - selist *fightervp = 0; - - assert(vs != NULL); - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fig; - - if (mask == FS_ENEMY) { - if (!enemy(s, vs)) - continue; - } - else if (mask == FS_HELP) { - if (enemy(s, vs) || !alliedside(s, vs->faction, HELP_FIGHT)) { - continue; - } - } - else { - assert(mask == (FS_HELP | FS_ENEMY) || !"invalid alliance state"); - } - for (fig = s->fighters; fig; fig = fig->next) { - if (cb(vs, fig, cbdata)) { - selist_push(&fightervp, fig); - } - } - } - - return fightervp; -} - -struct selector { - int minrow; - int maxrow; -}; - -static bool select_row(const side *vs, const fighter *fig, void *cbdata) -{ - struct selector *sel = (struct selector *)cbdata; - int row = get_unitrow(fig, vs); - return (row >= sel->minrow && row <= sel->maxrow); -} - -selist *fighters(battle * b, const side * vs, int minrow, int maxrow, int mask) -{ - struct selector sel; - sel.maxrow = maxrow; - sel.minrow = minrow; - return select_fighters(b, vs, mask, select_row, &sel); -} - -static void report_failed_spell(struct battle * b, struct unit * mage, const struct spell *sp) -{ - message *m = msg_message("spell_failed", "unit spell", mage, sp); - message_all(b, m); - msg_release(m); -} - -static castorder * create_castorder_combat(castorder *co, fighter *fig, const spell * sp, int level, double force) { - co = create_castorder(co, fig->unit, 0, sp, fig->unit->region, level, force, 0, 0, 0); - co->magician.fig = fig; - return co; -} - -static void summon_igjarjuk(battle *b, spellrank spellranks[]) { - side *s; - castorder *co; - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fig = 0; - if (s->bf->attacker && fval(s->faction, FFL_CURSED)) { - spell *sp = find_spell("igjarjuk"); - if (sp) { - int si; - for (si = 0; s->enemies[si]; ++si) { - side *se = s->enemies[si]; - if (se && !fval(se->faction, FFL_NPC)) { - fighter *fi; - for (fi = se->fighters; fi; fi = fi->next) { - if (fi && (!fig || fig->unit->number > fi->unit->number)) { - fig = fi; - if (fig->unit->number == 1) { - break; - } - } - } - if (fig && fig->unit->number == 1) { - break; - } - } - } - if (fig) { - co = create_castorder_combat(0, fig, sp, 10, 10); - co->magician.fig = fig; - add_castorder(&spellranks[sp->rank], co); - break; - } - } - } - } -} - -void do_combatmagic(battle * b, combatmagic_t was) -{ - side *s; - castorder *co; - region *r = b->region; - int level, rank, sl; - spellrank spellranks[MAX_SPELLRANK]; - - memset(spellranks, 0, sizeof(spellranks)); - - if (rule_igjarjuk_curse && was == DO_PRECOMBATSPELL) { - summon_igjarjuk(b, spellranks); - } - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fig; - for (fig = s->fighters; fig; fig = fig->next) { - unit *mage = fig->unit; - unit *caster = mage; - - if (fig->alive <= 0) - continue; /* fighter kann im Kampf getoetet worden sein */ - - level = effskill(mage, SK_MAGIC, r); - if (level > 0) { - double power; - const spell *sp; - const struct locale *lang = mage->faction->locale; - order *ord; - - switch (was) { - case DO_PRECOMBATSPELL: - sp = get_combatspell(mage, 0); - sl = get_combatspelllevel(mage, 0); - break; - case DO_POSTCOMBATSPELL: - sp = get_combatspell(mage, 2); - sl = get_combatspelllevel(mage, 2); - break; - default: - /* Fehler! */ - return; - } - if (sp == NULL) - continue; - - ord = create_order(K_CAST, lang, "'%s'", spell_name(sp, lang)); - if (!cancast(mage, sp, 1, 1, ord)) { - free_order(ord); - continue; - } - - level = eff_spelllevel(mage, caster, sp, level, 1); - if (sl > 0 && sl < level) { - level = sl; - } - if (level < 0) { - report_failed_spell(b, mage, sp); - free_order(ord); - continue; - } - - power = spellpower(r, mage, sp, level, ord); - free_order(ord); - if (power <= 0) { /* Effekt von Antimagie */ - report_failed_spell(b, mage, sp); - pay_spell(mage, NULL, sp, level, 1); - } - else if (fumble(r, mage, sp, level)) { - report_failed_spell(b, mage, sp); - pay_spell(mage, NULL, sp, level, 1); - } - else { - co = create_castorder_combat(0, fig, sp, level, power); - add_castorder(&spellranks[sp->rank], co); - } - } - } - } - for (rank = 0; rank < MAX_SPELLRANK; rank++) { - for (co = spellranks[rank].begin; co; co = co->next) { - fighter *fig = co->magician.fig; - const spell *sp = co->sp; - - level = cast_spell(co); - if (level > 0) { - pay_spell(fig->unit, NULL, sp, level, 1); - } - } - } - for (rank = 0; rank < MAX_SPELLRANK; rank++) { - free_castorders(spellranks[rank].begin); - } -} - -static int cast_combatspell(troop at, const spell * sp, int level, double force) -{ - castorder co; - - create_castorder_combat(&co, at.fighter, sp, level, force); - level = cast_spell(&co); - free_castorder(&co); - if (level > 0) { - pay_spell(at.fighter->unit, NULL, sp, level, 1); - } - return level; -} - -static void do_combatspell(troop at) -{ - const spell *sp; - fighter *fi = at.fighter; - unit *mage = fi->unit; - battle *b = fi->side->battle; - region *r = b->region; - selist *ql; - int level, qi; - double power; - int fumblechance = 0; - order *ord; - int sl; - const struct locale *lang = mage->faction->locale; - - sp = get_combatspell(mage, 1); - if (sp == NULL) { - fi->magic = 0; /* Hat keinen Kampfzauber, kaempft nichtmagisch weiter */ - return; - } - ord = create_order(K_CAST, lang, "'%s'", spell_name(sp, lang)); - if (!cancast(mage, sp, 1, 1, ord)) { - fi->magic = 0; /* Kann nicht mehr Zaubern, kaempft nichtmagisch weiter */ - return; - } - - level = eff_spelllevel(mage, mage, sp, fi->magic, 1); - sl = get_combatspelllevel(mage, 1); - if (sl > 0 && sl < level) { - level = sl; - } - - if (fumble(r, mage, sp, level)) { - report_failed_spell(b, mage, sp); - pay_spell(mage, NULL, sp, level, 1); - return; - } - - for (qi = 0, ql = b->meffects; ql; selist_advance(&ql, &qi, 1)) { - meffect *mblock = (meffect *)selist_get(ql, qi); - if (mblock->typ == SHIELD_BLOCK) { - if (meffect_blocked(b, mblock, fi->side) != 0) { - fumblechance += mblock->duration; - mblock->duration -= mblock->effect; - } - } - } - - /* Antimagie die Fehlschlag erhoeht */ - if (rng_int() % 100 < fumblechance) { - report_failed_spell(b, mage, sp); - pay_spell(mage, NULL, sp, level, 1); - free_order(ord); - return; - } - power = spellpower(r, mage, sp, level, ord); - free_order(ord); - if (power <= 0) { /* Effekt von Antimagie */ - report_failed_spell(b, mage, sp); - pay_spell(mage, NULL, sp, level, 1); - return; - } - - level = cast_combatspell(at, sp, level, power); -} - -/* Sonderattacken: Monster patzern nicht und zahlen auch keine - * Spruchkosten. Da die Spruchstaerke direkt durch den Level bestimmt - * wird, wirkt auch keine Antimagie (wird sonst in spellpower - * gemacht) */ - -static void do_extra_spell(troop at, const att * a) -{ - const spell *sp = spellref_get(a->data.sp); - - if (!sp) { - log_error("no such spell: '%s'", a->data.sp->_name); - } - else { - assert(a->level > 0); - cast_combatspell(at, sp, a->level, a->level); - } -} - -int skilldiff(troop at, troop dt, int dist) -{ - fighter *af = at.fighter, *df = dt.fighter; - unit *au = af->unit, *du = df->unit; - int is_protected = 0, skdiff = 0; - weapon *awp = select_weapon(at, true, dist > 1); - static int rc_cache; - static const race *rc_halfling, *rc_goblin; - - if (rc_changed(&rc_cache)) { - rc_halfling = get_race(RC_HALFLING); - rc_goblin = get_race(RC_GOBLIN); - } - skdiff += af->person[at.index].attack; - skdiff -= df->person[dt.index].defense; - - if (df->person[dt.index].flags & FL_SLEEPING) - skdiff += 2; - - /* Effekte durch Rassen */ - if (awp != NULL && u_race(au) == rc_halfling && dragonrace(u_race(du))) { - skdiff += 5; - } - else if (u_race(au) == rc_goblin) { - if (af->side->size[SUM_ROW] >= df->side->size[SUM_ROW] * rule_goblin_bonus) { - skdiff += 1; - } - } - - if (df->building) { - building *b = df->building; - if (b->attribs) { - curse *c = get_curse(b->attribs, &ct_strongwall); - if (curse_active(c)) { - /* wirkt auf alle Gebaeude */ - skdiff -= curse_geteffect_int(c); - is_protected = 2; - } - } - if (b->type->flags & BTF_FORTIFICATION) { - int stage = buildingeffsize(b, false); - int beff = building_protection(b->type, stage); - if (beff > 0) { - skdiff -= beff; - is_protected = 2; - if (b->attribs) { - if (curse_active(get_curse(b->attribs, &ct_magicwalls))) { - /* Verdoppelt Burgenbonus */ - skdiff -= beff; - } - } - } - } - } - /* Effekte der Waffen */ - skdiff += weapon_effskill(at, dt, awp, true, dist > 1); - if (awp && fval(awp->type, WTF_MISSILE)) { - skdiff -= is_protected; - if (awp->type->modifiers) { - int w; - for (w = 0; awp->type->modifiers[w].value != 0; ++w) { - if (awp->type->modifiers[w].flags & WMF_MISSILE_TARGET) { - /* skill decreases by targeting difficulty (bow -2, catapult -4) */ - skdiff -= awp->type->modifiers[w].value; - break; - } - } - } - } - if (skill_formula == FORMULA_ORIG) { - weapon *dwp = select_weapon(dt, false, dist > 1); - skdiff -= weapon_effskill(dt, at, dwp, false, dist > 1); - } - return skdiff; -} - -static int setreload(troop at) -{ - fighter *af = at.fighter; - const weapon_type *wtype = af->person[at.index].missile->type; - if (wtype->reload == 0) - return 0; - return af->person[at.index].reload = wtype->reload; -} - -int getreload(troop at) -{ - return at.fighter->person[at.index].reload; -} - -int hits(troop at, troop dt, weapon * awp) -{ - fighter *af = at.fighter, *df = dt.fighter; - const armor_type *armor, *shield = 0; - int skdiff = 0; - int dist = get_unitrow(af, df->side) + get_unitrow(df, af->side) - 1; - weapon *dwp = select_weapon(dt, false, dist > 1); - - if (!df->alive) - return 0; - if (getreload(at)) - return 0; - if (dist > 1 && (awp == NULL || !fval(awp->type, WTF_MISSILE))) - return 0; - - /* mark this person as hit. */ - df->person[dt.index].flags |= FL_HIT; - - if (af->person[at.index].flags & FL_STUNNED) { - af->person[at.index].flags &= ~FL_STUNNED; - return 0; - } - if ((af->person[at.index].flags & FL_TIRED && rng_int() % 100 < 50) - || (af->person[at.index].flags & FL_SLEEPING)) - return 0; - - /* effect of sp_reeling_arrows combatspell */ - if (af->side->battle->reelarrow && awp && fval(awp->type, WTF_MISSILE) - && rng_double() < 0.5) { - return 0; - } - - skdiff = skilldiff(at, dt, dist); - /* Verteidiger bekommt eine Ruestung */ - armor = select_armor(dt, true); - if (dwp == NULL || (dwp->type->flags & WTF_USESHIELD)) { - shield = select_armor(dt, false); - } - if (contest(skdiff, dt, armor, shield)) { - return 1; - } - return 0; -} - -void dazzle(battle * b, troop * td) -{ - UNUSED_ARG(b); - /* Nicht kumulativ ! */ - if (td->fighter->person[td->index].flags & (FL_COURAGE|FL_DAZZLED)) { - return; - } - - td->fighter->person[td->index].flags |= FL_DAZZLED; - td->fighter->person[td->index].defense--; -} - -void damage_building(battle * b, building * bldg, int damage_abs) -{ - assert(bldg); - bldg->size -= damage_abs; - if (bldg->size < 1) bldg->size = 1; - - /* Wenn Burg, dann gucken, ob die Leute alle noch in das Gebaeude passen. */ - - if (bldg->type->flags & BTF_FORTIFICATION) { + selist *select_fighters(battle * b, const side * vs, int mask, select_fun cb, void *cbdata) + { side *s; + selist *fightervp = 0; - bldg->sizeleft = bldg->size; + assert(vs != NULL); for (s = b->sides; s != b->sides + b->nsides; ++s) { fighter *fig; - for (fig = s->fighters; fig; fig = fig->next) { - if (fig->building == bldg) { - if (bldg->sizeleft >= fig->unit->number) { - fig->building = bldg; - bldg->sizeleft -= fig->unit->number; - } - else { - fig->building = NULL; - } - } + + if (mask == FS_ENEMY) { + if (!enemy(s, vs)) + continue; } - } - } -} - -static int attacks_per_round(troop t) -{ - return t.fighter->person[t.index].speed; -} - -static void make_heroes(battle * b) -{ - side *s; - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fig; - for (fig = s->fighters; fig; fig = fig->next) { - unit *u = fig->unit; - if (fval(u, UFL_HERO)) { - int i; - if (!playerrace(u_race(u))) { - log_error("Hero %s is a %s.\n", unitname(u), u_race(u)->_name); + else if (mask == FS_HELP) { + if (enemy(s, vs) || !alliedside(s, vs->faction, HELP_FIGHT)) { + continue; } - for (i = 0; i != u->number; ++i) { - fig->person[i].speed += (rule_hero_speed - 1); - } - } - } - } -} - -static void attack(battle * b, troop ta, const att * a, int numattack) -{ - fighter *af = ta.fighter; - troop td; - unit *au = af->unit; - - switch (a->type) { - case AT_COMBATSPELL: - /* Magier versuchen immer erstmal zu zaubern, erst wenn das - * fehlschlaegt, wird af->magic == 0 und der Magier kaempft - * konventionell weiter */ - if (numattack == 0 && af->magic > 0) { - /* wenn der magier in die potenzielle Reichweite von Attacken des - * Feindes kommt, beginnt er auch bei einem Status von KAEMPFE NICHT, - * Kampfzauber zu schleudern: */ - if (count_enemies(b, af, melee_range[0], missile_range[1], - SELECT_ADVANCE | SELECT_DISTANCE | SELECT_FIND)) { - do_combatspell(ta); - } - } - break; - case AT_STANDARD: /* Waffen, mag. Gegenstaende, Kampfzauber */ - if (numattack > 0 || af->magic <= 0) { - weapon *wp = ta.fighter->person[ta.index].missile; - int melee = - count_enemies(b, af, melee_range[0], melee_range[1], - SELECT_ADVANCE | SELECT_DISTANCE | SELECT_FIND); - if (melee) - wp = preferred_weapon(ta, true); - /* Sonderbehandlungen */ - - if (getreload(ta)) { - ta.fighter->person[ta.index].reload--; } else { - bool standard_attack = true; - bool reload = false; - /* spezialattacken der waffe nur, wenn erste attacke in der runde. - * sonst helden mit feuerschwertern zu maechtig */ - if (numattack == 0 && wp && wp->type->attack) { - int dead = 0; - standard_attack = wp->type->attack(&ta, wp->type, &dead); - if (!standard_attack) - reload = true; - af->catmsg += dead; - if (!standard_attack && af->person[ta.index].last_action < b->turn) { - af->person[ta.index].last_action = b->turn; - } - } - if (standard_attack) { - bool missile = false; - if (wp && fval(wp->type, WTF_MISSILE)) - missile = true; - if (missile) { - td = select_opponent(b, ta, missile_range[0], missile_range[1]); - } - else { - td = select_opponent(b, ta, melee_range[0], melee_range[1]); - } - if (!td.fighter) - return; - if (ta.fighter->person[ta.index].last_action < b->turn) { - ta.fighter->person[ta.index].last_action = b->turn; - } - reload = true; - if (hits(ta, td, wp)) { - const char *d; - if (wp == NULL) - d = u_race(au)->def_damage; - else if (is_riding(ta)) - d = wp->type->damage[1]; - else - d = wp->type->damage[0]; - terminate(td, ta, a->type, d, missile); - } - } - if (reload && wp && wp->type->reload && !getreload(ta)) { - setreload(ta); + assert(mask == (FS_HELP | FS_ENEMY) || !"invalid alliance state"); + } + for (fig = s->fighters; fig; fig = fig->next) { + if (cb(vs, fig, cbdata)) { + selist_push(&fightervp, fig); } } } - break; - case AT_SPELL: /* Extra-Sprueche. Kampfzauber in AT_COMBATSPELL! */ - do_extra_spell(ta, a); - break; - case AT_NATURAL: - td = select_opponent(b, ta, melee_range[0], melee_range[1]); - if (!td.fighter) - return; - if (ta.fighter->person[ta.index].last_action < b->turn) { - ta.fighter->person[ta.index].last_action = b->turn; - } - if (hits(ta, td, NULL)) { - terminate(td, ta, a->type, a->data.dice, false); - } - break; - case AT_DRAIN_ST: - td = select_opponent(b, ta, melee_range[0], melee_range[1]); - if (!td.fighter) - return; - if (ta.fighter->person[ta.index].last_action < b->turn) { - ta.fighter->person[ta.index].last_action = b->turn; - } - if (hits(ta, td, NULL)) { - int c = dice_rand(a->data.dice); - while (c > 0) { - if (rng_int() % 2) { - td.fighter->person[td.index].attack -= 1; - } - else { - td.fighter->person[td.index].defense -= 1; - } - c--; - } - } - break; - case AT_DRAIN_EXP: - td = select_opponent(b, ta, melee_range[0], melee_range[1]); - if (!td.fighter) - return; - if (ta.fighter->person[ta.index].last_action < b->turn) { - ta.fighter->person[ta.index].last_action = b->turn; - } - if (hits(ta, td, NULL)) { - drain_exp(td.fighter->unit, dice_rand(a->data.dice)); - } - break; - case AT_DAZZLE: - td = select_opponent(b, ta, melee_range[0], melee_range[1]); - if (!td.fighter) - return; - if (ta.fighter->person[ta.index].last_action < b->turn) { - ta.fighter->person[ta.index].last_action = b->turn; - } - if (hits(ta, td, NULL)) { - dazzle(b, &td); - } - break; - case AT_STRUCTURAL: - td = select_opponent(b, ta, melee_range[0], melee_range[1]); - if (!td.fighter) - return; - if (ta.fighter->person[ta.index].last_action < b->turn) { - ta.fighter->person[ta.index].last_action = b->turn; - } - if (td.fighter->unit->ship) { - int dice = dice_rand(a->data.dice); - ship * sh = td.fighter->unit->ship; - damage_ship(sh, dice / sh->type->damage / sh->size); - } - else if (td.fighter->unit->building) { - damage_building(b, td.fighter->unit->building, dice_rand(a->data.dice)); - } + + return fightervp; } -} -void do_attack(fighter * af) -{ - troop ta; - unit *au = af->unit; - side *side = af->side; - battle *b = side->battle; + struct selector { + int minrow; + int maxrow; + }; - ta.fighter = af; - - assert(au && au->number); - /* Da das Zuschlagen auf Einheiten und nicht auf den einzelnen - * Kaempfern beruht, darf die Reihenfolge und Groesse der Einheit keine - * Rolle spielen, Das tut sie nur dann, wenn jeder, der am Anfang der - * Runde lebte, auch zuschlagen darf. Ansonsten ist der, der zufaellig - * mit einer grossen Einheit zuerst drankommt, extrem bevorteilt. */ - ta.index = af->fighting; - - while (ta.index--) { - /* Wir suchen eine beliebige Feind-Einheit aus. An der koennen - * wir feststellen, ob noch jemand da ist. */ - int apr, attacks = attacks_per_round(ta); - if (!count_enemies(b, af, FIGHT_ROW, LAST_ROW, SELECT_FIND)) - break; - - for (apr = 0; apr != attacks; ++apr) { - int a; - for (a = 0; a < RACE_ATTACKS && u_race(au)->attack[a].type != AT_NONE; ++a) { - if (apr > 0) { - /* Wenn die Waffe nachladen muss, oder es sich nicht um einen - * Waffen-Angriff handelt, dann gilt der Speed nicht. */ - /* TODO: allow multiple AT_NATURAL attacks? */ - if (u_race(au)->attack[a].type != AT_STANDARD) - continue; - else { - weapon *wp = preferred_weapon(ta, true); - if (wp != NULL && wp->type->reload) - continue; - } - } - attack(b, ta, &(u_race(au)->attack[a]), apr); - } - } + static bool select_row(const side *vs, const fighter *fig, void *cbdata) + { + struct selector *sel = (struct selector *)cbdata; + int row = get_unitrow(fig, vs); + return (row >= sel->minrow && row <= sel->maxrow); } - /* Der letzte Katapultschuetze setzt die - * Ladezeit neu und generiert die Meldung. */ - if (af->catmsg >= 0) { - struct message *m = - msg_message("killed_battle", "unit dead", au, af->catmsg); + + selist *fighters(battle * b, const side * vs, int minrow, int maxrow, int mask) + { + struct selector sel; + sel.maxrow = maxrow; + sel.minrow = minrow; + return select_fighters(b, vs, mask, select_row, &sel); + } + + static void report_failed_spell(struct battle * b, struct unit * mage, const struct spell *sp) + { + message *m = msg_message("spell_failed", "unit spell", mage, sp); message_all(b, m); msg_release(m); - af->catmsg = -1; - } -} - -static void add_tactics(tactics * ta, fighter * fig, int value) -{ - if (value == 0 || value < ta->value) - return; - if (value > ta->value) { - selist_free(ta->fighters); - ta->fighters = 0; - } - selist_push(&ta->fighters, fig); - selist_push(&fig->side->battle->leaders, fig); - ta->value = value; -} - -static int horse_fleeing_bonus(const unit * u) -{ - const item_type *it_horse, *it_elvenhorse, *it_charger; - int n1 = 0, n2 = 0, n3 = 0; - item *itm; - int skl = effskill(u, SK_RIDING, NULL); - const resource_type *rtype; - - it_horse = ((rtype = get_resourcetype(R_HORSE)) != NULL) ? rtype->itype : 0; - it_elvenhorse = ((rtype = get_resourcetype(R_UNICORN)) != NULL) ? rtype->itype : 0; - it_charger = ((rtype = get_resourcetype(R_CHARGER)) != NULL) ? rtype->itype : 0; - - for (itm = u->items; itm; itm = itm->next) { - if (itm->type->flags & ITF_ANIMAL) { - if (itm->type == it_elvenhorse) - n3 += itm->number; - else if (itm->type == it_charger) - n2 += itm->number; - else if (itm->type == it_horse) - n1 += itm->number; - } - } - if (skl >= 5 && n3 >= u->number) - return 30; - if (skl >= 2 && n2 + n3 >= u->number) - return 20; - if (n1 + n2 + n3 >= u->number) - return 10; - return 0; -} - -static int fleechance(unit * u) -{ - int p = flee_chance_base; /* Fluchtwahrscheinlichkeit in % */ - /* Einheit u versucht, dem Getuemmel zu entkommen */ - - p += (effskill(u, SK_STEALTH, NULL) * flee_chance_skill_bonus); - p += horse_fleeing_bonus(u); - - if (u_race(u) == get_race(RC_HALFLING)) { - p += flee_chance_base; - if (p > flee_chance_max_percent) { - p = flee_chance_max_percent; - } - } - return p; -} - -/** add a new army to the conflict. - * beware: armies need to be added _at the beginning_ of the list because - * otherwise join_allies() will get into trouble */ -side *make_side(battle * b, const faction * f, const group * g, - unsigned int flags, const faction * stealthfaction) -{ - side *s1 = b->sides + b->nsides; - bfaction *bf; - - if (fval(b->region->terrain, SEA_REGION)) { - /* every fight in an ocean is short */ - flags |= SIDE_HASGUARDS; - } - else { - unit *u; - for (u = b->region->units; u; u = u->next) { - if (is_guard(u)) { - if (alliedunit(u, f, HELP_GUARD)) { - flags |= SIDE_HASGUARDS; - break; - } - } - } } - s1->battle = b; - s1->group = g; - s1->flags = flags; - s1->stealthfaction = stealthfaction; - for (bf = b->factions; bf; bf = bf->next) { - faction *f2 = bf->faction; - - if (f2 == f) { - s1->bf = bf; - s1->faction = f2; - s1->index = b->nsides++; - s1->nextF = bf->sides; - bf->sides = s1; - assert(b->nsides <= MAXSIDES); - break; - } + static castorder * create_castorder_combat(castorder *co, fighter *fig, const spell * sp, int level, double force) { + co = create_castorder(co, fig->unit, 0, sp, fig->unit->region, level, force, 0, 0, 0); + co->magician.fig = fig; + return co; } - assert(bf); - return s1; -} -troop select_ally(fighter * af, int minrow, int maxrow, int allytype) -{ - side *as = af->side; - battle *b = as->battle; - side *ds; - int allies = count_allies(as, minrow, maxrow, SELECT_ADVANCE, allytype); + static void summon_igjarjuk(battle *b, spellrank spellranks[]) { + side *s; + castorder *co; - if (!allies) { - return no_troop; - } - allies = (int)(rng_int() % allies); - - for (ds = b->sides; ds != b->sides + b->nsides; ++ds) { - if ((allytype == ALLY_ANY && helping(as, ds)) || (allytype == ALLY_SELF - && as->faction == ds->faction)) { - fighter *df; - for (df = ds->fighters; df; df = df->next) { - int dr = get_unitrow(df, NULL); - if (dr >= minrow && dr <= maxrow) { - if (df->alive - df->removed > allies) { - troop dt; - assert(allies >= 0); - dt.index = allies; - dt.fighter = df; - return dt; - } - allies -= df->alive; - } - } - } - } - assert(!"we should never have gotten here"); - return no_troop; -} - -static int loot_quota(const unit * src, const unit * dst, - const item_type * type, int n) -{ - UNUSED_ARG(type); - if (dst && src && src->faction != dst->faction) { - double divisor = config_get_flt("rules.items.loot_divisor", 1); - assert(divisor <= 0 || divisor >= 1); - if (divisor >= 1) { - double r = n / divisor; - int x = (int)r; - - r = r - x; - if (chance(r)) - ++x; - - return x; - } - } - return n; -} - -static void loot_items(fighter * corpse) -{ - unit *u = corpse->unit; - item *itm = u->items; - battle *b = corpse->side->battle; - int dead = dead_fighters(corpse); - - if (dead <= 0) - return; - - while (itm) { - float lootfactor = (float)dead / (float)u->number; /* only loot the dead! */ - int maxloot = (int)((float)itm->number * lootfactor); - if (maxloot > 0) { - int i = (maxloot > 10) ? 10 : maxloot; - for (; i != 0; --i) { - int loot = maxloot / i; - - if (loot > 0) { - fighter *fig = NULL; - int looting = 0; - int maxrow = 0; - /* mustloot: we absolutely, positively must have somebody loot this thing */ - int mustloot = itm->type->flags & (ITF_CURSED | ITF_NOTLOST); - - itm->number -= loot; - maxloot -= loot; - - if (is_monsters(u->faction) && (rule_loot & LOOT_MONSTERS)) { - looting = 1; - } - else if (rule_loot & LOOT_OTHERS) { - looting = 1; - } - else if (rule_loot & LOOT_SELF) { - looting = 2; - } - if (looting) { - if (mustloot) { - maxrow = LAST_ROW; - } - else if (rule_loot & LOOT_KEEPLOOT) { - int lootchance = 50 + b->keeploot; - if (rng_int() % 100 < lootchance) { - maxrow = BEHIND_ROW; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig = 0; + if (s->bf->attacker && fval(s->faction, FFL_CURSED)) { + spell *sp = find_spell("igjarjuk"); + if (sp) { + int si; + for (si = 0; s->enemies[si]; ++si) { + side *se = s->enemies[si]; + if (se && !fval(se->faction, FFL_NPC)) { + fighter *fi; + for (fi = se->fighters; fi; fi = fi->next) { + if (fi && (!fig || fig->unit->number > fi->unit->number)) { + fig = fi; + if (fig->unit->number == 1) { + break; + } + } + } + if (fig && fig->unit->number == 1) { + break; } } - else { - maxrow = LAST_ROW; - } } - if (maxrow > 0) { - if (looting == 1) { - /* enemies get dibs */ - fig = select_enemy(corpse, FIGHT_ROW, maxrow, 0).fighter; - } - if (!fig) { - /* self and allies get second pick */ - fig = select_ally(corpse, FIGHT_ROW, LAST_ROW, ALLY_SELF).fighter; - } - } - if (fig) { - int trueloot = - mustloot ? loot : loot_quota(corpse->unit, fig->unit, itm->type, - loot); - if (trueloot > 0) { - i_change(&fig->loot, itm->type, trueloot); - } - } - } - } - } - itm = itm->next; - } -} - -bool seematrix(const faction * f, const side * s) -{ - if (f == s->faction) - return true; - if (s->flags & SIDE_STEALTH) - return false; - return true; -} - -static double PopulationDamage(void) -{ - return rule_population_damage / 100.0; -} - -static void battle_effects(battle * b, int dead_players) -{ - region *r = b->region; - int rp = rpeasants(r); - - if (rp > 0) { - int dead_peasants = (int)(dead_players * PopulationDamage()); - if (dead_peasants > rp) { - dead_peasants = rp; - } - if (dead_peasants) { - deathcounts(r, dead_peasants + dead_players); - rsetpeasants(r, rp - dead_peasants); - } - } -} - -static void reorder_fleeing(region * r) -{ - unit **usrc = &r->units; - unit **udst = &r->units; - unit *ufirst = NULL; - unit *u; - - for (; *udst; udst = &u->next) { - u = *udst; - } - - for (u = *usrc; u != ufirst; u = *usrc) { - if (u->next && fval(u, UFL_FLEEING)) { - *usrc = u->next; - *udst = u; - udst = &u->next; - if (!ufirst) - ufirst = u; - } - else { - usrc = &u->next; - } - } - *udst = NULL; -} - -static void aftermath(battle * b) -{ - region *r = b->region; - side *s; - int dead_players = 0; - bfaction *bf; - bool ships_damaged = (b->turn + (b->has_tactics_turn ? 1 : 0) > 2); /* only used for ship damage! */ - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *df; - s->dead = 0; - - for (df = s->fighters; df; df = df->next) { - unit *du = df->unit; - int dead = dead_fighters(df); - - /* tote insgesamt: */ - s->dead += dead; - /* Tote, die wiederbelebt werde koennen: */ - if (playerrace(u_race(df->unit))) { - s->casualties += dead; - } - if (df->hits + df->kills) { - struct message *m = - msg_message("killsandhits", "unit hits kills", du, df->hits, - df->kills); - battle_message_faction(b, du->faction, m); - msg_release(m); - } - } - } - - /* POSTCOMBAT */ - do_combatmagic(b, DO_POSTCOMBATSPELL); - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - int snumber = 0; - fighter *df; - bool relevant = false; /* Kampf relevant fuer diese Partei? */ - if (!fval(s, SIDE_HASGUARDS)) { - relevant = true; - } - s->flee = 0; - - for (df = s->fighters; df; df = df->next) { - unit *du = df->unit; - int dead = dead_fighters(df); - int sum_hp = 0; - int n; - int flags = 0; - - for (n = 0; n != df->alive; ++n) { - if (df->person[n].hp > 0) { - sum_hp += df->person[n].hp; - } - } - snumber += du->number; - if (dead == df->unit->number) { - flags = UFL_DEAD; - } - else if (relevant) { - flags = UFL_LONGACTION; - if ((du->status != ST_FLEE) && (df->run.hp <= 0)) { - flags |= UFL_NOTMOVING; - } - } - if (flags) { - fset(du, flags); - } - if (df->alive && df->alive == du->number) { - du->hp = sum_hp; - continue; /* nichts passiert */ - } - else if (df->run.hp) { - if (df->alive == 0) { - /* Report the casualties */ - reportcasualties(b, df, dead); - - /* Zuerst duerfen die Feinde pluendern, die mitgenommenen Items - * stehen in fig->run.items. Dann werden die Fliehenden auf - * die leere (tote) alte Einheit gemapt */ - if (!fval(df, FIG_NOLOOT)) { - loot_items(df); - } - scale_number(du, df->run.number); - du->hp = df->run.hp; - setguard(du, false); - /* must leave ships or buildings, or a stealthy hobbit - * can hold castles indefinitely */ - if (!fval(r->terrain, SEA_REGION)) { - leave(du, true); /* even region owners have to flee */ - } - fset(du, UFL_FLEEING); - } - else { - /* nur teilweise geflohene Einheiten mergen sich wieder */ - df->alive += df->run.number; - s->size[0] += df->run.number; - s->size[statusrow(df->status)] += df->run.number; - s->alive += df->run.number; - sum_hp += df->run.hp; - df->run.number = 0; - df->run.hp = 0; - /* df->run.region = NULL; */ - - reportcasualties(b, df, dead); - - scale_number(du, df->alive); - du->hp = sum_hp; - } - } - else { - if (df->alive == 0) { - /* alle sind tot, niemand geflohen. Einheit aufloesen */ - df->run.number = 0; - df->run.hp = 0; - - /* Report the casualties */ - reportcasualties(b, df, dead); - - /* Distribute Loot */ - loot_items(df); - - setguard(du, false); - scale_number(du, 0); - } - else { - df->run.number = 0; - df->run.hp = 0; - - reportcasualties(b, df, dead); - - scale_number(du, df->alive); - du->hp = sum_hp; - } - } - s->flee += df->run.number; - - if (playerrace(u_race(du))) { - /* tote im kampf werden zu regionsuntoten: - * for each of them, a peasant will die as well */ - dead_players += dead; - } - if (du->hp < du->number) { - log_error("%s has less hitpoints (%u) than people (%u)\n", itoa36(du->no), du->hp, du->number); - du->hp = du->number; - } - } - s->alive += s->healed; - assert(snumber == s->flee + s->alive + s->dead); - } - - battle_effects(b, dead_players); - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - message *seen = msg_message("army_report", - "index abbrev dead fled survived", - army_index(s), sideabkz(s, false), s->dead, s->flee, s->alive); - message *unseen = msg_message("army_report", - "index abbrev dead fled survived", - army_index(s), "-?-", s->dead, s->flee, s->alive); - - for (bf = b->factions; bf; bf = bf->next) { - faction *f = bf->faction; - message *m = seematrix(f, s) ? seen : unseen; - - battle_message_faction(b, f, m); - } - - msg_release(seen); - msg_release(unseen); - } - - /* Wir benutzen drifted, um uns zu merken, ob ein Schiff - * schonmal Schaden genommen hat. (moved und drifted - * sollten in flags ueberfuehrt werden */ - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *df; - - for (df = s->fighters; df; df = df->next) { - unit *du = df->unit; - item *l; - - /* Beute verteilen */ - for (l = df->loot; l; l = l->next) { - const item_type *itype = l->type; - message *m = - msg_message("battle_loot", "unit amount item", du, l->number, - itype->rtype); - battle_message_faction(b, du->faction, m); - msg_release(m); - i_change(&du->items, itype, l->number); - } - - /* Wenn sich die Einheit auf einem Schiff befindet, wird - * dieses Schiff beschaedigt. Andernfalls ein Schiff, welches - * evt. zuvor verlassen wurde. */ - if (ships_damaged) { - ship *sh; - if (du->ship) - sh = du->ship; - else - sh = leftship(du); - - if (sh && fval(sh, SF_DAMAGED)) { - int n = b->turn - 2; - if (n > 0) { - double dmg = - config_get_flt("rules.ship.damage.battleround", - 0.05F); - damage_ship(sh, dmg * n); - freset(sh, SF_DAMAGED); + co = create_castorder_combat(0, fig, sp, 10, 10); + co->magician.fig = fig; + add_castorder(&spellranks[sp->rank], co); + break; } } } } } - if (ships_damaged) { - ship **sp = &r->ships; + void do_combatmagic(battle * b, combatmagic_t was) + { + side *s; + castorder *co; + region *r = b->region; + int level, rank, sl; + spellrank spellranks[MAX_SPELLRANK]; - while (*sp) { - ship *sh = *sp; - freset(sh, SF_DAMAGED); - if (sh->damage >= sh->size * DAMAGE_SCALE) { - sink_ship(sh); - remove_ship(sp, sh); - } - else { - sp = &sh->next; - } + memset(spellranks, 0, sizeof(spellranks)); + + if (rule_igjarjuk_curse && was == DO_PRECOMBATSPELL) { + summon_igjarjuk(b, spellranks); } - } - - reorder_fleeing(r); -} - -static void battle_punit(unit * u, battle * b) -{ - bfaction *bf; - - for (bf = b->factions; bf; bf = bf->next) { - faction *f = bf->faction; - strlist *S = 0, *x; - - spunit(&S, f, u, 4, seen_battle); - for (x = S; x; x = x->next) { - fbattlerecord(b, f, x->s); - } - if (S) - freestrlist(S); - } -} - -static void print_fighters(battle * b, const side * s) -{ - fighter *df; - int row; - - for (row = 1; row != NUMROWS; ++row) { - message *m = NULL; - - for (df = s->fighters; df; df = df->next) { - unit *du = df->unit; - int thisrow = statusrow(df->unit->status); - - if (row == thisrow) { - if (m == NULL) { - m = msg_message("battle_row", "row", row); - message_all(b, m); - } - battle_punit(du, b); - } - } - if (m != NULL) - msg_release(m); - } -} - -bool is_attacker(const fighter * fig) -{ - return fval(fig, FIG_ATTACKER) != 0; -} - -static void set_attacker(fighter * fig) -{ - fset(fig, FIG_ATTACKER); -} - -static void print_stats(battle * b) -{ - side *s2; - side *s; - for (s = b->sides; s != b->sides + b->nsides; ++s) { - bfaction *bf; - - for (bf = b->factions; bf; bf = bf->next) { - faction *f = bf->faction; - const char *loc_army = LOC(f->locale, "battle_army"); - char *bufp; - const char *header; - size_t rsize, size; - int komma; - const char *sname = - seematrix(f, s) ? sidename(s) : LOC(f->locale, "unknown_faction"); - message *msg; - char buf[1024]; - - msg = msg_message("para_army_index", "index name", army_index(s), sname); - battle_message_faction(b, f, msg); - msg_release(msg); - - bufp = buf; - size = sizeof(buf); - komma = 0; - header = LOC(f->locale, "battle_opponents"); - - for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { - if (enemy(s2, s)) { - const char *abbrev = seematrix(f, s2) ? sideabkz(s2, false) : "-?-"; - rsize = slprintf(bufp, size, "%s %s %d(%s)", - komma++ ? "," : (const char *)header, loc_army, army_index(s2), - abbrev); - if (rsize > size) - rsize = size - 1; - size -= rsize; - bufp += rsize; - } - } - if (komma) - fbattlerecord(b, f, buf); - - bufp = buf; - size = sizeof(buf); - komma = 0; - header = LOC(f->locale, "battle_helpers"); - - for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { - if (friendly(s2, s)) { - const char *abbrev = seematrix(f, s2) ? sideabkz(s2, false) : "-?-"; - rsize = slprintf(bufp, size, "%s %s %d(%s)", - komma++ ? "," : (const char *)header, loc_army, army_index(s2), - abbrev); - if (rsize > size) - rsize = size - 1; - size -= rsize; - bufp += rsize; - } - } - if (komma) - fbattlerecord(b, f, buf); - - bufp = buf; - size = sizeof(buf); - komma = 0; - header = LOC(f->locale, "battle_attack"); - - for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { - if (s->relations[s2->index] & E_ATTACKING) { - const char *abbrev = seematrix(f, s2) ? sideabkz(s2, false) : "-?-"; - rsize = - slprintf(bufp, size, "%s %s %d(%s)", - komma++ ? "," : (const char *)header, loc_army, army_index(s2), - abbrev); - if (rsize > size) - rsize = size - 1; - size -= rsize; - bufp += rsize; - } - } - if (komma) - fbattlerecord(b, f, buf); - } - - print_fighters(b, s); - } - - /* Besten Taktiker ermitteln */ - - b->max_tactics = 0; - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - if (!selist_empty(s->leader.fighters)) { - if (s->leader.value > b->max_tactics) { - b->max_tactics = s->leader.value; - } - } - } - - if (b->max_tactics > 0) { for (s = b->sides; s != b->sides + b->nsides; ++s) { - if (s->leader.value == b->max_tactics) { - selist *ql; - int qi; + fighter *fig; + for (fig = s->fighters; fig; fig = fig->next) { + unit *mage = fig->unit; + unit *caster = mage; - for (qi = 0, ql = s->leader.fighters; ql; selist_advance(&ql, &qi, 1)) { - fighter *tf = (fighter *)selist_get(ql, qi); - unit *u = tf->unit; - message *m = NULL; - if (!is_attacker(tf)) { - m = msg_message("para_tactics_lost", "unit", u); + if (fig->alive <= 0) + continue; /* fighter kann im Kampf getoetet worden sein */ + + level = effskill(mage, SK_MAGIC, r); + if (level > 0) { + double power; + const spell *sp; + const struct locale *lang = mage->faction->locale; + order *ord; + + switch (was) { + case DO_PRECOMBATSPELL: + sp = get_combatspell(mage, 0); + sl = get_combatspelllevel(mage, 0); + break; + case DO_POSTCOMBATSPELL: + sp = get_combatspell(mage, 2); + sl = get_combatspelllevel(mage, 2); + break; + default: + /* Fehler! */ + return; + } + if (sp == NULL) + continue; + + ord = create_order(K_CAST, lang, "'%s'", spell_name(sp, lang)); + if (!cancast(mage, sp, 1, 1, ord)) { + free_order(ord); + continue; + } + + level = eff_spelllevel(mage, caster, sp, level, 1); + if (sl > 0 && sl < level) { + level = sl; + } + if (level < 0) { + report_failed_spell(b, mage, sp); + free_order(ord); + continue; + } + + power = spellpower(r, mage, sp, level, ord); + free_order(ord); + if (power <= 0) { /* Effekt von Antimagie */ + report_failed_spell(b, mage, sp); + pay_spell(mage, NULL, sp, level, 1); + } + else if (fumble(r, mage, sp, level)) { + report_failed_spell(b, mage, sp); + pay_spell(mage, NULL, sp, level, 1); } else { - m = msg_message("para_tactics_won", "unit", u); + co = create_castorder_combat(0, fig, sp, level, power); + add_castorder(&spellranks[sp->rank], co); } - message_all(b, m); - msg_release(m); } } } - } -} + for (rank = 0; rank < MAX_SPELLRANK; rank++) { + for (co = spellranks[rank].begin; co; co = co->next) { + fighter *fig = co->magician.fig; + const spell *sp = co->sp; -static int weapon_weight(const weapon * w, bool missile) -{ - if (missile == !!(fval(w->type, WTF_MISSILE))) { - return w->attackskill + w->defenseskill; - } - return 0; -} - -side * get_side(battle * b, const struct unit * u) -{ - side * s; - for (s = b->sides; s != b->sides + b->nsides; ++s) { - if (s->faction == u->faction) { - fighter * fig; - for (fig = s->fighters; fig; fig = fig->next) { - if (fig->unit == u) { - return s; + level = cast_spell(co); + if (level > 0) { + pay_spell(fig->unit, NULL, sp, level, 1); } } } + for (rank = 0; rank < MAX_SPELLRANK; rank++) { + free_castorders(spellranks[rank].begin); + } } - return 0; -} -side * find_side(battle * b, const faction * f, const group * g, unsigned int flags, const faction * stealthfaction) -{ - side * s; - for (s = b->sides; s != b->sides + b->nsides; ++s) { - if (s->faction == f && s->group == g) { - unsigned int s1flags = flags | SIDE_HASGUARDS; - unsigned int s2flags = s->flags | SIDE_HASGUARDS; - if (rule_anon_battle && s->stealthfaction != stealthfaction) { - continue; - } - if (s1flags == s2flags) { - return s; + static int cast_combatspell(troop at, const spell * sp, int level, double force) + { + castorder co; + + create_castorder_combat(&co, at.fighter, sp, level, force); + level = cast_spell(&co); + free_castorder(&co); + if (level > 0) { + pay_spell(at.fighter->unit, NULL, sp, level, 1); + } + return level; + } + + static void do_combatspell(troop at) + { + const spell *sp; + fighter *fi = at.fighter; + unit *mage = fi->unit; + battle *b = fi->side->battle; + region *r = b->region; + selist *ql; + int level, qi; + double power; + int fumblechance = 0; + order *ord; + int sl; + const struct locale *lang = mage->faction->locale; + + sp = get_combatspell(mage, 1); + if (sp == NULL) { + fi->magic = 0; /* Hat keinen Kampfzauber, kaempft nichtmagisch weiter */ + return; + } + ord = create_order(K_CAST, lang, "'%s'", spell_name(sp, lang)); + if (!cancast(mage, sp, 1, 1, ord)) { + fi->magic = 0; /* Kann nicht mehr Zaubern, kaempft nichtmagisch weiter */ + return; + } + + level = eff_spelllevel(mage, mage, sp, fi->magic, 1); + sl = get_combatspelllevel(mage, 1); + if (sl > 0 && sl < level) { + level = sl; + } + + if (fumble(r, mage, sp, level)) { + report_failed_spell(b, mage, sp); + pay_spell(mage, NULL, sp, level, 1); + return; + } + + for (qi = 0, ql = b->meffects; ql; selist_advance(&ql, &qi, 1)) { + meffect *mblock = (meffect *)selist_get(ql, qi); + if (mblock->typ == SHIELD_BLOCK) { + if (meffect_blocked(b, mblock, fi->side) != 0) { + fumblechance += mblock->duration; + mblock->duration -= mblock->effect; + } } } - } - return 0; -} -fighter *make_fighter(battle * b, unit * u, side * s1, bool attack) -{ -#define WMAX 20 - weapon weapons[WMAX]; - region *r = b->region; - item *itm; - fighter *fig = NULL; - int h, i, tactics = effskill(u, SK_TACTICS, NULL); - int berserk; - int strongmen; - int speeded = 0, speed = 1; - int rest; - const group *g = NULL; - const faction *stealthfaction = get_otherfaction(u); - unsigned int flags = 0; - - assert(u->number); - if (fval(u, UFL_ANON_FACTION) != 0) - flags |= SIDE_STEALTH; - if (!(AllianceAuto() & HELP_FIGHT) && fval(u, UFL_GROUP)) { - g = get_group(u); - } - - /* Illusionen und Zauber kaempfen nicht */ - if (fval(u_race(u), RCF_ILLUSIONARY) || u->number == 0) { - return NULL; - } - if (s1 == NULL) { - s1 = find_side(b, u->faction, g, flags, stealthfaction); - /* aliances are moved out of make_fighter and will be handled later */ - if (!s1) { - s1 = make_side(b, u->faction, g, flags, stealthfaction); + /* Antimagie die Fehlschlag erhoeht */ + if (rng_int() % 100 < fumblechance) { + report_failed_spell(b, mage, sp); + pay_spell(mage, NULL, sp, level, 1); + free_order(ord); + return; } - else if (!stealthfaction) { - s1->stealthfaction = NULL; + power = spellpower(r, mage, sp, level, ord); + free_order(ord); + if (power <= 0) { /* Effekt von Antimagie */ + report_failed_spell(b, mage, sp); + pay_spell(mage, NULL, sp, level, 1); + return; } - /* Zu diesem Zeitpunkt ist attacked noch 0, da die Einheit fuer noch - * keinen Kampf ausgewaehlt wurde (sonst wuerde ein fighter existieren) */ - } - fig = (struct fighter*)calloc(1, sizeof(struct fighter)); - fig->next = s1->fighters; - s1->fighters = fig; - - fig->unit = u; - /* In einer Burg muss man a) nicht Angreifer sein, und b) drin sein, und - * c) noch Platz finden. d) menschanaehnlich sein */ - if (attack) { - set_attacker(fig); + level = cast_combatspell(at, sp, level, power); } - else { - building *bld = u->building; - if (bld && bld->sizeleft >= u->number && playerrace(u_race(u))) { - fig->building = bld; - fig->building->sizeleft -= u->number; + + /* Sonderattacken: Monster patzern nicht und zahlen auch keine + * Spruchkosten. Da die Spruchstaerke direkt durch den Level bestimmt + * wird, wirkt auch keine Antimagie (wird sonst in spellpower + * gemacht) */ + + static void do_extra_spell(troop at, const att * a) + { + const spell *sp = spellref_get(a->data.sp); + + if (!sp) { + log_error("no such spell: '%s'", a->data.sp->_name); } - } - fig->status = u->status; - fig->side = s1; - fig->alive = u->number; - fig->side->alive += u->number; - fig->side->battle->alive += u->number; - fig->catmsg = -1; - - /* Freigeben nicht vergessen! */ - assert(fig->alive > 0); - fig->person = (struct person*)calloc((size_t)fig->alive, sizeof(struct person)); - - h = u->hp / u->number; - assert(h); - rest = u->hp % u->number; - - /* Effekte von Spruechen */ - - if (u->attribs) { - curse *c = get_curse(u->attribs, &ct_speed); - if (c) { - speeded = get_cursedmen(u, c); - speed = curse_geteffect_int(c); + else { + assert(a->level > 0); + cast_combatspell(at, sp, a->level, a->level); } } - /* Effekte von Alchemie */ - berserk = get_effect(u, oldpotiontype[P_BERSERK]); - /* change_effect wird in ageing gemacht */ + int skilldiff(troop at, troop dt, int dist) + { + fighter *af = at.fighter, *df = dt.fighter; + unit *au = af->unit, *du = df->unit; + int is_protected = 0, skdiff = 0; + weapon *awp = select_weapon(at, true, dist > 1); + static int rc_cache; + static const race *rc_halfling, *rc_goblin; - /* Effekte von Artefakten */ - strongmen = trollbelts(u); - if (strongmen > fig->unit->number) strongmen = fig->unit->number; - - /* Hitpoints, Attack- und Defense-Boni fuer alle Personen */ - for (i = 0; i < fig->alive; i++) { - assert(i < fig->unit->number); - fig->person[i].hp = h; - if (i < rest) - fig->person[i].hp++; - - if (i < speeded) - fig->person[i].speed = speed; - else - fig->person[i].speed = 1; - - if (i < berserk) { - fig->person[i].attack++; + if (rc_changed(&rc_cache)) { + rc_halfling = get_race(RC_HALFLING); + rc_goblin = get_race(RC_GOBLIN); } - /* Leute mit Kraftzauber machen +2 Schaden im Nahkampf. */ - if (i < strongmen) { - fig->person[i].damage += 2; - } - } + skdiff += af->person[at.index].attack; + skdiff -= df->person[dt.index].defense; - /* Fuer alle Waffengattungen wird bestimmt, wie viele der Personen mit - * ihr kaempfen koennten, und was ihr Wert darin ist. */ - if (u_race(u)->battle_flags & BF_EQUIPMENT) { - int owp[WMAX]; - int dwp[WMAX]; - int oi = 0, di = 0, w = 0; - for (itm = u->items; itm && w != WMAX; itm = itm->next) { - const weapon_type *wtype = resource2weapon(itm->type->rtype); - if (wtype == NULL || itm->number == 0) - continue; - weapons[w].attackskill = weapon_skill(wtype, u, true); - weapons[w].defenseskill = weapon_skill(wtype, u, false); - if (weapons[w].attackskill >= 0 || weapons[w].defenseskill >= 0) { - weapons[w].type = wtype; - weapons[w].used = 0; - weapons[w].count = itm->number; - ++w; - } - assert(w != WMAX); - } - assert(w >= 0); - fig->weapons = (weapon *)calloc((size_t)(w + 1), sizeof(weapon)); - memcpy(fig->weapons, weapons, (size_t)w * sizeof(weapon)); + if (df->person[dt.index].flags & FL_SLEEPING) + skdiff += 2; - for (i = 0; i != w; ++i) { - int j, o = 0, d = 0; - for (j = 0; j != i; ++j) { - if (weapon_weight(fig->weapons + j, - true) >= weapon_weight(fig->weapons + i, true)) - ++d; - if (weapon_weight(fig->weapons + j, - false) >= weapon_weight(fig->weapons + i, false)) - ++o; - } - for (j = i + 1; j != w; ++j) { - if (weapon_weight(fig->weapons + j, - true) > weapon_weight(fig->weapons + i, true)) - ++d; - if (weapon_weight(fig->weapons + j, - false) > weapon_weight(fig->weapons + i, false)) - ++o; - } - owp[o] = i; - dwp[d] = i; + /* Effekte durch Rassen */ + if (awp != NULL && u_race(au) == rc_halfling && dragonrace(u_race(du))) { + skdiff += 5; } - /* jetzt enthalten owp und dwp eine absteigend schlechter werdende Liste der Waffen - * oi and di are the current index to the sorted owp/dwp arrays - * owp, dwp contain indices to the figther::weapons array */ - - /* hand out melee weapons: */ - for (i = 0; i != fig->alive; ++i) { - int wpless = weapon_skill(NULL, u, true); - while (oi != w - && (fig->weapons[owp[oi]].used == fig->weapons[owp[oi]].count - || fval(fig->weapons[owp[oi]].type, WTF_MISSILE))) { - ++oi; - } - if (oi == w) - break; /* no more weapons available */ - if (weapon_weight(fig->weapons + owp[oi], false) <= wpless) { - continue; /* we fight better with bare hands */ - } - fig->person[i].melee = &fig->weapons[owp[oi]]; - ++fig->weapons[owp[oi]].used; - } - /* hand out missile weapons (from back to front, in case of mixed troops). */ - for (di = 0, i = fig->alive; i-- != 0;) { - while (di != w - && (fig->weapons[dwp[di]].used == fig->weapons[dwp[di]].count - || !fval(fig->weapons[dwp[di]].type, WTF_MISSILE))) { - ++di; - } - if (di == w) - break; /* no more weapons available */ - if (weapon_weight(fig->weapons + dwp[di], true) > 0) { - fig->person[i].missile = &fig->weapons[dwp[di]]; - ++fig->weapons[dwp[di]].used; + else if (u_race(au) == rc_goblin) { + if (af->side->size[SUM_ROW] >= df->side->size[SUM_ROW] * rule_goblin_bonus) { + skdiff += 1; } } - } - s1->size[statusrow(fig->status)] += u->number; - s1->size[SUM_ROW] += u->number; - if (u_race(u)->battle_flags & BF_NOBLOCK) { - s1->nonblockers[statusrow(fig->status)] += u->number; - } - - if (u_race(fig->unit)->flags & RCF_HORSE) { - fig->horses = fig->unit->number; - fig->elvenhorses = 0; - } - else { - const resource_type *rt_horse = 0; - const resource_type *rt_elvenhorse = 0; - rt_elvenhorse = get_resourcetype(R_UNICORN); - rt_horse = get_resourcetype(R_CHARGER); - if (!rt_horse) { - rt_horse = get_resourcetype(R_HORSE); - } - fig->horses = rt_horse ? i_get(u->items, rt_horse->itype) : 0; - fig->elvenhorses = rt_elvenhorse ? i_get(u->items, rt_elvenhorse->itype) : 0; - } - - if (u_race(u)->battle_flags & BF_EQUIPMENT) { - for (itm = u->items; itm; itm = itm->next) { - if (itm->type->rtype->atype) { - if (i_canuse(u, itm->type)) { - struct armor *adata = (struct armor *)malloc(sizeof(armor)), **aptr; - adata->atype = itm->type->rtype->atype; - adata->count = itm->number; - for (aptr = &fig->armors; *aptr; aptr = &(*aptr)->next) { - if (adata->atype->prot > (*aptr)->atype->prot) { - break; + if (df->building) { + building *b = df->building; + if (b->attribs) { + curse *c = get_curse(b->attribs, &ct_strongwall); + if (curse_active(c)) { + /* wirkt auf alle Gebaeude */ + skdiff -= curse_geteffect_int(c); + is_protected = 2; + } + } + if (b->type->flags & BTF_FORTIFICATION) { + int stage = buildingeffsize(b, false); + int beff = building_protection(b->type, stage); + if (beff > 0) { + skdiff -= beff; + is_protected = 2; + if (b->attribs) { + if (curse_active(get_curse(b->attribs, &ct_magicwalls))) { + /* Verdoppelt Burgenbonus */ + skdiff -= beff; } } - adata->next = *aptr; - *aptr = adata; } } } - } - - /* Jetzt muss noch geschaut werden, wo die Einheit die jeweils besten - * Werte hat, das kommt aber erst irgendwo spaeter. Ich entscheide - * waehrend des Kampfes, welche ich nehme, je nach Gegner. Deswegen auch - * keine addierten boni. */ - - /* Zuerst mal die Spezialbehandlung gewisser Sonderfaelle. */ - fig->magic = effskill(u, SK_MAGIC, NULL); - - if (fig->horses) { - if (!fval(r->terrain, CAVALRY_REGION) || r_isforest(r) - || effskill(u, SK_RIDING, NULL) < CavalrySkill() - || u_race(u) == get_race(RC_TROLL) || fval(u, UFL_WERE)) - fig->horses = 0; - } - - if (fig->elvenhorses) { - if (effskill(u, SK_RIDING, NULL) < 5 || u_race(u) == get_race(RC_TROLL) - || fval(u, UFL_WERE)) - fig->elvenhorses = 0; - } - - /* Schauen, wie gut wir in Taktik sind. */ - if (tactics > 0 && u_race(u) == get_race(RC_INSECT)) - tactics -= 1 - (int)log10(fig->side->size[SUM_ROW]); -#ifdef TACTICS_MODIFIER - if (tactics > 0 && statusrow(fig->status) == FIGHT_ROW) - tactics += TACTICS_MODIFIER; - if (tactics > 0 && statusrow(fig->status) > BEHIND_ROW) { - tactics -= TACTICS_MODIFIER; - } -#endif - - if (tactics > 0) { - int bonus = 0; - - for (i = 0; i < fig->alive; i++) { - int p_bonus = 0; - int rnd; - - do { - rnd = (int)(rng_int() % 100); - if (rnd >= 40 && rnd <= 69) - p_bonus += 1; - else if (rnd <= 89) - p_bonus += 2; - else - p_bonus += 3; - } while (rnd >= 97); - if (p_bonus > bonus) p_bonus = bonus; - } - tactics += bonus; - } - - add_tactics(&fig->side->leader, fig, tactics); - ++b->nfighters; - return fig; -} - -int join_battle(battle * b, unit * u, bool attack, fighter ** cp) -{ - side *s; - fighter *fc = NULL; - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fig; - if (s->faction == u->faction) { - for (fig = s->fighters; fig; fig = fig->next) { - if (fig->unit == u) { - fc = fig; - if (attack) { - set_attacker(fig); - } - break; - } - } - } - } - if (!fc) { - *cp = make_fighter(b, u, NULL, attack); - return *cp != NULL; - } - *cp = fc; - return false; -} - -battle *make_battle(region * r) -{ - unit *u; - bfaction *bf; - building * bld; - battle *b = (battle *)calloc(1, sizeof(battle)); - - assert(b); - /* Alle Mann raus aus der Burg! */ - for (bld = r->buildings; bld != NULL; bld = bld->next) - bld->sizeleft = bld->size; - - b->region = r; - b->plane = getplane(r); - /* Finde alle Parteien, die den Kampf beobachten koennen: */ - for (u = r->units; u; u = u->next) { - if (u->number > 0) { - if (!fval(u->faction, FFL_MARK)) { - fset(u->faction, FFL_MARK); - for (bf = b->factions; bf; bf = bf->next) { - if (bf->faction == u->faction) + /* Effekte der Waffen */ + skdiff += weapon_effskill(at, dt, awp, true, dist > 1); + if (awp && fval(awp->type, WTF_MISSILE)) { + skdiff -= is_protected; + if (awp->type->modifiers) { + int w; + for (w = 0; awp->type->modifiers[w].value != 0; ++w) { + if (awp->type->modifiers[w].flags & WMF_MISSILE_TARGET) { + /* skill decreases by targeting difficulty (bow -2, catapult -4) */ + skdiff -= awp->type->modifiers[w].value; break; - } - if (!bf) { - bf = (bfaction *)calloc(1, sizeof(bfaction)); - assert(bf); - ++b->nfactions; - bf->faction = u->faction; - bf->next = b->factions; - b->factions = bf; - } - } - } - } - - for (bf = b->factions; bf; bf = bf->next) { - faction *f = bf->faction; - freset(f, FFL_MARK); - } - return b; -} - -static void free_side(side * si) -{ - selist_free(si->leader.fighters); -} - -static void free_fighter(fighter * fig) -{ - armor **ap = &fig->armors; - while (*ap) { - armor *a = *ap; - *ap = a->next; - free(a); - } - while (fig->loot) { - i_free(i_remove(&fig->loot, fig->loot)); - } - free(fig->person); - free(fig->weapons); - -} - -static void battle_free(battle * b) { - side *s; - - assert(b); - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter **fp = &s->fighters; - while (*fp) { - fighter *fig = *fp; - *fp = fig->next; - free_fighter(fig); - free(fig); - } - s->fighters = NULL; - free_side(s); - } - free(b); -} - -void free_battle(battle * b) -{ - while (b->factions) { - bfaction *bf = b->factions; - b->factions = bf->next; - free(bf); - } - - selist_free(b->leaders); - selist_foreach(b->meffects, free); - selist_free(b->meffects); - - battle_free(b); -} - -static int *get_alive(side * s) -{ - return s->size; -} - -static int battle_report(battle * b) -{ - side *s, *s2; - bool cont = false; - bfaction *bf; - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - if (s->alive - s->removed > 0) { - for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { - if (s2->alive - s2->removed > 0 && enemy(s, s2)) { - cont = true; - break; - } - } - if (cont) - break; - } - } - - fflush(stdout); - - for (bf = b->factions; bf; bf = bf->next) { - faction *fac = bf->faction; - char buf[32 * MAXSIDES]; - message *m; - sbstring sbs; - bool komma = false; - - sbs_init(&sbs, buf, sizeof(buf)); - - if (cont) - m = msg_message("para_lineup_battle", "turn", b->turn); - else - m = msg_message("para_after_battle", ""); - battle_message_faction(b, fac, m); - msg_release(m); - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - if (s->alive) { - int r, k = 0, *alive = get_alive(s); - int l = FIGHT_ROW; - const char *abbrev = seematrix(fac, s) ? sideabkz(s, false) : "-?-"; - const char *loc_army = LOC(fac->locale, "battle_army"); - char buffer[32]; - - if (komma) { - sbs_strcat(&sbs, ", "); - } - snprintf(buffer, sizeof(buffer), "%s %2d(%s): ", - loc_army, army_index(s), abbrev); - sbs_strcat(&sbs, buffer); - - for (r = FIGHT_ROW; r != NUMROWS; ++r) { - if (alive[r]) { - if (l != FIGHT_ROW) { - sbs_strcat(&sbs, "+"); - } - while (k--) { - sbs_strcat(&sbs, "0+"); - } - sprintf(buffer, "%d", alive[r]); - sbs_strcat(&sbs, buffer); - - k = 0; - l = r + 1; } - else - ++k; } - - komma = true; } } - fbattlerecord(b, fac, buf); + if (skill_formula == FORMULA_ORIG) { + weapon *dwp = select_weapon(dt, false, dist > 1); + skdiff -= weapon_effskill(dt, at, dwp, false, dist > 1); + } + return skdiff; } - return cont; -} -static void join_allies(battle * b) -{ - region *r = b->region; - unit *u; - side *s, *s_end = b->sides + b->nsides; - /* make_side might be adding a new faction, but it adds them to the end - * of the list, so we're safe in our iteration here if we remember the end - * up front. */ - for (u = r->units; u; u = u->next) { - /* Was ist mit Schiffen? */ - if (u->status != ST_FLEE && u->status != ST_AVOID - && !fval(u, UFL_LONGACTION | UFL_ISNEW) && u->number > 0) { - faction *f = u->faction; - fighter *c = NULL; + static int setreload(troop at) + { + fighter *af = at.fighter; + const weapon_type *wtype = af->person[at.index].missile->type; + if (wtype->reload == 0) + return 0; + return af->person[at.index].reload = wtype->reload; + } - for (s = b->sides; s != s_end; ++s) { - side *se; - /* Wenn alle attackierten noch FFL_NOAID haben, dann kaempfe nicht mit. */ - if (fval(s->faction, FFL_NOAID)) - continue; - if (s->faction != f) { - /* Wenn wir attackiert haben, kommt niemand mehr hinzu: */ - if (s->bf->attacker) - continue; - /* alliiert muessen wir schon sein, sonst ist's eh egal : */ - if (!alliedunit(u, s->faction, HELP_FIGHT)) - continue; - /* wenn die partei verborgen ist, oder gar eine andere - * vorgespiegelt wird, und er sich uns gegenueber nicht zu - * erkennen gibt, helfen wir ihm nicht */ - if (s->stealthfaction) { - if (!alliedside(s, u->faction, HELP_FSTEALTH)) { - continue; + int getreload(troop at) + { + return at.fighter->person[at.index].reload; + } + + int hits(troop at, troop dt, weapon * awp) + { + fighter *af = at.fighter, *df = dt.fighter; + const armor_type *armor, *shield = 0; + int skdiff = 0; + int dist = get_unitrow(af, df->side) + get_unitrow(df, af->side) - 1; + weapon *dwp = select_weapon(dt, false, dist > 1); + + if (!df->alive) + return 0; + if (getreload(at)) + return 0; + if (dist > 1 && (awp == NULL || !fval(awp->type, WTF_MISSILE))) + return 0; + + /* mark this person as hit. */ + df->person[dt.index].flags |= FL_HIT; + + if (af->person[at.index].flags & FL_STUNNED) { + af->person[at.index].flags &= ~FL_STUNNED; + return 0; + } + if ((af->person[at.index].flags & FL_TIRED && rng_int() % 100 < 50) + || (af->person[at.index].flags & FL_SLEEPING)) + return 0; + + /* effect of sp_reeling_arrows combatspell */ + if (af->side->battle->reelarrow && awp && fval(awp->type, WTF_MISSILE) + && rng_double() < 0.5) { + return 0; + } + + skdiff = skilldiff(at, dt, dist); + /* Verteidiger bekommt eine Ruestung */ + armor = select_armor(dt, true); + if (dwp == NULL || (dwp->type->flags & WTF_USESHIELD)) { + shield = select_armor(dt, false); + } + if (contest(skdiff, dt, armor, shield)) { + return 1; + } + return 0; + } + + void dazzle(battle * b, troop * td) + { + UNUSED_ARG(b); + /* Nicht kumulativ ! */ + if (td->fighter->person[td->index].flags & (FL_COURAGE | FL_DAZZLED)) { + return; + } + + td->fighter->person[td->index].flags |= FL_DAZZLED; + td->fighter->person[td->index].defense--; + } + + void damage_building(battle * b, building * bldg, int damage_abs) + { + assert(bldg); + bldg->size -= damage_abs; + if (bldg->size < 1) bldg->size = 1; + + /* Wenn Burg, dann gucken, ob die Leute alle noch in das Gebaeude passen. */ + + if (bldg->type->flags & BTF_FORTIFICATION) { + side *s; + + bldg->sizeleft = bldg->size; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + for (fig = s->fighters; fig; fig = fig->next) { + if (fig->building == bldg) { + if (bldg->sizeleft >= fig->unit->number) { + fig->building = bldg; + bldg->sizeleft -= fig->unit->number; + } + else { + fig->building = NULL; } } } - /* einen alliierten angreifen duerfen sie nicht, es sei denn, der - * ist mit einem alliierten verfeindet, der nicht attackiert - * hat: */ - for (se = b->sides; se != s_end; ++se) { - if (u->faction == se->faction) - continue; - if (alliedunit(u, se->faction, HELP_FIGHT) && !se->bf->attacker) { - continue; - } - if (enemy(s, se)) - break; - } - if (se == s_end) - continue; - /* keine Einwaende, also soll er mitmachen: */ - if (c == NULL) { - if (!join_battle(b, u, false, &c)) { - continue; - } - } - - /* the enemy of my friend is my enemy: */ - for (se = b->sides; se != s_end; ++se) { - if (se->faction != u->faction && enemy(s, se)) { - set_enemy(se, c->side, false); - } - } } } } - for (s = b->sides; s != b->sides + b->nsides; ++s) { - int si; - side *sa; - faction *f = s->faction; - - /* Den Feinden meiner Feinde gebe ich Deckung (gegen gemeinsame Feinde): */ - for (si = 0; s->enemies[si]; ++si) { - side *se = s->enemies[si]; - int ai; - for (ai = 0; se->enemies[ai]; ++ai) { - side *as = se->enemies[ai]; - if (as == s || !enemy(as, s)) { - set_friendly(as, s); - } - } - } - - for (sa = s + 1; sa != b->sides + b->nsides; ++sa) { - if (!enemy(s, sa) && !friendly(s, sa)) { - if (alliedfaction(f, sa->faction, HELP_FIGHT)) { - if (alliedfaction(sa->faction, f, HELP_FIGHT)) { - set_friendly(s, sa); - } - } - } - } + static int attacks_per_round(troop t) + { + return t.fighter->person[t.index].speed; } -} -static void flee(const troop dt) -{ - fighter *fig = dt.fighter; - unit *u = fig->unit; - int fchance = fleechance(u); - - if (fig->person[dt.index].flags & FL_PANICED) { - fchance += EFFECT_PANIC_SPELL; - } - if (fchance > flee_chance_max_percent) { - fchance = flee_chance_max_percent; - } - if (rng_int() % 100 < fchance) { - fig->run.hp += fig->person[dt.index].hp; - ++fig->run.number; - - setguard(u, false); - kill_troop(dt); - } -} - -static bool is_calmed(const unit *u, const faction *f) { - attrib *a = a_find(u->attribs, &at_curse); - - while (a && a->type == &at_curse) { - curse *c = (curse *)a->data.v; - if (c->type == &ct_calmmonster && curse_geteffect_int(c) == f->uid) { - if (curse_active(c)) { - return true; - } - } - a = a->next; - } - return false; -} - -static bool start_battle(region * r, battle ** bp) -{ - battle *b = NULL; - unit *u; - bool fighting = false; - - for (u = r->units; u != NULL; u = u->next) { - if (fval(u, UFL_LONGACTION)) - continue; - if (u->number > 0) { - order *ord; - - for (ord = u->orders; ord; ord = ord->next) { - if (getkeyword(ord) == K_ATTACK) { - unit *u2; - fighter *c1, *c2; - ship *lsh = NULL; - plane *pl = rplane(r); - - if (pl && fval(pl, PFL_NOATTACK)) { - cmistake(u, ord, 271, MSG_BATTLE); - continue; - } - - if (u_race(u)->battle_flags & BF_NO_ATTACK) { - ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "race_no_attack", - "race", u_race(u))); - continue; - } - /** - ** Fehlerbehandlung Angreifer - **/ - if (LongHunger(u)) { - cmistake(u, ord, 225, MSG_BATTLE); - continue; - } - - if (u->status == ST_AVOID || u->status == ST_FLEE) { - cmistake(u, ord, 226, MSG_BATTLE); - continue; - } - - /* ist ein Fluechtling aus einem andern Kampf */ - if (fval(u, UFL_LONGACTION)) - continue; - - if (curse_active(get_curse(r->attribs, &ct_peacezone))) { - ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "peace_active", "")); - continue; - } - - if (curse_active(get_curse(u->attribs, &ct_slavery))) { - ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "slave_active", "")); - continue; - } - - if ((u->ship != NULL && !fval(r->terrain, SEA_REGION)) - || (lsh = leftship(u)) != NULL) { - if (is_guarded(r, u)) { - if (lsh) { - cmistake(u, ord, 234, MSG_BATTLE); - } - else { - /* Fehler: "Das Schiff muss erst verlassen werden" */ - cmistake(u, ord, 19, MSG_BATTLE); - } - continue; - } - } - - /* Ende Fehlerbehandlung Angreifer */ - - init_order_depr(ord); - /* attackierte Einheit ermitteln */ - getunit(r, u->faction, &u2); - - /* Beginn Fehlerbehandlung */ - /* Fehler: "Die Einheit wurde nicht gefunden" */ - if (!u2 || u2->number == 0 || !cansee(u->faction, u->region, u2, 0)) { - ADDMSG(&u->faction->msgs, msg_feedback(u, ord, - "feedback_unit_not_found", "")); - continue; - } - /* Fehler: "Die Einheit ist eine der unsrigen" */ - if (u2->faction == u->faction) { - cmistake(u, ord, 45, MSG_BATTLE); - continue; - } - /* Fehler: "Die Einheit ist mit uns alliert" */ - if (alliedunit(u, u2->faction, HELP_FIGHT)) { - cmistake(u, ord, 47, MSG_BATTLE); - continue; - } - if (IsImmune(u2->faction)) { - add_message(&u->faction->msgs, - msg_feedback(u, u->thisorder, "newbie_immunity_error", "turns", - NewbieImmunity())); - continue; - } - - /* Fehler: "Die Einheit ist mit uns alliert" */ - if (is_calmed(u, u2->faction)) { - cmistake(u, ord, 47, MSG_BATTLE); - continue; - } - /* Ende Fehlerbehandlung */ - if (b == NULL) { - unit *utmp; - for (utmp = r->units; utmp != NULL; utmp = utmp->next) { - fset(utmp->faction, FFL_NOAID); - } - b = make_battle(r); - } - join_battle(b, u, true, &c1); - join_battle(b, u2, false, &c2); - - if (u2->attribs) { - if (it_mistletoe) { - int effect = get_effect(u2, it_mistletoe); - if (effect >= u->number) { - change_effect(u2, it_mistletoe, -u2->number); - c2->run.hp = u2->hp; - c2->run.number = u2->number; - c2->side->flee += u2->number; - setguard(u2, false); - rmfighter(c2, u2->number); - } - } - } - - /* Hat die attackierte Einheit keinen Noaid-Status, - * wird das Flag von der Faction genommen, andere - * Einheiten greifen ein. */ - if (!fval(u2, UFL_NOAID)) - freset(u2->faction, FFL_NOAID); - - if (c1 && c2 && c2->run.number < c2->unit->number) { - /* Merken, wer Angreifer ist, fuer die Rueckzahlung der - * Praecombataura bei kurzem Kampf. */ - c1->side->bf->attacker = true; - - set_enemy(c1->side, c2->side, true); - fighting = true; - } - } - } - } - } - *bp = b; - return fighting; -} - -/** execute one round of attacks - * fig->fighting is used to determine who attacks, not fig->alive, since - * the latter may be influenced by attacks that already took place. - */ -static void battle_attacks(battle * b) -{ - side *s; - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fig; - - if (b->turn != 0 || (b->max_tactics > 0 - && get_tactics(s, NULL) == b->max_tactics)) { - for (fig = s->fighters; fig; fig = fig->next) { - - /* ist in dieser Einheit noch jemand handlungsfaehig? */ - if (fig->fighting <= 0) - continue; - - /* Handle the unit's attack on someone */ - do_attack(fig); - } - } - } -} - -/** updates the number of attacking troops in each fighter struct. - * this has to be calculated _before_ the actual attacks take - * place because otherwise dead troops would not strike in the - * round they die. */ -static void battle_update(battle * b) -{ - side *s; - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fig; - for (fig = s->fighters; fig; fig = fig->next) { - fig->fighting = fig->alive - fig->removed; - } - } -} - -/** attempt to flee from battle before the next round begins - * there's a double attempt before the first round, but only - * one attempt before round zero, the potential tactics round. */ -static void battle_flee(battle * b) -{ - int attempt, flee_ops = 1; - - if (b->turn == 1) - flee_ops = 2; - - for (attempt = 1; attempt <= flee_ops; ++attempt) { + static void make_heroes(battle * b) + { side *s; for (s = b->sides; s != b->sides + b->nsides; ++s) { fighter *fig; for (fig = s->fighters; fig; fig = fig->next) { unit *u = fig->unit; - troop dt; - /* Flucht nicht bei mehr als 600 HP. Damit Wyrme toetbar bleiben. */ - int runhp = (int)(0.9 + unit_max_hp(u) * hpflee(u->status)); - if (runhp > 600) runhp = 600; - - if (u->ship && fval(u->region->terrain, SEA_REGION)) { - /* keine Flucht von Schiffen auf hoher See */ - continue; - } - if (fval(u_race(u), RCF_UNDEAD) || u_race(u) == get_race(RC_SHADOWKNIGHT)) { - /* Untote fliehen nicht. Warum eigentlich? */ - continue; - } - - dt.fighter = fig; - dt.index = fig->alive - fig->removed; - while (s->size[SUM_ROW] && dt.index != 0) { - --dt.index; - assert(dt.index >= 0 && dt.index < fig->unit->number); - assert(fig->person[dt.index].hp > 0); - - /* Versuche zu fliehen, wenn - * - Kampfstatus fliehe - * - schwer verwundet und nicht erste kampfrunde - * - in panik (Zauber) - * aber nicht, wenn der Zaubereffekt Held auf dir liegt! - */ - switch (u->status) { - case ST_FLEE: - break; - default: - if ((fig->person[dt.index].flags & FL_HIT) == 0) - continue; - if (fig->person[dt.index].hp <= runhp) - break; - if (fig->person[dt.index].flags & FL_PANICED) { - if ((fig->person[dt.index].flags & FL_COURAGE) == 0) - break; - } - continue; + if (fval(u, UFL_HERO)) { + int i; + if (!playerrace(u_race(u))) { + log_error("Hero %s is a %s.\n", unitname(u), u_race(u)->_name); + } + for (i = 0; i != u->number; ++i) { + fig->person[i].speed += (rule_hero_speed - 1); } - flee(dt); } } } } -} -static bool is_enemy(battle *b, unit *u1, unit *u2) { - if (u1->faction != u2->faction) { - if (b) { - side *es, *s1 = 0, *s2 = 0; - for (es = b->sides; es != b->sides + b->nsides; ++es) { - if (!s1 && es->faction == u1->faction) s1 = es; - else if (!s2 && es->faction == u2->faction) s2 = es; - if (s1 && s2) { - return enemy(s1, s2); + static void attack(battle * b, troop ta, const att * a, int numattack) + { + fighter *af = ta.fighter; + troop td; + unit *au = af->unit; + + switch (a->type) { + case AT_COMBATSPELL: + /* Magier versuchen immer erstmal zu zaubern, erst wenn das + * fehlschlaegt, wird af->magic == 0 und der Magier kaempft + * konventionell weiter */ + if (numattack == 0 && af->magic > 0) { + /* wenn der magier in die potenzielle Reichweite von Attacken des + * Feindes kommt, beginnt er auch bei einem Status von KAEMPFE NICHT, + * Kampfzauber zu schleudern: */ + if (count_enemies(b, af, melee_range[0], missile_range[1], + SELECT_ADVANCE | SELECT_DISTANCE | SELECT_FIND)) { + do_combatspell(ta); } } + break; + case AT_STANDARD: /* Waffen, mag. Gegenstaende, Kampfzauber */ + if (numattack > 0 || af->magic <= 0) { + weapon *wp = ta.fighter->person[ta.index].missile; + int melee = + count_enemies(b, af, melee_range[0], melee_range[1], + SELECT_ADVANCE | SELECT_DISTANCE | SELECT_FIND); + if (melee) + wp = preferred_weapon(ta, true); + /* Sonderbehandlungen */ + + if (getreload(ta)) { + ta.fighter->person[ta.index].reload--; + } + else { + bool standard_attack = true; + bool reload = false; + /* spezialattacken der waffe nur, wenn erste attacke in der runde. + * sonst helden mit feuerschwertern zu maechtig */ + if (numattack == 0 && wp && wp->type->attack) { + int dead = 0; + standard_attack = wp->type->attack(&ta, wp->type, &dead); + if (!standard_attack) + reload = true; + af->catmsg += dead; + if (!standard_attack && af->person[ta.index].last_action < b->turn) { + af->person[ta.index].last_action = b->turn; + } + } + if (standard_attack) { + bool missile = false; + if (wp && fval(wp->type, WTF_MISSILE)) + missile = true; + if (missile) { + td = select_opponent(b, ta, missile_range[0], missile_range[1]); + } + else { + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + } + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + reload = true; + if (hits(ta, td, wp)) { + const char *d; + if (wp == NULL) + d = u_race(au)->def_damage; + else if (is_riding(ta)) + d = wp->type->damage[1]; + else + d = wp->type->damage[0]; + terminate(td, ta, a->type, d, missile); + } + } + if (reload && wp && wp->type->reload && !getreload(ta)) { + setreload(ta); + } + } + } + break; + case AT_SPELL: /* Extra-Sprueche. Kampfzauber in AT_COMBATSPELL! */ + do_extra_spell(ta, a); + break; + case AT_NATURAL: + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + if (hits(ta, td, NULL)) { + terminate(td, ta, a->type, a->data.dice, false); + } + break; + case AT_DRAIN_ST: + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + if (hits(ta, td, NULL)) { + int c = dice_rand(a->data.dice); + while (c > 0) { + if (rng_int() % 2) { + td.fighter->person[td.index].attack -= 1; + } + else { + td.fighter->person[td.index].defense -= 1; + } + c--; + } + } + break; + case AT_DRAIN_EXP: + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + if (hits(ta, td, NULL)) { + drain_exp(td.fighter->unit, dice_rand(a->data.dice)); + } + break; + case AT_DAZZLE: + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + if (hits(ta, td, NULL)) { + dazzle(b, &td); + } + break; + case AT_STRUCTURAL: + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + if (td.fighter->unit->ship) { + int dice = dice_rand(a->data.dice); + ship * sh = td.fighter->unit->ship; + damage_ship(sh, dice / sh->type->damage / sh->size); + } + else if (td.fighter->unit->building) { + damage_building(b, td.fighter->unit->building, dice_rand(a->data.dice)); + } + } + } + + void do_attack(fighter * af) + { + troop ta; + unit *au = af->unit; + side *side = af->side; + battle *b = side->battle; + + ta.fighter = af; + + assert(au && au->number); + /* Da das Zuschlagen auf Einheiten und nicht auf den einzelnen + * Kaempfern beruht, darf die Reihenfolge und Groesse der Einheit keine + * Rolle spielen, Das tut sie nur dann, wenn jeder, der am Anfang der + * Runde lebte, auch zuschlagen darf. Ansonsten ist der, der zufaellig + * mit einer grossen Einheit zuerst drankommt, extrem bevorteilt. */ + ta.index = af->fighting; + + while (ta.index--) { + /* Wir suchen eine beliebige Feind-Einheit aus. An der koennen + * wir feststellen, ob noch jemand da ist. */ + int apr, attacks = attacks_per_round(ta); + if (!count_enemies(b, af, FIGHT_ROW, LAST_ROW, SELECT_FIND)) + break; + + for (apr = 0; apr != attacks; ++apr) { + int a; + for (a = 0; a < RACE_ATTACKS && u_race(au)->attack[a].type != AT_NONE; ++a) { + if (apr > 0) { + /* Wenn die Waffe nachladen muss, oder es sich nicht um einen + * Waffen-Angriff handelt, dann gilt der Speed nicht. */ + /* TODO: allow multiple AT_NATURAL attacks? */ + if (u_race(au)->attack[a].type != AT_STANDARD) + continue; + else { + weapon *wp = preferred_weapon(ta, true); + if (wp != NULL && wp->type->reload) + continue; + } + } + attack(b, ta, &(u_race(au)->attack[a]), apr); + } + } + } + /* Der letzte Katapultschuetze setzt die + * Ladezeit neu und generiert die Meldung. */ + if (af->catmsg >= 0) { + struct message *m = + msg_message("killed_battle", "unit dead", au, af->catmsg); + message_all(b, m); + msg_release(m); + af->catmsg = -1; + } + } + + static void add_tactics(tactics * ta, fighter * fig, int value) + { + if (value == 0 || value < ta->value) + return; + if (value > ta->value) { + selist_free(ta->fighters); + ta->fighters = 0; + } + selist_push(&ta->fighters, fig); + selist_push(&fig->side->battle->leaders, fig); + ta->value = value; + } + + static int horse_fleeing_bonus(const unit * u) + { + const item_type *it_horse, *it_elvenhorse, *it_charger; + int n1 = 0, n2 = 0, n3 = 0; + item *itm; + int skl = effskill(u, SK_RIDING, NULL); + const resource_type *rtype; + + it_horse = ((rtype = get_resourcetype(R_HORSE)) != NULL) ? rtype->itype : 0; + it_elvenhorse = ((rtype = get_resourcetype(R_UNICORN)) != NULL) ? rtype->itype : 0; + it_charger = ((rtype = get_resourcetype(R_CHARGER)) != NULL) ? rtype->itype : 0; + + for (itm = u->items; itm; itm = itm->next) { + if (itm->type->flags & ITF_ANIMAL) { + if (itm->type == it_elvenhorse) + n3 += itm->number; + else if (itm->type == it_charger) + n2 += itm->number; + else if (itm->type == it_horse) + n1 += itm->number; + } + } + if (skl >= 5 && n3 >= u->number) + return 30; + if (skl >= 2 && n2 + n3 >= u->number) + return 20; + if (n1 + n2 + n3 >= u->number) + return 10; + return 0; + } + + static int fleechance(unit * u) + { + int p = flee_chance_base; /* Fluchtwahrscheinlichkeit in % */ + /* Einheit u versucht, dem Getuemmel zu entkommen */ + + p += (effskill(u, SK_STEALTH, NULL) * flee_chance_skill_bonus); + p += horse_fleeing_bonus(u); + + if (u_race(u) == get_race(RC_HALFLING)) { + p += flee_chance_base; + if (p > flee_chance_max_percent) { + p = flee_chance_max_percent; + } + } + return p; + } + + /** add a new army to the conflict. + * beware: armies need to be added _at the beginning_ of the list because + * otherwise join_allies() will get into trouble */ + side *make_side(battle * b, const faction * f, const group * g, + unsigned int flags, const faction * stealthfaction) + { + side *s1 = b->sides + b->nsides; + bfaction *bf; + + if (fval(b->region->terrain, SEA_REGION)) { + /* every fight in an ocean is short */ + flags |= SIDE_HASGUARDS; } else { - return !help_enter(u1, u2); + unit *u; + for (u = b->region->units; u; u = u->next) { + if (is_guard(u)) { + if (alliedunit(u, f, HELP_GUARD)) { + flags |= SIDE_HASGUARDS; + break; + } + } + } + } + + s1->battle = b; + s1->group = g; + s1->flags = flags; + s1->stealthfaction = stealthfaction; + for (bf = b->factions; bf; bf = bf->next) { + faction *f2 = bf->faction; + + if (f2 == f) { + s1->bf = bf; + s1->faction = f2; + s1->index = b->nsides++; + s1->nextF = bf->sides; + bf->sides = s1; + assert(b->nsides <= MAXSIDES); + break; + } + } + assert(bf); + return s1; + } + + troop select_ally(fighter * af, int minrow, int maxrow, int allytype) + { + side *as = af->side; + battle *b = as->battle; + side *ds; + int allies = count_allies(as, minrow, maxrow, SELECT_ADVANCE, allytype); + + if (!allies) { + return no_troop; + } + allies = (int)(rng_int() % allies); + + for (ds = b->sides; ds != b->sides + b->nsides; ++ds) { + if ((allytype == ALLY_ANY && helping(as, ds)) || (allytype == ALLY_SELF + && as->faction == ds->faction)) { + fighter *df; + for (df = ds->fighters; df; df = df->next) { + int dr = get_unitrow(df, NULL); + if (dr >= minrow && dr <= maxrow) { + if (df->alive - df->removed > allies) { + troop dt; + assert(allies >= 0); + dt.index = allies; + dt.fighter = df; + return dt; + } + allies -= df->alive; + } + } + } + } + assert(!"we should never have gotten here"); + return no_troop; + } + + static int loot_quota(const unit * src, const unit * dst, + const item_type * type, int n) + { + UNUSED_ARG(type); + if (dst && src && src->faction != dst->faction) { + double divisor = config_get_flt("rules.items.loot_divisor", 1); + assert(divisor <= 0 || divisor >= 1); + if (divisor >= 1) { + double r = n / divisor; + int x = (int)r; + + r = r - x; + if (chance(r)) + ++x; + + return x; + } + } + return n; + } + + static void loot_items(fighter * corpse) + { + unit *u = corpse->unit; + item *itm = u->items; + battle *b = corpse->side->battle; + int dead = dead_fighters(corpse); + + if (dead <= 0) + return; + + while (itm) { + float lootfactor = (float)dead / (float)u->number; /* only loot the dead! */ + int maxloot = (int)((float)itm->number * lootfactor); + if (maxloot > 0) { + int i = (maxloot > 10) ? 10 : maxloot; + for (; i != 0; --i) { + int loot = maxloot / i; + + if (loot > 0) { + fighter *fig = NULL; + int looting = 0; + int maxrow = 0; + /* mustloot: we absolutely, positively must have somebody loot this thing */ + int mustloot = itm->type->flags & (ITF_CURSED | ITF_NOTLOST); + + itm->number -= loot; + maxloot -= loot; + + if (is_monsters(u->faction) && (rule_loot & LOOT_MONSTERS)) { + looting = 1; + } + else if (rule_loot & LOOT_OTHERS) { + looting = 1; + } + else if (rule_loot & LOOT_SELF) { + looting = 2; + } + if (looting) { + if (mustloot) { + maxrow = LAST_ROW; + } + else if (rule_loot & LOOT_KEEPLOOT) { + int lootchance = 50 + b->keeploot; + if (rng_int() % 100 < lootchance) { + maxrow = BEHIND_ROW; + } + } + else { + maxrow = LAST_ROW; + } + } + if (maxrow > 0) { + if (looting == 1) { + /* enemies get dibs */ + fig = select_enemy(corpse, FIGHT_ROW, maxrow, 0).fighter; + } + if (!fig) { + /* self and allies get second pick */ + fig = select_ally(corpse, FIGHT_ROW, LAST_ROW, ALLY_SELF).fighter; + } + } + + if (fig) { + int trueloot = + mustloot ? loot : loot_quota(corpse->unit, fig->unit, itm->type, + loot); + if (trueloot > 0) { + i_change(&fig->loot, itm->type, trueloot); + } + } + } + } + } + itm = itm->next; } } - return false; -} -void force_leave(region *r, battle *b) { - unit *u; + bool seematrix(const faction * f, const side * s) + { + if (f == s->faction) + return true; + if (s->flags & SIDE_STEALTH) + return false; + return true; + } - for (u = r->units; u; u = u->next) { - unit *uo = NULL; - if (u->building) { - uo = building_owner(u->building); + static double PopulationDamage(void) + { + return rule_population_damage / 100.0; + } + + static void battle_effects(battle * b, int dead_players) + { + region *r = b->region; + int rp = rpeasants(r); + + if (rp > 0) { + int dead_peasants = (int)(dead_players * PopulationDamage()); + if (dead_peasants > rp) { + dead_peasants = rp; + } + if (dead_peasants) { + deathcounts(r, dead_peasants + dead_players); + rsetpeasants(r, rp - dead_peasants); + } } - if (u->ship && r->land) { - uo = ship_owner(u->ship); + } + + static void reorder_fleeing(region * r) + { + unit **usrc = &r->units; + unit **udst = &r->units; + unit *ufirst = NULL; + unit *u; + + for (; *udst; udst = &u->next) { + u = *udst; } - if (uo && is_enemy(b, uo, u)) { - message *msg = NULL; - if (u->building) { - msg = msg_message("force_leave_building", "unit owner building", u, uo, u->building); + + for (u = *usrc; u != ufirst; u = *usrc) { + if (u->next && fval(u, UFL_FLEEING)) { + *usrc = u->next; + *udst = u; + udst = &u->next; + if (!ufirst) + ufirst = u; } else { - msg = msg_message("force_leave_ship", "unit owner ship", u, uo, u->ship); + usrc = &u->next; } - if (msg) { - ADDMSG(&u->faction->msgs, msg); + } + *udst = NULL; + } + + static void aftermath(battle * b) + { + region *r = b->region; + side *s; + int dead_players = 0; + bfaction *bf; + bool ships_damaged = (b->turn + (b->has_tactics_turn ? 1 : 0) > 2); /* only used for ship damage! */ + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *df; + s->dead = 0; + + for (df = s->fighters; df; df = df->next) { + unit *du = df->unit; + int dead = dead_fighters(df); + + /* tote insgesamt: */ + s->dead += dead; + /* Tote, die wiederbelebt werde koennen: */ + if (playerrace(u_race(df->unit))) { + s->casualties += dead; + } + if (df->hits + df->kills) { + struct message *m = + msg_message("killsandhits", "unit hits kills", du, df->hits, + df->kills); + battle_message_faction(b, du->faction, m); + msg_release(m); + } + } + } + + /* POSTCOMBAT */ + do_combatmagic(b, DO_POSTCOMBATSPELL); + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + int snumber = 0; + fighter *df; + bool relevant = false; /* Kampf relevant fuer diese Partei? */ + if (!fval(s, SIDE_HASGUARDS)) { + relevant = true; + } + s->flee = 0; + + for (df = s->fighters; df; df = df->next) { + unit *du = df->unit; + int dead = dead_fighters(df); + int sum_hp = 0; + int n; + int flags = 0; + + for (n = 0; n != df->alive; ++n) { + if (df->person[n].hp > 0) { + sum_hp += df->person[n].hp; + } + } + snumber += du->number; + if (dead == df->unit->number) { + flags = UFL_DEAD; + } + else if (relevant) { + flags = UFL_LONGACTION; + if ((du->status != ST_FLEE) && (df->run.hp <= 0)) { + flags |= UFL_NOTMOVING; + } + } + if (flags) { + fset(du, flags); + } + if (df->alive && df->alive == du->number) { + du->hp = sum_hp; + continue; /* nichts passiert */ + } + else if (df->run.hp) { + if (df->alive == 0) { + /* Report the casualties */ + reportcasualties(b, df, dead); + + /* Zuerst duerfen die Feinde pluendern, die mitgenommenen Items + * stehen in fig->run.items. Dann werden die Fliehenden auf + * die leere (tote) alte Einheit gemapt */ + if (!fval(df, FIG_NOLOOT)) { + loot_items(df); + } + scale_number(du, df->run.number); + du->hp = df->run.hp; + setguard(du, false); + /* must leave ships or buildings, or a stealthy hobbit + * can hold castles indefinitely */ + if (!fval(r->terrain, SEA_REGION)) { + leave(du, true); /* even region owners have to flee */ + } + fset(du, UFL_FLEEING); + } + else { + /* nur teilweise geflohene Einheiten mergen sich wieder */ + df->alive += df->run.number; + s->size[0] += df->run.number; + s->size[statusrow(df->status)] += df->run.number; + s->alive += df->run.number; + sum_hp += df->run.hp; + df->run.number = 0; + df->run.hp = 0; + /* df->run.region = NULL; */ + + reportcasualties(b, df, dead); + + scale_number(du, df->alive); + du->hp = sum_hp; + } + } + else { + if (df->alive == 0) { + /* alle sind tot, niemand geflohen. Einheit aufloesen */ + df->run.number = 0; + df->run.hp = 0; + + /* Report the casualties */ + reportcasualties(b, df, dead); + + /* Distribute Loot */ + loot_items(df); + + setguard(du, false); + scale_number(du, 0); + } + else { + df->run.number = 0; + df->run.hp = 0; + + reportcasualties(b, df, dead); + + scale_number(du, df->alive); + du->hp = sum_hp; + } + } + s->flee += df->run.number; + + if (playerrace(u_race(du))) { + /* tote im kampf werden zu regionsuntoten: + * for each of them, a peasant will die as well */ + dead_players += dead; + } + if (du->hp < du->number) { + log_error("%s has less hitpoints (%u) than people (%u)\n", itoa36(du->no), du->hp, du->number); + du->hp = du->number; + } + } + s->alive += s->healed; + assert(snumber == s->flee + s->alive + s->dead); + } + + battle_effects(b, dead_players); + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + message *seen = msg_message("army_report", + "index abbrev dead fled survived", + army_index(s), sideabkz(s, false), s->dead, s->flee, s->alive); + message *unseen = msg_message("army_report", + "index abbrev dead fled survived", + army_index(s), "-?-", s->dead, s->flee, s->alive); + + for (bf = b->factions; bf; bf = bf->next) { + faction *f = bf->faction; + message *m = seematrix(f, s) ? seen : unseen; + + battle_message_faction(b, f, m); + } + + msg_release(seen); + msg_release(unseen); + } + + /* Wir benutzen drifted, um uns zu merken, ob ein Schiff + * schonmal Schaden genommen hat. (moved und drifted + * sollten in flags ueberfuehrt werden */ + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *df; + + for (df = s->fighters; df; df = df->next) { + unit *du = df->unit; + item *l; + + /* Beute verteilen */ + for (l = df->loot; l; l = l->next) { + const item_type *itype = l->type; + message *m = + msg_message("battle_loot", "unit amount item", du, l->number, + itype->rtype); + battle_message_faction(b, du->faction, m); + msg_release(m); + i_change(&du->items, itype, l->number); + } + + /* Wenn sich die Einheit auf einem Schiff befindet, wird + * dieses Schiff beschaedigt. Andernfalls ein Schiff, welches + * evt. zuvor verlassen wurde. */ + if (ships_damaged) { + ship *sh; + if (du->ship) + sh = du->ship; + else + sh = leftship(du); + + if (sh && fval(sh, SF_DAMAGED)) { + int n = b->turn - 2; + if (n > 0) { + double dmg = + config_get_flt("rules.ship.damage.battleround", + 0.05F); + damage_ship(sh, dmg * n); + freset(sh, SF_DAMAGED); + } + } + } + } + } + + if (ships_damaged) { + ship **sp = &r->ships; + + while (*sp) { + ship *sh = *sp; + freset(sh, SF_DAMAGED); + if (sh->damage >= sh->size * DAMAGE_SCALE) { + sink_ship(sh); + remove_ship(sp, sh); + } + else { + sp = &sh->next; + } + } + } + + reorder_fleeing(r); + } + + static void battle_punit(unit * u, battle * b) + { + bfaction *bf; + + for (bf = b->factions; bf; bf = bf->next) { + faction *f = bf->faction; + strlist *S = 0, *x; + + spunit(&S, f, u, 4, seen_battle); + for (x = S; x; x = x->next) { + fbattlerecord(b, f, x->s); + } + if (S) + freestrlist(S); + } + } + + static void print_fighters(battle * b, const side * s) + { + fighter *df; + int row; + + for (row = 1; row != NUMROWS; ++row) { + message *m = NULL; + + for (df = s->fighters; df; df = df->next) { + unit *du = df->unit; + int thisrow = statusrow(df->unit->status); + + if (row == thisrow) { + if (m == NULL) { + m = msg_message("battle_row", "row", row); + message_all(b, m); + } + battle_punit(du, b); + } + } + if (m != NULL) + msg_release(m); + } + } + + bool is_attacker(const fighter * fig) + { + return fval(fig, FIG_ATTACKER) != 0; + } + + static void set_attacker(fighter * fig) + { + fset(fig, FIG_ATTACKER); + } + + static void print_stats(battle * b) + { + side *s2; + side *s; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + bfaction *bf; + + for (bf = b->factions; bf; bf = bf->next) { + faction *f = bf->faction; + const char *loc_army = LOC(f->locale, "battle_army"); + char *bufp; + const char *header; + size_t rsize, size; + int komma; + const char *sname = + seematrix(f, s) ? sidename(s) : LOC(f->locale, "unknown_faction"); + message *msg; + char buf[1024]; + + msg = msg_message("para_army_index", "index name", army_index(s), sname); + battle_message_faction(b, f, msg); + msg_release(msg); + + bufp = buf; + size = sizeof(buf); + komma = 0; + header = LOC(f->locale, "battle_opponents"); + + for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { + if (enemy(s2, s)) { + const char *abbrev = seematrix(f, s2) ? sideabkz(s2, false) : "-?-"; + rsize = slprintf(bufp, size, "%s %s %d(%s)", + komma++ ? "," : (const char *)header, loc_army, army_index(s2), + abbrev); + if (rsize > size) + rsize = size - 1; + size -= rsize; + bufp += rsize; + } + } + if (komma) + fbattlerecord(b, f, buf); + + bufp = buf; + size = sizeof(buf); + komma = 0; + header = LOC(f->locale, "battle_helpers"); + + for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { + if (friendly(s2, s)) { + const char *abbrev = seematrix(f, s2) ? sideabkz(s2, false) : "-?-"; + rsize = slprintf(bufp, size, "%s %s %d(%s)", + komma++ ? "," : (const char *)header, loc_army, army_index(s2), + abbrev); + if (rsize > size) + rsize = size - 1; + size -= rsize; + bufp += rsize; + } + } + if (komma) + fbattlerecord(b, f, buf); + + bufp = buf; + size = sizeof(buf); + komma = 0; + header = LOC(f->locale, "battle_attack"); + + for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { + if (s->relations[s2->index] & E_ATTACKING) { + const char *abbrev = seematrix(f, s2) ? sideabkz(s2, false) : "-?-"; + rsize = + slprintf(bufp, size, "%s %s %d(%s)", + komma++ ? "," : (const char *)header, loc_army, army_index(s2), + abbrev); + if (rsize > size) + rsize = size - 1; + size -= rsize; + bufp += rsize; + } + } + if (komma) + fbattlerecord(b, f, buf); + } + + print_fighters(b, s); + } + + /* Besten Taktiker ermitteln */ + + b->max_tactics = 0; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (!selist_empty(s->leader.fighters)) { + if (s->leader.value > b->max_tactics) { + b->max_tactics = s->leader.value; + } + } + } + + if (b->max_tactics > 0) { + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (s->leader.value == b->max_tactics) { + selist *ql; + int qi; + + for (qi = 0, ql = s->leader.fighters; ql; selist_advance(&ql, &qi, 1)) { + fighter *tf = (fighter *)selist_get(ql, qi); + unit *u = tf->unit; + message *m = NULL; + if (!is_attacker(tf)) { + m = msg_message("para_tactics_lost", "unit", u); + } + else { + m = msg_message("para_tactics_won", "unit", u); + } + message_all(b, m); + msg_release(m); + } + } + } + } + } + + static int weapon_weight(const weapon * w, bool missile) + { + if (missile == !!(fval(w->type, WTF_MISSILE))) { + return w->attackskill + w->defenseskill; + } + return 0; + } + + side * get_side(battle * b, const struct unit * u) + { + side * s; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (s->faction == u->faction) { + fighter * fig; + for (fig = s->fighters; fig; fig = fig->next) { + if (fig->unit == u) { + return s; + } + } + } + } + return 0; + } + + side * find_side(battle * b, const faction * f, const group * g, unsigned int flags, const faction * stealthfaction) + { + side * s; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (s->faction == f && s->group == g) { + unsigned int s1flags = flags | SIDE_HASGUARDS; + unsigned int s2flags = s->flags | SIDE_HASGUARDS; + if (rule_anon_battle && s->stealthfaction != stealthfaction) { + continue; + } + if (s1flags == s2flags) { + return s; + } + } + } + return 0; + } + + fighter *make_fighter(battle * b, unit * u, side * s1, bool attack) + { +#define WMAX 20 + weapon weapons[WMAX]; + region *r = b->region; + item *itm; + fighter *fig = NULL; + int h, i, tactics = effskill(u, SK_TACTICS, NULL); + int berserk; + int strongmen; + int speeded = 0, speed = 1; + int rest; + const group *g = NULL; + const faction *stealthfaction = get_otherfaction(u); + unsigned int flags = 0; + + assert(u->number); + if (fval(u, UFL_ANON_FACTION) != 0) + flags |= SIDE_STEALTH; + if (!(AllianceAuto() & HELP_FIGHT) && fval(u, UFL_GROUP)) { + g = get_group(u); + } + + /* Illusionen und Zauber kaempfen nicht */ + if (fval(u_race(u), RCF_ILLUSIONARY) || u->number == 0) { + return NULL; + } + if (s1 == NULL) { + s1 = find_side(b, u->faction, g, flags, stealthfaction); + /* aliances are moved out of make_fighter and will be handled later */ + if (!s1) { + s1 = make_side(b, u->faction, g, flags, stealthfaction); + } + else if (!stealthfaction) { + s1->stealthfaction = NULL; + } + /* Zu diesem Zeitpunkt ist attacked noch 0, da die Einheit fuer noch + * keinen Kampf ausgewaehlt wurde (sonst wuerde ein fighter existieren) */ + } + fig = (struct fighter*)calloc(1, sizeof(struct fighter)); + + fig->next = s1->fighters; + s1->fighters = fig; + + fig->unit = u; + /* In einer Burg muss man a) nicht Angreifer sein, und b) drin sein, und + * c) noch Platz finden. d) menschanaehnlich sein */ + if (attack) { + set_attacker(fig); + } + else { + building *bld = u->building; + if (bld && bld->sizeleft >= u->number && playerrace(u_race(u))) { + fig->building = bld; + fig->building->sizeleft -= u->number; + } + } + fig->status = u->status; + fig->side = s1; + fig->alive = u->number; + fig->side->alive += u->number; + fig->side->battle->alive += u->number; + fig->catmsg = -1; + + /* Freigeben nicht vergessen! */ + assert(fig->alive > 0); + fig->person = (struct person*)calloc((size_t)fig->alive, sizeof(struct person)); + + h = u->hp / u->number; + assert(h); + rest = u->hp % u->number; + + /* Effekte von Spruechen */ + + if (u->attribs) { + curse *c = get_curse(u->attribs, &ct_speed); + if (c) { + speeded = get_cursedmen(u, c); + speed = curse_geteffect_int(c); + } + } + + /* Effekte von Alchemie */ + berserk = get_effect(u, oldpotiontype[P_BERSERK]); + /* change_effect wird in ageing gemacht */ + + /* Effekte von Artefakten */ + strongmen = trollbelts(u); + if (strongmen > fig->unit->number) strongmen = fig->unit->number; + + /* Hitpoints, Attack- und Defense-Boni fuer alle Personen */ + for (i = 0; i < fig->alive; i++) { + assert(i < fig->unit->number); + fig->person[i].hp = h; + if (i < rest) + fig->person[i].hp++; + + if (i < speeded) + fig->person[i].speed = speed; + else + fig->person[i].speed = 1; + + if (i < berserk) { + fig->person[i].attack++; + } + /* Leute mit Kraftzauber machen +2 Schaden im Nahkampf. */ + if (i < strongmen) { + fig->person[i].damage += 2; + } + } + + /* Fuer alle Waffengattungen wird bestimmt, wie viele der Personen mit + * ihr kaempfen koennten, und was ihr Wert darin ist. */ + if (u_race(u)->battle_flags & BF_EQUIPMENT) { + int owp[WMAX]; + int dwp[WMAX]; + int wcount[WMAX]; + int wused[WMAX]; + int oi = 0, di = 0, w = 0; + for (itm = u->items; itm && w != WMAX; itm = itm->next) { + const weapon_type *wtype = resource2weapon(itm->type->rtype); + if (wtype == NULL || itm->number == 0) + continue; + weapons[w].attackskill = weapon_skill(wtype, u, true); + weapons[w].defenseskill = weapon_skill(wtype, u, false); + if (weapons[w].attackskill >= 0 || weapons[w].defenseskill >= 0) { + weapons[w].type = wtype; + wused[w] = 0; + wcount[w] = itm->number; + ++w; + } + assert(w != WMAX); + } + assert(w >= 0); + fig->weapons = (weapon *)calloc((size_t)(w + 1), sizeof(weapon)); + memcpy(fig->weapons, weapons, (size_t)w * sizeof(weapon)); + + for (i = 0; i != w; ++i) { + int j, o = 0, d = 0; + for (j = 0; j != i; ++j) { + if (weapon_weight(fig->weapons + j, + true) >= weapon_weight(fig->weapons + i, true)) + ++d; + if (weapon_weight(fig->weapons + j, + false) >= weapon_weight(fig->weapons + i, false)) + ++o; + } + for (j = i + 1; j != w; ++j) { + if (weapon_weight(fig->weapons + j, + true) > weapon_weight(fig->weapons + i, true)) + ++d; + if (weapon_weight(fig->weapons + j, + false) > weapon_weight(fig->weapons + i, false)) + ++o; + } + owp[o] = i; + dwp[d] = i; + } + /* jetzt enthalten owp und dwp eine absteigend schlechter werdende Liste der Waffen + * oi and di are the current index to the sorted owp/dwp arrays + * owp, dwp contain indices to the figther::weapons array */ + + /* hand out melee weapons: */ + for (i = 0; i != fig->alive; ++i) { + int wpless = weapon_skill(NULL, u, true); + while (oi != w + && (wused[owp[oi]] == wcount[owp[oi]] + || fval(fig->weapons[owp[oi]].type, WTF_MISSILE))) { + ++oi; + } + if (oi == w) + break; /* no more weapons available */ + if (weapon_weight(fig->weapons + owp[oi], false) <= wpless) { + continue; /* we fight better with bare hands */ + } + fig->person[i].melee = &fig->weapons[owp[oi]]; + ++wused[owp[oi]]; + } + /* hand out missile weapons (from back to front, in case of mixed troops). */ + for (di = 0, i = fig->alive; i-- != 0;) { + while (di != w && (wused[dwp[di]] == wcount[dwp[di]] + || !fval(fig->weapons[dwp[di]].type, WTF_MISSILE))) { + ++di; + } + if (di == w) + break; /* no more weapons available */ + if (weapon_weight(fig->weapons + dwp[di], true) > 0) { + fig->person[i].missile = &fig->weapons[dwp[di]]; + ++wused[dwp[di]]; + } + } + } + + s1->size[statusrow(fig->status)] += u->number; + s1->size[SUM_ROW] += u->number; + if (u_race(u)->battle_flags & BF_NOBLOCK) { + s1->nonblockers[statusrow(fig->status)] += u->number; + } + + if (u_race(fig->unit)->flags & RCF_HORSE) { + fig->horses = fig->unit->number; + fig->elvenhorses = 0; + } + else { + const resource_type *rt_horse = 0; + const resource_type *rt_elvenhorse = 0; + rt_elvenhorse = get_resourcetype(R_UNICORN); + rt_horse = get_resourcetype(R_CHARGER); + if (!rt_horse) { + rt_horse = get_resourcetype(R_HORSE); + } + fig->horses = rt_horse ? i_get(u->items, rt_horse->itype) : 0; + fig->elvenhorses = rt_elvenhorse ? i_get(u->items, rt_elvenhorse->itype) : 0; + } + + if (u_race(u)->battle_flags & BF_EQUIPMENT) { + for (itm = u->items; itm; itm = itm->next) { + if (itm->type->rtype->atype) { + if (i_canuse(u, itm->type)) { + struct armor *adata = (struct armor *)malloc(sizeof(armor)), **aptr; + adata->atype = itm->type->rtype->atype; + adata->count = itm->number; + for (aptr = &fig->armors; *aptr; aptr = &(*aptr)->next) { + if (adata->atype->prot > (*aptr)->atype->prot) { + break; + } + } + adata->next = *aptr; + *aptr = adata; + } + } + } + } + + /* Jetzt muss noch geschaut werden, wo die Einheit die jeweils besten + * Werte hat, das kommt aber erst irgendwo spaeter. Ich entscheide + * waehrend des Kampfes, welche ich nehme, je nach Gegner. Deswegen auch + * keine addierten boni. */ + + /* Zuerst mal die Spezialbehandlung gewisser Sonderfaelle. */ + fig->magic = effskill(u, SK_MAGIC, NULL); + + if (fig->horses) { + if (!fval(r->terrain, CAVALRY_REGION) || r_isforest(r) + || effskill(u, SK_RIDING, NULL) < CavalrySkill() + || u_race(u) == get_race(RC_TROLL) || fval(u, UFL_WERE)) + fig->horses = 0; + } + + if (fig->elvenhorses) { + if (effskill(u, SK_RIDING, NULL) < 5 || u_race(u) == get_race(RC_TROLL) + || fval(u, UFL_WERE)) + fig->elvenhorses = 0; + } + + /* Schauen, wie gut wir in Taktik sind. */ + if (tactics > 0 && u_race(u) == get_race(RC_INSECT)) + tactics -= 1 - (int)log10(fig->side->size[SUM_ROW]); +#ifdef TACTICS_MODIFIER + if (tactics > 0 && statusrow(fig->status) == FIGHT_ROW) + tactics += TACTICS_MODIFIER; + if (tactics > 0 && statusrow(fig->status) > BEHIND_ROW) { + tactics -= TACTICS_MODIFIER; + } +#endif + + if (tactics > 0) { + int bonus = 0; + + for (i = 0; i < fig->alive; i++) { + int p_bonus = 0; + int rnd; + + do { + rnd = (int)(rng_int() % 100); + if (rnd >= 40 && rnd <= 69) + p_bonus += 1; + else if (rnd <= 89) + p_bonus += 2; + else + p_bonus += 3; + } while (rnd >= 97); + if (p_bonus > bonus) p_bonus = bonus; + } + tactics += bonus; + } + + add_tactics(&fig->side->leader, fig, tactics); + ++b->nfighters; + return fig; + } + + int join_battle(battle * b, unit * u, bool attack, fighter ** cp) + { + side *s; + fighter *fc = NULL; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + if (s->faction == u->faction) { + for (fig = s->fighters; fig; fig = fig->next) { + if (fig->unit == u) { + fc = fig; + if (attack) { + set_attacker(fig); + } + break; + } + } + } + } + if (!fc) { + *cp = make_fighter(b, u, NULL, attack); + return *cp != NULL; + } + *cp = fc; + return false; + } + + battle *make_battle(region * r) + { + unit *u; + bfaction *bf; + building * bld; + battle *b = (battle *)calloc(1, sizeof(battle)); + + assert(b); + /* Alle Mann raus aus der Burg! */ + for (bld = r->buildings; bld != NULL; bld = bld->next) + bld->sizeleft = bld->size; + + b->region = r; + b->plane = getplane(r); + /* Finde alle Parteien, die den Kampf beobachten koennen: */ + for (u = r->units; u; u = u->next) { + if (u->number > 0) { + if (!fval(u->faction, FFL_MARK)) { + fset(u->faction, FFL_MARK); + for (bf = b->factions; bf; bf = bf->next) { + if (bf->faction == u->faction) + break; + } + if (!bf) { + bf = (bfaction *)calloc(1, sizeof(bfaction)); + assert(bf); + ++b->nfactions; + bf->faction = u->faction; + bf->next = b->factions; + b->factions = bf; + } + } + } + } + + for (bf = b->factions; bf; bf = bf->next) { + faction *f = bf->faction; + freset(f, FFL_MARK); + } + return b; + } + + static void free_side(side * si) + { + selist_free(si->leader.fighters); + } + + static void free_fighter(fighter * fig) + { + armor **ap = &fig->armors; + while (*ap) { + armor *a = *ap; + *ap = a->next; + free(a); + } + while (fig->loot) { + i_free(i_remove(&fig->loot, fig->loot)); + } + free(fig->person); + free(fig->weapons); + + } + + static void battle_free(battle * b) { + side *s; + + assert(b); + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter **fp = &s->fighters; + while (*fp) { + fighter *fig = *fp; + *fp = fig->next; + free_fighter(fig); + free(fig); + } + s->fighters = NULL; + free_side(s); + } + free(b); + } + + void free_battle(battle * b) + { + while (b->factions) { + bfaction *bf = b->factions; + b->factions = bf->next; + free(bf); + } + + selist_free(b->leaders); + selist_foreach(b->meffects, free); + selist_free(b->meffects); + + battle_free(b); + } + + static int *get_alive(side * s) + { + return s->size; + } + + static int battle_report(battle * b) + { + side *s, *s2; + bool cont = false; + bfaction *bf; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (s->alive - s->removed > 0) { + for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { + if (s2->alive - s2->removed > 0 && enemy(s, s2)) { + cont = true; + break; + } + } + if (cont) + break; + } + } + + fflush(stdout); + + for (bf = b->factions; bf; bf = bf->next) { + faction *fac = bf->faction; + char buf[32 * MAXSIDES]; + message *m; + sbstring sbs; + bool komma = false; + + sbs_init(&sbs, buf, sizeof(buf)); + + if (cont) + m = msg_message("para_lineup_battle", "turn", b->turn); + else + m = msg_message("para_after_battle", ""); + battle_message_faction(b, fac, m); + msg_release(m); + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (s->alive) { + int r, k = 0, *alive = get_alive(s); + int l = FIGHT_ROW; + const char *abbrev = seematrix(fac, s) ? sideabkz(s, false) : "-?-"; + const char *loc_army = LOC(fac->locale, "battle_army"); + char buffer[32]; + + if (komma) { + sbs_strcat(&sbs, ", "); + } + snprintf(buffer, sizeof(buffer), "%s %2d(%s): ", + loc_army, army_index(s), abbrev); + sbs_strcat(&sbs, buffer); + + for (r = FIGHT_ROW; r != NUMROWS; ++r) { + if (alive[r]) { + if (l != FIGHT_ROW) { + sbs_strcat(&sbs, "+"); + } + while (k--) { + sbs_strcat(&sbs, "0+"); + } + sprintf(buffer, "%d", alive[r]); + sbs_strcat(&sbs, buffer); + + k = 0; + l = r + 1; + } + else + ++k; + } + + komma = true; + } + } + fbattlerecord(b, fac, buf); + } + return cont; + } + + static void join_allies(battle * b) + { + region *r = b->region; + unit *u; + side *s, *s_end = b->sides + b->nsides; + /* make_side might be adding a new faction, but it adds them to the end + * of the list, so we're safe in our iteration here if we remember the end + * up front. */ + for (u = r->units; u; u = u->next) { + /* Was ist mit Schiffen? */ + if (u->status != ST_FLEE && u->status != ST_AVOID + && !fval(u, UFL_LONGACTION | UFL_ISNEW) && u->number > 0) { + faction *f = u->faction; + fighter *c = NULL; + + for (s = b->sides; s != s_end; ++s) { + side *se; + /* Wenn alle attackierten noch FFL_NOAID haben, dann kaempfe nicht mit. */ + if (fval(s->faction, FFL_NOAID)) + continue; + if (s->faction != f) { + /* Wenn wir attackiert haben, kommt niemand mehr hinzu: */ + if (s->bf->attacker) + continue; + /* alliiert muessen wir schon sein, sonst ist's eh egal : */ + if (!alliedunit(u, s->faction, HELP_FIGHT)) + continue; + /* wenn die partei verborgen ist, oder gar eine andere + * vorgespiegelt wird, und er sich uns gegenueber nicht zu + * erkennen gibt, helfen wir ihm nicht */ + if (s->stealthfaction) { + if (!alliedside(s, u->faction, HELP_FSTEALTH)) { + continue; + } + } + } + /* einen alliierten angreifen duerfen sie nicht, es sei denn, der + * ist mit einem alliierten verfeindet, der nicht attackiert + * hat: */ + for (se = b->sides; se != s_end; ++se) { + if (u->faction == se->faction) + continue; + if (alliedunit(u, se->faction, HELP_FIGHT) && !se->bf->attacker) { + continue; + } + if (enemy(s, se)) + break; + } + if (se == s_end) + continue; + /* keine Einwaende, also soll er mitmachen: */ + if (c == NULL) { + if (!join_battle(b, u, false, &c)) { + continue; + } + } + + /* the enemy of my friend is my enemy: */ + for (se = b->sides; se != s_end; ++se) { + if (se->faction != u->faction && enemy(s, se)) { + set_enemy(se, c->side, false); + } + } + } + } + } + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + int si; + side *sa; + faction *f = s->faction; + + /* Den Feinden meiner Feinde gebe ich Deckung (gegen gemeinsame Feinde): */ + for (si = 0; s->enemies[si]; ++si) { + side *se = s->enemies[si]; + int ai; + for (ai = 0; se->enemies[ai]; ++ai) { + side *as = se->enemies[ai]; + if (as == s || !enemy(as, s)) { + set_friendly(as, s); + } + } + } + + for (sa = s + 1; sa != b->sides + b->nsides; ++sa) { + if (!enemy(s, sa) && !friendly(s, sa)) { + if (alliedfaction(f, sa->faction, HELP_FIGHT)) { + if (alliedfaction(sa->faction, f, HELP_FIGHT)) { + set_friendly(s, sa); + } + } + } + } + } + } + + static void flee(const troop dt) + { + fighter *fig = dt.fighter; + unit *u = fig->unit; + int fchance = fleechance(u); + + if (fig->person[dt.index].flags & FL_PANICED) { + fchance += EFFECT_PANIC_SPELL; + } + if (fchance > flee_chance_max_percent) { + fchance = flee_chance_max_percent; + } + if (rng_int() % 100 < fchance) { + fig->run.hp += fig->person[dt.index].hp; + ++fig->run.number; + + setguard(u, false); + kill_troop(dt); + } + } + + static bool is_calmed(const unit *u, const faction *f) { + attrib *a = a_find(u->attribs, &at_curse); + + while (a && a->type == &at_curse) { + curse *c = (curse *)a->data.v; + if (c->type == &ct_calmmonster && curse_geteffect_int(c) == f->uid) { + if (curse_active(c)) { + return true; + } + } + a = a->next; + } + return false; + } + + static bool start_battle(region * r, battle ** bp) + { + battle *b = NULL; + unit *u; + bool fighting = false; + + for (u = r->units; u != NULL; u = u->next) { + if (fval(u, UFL_LONGACTION)) + continue; + if (u->number > 0) { + order *ord; + + for (ord = u->orders; ord; ord = ord->next) { + if (getkeyword(ord) == K_ATTACK) { + unit *u2; + fighter *c1, *c2; + ship *lsh = NULL; + plane *pl = rplane(r); + + if (pl && fval(pl, PFL_NOATTACK)) { + cmistake(u, ord, 271, MSG_BATTLE); + continue; + } + + if (u_race(u)->battle_flags & BF_NO_ATTACK) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "race_no_attack", + "race", u_race(u))); + continue; + } + /** + ** Fehlerbehandlung Angreifer + **/ + if (LongHunger(u)) { + cmistake(u, ord, 225, MSG_BATTLE); + continue; + } + + if (u->status == ST_AVOID || u->status == ST_FLEE) { + cmistake(u, ord, 226, MSG_BATTLE); + continue; + } + + /* ist ein Fluechtling aus einem andern Kampf */ + if (fval(u, UFL_LONGACTION)) + continue; + + if (curse_active(get_curse(r->attribs, &ct_peacezone))) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "peace_active", "")); + continue; + } + + if (curse_active(get_curse(u->attribs, &ct_slavery))) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "slave_active", "")); + continue; + } + + if ((u->ship != NULL && !fval(r->terrain, SEA_REGION)) + || (lsh = leftship(u)) != NULL) { + if (is_guarded(r, u)) { + if (lsh) { + cmistake(u, ord, 234, MSG_BATTLE); + } + else { + /* Fehler: "Das Schiff muss erst verlassen werden" */ + cmistake(u, ord, 19, MSG_BATTLE); + } + continue; + } + } + + /* Ende Fehlerbehandlung Angreifer */ + + init_order_depr(ord); + /* attackierte Einheit ermitteln */ + getunit(r, u->faction, &u2); + + /* Beginn Fehlerbehandlung */ + /* Fehler: "Die Einheit wurde nicht gefunden" */ + if (!u2 || u2->number == 0 || !cansee(u->faction, u->region, u2, 0)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, + "feedback_unit_not_found", "")); + continue; + } + /* Fehler: "Die Einheit ist eine der unsrigen" */ + if (u2->faction == u->faction) { + cmistake(u, ord, 45, MSG_BATTLE); + continue; + } + /* Fehler: "Die Einheit ist mit uns alliert" */ + if (alliedunit(u, u2->faction, HELP_FIGHT)) { + cmistake(u, ord, 47, MSG_BATTLE); + continue; + } + if (IsImmune(u2->faction)) { + add_message(&u->faction->msgs, + msg_feedback(u, u->thisorder, "newbie_immunity_error", "turns", + NewbieImmunity())); + continue; + } + + /* Fehler: "Die Einheit ist mit uns alliert" */ + if (is_calmed(u, u2->faction)) { + cmistake(u, ord, 47, MSG_BATTLE); + continue; + } + /* Ende Fehlerbehandlung */ + if (b == NULL) { + unit *utmp; + for (utmp = r->units; utmp != NULL; utmp = utmp->next) { + fset(utmp->faction, FFL_NOAID); + } + b = make_battle(r); + } + join_battle(b, u, true, &c1); + join_battle(b, u2, false, &c2); + + if (u2->attribs) { + if (it_mistletoe) { + int effect = get_effect(u2, it_mistletoe); + if (effect >= u->number) { + change_effect(u2, it_mistletoe, -u2->number); + c2->run.hp = u2->hp; + c2->run.number = u2->number; + c2->side->flee += u2->number; + setguard(u2, false); + rmfighter(c2, u2->number); + } + } + } + + /* Hat die attackierte Einheit keinen Noaid-Status, + * wird das Flag von der Faction genommen, andere + * Einheiten greifen ein. */ + if (!fval(u2, UFL_NOAID)) + freset(u2->faction, FFL_NOAID); + + if (c1 && c2 && c2->run.number < c2->unit->number) { + /* Merken, wer Angreifer ist, fuer die Rueckzahlung der + * Praecombataura bei kurzem Kampf. */ + c1->side->bf->attacker = true; + + set_enemy(c1->side, c2->side, true); + fighting = true; + } + } + } + } + } + *bp = b; + return fighting; + } + + /** execute one round of attacks + * fig->fighting is used to determine who attacks, not fig->alive, since + * the latter may be influenced by attacks that already took place. + */ + static void battle_attacks(battle * b) + { + side *s; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + + if (b->turn != 0 || (b->max_tactics > 0 + && get_tactics(s, NULL) == b->max_tactics)) { + for (fig = s->fighters; fig; fig = fig->next) { + + /* ist in dieser Einheit noch jemand handlungsfaehig? */ + if (fig->fighting <= 0) + continue; + + /* Handle the unit's attack on someone */ + do_attack(fig); + } + } + } + } + + /** updates the number of attacking troops in each fighter struct. + * this has to be calculated _before_ the actual attacks take + * place because otherwise dead troops would not strike in the + * round they die. */ + static void battle_update(battle * b) + { + side *s; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + for (fig = s->fighters; fig; fig = fig->next) { + fig->fighting = fig->alive - fig->removed; + } + } + } + + /** attempt to flee from battle before the next round begins + * there's a double attempt before the first round, but only + * one attempt before round zero, the potential tactics round. */ + static void battle_flee(battle * b) + { + int attempt, flee_ops = 1; + + if (b->turn == 1) + flee_ops = 2; + + for (attempt = 1; attempt <= flee_ops; ++attempt) { + side *s; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + for (fig = s->fighters; fig; fig = fig->next) { + unit *u = fig->unit; + troop dt; + /* Flucht nicht bei mehr als 600 HP. Damit Wyrme toetbar bleiben. */ + int runhp = (int)(0.9 + unit_max_hp(u) * hpflee(u->status)); + if (runhp > 600) runhp = 600; + + if (u->ship && fval(u->region->terrain, SEA_REGION)) { + /* keine Flucht von Schiffen auf hoher See */ + continue; + } + if (fval(u_race(u), RCF_UNDEAD) || u_race(u) == get_race(RC_SHADOWKNIGHT)) { + /* Untote fliehen nicht. Warum eigentlich? */ + continue; + } + + dt.fighter = fig; + dt.index = fig->alive - fig->removed; + while (s->size[SUM_ROW] && dt.index != 0) { + --dt.index; + assert(dt.index >= 0 && dt.index < fig->unit->number); + assert(fig->person[dt.index].hp > 0); + + /* Versuche zu fliehen, wenn + * - Kampfstatus fliehe + * - schwer verwundet und nicht erste kampfrunde + * - in panik (Zauber) + * aber nicht, wenn der Zaubereffekt Held auf dir liegt! + */ + switch (u->status) { + case ST_FLEE: + break; + default: + if ((fig->person[dt.index].flags & FL_HIT) == 0) + continue; + if (fig->person[dt.index].hp <= runhp) + break; + if (fig->person[dt.index].flags & FL_PANICED) { + if ((fig->person[dt.index].flags & FL_COURAGE) == 0) + break; + } + continue; + } + flee(dt); + } + } + } + } + } + + static bool is_enemy(battle *b, unit *u1, unit *u2) { + if (u1->faction != u2->faction) { + if (b) { + side *es, *s1 = 0, *s2 = 0; + for (es = b->sides; es != b->sides + b->nsides; ++es) { + if (!s1 && es->faction == u1->faction) s1 = es; + else if (!s2 && es->faction == u2->faction) s2 = es; + if (s1 && s2) { + return enemy(s1, s2); + } + } + } + else { + return !help_enter(u1, u2); + } + } + return false; + } + + void force_leave(region *r, battle *b) { + unit *u; + + for (u = r->units; u; u = u->next) { + unit *uo = NULL; + if (u->building) { + uo = building_owner(u->building); + } + if (u->ship && r->land) { + uo = ship_owner(u->ship); + } + if (uo && is_enemy(b, uo, u)) { + message *msg = NULL; + if (u->building) { + msg = msg_message("force_leave_building", "unit owner building", u, uo, u->building); + } + else { + msg = msg_message("force_leave_ship", "unit owner ship", u, uo, u->ship); + } + if (msg) { + ADDMSG(&u->faction->msgs, msg); + } + leave(u, false); } - leave(u, false); } } -} -static void do_battle(region * r) { - battle *b = NULL; - bool fighting; - ship *sh; + static void do_battle(region * r) { + battle *b = NULL; + bool fighting; + ship *sh; - fighting = start_battle(r, &b); + fighting = start_battle(r, &b); - if (b == NULL) - return; + if (b == NULL) + return; + + /* Bevor wir die alliierten hineinziehen, sollten wir schauen, * + * Ob jemand fliehen kann. Dann eruebrigt sich das ganze ja + * vielleicht schon. */ + report_battle_start(b); + if (!fighting) { + /* Niemand mehr da, Kampf kann nicht stattfinden. */ + message *m = msg_message("aborted_battle", ""); + message_all(b, m); + msg_release(m); + free_battle(b); + return; + } + join_allies(b); + make_heroes(b); + + /* make sure no ships are damaged initially */ + for (sh = r->ships; sh; sh = sh->next) + freset(sh, SF_DAMAGED); + + /* Gibt es eine Taktikrunde ? */ + if (!selist_empty(b->leaders)) { + b->turn = 0; + b->has_tactics_turn = true; + } + else { + b->turn = 1; + b->has_tactics_turn = false; + } + + /* PRECOMBATSPELLS */ + do_combatmagic(b, DO_PRECOMBATSPELL); + + print_stats(b); /* gibt die Kampfaufstellung aus */ + log_debug("battle in %s (%d, %d) : ", regionname(r, 0), r->x, r->y); + + for (; battle_report(b) && b->turn <= max_turns; ++b->turn) { + battle_flee(b); + battle_update(b); + battle_attacks(b); + + } + + /* Auswirkungen berechnen: */ + aftermath(b); + if (rule_force_leave(FORCE_LEAVE_POSTCOMBAT)) { + force_leave(b->region, b); + } + /* Hier ist das Gefecht beendet, und wir koennen die + * Hilfsstrukturen * wieder loeschen: */ - /* Bevor wir die alliierten hineinziehen, sollten wir schauen, * - * Ob jemand fliehen kann. Dann eruebrigt sich das ganze ja - * vielleicht schon. */ - report_battle_start(b); - if (!fighting) { - /* Niemand mehr da, Kampf kann nicht stattfinden. */ - message *m = msg_message("aborted_battle", ""); - message_all(b, m); - msg_release(m); free_battle(b); - return; - } - join_allies(b); - make_heroes(b); - - /* make sure no ships are damaged initially */ - for (sh = r->ships; sh; sh = sh->next) - freset(sh, SF_DAMAGED); - - /* Gibt es eine Taktikrunde ? */ - if (!selist_empty(b->leaders)) { - b->turn = 0; - b->has_tactics_turn = true; - } - else { - b->turn = 1; - b->has_tactics_turn = false; } - /* PRECOMBATSPELLS */ - do_combatmagic(b, DO_PRECOMBATSPELL); - - print_stats(b); /* gibt die Kampfaufstellung aus */ - log_debug("battle in %s (%d, %d) : ", regionname(r, 0), r->x, r->y); - - for (; battle_report(b) && b->turn <= max_turns; ++b->turn) { - battle_flee(b); - battle_update(b); - battle_attacks(b); - + void do_battles(void) { + region *r; + init_rules(); + for (r = regions; r; r = r->next) { + do_battle(r); + } } - - /* Auswirkungen berechnen: */ - aftermath(b); - if (rule_force_leave(FORCE_LEAVE_POSTCOMBAT)) { - force_leave(b->region, b); - } - /* Hier ist das Gefecht beendet, und wir koennen die - * Hilfsstrukturen * wieder loeschen: */ - - free_battle(b); -} - -void do_battles(void) { - region *r; - init_rules(); - for (r = regions; r; r = r->next) { - do_battle(r); - } -} diff --git a/src/battle.h b/src/battle.h index 1b0962eeb..738fe928c 100644 --- a/src/battle.h +++ b/src/battle.h @@ -121,7 +121,6 @@ extern "C" { } battle; typedef struct weapon { - int count, used; const struct weapon_type *type; int attackskill; int defenseskill; diff --git a/src/battle.test.c b/src/battle.test.c index be43a8ff6..b38513db5 100644 --- a/src/battle.test.c +++ b/src/battle.test.c @@ -96,6 +96,7 @@ static void test_make_fighter(CuTest * tc) static void test_select_weapon_restricted(CuTest *tc) { item_type *itype; + weapon_type * wtype; unit *au; fighter *af; battle *b; @@ -104,7 +105,7 @@ static void test_select_weapon_restricted(CuTest *tc) { test_setup(); au = test_create_unit(test_create_faction(NULL), test_create_plain(0, 0)); itype = test_create_itemtype("halberd"); - new_weapontype(itype, 0, frac_zero, NULL, 0, 0, 0, SK_MELEE); + wtype = new_weapontype(itype, 0, frac_zero, NULL, 0, 0, 0, SK_MELEE); i_change(&au->items, itype, 1); rc = test_create_race("smurf"); CuAssertIntEquals(tc, 0, rc->mask_item & au->_race->mask_item); @@ -112,15 +113,15 @@ static void test_select_weapon_restricted(CuTest *tc) { b = make_battle(au->region); af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); CuAssertPtrNotNull(tc, af->weapons); - CuAssertIntEquals(tc, 1, af->weapons[0].count); - CuAssertIntEquals(tc, 0, af->weapons[1].count); + CuAssertPtrEquals(tc, wtype, (void *)af->weapons[0].type); + CuAssertPtrEquals(tc, NULL, (void *)af->weapons[1].type); free_battle(b); itype->mask_deny = rc_mask(au->_race); b = make_battle(au->region); af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); CuAssertPtrNotNull(tc, af->weapons); - CuAssertIntEquals(tc, 0, af->weapons[0].count); + CuAssertPtrEquals(tc, NULL, (void *)af->weapons[0].type); free_battle(b); itype->mask_deny = 0; @@ -128,9 +129,8 @@ static void test_select_weapon_restricted(CuTest *tc) { b = make_battle(au->region); af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); CuAssertPtrNotNull(tc, af->weapons); - CuAssertIntEquals(tc, 1, af->weapons[0].count); - CuAssertPtrEquals(tc, itype->rtype->wtype, (void *)af->weapons[0].type); - CuAssertIntEquals(tc, 0, af->weapons[1].count); + CuAssertPtrEquals(tc, wtype, (void *)af->weapons[0].type); + CuAssertPtrEquals(tc, NULL, (void *)af->weapons[1].type); free_battle(b); itype->mask_deny = 0; @@ -138,7 +138,7 @@ static void test_select_weapon_restricted(CuTest *tc) { b = make_battle(au->region); af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); CuAssertPtrNotNull(tc, af->weapons); - CuAssertIntEquals(tc, 0, af->weapons[0].count); + CuAssertPtrEquals(tc, NULL, (void *)af->weapons[0].type); free_battle(b); itype->mask_deny = 0; @@ -146,8 +146,8 @@ static void test_select_weapon_restricted(CuTest *tc) { b = make_battle(au->region); af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); CuAssertPtrNotNull(tc, af->weapons); - CuAssertIntEquals(tc, 1, af->weapons[0].count); - CuAssertIntEquals(tc, 0, af->weapons[1].count); + CuAssertPtrEquals(tc, wtype, (void *)af->weapons[0].type); + CuAssertPtrEquals(tc, NULL, (void *)af->weapons[1].type); free_battle(b); test_teardown(); diff --git a/src/spells/combatspells.c b/src/spells/combatspells.c index 65bfff59b..2eae5dc6a 100644 --- a/src/spells/combatspells.c +++ b/src/spells/combatspells.c @@ -298,35 +298,35 @@ int sp_combatrosthauch(struct castorder * co) for (w = 0; df->weapons[w].type != NULL; ++w) { weapon *wp = df->weapons; - int n = force; - if (n > wp->used) n = wp->used; - if (n) { - requirement *mat = wp->type->itype->construction->materials; - bool iron = false; - while (mat && mat->number > 0) { - if (mat->rtype == get_resourcetype(R_IRON)) { - iron = true; - break; - } - mat++; - } - if (iron) { - int p; - force -= n; - wp->used -= n; - k += n; - i_change(&df->unit->items, wp->type->itype, -n); - for (p = 0; n && p != df->unit->number; ++p) { - if (df->person[p].missile == wp) { - df->person[p].missile = NULL; - --n; - } - } - for (p = 0; n && p != df->unit->number; ++p) { - if (df->person[p].melee == wp) { - df->person[p].melee = NULL; - --n; + if (df->unit->items && force > 0) { + item ** itp = i_find(&df->unit->items, wp->type->itype); + if (*itp) { + item *it = *itp; + requirement *mat = wp->type->itype->construction->materials; + int n = force; + if (it->number < n) n = it->number; + + while (mat && mat->number > 0) { + if (mat->rtype == get_resourcetype(R_IRON)) { + int p; + force -= n; + k += n; + i_change(itp, wp->type->itype, -n); + for (p = 0; n && p != df->unit->number; ++p) { + if (df->person[p].melee == wp) { + df->person[p].melee = NULL; + --n; + } + } + for (p = 0; n && p != df->unit->number; ++p) { + if (df->person[p].missile == wp) { + df->person[p].missile = NULL; + --n; + } + } + break; } + mat++; } } }