server/src/kernel/unit.c
2018-12-11 19:57:27 +01:00

1851 lines
45 KiB
C

/*
Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**/
#include <platform.h>
#include <kernel/config.h>
#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 <attributes/otherfaction.h>
#include <attributes/racename.h>
#include <attributes/stealth.h>
#include <spells/unitcurse.h>
#include <spells/regioncurse.h>
/* util includes */
#include <util/base36.h>
#include <util/language.h>
#include <util/lists.h>
#include <util/log.h>
#include <util/macros.h>
#include <util/param.h>
#include <util/parser.h>
#include <util/rand.h>
#include <util/resolve.h>
#include <util/rng.h>
#include <util/strings.h>
#include <util/variant.h>
#include <storage.h>
/* libc includes */
#include <assert.h>
#include <ctype.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
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;
int rule = rule_give();
assert(u->region);
if ((rule & GIVE_ONDEATH) == 0 || !u->faction || (u->faction->flags & FFL_QUIT) == 0) {
if ((rule & GIVE_ALLITEMS) == 0 && (flags & GIFT_FRIENDS))
flags -= GIFT_FRIENDS;
if ((rule & GIVE_PEASANTS) == 0 && (flags & GIFT_PEASANTS))
flags -= GIFT_PEASANTS;
if ((rule & GIVE_SELF) == 0 && (flags & GIFT_SELF))
flags -= GIFT_SELF;
}
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 || !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 = 0;
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 = 0;
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_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;
}
join_group(u, NULL);
free_orders(&u->orders);
set_order(&u->thisorder, 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) {
hidden = MIN(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 */
unit_setstatus(u, creator->status);
/* 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_MAGIC, SK_ALCHEMY, SK_TACTICS, SK_SPY, SK_HERBALISM, NOSKILL };
bool has_limited_skills(const struct unit * u)
{
int i, j;
for (i = 0; i != u->skill_size; ++i) {
skill *sv = u->skills + i;
for (j = 0; limited_skills[j] != NOSKILL; ++j) {
if (sv->id == limited_skills[j]) {
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;
}