forked from github/server
1263 lines
25 KiB
C
1263 lines
25 KiB
C
/* vi: set ts=2:
|
|
*
|
|
*
|
|
* Eressea PB(E)M host Copyright (C) 1998-2003
|
|
* Christian Schlittchen (corwin@amber.kn-bremen.de)
|
|
* Katja Zedel (katze@felidae.kn-bremen.de)
|
|
* Henning Peters (faroul@beyond.kn-bremen.de)
|
|
* Enno Rehling (enno@eressea-pbem.de)
|
|
* Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
|
|
*
|
|
* based on:
|
|
*
|
|
* Atlantis v1.0 13 September 1993 Copyright 1993 by Russell Wallace
|
|
* Atlantis v1.7 Copyright 1996 by Alex Schröder
|
|
*
|
|
* This program may not be used, modified or distributed without
|
|
* prior permission by the authors of Eressea.
|
|
* This program may not be sold or used commercially without prior written
|
|
* permission from the authors.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include "eressea.h"
|
|
#include "unit.h"
|
|
|
|
#include "building.h"
|
|
#include "faction.h"
|
|
#include "group.h"
|
|
#include "karma.h"
|
|
#include "border.h"
|
|
#include "item.h"
|
|
#include "movement.h"
|
|
#include "order.h"
|
|
#include "plane.h"
|
|
#include "race.h"
|
|
#include "region.h"
|
|
#include "ship.h"
|
|
#include "skill.h"
|
|
|
|
#include <attributes/moved.h>
|
|
|
|
/* util includes */
|
|
#include <util/base36.h>
|
|
#include <event.h>
|
|
#include <goodies.h>
|
|
#include <resolve.h>
|
|
#include <variant.h>
|
|
|
|
/* libc includes */
|
|
#include <assert.h>
|
|
#include <limits.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#define FIND_FOREIGN_TEMP
|
|
|
|
int demonfix = 0;
|
|
|
|
const unit *
|
|
u_peasants(void)
|
|
{
|
|
static unit peasants = { 0 };
|
|
if (peasants.name==NULL) {
|
|
peasants.name = strdup("die Bauern");
|
|
peasants.no = 2;
|
|
}
|
|
return &peasants;
|
|
}
|
|
|
|
const unit *
|
|
u_unknown(void)
|
|
{
|
|
static unit unknown = { 0 };
|
|
if (unknown.name==NULL) {
|
|
unknown.name =strdup("eine unbekannte Einheit");
|
|
unknown.no = 1;
|
|
}
|
|
return &unknown;
|
|
}
|
|
|
|
#define DMAXHASH 7919
|
|
typedef struct dead {
|
|
struct dead * nexthash;
|
|
faction * f;
|
|
int no;
|
|
} dead;
|
|
|
|
static dead* deadhash[DMAXHASH];
|
|
|
|
static void
|
|
dhash(int no, faction * f)
|
|
{
|
|
dead * hash = (dead*)calloc(1, sizeof(dead));
|
|
dead * old = deadhash[no % DMAXHASH];
|
|
hash->no = no;
|
|
hash->f = f;
|
|
deadhash[no % DMAXHASH] = hash;
|
|
hash->nexthash = old;
|
|
}
|
|
|
|
faction *
|
|
dfindhash(int no)
|
|
{
|
|
dead * old;
|
|
|
|
if(no < 0) return 0;
|
|
|
|
for (old = deadhash[no % DMAXHASH]; old; old = old->nexthash)
|
|
if (old->no == no)
|
|
return old->f;
|
|
return 0;
|
|
}
|
|
|
|
unit * udestroy = NULL;
|
|
|
|
/** distributes a unit's posessions to friendly units
|
|
* this happens when units die and no own units are in the region
|
|
*/
|
|
void
|
|
distribute_items(unit * u)
|
|
{
|
|
faction * f = u->faction;
|
|
region * r = u->region;
|
|
unit * au;
|
|
int number = 0;
|
|
struct friend {
|
|
struct friend * next;
|
|
int number;
|
|
faction * faction;
|
|
unit * unit;
|
|
} * friends = NULL;
|
|
|
|
if (u->items==NULL) return;
|
|
|
|
for (au=r->units;au;au=au->next) if (au->faction!=f) {
|
|
if (alliedunit(u, au->faction, HELP_MONEY) && alliedunit(au, f, HELP_GIVE)) {
|
|
struct friend * nf, ** fr = &friends;
|
|
|
|
while (*fr && (*fr)->faction->no<au->faction->no) fr = &(*fr)->next;
|
|
nf = *fr;
|
|
if (nf==NULL || nf->faction!=au->faction) {
|
|
nf = malloc(sizeof(struct friend));
|
|
nf->next = *fr;
|
|
nf->faction = au->faction;
|
|
nf->unit = au;
|
|
nf->number = 0;
|
|
*fr = nf;
|
|
}
|
|
nf->number += au->number;
|
|
number += au->number;
|
|
}
|
|
}
|
|
|
|
if (friends && number) {
|
|
struct friend * nf = friends;
|
|
while (nf) {
|
|
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;
|
|
nf = nf->next;
|
|
free(friends);
|
|
friends = nf;
|
|
}
|
|
friends = NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
destroy_unit(unit * u)
|
|
{
|
|
region *r = u->region;
|
|
boolean zombie = false;
|
|
unit *clone;
|
|
|
|
if (!ufindhash(u->no)) return;
|
|
|
|
if (!fval(u->race, RCF_ILLUSIONARY)) {
|
|
item ** p_item = &u->items;
|
|
unit * u3;
|
|
|
|
/* u->faction->no_units--; */ /* happens in u_setfaction now */
|
|
|
|
if (r) for (u3 = r->units; u3; u3 = u3->next) {
|
|
if (u3 != u && u3->faction == u->faction && playerrace(u3->race)) {
|
|
i_merge(&u3->items, &u->items);
|
|
u->items = NULL;
|
|
break;
|
|
}
|
|
}
|
|
u3 = NULL;
|
|
while (*p_item) {
|
|
item * item = *p_item;
|
|
if (item->number && item->type->flags & ITF_NOTLOST) {
|
|
if (u3==NULL) {
|
|
u3 = r->units;
|
|
while (u3 && u3!=u) u3 = u3->next;
|
|
if (!u3) {
|
|
zombie = true;
|
|
break;
|
|
}
|
|
}
|
|
if (u3) {
|
|
i_add(&u3->items, i_remove(p_item, item));
|
|
}
|
|
}
|
|
if (*p_item == item) p_item=&item->next;
|
|
}
|
|
if (u->items && strlen(u->faction->passw)>0) {
|
|
distribute_items(u);
|
|
}
|
|
}
|
|
|
|
/* Wir machen das erst nach dem Löschen der Items. Der Klon darf keine
|
|
* Items haben, sonst Memory-Leak. */
|
|
|
|
clone = has_clone(u);
|
|
if (clone && rand()%100 < 90) {
|
|
attrib *a;
|
|
int i;
|
|
|
|
/* TODO: Messages generieren. */
|
|
u->region = clone->region;
|
|
u->ship = clone->ship;
|
|
u->building = clone->building;
|
|
u->hp = 1;
|
|
i = u->no;
|
|
uunhash(u);
|
|
uunhash(clone);
|
|
u->no = clone->no;
|
|
clone->no = i;
|
|
uhash(u);
|
|
uhash(clone);
|
|
set_number(u, 1);
|
|
set_spellpoints(u, 0);
|
|
a = a_find(u->attribs, &at_clone);
|
|
a_remove(&u->attribs, a);
|
|
a = a_find(clone->attribs, &at_clonemage);
|
|
a_remove(&clone->attribs, a);
|
|
fset(u, UFL_LONGACTION);
|
|
set_number(clone, 0);
|
|
u = clone;
|
|
zombie = false;
|
|
}
|
|
|
|
if (zombie) {
|
|
u_setfaction(u, findfaction(MONSTER_FACTION));
|
|
scale_number(u, 1);
|
|
u->race = u->irace = new_race[RC_ZOMBIE];
|
|
} else {
|
|
if (u->number) set_number(u, 0);
|
|
handle_event(&u->attribs, "destroy", u);
|
|
if (r && rterrain(r) != T_OCEAN)
|
|
rsetmoney(r, rmoney(r) + get_money(u));
|
|
dhash(u->no, u->faction);
|
|
u_setfaction(u, NULL);
|
|
if (r) leave(r, u);
|
|
uunhash(u);
|
|
if (r) choplist(&r->units, u);
|
|
u->next = udestroy;
|
|
udestroy = u;
|
|
}
|
|
}
|
|
|
|
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;
|
|
#ifdef FIND_FOREIGN_TEMP
|
|
for (u2 = r->units; u2; u2 = u2->next)
|
|
if (ualias(u2) == n)
|
|
return u2;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------- */
|
|
|
|
|
|
/*********************/
|
|
/* at_alias */
|
|
/*********************/
|
|
attrib_type at_alias = {
|
|
"alias",
|
|
DEFAULT_INIT,
|
|
DEFAULT_FINALIZE,
|
|
DEFAULT_AGE,
|
|
NO_WRITE,
|
|
NO_READ
|
|
};
|
|
|
|
int
|
|
ualias(const unit * u) {
|
|
attrib * a = a_find(u->attribs, &at_alias);
|
|
if (!a) return 0;
|
|
return a->data.i;
|
|
}
|
|
|
|
/*********************/
|
|
/* at_private */
|
|
/*********************/
|
|
attrib_type at_private = {
|
|
"private",
|
|
DEFAULT_INIT,
|
|
a_finalizestring,
|
|
DEFAULT_AGE,
|
|
a_writestring,
|
|
a_readstring
|
|
};
|
|
|
|
const char *
|
|
uprivate(const unit * u) {
|
|
attrib * a = a_find(u->attribs, &at_private);
|
|
if (!a) return NULL;
|
|
return (const char*)a->data.v;
|
|
}
|
|
|
|
void
|
|
usetprivate(unit * u, const char * str) {
|
|
attrib * a = a_find(u->attribs, &at_private);
|
|
|
|
if(str == NULL) {
|
|
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 = strdup(str);
|
|
}
|
|
|
|
/*********************/
|
|
/* at_potionuser */
|
|
/*********************/
|
|
/* Einheit BENUTZT einen Trank */
|
|
attrib_type at_potionuser = {
|
|
"potionuser",
|
|
DEFAULT_INIT,
|
|
DEFAULT_FINALIZE,
|
|
DEFAULT_AGE,
|
|
NO_WRITE,
|
|
NO_READ
|
|
};
|
|
|
|
void
|
|
usetpotionuse(unit * u, const potion_type * ptype)
|
|
{
|
|
attrib * a = a_find(u->attribs, &at_potionuser);
|
|
if (!a) a = a_add(&u->attribs, a_new(&at_potionuser));
|
|
a->data.v = (void*)ptype;
|
|
}
|
|
|
|
const potion_type *
|
|
ugetpotionuse(const unit * u) {
|
|
attrib * a = a_find(u->attribs, &at_potionuser);
|
|
if (!a) return NULL;
|
|
return (const potion_type *)a->data.v;
|
|
}
|
|
|
|
/*********************/
|
|
/* at_target */
|
|
/*********************/
|
|
attrib_type at_target = {
|
|
"target",
|
|
DEFAULT_INIT,
|
|
DEFAULT_FINALIZE,
|
|
DEFAULT_AGE,
|
|
NO_WRITE,
|
|
NO_READ
|
|
};
|
|
|
|
unit *
|
|
utarget(const unit * u) {
|
|
attrib * a;
|
|
if (!fval(u, UFL_TARGET)) return NULL;
|
|
a = a_find(u->attribs, &at_target);
|
|
assert (a || !"flag set, but no target found");
|
|
return (unit*)a->data.v;
|
|
}
|
|
|
|
void
|
|
usettarget(unit * u, const unit * t)
|
|
{
|
|
attrib * a = a_find(u->attribs, &at_target);
|
|
if (!a && t) a = a_add(&u->attribs, a_new(&at_target));
|
|
if (a) {
|
|
if (!t) {
|
|
a_remove(&u->attribs, a);
|
|
freset(u, UFL_TARGET);
|
|
}
|
|
else {
|
|
a->data.v = (void*)t;
|
|
fset(u, UFL_TARGET);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************/
|
|
/* at_siege */
|
|
/*********************/
|
|
|
|
void
|
|
a_writesiege(const attrib * a, FILE * f)
|
|
{
|
|
struct building * b = (struct building*)a->data.v;
|
|
write_building_reference(b, f);
|
|
}
|
|
|
|
int
|
|
a_readsiege(attrib * a, FILE * f)
|
|
{
|
|
return read_building_reference((struct building**)&a->data.v, f);
|
|
}
|
|
|
|
attrib_type at_siege = {
|
|
"siege",
|
|
DEFAULT_INIT,
|
|
DEFAULT_FINALIZE,
|
|
DEFAULT_AGE,
|
|
a_writesiege,
|
|
a_readsiege
|
|
};
|
|
|
|
struct building *
|
|
usiege(const unit * u) {
|
|
attrib * a;
|
|
if (!fval(u, UFL_SIEGE)) return NULL;
|
|
a = a_find(u->attribs, &at_siege);
|
|
assert (a || !"flag set, but no siege found");
|
|
return (struct building *)a->data.v;
|
|
}
|
|
|
|
void
|
|
usetsiege(unit * u, const struct building * t)
|
|
{
|
|
attrib * a = a_find(u->attribs, &at_siege);
|
|
if (!a && t) a = a_add(&u->attribs, a_new(&at_siege));
|
|
if (a) {
|
|
if (!t) {
|
|
a_remove(&u->attribs, a);
|
|
freset(u, UFL_SIEGE);
|
|
}
|
|
else {
|
|
a->data.v = (void*)t;
|
|
fset(u, UFL_SIEGE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*********************/
|
|
/* at_contact */
|
|
/*********************/
|
|
attrib_type at_contact = {
|
|
"contact",
|
|
DEFAULT_INIT,
|
|
DEFAULT_FINALIZE,
|
|
DEFAULT_AGE,
|
|
NO_WRITE,
|
|
NO_READ
|
|
};
|
|
|
|
void
|
|
usetcontact(unit * u, const unit * u2)
|
|
{
|
|
attrib * a = a_find(u->attribs, &at_contact);
|
|
while (a && a->data.v!=u2) a = a->nexttype;
|
|
if (a) return;
|
|
a_add(&u->attribs, a_new(&at_contact))->data.v = (void*)u2;
|
|
}
|
|
|
|
boolean
|
|
ucontact(const unit * u, const unit * u2)
|
|
/* Prüft, ob u den Kontaktiere-Befehl zu u2 gesetzt hat. */
|
|
{
|
|
attrib *ru;
|
|
if (u->faction==u2->faction) return true;
|
|
|
|
/* Explizites KONTAKTIERE */
|
|
for (ru = a_find(u->attribs, &at_contact); ru; ru = ru->nexttype)
|
|
if (((unit*)ru->data.v) == u2)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/***
|
|
** init & cleanup module
|
|
**/
|
|
|
|
void
|
|
free_units(void)
|
|
{
|
|
while (udestroy) {
|
|
unit * u = udestroy;
|
|
udestroy = udestroy->next;
|
|
stripunit(u);
|
|
free(u);
|
|
}
|
|
}
|
|
|
|
void
|
|
write_unit_reference(const unit * u, FILE * F)
|
|
{
|
|
fprintf(F, "%s ", (u!=NULL && u->no!=0)?itoa36(u->no):"0");
|
|
}
|
|
|
|
void *
|
|
resolve_unit(variant id)
|
|
{
|
|
return ufindhash(id.i);
|
|
}
|
|
|
|
int
|
|
read_unit_reference(unit ** up, FILE * F)
|
|
{
|
|
char zId[10];
|
|
variant var;
|
|
|
|
assert(up!=NULL);
|
|
fscanf(F, "%s", zId);
|
|
var.i = atoi36(zId);
|
|
if (var.i==0) {
|
|
*up = NULL;
|
|
return AT_READ_FAIL;
|
|
}
|
|
*up = findunit(var.i);
|
|
if (*up==NULL) ur_add(var, (void**)up, resolve_unit);
|
|
return AT_READ_OK;
|
|
}
|
|
|
|
attrib_type at_stealth = {
|
|
"stealth", NULL, NULL, NULL, DEFAULT_WRITE, DEFAULT_READ
|
|
};
|
|
|
|
void
|
|
u_seteffstealth(unit * u, int value)
|
|
{
|
|
attrib * a = NULL;
|
|
if (fval(u, UFL_STEALTH)) {
|
|
a = a_find(u->attribs, &at_stealth);
|
|
}
|
|
if (value<0) {
|
|
if (a!=NULL) {
|
|
freset(u, UFL_STEALTH);
|
|
a_remove(&u->attribs, a);
|
|
}
|
|
return;
|
|
}
|
|
if (a==NULL) {
|
|
a = a_add(&u->attribs, a_new(&at_stealth));
|
|
fset(u, UFL_STEALTH);
|
|
}
|
|
a->data.i = value;
|
|
}
|
|
|
|
int
|
|
u_geteffstealth(const struct unit * u)
|
|
{
|
|
if (fval(u, UFL_STEALTH)) {
|
|
attrib * a = a_find(u->attribs, &at_stealth);
|
|
if (a!=NULL) return a->data.i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
get_level(const unit * u, skill_t id)
|
|
{
|
|
skill * sv = u->skills;
|
|
while (sv != u->skills + u->skill_size) {
|
|
if (sv->id == id) {
|
|
return sv->level;
|
|
}
|
|
++sv;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
set_level(unit * u, skill_t sk, int value)
|
|
{
|
|
skill * sv = u->skills;
|
|
if (value==0) {
|
|
remove_skill(u, sk);
|
|
return;
|
|
}
|
|
while (sv != u->skills + u->skill_size) {
|
|
if (sv->id == sk) {
|
|
sk_set(sv, value);
|
|
return;
|
|
}
|
|
++sv;
|
|
}
|
|
sk_set(add_skill(u, sk), value);
|
|
}
|
|
|
|
static int
|
|
leftship_age(struct attrib * a)
|
|
{
|
|
/* must be aged, so it doesn't affect report generation (cansee) */
|
|
unused(a);
|
|
return 0; /* 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, daß der Rückgabewert zu jedem
|
|
* Zeitpunkt noch auf ein existierendes Schiff zeigt! */
|
|
|
|
if (a) return (ship *)(a->data.v);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
leave_ship(unit * u)
|
|
{
|
|
struct ship * sh = u->ship;
|
|
if (sh==NULL) return;
|
|
u->ship = NULL;
|
|
set_leftship(u, sh);
|
|
|
|
if (fval(u, UFL_OWNER)) {
|
|
unit *u2, *owner = NULL;
|
|
freset(u, UFL_OWNER);
|
|
|
|
for (u2 = u->region->units; u2; u2 = u2->next) {
|
|
if (u2->ship == sh) {
|
|
if (u2->faction == u->faction) {
|
|
owner = u2;
|
|
break;
|
|
}
|
|
else if (owner==NULL) owner = u2;
|
|
}
|
|
}
|
|
if (owner!=NULL) fset(owner, UFL_OWNER);
|
|
}
|
|
}
|
|
|
|
void
|
|
leave_building(unit * u)
|
|
{
|
|
struct building * b = u->building;
|
|
if (!b) return;
|
|
u->building = NULL;
|
|
|
|
if (fval(u, UFL_OWNER)) {
|
|
unit *u2, *owner = NULL;
|
|
freset(u, UFL_OWNER);
|
|
|
|
for (u2 = u->region->units; u2; u2 = u2->next) {
|
|
if (u2->building == b) {
|
|
if (u2->faction == u->faction) {
|
|
owner = u2;
|
|
break;
|
|
}
|
|
else if (owner==NULL) owner = u2;
|
|
}
|
|
}
|
|
if (owner!=NULL) fset(owner, UFL_OWNER);
|
|
}
|
|
}
|
|
|
|
void
|
|
leave(struct region * r, unit * u)
|
|
{
|
|
if (u->building) leave_building(u);
|
|
else if (u->ship) leave_ship(u);
|
|
unused(r);
|
|
}
|
|
|
|
const struct race *
|
|
urace(const struct unit * u)
|
|
{
|
|
return u->race;
|
|
}
|
|
|
|
boolean
|
|
can_survive(const unit *u, const region *r)
|
|
{
|
|
if (((terrain[rterrain(r)].flags & WALK_INTO)
|
|
&& (u->race->flags & RCF_WALK)) ||
|
|
((terrain[rterrain(r)].flags & SWIM_INTO)
|
|
&& (u->race->flags & RCF_SWIM)) ||
|
|
((terrain[rterrain(r)].flags & FLY_INTO)
|
|
&& (u->race->flags & RCF_FLY))) {
|
|
|
|
if (get_item(u, I_HORSE) && !(terrain[rterrain(r)].flags & WALK_INTO))
|
|
return false;
|
|
|
|
if (fval(u->race, RCF_UNDEAD) && is_cursed(r->attribs, C_HOLYGROUND, 0))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
move_unit(unit * u, region * r, unit ** ulist)
|
|
{
|
|
int maxhp = 0;
|
|
assert(u && r);
|
|
|
|
if (u->region == r) return;
|
|
if (u->region!=NULL) maxhp = unit_max_hp(u);
|
|
if (!ulist) ulist = (&r->units);
|
|
if (u->region) {
|
|
#ifdef DELAYED_OFFENSE
|
|
set_moved(&u->attribs);
|
|
#endif
|
|
setguard(u, GUARD_NONE);
|
|
fset(u, UFL_MOVED);
|
|
if (u->ship || u->building) leave(u->region, u);
|
|
translist(&u->region->units, ulist, u);
|
|
} else
|
|
addlist(ulist, u);
|
|
|
|
#ifdef SMART_INTERVALS
|
|
update_interval(u->faction, r);
|
|
#endif
|
|
u->region = r;
|
|
/* keine automatische hp reduzierung bei bewegung */
|
|
/* if (maxhp>0) u->hp = u->hp * unit_max_hp(u) / maxhp; */
|
|
}
|
|
|
|
/* ist mist, aber wegen nicht skalierender attirbute notwendig: */
|
|
#include "alchemy.h"
|
|
|
|
void
|
|
transfermen(unit * u, unit * u2, int n)
|
|
{
|
|
const attrib * a;
|
|
int hp = u->hp;
|
|
region * r = u->region;
|
|
|
|
if (!n) return;
|
|
|
|
/* "hat attackiert"-status wird übergeben */
|
|
|
|
if (u2) {
|
|
skill *sv, *sn;
|
|
skill_t sk;
|
|
assert(u2->number+n>0);
|
|
|
|
if (demonfix && u2->race==new_race[RC_DAEMON]) fset(u2, UFL_DEBUG);
|
|
|
|
for (sk=0; sk!=MAXSKILLS; ++sk) {
|
|
double dlevel = 0.0;
|
|
int weeks, level = 0;
|
|
|
|
sv = get_skill(u, sk);
|
|
sn = get_skill(u2, sk);
|
|
|
|
if (sv==NULL && sn==NULL) continue;
|
|
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)) * u2->number;
|
|
level += sn->level * u2->number;
|
|
}
|
|
|
|
dlevel = dlevel / (n + u2->number);
|
|
level = level / (n + u2->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(u2, sk);
|
|
sn->level = (unsigned char)level;
|
|
sn->weeks = (unsigned char)weeks;
|
|
assert(sn->weeks>0 && sn->weeks<=sn->level*2+1);
|
|
} else if (sn) {
|
|
remove_skill(u2, sk);
|
|
sn = NULL;
|
|
}
|
|
}
|
|
a = a_find(u->attribs, &at_effect);
|
|
while (a) {
|
|
effect_data * olde = (effect_data*)a->data.v;
|
|
if (olde->value) change_effect(u2, olde->type, olde->value);
|
|
a = a->nexttype;
|
|
}
|
|
if (fval(u, UFL_LONGACTION)) fset(u2, UFL_LONGACTION);
|
|
if (u->attribs) {
|
|
transfer_curse(u, u2, n);
|
|
}
|
|
}
|
|
scale_number(u, u->number - n);
|
|
if (u2) {
|
|
set_number(u2, u2->number + n);
|
|
hp -= u->hp;
|
|
u2->hp += hp;
|
|
/* TODO: Das ist schnarchlahm! und gehört ncht hierhin */
|
|
a = a_find(u2->attribs, &at_effect);
|
|
while (a) {
|
|
attrib * an = a->nexttype;
|
|
effect_data * olde = (effect_data*)a->data.v;
|
|
int e = get_effect(u, olde->type);
|
|
if (e!=0) change_effect(u2, olde->type, -e);
|
|
a = an;
|
|
}
|
|
}
|
|
else if (r->land)
|
|
if(u->race != new_race[RC_DAEMON]) {
|
|
if(u->race != new_race[RC_URUK]) {
|
|
rsetpeasants(r, rpeasants(r) + n);
|
|
} else {
|
|
rsetpeasants(r, rpeasants(r) + n/2);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct building *
|
|
inside_building(const struct unit * u)
|
|
{
|
|
if (u->building==NULL) return NULL;
|
|
|
|
if (!fval(u->building, BLD_WORKING)) {
|
|
/* Unterhalt nicht bezahlt */
|
|
return NULL;
|
|
} else if (u->building->size < u->building->type->maxsize) {
|
|
/* Gebäude 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)
|
|
{
|
|
int cnt = u->number;
|
|
|
|
if (u->faction==f) return;
|
|
if (u->faction) {
|
|
unit ** iunit;
|
|
set_number(u, 0);
|
|
if (playerrace(u->race)) {
|
|
--u->faction->no_units;
|
|
}
|
|
join_group(u, NULL);
|
|
free_orders(&u->orders);
|
|
set_order(&u->thisorder, NULL);
|
|
#ifdef LASTORDER
|
|
set_order(&u->lastorder, NULL);
|
|
#endif
|
|
|
|
iunit = &u->faction->units;
|
|
while (*iunit && *iunit!=u) {
|
|
iunit=&(*iunit)->nextF;
|
|
}
|
|
assert(*iunit);
|
|
*iunit = u->nextF;
|
|
}
|
|
|
|
if (f!=NULL) {
|
|
u->nextF = f->units;
|
|
f->units = u;
|
|
}
|
|
else u->nextF = NULL;
|
|
|
|
u->faction = f;
|
|
if (u->region) update_interval(f, u->region);
|
|
if (cnt && f) {
|
|
set_number(u, cnt);
|
|
if (playerrace(u->race)) {
|
|
++f->no_units;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* vorsicht Sprüche können u->number == 0 (RS_FARVISION) haben! */
|
|
void
|
|
set_number(unit * u, int count)
|
|
{
|
|
assert (count >= 0);
|
|
assert (count <= USHRT_MAX);
|
|
|
|
#ifndef NDEBUG
|
|
assert (u->faction != 0 || u->number > 0);
|
|
#endif
|
|
|
|
if (playerrace(u->race)) {
|
|
u->faction->num_people += count - u->number;
|
|
}
|
|
u->number = (unsigned short)count;
|
|
}
|
|
|
|
boolean
|
|
learn_skill(unit * u, skill_t sk, double chance)
|
|
{
|
|
skill * sv = u->skills;
|
|
if (chance < 1.0 && rand()%10000>=chance*10000) return false;
|
|
while (sv != u->skills + u->skill_size) {
|
|
assert (sv->weeks>0);
|
|
if (sv->id == sk) {
|
|
if (sv->weeks<=1) {
|
|
sk_set(sv, sv->level+1);
|
|
} else {
|
|
sv->weeks--;
|
|
}
|
|
return true;
|
|
}
|
|
++sv;
|
|
}
|
|
sv = add_skill(u, sk);
|
|
sk_set(sv, 1);
|
|
return true;
|
|
}
|
|
|
|
void
|
|
remove_skill(unit *u, skill_t sk)
|
|
{
|
|
skill * sv = u->skills;
|
|
for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) {
|
|
if (sv->id==sk) {
|
|
skill * sl = u->skills + u->skill_size - 1;
|
|
if (sl!=sv) {
|
|
*sv = *sl;
|
|
}
|
|
--u->skill_size;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
skill *
|
|
add_skill(unit * u, skill_t id)
|
|
{
|
|
skill * sv = u->skills;
|
|
#ifndef NDEBUG
|
|
for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) {
|
|
assert(sv->id != id);
|
|
}
|
|
#endif
|
|
++u->skill_size;
|
|
u->skills = realloc(u->skills, u->skill_size * sizeof(skill));
|
|
sv = (u->skills + u->skill_size - 1);
|
|
sv->level = (unsigned char)0;
|
|
sv->weeks = (unsigned char)1;
|
|
sv->old = (unsigned char)0;
|
|
sv->id = (unsigned char)id;
|
|
return sv;
|
|
}
|
|
|
|
skill *
|
|
get_skill(const unit * u, skill_t sk)
|
|
{
|
|
skill * sv = u->skills;
|
|
while (sv!=u->skills+u->skill_size) {
|
|
if (sv->id==sk) return sv;
|
|
++sv;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
boolean
|
|
has_skill(const unit * u, skill_t sk)
|
|
{
|
|
skill * sv = u->skills;
|
|
while (sv!=u->skills+u->skill_size) {
|
|
if (sv->id==sk) {
|
|
return (sv->level>0);
|
|
}
|
|
++sv;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int
|
|
item_modification(const unit *u, skill_t sk, int val)
|
|
{
|
|
/* Presseausweis: *2 Spionage, 0 Tarnung */
|
|
if(sk == SK_SPY && get_item(u, I_PRESSCARD) >= u->number) {
|
|
val = val * 2;
|
|
} else if(sk == SK_STEALTH) {
|
|
#if NEWATSROI == 1
|
|
if (get_item(u, I_RING_OF_INVISIBILITY)
|
|
+ 100 * get_item(u, I_SPHERE_OF_INVISIBILITY) >= u->number) {
|
|
val += ROIBONUS;
|
|
}
|
|
#endif
|
|
if(get_item(u, I_PRESSCARD) >= u->number) {
|
|
val = 0;
|
|
}
|
|
}
|
|
#if NEWATSROI == 1
|
|
if(sk == SK_OBSERVATION) {
|
|
if(get_item(u, I_AMULET_OF_TRUE_SEEING) >= u->number) {
|
|
val += ATSBONUS;
|
|
}
|
|
}
|
|
#endif
|
|
return val;
|
|
}
|
|
|
|
static int
|
|
att_modification(const unit *u, skill_t sk)
|
|
{
|
|
int bonus = 0, malus = 0;
|
|
attrib * a;
|
|
int result = 0;
|
|
static boolean init = false;
|
|
static const curse_type * skillmod_ct, * gbdream_ct, * worse_ct;
|
|
if (!init) {
|
|
init = true;
|
|
skillmod_ct = ct_find("skillmod");
|
|
gbdream_ct = ct_find("gbdream");
|
|
worse_ct = ct_find("worse");
|
|
}
|
|
|
|
result += curse_geteffect(get_curse(u->attribs, worse_ct));
|
|
if (skillmod_ct) {
|
|
curse * c;
|
|
variant var;
|
|
var.i = sk;
|
|
c = get_cursex(u->attribs, skillmod_ct, var, cmp_cursedata_int);
|
|
result += curse_geteffect(c);
|
|
}
|
|
|
|
/* TODO hier kann nicht mit get/iscursed gearbeitet werden, da nur der
|
|
* jeweils erste vom Typ C_GBDREAM zurückgegen wird, wir aber alle
|
|
* durchsuchen und aufaddieren müssen */
|
|
a = a_select(u->region->attribs, gbdream_ct, cmp_cursetype);
|
|
while (a) {
|
|
curse * c = (curse*)a->data.v;
|
|
int mod = curse_geteffect(c);
|
|
unit * mage = c->magician;
|
|
/* wir suchen jeweils den größten Bonus und den größten Malus */
|
|
if (mod>0 && (mage==NULL || alliedunit(mage, u->faction, HELP_GUARD)))
|
|
{
|
|
if (mod > bonus ) bonus = mod;
|
|
} else if (mod < 0 &&
|
|
(mage == NULL || !alliedunit(mage, u->faction, HELP_GUARD)))
|
|
{
|
|
if (mod < malus ) malus = mod;
|
|
}
|
|
a = a_select(a->next, gbdream_ct, cmp_cursetype);
|
|
}
|
|
result = result + bonus + malus;
|
|
|
|
return result;
|
|
}
|
|
|
|
int
|
|
get_modifier(const unit *u, skill_t sk, int level, const region *r, boolean noitem)
|
|
{
|
|
int bskill = level;
|
|
int skill = bskill;
|
|
|
|
if (r->planep && sk == SK_STEALTH && fval(r->planep, PFL_NOSTEALTH)) return 0;
|
|
|
|
assert(r);
|
|
skill += rc_skillmod(u->race, r, sk);
|
|
skill += att_modification(u, sk);
|
|
|
|
if (noitem == false) {
|
|
skill = item_modification(u, sk, skill);
|
|
}
|
|
skill = skillmod(u->attribs, u, r, sk, skill, SMF_ALWAYS);
|
|
|
|
if (fspecial(u->faction, FS_TELEPATHY)) {
|
|
switch(sk) {
|
|
case SK_ALCHEMY:
|
|
case SK_HERBALISM:
|
|
case SK_MAGIC:
|
|
case SK_SPY:
|
|
case SK_STEALTH:
|
|
case SK_OBSERVATION:
|
|
break;
|
|
default:
|
|
skill -= 2;
|
|
}
|
|
}
|
|
|
|
#ifdef HUNGER_REDUCES_SKILL
|
|
if (fval(u, UFL_HUNGER)) {
|
|
skill = skill/2;
|
|
}
|
|
#endif
|
|
return skill - bskill;
|
|
}
|
|
|
|
int
|
|
eff_skill(const unit * u, skill_t sk, const region * r)
|
|
{
|
|
int level = get_level(u, sk);
|
|
if (level>0) {
|
|
int mlevel = level + get_modifier(u, sk, level, r, false);
|
|
|
|
if (mlevel>0) {
|
|
int skillcap = SkillCap(sk);
|
|
if (skillcap && mlevel>skillcap) {
|
|
return skillcap;
|
|
}
|
|
return mlevel;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
eff_skill_study(const unit * u, skill_t sk, const region * r)
|
|
{
|
|
int level = get_level(u, sk);
|
|
if (level>0) {
|
|
int mlevel = level + get_modifier(u, sk, level, r, true);
|
|
|
|
if (mlevel>0) return mlevel;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
invisible(const unit *target, const unit * viewer)
|
|
{
|
|
#if NEWATSROI == 1
|
|
return 0;
|
|
#else
|
|
if (viewer && viewer->faction==target->faction) return 0;
|
|
else {
|
|
int hidden = get_item(target, I_RING_OF_INVISIBILITY) + 100 * get_item(target, I_SPHERE_OF_INVISIBILITY);
|
|
if (hidden) {
|
|
hidden = min(hidden, target->number);
|
|
if (viewer) hidden -= get_item(viewer, I_AMULET_OF_TRUE_SEEING);
|
|
}
|
|
return hidden;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void
|
|
stripunit(unit * u)
|
|
{
|
|
free(u->name);
|
|
free(u->display);
|
|
free_orders(&u->orders);
|
|
if(u->skills) free(u->skills);
|
|
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);
|
|
while (u->reservations) {
|
|
struct reservation *res = u->reservations;
|
|
u->reservations = res->next;
|
|
free(res);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
unitlist_clear(struct unit_list **ul)
|
|
{
|
|
while (*ul) {
|
|
unit_list * rl2 = (*ul)->next;
|
|
free(*ul);
|
|
*ul = rl2;
|
|
}
|
|
}
|
|
|
|
void
|
|
unitlist_insert(struct unit_list **ul, struct unit *u)
|
|
{
|
|
unit_list *rl2 = (unit_list*)malloc(sizeof(unit_list));
|
|
|
|
rl2->data = u;
|
|
rl2->next = *ul;
|
|
|
|
*ul = rl2;
|
|
}
|
|
|
|
#ifdef HEROES
|
|
int
|
|
maxheroes(const struct faction * f)
|
|
{
|
|
int nsize = count_all(f);
|
|
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;
|
|
}
|
|
|
|
#endif
|