From 9950071e5d7f1b7bb6620697210103dc80b0082b Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 27 Aug 2023 20:38:19 +0200 Subject: [PATCH 01/29] version increase, 28.4 dev begins --- src/kernel/version.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kernel/version.c b/src/kernel/version.c index beac576bb..f6f22a88e 100644 --- a/src/kernel/version.c +++ b/src/kernel/version.c @@ -8,7 +8,7 @@ #ifndef ERESSEA_VERSION /* the version number, if it was not passed to make with -D */ -#define ERESSEA_VERSION "28.3.0" +#define ERESSEA_VERSION "28.4.0" #endif const char *eressea_version(void) { From 2a0c8c0629727aed7aacc1ace368b519194549cc Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Wed, 30 Aug 2023 21:37:09 +0200 Subject: [PATCH 02/29] fix some static analysis warnings --- src/spells.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/spells.c b/src/spells.c index 7f539ea9f..859d46dd2 100644 --- a/src/spells.c +++ b/src/spells.c @@ -2593,11 +2593,11 @@ static int sp_firewall(castorder * co) int cast_level = co->level; double force = co->force; spellparameter *pa = co->par; - direction_t dir; + int dir; region *r2; - dir = get_direction(pa->param[0]->data.xs, caster->faction->locale); - if (dir < MAXDIRECTIONS && dir != NODIRECTION) { + dir = (int) get_direction(pa->param[0]->data.xs, caster->faction->locale); + if (dir >= 0) { r2 = rconnect(r, dir); } else { @@ -3587,16 +3587,16 @@ static int sp_song_susceptmagic(castorder * co) static int sp_rallypeasantmob(castorder * co) { - unit *u, *un; int erfolg = 0; region *r = co_get_region(co); unit *mage = co_get_caster(co); int cast_level = co->level; + unit *u = r->units; message *msg; curse *c; - for (u = r->units; u; u = un) { - un = u->next; + while (u) { + unit *un = u->next; if (is_monsters(u->faction) && u_race(u) == get_race(RC_PEASANT)) { rsetpeasants(r, rpeasants(r) + u->number); rsetmoney(r, rmoney(r) + get_money(u)); @@ -3605,6 +3605,7 @@ static int sp_rallypeasantmob(castorder * co) set_number(u, 0); erfolg = cast_level; } + u = un; } c = get_curse(r->attribs, &ct_riotzone); From eb0477584c975e2ca5014790486b4fa5c09b7c88 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 1 Sep 2023 21:34:01 +0200 Subject: [PATCH 03/29] rewrite equip_weapons --- src/battle.c | 281 ++++++++++++++++++-------------------- src/battle.h | 8 +- src/battle.test.c | 88 ++++++++---- src/items/weapons.c | 12 +- src/spells/combatspells.c | 11 +- 5 files changed, 211 insertions(+), 189 deletions(-) diff --git a/src/battle.c b/src/battle.c index 43cf12f71..b277a1124 100644 --- a/src/battle.c +++ b/src/battle.c @@ -11,6 +11,13 @@ #include "study.h" #include "spy.h" +#include "reports.h" + +/* attributes includes */ +#include "attributes/key.h" +#include "attributes/racename.h" +#include "attributes/otherfaction.h" + #include "spells/buildingcurse.h" #include "spells/regioncurse.h" #include "spells/unitcurse.h" @@ -36,13 +43,6 @@ #include "kernel/unit.h" #include "kernel/spell.h" -#include "reports.h" - -/* attributes includes */ -#include "attributes/key.h" -#include "attributes/racename.h" -#include "attributes/otherfaction.h" - /* util includes */ #include "kernel/attrib.h" #include "util/base36.h" @@ -68,6 +68,8 @@ #include #include +#include + #define TACTICS_BONUS 1 /* when undefined, we have a tactics round. else this is the bonus tactics give */ #define TACTICS_MODIFIER 1 /* modifier for generals in the front/rear */ @@ -510,10 +512,10 @@ static bool is_riding(const troop t) return false; } -static weapon *preferred_weapon(const troop t, bool attacking) +static const weapon *preferred_weapon(const troop t, bool attacking) { - weapon *missile = t.fighter->person[t.index].missile; - weapon *melee = t.fighter->person[t.index].melee; + const weapon *missile = t.fighter->person[t.index].missile; + const weapon *melee = t.fighter->person[t.index].melee; if (attacking) { if (melee == NULL || (missile && missile->attackskill > melee->attackskill)) { return missile; @@ -528,22 +530,26 @@ static weapon *preferred_weapon(const troop t, bool attacking) return melee; } -weapon *select_weapon(const troop t, bool attacking, bool ismissile) +const struct weapon *select_weapon(const troop t, bool attacking, bool ismissile) /* select the primary weapon for this trooper */ { + const weapon *w = NULL; if (attacking) { if (ismissile) { /* from the back rows, have to use your missile weapon */ - return t.fighter->person[t.index].missile; + w = t.fighter->person[t.index].missile; } } else { if (!ismissile) { /* have to use your melee weapon if it's melee */ - return t.fighter->person[t.index].melee; + w = t.fighter->person[t.index].melee; } } - return preferred_weapon(t, attacking); + if (!w) { + w = preferred_weapon(t, attacking); + } + return w; } static bool i_canuse(const unit * u, const item_type * itype) @@ -657,7 +663,7 @@ weapon_effskill(troop t, troop enemy, const weapon * w, /* Alle Modifier berechnen, die fig durch die Waffen bekommt. */ if (w) { int skill = 0; - const weapon_type *wtype = w->type; + const weapon_type *wtype = WEAPON_TYPE(w); if (attacking) { skill = w->attackskill; @@ -1052,7 +1058,7 @@ 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; - + if (!magic) return damage; @@ -1159,7 +1165,7 @@ static void calculate_defense_type(troop at, troop dt, int type, bool missile, 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; + *dwtype = WEAPON_TYPE(weapon); } static void calculate_attack_type(troop at, troop dt, int type, bool missile, @@ -1171,7 +1177,7 @@ static void calculate_attack_type(troop at, troop dt, int type, bool missile, weapon = select_weapon(at, true, missile); *attskill = weapon_effskill(at, dt, weapon, true, missile); if (weapon) - *awtype = weapon->type; + *awtype = WEAPON_TYPE(weapon); if (*awtype && fval(*awtype, WTF_MAGICAL)) *magic = true; break; @@ -1441,24 +1447,18 @@ troop select_enemy(fighter * af, int minrow, int maxrow, int select) for (si = 0; as->enemies[si]; ++si) { side *ds = as->enemies[si]; fighter *df; - int unitrow[NUMROWS]; + int unitrow[NUMROWS] = { 0 }; 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; - } - for (df = ds->fighters; df; df = df->next) { int dr; dr = statusrow(df->status); if (select & SELECT_ADVANCE) { - if (unitrow[dr] < 0) { + if (unitrow[dr] == 0) { unitrow[dr] = get_unitrow(df, as); } dr = unitrow[dr]; @@ -1612,9 +1612,7 @@ static bool select_row(const side *vs, const fighter *fig, void *cbdata) selist *fighters(battle * b, const side * vs, int minrow, int maxrow, int mask) { - struct selector sel; - sel.maxrow = maxrow; - sel.minrow = minrow; + struct selector sel = { .maxrow = maxrow, .minrow = minrow }; return select_fighters(b, vs, mask, select_row, &sel); } @@ -1853,9 +1851,10 @@ 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); + const weapon *awp = select_weapon(at, true, dist > 1); static int rc_cache; static const race *rc_halfling, *rc_goblin; + const weapon_type *wtype; if (rc_changed(&rc_cache)) { rc_halfling = get_race(RC_HALFLING); @@ -1903,21 +1902,22 @@ int skilldiff(troop at, troop dt, int dist) } /* Effekte der Waffen */ skdiff += weapon_effskill(at, dt, awp, true, dist > 1); - if (awp && fval(awp->type, WTF_MISSILE)) { + wtype = awp ? WEAPON_TYPE(awp) : NULL; + if (wtype && fval(wtype, WTF_MISSILE)) { skdiff -= is_protected; - if (awp->type->modifiers) { + if (wtype->modifiers) { int w; - for (w = 0; awp->type->modifiers[w].value != 0; ++w) { - if (awp->type->modifiers[w].flags & WMF_MISSILE_TARGET) { + for (w = 0; wtype->modifiers[w].value != 0; ++w) { + if (wtype->modifiers[w].flags & WMF_MISSILE_TARGET) { /* skill decreases by targeting difficulty (bow -2, catapult -4) */ - skdiff -= awp->type->modifiers[w].value; + skdiff -= wtype->modifiers[w].value; break; } } } } if (skill_formula == FORMULA_ORIG) { - weapon *dwp = select_weapon(dt, false, dist > 1); + const weapon *dwp = select_weapon(dt, false, dist > 1); skdiff -= weapon_effskill(dt, at, dwp, false, dist > 1); } return skdiff; @@ -1926,7 +1926,7 @@ int skilldiff(troop at, troop dt, int dist) static int setreload(troop at) { fighter *af = at.fighter; - const weapon_type *wtype = af->person[at.index].missile->type; + const weapon_type *wtype = WEAPON_TYPE(af->person[at.index].missile); if (wtype->reload == 0) return 0; return af->person[at.index].reload = wtype->reload; @@ -1937,20 +1937,20 @@ int getreload(troop at) return at.fighter->person[at.index].reload; } -bool hits(troop at, troop dt, weapon * awp) +bool hits(troop at, troop dt, const weapon_type *awp) { fighter *af = at.fighter, *df = dt.fighter; const armor_type *armor, *shield = NULL; int skdiff = 0; const int dist = get_unitrow(af, df->side) + get_unitrow(df, af->side) - 1; const bool missiles_only = dist > 1; - weapon *dwp = select_weapon(dt, false, missiles_only); + const weapon_type *dwp; if (!df->alive) return false; if (getreload(at)) return false; - if (missiles_only && (awp == NULL || !fval(awp->type, WTF_MISSILE))) + if (missiles_only && (awp == NULL || !fval(awp, WTF_MISSILE))) return false; /* mark this person as hit. */ @@ -1965,17 +1965,18 @@ bool hits(troop at, troop dt, weapon * awp) return false; /* effect of sp_reeling_arrows combatspell */ - if (af->side->battle->reelarrow && awp && fval(awp->type, WTF_MISSILE) + if (af->side->battle->reelarrow && awp && fval(awp, WTF_MISSILE) && rng_double() < 0.5) { return false; } - skdiff = skilldiff(at, dt, dist); /* Verteidiger bekommt eine Ruestung */ - armor = select_armor(dt, true); - if (dwp == NULL || (dwp->type->flags & WTF_USESHIELD)) { + dwp = WEAPON_TYPE(select_weapon(dt, false, missiles_only)); + if (dwp == NULL || (dwp->flags & WTF_USESHIELD)) { shield = select_armor(dt, false); } + armor = select_armor(dt, true); + skdiff = skilldiff(at, dt, dist); if (!contest(skdiff, dt, armor, shield)) { return false; } @@ -2104,15 +2105,20 @@ static void attack(battle * b, troop ta, const att * a, int numattack) ta.fighter->person[ta.index].reload--; } else { - weapon* wp = ta.fighter->person[ta.index].missile; + const weapon* wp = ta.fighter->person[ta.index].missile; + const weapon_type *wtype = NULL; bool missile = false; if (count_enemies(b, af, melee_range[0], melee_range[1], SELECT_ADVANCE | SELECT_DISTANCE | SELECT_FIND) > 0) { wp = preferred_weapon(ta, true); } - if (wp && fval(wp->type, WTF_MISSILE)) - missile = true; + if (wp) { + wtype = WEAPON_TYPE(wp); + if (fval(wtype, WTF_MISSILE)) { + missile = true; + } + } if (missile) { td = select_opponent(b, ta, missile_range[0], missile_range[1]); } @@ -2124,29 +2130,29 @@ static void attack(battle * b, troop ta, const att * a, int numattack) if (ta.fighter->person[ta.index].last_action < b->turn) { ta.fighter->person[ta.index].last_action = b->turn; } - if (hits(ta, td, wp)) { + if (hits(ta, td, wtype)) { const char* d; - if (wp == NULL) + if (wtype == NULL) d = u_race(au)->def_damage; else if (is_riding(ta)) - d = wp->type->damage[1]; + d = wtype->damage[1]; else - d = wp->type->damage[0]; + d = wtype->damage[0]; terminate(td, ta, a->type, d, missile); } /* spezialattacken der waffe nur, wenn erste attacke in der runde. * sonst helden mit feuerschwertern zu maechtig */ - if (numattack == 0 && wp && wp->type->attack) { + if (numattack == 0 && wtype && wtype->attack) { int dead = 0; - if (wp->type->attack(&ta, wp->type, &dead)) { + if (wtype->attack(&ta, wtype, &dead)) { af->catmsg += dead; if (af->person[ta.index].last_action < b->turn) { af->person[ta.index].last_action = b->turn; } } } - if (wp && wp->type->reload && !getreload(ta)) { + if (wtype && wtype->reload && !getreload(ta)) { setreload(ta); } } @@ -2221,12 +2227,10 @@ static void attack(battle * b, troop ta, const att * a, int numattack) static void do_attack(fighter * af) { - troop ta; unit *au = af->unit; side *side = af->side; battle *b = side->battle; - - ta.fighter = af; + troop ta = { .fighter = af, .index = af->fighting }; assert(au && au->number); /* Da das Zuschlagen auf Einheiten und nicht auf den einzelnen @@ -2234,7 +2238,6 @@ static void do_attack(fighter * af) * 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 @@ -2253,8 +2256,9 @@ static void do_attack(fighter * af) if (u_race(au)->attack[a].type != AT_STANDARD) continue; else { - weapon *wp = preferred_weapon(ta, true); - if (wp != NULL && wp->type->reload) + const weapon *wp = preferred_weapon(ta, true); + const weapon_type *wtype = WEAPON_TYPE(wp); + if (wp != NULL && wtype->reload) continue; } } @@ -2400,10 +2404,7 @@ troop select_ally(fighter * af, int minrow, int maxrow, int allytype) 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; + troop dt = { .fighter = df, .index = allies }; return dt; } allies -= (df->alive - df->removed); @@ -2551,7 +2552,7 @@ static void reorder_fleeing(region * r) unit **usrc = &r->units; unit **udst = &r->units; unit *ufirst = NULL; - unit *u; + unit *u = NULL; for (; *udst; udst = &u->next) { u = *udst; @@ -2902,7 +2903,8 @@ static void print_stats(battle * b) bfaction *bf; for (bf = b->factions; bf; bf = bf->next) { - char buf[1024], *bufp = buf; + static char buf[1024]; + char *bufp = buf; size_t rsize, size = sizeof(buf); int komma = 0; faction *f = bf->faction; @@ -2931,6 +2933,7 @@ static void print_stats(battle * b) fbattlerecord(b, f, buf); bufp = buf; + buf[0] = 0; size = sizeof(buf); komma = 0; header = LOC(f->locale, "battle_helpers"); @@ -3011,12 +3014,9 @@ static void print_stats(battle * b) } } -static int weapon_weight(const weapon * w, bool missile) +static int weapon_weight(const weapon * w) { - if (missile == !!(fval(w->type, WTF_MISSILE))) { - return w->attackskill + w->defenseskill; - } - return 0; + return w->attackskill + w->defenseskill; } side * get_side(battle * b, const struct unit * u) @@ -3074,6 +3074,19 @@ static int tactics_bonus(int num) { return bonus; } +typedef struct weight_s { + unsigned int index; + int weight; + bool missile; +} weight_s; + +static int cmp_weight(const void *lhs, const void *rhs) +{ + const weight_s *a = (const weight_s *)lhs; + const weight_s *b = (const weight_s *)rhs; + return b->weight - a->weight; +} + /* Fuer alle Waffengattungen wird bestimmt, wie viele der Personen mit * ihr kaempfen koennten, und was ihr Wert darin ist. */ static void equip_weapons(fighter* fig) @@ -3081,87 +3094,61 @@ static void equip_weapons(fighter* fig) #define WMAX 20 item* itm; unit* u = fig->unit; - weapon weapons[WMAX]; - int owp[WMAX]; - int dwp[WMAX]; - int wcount[WMAX]; - int wused[WMAX]; - int i, oi = 0, di = 0, w = 0; + int wpless = weapon_skill(NULL, u, true); + unsigned int w = 0; + static weight_s index[WMAX]; + int p_melee = 0, p_missile = 0, i; + fig->weapons = NULL; for (itm = u->items; itm && w != WMAX; itm = itm->next) { - const weapon_type* wtype = resource2weapon(itm->type->rtype); - if (wtype == NULL || itm->number == 0) + weapon wp; + 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; + } + wp.attackskill = weapon_skill(wtype, u, true); + wp.defenseskill = weapon_skill(wtype, u, false); + wp.item = itm; + arrput(fig->weapons, wp); + if (wp.attackskill >= 0 || wp.defenseskill >= 0) { + assert(w < WMAX); + index[w].index = w; + index[w].weight = weapon_weight(&wp); + index[w].missile = fval(wtype, WTF_MISSILE); ++w; } - assert(w != WMAX); - } - fig->weapons = malloc((1 + (size_t)w) * sizeof(weapon)); - if (fig->weapons) { - memcpy(fig->weapons, weapons, w * sizeof(weapon)); - fig->weapons[w].type = NULL; - 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]]; + } + if (w == 0) { + /* this unit has no useful wepons */ + return; + } + qsort(index, w, sizeof(weight_s), cmp_weight); + + /* now fig->weapons[index[0].index].item is the units' best weapon */ + + /* hand out weapons: */ + for (i = 0; i != w; ++i) { + int idx = index[i].index; + int count = fig->weapons[idx].item->number; + while (count > 0 && (p_missile < fig->alive || p_melee < fig->alive)) { + if (index[i].missile) { + if (p_missile < fig->alive) { + struct person *p = fig->person + fig->alive - ++p_missile; + p->missile = fig->weapons + idx; + --count; + } + } + else { + if (p_melee < fig->alive) { + struct person *p = fig->person + p_melee++; + p->melee = fig->weapons + idx; + --count; + } + } } } } + static void equip_armor(fighter* fig) { item* itm; @@ -3451,7 +3438,7 @@ static void free_fighter(fighter * fig) } i_freeall(&fig->loot); free(fig->person); - free(fig->weapons); + arrfree(fig->weapons); } @@ -3910,7 +3897,7 @@ static void battle_flee(battle * b) fighter *fig; for (fig = s->fighters; fig; fig = fig->next) { unit *u = fig->unit; - troop dt; + troop dt = { .fighter = fig, .index = fig->alive - fig->removed }; /* 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; @@ -3920,8 +3907,6 @@ static void battle_flee(battle * b) 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); diff --git a/src/battle.h b/src/battle.h index 6bfb3c2b7..903a86ac5 100644 --- a/src/battle.h +++ b/src/battle.h @@ -95,11 +95,13 @@ typedef struct battle { } battle; typedef struct weapon { - const struct weapon_type* type; + const struct item* item; int attackskill; int defenseskill; } weapon; +#define WEAPON_TYPE(wp) ((wp)->item->type->rtype->wtype) + typedef struct troop { struct fighter* fighter; int index; @@ -195,13 +197,13 @@ int count_enemies(struct battle* b, const struct fighter* af, int minrow, int maxrow, int select); int natural_armor(struct unit* u); const struct armor_type* select_armor(struct troop t, bool shield); -struct weapon* select_weapon(const struct troop t, bool attacking, bool ismissile); +const struct weapon* select_weapon(const struct troop t, bool attacking, bool ismissile); int calculate_armor(troop dt, const struct weapon_type* dwtype, const struct weapon_type* awtype, const struct armor_type* armor, const struct armor_type* shield, bool magic); int apply_resistance(int damage, struct troop dt, const struct weapon_type* dwtype, const struct armor_type* armor, const struct armor_type* shield, bool magic); bool terminate(troop dt, troop at, int type, const char* damage, bool missile); void message_all(battle* b, struct message* m); -bool hits(troop at, troop dt, weapon* awp); +bool hits(troop at, troop dt, const struct weapon_type *awp); void damage_building(struct battle* b, struct building* bldg, int damage_abs); diff --git a/src/battle.test.c b/src/battle.test.c index 0e398e4b5..42351a0b7 100644 --- a/src/battle.test.c +++ b/src/battle.test.c @@ -34,6 +34,8 @@ #include +#include + #include #include @@ -98,9 +100,43 @@ static void test_make_fighter(CuTest * tc) test_teardown(); } +static void test_select_weapon(CuTest *tc) { + item_type *it_missile, *it_axe, *it_sword; + item *i_missile, *i_sword, *i_axe; + unit *au; + fighter *af; + battle *b; + + test_setup(); + au = test_create_unit(test_create_faction(), test_create_plain(0, 0)); + set_number(au, 3); + set_level(au, SK_MELEE, 1); + it_axe = test_create_itemtype("axe"); + new_weapontype(it_axe, 0, frac_zero, NULL, 1, 0, 0, SK_MELEE); + i_axe = i_change(&au->items, it_axe, 1); + it_sword = test_create_itemtype("sword"); + new_weapontype(it_sword, 0, frac_zero, NULL, 0, 0, 0, SK_MELEE); + i_sword = i_change(&au->items, it_sword, 1); + it_missile = test_create_itemtype("crossbow"); + new_weapontype(it_missile, WTF_MISSILE, frac_zero, NULL, 0, 0, 0, SK_CROSSBOW); + i_missile = i_change(&au->items, it_missile, 2); + + b = make_battle(au->region); + af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); + CuAssertIntEquals(tc, 3, (int)arrlen(af->weapons)); + CuAssertPtrEquals(tc, i_axe, (item *)af->person[0].melee->item); + CuAssertPtrEquals(tc, NULL, (weapon *)af->person[0].missile); + CuAssertPtrEquals(tc, i_sword, (item *)af->person[1].melee->item); + CuAssertPtrEquals(tc, i_missile, (item *)af->person[1].missile->item); + CuAssertPtrEquals(tc, NULL, (weapon *)af->person[2].melee); + CuAssertPtrEquals(tc, i_missile, (item *)af->person[2].missile->item); + free_battle(b); + + test_teardown(); +} + static void test_select_weapon_restricted(CuTest *tc) { item_type *itype; - weapon_type * wtype; unit *au; fighter *af; battle *b; @@ -109,49 +145,48 @@ static void test_select_weapon_restricted(CuTest *tc) { test_setup(); au = test_create_unit(test_create_faction(), test_create_plain(0, 0)); itype = test_create_itemtype("halberd"); - wtype = new_weapontype(itype, 0, frac_zero, NULL, 0, 0, 0, SK_MELEE); + 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); + /* melee weapon, can be used by any race: */ b = make_battle(au->region); af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); - CuAssertPtrNotNull(tc, af->weapons); - CuAssertPtrEquals(tc, wtype, (void *)af->weapons[0].type); - CuAssertPtrEquals(tc, NULL, (void *)af->weapons[1].type); + CuAssertIntEquals(tc, 1, (int)arrlen(af->weapons)); + CuAssertPtrEquals(tc, au->items, (void *)af->weapons[0].item); + CuAssertPtrEquals(tc, af->weapons, (void *)af->person[0].melee); free_battle(b); + /* weapon is for denied to our race: */ 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); - CuAssertPtrEquals(tc, NULL, (void *)af->weapons[0].type); + CuAssertIntEquals(tc, 1, (int)arrlen(af->weapons)); + CuAssertPtrEquals(tc, au->items, (void *)af->weapons[0].item); + CuAssertPtrNotNull(tc, af->person); + CuAssertPtrEquals(tc, NULL, (void *)af->person[0].melee); free_battle(b); + /* weapon is for exclusive use by our race: */ itype->mask_deny = 0; itype->mask_allow = 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); - CuAssertPtrEquals(tc, wtype, (void *)af->weapons[0].type); - CuAssertPtrEquals(tc, NULL, (void *)af->weapons[1].type); + CuAssertIntEquals(tc, 1, (int)arrlen(af->weapons)); + CuAssertPtrEquals(tc, au->items, (void *)af->weapons[0].item); + CuAssertPtrEquals(tc, af->weapons, (void *)af->person[0].melee); free_battle(b); + /* weapon is for exclusive use by another race: */ itype->mask_deny = 0; itype->mask_allow = rc_mask(rc); b = make_battle(au->region); af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); - CuAssertPtrNotNull(tc, af->weapons); - CuAssertPtrEquals(tc, NULL, (void *)af->weapons[0].type); - free_battle(b); - - itype->mask_deny = 0; - itype->mask_allow = 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); - CuAssertPtrEquals(tc, wtype, (void *)af->weapons[0].type); - CuAssertPtrEquals(tc, NULL, (void *)af->weapons[1].type); + CuAssertIntEquals(tc, 1, (int)arrlen(af->weapons)); + CuAssertPtrEquals(tc, au->items, (void *)af->weapons[0].item); + CuAssertPtrEquals(tc, NULL, (void *)af->person[0].melee); free_battle(b); test_teardown(); @@ -369,9 +404,11 @@ static int test_armor(troop dt, weapon_type *awtype, bool magic) { } static int test_resistance(troop dt) { - return apply_resistance(1000, dt, - select_weapon(dt, false, true) ? select_weapon(dt, false, true)->type : 0, - select_armor(dt, false), select_armor(dt, true), true); + const weapon *dw = select_weapon(dt, false, true); + return apply_resistance(1000, dt, + dw ? WEAPON_TYPE(dw) : NULL, + select_armor(dt, false), + select_armor(dt, true), true); } static void test_calculate_armor(CuTest * tc) @@ -505,9 +542,7 @@ static void test_magic_resistance(CuTest *tc) ashield->magres = v10p; CuAssertIntEquals_Msg(tc, "laen reduction => 10%%", 900, test_resistance(dt)); CuAssertIntEquals_Msg(tc, "no magic, no resistance", 1000, - apply_resistance(1000, dt, - select_weapon(dt, false, true) ? select_weapon(dt, false, true)->type : 0, - select_armor(dt, false), select_armor(dt, true), false)); + test_resistance(dt)); free_battle(b); b = NULL; @@ -969,6 +1004,7 @@ CuSuite *get_battle_suite(void) { CuSuite *suite = CuSuiteNew(); SUITE_ADD_TEST(suite, test_make_fighter); + SUITE_ADD_TEST(suite, test_select_weapon); SUITE_ADD_TEST(suite, test_select_weapon_restricted); SUITE_ADD_TEST(suite, test_select_armor); SUITE_ADD_TEST(suite, test_battle_fleeing); diff --git a/src/items/weapons.c b/src/items/weapons.c index 96b9cd3ed..35a10e7ec 100644 --- a/src/items/weapons.c +++ b/src/items/weapons.c @@ -43,7 +43,7 @@ int *casualties) message *msg; for (i = 0; i <= at->index; ++i) { struct weapon *wp = fi->person[i].melee; - if (wp != NULL && wp->type == wtype) + if (wp != NULL && wp->item->type == wtype->itype) ++k; } msg = msg_message("useflamingsword", "amount unit", k, fi->unit); @@ -75,7 +75,6 @@ int *casualties) battle *b = af->side->battle; troop dt; int d = 0, enemies; - weapon *wp; const resource_type *rtype; if (au->status >= ST_AVOID) { @@ -83,8 +82,7 @@ int *casualties) return false; } - wp = af->person[at->index].missile; - assert(wp->type == wtype); + assert(wtype == WEAPON_TYPE(af->person[at->index].missile)); assert(af->person[at->index].reload == 0); rtype = rt_find("catapultammo"); @@ -105,7 +103,7 @@ int *casualties) message *msg; for (i = 0; i <= at->index; ++i) { - if (af->person[i].reload == 0 && af->person[i].missile == wp) + if (af->person[i].reload == 0 && af->person[i].missile->item->type == wtype->itype) ++k; } msg = msg_message("usecatapult", "amount unit", k, au); @@ -125,9 +123,9 @@ int *casualties) break; /* If battle succeeds */ - if (hits(*at, dt, wp)) { + if (hits(*at, dt, wtype)) { int chance_pct = config_get_int("rules.catapult.damage.chance_percent", 5); - d += terminate(dt, *at, AT_STANDARD, wp->type->damage[0], true); + d += terminate(dt, *at, AT_STANDARD, wtype->damage[0], true); structural_damage(dt, 0, chance_pct); } } diff --git a/src/spells/combatspells.c b/src/spells/combatspells.c index 83c1bca2d..1bdbbe0c9 100644 --- a/src/spells/combatspells.c +++ b/src/spells/combatspells.c @@ -281,15 +281,16 @@ int sp_combatrosthauch(struct castorder * co) for (qi = 0, ql = fgs; force>0 && ql; selist_advance(&ql, &qi, 1)) { fighter *df = (fighter *)selist_get(ql, qi); - int w; + unsigned int w; + size_t len = arrlen(df->weapons); - for (w = 0; df->weapons[w].type != NULL; ++w) { + for (w = 0; w != len; ++w) { weapon *wp = df->weapons; if (df->unit->items && force > 0) { - item ** itp = i_find(&df->unit->items, wp->type->itype); + item ** itp = i_find(&df->unit->items, wp->item->type); if (*itp) { item *it = *itp; - requirement *mat = wp->type->itype->construction->materials; + requirement *mat = wp->item->type->construction->materials; int n = force; if (it->number < n) n = it->number; @@ -298,7 +299,7 @@ int sp_combatrosthauch(struct castorder * co) int p; force -= n; k += n; - i_change(itp, wp->type->itype, -n); + i_change(itp, wp->item->type, -n); for (p = 0; n && p != df->unit->number; ++p) { if (df->person[p].melee == wp) { df->person[p].melee = NULL; From e9343cc6deaf73f726ccc5dbec695a922e39a06d Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 1 Sep 2023 22:08:22 +0200 Subject: [PATCH 04/29] simplify equip_weapons fix resistance tests --- src/battle.c | 83 ++++++++++++++++++++------------------------- src/battle.h | 4 +-- src/battle.test.c | 24 ++++++------- src/items/weapons.c | 2 +- 4 files changed, 52 insertions(+), 61 deletions(-) diff --git a/src/battle.c b/src/battle.c index b277a1124..ed90e5386 100644 --- a/src/battle.c +++ b/src/battle.c @@ -3074,75 +3074,66 @@ static int tactics_bonus(int num) { return bonus; } -typedef struct weight_s { - unsigned int index; - int weight; - bool missile; -} weight_s; - -static int cmp_weight(const void *lhs, const void *rhs) +static int cmp_weapon(const void *lhs, const void *rhs) { - const weight_s *a = (const weight_s *)lhs; - const weight_s *b = (const weight_s *)rhs; - return b->weight - a->weight; + const weapon *a = (const weapon *)lhs; + const weapon *b = (const weapon *)rhs; + return b->attackskill - a->attackskill + b->defenseskill - a->defenseskill; } /* Fuer alle Waffengattungen wird bestimmt, wie viele der Personen mit * ihr kaempfen koennten, und was ihr Wert darin ist. */ static void equip_weapons(fighter* fig) { -#define WMAX 20 item* itm; unit* u = fig->unit; int wpless = weapon_skill(NULL, u, true); - unsigned int w = 0; - static weight_s index[WMAX]; + size_t w; int p_melee = 0, p_missile = 0, i; fig->weapons = NULL; - for (itm = u->items; itm && w != WMAX; itm = itm->next) { - weapon wp; + for (itm = u->items; itm; itm = itm->next) { + weapon * wp; const weapon_type *wtype = resource2weapon(itm->type->rtype); if (wtype == NULL || itm->number == 0) { continue; } - wp.attackskill = weapon_skill(wtype, u, true); - wp.defenseskill = weapon_skill(wtype, u, false); - wp.item = itm; - arrput(fig->weapons, wp); - if (wp.attackskill >= 0 || wp.defenseskill >= 0) { - assert(w < WMAX); - index[w].index = w; - index[w].weight = weapon_weight(&wp); - index[w].missile = fval(wtype, WTF_MISSILE); - ++w; - } + wp = arraddnptr(fig->weapons, 1); + wp->attackskill = weapon_skill(wtype, u, true); + wp->defenseskill = weapon_skill(wtype, u, false); + wp->item = itm; } - if (w == 0) { - /* this unit has no useful wepons */ - return; - } - qsort(index, w, sizeof(weight_s), cmp_weight); + w = arrlen(fig->weapons); + qsort(fig->weapons, w, sizeof(weapon), cmp_weapon); - /* now fig->weapons[index[0].index].item is the units' best weapon */ + /* now fig->weapons[0].item is the unit's best weapon */ /* hand out weapons: */ for (i = 0; i != w; ++i) { - int idx = index[i].index; - int count = fig->weapons[idx].item->number; - while (count > 0 && (p_missile < fig->alive || p_melee < fig->alive)) { - if (index[i].missile) { - if (p_missile < fig->alive) { - struct person *p = fig->person + fig->alive - ++p_missile; - p->missile = fig->weapons + idx; - --count; + const weapon *wp = fig->weapons + i; + const weapon_type *wtype = WEAPON_TYPE(wp); + bool is_missile = wtype->flags & WTF_MISSILE; + if (!is_missile && wpless > wp->attackskill + wp->defenseskill) { + /* we fight better with bare hands than this melee weapon */ + continue; + } + if (wp->attackskill >= 0 || wp->defenseskill >= 0) + { + int count = wp->item->number; + while (count > 0 && (p_missile < fig->alive || p_melee < fig->alive)) { + if (is_missile) { + if (p_missile < fig->alive) { + struct person *p = fig->person + fig->alive - ++p_missile; + p->missile = wp; + --count; + } } - } - else { - if (p_melee < fig->alive) { - struct person *p = fig->person + p_melee++; - p->melee = fig->weapons + idx; - --count; + else { + if (p_melee < fig->alive) { + struct person *p = fig->person + p_melee++; + p->melee = wp; + --count; + } } } } diff --git a/src/battle.h b/src/battle.h index 903a86ac5..0ded45edb 100644 --- a/src/battle.h +++ b/src/battle.h @@ -144,8 +144,8 @@ typedef struct fighter { unsigned char speed; unsigned char reload; unsigned char last_action; - struct weapon* missile; /* missile weapon */ - struct weapon* melee; /* melee weapon */ + const struct weapon* missile; /* missile weapon */ + const struct weapon* melee; /* melee weapon */ } *person; unsigned int flags; struct { diff --git a/src/battle.test.c b/src/battle.test.c index 42351a0b7..7790b234b 100644 --- a/src/battle.test.c +++ b/src/battle.test.c @@ -403,12 +403,12 @@ static int test_armor(troop dt, weapon_type *awtype, bool magic) { return calculate_armor(dt, 0, awtype, select_armor(dt, false), select_armor(dt, true), magic); } -static int test_resistance(troop dt) { +static int test_resistance(troop dt, bool magic) { const weapon *dw = select_weapon(dt, false, true); return apply_resistance(1000, dt, dw ? WEAPON_TYPE(dw) : NULL, select_armor(dt, false), - select_armor(dt, true), true); + select_armor(dt, true), magic); } static void test_calculate_armor(CuTest * tc) @@ -438,7 +438,7 @@ static void test_calculate_armor(CuTest * tc) dt.fighter = setup_fighter(&b, du); CuAssertIntEquals_Msg(tc, "default ac", 0, test_armor(dt, 0, false)); - CuAssertIntEquals_Msg(tc, "magres unmodified", 1000, test_resistance(dt)); + CuAssertIntEquals_Msg(tc, "magres unmodified", 1000, test_resistance(dt, true)); free_battle(b); b = NULL; @@ -474,13 +474,13 @@ static void test_calculate_armor(CuTest * tc) CuAssertIntEquals_Msg(tc, "magical attack", 3, test_armor(dt, wtype, true)); CuAssertIntEquals_Msg(tc, "magres unmodified", 1000, - test_resistance(dt)); + test_resistance(dt, true)); ashield->flags |= ATF_LAEN; achain->flags |= ATF_LAEN; CuAssertIntEquals_Msg(tc, "laen armor", 3, test_armor(dt, wtype, true)); - CuAssertIntEquals_Msg(tc, "laen magres bonus", 250, test_resistance(dt)); + CuAssertIntEquals_Msg(tc, "laen magres bonus", 250, test_resistance(dt, true)); free_battle(b); test_teardown(); } @@ -534,15 +534,15 @@ static void test_magic_resistance(CuTest *tc) i_change(&du->items, ishield, 1); dt.fighter = setup_fighter(&b, du); - CuAssertIntEquals_Msg(tc, "no magres reduction", 1000, test_resistance(dt)); + CuAssertIntEquals_Msg(tc, "no magres reduction", 1000, test_resistance(dt, true)); magres = magic_resistance(du); CuAssertIntEquals_Msg(tc, "no magres reduction", 0, magres.sa[0]); ashield->flags |= ATF_LAEN; ashield->magres = v10p; - CuAssertIntEquals_Msg(tc, "laen reduction => 10%%", 900, test_resistance(dt)); + CuAssertIntEquals_Msg(tc, "laen reduction => 10%%", 900, test_resistance(dt, true)); CuAssertIntEquals_Msg(tc, "no magic, no resistance", 1000, - test_resistance(dt)); + test_resistance(dt, false)); free_battle(b); b = NULL; @@ -552,7 +552,7 @@ static void test_magic_resistance(CuTest *tc) ashield->flags |= ATF_LAEN; ashield->magres = v10p; dt.fighter = setup_fighter(&b, du); - CuAssertIntEquals_Msg(tc, "2x laen reduction => 81%%", 810, test_resistance(dt)); + CuAssertIntEquals_Msg(tc, "2x laen reduction => 81%%", 810, test_resistance(dt, true)); free_battle(b); b = NULL; @@ -560,18 +560,18 @@ static void test_magic_resistance(CuTest *tc) i_change(&du->items, ichain, -1); set_level(du, SK_MAGIC, 2); dt.fighter = setup_fighter(&b, du); - CuAssertIntEquals_Msg(tc, "skill reduction => 90%%", 900, test_resistance(dt)); + CuAssertIntEquals_Msg(tc, "skill reduction => 90%%", 900, test_resistance(dt, true)); magres = magic_resistance(du); CuAssert(tc, "skill reduction", frac_equal(magres, v10p)); rc->magres = v50p; /* percentage, gets added to skill bonus */ - CuAssertIntEquals_Msg(tc, "race reduction => 40%%", 400, test_resistance(dt)); + CuAssertIntEquals_Msg(tc, "race reduction => 40%%", 400, test_resistance(dt, true)); magres = magic_resistance(du); CuAssert(tc, "race bonus => 60%%", frac_equal(magres, frac_make(60, 100))); rc->magres = frac_make(15, 10); /* 150% resistance should not cause negative damage multiplier */ magres = magic_resistance(du); CuAssert(tc, "magic resistance is never > 0.9", frac_equal(magres, frac_make(9, 10))); - CuAssertIntEquals_Msg(tc, "damage reduction is never < 0.1", 100, test_resistance(dt)); + CuAssertIntEquals_Msg(tc, "damage reduction is never < 0.1", 100, test_resistance(dt, true)); free_battle(b); test_teardown(); diff --git a/src/items/weapons.c b/src/items/weapons.c index 35a10e7ec..168bd61f5 100644 --- a/src/items/weapons.c +++ b/src/items/weapons.c @@ -42,7 +42,7 @@ int *casualties) int i, k = 0; message *msg; for (i = 0; i <= at->index; ++i) { - struct weapon *wp = fi->person[i].melee; + const weapon *wp = fi->person[i].melee; if (wp != NULL && wp->item->type == wtype->itype) ++k; } From eee05ef8e91fe9975534ae06c37de944f7c106ff Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 1 Sep 2023 22:38:35 +0200 Subject: [PATCH 05/29] break infinite loops fix null-pointer crash --- src/battle.c | 45 ++++++++++++++++++++++++++++++++++++--------- src/battle.h | 2 +- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/battle.c b/src/battle.c index ed90e5386..cb7e8b90a 100644 --- a/src/battle.c +++ b/src/battle.c @@ -516,15 +516,19 @@ static const weapon *preferred_weapon(const troop t, bool attacking) { const weapon *missile = t.fighter->person[t.index].missile; const weapon *melee = t.fighter->person[t.index].melee; - if (attacking) { - if (melee == NULL || (missile && missile->attackskill > melee->attackskill)) { - return missile; - } + if (!melee) { + return missile; } - else { - if (melee == NULL || (missile - && missile->defenseskill > melee->defenseskill)) { - return missile; + if (missile) { + if (attacking) { + if (missile && missile->attackskill > melee->attackskill) { + return missile; + } + } + else { + if (missile && missile->defenseskill > melee->defenseskill) { + return missile; + } } } return melee; @@ -3112,11 +3116,26 @@ static void equip_weapons(fighter* fig) for (i = 0; i != w; ++i) { const weapon *wp = fig->weapons + i; const weapon_type *wtype = WEAPON_TYPE(wp); - bool is_missile = wtype->flags & WTF_MISSILE; + bool is_missile; + assert(wtype); + + is_missile = (wtype->flags & WTF_MISSILE); if (!is_missile && wpless > wp->attackskill + wp->defenseskill) { /* we fight better with bare hands than this melee weapon */ continue; } + if (is_missile) { + if (p_missile == fig->alive) { + /* everyone already has a missile weapon */ + continue; + } + } + else { + if (p_melee == fig->alive) { + /* everyone already has a melee weapon */ + continue; + } + } if (wp->attackskill >= 0 || wp->defenseskill >= 0) { int count = wp->item->number; @@ -3127,6 +3146,10 @@ static void equip_weapons(fighter* fig) p->missile = wp; --count; } + else { + /* everyone already has a missile weapon */ + break; + } } else { if (p_melee < fig->alive) { @@ -3134,6 +3157,10 @@ static void equip_weapons(fighter* fig) p->melee = wp; --count; } + else { + /* everyone already has a melee weapon */ + break; + } } } } diff --git a/src/battle.h b/src/battle.h index 0ded45edb..9392c6529 100644 --- a/src/battle.h +++ b/src/battle.h @@ -100,7 +100,7 @@ typedef struct weapon { int defenseskill; } weapon; -#define WEAPON_TYPE(wp) ((wp)->item->type->rtype->wtype) +#define WEAPON_TYPE(wp) ((wp) ? (wp)->item->type->rtype->wtype : NULL) typedef struct troop { struct fighter* fighter; From 68dfdda7404d2a86c83312ec3dee41efef226375 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 1 Sep 2023 22:56:05 +0200 Subject: [PATCH 06/29] fix Linux compilation --- s/build | 6 +----- src/battle.c | 9 ++------- src/battle.h | 1 + src/tests.c | 1 - 4 files changed, 4 insertions(+), 13 deletions(-) diff --git a/s/build b/s/build index f47ff2d95..3e0a52ffe 100755 --- a/s/build +++ b/s/build @@ -2,11 +2,7 @@ ROOT=$(git rev-parse --show-toplevel) BUILD=build if [ -z "$JOBS" ] ; then - if [ -e /usr/sbin/sysctl ]; then - JOBS=`sysctl -n hw.ncpu` - else - JOBS=`nproc` - fi + JOBS=`nproc` fi DISTCC=`which distcc` if [ ! -z "$DISTCC" ] ; then diff --git a/src/battle.c b/src/battle.c index cb7e8b90a..8a039ab4b 100644 --- a/src/battle.c +++ b/src/battle.c @@ -3018,11 +3018,6 @@ static void print_stats(battle * b) } } -static int weapon_weight(const weapon * w) -{ - return w->attackskill + w->defenseskill; -} - side * get_side(battle * b, const struct unit * u) { side * s; @@ -3092,8 +3087,8 @@ static void equip_weapons(fighter* fig) item* itm; unit* u = fig->unit; int wpless = weapon_skill(NULL, u, true); - size_t w; - int p_melee = 0, p_missile = 0, i; + size_t w, i; + int p_melee = 0, p_missile = 0; fig->weapons = NULL; for (itm = u->items; itm; itm = itm->next) { diff --git a/src/battle.h b/src/battle.h index 9392c6529..84ff3694b 100644 --- a/src/battle.h +++ b/src/battle.h @@ -6,6 +6,7 @@ struct message; struct selist; +struct weapon_type; union variant; /** more defines **/ diff --git a/src/tests.c b/src/tests.c index 23125b1cb..1ddd80519 100644 --- a/src/tests.c +++ b/src/tests.c @@ -48,7 +48,6 @@ #include #include -#include #include // for true #include // for fprintf, stderr #include From 9a31ded57d1306b2b05d961301870896c86f52ff Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 3 Sep 2023 11:01:39 +0200 Subject: [PATCH 07/29] imyu tests.c --- src/tests.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tests.c b/src/tests.c index 1ddd80519..23125b1cb 100644 --- a/src/tests.c +++ b/src/tests.c @@ -48,6 +48,7 @@ #include #include +#include #include // for true #include // for fprintf, stderr #include From 1ae00505a8c943d53b3dc48ff430c070816d6fc9 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Mon, 4 Sep 2023 20:51:56 +0200 Subject: [PATCH 08/29] testing cursed and notlost behavior some mild refactoring, but no real changes. --- res/core/common/items.xml | 2 +- res/eressea/items.xml | 4 +- scripts/tests/pool.lua | 10 +-- src/battle.c | 27 ++++--- src/battle.h | 3 +- src/battle.test.c | 164 ++++++++++++++++++++++++++++++++++++++ src/spells/combatspells.c | 2 +- 7 files changed, 193 insertions(+), 19 deletions(-) diff --git a/res/core/common/items.xml b/res/core/common/items.xml index 67f2c1119..d9b01680b 100644 --- a/res/core/common/items.xml +++ b/res/core/common/items.xml @@ -35,7 +35,7 @@ - + diff --git a/res/eressea/items.xml b/res/eressea/items.xml index ea6ab047d..5bec46908 100644 --- a/res/eressea/items.xml +++ b/res/eressea/items.xml @@ -24,7 +24,7 @@ - + @@ -74,7 +74,7 @@ - + diff --git a/scripts/tests/pool.lua b/scripts/tests/pool.lua index c5331c8ad..6a70f8185 100644 --- a/scripts/tests/pool.lua +++ b/scripts/tests/pool.lua @@ -12,7 +12,7 @@ function setup() eressea.settings.set("rules.magic.playerschools", "") conf = [[{ "races": { - "human" : { "flags" : [ "giveitem", "getitem" ] } + "human" : { "flags" : [ "getitem" ] } }, "terrains" : { "plain": { "flags" : [ "land" ] } @@ -35,7 +35,7 @@ end function test_give_nopool() local r = region.create(1, 1, "plain") - local f = faction.create("human", "test@example.com", "de") + local f = faction.create("human") local u1 = unit.create(f, r, 1) local u2 = unit.create(f, r, 1) u1:add_item("money", 100) @@ -47,7 +47,7 @@ end function test_give_from_faction() local r = region.create(1, 1, "plain") - local f = faction.create("human", "test@example.com", "de") + local f = faction.create("human") local u1 = unit.create(f, r, 1) local u2 = unit.create(f, r, 1) local u3 = unit.create(f, r, 1) @@ -64,8 +64,8 @@ function test_give_divisor() eressea.settings.set("rules.items.give_divisor", 2) eressea.settings.set("GiveRestriction", 0) local r = region.create(1, 1, "plain") - local f1 = faction.create("human", "test@example.com", "de") - local f2 = faction.create("human", "test@example.com", "de") + local f1 = faction.create("human") + local f2 = faction.create("human") local u1 = unit.create(f1, r, 1) local u2 = unit.create(f2, r, 1) u2:add_order("KONTAKTIERE " .. itoa36(u1.id)) diff --git a/src/battle.c b/src/battle.c index 8a039ab4b..195fc3ff3 100644 --- a/src/battle.c +++ b/src/battle.c @@ -827,7 +827,7 @@ bool meffect_blocked(battle * b, meffect * s, side * as) /* rmfighter wird schon im PRAECOMBAT gebraucht, da gibt es noch keine * troops */ -void rmfighter(fighter * df, int i) +void reduce_fighter(fighter * df, int i) { side *ds = df->side; @@ -853,12 +853,22 @@ void rmfighter(fighter * df, int i) df->alive -= i; } +void flee_all(fighter *fig) +{ + unit *u = fig->unit; + fig->run.hp = u->hp; + fig->run.number = u->number; + fig->side->flee += u->number; + setguard(u, false); + reduce_fighter(fig, u->number); +} + static void rmtroop(troop dt) { fighter *df = dt.fighter; /* troop ist immer eine einzelne Person */ - rmfighter(df, 1); + reduce_fighter(df, 1); assert(dt.index >= 0 && dt.index < df->unit->number); if (dt.index != df->alive - df->removed) { @@ -1150,7 +1160,7 @@ static void destroy_items(troop dt) { for (pitm = &du->items; *pitm;) { item *itm = *pitm; const item_type *itype = itm->type; - if (!(itype->flags & (ITF_CURSED | ITF_NOTLOST)) && dt.index < itm->number) { + if (!(itype->flags & ITF_NOTLOST) && dt.index < itm->number) { /* 25% Grundchance, dass ein Item kaputtgeht. */ if (rng_int() % 4 < 1) { i_change(pitm, itype, -1); @@ -2424,6 +2434,9 @@ static int loot_quota(const unit * src, const unit * dst, const item_type * type, int n) { UNUSED_ARG(type); + if (loot_divisor <= 0) { + return 0; + } if (dst && src && src->faction != dst->faction) { assert(loot_divisor <= 0 || loot_divisor >= 1); if (loot_divisor > 1) { @@ -2466,7 +2479,7 @@ void loot_items(fighter * corpse) int looting = 0; int maxrow = 0; /* mustloot: we absolutely, positively must have somebody loot this thing */ - bool mustloot = 0 != (itm->type->flags & (ITF_CURSED | ITF_NOTLOST)); + bool mustloot = 0 != (itm->type->flags & (ITF_CURSED|ITF_NOTLOST)); item_add(itm, -loot); maxloot -= loot; @@ -3822,11 +3835,7 @@ static bool start_battle(region * r, battle ** bp) 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); + flee_all(c2); } } } diff --git a/src/battle.h b/src/battle.h index 84ff3694b..3929b6336 100644 --- a/src/battle.h +++ b/src/battle.h @@ -212,11 +212,12 @@ typedef bool(*select_fun)(const struct side* vs, const struct fighter* fig, void struct selist* select_fighters(struct battle* b, const struct side* vs, int mask, select_fun cb, void* cbdata); struct selist* fighters(struct battle* b, const struct side* vs, int minrow, int maxrow, int mask); +void flee_all(struct fighter *fig); int count_allies(const struct side* as, int minrow, int maxrow, int select, int allytype); bool helping(const struct side* as, const struct side* ds); -void rmfighter(fighter* df, int i); +void reduce_fighter(fighter* df, int i); struct fighter* select_corpse(struct battle* b, struct fighter* af); int statusrow(int status); void drain_exp(struct unit* u, int d); diff --git a/src/battle.test.c b/src/battle.test.c index 7790b234b..24574fa86 100644 --- a/src/battle.test.c +++ b/src/battle.test.c @@ -650,6 +650,44 @@ static void test_battle_skilldiff(CuTest *tc) } static void test_loot_items(CuTest* tc) +{ + troop ta, td; + region* r; + faction *f; + unit* ua, * ud; + battle* b = NULL; + const resource_type* rtype; + race* rc; + + test_setup(); + config_set_int("rules.items.loot_divisor", 1); // everything is looted 100% + test_create_horse(); + rc = test_create_race("ghost"); + rc->flags |= RCF_FLY; /* bug 2887 */ + + r = test_create_plain(0, 0); + ud = test_create_unit(f = test_create_faction(), r); + ud->status = ST_FLEE; /* bug 2887 */ + ua = test_create_unit(f, r); + u_setrace(ua, rc); + td.fighter = setup_fighter(&b, ud); + td.index = 0; + ta.fighter = setup_fighter(&b, ua); + ta.index = 0; + + ta.fighter->alive = 0; + + rtype = get_resourcetype(R_HORSE); + i_change(&ua->items, rtype->itype, 1); + loot_items(ta.fighter); + CuAssertIntEquals(tc, 1, i_get(td.fighter->loot, rtype->itype)); + CuAssertIntEquals(tc, 0, i_get(ua->items, rtype->itype)); + + free_battle(b); + test_teardown(); +} + +static void test_loot_notlost_items(CuTest* tc) { troop ta, td; region* r; @@ -659,6 +697,7 @@ static void test_loot_items(CuTest* tc) race* rc; test_setup(); + config_set_int("rules.items.loot_divisor", 0); // nothing is looted test_create_horse(); rc = test_create_race("ghost"); rc->flags |= RCF_FLY; /* bug 2887 */ @@ -692,6 +731,127 @@ static void test_loot_items(CuTest* tc) test_teardown(); } +static void test_loot_cursed_items_self(CuTest* tc) +{ + troop ta, td; + region* r; + faction *f; + unit* ua, * ud; + battle* b = NULL; + const resource_type* rtype; + race* rc; + + test_setup(); + config_set_int("rules.items.loot_divisor", 1); // everything is looted + test_create_horse(); + rc = test_create_race("ghost"); + rc->flags |= RCF_FLY; /* bug 2887 */ + + r = test_create_plain(0, 0); + ud = test_create_unit(f = test_create_faction(), r); + ud->status = ST_FLEE; /* bug 2887 */ + ua = test_create_unit(f, r); + u_setrace(ua, rc); + td.fighter = setup_fighter(&b, ud); + td.index = 0; + ta.fighter = setup_fighter(&b, ua); + ta.index = 0; + + ta.fighter->alive = 0; + + rtype = get_resourcetype(R_HORSE); + rtype->itype->flags |= ITF_CURSED; /* must be looted by own faction */ + i_change(&ua->items, rtype->itype, 1); + loot_items(ta.fighter); + CuAssertIntEquals(tc, 1, i_get(td.fighter->loot, rtype->itype)); + CuAssertIntEquals(tc, 0, i_get(ua->items, rtype->itype)); + + free_battle(b); + test_teardown(); +} + +static void test_loot_cursed_items_other(CuTest* tc) +{ + troop ta, td; + region* r; + unit* ua, * ud; + battle* b = NULL; + const resource_type* rtype; + race* rc; + + test_setup(); + config_set_int("rules.items.loot_divisor", 1); // everything is looted + test_create_horse(); + rc = test_create_race("ghost"); + rc->flags |= RCF_FLY; /* bug 2887 */ + + r = test_create_plain(0, 0); + ud = test_create_unit(test_create_faction(), r); + ud->status = ST_FLEE; /* bug 2887 */ + ua = test_create_unit(test_create_faction(), r); + u_setrace(ua, rc); + td.fighter = setup_fighter(&b, ud); + td.index = 0; + ta.fighter = setup_fighter(&b, ua); + ta.index = 0; + + ta.fighter->alive = 0; + + rtype = get_resourcetype(R_HORSE); + rtype->itype->flags |= ITF_CURSED; /* must not be looted */ + i_change(&ua->items, rtype->itype, 1); + loot_items(ta.fighter); + CuAssertIntEquals(tc, 0, i_get(td.fighter->loot, rtype->itype)); + CuAssertIntEquals(tc, 0, i_get(ua->items, rtype->itype)); + + free_battle(b); + test_teardown(); +} + +static void test_no_loot_from_fleeing(CuTest* tc) +{ + troop ta, td; + region* r; + unit* ua, * ud; + battle* b = NULL; + const resource_type* rtype; + race* rc; + + test_setup(); + test_create_horse(); + rc = test_create_race("ghost"); + rc->flags |= RCF_FLY; /* bug 2887 */ + + r = test_create_plain(0, 0); + ud = test_create_unit(test_create_faction(), r); + ud->status = ST_FLEE; /* bug 2887 */ + ua = test_create_unit(test_create_faction(), r); + u_setrace(ua, rc); + td.fighter = setup_fighter(&b, ud); + td.index = 0; + ta.fighter = setup_fighter(&b, ua); + ta.index = 0; + + ta.fighter->side->relations[td.fighter->side->index] |= E_ENEMY; + ta.fighter->side->enemies[0] = td.fighter->side; + ta.fighter->side->enemies[1] = NULL; + td.fighter->side->relations[ta.fighter->side->index] |= E_ENEMY; + td.fighter->side->enemies[0] = ta.fighter->side; + td.fighter->side->enemies[1] = NULL; + + flee_all(ta.fighter); + + rtype = get_resourcetype(R_HORSE); + rtype->itype->flags |= ITF_NOTLOST; /* must always be looted */ + i_change(&ua->items, rtype->itype, 1); + loot_items(ta.fighter); + CuAssertIntEquals(tc, 0, i_get(td.fighter->loot, rtype->itype)); + CuAssertIntEquals(tc, 1, i_get(ua->items, rtype->itype)); + + free_battle(b); + test_teardown(); +} + static void test_terminate(CuTest * tc) { troop at, dt; @@ -1026,6 +1186,10 @@ CuSuite *get_battle_suite(void) SUITE_ADD_TEST(suite, test_tactics_chance); SUITE_ADD_TEST(suite, test_terminate); SUITE_ADD_TEST(suite, test_loot_items); + SUITE_ADD_TEST(suite, test_loot_notlost_items); + SUITE_ADD_TEST(suite, test_loot_cursed_items_self); + SUITE_ADD_TEST(suite, test_loot_cursed_items_other); + SUITE_ADD_TEST(suite, test_no_loot_from_fleeing); DISABLE_TEST(suite, test_drain_exp); return suite; } diff --git a/src/spells/combatspells.c b/src/spells/combatspells.c index 1bdbbe0c9..2a0c51cf0 100644 --- a/src/spells/combatspells.c +++ b/src/spells/combatspells.c @@ -1196,7 +1196,7 @@ int sp_appeasement(struct castorder * co) fi->run.hp = mage->hp; fi->run.number = mage->number; /* fighter leeren */ - rmfighter(fi, mage->number); + reduce_fighter(fi, mage->number); m = msg_message("cast_escape_effect", "mage spell", fi->unit, sp); message_all(b, m); From 6e5a947cf9d3b4ab1510ef657e09dc2b51c42541 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 9 Sep 2023 07:02:37 +0200 Subject: [PATCH 09/29] Typo in Pavillon --- res/translations/strings.de.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/translations/strings.de.po b/res/translations/strings.de.po index 52fe24c90..0f9090a21 100644 --- a/res/translations/strings.de.po +++ b/res/translations/strings.de.po @@ -4690,7 +4690,7 @@ msgid "unit_guards" msgstr "bewacht die Region" msgid "pavilion" -msgstr "Pavillion" +msgstr "Pavillon" msgctxt "race" msgid "spell_d" From d639310ae112c8031f132c251da7845498bb5961 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 9 Sep 2023 07:03:30 +0200 Subject: [PATCH 10/29] fix a crash caused by new weapons code --- src/battle.c | 24 ++++++++++++++---------- src/battle.h | 7 +++++-- src/battle.test.c | 16 ++++++++-------- src/items/weapons.c | 4 ++-- src/spells/combatspells.c | 7 ++++--- 5 files changed, 33 insertions(+), 25 deletions(-) diff --git a/src/battle.c b/src/battle.c index 195fc3ff3..33098abe5 100644 --- a/src/battle.c +++ b/src/battle.c @@ -665,9 +665,9 @@ weapon_effskill(troop t, troop enemy, const weapon * w, fighter *tf = t.fighter; unit *tu = t.fighter->unit; /* Alle Modifier berechnen, die fig durch die Waffen bekommt. */ - if (w) { + if (w && w->item.type) { int skill = 0; - const weapon_type *wtype = WEAPON_TYPE(w); + const weapon_type *wtype = resource2weapon(w->item.type->rtype); if (attacking) { skill = w->attackskill; @@ -1941,9 +1941,12 @@ static int setreload(troop at) { fighter *af = at.fighter; const weapon_type *wtype = WEAPON_TYPE(af->person[at.index].missile); - if (wtype->reload == 0) - return 0; - return af->person[at.index].reload = wtype->reload; + if (wtype) { + if (wtype->reload != 0) { + return af->person[at.index].reload = wtype->reload; + } + } + return 0; } int getreload(troop at) @@ -3113,7 +3116,7 @@ static void equip_weapons(fighter* fig) wp = arraddnptr(fig->weapons, 1); wp->attackskill = weapon_skill(wtype, u, true); wp->defenseskill = weapon_skill(wtype, u, false); - wp->item = itm; + wp->item.ref = itm; } w = arrlen(fig->weapons); qsort(fig->weapons, w, sizeof(weapon), cmp_weapon); @@ -3122,11 +3125,12 @@ static void equip_weapons(fighter* fig) /* hand out weapons: */ for (i = 0; i != w; ++i) { - const weapon *wp = fig->weapons + i; - const weapon_type *wtype = WEAPON_TYPE(wp); + weapon *wp = fig->weapons + i; + const item *itm = wp->item.ref; + const weapon_type *wtype = resource2weapon(item2resource(itm->type)); bool is_missile; assert(wtype); - + wp->item.type = itm->type; is_missile = (wtype->flags & WTF_MISSILE); if (!is_missile && wpless > wp->attackskill + wp->defenseskill) { /* we fight better with bare hands than this melee weapon */ @@ -3146,7 +3150,7 @@ static void equip_weapons(fighter* fig) } if (wp->attackskill >= 0 || wp->defenseskill >= 0) { - int count = wp->item->number; + int count = itm->number; while (count > 0 && (p_missile < fig->alive || p_melee < fig->alive)) { if (is_missile) { if (p_missile < fig->alive) { diff --git a/src/battle.h b/src/battle.h index 3929b6336..b53b62abb 100644 --- a/src/battle.h +++ b/src/battle.h @@ -96,12 +96,15 @@ typedef struct battle { } battle; typedef struct weapon { - const struct item* item; + union { + struct item *ref; + const struct item_type *type; + } item; int attackskill; int defenseskill; } weapon; -#define WEAPON_TYPE(wp) ((wp) ? (wp)->item->type->rtype->wtype : NULL) +#define WEAPON_TYPE(wp) ((wp && (wp)->item.type) ? (wp)->item.type->rtype->wtype : NULL) typedef struct troop { struct fighter* fighter; diff --git a/src/battle.test.c b/src/battle.test.c index 24574fa86..9de185d19 100644 --- a/src/battle.test.c +++ b/src/battle.test.c @@ -124,12 +124,12 @@ static void test_select_weapon(CuTest *tc) { b = make_battle(au->region); af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); CuAssertIntEquals(tc, 3, (int)arrlen(af->weapons)); - CuAssertPtrEquals(tc, i_axe, (item *)af->person[0].melee->item); + CuAssertPtrEquals(tc, it_axe, (item_type *)af->person[0].melee->item.type); CuAssertPtrEquals(tc, NULL, (weapon *)af->person[0].missile); - CuAssertPtrEquals(tc, i_sword, (item *)af->person[1].melee->item); - CuAssertPtrEquals(tc, i_missile, (item *)af->person[1].missile->item); + CuAssertPtrEquals(tc, it_sword, (item_type *)af->person[1].melee->item.type); + CuAssertPtrEquals(tc, it_missile, (item_type *)af->person[1].missile->item.type); CuAssertPtrEquals(tc, NULL, (weapon *)af->person[2].melee); - CuAssertPtrEquals(tc, i_missile, (item *)af->person[2].missile->item); + CuAssertPtrEquals(tc, it_missile, (item_type *)af->person[2].missile->item.type); free_battle(b); test_teardown(); @@ -154,7 +154,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); CuAssertIntEquals(tc, 1, (int)arrlen(af->weapons)); - CuAssertPtrEquals(tc, au->items, (void *)af->weapons[0].item); + CuAssertPtrEquals(tc, (item_type *)au->items->type, (item_type *)af->weapons[0].item.type); CuAssertPtrEquals(tc, af->weapons, (void *)af->person[0].melee); free_battle(b); @@ -163,7 +163,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); CuAssertIntEquals(tc, 1, (int)arrlen(af->weapons)); - CuAssertPtrEquals(tc, au->items, (void *)af->weapons[0].item); + CuAssertPtrEquals(tc, (item_type *)au->items->type, (item_type *)af->weapons[0].item.type); CuAssertPtrNotNull(tc, af->person); CuAssertPtrEquals(tc, NULL, (void *)af->person[0].melee); free_battle(b); @@ -175,7 +175,7 @@ static void test_select_weapon_restricted(CuTest *tc) { af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); CuAssertPtrNotNull(tc, af->weapons); CuAssertIntEquals(tc, 1, (int)arrlen(af->weapons)); - CuAssertPtrEquals(tc, au->items, (void *)af->weapons[0].item); + CuAssertPtrEquals(tc, (item_type *)au->items->type, (item_type *)af->weapons[0].item.type); CuAssertPtrEquals(tc, af->weapons, (void *)af->person[0].melee); free_battle(b); @@ -185,7 +185,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); CuAssertIntEquals(tc, 1, (int)arrlen(af->weapons)); - CuAssertPtrEquals(tc, au->items, (void *)af->weapons[0].item); + CuAssertPtrEquals(tc, (item_type *)au->items->type, (void *)af->weapons[0].item.type); CuAssertPtrEquals(tc, NULL, (void *)af->person[0].melee); free_battle(b); diff --git a/src/items/weapons.c b/src/items/weapons.c index 168bd61f5..23805e5c5 100644 --- a/src/items/weapons.c +++ b/src/items/weapons.c @@ -43,7 +43,7 @@ int *casualties) message *msg; for (i = 0; i <= at->index; ++i) { const weapon *wp = fi->person[i].melee; - if (wp != NULL && wp->item->type == wtype->itype) + if (WEAPON_TYPE(wp) == wtype) ++k; } msg = msg_message("useflamingsword", "amount unit", k, fi->unit); @@ -103,7 +103,7 @@ int *casualties) message *msg; for (i = 0; i <= at->index; ++i) { - if (af->person[i].reload == 0 && af->person[i].missile->item->type == wtype->itype) + if (af->person[i].reload == 0 && WEAPON_TYPE(af->person[i].missile) == wtype) ++k; } msg = msg_message("usecatapult", "amount unit", k, au); diff --git a/src/spells/combatspells.c b/src/spells/combatspells.c index 2a0c51cf0..7bfc49df9 100644 --- a/src/spells/combatspells.c +++ b/src/spells/combatspells.c @@ -287,10 +287,11 @@ int sp_combatrosthauch(struct castorder * co) for (w = 0; w != len; ++w) { weapon *wp = df->weapons; if (df->unit->items && force > 0) { - item ** itp = i_find(&df->unit->items, wp->item->type); + const item_type *itype = wp->item.type; + item ** itp = i_find(&df->unit->items, itype); if (*itp) { item *it = *itp; - requirement *mat = wp->item->type->construction->materials; + requirement *mat = itype->construction->materials; int n = force; if (it->number < n) n = it->number; @@ -299,7 +300,7 @@ int sp_combatrosthauch(struct castorder * co) int p; force -= n; k += n; - i_change(itp, wp->item->type, -n); + i_change(itp, itype, -n); for (p = 0; n && p != df->unit->number; ++p) { if (df->person[p].melee == wp) { df->person[p].melee = NULL; From e051528f9605c4c3fe4f148cb0a8456d10b3204f Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 9 Sep 2023 07:29:31 +0200 Subject: [PATCH 11/29] ex unused variables --- src/battle.test.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/battle.test.c b/src/battle.test.c index 9de185d19..d949ec728 100644 --- a/src/battle.test.c +++ b/src/battle.test.c @@ -102,7 +102,6 @@ static void test_make_fighter(CuTest * tc) static void test_select_weapon(CuTest *tc) { item_type *it_missile, *it_axe, *it_sword; - item *i_missile, *i_sword, *i_axe; unit *au; fighter *af; battle *b; @@ -113,13 +112,13 @@ static void test_select_weapon(CuTest *tc) { set_level(au, SK_MELEE, 1); it_axe = test_create_itemtype("axe"); new_weapontype(it_axe, 0, frac_zero, NULL, 1, 0, 0, SK_MELEE); - i_axe = i_change(&au->items, it_axe, 1); + i_change(&au->items, it_axe, 1); it_sword = test_create_itemtype("sword"); new_weapontype(it_sword, 0, frac_zero, NULL, 0, 0, 0, SK_MELEE); - i_sword = i_change(&au->items, it_sword, 1); + i_change(&au->items, it_sword, 1); it_missile = test_create_itemtype("crossbow"); new_weapontype(it_missile, WTF_MISSILE, frac_zero, NULL, 0, 0, 0, SK_CROSSBOW); - i_missile = i_change(&au->items, it_missile, 2); + i_change(&au->items, it_missile, 2); b = make_battle(au->region); af = make_fighter(b, au, make_side(b, au->faction, 0, 0, 0), false); From 85dfd9a25464dd923620d699b45ef8ee0624659a Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 9 Sep 2023 11:31:38 +0200 Subject: [PATCH 12/29] correct message for buildings that cannot be built https://bugs.eressea.de/view.php?id=2977 --- src/kernel/build.c | 10 +++--- src/kernel/build.test.c | 70 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 71 insertions(+), 9 deletions(-) diff --git a/src/kernel/build.c b/src/kernel/build.c index 532a184a7..fba7e9380 100644 --- a/src/kernel/build.c +++ b/src/kernel/build.c @@ -586,11 +586,6 @@ build_building(unit * u, const building_type * btype, int id, int want, order * btype = b->type; } - if (fval(btype, BTF_UNIQUE) && buildingtype_exists(r, btype, false)) { - /* only one of these per region */ - cmistake(u, ord, 93, MSG_PRODUCE); - return 0; - } if (btype->flags & BTF_NOBUILD) { /* special building, cannot be built */ cmistake(u, ord, 221, MSG_PRODUCE); @@ -601,6 +596,11 @@ build_building(unit * u, const building_type * btype, int id, int want, order * cmistake(u, ord, 221, MSG_PRODUCE); return 0; } + if (fval(btype, BTF_UNIQUE) && buildingtype_exists(r, btype, false)) { + /* only one of these per region */ + cmistake(u, ord, 93, MSG_PRODUCE); + return 0; + } if (btype->flags & BTF_ONEPERTURN) { if (b && fval(b, BLD_EXPANDED)) { cmistake(u, ord, 318, MSG_PRODUCE); diff --git a/src/kernel/build.test.c b/src/kernel/build.test.c index 8de422591..d8d748f00 100644 --- a/src/kernel/build.test.c +++ b/src/kernel/build.test.c @@ -341,7 +341,7 @@ static void test_build_building_no_materials(CuTest *tc) { u = setup_build(&bf); btype = bf.btype; set_level(u, SK_BUILDING, 1); - u->orders = create_order(K_MAKE, u->faction->locale, 0); + u->orders = create_order(K_MAKE, u->faction->locale, NULL); CuAssertIntEquals(tc, ENOMATERIALS, build_building(u, btype, 0, 4, u->orders)); CuAssertPtrEquals(tc, NULL, u->region->buildings); CuAssertPtrEquals(tc, NULL, u->building); @@ -358,7 +358,7 @@ static void test_build_building_with_golem(CuTest *tc) { btype = bf.btype; set_level(bf.u, SK_BUILDING, 1); - u->orders = create_order(K_MAKE, u->faction->locale, 0); + u->orders = create_order(K_MAKE, u->faction->locale, NULL); CuAssertIntEquals(tc, 1, build_building(u, btype, 0, 1, u->orders)); CuAssertPtrNotNull(tc, u->region->buildings); CuAssertIntEquals(tc, 1, u->region->buildings->size); @@ -379,9 +379,9 @@ static void test_build_building_success(CuTest *tc) { assert(btype && rtype && rtype->itype); assert(!u->region->buildings); - i_change(&bf.u->items, rtype->itype, 1); + i_change(&u->items, rtype->itype, 1); set_level(u, SK_BUILDING, 1); - u->orders = create_order(K_MAKE, u->faction->locale, 0); + u->orders = create_order(K_MAKE, u->faction->locale, NULL); CuAssertIntEquals(tc, 1, build_building(u, btype, 0, 4, u->orders)); CuAssertPtrNotNull(tc, u->region->buildings); CuAssertPtrEquals(tc, u->region->buildings, u->building); @@ -398,6 +398,66 @@ static void test_build_roqf_factor(CuTest *tc) { test_teardown(); } +static void test_build_building_nobuild_fail(CuTest *tc) { + unit *u; + build_fixture bf = { 0 }; + building_type *btype; + const resource_type *rtype; + + u = setup_build(&bf); + + rtype = get_resourcetype(R_STONE); + btype = bf.btype; + assert(btype && rtype && rtype->itype); + assert(!u->region->buildings); + btype->flags |= BTF_NOBUILD; + + i_change(&u->items, rtype->itype, 1); + set_level(u, SK_BUILDING, 1); + u->orders = create_order(K_MAKE, u->faction->locale, NULL); + CuAssertIntEquals(tc, 0, build_building(u, btype, 0, 4, u->orders)); + CuAssertPtrNotNull(tc, test_find_faction_message(u->faction, "error221")); + CuAssertPtrEquals(tc, NULL, u->region->buildings); + CuAssertPtrEquals(tc, NULL, u->building); + CuAssertIntEquals(tc, 1, i_get(u->items, rtype->itype)); + teardown_build(&bf); +} + +static void test_build_building_unique(CuTest *tc) { + unit *u; + build_fixture bf = { 0 }; + building_type *btype; + const resource_type *rtype; + + u = setup_build(&bf); + + rtype = get_resourcetype(R_STONE); + btype = bf.btype; + assert(btype && rtype && rtype->itype); + i_change(&u->items, rtype->itype, 2); + set_level(u, SK_BUILDING, 1); + u->orders = create_order(K_MAKE, u->faction->locale, NULL); + + btype->flags |= BTF_UNIQUE; + CuAssertIntEquals(tc, 1, build_building(u, btype, 0, 4, u->orders)); + CuAssertPtrEquals(tc, NULL, test_find_faction_message(u->faction, "error93")); + CuAssertIntEquals(tc, 1, i_get(u->items, rtype->itype)); + CuAssertPtrNotNull(tc, u->building); + leave_building(u); + CuAssertIntEquals(tc, 0, build_building(u, btype, 0, 4, u->orders)); + CuAssertPtrNotNull(tc, test_find_faction_message(u->faction, "error93")); + CuAssertIntEquals(tc, 1, i_get(u->items, rtype->itype)); + test_clear_messages(u->faction); + + /* NOBUILD before UNIQUE*/ + btype->flags |= BTF_UNIQUE|BTF_NOBUILD; + CuAssertIntEquals(tc, 0, build_building(u, btype, 0, 4, u->orders)); + CuAssertPtrNotNull(tc, test_find_faction_message(u->faction, "error221")); + CuAssertPtrEquals(tc, NULL, test_find_faction_message(u->faction, "error93")); + + teardown_build(&bf); +} + CuSuite *get_build_suite(void) { CuSuite *suite = CuSuiteNew(); @@ -416,6 +476,8 @@ CuSuite *get_build_suite(void) SUITE_ADD_TEST(suite, test_build_building_stage_continue); SUITE_ADD_TEST(suite, test_build_building_with_golem); SUITE_ADD_TEST(suite, test_build_building_no_materials); + SUITE_ADD_TEST(suite, test_build_building_nobuild_fail); + SUITE_ADD_TEST(suite, test_build_building_unique); return suite; } From 75d3e832e230604ab8039573d841912991af1bcb Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 9 Sep 2023 16:23:45 +0200 Subject: [PATCH 13/29] nobuild implies unique when building. --- res/eressea/buildings.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/res/eressea/buildings.xml b/res/eressea/buildings.xml index d239907f1..8d5a0e26f 100644 --- a/res/eressea/buildings.xml +++ b/res/eressea/buildings.xml @@ -1,9 +1,9 @@ - - - - + + + + From dc10a3f4368130256e47afa64f89ea7afaf8080c Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 10 Sep 2023 11:28:37 +0200 Subject: [PATCH 14/29] Katapultmeldung repariert https://bugs.eressea.de/view.php?id=2980 Ausserdem: Flammenschwerter vor Laenschwertern bevorzugen. --- src/battle.c | 40 ++++++++++++++++++++++----- src/battle.h | 6 ++++- src/items/weapons.c | 61 +++++++++++++++++++----------------------- src/items/weapons.h | 7 +++-- src/kernel/callbacks.h | 1 + 5 files changed, 69 insertions(+), 46 deletions(-) diff --git a/src/battle.c b/src/battle.c index 33098abe5..1b884117b 100644 --- a/src/battle.c +++ b/src/battle.c @@ -26,6 +26,7 @@ #include "kernel/alliance.h" #include "kernel/build.h" #include "kernel/building.h" +#include "kernel/callbacks.h" #include "kernel/config.h" #include "kernel/curse.h" #include "kernel/equipment.h" @@ -2163,7 +2164,8 @@ static void attack(battle * b, troop ta, const att * a, int numattack) if (numattack == 0 && wtype && wtype->attack) { int dead = 0; if (wtype->attack(&ta, wtype, &dead)) { - af->catmsg += dead; + ++af->special.attacks; + af->special.kills += dead; if (af->person[ta.index].last_action < b->turn) { af->person[ta.index].last_action = b->turn; } @@ -2285,12 +2287,30 @@ static void do_attack(fighter * af) } /* 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); + if (af->special.attacks > 0) { + struct message *m; + if (callbacks.report_special_attacks) { + const weapon_type *wtype = NULL; + for (ta.index = 0; ta.index != af->fighting; ++ta.index) { + const weapon *wp = preferred_weapon(ta, true); + wtype = WEAPON_TYPE(wp); + if (wtype && wtype->attack) { + break; + } + else { + wtype = NULL; + } + + } + if (wtype && wtype->attack) { + callbacks.report_special_attacks(af, wtype->itype); + } + } + m = msg_message("killed_battle", "unit dead", au, af->special.kills); message_all(b, m); msg_release(m); - af->catmsg = -1; + af->special.kills = 0; + af->special.attacks = 0; } } @@ -3093,7 +3113,12 @@ static int cmp_weapon(const void *lhs, const void *rhs) { const weapon *a = (const weapon *)lhs; const weapon *b = (const weapon *)rhs; - return b->attackskill - a->attackskill + b->defenseskill - a->defenseskill; + int diff = b->attackskill - a->attackskill + b->defenseskill - a->defenseskill; + if (diff == 0) { + if (a->item.ref->type->rtype->wtype->attack) return 1; + if (b->item.ref->type->rtype->wtype->attack) return -1; + } + return diff; } /* Fuer alle Waffengattungen wird bestimmt, wie viele der Personen mit @@ -3263,7 +3288,8 @@ fighter *make_fighter(battle * b, unit * u, side * s1, bool attack) fig->side = s1; fig->alive = u->number; fig->side->alive += u->number; - fig->catmsg = -1; + fig->special.kills = 0; + fig->special.attacks = 0; /* Freigeben nicht vergessen! */ assert(fig->alive > 0); diff --git a/src/battle.h b/src/battle.h index b53b62abb..dbacc3605 100644 --- a/src/battle.h +++ b/src/battle.h @@ -120,6 +120,7 @@ typedef struct armor { /*** fighter::flags ***/ #define FIG_ATTACKER 1<<0 #define FIG_NOLOOT 1<<1 + typedef struct fighter { struct fighter* next; struct side* side; @@ -138,7 +139,10 @@ typedef struct fighter { int horses; /* Anzahl brauchbarer Pferde der Einheit */ int elvenhorses; /* Anzahl brauchbarer Elfenpferde der Einheit */ struct item* loot; - int catmsg; /* Merkt sich, ob Katapultmessage schon generiert. */ + struct { + int attacks; + int kills; + } special; struct person { int hp; /* Trefferpunkte der Personen */ int flags; diff --git a/src/items/weapons.c b/src/items/weapons.c index 23805e5c5..110f7b064 100644 --- a/src/items/weapons.c +++ b/src/items/weapons.c @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -20,6 +21,24 @@ /* damage types */ +static void report_special_attacks(const fighter *af, const item_type *itype) +{ + battle *b = af->side->battle; + unit *au = af->unit; + message *msg; + int k = af->special.attacks; + const weapon_type *wtype = resource2weapon(itype->rtype); + + if (wtype->skill == SK_CATAPULT) { + msg = msg_message("usecatapult", "amount unit", k, au); + } + else { + msg = msg_message("useflamingsword", "amount unit", k, au); + } + message_all(b, msg); + msg_release(msg); +} + static bool attack_firesword(const troop * at, const struct weapon_type *wtype, int *casualties) @@ -35,21 +54,7 @@ int *casualties) if (!enemies) { if (casualties) *casualties = 0; - return true; /* if no enemy found, no use doing standarad attack */ - } - - if (fi->catmsg == -1) { - int i, k = 0; - message *msg; - for (i = 0; i <= at->index; ++i) { - const weapon *wp = fi->person[i].melee; - if (WEAPON_TYPE(wp) == wtype) - ++k; - } - msg = msg_message("useflamingsword", "amount unit", k, fi->unit); - message_all(fi->side->battle, msg); - msg_release(msg); - fi->catmsg = 0; + return false; /* if no enemy found, no use doing standarad attack */ } do { @@ -68,13 +73,13 @@ int *casualties) static bool attack_catapult(const troop * at, const struct weapon_type *wtype, -int *casualties) + int *casualties) { fighter *af = at->fighter; unit *au = af->unit; battle *b = af->side->battle; troop dt; - int d = 0, enemies; + int shots = INT_MAX, d = 0, enemies; const resource_type *rtype; if (au->status >= ST_AVOID) { @@ -82,12 +87,13 @@ int *casualties) return false; } - assert(wtype == WEAPON_TYPE(af->person[at->index].missile)); + assert(wtype && wtype == WEAPON_TYPE(af->person[at->index].missile)); assert(af->person[at->index].reload == 0); rtype = rt_find("catapultammo"); if (rtype) { - if (get_pooled(au, rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, 1) <= 0) { + shots = get_pooled(au, rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK, 1); + if (shots <= 0) { return false; } } @@ -98,25 +104,11 @@ int *casualties) return false; } - if (af->catmsg == -1) { - int i, k = 0; - message *msg; - - for (i = 0; i <= at->index; ++i) { - if (af->person[i].reload == 0 && WEAPON_TYPE(af->person[i].missile) == wtype) - ++k; - } - msg = msg_message("usecatapult", "amount unit", k, au); - message_all(b, msg); - msg_release(msg); - af->catmsg = 0; - } - if (rtype) { use_pooled(au, rtype, GET_DEFAULT, 1); } - while (--enemies >= 0) { + while (enemies-- > 0) { /* Select defender */ dt = select_enemy(af, FIGHT_ROW, FIGHT_ROW, SELECT_ADVANCE); if (!dt.fighter) @@ -138,6 +130,7 @@ int *casualties) void register_weapons(void) { + callbacks.report_special_attacks = report_special_attacks; register_function((pf_generic)attack_catapult, "attack_catapult"); register_function((pf_generic)attack_firesword, "attack_firesword"); } diff --git a/src/items/weapons.h b/src/items/weapons.h index aa150fa13..5821a9c21 100644 --- a/src/items/weapons.h +++ b/src/items/weapons.h @@ -1,7 +1,6 @@ #pragma once -#ifndef H_ITM_WEAPONS -#define H_ITM_WEAPONS -void register_weapons(void); +struct fighter; +struct weapon_type; -#endif +void register_weapons(void); diff --git a/src/kernel/callbacks.h b/src/kernel/callbacks.h index f42707f45..f319c9170 100644 --- a/src/kernel/callbacks.h +++ b/src/kernel/callbacks.h @@ -21,6 +21,7 @@ extern "C" { int amount, struct order *ord); void(*produce_resource)(struct region *, const struct resource_type *, int); int(*limit_resource)(const struct region *, const struct resource_type *); + void (*report_special_attacks)(const struct fighter *fig, const struct item_type *itype); }; extern struct callback_struct callbacks; From 2e165de4d83f5c3bcfe00d994b5e5bf851c190a3 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sun, 10 Sep 2023 13:02:19 +0200 Subject: [PATCH 15/29] fix gxx compile --- src/items/weapons.c | 1 + src/kernel/callbacks.h | 45 +++++++++++++++++------------------------- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/src/items/weapons.c b/src/items/weapons.c index 110f7b064..6bd514a04 100644 --- a/src/items/weapons.c +++ b/src/items/weapons.c @@ -17,6 +17,7 @@ /* libc includes */ #include +#include #include /* damage types */ diff --git a/src/kernel/callbacks.h b/src/kernel/callbacks.h index f319c9170..b861aef05 100644 --- a/src/kernel/callbacks.h +++ b/src/kernel/callbacks.h @@ -1,32 +1,23 @@ -#ifndef H_KRNL_CALLBACKS_H -#define H_KRNL_CALLBACKS_H +#pragma once #include -#ifdef __cplusplus -extern "C" { -#endif +struct castorder; +struct order; +struct unit; +struct region; +struct fighter; +struct item_type; +struct resource_type; - struct castorder; - struct order; - struct unit; - struct region; - struct item_type; - struct resource_type; - - struct callback_struct { - bool (*equip_unit)(struct unit *u, const char *eqname, int mask); - int (*cast_spell)(struct castorder *co, const char *fname); - int (*use_item)(struct unit *u, const struct item_type *itype, - int amount, struct order *ord); - void(*produce_resource)(struct region *, const struct resource_type *, int); - int(*limit_resource)(const struct region *, const struct resource_type *); - void (*report_special_attacks)(const struct fighter *fig, const struct item_type *itype); - }; - - extern struct callback_struct callbacks; -#ifdef __cplusplus -} -#endif -#endif /* H_KRNL_CALLBACKS_H */ +struct callback_struct { + bool (*equip_unit)(struct unit *u, const char *eqname, int mask); + int (*cast_spell)(struct castorder *co, const char *fname); + int (*use_item)(struct unit *u, const struct item_type *itype, + int amount, struct order *ord); + void(*produce_resource)(struct region *, const struct resource_type *, int); + int(*limit_resource)(const struct region *, const struct resource_type *); + void (*report_special_attacks)(const struct fighter *fig, const struct item_type *itype); +}; +extern struct callback_struct callbacks; From a51de123d097274d4744eb9285573e7a83479bdf Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Thu, 14 Sep 2023 21:32:29 +0200 Subject: [PATCH 16/29] long orders prevent trading https://bugs.eressea.de/view.php?id=2978 --- scripts/tests/e2/e2features.lua | 19 ++++++++++ scripts/tests/e2/recruit.lua | 1 - src/battle.c | 2 +- src/economy.c | 66 +++++++++++++++++---------------- src/economy.test.c | 37 ++++++++++++++++++ 5 files changed, 92 insertions(+), 33 deletions(-) diff --git a/scripts/tests/e2/e2features.lua b/scripts/tests/e2/e2features.lua index d4878bd39..5b2488280 100644 --- a/scripts/tests/e2/e2features.lua +++ b/scripts/tests/e2/e2features.lua @@ -636,3 +636,22 @@ function test_seacast() process_orders() assert_equal(8, u2.region.x) end + +-- Solthar: long orders prevent trading +function test_bug_2978() + local r = region.create(0, 0, 'plain') + r.peasants = 10000 + r.luxury = 'balm' + local f = faction.create('human') + local u = unit.create(f, r, 1) + u:add_item("jewel", 1) + assert_equal(1, u:get_item("jewel")) + u:add_item("stone", 2) + u:add_order("MACHE 2 Burg") + u:add_order("VERKAUFE ALLES Juwel") + u:set_skill("trade", 10) + u:set_skill("building", 10) + process_orders() + write_report(f) + assert_equal(1, u:get_item("jewel")) +end diff --git a/scripts/tests/e2/recruit.lua b/scripts/tests/e2/recruit.lua index 74304519e..2766f195b 100644 --- a/scripts/tests/e2/recruit.lua +++ b/scripts/tests/e2/recruit.lua @@ -57,7 +57,6 @@ function test_guarded_empty_units_cannot_recruit() u2:set_skill("melee", 1) u2.guard = true - u1.name='Xolthar' u1:add_item("money", 100) u1:add_order("GIB 0 ALLES PERSONEN") u1:add_order("REKRUTIERE 1") diff --git a/src/battle.c b/src/battle.c index 1b884117b..0eed565cf 100644 --- a/src/battle.c +++ b/src/battle.c @@ -2502,7 +2502,7 @@ void loot_items(fighter * corpse) int looting = 0; int maxrow = 0; /* mustloot: we absolutely, positively must have somebody loot this thing */ - bool mustloot = 0 != (itm->type->flags & (ITF_CURSED|ITF_NOTLOST)); + bool mustloot = (loot_divisor == 1) || (0 != (itm->type->flags & (ITF_CURSED|ITF_NOTLOST))); item_add(itm, -loot); maxloot -= loot; diff --git a/src/economy.c b/src/economy.c index 4e6aa5f85..a3ad3682a 100644 --- a/src/economy.c +++ b/src/economy.c @@ -1385,6 +1385,11 @@ static void buy(unit * u, econ_request ** buyorders, struct order *ord) const luxury_type *ltype = NULL; const char *s; + if (fval(u, UFL_LONGACTION)) { + cmistake(u, ord, 52, MSG_PRODUCE); + return; + } + if (u->ship && is_guarded(r, u)) { cmistake(u, ord, 69, MSG_INCOME); return; @@ -1674,9 +1679,9 @@ static bool sell(unit * u, econ_request ** sellorders, struct order *ord) static int bt_cache; static const struct building_type *castle_bt, *caravan_bt; - if (bt_changed(&bt_cache)) { - castle_bt = bt_find("castle"); - caravan_bt = bt_find("caravan"); + if (fval(u, UFL_LONGACTION)) { + cmistake(u, ord, 52, MSG_PRODUCE); + return false; } if (u->ship && is_guarded(r, u)) { @@ -1686,6 +1691,11 @@ static bool sell(unit * u, econ_request ** sellorders, struct order *ord) /* sellorders sind KEIN array, weil fuer alle items DIE SELBE resource * (das geld der region) aufgebraucht wird. */ + if (bt_changed(&bt_cache)) { + castle_bt = bt_find("castle"); + caravan_bt = bt_find("caravan"); + } + init_order(ord, NULL); s = gettoken(token, sizeof(token)); @@ -2472,9 +2482,9 @@ static void peasant_taxes(region * r) void produce(struct region *r) { - econ_request *taxorders, *lootorders, *sellorders, *stealorders, *buyorders; - unit *u; bool limited = true; + econ_request *taxorders = NULL, *lootorders = NULL, *sellorders = NULL, *stealorders = NULL, *buyorders = NULL; + unit *u; long entertaining = 0, working = 0; econ_request *nextrequest = econ_requests; static int bt_cache; @@ -2509,15 +2519,8 @@ void produce(struct region *r) peasant_taxes(r); } - buyorders = 0; - sellorders = 0; - taxorders = 0; - lootorders = 0; - stealorders = 0; - for (u = r->units; u; u = u->next) { order *ord; - bool trader = false; keyword_t todo; if (!long_order_allowed(u, false)) continue; @@ -2534,28 +2537,29 @@ void produce(struct region *r) continue; } - for (ord = u->orders; ord; ord = ord->next) { - keyword_t kwd = getkeyword(ord); - if (kwd == K_BUY) { - buy(u, &buyorders, ord); - trader = true; - } - else if (kwd == K_SELL) { - /* sell returns true if the sale is not limited - * by the region limit */ - limited &= !sell(u, &sellorders, ord); - trader = true; + if (u->thisorder == NULL) { + bool trader = false; + for (ord = u->orders; ord; ord = ord->next) { + keyword_t kwd = getkeyword(ord); + if (kwd == K_BUY) { + buy(u, &buyorders, ord); + trader = true; + } + else if (kwd == K_SELL) { + /* sell returns true if the sale is not limited + * by the region limit */ + limited &= !sell(u, &sellorders, ord); + trader = true; + } } - } - if (trader) { - attrib *a = a_find(u->attribs, &at_trades); - if (a && a->data.i) { - produceexp(u, SK_TRADE, u->number); + if (trader) { + attrib *a = a_find(u->attribs, &at_trades); + if (a && a->data.i) { + produceexp(u, SK_TRADE, u->number); + } + fset(u, UFL_LONGACTION | UFL_NOTMOVING); } - fset(u, UFL_LONGACTION | UFL_NOTMOVING); - continue; } - todo = getkeyword(u->thisorder); if (todo == NOKEYWORD) continue; diff --git a/src/economy.test.c b/src/economy.test.c index 03959e800..46dbba2a4 100644 --- a/src/economy.test.c +++ b/src/economy.test.c @@ -617,6 +617,42 @@ static void test_buy_prices_rising(CuTest* tc) { test_teardown(); } +static void test_trade_is_long_action(CuTest *tc) { + unit *u; + region *r; + const item_type *it_luxury, *it_sold; + const resource_type *rt_silver; + test_setup(); + setup_production(); + r = setup_trade_region(tc, NULL); + test_create_building(r, test_create_buildingtype("castle"))->size = 2; + rt_silver = get_resourcetype(R_SILVER); + CuAssertPtrNotNull(tc, rt_silver); + CuAssertPtrNotNull(tc, rt_silver->itype); + it_luxury = r_luxury(r); + it_sold = it_find("balm"); + CuAssertTrue(tc, it_sold != it_luxury); + CuAssertPtrNotNull(tc, it_luxury); + u = test_create_unit(test_create_faction(), r); + test_set_item(u, it_sold, 100); + test_set_item(u, rt_silver->itype, 1000); + set_level(u, SK_TRADE, 1); + unit_addorder(u, create_order(K_BUY, u->faction->locale, "1 %s", LOC(u->faction->locale, resourcename(it_luxury->rtype, 0)))); + unit_addorder(u, create_order(K_SELL, u->faction->locale, "1 %s", LOC(u->faction->locale, resourcename(it_sold->rtype, 0)))); + produce(u->region); + CuAssertPtrEquals(tc, NULL, test_find_faction_message(u->faction, "error52")); + CuAssertIntEquals(tc, UFL_NOTMOVING | UFL_LONGACTION, u->flags & (UFL_NOTMOVING | UFL_LONGACTION)); + CuAssertIntEquals(tc, 1, i_get(u->items, it_luxury)); + CuAssertIntEquals(tc, 99, i_get(u->items, it_sold)); + + produce(u->region); + // error, but message is created in update_long_order! + CuAssertPtrEquals(tc, NULL, test_find_faction_message(u->faction, "error52")); + CuAssertIntEquals(tc, 1, i_get(u->items, it_luxury)); + CuAssertIntEquals(tc, 99, i_get(u->items, it_sold)); + test_teardown(); +} + static void test_buy_cmd(CuTest *tc) { region * r; unit *u; @@ -1459,6 +1495,7 @@ CuSuite *get_economy_suite(void) SUITE_ADD_TEST(suite, test_normals_recruit); SUITE_ADD_TEST(suite, test_heroes_dont_recruit); SUITE_ADD_TEST(suite, test_tax_cmd); + SUITE_ADD_TEST(suite, test_trade_is_long_action); SUITE_ADD_TEST(suite, test_buy_cmd); SUITE_ADD_TEST(suite, test_buy_twice); SUITE_ADD_TEST(suite, test_buy_prices); From 062837c281645c9cac21d8c0d2077bd6fb7f7272 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Fri, 15 Sep 2023 19:53:17 +0200 Subject: [PATCH 17/29] additional test for trade+other long order --- src/defaults.test.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/defaults.test.c b/src/defaults.test.c index a69008133..8fac689ee 100644 --- a/src/defaults.test.c +++ b/src/defaults.test.c @@ -165,8 +165,8 @@ static void test_long_order_multi_long(CuTest* tc) { unit_addorder(u, create_order(K_MOVE, u->faction->locale, NULL)); unit_addorder(u, create_order(K_DESTROY, u->faction->locale, NULL)); update_long_orders(); - CuAssertPtrNotNull(tc, u->thisorder); CuAssertPtrNotNull(tc, u->orders); + CuAssertPtrNotNull(tc, u->thisorder); CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "error52")); test_teardown(); } @@ -180,8 +180,23 @@ static void test_long_order_multi_buy(CuTest* tc) { unit_addorder(u, create_order(K_BUY, u->faction->locale, 0)); unit_addorder(u, create_order(K_BUY, u->faction->locale, 0)); update_long_orders(); + CuAssertPtrNotNull(tc, u->orders); CuAssertPtrEquals(tc, NULL, u->thisorder); + CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "error52")); + test_teardown(); +} + +static void test_long_order_trade_and_other(CuTest *tc) { + + unit *u; + test_setup(); + mt_create_error(52); + u = test_create_unit(test_create_faction(), test_create_plain(0, 0)); + unit_addorder(u, create_order(K_WORK, u->faction->locale, 0)); + unit_addorder(u, create_order(K_BUY, u->faction->locale, 0)); + update_long_orders(); CuAssertPtrNotNull(tc, u->orders); + CuAssertIntEquals(tc, K_WORK, getkeyword(u->thisorder)); CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "error52")); test_teardown(); } @@ -362,6 +377,7 @@ CuSuite *get_defaults_suite(void) SUITE_ADD_TEST(suite, test_long_order_cast); SUITE_ADD_TEST(suite, test_long_order_attack); SUITE_ADD_TEST(suite, test_long_order_buy_sell); + SUITE_ADD_TEST(suite, test_long_order_trade_and_other); SUITE_ADD_TEST(suite, test_long_order_multi_long); SUITE_ADD_TEST(suite, test_long_order_multi_buy); SUITE_ADD_TEST(suite, test_long_order_multi_sell); From 75f5b6f1cf48103ece3812418fa040a7a3197115 Mon Sep 17 00:00:00 2001 From: Steffen <1808071+stm2@users.noreply.github.com> Date: Sat, 16 Sep 2023 13:22:21 +0200 Subject: [PATCH 18/29] install inifile (required by compress.sh) (#1025) Co-authored-by: Enno Rehling --- tools/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 040216bfd..bb7a17ae5 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -12,3 +12,5 @@ add_executable(atoi36 ${CMAKE_SOURCE_DIR}/src/util/base36.c ) target_include_directories(atoi36 PRIVATE ${CMAKE_SOURCE_DIR}/src/util) + +install(TARGETS inifile DESTINATION "bin") From c7981845c5af3c2a2301727c6adc7c27db357b26 Mon Sep 17 00:00:00 2001 From: Steffen <1808071+stm2@users.noreply.github.com> Date: Sat, 16 Sep 2023 13:58:52 +0200 Subject: [PATCH 19/29] help screen for gmtool (#1023) Co-authored-by: Enno Rehling --- src/gmtool.c | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/src/gmtool.c b/src/gmtool.c index d49f549f7..017de45a7 100644 --- a/src/gmtool.c +++ b/src/gmtool.c @@ -43,6 +43,7 @@ #include #include #include +#include static int g_quit; int force_color = 0; @@ -1089,6 +1090,132 @@ static int exec_key_binding(int keycode) return -1; } +static void show_help(void) +{ + WINDOW *wn, *pad; + int line, col, ch=0; + int height, width; + const char* const help[] = { + "", + "GETTING AROUND", + "", + "arrow keys, position keys: move cursor", + "g: go to coordinate", + "/: search for ... r: region by name, u: unit by id, f: faction by id, F: faction from list", + "n: find next element according to last search", + "SPACE: select / unselect current region", + "TAB: jump to next selected region (by space or t)", + "p: jump between planes", + "a: jump to corresponding astral region / real region", + "", + "", + "DISPLAY", + "", + "I: show/hide info about ... f: factions, u: units, s: ships, b: buildings", + "d: map mode ... t: show terrains, l: show luxurues", + "", + "", + "MANIPULATING DATA", + "", + "O: open data file", + "S: save data file", + "f, Ctrl-t: terraform region at cursor", + "CTRL+b: fill block with oceans", + "B: build island E3 style", + "s: seed next player from newfactions at current region", + "A: reset area (set region age to 0) for whole contiguos region", + "c: clear (reset resources) region under cursor", + "C: clear rectangle under cursor (2 regions up and to the right)" + "", + "h: mark regions ... n: none, i: island under cursor, t: terrain type, s: with ships,", + " u: with units, p: with player units, m: with monsters, f: with units of a faction,", + " c: chaos regions, v: new regions with age 0", + "H: unmark regions (as above)", + "t: select regions (for batch commands, as above)", + "T: un-select regions (as above)", + ";: run batch command for selected regions ... 'r': reset region, 't': terraform', 'f': fix (very special)", + "", + "", + "OTHER", + "", + "Ctrl+L: redraw", + "L: open lua prompt (exit/execute with enter)", + "Q: quit", + "" }; + int lines = sizeof help / sizeof *help, cols = 0; + const char* title = "HELP (exit with q)"; + const int BORDERX = 2, BORDERY = 2; + bool exit = FALSE; + + for (line = 0; line < lines; ++line) { + if (cols < (int) strlen(help[line])) cols = (int) strlen(help[line]); + } + + getmaxyx(stdscr, height, width); + + wn = newwin(height - 2 * BORDERY, width - 2 * BORDERY, 2, 2); + pad = newpad(lines, cols); + box(wn, 0, 0); + mvwprintw(wn, 0, 2, "[ %s ]", title); + for (line=0;line lines - 1) line = lines - 1; + if (col > cols - width / 2 + BORDERX * 2) col = cols - width / 2 + BORDERX * 2; + if (col < 0) col = 0; + + box(wn, 0, 0); + mvwprintw(wn, 0, 2, "[ %s %d/%d %d/%d]", title, line+1, lines, col+1, cols); + wnoutrefresh(wn); + wrefresh(wn); + prefresh(pad, line, col, BORDERY + 1, BORDERX + 2, height - BORDERY * 2, width - BORDERX * 2); + + if (!exit) ch = getch(); + } +} + static void handlekey(state * st, int c) { window *wnd; @@ -1148,6 +1275,13 @@ static void handlekey(state * st, int c) case KEY_OPEN: loaddata(st); break; + case '?': /* help */ + show_help(); + st->modified = 1; + st->wnd_info->update |= 1; + st->wnd_status->update |= 1; + st->wnd_map->update |= 3; + break; case 'B': cnormalize(&st->cursor, &nx, &ny); minpop = config_get_int("editor.island.min", 8); From f29e8055f42dec36e7549438f4a6b4675ba694cd Mon Sep 17 00:00:00 2001 From: Steffen <1808071+stm2@users.noreply.github.com> Date: Sat, 16 Sep 2023 14:01:58 +0200 Subject: [PATCH 20/29] units without items given to peasants on QUIT (#1020) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sehr schön, sogar mit Tests! --- src/kernel/faction.c | 18 +++++++++--------- src/kernel/faction.test.c | 25 ++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/kernel/faction.c b/src/kernel/faction.c index e2f364867..6a54f56aa 100755 --- a/src/kernel/faction.c +++ b/src/kernel/faction.c @@ -424,21 +424,21 @@ void destroyfaction(faction ** fp) } while (u) { + region *r = u->region; /* give away your stuff, to ghosts if you cannot (quest items) */ if (u->items) { - region *r = u->region; int result = gift_items(u, GIFT_FRIENDS | GIFT_PEASANTS); if (result != 0) { save_special_items(u); } - if (r->land && playerrace(u_race(u))) { - const race *rc = u_race(u); - /* Personen gehen nur an die Bauern, wenn sie auch von dort stammen */ - if ((rc->ec_flags & ECF_REC_ETHEREAL) == 0) { - int p = rpeasants(u->region); - p += (int)(u->number / rc->recruit_multi); - rsetpeasants(r, p); - } + } + if (r->land && playerrace(u_race(u))) { + const race *rc = u_race(u); + /* Personen gehen nur an die Bauern, wenn sie auch von dort stammen */ + if ((rc->ec_flags & ECF_REC_ETHEREAL) == 0) { + int p = rpeasants(u->region); + p += (int)(u->number / rc->recruit_multi); + rsetpeasants(r, p); } } set_number(u, 0); diff --git a/src/kernel/faction.test.c b/src/kernel/faction.test.c index 728ceac8a..066431e04 100644 --- a/src/kernel/faction.test.c +++ b/src/kernel/faction.test.c @@ -42,20 +42,38 @@ static void test_destroyfaction(CuTest *tc) { init_resources(); r = test_create_plain(0, 0); - rsethorses(r, 10); rsetpeasants(r, 100); + f = test_create_faction(); + u = test_create_unit(f, r); + scale_number(u, 100); + CuAssertPtrEquals(tc, f, factions); + CuAssertPtrEquals(tc, NULL, f->next); + destroyfaction(&factions); + CuAssertPtrEquals(tc, NULL, factions); + CuAssertIntEquals(tc, 200, rpeasants(r)); + test_teardown(); +} + +static void test_destroyfaction_items(CuTest *tc) { + faction *f; + region *r; + unit* u; + + test_setup(); + init_resources(); + + r = test_create_plain(0, 0); + rsethorses(r, 10); rsetmoney(r, 1000); f = test_create_faction(); u = test_create_unit(f, r); i_change(&u->items, it_find("horse"), 10); i_change(&u->items, it_find("money"), 1000); - scale_number(u, 100); CuAssertPtrEquals(tc, f, factions); CuAssertPtrEquals(tc, NULL, f->next); destroyfaction(&factions); CuAssertPtrEquals(tc, NULL, factions); CuAssertIntEquals(tc, 20, rhorses(r)); - CuAssertIntEquals(tc, 200, rpeasants(r)); CuAssertIntEquals(tc, 2000, rmoney(r)); test_teardown(); } @@ -563,6 +581,7 @@ CuSuite *get_faction_suite(void) SUITE_ADD_TEST(suite, test_addfaction); SUITE_ADD_TEST(suite, test_remove_empty_factions); SUITE_ADD_TEST(suite, test_destroyfaction); + SUITE_ADD_TEST(suite, test_destroyfaction_items); SUITE_ADD_TEST(suite, test_destroyfaction_undead); SUITE_ADD_TEST(suite, test_destroyfaction_demon); SUITE_ADD_TEST(suite, test_destroyfaction_orc); From 8210e76304a46839d12507c6a8564d432e88c15f Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 16 Sep 2023 18:39:02 +0200 Subject: [PATCH 21/29] try location error 22 cause (#1027) * narrow down failing test' * on failure, print the filename --- .gitignore | 3 --- scripts/tests/e2/report.lua | 6 ++++++ scripts/tests/e3/items.lua | 1 + src/gmtool.c | 1 - src/reports.c | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 0d59349f5..a894eecff 100644 --- a/.gitignore +++ b/.gitignore @@ -38,10 +38,7 @@ Thumbs.db *.cfg *.cmd tmp/ -tests/orders.txt tests/config.lua -/tests/reports -/tests/data /quicklist /cutest /critbit diff --git a/scripts/tests/e2/report.lua b/scripts/tests/e2/report.lua index 00a3e0628..e4ece2012 100644 --- a/scripts/tests/e2/report.lua +++ b/scripts/tests/e2/report.lua @@ -39,6 +39,7 @@ function test_coordinates_unnamed_plane() local f = faction.create("human", "unnamed@eressea.de", "de") local u = unit.create(f, r, 1) init_reports() + f.id = 1 write_report(f) assert_true(find_in_report(f, r.name .. " %(0,0%), Berg")) remove_report(f) @@ -49,6 +50,7 @@ function test_coordinates_no_plane() local f = faction.create("human", "noplane@eressea.de", "de") local u = unit.create(f, r, 1) init_reports() + f.id = 2 write_report(f) assert_true(find_in_report(f, r.name .. " %(0,0%), Berg")) remove_report(f) @@ -63,6 +65,7 @@ function test_show_shadowmaster_attacks() u:add_order("ZEIGE Schattenmeister") process_orders() init_reports() + f.id = 3 write_report(f) assert_false(find_in_report(f, ", ,")) remove_report(f) @@ -74,6 +77,7 @@ function test_coordinates_named_plane() local f = faction.create("human", "noreply@eressea.de", "de") local u = unit.create(f, r, 1) init_reports() + f.id = 4 write_report(f) assert_true(find_in_report(f, r.name .. " %(0,0,Hell%), Berg")) remove_report(f) @@ -85,6 +89,7 @@ function test_coordinates_noname_plane() local f = faction.create("human", "noreply@eressea.de", "de") local u = unit.create(f, r, 1) init_reports() + f.id = 5 write_report(f) assert_true(find_in_report(f, r.name .. " %(0,0%), Berg")) remove_report(f) @@ -110,6 +115,7 @@ function test_lighthouse() assert_not_nil(b) init_reports() + f.id = 6 write_report(f) assert_false(find_in_report(f, "The Babadook")) assert_true(find_in_report(f, " %(1,0%) %(vom Turm erblickt%)")) diff --git a/scripts/tests/e3/items.lua b/scripts/tests/e3/items.lua index e1a20430b..08c9199a7 100644 --- a/scripts/tests/e3/items.lua +++ b/scripts/tests/e3/items.lua @@ -26,6 +26,7 @@ function test_water_of_life() trees = r:get_resource('tree')-trees if trees~=5 then init_reports() + f.id = atoi36('tree') write_report(f) print(f, get_turn()) end diff --git a/src/gmtool.c b/src/gmtool.c index 017de45a7..7a31a3522 100644 --- a/src/gmtool.c +++ b/src/gmtool.c @@ -43,7 +43,6 @@ #include #include #include -#include static int g_quit; int force_color = 0; diff --git a/src/reports.c b/src/reports.c index ef6833921..66451bd8c 100644 --- a/src/reports.c +++ b/src/reports.c @@ -1542,7 +1542,7 @@ int write_reports(faction * f, int options, const char *password) } if (errno) { error = errno; - log_fatal("error %d during %s report for faction %s: %s", errno, rtype->extension, factionname(f), strerror(error)); + log_fatal("error %d writing report %s for faction %s: %s", errno, filename, factionname(f), strerror(error)); errno = 0; } } while (error); From 40480d0669a06e16cf364ba336b2682a5d2e200d Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 16 Sep 2023 20:58:46 +0200 Subject: [PATCH 22/29] remove CMAKE_INSTALL_PREFIX variable let install script use cmake --install --- CMakeLists.txt | 14 ++++++-------- s/cmake-init | 1 - s/install | 5 +++-- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index de1b08fa0..02c2fcd9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,12 +79,10 @@ add_subdirectory (clibs) add_subdirectory (process) add_subdirectory (src eressea) -install(DIRECTORY etc DESTINATION ${CMAKE_INSTALL_PREFIX} FILES_MATCHING PATTERN "*.txt") -install(DIRECTORY res conf DESTINATION ${CMAKE_INSTALL_PREFIX} FILES_MATCHING PATTERN "*.po") -install(DIRECTORY res conf DESTINATION ${CMAKE_INSTALL_PREFIX} FILES_MATCHING PATTERN "*.xml") -install(DIRECTORY res conf DESTINATION ${CMAKE_INSTALL_PREFIX} FILES_MATCHING PATTERN "*.json") -install(DIRECTORY scripts DESTINATION ${CMAKE_INSTALL_PREFIX} PATTERN "tests" EXCLUDE) -install(DIRECTORY lunit DESTINATION ${CMAKE_INSTALL_PREFIX} FILES_MATCHING PATTERN "*.lua") -install(DIRECTORY share DESTINATION ${CMAKE_INSTALL_PREFIX}) - +install(DIRECTORY etc DESTINATION "." FILES_MATCHING PATTERN "*.txt") +install(DIRECTORY res conf DESTINATION "." FILES_MATCHING PATTERN "*.po") +install(DIRECTORY res conf DESTINATION "." FILES_MATCHING PATTERN "*.xml") +install(DIRECTORY res conf DESTINATION "." FILES_MATCHING PATTERN "*.json") +install(DIRECTORY scripts DESTINATION "." PATTERN "tests" EXCLUDE) +install(DIRECTORY share DESTINATION ".") diff --git a/s/cmake-init b/s/cmake-init index e416a560a..33391505d 100755 --- a/s/cmake-init +++ b/s/cmake-init @@ -98,7 +98,6 @@ cat >| build/config.cmake < Date: Sat, 16 Sep 2023 21:48:09 +0200 Subject: [PATCH 23/29] print full pathname in case of error --- src/reports.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/reports.c b/src/reports.c index 66451bd8c..1b5872260 100644 --- a/src/reports.c +++ b/src/reports.c @@ -1542,7 +1542,7 @@ int write_reports(faction * f, int options, const char *password) } if (errno) { error = errno; - log_fatal("error %d writing report %s for faction %s: %s", errno, filename, factionname(f), strerror(error)); + log_fatal("error %d writing report %s for faction %s: %s", errno, path, factionname(f), strerror(error)); errno = 0; } } while (error); From 1b5059c718fd1a0b6bd63e8dd30262eee0171a9f Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Sat, 16 Sep 2023 21:52:26 +0200 Subject: [PATCH 24/29] remove stray write_report call this resolves issue #1026 --- scripts/tests/e2/e2features.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/tests/e2/e2features.lua b/scripts/tests/e2/e2features.lua index 5b2488280..7f703d788 100644 --- a/scripts/tests/e2/e2features.lua +++ b/scripts/tests/e2/e2features.lua @@ -652,6 +652,5 @@ function test_bug_2978() u:set_skill("trade", 10) u:set_skill("building", 10) process_orders() - write_report(f) assert_equal(1, u:get_item("jewel")) end From e2756ef277c5e5e9d19ffff7a4ee66238df3d9a5 Mon Sep 17 00:00:00 2001 From: Steffen <1808071+stm2@users.noreply.github.com> Date: Sat, 16 Sep 2023 21:57:29 +0200 Subject: [PATCH 25/29] added 'O' as shortcut for loaddata (#1022) Co-authored-by: Enno Rehling --- src/gmtool.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gmtool.c b/src/gmtool.c index 7a31a3522..5c4cdf649 100644 --- a/src/gmtool.c +++ b/src/gmtool.c @@ -1271,6 +1271,7 @@ static void handlekey(state * st, int c) case KEY_SAVE: savedata(st); break; + case 'O': case KEY_OPEN: loaddata(st); break; From 6e06b28b5a75ca8beebada7b57da6068e100944c Mon Sep 17 00:00:00 2001 From: Steffen <1808071+stm2@users.noreply.github.com> Date: Sun, 17 Sep 2023 10:14:33 +0200 Subject: [PATCH 26/29] Python3 (#1024) * upgrade to python3 * get rid of python2 compatibility --------- Co-authored-by: Enno Rehling --- process/accept-orders.py | 104 ++++++++++++++++++++------------------- process/checkpasswd.py | 2 +- process/compress.py | 9 ++-- process/epasswd.py | 2 +- process/getemail.py | 2 +- process/getfaction.py | 2 +- process/orders-accept | 2 +- 7 files changed, 63 insertions(+), 60 deletions(-) diff --git a/process/accept-orders.py b/process/accept-orders.py index 9a2987530..767d3b3e2 100755 --- a/process/accept-orders.py +++ b/process/accept-orders.py @@ -1,23 +1,20 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- import os import os.path -import ConfigParser +import io +from configparser import ConfigParser import string import logging import sys import subprocess import time import socket -import rfc822 from stat import ST_MTIME -from email.Utils import parseaddr -from email.Parser import Parser +from email.utils import parseaddr, parsedate_tz, mktime_tz +from email.parser import Parser -if sys.version_info[0] > 2: - print("this script has not yet been converted to work with python 3") - sys.exit(2) if 'ERESSEA' in os.environ: dir = os.environ['ERESSEA'] elif 'HOME' in os.environ: @@ -39,7 +36,7 @@ if not os.path.exists(inifile): print("no such file: " . inifile) else: - config = ConfigParser.ConfigParser() + config = ConfigParser() config.read(inifile) if config.has_option('game', 'email'): frommail = config.get('game', 'email') @@ -63,57 +60,57 @@ messages = { "multipart-en" : - "ERROR: The orders you sent contain no plaintext. " \ - "The Eressea server cannot process orders containing HTML " \ - "or invalid attachments, which are the reasons why this " \ - "usually happens. Please change the settings of your mail " \ - "software and re-send the orders.", + u"ERROR: The orders you sent contain no plaintext. " \ + u"The Eressea server cannot process orders containing HTML " \ + u"or invalid attachments, which are the reasons why this " \ + u"usually happens. Please change the settings of your mail " \ + u"software and re-send the orders.", "multipart-de" : - "FEHLER: Die von dir eingeschickte Mail enthält keinen " \ - "Text. Evtl. hast Du den Zug als HTML oder als anderweitig " \ - "ungültig formatierte Mail ingeschickt. Wir können ihn " \ - "deshalb nicht berücksichtigen. Schicke den Zug nochmals " \ - "als reinen Text ohne Formatierungen ein.", + u"FEHLER: Die von dir eingeschickte Mail enthält keinen " \ + u"Text. Evtl. hast Du den Zug als HTML oder als anderweitig " \ + u"ungültig formatierte Mail ingeschickt. Wir können ihn " \ + u"deshalb nicht berücksichtigen. Schicke den Zug nochmals " \ + u"als reinen Text ohne Formatierungen ein.", "maildate-de": - "Es erreichte uns bereits ein Zug mit einem späteren " \ - "Absendedatum (%s > %s). Entweder ist deine " \ - "Systemzeit verstellt, oder ein Zug hat einen anderen Zug von " \ - "dir auf dem Transportweg überholt. Entscheidend für die " \ - "Auswertungsreihenfolge ist das Absendedatum, d.h. der Date:-Header " \ - "deiner Mail.", + u"Es erreichte uns bereits ein Zug mit einem späteren " \ + u"Absendedatum (%s > %s). Entweder ist deine " \ + u"Systemzeit verstellt, oder ein Zug hat einen anderen Zug von " \ + u"dir auf dem Transportweg überholt. Entscheidend für die " \ + u"Auswertungsreihenfolge ist das Absendedatum, d.h. der Date:-Header " \ + u"deiner Mail.", "maildate-en": - "The server already received an order file that was sent at a later " \ - "date (%s > %s). Either your system clock is wrong, or two messages have " \ - "overtaken each other on the way to the server. The order of " \ - "execution on the server is always according to the Date: header in " \ - "your mail.", + u"The server already received an order file that was sent at a later " \ + u"date (%s > %s). Either your system clock is wrong, or two messages have " \ + u"overtaken each other on the way to the server. The order of " \ + u"execution on the server is always according to the Date: header in " \ + u"your mail.", "nodate-en": - "Your message did not contain a valid Date: header in accordance with RFC2822.", + u"Your message did not contain a valid Date: header in accordance with RFC2822.", "nodate-de": - "Deine Nachricht enthielt keinen gueltigen Date: header nach RFC2822.", + u"Deine Nachricht enthielt keinen gueltigen Date: header nach RFC2822.", "error-de": - "Fehler", + u"Fehler", "error-en": - "Error", + u"Error", "warning-de": - "Warnung", + u"Warnung", "warning-en": - "Warning", + u"Warning", "subject-de": - "Befehle angekommen", + u"Befehle angekommen", "subject-en": - "orders received" + u"orders received" } # return 1 if addr is a valid email address @@ -178,7 +175,7 @@ def available_file(dirname, basename): return maxdate, filename def formatpar(string, l=76, indent=2): - words = string.split(string) + words = string.split() res = "" ll = 0 first = 1 @@ -190,7 +187,7 @@ def formatpar(string, l=76, indent=2): ll = len(word) else: if ll + len(word) > l: - res = res + "\n"+" "*indent+word + res = res + u"\n"+" "*indent+word ll = len(word) + indent else: res = res+" "+word @@ -199,7 +196,7 @@ def formatpar(string, l=76, indent=2): return res+"\n" def store_message(message, filename): - outfile = open(filename, "w") + outfile = io.open(filename, "wb") outfile.write(message.as_string()) outfile.close() return @@ -225,7 +222,7 @@ def write_part(outfile, part, sender): except: outfile.write(msg) return False - outfile.write("\n"); + outfile.write("\n".encode('ascii')); return True def copy_orders(message, filename, sender, mtime): @@ -234,13 +231,13 @@ def copy_orders(message, filename, sender, mtime): if writeheaders: header_dir = dirname + '/headers' if not os.path.exists(header_dir): os.mkdir(header_dir) - outfile = open(header_dir + '/' + basename, "w") + outfile = io.open(header_dir + '/' + basename, "wb") for name, value in message.items(): - outfile.write(name + ": " + value + "\n") + outfile.write((name + ": " + value + "\n").encode('utf8', 'ignore')) outfile.close() found = False - outfile = open(filename, "w") + outfile = io.open(filename, "wb") if message.is_multipart(): for part in message.get_payload(): if write_part(outfile, part, sender): @@ -284,7 +281,7 @@ def accept(game, locale, stream, extend=None): if maildate is None: turndate = time.time() else: - turndate = rfc822.mktime_tz(rfc822.parsedate_tz(maildate)) + turndate = mktime_tz(parsedate_tz(maildate)) text_ok = copy_orders(message, filename, email, turndate) @@ -292,7 +289,7 @@ def accept(game, locale, stream, extend=None): if not maildate is None: os.utime(filename, (turndate, turndate)) logger.debug("mail date is '%s' (%d)" % (maildate, turndate)) - if False and turndate < maxdate: + if turndate < maxdate: logger.warning("inconsistent message date " + email) warning = " (" + messages["warning-" + locale] + ")" msg = msg + formatpar(messages["maildate-" + locale] % (time.ctime(maxdate), time.ctime(turndate)), 76, 2) + "\n" @@ -312,14 +309,19 @@ def accept(game, locale, stream, extend=None): savedir = savedir + "/rejected" if not os.path.exists(savedir): os.mkdir(savedir) maxdate, filename = available_file(savedir, prefix + email) - store_message(message, filename) + if filename is None: + logger.error("too many failed attempts") + else: + store_message(message, filename) fail = True if sendmail and warning is not None: logger.warning(warning) subject = gamename + " " + messages["subject-"+locale] + warning - ps = subprocess.Popen(['mutt', '-s', subject, email], stdin=subprocess.PIPE) - ps.communicate(msg) + ps = subprocess.Popen(['mutt', '-s', subject, email], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output = ps.communicate(msg.encode("utf8", "ignore")) + if output[0] != '': + logger.warning(output[0]) if not sendmail: print(text_ok, fail, email) @@ -341,7 +343,7 @@ def accept(game, locale, stream, extend=None): locale = sys.argv[2] infile = sys.stdin if len(sys.argv)>3: - infile = open(sys.argv[3], "r") + infile = io.open(sys.argv[3], "rt") retval = accept(game, locale, infile, delay) if infile!=sys.stdin: infile.close() diff --git a/process/checkpasswd.py b/process/checkpasswd.py index ac58e7c73..fdc8613d0 100755 --- a/process/checkpasswd.py +++ b/process/checkpasswd.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys, re from epasswd import EPasswd diff --git a/process/compress.py b/process/compress.py index f87c6a069..ade1a6c0d 100755 --- a/process/compress.py +++ b/process/compress.py @@ -1,7 +1,8 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 from sys import argv, exit import os +import io import os.path gamename='Eressea' @@ -9,7 +10,7 @@ if(len(argv) >= 3): gamename=argv[2] -template="""#!/bin/bash +template=u"""#!/bin/bash #PATH=$PATH:$HOME/bin addr=%(email)s @@ -19,7 +20,7 @@ turn = argv[1] try: - infile = open("reports.txt", "rt") + infile = io.open("reports.txt", "rt") except: print("%s: reports.txt file does not exist" % (argv[0], )) exit(0) @@ -78,7 +79,7 @@ if os.path.isfile(extra): files = files + [extra] options["files"] = ' '.join(files) - batch = open("%s.sh" % options["faction"], "wt") + batch = io.open("%s.sh" % options["faction"], "wt") batch.write(template % options) batch.close() infile.close() diff --git a/process/epasswd.py b/process/epasswd.py index 913b2ac93..0ddafd8bc 100755 --- a/process/epasswd.py +++ b/process/epasswd.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/python3 import bcrypt import sqlite3 diff --git a/process/getemail.py b/process/getemail.py index d9951bcb0..de648508e 100755 --- a/process/getemail.py +++ b/process/getemail.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys, re from epasswd import EPasswd diff --git a/process/getfaction.py b/process/getfaction.py index 11350ec8a..ad00e2b51 100755 --- a/process/getfaction.py +++ b/process/getfaction.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import sys, re from epasswd import EPasswd diff --git a/process/orders-accept b/process/orders-accept index c5db0d38a..8058eb023 100755 --- a/process/orders-accept +++ b/process/orders-accept @@ -11,7 +11,7 @@ BIN=$(dirname "$SCRIPT") cd "$ERESSEA/game-$game" mkdir -p orders.dir cd orders.dir -eval "$(python2.7 "$BIN/accept-orders.py" "$@")" +eval "$("$BIN/accept-orders.py" "$@")" if [ -e "$ACCEPT_FILE" ] then rm -f "$LOCKFILE" From 4eb9f067cffb96568f3c1feff76067c155955b8f Mon Sep 17 00:00:00 2001 From: Steffen Mecke Date: Mon, 25 Sep 2023 12:27:13 +0200 Subject: [PATCH 27/29] fixed build-e3.lua --- scripts/tools/build-e3.lua | 4 ++-- src/bindings.c | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/tools/build-e3.lua b/scripts/tools/build-e3.lua index b33927877..09ef0b114 100644 --- a/scripts/tools/build-e3.lua +++ b/scripts/tools/build-e3.lua @@ -177,7 +177,7 @@ function justWords(str) end function rebuild() - free_game() + eressea.free_game() local w = 110 local h = 80 local pl = plane.create(0, -w/2, -h/2, w+1, h+1) @@ -187,7 +187,7 @@ function rebuild() end function testwelt() - free_game() + eressea.free_game() local w = 10 local h = 10 local pl = plane.create(0, -w/2, -h/2, w+1, h+1) diff --git a/src/bindings.c b/src/bindings.c index 11cb57299..e175bb51b 100755 --- a/src/bindings.c +++ b/src/bindings.c @@ -5,6 +5,7 @@ #include "bind_tolua.h" #include "console.h" +#include "creport.h" #include "gamedb.h" #include "helpers.h" #include "laws.h" @@ -449,7 +450,7 @@ static int tolua_write_summary(lua_State * L) free_summary(sum); return 0; } -/* + static int tolua_write_map(lua_State * L) { const char *filename = tolua_tostring(L, 1, 0); @@ -458,7 +459,7 @@ static int tolua_write_map(lua_State * L) } return 0; } -*/ + static int tolua_read_turn(lua_State * L) { int cturn = current_turn(); @@ -973,6 +974,7 @@ int tolua_bindings_open(lua_State * L, const dictionary *inifile) tolua_function(L, "write_summary", tolua_write_summary); tolua_function(L, "write_passwords", tolua_write_passwords); tolua_function(L, "write_database", tolua_write_database); + tolua_function(L, "write_map", tolua_write_map); tolua_function(L, "message_unit", tolua_message_unit); tolua_function(L, "message_faction", tolua_message_faction); tolua_function(L, "message_region", tolua_message_region); From 1ab7e14720faf481c46afc9e4b5c89b1d6de71a2 Mon Sep 17 00:00:00 2001 From: Steffen Mecke Date: Thu, 21 Sep 2023 23:33:07 +0200 Subject: [PATCH 28/29] fixed NPE --- src/modules/autoseed.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/autoseed.c b/src/modules/autoseed.c index 8817f1854..5b7e1d137 100644 --- a/src/modules/autoseed.c +++ b/src/modules/autoseed.c @@ -791,7 +791,7 @@ static void smooth_island(region_list * island) int n, nland = 0; get_neighbours(r, rn); for (n = 0; n != MAXDIRECTIONS && nland <= 1; ++n) { - if (rn[n]->land) { + if (rn[n] && rn[n]->land) { ++nland; r = rn[n]; } @@ -803,7 +803,7 @@ static void smooth_island(region_list * island) for (n = 0; n != MAXDIRECTIONS; ++n) { int n1 = (n + 1) % MAXDIRECTIONS; int n2 = (n + 1 + MAXDIRECTIONS) % MAXDIRECTIONS; - if (!rn[n]->land && rn[n1] != r && rn[n2] != r) { + if (rn[n] && !rn[n]->land && rn[n1] != r && rn[n2] != r) { r = rlist->data; runhash(r); runhash(rn[n]); From d23ead594d9cc05aedbe386471ab14c09687ccb3 Mon Sep 17 00:00:00 2001 From: Steffen Mecke Date: Thu, 21 Sep 2023 23:48:44 +0200 Subject: [PATCH 29/29] fixed autoseed --- scripts/eressea/autoseed.lua | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/scripts/eressea/autoseed.lua b/scripts/eressea/autoseed.lua index fc658d1a1..1aee4e3ea 100644 --- a/scripts/eressea/autoseed.lua +++ b/scripts/eressea/autoseed.lua @@ -8,7 +8,8 @@ local trees = 800 local min_peasants = 2000 -- number of starters per region: local per_region = 2 - + +-- count a resource in a neighborhood local function score(r, res) assert(r) res = res or "peasant" @@ -22,10 +23,11 @@ local function score(r, res) return peas end +-- select regions with enough resources local function select_regions(regions, peasants, trees) local sel = {} for r in regions do - if not r.plane and r.terrain~="ocean" and not r.units() then + if (not r.plane or r.plane.id == 0) and r.terrain~="ocean" and not r.units() then if r:get_resource("peasant") >= min_peasants then sp = score(r, "peasant") st = score(r, "tree") @@ -40,6 +42,7 @@ local function select_regions(regions, peasants, trees) return sel end +-- read a newfactions file local function read_players() -- return {{ email = "noreply@mailinator.com", race = "dwarf", lang = "de" }} local players = {} @@ -59,6 +62,7 @@ local function read_players() return players end +-- create a new faction local function seed(r, email, race, lang) assert(r) local f = faction.create(race, email, lang) @@ -83,8 +87,8 @@ local function get_faction_by_email(email) return nil end +-- seeds players from newfactions into existing regions with enough resources function autoseed.init() - -- local newbs = {} local num_seeded = per_region local start = nil @@ -97,17 +101,18 @@ function autoseed.init() local sel eressea.log.info(#players .. ' new players') sel = select_regions(regions(), peasants, trees) - if #sel == 0 then - eressea.log.error("autoseed could not select regions for new factions") + local num_sel = #sel + if num_sel * per_region < #players then + eressea.log.error("autoseed could not select enough regions for new factions") else for _, p in ipairs(players) do if num_seeded == per_region then - local index = rng_int() % #sel + local index = rng_int() % num_sel start = nil while not start do start = sel[index + 1] - sel[index+1] = nil - index = (index + 1) % #sel + sel[index + 1] = nil + index = (index + 1) % num_sel end num_seeded = 0 end