server/src/magic.c

3075 lines
88 KiB
C
Raw Normal View History

/*
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
2014-08-08 01:03:46 +02:00
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>
2010-08-08 10:06:34 +02:00
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.
**/
#ifdef _MSC_VER
#include <platform.h>
#endif
2010-08-08 10:06:34 +02:00
#include "magic.h"
#include "contact.h"
#include "helpers.h"
#include "laws.h"
#include "skill.h"
#include "spells.h"
#include "study.h"
#include <triggers/timeout.h>
#include <triggers/shock.h>
#include <triggers/killunit.h>
#include <triggers/giveitem.h>
#include <triggers/changerace.h>
#include <triggers/clonedied.h>
#include <spells/regioncurse.h>
#include <spells/buildingcurse.h>
#include <spells/unitcurse.h>
#include <kernel/ally.h>
#include <kernel/building.h>
#include <kernel/callbacks.h>
#include <kernel/config.h>
#include <kernel/curse.h>
#include <kernel/equipment.h>
#include <kernel/faction.h>
#include <kernel/item.h>
#include <kernel/messages.h>
#include <kernel/objtypes.h>
#include <kernel/order.h>
#include <kernel/pathfinder.h>
#include <kernel/plane.h>
#include <kernel/pool.h>
#include <kernel/race.h>
#include <kernel/region.h>
#include <kernel/ship.h>
#include <kernel/spell.h>
#include <kernel/spellbook.h>
#include <kernel/terrain.h>
#include <kernel/unit.h>
2010-08-08 10:06:34 +02:00
/* util includes */
#include <kernel/attrib.h>
#include <util/base36.h>
#include <kernel/event.h>
2018-09-29 13:21:46 +02:00
#include <kernel/gamedata.h>
2010-08-08 10:06:34 +02:00
#include <util/language.h>
#include <util/lists.h>
#include <util/log.h>
#include <util/macros.h>
2018-09-29 19:32:39 +02:00
#include <util/param.h>
2010-08-08 10:06:34 +02:00
#include <util/parser.h>
#include <util/rand.h>
#include <util/resolve.h>
2010-08-08 10:06:34 +02:00
#include <util/rng.h>
#include <util/strings.h>
#include <util/umlaut.h>
2010-08-08 10:06:34 +02:00
#include <selist.h>
#include <critbit.h>
#include <storage.h>
2010-08-08 10:06:34 +02:00
/* libc includes */
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <assert.h>
2015-07-12 14:15:20 +02:00
#include <errno.h>
2010-08-08 10:06:34 +02:00
#include <math.h>
2011-03-07 08:02:35 +01:00
const char *magic_school[MAXMAGIETYP] = {
2014-08-08 01:03:46 +02:00
"gray",
"illaun",
"tybied",
"cerddor",
"gwyrrd",
"draig",
"common"
2010-08-08 10:06:34 +02:00
};
struct combatspell {
int level;
const struct spell *sp;
};
typedef struct sc_mage {
magic_t magietyp;
int spellpoints;
int spchange;
int spellcount;
struct combatspell combatspells[MAXCOMBATSPELLS];
struct spellbook *spellbook;
} sc_mage;
void mage_set_spellpoints(sc_mage *m, int aura)
{
m->spellpoints = aura;
}
int mage_get_spellpoints(const sc_mage *m)
{
return m ? m->spellpoints : 0;
}
int mage_change_spellpoints(sc_mage *m, int delta)
{
if (m) {
int val = m->spellpoints + delta;
return m->spellpoints = (val >= 0) ? val : m->spellpoints;
}
return 0;
}
magic_t mage_get_type(const sc_mage *m)
{
return m ? m->magietyp : M_GRAY;
}
const spell *mage_get_combatspell(const sc_mage *mage, int nr, int *level)
{
assert(nr < MAXCOMBATSPELLS);
if (mage) {
if (level) {
*level = mage->combatspells[nr].level;
}
return mage->combatspells[nr].sp;
}
return NULL;
}
void unit_set_magic(struct unit *u, enum magic_t mtype)
{
sc_mage *mage = get_mage(u);
if (mage) {
mage->magietyp = mtype;
}
}
magic_t unit_get_magic(const unit *u)
{
return mage_get_type(get_mage(u));
}
void unit_add_spell(unit * u, struct spell * sp, int level)
{
sc_mage *mage = get_mage(u);
if (!mage) {
log_error("adding new spell %s to a previously non-magical unit %s\n", sp->sname, unitname(u));
mage = create_mage(u, M_GRAY);
}
if (!mage->spellbook) {
mage->spellbook = create_spellbook(0);
}
spellbook_add(mage->spellbook, sp, level);
}
2010-08-08 10:06:34 +02:00
/**
** at_icastle
** TODO: separate castle-appearance from illusion-effects
**/
static double MagicRegeneration(void)
2010-08-08 10:06:34 +02:00
{
return config_get_flt("magic.regeneration", 1.0);
2010-08-08 10:06:34 +02:00
}
static double MagicPower(double force)
2010-08-08 10:06:34 +02:00
{
if (force > 0) {
const char *str = config_get("magic.power");
double value = str ? atof(str) : 1.0;
return fmax(value * force, 1.0f);
2014-08-08 01:03:46 +02:00
}
return 0;
2010-08-08 10:06:34 +02:00
}
typedef struct icastle_data {
const struct building_type *type;
int time;
} icastle_data;
static int a_readicastle(variant *var, void *owner, struct gamedata *data)
2010-08-08 10:06:34 +02:00
{
storage *store = data->store;
icastle_data *idata = (icastle_data *)var->v;
2014-08-08 01:03:46 +02:00
char token[32];
UNUSED_ARG(owner);
2014-08-08 01:03:46 +02:00
READ_TOK(store, token, sizeof(token));
if (data->version < ATTRIBOWNER_VERSION) {
READ_INT(store, NULL);
2014-08-08 01:03:46 +02:00
}
READ_INT(store, &idata->time);
idata->type = bt_find(token);
2014-08-08 01:03:46 +02:00
return AT_READ_OK;
2010-08-08 10:06:34 +02:00
}
static void
a_writeicastle(const variant *var, const void *owner, struct storage *store)
2010-08-08 10:06:34 +02:00
{
icastle_data *data = (icastle_data *)var->v;
UNUSED_ARG(owner);
2014-08-08 01:03:46 +02:00
WRITE_TOK(store, data->type->_name);
WRITE_INT(store, data->time);
2010-08-08 10:06:34 +02:00
}
static int a_ageicastle(struct attrib *a, void *owner)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
icastle_data *data = (icastle_data *)a->data.v;
if (data->time <= 0) {
building *b = (building *)owner;
2014-08-08 01:03:46 +02:00
region *r = b->region;
assert(owner == b);
2014-08-08 01:03:46 +02:00
ADDMSG(&r->msgs, msg_message("icastle_dissolve", "building", b));
/* remove_building lets units leave the building */
remove_building(&r->buildings, b);
return AT_AGE_REMOVE;
}
else
data->time--;
return AT_AGE_KEEP;
2010-08-08 10:06:34 +02:00
}
static void a_initicastle(variant *var)
2010-08-08 10:06:34 +02:00
{
var->v = calloc(1, sizeof(icastle_data));
2010-08-08 10:06:34 +02:00
}
attrib_type at_icastle = {
2014-08-08 01:03:46 +02:00
"zauber_icastle",
a_initicastle,
a_free_voidptr,
2014-08-08 01:03:46 +02:00
a_ageicastle,
a_writeicastle,
a_readicastle
2010-08-08 10:06:34 +02:00
};
void make_icastle(building *b, const building_type *btype, int timeout) {
attrib *a = a_add(&b->attribs, a_new(&at_icastle));
icastle_data *data = (icastle_data *)a->data.v;
data->type = btype;
data->time = timeout;
}
const building_type *icastle_type(const struct attrib *a) {
icastle_data *icastle = (icastle_data *)a->data.v;
return icastle->type;
}
2010-08-08 10:06:34 +02:00
/* ------------------------------------------------------------- */
extern int dice(int count, int value);
bool FactionSpells(void)
{
static int config, rule;
if (config_changed(&config)) {
rule = config_get_int("rules.magic.factionlist", 0);
}
return rule != 0;
}
int get_spell_level_mage(const spell * sp, void * cbdata)
{
sc_mage *mage = (sc_mage *)cbdata;
spellbook *book = get_spellbook(magic_school[mage->magietyp]);
spellbook_entry *sbe = spellbook_get(book, sp);
return sbe ? sbe->level : 0;
}
2010-08-08 10:06:34 +02:00
/* ------------------------------------------------------------- */
2019-02-08 11:37:32 +01:00
/* aus dem alten System uebriggebliegene Funktionen, die bei der
2010-08-08 10:06:34 +02:00
* Umwandlung von alt nach neu gebraucht werden */
/* ------------------------------------------------------------- */
2010-08-08 10:06:34 +02:00
static void init_mage(variant *var)
2011-03-07 08:02:35 +01:00
{
var->v = calloc(1, sizeof(sc_mage));
2010-08-08 10:06:34 +02:00
}
static void free_mage(variant *var)
2010-08-08 10:06:34 +02:00
{
sc_mage *mage = (sc_mage *)var->v;
2014-08-08 01:03:46 +02:00
if (mage->spellbook) {
spellbook_clear(mage->spellbook);
free(mage->spellbook);
}
free(mage);
2010-08-08 10:06:34 +02:00
}
static int read_mage(variant *var, void *owner, struct gamedata *data)
2010-08-08 10:06:34 +02:00
{
storage *store = data->store;
2014-08-08 01:03:46 +02:00
int i, mtype;
sc_mage *mage = (sc_mage *)var->v;
2014-08-08 01:03:46 +02:00
char spname[64];
UNUSED_ARG(owner);
2014-08-08 01:03:46 +02:00
READ_INT(store, &mtype);
mage->magietyp = (magic_t)mtype;
READ_INT(store, &mage->spellpoints);
READ_INT(store, &mage->spchange);
for (i = 0; i != MAXCOMBATSPELLS; ++i) {
spell *sp = NULL;
int level = 0;
READ_TOK(store, spname, sizeof(spname));
READ_INT(store, &level);
if (strcmp("none", spname) != 0) {
sp = find_spell(spname);
if (!sp) {
log_error("read_mage: could not find combat spell '%s' in school '%s'\n", spname, magic_school[mage->magietyp]);
2014-08-08 01:03:46 +02:00
}
}
if (sp && level >= 0) {
int slot = -1;
if (sp->sptyp & PRECOMBATSPELL)
slot = 0;
else if (sp->sptyp & COMBATSPELL)
slot = 1;
else if (sp->sptyp & POSTCOMBATSPELL)
slot = 2;
if (slot >= 0) {
mage->combatspells[slot].level = level;
mage->combatspells[slot].sp = sp;
}
}
}
if (mage->magietyp == M_GRAY) {
read_spellbook(&mage->spellbook, data, get_spell_level_mage, mage);
2014-08-08 01:03:46 +02:00
}
else {
read_spellbook(NULL, data, NULL, mage);
2014-08-08 01:03:46 +02:00
}
return AT_READ_OK;
2010-08-08 10:06:34 +02:00
}
static void
write_mage(const variant *var, const void *owner, struct storage *store)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
int i;
sc_mage *mage = (sc_mage *)var->v;
2010-08-08 10:06:34 +02:00
UNUSED_ARG(owner);
2014-08-08 01:03:46 +02:00
WRITE_INT(store, mage->magietyp);
WRITE_INT(store, mage->spellpoints);
WRITE_INT(store, mage->spchange);
for (i = 0; i != MAXCOMBATSPELLS; ++i) {
WRITE_TOK(store,
mage->combatspells[i].sp ? mage->combatspells[i].sp->sname : "none");
WRITE_INT(store, mage->combatspells[i].level);
}
write_spellbook(mage->spellbook, store);
2010-08-08 10:06:34 +02:00
}
attrib_type at_mage = {
2014-08-08 01:03:46 +02:00
"mage",
init_mage,
free_mage,
NULL,
write_mage,
read_mage,
NULL,
2014-08-08 01:03:46 +02:00
ATF_UNIQUE
2010-08-08 10:06:34 +02:00
};
bool is_mage(const struct unit * u)
2010-08-08 10:06:34 +02:00
{
sc_mage *m = get_mage(u);
return (m && m->magietyp != M_GRAY);
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
sc_mage *get_mage(const unit * u)
{
attrib *a = a_find(u->attribs, &at_mage);
if (a) {
return (sc_mage *)a->data.v;
}
return NULL;
}
struct spellbook * mage_get_spellbook(const struct sc_mage * mage) {
if (mage) {
return mage->spellbook;
}
return NULL;
}
struct spellbook * unit_get_spellbook(const struct unit * u)
2010-08-08 10:06:34 +02:00
{
sc_mage * mage = get_mage(u);
if (mage) {
if (mage->spellbook) {
return mage->spellbook;
}
if (mage->magietyp != M_GRAY) {
return faction_get_spellbook(u->faction);
2017-08-25 08:52:15 +02:00
}
2014-08-08 01:03:46 +02:00
}
return NULL;
2010-08-08 10:06:34 +02:00
}
#define MAXSPELLS 256
2010-08-08 10:06:34 +02:00
/** update the spellbook with a new level
* Written for E3
2010-08-08 10:06:34 +02:00
*/
void pick_random_spells(faction * f, int level, spellbook * book, int num_spells)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
spellbook_entry *commonspells[MAXSPELLS];
int qi, numspells = 0;
selist *ql;
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
if (level <= f->max_spelllevel) {
return;
}
for (qi = 0, ql = book->spells; ql; selist_advance(&ql, &qi, 1)) {
spellbook_entry * sbe = (spellbook_entry *)selist_get(ql, qi);
2018-12-15 20:01:51 +01:00
if (sbe && sbe->level <= level) {
2014-08-08 01:03:46 +02:00
commonspells[numspells++] = sbe;
}
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
while (numspells > 0 && level > f->max_spelllevel) {
int i;
++f->max_spelllevel;
for (i = 0; i < num_spells; ++i) {
int maxspell = numspells;
int spellno = -1;
spellbook_entry *sbe = 0;
while (!sbe && maxspell > 0) {
2014-08-08 01:03:46 +02:00
spellno = rng_int() % maxspell;
sbe = commonspells[spellno];
if (sbe->level > f->max_spelllevel) {
/* not going to pick it in this round, move it to the end for later */
2014-08-08 01:03:46 +02:00
commonspells[spellno] = commonspells[--maxspell];
commonspells[maxspell] = sbe;
sbe = 0;
}
2017-02-02 16:52:32 +01:00
else {
if (f->spellbook) {
const spell *sp = spellref_get(&sbe->spref);
if (sp && spellbook_get(f->spellbook, sp)) {
/* already have this spell, remove it from the list of candidates */
commonspells[spellno] = commonspells[--numspells];
if (maxspell > numspells) {
maxspell = numspells;
}
sbe = 0;
2017-02-02 16:52:32 +01:00
}
2014-08-08 01:03:46 +02:00
}
}
}
2015-11-04 14:19:43 +01:00
if (sbe && spellno < maxspell) {
2014-08-08 01:03:46 +02:00
if (!f->spellbook) {
f->spellbook = create_spellbook(0);
}
spellbook_add(f->spellbook, spellref_get(&sbe->spref), sbe->level);
2014-08-08 01:03:46 +02:00
commonspells[spellno] = commonspells[--numspells];
}
}
}
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
/* Erzeugen eines neuen Magiers */
2011-03-07 08:02:35 +01:00
sc_mage *create_mage(unit * u, magic_t mtyp)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
sc_mage *mage;
attrib *a;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
a = a_find(u->attribs, &at_mage);
if (a == NULL) {
a = a_add(&u->attribs, a_new(&at_mage));
2014-08-08 01:03:46 +02:00
}
mage = (sc_mage *)a->data.v;
2014-08-08 01:03:46 +02:00
mage->magietyp = mtyp;
return mage;
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
2019-02-08 11:37:32 +01:00
/* Funktionen fuer die Bearbeitung der List-of-known-spells */
2010-08-08 10:06:34 +02:00
2012-05-26 17:20:26 +02:00
int u_hasspell(const unit *u, const struct spell *sp)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
spellbook * book = unit_get_spellbook(u);
spellbook_entry * sbe = book ? spellbook_get(book, sp) : 0;
if (sbe) {
return sbe->level <= effskill(u, SK_MAGIC, NULL);
2014-08-08 01:03:46 +02:00
}
return 0;
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
/* Eingestellte Kampfzauberstufe ermitteln */
2011-03-07 08:02:35 +01:00
int get_combatspelllevel(const unit * u, int nr)
2010-08-08 10:06:34 +02:00
{
int level;
if (mage_get_combatspell(get_mage(u), nr, &level) != NULL) {
int maxlevel = effskill(u, SK_MAGIC, NULL);
if (level > maxlevel) {
return maxlevel;
}
return level;
2014-08-08 01:03:46 +02:00
}
return 0;
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
2019-02-08 11:37:32 +01:00
/* Kampfzauber ermitteln, setzen oder loeschen */
2010-08-08 10:06:34 +02:00
2011-03-07 08:02:35 +01:00
const spell *get_combatspell(const unit * u, int nr)
2010-08-08 10:06:34 +02:00
{
return mage_get_combatspell(get_mage(u), nr, NULL);
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
void set_combatspell(unit * u, spell * sp, struct order *ord, int level)
2010-08-08 10:06:34 +02:00
{
sc_mage *mage = get_mage(u);
2014-08-08 01:03:46 +02:00
int i = -1;
assert(mage || !"trying to set a combat spell for non-mage");
if (sp->sptyp & PRECOMBATSPELL)
i = 0;
else if (sp->sptyp & COMBATSPELL)
i = 1;
else if (sp->sptyp & POSTCOMBATSPELL)
i = 2;
assert(i >= 0);
mage->combatspells[i].sp = sp;
mage->combatspells[i].level = level;
2010-08-08 10:06:34 +02:00
return;
}
2011-03-07 08:02:35 +01:00
void unset_combatspell(unit * u, spell * sp)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
int nr = 0;
sc_mage *m = get_mage(u);
2010-08-08 10:06:34 +02:00
if (!m) {
2014-08-08 01:03:46 +02:00
return;
}
2014-08-08 01:03:46 +02:00
if (!sp) {
2018-02-11 15:57:31 +01:00
int i;
2014-08-08 01:03:46 +02:00
for (i = 0; i < MAXCOMBATSPELLS; i++) {
m->combatspells[i].sp = NULL;
}
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
else if (sp->sptyp & PRECOMBATSPELL) {
if (sp != get_combatspell(u, 0))
return;
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
else if (sp->sptyp & COMBATSPELL) {
if (sp != get_combatspell(u, 1)) {
return;
}
nr = 1;
}
else if (sp->sptyp & POSTCOMBATSPELL) {
if (sp != get_combatspell(u, 2)) {
return;
}
nr = 2;
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
m->combatspells[nr].sp = NULL;
m->combatspells[nr].level = 0;
return;
2010-08-08 10:06:34 +02:00
}
2019-02-08 11:37:32 +01:00
/**
* Gibt die aktuelle Anzahl der Magiepunkte der Einheit zurueck
*/
2011-03-07 08:02:35 +01:00
int get_spellpoints(const unit * u)
2010-08-08 10:06:34 +02:00
{
return mage_get_spellpoints(get_mage(u));
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
void set_spellpoints(unit * u, int sp)
2010-08-08 10:06:34 +02:00
{
sc_mage *m = get_mage(u);
if (m) {
mage_set_spellpoints(m, sp);
}
2010-08-08 10:06:34 +02:00
}
2019-02-08 11:37:32 +01:00
/**
* Veraendert die Anzahl der Magiepunkte der Einheit um +mp
2010-08-08 10:06:34 +02:00
*/
2011-03-07 08:02:35 +01:00
int change_spellpoints(unit * u, int mp)
2010-08-08 10:06:34 +02:00
{
return mage_change_spellpoints(get_mage(u), mp);
2010-08-08 10:06:34 +02:00
}
/* ein Magier kann normalerweise maximal Stufe^2.1/1.2+1 Magiepunkte
* haben.
2019-02-08 11:37:32 +01:00
* Manche Rassen haben einen zusaetzlichen Multiplikator
* Durch Talentverlust (zB Insekten im Berg) koennen negative Werte
2010-08-08 10:06:34 +02:00
* entstehen
*/
2019-02-08 11:37:32 +01:00
/* Artefakt der Staerke
* Ermoeglicht dem Magier mehr Magiepunkte zu 'speichern'
*/
/** TODO: at_skillmod daraus machen */
2011-03-07 08:02:35 +01:00
static int use_item_aura(const region * r, const unit * u)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
int sk, n;
2010-08-08 10:06:34 +02:00
sk = effskill(u, SK_MAGIC, r);
2017-02-03 19:50:48 +01:00
n = (int)(sk * sk * rc_maxaura(u_race(u)) / 4);
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
return n;
2010-08-08 10:06:34 +02:00
}
2018-11-24 12:26:52 +01:00
int max_spellpoints(const struct unit *u, const region * r)
2010-08-08 10:06:34 +02:00
{
int sk;
2017-02-03 19:50:48 +01:00
double n, msp = 0;
double potenz = 2.1;
double divisor = 1.2;
const struct resource_type *rtype;
const sc_mage *m;
2018-11-24 12:26:52 +01:00
assert(u);
m = get_mage(u);
if (!m) return 0;
2018-11-24 12:26:52 +01:00
if (!r) r = u->region;
sk = effskill(u, SK_MAGIC, r);
msp = rc_maxaura(u_race(u)) * (pow(sk, potenz) / divisor + 1);
msp += m->spchange;
2014-08-08 01:03:46 +02:00
rtype = rt_find("aurafocus");
if (rtype && i_get(u->items, rtype->itype) > 0) {
msp += use_item_aura(r, u);
}
n = get_curseeffect(u->attribs, &ct_auraboost);
if (n > 0) {
msp = (msp * n) / 100;
}
return (msp > 0) ? (int)msp : 0;
2010-08-08 10:06:34 +02:00
}
2018-11-24 12:26:52 +01:00
int max_spellpoints_depr(const struct region *r, const struct unit *u)
{
return max_spellpoints(u, r);
}
2011-03-07 08:02:35 +01:00
int change_maxspellpoints(unit * u, int csp)
2010-08-08 10:06:34 +02:00
{
sc_mage *m = get_mage(u);
if (!m) {
return 0;
}
m->spchange += csp;
2018-11-24 12:26:52 +01:00
return max_spellpoints_depr(u->region, u);
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
2019-02-08 11:37:32 +01:00
/* Counter fuer die bereits gezauberte Anzahl Sprueche pro Runde.
2010-08-08 10:06:34 +02:00
*/
2011-03-07 08:02:35 +01:00
int countspells(unit * u, int step)
2010-08-08 10:06:34 +02:00
{
sc_mage * m = get_mage(u);
if (m) {
int count;
if (step == 0) {
return m->spellcount;
}
count = m->spellcount + step;
m->spellcount = (count > 0) ? count : 0;
2014-08-08 01:03:46 +02:00
return m->spellcount;
}
return 0;
2010-08-08 10:06:34 +02:00
}
int spellcount(const unit *u) {
sc_mage *m = get_mage(u);
return m ? m->spellcount : 0;
}
/**
2019-02-08 11:37:32 +01:00
* Die Grundkosten pro Stufe werden um 2^count erhoeht. countspells(u)
* ist dabei die Anzahl der bereits gezauberten Sprueche
2010-08-08 10:06:34 +02:00
*/
int aura_multiplier(const unit * u) {
int count = spellcount(u);
return (1 << count);
}
int spellcost(const unit * caster, const struct spell_component *spc)
2010-08-08 10:06:34 +02:00
{
const resource_type *r_aura = get_resourcetype(R_AURA);
2010-08-08 10:06:34 +02:00
if (spc->type == r_aura) {
return spc->amount * aura_multiplier(caster);
}
return spc->amount;
}
/**
2019-02-08 11:37:32 +01:00
* Die fuer den Spruch benoetigte Aura pro Stufe.
*/
int auracost(const unit *caster, const spell *sp) {
const resource_type *r_aura = get_resourcetype(R_AURA);
if (sp->components) {
int k;
for (k = 0; sp->components[k].type; ++k) {
const struct spell_component *spc = sp->components + k;
if (r_aura == spc->type) {
return spc->amount * aura_multiplier(caster);
}
2014-08-08 01:03:46 +02:00
}
2010-08-08 10:06:34 +02:00
}
return 0;
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
2019-02-08 11:37:32 +01:00
/* SPC_LINEAR ist am hoechstwertigen, dann muessen Komponenten fuer die
2010-08-08 10:06:34 +02:00
* Stufe des Magiers vorhanden sein.
2019-02-08 11:37:32 +01:00
* SPC_LINEAR hat die gewuenschte Stufe als multiplikator,
2010-08-08 10:06:34 +02:00
* nur SPC_FIX muss nur einmal vorhanden sein, ist also am
* niedrigstwertigen und sollte von den beiden anderen Typen
2019-02-08 11:37:32 +01:00
* ueberschrieben werden */
2011-03-07 08:02:35 +01:00
static int spl_costtyp(const spell * sp)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
int k;
int costtyp = SPC_FIX;
2010-08-08 10:06:34 +02:00
for (k = 0; sp->components && sp->components[k].type; k++) {
2014-08-08 01:03:46 +02:00
if (costtyp == SPC_LINEAR)
return SPC_LINEAR;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (sp->components[k].cost == SPC_LINEAR) {
return SPC_LINEAR;
}
2010-08-08 10:06:34 +02:00
2019-02-08 11:37:32 +01:00
/* wenn keine Fixkosten, Typ uebernehmen */
2014-08-08 01:03:46 +02:00
if (sp->components[k].cost != SPC_FIX) {
costtyp = sp->components[k].cost;
}
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
return costtyp;
2010-08-08 10:06:34 +02:00
}
/**
2019-02-08 11:37:32 +01:00
* Durch Komponenten und cast_level begrenzter maximal moeglicher Level.
*
2010-08-08 10:06:34 +02:00
* Da die Funktion nicht alle Komponenten durchprobiert sondern beim
2019-02-08 11:37:32 +01:00
* ersten Fehler abbricht, muss die Fehlermeldung spaeter mit cancast()
2010-08-08 10:06:34 +02:00
* generiert werden.
*/
int eff_spelllevel(unit * u, unit *caster, const spell * sp, int cast_level, int range)
2010-08-08 10:06:34 +02:00
{
const resource_type *r_aura = get_resourcetype(R_AURA);
int k, maxlevel;
int costtyp = SPC_FIX;
2011-03-07 08:02:35 +01:00
for (k = 0; sp->components && sp->components[k].type; k++) {
2014-08-08 01:03:46 +02:00
if (cast_level == 0)
return 0;
if (sp->components[k].amount > 0) {
int level_cost = sp->components[k].amount * range;
2014-08-08 01:03:46 +02:00
if (sp->components[k].type == r_aura) {
/* Die Kosten fuer Aura sind auch von der Zahl der bereits
* gezauberten Sprueche abhaengig */
level_cost *= aura_multiplier(caster);
2014-08-08 01:03:46 +02:00
}
maxlevel =
get_pooled(u, sp->components[k].type, GET_DEFAULT,
level_cost * cast_level) / level_cost;
2014-08-08 01:03:46 +02:00
/* sind die Kosten fix, so muss die Komponente nur einmal vorhanden
2019-02-08 11:37:32 +01:00
* sein und der cast_level aendert sich nicht */
2014-08-08 01:03:46 +02:00
if (sp->components[k].cost == SPC_FIX) {
if (maxlevel < 1)
cast_level = 0;
2019-02-08 11:37:32 +01:00
/* ansonsten wird das Minimum aus maximal moeglicher Stufe und der
* gewuenschten gebildet */
2014-08-08 01:03:46 +02:00
}
else if (sp->components[k].cost == SPC_LEVEL) {
costtyp = SPC_LEVEL;
if (maxlevel < cast_level) {
cast_level = maxlevel;
}
2019-02-08 11:37:32 +01:00
/* bei Typ Linear muessen die Kosten in Hoehe der Stufe vorhanden
* sein, ansonsten schlaegt der Spruch fehl */
2014-08-08 01:03:46 +02:00
}
else if (sp->components[k].cost == SPC_LINEAR) {
costtyp = SPC_LINEAR;
if (maxlevel < cast_level)
cast_level = 0;
}
}
}
/* Ein Spruch mit Fixkosten wird immer mit der Stufe des Spruchs und
* nicht auf der Stufe des Magiers gezaubert */
if (costtyp == SPC_FIX) {
spellbook * sb = unit_get_spellbook(u);
if (sb) {
spellbook_entry * sbe = spellbook_get(sb, sp);
if (sbe && cast_level > sbe->level) {
return sbe->level;
2014-08-08 01:03:46 +02:00
}
}
else {
log_error("spell %s is not in the spellbook for %s\n", sp->sname, unitname(u));
}
2014-08-08 01:03:46 +02:00
}
return cast_level;
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
/* Die Spruchgrundkosten werden mit der Entfernung (Farcasting)
* multipliziert, wobei die Aurakosten ein Sonderfall sind, da sie sich
2019-02-08 11:37:32 +01:00
* auch durch die Menge der bereits gezauberten Sprueche erhoeht.
2010-08-08 10:06:34 +02:00
* Je nach Kostenart werden dann die Komponenten noch mit cast_level
* multipliziert.
*/
void pay_spell(unit * mage, const unit *caster, const spell * sp, int cast_level, int range)
2010-08-08 10:06:34 +02:00
{
int k;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
assert(cast_level > 0);
for (k = 0; sp->components && sp->components[k].type; k++) {
const struct spell_component *spc = sp->components + k;
int resuse = spellcost(caster ? caster : mage, spc) * range;
2010-08-08 10:06:34 +02:00
if (spc->cost == SPC_LINEAR || spc->cost == SPC_LEVEL) {
2014-08-08 01:03:46 +02:00
resuse *= cast_level;
}
2010-08-08 10:06:34 +02:00
use_pooled(mage, spc->type, GET_DEFAULT, resuse);
2014-08-08 01:03:46 +02:00
}
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
/* Ein Magier kennt den Spruch und kann sich die Beschreibung anzeigen
* lassen, wenn diese in seiner Spruchliste steht. Zaubern muss er ihn
2019-02-08 11:37:32 +01:00
* aber dann immer noch nicht koennen, vieleicht ist seine Stufe derzeit
2010-08-08 10:06:34 +02:00
* nicht ausreichend oder die Komponenten fehlen.
*/
bool knowsspell(const region * r, const unit * u, const spell * sp)
2010-08-08 10:06:34 +02:00
{
UNUSED_ARG(r);
assert(sp);
2014-08-08 01:03:46 +02:00
/* steht der Spruch in der Spruchliste? */
return u_hasspell(u, sp) != 0;
2010-08-08 10:06:34 +02:00
}
/* Um einen Spruch zu beherrschen, muss der Magier die Stufe des
* Spruchs besitzen, nicht nur wissen, das es ihn gibt (also den Spruch
* in seiner Spruchliste haben).
2019-02-08 11:37:32 +01:00
* Kosten fuer einen Spruch koennen Magiepunkte, Silber, Kraeuter
2010-08-08 10:06:34 +02:00
* und sonstige Gegenstaende sein.
*/
bool
2010-08-08 10:06:34 +02:00
cancast(unit * u, const spell * sp, int level, int range, struct order * ord)
{
int k;
resource *reslist = NULL;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (!knowsspell(u->region, u, sp)) {
/* Diesen Zauber kennt die Einheit nicht */
cmistake(u, ord, 173, MSG_MAGIC);
return false;
}
/* reicht die Stufe aus? */
if (effskill(u, SK_MAGIC, NULL) < level) {
2019-02-08 11:37:32 +01:00
/* die Einheit ist nicht erfahren genug fuer diesen Zauber */
2014-08-08 01:03:46 +02:00
cmistake(u, ord, 169, MSG_MAGIC);
return false;
}
for (k = 0; sp->components && sp->components[k].type; ++k) {
const struct spell_component *spc = sp->components + k;
if (spc->amount > 0) {
const resource_type *rtype = spc->type;
int itemhave, itemanz;
2014-08-08 01:03:46 +02:00
2019-02-08 11:37:32 +01:00
/* Die Kosten fuer Aura sind auch von der Zahl der bereits
* gezauberten Sprueche abhaengig */
itemanz = spellcost(u, spc) * range;
2014-08-08 01:03:46 +02:00
2019-02-08 11:37:32 +01:00
/* sind die Kosten stufenabhaengig, so muss itemanz noch mit dem
2014-08-08 01:03:46 +02:00
* level multipliziert werden */
switch (spc->cost) {
2014-08-08 01:03:46 +02:00
case SPC_LEVEL:
case SPC_LINEAR:
itemanz *= level;
break;
case SPC_FIX:
default:
break;
}
itemhave = get_pooled(u, rtype, GET_DEFAULT, itemanz);
if (itemhave < itemanz) {
resource *res = malloc(sizeof(resource));
assert(res);
2014-08-08 01:03:46 +02:00
res->number = itemanz - itemhave;
res->type = rtype;
res->next = reslist;
reslist = res;
}
}
}
if (reslist != NULL) {
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "missing_components_list",
"list", reslist));
while (reslist) {
resource *res = reslist->next;
free(reslist);
reslist = res;
}
2014-08-08 01:03:46 +02:00
return false;
}
return true;
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
/* generische Spruchstaerke,
*
* setzt sich derzeit aus der Stufe des Magiers, Magieturmeffekt,
* Spruchitems und Antimagiefeldern zusammen. Es koennen noch die
* Stufe des Spruchs und Magiekosten mit einfliessen.
*
2019-02-08 11:37:32 +01:00
* Die effektive Spruchstaerke und ihre Auswirkungen werden in der
2010-08-08 10:06:34 +02:00
* Spruchfunktionsroutine ermittelt.
*/
double
spellpower(region * r, unit * u, const spell * sp, int cast_level, struct order *ord)
2010-08-08 10:06:34 +02:00
{
double force = cast_level;
2016-10-03 20:27:36 +02:00
static int elf_power, config;
const struct resource_type *rtype;
2010-08-08 10:06:34 +02:00
if (sp == NULL) {
return 0;
2014-08-08 01:03:46 +02:00
}
else {
/* Bonus durch Magieturm und gesegneten Steinkreis */
struct building *b = inside_building(u);
const struct building_type *btype = building_is_active(b) ? b->type : NULL;
if (btype && btype->flags & BTF_MAGIC) ++force;
}
2010-08-08 10:06:34 +02:00
2016-10-03 20:27:36 +02:00
if (config_changed(&config)) {
elf_power = config_get_int("rules.magic.elfpower", 0);
}
if (elf_power) {
static int rc_cache;
static const race *rc_elf;
if (rc_changed(&rc_cache)) {
rc_elf = get_race(RC_ELF);
}
if (u_race(u) == rc_elf && r_isforest(r)) {
++force;
}
}
rtype = rt_find("rop");
if (rtype && i_get(u->items, rtype->itype) > 0) {
++force;
}
2010-08-08 10:06:34 +02:00
2016-10-03 20:27:36 +02:00
if (r->attribs) {
curse *c;
/* Antimagie in der Zielregion */
c = get_curse(r->attribs, &ct_antimagiczone);
2016-10-03 20:27:36 +02:00
if (curse_active(c)) {
unit *mage = c->magician;
force -= curse_geteffect(c);
curse_changevigour(&r->attribs, c, -cast_level);
cmistake(u, ord, 185, MSG_MAGIC);
if (mage != NULL && mage->faction != NULL) {
if (force > 0) {
ADDMSG(&mage->faction->msgs, msg_message("reduce_spell",
"self mage region", mage, u, r));
}
else {
ADDMSG(&mage->faction->msgs, msg_message("block_spell",
"self mage region", mage, u, r));
}
2014-08-08 01:03:46 +02:00
}
}
2016-10-03 20:27:36 +02:00
/* Patzerfluch-Effekt: */
c = get_curse(r->attribs, &ct_fumble);
2016-10-03 20:27:36 +02:00
if (curse_active(c)) {
unit *mage = c->magician;
force -= curse_geteffect(c);
curse_changevigour(&u->attribs, c, -1);
cmistake(u, ord, 185, MSG_MAGIC);
if (mage != NULL && mage->faction != NULL) {
if (force > 0) {
ADDMSG(&mage->faction->msgs, msg_message("reduce_spell",
"self mage region", mage, u, r));
}
else {
ADDMSG(&mage->faction->msgs, msg_message("block_spell",
"self mage region", mage, u, r));
}
2014-08-08 01:03:46 +02:00
}
}
}
return (force > 0) ? (int)force : 0;
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
2019-02-08 11:37:32 +01:00
/* farcasting() == 1 -> gleiche Region, da man mit Null nicht vernuenfigt
2010-08-08 10:06:34 +02:00
* rechnen kann */
2011-03-07 08:02:35 +01:00
static int farcasting(unit * magician, region * r)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
int dist;
int mult;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (!r) {
return INT_MAX;
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
dist = koor_distance(r->x, r->y, magician->region->x, magician->region->y);
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (dist > 24)
return INT_MAX;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
mult = 1 << dist;
if (dist > 1) {
if (!path_exists(magician->region, r, dist * 2, allowed_fly))
mult = INT_MAX;
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
return mult;
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
/* Antimagie - Magieresistenz */
/* ------------------------------------------------------------- */
/* allgemeine Magieresistenz einer Einheit,
* reduziert magischen Schaden */
variant magic_resistance(unit * target)
2010-08-08 10:06:34 +02:00
{
attrib *a;
curse *c;
const resource_type *rtype;
const race *rc = u_race(target);
variant v, prob = rc_magres(rc);
const plane *pl = rplane(target->region);
bool good_resist = true;
bool bad_resist = true;
if (rc == get_race(RC_HIRNTOETER) && !pl) {
prob = frac_mul(prob, frac_make(1, 2));
}
assert(target->number > 0);
/* Magier haben einen Resistenzbonus vom Magietalent * 5% */
prob = frac_add(prob, frac_make(effskill(target, SK_MAGIC, NULL), 20));
/* Auswirkungen von Zaubern auf der Einheit */
c = get_curse(target->attribs, &ct_magicresistance);
if (c) {
/* TODO: legacy. magicresistance-effect is an integer-percentage stored in a double */
int effect = curse_geteffect_int(c) * get_cursedmen(target, c);
prob = frac_add(prob, frac_make(effect, 100));
}
2010-08-08 10:06:34 +02:00
/* Unicorn +10 */
rtype = get_resourcetype(R_UNICORN);
if (rtype) {
int n = i_get(target->items, rtype->itype);
if (n) {
prob = frac_add(prob, frac_make(n, target->number * 10));
}
}
2014-08-08 01:03:46 +02:00
/* Auswirkungen von Zaubern auf der Region */
a = a_find(target->region->attribs, &at_curse);
while (a && a->type == &at_curse) {
unit *mage;
c = (curse *)a->data.v;
mage = c->magician;
2014-08-08 01:03:46 +02:00
if (mage != NULL) {
if (good_resist && c->type == &ct_goodmagicresistancezone) {
if (alliedunit(mage, target->faction, HELP_GUARD)) {
/* TODO: legacy. magicresistance-effect is an integer-percentage stored in a double */
prob = frac_add(prob, frac_make(curse_geteffect_int(c), 100));
good_resist = false; /* only one effect per region */
}
2014-08-08 01:03:46 +02:00
}
else if (bad_resist && c->type == &ct_badmagicresistancezone) {
if (!alliedunit(mage, target->faction, HELP_GUARD)) {
/* TODO: legacy. magicresistance-effect is an integer-percentage stored in a double */
prob = frac_sub(prob, frac_make(curse_geteffect_int(c), 100));
bad_resist = false; /* only one effect per region */
}
}
2010-08-08 10:06:34 +02:00
}
a = a->next;
2010-08-08 10:06:34 +02:00
}
/* Bonus durch Artefakte */
/* TODO (noch gibs keine) */
2014-08-08 01:03:46 +02:00
2019-02-08 11:37:32 +01:00
/* Bonus durch Gebaeude */
{
struct building *b = inside_building(target);
const struct building_type *btype = building_is_active(b) ? b->type : NULL;
2014-08-08 01:03:46 +02:00
/* gesegneter Steinkreis gibt 30% dazu */
if (btype) {
/* TODO: legacy. building-bonus is an integer-percentage */
prob = frac_add(prob, frac_make(btype->magresbonus, 100));
}
}
/* resistance must never be more than 90% */
v = frac_make(9, 10);
if (frac_sign(frac_sub(prob, v)) > 0) { /* prob < 90% */
return v; /* at most 90% */
}
return prob;
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
2019-02-08 11:37:32 +01:00
/* Prueft, ob das Objekt dem Zauber widerstehen kann.
* Objekte koennen Regionen, Units, Gebaeude oder Schiffe sein.
2010-08-08 10:06:34 +02:00
* TYP_UNIT:
2019-02-08 11:37:32 +01:00
* Das hoechste Talent des Ziels ist sein 'Magieresistenz-Talent', Magier
* bekommen einen Bonus. Grundchance ist 50%, fuer jede Stufe
* Unterschied gibt es 5%, minimalchance ist 5% fuer jeden (5-95%)
2010-08-08 10:06:34 +02:00
* Scheitert der Spruch an der Magieresistenz, so gibt die Funktion
2019-02-08 11:37:32 +01:00
* true zurueck
2010-08-08 10:06:34 +02:00
*/
bool
2011-03-07 08:02:35 +01:00
target_resists_magic(unit * magician, void *obj, int objtyp, int t_bonus)
2010-08-08 10:06:34 +02:00
{
variant v02p, v98p, prob = frac_make(t_bonus, 100);
attrib *a = NULL;
2010-08-08 10:06:34 +02:00
if (magician == NULL || obj == NULL) {
2014-08-08 01:03:46 +02:00
return true;
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
switch (objtyp) {
case TYP_UNIT:
{
int at, pa = 0;
skill *sv;
unit *u = (unit *)obj;
2010-08-08 10:06:34 +02:00
at = effskill(magician, SK_MAGIC, NULL);
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) {
int sk = eff_skill(u, sv, NULL);
2014-08-08 01:03:46 +02:00
if (pa < sk)
pa = sk;
}
2011-03-07 08:02:35 +01:00
/* Contest, probability = 0.05 * (10 + pa - at); */
prob = frac_add(prob, frac_make(10 + pa - at, 20));
prob = frac_add(prob, magic_resistance((unit *)obj));
2014-08-08 01:03:46 +02:00
break;
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
case TYP_REGION:
/* Bonus durch Zauber
2014-08-08 01:03:46 +02:00
probability +=
0.01 * get_curseeffect(((region *)obj)->attribs, C_RESIST_MAGIC, 0); */
a = ((region *)obj)->attribs;
2014-08-08 01:03:46 +02:00
break;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
case TYP_BUILDING:
/* Bonus durch Zauber
2014-08-08 01:03:46 +02:00
probability +=
0.01 * get_curseeffect(((building *)obj)->attribs, C_RESIST_MAGIC, 0); */
a = ((building *)obj)->attribs;
/* Bonus durch Typ
probability += 0.01 * ((building *)obj)->type->magres; */
prob = frac_add(prob, ((building *)obj)->type->magres);
2014-08-08 01:03:46 +02:00
break;
2011-03-08 08:44:20 +01:00
2014-08-08 01:03:46 +02:00
case TYP_SHIP:
/* Bonus durch Zauber */
a = ((ship *)obj)->attribs;
2014-08-08 01:03:46 +02:00
break;
}
2010-08-08 10:06:34 +02:00
if (a) {
curse * c = get_curse(a, &ct_magicrunes);
int effect = curse_geteffect_int(c);
prob = frac_add(prob, frac_make(effect, 100));
}
/* ignore results < 2% and > 98% */
v02p = frac_make(1, 50);
v98p = frac_make(49, 50);
if (frac_sign(frac_sub(prob, v02p)) < 0) {
prob = v02p;
}
else if (frac_sign(frac_sub(prob, v98p)) > 0) {
prob = v98p;
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
/* gibt true, wenn die Zufallszahl kleiner als die chance ist und
2019-02-08 11:37:32 +01:00
* false, wenn sie gleich oder groesser ist, dh je groesser die
* Magieresistenz (chance) desto eher gibt die Funktion true zurueck */
return rng_int() % prob.sa[1] < prob.sa[0];
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
bool is_magic_resistant(unit * magician, unit * target, int resist_bonus)
2010-08-08 10:06:34 +02:00
{
return target_resists_magic(magician, target, TYP_UNIT,
2014-08-08 01:03:46 +02:00
resist_bonus);
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
/* Patzer */
/* ------------------------------------------------------------- */
/* allgemeine Zauberpatzer:
* Treten auf, wenn die Stufe des Magieres zu niedrig ist.
* Abhaengig von Staerke des Spruchs.
*
* Die Warscheinlichkeit reicht von 20% (beherrscht den Spruch gerade
* eben) bis zu etwa 1% bei doppelt so gut wie notwendig
*/
bool fumble(region * r, unit * u, const spell * sp, int cast_grade)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
/* X ergibt Zahl zwischen 1 und 0, je kleiner, desto besser der Magier.
* 0,5*40-20=0, dh wenn der Magier doppelt so gut ist, wie der Spruch
2019-02-08 11:37:32 +01:00
* benoetigt, gelingt er immer, ist er gleich gut, gelingt der Spruch mit
2014-08-08 01:03:46 +02:00
* 20% Warscheinlichkeit nicht
* */
2010-08-08 10:06:34 +02:00
int fumble_chance, rnd = 0;
int effsk = effskill(u, SK_MAGIC, r);
2014-08-08 01:03:46 +02:00
struct building *b = inside_building(u);
const struct building_type *btype = building_is_active(b) ? b->type : NULL;
int fumble_enabled = config_get_int("magic.fumble.enable", 1);
2014-08-08 01:03:46 +02:00
sc_mage * mage;
2011-03-07 08:02:35 +01:00
UNUSED_ARG(sp);
if (effsk <= 0 || !fumble_enabled) {
2014-08-08 01:03:46 +02:00
return false;
}
fumble_chance = (int)((cast_grade * 40.0 / (double)effsk) - 20.0);
if (btype) {
2014-08-08 01:03:46 +02:00
fumble_chance -= btype->fumblebonus;
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
/* CHAOSPATZERCHANCE 10 : +10% Chance zu Patzern */
mage = get_mage(u);
if (mage && mage->magietyp == M_DRAIG) {
2014-08-08 01:03:46 +02:00
fumble_chance += CHAOSPATZERCHANCE;
}
if (is_cursed(u->attribs, &ct_magicboost)) {
2014-08-08 01:03:46 +02:00
fumble_chance += CHAOSPATZERCHANCE;
}
if (is_cursed(u->attribs, &ct_fumble)) {
2014-08-08 01:03:46 +02:00
fumble_chance += CHAOSPATZERCHANCE;
}
2019-02-08 11:37:32 +01:00
/* wenn die Chance kleiner als 0 ist, koennen wir gleich false
* zurueckgeben */
2014-08-08 01:03:46 +02:00
if (fumble_chance <= 0) {
return false;
}
rnd = rng_int() % 100;
return (rnd <= fumble_chance);
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
2019-02-08 11:37:32 +01:00
/* Dummy-Zauberpatzer, Platzhalter fuer speziell auf die Sprueche
2010-08-08 10:06:34 +02:00
* zugeschnittene Patzer */
static void fumble_default(castorder * co)
2010-08-08 10:06:34 +02:00
{
unit *mage = co_get_magician(co);
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
cmistake(mage, co->order, 180, MSG_MAGIC);
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
return;
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
2019-02-08 11:37:32 +01:00
/* Die normalen Spruchkosten muessen immer bezahlt werden, hier noch
2010-08-08 10:06:34 +02:00
* alle weiteren Folgen eines Patzers
*/
2011-03-07 08:02:35 +01:00
static void do_fumble(castorder * co)
2010-08-08 10:06:34 +02:00
{
curse *c;
region *r = co_get_region(co);
unit *mage = co_get_magician(co);
unit *caster = co_get_caster(co);
const spell *sp = co->sp;
int level = co->level;
int duration;
double effect;
2016-09-22 15:51:11 +02:00
static int rc_cache;
fumble_f fun;
ADDMSG(&mage->faction->msgs,
msg_message("patzer", "unit region spell", mage, r, sp));
switch (rng_int() % 10) {
case 0:
/* wenn vorhanden spezieller Patzer, ansonsten nix */
fun = get_fumble(sp->sname);
if (fun) {
fun(co);
2014-08-08 01:03:46 +02:00
}
else {
fumble_default(co);
}
break;
2014-08-08 01:03:46 +02:00
case 1: /* toad */
2014-08-08 01:03:46 +02:00
{
/* one or two things will happen: the toad changes her race back,
* and may or may not get toadslime.
* The list of things to happen are attached to a timeout
* trigger and that's added to the triggerlit of the mage gone toad.
*/
static const race *rc_toad;
trigger *trestore = trigger_changerace(mage, u_race(mage), mage->irace);
2014-08-08 01:03:46 +02:00
if (chance(0.7)) {
const resource_type *rtype = rt_find("toadslime");
if (rtype) {
t_add(&trestore, trigger_giveitem(mage, rtype->itype, 1));
}
}
2014-08-08 01:03:46 +02:00
duration = rng_int() % level / 2;
if (duration < 2) duration = 2;
add_trigger(&mage->attribs, "timer", trigger_timeout(duration, trestore));
2016-09-22 15:51:11 +02:00
if (rc_changed(&rc_cache)) {
rc_toad = get_race(RC_TOAD);
}
u_setrace(mage, rc_toad);
mage->irace = NULL;
ADDMSG(&r->msgs, msg_message("patzer6", "unit region spell", mage, r, sp));
2014-08-08 01:03:46 +02:00
break;
}
/* fall-through is intentional! */
2014-08-08 01:03:46 +02:00
case 2:
/* temporary skill loss */
duration = rng_int() % level / 2;
if (duration < 2) duration = 2;
effect = level / -2.0;
c = create_curse(mage, &mage->attribs, &ct_skillmod, level,
2014-08-08 01:03:46 +02:00
duration, effect, 1);
c->data.i = SK_MAGIC;
ADDMSG(&mage->faction->msgs, msg_message("patzer2", "unit region", mage, r));
break;
case 3:
case 4:
2019-02-08 11:37:32 +01:00
/* Spruch schlaegt fehl, alle Magiepunkte weg */
set_spellpoints(mage, 0);
ADDMSG(&mage->faction->msgs, msg_message("patzer3", "unit region spell",
mage, r, sp));
break;
2014-08-08 01:03:46 +02:00
case 5:
case 6:
/* Spruch gelingt, aber alle Magiepunkte weg */
co->level = cast_spell(co);
set_spellpoints(mage, 0);
ADDMSG(&mage->faction->msgs, msg_message("patzer4", "unit region spell",
mage, r, sp));
break;
2014-08-08 01:03:46 +02:00
case 7:
case 8:
case 9:
default:
2019-02-08 11:37:32 +01:00
/* Spruch gelingt, alle nachfolgenden Sprueche werden 2^4 so teuer */
co->level = cast_spell(co);
ADDMSG(&mage->faction->msgs, msg_message("patzer5", "unit region spell",
mage, r, sp));
countspells(caster, 3);
2010-08-08 10:06:34 +02:00
}
}
/* ------------------------------------------------------------- */
/* Regeneration von Aura */
/* ------------------------------------------------------------- */
/* Ein Magier regeneriert pro Woche W(Stufe^1.5/2+1), mindestens 1
2019-02-08 11:37:32 +01:00
* Zwerge nur die Haelfte
2010-08-08 10:06:34 +02:00
*/
2011-03-07 08:02:35 +01:00
static double regeneration(unit * u)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
int sk;
double aura, d;
double potenz = 1.5;
double divisor = 2.0;
2010-08-08 10:06:34 +02:00
sk = effskill(u, SK_MAGIC, NULL);
2014-08-08 01:03:46 +02:00
/* Rassenbonus/-malus */
d = pow(sk, potenz) * u_race(u)->regaura / divisor;
d++;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
/* Einfluss von Artefakten */
/* TODO (noch gibs keine) */
2010-08-08 10:06:34 +02:00
2019-02-08 11:37:32 +01:00
/* Wuerfeln */
2014-08-08 01:03:46 +02:00
aura = (rng_double() * d + rng_double() * d) / 2 + 1;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
aura *= MagicRegeneration();
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
return aura;
2010-08-08 10:06:34 +02:00
}
void regenerate_aura(void)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
region *r;
unit *u;
int aura, auramax;
double reg_aura;
int regen;
double mod;
int regen_enabled = config_get_int("magic.regeneration.enable", 1);
2014-08-08 01:03:46 +02:00
if (!regen_enabled) return;
for (r = regions; r; r = r->next) {
for (u = r->units; u; u = u->next) {
if (u->number && u->attribs) {
sc_mage *m = get_mage(u);
if (m) {
aura = mage_get_spellpoints(m);
auramax = max_spellpoints(u, r);
if (aura < auramax) {
struct building *b = inside_building(u);
const struct building_type *btype = building_is_active(b) ? b->type : NULL;
reg_aura = regeneration(u);
/* Magierturm erhoeht die Regeneration um 75% */
/* Steinkreis erhoeht die Regeneration um 50% */
if (btype)
reg_aura *= btype->auraregen;
/* Bonus/Malus durch Zauber */
mod = get_curseeffect(u->attribs, &ct_auraboost);
if (mod > 0) {
reg_aura = (reg_aura * mod) / 100.0;
}
2014-08-08 01:03:46 +02:00
/* Einfluss von Artefakten */
/* TODO (noch gibs keine) */
2014-08-08 01:03:46 +02:00
/* maximal Differenz bis Maximale-Aura regenerieren
* mindestens 1 Aura pro Monat */
regen = (int)reg_aura;
reg_aura -= regen;
if (chance(reg_aura)) {
++regen;
}
if (regen < 1) regen = 1;
if (regen > auramax - aura) regen = auramax - aura;
2014-08-08 01:03:46 +02:00
aura += regen;
ADDMSG(&u->faction->msgs, msg_message("regenaura",
"unit region amount", u, r, regen));
}
if (aura > auramax) aura = auramax;
mage_set_spellpoints(m, aura);
2014-08-08 01:03:46 +02:00
}
}
}
}
2010-08-08 10:06:34 +02:00
}
static bool
2011-03-07 08:02:35 +01:00
verify_ship(region * r, unit * mage, const spell * sp, spllprm * spobj,
order * ord)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
ship *sh = findship(spobj->data.i);
2010-08-08 10:06:34 +02:00
if (sh != NULL && sh->region != r && (sp->sptyp & GLOBALTARGET) == 0) {
2014-08-08 01:03:46 +02:00
/* Burg muss in gleicher Region sein */
sh = NULL;
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (sh == NULL) {
/* Burg nicht gefunden */
spobj->flag = TARGET_NOTFOUND;
ADDMSG(&mage->faction->msgs, msg_message("spellshipnotfound",
"unit region command id", mage, mage->region, ord, spobj->data.i));
return false;
}
spobj->flag = 0;
spobj->data.sh = sh;
return true;
2010-08-08 10:06:34 +02:00
}
static bool
2011-03-07 08:02:35 +01:00
verify_building(region * r, unit * mage, const spell * sp, spllprm * spobj,
order * ord)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
building *b = findbuilding(spobj->data.i);
2010-08-08 10:06:34 +02:00
if (b != NULL && b->region != r && (sp->sptyp & GLOBALTARGET) == 0) {
2014-08-08 01:03:46 +02:00
/* Burg muss in gleicher Region sein */
b = NULL;
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (b == NULL) {
/* Burg nicht gefunden */
spobj->flag = TARGET_NOTFOUND;
ADDMSG(&mage->faction->msgs, msg_message("spellbuildingnotfound",
"unit region command id", mage, mage->region, ord, spobj->data.i));
return false;
}
spobj->flag = 0;
spobj->data.b = b;
return true;
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
message *msg_unitnotfound(const struct unit * mage, struct order * ord,
2014-08-08 01:03:46 +02:00
const struct spllprm * spobj)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
/* Einheit nicht gefunden */
char tbuf[20];
const char *uid = 0;
2011-03-07 08:02:35 +01:00
if (spobj->typ == SPP_TEMP) {
2014-08-08 01:03:46 +02:00
sprintf(tbuf, "%s %s", LOC(mage->faction->locale,
parameters[P_TEMP]), itoa36(spobj->data.i));
uid = tbuf;
}
else if (spobj->typ == SPP_UNIT) {
uid = itoa36(spobj->data.i);
}
assert(uid);
2014-08-08 01:03:46 +02:00
return msg_message("unitnotfound_id",
"unit region command id", mage, mage->region, ord, uid);
2010-08-08 10:06:34 +02:00
}
static bool
2011-03-07 08:02:35 +01:00
verify_unit(region * r, unit * mage, const spell * sp, spllprm * spobj,
order * ord)
2014-08-08 01:03:46 +02:00
{
unit *u = NULL;
switch (spobj->typ) {
case SPP_UNIT:
u = findunit(spobj->data.i);
break;
case SPP_TEMP:
u = findnewunit(r, mage->faction, spobj->data.i);
if (u == NULL)
u = findnewunit(mage->region, mage->faction, spobj->data.i);
break;
default:
assert(!"shouldn't happen, this");
}
if (u != NULL) {
if (u->region == r) {
2014-08-08 01:03:46 +02:00
if (!cansee(mage->faction, r, u, 0) && !ucontact(u, mage)) {
u = NULL;
}
}
else if ((sp->sptyp & GLOBALTARGET) == 0) {
u = NULL;
}
2014-08-08 01:03:46 +02:00
}
if (u == NULL) {
/* Einheit nicht gefunden */
spobj->flag = TARGET_NOTFOUND;
ADDMSG(&mage->faction->msgs, msg_unitnotfound(mage, ord, spobj));
return false;
}
/* Einheit wurde gefunden, pointer setzen */
spobj->flag = 0;
spobj->data.u = u;
return true;
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
/* Zuerst wird versucht alle noch nicht gefundenen Objekte zu finden
2019-02-08 11:37:32 +01:00
* oder zu pruefen, ob das gefundene Objekt wirklich haette gefunden
* werden duerfen (nicht alle Zauber wirken global). Dabei zaehlen wir die
2010-08-08 10:06:34 +02:00
* Misserfolge (failed).
* Dann folgen die Tests der gefundenen Objekte auf Magieresistenz und
2019-02-08 11:37:32 +01:00
* Sichtbarkeit. Dabei zaehlen wir die magieresistenten (resists)
2010-08-08 10:06:34 +02:00
* Objekte. Alle anderen werten wir als Erfolge (success) */
2019-02-08 11:37:32 +01:00
/* gibt bei Misserfolg 0 zurueck, bei Magieresistenz zumindeste eines
* Objektes 1 und bei Erfolg auf ganzer Linie 2 */
2010-08-08 10:06:34 +02:00
static void
2011-03-07 08:02:35 +01:00
verify_targets(castorder * co, int *invalid, int *resist, int *success)
2010-08-08 10:06:34 +02:00
{
unit *mage = co_get_magician(co);
2014-08-08 01:03:46 +02:00
const spell *sp = co->sp;
region *target_r = co_get_region(co);
spellparameter *sa = co->par;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
*invalid = 0;
*resist = 0;
*success = 0;
if (sa && sa->length) {
2018-02-11 15:57:31 +01:00
int i;
2014-08-08 01:03:46 +02:00
/* zuerst versuchen wir vorher nicht gefundene Objekte zu finden.
* Wurde ein Objekt durch globalsuche gefunden, obwohl der Zauber
2019-02-08 11:37:32 +01:00
* gar nicht global haette suchen duerften, setzen wir das Objekt
* zurueck. */
2014-08-08 01:03:46 +02:00
for (i = 0; i < sa->length; i++) {
spllprm *spobj = sa->param[i];
switch (spobj->typ) {
case SPP_TEMP:
case SPP_UNIT:
if (!verify_unit(target_r, mage, sp, spobj, co->order))
++ * invalid;
break;
case SPP_BUILDING:
if (!verify_building(target_r, mage, sp, spobj, co->order))
++ * invalid;
break;
case SPP_SHIP:
if (!verify_ship(target_r, mage, sp, spobj, co->order))
++ * invalid;
break;
default:
break;
}
2011-03-08 08:44:20 +01:00
}
2014-08-08 01:03:46 +02:00
/* Nun folgen die Tests auf cansee und Magieresistenz */
for (i = 0; i < sa->length; i++) {
spllprm *spobj = sa->param[i];
unit *u;
building *b;
ship *sh;
region *tr;
if (spobj->flag == TARGET_NOTFOUND)
continue;
switch (spobj->typ) {
case SPP_TEMP:
case SPP_UNIT:
u = spobj->data.u;
if ((sp->sptyp & TESTRESISTANCE)
&& target_resists_magic(mage, u, TYP_UNIT, 0)) {
/* Fehlermeldung */
spobj->data.i = u->no;
spobj->flag = TARGET_RESISTS;
++*resist;
ADDMSG(&mage->faction->msgs, msg_message("spellunitresists",
"unit region command target", mage, mage->region, co->order, u));
break;
}
/* TODO: Test auf Parteieigenschaft Magieresistsenz */
++*success;
break;
case SPP_BUILDING:
b = spobj->data.b;
if ((sp->sptyp & TESTRESISTANCE)
&& target_resists_magic(mage, b, TYP_BUILDING, 0)) { /* Fehlermeldung */
spobj->data.i = b->no;
spobj->flag = TARGET_RESISTS;
++*resist;
ADDMSG(&mage->faction->msgs, msg_message("spellbuildingresists",
"unit region command id",
mage, mage->region, co->order, spobj->data.i));
break;
}
++*success;
break;
case SPP_SHIP:
sh = spobj->data.sh;
if ((sp->sptyp & TESTRESISTANCE)
&& target_resists_magic(mage, sh, TYP_SHIP, 0)) { /* Fehlermeldung */
spobj->data.i = sh->no;
spobj->flag = TARGET_RESISTS;
++*resist;
ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order,
"spellshipresists", "ship", sh));
break;
}
++*success;
break;
case SPP_REGION:
/* haben wir ein Regionsobjekt, dann wird auch dieses und
2019-02-08 11:37:32 +01:00
nicht target_r ueberprueft. */
2014-08-08 01:03:46 +02:00
tr = spobj->data.r;
if ((sp->sptyp & TESTRESISTANCE)
&& target_resists_magic(mage, tr, TYP_REGION, 0)) { /* Fehlermeldung */
spobj->flag = TARGET_RESISTS;
++*resist;
ADDMSG(&mage->faction->msgs, msg_message("spellregionresists",
"unit region command", mage, mage->region, co->order));
break;
}
++*success;
break;
case SPP_INT:
case SPP_STRING:
++*success;
break;
default:
break;
}
}
}
else {
/* der Zauber hat keine expliziten Parameter/Ziele, es kann sich
* aber um einen Regionszauber handeln. Wenn notwendig hier die
2019-02-08 11:37:32 +01:00
* Magieresistenz der Region pruefen. */
2014-08-08 01:03:46 +02:00
if ((sp->sptyp & REGIONSPELL)) {
/* Zielobjekt Region anlegen */
spllprm *spobj = (spllprm *)malloc(sizeof(spllprm));
2018-12-15 20:01:51 +01:00
if (!spobj) abort();
2014-08-08 01:03:46 +02:00
spobj->flag = 0;
spobj->typ = SPP_REGION;
spobj->data.r = target_r;
sa = calloc(1, sizeof(spellparameter));
2018-12-15 20:01:51 +01:00
if (!sa) abort();
2014-08-08 01:03:46 +02:00
sa->length = 1;
sa->param = calloc(sa->length, sizeof(spllprm *));
sa->param[0] = spobj;
co->par = sa;
if ((sp->sptyp & TESTRESISTANCE)) {
if (target_resists_magic(mage, target_r, TYP_REGION, 0)) {
/* Fehlermeldung */
ADDMSG(&mage->faction->msgs, msg_message("spellregionresists",
"unit region command", mage, mage->region, co->order));
spobj->flag = TARGET_RESISTS;
++*resist;
}
else {
++*success;
}
}
else {
++*success;
}
}
else {
++*success;
}
}
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
2019-02-08 11:37:32 +01:00
/* Hilfsstrukturen fuer ZAUBERE */
2010-08-08 10:06:34 +02:00
/* ------------------------------------------------------------- */
2011-03-07 08:02:35 +01:00
static void free_spellparameter(spellparameter * pa)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
int i;
2010-08-08 10:06:34 +02:00
assert(pa->param);
2010-08-08 10:06:34 +02:00
for (i = 0; i < pa->length; i++) {
assert(pa->param[i]);
2014-08-08 01:03:46 +02:00
switch (pa->param[i]->typ) {
case SPP_STRING:
free(pa->param[i]->data.s);
break;
default:
break;
}
free(pa->param[i]);
2010-08-08 10:06:34 +02:00
}
free(pa->param);
2014-08-08 01:03:46 +02:00
free(pa);
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
static int addparam_string(const char *const param[], spllprm ** spobjp)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
spllprm *spobj = *spobjp = malloc(sizeof(spllprm));
assert(param[0]);
2010-08-08 10:06:34 +02:00
2018-12-15 20:01:51 +01:00
if (!spobj) abort();
2014-08-08 01:03:46 +02:00
spobj->flag = 0;
spobj->typ = SPP_STRING;
spobj->data.xs = str_strdup(param[0]);
2014-08-08 01:03:46 +02:00
return 1;
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
static int addparam_int(const char *const param[], spllprm ** spobjp)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
spllprm *spobj = *spobjp = malloc(sizeof(spllprm));
assert(param[0]);
2010-08-08 10:06:34 +02:00
2018-12-15 20:01:51 +01:00
if (!spobj) abort();
2014-08-08 01:03:46 +02:00
spobj->flag = 0;
spobj->typ = SPP_INT;
spobj->data.i = atoi((char *)param[0]);
return 1;
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
static int addparam_ship(const char *const param[], spllprm ** spobjp)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
spllprm *spobj = *spobjp = malloc(sizeof(spllprm));
int id = atoi36((const char *)param[0]);
2010-08-08 10:06:34 +02:00
2018-12-15 20:01:51 +01:00
if (!spobj) abort();
2014-08-08 01:03:46 +02:00
spobj->flag = 0;
spobj->typ = SPP_SHIP;
spobj->data.i = id;
return 1;
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
static int addparam_building(const char *const param[], spllprm ** spobjp)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
spllprm *spobj = *spobjp = malloc(sizeof(spllprm));
int id = atoi36((const char *)param[0]);
2011-03-07 08:02:35 +01:00
2018-12-15 20:01:51 +01:00
if (!spobj) abort();
2014-08-08 01:03:46 +02:00
spobj->flag = 0;
spobj->typ = SPP_BUILDING;
spobj->data.i = id;
return 1;
2010-08-08 10:06:34 +02:00
}
static int
2011-03-07 08:02:35 +01:00
addparam_region(const char *const param[], spllprm ** spobjp, const unit * u,
order * ord, message **err)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
assert(param[0]);
if (param[1] == NULL) {
2014-08-08 01:03:46 +02:00
/* Fehler: Zielregion vergessen */
*err = msg_error(u, ord, 194);
2014-08-08 01:03:46 +02:00
return -1;
}
else {
plane *pl = getplanebyid(0);
2014-08-08 01:03:46 +02:00
int tx = atoi((const char *)param[0]), ty = atoi((const char *)param[1]);
int x = rel_to_abs(pl, u->faction, tx, 0);
int y = rel_to_abs(pl, u->faction, ty, 1);
region *rt;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
pnormalize(&x, &y, pl);
rt = findregion(x, y);
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (rt != NULL) {
spllprm *spobj = *spobjp = (spllprm *)malloc(sizeof(spllprm));
2010-08-08 10:06:34 +02:00
2018-12-15 20:01:51 +01:00
if (!spobj) abort();
2014-08-08 01:03:46 +02:00
spobj->flag = 0;
spobj->typ = SPP_REGION;
spobj->data.r = rt;
}
else {
/* Fehler: Zielregion vergessen */
*err = msg_error(u, ord, 194);
2014-08-08 01:03:46 +02:00
return -1;
}
return 2;
2010-08-08 10:06:34 +02:00
}
}
static int
2011-03-07 08:02:35 +01:00
addparam_unit(const char *const param[], spllprm ** spobjp, const unit * u,
order * ord, message **err)
2014-08-08 01:03:46 +02:00
{
spllprm *spobj;
int i = 0;
sppobj_t otype = SPP_UNIT;
*spobjp = NULL;
if (isparam(param[0], u->faction->locale, P_TEMP)) {
if (param[1] == NULL) {
/* Fehler: Ziel vergessen */
*err = msg_error(u, ord, 203);
2014-08-08 01:03:46 +02:00
return -1;
}
++i;
otype = SPP_TEMP;
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
spobj = *spobjp = malloc(sizeof(spllprm));
2018-12-15 20:01:51 +01:00
if (!spobj) abort();
2014-08-08 01:03:46 +02:00
spobj->flag = 0;
spobj->typ = otype;
spobj->data.i = atoi36((const char *)param[i]);
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
return i + 1;
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
static spellparameter *add_spellparameter(region * target_r, unit * u,
2014-08-08 01:03:46 +02:00
const char *syntax, const char *const param[], int size, struct order *ord)
{
struct message *err = NULL;
2014-08-08 01:03:46 +02:00
int i = 0;
int p = 0;
const char *c;
spellparameter *par;
int minlen = 0;
for (c = syntax; *c != 0; ++c) {
/* this makes sure that:
* minlen("kc?") = 0
* minlen("kc+") = 1
* minlen("cccc+c?") = 4
*/
if (*c == '?')
--minlen;
else if (*c != '+' && *c != 'k')
++minlen;
}
c = syntax;
/* mindestens ein Ziel (Ziellose Zauber werden nicht
* geparst) */
if (minlen && size == 0) {
/* Fehler: Ziel vergessen */
cmistake(u, ord, 203, MSG_MAGIC);
return 0;
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
par = malloc(sizeof(spellparameter));
2018-12-15 20:01:51 +01:00
if (!par) abort();
2014-08-08 01:03:46 +02:00
par->length = size;
if (!size) {
par->param = NULL;
return par;
}
par->param = malloc(size * sizeof(spllprm *));
2018-12-15 20:01:51 +01:00
if (!par->param) abort();
2014-08-08 01:03:46 +02:00
while (!err && *c && i < size && param[i] != NULL) {
2014-08-08 01:03:46 +02:00
spllprm *spobj = NULL;
int j = -1;
switch (*c) {
case '?':
/* tja. das sollte moeglichst nur am Ende passieren,
* weil sonst die kacke dampft. */
j = 0;
++c;
assert(*c == 0);
break;
case '+':
/* das vorhergehende Element kommt ein oder mehrmals vor, wir
2019-02-08 11:37:32 +01:00
* springen zum key zurueck */
2014-08-08 01:03:46 +02:00
j = 0;
--c;
break;
case 'u':
/* Parameter ist eine Einheit, evtl. TEMP */
j = addparam_unit(param + i, &spobj, u, ord, &err);
2014-08-08 01:03:46 +02:00
++c;
break;
case 'r':
/* Parameter sind zwei Regionskoordinaten innerhalb der "normalen" Plane */
2018-05-06 16:35:23 +02:00
if (i + 1 < size) {
j = addparam_region(param + i, &spobj, u, ord, &err);
2018-05-06 16:35:23 +02:00
}
2014-08-08 01:03:46 +02:00
++c;
break;
case 'b':
/* Parameter ist eine Burgnummer */
j = addparam_building(param + i, &spobj);
++c;
break;
case 's':
j = addparam_ship(param + i, &spobj);
++c;
break;
case 'c':
/* Text, wird im Spruch ausgewertet */
j = addparam_string(param + i, &spobj);
++c;
break;
case 'i': /* Zahl */
j = addparam_int(param + i, &spobj);
++c;
break;
case 'k':
++c;
switch (findparam_ex(param[i++], u->faction->locale)) {
case P_REGION:
spobj = (spllprm *)malloc(sizeof(spllprm));
2018-12-15 20:01:51 +01:00
if (!spobj) abort();
2014-08-08 01:03:46 +02:00
spobj->flag = 0;
spobj->typ = SPP_REGION;
spobj->data.r = target_r;
j = 0;
++c;
break;
case P_UNIT:
if (i < size) {
j = addparam_unit(param + i, &spobj, u, ord, &err);
2014-08-08 01:03:46 +02:00
++c;
}
break;
case P_BUILDING:
case P_GEBAEUDE:
if (i < size) {
j = addparam_building(param + i, &spobj);
++c;
}
break;
case P_SHIP:
if (i < size) {
j = addparam_ship(param + i, &spobj);
++c;
}
break;
default:
j = -1;
break;
}
break;
default:
j = -1;
break;
2011-03-08 08:44:20 +01:00
}
if (j < 0 && !err) {
/* syntax error */
err = msg_error(u, ord, 209);
}
2014-08-08 01:03:46 +02:00
else {
if (spobj != NULL)
par->param[p++] = spobj;
i += j;
2010-08-08 10:06:34 +02:00
}
}
/* im Endeffekt waren es evtl. nur p parameter (weniger weil TEMP nicht gilt) */
2014-08-08 01:03:46 +02:00
par->length = p;
if (!err && p < minlen) {
/* syntax error */
err = msg_error(u, ord, 209);
}
if (err) {
ADDMSG(&u->faction->msgs, err);
2014-08-08 01:03:46 +02:00
free_spellparameter(par);
par = NULL;
2014-08-08 01:03:46 +02:00
}
return par;
2010-08-08 10:06:34 +02:00
}
struct unit * co_get_caster(const struct castorder * co) {
2014-08-08 01:03:46 +02:00
return co->_familiar ? co->_familiar : co->magician.u;
}
struct unit * co_get_magician(const struct castorder * co) {
return co->magician.u;
}
struct region * co_get_region(const struct castorder * co) {
2014-08-08 01:03:46 +02:00
return co->_rtarget;
}
2010-08-08 10:06:34 +02:00
castorder *create_castorder(castorder * co, unit *caster, unit * familiar, const spell * sp, region * r,
int lev, double force, int range, struct order * ord, spellparameter * p)
{
2014-08-08 01:03:46 +02:00
if (!co) co = (castorder*)calloc(1, sizeof(castorder));
2018-12-15 20:01:51 +01:00
if (!co) abort();
2014-08-08 01:03:46 +02:00
co->magician.u = caster;
co->_familiar = familiar;
co->sp = sp;
co->level = lev;
co->force = MagicPower(force);
co->_rtarget = r ? r : (familiar ? familiar->region : (caster ? caster->region : NULL));
2014-08-08 01:03:46 +02:00
co->distance = range;
co->order = copy_order(ord);
co->par = p;
2014-08-08 01:03:46 +02:00
return co;
}
void free_castorder(struct castorder *co)
{
2014-08-08 01:03:46 +02:00
if (co->par) free_spellparameter(co->par);
if (co->order) free_order(co->order);
}
2019-02-08 11:37:32 +01:00
/* Haenge c-order co an die letze c-order von cll an */
2011-03-07 08:02:35 +01:00
void add_castorder(spellrank * cll, castorder * co)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
if (cll->begin == NULL) {
cll->handle_end = &cll->begin;
2014-08-08 01:03:46 +02:00
}
2011-03-07 08:02:35 +01:00
*cll->handle_end = co;
cll->handle_end = &co->next;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
return;
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
void free_castorders(castorder * co)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
while (co) {
castorder *co2 = co;
2014-08-08 01:03:46 +02:00
co = co->next;
free_castorder(co2);
free(co2);
}
return;
2010-08-08 10:06:34 +02:00
}
bool is_familiar(const unit * u)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
attrib *a = a_find(u->attribs, &at_familiarmage);
2016-11-17 21:23:49 +01:00
return a != NULL;
2010-08-08 10:06:34 +02:00
}
static void
a_write_unit(const variant *var, const void *owner, struct storage *store)
2010-08-08 10:06:34 +02:00
{
unit *u = (unit *)var->v;
UNUSED_ARG(owner);
2014-08-08 01:03:46 +02:00
write_unit_reference(u, store);
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
static int sm_familiar(const unit * u, const region * r, skill_t sk, int value)
{ /* skillmod */
2014-08-08 01:03:46 +02:00
if (sk == SK_MAGIC)
return value;
else {
int mod;
unit *familiar = get_familiar(u);
if (familiar == NULL) {
/* the familiar is dead */
return value;
}
mod = effskill(familiar, sk, r) / 2;
2014-08-08 01:03:46 +02:00
if (r != familiar->region) {
mod /= distance(r, familiar->region);
}
return value + mod;
2010-08-08 10:06:34 +02:00
}
}
2017-08-31 21:19:25 +02:00
void set_familiar(unit * mage, unit * familiar)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
/* if the skill modifier for the mage does not yet exist, add it */
attrib *a = a_find(mage->attribs, &at_skillmod);
while (a && a->type == &at_skillmod) {
skillmod_data *smd = (skillmod_data *)a->data.v;
if (smd->special == sm_familiar)
break;
a = a->next;
}
if (a == NULL) {
a = make_skillmod(NOSKILL, sm_familiar, 0.0, 0);
a_add(&mage->attribs, a);
2014-08-08 01:03:46 +02:00
}
a = a_find(mage->attribs, &at_familiar);
if (a == NULL) {
a = a_add(&mage->attribs, a_new(&at_familiar));
a->data.v = familiar;
}
2017-10-05 22:13:39 +02:00
else {
2014-08-08 01:03:46 +02:00
assert(!a->data.v || a->data.v == familiar);
2017-10-05 22:13:39 +02:00
}
2014-08-08 01:03:46 +02:00
2017-10-05 22:13:39 +02:00
/* TODO: Diese Attribute beim Tod des Familiars entfernen: */
2014-08-08 01:03:46 +02:00
a = a_find(familiar->attribs, &at_familiarmage);
if (a == NULL) {
a = a_add(&familiar->attribs, a_new(&at_familiarmage));
a->data.v = mage;
}
2017-10-05 22:13:39 +02:00
else {
2014-08-08 01:03:46 +02:00
assert(!a->data.v || a->data.v == mage);
2017-10-05 22:13:39 +02:00
}
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
void remove_familiar(unit * mage)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
attrib *a = a_find(mage->attribs, &at_familiar);
attrib *an;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (a != NULL) {
a_remove(&mage->attribs, a);
}
a = a_find(mage->attribs, &at_skillmod);
while (a && a->type == &at_skillmod) {
skillmod_data *smd = (skillmod_data *)a->data.v;
2014-08-08 01:03:46 +02:00
an = a->next;
if (smd->special == sm_familiar) {
2014-08-08 01:03:46 +02:00
a_remove(&mage->attribs, a);
}
2014-08-08 01:03:46 +02:00
a = an;
}
2010-08-08 10:06:34 +02:00
}
static void equip_familiar(unit *fam) {
/* items, skills and spells: */
char eqname[64];
const race *rc = u_race(fam);
2014-08-08 01:03:46 +02:00
snprintf(eqname, sizeof(eqname), "fam_%s", rc->_name);
if (!equip_unit(fam, eqname)) {
log_debug("could not perform initialization for familiar %s.\n", rc->_name);
}
}
static int copy_spell_cb(spellbook_entry *sbe, void *udata) {
spellbook *sb = (spellbook *)udata;
spell * sp = spellref_get(&sbe->spref);
if (!spellbook_get(sb, sp)) {
spellbook_add(sb, sp, sbe->level);
}
return 0;
}
/**
* Entferne Magie-Attribut von Migranten, die keine Vertrauten sind.
*
* Einmalige Reparatur von Vertrauten (Bug 2585).
*/
void fix_fam_migrant(unit *u) {
if (!is_familiar(u)) {
a_removeall(&u->attribs, &at_mage);
}
}
/**
* Einheiten, die Vertraute sind, bekommen ihre fehlenden Zauber.
*
* Einmalige Reparatur von Vertrauten (Bugs 2451, 2517).
*/
void fix_fam_spells(unit *u) {
sc_mage *dmage;
unit *du = unit_create(0);
if (!is_familiar(u)) {
return;
}
u_setrace(du, u_race(u));
dmage = create_mage(du, M_GRAY);
equip_familiar(du);
if (dmage) {
sc_mage *mage = get_mage(u);
if (!mage) {
mage = create_mage(u, dmage->magietyp);
}
else if (dmage->magietyp != mage->magietyp) {
mage->magietyp = dmage->magietyp;
}
if (dmage->spellbook) {
if (!mage->spellbook) {
mage->spellbook = create_spellbook(NULL);
spellbook_foreach(dmage->spellbook, copy_spell_cb, mage->spellbook);
}
}
else {
if (mage->spellbook) {
spellbook_clear(mage->spellbook);
mage->spellbook = NULL;
}
}
}
free_unit(du);
}
void create_newfamiliar(unit * mage, unit * fam)
{
create_mage(fam, M_GRAY);
set_familiar(mage, fam);
equip_familiar(fam);
2014-08-08 01:03:46 +02:00
/* TODO: Diese Attribute beim Tod des Familiars entfernen: */
/* Wenn der Magier stirbt, dann auch der Vertraute */
add_trigger(&mage->attribs, "destroy", trigger_killunit(fam));
2014-08-08 01:03:46 +02:00
/* Wenn der Vertraute stirbt, dann bekommt der Magier einen Schock */
add_trigger(&fam->attribs, "destroy", trigger_shock(mage));
2010-08-08 10:06:34 +02:00
}
2017-09-21 16:26:53 +02:00
static void * resolve_familiar(int id, void *data) {
UNUSED_ARG(id);
2017-09-21 16:26:53 +02:00
if (data) {
unit *familiar = (unit *)data;
2014-08-08 01:03:46 +02:00
attrib *a = a_find(familiar->attribs, &at_familiarmage);
if (a != NULL && a->data.v) {
unit *mage = (unit *)a->data.v;
set_familiar(mage, familiar);
}
2010-08-08 10:06:34 +02:00
}
2017-09-21 16:26:53 +02:00
return data;
2010-08-08 10:06:34 +02:00
}
static int read_familiar(variant *var, void *owner, struct gamedata *data)
2010-08-08 10:06:34 +02:00
{
UNUSED_ARG(owner);
if (read_unit_reference(data, (unit **)&var->v, resolve_familiar) <= 0) {
2014-08-08 01:03:46 +02:00
return AT_READ_FAIL;
}
return AT_READ_OK;
2010-08-08 10:06:34 +02:00
}
/* clones */
2011-03-07 08:02:35 +01:00
void create_newclone(unit * mage, unit * clone)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
attrib *a;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
a = a_find(mage->attribs, &at_clone);
if (a == NULL) {
a = a_add(&mage->attribs, a_new(&at_clone));
a->data.v = clone;
}
else
assert(!a->data.v || a->data.v == clone);
/* TODO: Diese Attribute beim Tod des Klons entfernen: */
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
a = a_find(clone->attribs, &at_clonemage);
if (a == NULL) {
a = a_add(&clone->attribs, a_new(&at_clonemage));
a->data.v = mage;
}
else
assert(!a->data.v || a->data.v == mage);
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
/* Wenn der Magier stirbt, wird das in destroy_unit abgefangen.
* Kein Trigger, zu kompliziert. */
2010-08-08 10:06:34 +02:00
/* Wenn der Klon stirbt, dann bekommt der Magier einen Schock */
2014-08-08 01:03:46 +02:00
add_trigger(&clone->attribs, "destroy", trigger_clonedied(mage));
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
static void set_clone(unit * mage, unit * clone)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
attrib *a;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
a = a_find(mage->attribs, &at_clone);
if (a == NULL) {
a = a_add(&mage->attribs, a_new(&at_clone));
a->data.v = clone;
}
else
assert(!a->data.v || a->data.v == clone);
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
a = a_find(clone->attribs, &at_clonemage);
if (a == NULL) {
a = a_add(&clone->attribs, a_new(&at_clonemage));
a->data.v = mage;
}
else
assert(!a->data.v || a->data.v == mage);
2010-08-08 10:06:34 +02:00
}
2017-09-21 16:26:53 +02:00
static void * resolve_clone(int id, void *data) {
UNUSED_ARG(id);
2017-09-21 16:26:53 +02:00
if (data) {
unit *clone = (unit *)data;
2014-08-08 01:03:46 +02:00
attrib *a = a_find(clone->attribs, &at_clonemage);
if (a != NULL && a->data.v) {
unit *mage = (unit *)a->data.v;
set_clone(mage, clone);
}
2010-08-08 10:06:34 +02:00
}
2017-09-21 16:26:53 +02:00
return data;
2010-08-08 10:06:34 +02:00
}
static int read_clone(variant *var, void *owner, struct gamedata *data)
2010-08-08 10:06:34 +02:00
{
UNUSED_ARG(owner);
if (read_unit_reference(data, (unit **)&var->v, resolve_clone) <= 0) {
2014-08-08 01:03:46 +02:00
return AT_READ_FAIL;
}
return AT_READ_OK;
2010-08-08 10:06:34 +02:00
}
/* mages */
2017-09-21 16:26:53 +02:00
static void * resolve_mage(int id, void *data) {
UNUSED_ARG(id);
2017-09-21 16:26:53 +02:00
if (data) {
unit *mage = (unit *)data;
2014-08-08 01:03:46 +02:00
attrib *a = a_find(mage->attribs, &at_familiar);
if (a != NULL && a->data.v) {
unit *familiar = (unit *)a->data.v;
set_familiar(mage, familiar);
}
2010-08-08 10:06:34 +02:00
}
2017-09-21 16:26:53 +02:00
return data;
2010-08-08 10:06:34 +02:00
}
static int read_magician(variant *var, void *owner, struct gamedata *data)
2010-08-08 10:06:34 +02:00
{
UNUSED_ARG(owner);
if (read_unit_reference(data, (unit **)&var->v, resolve_mage) <= 0) {
2014-08-08 01:03:46 +02:00
return AT_READ_FAIL;
}
return AT_READ_OK;
2010-08-08 10:06:34 +02:00
}
static int age_unit(attrib * a, void *owner)
2010-08-08 10:06:34 +02:00
/* if unit is gone or dead, remove the attribute */
{
2014-08-08 01:03:46 +02:00
unit *u = (unit *)a->data.v;
UNUSED_ARG(owner);
2014-08-08 01:03:46 +02:00
return (u != NULL && u->number > 0) ? AT_AGE_KEEP : AT_AGE_REMOVE;
2010-08-08 10:06:34 +02:00
}
attrib_type at_familiarmage = {
2014-08-08 01:03:46 +02:00
"familiarmage",
NULL,
NULL,
age_unit,
a_write_unit,
read_magician,
NULL,
2014-08-08 01:03:46 +02:00
ATF_UNIQUE
2010-08-08 10:06:34 +02:00
};
attrib_type at_familiar = {
2014-08-08 01:03:46 +02:00
"familiar",
NULL,
NULL,
age_unit,
a_write_unit,
read_familiar,
NULL,
2014-08-08 01:03:46 +02:00
ATF_UNIQUE
2010-08-08 10:06:34 +02:00
};
attrib_type at_clonemage = {
2014-08-08 01:03:46 +02:00
"clonemage",
NULL,
NULL,
age_unit,
a_write_unit,
read_magician,
NULL,
2014-08-08 01:03:46 +02:00
ATF_UNIQUE
2010-08-08 10:06:34 +02:00
};
attrib_type at_clone = {
2014-08-08 01:03:46 +02:00
"clone",
NULL,
NULL,
age_unit,
a_write_unit,
read_clone,
NULL,
2014-08-08 01:03:46 +02:00
ATF_UNIQUE
2010-08-08 10:06:34 +02:00
};
2011-03-07 08:02:35 +01:00
unit *get_familiar(const unit * u)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
attrib *a = a_find(u->attribs, &at_familiar);
if (a != NULL) {
unit *uf = (unit *)a->data.v;
if (uf->number > 0)
return uf;
2014-08-08 01:03:46 +02:00
}
return NULL;
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
unit *get_familiar_mage(const unit * u)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
attrib *a = a_find(u->attribs, &at_familiarmage);
if (a != NULL) {
unit *um = (unit *)a->data.v;
if (um->number > 0)
return um;
2014-08-08 01:03:46 +02:00
}
return NULL;
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
unit *get_clone(const unit * u)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
attrib *a = a_find(u->attribs, &at_clone);
if (a != NULL) {
unit *uc = (unit *)a->data.v;
2018-05-06 15:50:32 +02:00
if (uc->number > 0) {
return uc;
2018-05-06 15:50:32 +02:00
}
2014-08-08 01:03:46 +02:00
}
return NULL;
2010-08-08 10:06:34 +02:00
}
static bool is_moving_ship(ship * sh)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
const unit *u = ship_owner(sh);
2010-08-08 10:06:34 +02:00
2018-05-06 15:50:32 +02:00
if (u) {
2014-08-08 01:03:46 +02:00
switch (getkeyword(u->thisorder)) {
case K_ROUTE:
case K_MOVE:
case K_FOLLOW:
return true;
default:
return false;
}
2018-05-06 15:50:32 +02:00
}
2014-08-08 01:03:46 +02:00
return false;
2010-08-08 10:06:34 +02:00
}
#define MAX_PARAMETERS 48
2011-03-07 08:02:35 +01:00
static castorder *cast_cmd(unit * u, order * ord)
2010-08-08 10:06:34 +02:00
{
char token[128];
2014-08-08 01:03:46 +02:00
region *r = u->region;
region *target_r = r;
int level, range;
unit *familiar = NULL;
const char *s;
spell *sp = 0;
plane *pl;
spellparameter *args = NULL;
unit * mage = u;
2014-08-08 01:03:46 +02:00
param_t param;
if (LongHunger(u)) {
cmistake(u, ord, 224, MSG_MAGIC);
return 0;
2010-08-08 10:06:34 +02:00
}
pl = getplane(r);
2014-08-08 01:03:46 +02:00
if (pl && fval(pl, PFL_NOMAGIC)) {
cmistake(u, ord, 269, MSG_MAGIC);
return 0;
2010-08-08 10:06:34 +02:00
}
level = effskill(u, SK_MAGIC, NULL);
2014-08-08 01:03:46 +02:00
2017-10-09 20:33:47 +02:00
init_order_depr(ord);
s = gettoken(token, sizeof(token));
2012-05-26 19:16:39 +02:00
param = findparam(s, u->faction->locale);
2019-02-08 11:37:32 +01:00
/* fuer Syntax ' STUFE x REGION y z ' */
2014-08-08 01:03:46 +02:00
if (param == P_LEVEL) {
int p = getint();
if (level > p) level = p;
2014-08-08 01:03:46 +02:00
if (level < 1) {
/* Fehler "Das macht wenig Sinn" */
syntax_error(u, ord);
2014-08-08 01:03:46 +02:00
return 0;
}
s = gettoken(token, sizeof(token));
2014-08-08 01:03:46 +02:00
param = findparam(s, u->faction->locale);
}
if (param == P_REGION) {
int t_x = getint();
int t_y = getint();
2014-08-08 01:03:46 +02:00
t_x = rel_to_abs(pl, u->faction, t_x, 0);
t_y = rel_to_abs(pl, u->faction, t_y, 1);
pnormalize(&t_x, &t_y, pl);
target_r = findregion(t_x, t_y);
if (!target_r) {
/* Fehler "Die Region konnte nicht verzaubert werden" */
ADDMSG(&u->faction->msgs, msg_message("spellregionresists",
"unit region command", u, u->region, ord));
return 0;
}
s = gettoken(token, sizeof(token));
2014-08-08 01:03:46 +02:00
param = findparam(s, u->faction->locale);
}
2019-02-08 11:37:32 +01:00
/* fuer Syntax ' REGION x y STUFE z '
* hier nach REGION nochmal auf STUFE pruefen */
2014-08-08 01:03:46 +02:00
if (param == P_LEVEL) {
int p = getint();
if (level > p) level = p;
2014-08-08 01:03:46 +02:00
if (level < 1) {
/* Fehler "Das macht wenig Sinn" */
syntax_error(u, ord);
2014-08-08 01:03:46 +02:00
return 0;
}
s = gettoken(token, sizeof(token));
2010-08-08 10:06:34 +02:00
}
if (!s || !s[0]) {
2014-08-08 01:03:46 +02:00
/* Fehler "Es wurde kein Zauber angegeben" */
cmistake(u, ord, 172, MSG_MAGIC);
2010-08-08 10:06:34 +02:00
return 0;
2014-08-08 01:03:46 +02:00
}
sp = unit_getspell(u, s, u->faction->locale);
2019-02-08 11:37:32 +01:00
/* Vertraute koennen auch Zauber sprechen, die sie selbst nicht
* koennen. unit_getspell findet aber nur jene Sprueche, die
2014-08-08 01:03:46 +02:00
* die Einheit beherrscht. */
if (!sp && is_familiar(u)) {
mage = get_familiar_mage(u);
if (mage) {
2014-08-08 01:03:46 +02:00
familiar = u;
sp = unit_getspell(mage, s, mage->faction->locale);
2014-08-08 01:03:46 +02:00
}
else {
/* somehow, this familiar has no mage! */
log_error("cast_cmd: familiar %s is without a mage?\n", unitname(u));
mage = u;
2014-08-08 01:03:46 +02:00
}
}
if (!sp) {
/* Fehler 'Spell not found' */
cmistake(u, ord, 173, MSG_MAGIC);
2010-08-08 10:06:34 +02:00
return 0;
2014-08-08 01:03:46 +02:00
}
/* um testen auf spruchnamen zu unterbinden sollte vor allen
* fehlermeldungen die anzeigen das der magier diesen Spruch
* nur in diese Situation nicht anwenden kann, noch eine
2019-02-08 11:37:32 +01:00
* einfache Sicherheitspruefung kommen */
2014-08-08 01:03:46 +02:00
if (!knowsspell(r, u, sp)) {
/* vorsicht! u kann der familiar sein */
if (!familiar) {
cmistake(u, ord, 173, MSG_MAGIC);
return 0;
}
}
if (sp->sptyp & ISCOMBATSPELL) {
/* Fehler: "Dieser Zauber ist nur im Kampf sinnvoll" */
cmistake(u, ord, 174, MSG_MAGIC);
2010-08-08 10:06:34 +02:00
return 0;
2014-08-08 01:03:46 +02:00
}
2019-02-08 11:37:32 +01:00
/* Auf dem Ozean Zaubern als quasi-langer Befehl koennen
2014-08-08 01:03:46 +02:00
* normalerweise nur Meermenschen, ausgenommen explizit als
2019-02-08 11:37:32 +01:00
* OCEANCASTABLE deklarierte Sprueche */
2014-08-08 01:03:46 +02:00
if (fval(r->terrain, SEA_REGION)) {
if (u_race(u) != get_race(RC_AQUARIAN)
&& !fval(u_race(u), RCF_SWIM)
&& !(sp->sptyp & OCEANCASTABLE)) {
/* Fehlermeldung */
ADDMSG(&u->faction->msgs, msg_message("spellfail_onocean",
"unit region command", u, u->region, ord));
return 0;
}
/* Auf bewegenden Schiffen kann man nur explizit als
* ONSHIPCAST deklarierte Zauber sprechen */
}
else if (u->ship) {
if (is_moving_ship(u->ship)) {
2014-08-08 01:03:46 +02:00
if (!(sp->sptyp & ONSHIPCAST)) {
/* Fehler: "Diesen Spruch kann man nicht auf einem sich
* bewegenden Schiff stehend zaubern" */
cmistake(u, ord, 175, MSG_MAGIC);
return 0;
}
}
}
2019-02-08 11:37:32 +01:00
/* Farcasting bei nicht farcastbaren Spruechen abfangen */
2014-08-08 01:03:46 +02:00
range = farcasting(u, target_r);
if (range > 1) {
if (!(sp->sptyp & FARCASTING)) {
/* Fehler "Diesen Spruch kann man nicht in die Ferne
* richten" */
cmistake(u, ord, 176, MSG_MAGIC);
return 0;
}
if (range > 1024) { /* (2^10) weiter als 10 Regionen entfernt */
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "spellfail::nocontact",
"target", target_r));
return 0;
}
}
2019-02-08 11:37:32 +01:00
/* Stufenangabe bei nicht Stufenvariierbaren Spruechen abfangen */
2014-08-08 01:03:46 +02:00
if (!(sp->sptyp & SPELLLEVEL)) {
int ilevel = effskill(u, SK_MAGIC, NULL);
2014-08-08 01:03:46 +02:00
if (ilevel != level) {
level = ilevel;
ADDMSG(&u->faction->msgs, msg_message("spellfail::nolevel",
"mage region command", u, u->region, ord));
}
}
/* Vertrautenmagie */
/* Kennt der Vertraute den Spruch, so zaubert er ganz normal.
* Ansonsten zaubert der Magier durch seinen Vertrauten, dh
* zahlt Komponenten und Aura. Dabei ist die maximale Stufe
* die des Vertrauten!
* Der Spruch wirkt dann auf die Region des Vertrauten und
* gilt nicht als Farcasting. */
if (familiar) {
if ((sp->sptyp & NOTFAMILIARCAST)) {
/* Fehler: "Diesen Spruch kann der Vertraute nicht zaubern" */
cmistake(u, ord, 177, MSG_MAGIC);
return 0;
}
if (mage != familiar) { /* Magier zaubert durch Vertrauten */
int sk;
2014-08-08 01:03:46 +02:00
if (range > 1) { /* Fehler! Versucht zu Farcasten */
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "familiar_farcast",
"mage", mage));
2014-08-08 01:03:46 +02:00
return 0;
}
sk = effskill(mage, SK_MAGIC, NULL);
if (distance(mage->region, r) > sk) {
2014-08-08 01:03:46 +02:00
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "familiar_toofar",
"mage", mage));
2014-08-08 01:03:46 +02:00
return 0;
}
2019-02-08 11:37:32 +01:00
/* mage auf magier setzen, level anpassen, range fuer Erhoehung
* der Spruchkosten nutzen */
2014-08-08 01:03:46 +02:00
range *= 2;
sk /= 2;
if (level > sk) level = sk;
2014-08-08 01:03:46 +02:00
}
}
/* Weitere Argumente zusammenbasteln */
if (sp->parameter) {
char *params[MAX_PARAMETERS];
2018-05-06 19:27:22 +02:00
int i, p;
for (p = 0; p != MAX_PARAMETERS; ++p) {
s = gettoken(token, sizeof(token));
if (!s || *s == 0) {
2014-08-08 01:03:46 +02:00
break;
}
2018-05-06 19:27:22 +02:00
params[p] = str_strdup(s);
}
if (p == MAX_PARAMETERS) {
log_error("%s: MAX_PARAMETERS (%d) too small to CAST %s, parsing stopped early.",
unitname(u), MAX_PARAMETERS, sp->sname);
2014-08-08 01:03:46 +02:00
}
args =
add_spellparameter(target_r, mage, sp->parameter,
2014-08-08 01:03:46 +02:00
(const char *const *)params, p, ord);
for (i = 0; i != p; ++i) {
free(params[i]);
}
2014-08-08 01:03:46 +02:00
if (args == NULL) {
/* Syntax war falsch */
return 0;
}
}
return create_castorder(0, mage, familiar, sp, target_r, level, 0, range, ord,
2014-08-08 01:03:46 +02:00
args);
2010-08-08 10:06:34 +02:00
}
/* ------------------------------------------------------------- */
/* Damit man keine Rituale in fremden Gebiet machen kann, diese vor
* Bewegung zaubern. Magier sind also in einem fremden Gebiet eine Runde
2019-02-08 11:37:32 +01:00
* lang verletzlich, da sie es betreten, und angegriffen werden koennen,
* bevor sie ein Ritual machen koennen.
2010-08-08 10:06:34 +02:00
*
* Syntax: ZAUBER [REGION X Y] [STUFE <stufe>] "Spruchname" [Einheit-1
* Einheit-2 ..]
*
2019-02-08 11:37:32 +01:00
* Nach Prioritaet geordnet die Zauber global auswerten.
2010-08-08 10:06:34 +02:00
*
2019-02-08 11:37:32 +01:00
* Die Kosten fuer Farcasting multiplizieren sich mit der Entfernung,
2010-08-08 10:06:34 +02:00
* cast_level gibt die virtuelle Stufe an, die den durch das Farcasten
* entstandenen Spruchkosten entspricht. Sind die Spruchkosten nicht
2019-02-08 11:37:32 +01:00
* levelabhaengig, so sind die Kosten nur von der Entfernung bestimmt,
* die Staerke/Level durch den realen Skill des Magiers
2010-08-08 10:06:34 +02:00
*/
2011-03-07 08:02:35 +01:00
void magic(void)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
region *r;
int rank;
spellrank spellranks[MAX_SPELLRANK];
2016-10-03 20:27:36 +02:00
const race *rc_insect = get_race(RC_INSECT);
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
memset(spellranks, 0, sizeof(spellranks));
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
for (r = regions; r; r = r->next) {
unit *u;
for (u = r->units; u; u = u->next) {
order *ord;
2017-05-24 08:52:19 +02:00
if (u->number <= 0)
2014-08-08 01:03:46 +02:00
continue;
2016-10-03 20:27:36 +02:00
if (u_race(u) == rc_insect && r_insectstalled(r) &&
!is_cursed(u->attribs, &ct_insectfur))
2014-08-08 01:03:46 +02:00
continue;
if (fval(u, UFL_WERE | UFL_LONGACTION)) {
continue;
}
2014-08-08 01:03:46 +02:00
for (ord = u->orders; ord; ord = ord->next) {
if (getkeyword(ord) == K_CAST) {
castorder *co = cast_cmd(u, ord);
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
if (co) {
const spell *sp = co->sp;
add_castorder(&spellranks[sp->rank], co);
2014-08-08 01:03:46 +02:00
}
}
}
}
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
2019-02-08 11:37:32 +01:00
/* Da sich die Aura und Komponenten in der Zwischenzeit veraendert
* haben koennen und sich durch vorherige Sprueche das Zaubern
* erschwert haben kann, muss beim zaubern erneut geprueft werden, ob der
* Spruch ueberhaupt gezaubert werden kann.
* (level) die effektive Staerke des Spruchs (= Stufe, auf der der
2014-08-08 01:03:46 +02:00
* Spruch gezaubert wird) */
for (rank = 0; rank < MAX_SPELLRANK; rank++) {
castorder *co;
2014-08-08 01:03:46 +02:00
for (co = spellranks[rank].begin; co; co = co->next) {
order *ord = co->order;
int invalid, resist, success, cast_level = co->level;
bool fumbled = false;
unit *mage = co_get_magician(co);
unit *caster = co_get_caster(co);
2014-08-08 01:03:46 +02:00
const spell *sp = co->sp;
region *target_r = co_get_region(co);
/* reichen die Komponenten nicht, wird der Level reduziert. */
co->level = eff_spelllevel(mage, caster, sp, cast_level, co->distance);
2014-08-08 01:03:46 +02:00
if (co->level < 1) {
/* Fehlermeldung mit Komponenten generieren */
cancast(mage, sp, cast_level, co->distance, ord);
2014-08-08 01:03:46 +02:00
continue;
}
if (cast_level > co->level) {
2019-02-08 11:37:32 +01:00
/* Sprueche mit Fixkosten werden immer auf Stufe des Spruchs
* gezaubert, co->level ist aber defaultmaessig Stufe des Magiers */
2014-08-08 01:03:46 +02:00
if (spl_costtyp(sp) != SPC_FIX) {
ADDMSG(&mage->faction->msgs, msg_message("missing_components",
"unit spell level", mage, sp, cast_level));
2014-08-08 01:03:46 +02:00
}
}
2019-02-08 11:37:32 +01:00
/* Pruefen, ob die realen Kosten fuer die gewuenschten Stufe bezahlt
* werden koennen */
if (!cancast(mage, sp, co->level, co->distance, ord)) {
2014-08-08 01:03:46 +02:00
/* die Fehlermeldung wird in cancast generiert */
continue;
}
co->force = MagicPower(spellpower(target_r, mage, sp, co->level, ord));
2019-02-08 11:37:32 +01:00
/* die Staerke kann durch Antimagie auf 0 sinken */
2014-08-08 01:03:46 +02:00
if (co->force <= 0) {
co->force = 0;
ADDMSG(&mage->faction->msgs, msg_message("missing_force",
"unit spell level", mage, sp, co->level));
2014-08-08 01:03:46 +02:00
}
2019-02-08 11:37:32 +01:00
/* Ziele auf Existenz pruefen und Magieresistenz feststellen. Wurde
2014-08-08 01:03:46 +02:00
* kein Ziel gefunden, so ist verify_targets=0. Scheitert der
* Spruch an der Magieresistenz, so ist verify_targets = 1, bei
* Erfolg auf ganzer Linie ist verify_targets= 2
*/
verify_targets(co, &invalid, &resist, &success);
if (success + resist == 0) {
/* kein Ziel gefunden, Fehlermeldungen sind in verify_targets */
2019-02-08 11:37:32 +01:00
/* keine kosten fuer den zauber */
continue; /* aeussere Schleife, naechster Zauberer */
2014-08-08 01:03:46 +02:00
}
else if (co->force > 0 && resist > 0) {
/* einige oder alle Ziele waren magieresistent */
if (success == 0) {
co->force = 0;
/* zwar wurde mindestens ein Ziel gefunden, das widerstand
* jedoch dem Zauber. Kosten abziehen und abbrechen. */
ADDMSG(&mage->faction->msgs, msg_message("spell_resist",
"unit region spell", mage, r, sp));
2014-08-08 01:03:46 +02:00
}
}
2019-02-08 11:37:32 +01:00
/* Auch fuer Patzer gibt es Erfahrung, muessen die Spruchkosten
* bezahlt werden und die nachfolgenden Sprueche werden teurer */
2014-08-08 01:03:46 +02:00
if (co->force > 0) {
if (fumble(target_r, mage, sp, co->level)) {
2014-08-08 01:03:46 +02:00
/* zuerst bezahlen, dann evt in do_fumble alle Aura verlieren */
fumbled = true;
}
else {
co->level = cast_spell(co);
2014-08-08 01:03:46 +02:00
if (co->level <= 0) {
2019-02-08 11:37:32 +01:00
/* Kosten nur fuer real benoetige Stufe berechnen */
2014-08-08 01:03:46 +02:00
continue;
}
}
}
2019-02-08 11:37:32 +01:00
/* erst bezahlen, dann Kostenzaehler erhoehen */
2014-08-08 01:03:46 +02:00
if (co->level > 0) {
pay_spell(mage, caster, sp, co->level, co->distance);
2014-08-08 01:03:46 +02:00
}
if (fumbled) {
do_fumble(co);
}
countspells(caster, 1);
2014-08-08 01:03:46 +02:00
}
}
/* Sind alle Zauber gesprochen gibts Erfahrung */
for (r = regions; r; r = r->next) {
unit *u;
for (u = r->units; u; u = u->next) {
if (is_mage(u) && spellcount(u) > 0) {
2014-08-08 01:03:46 +02:00
produceexp(u, SK_MAGIC, u->number);
/* Spruchlistenaktualisierung ist in Regeneration */
2014-08-08 01:03:46 +02:00
}
}
}
for (rank = 0; rank < MAX_SPELLRANK; rank++) {
free_castorders(spellranks[rank].begin);
}
remove_empty_units();
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
const char *spell_info(const spell * sp, const struct locale *lang)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
return LOC(lang, mkname("spellinfo", sp->sname));
2010-08-08 10:06:34 +02:00
}
/* TODO: should take the name, not the spell (spellref optimizations) */
2011-03-07 08:02:35 +01:00
const char *spell_name(const spell * sp, const struct locale *lang)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
return LOC(lang, mkname("spell", sp->sname));
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
const char *curse_name(const curse_type * ctype, const struct locale *lang)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
return LOC(lang, mkname("spell", ctype->cname));
2010-08-08 10:06:34 +02:00
}
static void select_spellbook(void **tokens, spellbook *sb, const struct locale * lang) {
selist * ql;
int qi;
assert(sb);
assert(lang);
for (qi = 0, ql = sb->spells; ql; selist_advance(&ql, &qi, 1)) {
spellbook_entry *sbe = (spellbook_entry *)selist_get(ql, qi);
const spell *sp = spellref_get(&sbe->spref);
const char *n = spell_name(sp, lang);
if (!n) {
log_error("no translation in locale %s for spell %s\n", locale_name(lang), sp->sname);
}
else {
variant token;
token.v = (void *)sp;
addtoken((struct tnode **)tokens, n, token);
}
}
}
2012-05-26 17:20:26 +02:00
spell *unit_getspell(struct unit *u, const char *name, const struct locale * lang)
{
spellbook *sb;
2014-08-08 01:03:46 +02:00
sb = unit_get_spellbook(u);
if (sb) {
void * tokens = NULL;
select_spellbook(&tokens, sb, lang);
if (tokens) {
variant token;
if (findtoken(tokens, name, &token) != E_TOK_NOMATCH) {
freetokens(tokens);
return (spell *)token.v;
}
2014-08-08 01:03:46 +02:00
freetokens(tokens);
}
}
return NULL;
}
int cast_spell(struct castorder *co)
{
const char *fname = co->sp->sname;
const char *hashpos = strchr(fname, '#');
char fbuf[64];
spell_f fun;
const spell *sp = co->sp;
if (hashpos != NULL) {
ptrdiff_t len = hashpos - fname;
assert(len < (ptrdiff_t) sizeof(fbuf));
memcpy(fbuf, fname, len);
fbuf[len] = '\0';
fname = fbuf;
}
fun = get_spellcast(sp->sname);
if (!fun) {
2017-06-11 14:47:33 +02:00
log_debug("no spell function for %s, try callback", sp->sname);
return callbacks.cast_spell(co, fname);
}
return fun(co);
}
2018-11-24 12:26:52 +01:00
const char *magic_name(magic_t mtype, const struct locale *lang)
{
return LOC(lang, mkname("school", magic_school[mtype]));
}
static critbit_tree cb_spellbooks;
2017-09-18 19:51:47 +02:00
#define SBNAMELEN 16
typedef struct sb_entry {
char key[SBNAMELEN];
spellbook *value;
} sb_entry;
spellbook * get_spellbook(const char * name)
{
2017-09-18 19:51:47 +02:00
size_t len = strlen(name);
const void * match;
2017-09-18 19:51:47 +02:00
if (len >= SBNAMELEN) {
log_error("spellbook name is longer than %d bytes: %s", SBNAMELEN - 1, name);
2017-09-18 19:51:47 +02:00
return NULL;
2014-08-08 01:03:46 +02:00
}
2017-09-18 19:51:47 +02:00
match = cb_find_str(&cb_spellbooks, name);
if (!match) {
sb_entry ent;
memset(ent.key, 0, SBNAMELEN);
memcpy(ent.key, name, len);
ent.value = create_spellbook(name);
if (cb_insert(&cb_spellbooks, &ent, sizeof(ent)) == CB_EXISTS) {
log_error("cb_insert failed although cb_find returned nothing for spellbook=%s", name);
assert(!"should not happen");
}
2017-09-18 19:51:47 +02:00
return ent.value;
2014-08-08 01:03:46 +02:00
}
2017-09-18 19:51:47 +02:00
return ((const sb_entry *)match)->value;
}
void free_spellbook(spellbook *sb) {
2014-12-31 01:38:49 +01:00
spellbook_clear(sb);
free(sb);
}
static int free_spellbook_cb(void *match, const void *key, size_t keylen, void *data) {
2017-09-18 19:51:47 +02:00
const sb_entry *ent = (const sb_entry *)match;
UNUSED_ARG(data);
UNUSED_ARG(keylen);
UNUSED_ARG(key);
2017-09-18 19:51:47 +02:00
free_spellbook(ent->value);
2014-12-31 01:38:49 +01:00
return 0;
}
void free_spellbooks(void)
{
2014-12-31 01:38:49 +01:00
cb_foreach(&cb_spellbooks, "", 0, free_spellbook_cb, NULL);
2014-08-08 01:03:46 +02:00
cb_clear(&cb_spellbooks);
}