From 932a6158372685dd17131128ead511a43391ca13 Mon Sep 17 00:00:00 2001 From: Enno Rehling Date: Wed, 27 Aug 2014 21:09:39 +0200 Subject: [PATCH] refactoring: special directions into their own file (vortex.c). moving spells.c out of spells. figured that movewhere should be in move.c --- src/CMakeLists.txt | 2 + src/bind_message.c | 2 +- src/direction.test.c | 29 +- src/helpers.c | 4 +- src/kernel/config.c | 35 - src/kernel/config.h | 9 - src/kernel/region.c | 200 -- src/kernel/region.h | 18 - src/kernel/xmlreader.c | 2 + src/main.c | 4 +- src/move.c | 65 + src/move.h | 79 +- src/report.c | 66 +- src/spells.c | 6876 +++++++++++++++++++++++++++++++++++++ src/{spells => }/spells.h | 0 src/spells/CMakeLists.txt | 1 - src/spells/spells.c | 6805 ------------------------------------ src/test_eressea.c | 11 +- src/vortex.c | 190 + src/vortex.h | 32 + src/vortex.test.c | 48 + 21 files changed, 7307 insertions(+), 7171 deletions(-) create mode 100644 src/spells.c rename src/{spells => }/spells.h (100%) delete mode 100644 src/spells/spells.c create mode 100644 src/vortex.c create mode 100644 src/vortex.h create mode 100644 src/vortex.test.c diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3b8bb4e27..338875049 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -51,9 +51,11 @@ ENDIF() set (ERESSEA_SRC move.c + spells.c battle.c alchemy.c stealth.c + vortex.c names.c reports.c eressea.c diff --git a/src/bind_message.c b/src/bind_message.c index 98927a476..07fb471dc 100644 --- a/src/bind_message.c +++ b/src/bind_message.c @@ -1,7 +1,7 @@ #include #include -#include +#include "spells.h" /* kernel includes */ #include diff --git a/src/direction.test.c b/src/direction.test.c index c76d6f64e..e47ab5034 100644 --- a/src/direction.test.c +++ b/src/direction.test.c @@ -2,14 +2,9 @@ #include #include "direction.h" -#include "util/language.h" #include "tests.h" -#include -#include -#include -#include -#include +#include #include @@ -63,27 +58,6 @@ static void test_get_direction_default(CuTest *tc) { CuAssertIntEquals(tc, D_EAST, get_direction("east", lang)); } -static void test_move_to_vortex(CuTest *tc) { - region *r1, *r2, *r = 0; - terrain_type *t_plain; - unit *u; - struct locale *lang; - - test_cleanup(); - lang = get_or_create_locale("en"); - locale_setstring(lang, "vortex", "wirbel"); - init_locale(lang); - register_special_direction("vortex"); - t_plain = test_create_terrain("plain", LAND_REGION | FOREST_REGION | WALK_INTO | CAVALRY_REGION); - r1 = test_create_region(0, 0, t_plain); - r2 = test_create_region(5, 0, t_plain); - CuAssertPtrNotNull(tc, create_special_direction(r1, r2, 10, "", "vortex", true)); - u = test_create_unit(test_create_faction(rc_get_or_create("hodor")), r1); - CuAssertIntEquals(tc, E_MOVE_NOREGION, movewhere(u, "barf", r1, &r)); - CuAssertIntEquals(tc, E_MOVE_OK, movewhere(u, "wirbel", r1, &r)); - CuAssertPtrEquals(tc, r2, r); -} - #define SUITE_DISABLE_TEST(suite, test) (void)test CuSuite *get_direction_suite(void) @@ -92,7 +66,6 @@ CuSuite *get_direction_suite(void) SUITE_ADD_TEST(suite, test_init_direction); SUITE_ADD_TEST(suite, test_init_directions); SUITE_ADD_TEST(suite, test_finddirection); - SUITE_ADD_TEST(suite, test_move_to_vortex); SUITE_DISABLE_TEST(suite, test_get_direction_default); return suite; } diff --git a/src/helpers.c b/src/helpers.c index 8ec55e7de..5f0b3a12d 100644 --- a/src/helpers.c +++ b/src/helpers.c @@ -10,8 +10,9 @@ This program may not be used, modified or distributed without prior permission by the authors of Eressea. */ -#include "helpers.h" #include +#include "helpers.h" +#include "vortex.h" #include #include @@ -541,6 +542,7 @@ int tolua_toid(lua_State * L, int idx, int def) void register_tolua_helpers(void) { + at_register(&at_direction); at_register(&at_building_action); register_function((pf_generic) & lua_building_protection, diff --git a/src/kernel/config.c b/src/kernel/config.c index 5b6af8725..10ab406b9 100644 --- a/src/kernel/config.c +++ b/src/kernel/config.c @@ -2667,40 +2667,6 @@ message *movement_error(unit * u, const char *token, order * ord, return NULL; } -int movewhere(const unit * u, const char *token, region * r, region ** resultp) -{ - region *r2; - direction_t d; - - if (!token || *token == '\0') { - *resultp = NULL; - return E_MOVE_OK; - } - - d = get_direction(token, u->faction->locale); - switch (d) { - case D_PAUSE: - *resultp = r; - break; - - case NODIRECTION: - r2 = find_special_direction(r, token, u->faction->locale); - if (r2 == NULL) { - return E_MOVE_NOREGION; - } - *resultp = r2; - break; - - default: - r2 = rconnect(r, d); - if (r2 == NULL || move_blocked(u, r, r2)) { - return E_MOVE_BLOCKED; - } - *resultp = r2; - } - return E_MOVE_OK; -} - bool move_blocked(const unit * u, const region * r, const region * r2) { connection *b; @@ -2779,7 +2745,6 @@ void attrib_init(void) at_register(&at_seenspell); /* neue REGION-Attribute */ - at_register(&at_direction); at_register(&at_moveblock); at_register(&at_deathcount); at_register(&at_chaoscount); diff --git a/src/kernel/config.h b/src/kernel/config.h index a81ff333a..02cf6e8f0 100644 --- a/src/kernel/config.h +++ b/src/kernel/config.h @@ -339,15 +339,6 @@ extern "C" { const struct region *dest); void add_income(struct unit *u, int type, int want, int qty); - /* movewhere error codes */ - enum { - E_MOVE_OK = 0, /* possible to move */ - E_MOVE_NOREGION, /* no region exists in this direction */ - E_MOVE_BLOCKED /* cannot see this region, there is a blocking connection. */ - }; - int movewhere(const struct unit *u, const char *token, - struct region *r, struct region **resultp); - const char *datapath(void); void set_datapath(const char *path); diff --git a/src/kernel/region.c b/src/kernel/region.c index a172e510a..8c57a220f 100644 --- a/src/kernel/region.c +++ b/src/kernel/region.c @@ -189,163 +189,6 @@ void chaoscounts(region * r, int fallen) a_remove(&r->attribs, a); } -/********************/ -/* at_direction */ -/********************/ -static void a_initdirection(attrib * a) -{ - a->data.v = calloc(1, sizeof(spec_direction)); -} - -static void a_freedirection(attrib * a) -{ - free(a->data.v); -} - -static int a_agedirection(attrib * a) -{ - spec_direction *d = (spec_direction *) (a->data.v); - --d->duration; - return (d->duration > 0) ? AT_AGE_KEEP : AT_AGE_REMOVE; -} - -typedef struct dir_lookup { - char *name; - const char *oldname; - struct dir_lookup *next; -} dir_lookup; - -static dir_lookup *dir_name_lookup; - -void register_special_direction(const char *name) -{ - struct locale *lang; - char *str = _strdup(name); - - for (lang = locales; lang; lang = nextlocale(lang)) { - void **tokens = get_translations(lang, UT_SPECDIR); - const char *token = LOC(lang, name); - - if (token) { - variant var; - - var.v = str; - addtoken(tokens, token, var); - - if (lang == default_locale) { - dir_lookup *dl = malloc(sizeof(dir_lookup)); - dl->name = str; - dl->oldname = token; - dl->next = dir_name_lookup; - dir_name_lookup = dl; - } - } else { - log_error("no translation for spec_direction '%s' in locale '%s'\n", name, locale_name(lang)); - } - } -} - -static int a_readdirection(attrib * a, void *owner, struct storage *store) -{ - spec_direction *d = (spec_direction *) (a->data.v); - - READ_INT(store, &d->x); - READ_INT(store, &d->y); - READ_INT(store, &d->duration); - if (global.data_version < UNICODE_VERSION) { - char lbuf[16]; - dir_lookup *dl = dir_name_lookup; - - READ_TOK(store, NULL, 0); - READ_TOK(store, lbuf, sizeof(lbuf)); - - cstring_i(lbuf); - for (; dl; dl = dl->next) { - if (strcmp(lbuf, dl->oldname) == 0) { - d->keyword = _strdup(dl->name); - sprintf(lbuf, "%s_desc", d->keyword); - d->desc = _strdup(dl->name); - break; - } - } - if (dl == NULL) { - log_error("unknown spec_direction '%s'\n", lbuf); - assert(!"not implemented"); - } - } else { - char lbuf[32]; - READ_TOK(store, lbuf, sizeof(lbuf)); - d->desc = _strdup(lbuf); - READ_TOK(store, lbuf, sizeof(lbuf)); - d->keyword = _strdup(lbuf); - } - d->active = true; - return AT_READ_OK; -} - -static void -a_writedirection(const attrib * a, const void *owner, struct storage *store) -{ - spec_direction *d = (spec_direction *) (a->data.v); - - WRITE_INT(store, d->x); - WRITE_INT(store, d->y); - WRITE_INT(store, d->duration); - WRITE_TOK(store, d->desc); - WRITE_TOK(store, d->keyword); -} - -attrib_type at_direction = { - "direction", - a_initdirection, - a_freedirection, - a_agedirection, - a_writedirection, - a_readdirection -}; - -region *find_special_direction(const region * r, const char *token, - const struct locale *lang) -{ - attrib *a; - spec_direction *d; - - if (strlen(token) == 0) - return NULL; - for (a = a_find(r->attribs, &at_direction); a && a->type == &at_direction; - a = a->next) { - d = (spec_direction *) (a->data.v); - - if (d->active) { - void **tokens = get_translations(lang, UT_SPECDIR); - variant var; - if (findtoken(*tokens, token, &var) == E_TOK_SUCCESS) { - if (strcmp((const char *)var.v, d->keyword) == 0) { - return findregion(d->x, d->y); - } - } - } - } - - return NULL; -} - -attrib *create_special_direction(region * r, region * rt, int duration, - const char *desc, const char *keyword, bool active) -{ - attrib *a = a_add(&r->attribs, a_new(&at_direction)); - spec_direction *d = (spec_direction *) (a->data.v); - - d->active = active; - d->x = rt->x; - d->y = rt->y; - d->duration = duration; - d->desc = _strdup(desc); - d->keyword = _strdup(keyword); - - return a; -} - /* Moveblock wird zur Zeit nicht über Attribute, sondern ein Bitfeld r->moveblock gemacht. Sollte umgestellt werden, wenn kompliziertere Dinge gefragt werden. */ @@ -630,49 +473,6 @@ int distance(const region * r1, const region * r2) return koor_distance(r1->x, r1->y, r2->x, r2->y); } -static direction_t -koor_reldirection(int ax, int ay, int bx, int by, const struct plane *pl) -{ - int dir; - for (dir = 0; dir != MAXDIRECTIONS; ++dir) { - int x = ax + delta_x[dir]; - int y = ay + delta_y[dir]; - pnormalize(&x, &y, pl); - if (bx == x && by == y) - return (direction_t)dir; - } - return NODIRECTION; -} - -spec_direction *special_direction(const region * from, const region * to) -{ - const attrib *a = a_findc(from->attribs, &at_direction); - - while (a != NULL && a->type == &at_direction) { - spec_direction *sd = (spec_direction *) a->data.v; - if (sd->x == to->x && sd->y == to->y) - return sd; - a = a->next; - } - return NULL; -} - -direction_t reldirection(const region * from, const region * to) -{ - plane *pl = rplane(from); - if (pl == rplane(to)) { - direction_t dir = koor_reldirection(from->x, from->y, to->x, to->y, pl); - - if (dir == NODIRECTION) { - spec_direction *sd = special_direction(from, to); - if (sd != NULL && sd->active) - return D_SPECIAL; - } - return dir; - } - return NODIRECTION; -} - void free_regionlist(region_list * rl) { while (rl) { diff --git a/src/kernel/region.h b/src/kernel/region.h index 39ce618c8..aa05853f0 100644 --- a/src/kernel/region.h +++ b/src/kernel/region.h @@ -153,14 +153,6 @@ extern "C" { struct message *r_addmessage(struct region *r, const struct faction *viewer, struct message *msg); - typedef struct spec_direction { - int x, y; - int duration; - bool active; - char *desc; - char *keyword; - } spec_direction; - typedef struct { direction_t dir; } moveblock; @@ -169,11 +161,9 @@ extern "C" { int distance(const struct region *, const struct region *); int koor_distance(int ax, int ay, int bx, int by); - direction_t reldirection(const struct region *from, const struct region *to); struct region *findregion(int x, int y); struct region *findregionbyid(int uid); - extern struct attrib_type at_direction; extern struct attrib_type at_moveblock; extern struct attrib_type at_peasantluck; extern struct attrib_type at_horseluck; @@ -189,14 +179,6 @@ extern "C" { void free_regionlist(region_list * rl); void add_regionlist(region_list ** rl, struct region *r); - struct region *find_special_direction(const struct region *r, - const char *token, const struct locale *lang); - void register_special_direction(const char *name); - struct spec_direction *special_direction(const region * from, - const region * to); - struct attrib *create_special_direction(struct region *r, struct region *rt, - int duration, const char *desc, const char *keyword, bool active); - int deathcount(const struct region *r); int chaoscount(const struct region *r); diff --git a/src/kernel/xmlreader.c b/src/kernel/xmlreader.c index bb072d8fa..d0554a4f8 100644 --- a/src/kernel/xmlreader.c +++ b/src/kernel/xmlreader.c @@ -29,6 +29,8 @@ without prior permission by the authors of Eressea. #include "spellbook.h" #include "calendar.h" +#include "vortex.h" + /* util includes */ #include #include diff --git a/src/main.c b/src/main.c index 123bccdbc..7ad39a2df 100644 --- a/src/main.c +++ b/src/main.c @@ -31,8 +31,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "build.h" #include "bindings.h" #include "races/races.h" -#include "spells/spells.h" -#include "spells/borders.h" +#include "spells.h" #include #include @@ -266,7 +265,6 @@ int main(int argc, char **argv) L = lua_init(); game_init(); register_races(); - register_borders(); register_spells(); bind_monsters(L); err = eressea_run(L, luafile); diff --git a/src/move.c b/src/move.c index df367bd8d..c86a61be2 100644 --- a/src/move.c +++ b/src/move.c @@ -22,6 +22,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "move.h" #include "reports.h" #include "alchemy.h" +#include "vortex.h" #include #include @@ -543,6 +544,36 @@ void travelthru(const unit * u, region * r) #endif } +static direction_t +koor_reldirection(int ax, int ay, int bx, int by, const struct plane *pl) +{ + int dir; + for (dir = 0; dir != MAXDIRECTIONS; ++dir) { + int x = ax + delta_x[dir]; + int y = ay + delta_y[dir]; + pnormalize(&x, &y, pl); + if (bx == x && by == y) + return (direction_t)dir; + } + return NODIRECTION; +} + +direction_t reldirection(const region * from, const region * to) +{ + plane *pl = rplane(from); + if (pl == rplane(to)) { + direction_t dir = koor_reldirection(from->x, from->y, to->x, to->y, pl); + + if (dir == NODIRECTION) { + spec_direction *sd = special_direction(from, to); + if (sd != NULL && sd->active) + return D_SPECIAL; + } + return dir; + } + return NODIRECTION; +} + static void leave_trail(ship * sh, region * from, region_list * route) { region *r = from; @@ -1038,6 +1069,40 @@ unit *is_guarded(region * r, unit * u, unsigned int mask) return NULL; } +int movewhere(const unit * u, const char *token, region * r, region ** resultp) +{ + region *r2; + direction_t d; + + if (!token || *token == '\0') { + *resultp = NULL; + return E_MOVE_OK; + } + + d = get_direction(token, u->faction->locale); + switch (d) { + case D_PAUSE: + *resultp = r; + break; + + case NODIRECTION: + r2 = find_special_direction(r, token, u->faction->locale); + if (r2 == NULL) { + return E_MOVE_NOREGION; + } + *resultp = r2; + break; + + default: + r2 = rconnect(r, d); + if (r2 == NULL || move_blocked(u, r, r2)) { + return E_MOVE_BLOCKED; + } + *resultp = r2; + } + return E_MOVE_OK; +} + static const char *shortdirections[MAXDIRECTIONS] = { "dir_nw", "dir_ne", diff --git a/src/move.h b/src/move.h index c76c362c1..dd158d224 100644 --- a/src/move.h +++ b/src/move.h @@ -1,7 +1,7 @@ /* Copyright (c) 1998-2010, Enno Rehling - Katja Zedel +Katja Zedel Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -18,61 +18,74 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #ifndef H_KRNL_MOVEMENT #define H_KRNL_MOVEMENT + +#include "direction.h" + #ifdef __cplusplus extern "C" { #endif - struct unit; - struct ship; - struct building_type; + struct unit; + struct ship; + struct building_type; -/* die Zahlen sind genau äquivalent zu den race Flags */ + extern struct attrib_type at_speedup; + + /* die Zahlen sind genau äquivalent zu den race Flags */ #define MV_CANNOTMOVE (1<<5) #define MV_FLY (1<<7) /* kann fliegen */ #define MV_SWIM (1<<8) /* kann schwimmen */ #define MV_WALK (1<<9) /* kann über Land gehen */ -/* Die tragekapaz. ist hardcodiert mit defines, da es bis jetzt sowieso nur 2 -** objekte gibt, die etwas tragen. */ + /* Die tragekapaz. ist hardcodiert mit defines, da es bis jetzt sowieso nur 2 + ** objekte gibt, die etwas tragen. */ #define SILVERWEIGHT 1 #define SCALEWEIGHT 100 /* Faktor, um den die Anzeige von gewichten - * * skaliert wird */ + * * skaliert wird */ #define HORSECAPACITY 7000 #define WAGONCAPACITY 14000 #define HORSESNEEDED 2 -/* ein mensch wiegt 10, traegt also 5, ein pferd wiegt 50, traegt also 20. ein -** wagen wird von zwei pferden gezogen und traegt total 140, davon 40 die -** pferde, macht nur noch 100, aber samt eigenem gewicht (40) macht also 140. */ + /* ein mensch wiegt 10, traegt also 5, ein pferd wiegt 50, traegt also 20. ein + ** wagen wird von zwei pferden gezogen und traegt total 140, davon 40 die + ** pferde, macht nur noch 100, aber samt eigenem gewicht (40) macht also 140. */ - int personcapacity(const struct unit *u); - void movement(void); - void run_to(struct unit *u, struct region *to); - struct unit *is_guarded(struct region *r, struct unit *u, unsigned int mask); - bool is_guard(const struct unit *u, int mask); - int enoughsailors(const struct ship *sh, const struct region *r); - bool canswim(struct unit *u); - bool canfly(struct unit *u); - struct unit *get_captain(const struct ship *sh); - void travelthru(const struct unit *u, struct region *r); - struct ship *move_ship(struct ship *sh, struct region *from, - struct region *to, struct region_list *route); - int walkingcapacity(const struct unit *u); - void follow_unit(struct unit *u); - bool buildingtype_exists(const struct region *r, - const struct building_type *bt, bool working); - struct unit *owner_buildingtyp(const struct region *r, - const struct building_type *bt); + /* movewhere error codes */ + enum { + E_MOVE_OK = 0, /* possible to move */ + E_MOVE_NOREGION, /* no region exists in this direction */ + E_MOVE_BLOCKED /* cannot see this region, there is a blocking connection. */ + }; + int movewhere(const struct unit *u, const char *token, + struct region *r, struct region **resultp); + direction_t reldirection(const struct region *from, const struct region *to); + + int personcapacity(const struct unit *u); + void movement(void); + void run_to(struct unit *u, struct region *to); + struct unit *is_guarded(struct region *r, struct unit *u, unsigned int mask); + bool is_guard(const struct unit *u, int mask); + int enoughsailors(const struct ship *sh, const struct region *r); + bool canswim(struct unit *u); + bool canfly(struct unit *u); + struct unit *get_captain(const struct ship *sh); + void travelthru(const struct unit *u, struct region *r); + struct ship *move_ship(struct ship *sh, struct region *from, + struct region *to, struct region_list *route); + int walkingcapacity(const struct unit *u); + void follow_unit(struct unit *u); + bool buildingtype_exists(const struct region *r, + const struct building_type *bt, bool working); + struct unit *owner_buildingtyp(const struct region *r, + const struct building_type *bt); - extern struct attrib_type at_speedup; - #define SA_HARBOUR 2 #define SA_COAST 1 #define SA_NO_INSECT -1 #define SA_NO_COAST -2 - extern int check_ship_allowed(struct ship *sh, const struct region * r); + int check_ship_allowed(struct ship *sh, const struct region * r); #ifdef __cplusplus } #endif diff --git a/src/report.c b/src/report.c index 4b50742ea..0de33ebec 100644 --- a/src/report.c +++ b/src/report.c @@ -38,6 +38,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "laws.h" #include "move.h" #include "alchemy.h" +#include "vortex.h" /* kernel includes */ #include @@ -1127,46 +1128,47 @@ static void describe(FILE * F, const seen_region * sr, faction * f) /* list directions */ dh = false; - for (d = 0; d != MAXDIRECTIONS; d++) + for (d = 0; d != MAXDIRECTIONS; d++) { if (see[d]) { - region *r2 = rconnect(r, d); - if (!r2) - continue; - nrd--; - if (dh) { - char regname[4096]; - if (nrd == 0) { + region *r2 = rconnect(r, d); + if (!r2) + continue; + nrd--; + if (dh) { + char regname[4096]; + if (nrd == 0) { + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, LOC(f->locale, "nr_nb_final"), size); + } + else { + bytes = (int)strlcpy(bufp, LOC(f->locale, "nr_nb_next"), size); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, LOC(f->locale, directions[d]), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); bytes = (int)strlcpy(bufp, " ", size); if (wrptr(&bufp, &size, bytes) != 0) WARN_STATIC_BUFFER(); - bytes = (int)strlcpy(bufp, LOC(f->locale, "nr_nb_final"), size); + f_regionid(r2, f, regname, sizeof(regname)); + bytes = _snprintf(bufp, size, trailinto(r2, f->locale), regname); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); } else { - bytes = (int)strlcpy(bufp, LOC(f->locale, "nr_nb_next"), size); + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + MSG(("nr_vicinitystart", "dir region", d, r2), bufp, size, f->locale, + f); + bufp += strlen(bufp); + dh = true; } - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - bytes = (int)strlcpy(bufp, LOC(f->locale, directions[d]), size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - bytes = (int)strlcpy(bufp, " ", size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - f_regionid(r2, f, regname, sizeof(regname)); - bytes = _snprintf(bufp, size, trailinto(r2, f->locale), regname); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - } - else { - bytes = (int)strlcpy(bufp, " ", size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - MSG(("nr_vicinitystart", "dir region", d, r2), bufp, size, f->locale, - f); - bufp += strlen(bufp); - dh = true; - } } + } /* Spezielle Richtungen */ for (a = a_find(r->attribs, &at_direction); a && a->type == &at_direction; a = a->next) { diff --git a/src/spells.c b/src/spells.c new file mode 100644 index 000000000..41b6e313c --- /dev/null +++ b/src/spells.c @@ -0,0 +1,6876 @@ +/* vi: set ts=2: + * + * + * Eressea PB(E)M host Copyright (C) 1998-2003 + * Christian Schlittchen (corwin@amber.kn-bremen.de) + * Katja Zedel (katze@felidae.kn-bremen.de) + * Henning Peters (faroul@beyond.kn-bremen.de) + * Enno Rehling (enno@eressea.de) + * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) + * + * This program may not be used, modified or distributed without + * prior permission by the authors of Eressea. + */ + +#include +#include +#include + +#include "spy.h" +#include "vortex.h" +#include "spells.h" +#include "direction.h" + +#include +#include +#include +#include +#include +#include +#include + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* libc includes */ +#include +#include +#include +#include +#include +#include +#include + +/* triggers includes */ +#include +#include +#include +#include +#include +#include +#include + +/* attributes includes */ +#include +#include +/* ----------------------------------------------------------------------- */ + +static float zero_effect = 0.0F; + +attrib_type at_wdwpyramid = { + "wdwpyramid", NULL, NULL, NULL, a_writevoid, a_readvoid +}; + +/* ----------------------------------------------------------------------- */ + +static void report_spell(unit * mage, region * r, message * msg) +{ + r_addmessage(r, NULL, msg); + if (mage && mage->region != r) { + add_message(&mage->faction->msgs, msg); + } +} + +static void report_failure(unit * mage, struct order *ord) +{ + /* Fehler: "Der Zauber schlaegt fehl" */ + cmistake(mage, ord, 180, MSG_MAGIC); +} + +/* ------------------------------------------------------------- */ +/* Spruchanalyse - Ausgabe von curse->info und curse->name */ +/* ------------------------------------------------------------- */ + +static double curse_chance(const struct curse *c, double force) +{ + return 1.0 + (force - c->vigour) * 0.1; +} + +static void magicanalyse_region(region * r, unit * mage, double force) +{ + attrib *a; + bool found = false; + + for (a = r->attribs; a; a = a->next) { + curse *c = (curse *)a->data.v; + double probability; + int mon; + + if (!fval(a->type, ATF_CURSE)) + continue; + + /* ist der curse schwaecher als der Analysezauber, so ergibt sich + * mehr als 100% probability und damit immer ein Erfolg. */ + probability = curse_chance(c, force); + mon = c->duration + (rng_int() % 10) - 5; + mon = _max(1, mon); + found = true; + + if (chance(probability)) { /* Analyse geglueckt */ + if (c_flags(c) & CURSE_NOAGE) { + ADDMSG(&mage->faction->msgs, msg_message("analyse_region_noage", + "mage region curse", mage, r, c->type)); + } + else { + ADDMSG(&mage->faction->msgs, msg_message("analyse_region_age", + "mage region curse months", mage, r, c->type, mon)); + } + } + else { + ADDMSG(&mage->faction->msgs, msg_message("analyse_region_fail", + "mage region", mage, r)); + } + } + if (!found) { + ADDMSG(&mage->faction->msgs, msg_message("analyse_region_nospell", + "mage region", mage, r)); + } +} + +static void magicanalyse_unit(unit * u, unit * mage, double force) +{ + attrib *a; + bool found = false; + + for (a = u->attribs; a; a = a->next) { + curse *c; + double probability; + int mon; + if (!fval(a->type, ATF_CURSE)) + continue; + + c = (curse *)a->data.v; + /* ist der curse schwaecher als der Analysezauber, so ergibt sich + * mehr als 100% probability und damit immer ein Erfolg. */ + probability = curse_chance(c, force); + mon = c->duration + (rng_int() % 10) - 5; + mon = _max(1, mon); + + if (chance(probability)) { /* Analyse geglueckt */ + if (c_flags(c) & CURSE_NOAGE) { + ADDMSG(&mage->faction->msgs, msg_message("analyse_unit_noage", + "mage unit curse", mage, u, c->type)); + } + else { + ADDMSG(&mage->faction->msgs, msg_message("analyse_unit_age", + "mage unit curse months", mage, u, c->type, mon)); + } + } + else { + ADDMSG(&mage->faction->msgs, msg_message("analyse_unit_fail", "mage unit", + mage, u)); + } + } + if (!found) { + ADDMSG(&mage->faction->msgs, msg_message("analyse_unit_nospell", + "mage target", mage, u)); + } +} + +static void magicanalyse_building(building * b, unit * mage, double force) +{ + attrib *a; + bool found = false; + + for (a = b->attribs; a; a = a->next) { + curse *c; + double probability; + int mon; + + if (!fval(a->type, ATF_CURSE)) + continue; + + c = (curse *)a->data.v; + /* ist der curse schwaecher als der Analysezauber, so ergibt sich + * mehr als 100% probability und damit immer ein Erfolg. */ + probability = curse_chance(c, force); + mon = c->duration + (rng_int() % 10) - 5; + mon = _max(1, mon); + + if (chance(probability)) { /* Analyse geglueckt */ + if (c_flags(c) & CURSE_NOAGE) { + ADDMSG(&mage->faction->msgs, msg_message("analyse_building_age", + "mage building curse", mage, b, c->type)); + } + else { + ADDMSG(&mage->faction->msgs, msg_message("analyse_building_age", + "mage building curse months", mage, b, c->type, mon)); + } + } + else { + ADDMSG(&mage->faction->msgs, msg_message("analyse_building_fail", + "mage building", mage, b)); + } + } + if (!found) { + ADDMSG(&mage->faction->msgs, msg_message("analyse_building_nospell", + "mage building", mage, b)); + } + +} + +static void magicanalyse_ship(ship * sh, unit * mage, double force) +{ + attrib *a; + bool found = false; + + for (a = sh->attribs; a; a = a->next) { + curse *c; + double probability; + int mon; + if (!fval(a->type, ATF_CURSE)) + continue; + + c = (curse *)a->data.v; + /* ist der curse schwaecher als der Analysezauber, so ergibt sich + * mehr als 100% probability und damit immer ein Erfolg. */ + probability = curse_chance(c, force); + mon = c->duration + (rng_int() % 10) - 5; + mon = _max(1, mon); + + if (chance(probability)) { /* Analyse geglueckt */ + if (c_flags(c) & CURSE_NOAGE) { + ADDMSG(&mage->faction->msgs, msg_message("analyse_ship_noage", + "mage ship curse", mage, sh, c->type)); + } + else { + ADDMSG(&mage->faction->msgs, msg_message("analyse_ship_age", + "mage ship curse months", mage, sh, c->type, mon)); + } + } + else { + ADDMSG(&mage->faction->msgs, msg_message("analyse_ship_fail", "mage ship", + mage, sh)); + + } + } + if (!found) { + ADDMSG(&mage->faction->msgs, msg_message("analyse_ship_nospell", + "mage ship", mage, sh)); + } + +} + +static int break_curse(attrib ** alist, int cast_level, float force, curse * c) +{ + int succ = 0; + /* attrib **a = a_find(*ap, &at_curse); */ + attrib **ap = alist; + + while (*ap && force > 0) { + curse *c1; + attrib *a = *ap; + if (!fval(a->type, ATF_CURSE)) { + do { + ap = &(*ap)->next; + } while (*ap && a->type == (*ap)->type); + continue; + } + c1 = (curse *)a->data.v; + + /* Immunitaet pruefen */ + if (c_flags(c1) & CURSE_IMMUNE) { + do { + ap = &(*ap)->next; + } while (*ap && a->type == (*ap)->type); + continue; + } + + /* Wenn kein spezieller cursetyp angegeben ist, soll die Antimagie + * auf alle Verzauberungen wirken. Ansonsten pruefe, ob der Curse vom + * richtigen Typ ist. */ + if (!c || c == c1) { + float remain = destr_curse(c1, cast_level, force); + if (remain < force) { + succ = cast_level; + force = remain; + } + if (c1->vigour <= 0) { + a_remove(alist, a); + } + } + if (*ap == a) + ap = &a->next; + } + return succ; +} + +int report_action(region * r, unit * actor, message * msg, int flags) +{ + int result = 0; + unit *u; + int view = flags & (ACTION_CANSEE | ACTION_CANNOTSEE); + + /* melden, 1x pro Partei */ + if (flags & ACTION_RESET) { + freset(actor->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + } + if (view) { + for (u = r->units; u; u = u->next) { + if (!fval(u->faction, FFL_SELECT)) { + bool show = u->faction == actor->faction; + fset(u->faction, FFL_SELECT); + if (view == ACTION_CANSEE) { + /* Bei Fernzaubern sieht nur die eigene Partei den Magier */ + show = show || (r == actor->region + && cansee(u->faction, r, actor, 0)); + } + else if (view == ACTION_CANNOTSEE) { + show = !show && !(r == actor->region + && cansee(u->faction, r, actor, 0)); + } + else { + /* the unliely (or lazy) case */ + show = true; + } + + if (show) { + r_addmessage(r, u->faction, msg); + } + else { /* Partei des Magiers, sieht diesen immer */ + result = 1; + } + } + } + /* Ist niemand von der Partei des Magiers in der Region, dem Magier + * nochmal gesondert melden */ + if ((flags & ACTION_CANSEE) && !fval(actor->faction, FFL_SELECT)) { + add_message(&actor->faction->msgs, msg); + } + } + return result; +} + +/* ------------------------------------------------------------- */ +/* Report a spell's effect to the units in the region. +*/ + +static void +report_effect(region * r, unit * mage, message * seen, message * unseen) +{ +#if 0 + unit *u; + + /* melden, 1x pro Partei */ + freset(mage->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) { + if (!fval(u->faction, FFL_SELECT)) { + fset(u->faction, FFL_SELECT); + + /* Bei Fernzaubern sieht nur die eigene Partei den Magier */ + if (u->faction != mage->faction) { + if (r == mage->region) { + /* kein Fernzauber, pruefe, ob der Magier ueberhaupt gesehen + * wird */ + if (cansee(u->faction, r, mage, 0)) { + r_addmessage(r, u->faction, seen); + } else { + r_addmessage(r, u->faction, unseen); + } + } else { /* Fernzauber, fremde Partei sieht den Magier niemals */ + r_addmessage(r, u->faction, unseen); + } + } else { /* Partei des Magiers, sieht diesen immer */ + r_addmessage(r, u->faction, seen); + } + } + } + /* Ist niemand von der Partei des Magiers in der Region, dem Magier + * nochmal gesondert melden */ + if (!fval(mage->faction, FFL_SELECT)) { + add_message(&mage->faction->msgs, seen); + } +#else + int err = report_action(r, mage, seen, ACTION_RESET | ACTION_CANSEE); + if (err) { + report_action(r, mage, seen, ACTION_CANNOTSEE); + } +#endif +} + +/* ------------------------------------------------------------- */ +/* Die Spruchfunktionen */ +/* ------------------------------------------------------------- */ +/* Meldungen: + * + * Fehlermeldungen sollten als MSG_MAGIC, level ML_MISTAKE oder + * ML_WARN ausgegeben werden. (stehen im Kopf der Auswertung unter + * Zauberwirkungen) + + sprintf(buf, "%s in %s: 'ZAUBER %s': [hier die Fehlermeldung].", + unitname(mage), regionname(mage->region, mage->faction), sa->strings[0]); + add_message(0, mage->faction, buf, MSG_MAGIC, ML_MISTAKE); + + * Allgemein sichtbare Auswirkungen in der Region sollten als + * Regionsereignisse auch dort auftauchen. + + { + message * seen = msg_message("harvest_effect", "mage", mage); + message * unseen = msg_message("harvest_effect", "mage", NULL); + report_effect(r, mage, seen, unseen); + } + + * Meldungen an den Magier ueber Erfolg sollten, wenn sie nicht als + * Regionsereigniss auftauchen, als MSG_MAGIC level ML_INFO unter + * Zauberwirkungen gemeldet werden. Direkt dem Magier zuordnen (wie + * Botschaft an Einheit) ist derzeit nicht moeglich. + * ACHTUNG! r muss nicht die Region des Magier sein! (FARCASTING) + * + * Parameter: + * die Struct castorder *co ist in magic.h deklariert + * die Parameterliste spellparameter *pa = co->par steht dort auch. + * + */ + +/* ------------------------------------------------------------- */ +/* Name: Vertrauter + * Stufe: 10 + * + * Wirkung: + * Der Magier beschwoert einen Vertrauten, ein kleines Tier, welches + * dem Magier zu Diensten ist. Der Magier kann durch die Augen des + * Vertrauten sehen, und durch den Vertrauten zaubern, allerdings nur + * mit seiner halben Stufe. Je nach Vertrautem erhaelt der Magier + * evtl diverse Skillmodifikationen. Der Typ des Vertrauten ist + * zufaellig bestimmt, wird aber durch Magiegebiet und Rasse beeinflußt. + * "Tierische" Vertraute brauchen keinen Unterhalt. + * + * Ein paar Moeglichkeiten: + * Magieg. Rasse Besonderheiten + * Eule Tybied -/- fliegt, Auraregeneration + * Rabe Ilaun -/- fliegt + * Imp Draig -/- Magieresistenz? + * Fuchs Gwyrrd -/- Wahrnehmung + * ???? Cerddor -/- ???? (Singvogel?, Papagei?) + * Adler -/- -/- fliegt, +Wahrnehmung, =^=Adlerauge-Spruch? + * Kraehe -/- -/- fliegt, +Tarnung (weil unauffaellig) + * Delphin -/- Meerm. schwimmt + * Wolf -/- Ork + * Hund -/- Mensch kann evtl BEWACHE ausfuehren + * Ratte -/- Goblin + * Albatros -/- -/- fliegt, kann auf Ozean "landen" + * Affe -/- -/- kann evtl BEKLAUE ausfuehren + * Goblin -/- !Goblin normale Einheit + * Katze -/- !Katze normale Einheit + * Daemon -/- !Daemon normale Einheit + * + * Spezielle V. fuer Katzen, Trolle, Elfen, Daemonen, Insekten, Zwerge? + */ + +static const race *select_familiar(const race * magerace, magic_t magiegebiet) +{ + const race *retval = 0; + int rnd = rng_int() % 100; + + assert(magerace->familiars[0]); + if (rnd >= 70) { + retval = magerace->familiars[magiegebiet]; + } + else { + retval = magerace->familiars[0]; + } + + if (!retval || rnd < 3) { + race_list *familiarraces = get_familiarraces(); + unsigned int maxlen = listlen(familiarraces); + if (maxlen > 0) { + race_list *rclist = familiarraces; + int index = rng_int() % maxlen; + while (index-- > 0) { + rclist = rclist->next; + } + retval = rclist->data; + } + } + + if (!retval) { + retval = magerace->familiars[0]; + } + if (!retval) { + log_error("select_familiar: No familiar (not even a default) defined for %s.\n", magerace->_name); + } + return retval; +} + +/* ------------------------------------------------------------- */ +/* der Vertraue des Magiers */ + +static void make_familiar(unit * familiar, unit * mage) +{ + /* skills and spells: */ + if (u_race(familiar)->init_familiar != NULL) { + u_race(familiar)->init_familiar(familiar); + } + else { + log_error("could not perform initialization for familiar %s.\n", familiar->faction->race->_name); + } + + /* triggers: */ + create_newfamiliar(mage, familiar); + + /* Hitpoints nach Talenten korrigieren, sonst starten vertraute + * mit Ausdauerbonus verwundet */ + familiar->hp = unit_max_hp(familiar); +} + +static int sp_summon_familiar(castorder * co) +{ + unit *familiar; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + const race *rc; + int sk; + int dh, dh1, bytes; + message *msg; + char zText[2048], *bufp = zText; + size_t size = sizeof(zText) - 1; + + if (get_familiar(mage) != NULL) { + cmistake(mage, co->order, 199, MSG_MAGIC); + return 0; + } + rc = select_familiar(mage->faction->race, mage->faction->magiegebiet); + if (rc == NULL) { + log_error("could not find suitable familiar for %s.\n", mage->faction->race->_name); + return 0; + } + + if (fval(rc, RCF_SWIM) && !fval(rc, RCF_WALK)) { + int coasts = is_coastregion(r); + int dir; + if (coasts == 0) { + cmistake(mage, co->order, 229, MSG_MAGIC); + return 0; + } + + /* In welcher benachbarten Ozeanregion soll der Familiar erscheinen? */ + coasts = rng_int() % coasts; + dh = -1; + for (dir = 0; dir != MAXDIRECTIONS; ++dir) { + region *rn = rconnect(r, dir); + if (rn && fval(rn->terrain, SEA_REGION)) { + dh++; + if (dh == coasts) + break; + } + } + r = rconnect(r, dir); + } + + msg = msg_message("familiar_name", "unit", mage); + nr_render(msg, mage->faction->locale, zText, sizeof(zText), mage->faction); + msg_release(msg); + familiar = create_unit(r, mage->faction, 1, rc, 0, zText, mage); + setstatus(familiar, ST_FLEE); + fset(familiar, UFL_LOCKED); + make_familiar(familiar, mage); + + dh = 0; + dh1 = 0; + for (sk = 0; sk < MAXSKILLS; ++sk) { + if (skill_enabled(sk) && rc->bonus[sk] > -5) + dh++; + } + + for (sk = 0; sk < MAXSKILLS; sk++) { + if (skill_enabled(sk) && rc->bonus[sk] > -5) { + dh--; + if (dh1 == 0) { + dh1 = 1; + } + else { + if (dh == 0) { + bytes = + (int)strlcpy(bufp, (const char *)LOC(mage->faction->locale, + "list_and"), size); + } + else { + bytes = (int)strlcpy(bufp, (const char *)", ", size); + } + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + bytes = + (int)strlcpy(bufp, (const char *)skillname((skill_t)sk, mage->faction->locale), + size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + } + ADDMSG(&mage->faction->msgs, msg_message("familiar_describe", + "mage race skills", mage, rc, zText)); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Zerstoere Magie + * Wirkung: + * Zerstoert alle Zauberwirkungen auf dem Objekt. Jeder gebrochene + * Zauber verbraucht c->vigour an Zauberkraft. Wird der Spruch auf + * einer geringeren Stufe gezaubert, als der Zielzauber an c->vigour + * hat, so schlaegt die Aufloesung mit einer von der Differenz abhaengigen + * Chance fehl. Auch dann wird force verbraucht, der Zauber jedoch nur + * abgeschwaecht. + * + * Flag: + * (FARCASTING|SPELLLEVEL|ONSHIPCAST|TESTCANSEE) + * */ +static int sp_destroy_magic(castorder * co) +{ + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + spellparameter *pa = co->par; + curse *c = NULL; + char ts[80]; + attrib **ap; + int obj; + int succ; + + /* da jeder Zauber force verbraucht und der Zauber auf alles und nicht + * nur einen Spruch wirken soll, wird die Wirkung hier verstaerkt */ + force *= 4; + + /* Objekt ermitteln */ + obj = pa->param[0]->typ; + + switch (obj) { + case SPP_REGION: + { + /* region *tr = pa->param[0]->data.r; -- farcasting! */ + region *tr = co_get_region(co); + ap = &tr->attribs; + write_regionname(tr, mage->faction, ts, sizeof(ts)); + break; + } + case SPP_TEMP: + case SPP_UNIT: + { + unit *u; + u = pa->param[0]->data.u; + ap = &u->attribs; + write_unitname(u, ts, sizeof(ts)); + break; + } + case SPP_BUILDING: + { + building *b; + b = pa->param[0]->data.b; + ap = &b->attribs; + write_buildingname(b, ts, sizeof(ts)); + break; + } + case SPP_SHIP: + { + ship *sh; + sh = pa->param[0]->data.sh; + ap = &sh->attribs; + write_shipname(sh, ts, sizeof(ts)); + break; + } + default: + return 0; + } + + succ = break_curse(ap, cast_level, force, c); + + if (succ) { + ADDMSG(&mage->faction->msgs, msg_message("destroy_magic_effect", + "unit region command succ target", mage, mage->region, co->order, succ, + ts)); + } + else { + ADDMSG(&mage->faction->msgs, msg_message("destroy_magic_noeffect", + "unit region command", mage, mage->region, co->order)); + } + + return _max(succ, 1); +} + +/* ------------------------------------------------------------- */ +/* Name: Transferiere Aura + * Stufe: variabel + * Gebiet: alle + * Kategorie: Einheit, positiv + * Wirkung: + * Mit Hilfe dieses Zauber kann der Magier eigene Aura im Verhaeltnis + * 2:1 auf einen anderen Magier des gleichen Magiegebietes oder (nur + * bei Tybied) im Verhaeltnis 3:1 auf einen Magier eines anderen + * Magiegebietes uebertragen. + * + * Syntax: + * "ZAUBERE " + * "ui" + * Flags: + * (UNITSPELL|ONSHIPCAST) + * */ + +static int sp_transferaura(castorder * co) +{ + int aura, gain, multi = 2; + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + unit *u; + sc_mage *scm_dst, *scm_src = get_mage(mage); + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber + * abbrechen aber kosten lassen */ + if (pa->param[0]->flag == TARGET_RESISTS) + return cast_level; + + /* Wieviel Transferieren? */ + aura = pa->param[1]->data.i; + u = pa->param[0]->data.u; + scm_dst = get_mage(u); + + if (scm_dst == NULL) { + /* "Zu dieser Einheit kann ich keine Aura uebertragen." */ + cmistake(mage, co->order, 207, MSG_MAGIC); + return 0; + } + else if (scm_src->magietyp == M_TYBIED) { + if (scm_src->magietyp != scm_dst->magietyp) + multi = 3; + } + else if (scm_src->magietyp == M_GRAY) { + if (scm_src->magietyp != scm_dst->magietyp) + multi = 4; + } + else if (scm_dst->magietyp != scm_src->magietyp) { + /* "Zu dieser Einheit kann ich keine Aura uebertragen." */ + cmistake(mage, co->order, 207, MSG_MAGIC); + return 0; + } + + if (aura < multi) { + /* "Auraangabe fehlerhaft." */ + cmistake(mage, co->order, 208, MSG_MAGIC); + return 0; + } + + gain = _min(aura, scm_src->spellpoints) / multi; + scm_src->spellpoints -= gain * multi; + scm_dst->spellpoints += gain; + + /* sprintf(buf, "%s transferiert %d Aura auf %s", unitname(mage), + gain, unitname(u)); */ + ADDMSG(&mage->faction->msgs, msg_message("auratransfer_success", + "unit target aura", mage, u, gain)); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* DRUIDE */ +/* ------------------------------------------------------------- */ +/* Name: Guenstige Winde + * Stufe: 4 + * Gebiet: Gwyrrd + * Wirkung: + * Schiffsbewegung +1, kein Abtreiben. Haelt (Stufe) Runden an. + * Kombinierbar mit "Sturmwind" (das +1 wird dadurch aber nicht + * verdoppelt), und "Luftschiff". + * + * Flags: + * (SHIPSPELL|ONSHIPCAST|SPELLLEVEL|TESTRESISTANCE) + */ + +static int sp_goodwinds(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + int duration = cast_level + 1; + spellparameter *pa = co->par; + message *m; + ship *sh; + unit *u; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + sh = pa->param[0]->data.sh; + + /* keine Probleme mit C_SHIP_SPEEDUP und C_SHIP_FLYING */ + /* NODRIFT bewirkt auch +1 Geschwindigkeit */ + create_curse(mage, &sh->attribs, ct_find("nodrift"), power, duration, + zero_effect, 0); + + /* melden, 1x pro Partei */ + freset(mage->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + m = msg_message("wind_effect", "mage ship", mage, sh); + for (u = r->units; u; u = u->next) { + if (u->ship != sh) /* nur den Schiffsbesatzungen! */ + continue; + if (!fval(u->faction, FFL_SELECT)) { + r_addmessage(r, u->faction, m); + fset(u->faction, FFL_SELECT); + } + } + if (!fval(mage->faction, FFL_SELECT)) { + r_addmessage(r, mage->faction, m); + } + msg_release(m); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Magischer Pfad + * Stufe: 4 + * Gebiet: Gwyrrd + * Wirkung: + * fuer Stufe Runden wird eine (magische) Strasse erzeugt, die wie eine + * normale Strasse wirkt. + * Im Ozean schlaegt der Spruch fehl + * + * Flags: + * (FARCASTING|SPELLLEVEL|REGIONSPELL|ONSHIPCAST|TESTRESISTANCE) + */ +static int sp_magicstreet(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + + if (!fval(r->terrain, LAND_REGION)) { + cmistake(mage, co->order, 186, MSG_MAGIC); + return 0; + } + + /* wirkt schon in der Zauberrunde! */ + create_curse(mage, &r->attribs, ct_find("magicstreet"), co->force, + co->level + 1, zero_effect, 0); + + /* melden, 1x pro Partei */ + { + message *seen = msg_message("path_effect", "mage region", mage, r); + message *unseen = msg_message("path_effect", "mage region", NULL, r); + report_effect(r, mage, seen, unseen); + msg_release(seen); + msg_release(unseen); + } + + return co->level; +} + +/* ------------------------------------------------------------- */ +/* Name: Erwecke Ents + * Stufe: 10 + * Kategorie: Beschwoerung, positiv + * Gebiet: Gwyrrd + * Wirkung: + * Verwandelt (Stufe) Baeume in eine Gruppe von Ents, die sich fuer Stufe + * Runden der Partei des Druiden anschliessen und danach wieder zu + * Baeumen werden + * Patzer: + * Monster-Ents entstehen + * + * Flags: + * (SPELLLEVEL) + */ +static int sp_summonent(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + unit *u; + attrib *a; + int ents; + + if (rtrees(r, 2) == 0) { + cmistake(mage, co->order, 204, MSG_EVENT); + /* nicht ohne baeume */ + return 0; + } + + ents = (int)_min(power * power, rtrees(r, 2)); + + u = create_unit(r, mage->faction, ents, get_race(RC_TREEMAN), 0, NULL, mage); + + a = a_new(&at_unitdissolve); + a->data.ca[0] = 2; /* An r->trees. */ + a->data.ca[1] = 5; /* 5% */ + a_add(&u->attribs, a); + fset(u, UFL_LOCKED); + + rsettrees(r, 2, rtrees(r, 2) - ents); + + /* melden, 1x pro Partei */ + { + message *seen = msg_message("ent_effect", "mage amount", mage, ents); + message *unseen = msg_message("ent_effect", "mage amount", NULL, ents); + report_effect(r, mage, seen, unseen); + msg_release(unseen); + msg_release(seen); + } + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Segne Steinkreis + * Stufe: 11 + * Kategorie: Artefakt + * Gebiet: Gwyrrd + * Wirkung: + * Es werden zwei neue Gebaeude eingefuehrt: Steinkreis und Steinkreis + * (gesegnet). Ersteres kann man bauen, letzteres wird aus einem + * fertigen Steinkreis mittels des Zaubers erschaffen. + * + * Flags: + * (BUILDINGSPELL) + * + */ +static int sp_blessstonecircle(castorder * co) +{ + building *b; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *p = co->par; + message *msg; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (p->param[0]->flag == TARGET_NOTFOUND) + return 0; + + b = p->param[0]->data.b; + + if (b->type != bt_find("stonecircle")) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "error_notstonecircle", "building", b)); + return 0; + } + + if (b->size < b->type->maxsize) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "error_notcomplete", "building", b)); + return 0; + } + + b->type = bt_find("blessedstonecircle"); + + msg = msg_message("blessedstonecircle_effect", "mage building", mage, b); + add_message(&r->msgs, msg); + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Mahlstrom + * Stufe: 15 + * Kategorie: Region, negativ + * Gebiet: Gwyrrd + * Wirkung: + * Erzeugt auf See einen Mahlstrom fuer Stufe-Wochen. Jedes Schiff, das + * durch den Mahlstrom segelt, nimmt 0-150% Schaden. (D.h. es hat auch + * eine 1/3-Chance, ohne Federlesens zu sinken. Der Mahlstrom sollte + * aus den Nachbarregionen sichtbar sein. + * + * Flags: + * (OCEANCASTABLE | ONSHIPCAST | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_maelstrom(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + curse *c; + int duration = (int)co->force + 1; + + if (!fval(r->terrain, SEA_REGION)) { + cmistake(mage, co->order, 205, MSG_MAGIC); + /* nur auf ozean */ + return 0; + } + + /* Attribut auf Region. + * Existiert schon ein curse, so wird dieser verstaerkt + * (Max(Dauer), Max(Staerke))*/ + c = create_curse(mage, &r->attribs, ct_find("maelstrom"), co->force, duration, co->force, 0); + + /* melden, 1x pro Partei */ + if (c) { + message *seen = msg_message("maelstrom_effect", "mage", mage); + message *unseen = msg_message("maelstrom_effect", "mage", NULL); + report_effect(r, mage, seen, unseen); + msg_release(seen); + msg_release(unseen); + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Wurzeln der Magie + * Stufe: 16 + * Kategorie: Region, neutral + * Gebiet: Gwyrrd + * Wirkung: + * Wandelt einen Wald permanent in eine Mallornregion + * + * Flags: + * (FARCASTING | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_mallorn(castorder * co) +{ + region *r = co_get_region(co); + int cast_level = co->level; + unit *mage = co->magician.u; + + if (!fval(r->terrain, LAND_REGION)) { + cmistake(mage, co->order, 290, MSG_MAGIC); + return 0; + } + if (fval(r, RF_MALLORN)) { + cmistake(mage, co->order, 291, MSG_MAGIC); + return 0; + } + + /* half the trees will die */ + rsettrees(r, 2, rtrees(r, 2) / 2); + rsettrees(r, 1, rtrees(r, 1) / 2); + rsettrees(r, 0, rtrees(r, 0) / 2); + fset(r, RF_MALLORN); + + /* melden, 1x pro Partei */ + { + message *seen = msg_message("mallorn_effect", "mage", mage); + message *unseen = msg_message("mallorn_effect", "mage", NULL); + report_effect(r, mage, seen, unseen); + msg_release(seen); + msg_release(unseen); + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Segen der Erde / Regentanz + * Stufe: 1 + * Kategorie: Region, positiv + * Gebiet: Gwyrrd + * + * Wirkung: + * Alle Bauern verdienen Stufe-Wochen 1 Silber mehr. + * + * Flags: + * (FARCASTING | SPELLLEVEL | ONSHIPCAST | REGIONSPELL) + */ +static int sp_blessedharvest(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + int duration = (int)co->force + 1; + /* Attribut auf Region. + * Existiert schon ein curse, so wird dieser verstaerkt + * (Max(Dauer), Max(Staerke))*/ + + if (create_curse(mage, &r->attribs, ct_find("blessedharvest"), co->force, + duration, 1.0, 0)) { + message *seen = msg_message("harvest_effect", "mage", mage); + message *unseen = msg_message("harvest_effect", "mage", NULL); + report_effect(r, mage, seen, unseen); + msg_release(seen); + msg_release(unseen); + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Hainzauber + * Stufe: 2 + * Kategorie: Region, positiv + * Gebiet: Gwyrrd + * Syntax: ZAUBER [REGION x y] [STUFE 2] "Hain" + * Wirkung: + * Erschafft Stufe-10*Stufe Jungbaeume + * + * Flag: + * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + */ + +static int sp_hain(castorder * co) +{ + int trees; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + + if (!r->land) { + cmistake(mage, co->order, 296, MSG_MAGIC); + return 0; + } + if (fval(r, RF_MALLORN)) { + cmistake(mage, co->order, 92, MSG_MAGIC); + return 0; + } + + trees = lovar((int)(force * 10 * RESOURCE_QUANTITY)) + (int)force; + rsettrees(r, 1, rtrees(r, 1) + trees); + + /* melden, 1x pro Partei */ + { + message *seen = msg_message("growtree_effect", "mage amount", mage, trees); + message *unseen = + msg_message("growtree_effect", "mage amount", NULL, trees); + report_effect(r, mage, seen, unseen); + msg_release(seen); + msg_release(unseen); + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Segne Mallornstecken - Mallorn Hainzauber + * Stufe: 4 + * Kategorie: Region, positiv + * Gebiet: Gwyrrd + * Syntax: ZAUBER [REGION x y] [STUFE 4] "Segne Mallornstecken" + * Wirkung: + * Erschafft Stufe-10*Stufe Jungbaeume + * + * Flag: + * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + */ + +static int sp_mallornhain(castorder * co) +{ + int trees; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + + if (!r->land) { + cmistake(mage, co->order, 296, MSG_MAGIC); + return 0; + } + if (!fval(r, RF_MALLORN)) { + cmistake(mage, co->order, 91, MSG_MAGIC); + return 0; + } + + trees = lovar((int)(force * 10 * RESOURCE_QUANTITY)) + (int)force; + rsettrees(r, 1, rtrees(r, 1) + trees); + + /* melden, 1x pro Partei */ + { + message *seen = msg_message("growtree_effect", "mage amount", mage, trees); + message *unseen = + msg_message("growtree_effect", "mage amount", NULL, trees); + report_effect(r, mage, seen, unseen); + msg_release(seen); + msg_release(unseen); + } + + return cast_level; +} + +static void fumble_ents(const castorder * co) +{ + int ents; + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + /* int cast_level = co->level; */ + float force = co->force; + + if (!r->land) { + cmistake(mage, co->order, 296, MSG_MAGIC); + return; + } + + ents = (int)(force * 10); + u = create_unit(r, get_monsters(), ents, get_race(RC_TREEMAN), 0, NULL, NULL); + + if (u) { + message *unseen; + + /* 'Erfolg' melden */ + ADDMSG(&mage->faction->msgs, msg_message("regionmagic_patzer", + "unit region command", mage, mage->region, co->order)); + + /* melden, 1x pro Partei */ + unseen = msg_message("entrise", "region", r); + report_effect(r, mage, unseen, unseen); + msg_release(unseen); + } +} + +/* ------------------------------------------------------------- */ +/* Name: Rosthauch + * Stufe: 3 + * Kategorie: Einheit, negativ + * Gebiet: Gwyrrd + * Wirkung: + * Zerstoert zwischen Stufe und Stufe*10 Eisenwaffen + * + * Flag: + * (FARCASTING | SPELLLEVEL | UNITSPELL | TESTCANSEE | TESTRESISTANCE) + */ +/* Syntax: ZAUBER [REGION x y] [STUFE 2] "Rosthauch" 1111 2222 3333 */ + +typedef struct iron_weapon { + const struct item_type *type; + const struct item_type *rusty; + float chance; + struct iron_weapon *next; +} iron_weapon; + +static iron_weapon *ironweapons = NULL; + +void +add_ironweapon(const struct item_type *type, const struct item_type *rusty, +float chance) +{ + iron_weapon *iweapon = malloc(sizeof(iron_weapon)); + iweapon->type = type; + iweapon->rusty = rusty; + iweapon->chance = chance; + iweapon->next = ironweapons; + ironweapons = iweapon; +} + +static int sp_rosthauch(castorder * co) +{ + int n; + int success = 0; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + int force = (int)co->force; + spellparameter *pa = co->par; + + if (ironweapons == NULL) { + add_ironweapon(it_find("sword"), it_find("rustysword"), 1.0); + add_ironweapon(it_find("axe"), it_find("rustyaxe"), 1.0); + add_ironweapon(it_find("greatsword"), it_find("rustygreatsword"), 1.0); + add_ironweapon(it_find("halberd"), it_find("rustyhalberd"), 0.5f); +#ifndef NO_RUSTY_ARMOR + add_ironweapon(it_find("shield"), it_find("rustyshield"), 0.5f); + add_ironweapon(it_find("chainmail"), it_find("rustychainmail"), 0.2f); +#endif + } + + if (force > 0) { + force = rng_int() % ((int)(force * 10)) + force; + } + /* fuer jede Einheit */ + for (n = 0; n < pa->length; n++) { + unit *u = pa->param[n]->data.u; + int ironweapon = 0; + iron_weapon *iweapon = ironweapons; + + if (force <= 0) + break; + if (pa->param[n]->flag & (TARGET_RESISTS | TARGET_NOTFOUND)) + continue; + + for (; iweapon != NULL; iweapon = iweapon->next) { + item **ip = i_find(&u->items, iweapon->type); + if (*ip) { + int i = _min((*ip)->number, force); + if (iweapon->chance < 1.0) { + i = (int)(i * iweapon->chance); + } + if (i > 0) { + force -= i; + ironweapon += i; + i_change(ip, iweapon->type, -i); + if (iweapon->rusty) { + i_change(&u->items, iweapon->rusty, i); + } + } + } + if (force <= 0) + break; + } + + if (ironweapon > 0) { + /* {$mage mage} legt einen Rosthauch auf {target}. {amount} Waffen + * wurden vom Rost zerfressen */ + ADDMSG(&mage->faction->msgs, msg_message("rust_effect", + "mage target amount", mage, u, ironweapon)); + ADDMSG(&u->faction->msgs, msg_message("rust_effect", + "mage target amount", + cansee(u->faction, r, mage, 0) ? mage : NULL, u, ironweapon)); + success += ironweapon; + } + else { + /* {$mage mage} legt einen Rosthauch auf {target}, doch der + * Rosthauch fand keine Nahrung */ + ADDMSG(&mage->faction->msgs, msg_message("rust_fail", "mage target", mage, + u)); + } + } + /* in success stehen nun die insgesamt zerstoerten Waffen. Im + * unguenstigsten Fall kann pro Stufe nur eine Waffe verzaubert werden, + * darum wird hier nur fuer alle Faelle in denen noch weniger Waffen + * betroffen wurden ein Kostennachlass gegeben */ + return _min(success, cast_level); +} + +/* ------------------------------------------------------------- */ +/* Name: Kaelteschutz + * Stufe: 3 + * Kategorie: Einheit, positiv + * Gebiet: Gwyrrd + * + * Wirkung: + * schuetzt ein bis mehrere Einheiten mit bis zu Stufe*10 Insekten vor + * den Auswirkungen der Kaelte. Sie koennen Gletscher betreten und dort + * ganz normal alles machen. Die Wirkung haelt Stufe Wochen an + * Insekten haben in Gletschern den selben Malus wie in Bergen. Zu + * lange drin, nicht mehr aendern + * + * Flag: + * (UNITSPELL | SPELLLEVEL | ONSHIPCAST | TESTCANSEE) + */ +/* Syntax: ZAUBER [STUFE n] "Kaelteschutz" eh1 [eh2 [eh3 [...]]] */ + +static int sp_kaelteschutz(castorder * co) +{ + unit *u; + int n, i = 0; + int men; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + int duration = _max(cast_level, (int)force) + 1; + spellparameter *pa = co->par; + float effect; + + force *= 10; /* 10 Personen pro Force-Punkt */ + + /* fuer jede Einheit in der Kommandozeile */ + for (n = 0; n < pa->length; n++) { + if (force < 1) + break; + + if (pa->param[n]->flag == TARGET_RESISTS + || pa->param[n]->flag == TARGET_NOTFOUND) + continue; + + u = pa->param[n]->data.u; + + if (force < u->number) { + men = (int)force; + } + else { + men = u->number; + } + + effect = 1; + create_curse(mage, &u->attribs, ct_find("insectfur"), (float)cast_level, + duration, effect, men); + + force -= u->number; + ADDMSG(&mage->faction->msgs, msg_message("heat_effect", "mage target", mage, + u)); + if (u->faction != mage->faction) + ADDMSG(&u->faction->msgs, msg_message("heat_effect", "mage target", + cansee(u->faction, r, mage, 0) ? mage : NULL, u)); + i = cast_level; + } + /* Erstattung? */ + return i; +} + +/* ------------------------------------------------------------- */ +/* Name: Verwuenschung, Funkenregen, Naturfreund, ... + * Stufe: 1 + * Kategorie: Einheit, rein visuell + * Gebiet: Alle + * + * Wirkung: + * Die Einheit wird von einem magischen Effekt heimgesucht, der in ihrer + * Beschreibung auftaucht, aber nur visuellen Effekt hat. + * + * Flag: + * (UNITSPELL | TESTCANSEE | SPELLLEVEL) + */ +/* Syntax: ZAUBER "Funkenregen" eh1 */ + +static int sp_sparkle(castorder * co) +{ + unit *u; + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + int duration = cast_level + 1; + float effect; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber + * abbrechen aber kosten lassen */ + if (pa->param[0]->flag == TARGET_RESISTS) + return cast_level; + + u = pa->param[0]->data.u; + effect = (float)(rng_int() % 0xffffff); + create_curse(mage, &u->attribs, ct_find("sparkle"), (float)cast_level, + duration, effect, u->number); + + ADDMSG(&mage->faction->msgs, msg_message("sparkle_effect", "mage target", + mage, u)); + if (u->faction != mage->faction) { + ADDMSG(&u->faction->msgs, msg_message("sparkle_effect", "mage target", mage, + u)); + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Eisengolem + * Stufe: 2 + * Kategorie: Beschwoerung, positiv + * Gebiet: Gwyrrd + * Wirkung: + * Erschafft eine Einheit Eisengolems mit Stufe*8 Golems. Jeder Golem + * hat jede Runde eine Chance von 15% zu Staub zu zerfallen. Gibt man + * den Golems den Befehl 'mache Schwert/Bihaender' oder 'mache + * Schild/Kettenhemd/Plattenpanzer', so werden pro Golem 5 Eisenbarren + * verbaut und der Golem loest sich auf. + * + * Golems sind zu langsam um wirklich im Kampf von Nutzen zu sein. + * Jedoch fangen sie eine Menge Schaden auf und sollten sie zufaellig + * treffen, so ist der Schaden fast immer toedlich. (Eisengolem: HP + * 50, AT 4, PA 2, Ruestung 2(KH), 2d10+4 TP, Magieresistenz 0.25) + * + * Golems nehmen nix an und geben nix. Sie bewegen sich immer nur 1 + * Region weit und ziehen aus Strassen keinen Nutzen. Ein Golem wiegt + * soviel wie ein Stein. Kann nicht im Sumpf gezaubert werden + * + * Flag: + * (SPELLLEVEL) + * + * #define GOLEM_IRON 4 + */ + +static int sp_create_irongolem(castorder * co) +{ + unit *u2; + attrib *a; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + int number = lovar(force * 8 * RESOURCE_QUANTITY); + if (number < 1) + number = 1; + + if (r->terrain == newterrain(T_SWAMP)) { + cmistake(mage, co->order, 188, MSG_MAGIC); + return 0; + } + + u2 = + create_unit(r, mage->faction, number, rc_find("irongolem"), 0, NULL, mage); + + set_level(u2, SK_ARMORER, 1); + set_level(u2, SK_WEAPONSMITH, 1); + + a = a_new(&at_unitdissolve); + a->data.ca[0] = 0; + a->data.ca[1] = IRONGOLEM_CRUMBLE; + a_add(&u2->attribs, a); + + ADDMSG(&mage->faction->msgs, + msg_message("magiccreate_effect", "region command unit amount object", + mage->region, co->order, mage, number, + LOC(mage->faction->locale, rc_name(rc_find("irongolem"), (u2->number == 1) ? NAME_SINGULAR : NAME_PLURAL)))); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Steingolem + * Stufe: 1 + * Kategorie: Beschwoerung, positiv + * Gebiet: Gwyrrd + * Wirkung: + * Erschafft eine Einheit Steingolems mit Stufe*5 Golems. Jeder Golem + * hat jede Runde eine Chance von 10% zu Staub zu zerfallen. Gibt man + * den Golems den Befehl 'mache Burg' oder 'mache Strasse', so werden + * pro Golem 10 Steine verbaut und der Golem loest sich auf. + * + * Golems sind zu langsam um wirklich im Kampf von Nutzen zu sein. + * Jedoch fangen sie eine Menge Schaden auf und sollten sie zufaellig + * treffen, so ist der Schaden fast immer toedlich. (Steingolem: HP 60, + * AT 4, PA 2, Ruestung 4(PP), 2d12+6 TP) + * + * Golems nehmen nix an und geben nix. Sie bewegen sich immer nur 1 + * Region weit und ziehen aus Strassen keinen Nutzen. Ein Golem wiegt + * soviel wie ein Stein. + * + * Kann nicht im Sumpf gezaubert werden + * + * Flag: + * (SPELLLEVEL) + * + * #define GOLEM_STONE 4 + */ +static int sp_create_stonegolem(castorder * co) +{ + unit *u2; + attrib *a; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + int number = lovar(co->force * 5 * RESOURCE_QUANTITY); + if (number < 1) + number = 1; + + if (r->terrain == newterrain(T_SWAMP)) { + cmistake(mage, co->order, 188, MSG_MAGIC); + return 0; + } + + u2 = + create_unit(r, mage->faction, number, rc_find("stonegolem"), 0, NULL, mage); + set_level(u2, SK_ROAD_BUILDING, 1); + set_level(u2, SK_BUILDING, 1); + + a = a_new(&at_unitdissolve); + a->data.ca[0] = 0; + a->data.ca[1] = STONEGOLEM_CRUMBLE; + a_add(&u2->attribs, a); + + ADDMSG(&mage->faction->msgs, + msg_message("magiccreate_effect", "region command unit amount object", + mage->region, co->order, mage, number, + LOC(mage->faction->locale, rc_name(rc_find("stonegolem"), (u2->number == 1) ? NAME_SINGULAR : NAME_PLURAL)))); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Große Duerre + * Stufe: 17 + * Kategorie: Region, negativ + * Gebiet: Gwyrrd + * + * Wirkung: + * 50% alle Bauern, Pferde, Baeume sterben. + * Zu 25% terraform: Gletscher wird mit 50% zu Sumpf, sonst Ozean, + * Sumpf wird zu Steppe, Ebene zur Steppe, Steppe zur Wueste. + * Besonderheiten: + * neuer Terraintyp Steppe: + * 5000 Felder, 500 Baeume, Strasse: 250 Steine. Anlegen wie in Ebene + * moeglich + * + * Flags: + * (FARCASTING | REGIONSPELL | TESTRESISTANCE) + */ + +static void destroy_all_roads(region * r) +{ + int i; + + for (i = 0; i < MAXDIRECTIONS; i++) { + rsetroad(r, (direction_t)i, 0); + } +} + +static int sp_great_drought(castorder * co) +{ + unit *u; + bool terraform = false; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + int duration = 2; + float effect; + + if (fval(r->terrain, SEA_REGION)) { + cmistake(mage, co->order, 189, MSG_MAGIC); + /* TODO: vielleicht einen netten Patzer hier? */ + return 0; + } + + /* sterben */ + rsetpeasants(r, rpeasants(r) / 2); /* evtl wuerfeln */ + rsettrees(r, 2, rtrees(r, 2) / 2); + rsettrees(r, 1, rtrees(r, 1) / 2); + rsettrees(r, 0, rtrees(r, 0) / 2); + rsethorses(r, rhorses(r) / 2); + + /* Arbeitslohn = 1/4 */ + effect = 4.0; /* curses: higher is stronger */ + create_curse(mage, &r->attribs, ct_find("drought"), force, duration, effect, + 0); + + /* terraforming */ + if (rng_int() % 100 < 25) { + terraform = true; + + switch (rterrain(r)) { + case T_PLAIN: + /* rsetterrain(r, T_GRASSLAND); */ + destroy_all_roads(r); + break; + + case T_SWAMP: + /* rsetterrain(r, T_GRASSLAND); */ + destroy_all_roads(r); + break; + /* + case T_GRASSLAND: + rsetterrain(r, T_DESERT); + destroy_all_roads(r); + break; + */ + case T_GLACIER: + if (rng_int() % 100 < 50) { + rsetterrain(r, T_SWAMP); + destroy_all_roads(r); + } + else { /* Ozean */ + destroy_all_roads(r); + rsetterrain(r, T_OCEAN); + /* Einheiten duerfen hier auf keinen Fall geloescht werden! */ + for (u = r->units; u; u = u->next) { + if (u_race(u) != get_race(RC_SPELL) && u->ship == 0) { + set_number(u, 0); + } + } + while (r->buildings) { + remove_building(&r->buildings, r->buildings); + } + } + break; + + default: + terraform = false; + break; + } + } + + if (!fval(r->terrain, SEA_REGION)) { + /* not destroying the region, so it should be safe to make this a local + * message */ + message *msg; + const char *mtype; + if (r->terrain == newterrain(T_SWAMP) && terraform) { + mtype = "drought_effect_1"; + } + else if (!terraform) { + mtype = "drought_effect_2"; + } + else { + mtype = "drought_effect_3"; + } + msg = msg_message(mtype, "mage region", mage, r); + add_message(&r->msgs, msg); + msg_release(msg); + } + else { + /* possible that all units here get killed so better to inform with a global + * message */ + message *msg = msg_message("drought_effect_4", "mage region", mage, r); + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) { + if (!fval(u->faction, FFL_SELECT)) { + fset(u->faction, FFL_SELECT); + add_message(&u->faction->msgs, msg); + } + } + if (!fval(mage->faction, FFL_SELECT)) { + add_message(&mage->faction->msgs, msg); + } + msg_release(msg); + } + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: 'Weg der Baeume' + * Stufe: 9 + * Kategorie: Teleport + * Gebiet: Gwyrrd + * Wirkung: + * Der Druide kann 5*Stufe GE in die astrale Ebene schicken. + * Der Druide wird nicht mitteleportiert, es sei denn, er gibt sich + * selbst mit an. + * Der Zauber funktioniert nur in Waeldern. + * + * Syntax: Zauber "Weg der Baeume" ... + * + * Flags: + * (UNITSPELL | SPELLLEVEL | TESTCANSEE) + */ +static int sp_treewalkenter(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + spellparameter *pa = co->par; + float power = co->force; + int cast_level = co->level; + region *rt; + int remaining_cap; + int n; + int erfolg = 0; + + if (getplane(r) != 0) { + cmistake(mage, co->order, 190, MSG_MAGIC); + return 0; + } + + if (!r_isforest(r)) { + cmistake(mage, co->order, 191, MSG_MAGIC); + return 0; + } + + rt = r_standard_to_astral(r); + if (rt == NULL || is_cursed(rt->attribs, C_ASTRALBLOCK, 0) + || fval(rt->terrain, FORBIDDEN_REGION)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralblock", "")); + return 0; + } + + remaining_cap = (int)(power * 500); + + /* fuer jede Einheit */ + for (n = 0; n < pa->length; n++) { + unit *u = pa->param[n]->data.u; + spllprm *param = pa->param[n]; + + if (param->flag & (TARGET_RESISTS | TARGET_NOTFOUND)) { + continue; + } + + if (!ucontact(u, mage)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact", "target", u)); + } + else { + int w; + message *m; + unit *u2; + + if (!can_survive(u, rt)) { + cmistake(mage, co->order, 231, MSG_MAGIC); + continue; + } + + w = weight(u); + if (remaining_cap - w < 0) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "fail_tooheavy", "target", u)); + continue; + } + remaining_cap = remaining_cap - w; + move_unit(u, rt, NULL); + erfolg = cast_level; + + /* Meldungen in der Ausgangsregion */ + for (u2 = r->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = r->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, r, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_disappear", "unit", u); + r_addmessage(r, u2->faction, m); + } + } + } + if (m) + msg_release(m); + + /* Meldungen in der Zielregion */ + for (u2 = rt->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = rt->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, rt, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_appear", "unit", u); + r_addmessage(rt, u2->faction, m); + } + } + } + if (m) + msg_release(m); + } + } + return erfolg; +} + +/* ------------------------------------------------------------- */ +/* Name: 'Sog des Lebens' + * Stufe: 9 + * Kategorie: Teleport + * Gebiet: Gwyrrd + * Wirkung: + * Der Druide kann 5*Stufe GE aus die astrale Ebene schicken. Der + * Druide wird nicht mitteleportiert, es sei denn, er gibt sich selbst + * mit an. + * Der Zauber funktioniert nur, wenn die Zielregion ein Wald ist. + * + * Syntax: Zauber "Sog des Lebens" ... + * + * Flags: + * (UNITSPELL|SPELLLEVEL) + */ +static int sp_treewalkexit(castorder * co) +{ + region *rt; + region_list *rl, *rl2; + int tax, tay; + unit *u, *u2; + int remaining_cap; + int n; + int erfolg = 0; + region *r = co_get_region(co); + unit *mage = co->magician.u; + float power = co->force; + spellparameter *pa = co->par; + int cast_level = co->level; + + if (!is_astral(r)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralonly", "")); + return 0; + } + if (is_cursed(r->attribs, C_ASTRALBLOCK, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralblock", "")); + return 0; + } + + remaining_cap = (int)(power * 500); + + if (pa->param[0]->typ != SPP_REGION) { + report_failure(mage, co->order); + return 0; + } + + /* Koordinaten setzen und Region loeschen fuer Überpruefung auf + * Gueltigkeit */ + rt = pa->param[0]->data.r; + tax = rt->x; + tay = rt->y; + rt = NULL; + + rl = astralregions(r, inhabitable); + rt = 0; + + rl2 = rl; + while (rl2) { + if (rl2->data->x == tax && rl2->data->y == tay) { + rt = rl2->data; + break; + } + rl2 = rl2->next; + } + free_regionlist(rl); + + if (!rt) { + cmistake(mage, co->order, 195, MSG_MAGIC); + return 0; + } + + if (!r_isforest(rt)) { + cmistake(mage, co->order, 196, MSG_MAGIC); + return 0; + } + + /* fuer jede Einheit in der Kommandozeile */ + for (n = 1; n < pa->length; n++) { + if (pa->param[n]->flag == TARGET_RESISTS + || pa->param[n]->flag == TARGET_NOTFOUND) + continue; + + u = pa->param[n]->data.u; + + if (!ucontact(u, mage)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact", "target", u)); + } + else { + int w = weight(u); + if (!can_survive(u, rt)) { + cmistake(mage, co->order, 231, MSG_MAGIC); + } + else if (remaining_cap - w < 0) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "fail_tooheavy", "target", u)); + } + else { + message *m; + + remaining_cap = remaining_cap - w; + move_unit(u, rt, NULL); + erfolg = cast_level; + + /* Meldungen in der Ausgangsregion */ + + for (u2 = r->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = r->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, r, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_disappear", "unit", u); + r_addmessage(r, u2->faction, m); + } + } + } + if (m) + msg_release(m); + + /* Meldungen in der Zielregion */ + + for (u2 = rt->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = rt->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, rt, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_appear", "unit", u); + r_addmessage(rt, u2->faction, m); + } + } + } + if (m) + msg_release(m); + } + } + } + return erfolg; +} + +/* ------------------------------------------------------------- */ +/* Name: Heiliger Boden + * Stufe: 9 + * Kategorie: perm. Regionszauber + * Gebiet: Gwyrrd + * Wirkung: + * Es entstehen keine Untoten mehr, Untote betreten die Region + * nicht mehr. + * + * ZAUBER "Heiliger Boden" + * Flags: (0) + */ +static int sp_holyground(castorder * co) +{ + static const curse_type *ctype = NULL; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + message *msg = msg_message("sp_holyground_effect", "mage region", mage, r); + report_spell(mage, r, msg); + msg_release(msg); + + if (!ctype) { + ctype = ct_find("holyground"); + } + create_curse(mage, &r->attribs, ctype, power * power, 1, zero_effect, 0); + + a_removeall(&r->attribs, &at_deathcount); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Heimstein + * Stufe: 7 + * Kategorie: Artefakt + * Gebiet: Gwyrrd + * Wirkung: + * Die Burg kann nicht mehr durch Donnerbeben oder andere + * Gebaeudezerstoerenden Sprueche kaputt gemacht werden. Auch + * schuetzt der Zauber vor Belagerungskatapulten. + * + * ZAUBER Heimstein + * Flags: (0) + */ +static int sp_homestone(castorder * co) +{ + unit *u; + curse *c; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + float effect; + message *msg; + if (!mage->building || mage->building->type != bt_find("castle")) { + cmistake(mage, co->order, 197, MSG_MAGIC); + return 0; + } + + c = create_curse(mage, &mage->building->attribs, ct_find("magicwalls"), + force * force, 1, zero_effect, 0); + + if (c == NULL) { + cmistake(mage, co->order, 206, MSG_MAGIC); + return 0; + } + c_setflag(c, CURSE_NOAGE | CURSE_ONLYONE); + + /* Magieresistenz der Burg erhoeht sich um 50% */ + effect = 50.0F; + c = create_curse(mage, &mage->building->attribs, + ct_find("magicresistance"), force * force, 1, effect, 0); + c_setflag(c, CURSE_NOAGE); + + /* melden, 1x pro Partei in der Burg */ + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + msg = msg_message("homestone_effect", "mage building", mage, mage->building); + for (u = r->units; u; u = u->next) { + if (!fval(u->faction, FFL_SELECT)) { + fset(u->faction, FFL_SELECT); + if (u->building == mage->building) { + r_addmessage(r, u->faction, msg); + } + } + } + msg_release(msg); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Duerre + * Stufe: 13 + * Kategorie: Region, negativ + * Gebiet: Gwyrrd + * Wirkung: + * temporaer veraendert sich das Baummaximum und die maximalen Felder in + * einer Region auf die Haelfte des normalen. + * Die Haelfte der Baeume verdorren und Pferde verdursten. + * Arbeiten bringt nur noch 1/4 des normalen Verdienstes + * + * Flags: + * (FARCASTING|REGIONSPELL|TESTRESISTANCE), + */ +static int sp_drought(castorder * co) +{ + curse *c; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + int duration = (int)power + 1; + message *msg; + + if (fval(r->terrain, SEA_REGION)) { + cmistake(mage, co->order, 189, MSG_MAGIC); + /* TODO: vielleicht einen netten Patzer hier? */ + return 0; + } + + /* melden, 1x pro Partei */ + msg = msg_message("sp_drought_effect", "mage region", mage, r); + report_spell(mage, r, msg); + msg_release(msg); + + /* Wenn schon Duerre herrscht, dann setzen wir nur den Power-Level + * hoch (evtl dauert dann die Duerre laenger). Ansonsten volle + * Auswirkungen. + */ + c = get_curse(r->attribs, ct_find("drought")); + if (c) { + c->vigour = _max(c->vigour, power); + c->duration = _max(c->duration, (int)power); + } + else { + float effect = 4.0; + /* Baeume und Pferde sterben */ + rsettrees(r, 2, rtrees(r, 2) / 2); + rsettrees(r, 1, rtrees(r, 1) / 2); + rsettrees(r, 0, rtrees(r, 0) / 2); + rsethorses(r, rhorses(r) / 2); + + create_curse(mage, &r->attribs, ct_find("drought"), power, duration, effect, + 0); + } + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Bergwaechter + * Stufe: 9 + * Gebiet: Gwyrrd + * Kategorie: Beschwoerung, negativ + * + * Wirkung: + * Erschafft in Bergen oder Gletschern einen Waechter, der durch bewachen + * den Eisen/Laen-Abbau fuer nicht-Allierte verhindert. Bergwaechter + * verhindern auch Abbau durch getarnte/unsichtbare Einheiten und lassen + * sich auch durch Belagerungen nicht aufhalten. + * + * (Ansonsten in economic.c:manufacture() entsprechend anpassen). + * + * Faehigkeiten (factypes.c): 50% Magieresistenz, 25 HP, 4d4 Schaden, + * 4 Ruestung (=PP) + * Flags: + * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_ironkeeper(castorder * co) +{ + unit *keeper; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + message *msg; + + if (r->terrain != newterrain(T_MOUNTAIN) + && r->terrain != newterrain(T_GLACIER)) { + report_failure(mage, co->order); + return 0; + } + + keeper = + create_unit(r, mage->faction, 1, get_race(RC_IRONKEEPER), 0, NULL, mage); + + /*keeper->age = cast_level + 2; */ + setstatus(keeper, ST_AVOID); /* kaempft nicht */ + guard(keeper, GUARD_MINING); + fset(keeper, UFL_ISNEW); + /* Parteitarnen, damit man nicht sofort weiß, wer dahinter steckt */ + if (rule_stealth_faction()) { + fset(keeper, UFL_ANON_FACTION); + } + + { + trigger *tkill = trigger_killunit(keeper); + add_trigger(&keeper->attribs, "timer", trigger_timeout(cast_level + 2, + tkill)); + } + + msg = msg_message("summon_effect", "mage amount race", mage, 1, u_race(keeper)); + r_addmessage(r, NULL, msg); + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Sturmwind - Beschwoere einen Sturmelementar + * Stufe: 6 + * Gebiet: Gwyrrd + * + * Wirkung: + * Verdoppelt Geschwindigkeit aller angegebener Schiffe fuer diese + * Runde. Kombinierbar mit "Guenstige Winde", aber nicht mit + * "Luftschiff". + * + * Anstelle des alten ship->enchanted benutzen wir einen kurzfristigen + * Curse. Das ist zwar ein wenig aufwendiger, aber weitaus flexibler + * und erlaubt es zB, die Dauer spaeter problemlos zu veraendern. + * + * Flags: + * (SHIPSPELL|ONSHIPCAST|OCEANCASTABLE|TESTRESISTANCE) + */ + +static int sp_stormwinds(castorder * co) +{ + ship *sh; + unit *u; + int erfolg = 0; + region *r = co_get_region(co); + unit *mage = co->magician.u; + float power = co->force; + spellparameter *pa = co->par; + int n, force = (int)power; + message *m = NULL; + + /* melden vorbereiten */ + freset(mage->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + + for (n = 0; n < pa->length; n++) { + if (force <= 0) + break; + + if (pa->param[n]->flag == TARGET_RESISTS + || pa->param[n]->flag == TARGET_NOTFOUND) + continue; + + sh = pa->param[n]->data.sh; + + /* mit C_SHIP_NODRIFT haben wir kein Problem */ + if (is_cursed(sh->attribs, C_SHIP_FLYING, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "error_spell_on_flying_ship", "ship", sh)) + continue; + } + if (is_cursed(sh->attribs, C_SHIP_SPEEDUP, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "error_spell_on_ship_already", "ship", sh)) + continue; + } + + /* Duration = 1, nur diese Runde */ + create_curse(mage, &sh->attribs, ct_find("stormwind"), power, 1, + zero_effect, 0); + /* Da der Spruch nur diese Runde wirkt wird er nie im Report + * erscheinen */ + erfolg++; + force--; + + /* melden vorbereiten: */ + for (u = r->units; u; u = u->next) { + if (u->ship == sh) { + /* nur den Schiffsbesatzungen! */ + fset(u->faction, FFL_SELECT); + } + } + } + if (erfolg < pa->length) { + ADDMSG(&mage->faction->msgs, msg_message("stormwinds_reduced", + "unit ships maxships", mage, erfolg, pa->length)); + } + /* melden, 1x pro Partei auf Schiff und fuer den Magier */ + fset(mage->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) { + if (fval(u->faction, FFL_SELECT)) { + freset(u->faction, FFL_SELECT); + if (erfolg > 0) { + if (!m) { + m = msg_message("stormwinds_effect", "unit", mage); + } + r_addmessage(r, u->faction, m); + } + } + } + if (m) + msg_release(m); + return erfolg; +} + +/* ------------------------------------------------------------- */ +/* Name: Donnerbeben + * Stufe: 6 + * Gebiet: Gwyrrd + * + * Wirkung: + * Zerstoert Stufe*10 "Steineinheiten" aller Gebaeude der Region, aber nie + * mehr als 25% des gesamten Gebaeudes (aber natuerlich mindestens ein + * Stein). + * + * Flags: + * (FARCASTING|REGIONSPELL|TESTRESISTANCE) + */ +static int sp_earthquake(castorder * co) +{ + int kaputt; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + message *msg; + building **blist = &r->buildings; + + while (*blist) { + building *burg = *blist; + + if (burg->size != 0 && !is_cursed(burg->attribs, C_MAGICWALLS, 0)) { + /* Magieresistenz */ + if (!target_resists_magic(mage, burg, TYP_BUILDING, 0)) { + kaputt = _min(10 * cast_level, burg->size / 4); + kaputt = _max(kaputt, 1); + burg->size -= kaputt; + if (burg->size == 0) { + /* TODO: sollten die Insassen nicht Schaden nehmen? */ + remove_building(blist, burg); + } + } + } + if (*blist == burg) + blist = &burg->next; + } + + /* melden, 1x pro Partei */ + msg = msg_message("earthquake_effect", "mage region", mage, r); + r_addmessage(r, NULL, msg); + msg_release(msg); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* CHAOS / M_DRAIG / Draig */ +/* ------------------------------------------------------------- */ +void patzer_peasantmob(const castorder * co) +{ + int anteil = 6, n; + unit *u; + attrib *a; + region *r; + unit *mage = co->magician.u; + + r = mage->region->land ? mage->region : co_get_region(co); + + if (r->land) { + faction *f = get_monsters(); + const struct locale *lang = f->locale; + message *msg; + + anteil += rng_int() % 4; + n = rpeasants(r) * anteil / 10; + rsetpeasants(r, rpeasants(r) - n); + assert(rpeasants(r) >= 0); + + u = + create_unit(r, f, n, get_race(RC_PEASANT), 0, LOC(f->locale, "angry_mob"), + NULL); + fset(u, UFL_ISNEW); + /* guard(u, GUARD_ALL); hier zu frueh! Befehl BEWACHE setzten */ + addlist(&u->orders, create_order(K_GUARD, lang, NULL)); + set_order(&u->thisorder, default_order(lang)); + a = a_new(&at_unitdissolve); + a->data.ca[0] = 1; /* An rpeasants(r). */ + a->data.ca[1] = 10; /* 10% */ + a_add(&u->attribs, a); + a_add(&u->attribs, make_hate(mage)); + + msg = msg_message("mob_warning", ""); + r_addmessage(r, NULL, msg); + msg_release(msg); + } + return; +} + +/* ------------------------------------------------------------- */ +/* Name: Waldbrand + * Stufe: 10 + * Kategorie: Region, negativ + * Gebiet: Draig + * Wirkung: + * Vernichtet 10-80% aller Baeume in der Region. Kann sich auf benachbarte + * Regionen ausbreiten, wenn diese (stark) bewaldet sind. Fuer jeweils + * 10 verbrannte Baeume in der Startregion gibts es eine 1%-Chance, dass + * sich das Feuer auf stark bewaldete Nachbarregionen ausdehnt, auf + * bewaldeten mit halb so hoher Wahrscheinlichkeit. Dort verbrennen + * dann prozentual halbsoviele bzw ein viertel soviele Baeume wie in der + * Startregion. + * + * Im Extremfall: 1250 Baeume in Region, 80% davon verbrennen (1000). + * Dann breitet es sich mit 100% Chance in stark bewaldete Regionen + * aus, mit 50% in bewaldete. Dort verbrennen dann 40% bzw 20% der Baeume. + * Weiter als eine Nachbarregion breitet sich dass Feuer nicht aus. + * + * Sinn: Ein Feuer in einer "stark bewaldeten" Wueste hat so trotzdem kaum + * eine Chance, sich weiter auszubreiten, waehrend ein Brand in einem Wald + * sich fast mit Sicherheit weiter ausbreitet. + * + * Flags: + * (FARCASTING | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_forest_fire(castorder * co) +{ + unit *u; + region *nr; + direction_t i; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + double probability; + double percentage = (rng_int() % 8 + 1) * 0.1; /* 10 - 80% */ + message *msg; + + int vernichtet_schoesslinge = (int)(rtrees(r, 1) * percentage); + int destroyed = (int)(rtrees(r, 2) * percentage); + + if (destroyed < 1) { + cmistake(mage, co->order, 198, MSG_MAGIC); + return 0; + } + + rsettrees(r, 2, rtrees(r, 2) - destroyed); + rsettrees(r, 1, rtrees(r, 1) - vernichtet_schoesslinge); + probability = destroyed * 0.001; /* Chance, dass es sich ausbreitet */ + + /* melden, 1x pro Partei */ + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + msg = + msg_message("forestfire_effect", "mage region amount", mage, r, + destroyed + vernichtet_schoesslinge); + r_addmessage(r, NULL, msg); + add_message(&mage->faction->msgs, msg); + msg_release(msg); + + for (i = 0; i < MAXDIRECTIONS; i++) { + nr = rconnect(r, i); + assert(nr); + destroyed = 0; + vernichtet_schoesslinge = 0; + + if (rtrees(nr, 2) + rtrees(nr, 1) >= 800) { + if (chance(probability)) { + destroyed = (int)(rtrees(nr, 2) * percentage / 2); + vernichtet_schoesslinge = (int)(rtrees(nr, 1) * percentage / 2); + } + } + else if (rtrees(nr, 2) + rtrees(nr, 1) >= 600) { + if (chance(probability / 2)) { + destroyed = (int)(rtrees(nr, 2) * percentage / 4); + vernichtet_schoesslinge = (int)(rtrees(nr, 1) * percentage / 4); + } + } + + if (destroyed > 0 || vernichtet_schoesslinge > 0) { + message *m = msg_message("forestfire_spread", "region next trees", + r, nr, destroyed + vernichtet_schoesslinge); + + add_message(&r->msgs, m); + add_message(&mage->faction->msgs, m); + msg_release(m); + + rsettrees(nr, 2, rtrees(nr, 2) - destroyed); + rsettrees(nr, 1, rtrees(nr, 1) - vernichtet_schoesslinge); + } + } + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Chaosfluch + * Stufe: 5 + * Gebiet: Draig + * Kategorie: (Antimagie) Kraftreduzierer, Einheit, negativ + * Wirkung: + * Auf einen Magier gezaubert verhindert/erschwert dieser Chaosfluch + * das Zaubern. Patzer werden warscheinlicher. + * Jeder Zauber muss erst gegen den Wiederstand des Fluchs gezaubert + * werden und schwaecht dessen Antimagiewiederstand um 1. + * Wirkt _max(Stufe(Magier) - Stufe(Ziel), rand(3)) Wochen + * Patzer: + * Magier wird selbst betroffen + * + * Flags: + * (UNITSPELL | SPELLLEVEL | TESTCANSEE | TESTRESISTANCE) + * + */ +static int sp_fumblecurse(castorder * co) +{ + unit *target; + int rx, sx; + int duration; + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + float effect; + curse *c; + spellparameter *pa = co->par; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + target = pa->param[0]->data.u; + + rx = rng_int() % 3; + sx = cast_level - effskill(target, SK_MAGIC); + duration = _max(sx, rx) + 1; + + effect = force / 2; + c = create_curse(mage, &target->attribs, ct_find("fumble"), + force, duration, effect, 0); + if (c == NULL) { + report_failure(mage, co->order); + return 0; + } + + ADDMSG(&target->faction->msgs, msg_message("fumblecurse", "unit region", + target, target->region)); + + return cast_level; +} + +void patzer_fumblecurse(const castorder * co) +{ + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + int duration = (cast_level / 2) + 1; + float effect; + curse *c; + + effect = force / 2; + c = create_curse(mage, &mage->attribs, ct_find("fumble"), force, + duration, effect, 0); + if (c != NULL) { + ADDMSG(&mage->faction->msgs, msg_message("magic_fumble", + "unit region command", mage, mage->region, co->order)); + } + return; +} + +/* ------------------------------------------------------------- */ +/* Name: Drachenruf + * Stufe: 11 + * Gebiet: Draig + * Kategorie: Monster, Beschwoerung, negativ + * + * Wirkung: + * In einer Wueste, Sumpf oder Gletscher gezaubert kann innerhalb der + * naechsten 6 Runden ein bis 6 Dracheneinheiten bis Groeße Wyrm + * entstehen. + * + * Mit Stufe 12-15 erscheinen Jung- oder normaler Drachen, mit Stufe + * 16+ erscheinen normale Drachen oder Wyrme. + * + * Flag: + * (FARCASTING | REGIONSPELL | TESTRESISTANCE) + */ + +static int sp_summondragon(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + unit *u; + int cast_level = co->level; + float power = co->force; + region_list *rl, *rl2; + faction *f; + int time; + int number; + const race *race; + + f = get_monsters(); + + if (r->terrain != newterrain(T_SWAMP) && r->terrain != newterrain(T_DESERT) + && r->terrain != newterrain(T_GLACIER)) { + report_failure(mage, co->order); + return 0; + } + + for (time = 1; time < 7; time++) { + if (rng_int() % 100 < 25) { + switch (rng_int() % 3) { + case 0: + race = get_race(RC_WYRM); + number = 1; + break; + + case 1: + race = get_race(RC_DRAGON); + number = 2; + break; + + case 2: + default: + race = get_race(RC_FIREDRAGON); + number = 6; + break; + } + { + trigger *tsummon = trigger_createunit(r, f, race, number); + add_trigger(&r->attribs, "timer", trigger_timeout(time, tsummon)); + } + } + } + + rl = all_in_range(r, (short)power, NULL); + + for (rl2 = rl; rl2; rl2 = rl2->next) { + region *r2 = rl2->data; + for (u = r2->units; u; u = u->next) { + if (u_race(u) == get_race(RC_WYRM) || u_race(u) == get_race(RC_DRAGON)) { + attrib *a = a_find(u->attribs, &at_targetregion); + if (!a) { + a = a_add(&u->attribs, make_targetregion(r)); + } + else { + a->data.v = r; + } + } + } + } + + ADDMSG(&mage->faction->msgs, msg_message("summondragon", + "unit region command target", mage, mage->region, co->order, r)); + + free_regionlist(rl); + return cast_level; +} + +static int sp_firewall(castorder * co) +{ + connection *b; + wall_data *fd; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + spellparameter *pa = co->par; + direction_t dir; + region *r2; + + dir = get_direction(pa->param[0]->data.xs, mage->faction->locale); + if (dir < MAXDIRECTIONS && dir != NODIRECTION) { + r2 = rconnect(r, dir); + } + else { + report_failure(mage, co->order); + return 0; + } + + if (!r2 || r2 == r) { + report_failure(mage, co->order); + return 0; + } + + b = get_borders(r, r2); + while (b != NULL) { + if (b->type == &bt_firewall) + break; + b = b->next; + } + if (b == NULL) { + b = new_border(&bt_firewall, r, r2); + fd = (wall_data *)b->data.v; + fd->force = (int)(force / 2 + 0.5); + fd->mage = mage; + fd->active = false; + fd->countdown = cast_level + 1; + } + else { + fd = (wall_data *)b->data.v; + fd->force = (int)_max(fd->force, force / 2 + 0.5); + fd->countdown = _max(fd->countdown, cast_level + 1); + } + + /* melden, 1x pro Partei */ + { + message *seen = msg_message("firewall_effect", "mage region", mage, r); + message *unseen = msg_message("firewall_effect", "mage region", NULL, r); + report_effect(r, mage, seen, unseen); + msg_release(seen); + msg_release(unseen); + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Unheilige Kraft + * Stufe: 10 + * Gebiet: Draig + * Kategorie: Untote Einheit, positiv + * + * Wirkung: + * transformiert (Stufe)W10 Untote in ihre staerkere Form + * + * + * Flag: + * (SPELLLEVEL | TESTCANSEE) + */ + +static int sp_unholypower(castorder * co) +{ + region * r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + int i; + int n; + int wounds; + + n = dice((int)co->force, 10); + + for (i = 0; i < pa->length && n > 0; i++) { + const race *target_race; + unit *u; + + if (pa->param[i]->flag == TARGET_RESISTS + || pa->param[i]->flag == TARGET_NOTFOUND) + continue; + + u = pa->param[i]->data.u; + + switch (old_race(u_race(u))) { + case RC_SKELETON: + target_race = get_race(RC_SKELETON_LORD); + break; + case RC_ZOMBIE: + target_race = get_race(RC_ZOMBIE_LORD); + break; + case RC_GHOUL: + target_race = get_race(RC_GHOUL_LORD); + break; + default: + cmistake(mage, co->order, 284, MSG_MAGIC); + continue; + } + /* Untote heilen nicht, darum den neuen Untoten maximale hp geben + * und vorhandene Wunden abziehen */ + wounds = unit_max_hp(u) * u->number - u->hp; + + if (u->number <= n) { + n -= u->number; + u->irace = NULL; + u_setrace(u, target_race); + u->hp = unit_max_hp(u) * u->number - wounds; + ADDMSG(&r->msgs, msg_message("unholypower_effect", + "mage target race", mage, u, target_race)); + } + else { + unit *un; + + /* Wird hoffentlich niemals vorkommen. Es gibt im Source + * vermutlich eine ganze Reihe von Stellen, wo das nicht + * korrekt abgefangen wird. Besser (aber nicht gerade einfach) + * waere es, eine solche Konstruktion irgendwie zu kapseln. */ + if (fval(u, UFL_LOCKED) || fval(u, UFL_HUNGER) + || is_cursed(u->attribs, C_SLAVE, 0)) { + cmistake(mage, co->order, 74, MSG_MAGIC); + continue; + } + /* Verletzungsanteil der transferierten Personen berechnen */ + wounds = wounds * n / u->number; + + un = create_unit(r, u->faction, 0, target_race, 0, NULL, u); + transfermen(u, un, n); + un->hp = unit_max_hp(un) * n - wounds; + ADDMSG(&r->msgs, msg_message("unholypower_limitedeffect", + "mage target race amount", mage, u, target_race, n)); + n = 0; + } + } + + return cast_level; +} + +static int dc_age(struct curse *c) +/* age returns 0 if the attribute needs to be removed, !=0 otherwise */ +{ + region *r = (region *)c->data.v; + unit **up; + unit *mage = c->magician; + + if (r == NULL || mage == NULL || mage->number == 0) { + /* if the mage disappears, so does the spell. */ + return AT_AGE_REMOVE; + } + + up = &r->units; + if (curse_active(c)) + while (*up != NULL) { + unit *u = *up; + double damage = c->effect * u->number; + + freset(u->faction, FFL_SELECT); + if (u->number <= 0 || target_resists_magic(mage, u, TYP_UNIT, 0)) { + up = &u->next; + continue; + } + + /* Reduziert durch Magieresistenz */ + damage *= (1.0 - magic_resistance(u)); + change_hitpoints(u, -(int)damage); + + if (*up == u) + up = &u->next; + } + + return AT_AGE_KEEP; +} + +static struct curse_type ct_deathcloud = { + "deathcloud", CURSETYP_REGION, 0, NO_MERGE, cinfo_simple, NULL, NULL, NULL, + NULL, dc_age +}; + +static curse *mk_deathcloud(unit * mage, region * r, float force, int duration) +{ + float effect; + curse *c; + + effect = force / 2; + c = + create_curse(mage, &r->attribs, &ct_deathcloud, force, duration, effect, 0); + c->data.v = r; + return c; +} + +#define COMPAT_DEATHCLOUD +#ifdef COMPAT_DEATHCLOUD +static int dc_read_compat(struct attrib *a, void *target, struct storage * store) +/* return AT_READ_OK on success, AT_READ_FAIL if attrib needs removal */ +{ + region *r = NULL; + unit *u; + variant var; + int duration; + float strength; + int rx, ry; + + READ_INT(store, &duration); + READ_FLT(store, &strength); + READ_INT(store, &var.i); + u = findunit(var.i); + + /* this only affects really old data. no need to change: */ + READ_INT(store, &rx); + READ_INT(store, &ry); + r = findregion(rx, ry); + + if (r != NULL) { + float effect; + curse *c; + + effect = strength; + c = + create_curse(u, &r->attribs, &ct_deathcloud, strength * 2, duration, + effect, 0); + c->data.v = r; + if (u == NULL) { + ur_add(var, &c->magician, resolve_unit); + } + } + return AT_READ_FAIL; /* we don't care for the attribute. */ +} + +attrib_type at_deathcloud_compat = { + "zauber_todeswolke", NULL, NULL, NULL, NULL, dc_read_compat +}; +#endif + +/* ------------------------------------------------------------- */ +/* Name: Todeswolke +* Stufe: 11 +* Gebiet: Draig +* Kategorie: Region, negativ +* +* Wirkung: +* Personen in der Region verlieren stufe/2 Trefferpunkte pro Runde. +* Dauer force/2 +* Wirkt gegen MR +* Ruestung wirkt nicht +* Patzer: +* Magier geraet in den Staub und verliert zufaellige Zahl von HP bis +* auf _max(hp,2) +* Besonderheiten: +* Nicht als curse implementiert, was schlecht ist - man kann dadurch +* kein dispell machen. Wegen fix unter Zeitdruck erstmal nicht zu +* aendern... +* Missbrauchsmoeglichkeit: +* Hat der Magier mehr HP als Rasse des Feindes (extrem: Daemon/Goblin) +* so kann er per Farcasting durch mehrmaliges Zaubern eine +* Nachbarregion ausloeschen. Darum sollte dieser Spruch nur einmal auf +* eine Region gelegt werden koennen. +* +* Flag: +* (FARCASTING | REGIONSPELL | TESTRESISTANCE) +*/ + +static int sp_deathcloud(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + attrib *a = r->attribs; + unit *u; + + while (a) { + if ((a->type->flags & ATF_CURSE)) { + curse *c = a->data.v; + if (c->type == &ct_deathcloud) { + report_failure(mage, co->order); + return 0; + } + a = a->next; + } + else + a = a->nexttype; + } + + mk_deathcloud(mage, r, co->force, co->level); + + /* melden, 1x pro Partei */ + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) { + if (!fval(u->faction, FFL_SELECT)) { + fset(u->faction, FFL_SELECT); + ADDMSG(&u->faction->msgs, msg_message("deathcloud_effect", + "mage region", cansee(u->faction, r, mage, 0) ? mage : NULL, r)); + } + } + + if (!fval(mage->faction, FFL_SELECT)) { + ADDMSG(&mage->faction->msgs, msg_message("deathcloud_effect", + "mage region", mage, r)); + } + + return co->level; +} + +void patzer_deathcloud(castorder * co) +{ + unit *mage = co->magician.u; + int hp = (mage->hp - 2); + + change_hitpoints(mage, -rng_int() % hp); + + ADDMSG(&mage->faction->msgs, msg_message("magic_fumble", + "unit region command", mage, mage->region, co->order)); + + return; +} + +/* ------------------------------------------------------------- */ +/* Name: Pest + * Stufe: 7 + * Gebiet: Draig + * Wirkung: + * ruft eine Pest in der Region hervor. + * Flags: + * (FARCASTING | REGIONSPELL | TESTRESISTANCE) + * Syntax: ZAUBER [REGION x y] "Pest" + */ +static int sp_plague(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + + plagues(r, true); + + ADDMSG(&mage->faction->msgs, msg_message("plague_spell", + "region mage", r, mage)); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Beschwoere Schattendaemon + * Stufe: 8 + * Gebiet: Draig + * Kategorie: Beschwoerung, positiv + * Wirkung: + * Der Magier beschwoert Stufe^2 Schattendaemonen. + * Schattendaemonen haben Tarnung = (Magie_Magier+ Tarnung_Magier)/2 und + * Wahrnehmung 1. Sie haben einen Attacke-Bonus von 8, einen + * Verteidigungsbonus von 11 und machen 2d3 Schaden. Sie entziehen bei + * einem Treffer dem Getroffenen einen Attacke- oder + * Verteidigungspunkt. (50% Chance.) Sie haben 25 Hitpoints und + * Ruestungsschutz 3. + * Flag: + * (SPELLLEVEL) + */ +static int sp_summonshadow(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + unit *u; + int val, number = (int)(force * force); + + u = create_unit(r, mage->faction, number, get_race(RC_SHADOW), 0, NULL, mage); + + /* Bekommen Tarnung = (Magie+Tarnung)/2 und Wahrnehmung 1. */ + val = get_level(mage, SK_MAGIC) + get_level(mage, SK_STEALTH); + + set_level(u, SK_STEALTH, val); + set_level(u, SK_PERCEPTION, 1); + + ADDMSG(&mage->faction->msgs, msg_message("summonshadow_effect", + "mage number", mage, number)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Beschwoere Schattenmeister + * Stufe: 12 + * Gebiet: Draig + * Kategorie: Beschwoerung, positiv + * Wirkung: + * Diese hoeheren Schattendaemonen sind erheblich gefaehrlicher als die + * einfachen Schattendaemonen. Sie haben Tarnung entsprechend dem + * Magietalent des Beschwoerer-1 und Wahrnehmung 5, 75 HP, + * Ruestungsschutz 4, Attacke-Bonus 11 und Verteidigungsbonus 13, machen + * bei einem Treffer 2d4 Schaden, entziehen einen Staerkepunkt und + * entziehen 5 Talenttage in einem zufaelligen Talent. + * Stufe^2 Daemonen. + * + * Flag: + * (SPELLLEVEL) + * */ +static int sp_summonshadowlords(castorder * co) +{ + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + int amount = (int)(force * force); + + u = create_unit(r, mage->faction, amount, get_race(RC_SHADOWLORD), 0, + NULL, mage); + + /* Bekommen Tarnung = Magie und Wahrnehmung 5. */ + set_level(u, SK_STEALTH, get_level(mage, SK_MAGIC)); + set_level(u, SK_PERCEPTION, 5); + + ADDMSG(&mage->faction->msgs, msg_message("summon_effect", "mage amount race", + mage, amount, u_race(u))); + return cast_level; +} + +static bool chaosgate_valid(const connection * b) +{ + const attrib *a = a_findc(b->from->attribs, &at_direction); + if (!a) + a = a_findc(b->to->attribs, &at_direction); + if (!a) + return false; + return true; +} + +static struct region *chaosgate_move(const connection * b, struct unit *u, + struct region *from, struct region *to, bool routing) +{ + if (!routing) { + int maxhp = u->hp / 4; + if (maxhp < u->number) + maxhp = u->number; + u->hp = maxhp; + } + return to; +} + +border_type bt_chaosgate = { + "chaosgate", VAR_NONE, + b_transparent, /* transparent */ + NULL, /* init */ + NULL, /* destroy */ + NULL, /* read */ + NULL, /* write */ + b_blocknone, /* block */ + NULL, /* name */ + b_rinvisible, /* rvisible */ + b_finvisible, /* fvisible */ + b_uinvisible, /* uvisible */ + chaosgate_valid, + chaosgate_move +}; + +/* ------------------------------------------------------------- */ +/* Name: Chaossog + * Stufe: 14 + * Gebiet: Draig + * Kategorie: Teleport + * Wirkung: + * Durch das Opfern von 200 Bauern kann der Chaosmagier ein Tor zur + * astralen Welt oeffnen. Das Tor kann im Folgemonat verwendet werden, + * es loest sich am Ende des Folgemonats auf. + * + * Flag: (0) + */ +static int sp_chaossuction(castorder * co) +{ + region *rt; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + + if (getplane(r) != get_normalplane()) { + /* Der Zauber funktioniert nur in der materiellen Welt. */ + cmistake(mage, co->order, 190, MSG_MAGIC); + return 0; + } + + rt = r_standard_to_astral(r); + + if (rt == NULL || fval(rt->terrain, FORBIDDEN_REGION)) { + /* Hier gibt es keine Verbindung zur astralen Welt. */ + cmistake(mage, co->order, 216, MSG_MAGIC); + return 0; + } + else if (is_cursed(rt->attribs, C_ASTRALBLOCK, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralblock", "")); + return 0; + } + + /* TODO: implement with a building */ + create_special_direction(r, rt, 2, "vortex_desc", "vortex", false); + create_special_direction(rt, r, 2, "vortex_desc", "vortex", false); + new_border(&bt_chaosgate, r, rt); + + add_message(&r->msgs, msg_message("chaosgate_effect_1", "mage", mage)); + add_message(&rt->msgs, msg_message("chaosgate_effect_2", "")); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Magic Boost - Gabe des Chaos + * Stufe: 3 + * Gebiet: Draig + * Kategorie: Einheit, positiv + * + * Wirkung: + * Erhoeht die maximalen Magiepunkte und die monatliche Regeneration auf + * das doppelte. Dauer: 4 Wochen Danach sinkt beides auf die Haelfte des + * normalen ab. + * Dauer: 6 Wochen + * Patzer: + * permanenter Stufen- (Talenttage), Regenerations- oder maxMP-Verlust + * Besonderheiten: + * Patzer koennen waehrend der Zauberdauer haeufiger auftreten derzeit + * +10% + * + * Flag: + * (ONSHIPCAST) + */ + +static int sp_magicboost(castorder * co) +{ + curse *c; + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + float effect; + trigger *tsummon; + static const curse_type *ct_auraboost; + static const curse_type *ct_magicboost; + + if (!ct_auraboost) { + ct_auraboost = ct_find("auraboost"); + ct_magicboost = ct_find("magicboost"); + assert(ct_auraboost != NULL); + assert(ct_magicboost != NULL); + } + /* fehler, wenn schon ein boost */ + if (is_cursed(mage->attribs, C_MBOOST, 0)) { + report_failure(mage, co->order); + return 0; + } + + effect = 6; + c = create_curse(mage, &mage->attribs, ct_magicboost, power, 10, effect, 1); + + /* one aura boost with 200% aura now: */ + effect = 200; + c = create_curse(mage, &mage->attribs, ct_auraboost, power, 4, effect, 1); + + /* and one aura boost with 50% aura in 5 weeks: */ + tsummon = trigger_createcurse(mage, mage, ct_auraboost, power, 6, 50, 1); + add_trigger(&mage->attribs, "timer", trigger_timeout(5, tsummon)); + + ADDMSG(&mage->faction->msgs, msg_message("magicboost_effect", + "unit region command", c->magician, mage->region, co->order)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: kleines Blutopfer + * Stufe: 4 + * Gebiet: Draig + * Kategorie: Einheit, positiv + * + * Wirkung: + * Hitpoints to Aura: + * skill < 8 = 4:1 + * skill < 12 = 3:1 + * skill < 15 = 2:1 + * skill < 18 = 1:2 + * skill > = 2:1 + * Patzer: + * permanenter HP verlust + * + * Flag: + * (ONSHIPCAST) + */ +static int sp_bloodsacrifice(castorder * co) +{ + unit *mage = co->magician.u; + int cast_level = co->level; + int aura; + int skill = eff_skill(mage, SK_MAGIC, mage->region); + int hp = (int)(co->force * 8); + + if (hp <= 0) { + report_failure(mage, co->order); + return 0; + } + + aura = lovar(hp); + + if (skill < 8) { + aura /= 4; + } + else if (skill < 12) { + aura /= 3; + } + else if (skill < 15) { + aura /= 2; + /* von 15 bis 17 ist hp = aura */ + } + else if (skill > 17) { + aura *= 2; + } + + if (aura <= 0) { + report_failure(mage, co->order); + return 0; + } + + /* sicherheitshalber gibs hier einen HP gratis. sonst schaffen es + * garantiert ne ganze reihe von leuten ihren Magier damit umzubringen */ + mage->hp++; + change_spellpoints(mage, aura); + ADDMSG(&mage->faction->msgs, + msg_message("sp_bloodsacrifice_effect", + "unit region command amount", mage, mage->region, co->order, aura)); + return cast_level; +} + +/** gives a summoned undead unit some base skills. + */ +static void skill_summoned(unit * u, int level) +{ + if (level > 0) { + const race *rc = u_race(u); + skill_t sk; + for (sk = 0; sk != MAXSKILLS; ++sk) { + if (rc->bonus[sk] > 0) { + set_level(u, sk, level); + } + } + if (rc->bonus[SK_STAMINA]) { + u->hp = unit_max_hp(u) * u->number; + } + } +} + +/* ------------------------------------------------------------- */ +/* Name: Totenruf - Maechte des Todes + * Stufe: 6 + * Gebiet: Draig + * Kategorie: Beschwoerung, positiv + * Flag: FARCASTING + * Wirkung: + * Untote aus deathcounther ziehen, bis Stufe*10 Stueck + * + * Patzer: + * Erzeugt Monsteruntote + */ +static int sp_summonundead(castorder * co) +{ + int undead; + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + int force = (int)(co->force * 10); + const race *race = get_race(RC_SKELETON); + + if (!r->land || deathcount(r) == 0) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, "error_nograves", + "target", r)); + return 0; + } + + undead = _min(deathcount(r), 2 + lovar(force)); + + if (cast_level <= 8) { + race = get_race(RC_SKELETON); + } + else if (cast_level <= 12) { + race = get_race(RC_ZOMBIE); + } + else { + race = get_race(RC_GHOUL); + } + + u = create_unit(r, mage->faction, undead, race, 0, NULL, mage); + make_undead_unit(u); + skill_summoned(u, cast_level / 2); + + ADDMSG(&mage->faction->msgs, msg_message("summonundead_effect_1", + "mage region amount", mage, r, undead)); + ADDMSG(&r->msgs, msg_message("summonundead_effect_2", "mage region", mage, + r)); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Astraler Sog + * Stufe: 9 + * Gebiet: Draig + * Kategorie: Region, negativ + * Wirkung: + * Allen Magier in der betroffenen Region wird eine Teil ihrer + * Magischen Kraft in die Gefilde des Chaos entzogen Jeder Magier im + * Einflussbereich verliert Stufe(Zaubernden)*5% seiner Magiepunkte. + * Keine Regeneration in der Woche (fehlt noch) + * + * Flag: + * (REGIONSPELL | TESTRESISTANCE) + */ + +static int sp_auraleak(castorder * co) +{ + int lost_aura; + double lost; + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + message *msg; + + lost = _min(0.95, cast_level * 0.05); + + for (u = r->units; u; u = u->next) { + if (is_mage(u)) { + /* Magieresistenz Einheit? Bei gegenerischen Magiern nur sehr + * geringe Chance auf Erfolg wg erhoehter MR, wuerde Spruch sinnlos + * machen */ + lost_aura = (int)(get_spellpoints(u) * lost); + change_spellpoints(u, -lost_aura); + } + } + msg = msg_message("cast_auraleak_effect", "mage region", mage, r); + r_addmessage(r, NULL, msg); + msg_release(msg); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* BARDE - CERDDOR*/ +/* ------------------------------------------------------------- */ +/* ------------------------------------------------------------- */ +/* Name: Magie analysieren - Gebaeude, Schiffe, Region + * Name: Lied des Ortes analysieren + * Stufe: 8 + * Gebiet: Cerddor + * + * Wirkung: + * Zeigt die Verzauberungen eines Objekts an (curse->name, + * curse::info). Aus der Differenz Spruchstaerke und Curse->vigour + * ergibt sich die Chance den Spruch zu identifizieren ((force - + * c->vigour)*10 + 100 %). + * + * Flag: + * (SPELLLEVEL|ONSHIPCAST) + */ +static int sp_analysesong_obj(castorder * co) +{ + int obj; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + spellparameter *pa = co->par; + + obj = pa->param[0]->typ; + + switch (obj) { + case SPP_REGION: + magicanalyse_region(r, mage, force); + break; + + case SPP_BUILDING: + { + building *b = pa->param[0]->data.b; + magicanalyse_building(b, mage, force); + break; + } + case SPP_SHIP: + { + ship *sh = pa->param[0]->data.sh; + magicanalyse_ship(sh, mage, force); + break; + } + default: + /* Syntax fehlerhaft */ + return 0; + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Gesang des Lebens analysieren + * Name: Magie analysieren - Unit + * Stufe: 5 + * Gebiet: Cerddor + * Wirkung: + * Zeigt die Verzauberungen eines Objekts an (curse->name, + * curse::info). Aus der Differenz Spruchstaerke und Curse->vigour + * ergibt sich die Chance den Spruch zu identifizieren ((force - + * c->vigour)*10 + 100 %). + * + * Flag: + * (UNITSPELL|ONSHIPCAST|TESTCANSEE) + */ +static int sp_analysesong_unit(castorder * co) +{ + unit *u; + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + spellparameter *pa = co->par; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber + * abbrechen aber kosten lassen */ + if (pa->param[0]->flag == TARGET_RESISTS) + return cast_level; + + u = pa->param[0]->data.u; + + magicanalyse_unit(u, mage, force); + + return cast_level; +} + +static bool can_charm(const unit * u, int maxlevel) +{ + const skill_t expskills[] = + { SK_ALCHEMY, SK_HERBALISM, SK_MAGIC, SK_SPY, SK_TACTICS, NOSKILL }; + skill *sv = u->skills; + + if (fval(u, UFL_HERO)) + return false; + + for (; sv != u->skills + u->skill_size; ++sv) { + int l = 0, h = 5; + skill_t sk = sv->id; + assert(expskills[h] == NOSKILL); + while (l < h) { + int m = (l + h) / 2; + if (sk == expskills[m]) { + if (skill_limit(u->faction, sk) != INT_MAX) { + return false; + } + else if ((int)sv->level > maxlevel) { + return false; + } + break; + } + else if (sk > expskills[m]) + l = m + 1; + else + h = m; + } + } + return true; +} + +/* ------------------------------------------------------------- */ +/* Name: Charming + * Stufe: 13 + * Gebiet: Cerddor + * Flag: UNITSPELL + * Wirkung: + * bezauberte Einheit wechselt 'virtuell' die Partei und fuehrt fremde + * Befehle aus. + * Dauer: 3 - force+2 Wochen + * Wirkt gegen Magieresistenz + * + * wirkt auf eine Einheit mit maximal Talent Personen normal. Fuer jede + * zusaetzliche Person gibt es einen Bonus auf Magieresistenz, also auf + * nichtgelingen, von 10%. + * + * Das hoechste Talent der Einheit darf maximal so hoch sein wie das + * Magietalent des Magiers. Fuer jeden Talentpunkt mehr gibt es einen + * Bonus auf Magieresistenz von 15%, was dazu fuehrt, das bei +2 Stufen + * die Magiersistenz bei 90% liegt. + * + * Migrantenzaehlung muss Einheit ueberspringen + * + * Attackiere verbieten + * Flags: + * (UNITSPELL | TESTCANSEE) + */ +static int sp_charmingsong(castorder * co) +{ + unit *target; + int duration; + skill_t i; + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + spellparameter *pa = co->par; + int resist_bonus = 0; + int tb = 0; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + target = pa->param[0]->data.u; + + /* Auf eigene Einheiten versucht zu zaubern? Garantiert Tippfehler */ + if (target->faction == mage->faction) { + /* Die Einheit ist eine der unsrigen */ + cmistake(mage, co->order, 45, MSG_MAGIC); + } + /* niemand mit teurem Talent */ + if (!can_charm(target, cast_level / 2)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_noexpensives", "target", target)); + return 0; + } + + /* Magieresistensbonus fuer mehr als Stufe Personen */ + if (target->number > force) { + resist_bonus += (int)((target->number - force) * 10); + } + /* Magieresistensbonus fuer hoehere Talentwerte */ + for (i = 0; i < MAXSKILLS; i++) { + int sk = effskill(target, i); + if (tb < sk) + tb = sk; + } + tb -= effskill(mage, SK_MAGIC); + if (tb > 0) { + resist_bonus += tb * 15; + } + /* Magieresistenz */ + if (target_resists_magic(mage, target, TYP_UNIT, resist_bonus)) { + report_failure(mage, co->order); +#if 0 + sprintf(buf, "%s fuehlt sich einen Moment lang benommen und desorientiert.", + unitname(target)); + addmessage(target->region, target->faction, buf, MSG_EVENT, ML_WARN); +#endif + return 0; + } + + duration = 3 + rng_int() % (int)force; + { + trigger *trestore = trigger_changefaction(target, target->faction); + /* laeuft die Dauer ab, setze Partei zurueck */ + add_trigger(&target->attribs, "timer", trigger_timeout(duration, trestore)); + /* wird die alte Partei von Target aufgeloest, dann auch diese Einheit */ + add_trigger(&target->faction->attribs, "destroy", trigger_killunit(target)); + /* wird die neue Partei von Target aufgeloest, dann auch diese Einheit */ + add_trigger(&mage->faction->attribs, "destroy", trigger_killunit(target)); + } + /* sperre ATTACKIERE, GIB PERSON und ueberspringe Migranten */ + create_curse(mage, &target->attribs, ct_find("slavery"), force, duration, zero_effect, 0); + + /* setze Partei um und loesche langen Befehl aus Sicherheitsgruenden */ + u_setfaction(target, mage->faction); + set_order(&target->thisorder, NULL); + + /* setze Parteitarnung, damit nicht sofort klar ist, wer dahinter + * steckt */ + if (rule_stealth_faction()) { + fset(target, UFL_ANON_FACTION); + } + + ADDMSG(&mage->faction->msgs, msg_message("charming_effect", + "mage unit duration", mage, target, duration)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Gesang des wachen Geistes + * Stufe: 10 + * Gebiet: Cerddor + * Kosten: SPC_LEVEL + * Wirkung: + * Bringt einmaligen Bonus von +15% auf Magieresistenz. Wirkt auf alle + * Aliierten (HELFE BEWACHE) in der Region. + * Dauert Stufe Wochen an, ist nicht kumulativ. + * Flag: + * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_song_resistmagic(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + int duration = (int)force + 1; + + create_curse(mage, &r->attribs, ct_find("goodmagicresistancezone"), + force, duration, 15, 0); + + /* Erfolg melden */ + ADDMSG(&mage->faction->msgs, msg_message("regionmagic_effect", + "unit region command", mage, mage->region, co->order)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Gesang des schwachen Geistes + * Stufe: 12 + * Gebiet: Cerddor + * Wirkung: + * Bringt einmaligen Malus von -15% auf Magieresistenz. + * Wirkt auf alle Nicht-Aliierten (HELFE BEWACHE) in der Region. + * Dauert Stufe Wochen an, ist nicht kumulativ. + * Flag: + * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_song_susceptmagic(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + int duration = (int)force + 1; + + create_curse(mage, &r->attribs, ct_find("badmagicresistancezone"), + force, duration, 15, 0); + + ADDMSG(&mage->faction->msgs, msg_message("regionmagic_effect", + "unit region command", mage, mage->region, co->order)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Aufruhr beschwichtigen + * Stufe: 15 + * Gebiet: Cerddor + * Flag: FARCASTING + * Wirkung: + * zerstreut einen Monsterbauernmob, Antimagie zu 'Aufruhr + * verursachen' + */ + +static int sp_rallypeasantmob(castorder * co) +{ + unit *u, *un; + int erfolg = 0; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + message *msg; + curse *c; + + for (u = r->units; u; u = un) { + un = u->next; + if (is_monsters(u->faction) && u_race(u) == get_race(RC_PEASANT)) { + rsetpeasants(r, rpeasants(r) + u->number); + rsetmoney(r, rmoney(r) + get_money(u)); + set_money(u, 0); + setguard(u, GUARD_NONE); + set_number(u, 0); + erfolg = cast_level; + } + } + + c = get_curse(r->attribs, ct_find(oldcursename(C_RIOT))); + if (c != NULL) { + remove_curse(&r->attribs, c); + } + + msg = msg_message("cast_rally_effect", "mage region", mage, r); + r_addmessage(r, NULL, msg); + msg_release(msg); + return erfolg; +} + +/* ------------------------------------------------------------- */ +/* Name: Aufruhr verursachen + * Stufe: 16 + * Gebiet: Cerddor + * Wirkung: + * Wiegelt 60% bis 90% der Bauern einer Region auf. Bauern werden ein + * großer Mob, der zur Monsterpartei gehoert und die Region bewacht. + * Regionssilber sollte auch nicht durch Unterhaltung gewonnen werden + * koennen. + * + * Fehlt: Triggeraktion: loeste Bauernmob auf und gib alles an Region, + * dann koennen die Bauernmobs ihr Silber mitnehmen und bleiben x + * Wochen bestehen + * + * alternativ: Loesen sich langsam wieder auf + * Flag: + * (FARCASTING | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_raisepeasantmob(castorder * co) +{ + unit *u; + attrib *a; + int n; + int anteil; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + int duration = (int)force + 1; + faction *monsters = get_monsters(); + message *msg; + + anteil = 6 + (rng_int() % 4); + + n = rpeasants(r) * anteil / 10; + n = _max(0, n); + n = _min(n, rpeasants(r)); + + if (n <= 0) { + report_failure(mage, co->order); + return 0; + } + + rsetpeasants(r, rpeasants(r) - n); + assert(rpeasants(r) >= 0); + + u = + create_unit(r, monsters, n, get_race(RC_PEASANT), 0, LOC(monsters->locale, + "furious_mob"), NULL); + fset(u, UFL_ISNEW); + guard(u, GUARD_ALL); + a = a_new(&at_unitdissolve); + a->data.ca[0] = 1; /* An rpeasants(r). */ + a->data.ca[1] = 15; /* 15% */ + a_add(&u->attribs, a); + + create_curse(mage, &r->attribs, ct_find("riotzone"), (float)cast_level, duration, + (float)anteil, 0); + + msg = msg_message("sp_raisepeasantmob_effect", "mage region", mage, r); + report_spell(mage, r, msg); + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Ritual der Aufnahme / Migrantenwerben + * Stufe: 9 + * Gebiet: Cerddor + * Wirkung: + * Bis zu Stufe Personen fremder Rasse koennen angeworben werden. Die + * angeworbene Einheit muss kontaktieren. Keine teuren Talente + * + * Flag: + * (UNITSPELL | SPELLLEVEL | TESTCANSEE) + */ +static int sp_migranten(castorder * co) +{ + unit *target; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + target = pa->param[0]->data.u; /* Zieleinheit */ + + /* Personen unserer Rasse koennen problemlos normal uebergeben werden */ + if (u_race(target) == mage->faction->race) { + /* u ist von unserer Art, das Ritual waere verschwendete Aura. */ + ADDMSG(&mage->faction->msgs, msg_message("sp_migranten_fail1", + "unit region command target", mage, mage->region, co->order, target)); + } + /* Auf eigene Einheiten versucht zu zaubern? Garantiert Tippfehler */ + if (target->faction == mage->faction) { + cmistake(mage, co->order, 45, MSG_MAGIC); + } + + /* Keine Monstereinheiten */ + if (!playerrace(u_race(target))) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_nomonsters", "")); + return 0; + } + /* niemand mit teurem Talent */ + if (has_limited_skills(target)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_noexpensives", "target", target)); + return 0; + } + /* maximal Stufe Personen */ + if (target->number > cast_level || target->number > max_spellpoints(r, mage)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_toomanytargets", "")); + return 0; + } + + /* Kontakt pruefen (aus alter Teleportroutine uebernommen) */ + if (!ucontact(target, mage)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail::contact", "target", target)); + return 0; + } + u_setfaction(target, mage->faction); + set_order(&target->thisorder, NULL); + + /* Erfolg melden */ + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, "sp_migranten", + "target", target)); + + return target->number; +} + +/* ------------------------------------------------------------- */ +/* Name: Gesang der Friedfertigkeit + * Stufe: 12 + * Gebiet: Cerddor + * Wirkung: + * verhindert jede Attacke fuer lovar(Stufe/2) Runden + */ + +static int sp_song_of_peace(castorder * co) +{ + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + int duration = 2 + lovar(force / 2); + message *msg[2] = { NULL, NULL }; + + create_curse(mage, &r->attribs, ct_find("peacezone"), force, duration, + zero_effect, 0); + + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) { + if (!fval(u->faction, FFL_SELECT)) { + message *m = NULL; + fset(u->faction, FFL_SELECT); + if (cansee(u->faction, r, mage, 0)) { + if (msg[0] == NULL) + msg[0] = msg_message("song_of_peace_effect_0", "mage", mage); + m = msg[0]; + } + else { + if (msg[1] == NULL) + msg[1] = msg_message("song_of_peace_effect_1", ""); + m = msg[1]; + } + r_addmessage(r, u->faction, m); + } + } + if (msg[0]) + msg_release(msg[0]); + if (msg[1]) + msg_release(msg[1]); + return cast_level; + +} + +/* ------------------------------------------------------------- */ +/* Name: Hohes Lied der Gaukelei + * Stufe: 2 + * Gebiet: Cerddor + * Wirkung: + * Das Unterhaltungsmaximum steigt von 20% auf 40% des + * Regionsvermoegens. Der Spruch haelt Stufe Wochen an + */ + +static int sp_generous(castorder * co) +{ + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + int duration = (int)force + 1; + float effect; + message *msg[2] = { NULL, NULL }; + + if (is_cursed(r->attribs, C_DEPRESSION, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_generous", "")); + return 0; + } + + effect = 2; + create_curse(mage, &r->attribs, ct_find("generous"), force, duration, effect, + 0); + + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) { + if (!fval(u->faction, FFL_SELECT)) { + message *m = NULL; + fset(u->faction, FFL_SELECT); + if (cansee(u->faction, r, mage, 0)) { + if (msg[0] == NULL) + msg[0] = msg_message("generous_effect_0", "mage", mage); + m = msg[0]; + } + else { + if (msg[1] == NULL) + msg[1] = msg_message("generous_effect_1", ""); + m = msg[1]; + } + r_addmessage(r, u->faction, m); + } + } + if (msg[0]) + msg_release(msg[0]); + if (msg[1]) + msg_release(msg[1]); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Anwerbung + * Stufe: 4 + * Gebiet: Cerddor + * Wirkung: + * Bauern schliessen sich der eigenen Partei an + * ist zusaetzlich zur Rekrutierungsmenge in der Region + * */ + +static int sp_recruit(castorder * co) +{ + unit *u; + region *r = co_get_region(co); + int num, maxp = rpeasants(r); + double n; + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + faction *f = mage->faction; + const struct race *rc = f->race; + + if (maxp == 0) { + report_failure(mage, co->order); + return 0; + } + /* Immer noch zuviel auf niedrigen Stufen. Deshalb die Rekrutierungskosten + * mit einfliessen lassen und dafuer den Exponenten etwas groeßer. + * Wenn die Rekrutierungskosten deutlich hoeher sind als der Faktor, + * ist das Verhaeltniss von ausgegebene Aura pro Bauer bei Stufe 2 + * ein mehrfaches von Stufe 1, denn in beiden Faellen gibt es nur 1 + * Bauer, nur die Kosten steigen. */ + n = (pow(force, 1.6) * 100) / f->race->recruitcost; + if (rc->recruit_multi != 0) { + double multp = maxp / rc->recruit_multi; + n = _min(multp, n); + n = _max(n, 1); + rsetpeasants(r, maxp - (int)(n * rc->recruit_multi)); + } + else { + n = _min(maxp, n); + n = _max(n, 1); + rsetpeasants(r, maxp - (int)n); + } + + num = (int)n; + u = + create_unit(r, f, num, f->race, 0, LOC(f->locale, + (num == 1 ? "peasant" : "peasant_p")), mage); + set_order(&u->thisorder, default_order(f->locale)); + + ADDMSG(&mage->faction->msgs, msg_message("recruit_effect", "mage amount", + mage, num)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Wanderprediger - Große Anwerbung + * Stufe: 14 + * Gebiet: Cerddor + * Wirkung: + * Bauern schliessen sich der eigenen Partei an + * ist zusaetzlich zur Rekrutierungsmenge in der Region + * */ + +static int sp_bigrecruit(castorder * co) +{ + unit *u; + region *r = co_get_region(co); + int n, maxp = rpeasants(r); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + faction *f = mage->faction; + message *msg; + + if (maxp <= 0) { + report_failure(mage, co->order); + return 0; + } + /* Fuer vergleichbare Erfolge bei unterschiedlichen Rassen die + * Rekrutierungskosten mit einfliessen lassen. */ + + n = (int)force + lovar((force * force * 1000) / f->race->recruitcost); + if (f->race == get_race(RC_ORC)) { + n = _min(2 * maxp, n); + n = _max(n, 1); + rsetpeasants(r, maxp - (n + 1) / 2); + } + else { + n = _min(maxp, n); + n = _max(n, 1); + rsetpeasants(r, maxp - n); + } + + u = + create_unit(r, f, n, f->race, 0, LOC(f->locale, + (n == 1 ? "peasant" : "peasant_p")), mage); + set_order(&u->thisorder, default_order(f->locale)); + + msg = msg_message("recruit_effect", "mage amount", mage, n); + r_addmessage(r, mage->faction, msg); + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Aushorchen + * Stufe: 7 + * Gebiet: Cerddor + * Wirkung: + * Erliegt die Einheit dem Zauber, so wird sie dem Magier alles + * erzaehlen, was sie ueber die gefragte Region weiß. Ist in der Region + * niemand ihrer Partei, so weiß sie nichts zu berichten. Auch kann + * sie nur das erzaehlen, was sie selber sehen koennte. + * Flags: + * (UNITSPELL | TESTCANSEE) + */ + +/* restistenz der einheit pruefen */ +static int sp_pump(castorder * co) +{ + unit *u, *target; + region *rt; + bool see = false; + unit *mage = co->magician.u; + spellparameter *pa = co->par; + int cast_level = co->level; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + target = pa->param[0]->data.u; /* Zieleinheit */ + + if (fval(u_race(target), RCF_UNDEAD)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "error_not_on_undead", "")); + return 0; + } + if (is_magic_resistant(mage, target, 0) || is_monsters(target->faction)) { + report_failure(mage, co->order); + return 0; + } + + rt = pa->param[1]->data.r; + + for (u = rt->units; u; u = u->next) { + if (u->faction == target->faction) + see = true; + } + + if (see) { + ADDMSG(&mage->faction->msgs, msg_message("pump_effect", "mage unit tregion", + mage, target, rt)); + } + else { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, "spellfail_pump", + "target tregion", target, rt)); + return cast_level / 2; + } + + u = + create_unit(rt, mage->faction, RS_FARVISION, get_race(RC_SPELL), 0, + "spell/pump", NULL); + u->age = 2; + set_level(u, SK_PERCEPTION, eff_skill(target, SK_PERCEPTION, u->region)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Verfuehrung + * Stufe: 6 + * Gebiet: Cerddor + * Wirkung: + * Betoert eine Einheit, so das sie ihm den groeßten Teil ihres Bargelds + * und 50% ihres Besitzes schenkt. Sie behaelt jedoch immer soviel, wie + * sie zum ueberleben braucht. Wirkt gegen Magieresistenz. + * _min(Stufe*1000$, u->money - maintenace) + * Von jedem Item wird 50% abgerundet ermittelt und uebergeben. Dazu + * kommt Itemzahl%2 mit 50% chance + * + * Flags: + * (UNITSPELL | TESTRESISTANCE | TESTCANSEE) + */ +static int sp_seduce(castorder * co) +{ + const resource_type *rsilver = get_resourcetype(R_SILVER); + unit *target; + item **itmp, *items = 0;; + unit *mage = co->magician.u; + spellparameter *pa = co->par; + int cast_level = co->level; + float force = co->force; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + target = pa->param[0]->data.u; /* Zieleinheit */ + + if (fval(u_race(target), RCF_UNDEAD)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_noundead", "")); + return 0; + } + + /* Erfolgsmeldung */ + + itmp = &target->items; + while (*itmp) { + item *itm = *itmp; + int loot; + if (itm->type->rtype == rsilver) { + loot = + _min(cast_level * 1000, get_money(target) - (maintenance_cost(target))); + loot = _max(loot, 0); + } + else { + loot = itm->number / 2; + if (itm->number % 2) { + loot += rng_int() % 2; + } + if (loot > 0) { + loot = (int)_min(loot, force * 5); + } + } + if (loot > 0) { + i_change(&mage->items, itm->type, loot); + i_change(&items, itm->type, loot); + i_change(itmp, itm->type, -loot); + } + if (*itmp == itm) + itmp = &itm->next; + } + + if (items) { + ADDMSG(&mage->faction->msgs, msg_message("seduce_effect_0", "mage unit items", + mage, target, items)); + i_freeall(&items); + ADDMSG(&target->faction->msgs, msg_message("seduce_effect_1", "unit", + target)); + } + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Monster friedlich stimmen + * Stufe: 6 + * Gebiet: Cerddor + * Wirkung: + * verhindert Angriffe des bezauberten Monsters auf die Partei des + * Barden fuer Stufe Wochen. Nicht uebertragbar, dh Verbuendete werden vom + * Monster natuerlich noch angegriffen. Wirkt nicht gegen Untote + * Jede Einheit kann maximal unter einem Beherrschungszauber dieser Art + * stehen, dh wird auf die selbe Einheit dieser Zauber von einem + * anderen Magier nochmal gezaubert, schlaegt der Zauber fehl. + * + * Flags: + * (UNITSPELL | ONSHIPCAST | TESTRESISTANCE | TESTCANSEE) + */ + +static int sp_calm_monster(castorder * co) +{ + curse *c; + unit *target; + unit *mage = co->magician.u; + spellparameter *pa = co->par; + int cast_level = co->level; + float force = co->force; + float effect; + message *msg; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + target = pa->param[0]->data.u; /* Zieleinheit */ + + if (fval(u_race(target), RCF_UNDEAD)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_noundead", "")); + return 0; + } + + effect = (float)mage->faction->subscription; + c = create_curse(mage, &target->attribs, ct_find("calmmonster"), force, + (int)force, effect, 0); + if (c == NULL) { + report_failure(mage, co->order); + return 0; + } + + msg = msg_message("calm_effect", "mage unit", mage, target); + r_addmessage(mage->region, mage->faction, msg); + msg_release(msg); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: schaler Wein + * Stufe: 7 + * Gebiet: Cerddor + * Wirkung: + * wird gegen Magieresistenz gezaubert Das Opfer vergisst bis zu + * Talenttage seines hoechsten Talentes und tut die Woche nix. + * Nachfolgende Zauber sind erschwert. + * Wirkt auf bis zu 10 Personen in der Einheit + * + * Flags: + * (UNITSPELL | TESTRESISTANCE | TESTCANSEE) + */ + +static int sp_headache(castorder * co) +{ + skill *smax = NULL; + int i; + unit *target; + unit *mage = co->magician.u; + spellparameter *pa = co->par; + int cast_level = co->level; + message *msg; + + /* Macht alle nachfolgenden Zauber doppelt so teuer */ + countspells(mage, 1); + + target = pa->param[0]->data.u; /* Zieleinheit */ + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (target->number == 0 || pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* finde das groeßte Talent: */ + for (i = 0; i != target->skill_size; ++i) { + skill *sv = target->skills + i; + if (smax == NULL || skill_compare(sv, smax) > 0) { + smax = sv; + } + } + if (smax != NULL) { + /* wirkt auf maximal 10 Personen */ + int change = _min(10, target->number) * (rng_int() % 2 + 1) / target->number; + reduce_skill(target, smax, change); + } + set_order(&target->thisorder, NULL); + + msg = msg_message("hangover_effect_0", "mage unit", mage, target); + r_addmessage(mage->region, mage->faction, msg); + msg_release(msg); + + msg = msg_message("hangover_effect_1", "unit", target); + r_addmessage(target->region, target->faction, msg); + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Mob + * Stufe: 10 + * Gebiet: Cerddor + * Wirkung: + * Wiegelt Stufe*250 Bauern zu einem Mob auf, der sich der Partei des + * Magier anschliesst Pro Woche beruhigen sich etwa 15% wieder und + * kehren auf ihre Felder zurueck + * + * Flags: + * (SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_raisepeasants(castorder * co) +{ + int bauern; + unit *u2; + attrib *a; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + message *msg; + + if (rpeasants(r) == 0) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "error_nopeasants", "")); + return 0; + } + bauern = (int)_min(rpeasants(r), power * 250); + rsetpeasants(r, rpeasants(r) - bauern); + + u2 = + create_unit(r, mage->faction, bauern, get_race(RC_PEASANT), 0, + LOC(mage->faction->locale, "furious_mob"), mage); + + fset(u2, UFL_LOCKED); + if (rule_stealth_faction()) { + fset(u2, UFL_ANON_FACTION); + } + + a = a_new(&at_unitdissolve); + a->data.ca[0] = 1; /* An rpeasants(r). */ + a->data.ca[1] = 15; /* 15% */ + a_add(&u2->attribs, a); + + msg = + msg_message("sp_raisepeasants_effect", "mage region amount", mage, r, + u2->number); + r_addmessage(r, NULL, msg); + if (mage->region != r) { + add_message(&mage->faction->msgs, msg); + } + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Truebsal + * Stufe: 11 + * Kategorie: Region, negativ + * Wirkung: + * in der Region kann fuer einige Wochen durch Unterhaltung kein Geld + * mehr verdient werden + * + * Flag: + * (FARCASTING | REGIONSPELL | TESTRESISTANCE) + */ +static int sp_depression(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + int duration = (int)force + 1; + message *msg; + + create_curse(mage, &r->attribs, ct_find("depression"), force, duration, + zero_effect, 0); + + msg = msg_message("sp_depression_effect", "mage region", mage, r); + r_addmessage(r, NULL, msg); + if (mage->region != r) { + add_message(&mage->faction->msgs, msg); + } + msg_release(msg); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* TRAUM - Illaun */ +/* ------------------------------------------------------------- */ + +/* Name: Seelenfrieden + * Stufe: 2 + * Kategorie: Region, positiv + * Gebiet: Illaun + * Wirkung: + * Reduziert Untotencounter + * Flag: (0) + */ + +int sp_puttorest(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int dead = deathcount(r); + int laid_to_rest = dice((int)(co->force * 2), 100); + message *seen = msg_message("puttorest", "mage", mage); + message *unseen = msg_message("puttorest", "mage", NULL); + + laid_to_rest = _max(laid_to_rest, dead); + + deathcounts(r, -laid_to_rest); + + report_effect(r, mage, seen, unseen); + msg_release(seen); + msg_release(unseen); + return co->level; +} + +/* Name: Traumschloeßchen + * Stufe: 3 + * Kategorie: Region, Gebaeude, positiv + * Gebiet: Illaun + * Wirkung: + * Mit Hilfe dieses Zaubers kann der Traumweber die Illusion eines + * beliebigen Gebaeudes erzeugen. Die Illusion kann betreten werden, ist + * aber ansonsten funktionslos und benoetigt auch keinen Unterhalt + * Flag: (0) + */ + +int sp_icastle(castorder * co) +{ + building *b; + const building_type *type; + attrib *a; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + spellparameter *pa = co->par; + icastle_data *data; + const char *bname; + message *msg; + const building_type *bt_illusion = bt_find("illusioncastle"); + + if (!bt_illusion) { + return 0; + } + + if ((type = + findbuildingtype(pa->param[0]->data.xs, mage->faction->locale)) == NULL) { + type = bt_find("castle"); + } + + b = new_building(bt_illusion, r, mage->faction->locale); + + /* Groeße festlegen. */ + if (type == bt_illusion) { + b->size = (rng_int() % (int)((power * power) + 1) * 10); + } + else if (type->maxsize == -1) { + b->size = ((rng_int() % (int)(power)) + 1) * 5; + } + else { + b->size = type->maxsize; + } + + if (type->name == NULL) { + bname = LOC(mage->faction->locale, type->_name); + } + else { + bname = LOC(mage->faction->locale, buildingtype(type, b, 0)); + } + building_setname(b, bname); + + /* TODO: Auf timeout und action_destroy umstellen */ + a = a_add(&b->attribs, a_new(&at_icastle)); + data = (icastle_data *)a->data.v; + data->type = type; + data->building = b; + data->time = + 2 + (rng_int() % (int)(power)+1) * (rng_int() % (int)(power)+1); + + if (mage->region == r) { + if (leave(mage, false)) { + u_set_building(mage, b); + } + } + + ADDMSG(&mage->faction->msgs, msg_message("icastle_create", + "unit region command", mage, mage->region, co->order)); + + msg = msg_message("sp_icastle_effect", "region", r); + report_spell(mage, r, msg); + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Gestaltwandlung + * Stufe: 3 + * Gebiet: Illaun + * Wirkung: + * Zieleinheit erscheint fuer (Stufe) Wochen als eine andere Gestalt + * (wie bei daemonischer Rassetarnung). + * Syntax: ZAUBERE "Gestaltwandlung" + * Flags: + * (UNITSPELL | SPELLLEVEL) + */ + +int sp_illusionary_shapeshift(castorder * co) +{ + unit *u; + const race *rc; + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + spellparameter *pa = co->par; + const race *irace; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber + * abbrechen aber kosten lassen */ + if (pa->param[0]->flag == TARGET_RESISTS) + return cast_level; + + u = pa->param[0]->data.u; + + rc = findrace(pa->param[1]->data.xs, mage->faction->locale); + if (rc == NULL) { + cmistake(mage, co->order, 202, MSG_MAGIC); + return 0; + } + + /* aehnlich wie in laws.c:setealth() */ + if (!playerrace(rc)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "sp_shapeshift_fail", "target race", u, rc)); + return 0; + } + irace = u_irace(u); + if (irace == u_race(u)) { + trigger *trestore = trigger_changerace(u, NULL, irace); + add_trigger(&u->attribs, "timer", trigger_timeout((int)power + 2, + trestore)); + u->irace = rc; + } + + ADDMSG(&mage->faction->msgs, msg_message("shapeshift_effect", + "mage target race", mage, u, rc)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Regionstraum analysieren + * Stufe: 9 + * Aura: 18 + * Kosten: SPC_FIX + * Wirkung: + * Zeigt die Verzauberungen eines Objekts an (curse->name, + * curse::info). Aus der Differenz Spruchstaerke und Curse->vigour + * ergibt sich die Chance den Spruch zu identifizieren ((force - + * c->vigour)*10 + 100 %). + */ +int sp_analyseregionsdream(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + + magicanalyse_region(r, mage, cast_level); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Traumbilder erkennen + * Stufe: 5 + * Aura: 12 + * Kosten: SPC_FIX + * Wirkung: + * Zeigt die Verzauberungen eines Objekts an (curse->name, + * curse::info). Aus der Differenz Spruchstaerke und Curse->vigour + * ergibt sich die Chance den Spruch zu identifizieren ((force - + * c->vigour)*10 + 100 %). + */ +int sp_analysedream(castorder * co) +{ + unit *u; + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber + * abbrechen aber kosten lassen */ + if (pa->param[0]->flag == TARGET_RESISTS) + return cast_level; + + u = pa->param[0]->data.u; + magicanalyse_unit(u, mage, cast_level); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Schlechte Traeume + * Stufe: 10 + * Kategorie: Region, negativ + * Wirkung: + * Dieser Zauber ermoeglicht es dem Traeumer, den Schlaf aller + * nichtaliierten Einheiten (HELFE BEWACHE) in der Region so starkzu + * stoeren, das sie 1 Talentstufe in allen Talenten + * voruebergehend verlieren. Der Zauber wirkt erst im Folgemonat. + * + * Flags: + * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + * */ +int sp_baddreams(castorder * co) +{ + int duration; + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + region *r = co_get_region(co); + curse *c; + float effect; + + /* wirkt erst in der Folgerunde, soll mindestens eine Runde wirken, + * also duration+2 */ + duration = (int)_max(1, power / 2); /* Stufe 1 macht sonst mist */ + duration = 2 + rng_int() % duration; + + /* Nichts machen als ein entsprechendes Attribut in die Region legen. */ + effect = -1; + c = create_curse(mage, &r->attribs, ct_find("gbdream"), power, duration, effect, 0); + + /* Erfolg melden */ + ADDMSG(&mage->faction->msgs, msg_message("regionmagic_effect", + "unit region command", c->magician, mage->region, co->order)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Schoene Traeume + * Stufe: 8 + * Kategorie: + * Wirkung: + * Dieser Zauber ermoeglicht es dem Traeumer, den Schlaf aller aliierten + * Einheiten in der Region so zu beeinflussen, daß sie fuer einige Zeit + * einen Bonus von 1 Talentstufe in allen Talenten + * bekommen. Der Zauber wirkt erst im Folgemonat. + * Flags: + * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + */ +int sp_gooddreams(castorder * co) +{ + int duration; + curse *c; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + float effect; + + /* wirkt erst in der Folgerunde, soll mindestens eine Runde wirken, + * also duration+2 */ + duration = (int)_max(1, power / 2); /* Stufe 1 macht sonst mist */ + duration = 2 + rng_int() % duration; + effect = 1; + c = create_curse(mage, &r->attribs, ct_find("gbdream"), power, duration, effect, 0); + + /* Erfolg melden */ + ADDMSG(&mage->faction->msgs, msg_message("regionmagic_effect", + "unit region command", c->magician, mage->region, co->order)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: + * Stufe: 9 + * Kategorie: + * Wirkung: + * Es wird eine Kloneinheit erzeugt, die nichts kann. Stirbt der + * Magier, wird er mit einer Wahrscheinlichkeit von 90% in den Klon + * transferiert. + * Flags: + * (NOTFAMILARCAST) + */ +int sp_clonecopy(castorder * co) +{ + unit *clone; + region *r = co_get_region(co); + region *target_region = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + message *msg; + char name[NAMESIZE]; + + if (get_clone(mage) != NULL) { + cmistake(mage, co->order, 298, MSG_MAGIC); + return 0; + } + + _snprintf(name, sizeof(name), (const char *)LOC(mage->faction->locale, + "clone_of"), unitname(mage)); + clone = + create_unit(target_region, mage->faction, 1, get_race(RC_CLONE), 0, name, + mage); + setstatus(clone, ST_FLEE); + fset(clone, UFL_LOCKED); + + create_newclone(mage, clone); + + msg = msg_message("sp_clone_effet", "mage", mage); + r_addmessage(r, mage->faction, msg); + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +int sp_dreamreading(castorder * co) +{ + unit *u, *u2; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + float power = co->force; + message *msg; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber + * abbrechen aber kosten lassen */ + if (pa->param[0]->flag == TARGET_RESISTS) + return cast_level; + + u = pa->param[0]->data.u; + + /* Illusionen und Untote abfangen. */ + if (fval(u_race(u), RCF_UNDEAD | RCF_ILLUSIONARY)) { + ADDMSG(&mage->faction->msgs, msg_unitnotfound(mage, co->order, + pa->param[0])); + return 0; + } + + /* Entfernung */ + if (distance(mage->region, u->region) > power) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_distance", "")); + return 0; + } + + u2 = + create_unit(u->region, mage->faction, RS_FARVISION, get_race(RC_SPELL), 0, + "spell/dreamreading", NULL); + set_number(u2, 1); + u2->age = 2; /* Nur fuer diese Runde. */ + set_level(u2, SK_PERCEPTION, eff_skill(u, SK_PERCEPTION, u2->region)); + + msg = + msg_message("sp_dreamreading_effect", "mage unit region", mage, u, + u->region); + r_addmessage(r, mage->faction, msg); + msg_release(msg); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Wirkt power/2 Runden auf bis zu power^2 Personen + * mit einer Chance von 5% vermehren sie sich */ +int sp_sweetdreams(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + spellparameter *pa = co->par; + int men, n; + int duration = (int)(power / 2) + 1; + int opfer = (int)(power * power); + + /* Schleife ueber alle angegebenen Einheiten */ + for (n = 0; n < pa->length; n++) { + curse *c; + unit *u; + float effect; + message *msg; + /* sollte nie negativ werden */ + if (opfer < 1) + break; + + if (pa->param[n]->flag == TARGET_RESISTS || + pa->param[n]->flag == TARGET_NOTFOUND) + continue; + + /* Zieleinheit */ + u = pa->param[n]->data.u; + + if (!ucontact(u, mage)) { + cmistake(mage, co->order, 40, MSG_EVENT); + continue; + } + men = _min(opfer, u->number); + opfer -= men; + + /* Nichts machen als ein entsprechendes Attribut an die Einheit legen. */ + effect = 0.05f; + c = create_curse(mage, &u->attribs, ct_find("orcish"), power, duration, effect, men); + + msg = msg_message("sp_sweetdreams_effect", "mage unit region", c->magician, u, r); + r_addmessage(r, mage->faction, msg); + if (u->faction != mage->faction) { + r_addmessage(r, u->faction, msg); + } + msg_release(msg); + } + return cast_level; +} + +/* ------------------------------------------------------------- */ +int sp_disturbingdreams(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + int duration = 1 + (int)(power / 6); + float effect; + curse *c; + + effect = 10; + c = create_curse(mage, &r->attribs, ct_find("badlearn"), power, duration, effect, 0); + + ADDMSG(&mage->faction->msgs, msg_message("sp_disturbingdreams_effect", + "mage region", c->magician, r)); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* ASTRAL / THEORIE / M_TYBIED */ +/* ------------------------------------------------------------- */ +/* Name: Magie analysieren + * Stufe: 1 + * Aura: 1 + * Kosten: SPC_LINEAR + * Komponenten: + * + * Wirkung: + * Zeigt die Verzauberungen eines Objekts an (curse->name, + * curse::info). Aus der Differenz Spruchstaerke und Curse->vigour + * ergibt sich die Chance den Spruch zu identifizieren ((force - + * c->vigour)*10 + 100 %). + * + * Flags: + * UNITSPELL, SHIPSPELL, BUILDINGSPELL + */ + +int sp_analysemagic(castorder * co) +{ + int obj; + unit *mage = co_get_caster(co); + int cast_level = co->level; + spellparameter *pa = co->par; + + if (!pa->param) { + return 0; + } + /* Objekt ermitteln */ + obj = pa->param[0]->typ; + + switch (obj) { + case SPP_REGION: + { + region *tr = pa->param[0]->data.r; + magicanalyse_region(tr, mage, cast_level); + break; + } + case SPP_TEMP: + case SPP_UNIT: + { + unit *u; + u = pa->param[0]->data.u; + magicanalyse_unit(u, mage, cast_level); + break; + } + case SPP_BUILDING: + { + building *b; + b = pa->param[0]->data.b; + magicanalyse_building(b, mage, cast_level); + break; + } + case SPP_SHIP: + { + ship *sh; + sh = pa->param[0]->data.sh; + magicanalyse_ship(sh, mage, cast_level); + break; + } + default: + /* Fehlerhafter Parameter */ + return 0; + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ + +int sp_itemcloak(castorder * co) +{ + unit *target; + unit *mage = co->magician.u; + spellparameter *pa = co->par; + int cast_level = co->level; + float power = co->force; + int duration = (int)_max(2.0, power + 1); /* works in the report, and ageing this round would kill it if it's <=1 */ + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* Zieleinheit */ + target = pa->param[0]->data.u; + + create_curse(mage, &target->attribs, ct_find("itemcloak"), power, duration, + zero_effect, 0); + ADDMSG(&mage->faction->msgs, msg_message("itemcloak", "mage target", mage, + target)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Magieresistenz erhoehen + * Stufe: 3 + * Aura: 5 MP + * Kosten: SPC_LEVEL + * Komponenten: + * + * Wirkung: + * erhoeht die Magierestistenz der Personen um 20 Punkte fuer 6 Wochen + * Wirkt auf Stufe*5 Personen kann auf mehrere Einheiten gezaubert + * werden, bis die Zahl der moeglichen Personen erschoepft ist + * + * Flags: + * UNITSPELL + */ +int sp_resist_magic_bonus(castorder * co) +{ + unit *u; + int n, m; + int duration = 6; + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + spellparameter *pa = co->par; + /* Pro Stufe koennen bis zu 5 Personen verzaubert werden */ + double maxvictims = 5 * power; + int victims = (int)maxvictims; + + /* Schleife ueber alle angegebenen Einheiten */ + for (n = 0; n < pa->length; n++) { + message *msg; + /* sollte nie negativ werden */ + if (victims < 1) + break; + + if (pa->param[n]->flag == TARGET_RESISTS + || pa->param[n]->flag == TARGET_NOTFOUND) + continue; + + u = pa->param[n]->data.u; + + /* Ist die Einheit schon verzaubert, wirkt sich dies nur auf die + * Menge der Verzauberten Personen aus. + if (is_cursed(u->attribs, C_MAGICRESISTANCE, 0)) + continue; + */ + + m = _min(u->number, victims); + victims -= m; + + create_curse(mage, &u->attribs, ct_find("magicresistance"), + power, duration, 20, m); + + msg = msg_message("magicresistance_effect", "unit", u); + add_message(&u->faction->msgs, msg); + + /* und noch einmal dem Magier melden */ + if (u->faction != mage->faction) { + add_message(&mage->faction->msgs, msg); + } + msg_release(msg); + } + + cast_level = _min(cast_level, (int)(cast_level * (victims + 4) / maxvictims)); + return _max(cast_level, 1); +} + +/** spell 'Astraler Weg'. + * Syntax "ZAUBERE [STUFE n] 'Astraler Weg' [ ...]", + * + * Parameter: + * pa->param[0]->data.xs + */ +int sp_enterastral(castorder * co) +{ + region *rt, *ro; + unit *u, *u2; + int remaining_cap; + int n, w; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + spellparameter *pa = co->par; + + switch (getplaneid(r)) { + case 0: + rt = r_standard_to_astral(r); + ro = r; + break; + default: + cmistake(mage, co->order, 190, MSG_MAGIC); + return 0; + } + + if (!rt || fval(rt->terrain, FORBIDDEN_REGION)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_astralregion", "")); + return 0; + } + + if (is_cursed(rt->attribs, C_ASTRALBLOCK, 0) + || is_cursed(ro->attribs, C_ASTRALBLOCK, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralblock", "")); + return 0; + } + + remaining_cap = (int)((power - 3) * 1500); + + /* fuer jede Einheit in der Kommandozeile */ + for (n = 0; n < pa->length; n++) { + if (pa->param[n]->flag == TARGET_NOTFOUND) + continue; + u = pa->param[n]->data.u; + + if (!ucontact(u, mage)) { + if (power > 10 && !is_magic_resistant(mage, u, 0) && can_survive(u, rt)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact_no_resist", "target", u)); + ADDMSG(&u->faction->msgs, msg_message("send_astral", "unit target", + mage, u)); + } + else { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact_resist", "target", u)); + ADDMSG(&u->faction->msgs, msg_message("try_astral", "unit target", mage, + u)); + continue; + } + } + + w = weight(u); + if (!can_survive(u, rt)) { + cmistake(mage, co->order, 231, MSG_MAGIC); + } + else if (remaining_cap - w < 0) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "fail_tooheavy", "target", u)); + } + else { + message *m; + remaining_cap = remaining_cap - w; + move_unit(u, rt, NULL); + + /* Meldungen in der Ausgangsregion */ + + for (u2 = r->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = r->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, r, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_disappear", "unit", u); + r_addmessage(r, u2->faction, m); + } + } + } + if (m) + msg_release(m); + + /* Meldungen in der Zielregion */ + + for (u2 = rt->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = rt->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, rt, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_appear", "unit", u); + r_addmessage(rt, u2->faction, m); + } + } + } + if (m) + msg_release(m); + } + } + return cast_level; +} + +/** Spell 'Astraler Ruf' / 'Astral Call'. + */ +int sp_pullastral(castorder * co) +{ + region *rt, *ro; + unit *u, *u2; + region_list *rl, *rl2; + int remaining_cap; + int n, w; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + spellparameter *pa = co->par; + + switch (getplaneid(r)) { + case 1: + rt = r; + ro = pa->param[0]->data.r; + rl = astralregions(r, NULL); + rl2 = rl; + while (rl2 != NULL) { + region *r2 = rl2->data; + if (r2->x == ro->x && r2->y == ro->y) { + ro = r2; + break; + } + rl2 = rl2->next; + } + if (!rl2) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail::nocontact", "target", rt)); + free_regionlist(rl); + return 0; + } + free_regionlist(rl); + break; + default: + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralonly", "")); + return 0; + } + + if (is_cursed(rt->attribs, C_ASTRALBLOCK, 0) + || is_cursed(ro->attribs, C_ASTRALBLOCK, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralblock", "")); + return 0; + } + + remaining_cap = (int)((power - 3) * 1500); + + /* fuer jede Einheit in der Kommandozeile */ + for (n = 1; n < pa->length; n++) { + spllprm *spobj = pa->param[n]; + if (spobj->flag == TARGET_NOTFOUND) + continue; + + u = spobj->data.u; + + if (u->region != ro) { + /* Report this as unit not found */ + if (spobj->typ == SPP_UNIT) { + spobj->data.i = u->no; + } + else { + spobj->data.i = ualias(u); + } + spobj->flag = TARGET_NOTFOUND; + ADDMSG(&mage->faction->msgs, msg_unitnotfound(mage, co->order, spobj)); + return false; + } + + if (!ucontact(u, mage)) { + if (power > 12 && spobj->flag != TARGET_RESISTS && can_survive(u, rt)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact_no_resist", "target", u)); + } + else { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact_resist", "target", u)); + ADDMSG(&u->faction->msgs, msg_message("try_astral", "unit target", mage, + u)); + continue; + } + } + + w = weight(u); + + if (!can_survive(u, rt)) { + cmistake(mage, co->order, 231, MSG_MAGIC); + } + else if (remaining_cap - w < 0) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "fail_tooheavy", "target", u)); + } + else { + message *m; + + ADDMSG(&u->faction->msgs, msg_message("send_astral", "unit target", mage, + u)); + remaining_cap = remaining_cap - w; + move_unit(u, rt, NULL); + + /* Meldungen in der Ausgangsregion */ + + for (u2 = r->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = r->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, r, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_disappear", "unit", u); + r_addmessage(r, u2->faction, m); + } + } + } + if (m) + msg_release(m); + + /* Meldungen in der Zielregion */ + + for (u2 = rt->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = rt->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, rt, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_appear", "unit", u); + r_addmessage(rt, u2->faction, m); + } + } + } + if (m) + msg_release(m); + } + } + return cast_level; +} + +int sp_leaveastral(castorder * co) +{ + region *rt, *ro; + region_list *rl, *rl2; + unit *u, *u2; + int remaining_cap; + int n, w; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + spellparameter *pa = co->par; + + switch (getplaneid(r)) { + case 1: + ro = r; + rt = pa->param[0]->data.r; + if (!rt) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail::noway", "")); + return 0; + } + rl = astralregions(r, inhabitable); + rl2 = rl; + while (rl2 != NULL) { + if (rl2->data == rt) + break; + rl2 = rl2->next; + } + if (rl2 == NULL) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail::noway", "")); + free_regionlist(rl); + return 0; + } + free_regionlist(rl); + break; + default: + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spell_astral_only", "")); + return 0; + } + + if (ro == NULL || is_cursed(ro->attribs, C_ASTRALBLOCK, 0) + || is_cursed(rt->attribs, C_ASTRALBLOCK, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralblock", "")); + return 0; + } + + remaining_cap = (int)((power - 3) * 1500); + + /* fuer jede Einheit in der Kommandozeile */ + for (n = 1; n < pa->length; n++) { + if (pa->param[n]->flag == TARGET_NOTFOUND) + continue; + + u = pa->param[n]->data.u; + + if (!ucontact(u, mage)) { + if (power > 10 && !pa->param[n]->flag == TARGET_RESISTS + && can_survive(u, rt)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact_no_resist", "target", u)); + ADDMSG(&u->faction->msgs, msg_message("send_astral", "unit target", + mage, u)); + } + else { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact_resist", "target", u)); + ADDMSG(&u->faction->msgs, msg_message("try_astral", "unit target", mage, + u)); + continue; + } + } + + w = weight(u); + + if (!can_survive(u, rt)) { + cmistake(mage, co->order, 231, MSG_MAGIC); + } + else if (remaining_cap - w < 0) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "fail_tooheavy", "target", u)); + } + else { + message *m; + + remaining_cap = remaining_cap - w; + move_unit(u, rt, NULL); + + /* Meldungen in der Ausgangsregion */ + + for (u2 = r->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = r->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, r, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_disappear", "unit", u); + r_addmessage(r, u2->faction, m); + } + } + } + if (m) + msg_release(m); + + /* Meldungen in der Zielregion */ + + for (u2 = rt->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = rt->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, rt, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_appear", "unit", u); + r_addmessage(rt, u2->faction, m); + } + } + } + if (m) + msg_release(m); + } + } + return cast_level; +} + +int sp_fetchastral(castorder * co) +{ + int n; + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + float power = co->force; + int remaining_cap = (int)((power - 3) * 1500); + region_list *rtl = NULL; + region *rt = co_get_region(co); /* region to which we are fetching */ + region *ro = NULL; /* region in which the target is */ + + if (rplane(rt) != get_normalplane()) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, "error190", "")); + return 0; + } + + /* fuer jede Einheit in der Kommandozeile */ + for (n = 0; n != pa->length; ++n) { + unit *u2, *u = pa->param[n]->data.u; + int w; + message *m; + + if (pa->param[n]->flag & TARGET_NOTFOUND) + continue; + + if (u->region != ro) { + /* this can happen several times if the units are from different astral + * regions. Only possible on the intersections of schemes */ + region_list *rfind; + if (!is_astral(u->region)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralonly", "")); + continue; + } + if (rtl != NULL) + free_regionlist(rtl); + rtl = astralregions(u->region, NULL); + for (rfind = rtl; rfind != NULL; rfind = rfind->next) { + if (rfind->data == mage->region) + break; + } + if (rfind == NULL) { + /* the region r is not in the schemes of rt */ + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_distance", "target", u)); + continue; + } + ro = u->region; + } + + if (is_cursed(ro->attribs, C_ASTRALBLOCK, 0)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spellfail_astralblock", "")); + continue; + } + + if (!can_survive(u, rt)) { + cmistake(mage, co->order, 231, MSG_MAGIC); + continue; + } + + w = weight(u); + if (remaining_cap - w < 0) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "fail_tooheavy", "target", u)); + continue; + } + + if (!ucontact(u, mage)) { + if (power > 12 && !(pa->param[n]->flag & TARGET_RESISTS)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact_no_resist", "target", u)); + ADDMSG(&u->faction->msgs, msg_message("send_astral", "unit target", + mage, u)); + } + else { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "feedback_no_contact_resist", "target", u)); + ADDMSG(&u->faction->msgs, msg_message("try_astral", "unit target", mage, + u)); + continue; + } + } + + remaining_cap -= w; + move_unit(u, rt, NULL); + + /* Meldungen in der Ausgangsregion */ + for (u2 = ro->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = ro->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, ro, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_disappear", "unit", u); + r_addmessage(ro, u2->faction, m); + } + } + } + if (m) + msg_release(m); + + /* Meldungen in der Zielregion */ + for (u2 = rt->units; u2; u2 = u2->next) + freset(u2->faction, FFL_SELECT); + m = NULL; + for (u2 = rt->units; u2; u2 = u2->next) { + if (!fval(u2->faction, FFL_SELECT)) { + if (cansee(u2->faction, rt, u, 0)) { + fset(u2->faction, FFL_SELECT); + if (!m) + m = msg_message("astral_appear", "unit", u); + r_addmessage(rt, u2->faction, m); + } + } + } + if (m) + msg_release(m); + } + if (rtl != NULL) + free_regionlist(rtl); + return cast_level; +} + +#ifdef SHOWASTRAL_NOT_BORKED +int sp_showastral(castorder * co) +{ + unit *u; + region *rt; + int n = 0; + int c = 0; + region_list *rl, *rl2; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + + switch (getplaneid(r)) { + case 0: + rt = r_standard_to_astral(r); + if (!rt || fval(rt->terrain, FORBIDDEN_REGION)) { + /* Hier gibt es keine Verbindung zur astralen Welt */ + cmistake(mage, co->order, 216, MSG_MAGIC); + return 0; + } + break; + case 1: + rt = r; + break; + default: + /* Hier gibt es keine Verbindung zur astralen Welt */ + cmistake(mage, co->order, 216, MSG_MAGIC); + return 0; + } + + rl = all_in_range(rt, power / 5); + + /* Erst Einheiten zaehlen, fuer die Grammatik. */ + + for (rl2 = rl; rl2; rl2 = rl2->next) { + region *r2 = rl2->data; + if (!is_cursed(r2->attribs, C_ASTRALBLOCK, 0)) { + for (u = r2->units; u; u = u->next) { + if (u_race(u) != get_race(RC_SPECIAL) && u_race(u) != get_race(RC_SPELL)) + n++; + } + } + } + + if (n == 0) { + /* sprintf(buf, "%s kann niemanden im astralen Nebel entdecken.", + unitname(mage)); */ + cmistake(mage, co->order, 220, MSG_MAGIC); + } else { + + /* Ausgeben */ + + sprintf(buf, "%s hat eine Vision der astralen Ebene. Im astralen " + "Nebel zu erkennen sind ", unitname(mage)); + + for (rl2 = rl; rl2; rl2 = rl2->next) { + if (!is_cursed(rl2->data->attribs, C_ASTRALBLOCK, 0)) { + for (u = rl2->data->units; u; u = u->next) { + if (u_race(u) != get_race(RC_SPECIAL) && u_race(u) != get_race(RC_SPELL)) { + c++; + scat(unitname(u)); + scat(" ("); + if (!fval(u, UFL_ANON_FACTION)) { + scat(factionname(u->faction)); + scat(", "); + } + icat(u->number); + scat(" "); + scat(LOC(mage->faction->locale, rc_name(u_race(u), (u->number==1) ? NAME_SINGULAR:NAME_PLURAL))); + scat(", Entfernung "); + icat(distance(rl2->data, rt)); + scat(")"); + if (c == n - 1) { + scat(" und "); + } else if (c < n - 1) { + scat(", "); + } + } + } + } + } + scat("."); + addmessage(r, mage->faction, buf, MSG_MAGIC, ML_INFO); + } + + free_regionlist(rl); + return cast_level; + unused_arg(co); + return 0; +} +#endif + +/* ------------------------------------------------------------- */ +int sp_viewreality(castorder * co) +{ + region_list *rl, *rl2; + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + message *m; + + if (getplaneid(r) != 1) { + /* sprintf(buf, "Dieser Zauber kann nur im Astralraum gezaubert werden."); */ + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "spell_astral_only", "")); + return 0; + } + + rl = astralregions(r, NULL); + + /* Irgendwann mal auf Curses u/o Attribut umstellen. */ + for (rl2 = rl; rl2; rl2 = rl2->next) { + region *rt = rl2->data; + if (!is_cursed(rt->attribs, C_ASTRALBLOCK, 0)) { + u = + create_unit(rt, mage->faction, RS_FARVISION, get_race(RC_SPELL), 0, + "spell/viewreality", NULL); + set_level(u, SK_PERCEPTION, co->level / 2); + u->age = 2; + } + } + + free_regionlist(rl); + + m = msg_message("viewreality_effect", "unit", mage); + r_addmessage(r, mage->faction, m); + msg_release(m); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +int sp_disruptastral(castorder * co) +{ + region_list *rl, *rl2; + region *rt; + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + int duration = (int)(power / 3) + 1; + + switch (getplaneid(r)) { + case 0: + rt = r_standard_to_astral(r); + if (!rt || fval(rt->terrain, FORBIDDEN_REGION)) { + /* "Hier gibt es keine Verbindung zur astralen Welt." */ + cmistake(mage, co->order, 216, MSG_MAGIC); + return 0; + } + break; + case 1: + rt = r; + break; + default: + /* "Von hier aus kann man die astrale Ebene nicht erreichen." */ + cmistake(mage, co->order, 215, MSG_MAGIC); + return 0; + } + + rl = all_in_range(rt, (short)(power / 5), NULL); + + for (rl2 = rl; rl2 != NULL; rl2 = rl2->next) { + attrib *a; + float effect; + region *r2 = rl2->data; + spec_direction *sd; + int inhab_regions = 0; + region_list *trl = NULL; + + if (is_cursed(r2->attribs, C_ASTRALBLOCK, 0)) + continue; + + if (r2->units != NULL) { + region_list *trl2; + + trl = astralregions(rl2->data, inhabitable); + for (trl2 = trl; trl2; trl2 = trl2->next) + ++inhab_regions; + } + + /* Nicht-Permanente Tore zerstoeren */ + a = a_find(r->attribs, &at_direction); + + while (a != NULL && a->type == &at_direction) { + attrib *a2 = a->next; + sd = (spec_direction *)(a->data.v); + if (sd->duration != -1) + a_remove(&r->attribs, a); + a = a2; + } + + /* Einheiten auswerfen */ + + if (trl != NULL) { + for (u = r2->units; u; u = u->next) { + if (u_race(u) != get_race(RC_SPELL)) { + region_list *trl2 = trl; + region *tr; + int c = rng_int() % inhab_regions; + + /* Zufaellige Zielregion suchen */ + while (c-- != 0) + trl2 = trl2->next; + tr = trl2->data; + + if (!is_magic_resistant(mage, u, 0) && can_survive(u, tr)) { + message *msg = msg_message("disrupt_astral", "unit region", u, tr); + add_message(&u->faction->msgs, msg); + add_message(&tr->msgs, msg); + msg_release(msg); + + move_unit(u, tr, NULL); + } + } + } + free_regionlist(trl); + } + + /* Kontakt unterbinden */ + effect = 100; + create_curse(mage, &rl2->data->attribs, ct_find("astralblock"), + power, duration, effect, 0); + } + + free_regionlist(rl); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Mauern der Ewigkeit + * Stufe: 7 + * Kategorie: Artefakt + * Gebiet: Tybied + * Wirkung: + * Das Gebaeude kostet keinen Unterhalt mehr + * + * ZAUBER "Mauern der Ewigkeit" + * Flags: (0) + */ +static int sp_eternizewall(castorder * co) +{ + unit *u; + curse *c; + building *b; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + spellparameter *pa = co->par; + message *msg; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + b = pa->param[0]->data.b; + c = create_curse(mage, &b->attribs, ct_find("nocostbuilding"), + power * power, 1, zero_effect, 0); + + if (c == NULL) { /* ist bereits verzaubert */ + cmistake(mage, co->order, 206, MSG_MAGIC); + return 0; + } + + /* melden, 1x pro Partei in der Burg */ + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + msg = + msg_message("sp_eternizewall_effect", "mage building region", mage, b, r); + for (u = r->units; u; u = u->next) { + if (!fval(u->faction, FFL_SELECT)) { + fset(u->faction, FFL_SELECT); + if (u->building == b) { + r_addmessage(r, u->faction, msg); + } + } + } + if (r != mage->region) { + add_message(&mage->faction->msgs, msg); + } + else if (mage->building != b) { + r_addmessage(r, mage->faction, msg); + } + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Opfere Kraft + * Stufe: 15 + * Gebiet: Tybied + * Kategorie: Einheit, positiv + * Wirkung: + * Mit Hilfe dieses Zaubers kann der Magier einen Teil seiner + * magischen Kraft permanent auf einen anderen Magier uebertragen. Auf + * einen Tybied-Magier kann er die Haelfte der eingesetzten Kraft + * uebertragen, auf einen Magier eines anderen Gebietes ein Drittel. + * + * Flags: + * (UNITSPELL) + * + * Syntax: + * ZAUBERE \"Opfere Kraft\" + * "ui" + */ +int sp_permtransfer(castorder * co) +{ + int aura; + unit *tu; + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + const spell *sp = co->sp; + message *msg; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber + * abbrechen aber kosten lassen */ + if (pa->param[0]->flag == TARGET_RESISTS) + return cast_level; + + tu = pa->param[0]->data.u; + aura = pa->param[1]->data.i; + + if (!is_mage(tu)) { + /* sprintf(buf, "%s in %s: 'ZAUBER %s': Einheit ist kein Magier." + , unitname(mage), regionname(mage->region, mage->faction),sa->strings[0]); */ + cmistake(mage, co->order, 214, MSG_MAGIC); + return 0; + } + + aura = _min(get_spellpoints(mage) - spellcost(mage, sp), aura); + + change_maxspellpoints(mage, -aura); + change_spellpoints(mage, -aura); + + if (get_mage(tu)->magietyp == get_mage(mage)->magietyp) { + change_maxspellpoints(tu, aura / 2); + } + else { + change_maxspellpoints(tu, aura / 3); + } + + msg = + msg_message("sp_permtransfer_effect", "mage target amount", mage, tu, aura); + add_message(&mage->faction->msgs, msg); + if (tu->faction != mage->faction) { + add_message(&tu->faction->msgs, msg); + } + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* TODO: specialdirections? */ + +int sp_movecastle(castorder * co) +{ + building *b; + direction_t dir; + region *target_region; + unit *u, *unext; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + message *msg; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + b = pa->param[0]->data.b; + dir = get_direction(pa->param[1]->data.xs, mage->faction->locale); + + if (dir == NODIRECTION) { + /* Die Richtung wurde nicht erkannt */ + cmistake(mage, co->order, 71, MSG_PRODUCE); + return 0; + } + + if (b->size > (cast_level - 12) * 250) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "sp_movecastle_fail_0", "")); + return cast_level; + } + + target_region = rconnect(r, dir); + + if (!(target_region->terrain->flags & LAND_REGION)) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "sp_movecastle_fail_1", "direction", dir)); + return cast_level; + } + + bunhash(b); + translist(&r->buildings, &target_region->buildings, b); + b->region = target_region; + b->size -= b->size / (10 - rng_int() % 6); + bhash(b); + + for (u = r->units; u;) { + unext = u->next; + if (u->building == b) { + uunhash(u); + translist(&r->units, &target_region->units, u); + uhash(u); + } + u = unext; + } + + if ((b->type == bt_find("caravan") || b->type == bt_find("dam") + || b->type == bt_find("tunnel"))) { + direction_t d; + for (d = 0; d != MAXDIRECTIONS; ++d) { + if (rroad(r, d)) { + rsetroad(r, d, rroad(r, d) / 2); + } + } + } + + msg = msg_message("sp_movecastle_effect", "building direction", b, dir); + r_addmessage(r, NULL, msg); + msg_release(msg); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Luftschiff + * Stufe: 6 + * + * Wirkung: + * Laeßt ein Schiff eine Runde lang fliegen. Wirkt nur auf Boote und + * Langboote. + * Kombinierbar mit "Guenstige Winde", aber nicht mit "Sturmwind". + * + * Flag: + * (ONSHIPCAST | SHIPSPELL | TESTRESISTANCE) + */ +int sp_flying_ship(castorder * co) +{ + ship *sh; + unit *u; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + spellparameter *pa = co->par; + message *m = NULL; + int cno; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + sh = pa->param[0]->data.sh; + if (sh->type->construction->maxsize > 50) { + ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, + "error_flying_ship_too_big", "ship", sh)); + return 0; + } + + /* Duration = 1, nur diese Runde */ + + cno = levitate_ship(sh, mage, power, 1); + if (cno == 0) { + if (is_cursed(sh->attribs, C_SHIP_FLYING, 0)) { + /* Auf dem Schiff befindet liegt bereits so ein Zauber. */ + cmistake(mage, co->order, 211, MSG_MAGIC); + } + else if (is_cursed(sh->attribs, C_SHIP_SPEEDUP, 0)) { + /* Es ist zu gefaehrlich, ein sturmgepeitschtes Schiff fliegen zu lassen. */ + cmistake(mage, co->order, 210, MSG_MAGIC); + } + return 0; + } + sh->coast = NODIRECTION; + + /* melden, 1x pro Partei */ + for (u = r->units; u; u = u->next) + freset(u->faction, FFL_SELECT); + for (u = r->units; u; u = u->next) { + /* das sehen natuerlich auch die Leute an Land */ + if (!fval(u->faction, FFL_SELECT)) { + fset(u->faction, FFL_SELECT); + if (!m) + m = msg_message("flying_ship_result", "mage ship", mage, sh); + add_message(&u->faction->msgs, m); + } + } + if (m) + msg_release(m); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Stehle Aura + * Stufe: 6 + * Kategorie: Einheit, negativ + * Wirkung: + * Mit Hilfe dieses Zaubers kann der Magier einem anderen Magier + * seine Aura gegen dessen Willen entziehen und sich selber + * zufuehren. + * + * Flags: + * (FARCASTING | SPELLLEVEL | UNITSPELL | TESTRESISTANCE | + * TESTCANSEE) + * */ +int sp_stealaura(castorder * co) +{ + int taura; + unit *u; + unit *mage = co->magician.u; + int cast_level = co->level; + float power = co->force; + spellparameter *pa = co->par; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + /* Zieleinheit */ + u = pa->param[0]->data.u; + + if (!get_mage(u)) { + ADDMSG(&mage->faction->msgs, msg_message("stealaura_fail", "unit target", + mage, u)); + ADDMSG(&u->faction->msgs, msg_message("stealaura_fail_detect", "unit", u)); + return 0; + } + + taura = (get_mage(u)->spellpoints * (rng_int() % (int)(3 * power) + 1)) / 100; + + if (taura > 0) { + get_mage(u)->spellpoints -= taura; + get_mage(mage)->spellpoints += taura; + /* sprintf(buf, "%s entzieht %s %d Aura.", unitname(mage), unitname(u), + taura); */ + ADDMSG(&mage->faction->msgs, msg_message("stealaura_success", + "mage target aura", mage, u, taura)); + /* sprintf(buf, "%s fuehlt seine magischen Kraefte schwinden und verliert %d " + "Aura.", unitname(u), taura); */ + ADDMSG(&u->faction->msgs, msg_message("stealaura_detect", "unit aura", u, + taura)); + } + else { + ADDMSG(&mage->faction->msgs, msg_message("stealaura_fail", "unit target", + mage, u)); + ADDMSG(&u->faction->msgs, msg_message("stealaura_fail_detect", "unit", u)); + } + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Astrale Schwaechezone + * Stufe: 5 + * Kategorie: + * Wirkung: + * Reduziert die Staerke jedes Spruch in der Region um Level Haelt + * Sprueche bis zu einem Gesammtlevel von Staerke*10 aus, danach ist + * sie verbraucht. + * leibt bis zu Staerke Wochen aktiv. + * Ein Ring der Macht erhoeht die Staerke um 1, in einem Magierturm + * gezaubert gibt nochmal +1 auf Staerke. (force) + * + * Beispiel: + * Eine Antimagiezone Stufe 7 haelt bis zu 7 Wochen an oder Sprueche mit + * einem Gesammtlevel bis zu 70 auf. Also zB 7 Stufe 10 Sprueche, 10 + * Stufe 7 Sprueche oder 35 Stufe 2 Sprueche. Sie reduziert die Staerke + * (level+boni) jedes Spruchs, der in der Region gezaubert wird, um + * 7. Alle Sprueche mit einer Staerke kleiner als 7 schlagen fehl + * (power = 0). + * + * Flags: + * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) + * + */ +int sp_antimagiczone(castorder * co) +{ + float power; + float effect; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + int duration = (int)force + 1; + + /* Haelt Sprueche bis zu einem summierten Gesamtlevel von power aus. + * Jeder Zauber reduziert die 'Lebenskraft' (vigour) der Antimagiezone + * um seine Stufe */ + power = force * 10; + + /* Reduziert die Staerke jedes Spruchs um effect */ + effect = (float)cast_level; + + create_curse(mage, &r->attribs, ct_find("antimagiczone"), power, duration, + effect, 0); + + /* Erfolg melden */ + ADDMSG(&mage->faction->msgs, msg_message("regionmagic_effect", + "unit region command", mage, mage->region, co->order)); + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Schutzrunen + * Stufe: 8 + * Kosten: SPC_FIX + * + * Wirkung: + * Gibt Gebaeuden einen Bonus auf Magieresistenz von +20%. Der Zauber + * dauert 3+rng_int()%Level Wochen an, also im Extremfall bis zu 2 Jahre + * bei Stufe 20 + * + * Es koennen mehrere Zauber uebereinander gelegt werden, der Effekt + * summiert sich, jedoch wird die Dauer dadurch nicht verlaengert. + * + * oder: + * + * Gibt Schiffen einen Bonus auf Magieresistenz von +20%. Der Zauber + * dauert 3+rng_int()%Level Wochen an, also im Extremfall bis zu 2 Jahre + * bei Stufe 20 + * + * Es koennen mehrere Zauber uebereinander gelegt werden, der Effekt + * summiert sich, jedoch wird die Dauer dadurch nicht verlaengert. + * + * Flags: + * (ONSHIPCAST | TESTRESISTANCE) + * + * Syntax: + * ZAUBERE \"Runen des Schutzes\" GEBAEUDE + * ZAUBERE \"Runen des Schutzes\" SCHIFF + * "kc" + */ + +static int sp_magicrunes(castorder * co) +{ + int duration; + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + spellparameter *pa = co->par; + float effect; + + duration = 3 + rng_int() % cast_level; + effect = 20; + + switch (pa->param[0]->typ) { + case SPP_BUILDING: + { + building *b; + b = pa->param[0]->data.b; + + /* Magieresistenz der Burg erhoeht sich um 20% */ + create_curse(mage, &b->attribs, ct_find("magicrunes"), force, + duration, effect, 0); + + /* Erfolg melden */ + ADDMSG(&mage->faction->msgs, msg_message("objmagic_effect", + "unit region command target", mage, mage->region, co->order, + buildingname(b))); + break; + } + case SPP_SHIP: + { + ship *sh; + sh = pa->param[0]->data.sh; + /* Magieresistenz des Schiffs erhoeht sich um 20% */ + create_curse(mage, &sh->attribs, ct_find("magicrunes"), force, + duration, effect, 0); + + /* Erfolg melden */ + ADDMSG(&mage->faction->msgs, msg_message("objmagic_effect", + "unit region command target", mage, mage->region, co->order, + shipname(sh))); + break; + } + default: + /* fehlerhafter Parameter */ + return 0; + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Zeitdehnung + * + * Flags: + * (UNITSPELL | SPELLLEVEL | ONSHIPCAST | TESTCANSEE) + * Syntax: + * "u+" + */ + +int sp_speed2(castorder * co) +{ + int n, maxmen, used = 0, dur, men; + unit *u; + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + spellparameter *pa = co->par; + + maxmen = 2 * cast_level * cast_level; + dur = _max(1, cast_level / 2); + + for (n = 0; n < pa->length; n++) { + float effect; + /* sollte nie negativ werden */ + if (maxmen < 1) + break; + + if (pa->param[n]->flag == TARGET_RESISTS + || pa->param[n]->flag == TARGET_NOTFOUND) + continue; + + u = pa->param[n]->data.u; + + men = _min(maxmen, u->number); + effect = 2; + create_curse(mage, &u->attribs, ct_find("speed"), force, dur, effect, men); + maxmen -= men; + used += men; + } + + ADDMSG(&mage->faction->msgs, msg_message("speed_time_effect", + "unit region amount", mage, mage->region, used)); + /* Effektiv benoetigten cast_level (mindestens 1) zurueckgeben */ + used = (int)sqrt(used / 2); + return _max(1, used); +} + +/* ------------------------------------------------------------- */ +/* Name: Magiefresser + * Stufe: 7 + * Kosten: SPC_LEVEL + * + * Wirkung: + * Kann eine bestimmte Verzauberung angreifen und aufloesen. Die Staerke + * des Zaubers muss staerker sein als die der Verzauberung. + * Syntax: + * ZAUBERE \"Magiefresser\" REGION + * ZAUBERE \"Magiefresser\" EINHEIT + * ZAUBERE \"Magiefresser\" GEBAEUDE + * ZAUBERE \"Magiefresser\" SCHIFF + * + * "kc?c" + * Flags: + * (FARCASTING | SPELLLEVEL | ONSHIPCAST | TESTCANSEE) + */ +/* Jeder gebrochene Zauber verbraucht c->vigour an Zauberkraft + * (force) */ +int sp_q_antimagie(castorder * co) +{ + attrib **ap; + int obj; + curse *c = NULL; + int succ; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + spellparameter *pa = co->par; + const char *ts = NULL; + + obj = pa->param[0]->typ; + + switch (obj) { + case SPP_REGION: + ap = &r->attribs; + ts = regionname(r, mage->faction); + break; + + case SPP_TEMP: + case SPP_UNIT: + { + unit *u = pa->param[0]->data.u; + ap = &u->attribs; + ts = unitid(u); + break; + } + case SPP_BUILDING: + { + building *b = pa->param[0]->data.b; + ap = &b->attribs; + ts = buildingid(b); + break; + } + case SPP_SHIP: + { + ship *sh = pa->param[0]->data.sh; + ap = &sh->attribs; + ts = shipid(sh); + break; + } + default: + /* Das Zielobjekt wurde vergessen */ + cmistake(mage, co->order, 203, MSG_MAGIC); + return 0; + } + + succ = break_curse(ap, cast_level, force, c); + + if (succ) { + ADDMSG(&mage->faction->msgs, msg_message("destroy_magic_effect", + "unit region command succ target", mage, mage->region, co->order, succ, + ts)); + } + else { + ADDMSG(&mage->faction->msgs, msg_message("destroy_magic_noeffect", + "unit region command", mage, mage->region, co->order)); + } + return _max(succ, 1); +} + +/* ------------------------------------------------------------- */ +/* Name: Fluch brechen + * Stufe: 7 + * Kosten: SPC_LEVEL + * + * Wirkung: + * Kann eine bestimmte Verzauberung angreifen und aufloesen. Die Staerke + * des Zaubers muss staerker sein als die der Verzauberung. + * Syntax: + * ZAUBERE \"Fluch brechen\" REGION + * ZAUBERE \"Fluch brechen\" EINHEIT + * ZAUBERE \"Fluch brechen\" GEBAEUDE + * ZAUBERE \"Fluch brechen\" SCHIFF + * + * "kcc" + * Flags: + * (FARCASTING | SPELLLEVEL | ONSHIPCAST | TESTCANSEE) + */ +int sp_break_curse(castorder * co) +{ + attrib **ap; + int obj; + curse *c; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + float force = co->force; + spellparameter *pa = co->par; + const char *ts = NULL; + + if (pa->length < 2) { + /* Das Zielobjekt wurde vergessen */ + cmistake(mage, co->order, 203, MSG_MAGIC); + return 0; + } + + obj = pa->param[0]->typ; + + c = findcurse(atoi36(pa->param[1]->data.s)); + if (!c) { + /* Es wurde kein Ziel gefunden */ + ADDMSG(&mage->faction->msgs, msg_message("spelltargetnotfound", + "unit region command", mage, mage->region, co->order)); + } + else { + switch (obj) { + case SPP_REGION: + ap = &r->attribs; + ts = regionname(r, mage->faction); + break; + + case SPP_TEMP: + case SPP_UNIT: + { + unit *u = pa->param[0]->data.u; + ap = &u->attribs; + ts = unitid(u); + break; + } + case SPP_BUILDING: + { + building *b = pa->param[0]->data.b; + ap = &b->attribs; + ts = buildingid(b); + break; + } + case SPP_SHIP: + { + ship *sh = pa->param[0]->data.sh; + ap = &sh->attribs; + ts = shipid(sh); + break; + } + default: + /* Das Zielobjekt wurde vergessen */ + cmistake(mage, co->order, 203, MSG_MAGIC); + return 0; + } + + /* ueberpruefung, ob curse zu diesem objekt gehoert */ + if (!is_cursed_with(*ap, c)) { + /* Es wurde kein Ziel gefunden */ + ADDMSG(&mage->faction->msgs, + msg_message("spelltargetnotfound", "unit region command", + mage, mage->region, co->order)); + } + + /* curse aufloesen, wenn zauber staerker (force > vigour) */ + c->vigour -= force; + + if (c->vigour <= 0.0) { + remove_curse(ap, c); + + ADDMSG(&mage->faction->msgs, msg_message("destroy_curse_effect", + "unit region command id target", mage, mage->region, co->order, + pa->param[1]->data.xs, ts)); + } + else { + ADDMSG(&mage->faction->msgs, msg_message("destroy_curse_noeffect", + "unit region command id target", mage, mage->region, co->order, + pa->param[1]->data.xs, ts)); + } + } + + return cast_level; +} + +/* ------------------------------------------------------------- */ +int sp_becomewyrm(castorder * co) +{ + return 0; +} + +/* ------------------------------------------------------------- */ +/* Name: WDW-Pyramidenfindezauber + * Stufe: unterschiedlich + * Gebiet: alle + * Wirkung: + * gibt die ungefaehre Entfernung zur naechstgelegenen Pyramiden- + * region an. + * + * Flags: + */ +static int sp_wdwpyramid(castorder * co) +{ + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + + if (a_find(r->attribs, &at_wdwpyramid) != NULL) { + ADDMSG(&mage->faction->msgs, msg_message("wdw_pyramidspell_found", + "unit region command", mage, r, co->order)); + } + else { + region *r2; + int mindist = INT_MAX; + int minshowdist; + int maxshowdist; + + for (r2 = regions; r2; r2 = r2->next) { + if (a_find(r2->attribs, &at_wdwpyramid) != NULL) { + int dist = distance(mage->region, r2); + if (dist < mindist) { + mindist = dist; + } + } + } + + assert(mindist >= 1); + + minshowdist = mindist - rng_int() % 5; + maxshowdist = minshowdist + 4; + + ADDMSG(&mage->faction->msgs, msg_message("wdw_pyramidspell_notfound", + "unit region command mindist maxdist", mage, r, co->order, + _max(1, minshowdist), maxshowdist)); + } + + return cast_level; +} + +typedef struct spelldata { + const char *sname; + spell_f cast; + fumble_f fumble; +} spelldata; + +static spelldata spell_functions[] = { + /* M_GWYRRD */ + { "stonegolem", sp_create_stonegolem, 0 }, + { "irongolem", sp_create_irongolem, 0 }, + { "treegrow", sp_hain, fumble_ents }, + { "rustweapon", sp_rosthauch, 0 }, + { "cold_protection", sp_kaelteschutz, 0 }, + { "ironkeeper", sp_ironkeeper, 0 }, + { "magicstreet", sp_magicstreet, 0 }, + { "windshield", sp_windshield, 0 }, + { "mallorntreegrow", sp_mallornhain, fumble_ents }, + { "goodwinds", sp_goodwinds, 0 }, + { "healing", sp_healing, 0 }, + { "reelingarrows", sp_reeling_arrows, 0 }, + { "gwyrrdfumbleshield", sp_fumbleshield, 0 }, + { "transferauradruide", sp_transferaura, 0 }, + { "earthquake", sp_earthquake, 0 }, + { "stormwinds", sp_stormwinds, 0 }, + { "homestone", sp_homestone, 0 }, + { "wolfhowl", sp_wolfhowl, 0 }, + { "versteinern", sp_petrify, 0 }, + { "strongwall", sp_strong_wall, 0 }, + { "gwyrrddestroymagic", sp_destroy_magic, 0 }, + { "treewalkenter", sp_treewalkenter, 0 }, + { "treewalkexit", sp_treewalkexit, 0 }, + { "holyground", sp_holyground, 0 }, + { "summonent", sp_summonent, 0 }, + { "blessstonecircle", sp_blessstonecircle, 0 }, + { "barkskin", sp_armorshield, 0 }, + { "summonfireelemental", sp_drought, 0 }, + { "maelstrom", sp_maelstrom, 0 }, + { "magic_roots", sp_mallorn, 0 }, + { "great_drought", sp_great_drought, 0 }, + /* M_DRAIG */ + { "sparklechaos", sp_sparkle, 0 }, + { "magicboost", sp_magicboost, 0 }, + { "bloodsacrifice", sp_bloodsacrifice, 0 }, + { "berserk", sp_berserk, 0 }, + { "fumblecurse", sp_fumblecurse, patzer_fumblecurse }, + { "summonundead", sp_summonundead, patzer_peasantmob }, + { "combatrust", sp_combatrosthauch, 0 }, + { "transferaurachaos", sp_transferaura, 0 }, + { "firewall", sp_firewall, patzer_peasantmob }, + { "plague", sp_plague, patzer_peasantmob }, + { "chaosrow", sp_chaosrow, 0 }, + { "summonshadow", sp_summonshadow, patzer_peasantmob }, + { "undeadhero", sp_undeadhero, 0 }, + { "auraleak", sp_auraleak, 0 }, + { "draigfumbleshield", sp_fumbleshield, 0 }, + { "forestfire", sp_forest_fire, patzer_peasantmob }, + { "draigdestroymagic", sp_destroy_magic, 0 }, + { "unholypower", sp_unholypower, 0 }, + { "deathcloud", sp_deathcloud, patzer_peasantmob }, + { "summondragon", sp_summondragon, patzer_peasantmob }, + { "summonshadowlords", sp_summonshadowlords, patzer_peasantmob }, + { "chaossuction", sp_chaossuction, patzer_peasantmob }, + /* M_ILLAUN */ + { "sparkledream", sp_sparkle, 0 }, + { "shadowknights", sp_shadowknights, 0 }, + { "flee", sp_flee, 0 }, + { "puttorest", sp_puttorest, 0 }, + { "icastle", sp_icastle, 0 }, + { "transferauratraum", sp_transferaura, 0 }, + { "shapeshift", sp_illusionary_shapeshift, 0 }, + { "dreamreading", sp_dreamreading, 0 }, + { "tiredsoldiers", sp_tiredsoldiers, 0 }, + { "reanimate", sp_reanimate, 0 }, + { "analysedream", sp_analysedream, 0 }, + { "disturbingdreams", sp_disturbingdreams, 0 }, + { "sleep", sp_sleep, 0 }, + { "wisps", 0, 0 }, /* this spell is gone */ + { "gooddreams", sp_gooddreams, 0 }, + { "illaundestroymagic", sp_destroy_magic, 0 }, + { "clone", sp_clonecopy, 0 }, + { "bad_dreams", sp_baddreams, 0 }, + { "mindblast", sp_mindblast_temp, 0 }, + { "orkdream", sp_sweetdreams, 0 }, + { "summon_alp", sp_summon_alp, 0 }, + /* M_CERDDOR */ + { "appeasement", sp_denyattack, 0 }, + { "song_of_healing", sp_healing, 0 }, + { "generous", sp_generous, 0 }, + { "song_of_fear", sp_flee, 0 }, + { "courting", sp_recruit, 0 }, + { "song_of_confusion", sp_chaosrow, 0 }, + { "heroic_song", sp_hero, 0 }, + { "transfer_aura_song", sp_transferaura, 0 }, + { "analysesong_unit", sp_analysesong_unit, 0 }, + { "cerrdorfumbleshield", sp_fumbleshield, 0 }, + { "calm_monster", sp_calm_monster, 0 }, + { "seduction", sp_seduce, 0 }, + { "headache", sp_headache, 0 }, + { "sound_out", sp_pump, 0 }, + { "bloodthirst", sp_berserk, 0 }, + { "frighten", sp_frighten, 0 }, + { "analyse_object", sp_analysesong_obj, 0 }, + { "cerddor_destroymagic", sp_destroy_magic, 0 }, + { "migration", sp_migranten, 0 }, + { "summon_familiar", sp_summon_familiar, 0 }, + { "raise_mob", sp_raisepeasants, 0 }, + { "song_resist_magic", sp_song_resistmagic, 0 }, + { "melancholy", sp_depression, 0 }, + { "song_suscept_magic", sp_song_susceptmagic, 0 }, + { "song_of_peace", sp_song_of_peace, 0 }, + { "song_of_slavery", sp_charmingsong, 0 }, + { "big_recruit", sp_bigrecruit, 0 }, + { "calm_riot", sp_rallypeasantmob, 0 }, + { "incite_riot", sp_raisepeasantmob, 0 }, + /* M_TYBIED */ + { "analyze_magic", sp_analysemagic, 0 }, + { "concealing_aura", sp_itemcloak, 0 }, + { "tybiedfumbleshield", sp_fumbleshield, 0 }, +#ifdef SHOWASTRAL_NOT_BORKED + { "show_astral", sp_showastral, 0}, +#endif + { "resist_magic", sp_resist_magic_bonus, 0 }, + { "keeploot", sp_keeploot, 0 }, + { "enterastral", sp_enterastral, 0 }, + { "leaveastral", sp_leaveastral, 0 }, + { "auratransfer", sp_transferaura, 0 }, + { "shockwave", sp_stun, 0 }, + { "antimagiczone", sp_antimagiczone, 0 }, + { "destroy_magic", sp_destroy_magic, 0 }, + { "pull_astral", sp_pullastral, 0 }, + { "fetch_astral", sp_fetchastral, 0 }, + { "steal_aura", sp_stealaura, 0 }, + { "airship", sp_flying_ship, 0 }, + { "break_curse", sp_break_curse, 0 }, + { "eternal_walls", sp_eternizewall, 0 }, + { "protective_runes", sp_magicrunes, 0 }, + { "fish_shield", sp_reduceshield, 0 }, + { "combat_speed", sp_speed, 0 }, + { "view_reality", sp_viewreality, 0 }, + { "double_time", sp_speed2, 0 }, + { "armor_shield", sp_armorshield, 0 }, + { "living_rock", sp_movecastle, 0 }, + { "astral_disruption", sp_disruptastral, 0 }, + { "sacrifice_strength", sp_permtransfer, 0 }, + /* M_GRAY */ + /* Definitionen von Create_Artefaktspruechen */ + { "wyrm_transformation", sp_becomewyrm, 0 }, + /* Monstersprueche */ + { "fiery_dragonbreath", sp_dragonodem, 0 }, + { "icy_dragonbreath", sp_dragonodem, 0 }, + { "powerful_dragonbreath", sp_dragonodem, 0 }, + { "drain_skills", sp_dragonodem, 0 }, + { "aura_of_fear", sp_flee, 0 }, + { "shadowcall", sp_shadowcall, 0 }, + { "immolation", sp_immolation, 0 }, + { "firestorm", sp_immolation, 0 }, + { "coldfront", sp_immolation, 0 }, + { "acidrain", sp_immolation, 0 }, + /* SPL_NOSPELL MUSS der letzte Spruch der Liste sein */ + { 0, 0, 0 } +}; + +static void register_spelldata(void) +{ + int i; + char zText[32]; + strcpy(zText, "fumble_"); + for (i = 0; spell_functions[i].sname; ++i) { + spelldata *data = spell_functions + i; + if (data->cast) { + register_function((pf_generic)data->cast, data->sname); + } + if (data->fumble) { + strlcpy(zText + 7, data->sname, sizeof(zText) - 7); + register_function((pf_generic)data->fumble, zText); + } + } +} + +/* ------------------------------------------------------------- */ +/* Name: Plappermaul +* Stufe: 4 +* Gebiet: Cerddor +* Kategorie: Einheit +* +* Wirkung: +* Einheit ausspionieren. Gibt auch Zauber und Kampfstatus aus. Wirkt +* gegen Magieresistenz. Ist diese zu hoch, so wird der Zauber entdeckt +* (Meldung) und der Zauberer erhaelt nur die Talente, nicht die Werte +* der Einheit und auch keine Zauber. +* +* Flag: +* (UNITSPELL | TESTCANSEE) +*/ +static int sp_babbler(castorder * co) +{ + unit *target; + region *r = co_get_region(co); + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + message *msg; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + target = pa->param[0]->data.u; + + if (target->faction == mage->faction) { + /* Die Einheit ist eine der unsrigen */ + cmistake(mage, co->order, 45, MSG_MAGIC); + } + + /* Magieresistenz Unit */ + if (target_resists_magic(mage, target, TYP_UNIT, 0)) { + spy_message(5, mage, target); + msg = msg_message("babbler_resist", "unit mage", target, mage); + } + else { + spy_message(100, mage, target); + msg = msg_message("babbler_effect", "unit", target); + } + r_addmessage(r, target->faction, msg); + msg_release(msg); + return cast_level; +} + +/* ------------------------------------------------------------- */ +/* Name: Traumdeuten +* Stufe: 7 +* Kategorie: Einheit +* +* Wirkung: +* Wirkt gegen Magieresistenz. Spioniert die Einheit aus. Gibt alle +* Gegenstaende, Talente mit Stufe, Zauber und Kampfstatus an. +* +* Magieresistenz hier pruefen, wegen Fehlermeldung +* +* Flag: +* (UNITSPELL) +*/ +static int sp_readmind(castorder * co) +{ + unit *target; + unit *mage = co->magician.u; + int cast_level = co->level; + spellparameter *pa = co->par; + + /* wenn kein Ziel gefunden, Zauber abbrechen */ + if (pa->param[0]->flag == TARGET_NOTFOUND) + return 0; + + target = pa->param[0]->data.u; + + if (target->faction == mage->faction) { + /* Die Einheit ist eine der unsrigen */ + cmistake(mage, co->order, 45, MSG_MAGIC); + } + + /* Magieresistenz Unit */ + if (target_resists_magic(mage, target, TYP_UNIT, 0)) { + cmistake(mage, co->order, 180, MSG_MAGIC); + /* "Fuehlt sich beobachtet" */ + ADDMSG(&target->faction->msgs, msg_message("stealdetect", "unit", target)); + return 0; + } + spy_message(2, mage, target); + + return cast_level; +} + +void register_spells(void) +{ + register_borders(); + + at_register(&at_wdwpyramid); + at_register(&at_deathcloud_compat); + + /* sp_summon_alp */ + register_alp(); + + /* init_firewall(); */ + ct_register(&ct_firewall); + ct_register(&ct_deathcloud); + + register_function((pf_generic)sp_blessedharvest, "cast_blessedharvest"); + register_function((pf_generic)sp_wdwpyramid, "wdwpyramid"); + register_function((pf_generic)sp_summon_familiar, "cast_familiar"); + register_function((pf_generic)sp_babbler, "cast_babbler"); + register_function((pf_generic)sp_readmind, "cast_readmind"); + register_function((pf_generic)sp_kampfzauber, "combat_spell"); + + register_spelldata(); + + register_unitcurse(); + register_regioncurse(); + register_shipcurse(); + register_buildingcurse(); +} diff --git a/src/spells/spells.h b/src/spells.h similarity index 100% rename from src/spells/spells.h rename to src/spells.h diff --git a/src/spells/CMakeLists.txt b/src/spells/CMakeLists.txt index be2891c8f..0fbd5c5a0 100644 --- a/src/spells/CMakeLists.txt +++ b/src/spells/CMakeLists.txt @@ -6,7 +6,6 @@ buildingcurse.c combatspells.c regioncurse.c shipcurse.c -spells.c unitcurse.c ) FOREACH(_FILE ${_FILES}) diff --git a/src/spells/spells.c b/src/spells/spells.c deleted file mode 100644 index 40a0fc482..000000000 --- a/src/spells/spells.c +++ /dev/null @@ -1,6805 +0,0 @@ -/* vi: set ts=2: - * - * - * Eressea PB(E)M host Copyright (C) 1998-2003 - * Christian Schlittchen (corwin@amber.kn-bremen.de) - * Katja Zedel (katze@felidae.kn-bremen.de) - * Henning Peters (faroul@beyond.kn-bremen.de) - * Enno Rehling (enno@eressea.de) - * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) - * - * This program may not be used, modified or distributed without - * prior permission by the authors of Eressea. - */ - -#include -#include -#include - -#include "../spy.h" -#include "spells.h" -#include "borders.h" -#include "buildingcurse.h" -#include "direction.h" -#include "regioncurse.h" -#include "unitcurse.h" -#include "shipcurse.h" -#include "combatspells.h" - -/* kernel includes */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* spells includes */ -#include "alp.h" - -/* util includes */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -/* libc includes */ -#include -#include -#include -#include -#include -#include -#include - -/* triggers includes */ -#include -#include -#include -#include -#include -#include -#include - -/* attributes includes */ -#include -#include -/* ----------------------------------------------------------------------- */ - -static float zero_effect = 0.0F; - -attrib_type at_wdwpyramid = { - "wdwpyramid", NULL, NULL, NULL, a_writevoid, a_readvoid -}; - -/* ----------------------------------------------------------------------- */ - -static void report_spell(unit * mage, region * r, message * msg) -{ - r_addmessage(r, NULL, msg); - if (mage && mage->region != r) { - add_message(&mage->faction->msgs, msg); - } -} - -static void report_failure(unit * mage, struct order *ord) -{ - /* Fehler: "Der Zauber schlaegt fehl" */ - cmistake(mage, ord, 180, MSG_MAGIC); -} - -/* ------------------------------------------------------------- */ -/* Spruchanalyse - Ausgabe von curse->info und curse->name */ -/* ------------------------------------------------------------- */ - -static double curse_chance(const struct curse *c, double force) -{ - return 1.0 + (force - c->vigour) * 0.1; -} - -static void magicanalyse_region(region * r, unit * mage, double force) -{ - attrib *a; - bool found = false; - - for (a = r->attribs; a; a = a->next) { - curse *c = (curse *) a->data.v; - double probability; - int mon; - - if (!fval(a->type, ATF_CURSE)) - continue; - - /* ist der curse schwaecher als der Analysezauber, so ergibt sich - * mehr als 100% probability und damit immer ein Erfolg. */ - probability = curse_chance(c, force); - mon = c->duration + (rng_int() % 10) - 5; - mon = _max(1, mon); - found = true; - - if (chance(probability)) { /* Analyse geglueckt */ - if (c_flags(c) & CURSE_NOAGE) { - ADDMSG(&mage->faction->msgs, msg_message("analyse_region_noage", - "mage region curse", mage, r, c->type)); - } else { - ADDMSG(&mage->faction->msgs, msg_message("analyse_region_age", - "mage region curse months", mage, r, c->type, mon)); - } - } else { - ADDMSG(&mage->faction->msgs, msg_message("analyse_region_fail", - "mage region", mage, r)); - } - } - if (!found) { - ADDMSG(&mage->faction->msgs, msg_message("analyse_region_nospell", - "mage region", mage, r)); - } -} - -static void magicanalyse_unit(unit * u, unit * mage, double force) -{ - attrib *a; - bool found = false; - - for (a = u->attribs; a; a = a->next) { - curse *c; - double probability; - int mon; - if (!fval(a->type, ATF_CURSE)) - continue; - - c = (curse *) a->data.v; - /* ist der curse schwaecher als der Analysezauber, so ergibt sich - * mehr als 100% probability und damit immer ein Erfolg. */ - probability = curse_chance(c, force); - mon = c->duration + (rng_int() % 10) - 5; - mon = _max(1, mon); - - if (chance(probability)) { /* Analyse geglueckt */ - if (c_flags(c) & CURSE_NOAGE) { - ADDMSG(&mage->faction->msgs, msg_message("analyse_unit_noage", - "mage unit curse", mage, u, c->type)); - } else { - ADDMSG(&mage->faction->msgs, msg_message("analyse_unit_age", - "mage unit curse months", mage, u, c->type, mon)); - } - } else { - ADDMSG(&mage->faction->msgs, msg_message("analyse_unit_fail", "mage unit", - mage, u)); - } - } - if (!found) { - ADDMSG(&mage->faction->msgs, msg_message("analyse_unit_nospell", - "mage target", mage, u)); - } -} - -static void magicanalyse_building(building * b, unit * mage, double force) -{ - attrib *a; - bool found = false; - - for (a = b->attribs; a; a = a->next) { - curse *c; - double probability; - int mon; - - if (!fval(a->type, ATF_CURSE)) - continue; - - c = (curse *) a->data.v; - /* ist der curse schwaecher als der Analysezauber, so ergibt sich - * mehr als 100% probability und damit immer ein Erfolg. */ - probability = curse_chance(c, force); - mon = c->duration + (rng_int() % 10) - 5; - mon = _max(1, mon); - - if (chance(probability)) { /* Analyse geglueckt */ - if (c_flags(c) & CURSE_NOAGE) { - ADDMSG(&mage->faction->msgs, msg_message("analyse_building_age", - "mage building curse", mage, b, c->type)); - } else { - ADDMSG(&mage->faction->msgs, msg_message("analyse_building_age", - "mage building curse months", mage, b, c->type, mon)); - } - } else { - ADDMSG(&mage->faction->msgs, msg_message("analyse_building_fail", - "mage building", mage, b)); - } - } - if (!found) { - ADDMSG(&mage->faction->msgs, msg_message("analyse_building_nospell", - "mage building", mage, b)); - } - -} - -static void magicanalyse_ship(ship * sh, unit * mage, double force) -{ - attrib *a; - bool found = false; - - for (a = sh->attribs; a; a = a->next) { - curse *c; - double probability; - int mon; - if (!fval(a->type, ATF_CURSE)) - continue; - - c = (curse *) a->data.v; - /* ist der curse schwaecher als der Analysezauber, so ergibt sich - * mehr als 100% probability und damit immer ein Erfolg. */ - probability = curse_chance(c, force); - mon = c->duration + (rng_int() % 10) - 5; - mon = _max(1, mon); - - if (chance(probability)) { /* Analyse geglueckt */ - if (c_flags(c) & CURSE_NOAGE) { - ADDMSG(&mage->faction->msgs, msg_message("analyse_ship_noage", - "mage ship curse", mage, sh, c->type)); - } else { - ADDMSG(&mage->faction->msgs, msg_message("analyse_ship_age", - "mage ship curse months", mage, sh, c->type, mon)); - } - } else { - ADDMSG(&mage->faction->msgs, msg_message("analyse_ship_fail", "mage ship", - mage, sh)); - - } - } - if (!found) { - ADDMSG(&mage->faction->msgs, msg_message("analyse_ship_nospell", - "mage ship", mage, sh)); - } - -} - -static int break_curse(attrib ** alist, int cast_level, float force, curse * c) -{ - int succ = 0; -/* attrib **a = a_find(*ap, &at_curse); */ - attrib **ap = alist; - - while (*ap && force > 0) { - curse *c1; - attrib *a = *ap; - if (!fval(a->type, ATF_CURSE)) { - do { - ap = &(*ap)->next; - } while (*ap && a->type == (*ap)->type); - continue; - } - c1 = (curse *) a->data.v; - - /* Immunitaet pruefen */ - if (c_flags(c1) & CURSE_IMMUNE) { - do { - ap = &(*ap)->next; - } while (*ap && a->type == (*ap)->type); - continue; - } - - /* Wenn kein spezieller cursetyp angegeben ist, soll die Antimagie - * auf alle Verzauberungen wirken. Ansonsten pruefe, ob der Curse vom - * richtigen Typ ist. */ - if (!c || c == c1) { - float remain = destr_curse(c1, cast_level, force); - if (remain < force) { - succ = cast_level; - force = remain; - } - if (c1->vigour <= 0) { - a_remove(alist, a); - } - } - if (*ap == a) - ap = &a->next; - } - return succ; -} - -int report_action(region * r, unit * actor, message * msg, int flags) -{ - int result = 0; - unit *u; - int view = flags & (ACTION_CANSEE | ACTION_CANNOTSEE); - - /* melden, 1x pro Partei */ - if (flags & ACTION_RESET) { - freset(actor->faction, FFL_SELECT); - for (u = r->units; u; u = u->next) - freset(u->faction, FFL_SELECT); - } - if (view) { - for (u = r->units; u; u = u->next) { - if (!fval(u->faction, FFL_SELECT)) { - bool show = u->faction == actor->faction; - fset(u->faction, FFL_SELECT); - if (view == ACTION_CANSEE) { - /* Bei Fernzaubern sieht nur die eigene Partei den Magier */ - show = show || (r == actor->region - && cansee(u->faction, r, actor, 0)); - } - else if (view == ACTION_CANNOTSEE) { - show = !show && !(r == actor->region - && cansee(u->faction, r, actor, 0)); - } - else { - /* the unliely (or lazy) case */ - show = true; - } - - if (show) { - r_addmessage(r, u->faction, msg); - } - else { /* Partei des Magiers, sieht diesen immer */ - result = 1; - } - } - } - /* Ist niemand von der Partei des Magiers in der Region, dem Magier - * nochmal gesondert melden */ - if ((flags & ACTION_CANSEE) && !fval(actor->faction, FFL_SELECT)) { - add_message(&actor->faction->msgs, msg); - } - } - return result; -} - -/* ------------------------------------------------------------- */ -/* Report a spell's effect to the units in the region. -*/ - -static void -report_effect(region * r, unit * mage, message * seen, message * unseen) -{ -#if 0 - unit *u; - - /* melden, 1x pro Partei */ - freset(mage->faction, FFL_SELECT); - for (u = r->units; u; u = u->next) - freset(u->faction, FFL_SELECT); - for (u = r->units; u; u = u->next) { - if (!fval(u->faction, FFL_SELECT)) { - fset(u->faction, FFL_SELECT); - - /* Bei Fernzaubern sieht nur die eigene Partei den Magier */ - if (u->faction != mage->faction) { - if (r == mage->region) { - /* kein Fernzauber, pruefe, ob der Magier ueberhaupt gesehen - * wird */ - if (cansee(u->faction, r, mage, 0)) { - r_addmessage(r, u->faction, seen); - } else { - r_addmessage(r, u->faction, unseen); - } - } else { /* Fernzauber, fremde Partei sieht den Magier niemals */ - r_addmessage(r, u->faction, unseen); - } - } else { /* Partei des Magiers, sieht diesen immer */ - r_addmessage(r, u->faction, seen); - } - } - } - /* Ist niemand von der Partei des Magiers in der Region, dem Magier - * nochmal gesondert melden */ - if (!fval(mage->faction, FFL_SELECT)) { - add_message(&mage->faction->msgs, seen); - } -#else - int err = report_action(r, mage, seen, ACTION_RESET | ACTION_CANSEE); - if (err) { - report_action(r, mage, seen, ACTION_CANNOTSEE); - } -#endif -} - -/* ------------------------------------------------------------- */ -/* Die Spruchfunktionen */ -/* ------------------------------------------------------------- */ -/* Meldungen: - * - * Fehlermeldungen sollten als MSG_MAGIC, level ML_MISTAKE oder - * ML_WARN ausgegeben werden. (stehen im Kopf der Auswertung unter - * Zauberwirkungen) - - sprintf(buf, "%s in %s: 'ZAUBER %s': [hier die Fehlermeldung].", - unitname(mage), regionname(mage->region, mage->faction), sa->strings[0]); - add_message(0, mage->faction, buf, MSG_MAGIC, ML_MISTAKE); - - * Allgemein sichtbare Auswirkungen in der Region sollten als - * Regionsereignisse auch dort auftauchen. - - { - message * seen = msg_message("harvest_effect", "mage", mage); - message * unseen = msg_message("harvest_effect", "mage", NULL); - report_effect(r, mage, seen, unseen); - } - - * Meldungen an den Magier ueber Erfolg sollten, wenn sie nicht als - * Regionsereigniss auftauchen, als MSG_MAGIC level ML_INFO unter - * Zauberwirkungen gemeldet werden. Direkt dem Magier zuordnen (wie - * Botschaft an Einheit) ist derzeit nicht moeglich. - * ACHTUNG! r muss nicht die Region des Magier sein! (FARCASTING) - * - * Parameter: - * die Struct castorder *co ist in magic.h deklariert - * die Parameterliste spellparameter *pa = co->par steht dort auch. - * - */ - -/* ------------------------------------------------------------- */ -/* Name: Vertrauter - * Stufe: 10 - * - * Wirkung: - * Der Magier beschwoert einen Vertrauten, ein kleines Tier, welches - * dem Magier zu Diensten ist. Der Magier kann durch die Augen des - * Vertrauten sehen, und durch den Vertrauten zaubern, allerdings nur - * mit seiner halben Stufe. Je nach Vertrautem erhaelt der Magier - * evtl diverse Skillmodifikationen. Der Typ des Vertrauten ist - * zufaellig bestimmt, wird aber durch Magiegebiet und Rasse beeinflußt. - * "Tierische" Vertraute brauchen keinen Unterhalt. - * - * Ein paar Moeglichkeiten: - * Magieg. Rasse Besonderheiten - * Eule Tybied -/- fliegt, Auraregeneration - * Rabe Ilaun -/- fliegt - * Imp Draig -/- Magieresistenz? - * Fuchs Gwyrrd -/- Wahrnehmung - * ???? Cerddor -/- ???? (Singvogel?, Papagei?) - * Adler -/- -/- fliegt, +Wahrnehmung, =^=Adlerauge-Spruch? - * Kraehe -/- -/- fliegt, +Tarnung (weil unauffaellig) - * Delphin -/- Meerm. schwimmt - * Wolf -/- Ork - * Hund -/- Mensch kann evtl BEWACHE ausfuehren - * Ratte -/- Goblin - * Albatros -/- -/- fliegt, kann auf Ozean "landen" - * Affe -/- -/- kann evtl BEKLAUE ausfuehren - * Goblin -/- !Goblin normale Einheit - * Katze -/- !Katze normale Einheit - * Daemon -/- !Daemon normale Einheit - * - * Spezielle V. fuer Katzen, Trolle, Elfen, Daemonen, Insekten, Zwerge? - */ - -static const race *select_familiar(const race * magerace, magic_t magiegebiet) -{ - const race *retval = 0; - int rnd = rng_int() % 100; - - assert(magerace->familiars[0]); - if (rnd >= 70) { - retval = magerace->familiars[magiegebiet]; - } else { - retval = magerace->familiars[0]; - } - - if (!retval || rnd < 3) { - race_list *familiarraces = get_familiarraces(); - unsigned int maxlen = listlen(familiarraces); - if (maxlen > 0) { - race_list *rclist = familiarraces; - int index = rng_int() % maxlen; - while (index-- > 0) { - rclist = rclist->next; - } - retval = rclist->data; - } - } - - if (!retval) { - retval = magerace->familiars[0]; - } - if (!retval) { - log_error("select_familiar: No familiar (not even a default) defined for %s.\n", magerace->_name); - } - return retval; -} - -/* ------------------------------------------------------------- */ -/* der Vertraue des Magiers */ - -static void make_familiar(unit * familiar, unit * mage) -{ - /* skills and spells: */ - if (u_race(familiar)->init_familiar != NULL) { - u_race(familiar)->init_familiar(familiar); - } else { - log_error("could not perform initialization for familiar %s.\n", familiar->faction->race->_name); - } - - /* triggers: */ - create_newfamiliar(mage, familiar); - - /* Hitpoints nach Talenten korrigieren, sonst starten vertraute - * mit Ausdauerbonus verwundet */ - familiar->hp = unit_max_hp(familiar); -} - -static int sp_summon_familiar(castorder * co) -{ - unit *familiar; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - const race *rc; - int sk; - int dh, dh1, bytes; - message *msg; - char zText[2048], *bufp = zText; - size_t size = sizeof(zText) - 1; - - if (get_familiar(mage) != NULL) { - cmistake(mage, co->order, 199, MSG_MAGIC); - return 0; - } - rc = select_familiar(mage->faction->race, mage->faction->magiegebiet); - if (rc == NULL) { - log_error("could not find suitable familiar for %s.\n", mage->faction->race->_name); - return 0; - } - - if (fval(rc, RCF_SWIM) && !fval(rc, RCF_WALK)) { - int coasts = is_coastregion(r); - int dir; - if (coasts == 0) { - cmistake(mage, co->order, 229, MSG_MAGIC); - return 0; - } - - /* In welcher benachbarten Ozeanregion soll der Familiar erscheinen? */ - coasts = rng_int() % coasts; - dh = -1; - for (dir = 0; dir != MAXDIRECTIONS; ++dir) { - region *rn = rconnect(r, dir); - if (rn && fval(rn->terrain, SEA_REGION)) { - dh++; - if (dh == coasts) - break; - } - } - r = rconnect(r, dir); - } - - msg = msg_message("familiar_name", "unit", mage); - nr_render(msg, mage->faction->locale, zText, sizeof(zText), mage->faction); - msg_release(msg); - familiar = create_unit(r, mage->faction, 1, rc, 0, zText, mage); - setstatus(familiar, ST_FLEE); - fset(familiar, UFL_LOCKED); - make_familiar(familiar, mage); - - dh = 0; - dh1 = 0; - for (sk = 0; sk < MAXSKILLS; ++sk) { - if (skill_enabled(sk) && rc->bonus[sk] > -5) - dh++; - } - - for (sk = 0; sk < MAXSKILLS; sk++) { - if (skill_enabled(sk) && rc->bonus[sk] > -5) { - dh--; - if (dh1 == 0) { - dh1 = 1; - } else { - if (dh == 0) { - bytes = - (int)strlcpy(bufp, (const char *)LOC(mage->faction->locale, - "list_and"), size); - } else { - bytes = (int)strlcpy(bufp, (const char *)", ", size); - } - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - } - bytes = - (int)strlcpy(bufp, (const char *)skillname((skill_t)sk, mage->faction->locale), - size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - } - } - ADDMSG(&mage->faction->msgs, msg_message("familiar_describe", - "mage race skills", mage, rc, zText)); - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Zerstoere Magie - * Wirkung: - * Zerstoert alle Zauberwirkungen auf dem Objekt. Jeder gebrochene - * Zauber verbraucht c->vigour an Zauberkraft. Wird der Spruch auf - * einer geringeren Stufe gezaubert, als der Zielzauber an c->vigour - * hat, so schlaegt die Aufloesung mit einer von der Differenz abhaengigen - * Chance fehl. Auch dann wird force verbraucht, der Zauber jedoch nur - * abgeschwaecht. - * - * Flag: - * (FARCASTING|SPELLLEVEL|ONSHIPCAST|TESTCANSEE) - * */ -static int sp_destroy_magic(castorder * co) -{ - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - spellparameter *pa = co->par; - curse *c = NULL; - char ts[80]; - attrib **ap; - int obj; - int succ; - - /* da jeder Zauber force verbraucht und der Zauber auf alles und nicht - * nur einen Spruch wirken soll, wird die Wirkung hier verstaerkt */ - force *= 4; - - /* Objekt ermitteln */ - obj = pa->param[0]->typ; - - switch (obj) { - case SPP_REGION: - { - /* region *tr = pa->param[0]->data.r; -- farcasting! */ - region *tr = co_get_region(co); - ap = &tr->attribs; - write_regionname(tr, mage->faction, ts, sizeof(ts)); - break; - } - case SPP_TEMP: - case SPP_UNIT: - { - unit *u; - u = pa->param[0]->data.u; - ap = &u->attribs; - write_unitname(u, ts, sizeof(ts)); - break; - } - case SPP_BUILDING: - { - building *b; - b = pa->param[0]->data.b; - ap = &b->attribs; - write_buildingname(b, ts, sizeof(ts)); - break; - } - case SPP_SHIP: - { - ship *sh; - sh = pa->param[0]->data.sh; - ap = &sh->attribs; - write_shipname(sh, ts, sizeof(ts)); - break; - } - default: - return 0; - } - - succ = break_curse(ap, cast_level, force, c); - - if (succ) { - ADDMSG(&mage->faction->msgs, msg_message("destroy_magic_effect", - "unit region command succ target", mage, mage->region, co->order, succ, - ts)); - } else { - ADDMSG(&mage->faction->msgs, msg_message("destroy_magic_noeffect", - "unit region command", mage, mage->region, co->order)); - } - - return _max(succ, 1); -} - -/* ------------------------------------------------------------- */ -/* Name: Transferiere Aura - * Stufe: variabel - * Gebiet: alle - * Kategorie: Einheit, positiv - * Wirkung: - * Mit Hilfe dieses Zauber kann der Magier eigene Aura im Verhaeltnis - * 2:1 auf einen anderen Magier des gleichen Magiegebietes oder (nur - * bei Tybied) im Verhaeltnis 3:1 auf einen Magier eines anderen - * Magiegebietes uebertragen. - * - * Syntax: - * "ZAUBERE " - * "ui" - * Flags: - * (UNITSPELL|ONSHIPCAST) - * */ - -static int sp_transferaura(castorder * co) -{ - int aura, gain, multi = 2; - unit *mage = co->magician.u; - int cast_level = co->level; - spellparameter *pa = co->par; - unit *u; - sc_mage *scm_dst, *scm_src = get_mage(mage); - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber - * abbrechen aber kosten lassen */ - if (pa->param[0]->flag == TARGET_RESISTS) - return cast_level; - - /* Wieviel Transferieren? */ - aura = pa->param[1]->data.i; - u = pa->param[0]->data.u; - scm_dst = get_mage(u); - - if (scm_dst == NULL) { - /* "Zu dieser Einheit kann ich keine Aura uebertragen." */ - cmistake(mage, co->order, 207, MSG_MAGIC); - return 0; - } else if (scm_src->magietyp == M_TYBIED) { - if (scm_src->magietyp != scm_dst->magietyp) - multi = 3; - } else if (scm_src->magietyp == M_GRAY) { - if (scm_src->magietyp != scm_dst->magietyp) - multi = 4; - } else if (scm_dst->magietyp != scm_src->magietyp) { - /* "Zu dieser Einheit kann ich keine Aura uebertragen." */ - cmistake(mage, co->order, 207, MSG_MAGIC); - return 0; - } - - if (aura < multi) { - /* "Auraangabe fehlerhaft." */ - cmistake(mage, co->order, 208, MSG_MAGIC); - return 0; - } - - gain = _min(aura, scm_src->spellpoints) / multi; - scm_src->spellpoints -= gain * multi; - scm_dst->spellpoints += gain; - -/* sprintf(buf, "%s transferiert %d Aura auf %s", unitname(mage), - gain, unitname(u)); */ - ADDMSG(&mage->faction->msgs, msg_message("auratransfer_success", - "unit target aura", mage, u, gain)); - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* DRUIDE */ -/* ------------------------------------------------------------- */ -/* Name: Guenstige Winde - * Stufe: 4 - * Gebiet: Gwyrrd - * Wirkung: - * Schiffsbewegung +1, kein Abtreiben. Haelt (Stufe) Runden an. - * Kombinierbar mit "Sturmwind" (das +1 wird dadurch aber nicht - * verdoppelt), und "Luftschiff". - * - * Flags: - * (SHIPSPELL|ONSHIPCAST|SPELLLEVEL|TESTRESISTANCE) - */ - -static int sp_goodwinds(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - int duration = cast_level + 1; - spellparameter *pa = co->par; - message *m; - ship *sh; - unit *u; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - sh = pa->param[0]->data.sh; - - /* keine Probleme mit C_SHIP_SPEEDUP und C_SHIP_FLYING */ - /* NODRIFT bewirkt auch +1 Geschwindigkeit */ - create_curse(mage, &sh->attribs, ct_find("nodrift"), power, duration, - zero_effect, 0); - - /* melden, 1x pro Partei */ - freset(mage->faction, FFL_SELECT); - for (u = r->units; u; u = u->next) - freset(u->faction, FFL_SELECT); - m = msg_message("wind_effect", "mage ship", mage, sh); - for (u = r->units; u; u = u->next) { - if (u->ship != sh) /* nur den Schiffsbesatzungen! */ - continue; - if (!fval(u->faction, FFL_SELECT)) { - r_addmessage(r, u->faction, m); - fset(u->faction, FFL_SELECT); - } - } - if (!fval(mage->faction, FFL_SELECT)) { - r_addmessage(r, mage->faction, m); - } - msg_release(m); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Magischer Pfad - * Stufe: 4 - * Gebiet: Gwyrrd - * Wirkung: - * fuer Stufe Runden wird eine (magische) Strasse erzeugt, die wie eine - * normale Strasse wirkt. - * Im Ozean schlaegt der Spruch fehl - * - * Flags: - * (FARCASTING|SPELLLEVEL|REGIONSPELL|ONSHIPCAST|TESTRESISTANCE) - */ -static int sp_magicstreet(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - - if (!fval(r->terrain, LAND_REGION)) { - cmistake(mage, co->order, 186, MSG_MAGIC); - return 0; - } - - /* wirkt schon in der Zauberrunde! */ - create_curse(mage, &r->attribs, ct_find("magicstreet"), co->force, - co->level + 1, zero_effect, 0); - - /* melden, 1x pro Partei */ - { - message *seen = msg_message("path_effect", "mage region", mage, r); - message *unseen = msg_message("path_effect", "mage region", NULL, r); - report_effect(r, mage, seen, unseen); - msg_release(seen); - msg_release(unseen); - } - - return co->level; -} - -/* ------------------------------------------------------------- */ -/* Name: Erwecke Ents - * Stufe: 10 - * Kategorie: Beschwoerung, positiv - * Gebiet: Gwyrrd - * Wirkung: - * Verwandelt (Stufe) Baeume in eine Gruppe von Ents, die sich fuer Stufe - * Runden der Partei des Druiden anschliessen und danach wieder zu - * Baeumen werden - * Patzer: - * Monster-Ents entstehen - * - * Flags: - * (SPELLLEVEL) - */ -static int sp_summonent(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - unit *u; - attrib *a; - int ents; - - if (rtrees(r, 2) == 0) { - cmistake(mage, co->order, 204, MSG_EVENT); - /* nicht ohne baeume */ - return 0; - } - - ents = (int)_min(power * power, rtrees(r, 2)); - - u = create_unit(r, mage->faction, ents, get_race(RC_TREEMAN), 0, NULL, mage); - - a = a_new(&at_unitdissolve); - a->data.ca[0] = 2; /* An r->trees. */ - a->data.ca[1] = 5; /* 5% */ - a_add(&u->attribs, a); - fset(u, UFL_LOCKED); - - rsettrees(r, 2, rtrees(r, 2) - ents); - - /* melden, 1x pro Partei */ - { - message *seen = msg_message("ent_effect", "mage amount", mage, ents); - message *unseen = msg_message("ent_effect", "mage amount", NULL, ents); - report_effect(r, mage, seen, unseen); - msg_release(unseen); - msg_release(seen); - } - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Segne Steinkreis - * Stufe: 11 - * Kategorie: Artefakt - * Gebiet: Gwyrrd - * Wirkung: - * Es werden zwei neue Gebaeude eingefuehrt: Steinkreis und Steinkreis - * (gesegnet). Ersteres kann man bauen, letzteres wird aus einem - * fertigen Steinkreis mittels des Zaubers erschaffen. - * - * Flags: - * (BUILDINGSPELL) - * - */ -static int sp_blessstonecircle(castorder * co) -{ - building *b; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - spellparameter *p = co->par; - message *msg; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (p->param[0]->flag == TARGET_NOTFOUND) - return 0; - - b = p->param[0]->data.b; - - if (b->type != bt_find("stonecircle")) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "error_notstonecircle", "building", b)); - return 0; - } - - if (b->size < b->type->maxsize) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "error_notcomplete", "building", b)); - return 0; - } - - b->type = bt_find("blessedstonecircle"); - - msg = msg_message("blessedstonecircle_effect", "mage building", mage, b); - add_message(&r->msgs, msg); - msg_release(msg); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Mahlstrom - * Stufe: 15 - * Kategorie: Region, negativ - * Gebiet: Gwyrrd - * Wirkung: - * Erzeugt auf See einen Mahlstrom fuer Stufe-Wochen. Jedes Schiff, das - * durch den Mahlstrom segelt, nimmt 0-150% Schaden. (D.h. es hat auch - * eine 1/3-Chance, ohne Federlesens zu sinken. Der Mahlstrom sollte - * aus den Nachbarregionen sichtbar sein. - * - * Flags: - * (OCEANCASTABLE | ONSHIPCAST | REGIONSPELL | TESTRESISTANCE) - */ -static int sp_maelstrom(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - curse *c; - int duration = (int)co->force + 1; - - if (!fval(r->terrain, SEA_REGION)) { - cmistake(mage, co->order, 205, MSG_MAGIC); - /* nur auf ozean */ - return 0; - } - - /* Attribut auf Region. - * Existiert schon ein curse, so wird dieser verstaerkt - * (Max(Dauer), Max(Staerke))*/ - c = create_curse(mage, &r->attribs, ct_find("maelstrom"), co->force, duration, co->force, 0); - - /* melden, 1x pro Partei */ - if (c) { - message *seen = msg_message("maelstrom_effect", "mage", mage); - message *unseen = msg_message("maelstrom_effect", "mage", NULL); - report_effect(r, mage, seen, unseen); - msg_release(seen); - msg_release(unseen); - } - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Wurzeln der Magie - * Stufe: 16 - * Kategorie: Region, neutral - * Gebiet: Gwyrrd - * Wirkung: - * Wandelt einen Wald permanent in eine Mallornregion - * - * Flags: - * (FARCASTING | REGIONSPELL | TESTRESISTANCE) - */ -static int sp_mallorn(castorder * co) -{ - region *r = co_get_region(co); - int cast_level = co->level; - unit *mage = co->magician.u; - - if (!fval(r->terrain, LAND_REGION)) { - cmistake(mage, co->order, 290, MSG_MAGIC); - return 0; - } - if (fval(r, RF_MALLORN)) { - cmistake(mage, co->order, 291, MSG_MAGIC); - return 0; - } - - /* half the trees will die */ - rsettrees(r, 2, rtrees(r, 2) / 2); - rsettrees(r, 1, rtrees(r, 1) / 2); - rsettrees(r, 0, rtrees(r, 0) / 2); - fset(r, RF_MALLORN); - - /* melden, 1x pro Partei */ - { - message *seen = msg_message("mallorn_effect", "mage", mage); - message *unseen = msg_message("mallorn_effect", "mage", NULL); - report_effect(r, mage, seen, unseen); - msg_release(seen); - msg_release(unseen); - } - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Segen der Erde / Regentanz - * Stufe: 1 - * Kategorie: Region, positiv - * Gebiet: Gwyrrd - * - * Wirkung: - * Alle Bauern verdienen Stufe-Wochen 1 Silber mehr. - * - * Flags: - * (FARCASTING | SPELLLEVEL | ONSHIPCAST | REGIONSPELL) - */ -static int sp_blessedharvest(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - int duration = (int)co->force + 1; - /* Attribut auf Region. - * Existiert schon ein curse, so wird dieser verstaerkt - * (Max(Dauer), Max(Staerke))*/ - - if (create_curse(mage, &r->attribs, ct_find("blessedharvest"), co->force, - duration, 1.0, 0)) { - message *seen = msg_message("harvest_effect", "mage", mage); - message *unseen = msg_message("harvest_effect", "mage", NULL); - report_effect(r, mage, seen, unseen); - msg_release(seen); - msg_release(unseen); - } - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Hainzauber - * Stufe: 2 - * Kategorie: Region, positiv - * Gebiet: Gwyrrd - * Syntax: ZAUBER [REGION x y] [STUFE 2] "Hain" - * Wirkung: - * Erschafft Stufe-10*Stufe Jungbaeume - * - * Flag: - * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) - */ - -static int sp_hain(castorder * co) -{ - int trees; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - - if (!r->land) { - cmistake(mage, co->order, 296, MSG_MAGIC); - return 0; - } - if (fval(r, RF_MALLORN)) { - cmistake(mage, co->order, 92, MSG_MAGIC); - return 0; - } - - trees = lovar((int)(force * 10 * RESOURCE_QUANTITY)) + (int)force; - rsettrees(r, 1, rtrees(r, 1) + trees); - - /* melden, 1x pro Partei */ - { - message *seen = msg_message("growtree_effect", "mage amount", mage, trees); - message *unseen = - msg_message("growtree_effect", "mage amount", NULL, trees); - report_effect(r, mage, seen, unseen); - msg_release(seen); - msg_release(unseen); - } - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Segne Mallornstecken - Mallorn Hainzauber - * Stufe: 4 - * Kategorie: Region, positiv - * Gebiet: Gwyrrd - * Syntax: ZAUBER [REGION x y] [STUFE 4] "Segne Mallornstecken" - * Wirkung: - * Erschafft Stufe-10*Stufe Jungbaeume - * - * Flag: - * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) - */ - -static int sp_mallornhain(castorder * co) -{ - int trees; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - - if (!r->land) { - cmistake(mage, co->order, 296, MSG_MAGIC); - return 0; - } - if (!fval(r, RF_MALLORN)) { - cmistake(mage, co->order, 91, MSG_MAGIC); - return 0; - } - - trees = lovar((int)(force * 10 * RESOURCE_QUANTITY)) + (int)force; - rsettrees(r, 1, rtrees(r, 1) + trees); - - /* melden, 1x pro Partei */ - { - message *seen = msg_message("growtree_effect", "mage amount", mage, trees); - message *unseen = - msg_message("growtree_effect", "mage amount", NULL, trees); - report_effect(r, mage, seen, unseen); - msg_release(seen); - msg_release(unseen); - } - - return cast_level; -} - -static void fumble_ents(const castorder * co) -{ - int ents; - unit *u; - region *r = co_get_region(co); - unit *mage = co->magician.u; - /* int cast_level = co->level; */ - float force = co->force; - - if (!r->land) { - cmistake(mage, co->order, 296, MSG_MAGIC); - return; - } - - ents = (int)(force * 10); - u = create_unit(r, get_monsters(), ents, get_race(RC_TREEMAN), 0, NULL, NULL); - - if (u) { - message *unseen; - - /* 'Erfolg' melden */ - ADDMSG(&mage->faction->msgs, msg_message("regionmagic_patzer", - "unit region command", mage, mage->region, co->order)); - - /* melden, 1x pro Partei */ - unseen = msg_message("entrise", "region", r); - report_effect(r, mage, unseen, unseen); - msg_release(unseen); - } -} - -/* ------------------------------------------------------------- */ -/* Name: Rosthauch - * Stufe: 3 - * Kategorie: Einheit, negativ - * Gebiet: Gwyrrd - * Wirkung: - * Zerstoert zwischen Stufe und Stufe*10 Eisenwaffen - * - * Flag: - * (FARCASTING | SPELLLEVEL | UNITSPELL | TESTCANSEE | TESTRESISTANCE) - */ -/* Syntax: ZAUBER [REGION x y] [STUFE 2] "Rosthauch" 1111 2222 3333 */ - -typedef struct iron_weapon { - const struct item_type *type; - const struct item_type *rusty; - float chance; - struct iron_weapon *next; -} iron_weapon; - -static iron_weapon *ironweapons = NULL; - -void -add_ironweapon(const struct item_type *type, const struct item_type *rusty, - float chance) -{ - iron_weapon *iweapon = malloc(sizeof(iron_weapon)); - iweapon->type = type; - iweapon->rusty = rusty; - iweapon->chance = chance; - iweapon->next = ironweapons; - ironweapons = iweapon; -} - -static int sp_rosthauch(castorder * co) -{ - int n; - int success = 0; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - int force = (int)co->force; - spellparameter *pa = co->par; - - if (ironweapons == NULL) { - add_ironweapon(it_find("sword"), it_find("rustysword"), 1.0); - add_ironweapon(it_find("axe"), it_find("rustyaxe"), 1.0); - add_ironweapon(it_find("greatsword"), it_find("rustygreatsword"), 1.0); - add_ironweapon(it_find("halberd"), it_find("rustyhalberd"), 0.5f); -#ifndef NO_RUSTY_ARMOR - add_ironweapon(it_find("shield"), it_find("rustyshield"), 0.5f); - add_ironweapon(it_find("chainmail"), it_find("rustychainmail"), 0.2f); -#endif - } - - if (force > 0) { - force = rng_int() % ((int)(force * 10)) + force; - } - /* fuer jede Einheit */ - for (n = 0; n < pa->length; n++) { - unit *u = pa->param[n]->data.u; - int ironweapon = 0; - iron_weapon *iweapon = ironweapons; - - if (force <= 0) - break; - if (pa->param[n]->flag & (TARGET_RESISTS | TARGET_NOTFOUND)) - continue; - - for (; iweapon != NULL; iweapon = iweapon->next) { - item **ip = i_find(&u->items, iweapon->type); - if (*ip) { - int i = _min((*ip)->number, force); - if (iweapon->chance < 1.0) { - i = (int)(i * iweapon->chance); - } - if (i > 0) { - force -= i; - ironweapon += i; - i_change(ip, iweapon->type, -i); - if (iweapon->rusty) { - i_change(&u->items, iweapon->rusty, i); - } - } - } - if (force <= 0) - break; - } - - if (ironweapon > 0) { - /* {$mage mage} legt einen Rosthauch auf {target}. {amount} Waffen - * wurden vom Rost zerfressen */ - ADDMSG(&mage->faction->msgs, msg_message("rust_effect", - "mage target amount", mage, u, ironweapon)); - ADDMSG(&u->faction->msgs, msg_message("rust_effect", - "mage target amount", - cansee(u->faction, r, mage, 0) ? mage : NULL, u, ironweapon)); - success += ironweapon; - } else { - /* {$mage mage} legt einen Rosthauch auf {target}, doch der - * Rosthauch fand keine Nahrung */ - ADDMSG(&mage->faction->msgs, msg_message("rust_fail", "mage target", mage, - u)); - } - } - /* in success stehen nun die insgesamt zerstoerten Waffen. Im - * unguenstigsten Fall kann pro Stufe nur eine Waffe verzaubert werden, - * darum wird hier nur fuer alle Faelle in denen noch weniger Waffen - * betroffen wurden ein Kostennachlass gegeben */ - return _min(success, cast_level); -} - -/* ------------------------------------------------------------- */ -/* Name: Kaelteschutz - * Stufe: 3 - * Kategorie: Einheit, positiv - * Gebiet: Gwyrrd - * - * Wirkung: - * schuetzt ein bis mehrere Einheiten mit bis zu Stufe*10 Insekten vor - * den Auswirkungen der Kaelte. Sie koennen Gletscher betreten und dort - * ganz normal alles machen. Die Wirkung haelt Stufe Wochen an - * Insekten haben in Gletschern den selben Malus wie in Bergen. Zu - * lange drin, nicht mehr aendern - * - * Flag: - * (UNITSPELL | SPELLLEVEL | ONSHIPCAST | TESTCANSEE) - */ -/* Syntax: ZAUBER [STUFE n] "Kaelteschutz" eh1 [eh2 [eh3 [...]]] */ - -static int sp_kaelteschutz(castorder * co) -{ - unit *u; - int n, i = 0; - int men; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - int duration = _max(cast_level, (int)force) + 1; - spellparameter *pa = co->par; - float effect; - - force *= 10; /* 10 Personen pro Force-Punkt */ - - /* fuer jede Einheit in der Kommandozeile */ - for (n = 0; n < pa->length; n++) { - if (force < 1) - break; - - if (pa->param[n]->flag == TARGET_RESISTS - || pa->param[n]->flag == TARGET_NOTFOUND) - continue; - - u = pa->param[n]->data.u; - - if (force < u->number) { - men = (int)force; - } else { - men = u->number; - } - - effect = 1; - create_curse(mage, &u->attribs, ct_find("insectfur"), (float)cast_level, - duration, effect, men); - - force -= u->number; - ADDMSG(&mage->faction->msgs, msg_message("heat_effect", "mage target", mage, - u)); - if (u->faction != mage->faction) - ADDMSG(&u->faction->msgs, msg_message("heat_effect", "mage target", - cansee(u->faction, r, mage, 0) ? mage : NULL, u)); - i = cast_level; - } - /* Erstattung? */ - return i; -} - -/* ------------------------------------------------------------- */ -/* Name: Verwuenschung, Funkenregen, Naturfreund, ... - * Stufe: 1 - * Kategorie: Einheit, rein visuell - * Gebiet: Alle - * - * Wirkung: - * Die Einheit wird von einem magischen Effekt heimgesucht, der in ihrer - * Beschreibung auftaucht, aber nur visuellen Effekt hat. - * - * Flag: - * (UNITSPELL | TESTCANSEE | SPELLLEVEL) - */ -/* Syntax: ZAUBER "Funkenregen" eh1 */ - -static int sp_sparkle(castorder * co) -{ - unit *u; - unit *mage = co->magician.u; - int cast_level = co->level; - spellparameter *pa = co->par; - int duration = cast_level + 1; - float effect; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber - * abbrechen aber kosten lassen */ - if (pa->param[0]->flag == TARGET_RESISTS) - return cast_level; - - u = pa->param[0]->data.u; - effect = (float)(rng_int() % 0xffffff); - create_curse(mage, &u->attribs, ct_find("sparkle"), (float)cast_level, - duration, effect, u->number); - - ADDMSG(&mage->faction->msgs, msg_message("sparkle_effect", "mage target", - mage, u)); - if (u->faction != mage->faction) { - ADDMSG(&u->faction->msgs, msg_message("sparkle_effect", "mage target", mage, - u)); - } - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Eisengolem - * Stufe: 2 - * Kategorie: Beschwoerung, positiv - * Gebiet: Gwyrrd - * Wirkung: - * Erschafft eine Einheit Eisengolems mit Stufe*8 Golems. Jeder Golem - * hat jede Runde eine Chance von 15% zu Staub zu zerfallen. Gibt man - * den Golems den Befehl 'mache Schwert/Bihaender' oder 'mache - * Schild/Kettenhemd/Plattenpanzer', so werden pro Golem 5 Eisenbarren - * verbaut und der Golem loest sich auf. - * - * Golems sind zu langsam um wirklich im Kampf von Nutzen zu sein. - * Jedoch fangen sie eine Menge Schaden auf und sollten sie zufaellig - * treffen, so ist der Schaden fast immer toedlich. (Eisengolem: HP - * 50, AT 4, PA 2, Ruestung 2(KH), 2d10+4 TP, Magieresistenz 0.25) - * - * Golems nehmen nix an und geben nix. Sie bewegen sich immer nur 1 - * Region weit und ziehen aus Strassen keinen Nutzen. Ein Golem wiegt - * soviel wie ein Stein. Kann nicht im Sumpf gezaubert werden - * - * Flag: - * (SPELLLEVEL) - * - * #define GOLEM_IRON 4 - */ - -static int sp_create_irongolem(castorder * co) -{ - unit *u2; - attrib *a; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - int number = lovar(force * 8 * RESOURCE_QUANTITY); - if (number < 1) - number = 1; - - if (r->terrain == newterrain(T_SWAMP)) { - cmistake(mage, co->order, 188, MSG_MAGIC); - return 0; - } - - u2 = - create_unit(r, mage->faction, number, rc_find("irongolem"), 0, NULL, mage); - - set_level(u2, SK_ARMORER, 1); - set_level(u2, SK_WEAPONSMITH, 1); - - a = a_new(&at_unitdissolve); - a->data.ca[0] = 0; - a->data.ca[1] = IRONGOLEM_CRUMBLE; - a_add(&u2->attribs, a); - - ADDMSG(&mage->faction->msgs, - msg_message("magiccreate_effect", "region command unit amount object", - mage->region, co->order, mage, number, - LOC(mage->faction->locale, rc_name(rc_find("irongolem"), (u2->number == 1) ? NAME_SINGULAR : NAME_PLURAL)))); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Steingolem - * Stufe: 1 - * Kategorie: Beschwoerung, positiv - * Gebiet: Gwyrrd - * Wirkung: - * Erschafft eine Einheit Steingolems mit Stufe*5 Golems. Jeder Golem - * hat jede Runde eine Chance von 10% zu Staub zu zerfallen. Gibt man - * den Golems den Befehl 'mache Burg' oder 'mache Strasse', so werden - * pro Golem 10 Steine verbaut und der Golem loest sich auf. - * - * Golems sind zu langsam um wirklich im Kampf von Nutzen zu sein. - * Jedoch fangen sie eine Menge Schaden auf und sollten sie zufaellig - * treffen, so ist der Schaden fast immer toedlich. (Steingolem: HP 60, - * AT 4, PA 2, Ruestung 4(PP), 2d12+6 TP) - * - * Golems nehmen nix an und geben nix. Sie bewegen sich immer nur 1 - * Region weit und ziehen aus Strassen keinen Nutzen. Ein Golem wiegt - * soviel wie ein Stein. - * - * Kann nicht im Sumpf gezaubert werden - * - * Flag: - * (SPELLLEVEL) - * - * #define GOLEM_STONE 4 - */ -static int sp_create_stonegolem(castorder * co) -{ - unit *u2; - attrib *a; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - int number = lovar(co->force * 5 * RESOURCE_QUANTITY); - if (number < 1) - number = 1; - - if (r->terrain == newterrain(T_SWAMP)) { - cmistake(mage, co->order, 188, MSG_MAGIC); - return 0; - } - - u2 = - create_unit(r, mage->faction, number, rc_find("stonegolem"), 0, NULL, mage); - set_level(u2, SK_ROAD_BUILDING, 1); - set_level(u2, SK_BUILDING, 1); - - a = a_new(&at_unitdissolve); - a->data.ca[0] = 0; - a->data.ca[1] = STONEGOLEM_CRUMBLE; - a_add(&u2->attribs, a); - - ADDMSG(&mage->faction->msgs, - msg_message("magiccreate_effect", "region command unit amount object", - mage->region, co->order, mage, number, - LOC(mage->faction->locale, rc_name(rc_find("stonegolem"), (u2->number == 1) ? NAME_SINGULAR : NAME_PLURAL)))); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Große Duerre - * Stufe: 17 - * Kategorie: Region, negativ - * Gebiet: Gwyrrd - * - * Wirkung: - * 50% alle Bauern, Pferde, Baeume sterben. - * Zu 25% terraform: Gletscher wird mit 50% zu Sumpf, sonst Ozean, - * Sumpf wird zu Steppe, Ebene zur Steppe, Steppe zur Wueste. - * Besonderheiten: - * neuer Terraintyp Steppe: - * 5000 Felder, 500 Baeume, Strasse: 250 Steine. Anlegen wie in Ebene - * moeglich - * - * Flags: - * (FARCASTING | REGIONSPELL | TESTRESISTANCE) - */ - -static void destroy_all_roads(region * r) -{ - int i; - - for (i = 0; i < MAXDIRECTIONS; i++) { - rsetroad(r, (direction_t) i, 0); - } -} - -static int sp_great_drought(castorder * co) -{ - unit *u; - bool terraform = false; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - int duration = 2; - float effect; - - if (fval(r->terrain, SEA_REGION)) { - cmistake(mage, co->order, 189, MSG_MAGIC); - /* TODO: vielleicht einen netten Patzer hier? */ - return 0; - } - - /* sterben */ - rsetpeasants(r, rpeasants(r) / 2); /* evtl wuerfeln */ - rsettrees(r, 2, rtrees(r, 2) / 2); - rsettrees(r, 1, rtrees(r, 1) / 2); - rsettrees(r, 0, rtrees(r, 0) / 2); - rsethorses(r, rhorses(r) / 2); - - /* Arbeitslohn = 1/4 */ - effect = 4.0; /* curses: higher is stronger */ - create_curse(mage, &r->attribs, ct_find("drought"), force, duration, effect, - 0); - - /* terraforming */ - if (rng_int() % 100 < 25) { - terraform = true; - - switch (rterrain(r)) { - case T_PLAIN: - /* rsetterrain(r, T_GRASSLAND); */ - destroy_all_roads(r); - break; - - case T_SWAMP: - /* rsetterrain(r, T_GRASSLAND); */ - destroy_all_roads(r); - break; -/* - case T_GRASSLAND: - rsetterrain(r, T_DESERT); - destroy_all_roads(r); - break; -*/ - case T_GLACIER: - if (rng_int() % 100 < 50) { - rsetterrain(r, T_SWAMP); - destroy_all_roads(r); - } else { /* Ozean */ - destroy_all_roads(r); - rsetterrain(r, T_OCEAN); - /* Einheiten duerfen hier auf keinen Fall geloescht werden! */ - for (u = r->units; u; u = u->next) { - if (u_race(u) != get_race(RC_SPELL) && u->ship == 0) { - set_number(u, 0); - } - } - while (r->buildings) { - remove_building(&r->buildings, r->buildings); - } - } - break; - - default: - terraform = false; - break; - } - } - - if (!fval(r->terrain, SEA_REGION)) { - /* not destroying the region, so it should be safe to make this a local - * message */ - message *msg; - const char *mtype; - if (r->terrain == newterrain(T_SWAMP) && terraform) { - mtype = "drought_effect_1"; - } else if (!terraform) { - mtype = "drought_effect_2"; - } else { - mtype = "drought_effect_3"; - } - msg = msg_message(mtype, "mage region", mage, r); - add_message(&r->msgs, msg); - msg_release(msg); - } else { - /* possible that all units here get killed so better to inform with a global - * message */ - message *msg = msg_message("drought_effect_4", "mage region", mage, r); - for (u = r->units; u; u = u->next) - freset(u->faction, FFL_SELECT); - for (u = r->units; u; u = u->next) { - if (!fval(u->faction, FFL_SELECT)) { - fset(u->faction, FFL_SELECT); - add_message(&u->faction->msgs, msg); - } - } - if (!fval(mage->faction, FFL_SELECT)) { - add_message(&mage->faction->msgs, msg); - } - msg_release(msg); - } - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: 'Weg der Baeume' - * Stufe: 9 - * Kategorie: Teleport - * Gebiet: Gwyrrd - * Wirkung: - * Der Druide kann 5*Stufe GE in die astrale Ebene schicken. - * Der Druide wird nicht mitteleportiert, es sei denn, er gibt sich - * selbst mit an. - * Der Zauber funktioniert nur in Waeldern. - * - * Syntax: Zauber "Weg der Baeume" ... - * - * Flags: - * (UNITSPELL | SPELLLEVEL | TESTCANSEE) - */ -static int sp_treewalkenter(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - spellparameter *pa = co->par; - float power = co->force; - int cast_level = co->level; - region *rt; - int remaining_cap; - int n; - int erfolg = 0; - - if (getplane(r) != 0) { - cmistake(mage, co->order, 190, MSG_MAGIC); - return 0; - } - - if (!r_isforest(r)) { - cmistake(mage, co->order, 191, MSG_MAGIC); - return 0; - } - - rt = r_standard_to_astral(r); - if (rt == NULL || is_cursed(rt->attribs, C_ASTRALBLOCK, 0) - || fval(rt->terrain, FORBIDDEN_REGION)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_astralblock", "")); - return 0; - } - - remaining_cap = (int)(power * 500); - - /* fuer jede Einheit */ - for (n = 0; n < pa->length; n++) { - unit *u = pa->param[n]->data.u; - spllprm *param = pa->param[n]; - - if (param->flag & (TARGET_RESISTS | TARGET_NOTFOUND)) { - continue; - } - - if (!ucontact(u, mage)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "feedback_no_contact", "target", u)); - } else { - int w; - message *m; - unit *u2; - - if (!can_survive(u, rt)) { - cmistake(mage, co->order, 231, MSG_MAGIC); - continue; - } - - w = weight(u); - if (remaining_cap - w < 0) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "fail_tooheavy", "target", u)); - continue; - } - remaining_cap = remaining_cap - w; - move_unit(u, rt, NULL); - erfolg = cast_level; - - /* Meldungen in der Ausgangsregion */ - for (u2 = r->units; u2; u2 = u2->next) - freset(u2->faction, FFL_SELECT); - m = NULL; - for (u2 = r->units; u2; u2 = u2->next) { - if (!fval(u2->faction, FFL_SELECT)) { - if (cansee(u2->faction, r, u, 0)) { - fset(u2->faction, FFL_SELECT); - if (!m) - m = msg_message("astral_disappear", "unit", u); - r_addmessage(r, u2->faction, m); - } - } - } - if (m) - msg_release(m); - - /* Meldungen in der Zielregion */ - for (u2 = rt->units; u2; u2 = u2->next) - freset(u2->faction, FFL_SELECT); - m = NULL; - for (u2 = rt->units; u2; u2 = u2->next) { - if (!fval(u2->faction, FFL_SELECT)) { - if (cansee(u2->faction, rt, u, 0)) { - fset(u2->faction, FFL_SELECT); - if (!m) - m = msg_message("astral_appear", "unit", u); - r_addmessage(rt, u2->faction, m); - } - } - } - if (m) - msg_release(m); - } - } - return erfolg; -} - -/* ------------------------------------------------------------- */ -/* Name: 'Sog des Lebens' - * Stufe: 9 - * Kategorie: Teleport - * Gebiet: Gwyrrd - * Wirkung: - * Der Druide kann 5*Stufe GE aus die astrale Ebene schicken. Der - * Druide wird nicht mitteleportiert, es sei denn, er gibt sich selbst - * mit an. - * Der Zauber funktioniert nur, wenn die Zielregion ein Wald ist. - * - * Syntax: Zauber "Sog des Lebens" ... - * - * Flags: - * (UNITSPELL|SPELLLEVEL) - */ -static int sp_treewalkexit(castorder * co) -{ - region *rt; - region_list *rl, *rl2; - int tax, tay; - unit *u, *u2; - int remaining_cap; - int n; - int erfolg = 0; - region *r = co_get_region(co); - unit *mage = co->magician.u; - float power = co->force; - spellparameter *pa = co->par; - int cast_level = co->level; - - if (!is_astral(r)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_astralonly", "")); - return 0; - } - if (is_cursed(r->attribs, C_ASTRALBLOCK, 0)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_astralblock", "")); - return 0; - } - - remaining_cap = (int)(power * 500); - - if (pa->param[0]->typ != SPP_REGION) { - report_failure(mage, co->order); - return 0; - } - - /* Koordinaten setzen und Region loeschen fuer Überpruefung auf - * Gueltigkeit */ - rt = pa->param[0]->data.r; - tax = rt->x; - tay = rt->y; - rt = NULL; - - rl = astralregions(r, inhabitable); - rt = 0; - - rl2 = rl; - while (rl2) { - if (rl2->data->x == tax && rl2->data->y == tay) { - rt = rl2->data; - break; - } - rl2 = rl2->next; - } - free_regionlist(rl); - - if (!rt) { - cmistake(mage, co->order, 195, MSG_MAGIC); - return 0; - } - - if (!r_isforest(rt)) { - cmistake(mage, co->order, 196, MSG_MAGIC); - return 0; - } - - /* fuer jede Einheit in der Kommandozeile */ - for (n = 1; n < pa->length; n++) { - if (pa->param[n]->flag == TARGET_RESISTS - || pa->param[n]->flag == TARGET_NOTFOUND) - continue; - - u = pa->param[n]->data.u; - - if (!ucontact(u, mage)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "feedback_no_contact", "target", u)); - } else { - int w = weight(u); - if (!can_survive(u, rt)) { - cmistake(mage, co->order, 231, MSG_MAGIC); - } else if (remaining_cap - w < 0) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "fail_tooheavy", "target", u)); - } else { - message *m; - - remaining_cap = remaining_cap - w; - move_unit(u, rt, NULL); - erfolg = cast_level; - - /* Meldungen in der Ausgangsregion */ - - for (u2 = r->units; u2; u2 = u2->next) - freset(u2->faction, FFL_SELECT); - m = NULL; - for (u2 = r->units; u2; u2 = u2->next) { - if (!fval(u2->faction, FFL_SELECT)) { - if (cansee(u2->faction, r, u, 0)) { - fset(u2->faction, FFL_SELECT); - if (!m) - m = msg_message("astral_disappear", "unit", u); - r_addmessage(r, u2->faction, m); - } - } - } - if (m) - msg_release(m); - - /* Meldungen in der Zielregion */ - - for (u2 = rt->units; u2; u2 = u2->next) - freset(u2->faction, FFL_SELECT); - m = NULL; - for (u2 = rt->units; u2; u2 = u2->next) { - if (!fval(u2->faction, FFL_SELECT)) { - if (cansee(u2->faction, rt, u, 0)) { - fset(u2->faction, FFL_SELECT); - if (!m) - m = msg_message("astral_appear", "unit", u); - r_addmessage(rt, u2->faction, m); - } - } - } - if (m) - msg_release(m); - } - } - } - return erfolg; -} - -/* ------------------------------------------------------------- */ -/* Name: Heiliger Boden - * Stufe: 9 - * Kategorie: perm. Regionszauber - * Gebiet: Gwyrrd - * Wirkung: - * Es entstehen keine Untoten mehr, Untote betreten die Region - * nicht mehr. - * - * ZAUBER "Heiliger Boden" - * Flags: (0) - */ -static int sp_holyground(castorder * co) -{ - static const curse_type *ctype = NULL; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - message *msg = msg_message("sp_holyground_effect", "mage region", mage, r); - report_spell(mage, r, msg); - msg_release(msg); - - if (!ctype) { - ctype = ct_find("holyground"); - } - create_curse(mage, &r->attribs, ctype, power * power, 1, zero_effect, 0); - - a_removeall(&r->attribs, &at_deathcount); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Heimstein - * Stufe: 7 - * Kategorie: Artefakt - * Gebiet: Gwyrrd - * Wirkung: - * Die Burg kann nicht mehr durch Donnerbeben oder andere - * Gebaeudezerstoerenden Sprueche kaputt gemacht werden. Auch - * schuetzt der Zauber vor Belagerungskatapulten. - * - * ZAUBER Heimstein - * Flags: (0) - */ -static int sp_homestone(castorder * co) -{ - unit *u; - curse *c; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - float effect; - message *msg; - if (!mage->building || mage->building->type != bt_find("castle")) { - cmistake(mage, co->order, 197, MSG_MAGIC); - return 0; - } - - c = create_curse(mage, &mage->building->attribs, ct_find("magicwalls"), - force * force, 1, zero_effect, 0); - - if (c == NULL) { - cmistake(mage, co->order, 206, MSG_MAGIC); - return 0; - } - c_setflag(c, CURSE_NOAGE | CURSE_ONLYONE); - - /* Magieresistenz der Burg erhoeht sich um 50% */ - effect = 50.0F; - c = create_curse(mage, &mage->building->attribs, - ct_find("magicresistance"), force * force, 1, effect, 0); - c_setflag(c, CURSE_NOAGE); - - /* melden, 1x pro Partei in der Burg */ - for (u = r->units; u; u = u->next) - freset(u->faction, FFL_SELECT); - msg = msg_message("homestone_effect", "mage building", mage, mage->building); - for (u = r->units; u; u = u->next) { - if (!fval(u->faction, FFL_SELECT)) { - fset(u->faction, FFL_SELECT); - if (u->building == mage->building) { - r_addmessage(r, u->faction, msg); - } - } - } - msg_release(msg); - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Duerre - * Stufe: 13 - * Kategorie: Region, negativ - * Gebiet: Gwyrrd - * Wirkung: - * temporaer veraendert sich das Baummaximum und die maximalen Felder in - * einer Region auf die Haelfte des normalen. - * Die Haelfte der Baeume verdorren und Pferde verdursten. - * Arbeiten bringt nur noch 1/4 des normalen Verdienstes - * - * Flags: - * (FARCASTING|REGIONSPELL|TESTRESISTANCE), - */ -static int sp_drought(castorder * co) -{ - curse *c; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - int duration = (int)power + 1; - message *msg; - - if (fval(r->terrain, SEA_REGION)) { - cmistake(mage, co->order, 189, MSG_MAGIC); - /* TODO: vielleicht einen netten Patzer hier? */ - return 0; - } - - /* melden, 1x pro Partei */ - msg = msg_message("sp_drought_effect", "mage region", mage, r); - report_spell(mage, r, msg); - msg_release(msg); - - /* Wenn schon Duerre herrscht, dann setzen wir nur den Power-Level - * hoch (evtl dauert dann die Duerre laenger). Ansonsten volle - * Auswirkungen. - */ - c = get_curse(r->attribs, ct_find("drought")); - if (c) { - c->vigour = _max(c->vigour, power); - c->duration = _max(c->duration, (int)power); - } else { - float effect = 4.0; - /* Baeume und Pferde sterben */ - rsettrees(r, 2, rtrees(r, 2) / 2); - rsettrees(r, 1, rtrees(r, 1) / 2); - rsettrees(r, 0, rtrees(r, 0) / 2); - rsethorses(r, rhorses(r) / 2); - - create_curse(mage, &r->attribs, ct_find("drought"), power, duration, effect, - 0); - } - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Bergwaechter - * Stufe: 9 - * Gebiet: Gwyrrd - * Kategorie: Beschwoerung, negativ - * - * Wirkung: - * Erschafft in Bergen oder Gletschern einen Waechter, der durch bewachen - * den Eisen/Laen-Abbau fuer nicht-Allierte verhindert. Bergwaechter - * verhindern auch Abbau durch getarnte/unsichtbare Einheiten und lassen - * sich auch durch Belagerungen nicht aufhalten. - * - * (Ansonsten in economic.c:manufacture() entsprechend anpassen). - * - * Faehigkeiten (factypes.c): 50% Magieresistenz, 25 HP, 4d4 Schaden, - * 4 Ruestung (=PP) - * Flags: - * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) - */ -static int sp_ironkeeper(castorder * co) -{ - unit *keeper; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - message *msg; - - if (r->terrain != newterrain(T_MOUNTAIN) - && r->terrain != newterrain(T_GLACIER)) { - report_failure(mage, co->order); - return 0; - } - - keeper = - create_unit(r, mage->faction, 1, get_race(RC_IRONKEEPER), 0, NULL, mage); - - /*keeper->age = cast_level + 2; */ - setstatus(keeper, ST_AVOID); /* kaempft nicht */ - guard(keeper, GUARD_MINING); - fset(keeper, UFL_ISNEW); - /* Parteitarnen, damit man nicht sofort weiß, wer dahinter steckt */ - if (rule_stealth_faction()) { - fset(keeper, UFL_ANON_FACTION); - } - - { - trigger *tkill = trigger_killunit(keeper); - add_trigger(&keeper->attribs, "timer", trigger_timeout(cast_level + 2, - tkill)); - } - - msg = msg_message("summon_effect", "mage amount race", mage, 1, u_race(keeper)); - r_addmessage(r, NULL, msg); - msg_release(msg); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Sturmwind - Beschwoere einen Sturmelementar - * Stufe: 6 - * Gebiet: Gwyrrd - * - * Wirkung: - * Verdoppelt Geschwindigkeit aller angegebener Schiffe fuer diese - * Runde. Kombinierbar mit "Guenstige Winde", aber nicht mit - * "Luftschiff". - * - * Anstelle des alten ship->enchanted benutzen wir einen kurzfristigen - * Curse. Das ist zwar ein wenig aufwendiger, aber weitaus flexibler - * und erlaubt es zB, die Dauer spaeter problemlos zu veraendern. - * - * Flags: - * (SHIPSPELL|ONSHIPCAST|OCEANCASTABLE|TESTRESISTANCE) - */ - -static int sp_stormwinds(castorder * co) -{ - ship *sh; - unit *u; - int erfolg = 0; - region *r = co_get_region(co); - unit *mage = co->magician.u; - float power = co->force; - spellparameter *pa = co->par; - int n, force = (int)power; - message *m = NULL; - - /* melden vorbereiten */ - freset(mage->faction, FFL_SELECT); - for (u = r->units; u; u = u->next) - freset(u->faction, FFL_SELECT); - - for (n = 0; n < pa->length; n++) { - if (force <= 0) - break; - - if (pa->param[n]->flag == TARGET_RESISTS - || pa->param[n]->flag == TARGET_NOTFOUND) - continue; - - sh = pa->param[n]->data.sh; - - /* mit C_SHIP_NODRIFT haben wir kein Problem */ - if (is_cursed(sh->attribs, C_SHIP_FLYING, 0)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "error_spell_on_flying_ship", "ship", sh)) - continue; - } - if (is_cursed(sh->attribs, C_SHIP_SPEEDUP, 0)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "error_spell_on_ship_already", "ship", sh)) - continue; - } - - /* Duration = 1, nur diese Runde */ - create_curse(mage, &sh->attribs, ct_find("stormwind"), power, 1, - zero_effect, 0); - /* Da der Spruch nur diese Runde wirkt wird er nie im Report - * erscheinen */ - erfolg++; - force--; - - /* melden vorbereiten: */ - for (u = r->units; u; u = u->next) { - if (u->ship == sh) { - /* nur den Schiffsbesatzungen! */ - fset(u->faction, FFL_SELECT); - } - } - } - if (erfolg < pa->length) { - ADDMSG(&mage->faction->msgs, msg_message("stormwinds_reduced", - "unit ships maxships", mage, erfolg, pa->length)); - } - /* melden, 1x pro Partei auf Schiff und fuer den Magier */ - fset(mage->faction, FFL_SELECT); - for (u = r->units; u; u = u->next) { - if (fval(u->faction, FFL_SELECT)) { - freset(u->faction, FFL_SELECT); - if (erfolg > 0) { - if (!m) { - m = msg_message("stormwinds_effect", "unit", mage); - } - r_addmessage(r, u->faction, m); - } - } - } - if (m) - msg_release(m); - return erfolg; -} - -/* ------------------------------------------------------------- */ -/* Name: Donnerbeben - * Stufe: 6 - * Gebiet: Gwyrrd - * - * Wirkung: - * Zerstoert Stufe*10 "Steineinheiten" aller Gebaeude der Region, aber nie - * mehr als 25% des gesamten Gebaeudes (aber natuerlich mindestens ein - * Stein). - * - * Flags: - * (FARCASTING|REGIONSPELL|TESTRESISTANCE) - */ -static int sp_earthquake(castorder * co) -{ - int kaputt; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - message *msg; - building **blist = &r->buildings; - - while (*blist) { - building *burg = *blist; - - if (burg->size != 0 && !is_cursed(burg->attribs, C_MAGICWALLS, 0)) { - /* Magieresistenz */ - if (!target_resists_magic(mage, burg, TYP_BUILDING, 0)) { - kaputt = _min(10 * cast_level, burg->size / 4); - kaputt = _max(kaputt, 1); - burg->size -= kaputt; - if (burg->size == 0) { - /* TODO: sollten die Insassen nicht Schaden nehmen? */ - remove_building(blist, burg); - } - } - } - if (*blist == burg) - blist = &burg->next; - } - - /* melden, 1x pro Partei */ - msg = msg_message("earthquake_effect", "mage region", mage, r); - r_addmessage(r, NULL, msg); - msg_release(msg); - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* CHAOS / M_DRAIG / Draig */ -/* ------------------------------------------------------------- */ -void patzer_peasantmob(const castorder * co) -{ - int anteil = 6, n; - unit *u; - attrib *a; - region *r; - unit *mage = co->magician.u; - - r = mage->region->land ? mage->region : co_get_region(co); - - if (r->land) { - faction *f = get_monsters(); - const struct locale *lang = f->locale; - message *msg; - - anteil += rng_int() % 4; - n = rpeasants(r) * anteil / 10; - rsetpeasants(r, rpeasants(r) - n); - assert(rpeasants(r) >= 0); - - u = - create_unit(r, f, n, get_race(RC_PEASANT), 0, LOC(f->locale, "angry_mob"), - NULL); - fset(u, UFL_ISNEW); - /* guard(u, GUARD_ALL); hier zu frueh! Befehl BEWACHE setzten */ - addlist(&u->orders, create_order(K_GUARD, lang, NULL)); - set_order(&u->thisorder, default_order(lang)); - a = a_new(&at_unitdissolve); - a->data.ca[0] = 1; /* An rpeasants(r). */ - a->data.ca[1] = 10; /* 10% */ - a_add(&u->attribs, a); - a_add(&u->attribs, make_hate(mage)); - - msg = msg_message("mob_warning", ""); - r_addmessage(r, NULL, msg); - msg_release(msg); - } - return; -} - -/* ------------------------------------------------------------- */ -/* Name: Waldbrand - * Stufe: 10 - * Kategorie: Region, negativ - * Gebiet: Draig - * Wirkung: - * Vernichtet 10-80% aller Baeume in der Region. Kann sich auf benachbarte - * Regionen ausbreiten, wenn diese (stark) bewaldet sind. Fuer jeweils - * 10 verbrannte Baeume in der Startregion gibts es eine 1%-Chance, dass - * sich das Feuer auf stark bewaldete Nachbarregionen ausdehnt, auf - * bewaldeten mit halb so hoher Wahrscheinlichkeit. Dort verbrennen - * dann prozentual halbsoviele bzw ein viertel soviele Baeume wie in der - * Startregion. - * - * Im Extremfall: 1250 Baeume in Region, 80% davon verbrennen (1000). - * Dann breitet es sich mit 100% Chance in stark bewaldete Regionen - * aus, mit 50% in bewaldete. Dort verbrennen dann 40% bzw 20% der Baeume. - * Weiter als eine Nachbarregion breitet sich dass Feuer nicht aus. - * - * Sinn: Ein Feuer in einer "stark bewaldeten" Wueste hat so trotzdem kaum - * eine Chance, sich weiter auszubreiten, waehrend ein Brand in einem Wald - * sich fast mit Sicherheit weiter ausbreitet. - * - * Flags: - * (FARCASTING | REGIONSPELL | TESTRESISTANCE) - */ -static int sp_forest_fire(castorder * co) -{ - unit *u; - region *nr; - direction_t i; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - double probability; - double percentage = (rng_int() % 8 + 1) * 0.1; /* 10 - 80% */ - message *msg; - - int vernichtet_schoesslinge = (int)(rtrees(r, 1) * percentage); - int destroyed = (int)(rtrees(r, 2) * percentage); - - if (destroyed < 1) { - cmistake(mage, co->order, 198, MSG_MAGIC); - return 0; - } - - rsettrees(r, 2, rtrees(r, 2) - destroyed); - rsettrees(r, 1, rtrees(r, 1) - vernichtet_schoesslinge); - probability = destroyed * 0.001; /* Chance, dass es sich ausbreitet */ - - /* melden, 1x pro Partei */ - for (u = r->units; u; u = u->next) - freset(u->faction, FFL_SELECT); - msg = - msg_message("forestfire_effect", "mage region amount", mage, r, - destroyed + vernichtet_schoesslinge); - r_addmessage(r, NULL, msg); - add_message(&mage->faction->msgs, msg); - msg_release(msg); - - for (i = 0; i < MAXDIRECTIONS; i++) { - nr = rconnect(r, i); - assert(nr); - destroyed = 0; - vernichtet_schoesslinge = 0; - - if (rtrees(nr, 2) + rtrees(nr, 1) >= 800) { - if (chance(probability)) { - destroyed = (int)(rtrees(nr, 2) * percentage / 2); - vernichtet_schoesslinge = (int)(rtrees(nr, 1) * percentage / 2); - } - } else if (rtrees(nr, 2) + rtrees(nr, 1) >= 600) { - if (chance(probability / 2)) { - destroyed = (int)(rtrees(nr, 2) * percentage / 4); - vernichtet_schoesslinge = (int)(rtrees(nr, 1) * percentage / 4); - } - } - - if (destroyed > 0 || vernichtet_schoesslinge > 0) { - message *m = msg_message("forestfire_spread", "region next trees", - r, nr, destroyed + vernichtet_schoesslinge); - - add_message(&r->msgs, m); - add_message(&mage->faction->msgs, m); - msg_release(m); - - rsettrees(nr, 2, rtrees(nr, 2) - destroyed); - rsettrees(nr, 1, rtrees(nr, 1) - vernichtet_schoesslinge); - } - } - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Chaosfluch - * Stufe: 5 - * Gebiet: Draig - * Kategorie: (Antimagie) Kraftreduzierer, Einheit, negativ - * Wirkung: - * Auf einen Magier gezaubert verhindert/erschwert dieser Chaosfluch - * das Zaubern. Patzer werden warscheinlicher. - * Jeder Zauber muss erst gegen den Wiederstand des Fluchs gezaubert - * werden und schwaecht dessen Antimagiewiederstand um 1. - * Wirkt _max(Stufe(Magier) - Stufe(Ziel), rand(3)) Wochen - * Patzer: - * Magier wird selbst betroffen - * - * Flags: - * (UNITSPELL | SPELLLEVEL | TESTCANSEE | TESTRESISTANCE) - * - */ -static int sp_fumblecurse(castorder * co) -{ - unit *target; - int rx, sx; - int duration; - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - float effect; - curse *c; - spellparameter *pa = co->par; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - target = pa->param[0]->data.u; - - rx = rng_int() % 3; - sx = cast_level - effskill(target, SK_MAGIC); - duration = _max(sx, rx) + 1; - - effect = force/2; - c = create_curse(mage, &target->attribs, ct_find("fumble"), - force, duration, effect, 0); - if (c == NULL) { - report_failure(mage, co->order); - return 0; - } - - ADDMSG(&target->faction->msgs, msg_message("fumblecurse", "unit region", - target, target->region)); - - return cast_level; -} - -void patzer_fumblecurse(const castorder * co) -{ - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - int duration = (cast_level / 2) + 1; - float effect; - curse *c; - - effect = force/2; - c = create_curse(mage, &mage->attribs, ct_find("fumble"), force, - duration, effect, 0); - if (c != NULL) { - ADDMSG(&mage->faction->msgs, msg_message("magic_fumble", - "unit region command", mage, mage->region, co->order)); - } - return; -} - -/* ------------------------------------------------------------- */ -/* Name: Drachenruf - * Stufe: 11 - * Gebiet: Draig - * Kategorie: Monster, Beschwoerung, negativ - * - * Wirkung: - * In einer Wueste, Sumpf oder Gletscher gezaubert kann innerhalb der - * naechsten 6 Runden ein bis 6 Dracheneinheiten bis Groeße Wyrm - * entstehen. - * - * Mit Stufe 12-15 erscheinen Jung- oder normaler Drachen, mit Stufe - * 16+ erscheinen normale Drachen oder Wyrme. - * - * Flag: - * (FARCASTING | REGIONSPELL | TESTRESISTANCE) - */ - -static int sp_summondragon(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - unit *u; - int cast_level = co->level; - float power = co->force; - region_list *rl, *rl2; - faction *f; - int time; - int number; - const race *race; - - f = get_monsters(); - - if (r->terrain != newterrain(T_SWAMP) && r->terrain != newterrain(T_DESERT) - && r->terrain != newterrain(T_GLACIER)) { - report_failure(mage, co->order); - return 0; - } - - for (time = 1; time < 7; time++) { - if (rng_int() % 100 < 25) { - switch (rng_int() % 3) { - case 0: - race = get_race(RC_WYRM); - number = 1; - break; - - case 1: - race = get_race(RC_DRAGON); - number = 2; - break; - - case 2: - default: - race = get_race(RC_FIREDRAGON); - number = 6; - break; - } - { - trigger *tsummon = trigger_createunit(r, f, race, number); - add_trigger(&r->attribs, "timer", trigger_timeout(time, tsummon)); - } - } - } - - rl = all_in_range(r, (short)power, NULL); - - for (rl2 = rl; rl2; rl2 = rl2->next) { - region *r2 = rl2->data; - for (u = r2->units; u; u = u->next) { - if (u_race(u) == get_race(RC_WYRM) || u_race(u) == get_race(RC_DRAGON)) { - attrib *a = a_find(u->attribs, &at_targetregion); - if (!a) { - a = a_add(&u->attribs, make_targetregion(r)); - } else { - a->data.v = r; - } - } - } - } - - ADDMSG(&mage->faction->msgs, msg_message("summondragon", - "unit region command target", mage, mage->region, co->order, r)); - - free_regionlist(rl); - return cast_level; -} - -static int sp_firewall(castorder * co) -{ - connection *b; - wall_data *fd; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - spellparameter *pa = co->par; - direction_t dir; - region *r2; - - dir = get_direction(pa->param[0]->data.xs, mage->faction->locale); - if (dir < MAXDIRECTIONS && dir != NODIRECTION) { - r2 = rconnect(r, dir); - } else { - report_failure(mage, co->order); - return 0; - } - - if (!r2 || r2 == r) { - report_failure(mage, co->order); - return 0; - } - - b = get_borders(r, r2); - while (b != NULL) { - if (b->type == &bt_firewall) - break; - b = b->next; - } - if (b == NULL) { - b = new_border(&bt_firewall, r, r2); - fd = (wall_data *) b->data.v; - fd->force = (int)(force / 2 + 0.5); - fd->mage = mage; - fd->active = false; - fd->countdown = cast_level + 1; - } else { - fd = (wall_data *) b->data.v; - fd->force = (int)_max(fd->force, force / 2 + 0.5); - fd->countdown = _max(fd->countdown, cast_level + 1); - } - - /* melden, 1x pro Partei */ - { - message *seen = msg_message("firewall_effect", "mage region", mage, r); - message *unseen = msg_message("firewall_effect", "mage region", NULL, r); - report_effect(r, mage, seen, unseen); - msg_release(seen); - msg_release(unseen); - } - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Unheilige Kraft - * Stufe: 10 - * Gebiet: Draig - * Kategorie: Untote Einheit, positiv - * - * Wirkung: - * transformiert (Stufe)W10 Untote in ihre staerkere Form - * - * - * Flag: - * (SPELLLEVEL | TESTCANSEE) - */ - -static int sp_unholypower(castorder * co) -{ - region * r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - spellparameter *pa = co->par; - int i; - int n; - int wounds; - - n = dice((int)co->force, 10); - - for (i = 0; i < pa->length && n > 0; i++) { - const race *target_race; - unit *u; - - if (pa->param[i]->flag == TARGET_RESISTS - || pa->param[i]->flag == TARGET_NOTFOUND) - continue; - - u = pa->param[i]->data.u; - - switch (old_race(u_race(u))) { - case RC_SKELETON: - target_race = get_race(RC_SKELETON_LORD); - break; - case RC_ZOMBIE: - target_race = get_race(RC_ZOMBIE_LORD); - break; - case RC_GHOUL: - target_race = get_race(RC_GHOUL_LORD); - break; - default: - cmistake(mage, co->order, 284, MSG_MAGIC); - continue; - } - /* Untote heilen nicht, darum den neuen Untoten maximale hp geben - * und vorhandene Wunden abziehen */ - wounds = unit_max_hp(u) * u->number - u->hp; - - if (u->number <= n) { - n -= u->number; - u->irace = NULL; - u_setrace(u, target_race); - u->hp = unit_max_hp(u) * u->number - wounds; - ADDMSG(&r->msgs, msg_message("unholypower_effect", - "mage target race", mage, u, target_race)); - } else { - unit *un; - - /* Wird hoffentlich niemals vorkommen. Es gibt im Source - * vermutlich eine ganze Reihe von Stellen, wo das nicht - * korrekt abgefangen wird. Besser (aber nicht gerade einfach) - * waere es, eine solche Konstruktion irgendwie zu kapseln. */ - if (fval(u, UFL_LOCKED) || fval(u, UFL_HUNGER) - || is_cursed(u->attribs, C_SLAVE, 0)) { - cmistake(mage, co->order, 74, MSG_MAGIC); - continue; - } - /* Verletzungsanteil der transferierten Personen berechnen */ - wounds = wounds * n / u->number; - - un = create_unit(r, u->faction, 0, target_race, 0, NULL, u); - transfermen(u, un, n); - un->hp = unit_max_hp(un) * n - wounds; - ADDMSG(&r->msgs, msg_message("unholypower_limitedeffect", - "mage target race amount", mage, u, target_race, n)); - n = 0; - } - } - - return cast_level; -} - -static int dc_age(struct curse *c) -/* age returns 0 if the attribute needs to be removed, !=0 otherwise */ -{ - region *r = (region *) c->data.v; - unit **up; - unit *mage = c->magician; - - if (r == NULL || mage == NULL || mage->number == 0) { - /* if the mage disappears, so does the spell. */ - return AT_AGE_REMOVE; - } - - up = &r->units; - if (curse_active(c)) - while (*up != NULL) { - unit *u = *up; - double damage = c->effect * u->number; - - freset(u->faction, FFL_SELECT); - if (u->number <= 0 || target_resists_magic(mage, u, TYP_UNIT, 0)) { - up = &u->next; - continue; - } - - /* Reduziert durch Magieresistenz */ - damage *= (1.0 - magic_resistance(u)); - change_hitpoints(u, -(int)damage); - - if (*up == u) - up = &u->next; - } - - return AT_AGE_KEEP; -} - -static struct curse_type ct_deathcloud = { - "deathcloud", CURSETYP_REGION, 0, NO_MERGE, cinfo_simple, NULL, NULL, NULL, - NULL, dc_age -}; - -static curse *mk_deathcloud(unit * mage, region * r, float force, int duration) -{ - float effect; - curse *c; - - effect = force/2; - c = - create_curse(mage, &r->attribs, &ct_deathcloud, force, duration, effect, 0); - c->data.v = r; - return c; -} - -#define COMPAT_DEATHCLOUD -#ifdef COMPAT_DEATHCLOUD -static int dc_read_compat(struct attrib *a, void *target, struct storage * store) -/* return AT_READ_OK on success, AT_READ_FAIL if attrib needs removal */ -{ - region *r = NULL; - unit *u; - variant var; - int duration; - float strength; - int rx, ry; - - READ_INT(store, &duration); - READ_FLT(store, &strength); - READ_INT(store, &var.i); - u = findunit(var.i); - - /* this only affects really old data. no need to change: */ - READ_INT(store, &rx); - READ_INT(store, &ry); - r = findregion(rx, ry); - - if (r != NULL) { - float effect; - curse *c; - - effect = strength; - c = - create_curse(u, &r->attribs, &ct_deathcloud, strength * 2, duration, - effect, 0); - c->data.v = r; - if (u == NULL) { - ur_add(var, &c->magician, resolve_unit); - } - } - return AT_READ_FAIL; /* we don't care for the attribute. */ -} - -attrib_type at_deathcloud_compat = { - "zauber_todeswolke", NULL, NULL, NULL, NULL, dc_read_compat -}; -#endif - -/* ------------------------------------------------------------- */ -/* Name: Todeswolke -* Stufe: 11 -* Gebiet: Draig -* Kategorie: Region, negativ -* -* Wirkung: -* Personen in der Region verlieren stufe/2 Trefferpunkte pro Runde. -* Dauer force/2 -* Wirkt gegen MR -* Ruestung wirkt nicht -* Patzer: -* Magier geraet in den Staub und verliert zufaellige Zahl von HP bis -* auf _max(hp,2) -* Besonderheiten: -* Nicht als curse implementiert, was schlecht ist - man kann dadurch -* kein dispell machen. Wegen fix unter Zeitdruck erstmal nicht zu -* aendern... -* Missbrauchsmoeglichkeit: -* Hat der Magier mehr HP als Rasse des Feindes (extrem: Daemon/Goblin) -* so kann er per Farcasting durch mehrmaliges Zaubern eine -* Nachbarregion ausloeschen. Darum sollte dieser Spruch nur einmal auf -* eine Region gelegt werden koennen. -* -* Flag: -* (FARCASTING | REGIONSPELL | TESTRESISTANCE) -*/ - -static int sp_deathcloud(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - attrib *a = r->attribs; - unit *u; - - while (a) { - if ((a->type->flags & ATF_CURSE)) { - curse *c = a->data.v; - if (c->type == &ct_deathcloud) { - report_failure(mage, co->order); - return 0; - } - a = a->next; - } else - a = a->nexttype; - } - - mk_deathcloud(mage, r, co->force, co->level); - - /* melden, 1x pro Partei */ - for (u = r->units; u; u = u->next) - freset(u->faction, FFL_SELECT); - for (u = r->units; u; u = u->next) { - if (!fval(u->faction, FFL_SELECT)) { - fset(u->faction, FFL_SELECT); - ADDMSG(&u->faction->msgs, msg_message("deathcloud_effect", - "mage region", cansee(u->faction, r, mage, 0) ? mage : NULL, r)); - } - } - - if (!fval(mage->faction, FFL_SELECT)) { - ADDMSG(&mage->faction->msgs, msg_message("deathcloud_effect", - "mage region", mage, r)); - } - - return co->level; -} - -void patzer_deathcloud(castorder * co) -{ - unit *mage = co->magician.u; - int hp = (mage->hp - 2); - - change_hitpoints(mage, -rng_int() % hp); - - ADDMSG(&mage->faction->msgs, msg_message("magic_fumble", - "unit region command", mage, mage->region, co->order)); - - return; -} - -/* ------------------------------------------------------------- */ -/* Name: Pest - * Stufe: 7 - * Gebiet: Draig - * Wirkung: - * ruft eine Pest in der Region hervor. - * Flags: - * (FARCASTING | REGIONSPELL | TESTRESISTANCE) - * Syntax: ZAUBER [REGION x y] "Pest" - */ -static int sp_plague(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - - plagues(r, true); - - ADDMSG(&mage->faction->msgs, msg_message("plague_spell", - "region mage", r, mage)); - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Beschwoere Schattendaemon - * Stufe: 8 - * Gebiet: Draig - * Kategorie: Beschwoerung, positiv - * Wirkung: - * Der Magier beschwoert Stufe^2 Schattendaemonen. - * Schattendaemonen haben Tarnung = (Magie_Magier+ Tarnung_Magier)/2 und - * Wahrnehmung 1. Sie haben einen Attacke-Bonus von 8, einen - * Verteidigungsbonus von 11 und machen 2d3 Schaden. Sie entziehen bei - * einem Treffer dem Getroffenen einen Attacke- oder - * Verteidigungspunkt. (50% Chance.) Sie haben 25 Hitpoints und - * Ruestungsschutz 3. - * Flag: - * (SPELLLEVEL) - */ -static int sp_summonshadow(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - unit *u; - int val, number = (int)(force * force); - - u = create_unit(r, mage->faction, number, get_race(RC_SHADOW), 0, NULL, mage); - - /* Bekommen Tarnung = (Magie+Tarnung)/2 und Wahrnehmung 1. */ - val = get_level(mage, SK_MAGIC) + get_level(mage, SK_STEALTH); - - set_level(u, SK_STEALTH, val); - set_level(u, SK_PERCEPTION, 1); - - ADDMSG(&mage->faction->msgs, msg_message("summonshadow_effect", - "mage number", mage, number)); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Beschwoere Schattenmeister - * Stufe: 12 - * Gebiet: Draig - * Kategorie: Beschwoerung, positiv - * Wirkung: - * Diese hoeheren Schattendaemonen sind erheblich gefaehrlicher als die - * einfachen Schattendaemonen. Sie haben Tarnung entsprechend dem - * Magietalent des Beschwoerer-1 und Wahrnehmung 5, 75 HP, - * Ruestungsschutz 4, Attacke-Bonus 11 und Verteidigungsbonus 13, machen - * bei einem Treffer 2d4 Schaden, entziehen einen Staerkepunkt und - * entziehen 5 Talenttage in einem zufaelligen Talent. - * Stufe^2 Daemonen. - * - * Flag: - * (SPELLLEVEL) - * */ -static int sp_summonshadowlords(castorder * co) -{ - unit *u; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - int amount = (int)(force * force); - - u = - create_unit(r, mage->faction, amount, get_race(RC_SHADOWLORD), 0, NULL, - mage); - - /* Bekommen Tarnung = Magie und Wahrnehmung 5. */ - set_level(u, SK_STEALTH, get_level(mage, SK_MAGIC)); - set_level(u, SK_PERCEPTION, 5); - - ADDMSG(&mage->faction->msgs, msg_message("summon_effect", "mage amount race", - mage, amount, u_race(u))); - return cast_level; -} - -static bool chaosgate_valid(const connection * b) -{ - const attrib *a = a_findc(b->from->attribs, &at_direction); - if (!a) - a = a_findc(b->to->attribs, &at_direction); - if (!a) - return false; - return true; -} - -struct region *chaosgate_move(const connection * b, struct unit *u, - struct region *from, struct region *to, bool routing) -{ - if (!routing) { - int maxhp = u->hp / 4; - if (maxhp < u->number) - maxhp = u->number; - u->hp = maxhp; - } - return to; -} - -border_type bt_chaosgate = { - "chaosgate", VAR_NONE, - b_transparent, /* transparent */ - NULL, /* init */ - NULL, /* destroy */ - NULL, /* read */ - NULL, /* write */ - b_blocknone, /* block */ - NULL, /* name */ - b_rinvisible, /* rvisible */ - b_finvisible, /* fvisible */ - b_uinvisible, /* uvisible */ - chaosgate_valid, - chaosgate_move -}; - -/* ------------------------------------------------------------- */ -/* Name: Chaossog - * Stufe: 14 - * Gebiet: Draig - * Kategorie: Teleport - * Wirkung: - * Durch das Opfern von 200 Bauern kann der Chaosmagier ein Tor zur - * astralen Welt oeffnen. Das Tor kann im Folgemonat verwendet werden, - * es loest sich am Ende des Folgemonats auf. - * - * Flag: (0) - */ -static int sp_chaossuction(castorder * co) -{ - region *rt; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - - if (getplane(r) != get_normalplane()) { - /* Der Zauber funktioniert nur in der materiellen Welt. */ - cmistake(mage, co->order, 190, MSG_MAGIC); - return 0; - } - - rt = r_standard_to_astral(r); - - if (rt == NULL || fval(rt->terrain, FORBIDDEN_REGION)) { - /* Hier gibt es keine Verbindung zur astralen Welt. */ - cmistake(mage, co->order, 216, MSG_MAGIC); - return 0; - } else if (is_cursed(rt->attribs, C_ASTRALBLOCK, 0)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_astralblock", "")); - return 0; - } - - /* TODO: implement with a building */ - create_special_direction(r, rt, 2, "vortex_desc", "vortex", false); - create_special_direction(rt, r, 2, "vortex_desc", "vortex", false); - new_border(&bt_chaosgate, r, rt); - - add_message(&r->msgs, msg_message("chaosgate_effect_1", "mage", mage)); - add_message(&rt->msgs, msg_message("chaosgate_effect_2", "")); - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Magic Boost - Gabe des Chaos - * Stufe: 3 - * Gebiet: Draig - * Kategorie: Einheit, positiv - * - * Wirkung: - * Erhoeht die maximalen Magiepunkte und die monatliche Regeneration auf - * das doppelte. Dauer: 4 Wochen Danach sinkt beides auf die Haelfte des - * normalen ab. - * Dauer: 6 Wochen - * Patzer: - * permanenter Stufen- (Talenttage), Regenerations- oder maxMP-Verlust - * Besonderheiten: - * Patzer koennen waehrend der Zauberdauer haeufiger auftreten derzeit - * +10% - * - * Flag: - * (ONSHIPCAST) - */ - -static int sp_magicboost(castorder * co) -{ - curse *c; - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - float effect; - trigger *tsummon; - static const curse_type *ct_auraboost; - static const curse_type *ct_magicboost; - - if (!ct_auraboost) { - ct_auraboost = ct_find("auraboost"); - ct_magicboost = ct_find("magicboost"); - assert(ct_auraboost != NULL); - assert(ct_magicboost != NULL); - } - /* fehler, wenn schon ein boost */ - if (is_cursed(mage->attribs, C_MBOOST, 0)) { - report_failure(mage, co->order); - return 0; - } - - effect = 6; - c = create_curse(mage, &mage->attribs, ct_magicboost, power, 10, effect, 1); - - /* one aura boost with 200% aura now: */ - effect = 200; - c = create_curse(mage, &mage->attribs, ct_auraboost, power, 4, effect, 1); - - /* and one aura boost with 50% aura in 5 weeks: */ - tsummon = trigger_createcurse(mage, mage, ct_auraboost, power, 6, 50, 1); - add_trigger(&mage->attribs, "timer", trigger_timeout(5, tsummon)); - - ADDMSG(&mage->faction->msgs, msg_message("magicboost_effect", - "unit region command", c->magician, mage->region, co->order)); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: kleines Blutopfer - * Stufe: 4 - * Gebiet: Draig - * Kategorie: Einheit, positiv - * - * Wirkung: - * Hitpoints to Aura: - * skill < 8 = 4:1 - * skill < 12 = 3:1 - * skill < 15 = 2:1 - * skill < 18 = 1:2 - * skill > = 2:1 - * Patzer: - * permanenter HP verlust - * - * Flag: - * (ONSHIPCAST) - */ -static int sp_bloodsacrifice(castorder * co) -{ - unit *mage = co->magician.u; - int cast_level = co->level; - int aura; - int skill = eff_skill(mage, SK_MAGIC, mage->region); - int hp = (int)(co->force * 8); - - if (hp <= 0) { - report_failure(mage, co->order); - return 0; - } - - aura = lovar(hp); - - if (skill < 8) { - aura /= 4; - } else if (skill < 12) { - aura /= 3; - } else if (skill < 15) { - aura /= 2; - /* von 15 bis 17 ist hp = aura */ - } else if (skill > 17) { - aura *= 2; - } - - if (aura <= 0) { - report_failure(mage, co->order); - return 0; - } - - /* sicherheitshalber gibs hier einen HP gratis. sonst schaffen es - * garantiert ne ganze reihe von leuten ihren Magier damit umzubringen */ - mage->hp++; - change_spellpoints(mage, aura); - ADDMSG(&mage->faction->msgs, - msg_message("sp_bloodsacrifice_effect", - "unit region command amount", mage, mage->region, co->order, aura)); - return cast_level; -} - -/** gives a summoned undead unit some base skills. - */ -static void skill_summoned(unit * u, int level) -{ - if (level > 0) { - const race *rc = u_race(u); - skill_t sk; - for (sk = 0; sk != MAXSKILLS; ++sk) { - if (rc->bonus[sk] > 0) { - set_level(u, sk, level); - } - } - if (rc->bonus[SK_STAMINA]) { - u->hp = unit_max_hp(u) * u->number; - } - } -} - -/* ------------------------------------------------------------- */ -/* Name: Totenruf - Maechte des Todes - * Stufe: 6 - * Gebiet: Draig - * Kategorie: Beschwoerung, positiv - * Flag: FARCASTING - * Wirkung: - * Untote aus deathcounther ziehen, bis Stufe*10 Stueck - * - * Patzer: - * Erzeugt Monsteruntote - */ -static int sp_summonundead(castorder * co) -{ - int undead; - unit *u; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - int force = (int)(co->force * 10); - const race *race = get_race(RC_SKELETON); - - if (!r->land || deathcount(r) == 0) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, "error_nograves", - "target", r)); - return 0; - } - - undead = _min(deathcount(r), 2 + lovar(force)); - - if (cast_level <= 8) { - race = get_race(RC_SKELETON); - } else if (cast_level <= 12) { - race = get_race(RC_ZOMBIE); - } else { - race = get_race(RC_GHOUL); - } - - u = create_unit(r, mage->faction, undead, race, 0, NULL, mage); - make_undead_unit(u); - skill_summoned(u, cast_level / 2); - - ADDMSG(&mage->faction->msgs, msg_message("summonundead_effect_1", - "mage region amount", mage, r, undead)); - ADDMSG(&r->msgs, msg_message("summonundead_effect_2", "mage region", mage, - r)); - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Astraler Sog - * Stufe: 9 - * Gebiet: Draig - * Kategorie: Region, negativ - * Wirkung: - * Allen Magier in der betroffenen Region wird eine Teil ihrer - * Magischen Kraft in die Gefilde des Chaos entzogen Jeder Magier im - * Einflussbereich verliert Stufe(Zaubernden)*5% seiner Magiepunkte. - * Keine Regeneration in der Woche (fehlt noch) - * - * Flag: - * (REGIONSPELL | TESTRESISTANCE) - */ - -static int sp_auraleak(castorder * co) -{ - int lost_aura; - double lost; - unit *u; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - message *msg; - - lost = _min(0.95, cast_level * 0.05); - - for (u = r->units; u; u = u->next) { - if (is_mage(u)) { - /* Magieresistenz Einheit? Bei gegenerischen Magiern nur sehr - * geringe Chance auf Erfolg wg erhoehter MR, wuerde Spruch sinnlos - * machen */ - lost_aura = (int)(get_spellpoints(u) * lost); - change_spellpoints(u, -lost_aura); - } - } - msg = msg_message("cast_auraleak_effect", "mage region", mage, r); - r_addmessage(r, NULL, msg); - msg_release(msg); - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* BARDE - CERDDOR*/ -/* ------------------------------------------------------------- */ -/* ------------------------------------------------------------- */ -/* Name: Magie analysieren - Gebaeude, Schiffe, Region - * Name: Lied des Ortes analysieren - * Stufe: 8 - * Gebiet: Cerddor - * - * Wirkung: - * Zeigt die Verzauberungen eines Objekts an (curse->name, - * curse::info). Aus der Differenz Spruchstaerke und Curse->vigour - * ergibt sich die Chance den Spruch zu identifizieren ((force - - * c->vigour)*10 + 100 %). - * - * Flag: - * (SPELLLEVEL|ONSHIPCAST) - */ -static int sp_analysesong_obj(castorder * co) -{ - int obj; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - spellparameter *pa = co->par; - - obj = pa->param[0]->typ; - - switch (obj) { - case SPP_REGION: - magicanalyse_region(r, mage, force); - break; - - case SPP_BUILDING: - { - building *b = pa->param[0]->data.b; - magicanalyse_building(b, mage, force); - break; - } - case SPP_SHIP: - { - ship *sh = pa->param[0]->data.sh; - magicanalyse_ship(sh, mage, force); - break; - } - default: - /* Syntax fehlerhaft */ - return 0; - } - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Gesang des Lebens analysieren - * Name: Magie analysieren - Unit - * Stufe: 5 - * Gebiet: Cerddor - * Wirkung: - * Zeigt die Verzauberungen eines Objekts an (curse->name, - * curse::info). Aus der Differenz Spruchstaerke und Curse->vigour - * ergibt sich die Chance den Spruch zu identifizieren ((force - - * c->vigour)*10 + 100 %). - * - * Flag: - * (UNITSPELL|ONSHIPCAST|TESTCANSEE) - */ -static int sp_analysesong_unit(castorder * co) -{ - unit *u; - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - spellparameter *pa = co->par; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber - * abbrechen aber kosten lassen */ - if (pa->param[0]->flag == TARGET_RESISTS) - return cast_level; - - u = pa->param[0]->data.u; - - magicanalyse_unit(u, mage, force); - - return cast_level; -} - -static bool can_charm(const unit * u, int maxlevel) -{ - const skill_t expskills[] = - { SK_ALCHEMY, SK_HERBALISM, SK_MAGIC, SK_SPY, SK_TACTICS, NOSKILL }; - skill *sv = u->skills; - - if (fval(u, UFL_HERO)) - return false; - - for (; sv != u->skills + u->skill_size; ++sv) { - int l = 0, h = 5; - skill_t sk = sv->id; - assert(expskills[h] == NOSKILL); - while (l < h) { - int m = (l + h) / 2; - if (sk == expskills[m]) { - if (skill_limit(u->faction, sk) != INT_MAX) { - return false; - } else if ((int)sv->level > maxlevel) { - return false; - } - break; - } else if (sk > expskills[m]) - l = m + 1; - else - h = m; - } - } - return true; -} - -/* ------------------------------------------------------------- */ -/* Name: Charming - * Stufe: 13 - * Gebiet: Cerddor - * Flag: UNITSPELL - * Wirkung: - * bezauberte Einheit wechselt 'virtuell' die Partei und fuehrt fremde - * Befehle aus. - * Dauer: 3 - force+2 Wochen - * Wirkt gegen Magieresistenz - * - * wirkt auf eine Einheit mit maximal Talent Personen normal. Fuer jede - * zusaetzliche Person gibt es einen Bonus auf Magieresistenz, also auf - * nichtgelingen, von 10%. - * - * Das hoechste Talent der Einheit darf maximal so hoch sein wie das - * Magietalent des Magiers. Fuer jeden Talentpunkt mehr gibt es einen - * Bonus auf Magieresistenz von 15%, was dazu fuehrt, das bei +2 Stufen - * die Magiersistenz bei 90% liegt. - * - * Migrantenzaehlung muss Einheit ueberspringen - * - * Attackiere verbieten - * Flags: - * (UNITSPELL | TESTCANSEE) - */ -static int sp_charmingsong(castorder * co) -{ - unit *target; - int duration; - skill_t i; - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - spellparameter *pa = co->par; - int resist_bonus = 0; - int tb = 0; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - target = pa->param[0]->data.u; - - /* Auf eigene Einheiten versucht zu zaubern? Garantiert Tippfehler */ - if (target->faction == mage->faction) { - /* Die Einheit ist eine der unsrigen */ - cmistake(mage, co->order, 45, MSG_MAGIC); - } - /* niemand mit teurem Talent */ - if (!can_charm(target, cast_level / 2)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_noexpensives", "target", target)); - return 0; - } - - /* Magieresistensbonus fuer mehr als Stufe Personen */ - if (target->number > force) { - resist_bonus += (int)((target->number - force) * 10); - } - /* Magieresistensbonus fuer hoehere Talentwerte */ - for (i = 0; i < MAXSKILLS; i++) { - int sk = effskill(target, i); - if (tb < sk) - tb = sk; - } - tb -= effskill(mage, SK_MAGIC); - if (tb > 0) { - resist_bonus += tb * 15; - } - /* Magieresistenz */ - if (target_resists_magic(mage, target, TYP_UNIT, resist_bonus)) { - report_failure(mage, co->order); -#if 0 - sprintf(buf, "%s fuehlt sich einen Moment lang benommen und desorientiert.", - unitname(target)); - addmessage(target->region, target->faction, buf, MSG_EVENT, ML_WARN); -#endif - return 0; - } - - duration = 3 + rng_int() % (int)force; - { - trigger *trestore = trigger_changefaction(target, target->faction); - /* laeuft die Dauer ab, setze Partei zurueck */ - add_trigger(&target->attribs, "timer", trigger_timeout(duration, trestore)); - /* wird die alte Partei von Target aufgeloest, dann auch diese Einheit */ - add_trigger(&target->faction->attribs, "destroy", trigger_killunit(target)); - /* wird die neue Partei von Target aufgeloest, dann auch diese Einheit */ - add_trigger(&mage->faction->attribs, "destroy", trigger_killunit(target)); - } - /* sperre ATTACKIERE, GIB PERSON und ueberspringe Migranten */ - create_curse(mage, &target->attribs, ct_find("slavery"), force, duration, zero_effect, 0); - - /* setze Partei um und loesche langen Befehl aus Sicherheitsgruenden */ - u_setfaction(target, mage->faction); - set_order(&target->thisorder, NULL); - - /* setze Parteitarnung, damit nicht sofort klar ist, wer dahinter - * steckt */ - if (rule_stealth_faction()) { - fset(target, UFL_ANON_FACTION); - } - - ADDMSG(&mage->faction->msgs, msg_message("charming_effect", - "mage unit duration", mage, target, duration)); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Gesang des wachen Geistes - * Stufe: 10 - * Gebiet: Cerddor - * Kosten: SPC_LEVEL - * Wirkung: - * Bringt einmaligen Bonus von +15% auf Magieresistenz. Wirkt auf alle - * Aliierten (HELFE BEWACHE) in der Region. - * Dauert Stufe Wochen an, ist nicht kumulativ. - * Flag: - * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) - */ -static int sp_song_resistmagic(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - int duration = (int)force + 1; - - create_curse(mage, &r->attribs, ct_find("goodmagicresistancezone"), - force, duration, 15, 0); - - /* Erfolg melden */ - ADDMSG(&mage->faction->msgs, msg_message("regionmagic_effect", - "unit region command", mage, mage->region, co->order)); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Gesang des schwachen Geistes - * Stufe: 12 - * Gebiet: Cerddor - * Wirkung: - * Bringt einmaligen Malus von -15% auf Magieresistenz. - * Wirkt auf alle Nicht-Aliierten (HELFE BEWACHE) in der Region. - * Dauert Stufe Wochen an, ist nicht kumulativ. - * Flag: - * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) - */ -static int sp_song_susceptmagic(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - int duration = (int)force + 1; - - create_curse(mage, &r->attribs, ct_find("badmagicresistancezone"), - force, duration, 15, 0); - - ADDMSG(&mage->faction->msgs, msg_message("regionmagic_effect", - "unit region command", mage, mage->region, co->order)); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Aufruhr beschwichtigen - * Stufe: 15 - * Gebiet: Cerddor - * Flag: FARCASTING - * Wirkung: - * zerstreut einen Monsterbauernmob, Antimagie zu 'Aufruhr - * verursachen' - */ - -static int sp_rallypeasantmob(castorder * co) -{ - unit *u, *un; - int erfolg = 0; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - message *msg; - curse *c; - - for (u = r->units; u; u = un) { - un = u->next; - if (is_monsters(u->faction) && u_race(u) == get_race(RC_PEASANT)) { - rsetpeasants(r, rpeasants(r) + u->number); - rsetmoney(r, rmoney(r) + get_money(u)); - set_money(u, 0); - setguard(u, GUARD_NONE); - set_number(u, 0); - erfolg = cast_level; - } - } - - c = get_curse(r->attribs, ct_find(oldcursename(C_RIOT))); - if (c != NULL) { - remove_curse(&r->attribs, c); - } - - msg = msg_message("cast_rally_effect", "mage region", mage, r); - r_addmessage(r, NULL, msg); - msg_release(msg); - return erfolg; -} - -/* ------------------------------------------------------------- */ -/* Name: Aufruhr verursachen - * Stufe: 16 - * Gebiet: Cerddor - * Wirkung: - * Wiegelt 60% bis 90% der Bauern einer Region auf. Bauern werden ein - * großer Mob, der zur Monsterpartei gehoert und die Region bewacht. - * Regionssilber sollte auch nicht durch Unterhaltung gewonnen werden - * koennen. - * - * Fehlt: Triggeraktion: loeste Bauernmob auf und gib alles an Region, - * dann koennen die Bauernmobs ihr Silber mitnehmen und bleiben x - * Wochen bestehen - * - * alternativ: Loesen sich langsam wieder auf - * Flag: - * (FARCASTING | REGIONSPELL | TESTRESISTANCE) - */ -static int sp_raisepeasantmob(castorder * co) -{ - unit *u; - attrib *a; - int n; - int anteil; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - int duration = (int)force + 1; - faction *monsters = get_monsters(); - message *msg; - - anteil = 6 + (rng_int() % 4); - - n = rpeasants(r) * anteil / 10; - n = _max(0, n); - n = _min(n, rpeasants(r)); - - if (n <= 0) { - report_failure(mage, co->order); - return 0; - } - - rsetpeasants(r, rpeasants(r) - n); - assert(rpeasants(r) >= 0); - - u = - create_unit(r, monsters, n, get_race(RC_PEASANT), 0, LOC(monsters->locale, - "furious_mob"), NULL); - fset(u, UFL_ISNEW); - guard(u, GUARD_ALL); - a = a_new(&at_unitdissolve); - a->data.ca[0] = 1; /* An rpeasants(r). */ - a->data.ca[1] = 15; /* 15% */ - a_add(&u->attribs, a); - - create_curse(mage, &r->attribs, ct_find("riotzone"), (float)cast_level, duration, - (float)anteil, 0); - - msg = msg_message("sp_raisepeasantmob_effect", "mage region", mage, r); - report_spell(mage, r, msg); - msg_release(msg); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Ritual der Aufnahme / Migrantenwerben - * Stufe: 9 - * Gebiet: Cerddor - * Wirkung: - * Bis zu Stufe Personen fremder Rasse koennen angeworben werden. Die - * angeworbene Einheit muss kontaktieren. Keine teuren Talente - * - * Flag: - * (UNITSPELL | SPELLLEVEL | TESTCANSEE) - */ -static int sp_migranten(castorder * co) -{ - unit *target; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - spellparameter *pa = co->par; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - target = pa->param[0]->data.u; /* Zieleinheit */ - - /* Personen unserer Rasse koennen problemlos normal uebergeben werden */ - if (u_race(target) == mage->faction->race) { - /* u ist von unserer Art, das Ritual waere verschwendete Aura. */ - ADDMSG(&mage->faction->msgs, msg_message("sp_migranten_fail1", - "unit region command target", mage, mage->region, co->order, target)); - } - /* Auf eigene Einheiten versucht zu zaubern? Garantiert Tippfehler */ - if (target->faction == mage->faction) { - cmistake(mage, co->order, 45, MSG_MAGIC); - } - - /* Keine Monstereinheiten */ - if (!playerrace(u_race(target))) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_nomonsters", "")); - return 0; - } - /* niemand mit teurem Talent */ - if (has_limited_skills(target)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_noexpensives", "target", target)); - return 0; - } - /* maximal Stufe Personen */ - if (target->number > cast_level || target->number > max_spellpoints(r, mage)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_toomanytargets", "")); - return 0; - } - - /* Kontakt pruefen (aus alter Teleportroutine uebernommen) */ - if (!ucontact(target, mage)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail::contact", "target", target)); - return 0; - } - u_setfaction(target, mage->faction); - set_order(&target->thisorder, NULL); - - /* Erfolg melden */ - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, "sp_migranten", - "target", target)); - - return target->number; -} - -/* ------------------------------------------------------------- */ -/* Name: Gesang der Friedfertigkeit - * Stufe: 12 - * Gebiet: Cerddor - * Wirkung: - * verhindert jede Attacke fuer lovar(Stufe/2) Runden - */ - -static int sp_song_of_peace(castorder * co) -{ - unit *u; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - int duration = 2 + lovar(force / 2); - message *msg[2] = { NULL, NULL }; - - create_curse(mage, &r->attribs, ct_find("peacezone"), force, duration, - zero_effect, 0); - - for (u = r->units; u; u = u->next) - freset(u->faction, FFL_SELECT); - for (u = r->units; u; u = u->next) { - if (!fval(u->faction, FFL_SELECT)) { - message *m = NULL; - fset(u->faction, FFL_SELECT); - if (cansee(u->faction, r, mage, 0)) { - if (msg[0] == NULL) - msg[0] = msg_message("song_of_peace_effect_0", "mage", mage); - m = msg[0]; - } else { - if (msg[1] == NULL) - msg[1] = msg_message("song_of_peace_effect_1", ""); - m = msg[1]; - } - r_addmessage(r, u->faction, m); - } - } - if (msg[0]) - msg_release(msg[0]); - if (msg[1]) - msg_release(msg[1]); - return cast_level; - -} - -/* ------------------------------------------------------------- */ -/* Name: Hohes Lied der Gaukelei - * Stufe: 2 - * Gebiet: Cerddor - * Wirkung: - * Das Unterhaltungsmaximum steigt von 20% auf 40% des - * Regionsvermoegens. Der Spruch haelt Stufe Wochen an - */ - -static int sp_generous(castorder * co) -{ - unit *u; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - int duration = (int)force + 1; - float effect; - message *msg[2] = { NULL, NULL }; - - if (is_cursed(r->attribs, C_DEPRESSION, 0)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_generous", "")); - return 0; - } - - effect = 2; - create_curse(mage, &r->attribs, ct_find("generous"), force, duration, effect, - 0); - - for (u = r->units; u; u = u->next) - freset(u->faction, FFL_SELECT); - for (u = r->units; u; u = u->next) { - if (!fval(u->faction, FFL_SELECT)) { - message *m = NULL; - fset(u->faction, FFL_SELECT); - if (cansee(u->faction, r, mage, 0)) { - if (msg[0] == NULL) - msg[0] = msg_message("generous_effect_0", "mage", mage); - m = msg[0]; - } else { - if (msg[1] == NULL) - msg[1] = msg_message("generous_effect_1", ""); - m = msg[1]; - } - r_addmessage(r, u->faction, m); - } - } - if (msg[0]) - msg_release(msg[0]); - if (msg[1]) - msg_release(msg[1]); - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Anwerbung - * Stufe: 4 - * Gebiet: Cerddor - * Wirkung: - * Bauern schliessen sich der eigenen Partei an - * ist zusaetzlich zur Rekrutierungsmenge in der Region - * */ - -static int sp_recruit(castorder * co) -{ - unit *u; - region *r = co_get_region(co); - int num, maxp = rpeasants(r); - double n; - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - faction *f = mage->faction; - const struct race *rc = f->race; - - if (maxp == 0) { - report_failure(mage, co->order); - return 0; - } - /* Immer noch zuviel auf niedrigen Stufen. Deshalb die Rekrutierungskosten - * mit einfliessen lassen und dafuer den Exponenten etwas groeßer. - * Wenn die Rekrutierungskosten deutlich hoeher sind als der Faktor, - * ist das Verhaeltniss von ausgegebene Aura pro Bauer bei Stufe 2 - * ein mehrfaches von Stufe 1, denn in beiden Faellen gibt es nur 1 - * Bauer, nur die Kosten steigen. */ - n = (pow(force, 1.6) * 100) / f->race->recruitcost; - if (rc->recruit_multi != 0) { - double multp = maxp / rc->recruit_multi; - n = _min(multp, n); - n = _max(n, 1); - rsetpeasants(r, maxp - (int)(n * rc->recruit_multi)); - } else { - n = _min(maxp, n); - n = _max(n, 1); - rsetpeasants(r, maxp - (int)n); - } - - num = (int)n; - u = - create_unit(r, f, num, f->race, 0, LOC(f->locale, - (num == 1 ? "peasant" : "peasant_p")), mage); - set_order(&u->thisorder, default_order(f->locale)); - - ADDMSG(&mage->faction->msgs, msg_message("recruit_effect", "mage amount", - mage, num)); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Wanderprediger - Große Anwerbung - * Stufe: 14 - * Gebiet: Cerddor - * Wirkung: - * Bauern schliessen sich der eigenen Partei an - * ist zusaetzlich zur Rekrutierungsmenge in der Region - * */ - -static int sp_bigrecruit(castorder * co) -{ - unit *u; - region *r = co_get_region(co); - int n, maxp = rpeasants(r); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - faction *f = mage->faction; - message *msg; - - if (maxp <= 0) { - report_failure(mage, co->order); - return 0; - } - /* Fuer vergleichbare Erfolge bei unterschiedlichen Rassen die - * Rekrutierungskosten mit einfliessen lassen. */ - - n = (int)force + lovar((force * force * 1000) / f->race->recruitcost); - if (f->race == get_race(RC_ORC)) { - n = _min(2 * maxp, n); - n = _max(n, 1); - rsetpeasants(r, maxp - (n + 1) / 2); - } else { - n = _min(maxp, n); - n = _max(n, 1); - rsetpeasants(r, maxp - n); - } - - u = - create_unit(r, f, n, f->race, 0, LOC(f->locale, - (n == 1 ? "peasant" : "peasant_p")), mage); - set_order(&u->thisorder, default_order(f->locale)); - - msg = msg_message("recruit_effect", "mage amount", mage, n); - r_addmessage(r, mage->faction, msg); - msg_release(msg); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Aushorchen - * Stufe: 7 - * Gebiet: Cerddor - * Wirkung: - * Erliegt die Einheit dem Zauber, so wird sie dem Magier alles - * erzaehlen, was sie ueber die gefragte Region weiß. Ist in der Region - * niemand ihrer Partei, so weiß sie nichts zu berichten. Auch kann - * sie nur das erzaehlen, was sie selber sehen koennte. - * Flags: - * (UNITSPELL | TESTCANSEE) - */ - -/* restistenz der einheit pruefen */ -static int sp_pump(castorder * co) -{ - unit *u, *target; - region *rt; - bool see = false; - unit *mage = co->magician.u; - spellparameter *pa = co->par; - int cast_level = co->level; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - target = pa->param[0]->data.u; /* Zieleinheit */ - - if (fval(u_race(target), RCF_UNDEAD)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "error_not_on_undead", "")); - return 0; - } - if (is_magic_resistant(mage, target, 0) || is_monsters(target->faction)) { - report_failure(mage, co->order); - return 0; - } - - rt = pa->param[1]->data.r; - - for (u = rt->units; u; u = u->next) { - if (u->faction == target->faction) - see = true; - } - - if (see) { - ADDMSG(&mage->faction->msgs, msg_message("pump_effect", "mage unit tregion", - mage, target, rt)); - } else { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, "spellfail_pump", - "target tregion", target, rt)); - return cast_level / 2; - } - - u = - create_unit(rt, mage->faction, RS_FARVISION, get_race(RC_SPELL), 0, - "spell/pump", NULL); - u->age = 2; - set_level(u, SK_PERCEPTION, eff_skill(target, SK_PERCEPTION, u->region)); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Verfuehrung - * Stufe: 6 - * Gebiet: Cerddor - * Wirkung: - * Betoert eine Einheit, so das sie ihm den groeßten Teil ihres Bargelds - * und 50% ihres Besitzes schenkt. Sie behaelt jedoch immer soviel, wie - * sie zum ueberleben braucht. Wirkt gegen Magieresistenz. - * _min(Stufe*1000$, u->money - maintenace) - * Von jedem Item wird 50% abgerundet ermittelt und uebergeben. Dazu - * kommt Itemzahl%2 mit 50% chance - * - * Flags: - * (UNITSPELL | TESTRESISTANCE | TESTCANSEE) - */ -static int sp_seduce(castorder * co) -{ - const resource_type *rsilver = get_resourcetype(R_SILVER); - unit *target; - item **itmp, *items = 0;; - unit *mage = co->magician.u; - spellparameter *pa = co->par; - int cast_level = co->level; - float force = co->force; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - target = pa->param[0]->data.u; /* Zieleinheit */ - - if (fval(u_race(target), RCF_UNDEAD)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_noundead", "")); - return 0; - } - - /* Erfolgsmeldung */ - - itmp = &target->items; - while (*itmp) { - item *itm = *itmp; - int loot; - if (itm->type->rtype == rsilver) { - loot = - _min(cast_level * 1000, get_money(target) - (maintenance_cost(target))); - loot = _max(loot, 0); - } else { - loot = itm->number / 2; - if (itm->number % 2) { - loot += rng_int() % 2; - } - if (loot > 0) { - loot = (int)_min(loot, force * 5); - } - } - if (loot > 0) { - i_change(&mage->items, itm->type, loot); - i_change(&items, itm->type, loot); - i_change(itmp, itm->type, -loot); - } - if (*itmp == itm) - itmp = &itm->next; - } - - if (items) { - ADDMSG(&mage->faction->msgs, msg_message("seduce_effect_0", "mage unit items", - mage, target, items)); - i_freeall(&items); - ADDMSG(&target->faction->msgs, msg_message("seduce_effect_1", "unit", - target)); - } - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Monster friedlich stimmen - * Stufe: 6 - * Gebiet: Cerddor - * Wirkung: - * verhindert Angriffe des bezauberten Monsters auf die Partei des - * Barden fuer Stufe Wochen. Nicht uebertragbar, dh Verbuendete werden vom - * Monster natuerlich noch angegriffen. Wirkt nicht gegen Untote - * Jede Einheit kann maximal unter einem Beherrschungszauber dieser Art - * stehen, dh wird auf die selbe Einheit dieser Zauber von einem - * anderen Magier nochmal gezaubert, schlaegt der Zauber fehl. - * - * Flags: - * (UNITSPELL | ONSHIPCAST | TESTRESISTANCE | TESTCANSEE) - */ - -static int sp_calm_monster(castorder * co) -{ - curse *c; - unit *target; - unit *mage = co->magician.u; - spellparameter *pa = co->par; - int cast_level = co->level; - float force = co->force; - float effect; - message *msg; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - target = pa->param[0]->data.u; /* Zieleinheit */ - - if (fval(u_race(target), RCF_UNDEAD)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_noundead", "")); - return 0; - } - - effect = (float)mage->faction->subscription; - c = create_curse(mage, &target->attribs, ct_find("calmmonster"), force, - (int)force, effect, 0); - if (c == NULL) { - report_failure(mage, co->order); - return 0; - } - - msg = msg_message("calm_effect", "mage unit", mage, target); - r_addmessage(mage->region, mage->faction, msg); - msg_release(msg); - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: schaler Wein - * Stufe: 7 - * Gebiet: Cerddor - * Wirkung: - * wird gegen Magieresistenz gezaubert Das Opfer vergisst bis zu - * Talenttage seines hoechsten Talentes und tut die Woche nix. - * Nachfolgende Zauber sind erschwert. - * Wirkt auf bis zu 10 Personen in der Einheit - * - * Flags: - * (UNITSPELL | TESTRESISTANCE | TESTCANSEE) - */ - -static int sp_headache(castorder * co) -{ - skill *smax = NULL; - int i; - unit *target; - unit *mage = co->magician.u; - spellparameter *pa = co->par; - int cast_level = co->level; - message *msg; - - /* Macht alle nachfolgenden Zauber doppelt so teuer */ - countspells(mage, 1); - - target = pa->param[0]->data.u; /* Zieleinheit */ - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (target->number == 0 || pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - /* finde das groeßte Talent: */ - for (i = 0; i != target->skill_size; ++i) { - skill *sv = target->skills + i; - if (smax == NULL || skill_compare(sv, smax) > 0) { - smax = sv; - } - } - if (smax != NULL) { - /* wirkt auf maximal 10 Personen */ - int change = _min(10, target->number) * (rng_int() % 2 + 1) / target->number; - reduce_skill(target, smax, change); - } - set_order(&target->thisorder, NULL); - - msg = msg_message("hangover_effect_0", "mage unit", mage, target); - r_addmessage(mage->region, mage->faction, msg); - msg_release(msg); - - msg = msg_message("hangover_effect_1", "unit", target); - r_addmessage(target->region, target->faction, msg); - msg_release(msg); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Mob - * Stufe: 10 - * Gebiet: Cerddor - * Wirkung: - * Wiegelt Stufe*250 Bauern zu einem Mob auf, der sich der Partei des - * Magier anschliesst Pro Woche beruhigen sich etwa 15% wieder und - * kehren auf ihre Felder zurueck - * - * Flags: - * (SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) - */ -static int sp_raisepeasants(castorder * co) -{ - int bauern; - unit *u2; - attrib *a; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - message *msg; - - if (rpeasants(r) == 0) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "error_nopeasants", "")); - return 0; - } - bauern = (int)_min(rpeasants(r), power * 250); - rsetpeasants(r, rpeasants(r) - bauern); - - u2 = - create_unit(r, mage->faction, bauern, get_race(RC_PEASANT), 0, - LOC(mage->faction->locale, "furious_mob"), mage); - - fset(u2, UFL_LOCKED); - if (rule_stealth_faction()) { - fset(u2, UFL_ANON_FACTION); - } - - a = a_new(&at_unitdissolve); - a->data.ca[0] = 1; /* An rpeasants(r). */ - a->data.ca[1] = 15; /* 15% */ - a_add(&u2->attribs, a); - - msg = - msg_message("sp_raisepeasants_effect", "mage region amount", mage, r, - u2->number); - r_addmessage(r, NULL, msg); - if (mage->region != r) { - add_message(&mage->faction->msgs, msg); - } - msg_release(msg); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Truebsal - * Stufe: 11 - * Kategorie: Region, negativ - * Wirkung: - * in der Region kann fuer einige Wochen durch Unterhaltung kein Geld - * mehr verdient werden - * - * Flag: - * (FARCASTING | REGIONSPELL | TESTRESISTANCE) - */ -static int sp_depression(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - int duration = (int)force + 1; - message *msg; - - create_curse(mage, &r->attribs, ct_find("depression"), force, duration, - zero_effect, 0); - - msg = msg_message("sp_depression_effect", "mage region", mage, r); - r_addmessage(r, NULL, msg); - if (mage->region != r) { - add_message(&mage->faction->msgs, msg); - } - msg_release(msg); - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* TRAUM - Illaun */ -/* ------------------------------------------------------------- */ - -/* Name: Seelenfrieden - * Stufe: 2 - * Kategorie: Region, positiv - * Gebiet: Illaun - * Wirkung: - * Reduziert Untotencounter - * Flag: (0) - */ - -int sp_puttorest(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - int dead = deathcount(r); - int laid_to_rest = dice((int)(co->force * 2), 100); - message *seen = msg_message("puttorest", "mage", mage); - message *unseen = msg_message("puttorest", "mage", NULL); - - laid_to_rest = _max(laid_to_rest, dead); - - deathcounts(r, -laid_to_rest); - - report_effect(r, mage, seen, unseen); - msg_release(seen); - msg_release(unseen); - return co->level; -} - -/* Name: Traumschloeßchen - * Stufe: 3 - * Kategorie: Region, Gebaeude, positiv - * Gebiet: Illaun - * Wirkung: - * Mit Hilfe dieses Zaubers kann der Traumweber die Illusion eines - * beliebigen Gebaeudes erzeugen. Die Illusion kann betreten werden, ist - * aber ansonsten funktionslos und benoetigt auch keinen Unterhalt - * Flag: (0) - */ - -int sp_icastle(castorder * co) -{ - building *b; - const building_type *type; - attrib *a; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - spellparameter *pa = co->par; - icastle_data *data; - const char *bname; - message *msg; - const building_type *bt_illusion = bt_find("illusioncastle"); - - if (!bt_illusion) { - return 0; - } - - if ((type = - findbuildingtype(pa->param[0]->data.xs, mage->faction->locale)) == NULL) { - type = bt_find("castle"); - } - - b = new_building(bt_illusion, r, mage->faction->locale); - - /* Groeße festlegen. */ - if (type == bt_illusion) { - b->size = (rng_int() % (int)((power * power) + 1) * 10); - } else if (type->maxsize == -1) { - b->size = ((rng_int() % (int)(power)) + 1) * 5; - } else { - b->size = type->maxsize; - } - - if (type->name == NULL) { - bname = LOC(mage->faction->locale, type->_name); - } else { - bname = LOC(mage->faction->locale, buildingtype(type, b, 0)); - } - building_setname(b, bname); - - /* TODO: Auf timeout und action_destroy umstellen */ - a = a_add(&b->attribs, a_new(&at_icastle)); - data = (icastle_data *) a->data.v; - data->type = type; - data->building = b; - data->time = - 2 + (rng_int() % (int)(power) + 1) * (rng_int() % (int)(power) + 1); - - if (mage->region == r) { - if (leave(mage, false)) { - u_set_building(mage, b); - } - } - - ADDMSG(&mage->faction->msgs, msg_message("icastle_create", - "unit region command", mage, mage->region, co->order)); - - msg = msg_message("sp_icastle_effect", "region", r); - report_spell(mage, r, msg); - msg_release(msg); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Gestaltwandlung - * Stufe: 3 - * Gebiet: Illaun - * Wirkung: - * Zieleinheit erscheint fuer (Stufe) Wochen als eine andere Gestalt - * (wie bei daemonischer Rassetarnung). - * Syntax: ZAUBERE "Gestaltwandlung" - * Flags: - * (UNITSPELL | SPELLLEVEL) - */ - -int sp_illusionary_shapeshift(castorder * co) -{ - unit *u; - const race *rc; - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - spellparameter *pa = co->par; - const race *irace; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber - * abbrechen aber kosten lassen */ - if (pa->param[0]->flag == TARGET_RESISTS) - return cast_level; - - u = pa->param[0]->data.u; - - rc = findrace(pa->param[1]->data.xs, mage->faction->locale); - if (rc == NULL) { - cmistake(mage, co->order, 202, MSG_MAGIC); - return 0; - } - - /* aehnlich wie in laws.c:setealth() */ - if (!playerrace(rc)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "sp_shapeshift_fail", "target race", u, rc)); - return 0; - } - irace = u_irace(u); - if (irace == u_race(u)) { - trigger *trestore = trigger_changerace(u, NULL, irace); - add_trigger(&u->attribs, "timer", trigger_timeout((int)power + 2, - trestore)); - u->irace = rc; - } - - ADDMSG(&mage->faction->msgs, msg_message("shapeshift_effect", - "mage target race", mage, u, rc)); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Regionstraum analysieren - * Stufe: 9 - * Aura: 18 - * Kosten: SPC_FIX - * Wirkung: - * Zeigt die Verzauberungen eines Objekts an (curse->name, - * curse::info). Aus der Differenz Spruchstaerke und Curse->vigour - * ergibt sich die Chance den Spruch zu identifizieren ((force - - * c->vigour)*10 + 100 %). - */ -int sp_analyseregionsdream(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - - magicanalyse_region(r, mage, cast_level); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Traumbilder erkennen - * Stufe: 5 - * Aura: 12 - * Kosten: SPC_FIX - * Wirkung: - * Zeigt die Verzauberungen eines Objekts an (curse->name, - * curse::info). Aus der Differenz Spruchstaerke und Curse->vigour - * ergibt sich die Chance den Spruch zu identifizieren ((force - - * c->vigour)*10 + 100 %). - */ -int sp_analysedream(castorder * co) -{ - unit *u; - unit *mage = co->magician.u; - int cast_level = co->level; - spellparameter *pa = co->par; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber - * abbrechen aber kosten lassen */ - if (pa->param[0]->flag == TARGET_RESISTS) - return cast_level; - - u = pa->param[0]->data.u; - magicanalyse_unit(u, mage, cast_level); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Schlechte Traeume - * Stufe: 10 - * Kategorie: Region, negativ - * Wirkung: - * Dieser Zauber ermoeglicht es dem Traeumer, den Schlaf aller - * nichtaliierten Einheiten (HELFE BEWACHE) in der Region so starkzu - * stoeren, das sie 1 Talentstufe in allen Talenten - * voruebergehend verlieren. Der Zauber wirkt erst im Folgemonat. - * - * Flags: - * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) - * */ -int sp_baddreams(castorder * co) -{ - int duration; - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - region *r = co_get_region(co); - curse *c; - float effect; - - /* wirkt erst in der Folgerunde, soll mindestens eine Runde wirken, - * also duration+2 */ - duration = (int)_max(1, power / 2); /* Stufe 1 macht sonst mist */ - duration = 2 + rng_int() % duration; - - /* Nichts machen als ein entsprechendes Attribut in die Region legen. */ - effect = -1; - c = create_curse(mage, &r->attribs, ct_find("gbdream"), power, duration, effect, 0); - - /* Erfolg melden */ - ADDMSG(&mage->faction->msgs, msg_message("regionmagic_effect", - "unit region command", c->magician, mage->region, co->order)); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Schoene Traeume - * Stufe: 8 - * Kategorie: - * Wirkung: - * Dieser Zauber ermoeglicht es dem Traeumer, den Schlaf aller aliierten - * Einheiten in der Region so zu beeinflussen, daß sie fuer einige Zeit - * einen Bonus von 1 Talentstufe in allen Talenten - * bekommen. Der Zauber wirkt erst im Folgemonat. - * Flags: - * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) - */ -int sp_gooddreams(castorder * co) -{ - int duration; - curse *c; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - float effect; - - /* wirkt erst in der Folgerunde, soll mindestens eine Runde wirken, - * also duration+2 */ - duration = (int)_max(1, power / 2); /* Stufe 1 macht sonst mist */ - duration = 2 + rng_int() % duration; - effect = 1; - c = create_curse(mage, &r->attribs, ct_find("gbdream"), power, duration, effect, 0); - - /* Erfolg melden */ - ADDMSG(&mage->faction->msgs, msg_message("regionmagic_effect", - "unit region command", c->magician, mage->region, co->order)); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: - * Stufe: 9 - * Kategorie: - * Wirkung: - * Es wird eine Kloneinheit erzeugt, die nichts kann. Stirbt der - * Magier, wird er mit einer Wahrscheinlichkeit von 90% in den Klon - * transferiert. - * Flags: - * (NOTFAMILARCAST) - */ -int sp_clonecopy(castorder * co) -{ - unit *clone; - region *r = co_get_region(co); - region *target_region = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - message *msg; - char name[NAMESIZE]; - - if (get_clone(mage) != NULL) { - cmistake(mage, co->order, 298, MSG_MAGIC); - return 0; - } - - _snprintf(name, sizeof(name), (const char *)LOC(mage->faction->locale, - "clone_of"), unitname(mage)); - clone = - create_unit(target_region, mage->faction, 1, get_race(RC_CLONE), 0, name, - mage); - setstatus(clone, ST_FLEE); - fset(clone, UFL_LOCKED); - - create_newclone(mage, clone); - - msg = msg_message("sp_clone_effet", "mage", mage); - r_addmessage(r, mage->faction, msg); - msg_release(msg); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -int sp_dreamreading(castorder * co) -{ - unit *u, *u2; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - spellparameter *pa = co->par; - float power = co->force; - message *msg; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber - * abbrechen aber kosten lassen */ - if (pa->param[0]->flag == TARGET_RESISTS) - return cast_level; - - u = pa->param[0]->data.u; - - /* Illusionen und Untote abfangen. */ - if (fval(u_race(u), RCF_UNDEAD | RCF_ILLUSIONARY)) { - ADDMSG(&mage->faction->msgs, msg_unitnotfound(mage, co->order, - pa->param[0])); - return 0; - } - - /* Entfernung */ - if (distance(mage->region, u->region) > power) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_distance", "")); - return 0; - } - - u2 = - create_unit(u->region, mage->faction, RS_FARVISION, get_race(RC_SPELL), 0, - "spell/dreamreading", NULL); - set_number(u2, 1); - u2->age = 2; /* Nur fuer diese Runde. */ - set_level(u2, SK_PERCEPTION, eff_skill(u, SK_PERCEPTION, u2->region)); - - msg = - msg_message("sp_dreamreading_effect", "mage unit region", mage, u, - u->region); - r_addmessage(r, mage->faction, msg); - msg_release(msg); - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Wirkt power/2 Runden auf bis zu power^2 Personen - * mit einer Chance von 5% vermehren sie sich */ -int sp_sweetdreams(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - spellparameter *pa = co->par; - int men, n; - int duration = (int)(power / 2) + 1; - int opfer = (int)(power * power); - - /* Schleife ueber alle angegebenen Einheiten */ - for (n = 0; n < pa->length; n++) { - curse *c; - unit *u; - float effect; - message *msg; - /* sollte nie negativ werden */ - if (opfer < 1) - break; - - if (pa->param[n]->flag == TARGET_RESISTS || - pa->param[n]->flag == TARGET_NOTFOUND) - continue; - - /* Zieleinheit */ - u = pa->param[n]->data.u; - - if (!ucontact(u, mage)) { - cmistake(mage, co->order, 40, MSG_EVENT); - continue; - } - men = _min(opfer, u->number); - opfer -= men; - - /* Nichts machen als ein entsprechendes Attribut an die Einheit legen. */ - effect = 0.05f; - c = create_curse(mage, &u->attribs, ct_find("orcish"), power, duration, effect, men); - - msg = msg_message("sp_sweetdreams_effect", "mage unit region", c->magician, u, r); - r_addmessage(r, mage->faction, msg); - if (u->faction != mage->faction) { - r_addmessage(r, u->faction, msg); - } - msg_release(msg); - } - return cast_level; -} - -/* ------------------------------------------------------------- */ -int sp_disturbingdreams(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - int duration = 1 + (int)(power / 6); - float effect; - curse *c; - - effect = 10; - c = create_curse(mage, &r->attribs, ct_find("badlearn"), power, duration, effect, 0); - - ADDMSG(&mage->faction->msgs, msg_message("sp_disturbingdreams_effect", - "mage region", c->magician, r)); - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* ASTRAL / THEORIE / M_TYBIED */ -/* ------------------------------------------------------------- */ -/* Name: Magie analysieren - * Stufe: 1 - * Aura: 1 - * Kosten: SPC_LINEAR - * Komponenten: - * - * Wirkung: - * Zeigt die Verzauberungen eines Objekts an (curse->name, - * curse::info). Aus der Differenz Spruchstaerke und Curse->vigour - * ergibt sich die Chance den Spruch zu identifizieren ((force - - * c->vigour)*10 + 100 %). - * - * Flags: - * UNITSPELL, SHIPSPELL, BUILDINGSPELL - */ - -int sp_analysemagic(castorder * co) -{ - int obj; - unit *mage = co_get_caster(co); - int cast_level = co->level; - spellparameter *pa = co->par; - - if (!pa->param) { - return 0; - } - /* Objekt ermitteln */ - obj = pa->param[0]->typ; - - switch (obj) { - case SPP_REGION: - { - region *tr = pa->param[0]->data.r; - magicanalyse_region(tr, mage, cast_level); - break; - } - case SPP_TEMP: - case SPP_UNIT: - { - unit *u; - u = pa->param[0]->data.u; - magicanalyse_unit(u, mage, cast_level); - break; - } - case SPP_BUILDING: - { - building *b; - b = pa->param[0]->data.b; - magicanalyse_building(b, mage, cast_level); - break; - } - case SPP_SHIP: - { - ship *sh; - sh = pa->param[0]->data.sh; - magicanalyse_ship(sh, mage, cast_level); - break; - } - default: - /* Fehlerhafter Parameter */ - return 0; - } - - return cast_level; -} - -/* ------------------------------------------------------------- */ - -int sp_itemcloak(castorder * co) -{ - unit *target; - unit *mage = co->magician.u; - spellparameter *pa = co->par; - int cast_level = co->level; - float power = co->force; - int duration = (int)_max(2.0, power + 1); /* works in the report, and ageing this round would kill it if it's <=1 */ - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - /* Zieleinheit */ - target = pa->param[0]->data.u; - - create_curse(mage, &target->attribs, ct_find("itemcloak"), power, duration, - zero_effect, 0); - ADDMSG(&mage->faction->msgs, msg_message("itemcloak", "mage target", mage, - target)); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Magieresistenz erhoehen - * Stufe: 3 - * Aura: 5 MP - * Kosten: SPC_LEVEL - * Komponenten: - * - * Wirkung: - * erhoeht die Magierestistenz der Personen um 20 Punkte fuer 6 Wochen - * Wirkt auf Stufe*5 Personen kann auf mehrere Einheiten gezaubert - * werden, bis die Zahl der moeglichen Personen erschoepft ist - * - * Flags: - * UNITSPELL - */ -int sp_resist_magic_bonus(castorder * co) -{ - unit *u; - int n, m; - int duration = 6; - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - spellparameter *pa = co->par; - /* Pro Stufe koennen bis zu 5 Personen verzaubert werden */ - double maxvictims = 5 * power; - int victims = (int)maxvictims; - - /* Schleife ueber alle angegebenen Einheiten */ - for (n = 0; n < pa->length; n++) { - message *msg; - /* sollte nie negativ werden */ - if (victims < 1) - break; - - if (pa->param[n]->flag == TARGET_RESISTS - || pa->param[n]->flag == TARGET_NOTFOUND) - continue; - - u = pa->param[n]->data.u; - - /* Ist die Einheit schon verzaubert, wirkt sich dies nur auf die - * Menge der Verzauberten Personen aus. - if (is_cursed(u->attribs, C_MAGICRESISTANCE, 0)) - continue; - */ - - m = _min(u->number, victims); - victims -= m; - - create_curse(mage, &u->attribs, ct_find("magicresistance"), - power, duration, 20, m); - - msg = msg_message("magicresistance_effect", "unit", u); - add_message(&u->faction->msgs, msg); - - /* und noch einmal dem Magier melden */ - if (u->faction != mage->faction) { - add_message(&mage->faction->msgs, msg); - } - msg_release(msg); - } - - cast_level = _min(cast_level, (int)(cast_level * (victims + 4) / maxvictims)); - return _max(cast_level, 1); -} - -/** spell 'Astraler Weg'. - * Syntax "ZAUBERE [STUFE n] 'Astraler Weg' [ ...]", - * - * Parameter: - * pa->param[0]->data.xs -*/ -int sp_enterastral(castorder * co) -{ - region *rt, *ro; - unit *u, *u2; - int remaining_cap; - int n, w; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - spellparameter *pa = co->par; - - switch (getplaneid(r)) { - case 0: - rt = r_standard_to_astral(r); - ro = r; - break; - default: - cmistake(mage, co->order, 190, MSG_MAGIC); - return 0; - } - - if (!rt || fval(rt->terrain, FORBIDDEN_REGION)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "feedback_no_astralregion", "")); - return 0; - } - - if (is_cursed(rt->attribs, C_ASTRALBLOCK, 0) - || is_cursed(ro->attribs, C_ASTRALBLOCK, 0)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_astralblock", "")); - return 0; - } - - remaining_cap = (int)((power - 3) * 1500); - - /* fuer jede Einheit in der Kommandozeile */ - for (n = 0; n < pa->length; n++) { - if (pa->param[n]->flag == TARGET_NOTFOUND) - continue; - u = pa->param[n]->data.u; - - if (!ucontact(u, mage)) { - if (power > 10 && !is_magic_resistant(mage, u, 0) && can_survive(u, rt)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "feedback_no_contact_no_resist", "target", u)); - ADDMSG(&u->faction->msgs, msg_message("send_astral", "unit target", - mage, u)); - } else { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "feedback_no_contact_resist", "target", u)); - ADDMSG(&u->faction->msgs, msg_message("try_astral", "unit target", mage, - u)); - continue; - } - } - - w = weight(u); - if (!can_survive(u, rt)) { - cmistake(mage, co->order, 231, MSG_MAGIC); - } else if (remaining_cap - w < 0) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "fail_tooheavy", "target", u)); - } else { - message *m; - remaining_cap = remaining_cap - w; - move_unit(u, rt, NULL); - - /* Meldungen in der Ausgangsregion */ - - for (u2 = r->units; u2; u2 = u2->next) - freset(u2->faction, FFL_SELECT); - m = NULL; - for (u2 = r->units; u2; u2 = u2->next) { - if (!fval(u2->faction, FFL_SELECT)) { - if (cansee(u2->faction, r, u, 0)) { - fset(u2->faction, FFL_SELECT); - if (!m) - m = msg_message("astral_disappear", "unit", u); - r_addmessage(r, u2->faction, m); - } - } - } - if (m) - msg_release(m); - - /* Meldungen in der Zielregion */ - - for (u2 = rt->units; u2; u2 = u2->next) - freset(u2->faction, FFL_SELECT); - m = NULL; - for (u2 = rt->units; u2; u2 = u2->next) { - if (!fval(u2->faction, FFL_SELECT)) { - if (cansee(u2->faction, rt, u, 0)) { - fset(u2->faction, FFL_SELECT); - if (!m) - m = msg_message("astral_appear", "unit", u); - r_addmessage(rt, u2->faction, m); - } - } - } - if (m) - msg_release(m); - } - } - return cast_level; -} - -/** Spell 'Astraler Ruf' / 'Astral Call'. - */ -int sp_pullastral(castorder * co) -{ - region *rt, *ro; - unit *u, *u2; - region_list *rl, *rl2; - int remaining_cap; - int n, w; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - spellparameter *pa = co->par; - - switch (getplaneid(r)) { - case 1: - rt = r; - ro = pa->param[0]->data.r; - rl = astralregions(r, NULL); - rl2 = rl; - while (rl2 != NULL) { - region *r2 = rl2->data; - if (r2->x == ro->x && r2->y == ro->y) { - ro = r2; - break; - } - rl2 = rl2->next; - } - if (!rl2) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail::nocontact", "target", rt)); - free_regionlist(rl); - return 0; - } - free_regionlist(rl); - break; - default: - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_astralonly", "")); - return 0; - } - - if (is_cursed(rt->attribs, C_ASTRALBLOCK, 0) - || is_cursed(ro->attribs, C_ASTRALBLOCK, 0)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_astralblock", "")); - return 0; - } - - remaining_cap = (int)((power - 3) * 1500); - - /* fuer jede Einheit in der Kommandozeile */ - for (n = 1; n < pa->length; n++) { - spllprm *spobj = pa->param[n]; - if (spobj->flag == TARGET_NOTFOUND) - continue; - - u = spobj->data.u; - - if (u->region != ro) { - /* Report this as unit not found */ - if (spobj->typ == SPP_UNIT) { - spobj->data.i = u->no; - } else { - spobj->data.i = ualias(u); - } - spobj->flag = TARGET_NOTFOUND; - ADDMSG(&mage->faction->msgs, msg_unitnotfound(mage, co->order, spobj)); - return false; - } - - if (!ucontact(u, mage)) { - if (power > 12 && spobj->flag != TARGET_RESISTS && can_survive(u, rt)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "feedback_no_contact_no_resist", "target", u)); - } else { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "feedback_no_contact_resist", "target", u)); - ADDMSG(&u->faction->msgs, msg_message("try_astral", "unit target", mage, - u)); - continue; - } - } - - w = weight(u); - - if (!can_survive(u, rt)) { - cmistake(mage, co->order, 231, MSG_MAGIC); - } else if (remaining_cap - w < 0) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "fail_tooheavy", "target", u)); - } else { - message *m; - - ADDMSG(&u->faction->msgs, msg_message("send_astral", "unit target", mage, - u)); - remaining_cap = remaining_cap - w; - move_unit(u, rt, NULL); - - /* Meldungen in der Ausgangsregion */ - - for (u2 = r->units; u2; u2 = u2->next) - freset(u2->faction, FFL_SELECT); - m = NULL; - for (u2 = r->units; u2; u2 = u2->next) { - if (!fval(u2->faction, FFL_SELECT)) { - if (cansee(u2->faction, r, u, 0)) { - fset(u2->faction, FFL_SELECT); - if (!m) - m = msg_message("astral_disappear", "unit", u); - r_addmessage(r, u2->faction, m); - } - } - } - if (m) - msg_release(m); - - /* Meldungen in der Zielregion */ - - for (u2 = rt->units; u2; u2 = u2->next) - freset(u2->faction, FFL_SELECT); - m = NULL; - for (u2 = rt->units; u2; u2 = u2->next) { - if (!fval(u2->faction, FFL_SELECT)) { - if (cansee(u2->faction, rt, u, 0)) { - fset(u2->faction, FFL_SELECT); - if (!m) - m = msg_message("astral_appear", "unit", u); - r_addmessage(rt, u2->faction, m); - } - } - } - if (m) - msg_release(m); - } - } - return cast_level; -} - -int sp_leaveastral(castorder * co) -{ - region *rt, *ro; - region_list *rl, *rl2; - unit *u, *u2; - int remaining_cap; - int n, w; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - spellparameter *pa = co->par; - - switch (getplaneid(r)) { - case 1: - ro = r; - rt = pa->param[0]->data.r; - if (!rt) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail::noway", "")); - return 0; - } - rl = astralregions(r, inhabitable); - rl2 = rl; - while (rl2 != NULL) { - if (rl2->data == rt) - break; - rl2 = rl2->next; - } - if (rl2 == NULL) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail::noway", "")); - free_regionlist(rl); - return 0; - } - free_regionlist(rl); - break; - default: - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spell_astral_only", "")); - return 0; - } - - if (ro == NULL || is_cursed(ro->attribs, C_ASTRALBLOCK, 0) - || is_cursed(rt->attribs, C_ASTRALBLOCK, 0)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_astralblock", "")); - return 0; - } - - remaining_cap = (int)((power - 3) * 1500); - - /* fuer jede Einheit in der Kommandozeile */ - for (n = 1; n < pa->length; n++) { - if (pa->param[n]->flag == TARGET_NOTFOUND) - continue; - - u = pa->param[n]->data.u; - - if (!ucontact(u, mage)) { - if (power > 10 && !pa->param[n]->flag == TARGET_RESISTS - && can_survive(u, rt)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "feedback_no_contact_no_resist", "target", u)); - ADDMSG(&u->faction->msgs, msg_message("send_astral", "unit target", - mage, u)); - } else { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "feedback_no_contact_resist", "target", u)); - ADDMSG(&u->faction->msgs, msg_message("try_astral", "unit target", mage, - u)); - continue; - } - } - - w = weight(u); - - if (!can_survive(u, rt)) { - cmistake(mage, co->order, 231, MSG_MAGIC); - } else if (remaining_cap - w < 0) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "fail_tooheavy", "target", u)); - } else { - message *m; - - remaining_cap = remaining_cap - w; - move_unit(u, rt, NULL); - - /* Meldungen in der Ausgangsregion */ - - for (u2 = r->units; u2; u2 = u2->next) - freset(u2->faction, FFL_SELECT); - m = NULL; - for (u2 = r->units; u2; u2 = u2->next) { - if (!fval(u2->faction, FFL_SELECT)) { - if (cansee(u2->faction, r, u, 0)) { - fset(u2->faction, FFL_SELECT); - if (!m) - m = msg_message("astral_disappear", "unit", u); - r_addmessage(r, u2->faction, m); - } - } - } - if (m) - msg_release(m); - - /* Meldungen in der Zielregion */ - - for (u2 = rt->units; u2; u2 = u2->next) - freset(u2->faction, FFL_SELECT); - m = NULL; - for (u2 = rt->units; u2; u2 = u2->next) { - if (!fval(u2->faction, FFL_SELECT)) { - if (cansee(u2->faction, rt, u, 0)) { - fset(u2->faction, FFL_SELECT); - if (!m) - m = msg_message("astral_appear", "unit", u); - r_addmessage(rt, u2->faction, m); - } - } - } - if (m) - msg_release(m); - } - } - return cast_level; -} - -int sp_fetchastral(castorder * co) -{ - int n; - unit *mage = co->magician.u; - int cast_level = co->level; - spellparameter *pa = co->par; - float power = co->force; - int remaining_cap = (int)((power - 3) * 1500); - region_list *rtl = NULL; - region *rt = co_get_region(co); /* region to which we are fetching */ - region *ro = NULL; /* region in which the target is */ - - if (rplane(rt) != get_normalplane()) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, "error190", "")); - return 0; - } - - /* fuer jede Einheit in der Kommandozeile */ - for (n = 0; n != pa->length; ++n) { - unit *u2, *u = pa->param[n]->data.u; - int w; - message *m; - - if (pa->param[n]->flag & TARGET_NOTFOUND) - continue; - - if (u->region != ro) { - /* this can happen several times if the units are from different astral - * regions. Only possible on the intersections of schemes */ - region_list *rfind; - if (!is_astral(u->region)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_astralonly", "")); - continue; - } - if (rtl != NULL) - free_regionlist(rtl); - rtl = astralregions(u->region, NULL); - for (rfind = rtl; rfind != NULL; rfind = rfind->next) { - if (rfind->data == mage->region) - break; - } - if (rfind == NULL) { - /* the region r is not in the schemes of rt */ - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_distance", "target", u)); - continue; - } - ro = u->region; - } - - if (is_cursed(ro->attribs, C_ASTRALBLOCK, 0)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spellfail_astralblock", "")); - continue; - } - - if (!can_survive(u, rt)) { - cmistake(mage, co->order, 231, MSG_MAGIC); - continue; - } - - w = weight(u); - if (remaining_cap - w < 0) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "fail_tooheavy", "target", u)); - continue; - } - - if (!ucontact(u, mage)) { - if (power > 12 && !(pa->param[n]->flag & TARGET_RESISTS)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "feedback_no_contact_no_resist", "target", u)); - ADDMSG(&u->faction->msgs, msg_message("send_astral", "unit target", - mage, u)); - } else { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "feedback_no_contact_resist", "target", u)); - ADDMSG(&u->faction->msgs, msg_message("try_astral", "unit target", mage, - u)); - continue; - } - } - - remaining_cap -= w; - move_unit(u, rt, NULL); - - /* Meldungen in der Ausgangsregion */ - for (u2 = ro->units; u2; u2 = u2->next) - freset(u2->faction, FFL_SELECT); - m = NULL; - for (u2 = ro->units; u2; u2 = u2->next) { - if (!fval(u2->faction, FFL_SELECT)) { - if (cansee(u2->faction, ro, u, 0)) { - fset(u2->faction, FFL_SELECT); - if (!m) - m = msg_message("astral_disappear", "unit", u); - r_addmessage(ro, u2->faction, m); - } - } - } - if (m) - msg_release(m); - - /* Meldungen in der Zielregion */ - for (u2 = rt->units; u2; u2 = u2->next) - freset(u2->faction, FFL_SELECT); - m = NULL; - for (u2 = rt->units; u2; u2 = u2->next) { - if (!fval(u2->faction, FFL_SELECT)) { - if (cansee(u2->faction, rt, u, 0)) { - fset(u2->faction, FFL_SELECT); - if (!m) - m = msg_message("astral_appear", "unit", u); - r_addmessage(rt, u2->faction, m); - } - } - } - if (m) - msg_release(m); - } - if (rtl != NULL) - free_regionlist(rtl); - return cast_level; -} - -#ifdef SHOWASTRAL_NOT_BORKED -int sp_showastral(castorder * co) -{ - unit *u; - region *rt; - int n = 0; - int c = 0; - region_list *rl, *rl2; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - - switch (getplaneid(r)) { - case 0: - rt = r_standard_to_astral(r); - if (!rt || fval(rt->terrain, FORBIDDEN_REGION)) { - /* Hier gibt es keine Verbindung zur astralen Welt */ - cmistake(mage, co->order, 216, MSG_MAGIC); - return 0; - } - break; - case 1: - rt = r; - break; - default: - /* Hier gibt es keine Verbindung zur astralen Welt */ - cmistake(mage, co->order, 216, MSG_MAGIC); - return 0; - } - - rl = all_in_range(rt, power / 5); - - /* Erst Einheiten zaehlen, fuer die Grammatik. */ - - for (rl2 = rl; rl2; rl2 = rl2->next) { - region *r2 = rl2->data; - if (!is_cursed(r2->attribs, C_ASTRALBLOCK, 0)) { - for (u = r2->units; u; u = u->next) { - if (u_race(u) != get_race(RC_SPECIAL) && u_race(u) != get_race(RC_SPELL)) - n++; - } - } - } - - if (n == 0) { - /* sprintf(buf, "%s kann niemanden im astralen Nebel entdecken.", - unitname(mage)); */ - cmistake(mage, co->order, 220, MSG_MAGIC); - } else { - - /* Ausgeben */ - - sprintf(buf, "%s hat eine Vision der astralen Ebene. Im astralen " - "Nebel zu erkennen sind ", unitname(mage)); - - for (rl2 = rl; rl2; rl2 = rl2->next) { - if (!is_cursed(rl2->data->attribs, C_ASTRALBLOCK, 0)) { - for (u = rl2->data->units; u; u = u->next) { - if (u_race(u) != get_race(RC_SPECIAL) && u_race(u) != get_race(RC_SPELL)) { - c++; - scat(unitname(u)); - scat(" ("); - if (!fval(u, UFL_ANON_FACTION)) { - scat(factionname(u->faction)); - scat(", "); - } - icat(u->number); - scat(" "); - scat(LOC(mage->faction->locale, rc_name(u_race(u), (u->number==1) ? NAME_SINGULAR:NAME_PLURAL))); - scat(", Entfernung "); - icat(distance(rl2->data, rt)); - scat(")"); - if (c == n - 1) { - scat(" und "); - } else if (c < n - 1) { - scat(", "); - } - } - } - } - } - scat("."); - addmessage(r, mage->faction, buf, MSG_MAGIC, ML_INFO); - } - - free_regionlist(rl); - return cast_level; - unused_arg(co); - return 0; -} -#endif - -/* ------------------------------------------------------------- */ -int sp_viewreality(castorder * co) -{ - region_list *rl, *rl2; - unit *u; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - message *m; - - if (getplaneid(r) != 1) { - /* sprintf(buf, "Dieser Zauber kann nur im Astralraum gezaubert werden."); */ - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "spell_astral_only", "")); - return 0; - } - - rl = astralregions(r, NULL); - - /* Irgendwann mal auf Curses u/o Attribut umstellen. */ - for (rl2 = rl; rl2; rl2 = rl2->next) { - region *rt = rl2->data; - if (!is_cursed(rt->attribs, C_ASTRALBLOCK, 0)) { - u = - create_unit(rt, mage->faction, RS_FARVISION, get_race(RC_SPELL), 0, - "spell/viewreality", NULL); - set_level(u, SK_PERCEPTION, co->level / 2); - u->age = 2; - } - } - - free_regionlist(rl); - - m = msg_message("viewreality_effect", "unit", mage); - r_addmessage(r, mage->faction, m); - msg_release(m); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -int sp_disruptastral(castorder * co) -{ - region_list *rl, *rl2; - region *rt; - unit *u; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - int duration = (int)(power / 3) + 1; - - switch (getplaneid(r)) { - case 0: - rt = r_standard_to_astral(r); - if (!rt || fval(rt->terrain, FORBIDDEN_REGION)) { - /* "Hier gibt es keine Verbindung zur astralen Welt." */ - cmistake(mage, co->order, 216, MSG_MAGIC); - return 0; - } - break; - case 1: - rt = r; - break; - default: - /* "Von hier aus kann man die astrale Ebene nicht erreichen." */ - cmistake(mage, co->order, 215, MSG_MAGIC); - return 0; - } - - rl = all_in_range(rt, (short)(power / 5), NULL); - - for (rl2 = rl; rl2 != NULL; rl2 = rl2->next) { - attrib *a; - float effect; - region *r2 = rl2->data; - spec_direction *sd; - int inhab_regions = 0; - region_list *trl = NULL; - - if (is_cursed(r2->attribs, C_ASTRALBLOCK, 0)) - continue; - - if (r2->units != NULL) { - region_list *trl2; - - trl = astralregions(rl2->data, inhabitable); - for (trl2 = trl; trl2; trl2 = trl2->next) - ++inhab_regions; - } - - /* Nicht-Permanente Tore zerstoeren */ - a = a_find(r->attribs, &at_direction); - - while (a != NULL && a->type == &at_direction) { - attrib *a2 = a->next; - sd = (spec_direction *) (a->data.v); - if (sd->duration != -1) - a_remove(&r->attribs, a); - a = a2; - } - - /* Einheiten auswerfen */ - - if (trl != NULL) { - for (u = r2->units; u; u = u->next) { - if (u_race(u) != get_race(RC_SPELL)) { - region_list *trl2 = trl; - region *tr; - int c = rng_int() % inhab_regions; - - /* Zufaellige Zielregion suchen */ - while (c-- != 0) - trl2 = trl2->next; - tr = trl2->data; - - if (!is_magic_resistant(mage, u, 0) && can_survive(u, tr)) { - message *msg = msg_message("disrupt_astral", "unit region", u, tr); - add_message(&u->faction->msgs, msg); - add_message(&tr->msgs, msg); - msg_release(msg); - - move_unit(u, tr, NULL); - } - } - } - free_regionlist(trl); - } - - /* Kontakt unterbinden */ - effect = 100; - create_curse(mage, &rl2->data->attribs, ct_find("astralblock"), - power, duration, effect, 0); - } - - free_regionlist(rl); - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Mauern der Ewigkeit - * Stufe: 7 - * Kategorie: Artefakt - * Gebiet: Tybied - * Wirkung: - * Das Gebaeude kostet keinen Unterhalt mehr - * - * ZAUBER "Mauern der Ewigkeit" - * Flags: (0) - */ -static int sp_eternizewall(castorder * co) -{ - unit *u; - curse *c; - building *b; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - spellparameter *pa = co->par; - message *msg; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - b = pa->param[0]->data.b; - c = create_curse(mage, &b->attribs, ct_find("nocostbuilding"), - power * power, 1, zero_effect, 0); - - if (c == NULL) { /* ist bereits verzaubert */ - cmistake(mage, co->order, 206, MSG_MAGIC); - return 0; - } - - /* melden, 1x pro Partei in der Burg */ - for (u = r->units; u; u = u->next) - freset(u->faction, FFL_SELECT); - msg = - msg_message("sp_eternizewall_effect", "mage building region", mage, b, r); - for (u = r->units; u; u = u->next) { - if (!fval(u->faction, FFL_SELECT)) { - fset(u->faction, FFL_SELECT); - if (u->building == b) { - r_addmessage(r, u->faction, msg); - } - } - } - if (r != mage->region) { - add_message(&mage->faction->msgs, msg); - } else if (mage->building != b) { - r_addmessage(r, mage->faction, msg); - } - msg_release(msg); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Opfere Kraft - * Stufe: 15 - * Gebiet: Tybied - * Kategorie: Einheit, positiv - * Wirkung: - * Mit Hilfe dieses Zaubers kann der Magier einen Teil seiner - * magischen Kraft permanent auf einen anderen Magier uebertragen. Auf - * einen Tybied-Magier kann er die Haelfte der eingesetzten Kraft - * uebertragen, auf einen Magier eines anderen Gebietes ein Drittel. - * - * Flags: - * (UNITSPELL) - * - * Syntax: - * ZAUBERE \"Opfere Kraft\" - * "ui" - */ -int sp_permtransfer(castorder * co) -{ - int aura; - unit *tu; - unit *mage = co->magician.u; - int cast_level = co->level; - spellparameter *pa = co->par; - const spell *sp = co->sp; - message *msg; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - /* wenn Ziel gefunden, dieses aber Magieresistent war, Zauber - * abbrechen aber kosten lassen */ - if (pa->param[0]->flag == TARGET_RESISTS) - return cast_level; - - tu = pa->param[0]->data.u; - aura = pa->param[1]->data.i; - - if (!is_mage(tu)) { -/* sprintf(buf, "%s in %s: 'ZAUBER %s': Einheit ist kein Magier." - , unitname(mage), regionname(mage->region, mage->faction),sa->strings[0]); */ - cmistake(mage, co->order, 214, MSG_MAGIC); - return 0; - } - - aura = _min(get_spellpoints(mage) - spellcost(mage, sp), aura); - - change_maxspellpoints(mage, -aura); - change_spellpoints(mage, -aura); - - if (get_mage(tu)->magietyp == get_mage(mage)->magietyp) { - change_maxspellpoints(tu, aura / 2); - } else { - change_maxspellpoints(tu, aura / 3); - } - - msg = - msg_message("sp_permtransfer_effect", "mage target amount", mage, tu, aura); - add_message(&mage->faction->msgs, msg); - if (tu->faction != mage->faction) { - add_message(&tu->faction->msgs, msg); - } - msg_release(msg); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* TODO: specialdirections? */ - -int sp_movecastle(castorder * co) -{ - building *b; - direction_t dir; - region *target_region; - unit *u, *unext; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - spellparameter *pa = co->par; - message *msg; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - b = pa->param[0]->data.b; - dir = get_direction(pa->param[1]->data.xs, mage->faction->locale); - - if (dir == NODIRECTION) { - /* Die Richtung wurde nicht erkannt */ - cmistake(mage, co->order, 71, MSG_PRODUCE); - return 0; - } - - if (b->size > (cast_level - 12) * 250) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "sp_movecastle_fail_0", "")); - return cast_level; - } - - target_region = rconnect(r, dir); - - if (!(target_region->terrain->flags & LAND_REGION)) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "sp_movecastle_fail_1", "direction", dir)); - return cast_level; - } - - bunhash(b); - translist(&r->buildings, &target_region->buildings, b); - b->region = target_region; - b->size -= b->size / (10 - rng_int() % 6); - bhash(b); - - for (u = r->units; u;) { - unext = u->next; - if (u->building == b) { - uunhash(u); - translist(&r->units, &target_region->units, u); - uhash(u); - } - u = unext; - } - - if ((b->type == bt_find("caravan") || b->type == bt_find("dam") - || b->type == bt_find("tunnel"))) { - direction_t d; - for (d = 0; d != MAXDIRECTIONS; ++d) { - if (rroad(r, d)) { - rsetroad(r, d, rroad(r, d) / 2); - } - } - } - - msg = msg_message("sp_movecastle_effect", "building direction", b, dir); - r_addmessage(r, NULL, msg); - msg_release(msg); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Luftschiff - * Stufe: 6 - * - * Wirkung: - * Laeßt ein Schiff eine Runde lang fliegen. Wirkt nur auf Boote und - * Langboote. - * Kombinierbar mit "Guenstige Winde", aber nicht mit "Sturmwind". - * - * Flag: - * (ONSHIPCAST | SHIPSPELL | TESTRESISTANCE) - */ -int sp_flying_ship(castorder * co) -{ - ship *sh; - unit *u; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - spellparameter *pa = co->par; - message *m = NULL; - int cno; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - sh = pa->param[0]->data.sh; - if (sh->type->construction->maxsize > 50) { - ADDMSG(&mage->faction->msgs, msg_feedback(mage, co->order, - "error_flying_ship_too_big", "ship", sh)); - return 0; - } - - /* Duration = 1, nur diese Runde */ - - cno = levitate_ship(sh, mage, power, 1); - if (cno == 0) { - if (is_cursed(sh->attribs, C_SHIP_FLYING, 0)) { - /* Auf dem Schiff befindet liegt bereits so ein Zauber. */ - cmistake(mage, co->order, 211, MSG_MAGIC); - } else if (is_cursed(sh->attribs, C_SHIP_SPEEDUP, 0)) { - /* Es ist zu gefaehrlich, ein sturmgepeitschtes Schiff fliegen zu lassen. */ - cmistake(mage, co->order, 210, MSG_MAGIC); - } - return 0; - } - sh->coast = NODIRECTION; - - /* melden, 1x pro Partei */ - for (u = r->units; u; u = u->next) - freset(u->faction, FFL_SELECT); - for (u = r->units; u; u = u->next) { - /* das sehen natuerlich auch die Leute an Land */ - if (!fval(u->faction, FFL_SELECT)) { - fset(u->faction, FFL_SELECT); - if (!m) - m = msg_message("flying_ship_result", "mage ship", mage, sh); - add_message(&u->faction->msgs, m); - } - } - if (m) - msg_release(m); - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Stehle Aura - * Stufe: 6 - * Kategorie: Einheit, negativ - * Wirkung: - * Mit Hilfe dieses Zaubers kann der Magier einem anderen Magier - * seine Aura gegen dessen Willen entziehen und sich selber - * zufuehren. - * - * Flags: - * (FARCASTING | SPELLLEVEL | UNITSPELL | TESTRESISTANCE | - * TESTCANSEE) - * */ -int sp_stealaura(castorder * co) -{ - int taura; - unit *u; - unit *mage = co->magician.u; - int cast_level = co->level; - float power = co->force; - spellparameter *pa = co->par; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - /* Zieleinheit */ - u = pa->param[0]->data.u; - - if (!get_mage(u)) { - ADDMSG(&mage->faction->msgs, msg_message("stealaura_fail", "unit target", - mage, u)); - ADDMSG(&u->faction->msgs, msg_message("stealaura_fail_detect", "unit", u)); - return 0; - } - - taura = (get_mage(u)->spellpoints * (rng_int() % (int)(3 * power) + 1)) / 100; - - if (taura > 0) { - get_mage(u)->spellpoints -= taura; - get_mage(mage)->spellpoints += taura; -/* sprintf(buf, "%s entzieht %s %d Aura.", unitname(mage), unitname(u), - taura); */ - ADDMSG(&mage->faction->msgs, msg_message("stealaura_success", - "mage target aura", mage, u, taura)); -/* sprintf(buf, "%s fuehlt seine magischen Kraefte schwinden und verliert %d " - "Aura.", unitname(u), taura); */ - ADDMSG(&u->faction->msgs, msg_message("stealaura_detect", "unit aura", u, - taura)); - } else { - ADDMSG(&mage->faction->msgs, msg_message("stealaura_fail", "unit target", - mage, u)); - ADDMSG(&u->faction->msgs, msg_message("stealaura_fail_detect", "unit", u)); - } - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Astrale Schwaechezone - * Stufe: 5 - * Kategorie: - * Wirkung: - * Reduziert die Staerke jedes Spruch in der Region um Level Haelt - * Sprueche bis zu einem Gesammtlevel von Staerke*10 aus, danach ist - * sie verbraucht. - * leibt bis zu Staerke Wochen aktiv. - * Ein Ring der Macht erhoeht die Staerke um 1, in einem Magierturm - * gezaubert gibt nochmal +1 auf Staerke. (force) - * - * Beispiel: - * Eine Antimagiezone Stufe 7 haelt bis zu 7 Wochen an oder Sprueche mit - * einem Gesammtlevel bis zu 70 auf. Also zB 7 Stufe 10 Sprueche, 10 - * Stufe 7 Sprueche oder 35 Stufe 2 Sprueche. Sie reduziert die Staerke - * (level+boni) jedes Spruchs, der in der Region gezaubert wird, um - * 7. Alle Sprueche mit einer Staerke kleiner als 7 schlagen fehl - * (power = 0). - * - * Flags: - * (FARCASTING | SPELLLEVEL | REGIONSPELL | TESTRESISTANCE) - * - */ -int sp_antimagiczone(castorder * co) -{ - float power; - float effect; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - int duration = (int)force + 1; - - /* Haelt Sprueche bis zu einem summierten Gesamtlevel von power aus. - * Jeder Zauber reduziert die 'Lebenskraft' (vigour) der Antimagiezone - * um seine Stufe */ - power = force * 10; - - /* Reduziert die Staerke jedes Spruchs um effect */ - effect = (float)cast_level; - - create_curse(mage, &r->attribs, ct_find("antimagiczone"), power, duration, - effect, 0); - - /* Erfolg melden */ - ADDMSG(&mage->faction->msgs, msg_message("regionmagic_effect", - "unit region command", mage, mage->region, co->order)); - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Schutzrunen - * Stufe: 8 - * Kosten: SPC_FIX - * - * Wirkung: - * Gibt Gebaeuden einen Bonus auf Magieresistenz von +20%. Der Zauber - * dauert 3+rng_int()%Level Wochen an, also im Extremfall bis zu 2 Jahre - * bei Stufe 20 - * - * Es koennen mehrere Zauber uebereinander gelegt werden, der Effekt - * summiert sich, jedoch wird die Dauer dadurch nicht verlaengert. - * - * oder: - * - * Gibt Schiffen einen Bonus auf Magieresistenz von +20%. Der Zauber - * dauert 3+rng_int()%Level Wochen an, also im Extremfall bis zu 2 Jahre - * bei Stufe 20 - * - * Es koennen mehrere Zauber uebereinander gelegt werden, der Effekt - * summiert sich, jedoch wird die Dauer dadurch nicht verlaengert. - * - * Flags: - * (ONSHIPCAST | TESTRESISTANCE) - * - * Syntax: - * ZAUBERE \"Runen des Schutzes\" GEBAEUDE - * ZAUBERE \"Runen des Schutzes\" SCHIFF - * "kc" - */ - -static int sp_magicrunes(castorder * co) -{ - int duration; - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - spellparameter *pa = co->par; - float effect; - - duration = 3 + rng_int() % cast_level; - effect = 20; - - switch (pa->param[0]->typ) { - case SPP_BUILDING: - { - building *b; - b = pa->param[0]->data.b; - - /* Magieresistenz der Burg erhoeht sich um 20% */ - create_curse(mage, &b->attribs, ct_find("magicrunes"), force, - duration, effect, 0); - - /* Erfolg melden */ - ADDMSG(&mage->faction->msgs, msg_message("objmagic_effect", - "unit region command target", mage, mage->region, co->order, - buildingname(b))); - break; - } - case SPP_SHIP: - { - ship *sh; - sh = pa->param[0]->data.sh; - /* Magieresistenz des Schiffs erhoeht sich um 20% */ - create_curse(mage, &sh->attribs, ct_find("magicrunes"), force, - duration, effect, 0); - - /* Erfolg melden */ - ADDMSG(&mage->faction->msgs, msg_message("objmagic_effect", - "unit region command target", mage, mage->region, co->order, - shipname(sh))); - break; - } - default: - /* fehlerhafter Parameter */ - return 0; - } - - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Zeitdehnung - * - * Flags: - * (UNITSPELL | SPELLLEVEL | ONSHIPCAST | TESTCANSEE) - * Syntax: - * "u+" - */ - -int sp_speed2(castorder * co) -{ - int n, maxmen, used = 0, dur, men; - unit *u; - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - spellparameter *pa = co->par; - - maxmen = 2 * cast_level * cast_level; - dur = _max(1, cast_level / 2); - - for (n = 0; n < pa->length; n++) { - float effect; - /* sollte nie negativ werden */ - if (maxmen < 1) - break; - - if (pa->param[n]->flag == TARGET_RESISTS - || pa->param[n]->flag == TARGET_NOTFOUND) - continue; - - u = pa->param[n]->data.u; - - men = _min(maxmen, u->number); - effect = 2; - create_curse(mage, &u->attribs, ct_find("speed"), force, dur, effect, men); - maxmen -= men; - used += men; - } - - ADDMSG(&mage->faction->msgs, msg_message("speed_time_effect", - "unit region amount", mage, mage->region, used)); - /* Effektiv benoetigten cast_level (mindestens 1) zurueckgeben */ - used = (int)sqrt(used / 2); - return _max(1, used); -} - -/* ------------------------------------------------------------- */ -/* Name: Magiefresser - * Stufe: 7 - * Kosten: SPC_LEVEL - * - * Wirkung: - * Kann eine bestimmte Verzauberung angreifen und aufloesen. Die Staerke - * des Zaubers muss staerker sein als die der Verzauberung. - * Syntax: - * ZAUBERE \"Magiefresser\" REGION - * ZAUBERE \"Magiefresser\" EINHEIT - * ZAUBERE \"Magiefresser\" GEBAEUDE - * ZAUBERE \"Magiefresser\" SCHIFF - * - * "kc?c" - * Flags: - * (FARCASTING | SPELLLEVEL | ONSHIPCAST | TESTCANSEE) - */ -/* Jeder gebrochene Zauber verbraucht c->vigour an Zauberkraft - * (force) */ -int sp_q_antimagie(castorder * co) -{ - attrib **ap; - int obj; - curse *c = NULL; - int succ; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - spellparameter *pa = co->par; - const char *ts = NULL; - - obj = pa->param[0]->typ; - - switch (obj) { - case SPP_REGION: - ap = &r->attribs; - ts = regionname(r, mage->faction); - break; - - case SPP_TEMP: - case SPP_UNIT: - { - unit *u = pa->param[0]->data.u; - ap = &u->attribs; - ts = unitid(u); - break; - } - case SPP_BUILDING: - { - building *b = pa->param[0]->data.b; - ap = &b->attribs; - ts = buildingid(b); - break; - } - case SPP_SHIP: - { - ship *sh = pa->param[0]->data.sh; - ap = &sh->attribs; - ts = shipid(sh); - break; - } - default: - /* Das Zielobjekt wurde vergessen */ - cmistake(mage, co->order, 203, MSG_MAGIC); - return 0; - } - - succ = break_curse(ap, cast_level, force, c); - - if (succ) { - ADDMSG(&mage->faction->msgs, msg_message("destroy_magic_effect", - "unit region command succ target", mage, mage->region, co->order, succ, - ts)); - } else { - ADDMSG(&mage->faction->msgs, msg_message("destroy_magic_noeffect", - "unit region command", mage, mage->region, co->order)); - } - return _max(succ, 1); -} - -/* ------------------------------------------------------------- */ -/* Name: Fluch brechen - * Stufe: 7 - * Kosten: SPC_LEVEL - * - * Wirkung: - * Kann eine bestimmte Verzauberung angreifen und aufloesen. Die Staerke - * des Zaubers muss staerker sein als die der Verzauberung. - * Syntax: - * ZAUBERE \"Fluch brechen\" REGION - * ZAUBERE \"Fluch brechen\" EINHEIT - * ZAUBERE \"Fluch brechen\" GEBAEUDE - * ZAUBERE \"Fluch brechen\" SCHIFF - * - * "kcc" - * Flags: - * (FARCASTING | SPELLLEVEL | ONSHIPCAST | TESTCANSEE) - */ -int sp_break_curse(castorder * co) -{ - attrib **ap; - int obj; - curse *c; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - float force = co->force; - spellparameter *pa = co->par; - const char *ts = NULL; - - if (pa->length < 2) { - /* Das Zielobjekt wurde vergessen */ - cmistake(mage, co->order, 203, MSG_MAGIC); - return 0; - } - - obj = pa->param[0]->typ; - - c = findcurse(atoi36(pa->param[1]->data.s)); - if (!c) { - /* Es wurde kein Ziel gefunden */ - ADDMSG(&mage->faction->msgs, msg_message("spelltargetnotfound", - "unit region command", mage, mage->region, co->order)); - } else { - switch (obj) { - case SPP_REGION: - ap = &r->attribs; - ts = regionname(r, mage->faction); - break; - - case SPP_TEMP: - case SPP_UNIT: - { - unit *u = pa->param[0]->data.u; - ap = &u->attribs; - ts = unitid(u); - break; - } - case SPP_BUILDING: - { - building *b = pa->param[0]->data.b; - ap = &b->attribs; - ts = buildingid(b); - break; - } - case SPP_SHIP: - { - ship *sh = pa->param[0]->data.sh; - ap = &sh->attribs; - ts = shipid(sh); - break; - } - default: - /* Das Zielobjekt wurde vergessen */ - cmistake(mage, co->order, 203, MSG_MAGIC); - return 0; - } - - /* ueberpruefung, ob curse zu diesem objekt gehoert */ - if (!is_cursed_with(*ap, c)) { - /* Es wurde kein Ziel gefunden */ - ADDMSG(&mage->faction->msgs, - msg_message("spelltargetnotfound", "unit region command", - mage, mage->region, co->order)); - } - - /* curse aufloesen, wenn zauber staerker (force > vigour) */ - c->vigour -= force; - - if (c->vigour <= 0.0) { - remove_curse(ap, c); - - ADDMSG(&mage->faction->msgs, msg_message("destroy_curse_effect", - "unit region command id target", mage, mage->region, co->order, - pa->param[1]->data.xs, ts)); - } else { - ADDMSG(&mage->faction->msgs, msg_message("destroy_curse_noeffect", - "unit region command id target", mage, mage->region, co->order, - pa->param[1]->data.xs, ts)); - } - } - - return cast_level; -} - -/* ------------------------------------------------------------- */ -int sp_becomewyrm(castorder * co) -{ - return 0; -} - -/* ------------------------------------------------------------- */ -/* Name: WDW-Pyramidenfindezauber - * Stufe: unterschiedlich - * Gebiet: alle - * Wirkung: - * gibt die ungefaehre Entfernung zur naechstgelegenen Pyramiden- - * region an. - * - * Flags: - */ -static int sp_wdwpyramid(castorder * co) -{ - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - - if (a_find(r->attribs, &at_wdwpyramid) != NULL) { - ADDMSG(&mage->faction->msgs, msg_message("wdw_pyramidspell_found", - "unit region command", mage, r, co->order)); - } else { - region *r2; - int mindist = INT_MAX; - int minshowdist; - int maxshowdist; - - for (r2 = regions; r2; r2 = r2->next) { - if (a_find(r2->attribs, &at_wdwpyramid) != NULL) { - int dist = distance(mage->region, r2); - if (dist < mindist) { - mindist = dist; - } - } - } - - assert(mindist >= 1); - - minshowdist = mindist - rng_int() % 5; - maxshowdist = minshowdist + 4; - - ADDMSG(&mage->faction->msgs, msg_message("wdw_pyramidspell_notfound", - "unit region command mindist maxdist", mage, r, co->order, - _max(1, minshowdist), maxshowdist)); - } - - return cast_level; -} - -typedef struct spelldata { - const char *sname; - spell_f cast; - fumble_f fumble; -} spelldata; - -static spelldata spell_functions[] = { - /* M_GWYRRD */ - { "stonegolem", sp_create_stonegolem, 0}, - { "irongolem", sp_create_irongolem, 0}, - { "treegrow", sp_hain, fumble_ents}, - { "rustweapon", sp_rosthauch, 0}, - { "cold_protection", sp_kaelteschutz, 0}, - { "ironkeeper", sp_ironkeeper, 0}, - { "magicstreet", sp_magicstreet, 0}, - { "windshield", sp_windshield, 0}, - { "mallorntreegrow", sp_mallornhain, fumble_ents}, - { "goodwinds", sp_goodwinds, 0}, - { "healing", sp_healing, 0}, - { "reelingarrows", sp_reeling_arrows, 0}, - { "gwyrrdfumbleshield", sp_fumbleshield, 0}, - { "transferauradruide", sp_transferaura, 0}, - { "earthquake", sp_earthquake, 0}, - { "stormwinds", sp_stormwinds, 0}, - { "homestone", sp_homestone, 0}, - { "wolfhowl", sp_wolfhowl, 0}, - { "versteinern", sp_petrify, 0}, - { "strongwall", sp_strong_wall, 0}, - { "gwyrrddestroymagic", sp_destroy_magic, 0}, - { "treewalkenter", sp_treewalkenter, 0}, - { "treewalkexit", sp_treewalkexit, 0}, - { "holyground", sp_holyground, 0}, - { "summonent", sp_summonent, 0}, - { "blessstonecircle", sp_blessstonecircle, 0}, - { "barkskin", sp_armorshield, 0}, - { "summonfireelemental", sp_drought, 0}, - { "maelstrom", sp_maelstrom, 0}, - { "magic_roots", sp_mallorn, 0}, - { "great_drought", sp_great_drought, 0}, - /* M_DRAIG */ - { "sparklechaos", sp_sparkle, 0}, - { "magicboost", sp_magicboost, 0}, - { "bloodsacrifice", sp_bloodsacrifice, 0}, - { "berserk", sp_berserk, 0}, - { "fumblecurse", sp_fumblecurse, patzer_fumblecurse}, - { "summonundead", sp_summonundead, patzer_peasantmob}, - { "combatrust", sp_combatrosthauch, 0}, - { "transferaurachaos", sp_transferaura, 0}, - { "firewall", sp_firewall, patzer_peasantmob}, - { "plague", sp_plague, patzer_peasantmob}, - { "chaosrow", sp_chaosrow, 0}, - { "summonshadow", sp_summonshadow, patzer_peasantmob}, - { "undeadhero", sp_undeadhero, 0}, - { "auraleak", sp_auraleak, 0}, - { "draigfumbleshield", sp_fumbleshield, 0}, - { "forestfire", sp_forest_fire, patzer_peasantmob}, - { "draigdestroymagic", sp_destroy_magic, 0}, - { "unholypower", sp_unholypower, 0}, - { "deathcloud", sp_deathcloud, patzer_peasantmob}, - { "summondragon", sp_summondragon, patzer_peasantmob}, - { "summonshadowlords", sp_summonshadowlords, patzer_peasantmob}, - { "chaossuction", sp_chaossuction, patzer_peasantmob}, - /* M_ILLAUN */ - { "sparkledream", sp_sparkle, 0}, - { "shadowknights", sp_shadowknights, 0}, - { "flee", sp_flee, 0}, - { "puttorest", sp_puttorest, 0}, - { "icastle", sp_icastle, 0}, - { "transferauratraum", sp_transferaura, 0}, - { "shapeshift", sp_illusionary_shapeshift, 0}, - { "dreamreading", sp_dreamreading, 0}, - { "tiredsoldiers", sp_tiredsoldiers, 0}, - { "reanimate", sp_reanimate, 0}, - { "analysedream", sp_analysedream, 0}, - { "disturbingdreams", sp_disturbingdreams, 0}, - { "sleep", sp_sleep, 0}, - { "wisps", 0, 0}, /* this spell is gone */ - { "gooddreams", sp_gooddreams, 0}, - { "illaundestroymagic", sp_destroy_magic, 0}, - { "clone", sp_clonecopy, 0}, - { "bad_dreams", sp_baddreams, 0}, - { "mindblast", sp_mindblast_temp, 0}, - { "orkdream", sp_sweetdreams, 0}, - { "summon_alp", sp_summon_alp, 0}, - /* M_CERDDOR */ - { "appeasement", sp_denyattack, 0}, - { "song_of_healing", sp_healing, 0}, - { "generous", sp_generous, 0}, - { "song_of_fear", sp_flee, 0}, - { "courting", sp_recruit, 0}, - { "song_of_confusion", sp_chaosrow, 0}, - { "heroic_song", sp_hero, 0}, - { "transfer_aura_song", sp_transferaura, 0}, - { "analysesong_unit", sp_analysesong_unit, 0}, - { "cerrdorfumbleshield", sp_fumbleshield, 0}, - { "calm_monster", sp_calm_monster, 0}, - { "seduction", sp_seduce, 0}, - { "headache", sp_headache, 0}, - { "sound_out", sp_pump, 0}, - { "bloodthirst", sp_berserk, 0}, - { "frighten", sp_frighten, 0}, - { "analyse_object", sp_analysesong_obj, 0}, - { "cerddor_destroymagic", sp_destroy_magic, 0}, - { "migration", sp_migranten, 0}, - { "summon_familiar", sp_summon_familiar, 0}, - { "raise_mob", sp_raisepeasants, 0}, - { "song_resist_magic", sp_song_resistmagic, 0}, - { "melancholy", sp_depression, 0}, - { "song_suscept_magic", sp_song_susceptmagic, 0}, - { "song_of_peace", sp_song_of_peace, 0}, - { "song_of_slavery", sp_charmingsong, 0}, - { "big_recruit", sp_bigrecruit, 0}, - { "calm_riot", sp_rallypeasantmob, 0}, - { "incite_riot", sp_raisepeasantmob, 0}, - /* M_TYBIED */ - { "analyze_magic", sp_analysemagic, 0}, - { "concealing_aura", sp_itemcloak, 0}, - { "tybiedfumbleshield", sp_fumbleshield, 0}, -#ifdef SHOWASTRAL_NOT_BORKED - { "show_astral", sp_showastral, 0}, -#endif - { "resist_magic", sp_resist_magic_bonus, 0}, - { "keeploot", sp_keeploot, 0}, - { "enterastral", sp_enterastral, 0}, - { "leaveastral", sp_leaveastral, 0}, - { "auratransfer", sp_transferaura, 0}, - { "shockwave", sp_stun, 0}, - { "antimagiczone", sp_antimagiczone, 0}, - { "destroy_magic", sp_destroy_magic, 0}, - { "pull_astral", sp_pullastral, 0}, - { "fetch_astral", sp_fetchastral, 0}, - { "steal_aura", sp_stealaura, 0}, - { "airship", sp_flying_ship, 0}, - { "break_curse", sp_break_curse, 0}, - { "eternal_walls", sp_eternizewall, 0}, - { "protective_runes", sp_magicrunes, 0}, - { "fish_shield", sp_reduceshield, 0}, - { "combat_speed", sp_speed, 0}, - { "view_reality", sp_viewreality, 0}, - { "double_time", sp_speed2, 0}, - { "armor_shield", sp_armorshield, 0}, - { "living_rock", sp_movecastle, 0}, - { "astral_disruption", sp_disruptastral, 0}, - { "sacrifice_strength", sp_permtransfer, 0}, - /* M_GRAY */ - /* Definitionen von Create_Artefaktspruechen */ - { "wyrm_transformation", sp_becomewyrm, 0}, - /* Monstersprueche */ - { "fiery_dragonbreath", sp_dragonodem, 0}, - { "icy_dragonbreath", sp_dragonodem, 0}, - { "powerful_dragonbreath", sp_dragonodem, 0}, - { "drain_skills", sp_dragonodem, 0}, - { "aura_of_fear", sp_flee, 0}, - { "shadowcall", sp_shadowcall, 0}, - { "immolation", sp_immolation, 0}, - { "firestorm", sp_immolation, 0}, - { "coldfront", sp_immolation, 0}, - { "acidrain", sp_immolation, 0}, - /* SPL_NOSPELL MUSS der letzte Spruch der Liste sein */ - { 0, 0, 0 } -}; - -static void register_spelldata(void) -{ - int i; - char zText[32]; - strcpy(zText, "fumble_"); - for (i = 0; spell_functions[i].sname; ++i) { - spelldata *data = spell_functions + i; - if (data->cast) { - register_function((pf_generic)data->cast, data->sname); - } - if (data->fumble) { - strlcpy(zText+7, data->sname, sizeof(zText)-7); - register_function((pf_generic)data->fumble, zText); - } - } -} - -/* ------------------------------------------------------------- */ -/* Name: Plappermaul -* Stufe: 4 -* Gebiet: Cerddor -* Kategorie: Einheit -* -* Wirkung: -* Einheit ausspionieren. Gibt auch Zauber und Kampfstatus aus. Wirkt -* gegen Magieresistenz. Ist diese zu hoch, so wird der Zauber entdeckt -* (Meldung) und der Zauberer erhaelt nur die Talente, nicht die Werte -* der Einheit und auch keine Zauber. -* -* Flag: -* (UNITSPELL | TESTCANSEE) -*/ -static int sp_babbler(castorder * co) -{ - unit *target; - region *r = co_get_region(co); - unit *mage = co->magician.u; - int cast_level = co->level; - spellparameter *pa = co->par; - message *msg; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - target = pa->param[0]->data.u; - - if (target->faction == mage->faction) { - /* Die Einheit ist eine der unsrigen */ - cmistake(mage, co->order, 45, MSG_MAGIC); - } - - /* Magieresistenz Unit */ - if (target_resists_magic(mage, target, TYP_UNIT, 0)) { - spy_message(5, mage, target); - msg = msg_message("babbler_resist", "unit mage", target, mage); - } else { - spy_message(100, mage, target); - msg = msg_message("babbler_effect", "unit", target); - } - r_addmessage(r, target->faction, msg); - msg_release(msg); - return cast_level; -} - -/* ------------------------------------------------------------- */ -/* Name: Traumdeuten -* Stufe: 7 -* Kategorie: Einheit -* -* Wirkung: -* Wirkt gegen Magieresistenz. Spioniert die Einheit aus. Gibt alle -* Gegenstaende, Talente mit Stufe, Zauber und Kampfstatus an. -* -* Magieresistenz hier pruefen, wegen Fehlermeldung -* -* Flag: -* (UNITSPELL) -*/ -static int sp_readmind(castorder * co) -{ - unit *target; - unit *mage = co->magician.u; - int cast_level = co->level; - spellparameter *pa = co->par; - - /* wenn kein Ziel gefunden, Zauber abbrechen */ - if (pa->param[0]->flag == TARGET_NOTFOUND) - return 0; - - target = pa->param[0]->data.u; - - if (target->faction == mage->faction) { - /* Die Einheit ist eine der unsrigen */ - cmistake(mage, co->order, 45, MSG_MAGIC); - } - - /* Magieresistenz Unit */ - if (target_resists_magic(mage, target, TYP_UNIT, 0)) { - cmistake(mage, co->order, 180, MSG_MAGIC); - /* "Fuehlt sich beobachtet" */ - ADDMSG(&target->faction->msgs, msg_message("stealdetect", "unit", target)); - return 0; - } - spy_message(2, mage, target); - - return cast_level; -} - -void register_spells(void) -{ - at_register(&at_wdwpyramid); - at_register(&at_deathcloud_compat); - - /* sp_summon_alp */ - register_alp(); - - /* init_firewall(); */ - ct_register(&ct_firewall); - ct_register(&ct_deathcloud); - - register_function((pf_generic) sp_blessedharvest, "cast_blessedharvest"); - register_function((pf_generic) sp_wdwpyramid, "wdwpyramid"); - register_function((pf_generic) sp_summon_familiar, "cast_familiar"); - register_function((pf_generic) sp_babbler, "cast_babbler"); - register_function((pf_generic) sp_readmind, "cast_readmind"); - register_function((pf_generic) sp_kampfzauber, "combat_spell"); - - register_spelldata(); - - register_unitcurse(); - register_regioncurse(); - register_shipcurse(); - register_buildingcurse(); -} diff --git a/src/test_eressea.c b/src/test_eressea.c index 7244da4c8..fb78486d4 100644 --- a/src/test_eressea.c +++ b/src/test_eressea.c @@ -44,20 +44,21 @@ int RunAllTests(void) ADD_TESTS(suite, equipment); ADD_TESTS(suite, item); ADD_TESTS(suite, magic); - ADD_TESTS(suite, move); ADD_TESTS(suite, reports); ADD_TESTS(suite, save); ADD_TESTS(suite, ship); ADD_TESTS(suite, spellbook); ADD_TESTS(suite, building); ADD_TESTS(suite, spell); - ADD_TESTS(suite, battle); ADD_TESTS(suite, ally); /* gamecode */ - ADD_TESTS(suite, stealth); - ADD_TESTS(suite, market); - ADD_TESTS(suite, laws); + ADD_TESTS(suite, battle); ADD_TESTS(suite, economy); + ADD_TESTS(suite, laws); + ADD_TESTS(suite, market); + ADD_TESTS(suite, move); + ADD_TESTS(suite, stealth); + ADD_TESTS(suite, vortex); CuSuiteRun(suite); CuSuiteSummary(suite, output); diff --git a/src/vortex.c b/src/vortex.c new file mode 100644 index 000000000..5a0f6482e --- /dev/null +++ b/src/vortex.c @@ -0,0 +1,190 @@ +#include +#include +#include "vortex.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +typedef struct dir_lookup { + char *name; + const char *oldname; + struct dir_lookup *next; +} dir_lookup; + +static dir_lookup *dir_name_lookup; + +void register_special_direction(const char *name) +{ + struct locale *lang; + char *str = _strdup(name); + + for (lang = locales; lang; lang = nextlocale(lang)) { + void **tokens = get_translations(lang, UT_SPECDIR); + const char *token = LOC(lang, name); + + if (token) { + variant var; + + var.v = str; + addtoken(tokens, token, var); + + if (lang == default_locale) { + dir_lookup *dl = malloc(sizeof(dir_lookup)); + dl->name = str; + dl->oldname = token; + dl->next = dir_name_lookup; + dir_name_lookup = dl; + } + } + else { + log_error("no translation for spec_direction '%s' in locale '%s'\n", name, locale_name(lang)); + } + } +} + +/********************/ +/* at_direction */ +/********************/ +static void a_initdirection(attrib * a) +{ + a->data.v = calloc(1, sizeof(spec_direction)); +} + +static void a_freedirection(attrib * a) +{ + free(a->data.v); +} + +static int a_agedirection(attrib * a) +{ + spec_direction *d = (spec_direction *)(a->data.v); + --d->duration; + return (d->duration > 0) ? AT_AGE_KEEP : AT_AGE_REMOVE; +} + +static int a_readdirection(attrib * a, void *owner, struct storage *store) +{ + spec_direction *d = (spec_direction *)(a->data.v); + + _CRT_UNUSED(owner); + READ_INT(store, &d->x); + READ_INT(store, &d->y); + READ_INT(store, &d->duration); + if (global.data_version < UNICODE_VERSION) { + char lbuf[16]; + dir_lookup *dl = dir_name_lookup; + + READ_TOK(store, NULL, 0); + READ_TOK(store, lbuf, sizeof(lbuf)); + + cstring_i(lbuf); + for (; dl; dl = dl->next) { + if (strcmp(lbuf, dl->oldname) == 0) { + d->keyword = _strdup(dl->name); + _snprintf(lbuf, sizeof(lbuf), "%s_desc", d->keyword); + d->desc = _strdup(dl->name); + break; + } + } + if (dl == NULL) { + log_error("unknown spec_direction '%s'\n", lbuf); + assert(!"not implemented"); + } + } + else { + char lbuf[32]; + READ_TOK(store, lbuf, sizeof(lbuf)); + d->desc = _strdup(lbuf); + READ_TOK(store, lbuf, sizeof(lbuf)); + d->keyword = _strdup(lbuf); + } + d->active = true; + return AT_READ_OK; +} + +static void +a_writedirection(const attrib * a, const void *owner, struct storage *store) +{ + spec_direction *d = (spec_direction *)(a->data.v); + + _CRT_UNUSED(owner); + WRITE_INT(store, d->x); + WRITE_INT(store, d->y); + WRITE_INT(store, d->duration); + WRITE_TOK(store, d->desc); + WRITE_TOK(store, d->keyword); +} +attrib_type at_direction = { + "direction", + a_initdirection, + a_freedirection, + a_agedirection, + a_writedirection, + a_readdirection +}; + +region *find_special_direction(const region * r, const char *token, + const struct locale *lang) +{ + attrib *a; + spec_direction *d; + + if (strlen(token) == 0) + return NULL; + for (a = a_find(r->attribs, &at_direction); a && a->type == &at_direction; + a = a->next) { + d = (spec_direction *)(a->data.v); + + if (d->active) { + void **tokens = get_translations(lang, UT_SPECDIR); + variant var; + if (findtoken(*tokens, token, &var) == E_TOK_SUCCESS) { + if (strcmp((const char *)var.v, d->keyword) == 0) { + return findregion(d->x, d->y); + } + } + } + } + + return NULL; +} + +attrib *create_special_direction(region * r, region * rt, int duration, + const char *desc, const char *keyword, bool active) +{ + attrib *a = a_add(&r->attribs, a_new(&at_direction)); + spec_direction *d = (spec_direction *)(a->data.v); + + d->active = active; + d->x = rt->x; + d->y = rt->y; + d->duration = duration; + d->desc = _strdup(desc); + d->keyword = _strdup(keyword); + + return a; +} + +spec_direction *special_direction(const region * from, const region * to) +{ + const attrib *a = a_findc(from->attribs, &at_direction); + + while (a != NULL && a->type == &at_direction) { + spec_direction *sd = (spec_direction *)a->data.v; + if (sd->x == to->x && sd->y == to->y) + return sd; + a = a->next; + } + return NULL; +} diff --git a/src/vortex.h b/src/vortex.h new file mode 100644 index 000000000..910811cd1 --- /dev/null +++ b/src/vortex.h @@ -0,0 +1,32 @@ +#ifndef H_VORTEX +#define H_VORTEX +#ifdef __cplusplus +extern "C" { +#endif + + struct region; + struct attrib; + struct locale; + + typedef struct spec_direction { + int x, y; + int duration; + bool active; + char *desc; + char *keyword; + } spec_direction; + + extern struct attrib_type at_direction; + + struct region *find_special_direction(const struct region *r, + const char *token, const struct locale *lang); + void register_special_direction(const char *name); + struct spec_direction *special_direction(const struct region * from, + const struct region * to); + struct attrib *create_special_direction(struct region *r, struct region *rt, + int duration, const char *desc, const char *keyword, bool active); + +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file diff --git a/src/vortex.test.c b/src/vortex.test.c new file mode 100644 index 000000000..7185dc82d --- /dev/null +++ b/src/vortex.test.c @@ -0,0 +1,48 @@ +#include +#include + +#include "vortex.h" +#include "move.h" +#include "tests.h" + +#include +#include +#include +#include +#include + +#include + +#include + +static void test_move_to_vortex(CuTest *tc) { + region *r1, *r2, *r = 0; + terrain_type *t_plain; + unit *u; + struct locale *lang; + + test_cleanup(); + lang = get_or_create_locale("en"); + locale_setstring(lang, "vortex", "wirbel"); + init_locale(lang); + register_special_direction("vortex"); + t_plain = test_create_terrain("plain", LAND_REGION | FOREST_REGION | WALK_INTO | CAVALRY_REGION); + r1 = test_create_region(0, 0, t_plain); + r2 = test_create_region(5, 0, t_plain); + CuAssertPtrNotNull(tc, create_special_direction(r1, r2, 10, "", "vortex", true)); + u = test_create_unit(test_create_faction(rc_get_or_create("hodor")), r1); + CuAssertIntEquals(tc, E_MOVE_NOREGION, movewhere(u, "barf", r1, &r)); + CuAssertIntEquals(tc, E_MOVE_OK, movewhere(u, "wirbel", r1, &r)); + CuAssertPtrEquals(tc, r2, r); +} + +static void test_vortex(CuTest *tc) { +} + +CuSuite *get_vortex_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_vortex); + SUITE_ADD_TEST(suite, test_move_to_vortex); + return suite; +}