server/src/kernel/curse.c
Enno Rehling ec4060cb9a fix curse-vigour calculation math.
also fix a ton of small other niggles from pvs-studio.
2016-11-20 19:08:39 +01:00

854 lines
21 KiB
C
Raw Blame History

/*
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 "curse.h"
/* kernel includes */
#include "building.h"
#include "faction.h"
#include "messages.h"
#include "objtypes.h"
#include "race.h"
#include "region.h"
#include "ship.h"
#include "skill.h"
#include "unit.h"
/* util includes */
#include <util/attrib.h>
#include <util/base36.h>
#include <util/gamedata.h>
#include <util/goodies.h>
#include <util/language.h>
#include <util/log.h>
#include <util/nrmessage.h>
#include <util/rand.h>
#include <util/resolve.h>
#include <util/rng.h>
#include <util/variant.h>
#include <storage.h>
/* libc includes */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <assert.h>
#include <math.h>
#include <ctype.h>
#define MAXENTITYHASH 7919
static curse *cursehash[MAXENTITYHASH];
void c_setflag(curse * c, unsigned int flags)
{
assert(c);
c->flags = (c->flags & ~flags) | (flags & (c->type->flags ^ flags));
}
/* -------------------------------------------------------------------------- */
void c_clearflag(curse * c, unsigned int flags)
{
assert(c);
c->flags = (c->flags & ~flags) | (c->type->flags & flags);
}
void chash(curse * c)
{
int i = c->no % MAXENTITYHASH;
c->nexthash = cursehash[i];
cursehash[i] = c;
assert(c->nexthash != c);
}
static void cunhash(curse * c)
{
curse **show;
int i = c->no % MAXENTITYHASH;
for (show = &cursehash[i]; *show;
show = &(*show)->nexthash) {
if ((*show)->no == c->no)
break;
}
if (*show) {
assert(*show == c);
*show = (*show)->nexthash;
c->nexthash = 0;
}
}
/* ------------------------------------------------------------- */
/* at_curse */
void curse_init(attrib * a)
{
a->data.v = calloc(1, sizeof(curse));
}
int curse_age(attrib * a, void *owner)
{
curse *c = (curse *)a->data.v;
int result = 0;
unused_arg(owner);
c_clearflag(c, CURSE_ISNEW);
if (c_flags(c) & CURSE_NOAGE) {
c->duration = INT_MAX;
}
if (c->type->age) {
result = c->type->age(c);
}
if (result != 0) {
c->duration = 0;
}
else if (c->duration != INT_MAX) {
c->duration = _max(0, c->duration - 1);
}
return (c->duration > 0) ? AT_AGE_KEEP : AT_AGE_REMOVE;
}
void destroy_curse(curse * c)
{
cunhash(c);
free(c);
}
void curse_done(attrib * a)
{
destroy_curse((curse *)a->data.v);
}
/** reads curses that have been removed from the code */
static int read_ccompat(const char *cursename, struct storage *store)
{
struct compat {
const char *name;
const char *tokens;
} *seek, old_curses[] = { {
"disorientationzone", "" }, {
"shipdisorientation", "" }, {
NULL, NULL } };
for (seek = old_curses; seek->name; ++seek) {
if (strcmp(seek->tokens, cursename) == 0) {
const char *p;
for (p = seek->name; p; ++p) {
switch (*p) {
case 'd':
READ_INT(store, 0);
break;
case 's':
READ_STR(store, 0, 0);
break;
case 't':
READ_TOK(store, 0, 0);
break;
case 'i':
READ_INT(store, 0);
break;
case 'f':
READ_FLT(store, 0);
break;
}
}
return 0;
}
}
return -1;
}
int curse_read(attrib * a, void *owner, gamedata *data)
{
storage *store = data->store;
curse *c = (curse *)a->data.v;
int ur;
char cursename[64];
int n;
int flags;
float flt;
assert(!c->no);
READ_INT(store, &c->no);
chash(c);
READ_TOK(store, cursename, sizeof(cursename));
READ_INT(store, &flags);
READ_INT(store, &c->duration);
READ_FLT(store, &flt);
c->vigour = flt;
if (data->version < INTPAK_VERSION) {
ur = resolve_unit(read_int(data->store), &c->magician);
}
else {
ur = read_reference(&c->magician, data, read_unit_reference, resolve_unit);
}
if (data->version < CURSEFLOAT_VERSION) {
READ_INT(store, &n);
c->effect = (float)n;
}
else {
READ_FLT(store, &flt);
c->effect = flt;
}
c->type = ct_find(cursename);
if (c->type == NULL) {
int result = read_ccompat(cursename, store);
if (result != 0) {
log_error("missing curse %s, no compatibility code either.\n", cursename);
}
assert(result == 0);
return AT_READ_FAIL;
}
c->flags = flags;
if (data->version < EXPLICIT_CURSE_ISNEW_VERSION) {
c_clearflag(c, CURSE_ISNEW);
}
if (c->type->read) {
c->type->read(data, c, owner);
}
else if (c->type->typ == CURSETYP_UNIT) {
READ_INT(store, &c->data.i);
}
if (c->type->typ == CURSETYP_REGION) {
int rr =
read_reference(&c->data.v, data, read_region_reference,
RESOLVE_REGION(data->version));
if (ur == 0 && rr == 0 && !c->data.v) {
return AT_READ_FAIL;
}
}
return AT_READ_OK;
}
void curse_write(const attrib * a, const void *owner, struct storage *store)
{
unsigned int flags;
curse *c = (curse *)a->data.v;
const curse_type *ct = c->type;
unit *mage = (c->magician && c->magician->number) ? c->magician : NULL;
/* copied from c_clearflag */
#if RELEASE_VERSION < EXPLICIT_CURSE_ISNEW_VERSION
flags = (c->flags & ~CURSE_ISNEW) | (c->type->flags & CURSE_ISNEW);
#else
flags = c->flags | c->type->flags;
#endif
WRITE_INT(store, c->no);
WRITE_TOK(store, ct->cname);
WRITE_INT(store, flags);
WRITE_INT(store, c->duration);
WRITE_FLT(store, (float)c->vigour);
write_unit_reference(mage, store);
WRITE_FLT(store, (float)c->effect);
if (c->type->write)
c->type->write(store, c, owner);
else if (c->type->typ == CURSETYP_UNIT) {
WRITE_INT(store, c->data.i);
}
if (c->type->typ == CURSETYP_REGION) {
write_region_reference((region *)c->data.v, store);
}
}
attrib_type at_curse = {
"curse",
curse_init,
curse_done,
curse_age,
curse_write,
curse_read,
NULL,
ATF_CURSE
};
/* ------------------------------------------------------------- */
/* Spruch identifizieren */
#include <util/umlaut.h>
#include <quicklist.h>
#define MAXCTHASH 128
static quicklist *cursetypes[MAXCTHASH];
static int ct_changes = 1;
bool ct_changed(int *cache)
{
assert(cache);
if (*cache != ct_changes) {
*cache = ct_changes;
return true;
}
return false;
}
void ct_register(const curse_type * ct)
{
unsigned int hash = tolower(ct->cname[0]) & 0xFF;
quicklist **ctlp = cursetypes + hash;
ql_set_insert(ctlp, (void *)ct);
++ct_changes;
}
void ct_remove(const char *c)
{
unsigned int hash = tolower(c[0]);
quicklist *ctl = cursetypes[hash];
if (ctl) {
int qi;
for (qi = 0; ctl; ql_advance(&ctl, &qi, 1)) {
curse_type *type = (curse_type *)ql_get(ctl, qi);
if (strcmp(c, type->cname) == 0) {
ql_delete(&ctl, qi);
++ct_changes;
break;
}
}
}
}
const curse_type *ct_find(const char *c)
{
unsigned int hash = tolower(c[0]);
quicklist *ctl = cursetypes[hash];
if (ctl) {
size_t c_len = strlen(c);
int qi;
for (qi = 0; ctl; ql_advance(&ctl, &qi, 1)) {
curse_type *type = (curse_type *)ql_get(ctl, qi);
if (strcmp(c, type->cname) == 0) {
return type;
}
else {
size_t k = _min(c_len, strlen(type->cname));
if (!_memicmp(c, type->cname, k)) {
return type;
}
}
}
}
return NULL;
}
void ct_checknames(void) {
int i, qi;
quicklist *ctl;
for (i = 0; i < MAXCTHASH; ++i) {
ctl = cursetypes[i];
for (qi = 0; ctl; ql_advance(&ctl, &qi, 1)) {
curse_type *type = (curse_type *)ql_get(ctl, qi);
curse_name(type, default_locale);
}
}
}
/* ------------------------------------------------------------- */
/* get_curse identifiziert eine Verzauberung <20>ber die ID und gibt
* einen pointer auf die struct zur<75>ck.
*/
static bool cmp_curse(const attrib * a, const void *data)
{
const curse *c = (const curse *)data;
if (a->type->flags & ATF_CURSE) {
if (!data || c == (curse *)a->data.v)
return true;
}
return false;
}
curse *get_curse(attrib * ap, const curse_type * ctype)
{
attrib *a = ap;
if (!ctype) return NULL;
while (a) {
if (a->type->flags & ATF_CURSE) {
const attrib_type *at = a->type;
while (a && a->type == at) {
curse *c = (curse *)a->data.v;
if (c->type == ctype)
return c;
a = a->next;
}
}
else {
a = a->nexttype;
}
}
return NULL;
}
/* ------------------------------------------------------------- */
/* findet einen curse global anhand seiner 'curse-Einheitnummer' */
curse *findcurse(int i)
{
curse *old;
for (old = cursehash[i % MAXENTITYHASH]; old; old = old->nexthash)
if (old->no == i)
return old;
return NULL;
}
/* ------------------------------------------------------------- */
bool remove_curse(attrib ** ap, const curse * c)
{
attrib *a = a_select(*ap, c, cmp_curse);
return a && a_remove(ap, a) == 1;
}
/* gibt die allgemeine St<53>rke der Verzauberung zur<75>ck. id2 wird wie
* oben benutzt. Dies ist nicht die Wirkung, sondern die Kraft und
* damit der gegen Antimagie wirkende Widerstand einer Verzauberung */
static double get_cursevigour(const curse * c)
{
return c ? c->vigour : 0;
}
/* setzt die St<53>rke der Verzauberung auf i */
static void set_cursevigour(curse * c, double vigour)
{
assert(c && vigour > 0);
c->vigour = vigour;
}
/* ver<65>ndert die St<53>rke der Verzauberung um +i und gibt die neue
* St<53>rke zur<75>ck. Sollte die Zauberst<73>rke unter Null sinken, l<>st er
* sich auf.
*/
double curse_changevigour(attrib ** ap, curse * c, double vigour)
{
vigour += get_cursevigour(c);
if (vigour <= 0) {
remove_curse(ap, c);
vigour = 0;
}
else {
set_cursevigour(c, vigour);
}
return vigour;
}
/* ------------------------------------------------------------- */
double curse_geteffect(const curse * c)
{
if (c == NULL)
return 0;
if (c_flags(c) & CURSE_ISNEW)
return 0;
return c->effect;
}
int curse_geteffect_int(const curse * c)
{
double effect = curse_geteffect(c);
if (effect - (int)effect != 0) {
log_error("curse has an integer attribute with float value: '%s' = %lf",
c->type->cname, effect);
}
return (int)effect;
}
/* ------------------------------------------------------------- */
static void
set_curseingmagician(struct unit *magician, struct attrib *ap_target,
const curse_type * ct)
{
curse *c = get_curse(ap_target, ct);
if (c) {
c->magician = magician;
}
}
/* ------------------------------------------------------------- */
/* gibt bei Personenbeschr<68>nkten Verzauberungen die Anzahl der
* betroffenen Personen zur<75>ck. Ansonsten wird 0 zur<75>ckgegeben. */
int get_cursedmen(unit * u, const curse * c)
{
int cursedmen = u->number;
if (!c)
return 0;
/* je nach curse_type andere data struct */
if (c->type->typ == CURSETYP_UNIT) {
cursedmen = c->data.i;
}
return _min(u->number, cursedmen);
}
/* setzt die Anzahl der betroffenen Personen auf cursedmen */
static void set_cursedmen(curse * c, int cursedmen)
{
if (!c)
return;
/* je nach curse_type andere data struct */
if (c->type->typ == CURSETYP_UNIT) {
c->data.i = cursedmen;
}
}
static int newcurseid(void) {
int random_no;
int start_random_no;
random_no = 1 + (rng_int() % MAX_UNIT_NR);
start_random_no = random_no;
while (findcurse(random_no)) {
random_no++;
if (random_no == MAX_UNIT_NR + 1) {
random_no = 1;
}
if (random_no == start_random_no) {
random_no = (int)MAX_UNIT_NR + 1;
}
}
return random_no;
}
/* ------------------------------------------------------------- */
/* Legt eine neue Verzauberung an. Sollte es schon einen Zauber
* dieses Typs geben, gibt es den bestehenden zur<75>ck.
*/
static curse *make_curse(unit * mage, attrib ** ap, const curse_type * ct,
double vigour, int duration, double effect, int men)
{
curse *c;
attrib *a;
a = a_new(&at_curse);
a_add(ap, a);
c = (curse *)a->data.v;
c->type = ct;
c->flags = 0;
c->vigour = vigour;
c->duration = duration;
c->effect = effect;
c->magician = mage;
c->no = newcurseid();
chash(c);
switch (c->type->typ) {
case CURSETYP_NORM:
break;
case CURSETYP_UNIT:
c->data.i = men;
break;
}
return c;
}
/* Mapperfunktion f<>r das Anlegen neuer curse. Automatisch wird zum
* passenden Typ verzweigt und die relevanten Variablen weitergegeben.
*/
curse *create_curse(unit * magician, attrib ** ap, const curse_type * ct,
double vigour, int duration, double effect, int men)
{
curse *c;
/* die Kraft eines Spruchs darf nicht 0 sein */
assert(vigour > 0);
c = get_curse(*ap, ct);
if (c && (c_flags(c) & CURSE_ONLYONE)) {
return NULL;
}
assert(c == NULL || ct == c->type);
/* es gibt schon eins diese Typs */
if (c && ct->mergeflags != NO_MERGE) {
if (ct->mergeflags & M_DURATION) {
c->duration = _max(c->duration, duration);
}
if (ct->mergeflags & M_SUMDURATION) {
c->duration += duration;
}
if (ct->mergeflags & M_SUMEFFECT) {
c->effect += effect;
}
if (ct->mergeflags & M_MAXEFFECT) {
c->effect = _max(c->effect, effect);
}
if (ct->mergeflags & M_VIGOUR) {
c->vigour = _max(vigour, c->vigour);
}
if (ct->mergeflags & M_VIGOUR_ADD) {
c->vigour = vigour + c->vigour;
}
if (ct->mergeflags & M_MEN) {
switch (ct->typ) {
case CURSETYP_UNIT:
{
c->data.i += men;
}
}
}
set_curseingmagician(magician, *ap, ct);
}
else {
c = make_curse(magician, ap, ct, vigour, duration, effect, men);
}
return c;
}
/* ------------------------------------------------------------- */
/* hier m<>ssen alle c-typen, die auf Einheiten gezaubert werden k<>nnen,
* ber<65>cksichtigt werden */
static void do_transfer_curse(curse * c, unit * u, unit * u2, int n)
{
int cursedmen = 0;
int men = get_cursedmen(u, c);
bool dogive = false;
const curse_type *ct = c->type;
switch ((ct->flags | c->flags) & CURSE_SPREADMASK) {
case CURSE_SPREADALWAYS:
dogive = true;
men = u2->number + n;
break;
case CURSE_SPREADMODULO:
{
int i;
int u_number = u->number;
for (i = 0; i < n + 1 && u_number > 0; i++) {
if (rng_int() % u_number < cursedmen) {
++men;
--cursedmen;
dogive = true;
}
--u_number;
}
break;
}
case CURSE_SPREADCHANCE:
if (chance(u2->number / (double)(u2->number + n))) {
men = u2->number + n;
dogive = true;
}
break;
case CURSE_SPREADNEVER:
break;
}
if (dogive) {
curse *cnew = make_curse(c->magician, &u2->attribs, c->type, c->vigour,
c->duration, c->effect, men);
cnew->flags = c->flags;
if (ct->typ == CURSETYP_UNIT)
set_cursedmen(cnew, men);
}
}
void transfer_curse(unit * u, unit * u2, int n)
{
attrib *a;
a = a_find(u->attribs, &at_curse);
while (a && a->type == &at_curse) {
curse *c = (curse *)a->data.v;
do_transfer_curse(c, u, u2, n);
a = a->next;
}
}
/* ------------------------------------------------------------- */
int curse_cansee(const curse *c, const faction *viewer, objtype_t typ, const void *obj, int self) {
if (self < 3 && c->magician && c->magician->faction == viewer) {
// magicians can see their own curses better than anybody, no exceptions
self = 3;
}
else if (c->type->cansee) {
self = c->type->cansee(viewer, obj, typ, c, self);
}
return self;
}
bool curse_active(const curse * c)
{
if (!c)
return false;
if (c_flags(c) & CURSE_ISNEW)
return false;
if (c->vigour <= 0)
return false;
return true;
}
bool is_cursed_internal(attrib * ap, const curse_type * ct)
{
curse *c = get_curse(ap, ct);
if (!c)
return false;
return true;
}
bool is_cursed_with(const attrib * ap, const curse * c)
{
const attrib *a = ap;
while (a) {
if ((a->type->flags & ATF_CURSE) && (c == (const curse *)a->data.v)) {
return true;
}
a = a->next;
}
return false;
}
/* ------------------------------------------------------------- */
/* cursedata */
/* ------------------------------------------------------------- */
static const char *oldnames[MAXCURSE] = {
/* OBS: when removing curses, remember to update read_ccompat() */
"fogtrap",
"antimagiczone",
"farvision",
"gbdream",
"auraboost",
"maelstrom",
"blessedharvest",
"drought",
"badlearn",
"stormwind",
"flyingship",
"nodrift",
"depression",
"magicwalls",
"strongwall",
"astralblock",
"generous",
"peacezone",
"magicstreet",
"magicrunes",
"badmagicresistancezone",
"goodmagicresistancezone",
"slavery",
"calmmonster",
"oldrace",
"fumble",
"riotzone",
"godcursezone",
"speed",
"orcish",
"magicboost",
"insectfur"
};
const char *oldcursename(int id)
{
return oldnames[id];
}
/* ------------------------------------------------------------- */
message *cinfo_simple(const void *obj, objtype_t typ, const struct curse * c,
int self)
{
struct message *msg;
unused_arg(typ);
unused_arg(self);
unused_arg(obj);
msg = msg_message(mkname("curseinfo", c->type->cname), "id", c->no);
if (msg == NULL) {
log_error("There is no curseinfo for %s.\n", c->type->cname);
}
return msg;
}
/* ------------------------------------------------------------- */
/* Antimagie - curse aufl<66>sen */
/* ------------------------------------------------------------- */
/* Wenn der Curse schw<68>cher ist als der cast_level, dann wird er
* aufgel<65>st, bzw seine Kraft (vigour) auf 0 gesetzt.
* Ist der cast_level zu gering, hat die Antimagie nur mit einer Chance
* von 100-20*Stufenunterschied % eine Wirkung auf den Curse. Dann wird
* die Kraft des Curse um die halbe St<53>rke der Antimagie reduziert.
* Zur<75>ckgegeben wird der noch unverbrauchte Rest von force.
*/
double destr_curse(curse * c, int cast_level, double force)
{
if (cast_level < c->vigour) { /* Zauber ist nicht stark genug */
double probability = 0.1 + (cast_level - c->vigour) * 0.2;
/* pro Stufe Unterschied -20% */
if (chance(probability)) {
force -= c->vigour;
if (c->type->change_vigour) {
c->type->change_vigour(c, -(cast_level + 1) / 2);
}
else {
c->vigour -= (cast_level + 1) / 2.0;
}
}
}
else { /* Zauber ist st<73>rker als curse */
if (force >= c->vigour) { /* reicht die Kraft noch aus? */
force -= c->vigour;
if (c->type->change_vigour) {
c->type->change_vigour(c, -c->vigour);
}
else {
c->vigour = 0;
}
}
}
return force;
}
void curses_done(void) {
int i;
for (i = 0; i != MAXCTHASH; ++i) {
ql_free(cursetypes[i]);
cursetypes[i] = 0;
}
++ct_changes;
}