#ifdef _MSC_VER # include #endif #include #include "unit.h" #include "ally.h" #include "attrib.h" #include "building.h" #include "calendar.h" #include "connection.h" #include "curse.h" #include "event.h" #include "faction.h" #include "gamedata.h" #include "group.h" #include "guard.h" #include "item.h" #include "move.h" #include "order.h" #include "plane.h" #include "race.h" #include "region.h" #include "spell.h" #include "spellbook.h" #include "ship.h" #include "skill.h" #include "terrain.h" #include "terrainid.h" #include #include #include #include #include /* util includes */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* libc includes */ #include #include #include #include #include #include int weight(const unit * u) { int w = 0, n = 0, in_bag = 0; const resource_type *rtype = get_resourcetype(R_BAG_OF_HOLDING); item *itm; for (itm = u->items; itm; itm = itm->next) { w = itm->type->weight * itm->number; n += w; if (rtype && !fval(itm->type, ITF_BIG)) { in_bag += w; } } n += u->number * u_race(u)->weight; if (rtype) { w = i_get(u->items, rtype->itype) * BAGCAPACITY; if (w > in_bag) w = in_bag; n -= w; } return n; } attrib_type at_creator = { "creator" /* Rest ist NULL; temporaeres, nicht alterndes Attribut */ }; unit *findunit(int n) { if (n <= 0) { return NULL; } return ufindhash(n); } unit *findunitr(const region * r, int n) { unit *u; /* findunit regional! */ assert(n > 0); u = ufindhash(n); return (u && u->region == r) ? u : 0; } #define UMAXHASH MAXUNITS static unit *unithash[UMAXHASH]; static unit *delmarker = (unit *)unithash; /* a funny hack */ #define HASH_STATISTICS 1 #if HASH_STATISTICS static int hash_requests; static int hash_misses; #endif void uhash(unit * u) { int key = HASH1(u->no, UMAXHASH), gk = HASH2(u->no, UMAXHASH); while (unithash[key] != NULL && unithash[key] != delmarker && unithash[key] != u) { key = (key + gk) % UMAXHASH; } assert(unithash[key] != u || !"trying to add the same unit twice"); unithash[key] = u; } void uunhash(unit * u) { int key = HASH1(u->no, UMAXHASH), gk = HASH2(u->no, UMAXHASH); while (unithash[key] != NULL && unithash[key] != u) { key = (key + gk) % UMAXHASH; } assert(unithash[key] == u || !"trying to remove a unit that is not hashed"); unithash[key] = delmarker; } unit *ufindhash(int uid) { assert(uid >= 0); #if HASH_STATISTICS ++hash_requests; #endif if (uid >= 0) { int key = HASH1(uid, UMAXHASH), gk = HASH2(uid, UMAXHASH); while (unithash[key] != NULL && (unithash[key] == delmarker || unithash[key]->no != uid)) { key = (key + gk) % UMAXHASH; #if HASH_STATISTICS ++hash_misses; #endif } return unithash[key]; } return NULL; } typedef struct buddy { struct buddy *next; int number; faction *faction; unit *unit; } buddy; static buddy *get_friends(const unit * u, int *numfriends) { buddy *friends = 0; faction *f = u->faction; region *r = u->region; int number = 0; unit *u2; for (u2 = r->units; u2; u2 = u2->next) { if (u2->faction != f && u2->number > 0) { int allied = 0; if (config_get_int("rules.alliances", 0) != 0) { allied = (f->alliance && f->alliance == u2->faction->alliance); } else if (alliedunit(u, u2->faction, HELP_MONEY) && alliedunit(u2, f, HELP_GIVE)) { allied = 1; } if (allied) { buddy *nf, **fr = &friends; /* some units won't take stuff: */ if (u_race(u2)->ec_flags & ECF_GETITEM) { while (*fr && (*fr)->faction->no < u2->faction->no) fr = &(*fr)->next; nf = *fr; if (nf == NULL || nf->faction != u2->faction) { nf = malloc(sizeof(buddy)); assert(nf); nf->next = *fr; nf->faction = u2->faction; nf->unit = u2; nf->number = 0; *fr = nf; } nf->number += u2->number; number += u2->number; } } } } if (numfriends) *numfriends = number; return friends; } /** give all items to friends or peasants. * this function returns 0 on success, or 1 if there are items that * could not be destroyed. */ int gift_items(unit * u, int flags) { const struct resource_type *rsilver = get_resourcetype(R_SILVER); const struct resource_type *rhorse = get_resourcetype(R_HORSE); region *r = u->region; item **itm_p = &u->items; int retval = 0; assert(u->region); if (u->items == NULL || fval(u_race(u), RCF_ILLUSIONARY)) return 0; /* at first, I should try giving my crap to my own units in this region */ if (u->faction && (u->faction->flags & FFL_QUIT) == 0 && (flags & GIFT_SELF)) { unit *u2, *u3 = NULL; for (u2 = r->units; u2; u2 = u2->next) { if (u2 != u && u2->faction == u->faction && u2->number > 0) { /* some units won't take stuff: */ if (u_race(u2)->ec_flags & ECF_GETITEM) { i_merge(&u2->items, &u->items); u->items = NULL; break; } else if (!u3) { /* pick a last-chance recipient: */ u3 = u2; } } } if (u->items && u3) { /* if nobody else takes it, we give it to a unit that has issues */ i_merge(&u3->items, &u->items); u->items = NULL; } if (u->items == NULL) return 0; } /* if I have friends, I'll try to give my stuff to them */ if (u->faction && (flags & GIFT_FRIENDS)) { int number = 0; buddy *friends = get_friends(u, &number); while (friends) { struct buddy *nf = friends; unit *u2 = nf->unit; item *itm = u->items; while (itm != NULL) { const item_type *itype = itm->type; item *itn = itm->next; int n = itm->number; n = n * nf->number / number; if (n > 0) { i_change(&u->items, itype, -n); i_change(&u2->items, itype, n); } itm = itn; } number -= nf->number; friends = nf->next; free(nf); } if (u->items == NULL) return 0; } /* last, but not least, give money and horses to peasants */ while (*itm_p) { item *itm = *itm_p; if (flags & GIFT_PEASANTS) { if (u->region->land) { if (itm->type->rtype == rsilver) { rsetmoney(r, rmoney(r) + itm->number); itm->number = 0; } else if (itm->type->rtype == rhorse) { rsethorses(r, rhorses(r) + itm->number); itm->number = 0; } } } if (itm->number > 0 && (itm->type->flags & ITF_NOTLOST)) { itm_p = &itm->next; retval = -1; } else { i_remove(itm_p, itm); i_free(itm); } } return retval; } /** remove the unit from the list of active units. * the unit is not actually freed, because there may still be references * dangling to it (from messages, for example). To free all removed units, * call free_units(). * returns 0 on success, or -1 if unit could not be removed. */ static unit *deleted_units = NULL; struct faction *dfindhash(int no) { unit *u; for (u = deleted_units; u; u = u->next) { if (u->no == no) { return u->faction; } } return NULL; } int remove_unit(unit ** ulist, unit * u) { int result; assert(ufindhash(u->no)); handle_event(u->attribs, "destroy", u); result = gift_items(u, GIFT_SELF | GIFT_FRIENDS | GIFT_PEASANTS); if (result != 0) { make_zombie(u); return -1; } if (u->number) { set_number(u, 0); } leave(u, true); u->region = NULL; uunhash(u); if (ulist) { while (*ulist != u) { ulist = &(*ulist)->next; } assert(*ulist == u); *ulist = u->next; } if (u->faction) { if (count_unit(u)) { --u->faction->num_units; } if (u->faction->units == u) { u->faction->units = u->nextF; } } if (u->prevF) { u->prevF->nextF = u->nextF; } if (u->nextF) { u->nextF->prevF = u->prevF; } u->nextF = 0; u->prevF = 0; u->next = deleted_units; deleted_units = u; u->region = NULL; return 0; } unit *findnewunit(const region * r, const faction * f, int n) { unit *u2; if (n == 0) return 0; for (u2 = r->units; u2; u2 = u2->next) if (u2->faction == f && ualias(u2) == n) return u2; for (u2 = r->units; u2; u2 = u2->next) if (ualias(u2) == n) return u2; return 0; } /* ------------------------------------------------------------- */ /*********************/ /* at_alias */ /*********************/ static attrib_type at_alias = { "alias", DEFAULT_INIT, DEFAULT_FINALIZE, DEFAULT_AGE, NO_WRITE, NO_READ }; /** remember old unit.no (for the creport, mostly) * if alias is positive, then this unit was a TEMP * if alias is negative, then this unit has been RENUMBERed */ int ualias(const unit * u) { attrib *a = a_find(u->attribs, &at_alias); if (!a) return 0; return a->data.i; } void usetalias(unit *u, int alias) { a_add(&u->attribs, a_new(&at_alias))->data.i = alias; } int a_readprivate(variant *var, void *owner, gamedata *data) { struct storage *store = data->store; char lbuf[DISPLAYSIZE]; READ_STR(store, lbuf, sizeof(lbuf)); var->v = str_strdup(lbuf); return (var->v) ? AT_READ_OK : AT_READ_FAIL; } /*********************/ /* at_private */ /*********************/ attrib_type at_private = { "private", DEFAULT_INIT, a_finalizestring, DEFAULT_AGE, a_writestring, a_readprivate }; const char *u_description(const unit * u, const struct locale *lang) { if (u->display_id > 0) { return unit_getinfo(u); } else { char zText[64]; const char * d; const race * rc = u_race(u); snprintf(zText, sizeof(zText), "describe_%s", rc->_name); d = locale_getstring(lang, zText); if (d) { return d; } } return NULL; } const char *uprivate(const unit * u) { attrib *a = a_find(u->attribs, &at_private); if (!a) return NULL; return a->data.v; } void usetprivate(unit * u, const char *str) { attrib *a = a_find(u->attribs, &at_private); if (str == NULL || *str == 0) { if (a) { a_remove(&u->attribs, a); } return; } if (!a) { a = a_add(&u->attribs, a_new(&at_private)); } if (a->data.v) { free(a->data.v); } a->data.v = str_strdup(str); } /*** ** init & cleanup module **/ void free_units(void) { while (deleted_units) { unit *u = deleted_units; deleted_units = deleted_units->next; free_unit(u); free(u); } } void write_unit_reference(const unit * u, struct storage *store) { WRITE_INT(store, (u && u->region) ? u->no : 0); } void resolve_unit(unit *u) { resolve(RESOLVE_UNIT | u->no, u); } int read_unit_reference(gamedata * data, unit **up, resolve_fun fun) { int id; READ_INT(data->store, &id); if (id > 0) { *up = findunit(id); if (*up == NULL) { *up = NULL; ur_add(RESOLVE_UNIT | id, (void **)up, fun); } } else { *up = NULL; } return id; } int get_level(const unit * u, skill_t id) { assert(id != NOSKILL); if (skill_enabled(id)) { skill *sv = u->skills; while (sv != u->skills + u->skill_size && sv->id <= id) { if (sv->id == id) { return sv->level; } ++sv; } } return 0; } void set_level(unit * u, skill_t sk, int value) { skill *sv = u->skills; assert(sk != SK_MAGIC || value==0 || !u->faction || u->number == 1 || fval(u->faction, FFL_NPC)); assert(value <= CHAR_MAX && value >= CHAR_MIN); if (!skill_enabled(sk)) return; if (value == 0) { remove_skill(u, sk); return; } while (sv != u->skills + u->skill_size && sv->id <= sk) { if (sv->id == sk) { sk_set(sv, value); return; } ++sv; } sk_set(add_skill(u, sk), value); } static int leftship_age(struct attrib *a, void *owner) { /* must be aged, so it doesn't affect report generation (cansee) */ UNUSED_ARG(a); UNUSED_ARG(owner); return AT_AGE_REMOVE; /* remove me */ } static attrib_type at_leftship = { "leftship", NULL, NULL, leftship_age }; static attrib *make_leftship(struct ship *leftship) { attrib *a = a_new(&at_leftship); a->data.v = leftship; return a; } void set_leftship(unit * u, ship * sh) { a_add(&u->attribs, make_leftship(sh)); } ship *leftship(const unit * u) { attrib *a = a_find(u->attribs, &at_leftship); /* Achtung: Es ist nicht garantiert, dass der Rueckgabewert zu jedem * Zeitpunkt noch auf ein existierendes Schiff zeigt! */ if (a) return (ship *)(a->data.v); return NULL; } void u_set_building(unit * u, building * b) { assert(!b || !u->building); /* you must leave first */ u->building = b; if (b && (!b->_owner || b->_owner->number <= 0)) { building_set_owner(u); } } void u_set_ship(unit * u, ship * sh) { assert(!u->ship); /* you must leave_ship */ u->ship = sh; if (sh && (!sh->_owner || sh->_owner->number <= 0)) { ship_set_owner(u); } } void leave_ship(unit * u) { struct ship *sh = u->ship; u->ship = NULL; if (sh->_owner == u) { ship_update_owner(sh); sh->_owner = ship_owner(sh); } set_leftship(u, sh); } void leave_building(unit * u) { building * b = u->building; u->building = NULL; if (b->_owner == u) { building_update_owner(b); assert(b->_owner != u); } } bool can_leave(unit * u) { static int config; static bool rule_leave; if (!u->building) { return true; } if (config_changed(&config)) { rule_leave = config_get_int("rules.move.owner_leave", 0) != 0; } if (rule_leave && u->building && u == building_owner(u->building)) { return false; } return true; } bool leave(unit * u, bool force) { if (!force) { if (!can_leave(u)) { return false; } } if (u->building) { leave_building(u); } else if (u->ship) { leave_ship(u); } return true; } bool can_survive(const unit * u, const region * r) { if ((fval(r->terrain, WALK_INTO) && (u_race(u)->flags & RCF_WALK)) || (fval(r->terrain, SWIM_INTO) && (u_race(u)->flags & RCF_SWIM)) || (fval(r->terrain, FLY_INTO) && (u_race(u)->flags & RCF_FLY))) { if (has_horses(u) && !fval(r->terrain, WALK_INTO)) return false; if (r->attribs) { if (fval(u_race(u), RCF_UNDEAD) && curse_active(get_curse(r->attribs, &ct_holyground))) return false; } return true; } return false; } void move_unit(unit * u, region * r, unit ** ulist) { assert(u && r); assert(u->faction || !"this unit is dead"); if (u->region == r) return; if (!ulist) ulist = (&r->units); if (u->region) { setguard(u, false); fset(u, UFL_MOVED); if (u->ship || u->building) { /* can_leave must be checked in travel_i */ #ifndef NDEBUG bool result = leave(u, false); assert(result); #else leave(u, false); #endif } translist(&u->region->units, ulist, u); } else { addlist(ulist, u); } update_interval(u->faction, r); u->region = r; } /* ist mist, aber wegen nicht skalierender attribute notwendig: */ #include "alchemy.h" void clone_men(const unit * u, unit * dst, int n) { const attrib *a; region *r = u->region; if (n == 0) return; assert(n > 0); /* "hat attackiert"-status wird uebergeben */ if (dst) { skill_t sk; ship *sh; assert(dst->number + n > 0); for (sk = 0; sk != MAXSKILLS; ++sk) { int weeks, level = 0; skill *sv = unit_skill(u, sk); skill *sn = unit_skill(dst, sk); if (sv == NULL && sn == NULL) continue; if (sn == NULL && dst->number == 0) { /* new unit, easy to solve */ level = sv->level; weeks = sv->weeks; } else { double dlevel = 0.0; if (sv && sv->level) { dlevel += (sv->level + 1 - sv->weeks / (sv->level + 1.0)) * n; level += sv->level * n; } if (sn && sn->level) { dlevel += (sn->level + 1 - sn->weeks / (sn->level + 1.0)) * dst->number; level += sn->level * dst->number; } dlevel = dlevel / (n + dst->number); level = level / (n + dst->number); if (level <= dlevel) { /* apply the remaining fraction to the number of weeks to go. * subtract the according number of weeks, getting closer to the * next level */ level = (int)dlevel; weeks = (level + 1) - (int)((dlevel - level) * (level + 1)); } else { /* make it harder to reach the next level. * weeks+level is the max difficulty, 1 - the fraction between * level and dlevel applied to the number of weeks between this * and the previous level is the added difficutly */ level = (int)dlevel + 1; weeks = 1 + 2 * level - (int)((1 + dlevel - level) * level); } } if (level) { if (sn == NULL) sn = add_skill(dst, sk); sn->level = (unsigned char)level; sn->weeks = (unsigned char)weeks; assert(sn->weeks > 0 && sn->weeks <= sn->level * 2 + 1); assert(dst->number != 0 || (sv && sn->level == sv->level && sn->weeks == sv->weeks)); } else if (sn) { remove_skill(dst, sk); sn = NULL; } } a = a_find(u->attribs, &at_effect); while (a && a->type == &at_effect) { effect_data *olde = (effect_data *)a->data.v; if (olde->value) change_effect(dst, olde->type, olde->value); a = a->next; } sh = leftship(u); if (sh != NULL) set_leftship(dst, sh); dst->flags |= u->flags & (UFL_LONGACTION | UFL_NOTMOVING | UFL_HUNGER | UFL_MOVED | UFL_ENTER); if (u->attribs) { transfer_curse(u, dst, n); } set_number(dst, dst->number + n); dst->hp += (long long)u->hp * n / u->number; assert(dst->hp >= dst->number); /* TODO: Das ist schnarchlahm! und gehoert nicht hierhin */ a = a_find(dst->attribs, &at_effect); while (a && a->type == &at_effect) { attrib *an = a->next; effect_data *olde = (effect_data *)a->data.v; int e = get_effect(u, olde->type); if (e != 0) change_effect(dst, olde->type, -e); a = an; } } else if (r->land) { if ((u_race(u)->ec_flags & ECF_REC_ETHEREAL) == 0) { const race *rc = u_race(u); int p = rpeasants(r); p += (int)(n * rc->recruit_multi); rsetpeasants(r, p); } } } void transfermen(unit * u, unit * dst, int n) { clone_men(u, dst, n); scale_number(u, u->number - n); } struct building *inside_building(const struct unit *u) { if (!u->building) { return NULL; } else if (!building_finished(u->building)) { /* Gebaeude noch nicht fertig */ return NULL; } else { int p = 0, cap = buildingcapacity(u->building); const unit *u2; for (u2 = u->region->units; u2; u2 = u2->next) { if (u2->building == u->building) { p += u2->number; if (u2 == u) { if (p <= cap) return u->building; return NULL; } if (p > cap) return NULL; } } } return NULL; } void u_freeorders(unit *u) { free_orders(&u->orders); set_order(&u->thisorder, NULL); } void u_setfaction(unit * u, faction * f) { if (u->faction == f) return; if (u->faction) { if (count_unit(u)) { --u->faction->num_units; u->faction->num_people -= u->number; } set_group(u, NULL); if (u->nextF) { u->nextF->prevF = u->prevF; } if (u->prevF) { u->prevF->nextF = u->nextF; } else { u->faction->units = u->nextF; } } if (f != NULL) { if (f->units) { f->units->prevF = u; } u->prevF = NULL; u->nextF = f->units; f->units = u; } else { u->nextF = NULL; } u->faction = f; if (u->region) { update_interval(f, u->region); } if (f && count_unit(u)) { ++f->num_units; f->num_people += u->number; } } bool count_unit(const unit *u) { const race *rc = u_race(u); /* spells are invisible. units we cannot see do not count to our limit */ return rc == NULL || (rc->flags & RCF_INVISIBLE) == 0; } void set_number(unit * u, int count) { assert(count >= 0); assert(count <= UNIT_MAXSIZE); if (count == 0) { u->flags &= ~(UFL_HERO); } if (u->faction && count_unit(u)) { u->faction->num_people += count - u->number; } u->number = count; } void remove_skill(unit * u, skill_t sk) { int i; for (i = 0; i != u->skill_size; ++i) { skill *sv = u->skills + i; if (sv->id == sk) { if (u->skill_size - i - 1 > 0) { memmove(sv, sv + 1, (u->skill_size - i - 1) * sizeof(skill)); } if (--u->skill_size == 0) { free(u->skills); u->skills = NULL; } return; } } } skill *add_skill(unit * u, skill_t sk) { skill *sv; int i; assert(u); for (i = 0; i != u->skill_size; ++i) { sv = u->skills + i; if (sv->id >= sk) break; } sv = realloc(u->skills, (1 + u->skill_size) * sizeof(skill)); assert(sv); u->skills = sv; sv = u->skills + i; if (i < u->skill_size) { assert(sv->id != sk); memmove(sv + 1, sv, sizeof(skill) * (u->skill_size - i)); } ++u->skill_size; sv->level = 0; sv->weeks = 1; sv->old = 0; sv->id = sk; if (sk == SK_MAGIC && u->faction && !fval(u->faction, FFL_NPC)) { assert(u->number <= 1); assert(max_magicians(u->faction) >= u->number); } return sv; } skill *unit_skill(const unit * u, skill_t sk) { skill *sv = u->skills; while (sv != u->skills + u->skill_size && sv->id <= sk) { if (sv->id == sk) { return sv; } ++sv; } return NULL; } bool has_skill(const unit * u, skill_t sk) { skill *sv = u->skills; while (sv != u->skills + u->skill_size && sv->id <= sk) { if (sv->id == sk) { return (sv->level > 0); } ++sv; } return false; } static int item_invis(const unit *u) { const struct resource_type *rring = get_resourcetype(R_RING_OF_INVISIBILITY); const struct resource_type *rsphere = get_resourcetype(R_SPHERE_OF_INVISIBILITY); return (rring ? i_get(u->items, rring->itype) : 0) + (rsphere ? i_get(u->items, rsphere->itype) * 100 : 0); } static int att_modification(const unit * u, skill_t sk) { double result = 0; if (u->attribs) { curse *c; attrib *a; c = get_curse(u->attribs, &ct_worse); if (c != NULL) { result += curse_geteffect(c); } a = a_find(u->attribs, &at_curse); while (a && a->type == &at_curse) { c = (curse *)a->data.v; if (c->type == &ct_skillmod && c->data.i == sk) { result += curse_geteffect(c); break; } a = a->next; } } /* TODO hier kann nicht mit get/iscursed gearbeitet werden, da nur der * jeweils erste vom Typ C_GBDREAM zurueckgegen wird, wir aber alle * durchsuchen und aufaddieren muessen */ if (u->region && u->region->attribs) { int bonus = 0, malus = 0; attrib *a = a_find(u->region->attribs, &at_curse); while (a && a->type == &at_curse) { curse *c = (curse *)a->data.v; if (c->magician && curse_active(c) && c->type == &ct_gbdream) { int effect = curse_geteffect_int(c); bool allied = alliedunit(c->magician, u->faction, HELP_GUARD); if (allied) { if (effect > bonus) bonus = effect; } else { if (effect < malus) malus = effect; } } a = a->next; } result = result + bonus + malus; } return (int)result; } static int terrain_mod(const race * rc, skill_t sk, const region *r) { static int rc_cache, t_cache; static const race *rc_dwarf, *rc_insect, *rc_elf; static const terrain_type *t_mountain, *t_desert, *t_swamp; const struct terrain_type *terrain = r->terrain; if (terrain_changed(&t_cache)) { t_mountain = get_terrain(terrainnames[T_MOUNTAIN]); t_desert = get_terrain(terrainnames[T_DESERT]); t_swamp = get_terrain(terrainnames[T_SWAMP]); } if (rc_changed(&rc_cache)) { rc_elf = get_race(RC_ELF); rc_dwarf = get_race(RC_DWARF); rc_insect = get_race(RC_INSECT); } if (rc == rc_dwarf) { if (sk == SK_TACTICS) { if (terrain == t_mountain || fval(terrain, ARCTIC_REGION)) return 1; } } else if (rc == rc_insect) { if (terrain == t_mountain || fval(terrain, ARCTIC_REGION)) { return -1; } else if (terrain == t_desert || terrain == t_swamp) { return 1; } } else if (rc == rc_elf) { if (r_isforest(r)) { if (sk == SK_PERCEPTION || sk == SK_STEALTH) { return 1; } else if (sk == SK_TACTICS) { return 2; } } } return 0; } static int rc_skillmod(const struct race *rc, skill_t sk) { if (!skill_enabled(sk)) { return 0; } return rc->bonus[sk]; } int get_modifier(const unit * u, skill_t sk, int level, const region * r, bool noitem) { const struct race *rc = u_race(u); int bskill = level; int skill = bskill; if (r && sk == SK_STEALTH) { plane *pl = rplane(r); if (pl && fval(pl, PFL_NOSTEALTH)) { return 0; } } skill += rc_skillmod(rc, sk); if (skill > 0) { if (r) { skill += terrain_mod(rc, sk, r); } skill += att_modification(u, sk); if (u->attribs) { skill = skillmod(u, r, sk, skill); } } if (fval(u, UFL_HUNGER)) { if (sk == SK_SAILING && skill > 2) { skill = skill - 1; } else { skill = skill / 2; } } return skill - bskill; } int eff_skill(const unit * u, const skill *sv, const region *r) { assert(u); if (!r) r = u->region; if (sv && sv->level > 0) { int mlevel = sv->level + get_modifier(u, sv->id, sv->level, r, false); if (mlevel > 0) { return mlevel; } } return 0; } int effskill_study(const unit * u, skill_t sk) { skill *sv = unit_skill(u, sk); if (sv && sv->level > 0) { int mlevel = sv->level; mlevel += get_modifier(u, sv->id, sv->level, u->region, true); if (mlevel > 0) return mlevel; } return 0; } int invisible(const unit * target, const unit * viewer) { if (viewer && viewer->faction == target->faction) return 0; else { int hidden = item_invis(target); if (hidden) { if (hidden > target->number) hidden = target->number; if (viewer) { const resource_type *rtype = get_resourcetype(R_AMULET_OF_TRUE_SEEING); hidden -= i_get(viewer->items, rtype->itype); } } return hidden; } } /** remove the unit from memory. * this frees all memory that's only accessible through the unit, * and you should already have called uunhash and removed the unit from the * region. */ void free_unit(unit * u) { struct reservation **pres = &u->reservations; assert(!u->region); free(u->_name); free_order(u->thisorder); free_orders(&u->orders); while (*pres) { struct reservation *res = *pres; *pres = res->next; free(res); } if (u->skills) { free(u->skills); u->skills = NULL; } while (u->items) { item *it = u->items->next; u->items->next = NULL; i_free(u->items); u->items = it; } while (u->attribs) { a_remove(&u->attribs, u->attribs); } } static int newunitid(void) { int random_unit_no; int start_random_no; random_unit_no = 1 + (rng_int() % MAX_UNIT_NR); start_random_no = random_unit_no; while (ufindhash(random_unit_no) || dfindhash(random_unit_no) || forbiddenid(random_unit_no)) { random_unit_no++; if (random_unit_no == MAX_UNIT_NR + 1) { random_unit_no = 1; } if (random_unit_no == start_random_no) { random_unit_no = (int)MAX_UNIT_NR + 1; } } return random_unit_no; } static void createunitid(unit * u, int id) { if (id <= 0 || id > MAX_UNIT_NR || ufindhash(id) || dfindhash(id) || forbiddenid(id)) u->no = newunitid(); else u->no = id; uhash(u); } void default_name(const unit *u, char name[], int len) { const char * result; const struct locale * lang = u->faction ? u->faction->locale : default_locale; if (lang) { const char * prefix; prefix = LOC(lang, "unitdefault"); if (!prefix) { prefix = parameters[P_UNIT]; } result = prefix; } else { result = parameters[P_UNIT]; } snprintf(name, len, "%s %s", result, itoa36(u->no)); } void name_unit(unit * u) { const race *rc = u_race(u); if (rc->name_unit) { rc->name_unit(u); } else if (u->faction->flags & FFL_NPC) { unit_setname(u, NULL); } else { char name[32]; default_name(u, name, sizeof(name)); unit_setname(u, name); } } unit *unit_create(int id) { unit *u = (unit *)calloc(1, sizeof(unit)); createunitid(u, id); return u; } /** creates a new unit. * * @param dname: name, set to NULL to get a default. * @param creator: unit to inherit stealth, group, building, ship, etc. from */ unit *create_unit(region * r, faction * f, int number, const struct race *urace, int id, const char *dname, unit * creator) { unit *u = unit_create(id); assert(urace); u_setrace(u, urace); u->irace = NULL; if (f) { assert(faction_alive(f)); u_setfaction(u, f); } set_number(u, number); /* zuerst in die Region setzen, da zb Drachennamen den Regionsnamen * enthalten */ if (r) move_unit(u, r, NULL); /* u->race muss bereits gesetzt sein, wird fuer default-hp gebraucht */ /* u->region auch */ if (number > 0) { u->hp = unit_max_hp(u) * number; } if (dname) { u->_name = str_strdup(dname); } else if (urace->name_unit || playerrace(urace)) { name_unit(u); } if (creator) { attrib *a; /* erbt Kampfstatus und Tarnung */ unit_setstatus(u, creator->status); u->irace = creator->irace; /* erbt Gebaeude/Schiff */ if (creator->region == r) { if (creator->building) { u_set_building(u, creator->building); } if (creator->ship && fval(u_race(u), RCF_CANSAIL)) { u_set_ship(u, creator->ship); } } /* Tarnlimit wird vererbt */ if (fval(creator, UFL_STEALTH)) { attrib *a = a_find(creator->attribs, &at_stealth); if (a) { int stealth = a->data.i; a = a_add(&u->attribs, a_new(&at_stealth)); a->data.i = stealth; } } /* Temps von parteigetarnten Einheiten sind wieder parteigetarnt */ if (fval(creator, UFL_ANON_FACTION)) { fset(u, UFL_ANON_FACTION); } /* Daemonentarnung */ set_racename(&u->attribs, get_racename(creator->attribs)); if (fval(u_race(u), RCF_SHAPESHIFT) && fval(u_race(creator), RCF_SHAPESHIFT)) { u->irace = creator->irace; } /* Gruppen */ if (creator->faction == f) { group *g = get_group(creator); if (g) { set_group(u, g); } } if (creator->attribs) { faction *otherf = get_otherfaction(creator); if (otherf) { a_add(&u->attribs, make_otherfaction(otherf)); } } a = a_add(&u->attribs, a_new(&at_creator)); a->data.v = creator; } return u; } int maxheroes(const struct faction *f) { int nsize = f->num_people; if (nsize == 0) return 0; else { int nmax = (int)(log10(nsize / 50.0) * 20); return (nmax < 0) ? 0 : nmax; } } int countheroes(const struct faction *f) { const unit *u = f->units; int n = 0; while (u) { if (fval(u, UFL_HERO)) n += u->number; u = u->nextF; } return n; } /** Returns the raw unit name, like "Frodo", or "Seeschlange" */ const char *unit_getname(const unit * u) { if (!u->_name) { const struct locale * lang = u->faction ? u->faction->locale : default_locale; const char *rcname = rc_name_s(u->_race, u->number == 1 ? NAME_SINGULAR : NAME_PLURAL); return LOC(lang, rcname); } return u->_name; } void unit_setname(unit * u, const char *name) { free(u->_name); if (name && name[0]) u->_name = str_strdup(name); else u->_name = NULL; } const char *unit_getinfo(const unit * u) { if (u->display_id > 0) { return dbstring_load(u->display_id, NULL); } return NULL; } void unit_setinfo(unit * u, const char *info) { if (info) { u->display_id = dbstring_save(info); } else { u->display_id = 0; } } int unit_getid(const unit * u) { return u->no; } void unit_setid(unit * u, int id) { unit *nu = findunit(id); if (nu == NULL) { uunhash(u); u->no = id; uhash(u); } } int unit_gethp(const unit * u) { return u->hp; } void unit_sethp(unit * u, int hp) { u->hp = hp; } status_t unit_getstatus(const unit * u) { return u->status; } void unit_setstatus(unit * u, status_t status) { u->status = status; } int unit_getweight(const unit * u) { return weight(u); } int unit_getcapacity(const unit * u) { return walkingcapacity(u); } void renumber_unit(unit *u, int no) { if (no == 0) no = newunitid(); uunhash(u); if (!ualias(u)) { attrib *a = a_add(&u->attribs, a_new(&at_alias)); a->data.i = -u->no; } u->no = no; uhash(u); } void unit_addorder(unit * u, order * ord) { order **ordp = &u->orders; while (*ordp) ordp = &(*ordp)->next; *ordp = ord; u->faction->lastorders = turn; } int unit_max_hp(const unit * u) { int h; static int config; static bool rule_stamina; h = u_race(u)->hitpoints; if (config_changed(&config)) { rule_stamina = config_get_int("rules.stamina", 1) != 0; } if (rule_stamina) { double p = pow(effskill(u, SK_STAMINA, u->region) / 2.0, 1.5) * 0.2; h += (int)(h * p + 0.5); } /* der healing curse veraendert die maximalen hp */ if (u->region && u->region->attribs) { curse *c = get_curse(u->region->attribs, &ct_healing); if (c) { h = (int)(h * (1.0 + (curse_geteffect(c) / 100))); } } return h; } void scale_number(unit * u, int n) { if (n == u->number) { return; } if (u->number > 0) { if (n > 0) { const attrib *a = a_find(u->attribs, &at_effect); u->hp = (long long)u->hp * n / u->number; for (; a && a->type == &at_effect; a = a->next) { effect_data *data = (effect_data *)a->data.v; data->value = (long long)data->value * n / u->number; } } else { u->hp = 0; } } if (u->number == 0 || n == 0) { skill_t sk; for (sk = 0; sk < MAXSKILLS; sk++) { remove_skill(u, sk); } } set_number(u, n); } const struct race *u_irace(const struct unit *u) { if (u->irace) { return u->irace; } return u->_race; } const struct race *u_race(const struct unit *u) { return u->_race; } void u_setrace(struct unit *u, const struct race *rc) { assert(rc); if (!u->faction) { u->_race = rc; } else { int n = 0; if (count_unit(u)) { --n; } u->_race = rc; if (count_unit(u)) { ++n; } if (n != 0) { u->faction->num_units += n; u->faction->num_people += n * u->number; } } } int effskill(const unit * u, skill_t sk, const region *r) { assert(u); if (skill_enabled(sk)) { skill *sv = u->skills; while (sv != u->skills + u->skill_size) { if (sv->id == sk) { return eff_skill(u, sv, r); } ++sv; } } return 0; } void remove_empty_units_in_region(region * r) { unit **up = &r->units; while (*up) { unit *u = *up; if (u->number) { faction *f = u->faction; if (f == NULL || !faction_alive(f)) { set_number(u, 0); } } if (u->number == 0) { remove_unit(up, u); } if (*up == u) up = &u->next; } } void remove_empty_units(void) { region *r; for (r = regions; r; r = r->next) { remove_empty_units_in_region(r); } } typedef char name[OBJECTIDSIZE + 1]; static name idbuf[8]; static int nextbuf = 0; /** Puts human-readable unit name, with number, like "Frodo (hobb)" into buffer */ char *write_unitname(const unit * u, char *buffer, size_t size) { const char * name = unit_getname(u); snprintf(buffer, size, "%s (%s)", name, itoa36(u->no)); buffer[size - 1] = 0; return buffer; } /** Returns human-readable unit name, with number, like "Frodo (hobb)" */ const char *unitname(const unit * u) { char *ubuf = idbuf[(++nextbuf) % 8]; return write_unitname(u, ubuf, sizeof(idbuf[0])); } bool unit_name_equals_race(const unit *u) { if (u->_name) { char sing[32], plur[32]; const struct locale *lang = u->faction->locale; rc_name(u->_race, NAME_SINGULAR, sing, sizeof(sing)); rc_name(u->_race, NAME_PLURAL, plur, sizeof(plur)); if (strcmp(u->_name, sing) == 0 || strcmp(u->_name, plur) == 0 || strcmp(u->_name, LOC(lang, sing)) == 0 || strcmp(u->_name, LOC(lang, plur)) == 0) { return true; } } return false; } static int read_newunitid(const faction * f, const region * r) { int n; unit *u2; n = getid(); if (n == 0) return -1; u2 = findnewunit(r, f, n); if (u2) return u2->no; return -1; } int read_unitid(const faction * f, const region * r) { char token[16]; const char *s = gettoken(token, sizeof(token)); if (!s || *s == 0 || !isalnum(*s)) { return -1; } if (isparam(s, f->locale, P_TEMP)) { return read_newunitid(f, r); } return atoi36((const char *)s); } int getunit(const region * r, const faction * f, unit **uresult) { unit *u2 = NULL; int n = read_unitid(f, r); int result = GET_NOTFOUND; if (n == 0) { result = GET_PEASANTS; } else if (n > 0) { u2 = findunit(n); if (u2 != NULL && u2->region == r) { /* there used to be a 'u2->flags & UFL_ISNEW || u2->number>0' condition * here, but it got removed because of a bug that made units disappear: * http://eressea.upb.de/mantis/bug_view_page.php?bug_id=0000172 */ result = GET_UNIT; } else { u2 = NULL; } } if (uresult) { *uresult = u2; } return result; } bool has_horses(const unit * u) { item *itm = u->items; for (; itm; itm = itm->next) { if (itm->type->flags & ITF_ANIMAL) return true; } return false; } #define MAINTENANCE 10 int maintenance_cost(const struct unit *u) { if (u == NULL) return MAINTENANCE; return u_race(u)->maintenance * u->number; } static skill_t limited_skills[] = { SK_ALCHEMY, SK_HERBALISM, SK_MAGIC, SK_SPY, SK_TACTICS, NOSKILL }; bool is_limited_skill(skill_t sk) { int i; for (i = 0; limited_skills[i] != NOSKILL; ++i) { if (sk == limited_skills[i]) { return true; } } return false; } bool has_limited_skills(const struct unit * u) { int i; for (i = 0; i != u->skill_size; ++i) { skill *sv = u->skills + i; if (is_limited_skill(sv->id)) { return true; } } return false; } double u_heal_factor(const unit * u) { const race * rc = u_race(u); if (rc->healing > 0) { return rc->healing / 100.0; } if (r_isforest(u->region)) { static int rc_cache; static const race *rc_elf; if (rc_changed(&rc_cache)) { rc_elf = get_race(RC_ELF); } if (rc == rc_elf) { static int cache; static double elf_regen; if (config_changed(&cache)) { elf_regen = config_get_flt("healing.forest", 1.0); } return elf_regen; } } return 1.0; } void unit_convert_race(unit *u, const race *rc, const char *rcname) { if (rc && u->_race != rc) { u_setrace(u, rc); } if (rcname && strcmp(rcname, u->_race->_name) != 0) { set_racename(&u->attribs, rcname); } }