diff --git a/CMakeLists.txt b/CMakeLists.txt index 361cf6f5e..9730625b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,16 +54,6 @@ CONFIGURE_FILE ( INCLUDE_DIRECTORIES (${CMAKE_BINARY_DIR}/include) SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_AUTOCONF") -IF(CMAKE_COMPILER_IS_GNUCC) - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -DHAVE__BOOL") -elseif(MSVC) - set(CMAKE_EXE_LINKER_FLAGS_DEBUG - "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:libc.lib /NODEFAULTLIB:libcmt.lib /NODEFAULTLIB:libcd.lib /NODEFAULTLIB:libcmtd.lib /NODEFAULTLIB:msvcrt.lib") - set(CMAKE_EXE_LINKER_FLAGS_RELEASE - "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /NODEFAULTLIB:libc.lib /NODEFAULTLIB:libcmt.lib /NODEFAULTLIB:libcd.lib /NODEFAULTLIB:libcmtd.lib /NODEFAULTLIB:msvcrtd.lib") -ELSE(CMAKE_COMPILER_IS_GNUCC) - MESSAGE(STATUS "Unknown compiler ${CMAKE_C_COMPILER_ID}") -ENDIF(CMAKE_COMPILER_IS_GNUCC) find_package (LibXml2) find_package (SQLite3) diff --git a/conf/e2/config.xml b/conf/e2/config.xml index 2ca33346c..7265cbb89 100644 --- a/conf/e2/config.xml +++ b/conf/e2/config.xml @@ -22,7 +22,6 @@ - diff --git a/conf/e3/config.xml b/conf/e3/config.xml index b30ad8bfc..32f941cb8 100644 --- a/conf/e3/config.xml +++ b/conf/e3/config.xml @@ -10,7 +10,6 @@ - diff --git a/conf/e4/config.xml b/conf/e4/config.xml index 44cc35471..fff10fbc3 100644 --- a/conf/e4/config.xml +++ b/conf/e4/config.xml @@ -10,7 +10,6 @@ - diff --git a/game-e2/config.xml b/game-e2/config.xml index 871f893d8..ec8a8855e 100644 --- a/game-e2/config.xml +++ b/game-e2/config.xml @@ -22,7 +22,6 @@ - diff --git a/game-e3/config.xml b/game-e3/config.xml index 5841fe90b..a66b20393 100644 --- a/game-e3/config.xml +++ b/game-e3/config.xml @@ -1,7 +1,5 @@ - - diff --git a/res/directions.xml b/res/directions.xml deleted file mode 100644 index 0e2e0b5fe..000000000 --- a/res/directions.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f90c82258..71c397f5b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,14 +1,6 @@ cmake_minimum_required(VERSION 2.6) project (server C) -IF(CMAKE_COMPILER_IS_GNUCC) - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pedantic -Wall -Werror -Wno-unknown-pragmas -Wstrict-prototypes -Wpointer-arith -Wno-char-subscripts -Wno-long-long") - SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -DHAVE__BOOL") -ELSEIF(MSVC) -ELSE(CMAKE_COMPILER_IS_GNUCC) - MESSAGE(STATUS "Unknown compiler ${CMAKE_C_COMPILER_ID}") -ENDIF(CMAKE_COMPILER_IS_GNUCC) - include_directories (${CMAKE_CURRENT_SOURCE_DIR}) include_directories (${CRITBIT_INCLUDE_DIR}) include_directories (${CJSON_INCLUDE_DIR}) @@ -20,6 +12,19 @@ include_directories (${LUA_INCLUDE_DIR}) include_directories (${BSON_INCLUDE_DIR}) include_directories (${INIPARSER_INCLUDE_DIR}) +IF(CMAKE_COMPILER_IS_GNUCC) + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pedantic -Wall -Werror -Wno-unknown-pragmas -Wstrict-prototypes -Wpointer-arith -Wno-char-subscripts -Wno-long-long") + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -DHAVE__BOOL") +elseif(MSVC) + SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /W4 /WX /MP") + set(CMAKE_EXE_LINKER_FLAGS_DEBUG + "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:libc.lib /NODEFAULTLIB:libcmt.lib /NODEFAULTLIB:libcd.lib /NODEFAULTLIB:libcmtd.lib /NODEFAULTLIB:msvcrt.lib") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE + "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /NODEFAULTLIB:libc.lib /NODEFAULTLIB:libcmt.lib /NODEFAULTLIB:libcd.lib /NODEFAULTLIB:libcmtd.lib /NODEFAULTLIB:msvcrtd.lib") +ELSE(CMAKE_COMPILER_IS_GNUCC) + MESSAGE(STATUS "Unknown compiler ${CMAKE_C_COMPILER_ID}") +ENDIF(CMAKE_COMPILER_IS_GNUCC) + add_subdirectory(util) add_subdirectory(kernel) add_subdirectory(items) @@ -58,6 +63,14 @@ TOLUA_BINDING(settings.pkg bind_settings.h) ENDIF() set (ERESSEA_SRC + move.c + spells.c + battle.c + alchemy.c + stealth.c + vortex.c + names.c + reports.c eressea.c callback.c direction.c @@ -141,7 +154,12 @@ target_link_libraries(eressea set(TESTS_SRC test_eressea.c tests.c + battle.test.c + vortex.test.c tests.test.c + reports.test.c + stealth.test.c + move.test.c callback.test.c direction.test.c keyword.test.c diff --git a/src/kernel/alchemy.c b/src/alchemy.c similarity index 97% rename from src/kernel/alchemy.c rename to src/alchemy.c index bce078899..c9516740a 100644 --- a/src/kernel/alchemy.c +++ b/src/alchemy.c @@ -19,18 +19,18 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include "alchemy.h" - -#include "item.h" -#include "faction.h" -#include "messages.h" -#include "build.h" -#include "magic.h" -#include "region.h" -#include "pool.h" -#include "race.h" -#include "unit.h" -#include "skill.h" #include "move.h" +#include "skill.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include /* util includes */ #include diff --git a/src/kernel/alchemy.h b/src/alchemy.h similarity index 100% rename from src/kernel/alchemy.h rename to src/alchemy.h diff --git a/src/battle.c b/src/battle.c new file mode 100644 index 000000000..5a4cd2779 --- /dev/null +++ b/src/battle.c @@ -0,0 +1,4342 @@ +/* +Copyright (c) 1998-2010, Enno Rehling +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 +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#include +#include +#include "battle.h" +#include "alchemy.h" +#include "move.h" +#include "skill.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* attributes includes */ +#include +#include +#include +#include +#include + +/* util includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* libc includes */ +#include +#include +#include +#include +#include +#include +#include + +static FILE *bdebug; + +#define TACTICS_BONUS 1 /* when undefined, we have a tactics round. else this is the bonus tactics give */ +#define TACTICS_MODIFIER 1 /* modifier for generals in the front/rear */ + +#define CATAPULT_INITIAL_RELOAD 4 /* erster schuss in runde 1 + rng_int() % INITIAL */ +#define CATAPULT_STRUCTURAL_DAMAGE + +#define BASE_CHANCE 70 /* 70% Basis-Überlebenschance */ +#ifdef NEW_COMBATSKILLS_RULE +#define TDIFF_CHANGE 5 /* 5% höher pro Stufe */ +#define DAMAGE_QUOTIENT 2 /* damage += skilldiff/DAMAGE_QUOTIENT */ +#else +#define TDIFF_CHANGE 10 +# define DAMAGE_QUOTIENT 1 /* damage += skilldiff/DAMAGE_QUOTIENT */ +#endif + +#undef DEBUG_FAST /* should be disabled when b->fast and b->rowcache works */ +#define DEBUG_SELECT /* should be disabled if select_enemy works */ + +typedef enum combatmagic { + DO_PRECOMBATSPELL, + DO_POSTCOMBATSPELL +} combatmagic_t; + +/* globals */ +static int obs_count = 0; + +#define MINSPELLRANGE 1 +#define MAXSPELLRANGE 7 + +#ifndef ROW_FACTOR +# define ROW_FACTOR 10 +#endif +#define EFFECT_PANIC_SPELL 0.25 +#define TROLL_REGENERATION 0.10 + +/* Nach dem alten System: */ +static int missile_range[2] = { FIGHT_ROW, BEHIND_ROW }; +static int melee_range[2] = { FIGHT_ROW, FIGHT_ROW }; + +static message *msg_separator; + +const troop no_troop = { 0, 0 }; + +static int max_turns = 0; +static int damage_rules = 0; +static int loot_rules = 0; +static int skill_formula = 0; + +#define FORMULA_ORIG 0 +#define FORMULA_NEW 1 + +#define LOOT_MONSTERS (1<<0) +#define LOOT_SELF (1<<1) /* code is mutually exclusive with LOOT_OTHERS */ +#define LOOT_OTHERS (1<<2) +#define LOOT_KEEPLOOT (1<<4) + +#define DAMAGE_CRITICAL (1<<0) +#define DAMAGE_MELEE_BONUS (1<<1) +#define DAMAGE_MISSILE_BONUS (1<<2) +#define DAMAGE_UNARMED_BONUS (1<<3) +#define DAMAGE_SKILL_BONUS (1<<4) +/** initialize rules from configuration. + */ +static void static_rules(void) +{ + loot_rules = + get_param_int(global.parameters, "rules.combat.loot", + LOOT_MONSTERS | LOOT_OTHERS | LOOT_KEEPLOOT); + /* new formula to calculate to-hit-chance */ + skill_formula = + get_param_int(global.parameters, "rules.combat.skill_formula", + FORMULA_ORIG); + /* maximum number of combat turns */ + max_turns = + get_param_int(global.parameters, "rules.combat.turns", COMBAT_TURNS); + /* damage calculation */ + if (get_param_int(global.parameters, "rules.combat.critical", 1)) { + damage_rules |= DAMAGE_CRITICAL; + } + if (get_param_int(global.parameters, "rules.combat.melee_bonus", 1)) { + damage_rules |= DAMAGE_MELEE_BONUS; + } + if (get_param_int(global.parameters, "rules.combat.missile_bonus", 1)) { + damage_rules |= DAMAGE_MISSILE_BONUS; + } + if (get_param_int(global.parameters, "rules.combat.unarmed_bonus", 1)) { + damage_rules |= DAMAGE_UNARMED_BONUS; + } + if (get_param_int(global.parameters, "rules.combat.skill_bonus", 1)) { + damage_rules |= DAMAGE_SKILL_BONUS; + } +} + +static int army_index(side * s) +{ + return s->index; +} + +static char *sidename(side * s) +{ +#define SIDENAMEBUFLEN 256 + static int bufno; /* STATIC_XCALL: used across calls */ + static char sidename_buf[4][SIDENAMEBUFLEN]; /* STATIC_RESULT: used for return, not across calls */ + + bufno = bufno % 4; + strlcpy(sidename_buf[bufno], factionname(s->stealthfaction ? s->stealthfaction : s->faction), SIDENAMEBUFLEN); + return sidename_buf[bufno++]; +} + +static const char *sideabkz(side * s, bool truename) +{ + static char sideabkz_buf[8]; /* STATIC_RESULT: used for return, not across calls */ + const faction *f = (s->stealthfaction + && !truename) ? s->stealthfaction : s->faction; + +#undef SIDE_ABKZ +#ifdef SIDE_ABKZ + abkz(f->name, sideabkz_buf, sizeof(sideabkz_buf), 3); +#else + strcpy(sideabkz_buf, itoa36(f->no)); +#endif + return sideabkz_buf; +} + +static void message_faction(battle * b, faction * f, struct message *m) +{ + region *r = b->region; + + if (f->battles == NULL || f->battles->r != r) { + struct bmsg *bm = (struct bmsg *)calloc(1, sizeof(struct bmsg)); + bm->next = f->battles; + f->battles = bm; + bm->r = r; + } + add_message(&f->battles->msgs, m); +} + +int armedmen(const unit * u, bool siege_weapons) +{ + item *itm; + int n = 0; + if (!(urace(u)->flags & RCF_NOWEAPONS)) { + if (effskill(u, SK_WEAPONLESS) >= 1) { + /* kann ohne waffen bewachen: fuer drachen */ + n = u->number; + } + else { + /* alle Waffen werden gezaehlt, und dann wird auf die Anzahl + * Personen minimiert */ + for (itm = u->items; itm; itm = itm->next) { + const weapon_type *wtype = resource2weapon(itm->type->rtype); + if (wtype == NULL || (!siege_weapons && (wtype->flags & WTF_SIEGE))) + continue; + if (effskill(u, wtype->skill) >= 1) + n += itm->number; + /* if (effskill(u, wtype->skill) >= wtype->minskill) n += itm->number; */ + if (n > u->number) + break; + } + n = _min(n, u->number); + } + } + return n; +} + +void message_all(battle * b, message * m) +{ + bfaction *bf; + plane *p = rplane(b->region); + watcher *w; + + for (bf = b->factions; bf; bf = bf->next) { + message_faction(b, bf->faction, m); + } + if (p) + for (w = p->watchers; w; w = w->next) { + for (bf = b->factions; bf; bf = bf->next) + if (bf->faction == w->faction) + break; + if (bf == NULL) + message_faction(b, w->faction, m); + } +} + +static void fbattlerecord(battle * b, faction * f, const char *s) +{ + message *m = msg_message("battle_msg", "string", s); + message_faction(b, f, m); + msg_release(m); +} + +/* being an enemy or a friend is (and must always be!) symmetrical */ +#define enemy_i(as, di) (as->relations[di]&E_ENEMY) +#define friendly_i(as, di) (as->relations[di]&E_FRIEND) +#define enemy(as, ds) (as->relations[ds->index]&E_ENEMY) +#define friendly(as, ds) (as->relations[ds->index]&E_FRIEND) + +static bool set_enemy(side * as, side * ds, bool attacking) +{ + int i; + for (i = 0; i != MAXSIDES; ++i) { + if (ds->enemies[i] == NULL) + ds->enemies[i] = as; + if (ds->enemies[i] == as) + break; + } + for (i = 0; i != MAXSIDES; ++i) { + if (as->enemies[i] == NULL) + as->enemies[i] = ds; + if (as->enemies[i] == ds) + break; + } + assert(i != MAXSIDES); + if (attacking) + as->relations[ds->index] |= E_ATTACKING; + if ((ds->relations[as->index] & E_ENEMY) == 0) { + /* enemy-relation are always symmetrical */ + assert((as->relations[ds->index] & (E_ENEMY | E_FRIEND)) == 0); + ds->relations[as->index] |= E_ENEMY; + as->relations[ds->index] |= E_ENEMY; + return true; + } + return false; +} + +static void set_friendly(side * as, side * ds) +{ + assert((as->relations[ds->index] & E_ENEMY) == 0); + ds->relations[as->index] |= E_FRIEND; + as->relations[ds->index] |= E_FRIEND; +} + +static int allysfm(const side * s, const faction * f, int mode) +{ + if (s->faction == f) + return mode; + if (s->group) { + return alliedgroup(s->battle->plane, s->faction, f, s->group->allies, mode); + } + return alliedfaction(s->battle->plane, s->faction, f, mode); +} + +static int allysf(const side * s, const faction * f) +{ + return allysfm(s, f, HELP_FIGHT); +} + +static int dead_fighters(const fighter * df) +{ + return df->unit->number - df->alive - df->run.number; +} + +fighter *select_corpse(battle * b, fighter * af) +/* Wählt eine Leiche aus, der af hilft. casualties ist die Anzahl der + * Toten auf allen Seiten (im Array). Wenn af == NULL, wird die + * Parteizugehörigkeit ignoriert, und irgendeine Leiche genommen. + * + * Untote werden nicht ausgewählt (casualties, not dead) */ +{ + int si, di, maxcasualties = 0; + fighter *df; + side *s; + + for (si = 0; si != b->nsides; ++si) { + side *s = b->sides + si; + if (af == NULL || (!enemy_i(af->side, si) && allysf(af->side, s->faction))) { + maxcasualties += s->casualties; + } + } + di = rng_int() % maxcasualties; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + for (df = s->fighters; df; df = df->next) { + /* Geflohene haben auch 0 hp, dürfen hier aber nicht ausgewählt + * werden! */ + int dead = dead_fighters(df); + if (!playerrace(u_race(df->unit))) + continue; + + if (af && !helping(af->side, df->side)) + continue; + if (di < dead) { + return df; + } + di -= dead; + } + } + + return NULL; +} + +bool helping(const side * as, const side * ds) +{ + if (as->faction == ds->faction) + return true; + return (bool)(!enemy(as, ds) && allysf(as, ds->faction)); +} + +int statusrow(int status) +{ + switch (status) { + case ST_AGGRO: + case ST_FIGHT: + return FIGHT_ROW; + case ST_BEHIND: + case ST_CHICKEN: + return BEHIND_ROW; + case ST_AVOID: + return AVOID_ROW; + case ST_FLEE: + return FLEE_ROW; + default: + assert(!"unknown combatrow"); + } + return FIGHT_ROW; +} + +static double hpflee(int status) +/* if hp drop below this percentage, run away */ +{ + switch (status) { + case ST_AGGRO: + return 0.0; + case ST_FIGHT: + case ST_BEHIND: + return 0.2; + case ST_CHICKEN: + case ST_AVOID: + return 0.9; + case ST_FLEE: + return 1.0; + default: + assert(!"unknown combatrow"); + } + return 0.0; +} + +static int get_row(const side * s, int row, const side * vs) +{ + bool counted[MAXSIDES]; + int enemyfront = 0; + int line, result; + int retreat = 0; + int size[NUMROWS]; + int front = 0; + battle *b = s->battle; + + memset(counted, 0, sizeof(counted)); + memset(size, 0, sizeof(size)); + for (line = FIRST_ROW; line != NUMROWS; ++line) { + int si, sa_i; + /* how many enemies are there in the first row? */ + for (si = 0; s->enemies[si]; ++si) { + side *se = s->enemies[si]; + if (se->size[line] > 0) { + enemyfront += se->size[line]; + /* - s->nonblockers[line] (nicht, weil angreifer) */ + } + } + for (sa_i = 0; sa_i != b->nsides; ++sa_i) { + side *sa = b->sides + sa_i; + /* count people that like me, but don't like my enemy */ + if (friendly_i(s, sa_i) && enemy_i(vs, sa_i)) { + if (!counted[sa_i]) { + int i; + + for (i = 0; i != NUMROWS; ++i) { + size[i] += sa->size[i] - sa->nonblockers[i]; + } + counted[sa_i] = true; + } + } + } + if (enemyfront) + break; + } + if (enemyfront) { + for (line = FIRST_ROW; line != NUMROWS; ++line) { + front += size[line]; + if (!front || front < enemyfront / ROW_FACTOR) + ++retreat; + else if (front) + break; + } + } + + /* every entry in the size[] array means someone trying to defend us. + * 'retreat' is the number of rows falling. + */ + result = _max(FIRST_ROW, row - retreat); + + return result; +} + +int get_unitrow(const fighter * af, const side * vs) +{ + int row = statusrow(af->status); + if (vs == NULL) { + int i; + for (i = FIGHT_ROW; i != row; ++i) + if (af->side->size[i]) + break; + return FIGHT_ROW + (row - i); + } + else { + battle *b = vs->battle; + if (row != b->rowcache.row || b->alive != b->rowcache.alive + || af->side != b->rowcache.as || vs != b->rowcache.vs) { + b->rowcache.alive = b->alive; + b->rowcache.as = af->side; + b->rowcache.vs = vs; + b->rowcache.row = row; + b->rowcache.result = get_row(af->side, row, vs); + return b->rowcache.result; + } +#ifdef DEBUG_FAST /* validation code */ + { + int i = get_row(af->side, row, vs); + assert(i == b->rowcache.result); + } +#endif + return b->rowcache.result; + } +} + +static void reportcasualties(battle * b, fighter * fig, int dead) +{ + struct message *m; + region *r = NULL; + if (fig->alive == fig->unit->number) + return; + m = msg_message("casualties", "unit runto run alive fallen", + fig->unit, r, fig->run.number, fig->alive, dead); + message_all(b, m); + msg_release(m); +} + +static int +contest_classic(int skilldiff, const armor_type * ar, const armor_type * sh) +{ + int p, vw = BASE_CHANCE - TDIFF_CHANGE * skilldiff; + double mod = 1.0; + + if (ar != NULL) + mod *= (1 + ar->penalty); + if (sh != NULL) + mod *= (1 + sh->penalty); + vw = (int)(100 - ((100 - vw) * mod)); + + do { + p = rng_int() % 100; + vw -= p; + } while (vw >= 0 && p >= 90); + return (vw <= 0); +} + +/** new rule for Eressea 1.5 + * \param skilldiff - the attack skill with every modifier applied + */ +static int +contest_new(int skilldiff, const troop dt, const armor_type * ar, +const armor_type * sh) +{ + double tohit = 0.5 + skilldiff * 0.1; + if (tohit < 0.5) + tohit = 0.5; + if (chance(tohit)) { + int defense = effskill(dt.fighter->unit, SK_STAMINA); + double tosave = defense * 0.05; + return !chance(tosave); + } + return 0; +} + +static int +contest(int skdiff, const troop dt, const armor_type * ar, +const armor_type * sh) +{ + if (skill_formula == FORMULA_ORIG) { + return contest_classic(skdiff, ar, sh); + } + else { + return contest_new(skdiff, dt, ar, sh); + } +} + +static bool is_riding(const troop t) +{ + if (t.fighter->building != NULL) + return false; + if (t.fighter->horses + t.fighter->elvenhorses > t.index) + return true; + return false; +} + +static weapon *preferred_weapon(const troop t, bool attacking) +{ + weapon *missile = t.fighter->person[t.index].missile; + weapon *melee = t.fighter->person[t.index].melee; + if (attacking) { + if (melee == NULL || (missile && missile->attackskill > melee->attackskill)) { + return missile; + } + } + else { + if (melee == NULL || (missile + && missile->defenseskill > melee->defenseskill)) { + return missile; + } + } + return melee; +} + +static weapon *select_weapon(const troop t, bool attacking, + bool ismissile) + /* select the primary weapon for this trooper */ +{ + if (attacking) { + if (ismissile) { + /* from the back rows, have to use your missile weapon */ + return t.fighter->person[t.index].missile; + } + } + else { + if (!ismissile) { + /* have to use your melee weapon if it's melee */ + return t.fighter->person[t.index].melee; + } + } + return preferred_weapon(t, attacking); +} + +static bool i_canuse(const unit * u, const item_type * itype) +{ + if (itype->canuse) { + return itype->canuse(u, itype); + } + return true; +} + +static int +weapon_skill(const weapon_type * wtype, const unit * u, bool attacking) +/* the 'pure' skill when using this weapon to attack or defend. + * only undiscriminate modifiers (not affected by troops or enemies) + * are taken into account, e.g. no horses, magic, etc. */ +{ + int skill; + + if (wtype == NULL) { + skill = effskill(u, SK_WEAPONLESS); + if (skill <= 0) { + /* wenn kein waffenloser kampf, dann den rassen-defaultwert */ + if (u_race(u) == get_race(RC_ORC)) { + int sword = effskill(u, SK_MELEE); + int spear = effskill(u, SK_SPEAR); + skill = _max(sword, spear) - 3; + if (attacking) { + skill = _max(skill, u_race(u)->at_default); + } + else { + skill = _max(skill, u_race(u)->df_default); + } + } + else { + if (attacking) { + skill = u_race(u)->at_default; + } + else { + skill = u_race(u)->df_default; + } + } + } + else { + /* der rassen-defaultwert kann höher sein als der Talentwert von + * waffenloser kampf */ + if (attacking) { + if (skill < u_race(u)->at_default) + skill = u_race(u)->at_default; + } + else { + if (skill < u_race(u)->df_default) + skill = u_race(u)->df_default; + } + } + if (attacking) { + skill += u_race(u)->at_bonus; + if (fval(u->region->terrain, SEA_REGION) && u->ship) + skill += u->ship->type->at_bonus; + } + else { + skill += u_race(u)->df_bonus; + if (fval(u->region->terrain, SEA_REGION) && u->ship) + skill += u->ship->type->df_bonus; + } + } + else { + /* changed: if we own a weapon, we have at least a skill of 0 */ + if (!i_canuse(u, wtype->itype)) + return -1; + skill = effskill(u, wtype->skill); + if (skill < wtype->minskill) + skill = 0; + if (skill > 0) { + if (attacking) { + skill += u_race(u)->at_bonus; + } + else { + skill += u_race(u)->df_bonus; + } + } + if (attacking) { + skill += wtype->offmod; + } + else { + skill += wtype->defmod; + } + } + + return skill; +} + +static int CavalrySkill(void) +{ + static int skill = -1; + + if (skill < 0) { + skill = get_param_int(global.parameters, "rules.cavalry.skill", 2); + } + return skill; +} + +#define BONUS_SKILL 1 +#define BONUS_DAMAGE 2 +static int CavalryBonus(const unit * u, troop enemy, int type) +{ + static int mode = -1; + + if (mode < 0) { + mode = get_param_int(global.parameters, "rules.cavalry.mode", 1); + } + if (mode == 0) { + /* old rule, Eressea 1.0 compat */ + return (type == BONUS_SKILL) ? 2 : 0; + } + else { + /* new rule, chargers in Eressea 1.1 */ + int skl = effskill(u, SK_RIDING); + /* only half against trolls */ + if (skl > 0) { + if (type == BONUS_DAMAGE) { + int dmg = _min(skl, 8); + if (u_race(enemy.fighter->unit) == get_race(RC_TROLL)) { + dmg = dmg / 4; + } + else { + dmg = dmg / 2; + } + return dmg; + } + else { + skl = skl / 2; + return _min(skl, 4); + } + } + } + return 0; +} + +static int +weapon_effskill(troop t, troop enemy, const weapon * w, bool attacking, +bool missile) +/* effektiver Waffenskill während des Kampfes */ +{ + /* In dieser Runde alle die Modifier berechnen, die fig durch die + * Waffen bekommt. */ + fighter *tf = t.fighter; + unit *tu = t.fighter->unit; + int skill; + const weapon_type *wtype = w ? w->type : NULL; + + if (wtype == NULL) { + /* Ohne Waffe: Waffenlose Angriffe */ + skill = weapon_skill(NULL, tu, attacking); + } + else { + if (attacking) { + skill = w->attackskill; + } + else { + skill = w->defenseskill; + } + if (wtype->modifiers != NULL) { + /* Pferdebonus, Lanzenbonus, usw. */ + int m; + unsigned int flags = + WMF_SKILL | (attacking ? WMF_OFFENSIVE : WMF_DEFENSIVE); + + if (is_riding(t)) + flags |= WMF_RIDING; + else + flags |= WMF_WALKING; + if (is_riding(enemy)) + flags |= WMF_AGAINST_RIDING; + else + flags |= WMF_AGAINST_WALKING; + + for (m = 0; wtype->modifiers[m].value; ++m) { + if ((wtype->modifiers[m].flags & flags) == flags) { + race_list *rlist = wtype->modifiers[m].races; + if (rlist != NULL) { + while (rlist) { + if (rlist->data == u_race(tu)) + break; + rlist = rlist->next; + } + if (rlist == NULL) + continue; + } + skill += wtype->modifiers[m].value; + } + } + } + } + + /* Burgenbonus, Pferdebonus */ + if (is_riding(t) && (wtype == NULL || (fval(wtype, WTF_HORSEBONUS) + && !fval(wtype, WTF_MISSILE)))) { + skill += CavalryBonus(tu, enemy, BONUS_SKILL); + if (wtype) + skill = + skillmod(urace(tu)->attribs, tu, tu->region, wtype->skill, skill, + SMF_RIDING); + } + + if (t.index < tf->elvenhorses) { + /* Elfenpferde: Helfen dem Reiter, egal ob und welche Waffe. Das ist + * eleganter, und vor allem einfacher, sonst muß man noch ein + * WMF_ELVENHORSE einbauen. */ + skill += 2; + } + + if (skill > 0 && !attacking && missile) { + /* + * Wenn ich verteidige, und nicht direkt meinem Feind gegenüberstehe, + * halbiert sich mein Skill: (z.B. gegen Fernkämpfer. Nahkämpfer + * können mich eh nicht treffen) + */ + skill /= 2; + } + return skill; +} + +static const armor_type *select_armor(troop t, bool shield) +{ + unsigned int type = shield ? ATF_SHIELD : 0; + unit *u = t.fighter->unit; + const armor *a = t.fighter->armors; + int geschuetzt = 0; + + /* some monsters should not use armor (dragons in chainmail? ha!) */ + if (!(u_race(u)->battle_flags & BF_EQUIPMENT)) + return NULL; + + /* ... neither do werewolves */ + if (fval(u, UFL_WERE)) { + return NULL; + } + + for (; a; a = a->next) { + if ((a->atype->flags & ATF_SHIELD) == type) { + geschuetzt += a->count; + if (geschuetzt > t.index) { + /* unser Kandidat wird geschuetzt */ + return a->atype; + } + } + } + return NULL; +} + +/* Hier ist zu beachten, ob und wie sich Zauber und Artefakte, die + * Rüstungschutz geben, addieren. + * - Artefakt "trollbelt" gibt Rüstung +1 + * - Zauber Rindenhaut gibt Rüstung +3 + */ +static int trollbelts(const unit *u) { + const struct resource_type *belt = rt_find("trollbelt"); + return belt ? i_get(u->items, belt->itype) : 0; +} + +int select_magicarmor(troop t) +{ + unit *u = t.fighter->unit; + int ma = 0; + + if (trollbelts(u) > t.index) /* unser Kandidat wird geschuetzt */ + ma += 1; + + return ma; +} + +/* Sind side ds und Magier des meffect verbündet, dann return 1*/ +bool meffect_protection(battle * b, meffect * s, side * ds) +{ + if (!s->magician->alive) + return false; + if (s->duration <= 0) + return false; + if (enemy(s->magician->side, ds)) + return false; + if (allysf(s->magician->side, ds->faction)) + return true; + return false; +} + +/* Sind side as und Magier des meffect verfeindet, dann return 1*/ +bool meffect_blocked(battle * b, meffect * s, side * as) +{ + if (!s->magician->alive) + return false; + if (s->duration <= 0) + return false; + if (enemy(s->magician->side, as)) + return true; + return false; +} + +/* rmfighter wird schon im PRAECOMBAT gebraucht, da gibt es noch keine + * troops */ +void rmfighter(fighter * df, int i) +{ + side *ds = df->side; + + /* nicht mehr personen abziehen, als in der Einheit am Leben sind */ + assert(df->alive >= i); + assert(df->alive <= df->unit->number); + + /* erst ziehen wir die Anzahl der Personen von den Kämpfern in der + * Schlacht, dann von denen auf dieser Seite ab*/ + df->side->alive -= i; + df->side->battle->alive -= i; + + /* Dann die Kampfreihen aktualisieren */ + ds->size[SUM_ROW] -= i; + ds->size[statusrow(df->status)] -= i; + + /* Spezialwirkungen, z.B. Schattenritter */ + if (u_race(df->unit)->battle_flags & BF_NOBLOCK) { + ds->nonblockers[SUM_ROW] -= i; + ds->nonblockers[statusrow(df->status)] -= i; + } + + /* und die Einheit selbst aktualisieren */ + df->alive -= i; +} + +static void rmtroop(troop dt) +{ + fighter *df = dt.fighter; + + /* troop ist immer eine einzele Person */ + rmfighter(df, 1); + + assert(dt.index >= 0 && dt.index < df->unit->number); + df->person[dt.index] = df->person[df->alive - df->removed]; + if (df->removed) { + df->person[df->alive - df->removed] = df->person[df->alive]; + } + df->person[df->alive].hp = 0; +} + +void remove_troop(troop dt) +{ + fighter *df = dt.fighter; + struct person p = df->person[dt.index]; + battle *b = df->side->battle; + b->fast.alive = -1; /* invalidate cached value */ + b->rowcache.alive = -1; /* invalidate cached value */ + ++df->removed; + ++df->side->removed; + df->person[dt.index] = df->person[df->alive - df->removed]; + df->person[df->alive - df->removed] = p; +} + +void kill_troop(troop dt) +{ + fighter *df = dt.fighter; + unit *du = df->unit; + + rmtroop(dt); + if (!df->alive) { + char eqname[64]; + const struct equipment *eq; + if (u_race(du)->itemdrop) { + item *drops = u_race(du)->itemdrop(u_race(du), du->number - df->run.number); + + if (drops != NULL) { + i_merge(&du->items, &drops); + } + } + sprintf(eqname, "%s_spoils", u_race(du)->_name); + eq = get_equipment(eqname); + if (eq != NULL) { + equip_items(&du->items, eq); + } + } +} + +/** reduces the target's exp by an equivalent of n points learning + * 30 points = 1 week + */ +void drain_exp(struct unit *u, int n) +{ + skill_t sk = (skill_t)(rng_int() % MAXSKILLS); + skill_t ssk; + + ssk = sk; + + while (get_level(u, sk) == 0) { + sk++; + if (sk == MAXSKILLS) + sk = 0; + if (sk == ssk) { + sk = NOSKILL; + break; + } + } + if (sk != NOSKILL) { + skill *sv = unit_skill(u, sk); + while (n > 0) { + if (n >= 30 * u->number) { + reduce_skill(u, sv, 1); + n -= 30; + } + else { + if (rng_int() % (30 * u->number) < n) + reduce_skill(u, sv, 1); + n = 0; + } + } + } +} + +const char *rel_dam(int dam, int hp) +{ + double q = (double)dam / (double)hp; + + if (q > 0.75) { + return "eine klaffende Wunde"; + } + else if (q > 0.5) { + return "eine schwere Wunde"; + } + else if (q > 0.25) { + return "eine Wunde"; + } + return "eine kleine Wunde"; +} + +static void vampirism(troop at, int damage) +{ + static int vampire = -1; + if (vampire < 0) + vampire = get_param_int(global.parameters, "rules.combat.demon_vampire", 0); + if (vampire > 0) { + int gain = damage / vampire; + int chance = damage - vampire * gain; + if (chance > 0 && (rng_int() % vampire < chance)) + ++gain; + if (gain > 0) { + int maxhp = unit_max_hp(at.fighter->unit); + at.fighter->person[at.index].hp = + _min(gain + at.fighter->person[at.index].hp, maxhp); + } + } +} + +static int natural_armor(unit * du) +{ + static int *bonus = 0; + int an = u_race(du)->armor; + if (bonus == 0) { + bonus = calloc(sizeof(int), num_races); + } + if (bonus[u_race(du)->index] == 0) { + bonus[u_race(du)->index] = + get_param_int(u_race(du)->parameters, "armor.stamina", -1); + if (bonus[u_race(du)->index] == 0) + bonus[u_race(du)->index] = -1; + } + if (bonus[u_race(du)->index] > 0) { + int sk = effskill(du, SK_STAMINA); + sk /= bonus[u_race(du)->index]; + an += sk; + } + return an; +} + +bool +terminate(troop dt, troop at, int type, const char *damage, bool missile) +{ + item **pitm; + fighter *df = dt.fighter; + fighter *af = at.fighter; + unit *au = af->unit; + unit *du = df->unit; + battle *b = df->side->battle; + int heiltrank = 0; + static int rule_armor = -1; + + /* Schild */ + side *ds = df->side; + int hp; + + int ar = 0, an, am; + const armor_type *armor = select_armor(dt, true); + const armor_type *shield = select_armor(dt, false); + + const weapon_type *dwtype = NULL; + const weapon_type *awtype = NULL; + const weapon *weapon; + + int rda, sk = 0, sd; + bool magic = false; + int da = dice_rand(damage); + + assert(du->number > 0); + ++at.fighter->hits; + + switch (type) { + case AT_STANDARD: + weapon = select_weapon(at, true, missile); + sk = weapon_effskill(at, dt, weapon, true, missile); + if (weapon) + awtype = weapon->type; + if (awtype && fval(awtype, WTF_MAGICAL)) + magic = true; + break; + case AT_NATURAL: + sk = weapon_effskill(at, dt, NULL, true, missile); + break; + case AT_SPELL: + case AT_COMBATSPELL: + magic = true; + break; + default: + break; + } + weapon = select_weapon(dt, false, true); /* missile=true to get the unmodified best weapon she has */ + sd = weapon_effskill(dt, at, weapon, false, false); + if (weapon != NULL) + dwtype = weapon->type; + + if (is_riding(at) && (awtype == NULL || (fval(awtype, WTF_HORSEBONUS) + && !fval(awtype, WTF_MISSILE)))) { + da += CavalryBonus(au, dt, BONUS_DAMAGE); + } + + if (armor) { + ar += armor->prot; + if (armor->projectile > 0 && chance(armor->projectile)) { + return false; + } + } + if (shield) { + ar += shield->prot; + if (shield->projectile > 0 && chance(shield->projectile)) { + return false; + } + } + + /* natürliche Rüstung */ + an = natural_armor(du); + + /* magische Rüstung durch Artefakte oder Sprüche */ + /* Momentan nur Trollgürtel und Werwolf-Eigenschaft */ + am = select_magicarmor(dt); + +#if CHANGED_CROSSBOWS + if (awtype && fval(awtype, WTF_ARMORPIERCING)) { + /* crossbows */ + ar /= 2; + an /= 2; + } +#endif + + if (rule_armor < 0) { + rule_armor = get_param_int(global.parameters, "rules.combat.nat_armor", 0); + } + if (rule_armor == 0) { + /* natürliche Rüstung ist halbkumulativ */ + if (ar > 0) { + ar += an / 2; + } + else { + ar = an; + } + } + else { + /* use the higher value, add half the other value */ + ar = (ar > an) ? (ar + an / 2) : (an + ar / 2); + } + ar += am; + + if (type != AT_COMBATSPELL && type != AT_SPELL) { + if (damage_rules & DAMAGE_CRITICAL) { + double kritchance = (sk * 3 - sd) / 200.0; + + kritchance = _max(kritchance, 0.005); + kritchance = _min(0.9, kritchance); + + while (chance(kritchance)) { + if (bdebug) { + fprintf(bdebug, "%s/%d lands a critical hit\n", unitid(au), at.index); + } + da += dice_rand(damage); + } + } + + da += rc_specialdamage(u_race(au), u_race(du), awtype); + + if (awtype != NULL && fval(awtype, WTF_MISSILE)) { + /* missile weapon bonus */ + if (damage_rules & DAMAGE_MISSILE_BONUS) { + da += af->person[at.index].damage_rear; + } + } + else if (awtype == NULL) { + /* skill bonus for unarmed combat */ + if (damage_rules & DAMAGE_UNARMED_BONUS) { + da += effskill(au, SK_WEAPONLESS); + } + } + else { + /* melee bonus */ + if (damage_rules & DAMAGE_MELEE_BONUS) { + da += af->person[at.index].damage; + } + } + + /* Skilldifferenzbonus */ + if (damage_rules & DAMAGE_SKILL_BONUS) { + da += _max(0, (sk - sd) / DAMAGE_QUOTIENT); + } + } + + if (magic) { + /* Magischer Schaden durch Spruch oder magische Waffe */ + double res = 1.0; + + /* magic_resistance gib x% Resistenzbonus zurück */ + res -= magic_resistance(du) * 3.0; + + if (u_race(du)->battle_flags & BF_EQUIPMENT) { +#ifdef TODO_RUNESWORD + /* Runenschwert gibt im Kampf 80% Resistenzbonus */ + if (dwp == WP_RUNESWORD) + res -= 0.80; +#endif + /* der Effekt von Laen steigt nicht linear */ + if (armor && fval(armor, ATF_LAEN)) + res *= (1 - armor->magres); + if (shield && fval(shield, ATF_LAEN)) + res *= (1 - shield->magres); + if (dwtype) + res *= (1 - dwtype->magres); + } + + if (res > 0) { + da = (int)(_max(da * res, 0)); + } + /* gegen Magie wirkt nur natürliche und magische Rüstung */ + ar = an + am; + } + + rda = _max(da - ar, 0); + + if ((u_race(du)->battle_flags & BF_INV_NONMAGIC) && !magic) + rda = 0; + else { + int qi; + quicklist *ql; + unsigned int i = 0; + + if (u_race(du)->battle_flags & BF_RES_PIERCE) + i |= WTF_PIERCE; + if (u_race(du)->battle_flags & BF_RES_CUT) + i |= WTF_CUT; + if (u_race(du)->battle_flags & BF_RES_BASH) + i |= WTF_BLUNT; + + if (i && awtype && fval(awtype, i)) + rda /= 2; + + /* Schilde */ + for (qi = 0, ql = b->meffects; ql; ql_advance(&ql, &qi, 1)) { + meffect *me = (meffect *)ql_get(ql, qi); + if (meffect_protection(b, me, ds) != 0) { + assert(0 <= rda); /* rda sollte hier immer mindestens 0 sein */ + /* jeder Schaden wird um effect% reduziert bis der Schild duration + * Trefferpunkte aufgefangen hat */ + if (me->typ == SHIELD_REDUCE) { + hp = rda * (me->effect / 100); + rda -= hp; + me->duration -= hp; + } + /* gibt Rüstung +effect für duration Treffer */ + if (me->typ == SHIELD_ARMOR) { + rda = _max(rda - me->effect, 0); + me->duration--; + } + } + } + } + + assert(dt.index < du->number); + df->person[dt.index].hp -= rda; + if (u_race(au) == get_race(RC_DAEMON)) { + vampirism(at, rda); + } + + if (df->person[dt.index].hp > 0) { /* Hat überlebt */ + if (bdebug) { + fprintf(bdebug, "Damage %d, armor %d: %d -> %d HP\n", + da, ar, df->person[dt.index].hp + rda, df->person[dt.index].hp); + } + if (u_race(au) == get_race(RC_DAEMON)) { +#ifdef TODO_RUNESWORD + if (select_weapon(dt, 0, -1) == WP_RUNESWORD) + continue; +#endif + if (!(df->person[dt.index].flags & (FL_COURAGE | FL_DAZZLED))) { + df->person[dt.index].flags |= FL_DAZZLED; + df->person[dt.index].defence--; + } + } + df->person[dt.index].flags = (df->person[dt.index].flags & ~FL_SLEEPING); + return false; + } + + /* Sieben Leben */ + if (u_race(du) == get_race(RC_CAT) && (chance(1.0 / 7))) { + assert(dt.index >= 0 && dt.index < du->number); + df->person[dt.index].hp = unit_max_hp(du); + return false; + } + + /* Heiltrank schluerfen und hoffen */ + if (oldpotiontype[P_HEAL]) { + if (get_effect(du, oldpotiontype[P_HEAL]) > 0) { + change_effect(du, oldpotiontype[P_HEAL], -1); + heiltrank = 1; + } + else if (i_get(du->items, oldpotiontype[P_HEAL]->itype) > 0) { + i_change(&du->items, oldpotiontype[P_HEAL]->itype, -1); + change_effect(du, oldpotiontype[P_HEAL], 3); + heiltrank = 1; + } + if (heiltrank && (chance(0.50))) { + { + message *m = msg_message("battle::potionsave", "unit", du); + message_faction(b, du->faction, m); + msg_release(m); + } + assert(dt.index >= 0 && dt.index < du->number); + df->person[dt.index].hp = u_race(du)->hitpoints; + return false; + } + } + ++at.fighter->kills; + + if (bdebug) { + fprintf(bdebug, "Damage %d, armor %d, type %d: %d -> %d HP, tot.\n", + da, ar, type, df->person[dt.index].hp + rda, df->person[dt.index].hp); + } + for (pitm = &du->items; *pitm;) { + item *itm = *pitm; + const item_type *itype = itm->type; + if (!(itype->flags & ITF_CURSED) && dt.index < itm->number) { + /* 25% Grundchance, das ein Item kaputtgeht. */ + if (rng_int() % 4 < 1) { + i_change(pitm, itype, -1); + } + } + if (*pitm == itm) { + pitm = &itm->next; + } + } + kill_troop(dt); + + return true; +} + +static int +count_side(const side * s, const side * vs, int minrow, int maxrow, int select) +{ + fighter *fig; + int people = 0; + int unitrow[NUMROWS]; + + if (maxrow < FIGHT_ROW) + return 0; + if (select & SELECT_ADVANCE) { + memset(unitrow, -1, sizeof(unitrow)); + } + + for (fig = s->fighters; fig; fig = fig->next) { + if (fig->alive - fig->removed > 0) { + int row = statusrow(fig->status); + if (select & SELECT_ADVANCE) { + if (unitrow[row] == -1) { + unitrow[row] = get_unitrow(fig, vs); + } + row = unitrow[row]; + } + if (row >= minrow && row <= maxrow) { + people += fig->alive - fig->removed; + if (people > 0 && (select & SELECT_FIND)) + break; + } + } + } + return people; +} + +/* return the number of live allies warning: this function only considers +* troops that are still alive, not those that are still fighting although +* dead. */ +int +count_allies(const side * as, int minrow, int maxrow, int select, int allytype) +{ + battle *b = as->battle; + side *ds; + int count = 0; + + for (ds = b->sides; ds != b->sides + b->nsides; ++ds) { + if ((allytype == ALLY_ANY && helping(as, ds)) || (allytype == ALLY_SELF + && as->faction == ds->faction)) { + count += count_side(ds, NULL, minrow, maxrow, select); + if (count > 0 && (select & SELECT_FIND)) + break; + } + } + return count; +} + +static int +count_enemies_i(battle * b, const fighter * af, int minrow, int maxrow, +int select) +{ + side *es, *as = af->side; + int i = 0; + + for (es = b->sides; es != b->sides + b->nsides; ++es) { + if (as == NULL || enemy(es, as)) { + int offset = 0; + if (select & SELECT_DISTANCE) { + offset = get_unitrow(af, es) - FIGHT_ROW; + } + i += count_side(es, as, minrow - offset, maxrow - offset, select); + if (i > 0 && (select & SELECT_FIND)) + break; + } + } + return i; +} + +int +count_enemies(battle * b, const fighter * af, int minrow, int maxrow, +int select) +{ + int sr = statusrow(af->status); + side *as = af->side; + + if (b->alive == b->fast.alive && as == b->fast.side && sr == b->fast.status + && minrow == b->fast.minrow && maxrow == b->fast.maxrow) { + if (b->fast.enemies[select] >= 0) { +#ifdef DEBUG_FAST + int i = count_enemies_i(b, af, minrow, maxrow, select); + assert(i == b->fast.enemies[select]); +#endif + return b->fast.enemies[select]; + } + else if (select & SELECT_FIND) { + if (b->fast.enemies[select - SELECT_FIND] >= 0) { +#ifdef DEBUG_FAST + int i = count_enemies_i(b, af, minrow, maxrow, select); + assert((i > 0) == (b->fast.enemies[select - SELECT_FIND] > 0)); +#endif + return b->fast.enemies[select - SELECT_FIND]; + } + } + } + else if (select != SELECT_FIND || b->alive != b->fast.alive) { + b->fast.side = as; + b->fast.status = sr; + b->fast.minrow = minrow; + b->fast.alive = b->alive; + b->fast.maxrow = maxrow; + memset(b->fast.enemies, -1, sizeof(b->fast.enemies)); + } + if (maxrow >= FIRST_ROW) { + int i = count_enemies_i(b, af, minrow, maxrow, select); + b->fast.enemies[select] = i; + return i; + } + return 0; +} + +troop select_enemy(fighter * af, int minrow, int maxrow, int select) +{ + side *as = af->side; + battle *b = as->battle; + int si, selected; + int enemies; +#ifdef DEBUG_SELECT + troop result = no_troop; +#endif + if (u_race(af->unit)->flags & RCF_FLY) { + /* flying races ignore min- and maxrow and can attack anyone fighting + * them */ + minrow = FIGHT_ROW; + maxrow = BEHIND_ROW; + } + minrow = _max(minrow, FIGHT_ROW); + + enemies = count_enemies(b, af, minrow, maxrow, select); + + /* Niemand ist in der angegebenen Entfernung? */ + if (enemies <= 0) + return no_troop; + + selected = rng_int() % enemies; + for (si = 0; as->enemies[si]; ++si) { + side *ds = as->enemies[si]; + fighter *df; + int unitrow[NUMROWS]; + int offset = 0; + + if (select & SELECT_DISTANCE) + offset = get_unitrow(af, ds) - FIGHT_ROW; + + if (select & SELECT_ADVANCE) { + int ui; + for (ui = 0; ui != NUMROWS; ++ui) + unitrow[ui] = -1; + } + + for (df = ds->fighters; df; df = df->next) { + int dr; + + dr = statusrow(df->status); + if (select & SELECT_ADVANCE) { + if (unitrow[dr] < 0) { + unitrow[dr] = get_unitrow(df, as); + } + dr = unitrow[dr]; + } + + if (select & SELECT_DISTANCE) + dr += offset; + if (dr < minrow || dr > maxrow) + continue; + if (df->alive - df->removed > selected) { +#ifdef DEBUG_SELECT + if (result.fighter == NULL) { + result.index = selected; + result.fighter = df; + } +#else + troop dt; + dt.index = selected; + dt.fighter = df; + return dt; +#endif + } + selected -= (df->alive - df->removed); + enemies -= (df->alive - df->removed); + } + } + if (enemies != 0) { + log_error("select_enemies has a bug.\n"); + } +#ifdef DEBUG_SELECT + return result; +#else + assert(!selected); + return no_troop; +#endif +} + +static int get_tactics(const side * as, const side * ds) +{ + battle *b = as->battle; + side *stac; + int result = 0; + int defense = 0; + + if (b->max_tactics > 0) { + for (stac = b->sides; stac != b->sides + b->nsides; ++stac) { + if (stac->leader.value > result && helping(stac, as)) { + assert(ds == NULL || !helping(stac, ds)); + result = stac->leader.value; + } + if (ds && stac->leader.value > defense && helping(stac, ds)) { + assert(!helping(stac, as)); + defense = stac->leader.value; + } + } + } + return result - defense; +} + +static troop select_opponent(battle * b, troop at, int mindist, int maxdist) +{ + fighter *af = at.fighter; + troop dt; + + if (u_race(af->unit)->flags & RCF_FLY) { + /* flying races ignore min- and maxrow and can attack anyone fighting + * them */ + dt = select_enemy(at.fighter, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); + } + else { + mindist = _max(mindist, FIGHT_ROW); + dt = select_enemy(at.fighter, mindist, maxdist, SELECT_ADVANCE); + } + + if (b->turn == 0 && dt.fighter) { + int tactics_formula = -1; + + if (tactics_formula < 0) { + tactics_formula = + get_param_int(global.parameters, "rules.tactics.formula", 0); + } + if (tactics_formula == 1) { + int tactics = get_tactics(at.fighter->side, dt.fighter->side); + + /* percentage chance to get this attack */ + if (tactics > 0) { + double tacch = 0.1 * tactics; + if (fval(b->region->terrain, SEA_REGION)) { + ship *sh = at.fighter->unit->ship; + if (sh) + tacch *= sh->type->tac_bonus; + } + if (!chance(tacch)) { + dt.fighter = NULL; + } + } + else { + dt.fighter = NULL; + } + } + } + + return dt; +} + +quicklist *fighters(battle * b, const side * vs, int minrow, int maxrow, + int mask) +{ + side *s; + quicklist *fightervp = 0; + + assert(vs != NULL); + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + + if (mask == FS_ENEMY) { + if (!enemy(s, vs)) + continue; + } + else if (mask == FS_HELP) { + if (enemy(s, vs) || !allysf(s, vs->faction)) { + continue; + } + } + else { + assert(mask == (FS_HELP | FS_ENEMY) || !"invalid alliance state"); + } + for (fig = s->fighters; fig; fig = fig->next) { + int row = get_unitrow(fig, vs); + if (row >= minrow && row <= maxrow) { + ql_push(&fightervp, fig); + } + } + } + + return fightervp; +} + +static void report_failed_spell(struct battle * b, struct unit * mage, const struct spell *sp) +{ + message *m = msg_message("battle::spell_failed", "unit spell", mage, sp); + message_all(b, m); + msg_release(m); +} + +void do_combatmagic(battle * b, combatmagic_t was) +{ + side *s; + region *r = b->region; + castorder *co; + int level, rank, sl; + spellrank spellranks[MAX_SPELLRANK]; + + memset(spellranks, 0, sizeof(spellranks)); + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + for (fig = s->fighters; fig; fig = fig->next) { + unit *mage = fig->unit; + + if (fig->alive <= 0) + continue; /* fighter kann im Kampf getötet worden sein */ + + level = eff_skill(mage, SK_MAGIC, r); + if (level > 0) { + float power; + const spell *sp; + const struct locale *lang = mage->faction->locale; + order *ord; + + switch (was) { + case DO_PRECOMBATSPELL: + sp = get_combatspell(mage, 0); + sl = get_combatspelllevel(mage, 0); + break; + case DO_POSTCOMBATSPELL: + sp = get_combatspell(mage, 2); + sl = get_combatspelllevel(mage, 2); + break; + default: + /* Fehler! */ + return; + } + if (sp == NULL) + continue; + + ord = create_order(K_CAST, lang, "'%s'", spell_name(sp, lang)); + if (!cancast(mage, sp, 1, 1, ord)) { + free_order(ord); + continue; + } + + level = eff_spelllevel(mage, sp, level, 1); + if (sl > 0) + level = _min(sl, level); + if (level < 0) { + report_failed_spell(b, mage, sp); + free_order(ord); + continue; + } + + power = spellpower(r, mage, sp, level, ord); + free_order(ord); + if (power <= 0) { /* Effekt von Antimagie */ + report_failed_spell(b, mage, sp); + pay_spell(mage, sp, level, 1); + } + else if (fumble(r, mage, sp, level)) { + report_failed_spell(b, mage, sp); + pay_spell(mage, sp, level, 1); + } + else { + co = create_castorder(0, fig->unit, 0, sp, r, level, power, 0, 0, 0); + co->magician.fig = fig; + add_castorder(&spellranks[sp->rank], co); + } + } + } + } + for (rank = 0; rank < MAX_SPELLRANK; rank++) { + for (co = spellranks[rank].begin; co; co = co->next) { + fighter *fig = co->magician.fig; + const spell *sp = co->sp; + int level = co->level; + + if (!sp->cast) { + log_error("spell '%s' has no function.\n", sp->sname); + } + else { + level = sp->cast(co); + if (level > 0) { + pay_spell(fig->unit, sp, level, 1); + } + } + } + } + for (rank = 0; rank < MAX_SPELLRANK; rank++) { + free_castorders(spellranks[rank].begin); + } +} + +static int cast_combatspell(troop at, const spell * sp, int level, float force) +{ + castorder co; + + create_castorder(&co, at.fighter->unit, 0, sp, at.fighter->unit->region, level, force, 0, 0, 0); + co.magician.fig = at.fighter; + level = sp->cast(&co); + free_castorder(&co); + if (level > 0) { + pay_spell(at.fighter->unit, sp, level, 1); + } + return level; +} + +static void do_combatspell(troop at) +{ + const spell *sp; + fighter *fi = at.fighter; + unit *caster = fi->unit; + battle *b = fi->side->battle; + region *r = b->region; + quicklist *ql; + int level, qi; + float power; + int fumblechance = 0; + order *ord; + int sl; + const struct locale *lang = caster->faction->locale; + + sp = get_combatspell(caster, 1); + if (sp == NULL) { + fi->magic = 0; /* Hat keinen Kampfzauber, kämpft nichtmagisch weiter */ + return; + } + ord = create_order(K_CAST, lang, "'%s'", spell_name(sp, lang)); + if (!cancast(caster, sp, 1, 1, ord)) { + fi->magic = 0; /* Kann nicht mehr Zaubern, kämpft nichtmagisch weiter */ + return; + } + + level = eff_spelllevel(caster, sp, fi->magic, 1); + if ((sl = get_combatspelllevel(caster, 1)) > 0) + level = _min(level, sl); + + if (fumble(r, caster, sp, level)) { + report_failed_spell(b, caster, sp); + pay_spell(caster, sp, level, 1); + return; + } + + for (qi = 0, ql = b->meffects; ql; ql_advance(&ql, &qi, 1)) { + meffect *mblock = (meffect *)ql_get(ql, qi); + if (mblock->typ == SHIELD_BLOCK) { + if (meffect_blocked(b, mblock, fi->side) != 0) { + fumblechance += mblock->duration; + mblock->duration -= mblock->effect; + } + } + } + + /* Antimagie die Fehlschlag erhöht */ + if (rng_int() % 100 < fumblechance) { + report_failed_spell(b, caster, sp); + pay_spell(caster, sp, level, 1); + free_order(ord); + return; + } + power = spellpower(r, caster, sp, level, ord); + free_order(ord); + if (power <= 0) { /* Effekt von Antimagie */ + report_failed_spell(b, caster, sp); + pay_spell(caster, sp, level, 1); + return; + } + + if (!sp->cast) { + log_error("spell '%s' has no function.\n", sp->sname); + } + else { + level = cast_combatspell(at, sp, level, power); + } +} + +/* Sonderattacken: Monster patzern nicht und zahlen auch keine + * Spruchkosten. Da die Spruchstärke direkt durch den Level bestimmt + * wird, wirkt auch keine Antimagie (wird sonst in spellpower + * gemacht) */ + +static void do_extra_spell(troop at, const att * a) +{ + const spell *sp = a->data.sp; + + if (sp->cast == NULL) { + log_error("spell '%s' has no function.\n", sp->sname); + } + else { + int level = a->level; + assert(a->level > 0); + cast_combatspell(at, sp, level, level * MagicPower()); + } +} + +int skilldiff(troop at, troop dt, int dist) +{ + fighter *af = at.fighter, *df = dt.fighter; + unit *au = af->unit, *du = df->unit; + int is_protected = 0, skdiff = 0; + weapon *awp = select_weapon(at, true, dist > 1); + + skdiff += af->person[at.index].attack; + skdiff -= df->person[dt.index].defence; + + if (df->person[dt.index].flags & FL_SLEEPING) + skdiff += 2; + + /* Effekte durch Rassen */ + if (awp != NULL && u_race(au) == get_race(RC_HALFLING) && dragonrace(u_race(du))) { + skdiff += 5; + } + + if (u_race(au) == get_race(RC_GOBLIN)) { + static int goblin_bonus = -1; + if (goblin_bonus < 0) + goblin_bonus = + get_param_int(global.parameters, "rules.combat.goblinbonus", 10); + if (af->side->size[SUM_ROW] >= df->side->size[SUM_ROW] * goblin_bonus) { + skdiff += 1; + } + } + + if (df->building) { + bool init = false; + static const curse_type *strongwall_ct, *magicwalls_ct; + if (!init) { + strongwall_ct = ct_find("strongwall"); + magicwalls_ct = ct_find("magicwalls"); + init = true; + } + if (df->building->type->protection) { + int beff = df->building->type->protection(df->building, du); + if (beff) { + skdiff -= beff; + is_protected = 2; + } + } + if (strongwall_ct) { + curse *c = get_curse(df->building->attribs, strongwall_ct); + if (curse_active(c)) { + /* wirkt auf alle Gebäude */ + skdiff -= curse_geteffect_int(c); + is_protected = 2; + } + } + if (magicwalls_ct + && curse_active(get_curse(df->building->attribs, magicwalls_ct))) { + /* Verdoppelt Burgenbonus */ + skdiff -= buildingeffsize(df->building, false); + } + } + /* Goblin-Verteidigung + * ist direkt in der Rassentabelle als df_default + */ + + /* Effekte der Waffen */ + skdiff += weapon_effskill(at, dt, awp, true, dist > 1); + if (awp && fval(awp->type, WTF_MISSILE)) { + skdiff -= is_protected; + if (awp->type->modifiers) { + int w; + for (w = 0; awp->type->modifiers[w].value != 0; ++w) { + if (awp->type->modifiers[w].flags & WMF_MISSILE_TARGET) { + /* skill decreases by targeting difficulty (bow -2, catapult -4) */ + skdiff -= awp->type->modifiers[w].value; + break; + } + } + } + } + if (skill_formula == FORMULA_ORIG) { + weapon *dwp = select_weapon(dt, false, dist > 1); + skdiff -= weapon_effskill(dt, at, dwp, false, dist > 1); + } + return skdiff; +} + +static int setreload(troop at) +{ + fighter *af = at.fighter; + const weapon_type *wtype = af->person[at.index].missile->type; + if (wtype->reload == 0) + return 0; + return af->person[at.index].reload = wtype->reload; +} + +int getreload(troop at) +{ + return at.fighter->person[at.index].reload; +} + +static void +debug_hit(troop at, const weapon * awp, troop dt, const weapon * dwp, +int skdiff, int dist, bool success) +{ + fprintf(bdebug, "%.4s/%d [%6s/%d] %s %.4s/%d [%6s/%d] with %d, distance %d\n", + unitid(at.fighter->unit), at.index, + LOC(default_locale, awp ? resourcename(awp->type->itype->rtype, + 0) : "unarmed"), weapon_effskill(at, dt, awp, true, dist > 1), + success ? "hits" : "misses", unitid(dt.fighter->unit), dt.index, + LOC(default_locale, dwp ? resourcename(dwp->type->itype->rtype, + 0) : "unarmed"), weapon_effskill(dt, at, dwp, false, dist > 1), skdiff, + dist); +} + +int hits(troop at, troop dt, weapon * awp) +{ + fighter *af = at.fighter, *df = dt.fighter; + const armor_type *armor, *shield = 0; + int skdiff = 0; + int dist = get_unitrow(af, df->side) + get_unitrow(df, af->side) - 1; + weapon *dwp = select_weapon(dt, false, dist > 1); + + if (!df->alive) + return 0; + if (getreload(at)) + return 0; + if (dist > 1 && (awp == NULL || !fval(awp->type, WTF_MISSILE))) + return 0; + + /* mark this person as hit. */ + df->person[dt.index].flags |= FL_HIT; + + if (af->person[at.index].flags & FL_STUNNED) { + af->person[at.index].flags &= ~FL_STUNNED; + return 0; + } + if ((af->person[at.index].flags & FL_TIRED && rng_int() % 100 < 50) + || (af->person[at.index].flags & FL_SLEEPING)) + return 0; + + /* effect of sp_reeling_arrows combatspell */ + if (af->side->battle->reelarrow && awp && fval(awp->type, WTF_MISSILE) + && rng_double() < 0.5) { + return 0; + } + + skdiff = skilldiff(at, dt, dist); + /* Verteidiger bekommt eine Rüstung */ + armor = select_armor(dt, true); + if (dwp == NULL || (dwp->type->flags & WTF_USESHIELD)) { + shield = select_armor(dt, false); + } + if (contest(skdiff, dt, armor, shield)) { + if (bdebug) { + debug_hit(at, awp, dt, dwp, skdiff, dist, true); + } + return 1; + } + if (bdebug) { + debug_hit(at, awp, dt, dwp, skdiff, dist, false); + } + return 0; +} + +void dazzle(battle * b, troop * td) +{ + /* Nicht kumulativ ! */ + if (td->fighter->person[td->index].flags & FL_DAZZLED) + return; + +#ifdef TODO_RUNESWORD + if (td->fighter->weapon[WP_RUNESWORD].count > td->index) { + return; + } +#endif + if (td->fighter->person[td->index].flags & FL_COURAGE) { + return; + } + + if (td->fighter->person[td->index].flags & FL_DAZZLED) { + return; + } + + td->fighter->person[td->index].flags |= FL_DAZZLED; + td->fighter->person[td->index].defence--; +} + +/* TODO: Gebäude/Schiffe sollten auch zerstörbar sein. Schwierig im Kampf, + * besonders bei Schiffen. */ + +void damage_building(battle * b, building * bldg, int damage_abs) +{ + bldg->size = _max(1, bldg->size - damage_abs); + + /* Wenn Burg, dann gucken, ob die Leute alle noch in das Gebäude passen. */ + + if (bldg->type->protection) { + side *s; + + bldg->sizeleft = bldg->size; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + for (fig = s->fighters; fig; fig = fig->next) { + if (fig->building == bldg) { + if (bldg->sizeleft >= fig->unit->number) { + fig->building = bldg; + bldg->sizeleft -= fig->unit->number; + } + else { + fig->building = NULL; + } + } + } + } + } +} + +static int attacks_per_round(troop t) +{ + return t.fighter->person[t.index].speed; +} + +static void make_heroes(battle * b) +{ + side *s; + static int hero_speed = 0; + if (hero_speed == 0) { + hero_speed = get_param_int(global.parameters, "rules.combat.herospeed", 10); + } + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + for (fig = s->fighters; fig; fig = fig->next) { + unit *u = fig->unit; + if (fval(u, UFL_HERO)) { + int i; + if (!playerrace(u_race(u))) { + log_error("Hero %s is a %s.\n", unitname(u), u_race(u)->_name); + } + for (i = 0; i != u->number; ++i) { + fig->person[i].speed += (hero_speed - 1); + } + } + } + } +} + +static void attack(battle * b, troop ta, const att * a, int numattack) +{ + fighter *af = ta.fighter; + troop td; + unit *au = af->unit; + + switch (a->type) { + case AT_COMBATSPELL: + /* Magier versuchen immer erstmal zu zaubern, erst wenn das + * fehlschlägt, wird af->magic == 0 und der Magier kämpft + * konventionell weiter */ + if (numattack == 0 && af->magic > 0) { + /* wenn der magier in die potenzielle Reichweite von Attacken des + * Feindes kommt, beginnt er auch bei einem Status von KAEMPFE NICHT, + * Kampfzauber zu schleudern: */ + if (count_enemies(b, af, melee_range[0], missile_range[1], + SELECT_ADVANCE | SELECT_DISTANCE | SELECT_FIND)) { + do_combatspell(ta); + } + } + break; + case AT_STANDARD: /* Waffen, mag. Gegenstände, Kampfzauber */ + if (numattack > 0 || af->magic <= 0) { + weapon *wp = ta.fighter->person[ta.index].missile; + int melee = + count_enemies(b, af, melee_range[0], melee_range[1], + SELECT_ADVANCE | SELECT_DISTANCE | SELECT_FIND); + if (melee) + wp = preferred_weapon(ta, true); + /* Sonderbehandlungen */ + + if (getreload(ta)) { + ta.fighter->person[ta.index].reload--; + } + else { + bool standard_attack = true; + bool reload = false; + /* spezialattacken der waffe nur, wenn erste attacke in der runde. + * sonst helden mit feuerschwertern zu mächtig */ + if (numattack == 0 && wp && wp->type->attack) { + int dead = 0; + standard_attack = wp->type->attack(&ta, wp->type, &dead); + if (!standard_attack) + reload = true; + af->catmsg += dead; + if (!standard_attack && af->person[ta.index].last_action < b->turn) { + af->person[ta.index].last_action = b->turn; + } + } + if (standard_attack) { + bool missile = false; + if (wp && fval(wp->type, WTF_MISSILE)) + missile = true; + if (missile) { + td = select_opponent(b, ta, missile_range[0], missile_range[1]); + } + else { + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + } + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + reload = true; + if (hits(ta, td, wp)) { + const char *d; + if (wp == NULL) + d = u_race(au)->def_damage; + else if (is_riding(ta)) + d = wp->type->damage[1]; + else + d = wp->type->damage[0]; + terminate(td, ta, a->type, d, missile); + } + } + if (reload && wp && wp->type->reload && !getreload(ta)) { + int i = setreload(ta); + if (bdebug) { + fprintf(bdebug, "%s/%d reloading %d turns\n", unitid(au), + ta.index, i); + } + } + } + } + break; + case AT_SPELL: /* Extra-Sprüche. Kampfzauber in AT_COMBATSPELL! */ + do_extra_spell(ta, a); + break; + case AT_NATURAL: + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + if (hits(ta, td, NULL)) { + terminate(td, ta, a->type, a->data.dice, false); + } + break; + case AT_DRAIN_ST: + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + if (hits(ta, td, NULL)) { + int c = dice_rand(a->data.dice); + while (c > 0) { + if (rng_int() % 2) { + td.fighter->person[td.index].attack -= 1; + } + else { + td.fighter->person[td.index].defence -= 1; + } + c--; + } + } + break; + case AT_DRAIN_EXP: + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + if (hits(ta, td, NULL)) { + drain_exp(td.fighter->unit, dice_rand(a->data.dice)); + } + break; + case AT_DAZZLE: + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + if (hits(ta, td, NULL)) { + dazzle(b, &td); + } + break; + case AT_STRUCTURAL: + td = select_opponent(b, ta, melee_range[0], melee_range[1]); + if (!td.fighter) + return; + if (ta.fighter->person[ta.index].last_action < b->turn) { + ta.fighter->person[ta.index].last_action = b->turn; + } + if (td.fighter->unit->ship) { + /* FIXME should use damage_ship here? */ + td.fighter->unit->ship->damage += + DAMAGE_SCALE * dice_rand(a->data.dice); + } + else if (td.fighter->unit->building) { + damage_building(b, td.fighter->unit->building, dice_rand(a->data.dice)); + } + } +} + +void do_attack(fighter * af) +{ + troop ta; + unit *au = af->unit; + side *side = af->side; + battle *b = side->battle; + + ta.fighter = af; + + assert(au && au->number); + /* Da das Zuschlagen auf Einheiten und nicht auf den einzelnen + * Kämpfern beruht, darf die Reihenfolge und Größe der Einheit keine + * Rolle spielen, Das tut sie nur dann, wenn jeder, der am Anfang der + * Runde lebte, auch zuschlagen darf. Ansonsten ist der, der zufällig + * mit einer großen Einheit zuerst drankommt, extrem bevorteilt. */ + ta.index = af->fighting; + + while (ta.index--) { + /* Wir suchen eine beliebige Feind-Einheit aus. An der können + * wir feststellen, ob noch jemand da ist. */ + int apr, attacks = attacks_per_round(ta); + if (!count_enemies(b, af, FIGHT_ROW, LAST_ROW, SELECT_FIND)) + break; + + for (apr = 0; apr != attacks; ++apr) { + int a; + for (a = 0; a != 10 && u_race(au)->attack[a].type != AT_NONE; ++a) { + if (apr > 0) { + /* Wenn die Waffe nachladen muss, oder es sich nicht um einen + * Waffen-Angriff handelt, dann gilt der Speed nicht. */ + if (u_race(au)->attack[a].type != AT_STANDARD) + continue; + else { + weapon *wp = preferred_weapon(ta, true); + if (wp != NULL && wp->type->reload) + continue; + } + } + attack(b, ta, &(u_race(au)->attack[a]), apr); + } + } + } + /* Der letzte Katapultschütze setzt die + * Ladezeit neu und generiert die Meldung. */ + if (af->catmsg >= 0) { + struct message *m = + msg_message("battle::killed", "unit dead", au, af->catmsg); + message_all(b, m); + msg_release(m); + af->catmsg = -1; + } +} + +void do_regenerate(fighter * af) +{ + troop ta; + unit *au = af->unit; + + ta.fighter = af; + ta.index = af->fighting; + + while (ta.index--) { + af->person[ta.index].hp += effskill(au, SK_STAMINA); + af->person[ta.index].hp = _min(unit_max_hp(au), af->person[ta.index].hp); + } +} + +static void add_tactics(tactics * ta, fighter * fig, int value) +{ + if (value == 0 || value < ta->value) + return; + if (value > ta->value) { + ql_free(ta->fighters); + ta->fighters = 0; + } + ql_push(&ta->fighters, fig); + ql_push(&fig->side->battle->leaders, fig); + ta->value = value; +} + +static double horsebonus(const unit * u) +{ + const item_type *it_horse, *it_elvenhorse, *it_charger; + int n1 = 0, n2 = 0, n3 = 0; + item *itm; + int skl = eff_skill(u, SK_RIDING, u->region); + const resource_type *rtype; + + if (skl < 1) return 0.0; + + it_horse = ((rtype = get_resourcetype(R_HORSE)) != NULL) ? rtype->itype : 0; + it_elvenhorse = ((rtype = get_resourcetype(R_UNICORN)) != NULL) ? rtype->itype : 0; + it_charger = ((rtype = get_resourcetype(R_CHARGER)) != NULL) ? rtype->itype : 0; + + for (itm = u->items; itm; itm = itm->next) { + if (itm->type->flags & ITF_ANIMAL) { + if (itm->type == it_elvenhorse) + n3 += itm->number; + else if (itm->type == it_charger) + n2 += itm->number; + else if (itm->type == it_horse) + n1 += itm->number; + } + } + if (skl >= 5 && n3 >= u->number) + return 0.30; + if (skl >= 3 && n2 + n3 >= u->number) + return 0.20; + if (skl >= 1 && n1 + n2 + n3 >= u->number) + return 0.10; + return 0.0F; +} + +double fleechance(unit * u) +{ + double c = 0.20; /* Fluchtwahrscheinlichkeit in % */ + region *r = u->region; + attrib *a = a_find(u->attribs, &at_fleechance); + /* Einheit u versucht, dem Getümmel zu entkommen */ + + c += (eff_skill(u, SK_STEALTH, r) * 0.05); + c += horsebonus(u); + + if (u_race(u) == get_race(RC_HALFLING)) { + c += 0.20; + c = _min(c, 0.90); + } + else { + c = _min(c, 0.75); + } + + if (a != NULL) + c += a->data.flt; + + return c; +} + +/** add a new army to the conflict + * beware: armies need to be added _at the beginning_ of the list because + * otherwise join_allies() will get into trouble */ +side *make_side(battle * b, const faction * f, const group * g, + unsigned int flags, const faction * stealthfaction) +{ + side *s1 = b->sides + b->nsides; + bfaction *bf; + + if (fval(b->region->terrain, SEA_REGION)) { + /* every fight in an ocean is short */ + flags |= SIDE_HASGUARDS; + } + else { + unit *u; + for (u = b->region->units; u; u = u->next) { + if (is_guard(u, HELP_ALL)) { + if (alliedunit(u, f, HELP_GUARD)) { + flags |= SIDE_HASGUARDS; + break; + } + } + } + } + + s1->battle = b; + s1->group = g; + s1->flags = flags; + s1->stealthfaction = stealthfaction; + for (bf = b->factions; bf; bf = bf->next) { + faction *f2 = bf->faction; + + if (f2 == f) { + s1->bf = bf; + s1->faction = f2; + s1->index = b->nsides++; + s1->nextF = bf->sides; + bf->sides = s1; + assert(b->nsides <= MAXSIDES); + break; + } + } + assert(bf); + return s1; +} + +troop select_ally(fighter * af, int minrow, int maxrow, int allytype) +{ + side *as = af->side; + battle *b = as->battle; + side *ds; + int allies = count_allies(as, minrow, maxrow, SELECT_ADVANCE, allytype); + + if (!allies) { + return no_troop; + } + allies = rng_int() % allies; + + for (ds = b->sides; ds != b->sides + b->nsides; ++ds) { + if ((allytype == ALLY_ANY && helping(as, ds)) || (allytype == ALLY_SELF + && as->faction == ds->faction)) { + fighter *df; + for (df = ds->fighters; df; df = df->next) { + int dr = get_unitrow(df, NULL); + if (dr >= minrow && dr <= maxrow) { + if (df->alive - df->removed > allies) { + troop dt; + assert(allies >= 0); + dt.index = allies; + dt.fighter = df; + return dt; + } + allies -= df->alive; + } + } + } + } + assert(!"we should never have gotten here"); + return no_troop; +} + +static int loot_quota(const unit * src, const unit * dst, + const item_type * type, int n) +{ + static float divisor = -1; + if (dst && src && src->faction != dst->faction) { + if (divisor < 0) { + divisor = get_param_flt(global.parameters, "rules.items.loot_divisor", 1); + assert(divisor == 0 || divisor >= 1); + } + if (divisor >= 1) { + double r = n / divisor; + int x = (int)r; + + r = r - x; + if (chance(r)) + ++x; + + return x; + } + } + return n; +} + +static void loot_items(fighter * corpse) +{ + unit *u = corpse->unit; + item *itm = u->items; + battle *b = corpse->side->battle; + int dead = dead_fighters(corpse); + + if (dead <= 0) + return; + + while (itm) { + float lootfactor = dead / (float)u->number; /* only loot the dead! */ + int maxloot = (int)(itm->number * lootfactor); + if (maxloot > 0) { + int i = _min(10, maxloot); + for (; i != 0; --i) { + int loot = maxloot / i; + + if (loot > 0) { + fighter *fig = NULL; + int looting = 0; + int maxrow = 0; + /* mustloot: we absolutely, positively must have somebody loot this thing */ + int mustloot = itm->type->flags & (ITF_CURSED | ITF_NOTLOST); + + itm->number -= loot; + maxloot -= loot; + + if (is_monsters(u->faction) && (loot_rules & LOOT_MONSTERS)) { + looting = 1; + } + else if (loot_rules & LOOT_OTHERS) { + looting = 1; + } + else if (loot_rules & LOOT_SELF) { + looting = 2; + } + if (looting) { + if (mustloot) { + maxrow = LAST_ROW; + } + else if (loot_rules & LOOT_KEEPLOOT) { + int lootchance = 50 + b->keeploot; + if (rng_int() % 100 < lootchance) { + maxrow = BEHIND_ROW; + } + } + else { + maxrow = LAST_ROW; + } + } + if (maxrow > 0) { + if (looting == 1) { + /* enemies get dibs */ + fig = select_enemy(corpse, FIGHT_ROW, maxrow, 0).fighter; + } + if (!fig) { + /* self and allies get second pick */ + fig = select_ally(corpse, FIGHT_ROW, LAST_ROW, ALLY_SELF).fighter; + } + } + + if (fig) { + int trueloot = + mustloot ? loot : loot_quota(corpse->unit, fig->unit, itm->type, + loot); + if (trueloot > 0) { + item *l = fig->loot; + while (l && l->type != itm->type) + l = l->next; + if (!l) { + l = calloc(sizeof(item), 1); + l->next = fig->loot; + fig->loot = l; + l->type = itm->type; + } + l->number += trueloot; + } + } + } + } + } + itm = itm->next; + } +} + +static bool seematrix(const faction * f, const side * s) +{ + if (f == s->faction) + return true; + if (s->flags & SIDE_STEALTH) + return false; + return true; +} + +static double PopulationDamage(void) +{ + static double value = -1.0; + if (value < 0) { + int damage = + get_param_int(global.parameters, "rules.combat.populationdamage", + BATTLE_KILLS_PEASANTS); + value = damage / 100.0; + } + return value; +} + +static void battle_effects(battle * b, int dead_players) +{ + region *r = b->region; + int dead_peasants = + _min(rpeasants(r), (int)(dead_players * PopulationDamage())); + if (dead_peasants) { + deathcounts(r, dead_peasants + dead_players); + chaoscounts(r, dead_peasants / 2); + rsetpeasants(r, rpeasants(r) - dead_peasants); + } +} + +static void reorder_fleeing(region * r) +{ + unit **usrc = &r->units; + unit **udst = &r->units; + unit *ufirst = NULL; + unit *u; + + for (; *udst; udst = &u->next) { + u = *udst; + } + + for (u = *usrc; u != ufirst; u = *usrc) { + if (u->next && fval(u, UFL_FLEEING)) { + *usrc = u->next; + *udst = u; + udst = &u->next; + if (!ufirst) + ufirst = u; + } + else { + usrc = &u->next; + } + } + *udst = NULL; +} + +static void aftermath(battle * b) +{ + region *r = b->region; + ship *sh; + side *s; + int dead_players = 0; + bfaction *bf; + bool ships_damaged = (bool)(b->turn + (b->has_tactics_turn ? 1 : 0) > 2); /* only used for ship damage! */ + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *df; + s->dead = 0; + + for (df = s->fighters; df; df = df->next) { + unit *du = df->unit; + int dead = dead_fighters(df); + + /* tote insgesamt: */ + s->dead += dead; + /* Tote, die wiederbelebt werde koennen: */ + if (playerrace(u_race(df->unit))) { + s->casualties += dead; + } + if (df->hits + df->kills) { + struct message *m = + msg_message("killsandhits", "unit hits kills", du, df->hits, + df->kills); + message_faction(b, du->faction, m); + msg_release(m); + } + } + } + + /* POSTCOMBAT */ + do_combatmagic(b, DO_POSTCOMBATSPELL); + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + int snumber = 0; + fighter *df; + bool relevant = false; /* Kampf relevant für diese Partei? */ + if (!fval(s, SIDE_HASGUARDS)) { + relevant = true; + } + s->flee = 0; + + for (df = s->fighters; df; df = df->next) { + unit *du = df->unit; + int dead = dead_fighters(df); + int sum_hp = 0; + int n; + int flags = 0; + + for (n = 0; n != df->alive; ++n) { + if (df->person[n].hp > 0) { + sum_hp += df->person[n].hp; + } + } + snumber += du->number; + if (relevant) { + flags = UFL_LONGACTION | UFL_NOTMOVING; + if (du->status == ST_FLEE) { + flags -= UFL_NOTMOVING; + } + } + if (df->alive == 0) { + flags |= UFL_DEAD; + } + if (flags) { + fset(du, flags); + } + if (sum_hp + df->run.hp < du->hp) { + /* someone on the ship got damaged, damage the ship */ + ship *sh = du->ship ? du->ship : leftship(du); + if (sh) + fset(sh, SF_DAMAGED); + } + + if (df->alive == du->number) { + du->hp = sum_hp; + continue; /* nichts passiert */ + } + else if (df->run.hp) { + if (df->alive == 0) { + /* Report the casualties */ + reportcasualties(b, df, dead); + + /* Zuerst dürfen die Feinde plündern, die mitgenommenen Items + * stehen in fig->run.items. Dann werden die Fliehenden auf + * die leere (tote) alte Einheit gemapt */ + if (!fval(df, FIG_NOLOOT)) { + loot_items(df); + } + scale_number(du, df->run.number); + du->hp = df->run.hp; + setguard(du, GUARD_NONE); + /* must leave ships or buildings, or a stealthy hobbit + * can hold castles indefinitely */ + if (!fval(r->terrain, SEA_REGION)) { + leave(du, true); /* even region owners have to flee */ + } + fset(du, UFL_FLEEING); + } + else { + /* nur teilweise geflohene Einheiten mergen sich wieder */ + df->alive += df->run.number; + s->size[0] += df->run.number; + s->size[statusrow(df->status)] += df->run.number; + s->alive += df->run.number; + sum_hp += df->run.hp; + df->run.number = 0; + df->run.hp = 0; + /* df->run.region = NULL; */ + + reportcasualties(b, df, dead); + + scale_number(du, df->alive); + du->hp = sum_hp; + } + } + else { + if (df->alive == 0) { + /* alle sind tot, niemand geflohen. Einheit auflösen */ + df->run.number = 0; + df->run.hp = 0; + + /* Report the casualties */ + reportcasualties(b, df, dead); + + /* Distribute Loot */ + loot_items(df); + + setguard(du, GUARD_NONE); + scale_number(du, 0); + } + else { + df->run.number = 0; + df->run.hp = 0; + + reportcasualties(b, df, dead); + + scale_number(du, df->alive); + du->hp = sum_hp; + } + } + s->flee += df->run.number; + + if (playerrace(u_race(du))) { + /* tote im kampf werden zu regionsuntoten: + * for each of them, a peasant will die as well */ + dead_players += dead; + } + if (du->hp < du->number) { + log_error("%s has less hitpoints (%u) than people (%u)\n", itoa36(du->no), du->hp, du->number); + du->hp = du->number; + } + } + s->alive += s->healed; + assert(snumber == s->flee + s->alive + s->dead); + } + + battle_effects(b, dead_players); + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + message *seen = msg_message("battle::army_report", + "index abbrev dead fled survived", + army_index(s), sideabkz(s, false), s->dead, s->flee, s->alive); + message *unseen = msg_message("battle::army_report", + "index abbrev dead fled survived", + army_index(s), "-?-", s->dead, s->flee, s->alive); + + for (bf = b->factions; bf; bf = bf->next) { + faction *f = bf->faction; + message *m = seematrix(f, s) ? seen : unseen; + + message_faction(b, f, m); + } + + msg_release(seen); + msg_release(unseen); + } + + /* Wir benutzen drifted, um uns zu merken, ob ein Schiff + * schonmal Schaden genommen hat. (moved und drifted + * sollten in flags überführt werden */ + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *df; + + for (df = s->fighters; df; df = df->next) { + unit *du = df->unit; + item *l; + + /* Beute verteilen */ + for (l = df->loot; l; l = l->next) { + const item_type *itype = l->type; + message *m = + msg_message("battle_loot", "unit amount item", du, l->number, + itype->rtype); + message_faction(b, du->faction, m); + msg_release(m); + i_change(&du->items, itype, l->number); + } + + /* Wenn sich die Einheit auf einem Schiff befindet, wird + * dieses Schiff beschädigt. Andernfalls ein Schiff, welches + * evt. zuvor verlassen wurde. */ + if (ships_damaged) { + if (du->ship) + sh = du->ship; + else + sh = leftship(du); + + if (sh && fval(sh, SF_DAMAGED)) { + int n = b->turn - 2; + if (n > 0) { + float dmg = + get_param_flt(global.parameters, "rules.ship.damage.battleround", + 0.05F); + damage_ship(sh, dmg * n); + freset(sh, SF_DAMAGED); + } + } + } + } + } + + if (ships_damaged) { + ship **sp = &r->ships; + + while (*sp) { + ship *sh = *sp; + freset(sh, SF_DAMAGED); + if (sh->damage >= sh->size * DAMAGE_SCALE) { + remove_ship(sp, sh); + } + if (*sp == sh) + sp = &sh->next; + } + } + + reorder_fleeing(r); + + if (bdebug) { + fprintf(bdebug, "The battle lasted %d turns, %s and %s.\n", + b->turn, + b->has_tactics_turn ? "had a tactic turn" : "had no tactic turn", + ships_damaged ? "was relevant" : "was not relevant."); + } +} + +static void battle_punit(unit * u, battle * b) +{ + bfaction *bf; + strlist *S, *x; + + for (bf = b->factions; bf; bf = bf->next) { + faction *f = bf->faction; + + S = 0; + spunit(&S, f, u, 4, see_battle); + for (x = S; x; x = x->next) { + fbattlerecord(b, f, x->s); + if (bdebug && u->faction == f) { + fputs(x->s, bdebug); + fputc('\n', bdebug); + } + } + if (S) + freestrlist(S); + } +} + +static void print_fighters(battle * b, const side * s) +{ + fighter *df; + int row; + + for (row = 1; row != NUMROWS; ++row) { + message *m = NULL; + + for (df = s->fighters; df; df = df->next) { + unit *du = df->unit; + int thisrow = statusrow(df->unit->status); + + if (row == thisrow) { + if (m == NULL) { + m = msg_message("battle::row_header", "row", row); + message_all(b, m); + } + battle_punit(du, b); + } + } + if (m != NULL) + msg_release(m); + } +} + +bool is_attacker(const fighter * fig) +{ + return fval(fig, FIG_ATTACKER) != 0; +} + +static void set_attacker(fighter * fig) +{ + fset(fig, FIG_ATTACKER); +} + +static void print_header(battle * b) +{ + bfaction *bf; + char zText[32 * MAXSIDES]; + + for (bf = b->factions; bf; bf = bf->next) { + message *m; + faction *f = bf->faction; + const char *lastf = NULL; + bool first = false; + side *s; + char *bufp = zText; + size_t size = sizeof(zText) - 1; + int bytes; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *df; + for (df = s->fighters; df; df = df->next) { + if (is_attacker(df)) { + if (first) { + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + if (lastf) { + bytes = (int)strlcpy(bufp, (const char *)lastf, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + first = true; + } + if (seematrix(f, s)) + lastf = sidename(s); + else + lastf = LOC(f->locale, "unknown_faction_dative"); + break; + } + } + } + if (first) { + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, (const char *)LOC(f->locale, "and"), size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + bytes = (int)strlcpy(bufp, " ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + if (lastf) { + bytes = (int)strlcpy(bufp, (const char *)lastf, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + + m = msg_message("battle::starters", "factions", zText); + message_faction(b, f, m); + msg_release(m); + } +} + +static void print_stats(battle * b) +{ + side *s2; + side *s; + int i = 0; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + bfaction *bf; + + ++i; + + for (bf = b->factions; bf; bf = bf->next) { + faction *f = bf->faction; + const char *loc_army = LOC(f->locale, "battle_army"); + char *bufp; + const char *header; + size_t rsize, size; + int komma; + const char *sname = + seematrix(f, s) ? sidename(s) : LOC(f->locale, "unknown_faction"); + message *msg; + char buf[1024]; + + message_faction(b, f, msg_separator); + + msg = msg_message("battle_army", "index name", army_index(s), sname); + message_faction(b, f, msg); + msg_release(msg); + + bufp = buf; + size = sizeof(buf); + komma = 0; + header = LOC(f->locale, "battle_opponents"); + + for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { + if (enemy(s2, s)) { + const char *abbrev = seematrix(f, s2) ? sideabkz(s2, false) : "-?-"; + rsize = slprintf(bufp, size, "%s %s %d(%s)", + komma++ ? "," : (const char *)header, loc_army, army_index(s2), + abbrev); + if (rsize > size) + rsize = size - 1; + size -= rsize; + bufp += rsize; + } + } + if (komma) + fbattlerecord(b, f, buf); + + bufp = buf; + size = sizeof(buf); + komma = 0; + header = LOC(f->locale, "battle_helpers"); + + for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { + if (friendly(s2, s)) { + const char *abbrev = seematrix(f, s2) ? sideabkz(s2, false) : "-?-"; + rsize = slprintf(bufp, size, "%s %s %d(%s)", + komma++ ? "," : (const char *)header, loc_army, army_index(s2), + abbrev); + if (rsize > size) + rsize = size - 1; + size -= rsize; + bufp += rsize; + } + } + if (komma) + fbattlerecord(b, f, buf); + + bufp = buf; + size = sizeof(buf); + komma = 0; + header = LOC(f->locale, "battle_attack"); + + for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { + if (s->relations[s2->index] & E_ATTACKING) { + const char *abbrev = seematrix(f, s2) ? sideabkz(s2, false) : "-?-"; + rsize = + slprintf(bufp, size, "%s %s %d(%s)", + komma++ ? "," : (const char *)header, loc_army, army_index(s2), + abbrev); + if (rsize > size) + rsize = size - 1; + size -= rsize; + bufp += rsize; + } + } + if (komma) + fbattlerecord(b, f, buf); + } + + if (bdebug && s->faction) { + if (f_get_alliance(s->faction)) { + fprintf(bdebug, "##### %s (%s/%d)\n", s->faction->name, + itoa36(s->faction->no), + s->faction->alliance ? s->faction->alliance->id : 0); + } + else { + fprintf(bdebug, "##### %s (%s)\n", s->faction->name, + itoa36(s->faction->no)); + } + } + print_fighters(b, s); + } + + message_all(b, msg_separator); + + /* Besten Taktiker ermitteln */ + + b->max_tactics = 0; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (!ql_empty(s->leader.fighters)) { + b->max_tactics = _max(b->max_tactics, s->leader.value); + } + } + + if (b->max_tactics > 0) { + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (s->leader.value == b->max_tactics) { + quicklist *ql; + int qi; + + for (qi = 0, ql = s->leader.fighters; ql; ql_advance(&ql, &qi, 1)) { + fighter *tf = (fighter *)ql_get(ql, qi); + unit *u = tf->unit; + message *m = NULL; + if (!is_attacker(tf)) { + m = msg_message("battle::tactics_lost", "unit", u); + } + else { + m = msg_message("battle::tactics_won", "unit", u); + } + message_all(b, m); + msg_release(m); + } + } + } + } +} + +static int weapon_weight(const weapon * w, bool missile) +{ + if (missile == i2b(fval(w->type, WTF_MISSILE))) { + return w->attackskill + w->defenseskill; + } + return 0; +} + +side * get_side(battle * b, const struct unit * u) +{ + side * s; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (s->faction == u->faction) { + fighter * fig; + for (fig = s->fighters; fig; fig = fig->next) { + if (fig->unit == u) { + return s; + } + } + } + } + return 0; +} + +side * find_side(battle * b, const faction * f, const group * g, int flags, const faction * stealthfaction) +{ + side * s; + static int rule_anon_battle = -1; + + if (rule_anon_battle < 0) { + rule_anon_battle = get_param_int(global.parameters, "rules.stealth.anon_battle", 1); + } + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (s->faction == f && s->group == g) { + int s1flags = flags | SIDE_HASGUARDS; + int s2flags = s->flags | SIDE_HASGUARDS; + if (rule_anon_battle && s->stealthfaction != stealthfaction) { + continue; + } + if (s1flags == s2flags) { + return s; + } + } + } + return 0; +} + +fighter *make_fighter(battle * b, unit * u, side * s1, bool attack) +{ +#define WMAX 20 + weapon weapons[WMAX]; + int owp[WMAX]; + int dwp[WMAX]; + int w = 0; + region *r = b->region; + item *itm; + fighter *fig = NULL; + int h, i, tactics = eff_skill(u, SK_TACTICS, r); + int berserk; + int strongmen; + int speeded = 0, speed = 1; + bool pr_aid = false; + int rest; + const group *g = NULL; + const attrib *a = a_find(u->attribs, &at_otherfaction); + const faction *stealthfaction = a ? get_otherfaction(a) : NULL; + unsigned int flags = 0; + + assert(u->number); + if (fval(u, UFL_ANON_FACTION) != 0) + flags |= SIDE_STEALTH; + if (!(AllianceAuto() & HELP_FIGHT) && fval(u, UFL_GROUP)) { + const attrib *agroup = a_find(u->attribs, &at_group); + if (agroup != NULL) + g = (const group *)agroup->data.v; + } + + /* Illusionen und Zauber kaempfen nicht */ + if (fval(u_race(u), RCF_ILLUSIONARY) || idle(u->faction) || u->number == 0) { + return NULL; + } + if (s1 == NULL) { + s1 = find_side(b, u->faction, g, flags, stealthfaction); + /* aliances are moved out of make_fighter and will be handled later */ + if (!s1) { + s1 = make_side(b, u->faction, g, flags, stealthfaction); + } + else if (!stealthfaction) { + s1->stealthfaction = NULL; + } + /* Zu diesem Zeitpunkt ist attacked noch 0, da die Einheit für noch + * keinen Kampf ausgewählt wurde (sonst würde ein fighter existieren) */ + } + fig = (struct fighter*)calloc(1, sizeof(struct fighter)); + + fig->next = s1->fighters; + s1->fighters = fig; + + fig->unit = u; + /* In einer Burg muß man a) nicht Angreifer sein, und b) drin sein, und + * c) noch Platz finden. d) menschanähnlich sein */ + if (attack) { + set_attacker(fig); + } + else { + building *bld = u->building; + if (bld && bld->sizeleft >= u->number && playerrace(u_race(u))) { + fig->building = bld; + fig->building->sizeleft -= u->number; + } + } + fig->status = u->status; + fig->side = s1; + fig->alive = u->number; + fig->side->alive += u->number; + fig->side->battle->alive += u->number; + fig->catmsg = -1; + + /* Freigeben nicht vergessen! */ + fig->person = (struct person*)calloc(fig->alive, sizeof(struct person)); + + h = u->hp / u->number; + assert(h); + rest = u->hp % u->number; + + /* Effekte von Sprüchen */ + + { + static const curse_type *speed_ct; + speed_ct = ct_find("speed"); + if (speed_ct) { + curse *c = get_curse(u->attribs, speed_ct); + if (c) { + speeded = get_cursedmen(u, c); + speed = curse_geteffect_int(c); + } + } + } + + /* Effekte von Alchemie */ + berserk = get_effect(u, oldpotiontype[P_BERSERK]); + /* change_effect wird in ageing gemacht */ + + /* Effekte von Artefakten */ + strongmen = _min(fig->unit->number, trollbelts(u)); + + /* Hitpoints, Attack- und Defence-Boni für alle Personen */ + for (i = 0; i < fig->alive; i++) { + assert(i < fig->unit->number); + fig->person[i].hp = h; + if (i < rest) + fig->person[i].hp++; + + if (i < speeded) + fig->person[i].speed = speed; + else + fig->person[i].speed = 1; + + if (i < berserk) { + fig->person[i].attack++; + } + /* Leute mit einem Aid-Prayer bekommen +1 auf fast alles. */ + if (pr_aid) { + fig->person[i].attack++; + fig->person[i].defence++; + fig->person[i].damage++; + fig->person[i].damage_rear++; + fig->person[i].flags |= FL_COURAGE; + } + /* Leute mit Kraftzauber machen +2 Schaden im Nahkampf. */ + if (i < strongmen) { + fig->person[i].damage += 2; + } + } + + /* Für alle Waffengattungen wird bestimmt, wie viele der Personen mit + * ihr kämpfen könnten, und was ihr Wert darin ist. */ + if (u_race(u)->battle_flags & BF_EQUIPMENT) { + int oi = 0, di = 0; + for (itm = u->items; itm && w != WMAX; itm = itm->next) { + const weapon_type *wtype = resource2weapon(itm->type->rtype); + if (wtype == NULL || itm->number == 0) + continue; + weapons[w].attackskill = weapon_skill(wtype, u, true); + weapons[w].defenseskill = weapon_skill(wtype, u, false); + if (weapons[w].attackskill >= 0 || weapons[w].defenseskill >= 0) { + weapons[w].type = wtype; + weapons[w].used = 0; + weapons[w].count = itm->number; + ++w; + } + assert(w != WMAX); + } + fig->weapons = (weapon *)calloc(sizeof(weapon), w + 1); + memcpy(fig->weapons, weapons, w * sizeof(weapon)); + + for (i = 0; i != w; ++i) { + int j, o = 0, d = 0; + for (j = 0; j != i; ++j) { + if (weapon_weight(fig->weapons + j, + true) >= weapon_weight(fig->weapons + i, true)) + ++d; + if (weapon_weight(fig->weapons + j, + false) >= weapon_weight(fig->weapons + i, false)) + ++o; + } + for (j = i + 1; j != w; ++j) { + if (weapon_weight(fig->weapons + j, + true) > weapon_weight(fig->weapons + i, true)) + ++d; + if (weapon_weight(fig->weapons + j, + false) > weapon_weight(fig->weapons + i, false)) + ++o; + } + owp[o] = i; + dwp[d] = i; + } + /* jetzt enthalten owp und dwp eine absteigend schlechter werdende Liste der Waffen + * oi and di are the current index to the sorted owp/dwp arrays + * owp, dwp contain indices to the figther::weapons array */ + + /* hand out melee weapons: */ + for (i = 0; i != fig->alive; ++i) { + int wpless = weapon_skill(NULL, u, true); + while (oi != w + && (fig->weapons[owp[oi]].used == fig->weapons[owp[oi]].count + || fval(fig->weapons[owp[oi]].type, WTF_MISSILE))) { + ++oi; + } + if (oi == w) + break; /* no more weapons available */ + if (weapon_weight(fig->weapons + owp[oi], false) <= wpless) { + continue; /* we fight better with bare hands */ + } + fig->person[i].melee = &fig->weapons[owp[oi]]; + ++fig->weapons[owp[oi]].used; + } + /* hand out missile weapons (from back to front, in case of mixed troops). */ + for (di = 0, i = fig->alive; i-- != 0;) { + while (di != w + && (fig->weapons[dwp[di]].used == fig->weapons[dwp[di]].count + || !fval(fig->weapons[dwp[di]].type, WTF_MISSILE))) { + ++di; + } + if (di == w) + break; /* no more weapons available */ + if (weapon_weight(fig->weapons + dwp[di], true) > 0) { + fig->person[i].missile = &fig->weapons[dwp[di]]; + ++fig->weapons[dwp[di]].used; + } + } + } + + s1->size[statusrow(fig->status)] += u->number; + s1->size[SUM_ROW] += u->number; + if (u_race(u)->battle_flags & BF_NOBLOCK) { + s1->nonblockers[statusrow(fig->status)] += u->number; + } + + if (u_race(fig->unit)->flags & RCF_HORSE) { + fig->horses = fig->unit->number; + fig->elvenhorses = 0; + } + else { + const resource_type *rt_horse = 0; + const resource_type *rt_elvenhorse = 0; + rt_elvenhorse = get_resourcetype(R_UNICORN); + rt_horse = get_resourcetype(R_CHARGER); + if (!rt_horse) { + rt_horse = get_resourcetype(R_HORSE); + } + fig->horses = rt_horse ? i_get(u->items, rt_horse->itype) : 0; + fig->elvenhorses = rt_elvenhorse ? i_get(u->items, rt_elvenhorse->itype) : 0; + } + + if (u_race(u)->battle_flags & BF_EQUIPMENT) { + for (itm = u->items; itm; itm = itm->next) { + if (itm->type->rtype->atype) { + if (i_canuse(u, itm->type)) { + struct armor *adata = (struct armor *)malloc(sizeof(armor)), **aptr; + adata->atype = itm->type->rtype->atype; + adata->count = itm->number; + for (aptr = &fig->armors; *aptr; aptr = &(*aptr)->next) { + if (adata->atype->prot > (*aptr)->atype->prot) + break; + } + adata->next = *aptr; + *aptr = adata; + } + } + } + } + + /* Jetzt muß noch geschaut werden, wo die Einheit die jeweils besten + * Werte hat, das kommt aber erst irgendwo später. Ich entscheide + * wärend des Kampfes, welche ich nehme, je nach Gegner. Deswegen auch + * keine addierten boni. */ + + /* Zuerst mal die Spezialbehandlung gewisser Sonderfälle. */ + fig->magic = eff_skill(u, SK_MAGIC, r); + + if (fig->horses) { + if (!fval(r->terrain, CAVALRY_REGION) || r_isforest(r) + || eff_skill(u, SK_RIDING, r) < CavalrySkill() + || u_race(u) == get_race(RC_TROLL) || fval(u, UFL_WERE)) + fig->horses = 0; + } + + if (fig->elvenhorses) { + if (eff_skill(u, SK_RIDING, r) < 5 || u_race(u) == get_race(RC_TROLL) + || fval(u, UFL_WERE)) + fig->elvenhorses = 0; + } + + /* Schauen, wie gut wir in Taktik sind. */ + if (tactics > 0 && u_race(u) == get_race(RC_INSECT)) + tactics -= 1 - (int)log10(fig->side->size[SUM_ROW]); +#ifdef TACTICS_MODIFIER + if (tactics > 0 && statusrow(fig->status) == FIGHT_ROW) + tactics += TACTICS_MODIFIER; + if (tactics > 0 && statusrow(fig->status) > BEHIND_ROW) { + tactics -= TACTICS_MODIFIER; + } +#endif + + if (tactics > 0) { + int bonus = 0; + + for (i = 0; i < fig->alive; i++) { + int p_bonus = 0; + int rnd; + + do { + rnd = rng_int() % 100; + if (rnd >= 40 && rnd <= 69) + p_bonus += 1; + else if (rnd <= 89) + p_bonus += 2; + else + p_bonus += 3; + } while (rnd >= 97); + bonus = _max(p_bonus, bonus); + } + tactics += bonus; + } + + add_tactics(&fig->side->leader, fig, tactics); + ++b->nfighters; + return fig; +} + +fighter * get_fighter(battle * b, const struct unit * u) +{ + side * s; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + if (s->faction == u->faction) { + for (fig = s->fighters; fig; fig = fig->next) { + if (fig->unit == u) { + return fig; + } + } + } + } + return 0; +} + +static int join_battle(battle * b, unit * u, bool attack, fighter ** cp) +{ + side *s; + fighter *c = NULL; + + if (!attack) { + attrib *a = a_find(u->attribs, &at_fleechance); + if (a != NULL) { + if (rng_double() <= a->data.flt) { + *cp = NULL; + return false; + } + } + } + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + if (s->faction == u->faction) { + for (fig = s->fighters; fig; fig = fig->next) { + if (fig->unit == u) { + c = fig; + if (attack) { + set_attacker(fig); + } + break; + } + } + } + } + if (!c) { + *cp = make_fighter(b, u, NULL, attack); + return *cp != NULL; + } + *cp = c; + return false; +} + +static const char *simplename(region * r) +{ + int i; + static char name[17]; + const char *cp = rname(r, default_locale); + for (i = 0; *cp && i != 16; ++i, ++cp) { + int c = *(unsigned char *)cp; + while (c && !isalpha(c) && !isxspace(c)) { + ++cp; + c = *(unsigned char *)cp; + } + if (isxspace(c)) + name[i] = '_'; + else + name[i] = *cp; + if (c == 0) + break; + } + name[i] = 0; + return name; +} + +battle *make_battle(region * r) +{ + battle *b = (battle *)calloc(1, sizeof(battle)); + unit *u; + bfaction *bf; + building * bld; + static int max_fac_no = 0; /* need this only once */ + + /* Alle Mann raus aus der Burg! */ + for (bld = r->buildings; bld != NULL; bld = bld->next) + bld->sizeleft = bld->size; + + if (battledebug) { + char zText[MAX_PATH]; + char zFilename[MAX_PATH]; + sprintf(zText, "%s/battles", basepath()); + _mkdir(zText); + sprintf(zFilename, "%s/battle-%d-%s.log", zText, obs_count, simplename(r)); + bdebug = fopen(zFilename, "w"); + if (!bdebug) + log_error("battles cannot be debugged\n"); + else { + const unsigned char utf8_bom[4] = { 0xef, 0xbb, 0xbf, 0 }; + fwrite(utf8_bom, 1, 3, bdebug); + fprintf(bdebug, "In %s findet ein Kampf statt:\n", rname(r, + default_locale)); + } + obs_count++; + } + + b->region = r; + b->plane = getplane(r); + /* Finde alle Parteien, die den Kampf beobachten können: */ + for (u = r->units; u; u = u->next) { + if (u->number > 0) { + if (!fval(u->faction, FFL_MARK)) { + fset(u->faction, FFL_MARK); + for (bf = b->factions; bf; bf = bf->next) { + if (bf->faction == u->faction) + break; + } + if (!bf) { + bf = (bfaction *)calloc(sizeof(bfaction), 1); + ++b->nfactions; + bf->faction = u->faction; + bf->next = b->factions; + b->factions = bf; + } + } + } + } + + for (bf = b->factions; bf; bf = bf->next) { + faction *f = bf->faction; + max_fac_no = _max(max_fac_no, f->no); + freset(f, FFL_MARK); + } + return b; +} + +static void free_side(side * si) +{ + ql_free(si->leader.fighters); +} + +static void free_fighter(fighter * fig) +{ + while (fig->loot) { + i_free(i_remove(&fig->loot, fig->loot)); + } + while (fig->armors) { + armor *a = fig->armors; + fig->armors = a->next; + free(a); + } + free(fig->person); + free(fig->weapons); + +} + +static void free_battle(battle * b) +{ + int max_fac_no = 0; + + if (bdebug) { + fclose(bdebug); + } + + while (b->factions) { + bfaction *bf = b->factions; + faction *f = bf->faction; + b->factions = bf->next; + max_fac_no = _max(max_fac_no, f->no); + free(bf); + } + + ql_free(b->leaders); + ql_foreach(b->meffects, free); + ql_free(b->meffects); + + battle_free(b); +} + +static int *get_alive(side * s) +{ +#if 0 + static int alive[NUMROWS]; + fighter *fig; + memset(alive, 0, NUMROWS * sizeof(int)); + for (fig = s->fighters; fig; fig = fig->next) { + if (fig->alive > 0) { + int row = statusrow(fig); + alive[row] += fig->alive; + } + } + return alive; +#endif + return s->size; +} + +static int battle_report(battle * b) +{ + side *s, *s2; + bool cont = false; + bool komma; + bfaction *bf; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (s->alive - s->removed > 0) { + for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { + if (s2->alive - s2->removed > 0 && enemy(s, s2)) { + cont = true; + break; + } + } + if (cont) + break; + } + } + + if (verbosity > 0) + log_printf(stdout, " %d", b->turn); + fflush(stdout); + + for (bf = b->factions; bf; bf = bf->next) { + faction *fac = bf->faction; + char buf[32 * MAXSIDES]; + char *bufp = buf; + int bytes; + size_t size = sizeof(buf) - 1; + message *m; + + message_faction(b, fac, msg_separator); + + if (cont) + m = msg_message("battle::lineup", "turn", b->turn); + else + m = msg_message("battle::after", ""); + message_faction(b, fac, m); + msg_release(m); + + komma = false; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + if (s->alive) { + int r, k = 0, *alive = get_alive(s); + int l = FIGHT_ROW; + const char *abbrev = seematrix(fac, s) ? sideabkz(s, false) : "-?-"; + const char *loc_army = LOC(fac->locale, "battle_army"); + char buffer[32]; + + if (komma) { + bytes = (int)strlcpy(bufp, ", ", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + slprintf(buffer, sizeof(buffer), "%s %2d(%s): ", + loc_army, army_index(s), abbrev); + + bytes = (int)strlcpy(bufp, buffer, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + for (r = FIGHT_ROW; r != NUMROWS; ++r) { + if (alive[r]) { + if (l != FIGHT_ROW) { + bytes = (int)strlcpy(bufp, "+", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + while (k--) { + bytes = (int)strlcpy(bufp, "0+", size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + } + sprintf(buffer, "%d", alive[r]); + + bytes = (int)strlcpy(bufp, buffer, size); + if (wrptr(&bufp, &size, bytes) != 0) + WARN_STATIC_BUFFER(); + + k = 0; + l = r + 1; + } + else + ++k; + } + + komma = true; + } + } + *bufp = 0; + fbattlerecord(b, fac, buf); + } + return cont; +} + +static void join_allies(battle * b) +{ + region *r = b->region; + unit *u; + side *s, *s_end = b->sides + b->nsides; + /* make_side might be adding a new faction, but it adds them to the end + * of the list, so we're safe in our iteration here if we remember the end + * up front. */ + for (u = r->units; u; u = u->next) { + /* Was ist mit Schiffen? */ + if (u->status != ST_FLEE && u->status != ST_AVOID + && !fval(u, UFL_LONGACTION | UFL_ISNEW) && u->number > 0) { + faction *f = u->faction; + fighter *c = NULL; + + for (s = b->sides; s != s_end; ++s) { + side *se; + /* Wenn alle attackierten noch FFL_NOAID haben, dann kämpfe nicht mit. */ + if (fval(s->faction, FFL_NOAID)) + continue; + if (s->faction != f) { + /* Wenn wir attackiert haben, kommt niemand mehr hinzu: */ + if (s->bf->attacker) + continue; + /* alliiert müssen wir schon sein, sonst ist's eh egal : */ + if (!alliedunit(u, s->faction, HELP_FIGHT)) + continue; + /* wenn die partei verborgen ist, oder gar eine andere + * vorgespiegelt wird, und er sich uns gegenüber nicht zu + * erkennen gibt, helfen wir ihm nicht */ + if (s->stealthfaction) { + if (!allysfm(s, u->faction, HELP_FSTEALTH)) { + continue; + } + } + } + /* einen alliierten angreifen dürfen sie nicht, es sei denn, der + * ist mit einem alliierten verfeindet, der nicht attackiert + * hat: */ + for (se = b->sides; se != s_end; ++se) { + if (u->faction == se->faction) + continue; + if (alliedunit(u, se->faction, HELP_FIGHT) && !se->bf->attacker) { + continue; + } + if (enemy(s, se)) + break; + } + if (se == s_end) + continue; + /* Wenn die Einheit belagert ist, muß auch einer der Alliierten belagert sein: */ + if (besieged(u)) { + fighter *ally; + for (ally = s->fighters; ally; ally = ally->next) { + if (besieged(ally->unit)) { + break; + } + } + if (ally == NULL) + continue; + } + /* keine Einwände, also soll er mitmachen: */ + if (c == NULL) { + if (join_battle(b, u, false, &c)) { + if (battledebug) { + fprintf(bdebug, "%s joins to help %s against %s.\n", + unitname(u), factionname(s->faction), factionname(se->faction)); + } + } + else if (c == NULL) { + continue; + } + } + + /* the enemy of my friend is my enemy: */ + for (se = b->sides; se != s_end; ++se) { + if (se->faction != u->faction && enemy(s, se)) { + if (set_enemy(se, c->side, false) && battledebug) { + fprintf(bdebug, + "%u/%s hates %u/%s because they are enemies with %u/%s.\n", + c->side->index, sidename(c->side), se->index, sidename(se), + s->index, sidename(s)); + } + } + } + } + } + } + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + int si; + side *sa; + faction *f = s->faction; + + /* Den Feinden meiner Feinde gebe ich Deckung (gegen gemeinsame Feinde): */ + for (si = 0; s->enemies[si]; ++si) { + side *se = s->enemies[si]; + int ai; + for (ai = 0; se->enemies[ai]; ++ai) { + side *as = se->enemies[ai]; + if (as == s || !enemy(as, s)) { + set_friendly(as, s); + } + } + } + + for (sa = s + 1; sa != b->sides + b->nsides; ++sa) { + plane *pl = rplane(r); + if (enemy(s, sa)) + continue; + if (friendly(s, sa)) + continue; + if (!alliedgroup(pl, f, sa->faction, f->allies, HELP_FIGHT)) + continue; + if (!alliedgroup(pl, sa->faction, f, sa->faction->allies, HELP_FIGHT)) + continue; + + set_friendly(s, sa); + } + } +} + +static void flee(const troop dt) +{ + fighter *fig = dt.fighter; + unit *u = fig->unit; + + fig->run.hp += fig->person[dt.index].hp; + ++fig->run.number; + + setguard(u, GUARD_NONE); + + kill_troop(dt); +} + +static bool start_battle(region * r, battle ** bp) +{ + battle *b = NULL; + unit *u; + bool fighting = false; + + /* list_foreach geht nicht, wegen flucht */ + for (u = r->units; u != NULL; u = u->next) { + if (fval(u, UFL_LONGACTION)) + continue; + if (u->number > 0) { + order *ord; + + for (ord = u->orders; ord; ord = ord->next) { + static bool init = false; + static const curse_type *peace_ct, *slave_ct, *calm_ct; + + if (!init) { + init = true; + peace_ct = ct_find("peacezone"); + slave_ct = ct_find("slavery"); + calm_ct = ct_find("calmmonster"); + } + if (getkeyword(ord) == K_ATTACK) { + unit *u2; + fighter *c1, *c2; + ship *lsh = NULL; + plane *pl = rplane(r); + + if (pl && fval(pl, PFL_NOATTACK)) { + cmistake(u, ord, 271, MSG_BATTLE); + continue; + } + + if ((u_race(u)->battle_flags & BF_CANATTACK) == 0) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "race_no_attack", + "race", u_race(u))); + continue; + } + /** + ** Fehlerbehandlung Angreifer + **/ + if (LongHunger(u)) { + cmistake(u, ord, 225, MSG_BATTLE); + continue; + } + + if (u->status == ST_AVOID || u->status == ST_FLEE) { + cmistake(u, ord, 226, MSG_BATTLE); + continue; + } + + /* ist ein Flüchtling aus einem andern Kampf */ + if (fval(u, UFL_LONGACTION)) + continue; + + if (peace_ct && curse_active(get_curse(r->attribs, peace_ct))) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "peace_active", "")); + continue; + } + + if (slave_ct && curse_active(get_curse(u->attribs, slave_ct))) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "slave_active", "")); + continue; + } + + if ((u->ship != NULL && !fval(r->terrain, SEA_REGION)) + || (lsh = leftship(u)) != NULL) { + if (is_guarded(r, u, GUARD_TRAVELTHRU)) { + if (lsh) { + cmistake(u, ord, 234, MSG_BATTLE); + } + else { + /* Fehler: "Das Schiff muß erst verlassen werden" */ + cmistake(u, ord, 19, MSG_BATTLE); + } + continue; + } + } + + /* Ende Fehlerbehandlung Angreifer */ + + init_order(ord); + /* attackierte Einheit ermitteln */ + u2 = getunit(r, u->faction); + + /* Beginn Fehlerbehandlung */ + /* Fehler: "Die Einheit wurde nicht gefunden" */ + if (!u2 || u2->number == 0 || !cansee(u->faction, u->region, u2, 0)) { + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, + "feedback_unit_not_found", "")); + continue; + } + /* Fehler: "Die Einheit ist eine der unsrigen" */ + if (u2->faction == u->faction) { + cmistake(u, ord, 45, MSG_BATTLE); + continue; + } + /* Fehler: "Die Einheit ist mit uns alliert" */ + if (alliedunit(u, u2->faction, HELP_FIGHT)) { + cmistake(u, ord, 47, MSG_BATTLE); + continue; + } + if (IsImmune(u2->faction)) { + add_message(&u->faction->msgs, + msg_feedback(u, u->thisorder, "newbie_immunity_error", "turns", + NewbieImmunity())); + continue; + } + /* Fehler: "Die Einheit ist mit uns alliert" */ + + if (calm_ct) { + attrib *a = a_find(u->attribs, &at_curse); + bool calm = false; + while (a && a->type == &at_curse) { + curse *c = (curse *)a->data.v; + if (c->type == calm_ct + && curse_geteffect(c) == u2->faction->subscription) { + if (curse_active(c)) { + calm = true; + break; + } + } + a = a->next; + } + if (calm) { + cmistake(u, ord, 47, MSG_BATTLE); + continue; + } + } + /* Ende Fehlerbehandlung */ + if (b == NULL) { + unit *utmp; + for (utmp = r->units; utmp != NULL; utmp = utmp->next) { + fset(utmp->faction, FFL_NOAID); + } + b = make_battle(r); + } + if (join_battle(b, u, true, &c1)) { + if (battledebug) { + fprintf(bdebug, "%s joins by attacking %s.\n", + unitname(u), unitname(u2)); + } + } + if (join_battle(b, u2, false, &c2)) { + if (battledebug) { + fprintf(bdebug, "%s joins because of an attack from %s.\n", + unitname(u2), unitname(u)); + } + } + + /* Hat die attackierte Einheit keinen Noaid-Status, + * wird das Flag von der Faction genommen, andere + * Einheiten greifen ein. */ + if (!fval(u2, UFL_NOAID)) + freset(u2->faction, FFL_NOAID); + + if (c1 != NULL && c2 != NULL) { + /* Merken, wer Angreifer ist, für die Rückzahlung der + * Präcombataura bei kurzem Kampf. */ + c1->side->bf->attacker = true; + + if (set_enemy(c1->side, c2->side, true) && battledebug) { + fprintf(bdebug, "%u/%s hates %u/%s because they attacked them.\n", + c2->side->index, sidename(c2->side), + c1->side->index, sidename(c1->side)); + } + fighting = true; + } + } + } + } + } + *bp = b; + return fighting; +} + +/** execute one round of attacks + * fig->fighting is used to determine who attacks, not fig->alive, since + * the latter may be influenced by attacks that already took place. + */ +static void battle_attacks(battle * b) +{ + side *s; + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + + if (b->turn != 0 || (b->max_tactics > 0 + && get_tactics(s, NULL) == b->max_tactics)) { + for (fig = s->fighters; fig; fig = fig->next) { + + /* ist in dieser Einheit noch jemand handlungsfähig? */ + if (fig->fighting <= 0) + continue; + + /* Handle the unit's attack on someone */ + do_attack(fig); + } + } + } +} + +/** updates the number of attacking troops in each fighter struct. + * this has to be calculated _before_ the actual attacks take + * place because otherwise dead troops would not strike in the + * round they die. */ +static void battle_update(battle * b) +{ + side *s; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + for (fig = s->fighters; fig; fig = fig->next) { + fig->fighting = fig->alive - fig->removed; + } + } +} + +/** attempt to flee from battle before the next round begins + * there's a double attempt before the first round, but only + * one attempt before round zero, the potential tactics round. */ +static void battle_flee(battle * b) +{ + int attempt, flee_ops = 1; + + if (b->turn == 1) + flee_ops = 2; + + for (attempt = 1; attempt <= flee_ops; ++attempt) { + side *s; + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fig; + for (fig = s->fighters; fig; fig = fig->next) { + unit *u = fig->unit; + troop dt; + int runners = 0; + /* Flucht nicht bei mehr als 600 HP. Damit Wyrme tötbar bleiben. */ + int runhp = _min(600, (int)(0.9 + unit_max_hp(u) * hpflee(u->status))); + + if (u->ship && fval(u->region->terrain, SEA_REGION)) { + /* keine Flucht von Schiffen auf hoher See */ + continue; + } + if (fval(u_race(u), RCF_UNDEAD) || u_race(u) == get_race(RC_SHADOWKNIGHT)) { + /* Untote fliehen nicht. Warum eigentlich? */ + continue; + } + + dt.fighter = fig; + dt.index = fig->alive - fig->removed; + while (s->size[SUM_ROW] && dt.index != 0) { + double ispaniced = 0.0; + --dt.index; + assert(dt.index >= 0 && dt.index < fig->unit->number); + assert(fig->person[dt.index].hp > 0); + + /* Versuche zu fliehen, wenn + * - Kampfstatus fliehe + * - schwer verwundet und nicht erste kampfrunde + * - in panik (Zauber) + * aber nicht, wenn der Zaubereffekt Held auf dir liegt! + */ + switch (u->status) { + case ST_FLEE: + break; + default: + if ((fig->person[dt.index].flags & FL_HIT) == 0) + continue; + if (b->turn <= 1) + continue; + if (fig->person[dt.index].hp <= runhp) + break; + if (fig->person[dt.index].flags & FL_PANICED) { + if ((fig->person[dt.index].flags & FL_COURAGE) == 0) + break; + } + continue; + } + + if (fig->person[dt.index].flags & FL_PANICED) { + ispaniced = EFFECT_PANIC_SPELL; + } + if (chance(_min(fleechance(u) + ispaniced, 0.90))) { + ++runners; + flee(dt); + } + } + if (bdebug && runners > 0) { + fprintf(bdebug, "Fleeing: %d from %s\n", runners, + itoa36(fig->unit->no)); + } + } + } + } +} + +void do_battle(region * r) +{ + battle *b = NULL; + bool fighting = false; + ship *sh; + static int init_rules = 0; + + if (!init_rules) { + static_rules(); + init_rules = 1; + } + if (msg_separator == NULL) { + msg_separator = msg_message("battle::section", ""); + } + + fighting = start_battle(r, &b); + + if (b == NULL) + return; + + /* Bevor wir die alliierten hineinziehen, sollten wir schauen, * + * Ob jemand fliehen kann. Dann erübrigt sich das ganze ja + * vielleicht schon. */ + print_header(b); + if (!fighting) { + /* Niemand mehr da, Kampf kann nicht stattfinden. */ + message *m = msg_message("battle::aborted", ""); + message_all(b, m); + msg_release(m); + free_battle(b); + free(b); + return; + } + join_allies(b); + make_heroes(b); + + /* make sure no ships are damaged initially */ + for (sh = r->ships; sh; sh = sh->next) + freset(sh, SF_DAMAGED); + + /* Gibt es eine Taktikrunde ? */ + if (!ql_empty(b->leaders)) { + b->turn = 0; + b->has_tactics_turn = true; + } + else { + b->turn = 1; + b->has_tactics_turn = false; + } + + /* PRECOMBATSPELLS */ + do_combatmagic(b, DO_PRECOMBATSPELL); + + print_stats(b); /* gibt die Kampfaufstellung aus */ + if (verbosity > 0) + log_printf(stdout, "%s (%d, %d) : ", rname(r, default_locale), r->x, r->y); + + for (; battle_report(b) && b->turn <= max_turns; ++b->turn) { + if (bdebug) { + fprintf(bdebug, "*** Turn: %d\n", b->turn); + } + battle_flee(b); + battle_update(b); + battle_attacks(b); + + } + + if (verbosity > 0) + log_printf(stdout, "\n"); + + /* Auswirkungen berechnen: */ + aftermath(b); + /* Hier ist das Gefecht beendet, und wir können die + * Hilfsstrukturen * wieder löschen: */ + + if (b) { + free_battle(b); + free(b); + } +} + +void battle_free(battle * b) { + side *s; + + assert(b); + + for (s = b->sides; s != b->sides + b->nsides; ++s) { + fighter *fnext = s->fighters; + while (fnext) { + fighter *fig = fnext; + fnext = fig->next; + free_fighter(fig); + free(fig); + } + free_side(s); + } +} + diff --git a/src/kernel/battle.h b/src/battle.h similarity index 100% rename from src/kernel/battle.h rename to src/battle.h diff --git a/src/kernel/battle.test.c b/src/battle.test.c similarity index 96% rename from src/kernel/battle.test.c rename to src/battle.test.c index efe1d8623..47e0d69d8 100644 --- a/src/kernel/battle.test.c +++ b/src/battle.test.c @@ -1,13 +1,15 @@ #include #include + #include "battle.h" -#include "building.h" -#include "faction.h" -#include "item.h" -#include "race.h" -#include "region.h" #include "skill.h" -#include "unit.h" + +#include +#include +#include +#include +#include +#include #include #include "tests.h" diff --git a/src/bind_message.c b/src/bind_message.c index 4b1b8069d..07fb471dc 100644 --- a/src/bind_message.c +++ b/src/bind_message.c @@ -1,11 +1,12 @@ #include #include +#include "spells.h" + /* kernel includes */ #include #include #include -#include #include #include diff --git a/src/bind_process.c b/src/bind_process.c index e6018e69f..3bcfd835b 100755 --- a/src/bind_process.c +++ b/src/bind_process.c @@ -6,11 +6,11 @@ #include #include #include -#include #include #include #include -#include +#include "battle.h" +#include "move.h" #include "economy.h" #include "laws.h" #include "market.h" diff --git a/src/bind_ship.c b/src/bind_ship.c index d38857b20..a089ab084 100644 --- a/src/bind_ship.c +++ b/src/bind_ship.c @@ -11,12 +11,14 @@ without prior permission by the authors of Eressea. */ #include +#include #include "bind_ship.h" #include "bind_unit.h" +#include "move.h" + #include #include -#include #include #include diff --git a/src/bind_unit.c b/src/bind_unit.c index 66be03a0c..a7dc1cab5 100755 --- a/src/bind_unit.c +++ b/src/bind_unit.c @@ -17,14 +17,15 @@ without prior permission by the authors of Eressea. #ifdef BSON_ATTRIB # include "bind_attrib.h" #endif +#include "alchemy.h" #include "bindings.h" +#include "move.h" /* attributes includes */ #include #include /* kernel includes */ -#include #include #include #include @@ -32,7 +33,6 @@ without prior permission by the authors of Eressea. #include #include #include -#include #include #include #include diff --git a/src/bindings.c b/src/bindings.c index 140e738d3..94caa2d42 100755 --- a/src/bindings.c +++ b/src/bindings.c @@ -11,6 +11,7 @@ without prior permission by the authors of Eressea. */ #include +#include #include "bindings.h" #include "bind_unit.h" #include "bind_storage.h" @@ -24,6 +25,7 @@ without prior permission by the authors of Eressea. #include "bind_region.h" #include "helpers.h" #include "console.h" +#include "reports.h" #include @@ -36,7 +38,6 @@ without prior permission by the authors of Eressea. #include #include #include -#include #include #include #include diff --git a/src/creation.c b/src/creation.c index eb4e2fde6..aa555d708 100644 --- a/src/creation.c +++ b/src/creation.c @@ -20,9 +20,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include "creation.h" #include "monster.h" +#include "alchemy.h" /* kernel includes */ -#include #include #include #include diff --git a/src/creport.c b/src/creport.c index 278c96bd7..a56c28888 100644 --- a/src/creport.c +++ b/src/creport.c @@ -30,9 +30,12 @@ without prior permission by the authors of Eressea. /* gamecode includes */ #include "laws.h" #include "economy.h" +#include "stealth.h" +#include "move.h" +#include "reports.h" +#include "alchemy.h" /* kernel includes */ -#include #include #include #include @@ -42,12 +45,10 @@ without prior permission by the authors of Eressea. #include #include #include -#include #include #include #include #include -#include #include #include #include diff --git a/src/direction.test.c b/src/direction.test.c index b9bb06d65..e47ab5034 100644 --- a/src/direction.test.c +++ b/src/direction.test.c @@ -1,9 +1,11 @@ #include -#include "kernel/types.h" +#include + #include "direction.h" -#include "util/language.h" #include "tests.h" +#include + #include static void test_init_directions(CuTest *tc) { diff --git a/src/economy.c b/src/economy.c index 271482f89..49f4153a6 100644 --- a/src/economy.c +++ b/src/economy.c @@ -22,14 +22,16 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include "economy.h" +#include "alchemy.h" #include "direction.h" #include "give.h" #include "laws.h" #include "randenc.h" #include "spy.h" +#include "move.h" +#include "reports.h" /* kernel includes */ -#include #include #include #include @@ -38,13 +40,11 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include -#include #include #include #include #include #include -#include #include #include #include diff --git a/src/eressea.c b/src/eressea.c index f62c601a6..a0d16ebd1 100755 --- a/src/eressea.c +++ b/src/eressea.c @@ -14,10 +14,7 @@ #include #include #include -#include #include -#include -#include #include #include #include @@ -27,6 +24,7 @@ #include "report.h" #include "items.h" #include "creport.h" +#include "names.h" void game_done(void) { diff --git a/src/give.c b/src/give.c index feb28a877..a3dd08f98 100644 --- a/src/give.c +++ b/src/give.c @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include diff --git a/src/gmtool.c b/src/gmtool.c index 53cb33efd..ec1a77ab3 100644 --- a/src/gmtool.c +++ b/src/gmtool.c @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include 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/items.c b/src/items.c index 983284ea8..0ce2ecb8e 100644 --- a/src/items.c +++ b/src/items.c @@ -3,6 +3,7 @@ #include "items.h" #include "study.h" +#include "move.h" #include #include @@ -10,7 +11,6 @@ #include #include #include -#include #include #include #include diff --git a/src/items/speedsail.c b/src/items/speedsail.c index 254bc1e6a..8f76cca00 100644 --- a/src/items/speedsail.c +++ b/src/items/speedsail.c @@ -24,12 +24,13 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include -#include #include #include #include #include +#include + /* util includes */ #include #include diff --git a/src/items/weapons.c b/src/items/weapons.c index 5dab42fa3..a7114d321 100644 --- a/src/items/weapons.c +++ b/src/items/weapons.c @@ -19,13 +19,13 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include "weapons.h" +#include "battle.h" #include #include #include #include #include -#include #include /* util includes */ diff --git a/src/kernel/CMakeLists.txt b/src/kernel/CMakeLists.txt index b2bdece72..9566317e7 100644 --- a/src/kernel/CMakeLists.txt +++ b/src/kernel/CMakeLists.txt @@ -4,31 +4,27 @@ SET(_TEST_FILES build.test.c config.test.c faction.test.c +unit.test.c save.test.c ship.test.c spell.test.c ally.test.c -battle.test.c building.test.c magic.test.c equipment.test.c curse.test.c item.test.c -move.test.c order.test.c pool.test.c race.test.c -reports.test.c spellbook.test.c curse.test.c jsonconf.test.c ) SET(_FILES -alchemy.c alliance.c ally.c -battle.c build.c building.c calendar.c @@ -42,15 +38,12 @@ group.c item.c magic.c messages.c -move.c -names.c order.c pathfinder.c plane.c pool.c race.c region.c -reports.c resources.c save.c ship.c diff --git a/src/kernel/battle.c b/src/kernel/battle.c deleted file mode 100644 index 11d4aeb02..000000000 --- a/src/kernel/battle.c +++ /dev/null @@ -1,4280 +0,0 @@ -/* -Copyright (c) 1998-2010, Enno Rehling - 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 -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -**/ - -#include -#include -#include "battle.h" - -#include "alchemy.h" -#include "alliance.h" -#include "build.h" -#include "building.h" -#include "curse.h" -#include "equipment.h" -#include "faction.h" -#include "group.h" -#include "item.h" -#include "magic.h" -#include "messages.h" -#include "move.h" -#include "names.h" -#include "order.h" -#include "plane.h" -#include "race.h" -#include "region.h" -#include "reports.h" -#include "ship.h" -#include "skill.h" -#include "spell.h" -#include "terrain.h" -#include "unit.h" - -/* attributes includes */ -#include -#include -#include -#include -#include - -/* util includes */ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* libc includes */ -#include -#include -#include -#include -#include -#include -#include - -static FILE *bdebug; - -#define TACTICS_BONUS 1 /* when undefined, we have a tactics round. else this is the bonus tactics give */ -#define TACTICS_MODIFIER 1 /* modifier for generals in the front/rear */ - -#define CATAPULT_INITIAL_RELOAD 4 /* erster schuss in runde 1 + rng_int() % INITIAL */ -#define CATAPULT_STRUCTURAL_DAMAGE - -#define BASE_CHANCE 70 /* 70% Basis-Überlebenschance */ -#ifdef NEW_COMBATSKILLS_RULE -#define TDIFF_CHANGE 5 /* 5% höher pro Stufe */ -#define DAMAGE_QUOTIENT 2 /* damage += skilldiff/DAMAGE_QUOTIENT */ -#else -#define TDIFF_CHANGE 10 -# define DAMAGE_QUOTIENT 1 /* damage += skilldiff/DAMAGE_QUOTIENT */ -#endif - -#undef DEBUG_FAST /* should be disabled when b->fast and b->rowcache works */ -#define DEBUG_SELECT /* should be disabled if select_enemy works */ - -typedef enum combatmagic { - DO_PRECOMBATSPELL, - DO_POSTCOMBATSPELL -} combatmagic_t; - -/* globals */ -static int obs_count = 0; - -#define MINSPELLRANGE 1 -#define MAXSPELLRANGE 7 - -#ifndef ROW_FACTOR -# define ROW_FACTOR 10 -#endif -#define EFFECT_PANIC_SPELL 0.25 -#define TROLL_REGENERATION 0.10 - -/* Nach dem alten System: */ -static int missile_range[2] = { FIGHT_ROW, BEHIND_ROW }; -static int melee_range[2] = { FIGHT_ROW, FIGHT_ROW }; - -static message *msg_separator; - -const troop no_troop = { 0, 0 }; - -static int max_turns = 0; -static int damage_rules = 0; -static int loot_rules = 0; -static int skill_formula = 0; - -#define FORMULA_ORIG 0 -#define FORMULA_NEW 1 - -#define LOOT_MONSTERS (1<<0) -#define LOOT_SELF (1<<1) /* code is mutually exclusive with LOOT_OTHERS */ -#define LOOT_OTHERS (1<<2) -#define LOOT_KEEPLOOT (1<<4) - -#define DAMAGE_CRITICAL (1<<0) -#define DAMAGE_MELEE_BONUS (1<<1) -#define DAMAGE_MISSILE_BONUS (1<<2) -#define DAMAGE_UNARMED_BONUS (1<<3) -#define DAMAGE_SKILL_BONUS (1<<4) -/** initialize rules from configuration. - */ -static void static_rules(void) -{ - loot_rules = - get_param_int(global.parameters, "rules.combat.loot", - LOOT_MONSTERS | LOOT_OTHERS | LOOT_KEEPLOOT); - /* new formula to calculate to-hit-chance */ - skill_formula = - get_param_int(global.parameters, "rules.combat.skill_formula", - FORMULA_ORIG); - /* maximum number of combat turns */ - max_turns = - get_param_int(global.parameters, "rules.combat.turns", COMBAT_TURNS); - /* damage calculation */ - if (get_param_int(global.parameters, "rules.combat.critical", 1)) { - damage_rules |= DAMAGE_CRITICAL; - } - if (get_param_int(global.parameters, "rules.combat.melee_bonus", 1)) { - damage_rules |= DAMAGE_MELEE_BONUS; - } - if (get_param_int(global.parameters, "rules.combat.missile_bonus", 1)) { - damage_rules |= DAMAGE_MISSILE_BONUS; - } - if (get_param_int(global.parameters, "rules.combat.unarmed_bonus", 1)) { - damage_rules |= DAMAGE_UNARMED_BONUS; - } - if (get_param_int(global.parameters, "rules.combat.skill_bonus", 1)) { - damage_rules |= DAMAGE_SKILL_BONUS; - } -} - -static int army_index(side * s) -{ - return s->index; -} - -static char *sidename(side * s) -{ -#define SIDENAMEBUFLEN 256 - static int bufno; /* STATIC_XCALL: used across calls */ - static char sidename_buf[4][SIDENAMEBUFLEN]; /* STATIC_RESULT: used for return, not across calls */ - - bufno = bufno % 4; - strlcpy(sidename_buf[bufno], factionname(s->stealthfaction?s->stealthfaction:s->faction), SIDENAMEBUFLEN); - return sidename_buf[bufno++]; -} - -static const char *sideabkz(side * s, bool truename) -{ - static char sideabkz_buf[8]; /* STATIC_RESULT: used for return, not across calls */ - const faction *f = (s->stealthfaction - && !truename) ? s->stealthfaction : s->faction; - -#undef SIDE_ABKZ -#ifdef SIDE_ABKZ - abkz(f->name, sideabkz_buf, sizeof(sideabkz_buf), 3); -#else - strcpy(sideabkz_buf, itoa36(f->no)); -#endif - return sideabkz_buf; -} - -static void message_faction(battle * b, faction * f, struct message *m) -{ - region *r = b->region; - - if (f->battles == NULL || f->battles->r != r) { - struct bmsg *bm = (struct bmsg *)calloc(1, sizeof(struct bmsg)); - bm->next = f->battles; - f->battles = bm; - bm->r = r; - } - add_message(&f->battles->msgs, m); -} - -int armedmen(const unit * u, bool siege_weapons) -{ - item *itm; - int n = 0; - if (!(urace(u)->flags & RCF_NOWEAPONS)) { - if (effskill(u, SK_WEAPONLESS) >= 1) { - /* kann ohne waffen bewachen: fuer drachen */ - n = u->number; - } else { - /* alle Waffen werden gezaehlt, und dann wird auf die Anzahl - * Personen minimiert */ - for (itm = u->items; itm; itm = itm->next) { - const weapon_type *wtype = resource2weapon(itm->type->rtype); - if (wtype == NULL || (!siege_weapons && (wtype->flags & WTF_SIEGE))) - continue; - if (effskill(u, wtype->skill) >= 1) - n += itm->number; - /* if (effskill(u, wtype->skill) >= wtype->minskill) n += itm->number; */ - if (n > u->number) - break; - } - n = _min(n, u->number); - } - } - return n; -} - -void message_all(battle * b, message * m) -{ - bfaction *bf; - plane *p = rplane(b->region); - watcher *w; - - for (bf = b->factions; bf; bf = bf->next) { - message_faction(b, bf->faction, m); - } - if (p) - for (w = p->watchers; w; w = w->next) { - for (bf = b->factions; bf; bf = bf->next) - if (bf->faction == w->faction) - break; - if (bf == NULL) - message_faction(b, w->faction, m); - } -} - -static void fbattlerecord(battle * b, faction * f, const char *s) -{ - message *m = msg_message("battle_msg", "string", s); - message_faction(b, f, m); - msg_release(m); -} - -/* being an enemy or a friend is (and must always be!) symmetrical */ -#define enemy_i(as, di) (as->relations[di]&E_ENEMY) -#define friendly_i(as, di) (as->relations[di]&E_FRIEND) -#define enemy(as, ds) (as->relations[ds->index]&E_ENEMY) -#define friendly(as, ds) (as->relations[ds->index]&E_FRIEND) - -static bool set_enemy(side * as, side * ds, bool attacking) -{ - int i; - for (i = 0; i != MAXSIDES; ++i) { - if (ds->enemies[i] == NULL) - ds->enemies[i] = as; - if (ds->enemies[i] == as) - break; - } - for (i = 0; i != MAXSIDES; ++i) { - if (as->enemies[i] == NULL) - as->enemies[i] = ds; - if (as->enemies[i] == ds) - break; - } - assert(i != MAXSIDES); - if (attacking) - as->relations[ds->index] |= E_ATTACKING; - if ((ds->relations[as->index] & E_ENEMY) == 0) { - /* enemy-relation are always symmetrical */ - assert((as->relations[ds->index] & (E_ENEMY | E_FRIEND)) == 0); - ds->relations[as->index] |= E_ENEMY; - as->relations[ds->index] |= E_ENEMY; - return true; - } - return false; -} - -static void set_friendly(side * as, side * ds) -{ - assert((as->relations[ds->index] & E_ENEMY) == 0); - ds->relations[as->index] |= E_FRIEND; - as->relations[ds->index] |= E_FRIEND; -} - -static int allysfm(const side * s, const faction * f, int mode) -{ - if (s->faction == f) - return mode; - if (s->group) { - return alliedgroup(s->battle->plane, s->faction, f, s->group->allies, mode); - } - return alliedfaction(s->battle->plane, s->faction, f, mode); -} - -static int allysf(const side * s, const faction * f) -{ - return allysfm(s, f, HELP_FIGHT); -} - -static int dead_fighters(const fighter * df) -{ - return df->unit->number - df->alive - df->run.number; -} - -fighter *select_corpse(battle * b, fighter * af) -/* Wählt eine Leiche aus, der af hilft. casualties ist die Anzahl der - * Toten auf allen Seiten (im Array). Wenn af == NULL, wird die - * Parteizugehörigkeit ignoriert, und irgendeine Leiche genommen. - * - * Untote werden nicht ausgewählt (casualties, not dead) */ -{ - int si, di, maxcasualties = 0; - fighter *df; - side *s; - - for (si = 0; si != b->nsides; ++si) { - side *s = b->sides + si; - if (af == NULL || (!enemy_i(af->side, si) && allysf(af->side, s->faction))) { - maxcasualties += s->casualties; - } - } - di = rng_int() % maxcasualties; - for (s = b->sides; s != b->sides + b->nsides; ++s) { - for (df = s->fighters; df; df = df->next) { - /* Geflohene haben auch 0 hp, dürfen hier aber nicht ausgewählt - * werden! */ - int dead = dead_fighters(df); - if (!playerrace(u_race(df->unit))) - continue; - - if (af && !helping(af->side, df->side)) - continue; - if (di < dead) { - return df; - } - di -= dead; - } - } - - return NULL; -} - -bool helping(const side * as, const side * ds) -{ - if (as->faction == ds->faction) - return true; - return (bool) (!enemy(as, ds) && allysf(as, ds->faction)); -} - -int statusrow(int status) -{ - switch (status) { - case ST_AGGRO: - case ST_FIGHT: - return FIGHT_ROW; - case ST_BEHIND: - case ST_CHICKEN: - return BEHIND_ROW; - case ST_AVOID: - return AVOID_ROW; - case ST_FLEE: - return FLEE_ROW; - default: - assert(!"unknown combatrow"); - } - return FIGHT_ROW; -} - -static double hpflee(int status) - /* if hp drop below this percentage, run away */ -{ - switch (status) { - case ST_AGGRO: - return 0.0; - case ST_FIGHT: - case ST_BEHIND: - return 0.2; - case ST_CHICKEN: - case ST_AVOID: - return 0.9; - case ST_FLEE: - return 1.0; - default: - assert(!"unknown combatrow"); - } - return 0.0; -} - -static int get_row(const side * s, int row, const side * vs) -{ - bool counted[MAXSIDES]; - int enemyfront = 0; - int line, result; - int retreat = 0; - int size[NUMROWS]; - int front = 0; - battle *b = s->battle; - - memset(counted, 0, sizeof(counted)); - memset(size, 0, sizeof(size)); - for (line = FIRST_ROW; line != NUMROWS; ++line) { - int si, sa_i; - /* how many enemies are there in the first row? */ - for (si = 0; s->enemies[si]; ++si) { - side *se = s->enemies[si]; - if (se->size[line] > 0) { - enemyfront += se->size[line]; - /* - s->nonblockers[line] (nicht, weil angreifer) */ - } - } - for (sa_i = 0; sa_i != b->nsides; ++sa_i) { - side *sa = b->sides + sa_i; - /* count people that like me, but don't like my enemy */ - if (friendly_i(s, sa_i) && enemy_i(vs, sa_i)) { - if (!counted[sa_i]) { - int i; - - for (i = 0; i != NUMROWS; ++i) { - size[i] += sa->size[i] - sa->nonblockers[i]; - } - counted[sa_i] = true; - } - } - } - if (enemyfront) - break; - } - if (enemyfront) { - for (line = FIRST_ROW; line != NUMROWS; ++line) { - front += size[line]; - if (!front || front < enemyfront / ROW_FACTOR) - ++retreat; - else if (front) - break; - } - } - - /* every entry in the size[] array means someone trying to defend us. - * 'retreat' is the number of rows falling. - */ - result = _max(FIRST_ROW, row - retreat); - - return result; -} - -int get_unitrow(const fighter * af, const side * vs) -{ - int row = statusrow(af->status); - if (vs == NULL) { - int i; - for (i = FIGHT_ROW; i != row; ++i) - if (af->side->size[i]) - break; - return FIGHT_ROW + (row - i); - } else { - battle *b = vs->battle; - if (row != b->rowcache.row || b->alive != b->rowcache.alive - || af->side != b->rowcache.as || vs != b->rowcache.vs) { - b->rowcache.alive = b->alive; - b->rowcache.as = af->side; - b->rowcache.vs = vs; - b->rowcache.row = row; - b->rowcache.result = get_row(af->side, row, vs); - return b->rowcache.result; - } -#ifdef DEBUG_FAST /* validation code */ - { - int i = get_row(af->side, row, vs); - assert(i == b->rowcache.result); - } -#endif - return b->rowcache.result; - } -} - -static void reportcasualties(battle * b, fighter * fig, int dead) -{ - struct message *m; - region *r = NULL; - if (fig->alive == fig->unit->number) - return; - m = msg_message("casualties", "unit runto run alive fallen", - fig->unit, r, fig->run.number, fig->alive, dead); - message_all(b, m); - msg_release(m); -} - -static int -contest_classic(int skilldiff, const armor_type * ar, const armor_type * sh) -{ - int p, vw = BASE_CHANCE - TDIFF_CHANGE * skilldiff; - double mod = 1.0; - - if (ar != NULL) - mod *= (1 + ar->penalty); - if (sh != NULL) - mod *= (1 + sh->penalty); - vw = (int)(100 - ((100 - vw) * mod)); - - do { - p = rng_int() % 100; - vw -= p; - } - while (vw >= 0 && p >= 90); - return (vw <= 0); -} - -/** new rule for Eressea 1.5 - * \param skilldiff - the attack skill with every modifier applied - */ -static int -contest_new(int skilldiff, const troop dt, const armor_type * ar, - const armor_type * sh) -{ - double tohit = 0.5 + skilldiff * 0.1; - if (tohit < 0.5) - tohit = 0.5; - if (chance(tohit)) { - int defense = effskill(dt.fighter->unit, SK_STAMINA); - double tosave = defense * 0.05; - return !chance(tosave); - } - return 0; -} - -static int -contest(int skdiff, const troop dt, const armor_type * ar, - const armor_type * sh) -{ - if (skill_formula == FORMULA_ORIG) { - return contest_classic(skdiff, ar, sh); - } else { - return contest_new(skdiff, dt, ar, sh); - } -} - -static bool is_riding(const troop t) -{ - if (t.fighter->building != NULL) - return false; - if (t.fighter->horses + t.fighter->elvenhorses > t.index) - return true; - return false; -} - -static weapon *preferred_weapon(const troop t, bool attacking) -{ - weapon *missile = t.fighter->person[t.index].missile; - weapon *melee = t.fighter->person[t.index].melee; - if (attacking) { - if (melee == NULL || (missile && missile->attackskill > melee->attackskill)) { - return missile; - } - } else { - if (melee == NULL || (missile - && missile->defenseskill > melee->defenseskill)) { - return missile; - } - } - return melee; -} - -static weapon *select_weapon(const troop t, bool attacking, - bool ismissile) - /* select the primary weapon for this trooper */ -{ - if (attacking) { - if (ismissile) { - /* from the back rows, have to use your missile weapon */ - return t.fighter->person[t.index].missile; - } - } else { - if (!ismissile) { - /* have to use your melee weapon if it's melee */ - return t.fighter->person[t.index].melee; - } - } - return preferred_weapon(t, attacking); -} - -static bool i_canuse(const unit * u, const item_type * itype) -{ - if (itype->canuse) { - return itype->canuse(u, itype); - } - return true; -} - -static int -weapon_skill(const weapon_type * wtype, const unit * u, bool attacking) - /* the 'pure' skill when using this weapon to attack or defend. - * only undiscriminate modifiers (not affected by troops or enemies) - * are taken into account, e.g. no horses, magic, etc. */ -{ - int skill; - - if (wtype == NULL) { - skill = effskill(u, SK_WEAPONLESS); - if (skill <= 0) { - /* wenn kein waffenloser kampf, dann den rassen-defaultwert */ - if (u_race(u) == get_race(RC_ORC)) { - int sword = effskill(u, SK_MELEE); - int spear = effskill(u, SK_SPEAR); - skill = _max(sword, spear) - 3; - if (attacking) { - skill = _max(skill, u_race(u)->at_default); - } else { - skill = _max(skill, u_race(u)->df_default); - } - } else { - if (attacking) { - skill = u_race(u)->at_default; - } else { - skill = u_race(u)->df_default; - } - } - } else { - /* der rassen-defaultwert kann höher sein als der Talentwert von - * waffenloser kampf */ - if (attacking) { - if (skill < u_race(u)->at_default) - skill = u_race(u)->at_default; - } else { - if (skill < u_race(u)->df_default) - skill = u_race(u)->df_default; - } - } - if (attacking) { - skill += u_race(u)->at_bonus; - if (fval(u->region->terrain, SEA_REGION) && u->ship) - skill += u->ship->type->at_bonus; - } else { - skill += u_race(u)->df_bonus; - if (fval(u->region->terrain, SEA_REGION) && u->ship) - skill += u->ship->type->df_bonus; - } - } else { - /* changed: if we own a weapon, we have at least a skill of 0 */ - if (!i_canuse(u, wtype->itype)) - return -1; - skill = effskill(u, wtype->skill); - if (skill < wtype->minskill) - skill = 0; - if (skill > 0) { - if (attacking) { - skill += u_race(u)->at_bonus; - } else { - skill += u_race(u)->df_bonus; - } - } - if (attacking) { - skill += wtype->offmod; - } else { - skill += wtype->defmod; - } - } - - return skill; -} - -static int CavalrySkill(void) -{ - static int skill = -1; - - if (skill < 0) { - skill = get_param_int(global.parameters, "rules.cavalry.skill", 2); - } - return skill; -} - -#define BONUS_SKILL 1 -#define BONUS_DAMAGE 2 -static int CavalryBonus(const unit * u, troop enemy, int type) -{ - static int mode = -1; - - if (mode < 0) { - mode = get_param_int(global.parameters, "rules.cavalry.mode", 1); - } - if (mode == 0) { - /* old rule, Eressea 1.0 compat */ - return (type == BONUS_SKILL) ? 2 : 0; - } else { - /* new rule, chargers in Eressea 1.1 */ - int skl = effskill(u, SK_RIDING); - /* only half against trolls */ - if (skl > 0) { - if (type == BONUS_DAMAGE) { - int dmg = _min(skl, 8); - if (u_race(enemy.fighter->unit) == get_race(RC_TROLL)) { - dmg = dmg / 4; - } else { - dmg = dmg / 2; - } - return dmg; - } else { - skl = skl / 2; - return _min(skl, 4); - } - } - } - return 0; -} - -static int -weapon_effskill(troop t, troop enemy, const weapon * w, bool attacking, - bool missile) - /* effektiver Waffenskill während des Kampfes */ -{ - /* In dieser Runde alle die Modifier berechnen, die fig durch die - * Waffen bekommt. */ - fighter *tf = t.fighter; - unit *tu = t.fighter->unit; - int skill; - const weapon_type *wtype = w ? w->type : NULL; - - if (wtype == NULL) { - /* Ohne Waffe: Waffenlose Angriffe */ - skill = weapon_skill(NULL, tu, attacking); - } else { - if (attacking) { - skill = w->attackskill; - } else { - skill = w->defenseskill; - } - if (wtype->modifiers != NULL) { - /* Pferdebonus, Lanzenbonus, usw. */ - int m; - unsigned int flags = - WMF_SKILL | (attacking ? WMF_OFFENSIVE : WMF_DEFENSIVE); - - if (is_riding(t)) - flags |= WMF_RIDING; - else - flags |= WMF_WALKING; - if (is_riding(enemy)) - flags |= WMF_AGAINST_RIDING; - else - flags |= WMF_AGAINST_WALKING; - - for (m = 0; wtype->modifiers[m].value; ++m) { - if ((wtype->modifiers[m].flags & flags) == flags) { - race_list *rlist = wtype->modifiers[m].races; - if (rlist != NULL) { - while (rlist) { - if (rlist->data == u_race(tu)) - break; - rlist = rlist->next; - } - if (rlist == NULL) - continue; - } - skill += wtype->modifiers[m].value; - } - } - } - } - - /* Burgenbonus, Pferdebonus */ - if (is_riding(t) && (wtype == NULL || (fval(wtype, WTF_HORSEBONUS) - && !fval(wtype, WTF_MISSILE)))) { - skill += CavalryBonus(tu, enemy, BONUS_SKILL); - if (wtype) - skill = - skillmod(urace(tu)->attribs, tu, tu->region, wtype->skill, skill, - SMF_RIDING); - } - - if (t.index < tf->elvenhorses) { - /* Elfenpferde: Helfen dem Reiter, egal ob und welche Waffe. Das ist - * eleganter, und vor allem einfacher, sonst muß man noch ein - * WMF_ELVENHORSE einbauen. */ - skill += 2; - } - - if (skill > 0 && !attacking && missile) { - /* - * Wenn ich verteidige, und nicht direkt meinem Feind gegenüberstehe, - * halbiert sich mein Skill: (z.B. gegen Fernkämpfer. Nahkämpfer - * können mich eh nicht treffen) - */ - skill /= 2; - } - return skill; -} - -static const armor_type *select_armor(troop t, bool shield) -{ - unsigned int type = shield ? ATF_SHIELD : 0; - unit *u = t.fighter->unit; - const armor *a = t.fighter->armors; - int geschuetzt = 0; - - /* some monsters should not use armor (dragons in chainmail? ha!) */ - if (!(u_race(u)->battle_flags & BF_EQUIPMENT)) - return NULL; - - /* ... neither do werewolves */ - if (fval(u, UFL_WERE)) { - return NULL; - } - - for (; a; a = a->next) { - if ((a->atype->flags & ATF_SHIELD) == type) { - geschuetzt += a->count; - if (geschuetzt > t.index) { - /* unser Kandidat wird geschuetzt */ - return a->atype; - } - } - } - return NULL; -} - -/* Hier ist zu beachten, ob und wie sich Zauber und Artefakte, die - * Rüstungschutz geben, addieren. - * - Artefakt "trollbelt" gibt Rüstung +1 - * - Zauber Rindenhaut gibt Rüstung +3 - */ -static int trollbelts(const unit *u) { - const struct resource_type *belt = rt_find("trollbelt"); - return belt ? i_get(u->items, belt->itype) : 0; -} - -int select_magicarmor(troop t) -{ - unit *u = t.fighter->unit; - int ma = 0; - - if (trollbelts(u) > t.index) /* unser Kandidat wird geschuetzt */ - ma += 1; - - return ma; -} - -/* Sind side ds und Magier des meffect verbündet, dann return 1*/ -bool meffect_protection(battle * b, meffect * s, side * ds) -{ - if (!s->magician->alive) - return false; - if (s->duration <= 0) - return false; - if (enemy(s->magician->side, ds)) - return false; - if (allysf(s->magician->side, ds->faction)) - return true; - return false; -} - -/* Sind side as und Magier des meffect verfeindet, dann return 1*/ -bool meffect_blocked(battle * b, meffect * s, side * as) -{ - if (!s->magician->alive) - return false; - if (s->duration <= 0) - return false; - if (enemy(s->magician->side, as)) - return true; - return false; -} - -/* rmfighter wird schon im PRAECOMBAT gebraucht, da gibt es noch keine - * troops */ -void rmfighter(fighter * df, int i) -{ - side *ds = df->side; - - /* nicht mehr personen abziehen, als in der Einheit am Leben sind */ - assert(df->alive >= i); - assert(df->alive <= df->unit->number); - - /* erst ziehen wir die Anzahl der Personen von den Kämpfern in der - * Schlacht, dann von denen auf dieser Seite ab*/ - df->side->alive -= i; - df->side->battle->alive -= i; - - /* Dann die Kampfreihen aktualisieren */ - ds->size[SUM_ROW] -= i; - ds->size[statusrow(df->status)] -= i; - - /* Spezialwirkungen, z.B. Schattenritter */ - if (u_race(df->unit)->battle_flags & BF_NOBLOCK) { - ds->nonblockers[SUM_ROW] -= i; - ds->nonblockers[statusrow(df->status)] -= i; - } - - /* und die Einheit selbst aktualisieren */ - df->alive -= i; -} - -static void rmtroop(troop dt) -{ - fighter *df = dt.fighter; - - /* troop ist immer eine einzele Person */ - rmfighter(df, 1); - - assert(dt.index >= 0 && dt.index < df->unit->number); - df->person[dt.index] = df->person[df->alive - df->removed]; - if (df->removed) { - df->person[df->alive - df->removed] = df->person[df->alive]; - } - df->person[df->alive].hp = 0; -} - -void remove_troop(troop dt) -{ - fighter *df = dt.fighter; - struct person p = df->person[dt.index]; - battle *b = df->side->battle; - b->fast.alive = -1; /* invalidate cached value */ - b->rowcache.alive = -1; /* invalidate cached value */ - ++df->removed; - ++df->side->removed; - df->person[dt.index] = df->person[df->alive - df->removed]; - df->person[df->alive - df->removed] = p; -} - -void kill_troop(troop dt) -{ - fighter *df = dt.fighter; - unit *du = df->unit; - - rmtroop(dt); - if (!df->alive) { - char eqname[64]; - const struct equipment *eq; - if (u_race(du)->itemdrop) { - item *drops = u_race(du)->itemdrop(u_race(du), du->number - df->run.number); - - if (drops != NULL) { - i_merge(&du->items, &drops); - } - } - sprintf(eqname, "%s_spoils", u_race(du)->_name); - eq = get_equipment(eqname); - if (eq != NULL) { - equip_items(&du->items, eq); - } - } -} - -/** reduces the target's exp by an equivalent of n points learning - * 30 points = 1 week - */ -void drain_exp(struct unit *u, int n) -{ - skill_t sk = (skill_t) (rng_int() % MAXSKILLS); - skill_t ssk; - - ssk = sk; - - while (get_level(u, sk) == 0) { - sk++; - if (sk == MAXSKILLS) - sk = 0; - if (sk == ssk) { - sk = NOSKILL; - break; - } - } - if (sk != NOSKILL) { - skill *sv = unit_skill(u, sk); - while (n > 0) { - if (n >= 30 * u->number) { - reduce_skill(u, sv, 1); - n -= 30; - } else { - if (rng_int() % (30 * u->number) < n) - reduce_skill(u, sv, 1); - n = 0; - } - } - } -} - -const char *rel_dam(int dam, int hp) -{ - double q = (double)dam / (double)hp; - - if (q > 0.75) { - return "eine klaffende Wunde"; - } else if (q > 0.5) { - return "eine schwere Wunde"; - } else if (q > 0.25) { - return "eine Wunde"; - } - return "eine kleine Wunde"; -} - -static void vampirism(troop at, int damage) -{ - static int vampire = -1; - if (vampire < 0) - vampire = get_param_int(global.parameters, "rules.combat.demon_vampire", 0); - if (vampire > 0) { - int gain = damage / vampire; - int chance = damage - vampire * gain; - if (chance > 0 && (rng_int() % vampire < chance)) - ++gain; - if (gain > 0) { - int maxhp = unit_max_hp(at.fighter->unit); - at.fighter->person[at.index].hp = - _min(gain + at.fighter->person[at.index].hp, maxhp); - } - } -} - -static int natural_armor(unit * du) -{ - static int *bonus = 0; - int an = u_race(du)->armor; - if (bonus == 0) { - bonus = calloc(sizeof(int), num_races); - } - if (bonus[u_race(du)->index] == 0) { - bonus[u_race(du)->index] = - get_param_int(u_race(du)->parameters, "armor.stamina", -1); - if (bonus[u_race(du)->index] == 0) - bonus[u_race(du)->index] = -1; - } - if (bonus[u_race(du)->index] > 0) { - int sk = effskill(du, SK_STAMINA); - sk /= bonus[u_race(du)->index]; - an += sk; - } - return an; -} - -bool -terminate(troop dt, troop at, int type, const char *damage, bool missile) -{ - item **pitm; - fighter *df = dt.fighter; - fighter *af = at.fighter; - unit *au = af->unit; - unit *du = df->unit; - battle *b = df->side->battle; - int heiltrank = 0; - static int rule_armor = -1; - - /* Schild */ - side *ds = df->side; - int hp; - - int ar = 0, an, am; - const armor_type *armor = select_armor(dt, true); - const armor_type *shield = select_armor(dt, false); - - const weapon_type *dwtype = NULL; - const weapon_type *awtype = NULL; - const weapon *weapon; - - int rda, sk = 0, sd; - bool magic = false; - int da = dice_rand(damage); - - assert(du->number > 0); - ++at.fighter->hits; - - switch (type) { - case AT_STANDARD: - weapon = select_weapon(at, true, missile); - sk = weapon_effskill(at, dt, weapon, true, missile); - if (weapon) - awtype = weapon->type; - if (awtype && fval(awtype, WTF_MAGICAL)) - magic = true; - break; - case AT_NATURAL: - sk = weapon_effskill(at, dt, NULL, true, missile); - break; - case AT_SPELL: - case AT_COMBATSPELL: - magic = true; - break; - default: - break; - } - weapon = select_weapon(dt, false, true); /* missile=true to get the unmodified best weapon she has */ - sd = weapon_effskill(dt, at, weapon, false, false); - if (weapon != NULL) - dwtype = weapon->type; - - if (is_riding(at) && (awtype == NULL || (fval(awtype, WTF_HORSEBONUS) - && !fval(awtype, WTF_MISSILE)))) { - da += CavalryBonus(au, dt, BONUS_DAMAGE); - } - - if (armor) { - ar += armor->prot; - if (armor->projectile > 0 && chance(armor->projectile)) { - return false; - } - } - if (shield) { - ar += shield->prot; - if (shield->projectile > 0 && chance(shield->projectile)) { - return false; - } - } - - /* natürliche Rüstung */ - an = natural_armor(du); - - /* magische Rüstung durch Artefakte oder Sprüche */ - /* Momentan nur Trollgürtel und Werwolf-Eigenschaft */ - am = select_magicarmor(dt); - -#if CHANGED_CROSSBOWS - if (awtype && fval(awtype, WTF_ARMORPIERCING)) { - /* crossbows */ - ar /= 2; - an /= 2; - } -#endif - - if (rule_armor < 0) { - rule_armor = get_param_int(global.parameters, "rules.combat.nat_armor", 0); - } - if (rule_armor == 0) { - /* natürliche Rüstung ist halbkumulativ */ - if (ar > 0) { - ar += an / 2; - } else { - ar = an; - } - } else { - /* use the higher value, add half the other value */ - ar = (ar > an) ? (ar + an / 2) : (an + ar / 2); - } - ar += am; - - if (type != AT_COMBATSPELL && type != AT_SPELL) { - if (damage_rules & DAMAGE_CRITICAL) { - double kritchance = (sk * 3 - sd) / 200.0; - - kritchance = _max(kritchance, 0.005); - kritchance = _min(0.9, kritchance); - - while (chance(kritchance)) { - if (bdebug) { - fprintf(bdebug, "%s/%d lands a critical hit\n", unitid(au), at.index); - } - da += dice_rand(damage); - } - } - - da += rc_specialdamage(u_race(au), u_race(du), awtype); - - if (awtype != NULL && fval(awtype, WTF_MISSILE)) { - /* missile weapon bonus */ - if (damage_rules & DAMAGE_MISSILE_BONUS) { - da += af->person[at.index].damage_rear; - } - } else if (awtype == NULL) { - /* skill bonus for unarmed combat */ - if (damage_rules & DAMAGE_UNARMED_BONUS) { - da += effskill(au, SK_WEAPONLESS); - } - } else { - /* melee bonus */ - if (damage_rules & DAMAGE_MELEE_BONUS) { - da += af->person[at.index].damage; - } - } - - /* Skilldifferenzbonus */ - if (damage_rules & DAMAGE_SKILL_BONUS) { - da += _max(0, (sk - sd) / DAMAGE_QUOTIENT); - } - } - - if (magic) { - /* Magischer Schaden durch Spruch oder magische Waffe */ - double res = 1.0; - - /* magic_resistance gib x% Resistenzbonus zurück */ - res -= magic_resistance(du) * 3.0; - - if (u_race(du)->battle_flags & BF_EQUIPMENT) { -#ifdef TODO_RUNESWORD - /* Runenschwert gibt im Kampf 80% Resistenzbonus */ - if (dwp == WP_RUNESWORD) - res -= 0.80; -#endif - /* der Effekt von Laen steigt nicht linear */ - if (armor && fval(armor, ATF_LAEN)) - res *= (1 - armor->magres); - if (shield && fval(shield, ATF_LAEN)) - res *= (1 - shield->magres); - if (dwtype) - res *= (1 - dwtype->magres); - } - - if (res > 0) { - da = (int)(_max(da * res, 0)); - } - /* gegen Magie wirkt nur natürliche und magische Rüstung */ - ar = an + am; - } - - rda = _max(da - ar, 0); - - if ((u_race(du)->battle_flags & BF_INV_NONMAGIC) && !magic) - rda = 0; - else { - int qi; - quicklist *ql; - unsigned int i = 0; - - if (u_race(du)->battle_flags & BF_RES_PIERCE) - i |= WTF_PIERCE; - if (u_race(du)->battle_flags & BF_RES_CUT) - i |= WTF_CUT; - if (u_race(du)->battle_flags & BF_RES_BASH) - i |= WTF_BLUNT; - - if (i && awtype && fval(awtype, i)) - rda /= 2; - - /* Schilde */ - for (qi = 0, ql = b->meffects; ql; ql_advance(&ql, &qi, 1)) { - meffect *me = (meffect *) ql_get(ql, qi); - if (meffect_protection(b, me, ds) != 0) { - assert(0 <= rda); /* rda sollte hier immer mindestens 0 sein */ - /* jeder Schaden wird um effect% reduziert bis der Schild duration - * Trefferpunkte aufgefangen hat */ - if (me->typ == SHIELD_REDUCE) { - hp = rda * (me->effect / 100); - rda -= hp; - me->duration -= hp; - } - /* gibt Rüstung +effect für duration Treffer */ - if (me->typ == SHIELD_ARMOR) { - rda = _max(rda - me->effect, 0); - me->duration--; - } - } - } - } - - assert(dt.index < du->number); - df->person[dt.index].hp -= rda; - if (u_race(au) == get_race(RC_DAEMON)) { - vampirism(at, rda); - } - - if (df->person[dt.index].hp > 0) { /* Hat überlebt */ - if (bdebug) { - fprintf(bdebug, "Damage %d, armor %d: %d -> %d HP\n", - da, ar, df->person[dt.index].hp + rda, df->person[dt.index].hp); - } - if (u_race(au) == get_race(RC_DAEMON)) { -#ifdef TODO_RUNESWORD - if (select_weapon(dt, 0, -1) == WP_RUNESWORD) - continue; -#endif - if (!(df->person[dt.index].flags & (FL_COURAGE | FL_DAZZLED))) { - df->person[dt.index].flags |= FL_DAZZLED; - df->person[dt.index].defence--; - } - } - df->person[dt.index].flags = (df->person[dt.index].flags & ~FL_SLEEPING); - return false; - } - - /* Sieben Leben */ - if (u_race(du) == get_race(RC_CAT) && (chance(1.0 / 7))) { - assert(dt.index >= 0 && dt.index < du->number); - df->person[dt.index].hp = unit_max_hp(du); - return false; - } - - /* Heiltrank schluerfen und hoffen */ - if (oldpotiontype[P_HEAL]) { - if (get_effect(du, oldpotiontype[P_HEAL]) > 0) { - change_effect(du, oldpotiontype[P_HEAL], -1); - heiltrank = 1; - } else if (i_get(du->items, oldpotiontype[P_HEAL]->itype) > 0) { - i_change(&du->items, oldpotiontype[P_HEAL]->itype, -1); - change_effect(du, oldpotiontype[P_HEAL], 3); - heiltrank = 1; - } - if (heiltrank && (chance(0.50))) { - { - message *m = msg_message("battle::potionsave", "unit", du); - message_faction(b, du->faction, m); - msg_release(m); - } - assert(dt.index >= 0 && dt.index < du->number); - df->person[dt.index].hp = u_race(du)->hitpoints; - return false; - } - } - ++at.fighter->kills; - - if (bdebug) { - fprintf(bdebug, "Damage %d, armor %d, type %d: %d -> %d HP, tot.\n", - da, ar, type, df->person[dt.index].hp + rda, df->person[dt.index].hp); - } - for (pitm = &du->items; *pitm;) { - item *itm = *pitm; - const item_type *itype = itm->type; - if (!(itype->flags & ITF_CURSED) && dt.index < itm->number) { - /* 25% Grundchance, das ein Item kaputtgeht. */ - if (rng_int() % 4 < 1) { - i_change(pitm, itype, -1); - } - } - if (*pitm == itm) { - pitm = &itm->next; - } - } - kill_troop(dt); - - return true; -} - -static int -count_side(const side * s, const side * vs, int minrow, int maxrow, int select) -{ - fighter *fig; - int people = 0; - int unitrow[NUMROWS]; - - if (maxrow < FIGHT_ROW) - return 0; - if (select & SELECT_ADVANCE) { - memset(unitrow, -1, sizeof(unitrow)); - } - - for (fig = s->fighters; fig; fig = fig->next) { - if (fig->alive - fig->removed > 0) { - int row = statusrow(fig->status); - if (select & SELECT_ADVANCE) { - if (unitrow[row] == -1) { - unitrow[row] = get_unitrow(fig, vs); - } - row = unitrow[row]; - } - if (row >= minrow && row <= maxrow) { - people += fig->alive - fig->removed; - if (people > 0 && (select & SELECT_FIND)) - break; - } - } - } - return people; -} - -/* return the number of live allies warning: this function only considers -* troops that are still alive, not those that are still fighting although -* dead. */ -int -count_allies(const side * as, int minrow, int maxrow, int select, int allytype) -{ - battle *b = as->battle; - side *ds; - int count = 0; - - for (ds = b->sides; ds != b->sides + b->nsides; ++ds) { - if ((allytype == ALLY_ANY && helping(as, ds)) || (allytype == ALLY_SELF - && as->faction == ds->faction)) { - count += count_side(ds, NULL, minrow, maxrow, select); - if (count > 0 && (select & SELECT_FIND)) - break; - } - } - return count; -} - -static int -count_enemies_i(battle * b, const fighter * af, int minrow, int maxrow, - int select) -{ - side *es, *as = af->side; - int i = 0; - - for (es = b->sides; es != b->sides + b->nsides; ++es) { - if (as == NULL || enemy(es, as)) { - int offset = 0; - if (select & SELECT_DISTANCE) { - offset = get_unitrow(af, es) - FIGHT_ROW; - } - i += count_side(es, as, minrow - offset, maxrow - offset, select); - if (i > 0 && (select & SELECT_FIND)) - break; - } - } - return i; -} - -int -count_enemies(battle * b, const fighter * af, int minrow, int maxrow, - int select) -{ - int sr = statusrow(af->status); - side *as = af->side; - - if (b->alive == b->fast.alive && as == b->fast.side && sr == b->fast.status - && minrow == b->fast.minrow && maxrow == b->fast.maxrow) { - if (b->fast.enemies[select] >= 0) { -#ifdef DEBUG_FAST - int i = count_enemies_i(b, af, minrow, maxrow, select); - assert(i == b->fast.enemies[select]); -#endif - return b->fast.enemies[select]; - } else if (select & SELECT_FIND) { - if (b->fast.enemies[select - SELECT_FIND] >= 0) { -#ifdef DEBUG_FAST - int i = count_enemies_i(b, af, minrow, maxrow, select); - assert((i > 0) == (b->fast.enemies[select - SELECT_FIND] > 0)); -#endif - return b->fast.enemies[select - SELECT_FIND]; - } - } - } else if (select != SELECT_FIND || b->alive != b->fast.alive) { - b->fast.side = as; - b->fast.status = sr; - b->fast.minrow = minrow; - b->fast.alive = b->alive; - b->fast.maxrow = maxrow; - memset(b->fast.enemies, -1, sizeof(b->fast.enemies)); - } - if (maxrow >= FIRST_ROW) { - int i = count_enemies_i(b, af, minrow, maxrow, select); - b->fast.enemies[select] = i; - return i; - } - return 0; -} - -troop select_enemy(fighter * af, int minrow, int maxrow, int select) -{ - side *as = af->side; - battle *b = as->battle; - int si, selected; - int enemies; -#ifdef DEBUG_SELECT - troop result = no_troop; -#endif - if (u_race(af->unit)->flags & RCF_FLY) { - /* flying races ignore min- and maxrow and can attack anyone fighting - * them */ - minrow = FIGHT_ROW; - maxrow = BEHIND_ROW; - } - minrow = _max(minrow, FIGHT_ROW); - - enemies = count_enemies(b, af, minrow, maxrow, select); - - /* Niemand ist in der angegebenen Entfernung? */ - if (enemies <= 0) - return no_troop; - - selected = rng_int() % enemies; - for (si = 0; as->enemies[si]; ++si) { - side *ds = as->enemies[si]; - fighter *df; - int unitrow[NUMROWS]; - int offset = 0; - - if (select & SELECT_DISTANCE) - offset = get_unitrow(af, ds) - FIGHT_ROW; - - if (select & SELECT_ADVANCE) { - int ui; - for (ui = 0; ui != NUMROWS; ++ui) - unitrow[ui] = -1; - } - - for (df = ds->fighters; df; df = df->next) { - int dr; - - dr = statusrow(df->status); - if (select & SELECT_ADVANCE) { - if (unitrow[dr] < 0) { - unitrow[dr] = get_unitrow(df, as); - } - dr = unitrow[dr]; - } - - if (select & SELECT_DISTANCE) - dr += offset; - if (dr < minrow || dr > maxrow) - continue; - if (df->alive - df->removed > selected) { -#ifdef DEBUG_SELECT - if (result.fighter == NULL) { - result.index = selected; - result.fighter = df; - } -#else - troop dt; - dt.index = selected; - dt.fighter = df; - return dt; -#endif - } - selected -= (df->alive - df->removed); - enemies -= (df->alive - df->removed); - } - } - if (enemies != 0) { - log_error("select_enemies has a bug.\n"); - } -#ifdef DEBUG_SELECT - return result; -#else - assert(!selected); - return no_troop; -#endif -} - -static int get_tactics(const side * as, const side * ds) -{ - battle *b = as->battle; - side *stac; - int result = 0; - int defense = 0; - - if (b->max_tactics > 0) { - for (stac = b->sides; stac != b->sides + b->nsides; ++stac) { - if (stac->leader.value > result && helping(stac, as)) { - assert(ds == NULL || !helping(stac, ds)); - result = stac->leader.value; - } - if (ds && stac->leader.value > defense && helping(stac, ds)) { - assert(!helping(stac, as)); - defense = stac->leader.value; - } - } - } - return result - defense; -} - -static troop select_opponent(battle * b, troop at, int mindist, int maxdist) -{ - fighter *af = at.fighter; - troop dt; - - if (u_race(af->unit)->flags & RCF_FLY) { - /* flying races ignore min- and maxrow and can attack anyone fighting - * them */ - dt = select_enemy(at.fighter, FIGHT_ROW, BEHIND_ROW, SELECT_ADVANCE); - } else { - mindist = _max(mindist, FIGHT_ROW); - dt = select_enemy(at.fighter, mindist, maxdist, SELECT_ADVANCE); - } - - if (b->turn == 0 && dt.fighter) { - int tactics_formula = -1; - - if (tactics_formula < 0) { - tactics_formula = - get_param_int(global.parameters, "rules.tactics.formula", 0); - } - if (tactics_formula == 1) { - int tactics = get_tactics(at.fighter->side, dt.fighter->side); - - /* percentage chance to get this attack */ - if (tactics > 0) { - double tacch = 0.1 * tactics; - if (fval(b->region->terrain, SEA_REGION)) { - ship *sh = at.fighter->unit->ship; - if (sh) - tacch *= sh->type->tac_bonus; - } - if (!chance(tacch)) { - dt.fighter = NULL; - } - } else { - dt.fighter = NULL; - } - } - } - - return dt; -} - -quicklist *fighters(battle * b, const side * vs, int minrow, int maxrow, - int mask) -{ - side *s; - quicklist *fightervp = 0; - - assert(vs != NULL); - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fig; - - if (mask == FS_ENEMY) { - if (!enemy(s, vs)) - continue; - } else if (mask == FS_HELP) { - if (enemy(s, vs) || !allysf(s, vs->faction)) { - continue; - } - } else { - assert(mask == (FS_HELP | FS_ENEMY) || !"invalid alliance state"); - } - for (fig = s->fighters; fig; fig = fig->next) { - int row = get_unitrow(fig, vs); - if (row >= minrow && row <= maxrow) { - ql_push(&fightervp, fig); - } - } - } - - return fightervp; -} - -static void report_failed_spell(battle * b, unit * mage, const spell * sp) -{ - message *m = msg_message("battle::spell_failed", "unit spell", mage, sp); - message_all(b, m); - msg_release(m); -} - -void do_combatmagic(battle * b, combatmagic_t was) -{ - side *s; - region *r = b->region; - castorder *co; - int level, rank, sl; - spellrank spellranks[MAX_SPELLRANK]; - - memset(spellranks, 0, sizeof(spellranks)); - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fig; - for (fig = s->fighters; fig; fig = fig->next) { - unit *mage = fig->unit; - - if (fig->alive <= 0) - continue; /* fighter kann im Kampf getötet worden sein */ - - level = eff_skill(mage, SK_MAGIC, r); - if (level > 0) { - float power; - const spell *sp; - const struct locale *lang = mage->faction->locale; - order *ord; - - switch (was) { - case DO_PRECOMBATSPELL: - sp = get_combatspell(mage, 0); - sl = get_combatspelllevel(mage, 0); - break; - case DO_POSTCOMBATSPELL: - sp = get_combatspell(mage, 2); - sl = get_combatspelllevel(mage, 2); - break; - default: - /* Fehler! */ - return; - } - if (sp == NULL) - continue; - - ord = create_order(K_CAST, lang, "'%s'", spell_name(sp, lang)); - if (!cancast(mage, sp, 1, 1, ord)) { - free_order(ord); - continue; - } - - level = eff_spelllevel(mage, sp, level, 1); - if (sl > 0) - level = _min(sl, level); - if (level < 0) { - report_failed_spell(b, mage, sp); - free_order(ord); - continue; - } - - power = spellpower(r, mage, sp, level, ord); - free_order(ord); - if (power <= 0) { /* Effekt von Antimagie */ - report_failed_spell(b, mage, sp); - pay_spell(mage, sp, level, 1); - } else if (fumble(r, mage, sp, level)) { - report_failed_spell(b, mage, sp); - pay_spell(mage, sp, level, 1); - } else { - co = create_castorder(0, fig->unit, 0, sp, r, level, power, 0, 0, 0); - co->magician.fig = fig; - add_castorder(&spellranks[sp->rank], co); - } - } - } - } - for (rank = 0; rank < MAX_SPELLRANK; rank++) { - for (co = spellranks[rank].begin; co; co = co->next) { - fighter *fig = co->magician.fig; - const spell *sp = co->sp; - int level = co->level; - - if (!sp->cast) { - log_error("spell '%s' has no function.\n", sp->sname); - } else { - level = sp->cast(co); - if (level > 0) { - pay_spell(fig->unit, sp, level, 1); - } - } - } - } - for (rank = 0; rank < MAX_SPELLRANK; rank++) { - free_castorders(spellranks[rank].begin); - } -} - -static int cast_combatspell(troop at, const spell * sp, int level, float force) -{ - castorder co; - - create_castorder(&co, at.fighter->unit, 0, sp, at.fighter->unit->region, level, force, 0, 0, 0); - co.magician.fig = at.fighter; - level = sp->cast(&co); - free_castorder(&co); - if (level > 0) { - pay_spell(at.fighter->unit, sp, level, 1); - } - return level; -} - -static void do_combatspell(troop at) -{ - const spell *sp; - fighter *fi = at.fighter; - unit *caster = fi->unit; - battle *b = fi->side->battle; - region *r = b->region; - quicklist *ql; - int level, qi; - float power; - int fumblechance = 0; - order *ord; - int sl; - const struct locale *lang = caster->faction->locale; - - sp = get_combatspell(caster, 1); - if (sp == NULL) { - fi->magic = 0; /* Hat keinen Kampfzauber, kämpft nichtmagisch weiter */ - return; - } - ord = create_order(K_CAST, lang, "'%s'", spell_name(sp, lang)); - if (!cancast(caster, sp, 1, 1, ord)) { - fi->magic = 0; /* Kann nicht mehr Zaubern, kämpft nichtmagisch weiter */ - return; - } - - level = eff_spelllevel(caster, sp, fi->magic, 1); - if ((sl = get_combatspelllevel(caster, 1)) > 0) - level = _min(level, sl); - - if (fumble(r, caster, sp, level)) { - report_failed_spell(b, caster, sp); - pay_spell(caster, sp, level, 1); - return; - } - - for (qi = 0, ql = b->meffects; ql; ql_advance(&ql, &qi, 1)) { - meffect *mblock = (meffect *) ql_get(ql, qi); - if (mblock->typ == SHIELD_BLOCK) { - if (meffect_blocked(b, mblock, fi->side) != 0) { - fumblechance += mblock->duration; - mblock->duration -= mblock->effect; - } - } - } - - /* Antimagie die Fehlschlag erhöht */ - if (rng_int() % 100 < fumblechance) { - report_failed_spell(b, caster, sp); - pay_spell(caster, sp, level, 1); - free_order(ord); - return; - } - power = spellpower(r, caster, sp, level, ord); - free_order(ord); - if (power <= 0) { /* Effekt von Antimagie */ - report_failed_spell(b, caster, sp); - pay_spell(caster, sp, level, 1); - return; - } - - if (!sp->cast) { - log_error("spell '%s' has no function.\n", sp->sname); - } else { - level = cast_combatspell(at, sp, level, power); - } -} - -/* Sonderattacken: Monster patzern nicht und zahlen auch keine - * Spruchkosten. Da die Spruchstärke direkt durch den Level bestimmt - * wird, wirkt auch keine Antimagie (wird sonst in spellpower - * gemacht) */ - -static void do_extra_spell(troop at, const att * a) -{ - const spell *sp = a->data.sp; - - if (sp->cast == NULL) { - log_error("spell '%s' has no function.\n", sp->sname); - } else { - int level = a->level; - assert(a->level>0); - cast_combatspell(at, sp, level, level * MagicPower()); - } -} - -int skilldiff(troop at, troop dt, int dist) -{ - fighter *af = at.fighter, *df = dt.fighter; - unit *au = af->unit, *du = df->unit; - int is_protected = 0, skdiff = 0; - weapon *awp = select_weapon(at, true, dist > 1); - - skdiff += af->person[at.index].attack; - skdiff -= df->person[dt.index].defence; - - if (df->person[dt.index].flags & FL_SLEEPING) - skdiff += 2; - - /* Effekte durch Rassen */ - if (awp != NULL && u_race(au) == get_race(RC_HALFLING) && dragonrace(u_race(du))) { - skdiff += 5; - } - - if (u_race(au) == get_race(RC_GOBLIN)) { - static int goblin_bonus = -1; - if (goblin_bonus < 0) - goblin_bonus = - get_param_int(global.parameters, "rules.combat.goblinbonus", 10); - if (af->side->size[SUM_ROW] >= df->side->size[SUM_ROW] * goblin_bonus) { - skdiff += 1; - } - } - - if (df->building) { - bool init = false; - static const curse_type *strongwall_ct, *magicwalls_ct; - if (!init) { - strongwall_ct = ct_find("strongwall"); - magicwalls_ct = ct_find("magicwalls"); - init = true; - } - if (df->building->type->protection) { - int beff = df->building->type->protection(df->building, du); - if (beff) { - skdiff -= beff; - is_protected = 2; - } - } - if (strongwall_ct) { - curse *c = get_curse(df->building->attribs, strongwall_ct); - if (curse_active(c)) { - /* wirkt auf alle Gebäude */ - skdiff -= curse_geteffect_int(c); - is_protected = 2; - } - } - if (magicwalls_ct - && curse_active(get_curse(df->building->attribs, magicwalls_ct))) { - /* Verdoppelt Burgenbonus */ - skdiff -= buildingeffsize(df->building, false); - } - } - /* Goblin-Verteidigung - * ist direkt in der Rassentabelle als df_default - */ - - /* Effekte der Waffen */ - skdiff += weapon_effskill(at, dt, awp, true, dist > 1); - if (awp && fval(awp->type, WTF_MISSILE)) { - skdiff -= is_protected; - if (awp->type->modifiers) { - int w; - for (w = 0; awp->type->modifiers[w].value != 0; ++w) { - if (awp->type->modifiers[w].flags & WMF_MISSILE_TARGET) { - /* skill decreases by targeting difficulty (bow -2, catapult -4) */ - skdiff -= awp->type->modifiers[w].value; - break; - } - } - } - } - if (skill_formula == FORMULA_ORIG) { - weapon *dwp = select_weapon(dt, false, dist > 1); - skdiff -= weapon_effskill(dt, at, dwp, false, dist > 1); - } - return skdiff; -} - -static int setreload(troop at) -{ - fighter *af = at.fighter; - const weapon_type *wtype = af->person[at.index].missile->type; - if (wtype->reload == 0) - return 0; - return af->person[at.index].reload = wtype->reload; -} - -int getreload(troop at) -{ - return at.fighter->person[at.index].reload; -} - -static void -debug_hit(troop at, const weapon * awp, troop dt, const weapon * dwp, - int skdiff, int dist, bool success) -{ - fprintf(bdebug, "%.4s/%d [%6s/%d] %s %.4s/%d [%6s/%d] with %d, distance %d\n", - unitid(at.fighter->unit), at.index, - LOC(default_locale, awp ? resourcename(awp->type->itype->rtype, - 0) : "unarmed"), weapon_effskill(at, dt, awp, true, dist > 1), - success ? "hits" : "misses", unitid(dt.fighter->unit), dt.index, - LOC(default_locale, dwp ? resourcename(dwp->type->itype->rtype, - 0) : "unarmed"), weapon_effskill(dt, at, dwp, false, dist > 1), skdiff, - dist); -} - -int hits(troop at, troop dt, weapon * awp) -{ - fighter *af = at.fighter, *df = dt.fighter; - const armor_type *armor, *shield = 0; - int skdiff = 0; - int dist = get_unitrow(af, df->side) + get_unitrow(df, af->side) - 1; - weapon *dwp = select_weapon(dt, false, dist > 1); - - if (!df->alive) - return 0; - if (getreload(at)) - return 0; - if (dist > 1 && (awp == NULL || !fval(awp->type, WTF_MISSILE))) - return 0; - - /* mark this person as hit. */ - df->person[dt.index].flags |= FL_HIT; - - if (af->person[at.index].flags & FL_STUNNED) { - af->person[at.index].flags &= ~FL_STUNNED; - return 0; - } - if ((af->person[at.index].flags & FL_TIRED && rng_int() % 100 < 50) - || (af->person[at.index].flags & FL_SLEEPING)) - return 0; - - /* effect of sp_reeling_arrows combatspell */ - if (af->side->battle->reelarrow && awp && fval(awp->type, WTF_MISSILE) - && rng_double() < 0.5) { - return 0; - } - - skdiff = skilldiff(at, dt, dist); - /* Verteidiger bekommt eine Rüstung */ - armor = select_armor(dt, true); - if (dwp == NULL || (dwp->type->flags & WTF_USESHIELD)) { - shield = select_armor(dt, false); - } - if (contest(skdiff, dt, armor, shield)) { - if (bdebug) { - debug_hit(at, awp, dt, dwp, skdiff, dist, true); - } - return 1; - } - if (bdebug) { - debug_hit(at, awp, dt, dwp, skdiff, dist, false); - } - return 0; -} - -void dazzle(battle * b, troop * td) -{ - /* Nicht kumulativ ! */ - if (td->fighter->person[td->index].flags & FL_DAZZLED) - return; - -#ifdef TODO_RUNESWORD - if (td->fighter->weapon[WP_RUNESWORD].count > td->index) { - return; - } -#endif - if (td->fighter->person[td->index].flags & FL_COURAGE) { - return; - } - - if (td->fighter->person[td->index].flags & FL_DAZZLED) { - return; - } - - td->fighter->person[td->index].flags |= FL_DAZZLED; - td->fighter->person[td->index].defence--; -} - -/* TODO: Gebäude/Schiffe sollten auch zerstörbar sein. Schwierig im Kampf, - * besonders bei Schiffen. */ - -void damage_building(battle * b, building * bldg, int damage_abs) -{ - bldg->size = _max(1, bldg->size - damage_abs); - - /* Wenn Burg, dann gucken, ob die Leute alle noch in das Gebäude passen. */ - - if (bldg->type->protection) { - side *s; - - bldg->sizeleft = bldg->size; - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fig; - for (fig = s->fighters; fig; fig = fig->next) { - if (fig->building == bldg) { - if (bldg->sizeleft >= fig->unit->number) { - fig->building = bldg; - bldg->sizeleft -= fig->unit->number; - } else { - fig->building = NULL; - } - } - } - } - } -} - -static int attacks_per_round(troop t) -{ - return t.fighter->person[t.index].speed; -} - -static void make_heroes(battle * b) -{ - side *s; - static int hero_speed = 0; - if (hero_speed == 0) { - hero_speed = get_param_int(global.parameters, "rules.combat.herospeed", 10); - } - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fig; - for (fig = s->fighters; fig; fig = fig->next) { - unit *u = fig->unit; - if (fval(u, UFL_HERO)) { - int i; - if (!playerrace(u_race(u))) { - log_error("Hero %s is a %s.\n", unitname(u), u_race(u)->_name); - } - for (i = 0; i != u->number; ++i) { - fig->person[i].speed += (hero_speed - 1); - } - } - } - } -} - -static void attack(battle * b, troop ta, const att * a, int numattack) -{ - fighter *af = ta.fighter; - troop td; - unit *au = af->unit; - - switch (a->type) { - case AT_COMBATSPELL: - /* Magier versuchen immer erstmal zu zaubern, erst wenn das - * fehlschlägt, wird af->magic == 0 und der Magier kämpft - * konventionell weiter */ - if (numattack == 0 && af->magic > 0) { - /* wenn der magier in die potenzielle Reichweite von Attacken des - * Feindes kommt, beginnt er auch bei einem Status von KAEMPFE NICHT, - * Kampfzauber zu schleudern: */ - if (count_enemies(b, af, melee_range[0], missile_range[1], - SELECT_ADVANCE | SELECT_DISTANCE | SELECT_FIND)) { - do_combatspell(ta); - } - } - break; - case AT_STANDARD: /* Waffen, mag. Gegenstände, Kampfzauber */ - if (numattack > 0 || af->magic <= 0) { - weapon *wp = ta.fighter->person[ta.index].missile; - int melee = - count_enemies(b, af, melee_range[0], melee_range[1], - SELECT_ADVANCE | SELECT_DISTANCE | SELECT_FIND); - if (melee) - wp = preferred_weapon(ta, true); - /* Sonderbehandlungen */ - - if (getreload(ta)) { - ta.fighter->person[ta.index].reload--; - } else { - bool standard_attack = true; - bool reload = false; - /* spezialattacken der waffe nur, wenn erste attacke in der runde. - * sonst helden mit feuerschwertern zu mächtig */ - if (numattack == 0 && wp && wp->type->attack) { - int dead = 0; - standard_attack = wp->type->attack(&ta, wp->type, &dead); - if (!standard_attack) - reload = true; - af->catmsg += dead; - if (!standard_attack && af->person[ta.index].last_action < b->turn) { - af->person[ta.index].last_action = b->turn; - } - } - if (standard_attack) { - bool missile = false; - if (wp && fval(wp->type, WTF_MISSILE)) - missile = true; - if (missile) { - td = select_opponent(b, ta, missile_range[0], missile_range[1]); - } else { - td = select_opponent(b, ta, melee_range[0], melee_range[1]); - } - if (!td.fighter) - return; - if (ta.fighter->person[ta.index].last_action < b->turn) { - ta.fighter->person[ta.index].last_action = b->turn; - } - reload = true; - if (hits(ta, td, wp)) { - const char *d; - if (wp == NULL) - d = u_race(au)->def_damage; - else if (is_riding(ta)) - d = wp->type->damage[1]; - else - d = wp->type->damage[0]; - terminate(td, ta, a->type, d, missile); - } - } - if (reload && wp && wp->type->reload && !getreload(ta)) { - int i = setreload(ta); - if (bdebug) { - fprintf(bdebug, "%s/%d reloading %d turns\n", unitid(au), - ta.index, i); - } - } - } - } - break; - case AT_SPELL: /* Extra-Sprüche. Kampfzauber in AT_COMBATSPELL! */ - do_extra_spell(ta, a); - break; - case AT_NATURAL: - td = select_opponent(b, ta, melee_range[0], melee_range[1]); - if (!td.fighter) - return; - if (ta.fighter->person[ta.index].last_action < b->turn) { - ta.fighter->person[ta.index].last_action = b->turn; - } - if (hits(ta, td, NULL)) { - terminate(td, ta, a->type, a->data.dice, false); - } - break; - case AT_DRAIN_ST: - td = select_opponent(b, ta, melee_range[0], melee_range[1]); - if (!td.fighter) - return; - if (ta.fighter->person[ta.index].last_action < b->turn) { - ta.fighter->person[ta.index].last_action = b->turn; - } - if (hits(ta, td, NULL)) { - int c = dice_rand(a->data.dice); - while (c > 0) { - if (rng_int() % 2) { - td.fighter->person[td.index].attack -= 1; - } else { - td.fighter->person[td.index].defence -= 1; - } - c--; - } - } - break; - case AT_DRAIN_EXP: - td = select_opponent(b, ta, melee_range[0], melee_range[1]); - if (!td.fighter) - return; - if (ta.fighter->person[ta.index].last_action < b->turn) { - ta.fighter->person[ta.index].last_action = b->turn; - } - if (hits(ta, td, NULL)) { - drain_exp(td.fighter->unit, dice_rand(a->data.dice)); - } - break; - case AT_DAZZLE: - td = select_opponent(b, ta, melee_range[0], melee_range[1]); - if (!td.fighter) - return; - if (ta.fighter->person[ta.index].last_action < b->turn) { - ta.fighter->person[ta.index].last_action = b->turn; - } - if (hits(ta, td, NULL)) { - dazzle(b, &td); - } - break; - case AT_STRUCTURAL: - td = select_opponent(b, ta, melee_range[0], melee_range[1]); - if (!td.fighter) - return; - if (ta.fighter->person[ta.index].last_action < b->turn) { - ta.fighter->person[ta.index].last_action = b->turn; - } - if (td.fighter->unit->ship) { - /* FIXME should use damage_ship here? */ - td.fighter->unit->ship->damage += - DAMAGE_SCALE * dice_rand(a->data.dice); - } else if (td.fighter->unit->building) { - damage_building(b, td.fighter->unit->building, dice_rand(a->data.dice)); - } - } -} - -void do_attack(fighter * af) -{ - troop ta; - unit *au = af->unit; - side *side = af->side; - battle *b = side->battle; - - ta.fighter = af; - - assert(au && au->number); - /* Da das Zuschlagen auf Einheiten und nicht auf den einzelnen - * Kämpfern beruht, darf die Reihenfolge und Größe der Einheit keine - * Rolle spielen, Das tut sie nur dann, wenn jeder, der am Anfang der - * Runde lebte, auch zuschlagen darf. Ansonsten ist der, der zufällig - * mit einer großen Einheit zuerst drankommt, extrem bevorteilt. */ - ta.index = af->fighting; - - while (ta.index--) { - /* Wir suchen eine beliebige Feind-Einheit aus. An der können - * wir feststellen, ob noch jemand da ist. */ - int apr, attacks = attacks_per_round(ta); - if (!count_enemies(b, af, FIGHT_ROW, LAST_ROW, SELECT_FIND)) - break; - - for (apr = 0; apr != attacks; ++apr) { - int a; - for (a = 0; a != 10 && u_race(au)->attack[a].type != AT_NONE; ++a) { - if (apr > 0) { - /* Wenn die Waffe nachladen muss, oder es sich nicht um einen - * Waffen-Angriff handelt, dann gilt der Speed nicht. */ - if (u_race(au)->attack[a].type != AT_STANDARD) - continue; - else { - weapon *wp = preferred_weapon(ta, true); - if (wp != NULL && wp->type->reload) - continue; - } - } - attack(b, ta, &(u_race(au)->attack[a]), apr); - } - } - } - /* Der letzte Katapultschütze setzt die - * Ladezeit neu und generiert die Meldung. */ - if (af->catmsg >= 0) { - struct message *m = - msg_message("battle::killed", "unit dead", au, af->catmsg); - message_all(b, m); - msg_release(m); - af->catmsg = -1; - } -} - -void do_regenerate(fighter * af) -{ - troop ta; - unit *au = af->unit; - - ta.fighter = af; - ta.index = af->fighting; - - while (ta.index--) { - af->person[ta.index].hp += effskill(au, SK_STAMINA); - af->person[ta.index].hp = _min(unit_max_hp(au), af->person[ta.index].hp); - } -} - -static void add_tactics(tactics * ta, fighter * fig, int value) -{ - if (value == 0 || value < ta->value) - return; - if (value > ta->value) { - ql_free(ta->fighters); - ta->fighters = 0; - } - ql_push(&ta->fighters, fig); - ql_push(&fig->side->battle->leaders, fig); - ta->value = value; -} - -static double horsebonus(const unit * u) -{ - const item_type *it_horse, *it_elvenhorse, *it_charger; - int n1 = 0, n2 = 0, n3 = 0; - item *itm; - int skl = eff_skill(u, SK_RIDING, u->region); - const resource_type *rtype; - - if (skl < 1) return 0.0; - - it_horse = ((rtype = get_resourcetype(R_HORSE))!=NULL) ? rtype->itype : 0; - it_elvenhorse = ((rtype = get_resourcetype(R_UNICORN))!=NULL) ? rtype->itype : 0; - it_charger = ((rtype = get_resourcetype(R_CHARGER))!=NULL) ? rtype->itype : 0; - - for (itm=u->items; itm; itm = itm->next) { - if (itm->type->flags & ITF_ANIMAL) { - if (itm->type == it_elvenhorse) - n3 += itm->number; - else if (itm->type == it_charger) - n2 += itm->number; - else if (itm->type == it_horse) - n1 += itm->number; - } - } - if (skl >= 5 && n3 >= u->number) - return 0.30; - if (skl >= 3 && n2 + n3 >= u->number) - return 0.20; - if (skl >= 1 && n1 + n2 + n3 >= u->number) - return 0.10; - return 0.0F; -} - -double fleechance(unit * u) -{ - double c = 0.20; /* Fluchtwahrscheinlichkeit in % */ - region *r = u->region; - attrib *a = a_find(u->attribs, &at_fleechance); - /* Einheit u versucht, dem Getümmel zu entkommen */ - - c += (eff_skill(u, SK_STEALTH, r) * 0.05); - c += horsebonus(u); - - if (u_race(u) == get_race(RC_HALFLING)) { - c += 0.20; - c = _min(c, 0.90); - } else { - c = _min(c, 0.75); - } - - if (a != NULL) - c += a->data.flt; - - return c; -} - -/** add a new army to the conflict - * beware: armies need to be added _at the beginning_ of the list because - * otherwise join_allies() will get into trouble */ -side *make_side(battle * b, const faction * f, const group * g, - unsigned int flags, const faction * stealthfaction) -{ - side *s1 = b->sides + b->nsides; - bfaction *bf; - - if (fval(b->region->terrain, SEA_REGION)) { - /* every fight in an ocean is short */ - flags |= SIDE_HASGUARDS; - } else { - unit *u; - for (u = b->region->units; u; u = u->next) { - if (is_guard(u, HELP_ALL)) { - if (alliedunit(u, f, HELP_GUARD)) { - flags |= SIDE_HASGUARDS; - break; - } - } - } - } - - s1->battle = b; - s1->group = g; - s1->flags = flags; - s1->stealthfaction = stealthfaction; - for (bf = b->factions; bf; bf = bf->next) { - faction *f2 = bf->faction; - - if (f2 == f) { - s1->bf = bf; - s1->faction = f2; - s1->index = b->nsides++; - s1->nextF = bf->sides; - bf->sides = s1; - assert(b->nsides <= MAXSIDES); - break; - } - } - assert(bf); - return s1; -} - -troop select_ally(fighter * af, int minrow, int maxrow, int allytype) -{ - side *as = af->side; - battle *b = as->battle; - side *ds; - int allies = count_allies(as, minrow, maxrow, SELECT_ADVANCE, allytype); - - if (!allies) { - return no_troop; - } - allies = rng_int() % allies; - - for (ds = b->sides; ds != b->sides + b->nsides; ++ds) { - if ((allytype == ALLY_ANY && helping(as, ds)) || (allytype == ALLY_SELF - && as->faction == ds->faction)) { - fighter *df; - for (df = ds->fighters; df; df = df->next) { - int dr = get_unitrow(df, NULL); - if (dr >= minrow && dr <= maxrow) { - if (df->alive - df->removed > allies) { - troop dt; - assert(allies >= 0); - dt.index = allies; - dt.fighter = df; - return dt; - } - allies -= df->alive; - } - } - } - } - assert(!"we should never have gotten here"); - return no_troop; -} - -static int loot_quota(const unit * src, const unit * dst, - const item_type * type, int n) -{ - static float divisor = -1; - if (dst && src && src->faction != dst->faction) { - if (divisor < 0) { - divisor = get_param_flt(global.parameters, "rules.items.loot_divisor", 1); - assert(divisor == 0 || divisor >= 1); - } - if (divisor >= 1) { - double r = n / divisor; - int x = (int)r; - - r = r - x; - if (chance(r)) - ++x; - - return x; - } - } - return n; -} - -static void loot_items(fighter * corpse) -{ - unit *u = corpse->unit; - item *itm = u->items; - battle *b = corpse->side->battle; - int dead = dead_fighters(corpse); - - if (dead <= 0) - return; - - while (itm) { - float lootfactor = dead / (float)u->number; /* only loot the dead! */ - int maxloot = (int)(itm->number * lootfactor); - if (maxloot > 0) { - int i = _min(10, maxloot); - for (; i != 0; --i) { - int loot = maxloot / i; - - if (loot > 0) { - fighter *fig = NULL; - int looting = 0; - int maxrow = 0; - /* mustloot: we absolutely, positively must have somebody loot this thing */ - int mustloot = itm->type->flags & (ITF_CURSED | ITF_NOTLOST); - - itm->number -= loot; - maxloot -= loot; - - if (is_monsters(u->faction) && (loot_rules & LOOT_MONSTERS)) { - looting = 1; - } else if (loot_rules & LOOT_OTHERS) { - looting = 1; - } else if (loot_rules & LOOT_SELF) { - looting = 2; - } - if (looting) { - if (mustloot) { - maxrow = LAST_ROW; - } else if (loot_rules & LOOT_KEEPLOOT) { - int lootchance = 50 + b->keeploot; - if (rng_int() % 100 < lootchance) { - maxrow = BEHIND_ROW; - } - } else { - maxrow = LAST_ROW; - } - } - if (maxrow > 0) { - if (looting == 1) { - /* enemies get dibs */ - fig = select_enemy(corpse, FIGHT_ROW, maxrow, 0).fighter; - } - if (!fig) { - /* self and allies get second pick */ - fig = select_ally(corpse, FIGHT_ROW, LAST_ROW, ALLY_SELF).fighter; - } - } - - if (fig) { - int trueloot = - mustloot ? loot : loot_quota(corpse->unit, fig->unit, itm->type, - loot); - if (trueloot > 0) { - item *l = fig->loot; - while (l && l->type != itm->type) - l = l->next; - if (!l) { - l = calloc(sizeof(item), 1); - l->next = fig->loot; - fig->loot = l; - l->type = itm->type; - } - l->number += trueloot; - } - } - } - } - } - itm = itm->next; - } -} - -static bool seematrix(const faction * f, const side * s) -{ - if (f == s->faction) - return true; - if (s->flags & SIDE_STEALTH) - return false; - return true; -} - -static double PopulationDamage(void) -{ - static double value = -1.0; - if (value < 0) { - int damage = - get_param_int(global.parameters, "rules.combat.populationdamage", - BATTLE_KILLS_PEASANTS); - value = damage / 100.0; - } - return value; -} - -static void battle_effects(battle * b, int dead_players) -{ - region *r = b->region; - int dead_peasants = - _min(rpeasants(r), (int)(dead_players * PopulationDamage())); - if (dead_peasants) { - deathcounts(r, dead_peasants + dead_players); - chaoscounts(r, dead_peasants / 2); - rsetpeasants(r, rpeasants(r) - dead_peasants); - } -} - -static void reorder_fleeing(region * r) -{ - unit **usrc = &r->units; - unit **udst = &r->units; - unit *ufirst = NULL; - unit *u; - - for (; *udst; udst = &u->next) { - u = *udst; - } - - for (u = *usrc; u != ufirst; u = *usrc) { - if (u->next && fval(u, UFL_FLEEING)) { - *usrc = u->next; - *udst = u; - udst = &u->next; - if (!ufirst) - ufirst = u; - } else { - usrc = &u->next; - } - } - *udst = NULL; -} - -static void aftermath(battle * b) -{ - region *r = b->region; - ship *sh; - side *s; - int dead_players = 0; - bfaction *bf; - bool ships_damaged = (bool) (b->turn + (b->has_tactics_turn ? 1 : 0) > 2); /* only used for ship damage! */ - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *df; - s->dead = 0; - - for (df = s->fighters; df; df = df->next) { - unit *du = df->unit; - int dead = dead_fighters(df); - - /* tote insgesamt: */ - s->dead += dead; - /* Tote, die wiederbelebt werde koennen: */ - if (playerrace(u_race(df->unit))) { - s->casualties += dead; - } - if (df->hits + df->kills) { - struct message *m = - msg_message("killsandhits", "unit hits kills", du, df->hits, - df->kills); - message_faction(b, du->faction, m); - msg_release(m); - } - } - } - - /* POSTCOMBAT */ - do_combatmagic(b, DO_POSTCOMBATSPELL); - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - int snumber = 0; - fighter *df; - bool relevant = false; /* Kampf relevant für diese Partei? */ - if (!fval(s, SIDE_HASGUARDS)) { - relevant = true; - } - s->flee = 0; - - for (df = s->fighters; df; df = df->next) { - unit *du = df->unit; - int dead = dead_fighters(df); - int sum_hp = 0; - int n; - int flags = 0; - - for (n = 0; n != df->alive; ++n) { - if (df->person[n].hp > 0) { - sum_hp += df->person[n].hp; - } - } - snumber += du->number; - if (relevant) { - flags = UFL_LONGACTION | UFL_NOTMOVING; - if (du->status == ST_FLEE) { - flags -= UFL_NOTMOVING; - } - } - if (df->alive == 0) { - flags |= UFL_DEAD; - } - if (flags) { - fset(du, flags); - } - if (sum_hp + df->run.hp < du->hp) { - /* someone on the ship got damaged, damage the ship */ - ship *sh = du->ship ? du->ship : leftship(du); - if (sh) - fset(sh, SF_DAMAGED); - } - - if (df->alive == du->number) { - du->hp = sum_hp; - continue; /* nichts passiert */ - } else if (df->run.hp) { - if (df->alive == 0) { - /* Report the casualties */ - reportcasualties(b, df, dead); - - /* Zuerst dürfen die Feinde plündern, die mitgenommenen Items - * stehen in fig->run.items. Dann werden die Fliehenden auf - * die leere (tote) alte Einheit gemapt */ - if (!fval(df, FIG_NOLOOT)) { - loot_items(df); - } - scale_number(du, df->run.number); - du->hp = df->run.hp; - setguard(du, GUARD_NONE); - /* must leave ships or buildings, or a stealthy hobbit - * can hold castles indefinitely */ - if (!fval(r->terrain, SEA_REGION)) { - leave(du, true); /* even region owners have to flee */ - } - fset(du, UFL_FLEEING); - } else { - /* nur teilweise geflohene Einheiten mergen sich wieder */ - df->alive += df->run.number; - s->size[0] += df->run.number; - s->size[statusrow(df->status)] += df->run.number; - s->alive += df->run.number; - sum_hp += df->run.hp; - df->run.number = 0; - df->run.hp = 0; - /* df->run.region = NULL; */ - - reportcasualties(b, df, dead); - - scale_number(du, df->alive); - du->hp = sum_hp; - } - } else { - if (df->alive == 0) { - /* alle sind tot, niemand geflohen. Einheit auflösen */ - df->run.number = 0; - df->run.hp = 0; - - /* Report the casualties */ - reportcasualties(b, df, dead); - - /* Distribute Loot */ - loot_items(df); - - setguard(du, GUARD_NONE); - scale_number(du, 0); - } else { - df->run.number = 0; - df->run.hp = 0; - - reportcasualties(b, df, dead); - - scale_number(du, df->alive); - du->hp = sum_hp; - } - } - s->flee += df->run.number; - - if (playerrace(u_race(du))) { - /* tote im kampf werden zu regionsuntoten: - * for each of them, a peasant will die as well */ - dead_players += dead; - } - if (du->hp < du->number) { - log_error("%s has less hitpoints (%u) than people (%u)\n", itoa36(du->no), du->hp, du->number); - du->hp = du->number; - } - } - s->alive += s->healed; - assert(snumber == s->flee + s->alive + s->dead); - } - - battle_effects(b, dead_players); - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - message *seen = msg_message("battle::army_report", - "index abbrev dead fled survived", - army_index(s), sideabkz(s, false), s->dead, s->flee, s->alive); - message *unseen = msg_message("battle::army_report", - "index abbrev dead fled survived", - army_index(s), "-?-", s->dead, s->flee, s->alive); - - for (bf = b->factions; bf; bf = bf->next) { - faction *f = bf->faction; - message *m = seematrix(f, s) ? seen : unseen; - - message_faction(b, f, m); - } - - msg_release(seen); - msg_release(unseen); - } - - /* Wir benutzen drifted, um uns zu merken, ob ein Schiff - * schonmal Schaden genommen hat. (moved und drifted - * sollten in flags überführt werden */ - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *df; - - for (df = s->fighters; df; df = df->next) { - unit *du = df->unit; - item *l; - - /* Beute verteilen */ - for (l = df->loot; l; l = l->next) { - const item_type *itype = l->type; - message *m = - msg_message("battle_loot", "unit amount item", du, l->number, - itype->rtype); - message_faction(b, du->faction, m); - msg_release(m); - i_change(&du->items, itype, l->number); - } - - /* Wenn sich die Einheit auf einem Schiff befindet, wird - * dieses Schiff beschädigt. Andernfalls ein Schiff, welches - * evt. zuvor verlassen wurde. */ - if (ships_damaged) { - if (du->ship) - sh = du->ship; - else - sh = leftship(du); - - if (sh && fval(sh, SF_DAMAGED)) { - int n = b->turn - 2; - if (n > 0) { - float dmg = - get_param_flt(global.parameters, "rules.ship.damage.battleround", - 0.05F); - damage_ship(sh, dmg * n); - freset(sh, SF_DAMAGED); - } - } - } - } - } - - if (ships_damaged) { - ship **sp = &r->ships; - - while (*sp) { - ship *sh = *sp; - freset(sh, SF_DAMAGED); - if (sh->damage >= sh->size * DAMAGE_SCALE) { - remove_ship(sp, sh); - } - if (*sp == sh) - sp = &sh->next; - } - } - - reorder_fleeing(r); - - if (bdebug) { - fprintf(bdebug, "The battle lasted %d turns, %s and %s.\n", - b->turn, - b->has_tactics_turn ? "had a tactic turn" : "had no tactic turn", - ships_damaged ? "was relevant" : "was not relevant."); - } -} - -static void battle_punit(unit * u, battle * b) -{ - bfaction *bf; - strlist *S, *x; - - for (bf = b->factions; bf; bf = bf->next) { - faction *f = bf->faction; - - S = 0; - spunit(&S, f, u, 4, see_battle); - for (x = S; x; x = x->next) { - fbattlerecord(b, f, x->s); - if (bdebug && u->faction == f) { - fputs(x->s, bdebug); - fputc('\n', bdebug); - } - } - if (S) - freestrlist(S); - } -} - -static void print_fighters(battle * b, const side * s) -{ - fighter *df; - int row; - - for (row = 1; row != NUMROWS; ++row) { - message *m = NULL; - - for (df = s->fighters; df; df = df->next) { - unit *du = df->unit; - int thisrow = statusrow(df->unit->status); - - if (row == thisrow) { - if (m == NULL) { - m = msg_message("battle::row_header", "row", row); - message_all(b, m); - } - battle_punit(du, b); - } - } - if (m != NULL) - msg_release(m); - } -} - -bool is_attacker(const fighter * fig) -{ - return fval(fig, FIG_ATTACKER) != 0; -} - -static void set_attacker(fighter * fig) -{ - fset(fig, FIG_ATTACKER); -} - -static void print_header(battle * b) -{ - bfaction *bf; - char zText[32 * MAXSIDES]; - - for (bf = b->factions; bf; bf = bf->next) { - message *m; - faction *f = bf->faction; - const char *lastf = NULL; - bool first = false; - side *s; - char *bufp = zText; - size_t size = sizeof(zText) - 1; - int bytes; - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *df; - for (df = s->fighters; df; df = df->next) { - if (is_attacker(df)) { - if (first) { - bytes = (int)strlcpy(bufp, ", ", size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - } - if (lastf) { - bytes = (int)strlcpy(bufp, (const char *)lastf, size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - first = true; - } - if (seematrix(f, s)) - lastf = sidename(s); - else - lastf = LOC(f->locale, "unknown_faction_dative"); - break; - } - } - } - if (first) { - bytes = (int)strlcpy(bufp, " ", size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - bytes = (int)strlcpy(bufp, (const char *)LOC(f->locale, "and"), size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - bytes = (int)strlcpy(bufp, " ", size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - } - if (lastf) { - bytes = (int)strlcpy(bufp, (const char *)lastf, size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - } - - m = msg_message("battle::starters", "factions", zText); - message_faction(b, f, m); - msg_release(m); - } -} - -static void print_stats(battle * b) -{ - side *s2; - side *s; - int i = 0; - for (s = b->sides; s != b->sides + b->nsides; ++s) { - bfaction *bf; - - ++i; - - for (bf = b->factions; bf; bf = bf->next) { - faction *f = bf->faction; - const char *loc_army = LOC(f->locale, "battle_army"); - char *bufp; - const char *header; - size_t rsize, size; - int komma; - const char *sname = - seematrix(f, s) ? sidename(s) : LOC(f->locale, "unknown_faction"); - message *msg; - char buf[1024]; - - message_faction(b, f, msg_separator); - - msg = msg_message("battle_army", "index name", army_index(s), sname); - message_faction(b, f, msg); - msg_release(msg); - - bufp = buf; - size = sizeof(buf); - komma = 0; - header = LOC(f->locale, "battle_opponents"); - - for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { - if (enemy(s2, s)) { - const char *abbrev = seematrix(f, s2) ? sideabkz(s2, false) : "-?-"; - rsize = slprintf(bufp, size, "%s %s %d(%s)", - komma++ ? "," : (const char *)header, loc_army, army_index(s2), - abbrev); - if (rsize > size) - rsize = size - 1; - size -= rsize; - bufp += rsize; - } - } - if (komma) - fbattlerecord(b, f, buf); - - bufp = buf; - size = sizeof(buf); - komma = 0; - header = LOC(f->locale, "battle_helpers"); - - for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { - if (friendly(s2, s)) { - const char *abbrev = seematrix(f, s2) ? sideabkz(s2, false) : "-?-"; - rsize = slprintf(bufp, size, "%s %s %d(%s)", - komma++ ? "," : (const char *)header, loc_army, army_index(s2), - abbrev); - if (rsize > size) - rsize = size - 1; - size -= rsize; - bufp += rsize; - } - } - if (komma) - fbattlerecord(b, f, buf); - - bufp = buf; - size = sizeof(buf); - komma = 0; - header = LOC(f->locale, "battle_attack"); - - for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { - if (s->relations[s2->index] & E_ATTACKING) { - const char *abbrev = seematrix(f, s2) ? sideabkz(s2, false) : "-?-"; - rsize = - slprintf(bufp, size, "%s %s %d(%s)", - komma++ ? "," : (const char *)header, loc_army, army_index(s2), - abbrev); - if (rsize > size) - rsize = size - 1; - size -= rsize; - bufp += rsize; - } - } - if (komma) - fbattlerecord(b, f, buf); - } - - if (bdebug && s->faction) { - if (f_get_alliance(s->faction)) { - fprintf(bdebug, "##### %s (%s/%d)\n", s->faction->name, - itoa36(s->faction->no), - s->faction->alliance ? s->faction->alliance->id : 0); - } else { - fprintf(bdebug, "##### %s (%s)\n", s->faction->name, - itoa36(s->faction->no)); - } - } - print_fighters(b, s); - } - - message_all(b, msg_separator); - - /* Besten Taktiker ermitteln */ - - b->max_tactics = 0; - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - if (!ql_empty(s->leader.fighters)) { - b->max_tactics = _max(b->max_tactics, s->leader.value); - } - } - - if (b->max_tactics > 0) { - for (s = b->sides; s != b->sides + b->nsides; ++s) { - if (s->leader.value == b->max_tactics) { - quicklist *ql; - int qi; - - for (qi = 0, ql = s->leader.fighters; ql; ql_advance(&ql, &qi, 1)) { - fighter *tf = (fighter *) ql_get(ql, qi); - unit *u = tf->unit; - message *m = NULL; - if (!is_attacker(tf)) { - m = msg_message("battle::tactics_lost", "unit", u); - } else { - m = msg_message("battle::tactics_won", "unit", u); - } - message_all(b, m); - msg_release(m); - } - } - } - } -} - -static int weapon_weight(const weapon * w, bool missile) -{ - if (missile == i2b(fval(w->type, WTF_MISSILE))) { - return w->attackskill + w->defenseskill; - } - return 0; -} - -side * get_side(battle * b, const struct unit * u) -{ - side * s; - for (s = b->sides; s != b->sides + b->nsides; ++s) { - if (s->faction==u->faction) { - fighter * fig; - for (fig=s->fighters;fig;fig=fig->next) { - if (fig->unit==u) { - return s; - } - } - } - } - return 0; -} - -side * find_side(battle * b, const faction * f, const group * g, int flags, const faction * stealthfaction) -{ - side * s; - static int rule_anon_battle = -1; - - if (rule_anon_battle < 0) { - rule_anon_battle = get_param_int(global.parameters, "rules.stealth.anon_battle", 1); - } - for (s = b->sides; s != b->sides + b->nsides; ++s) { - if (s->faction == f && s->group == g) { - int s1flags = flags | SIDE_HASGUARDS; - int s2flags = s->flags | SIDE_HASGUARDS; - if (rule_anon_battle && s->stealthfaction != stealthfaction) { - continue; - } - if (s1flags == s2flags) { - return s; - } - } - } - return 0; -} - -fighter *make_fighter(battle * b, unit * u, side * s1, bool attack) -{ -#define WMAX 20 - weapon weapons[WMAX]; - int owp[WMAX]; - int dwp[WMAX]; - int w = 0; - region *r = b->region; - item *itm; - fighter *fig = NULL; - int h, i, tactics = eff_skill(u, SK_TACTICS, r); - int berserk; - int strongmen; - int speeded = 0, speed = 1; - bool pr_aid = false; - int rest; - const group *g = NULL; - const attrib *a = a_find(u->attribs, &at_otherfaction); - const faction *stealthfaction = a ? get_otherfaction(a) : NULL; - unsigned int flags = 0; - - assert(u->number); - if (fval(u, UFL_ANON_FACTION) != 0) - flags |= SIDE_STEALTH; - if (!(AllianceAuto() & HELP_FIGHT) && fval(u, UFL_GROUP)) { - const attrib *agroup = a_find(u->attribs, &at_group); - if (agroup != NULL) - g = (const group *)agroup->data.v; - } - - /* Illusionen und Zauber kaempfen nicht */ - if (fval(u_race(u), RCF_ILLUSIONARY) || idle(u->faction) || u->number == 0) { - return NULL; - } - if (s1 == NULL) { - s1 = find_side(b, u->faction, g, flags, stealthfaction); - /* aliances are moved out of make_fighter and will be handled later */ - if (!s1) { - s1 = make_side(b, u->faction, g, flags, stealthfaction); - } else if (!stealthfaction) { - s1->stealthfaction = NULL; - } - /* Zu diesem Zeitpunkt ist attacked noch 0, da die Einheit für noch - * keinen Kampf ausgewählt wurde (sonst würde ein fighter existieren) */ - } - fig = (struct fighter*)calloc(1, sizeof(struct fighter)); - - fig->next = s1->fighters; - s1->fighters = fig; - - fig->unit = u; - /* In einer Burg muß man a) nicht Angreifer sein, und b) drin sein, und - * c) noch Platz finden. d) menschanähnlich sein */ - if (attack) { - set_attacker(fig); - } else { - building *bld = u->building; - if (bld && bld->sizeleft >= u->number && playerrace(u_race(u))) { - fig->building = bld; - fig->building->sizeleft -= u->number; - } - } - fig->status = u->status; - fig->side = s1; - fig->alive = u->number; - fig->side->alive += u->number; - fig->side->battle->alive += u->number; - fig->catmsg = -1; - - /* Freigeben nicht vergessen! */ - fig->person = (struct person*)calloc(fig->alive, sizeof(struct person)); - - h = u->hp / u->number; - assert(h); - rest = u->hp % u->number; - - /* Effekte von Sprüchen */ - - { - static const curse_type *speed_ct; - speed_ct = ct_find("speed"); - if (speed_ct) { - curse *c = get_curse(u->attribs, speed_ct); - if (c) { - speeded = get_cursedmen(u, c); - speed = curse_geteffect_int(c); - } - } - } - - /* Effekte von Alchemie */ - berserk = get_effect(u, oldpotiontype[P_BERSERK]); - /* change_effect wird in ageing gemacht */ - - /* Effekte von Artefakten */ - strongmen = _min(fig->unit->number, trollbelts(u)); - - /* Hitpoints, Attack- und Defence-Boni für alle Personen */ - for (i = 0; i < fig->alive; i++) { - assert(i < fig->unit->number); - fig->person[i].hp = h; - if (i < rest) - fig->person[i].hp++; - - if (i < speeded) - fig->person[i].speed = speed; - else - fig->person[i].speed = 1; - - if (i < berserk) { - fig->person[i].attack++; - } - /* Leute mit einem Aid-Prayer bekommen +1 auf fast alles. */ - if (pr_aid) { - fig->person[i].attack++; - fig->person[i].defence++; - fig->person[i].damage++; - fig->person[i].damage_rear++; - fig->person[i].flags |= FL_COURAGE; - } - /* Leute mit Kraftzauber machen +2 Schaden im Nahkampf. */ - if (i < strongmen) { - fig->person[i].damage += 2; - } - } - - /* Für alle Waffengattungen wird bestimmt, wie viele der Personen mit - * ihr kämpfen könnten, und was ihr Wert darin ist. */ - if (u_race(u)->battle_flags & BF_EQUIPMENT) { - int oi = 0, di = 0; - for (itm = u->items; itm && w != WMAX; itm = itm->next) { - const weapon_type *wtype = resource2weapon(itm->type->rtype); - if (wtype == NULL || itm->number == 0) - continue; - weapons[w].attackskill = weapon_skill(wtype, u, true); - weapons[w].defenseskill = weapon_skill(wtype, u, false); - if (weapons[w].attackskill >= 0 || weapons[w].defenseskill >= 0) { - weapons[w].type = wtype; - weapons[w].used = 0; - weapons[w].count = itm->number; - ++w; - } - assert(w != WMAX); - } - fig->weapons = (weapon *) calloc(sizeof(weapon), w + 1); - memcpy(fig->weapons, weapons, w * sizeof(weapon)); - - for (i = 0; i != w; ++i) { - int j, o = 0, d = 0; - for (j = 0; j != i; ++j) { - if (weapon_weight(fig->weapons + j, - true) >= weapon_weight(fig->weapons + i, true)) - ++d; - if (weapon_weight(fig->weapons + j, - false) >= weapon_weight(fig->weapons + i, false)) - ++o; - } - for (j = i + 1; j != w; ++j) { - if (weapon_weight(fig->weapons + j, - true) > weapon_weight(fig->weapons + i, true)) - ++d; - if (weapon_weight(fig->weapons + j, - false) > weapon_weight(fig->weapons + i, false)) - ++o; - } - owp[o] = i; - dwp[d] = i; - } - /* jetzt enthalten owp und dwp eine absteigend schlechter werdende Liste der Waffen - * oi and di are the current index to the sorted owp/dwp arrays - * owp, dwp contain indices to the figther::weapons array */ - - /* hand out melee weapons: */ - for (i = 0; i != fig->alive; ++i) { - int wpless = weapon_skill(NULL, u, true); - while (oi != w - && (fig->weapons[owp[oi]].used == fig->weapons[owp[oi]].count - || fval(fig->weapons[owp[oi]].type, WTF_MISSILE))) { - ++oi; - } - if (oi == w) - break; /* no more weapons available */ - if (weapon_weight(fig->weapons + owp[oi], false) <= wpless) { - continue; /* we fight better with bare hands */ - } - fig->person[i].melee = &fig->weapons[owp[oi]]; - ++fig->weapons[owp[oi]].used; - } - /* hand out missile weapons (from back to front, in case of mixed troops). */ - for (di = 0, i = fig->alive; i-- != 0;) { - while (di != w - && (fig->weapons[dwp[di]].used == fig->weapons[dwp[di]].count - || !fval(fig->weapons[dwp[di]].type, WTF_MISSILE))) { - ++di; - } - if (di == w) - break; /* no more weapons available */ - if (weapon_weight(fig->weapons + dwp[di], true) > 0) { - fig->person[i].missile = &fig->weapons[dwp[di]]; - ++fig->weapons[dwp[di]].used; - } - } - } - - s1->size[statusrow(fig->status)] += u->number; - s1->size[SUM_ROW] += u->number; - if (u_race(u)->battle_flags & BF_NOBLOCK) { - s1->nonblockers[statusrow(fig->status)] += u->number; - } - - if (u_race(fig->unit)->flags & RCF_HORSE) { - fig->horses = fig->unit->number; - fig->elvenhorses = 0; - } else { - const resource_type *rt_horse = 0; - const resource_type *rt_elvenhorse = 0; - rt_elvenhorse = get_resourcetype(R_UNICORN); - rt_horse = get_resourcetype(R_CHARGER); - if (!rt_horse) { - rt_horse = get_resourcetype(R_HORSE); - } - fig->horses = rt_horse ? i_get(u->items, rt_horse->itype) : 0; - fig->elvenhorses = rt_elvenhorse ? i_get(u->items, rt_elvenhorse->itype) : 0; - } - - if (u_race(u)->battle_flags & BF_EQUIPMENT) { - for (itm = u->items; itm; itm = itm->next) { - if (itm->type->rtype->atype) { - if (i_canuse(u, itm->type)) { - struct armor *adata = (struct armor *)malloc(sizeof(armor)), **aptr; - adata->atype = itm->type->rtype->atype; - adata->count = itm->number; - for (aptr = &fig->armors; *aptr; aptr = &(*aptr)->next) { - if (adata->atype->prot > (*aptr)->atype->prot) - break; - } - adata->next = *aptr; - *aptr = adata; - } - } - } - } - - /* Jetzt muß noch geschaut werden, wo die Einheit die jeweils besten - * Werte hat, das kommt aber erst irgendwo später. Ich entscheide - * wärend des Kampfes, welche ich nehme, je nach Gegner. Deswegen auch - * keine addierten boni. */ - - /* Zuerst mal die Spezialbehandlung gewisser Sonderfälle. */ - fig->magic = eff_skill(u, SK_MAGIC, r); - - if (fig->horses) { - if (!fval(r->terrain, CAVALRY_REGION) || r_isforest(r) - || eff_skill(u, SK_RIDING, r) < CavalrySkill() - || u_race(u) == get_race(RC_TROLL) || fval(u, UFL_WERE)) - fig->horses = 0; - } - - if (fig->elvenhorses) { - if (eff_skill(u, SK_RIDING, r) < 5 || u_race(u) == get_race(RC_TROLL) - || fval(u, UFL_WERE)) - fig->elvenhorses = 0; - } - - /* Schauen, wie gut wir in Taktik sind. */ - if (tactics > 0 && u_race(u) == get_race(RC_INSECT)) - tactics -= 1 - (int)log10(fig->side->size[SUM_ROW]); -#ifdef TACTICS_MODIFIER - if (tactics > 0 && statusrow(fig->status) == FIGHT_ROW) - tactics += TACTICS_MODIFIER; - if (tactics > 0 && statusrow(fig->status) > BEHIND_ROW) { - tactics -= TACTICS_MODIFIER; - } -#endif - - if (tactics > 0) { - int bonus = 0; - - for (i = 0; i < fig->alive; i++) { - int p_bonus = 0; - int rnd; - - do { - rnd = rng_int() % 100; - if (rnd >= 40 && rnd <= 69) - p_bonus += 1; - else if (rnd <= 89) - p_bonus += 2; - else - p_bonus += 3; - } while (rnd >= 97); - bonus = _max(p_bonus, bonus); - } - tactics += bonus; - } - - add_tactics(&fig->side->leader, fig, tactics); - ++b->nfighters; - return fig; -} - -fighter * get_fighter(battle * b, const struct unit * u) -{ - side * s; - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fig; - if (s->faction == u->faction) { - for (fig = s->fighters; fig; fig = fig->next) { - if (fig->unit == u) { - return fig; - } - } - } - } - return 0; -} - -static int join_battle(battle * b, unit * u, bool attack, fighter ** cp) -{ - side *s; - fighter *c = NULL; - - if (!attack) { - attrib *a = a_find(u->attribs, &at_fleechance); - if (a != NULL) { - if (rng_double() <= a->data.flt) { - *cp = NULL; - return false; - } - } - } - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fig; - if (s->faction == u->faction) { - for (fig = s->fighters; fig; fig = fig->next) { - if (fig->unit == u) { - c = fig; - if (attack) { - set_attacker(fig); - } - break; - } - } - } - } - if (!c) { - *cp = make_fighter(b, u, NULL, attack); - return *cp != NULL; - } - *cp = c; - return false; -} - -static const char *simplename(region * r) -{ - int i; - static char name[17]; - const char *cp = rname(r, default_locale); - for (i = 0; *cp && i != 16; ++i, ++cp) { - int c = *(unsigned char *)cp; - while (c && !isalpha(c) && !isxspace(c)) { - ++cp; - c = *(unsigned char *)cp; - } - if (isxspace(c)) - name[i] = '_'; - else - name[i] = *cp; - if (c == 0) - break; - } - name[i] = 0; - return name; -} - -battle *make_battle(region * r) -{ - battle *b = (battle *)calloc(1, sizeof(battle)); - unit *u; - bfaction *bf; - building * bld; - static int max_fac_no = 0; /* need this only once */ - - /* Alle Mann raus aus der Burg! */ - for (bld = r->buildings; bld != NULL; bld = bld->next) - bld->sizeleft = bld->size; - - if (battledebug) { - char zText[MAX_PATH]; - char zFilename[MAX_PATH]; - sprintf(zText, "%s/battles", basepath()); - _mkdir(zText); - sprintf(zFilename, "%s/battle-%d-%s.log", zText, obs_count, simplename(r)); - bdebug = fopen(zFilename, "w"); - if (!bdebug) - log_error("battles cannot be debugged\n"); - else { - const unsigned char utf8_bom[4] = { 0xef, 0xbb, 0xbf, 0 }; - fwrite(utf8_bom, 1, 3, bdebug); - fprintf(bdebug, "In %s findet ein Kampf statt:\n", rname(r, - default_locale)); - } - obs_count++; - } - - b->region = r; - b->plane = getplane(r); - /* Finde alle Parteien, die den Kampf beobachten können: */ - for (u = r->units; u; u = u->next) { - if (u->number > 0) { - if (!fval(u->faction, FFL_MARK)) { - fset(u->faction, FFL_MARK); - for (bf = b->factions; bf; bf = bf->next) { - if (bf->faction == u->faction) - break; - } - if (!bf) { - bf = (bfaction *)calloc(sizeof(bfaction), 1); - ++b->nfactions; - bf->faction = u->faction; - bf->next = b->factions; - b->factions = bf; - } - } - } - } - - for (bf = b->factions; bf; bf = bf->next) { - faction *f = bf->faction; - max_fac_no = _max(max_fac_no, f->no); - freset(f, FFL_MARK); - } - return b; -} - -static void free_side(side * si) -{ - ql_free(si->leader.fighters); -} - -static void free_fighter(fighter * fig) -{ - while (fig->loot) { - i_free(i_remove(&fig->loot, fig->loot)); - } - while (fig->armors) { - armor *a = fig->armors; - fig->armors = a->next; - free(a); - } - free(fig->person); - free(fig->weapons); - -} - -static void free_battle(battle * b) -{ - int max_fac_no = 0; - - if (bdebug) { - fclose(bdebug); - } - - while (b->factions) { - bfaction *bf = b->factions; - faction *f = bf->faction; - b->factions = bf->next; - max_fac_no = _max(max_fac_no, f->no); - free(bf); - } - - ql_free(b->leaders); - ql_foreach(b->meffects, free); - ql_free(b->meffects); - - battle_free(b); -} - -static int *get_alive(side * s) -{ -#if 0 - static int alive[NUMROWS]; - fighter *fig; - memset(alive, 0, NUMROWS * sizeof(int)); - for (fig = s->fighters; fig; fig = fig->next) { - if (fig->alive > 0) { - int row = statusrow(fig); - alive[row] += fig->alive; - } - } - return alive; -#endif - return s->size; -} - -static int battle_report(battle * b) -{ - side *s, *s2; - bool cont = false; - bool komma; - bfaction *bf; - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - if (s->alive - s->removed > 0) { - for (s2 = b->sides; s2 != b->sides + b->nsides; ++s2) { - if (s2->alive - s2->removed > 0 && enemy(s, s2)) { - cont = true; - break; - } - } - if (cont) - break; - } - } - - if (verbosity > 0) - log_printf(stdout, " %d", b->turn); - fflush(stdout); - - for (bf = b->factions; bf; bf = bf->next) { - faction *fac = bf->faction; - char buf[32 * MAXSIDES]; - char *bufp = buf; - int bytes; - size_t size = sizeof(buf) - 1; - message *m; - - message_faction(b, fac, msg_separator); - - if (cont) - m = msg_message("battle::lineup", "turn", b->turn); - else - m = msg_message("battle::after", ""); - message_faction(b, fac, m); - msg_release(m); - - komma = false; - for (s = b->sides; s != b->sides + b->nsides; ++s) { - if (s->alive) { - int r, k = 0, *alive = get_alive(s); - int l = FIGHT_ROW; - const char *abbrev = seematrix(fac, s) ? sideabkz(s, false) : "-?-"; - const char *loc_army = LOC(fac->locale, "battle_army"); - char buffer[32]; - - if (komma) { - bytes = (int)strlcpy(bufp, ", ", size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - } - slprintf(buffer, sizeof(buffer), "%s %2d(%s): ", - loc_army, army_index(s), abbrev); - - bytes = (int)strlcpy(bufp, buffer, size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - - for (r = FIGHT_ROW; r != NUMROWS; ++r) { - if (alive[r]) { - if (l != FIGHT_ROW) { - bytes = (int)strlcpy(bufp, "+", size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - } - while (k--) { - bytes = (int)strlcpy(bufp, "0+", size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - } - sprintf(buffer, "%d", alive[r]); - - bytes = (int)strlcpy(bufp, buffer, size); - if (wrptr(&bufp, &size, bytes) != 0) - WARN_STATIC_BUFFER(); - - k = 0; - l = r + 1; - } else - ++k; - } - - komma = true; - } - } - *bufp = 0; - fbattlerecord(b, fac, buf); - } - return cont; -} - -static void join_allies(battle * b) -{ - region *r = b->region; - unit *u; - side *s, *s_end = b->sides + b->nsides; - /* make_side might be adding a new faction, but it adds them to the end - * of the list, so we're safe in our iteration here if we remember the end - * up front. */ - for (u = r->units; u; u = u->next) { - /* Was ist mit Schiffen? */ - if (u->status != ST_FLEE && u->status != ST_AVOID - && !fval(u, UFL_LONGACTION | UFL_ISNEW) && u->number > 0) { - faction *f = u->faction; - fighter *c = NULL; - - for (s = b->sides; s != s_end; ++s) { - side *se; - /* Wenn alle attackierten noch FFL_NOAID haben, dann kämpfe nicht mit. */ - if (fval(s->faction, FFL_NOAID)) - continue; - if (s->faction != f) { - /* Wenn wir attackiert haben, kommt niemand mehr hinzu: */ - if (s->bf->attacker) - continue; - /* alliiert müssen wir schon sein, sonst ist's eh egal : */ - if (!alliedunit(u, s->faction, HELP_FIGHT)) - continue; - /* wenn die partei verborgen ist, oder gar eine andere - * vorgespiegelt wird, und er sich uns gegenüber nicht zu - * erkennen gibt, helfen wir ihm nicht */ - if (s->stealthfaction) { - if (!allysfm(s, u->faction, HELP_FSTEALTH)) { - continue; - } - } - } - /* einen alliierten angreifen dürfen sie nicht, es sei denn, der - * ist mit einem alliierten verfeindet, der nicht attackiert - * hat: */ - for (se = b->sides; se != s_end; ++se) { - if (u->faction == se->faction) - continue; - if (alliedunit(u, se->faction, HELP_FIGHT) && !se->bf->attacker) { - continue; - } - if (enemy(s, se)) - break; - } - if (se == s_end) - continue; - /* Wenn die Einheit belagert ist, muß auch einer der Alliierten belagert sein: */ - if (besieged(u)) { - fighter *ally; - for (ally = s->fighters; ally; ally = ally->next) { - if (besieged(ally->unit)) { - break; - } - } - if (ally == NULL) - continue; - } - /* keine Einwände, also soll er mitmachen: */ - if (c == NULL) { - if (join_battle(b, u, false, &c)) { - if (battledebug) { - fprintf(bdebug, "%s joins to help %s against %s.\n", - unitname(u), factionname(s->faction), factionname(se->faction)); - } - } else if (c == NULL) { - continue; - } - } - - /* the enemy of my friend is my enemy: */ - for (se = b->sides; se != s_end; ++se) { - if (se->faction != u->faction && enemy(s, se)) { - if (set_enemy(se, c->side, false) && battledebug) { - fprintf(bdebug, - "%u/%s hates %u/%s because they are enemies with %u/%s.\n", - c->side->index, sidename(c->side), se->index, sidename(se), - s->index, sidename(s)); - } - } - } - } - } - } - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - int si; - side *sa; - faction *f = s->faction; - - /* Den Feinden meiner Feinde gebe ich Deckung (gegen gemeinsame Feinde): */ - for (si = 0; s->enemies[si]; ++si) { - side *se = s->enemies[si]; - int ai; - for (ai = 0; se->enemies[ai]; ++ai) { - side *as = se->enemies[ai]; - if (as == s || !enemy(as, s)) { - set_friendly(as, s); - } - } - } - - for (sa = s + 1; sa != b->sides + b->nsides; ++sa) { - plane *pl = rplane(r); - if (enemy(s, sa)) - continue; - if (friendly(s, sa)) - continue; - if (!alliedgroup(pl, f, sa->faction, f->allies, HELP_FIGHT)) - continue; - if (!alliedgroup(pl, sa->faction, f, sa->faction->allies, HELP_FIGHT)) - continue; - - set_friendly(s, sa); - } - } -} - -static void flee(const troop dt) -{ - fighter *fig = dt.fighter; - unit *u = fig->unit; - - fig->run.hp += fig->person[dt.index].hp; - ++fig->run.number; - - setguard(u, GUARD_NONE); - - kill_troop(dt); -} - -static bool start_battle(region * r, battle ** bp) -{ - battle *b = NULL; - unit *u; - bool fighting = false; - - /* list_foreach geht nicht, wegen flucht */ - for (u = r->units; u != NULL; u = u->next) { - if (fval(u, UFL_LONGACTION)) - continue; - if (u->number > 0) { - order *ord; - - for (ord = u->orders; ord; ord = ord->next) { - static bool init = false; - static const curse_type *peace_ct, *slave_ct, *calm_ct; - - if (!init) { - init = true; - peace_ct = ct_find("peacezone"); - slave_ct = ct_find("slavery"); - calm_ct = ct_find("calmmonster"); - } - if (getkeyword(ord) == K_ATTACK) { - unit *u2; - fighter *c1, *c2; - ship *lsh = NULL; - plane *pl = rplane(r); - - if (pl && fval(pl, PFL_NOATTACK)) { - cmistake(u, ord, 271, MSG_BATTLE); - continue; - } - - if ((u_race(u)->battle_flags & BF_CANATTACK) == 0) { - ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "race_no_attack", - "race", u_race(u))); - continue; - } - /** - ** Fehlerbehandlung Angreifer - **/ - if (LongHunger(u)) { - cmistake(u, ord, 225, MSG_BATTLE); - continue; - } - - if (u->status == ST_AVOID || u->status == ST_FLEE) { - cmistake(u, ord, 226, MSG_BATTLE); - continue; - } - - /* ist ein Flüchtling aus einem andern Kampf */ - if (fval(u, UFL_LONGACTION)) - continue; - - if (peace_ct && curse_active(get_curse(r->attribs, peace_ct))) { - ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "peace_active", "")); - continue; - } - - if (slave_ct && curse_active(get_curse(u->attribs, slave_ct))) { - ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "slave_active", "")); - continue; - } - - if ((u->ship != NULL && !fval(r->terrain, SEA_REGION)) - || (lsh = leftship(u)) != NULL) { - if (is_guarded(r, u, GUARD_TRAVELTHRU)) { - if (lsh) { - cmistake(u, ord, 234, MSG_BATTLE); - } else { - /* Fehler: "Das Schiff muß erst verlassen werden" */ - cmistake(u, ord, 19, MSG_BATTLE); - } - continue; - } - } - - /* Ende Fehlerbehandlung Angreifer */ - - init_order(ord); - /* attackierte Einheit ermitteln */ - u2 = getunit(r, u->faction); - - /* Beginn Fehlerbehandlung */ - /* Fehler: "Die Einheit wurde nicht gefunden" */ - if (!u2 || u2->number == 0 || !cansee(u->faction, u->region, u2, 0)) { - ADDMSG(&u->faction->msgs, msg_feedback(u, ord, - "feedback_unit_not_found", "")); - continue; - } - /* Fehler: "Die Einheit ist eine der unsrigen" */ - if (u2->faction == u->faction) { - cmistake(u, ord, 45, MSG_BATTLE); - continue; - } - /* Fehler: "Die Einheit ist mit uns alliert" */ - if (alliedunit(u, u2->faction, HELP_FIGHT)) { - cmistake(u, ord, 47, MSG_BATTLE); - continue; - } - if (IsImmune(u2->faction)) { - add_message(&u->faction->msgs, - msg_feedback(u, u->thisorder, "newbie_immunity_error", "turns", - NewbieImmunity())); - continue; - } - /* Fehler: "Die Einheit ist mit uns alliert" */ - - if (calm_ct) { - attrib *a = a_find(u->attribs, &at_curse); - bool calm = false; - while (a && a->type == &at_curse) { - curse *c = (curse *) a->data.v; - if (c->type == calm_ct - && curse_geteffect(c) == u2->faction->subscription) { - if (curse_active(c)) { - calm = true; - break; - } - } - a = a->next; - } - if (calm) { - cmistake(u, ord, 47, MSG_BATTLE); - continue; - } - } - /* Ende Fehlerbehandlung */ - if (b == NULL) { - unit *utmp; - for (utmp = r->units; utmp != NULL; utmp = utmp->next) { - fset(utmp->faction, FFL_NOAID); - } - b = make_battle(r); - } - if (join_battle(b, u, true, &c1)) { - if (battledebug) { - fprintf(bdebug, "%s joins by attacking %s.\n", - unitname(u), unitname(u2)); - } - } - if (join_battle(b, u2, false, &c2)) { - if (battledebug) { - fprintf(bdebug, "%s joins because of an attack from %s.\n", - unitname(u2), unitname(u)); - } - } - - /* Hat die attackierte Einheit keinen Noaid-Status, - * wird das Flag von der Faction genommen, andere - * Einheiten greifen ein. */ - if (!fval(u2, UFL_NOAID)) - freset(u2->faction, FFL_NOAID); - - if (c1 != NULL && c2 != NULL) { - /* Merken, wer Angreifer ist, für die Rückzahlung der - * Präcombataura bei kurzem Kampf. */ - c1->side->bf->attacker = true; - - if (set_enemy(c1->side, c2->side, true) && battledebug) { - fprintf(bdebug, "%u/%s hates %u/%s because they attacked them.\n", - c2->side->index, sidename(c2->side), - c1->side->index, sidename(c1->side)); - } - fighting = true; - } - } - } - } - } - *bp = b; - return fighting; -} - -/** execute one round of attacks - * fig->fighting is used to determine who attacks, not fig->alive, since - * the latter may be influenced by attacks that already took place. - */ -static void battle_attacks(battle * b) -{ - side *s; - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fig; - - if (b->turn != 0 || (b->max_tactics > 0 - && get_tactics(s, NULL) == b->max_tactics)) { - for (fig = s->fighters; fig; fig = fig->next) { - - /* ist in dieser Einheit noch jemand handlungsfähig? */ - if (fig->fighting <= 0) - continue; - - /* Handle the unit's attack on someone */ - do_attack(fig); - } - } - } -} - -/** updates the number of attacking troops in each fighter struct. - * this has to be calculated _before_ the actual attacks take - * place because otherwise dead troops would not strike in the - * round they die. */ -static void battle_update(battle * b) -{ - side *s; - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fig; - for (fig = s->fighters; fig; fig = fig->next) { - fig->fighting = fig->alive - fig->removed; - } - } -} - -/** attempt to flee from battle before the next round begins - * there's a double attempt before the first round, but only - * one attempt before round zero, the potential tactics round. */ -static void battle_flee(battle * b) -{ - int attempt, flee_ops = 1; - - if (b->turn == 1) - flee_ops = 2; - - for (attempt = 1; attempt <= flee_ops; ++attempt) { - side *s; - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fig; - for (fig = s->fighters; fig; fig = fig->next) { - unit *u = fig->unit; - troop dt; - int runners = 0; - /* Flucht nicht bei mehr als 600 HP. Damit Wyrme tötbar bleiben. */ - int runhp = _min(600, (int)(0.9 + unit_max_hp(u) * hpflee(u->status))); - - if (u->ship && fval(u->region->terrain, SEA_REGION)) { - /* keine Flucht von Schiffen auf hoher See */ - continue; - } - if (fval(u_race(u), RCF_UNDEAD) || u_race(u) == get_race(RC_SHADOWKNIGHT)) { - /* Untote fliehen nicht. Warum eigentlich? */ - continue; - } - - dt.fighter = fig; - dt.index = fig->alive - fig->removed; - while (s->size[SUM_ROW] && dt.index != 0) { - double ispaniced = 0.0; - --dt.index; - assert(dt.index >= 0 && dt.index < fig->unit->number); - assert(fig->person[dt.index].hp > 0); - - /* Versuche zu fliehen, wenn - * - Kampfstatus fliehe - * - schwer verwundet und nicht erste kampfrunde - * - in panik (Zauber) - * aber nicht, wenn der Zaubereffekt Held auf dir liegt! - */ - switch (u->status) { - case ST_FLEE: - break; - default: - if ((fig->person[dt.index].flags & FL_HIT) == 0) - continue; - if (b->turn <= 1) - continue; - if (fig->person[dt.index].hp <= runhp) - break; - if (fig->person[dt.index].flags & FL_PANICED) { - if ((fig->person[dt.index].flags & FL_COURAGE) == 0) - break; - } - continue; - } - - if (fig->person[dt.index].flags & FL_PANICED) { - ispaniced = EFFECT_PANIC_SPELL; - } - if (chance(_min(fleechance(u) + ispaniced, 0.90))) { - ++runners; - flee(dt); - } - } - if (bdebug && runners > 0) { - fprintf(bdebug, "Fleeing: %d from %s\n", runners, - itoa36(fig->unit->no)); - } - } - } - } -} - -void do_battle(region * r) -{ - battle *b = NULL; - bool fighting = false; - ship *sh; - static int init_rules = 0; - - if (!init_rules) { - static_rules(); - init_rules = 1; - } - if (msg_separator == NULL) { - msg_separator = msg_message("battle::section", ""); - } - - fighting = start_battle(r, &b); - - if (b == NULL) - return; - - /* Bevor wir die alliierten hineinziehen, sollten wir schauen, * - * Ob jemand fliehen kann. Dann erübrigt sich das ganze ja - * vielleicht schon. */ - print_header(b); - if (!fighting) { - /* Niemand mehr da, Kampf kann nicht stattfinden. */ - message *m = msg_message("battle::aborted", ""); - message_all(b, m); - msg_release(m); - free_battle(b); - free(b); - return; - } - join_allies(b); - make_heroes(b); - - /* make sure no ships are damaged initially */ - for (sh = r->ships; sh; sh = sh->next) - freset(sh, SF_DAMAGED); - - /* Gibt es eine Taktikrunde ? */ - if (!ql_empty(b->leaders)) { - b->turn = 0; - b->has_tactics_turn = true; - } else { - b->turn = 1; - b->has_tactics_turn = false; - } - - /* PRECOMBATSPELLS */ - do_combatmagic(b, DO_PRECOMBATSPELL); - - print_stats(b); /* gibt die Kampfaufstellung aus */ - if (verbosity > 0) - log_printf(stdout, "%s (%d, %d) : ", rname(r, default_locale), r->x, r->y); - - for (; battle_report(b) && b->turn <= max_turns; ++b->turn) { - if (bdebug) { - fprintf(bdebug, "*** Turn: %d\n", b->turn); - } - battle_flee(b); - battle_update(b); - battle_attacks(b); - - } - - if (verbosity > 0) - log_printf(stdout, "\n"); - - /* Auswirkungen berechnen: */ - aftermath(b); - /* Hier ist das Gefecht beendet, und wir können die - * Hilfsstrukturen * wieder löschen: */ - - if (b) { - free_battle(b); - free(b); - } -} - -void battle_free(battle * b) { - side *s; - - assert(b); - - for (s = b->sides; s != b->sides + b->nsides; ++s) { - fighter *fnext = s->fighters; - while (fnext) { - fighter *fig = fnext; - fnext = fig->next; - free_fighter(fig); - free(fig); - } - free_side(s); - } -} - diff --git a/src/kernel/build.c b/src/kernel/build.c index 8e977fc91..44352e2c3 100644 --- a/src/kernel/build.c +++ b/src/kernel/build.c @@ -521,9 +521,7 @@ int build(unit * u, const construction * ctype, int completed, int want) } } - if (want > 0) { - n = _min(want, n); - } + if (want < n) n = want; if (type->maxsize > 0) { n = _min(type->maxsize - completed, n); diff --git a/src/kernel/build.test.c b/src/kernel/build.test.c index efac217ae..1dc8e86e8 100644 --- a/src/kernel/build.test.c +++ b/src/kernel/build.test.c @@ -1,5 +1,6 @@ #include #include +#include "alchemy.h" #include "types.h" #include "build.h" #include "order.h" @@ -21,6 +22,7 @@ typedef struct build_fixture { unit *u; region *r; race *rc; + construction cons; } build_fixture; static unit * setup_build(build_fixture *bf) { @@ -32,37 +34,159 @@ static unit * setup_build(build_fixture *bf) { assert(bf->rc && bf->f && bf->r); bf->u = test_create_unit(bf->f, bf->r); assert(bf->u); + + bf->cons.materials = calloc(2, sizeof(requirement)); + bf->cons.materials[0].number = 1; + bf->cons.materials[0].rtype = get_resourcetype(R_SILVER); + bf->cons.skill = SK_ARMORER; + bf->cons.minskill = 2; + bf->cons.maxsize = -1; + bf->cons.reqsize = 1; return bf->u; } -static void test_build(CuTest *tc) { - build_fixture bf; +static void test_build_requires_materials(CuTest *tc) { + build_fixture bf = { 0 }; + unit *u; + const struct item_type *itype; + + u = setup_build(&bf); + set_level(u, SK_ARMORER, 2); + CuAssertIntEquals(tc, ENOMATERIALS, build(u, &bf.cons, 0, 1)); + itype = bf.cons.materials[0].rtype->itype; + i_change(&u->items, itype, 2); + CuAssertIntEquals(tc, 1, build(u, &bf.cons, 0, 1)); + CuAssertIntEquals(tc, 1, i_get(u->items, itype)); +} + +static void test_build_requires_building(CuTest *tc) { + build_fixture bf = { 0 }; + unit *u; + const struct resource_type *rtype; + building_type *btype; + + u = setup_build(&bf); + rtype = bf.cons.materials[0].rtype; + i_change(&u->items, rtype->itype, 1); + set_level(u, SK_ARMORER, 2); + bf.cons.btype = btype = bt_get_or_create("hodor"); + btype->maxcapacity = 1; + btype->capacity = 1; + CuAssertIntEquals_Msg(tc, "must be inside a production building", EBUILDINGREQ, build(u, &bf.cons, 0, 1)); + u->building = test_create_building(u->region, btype); + fset(u->building, BLD_WORKING); + CuAssertIntEquals(tc, 1, build(u, &bf.cons, 0, 1)); + btype->maxcapacity = 0; + CuAssertIntEquals_Msg(tc, "cannot build when production building capacity exceeded", EBUILDINGREQ, build(u, &bf.cons, 0, 1)); +} + +static void test_build_failure_missing_skill(CuTest *tc) { + build_fixture bf = { 0 }; unit *u; - construction cons = { 0 }; const struct resource_type *rtype; u = setup_build(&bf); - rtype = get_resourcetype(R_SILVER); - assert(rtype); - - cons.materials = calloc(2, sizeof(requirement)); - cons.materials[0].number = 1; - cons.materials[0].rtype = rtype; - cons.skill = SK_ARMORER; - cons.minskill = 2; - cons.reqsize = 1; - CuAssertIntEquals(tc, ENEEDSKILL, build(u, &cons, 1, 1)); - set_level(u, SK_ARMORER, 1); - CuAssertIntEquals(tc, ELOWSKILL, build(u, &cons, 1, 1)); - set_level(u, SK_ARMORER, 2); - CuAssertIntEquals(tc, ENOMATERIALS, build(u, &cons, 1, 1)); + rtype = bf.cons.materials[0].rtype; i_change(&u->items, rtype->itype, 1); - CuAssertIntEquals(tc, 1, build(u, &cons, 1, 1)); + CuAssertIntEquals(tc, ENEEDSKILL, build(u, &bf.cons, 1, 1)); +} + +static void test_build_failure_low_skill(CuTest *tc) { + build_fixture bf = { 0 }; + unit *u; + const struct resource_type *rtype; + + u = setup_build(&bf); + rtype = bf.cons.materials[0].rtype; + i_change(&u->items, rtype->itype, 1); + set_level(u, SK_ARMORER, bf.cons.minskill-1); + CuAssertIntEquals(tc, ELOWSKILL, build(u, &bf.cons, 0, 10)); +} + +static void test_build_failure_completed(CuTest *tc) { + build_fixture bf = { 0 }; + unit *u; + const struct resource_type *rtype; + + u = setup_build(&bf); + rtype = bf.cons.materials[0].rtype; + i_change(&u->items, rtype->itype, 1); + set_level(u, SK_ARMORER, bf.cons.minskill); + bf.cons.maxsize = 1; + CuAssertIntEquals(tc, ECOMPLETE, build(u, &bf.cons, bf.cons.maxsize, 10)); + CuAssertIntEquals(tc, 1, i_get(u->items, rtype->itype)); +} + +static void test_build_limits(CuTest *tc) { + build_fixture bf = { 0 }; + unit *u; + const struct resource_type *rtype; + + u = setup_build(&bf); + rtype = bf.cons.materials[0].rtype; + i_change(&u->items, rtype->itype, 1); + set_level(u, SK_ARMORER, bf.cons.minskill); + CuAssertIntEquals(tc, 1, build(u, &bf.cons, 0, 10)); CuAssertIntEquals(tc, 0, i_get(u->items, rtype->itype)); + scale_number(u, 2); + set_level(u, SK_ARMORER, bf.cons.minskill); i_change(&u->items, rtype->itype, 2); - CuAssertIntEquals(tc, 2, build(u, &cons, 2, 2)); + CuAssertIntEquals(tc, 2, build(u, &bf.cons, 0, 10)); CuAssertIntEquals(tc, 0, i_get(u->items, rtype->itype)); + + scale_number(u, 2); + set_level(u, SK_ARMORER, bf.cons.minskill * 2); + i_change(&u->items, rtype->itype, 4); + CuAssertIntEquals(tc, 4, build(u, &bf.cons, 0, 10)); + CuAssertIntEquals(tc, 0, i_get(u->items, rtype->itype)); + test_cleanup(); +} + +static void test_build_with_ring(CuTest *tc) { + build_fixture bf = { 0 }; + unit *u; + item_type *ring; + const struct resource_type *rtype; + + u = setup_build(&bf); + rtype = bf.cons.materials[0].rtype; + ring = it_get_or_create(rt_get_or_create("roqf")); + assert(rtype && ring); + + set_level(u, SK_ARMORER, bf.cons.minskill); + i_change(&u->items, rtype->itype, 20); + i_change(&u->items, ring, 1); + CuAssertIntEquals(tc, 10, build(u, &bf.cons, 0, 20)); + CuAssertIntEquals(tc, 10, i_get(u->items, rtype->itype)); + test_cleanup(); +} + +static void test_build_with_potion(CuTest *tc) { + build_fixture bf = { 0 }; + unit *u; + const potion_type *ptype; + const struct resource_type *rtype; + + u = setup_build(&bf); + rtype = bf.cons.materials[0].rtype; + oldpotiontype[P_DOMORE] = ptype = new_potiontype(it_get_or_create(rt_get_or_create("hodor")), 1); + assert(rtype && ptype); + + i_change(&u->items, rtype->itype, 20); + change_effect(u, ptype, 4); + set_level(u, SK_ARMORER, bf.cons.minskill); + CuAssertIntEquals(tc, 2, build(u, &bf.cons, 0, 20)); + CuAssertIntEquals(tc, 18, i_get(u->items, rtype->itype)); + CuAssertIntEquals(tc, 3, get_effect(u, ptype)); + set_level(u, SK_ARMORER, bf.cons.minskill*2); + CuAssertIntEquals(tc, 4, build(u, &bf.cons, 0, 20)); + CuAssertIntEquals(tc, 2, get_effect(u, ptype)); + set_level(u, SK_ARMORER, bf.cons.minskill); + scale_number(u, 2); // OBS: this scales the effects, too: + CuAssertIntEquals(tc, 4, get_effect(u, ptype)); + CuAssertIntEquals(tc, 4, build(u, &bf.cons, 0, 20)); + CuAssertIntEquals(tc, 2, get_effect(u, ptype)); test_cleanup(); } @@ -82,7 +206,7 @@ static void test_build_building_no_materials(CuTest *tc) { static void test_build_building_with_golem(CuTest *tc) { unit *u; - build_fixture bf; + build_fixture bf = { 0 }; const building_type *btype; u = setup_build(&bf); @@ -92,7 +216,7 @@ static void test_build_building_with_golem(CuTest *tc) { assert(btype->construction); set_level(bf.u, SK_BUILDING, 1); - CuAssertIntEquals(tc, 1, build_building(u, btype, 0, 4, 0)); + CuAssertIntEquals(tc, 1, build_building(u, btype, 0, 1, 0)); CuAssertPtrNotNull(tc, u->region->buildings); CuAssertIntEquals(tc, 1, u->region->buildings->size); CuAssertIntEquals(tc, 0, u->number); @@ -124,7 +248,14 @@ static void test_build_building_success(CuTest *tc) { CuSuite *get_build_suite(void) { CuSuite *suite = CuSuiteNew(); - SUITE_ADD_TEST(suite, test_build); + SUITE_ADD_TEST(suite, test_build_limits); + SUITE_ADD_TEST(suite, test_build_failure_low_skill); + SUITE_ADD_TEST(suite, test_build_failure_missing_skill); + SUITE_ADD_TEST(suite, test_build_requires_materials); + SUITE_ADD_TEST(suite, test_build_requires_building); + SUITE_ADD_TEST(suite, test_build_failure_completed); + SUITE_ADD_TEST(suite, test_build_with_ring); + SUITE_ADD_TEST(suite, test_build_with_potion); SUITE_ADD_TEST(suite, test_build_building_success); SUITE_ADD_TEST(suite, test_build_building_with_golem); SUITE_ADD_TEST(suite, test_build_building_no_materials); diff --git a/src/kernel/config.c b/src/kernel/config.c index 90cf0765d..10ab406b9 100644 --- a/src/kernel/config.c +++ b/src/kernel/config.c @@ -27,7 +27,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "alliance.h" #include "ally.h" #include "alchemy.h" -#include "battle.h" #include "connection.h" #include "building.h" #include "calendar.h" @@ -40,7 +39,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "magic.h" #include "messages.h" #include "move.h" -#include "names.h" #include "objtypes.h" #include "order.h" #include "plane.h" @@ -76,6 +74,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include +#include + #ifdef USE_LIBXML2 /* libxml includes */ #include @@ -669,23 +669,6 @@ region *findunitregion(const unit * su) #endif } -int eff_stealth(const unit * u, const region * r) -{ - int e = 0; - - /* Auf Schiffen keine Tarnung! */ - if (!u->ship && skill_enabled(SK_STEALTH)) { - e = eff_skill(u, SK_STEALTH, r); - - if (fval(u, UFL_STEALTH)) { - int es = u_geteffstealth(u); - if (es >= 0 && es < e) - return es; - } - } - return e; -} - bool unit_has_cursed_item(unit * u) { item *itm = u->items; @@ -2684,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; @@ -2796,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 bca500ab9..02cf6e8f0 100644 --- a/src/kernel/config.h +++ b/src/kernel/config.h @@ -251,7 +251,6 @@ extern "C" { bool has_limited_skills(const struct unit *u); const struct race *findrace(const char *, const struct locale *); - int eff_stealth(const struct unit *u, const struct region *r); int ispresent(const struct faction *f, const struct region *r); int check_option(struct faction *f, int option); @@ -340,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/item.c b/src/kernel/item.c index 94b3b8b48..72ca65885 100644 --- a/src/kernel/item.c +++ b/src/kernel/item.c @@ -838,7 +838,7 @@ static int use_bloodpotion(struct unit *u, const struct item_type *itype, int amount, struct order *ord) { - if (u_race(u) == get_race(RC_DAEMON)) { + if (u->number == 0 || u_race(u) == get_race(RC_DAEMON)) { change_effect(u, itype->rtype->ptype, 100 * amount); } else { diff --git a/src/kernel/move.h b/src/kernel/move.h deleted file mode 100644 index c76c362c1..000000000 --- a/src/kernel/move.h +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright (c) 1998-2010, Enno Rehling - 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 -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -**/ - -#ifndef H_KRNL_MOVEMENT -#define H_KRNL_MOVEMENT -#ifdef __cplusplus -extern "C" { -#endif - - struct unit; - struct ship; - struct building_type; - -/* 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. */ -#define SILVERWEIGHT 1 -#define SCALEWEIGHT 100 /* Faktor, um den die Anzeige von gewichten - * * 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. */ - - 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); -#ifdef __cplusplus -} -#endif -#endif diff --git a/src/kernel/region.c b/src/kernel/region.c index f75bfb46f..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) -{ - attrib *a = a_add(&r->attribs, a_new(&at_direction)); - spec_direction *d = (spec_direction *) (a->data.v); - - d->active = false; - 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 367d77385..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); - int deathcount(const struct region *r); int chaoscount(const struct region *r); diff --git a/src/kernel/unit.c b/src/kernel/unit.c index 967ff5db5..375500f15 100644 --- a/src/kernel/unit.c +++ b/src/kernel/unit.c @@ -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 @@ -56,6 +56,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include +#include + #include /* libc includes */ @@ -68,13 +70,13 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #define FIND_FOREIGN_TEMP attrib_type at_creator = { - "creator" + "creator" /* Rest ist NULL; temporaeres, nicht alterndes Attribut */ }; #define UMAXHASH MAXUNITS static unit *unithash[UMAXHASH]; -static unit *delmarker = (unit *) unithash; /* a funny hack */ +static unit *delmarker = (unit *)unithash; /* a funny hack */ #define HASH_STATISTICS 1 #if HASH_STATISTICS @@ -84,134 +86,136 @@ static int hash_misses; void uhash(unit * u) { - int key = HASH1(u->no, UMAXHASH), gk = HASH2(u->no, UMAXHASH); - while (unithash[key] != NULL && unithash[key] != delmarker - && unithash[key] != u) { - key = (key + gk) % UMAXHASH; - } - assert(unithash[key] != u || !"trying to add the same unit twice"); - unithash[key] = u; + int key = HASH1(u->no, UMAXHASH), gk = HASH2(u->no, UMAXHASH); + while (unithash[key] != NULL && unithash[key] != delmarker + && unithash[key] != u) { + key = (key + gk) % UMAXHASH; + } + assert(unithash[key] != u || !"trying to add the same unit twice"); + unithash[key] = u; } void uunhash(unit * u) { - int key = HASH1(u->no, UMAXHASH), gk = HASH2(u->no, UMAXHASH); - while (unithash[key] != NULL && unithash[key] != u) { - key = (key + gk) % UMAXHASH; - } - assert(unithash[key] == u || !"trying to remove a unit that is not hashed"); - unithash[key] = delmarker; + int key = HASH1(u->no, UMAXHASH), gk = HASH2(u->no, UMAXHASH); + while (unithash[key] != NULL && unithash[key] != u) { + key = (key + gk) % UMAXHASH; + } + assert(unithash[key] == u || !"trying to remove a unit that is not hashed"); + unithash[key] = delmarker; } unit *ufindhash(int uid) { - assert(uid >= 0); + assert(uid >= 0); #if HASH_STATISTICS - ++hash_requests; + ++hash_requests; #endif - if (uid >= 0) { - int key = HASH1(uid, UMAXHASH), gk = HASH2(uid, UMAXHASH); - while (unithash[key] != NULL && (unithash[key] == delmarker - || unithash[key]->no != uid)) { - key = (key + gk) % UMAXHASH; + if (uid >= 0) { + int key = HASH1(uid, UMAXHASH), gk = HASH2(uid, UMAXHASH); + while (unithash[key] != NULL && (unithash[key] == delmarker + || unithash[key]->no != uid)) { + key = (key + gk) % UMAXHASH; #if HASH_STATISTICS - ++hash_misses; + ++hash_misses; #endif + } + return unithash[key]; } - return unithash[key]; - } - return NULL; + return NULL; } #define DMAXHASH 7919 typedef struct dead { - struct dead *nexthash; - faction *f; - int no; + struct dead *nexthash; + faction *f; + int no; } dead; static dead *deadhash[DMAXHASH]; static void dhash(int no, faction * f) { - dead *hash = (dead *) calloc(1, sizeof(dead)); - dead *old = deadhash[no % DMAXHASH]; - hash->no = no; - hash->f = f; - deadhash[no % DMAXHASH] = hash; - hash->nexthash = old; + dead *hash = (dead *)calloc(1, sizeof(dead)); + dead *old = deadhash[no % DMAXHASH]; + hash->no = no; + hash->f = f; + deadhash[no % DMAXHASH] = hash; + hash->nexthash = old; } faction *dfindhash(int no) { - dead *old; + dead *old; - if (no < 0) - return 0; + if (no < 0) + return 0; - for (old = deadhash[no % DMAXHASH]; old; old = old->nexthash) { - if (old->no == no) { - return old->f; + for (old = deadhash[no % DMAXHASH]; old; old = old->nexthash) { + if (old->no == no) { + return old->f; + } } - } - return 0; + return 0; } typedef struct buddy { - struct buddy *next; - int number; - faction *faction; - unit *unit; + struct buddy *next; + int number; + faction *faction; + unit *unit; } buddy; static buddy *get_friends(const unit * u, int *numfriends) { - buddy *friends = 0; - faction *f = u->faction; - region *r = u->region; - int number = 0; - unit *u2; + buddy *friends = 0; + faction *f = u->faction; + region *r = u->region; + int number = 0; + unit *u2; - for (u2 = r->units; u2; u2 = u2->next) { - if (u2->faction != f && u2->number > 0) { - int allied = 0; - if (get_param_int(global.parameters, "rules.alliances", 0) != 0) { - allied = (f->alliance && f->alliance == u2->faction->alliance); - } else if (alliedunit(u, u2->faction, HELP_MONEY) - && alliedunit(u2, f, HELP_GIVE)) { - allied = 1; - } - if (allied) { - buddy *nf, **fr = &friends; - - /* some units won't take stuff: */ - if (u_race(u2)->ec_flags & GETITEM) { - while (*fr && (*fr)->faction->no < u2->faction->no) - fr = &(*fr)->next; - nf = *fr; - if (nf == NULL || nf->faction != u2->faction) { - nf = malloc(sizeof(buddy)); - nf->next = *fr; - nf->faction = u2->faction; - nf->unit = u2; - nf->number = 0; - *fr = nf; - } else if (nf->faction == u2->faction - && (u_race(u2)->ec_flags & GIVEITEM)) { - /* we don't like to gift it to units that won't give it back */ - if ((u_race(nf->unit)->ec_flags & GIVEITEM) == 0) { - nf->unit = u2; + for (u2 = r->units; u2; u2 = u2->next) { + if (u2->faction != f && u2->number > 0) { + int allied = 0; + if (get_param_int(global.parameters, "rules.alliances", 0) != 0) { + allied = (f->alliance && f->alliance == u2->faction->alliance); + } + else if (alliedunit(u, u2->faction, HELP_MONEY) + && alliedunit(u2, f, HELP_GIVE)) { + allied = 1; + } + if (allied) { + buddy *nf, **fr = &friends; + + /* some units won't take stuff: */ + if (u_race(u2)->ec_flags & GETITEM) { + while (*fr && (*fr)->faction->no < u2->faction->no) + fr = &(*fr)->next; + nf = *fr; + if (nf == NULL || nf->faction != u2->faction) { + nf = malloc(sizeof(buddy)); + nf->next = *fr; + nf->faction = u2->faction; + nf->unit = u2; + nf->number = 0; + *fr = nf; + } + else if (nf->faction == u2->faction + && (u_race(u2)->ec_flags & GIVEITEM)) { + /* we don't like to gift it to units that won't give it back */ + if ((u_race(nf->unit)->ec_flags & GIVEITEM) == 0) { + nf->unit = u2; + } + } + nf->number += u2->number; + number += u2->number; + } } - } - nf->number += u2->number; - number += u2->number; } - } } - } - if (numfriends) - *numfriends = number; - return friends; + if (numfriends) + *numfriends = number; + return friends; } /** give all items to friends or peasants. @@ -226,7 +230,7 @@ int gift_items(unit * u, int flags) item **itm_p = &u->items; int retval = 0; int rule = rule_give(); - + assert(u->region); assert(u->faction); @@ -243,100 +247,102 @@ int gift_items(unit * u, int flags) return 0; if ((u_race(u)->ec_flags & GIVEITEM) == 0) return 0; - - /* at first, I should try giving my crap to my own units in this region */ - if (u->faction && (u->faction->flags & FFL_QUIT) == 0 && (flags & GIFT_SELF)) { - unit *u2, *u3 = NULL; - for (u2 = r->units; u2; u2 = u2->next) { - if (u2 != u && u2->faction == u->faction && u2->number > 0) { - /* some units won't take stuff: */ - if (u_race(u2)->ec_flags & GETITEM) { - /* we don't like to gift it to units that won't give it back */ - if (u_race(u2)->ec_flags & GIVEITEM) { - i_merge(&u2->items, &u->items); + + /* at first, I should try giving my crap to my own units in this region */ + if (u->faction && (u->faction->flags & FFL_QUIT) == 0 && (flags & GIFT_SELF)) { + unit *u2, *u3 = NULL; + for (u2 = r->units; u2; u2 = u2->next) { + if (u2 != u && u2->faction == u->faction && u2->number > 0) { + /* some units won't take stuff: */ + if (u_race(u2)->ec_flags & GETITEM) { + /* we don't like to gift it to units that won't give it back */ + if (u_race(u2)->ec_flags & GIVEITEM) { + i_merge(&u2->items, &u->items); + u->items = NULL; + break; + } + else { + u3 = u2; + } + } + } + } + if (u->items && u3) { + /* if nobody else takes it, we give it to a unit that has issues */ + i_merge(&u3->items, &u->items); u->items = NULL; - break; - } else { - u3 = u2; - } } - } + if (u->items == NULL) + return 0; } - if (u->items && u3) { - /* if nobody else takes it, we give it to a unit that has issues */ - i_merge(&u3->items, &u->items); - u->items = NULL; - } - if (u->items == NULL) - return 0; - } - /* if I have friends, I'll try to give my stuff to them */ - if (u->faction && (flags & GIFT_FRIENDS)) { - int number = 0; - buddy *friends = get_friends(u, &number); + /* if I have friends, I'll try to give my stuff to them */ + if (u->faction && (flags & GIFT_FRIENDS)) { + int number = 0; + buddy *friends = get_friends(u, &number); - while (friends) { - struct buddy *nf = friends; - unit *u2 = nf->unit; - item *itm = u->items; - while (itm != NULL) { - const item_type *itype = itm->type; - item *itn = itm->next; - int n = itm->number; - n = n * nf->number / number; - if (n > 0) { - i_change(&u->items, itype, -n); - i_change(&u2->items, itype, n); + while (friends) { + struct buddy *nf = friends; + unit *u2 = nf->unit; + item *itm = u->items; + while (itm != NULL) { + const item_type *itype = itm->type; + item *itn = itm->next; + int n = itm->number; + n = n * nf->number / number; + if (n > 0) { + i_change(&u->items, itype, -n); + i_change(&u2->items, itype, n); + } + itm = itn; + } + number -= nf->number; + friends = nf->next; + free(nf); } - itm = itn; - } - number -= nf->number; - friends = nf->next; - free(nf); + if (u->items == NULL) + return 0; } - if (u->items == NULL) - return 0; - } - /* last, but not least, give money and horses to peasants */ - while (*itm_p) { - item *itm = *itm_p; + /* last, but not least, give money and horses to peasants */ + while (*itm_p) { + item *itm = *itm_p; - if (flags & GIFT_PEASANTS) { - if (!fval(u->region->terrain, SEA_REGION)) { - if (itm->type->rtype == rsilver) { - rsetmoney(r, rmoney(r) + itm->number); - itm->number = 0; + if (flags & GIFT_PEASANTS) { + if (!fval(u->region->terrain, SEA_REGION)) { + if (itm->type->rtype == rsilver) { + rsetmoney(r, rmoney(r) + itm->number); + itm->number = 0; + } + else if (itm->type->rtype == rhorse) { + rsethorses(r, rhorses(r) + itm->number); + itm->number = 0; + } + } } - else if (itm->type->rtype == rhorse) { - rsethorses(r, rhorses(r) + itm->number); - itm->number = 0; + if (itm->number > 0 && (itm->type->flags & ITF_NOTLOST)) { + itm_p = &itm->next; + retval = -1; + } + else { + i_remove(itm_p, itm); + i_free(itm); } - } } - if (itm->number > 0 && (itm->type->flags & ITF_NOTLOST)) { - itm_p = &itm->next; - retval = -1; - } else { - i_remove(itm_p, itm); - i_free(itm); - } - } - return retval; + return retval; } void make_zombie(unit * u) { - u_setfaction(u, get_monsters()); - scale_number(u, 1); - u_setrace(u, get_race(RC_ZOMBIE)); - u->irace = NULL; + u_setfaction(u, get_monsters()); + scale_number(u, 1); + u_setrace(u, get_race(RC_ZOMBIE)); + u->irace = NULL; } /** remove the unit from the list of active units. * the unit is not actually freed, because there may still be references - * dangling to it (from messages, for example). To free all removed units, + * dangling to it (from messages, for example). To free all removed units, * call free_units(). * returns 0 on success, or -1 if unit could not be removed. */ @@ -345,57 +351,57 @@ static unit *deleted_units = NULL; int remove_unit(unit ** ulist, unit * u) { - int result; + int result; - assert(ufindhash(u->no)); - handle_event(u->attribs, "destroy", u); + assert(ufindhash(u->no)); + handle_event(u->attribs, "destroy", u); - result = gift_items(u, GIFT_SELF | GIFT_FRIENDS | GIFT_PEASANTS); - if (result != 0) { - make_zombie(u); - return -1; - } - - if (u->number) - set_number(u, 0); - leave(u, true); - u->region = NULL; - - uunhash(u); - if (ulist) { - while (*ulist != u) { - ulist = &(*ulist)->next; + result = gift_items(u, GIFT_SELF | GIFT_FRIENDS | GIFT_PEASANTS); + if (result != 0) { + make_zombie(u); + return -1; } - assert(*ulist == u); - *ulist = u->next; - } - u->next = deleted_units; - deleted_units = u; - dhash(u->no, u->faction); + if (u->number) + set_number(u, 0); + leave(u, true); + u->region = NULL; - u_setfaction(u, NULL); - u->region = NULL; + uunhash(u); + if (ulist) { + while (*ulist != u) { + ulist = &(*ulist)->next; + } + assert(*ulist == u); + *ulist = u->next; + } - return 0; + u->next = deleted_units; + deleted_units = u; + dhash(u->no, u->faction); + + u_setfaction(u, NULL); + u->region = NULL; + + return 0; } unit *findnewunit(const region * r, const faction * f, int n) { - unit *u2; + unit *u2; - if (n == 0) - return 0; + if (n == 0) + return 0; - for (u2 = r->units; u2; u2 = u2->next) - if (u2->faction == f && ualias(u2) == n) - return u2; + for (u2 = r->units; u2; u2 = u2->next) + if (u2->faction == f && ualias(u2) == n) + return u2; #ifdef FIND_FOREIGN_TEMP - for (u2 = r->units; u2; u2 = u2->next) - if (ualias(u2) == n) - return u2; + for (u2 = r->units; u2; u2 = u2->next) + if (ualias(u2) == n) + return u2; #endif - return 0; + return 0; } /* ------------------------------------------------------------- */ @@ -404,74 +410,75 @@ unit *findnewunit(const region * r, const faction * f, int n) /* at_alias */ /*********************/ attrib_type at_alias = { - "alias", - DEFAULT_INIT, - DEFAULT_FINALIZE, - DEFAULT_AGE, - NO_WRITE, - NO_READ + "alias", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + NO_WRITE, + NO_READ }; int ualias(const unit * u) { - attrib *a = a_find(u->attribs, &at_alias); - if (!a) - return 0; - return a->data.i; + attrib *a = a_find(u->attribs, &at_alias); + if (!a) + return 0; + return a->data.i; } int a_readprivate(attrib * a, void *owner, struct storage *store) { - char lbuf[DISPLAYSIZE]; - READ_STR(store, lbuf, sizeof(lbuf)); - a->data.v = _strdup(lbuf); - return (a->data.v) ? AT_READ_OK : AT_READ_FAIL; + char lbuf[DISPLAYSIZE]; + READ_STR(store, lbuf, sizeof(lbuf)); + a->data.v = _strdup(lbuf); + return (a->data.v) ? AT_READ_OK : AT_READ_FAIL; } /*********************/ /* at_private */ /*********************/ attrib_type at_private = { - "private", - DEFAULT_INIT, - a_finalizestring, - DEFAULT_AGE, - a_writestring, - a_readprivate + "private", + DEFAULT_INIT, + a_finalizestring, + DEFAULT_AGE, + a_writestring, + a_readprivate }; const char *u_description(const unit * u, const struct locale *lang) { - if (u->display && u->display[0]) { - return u->display; - } else if (u_race(u)->describe) { - return u_race(u)->describe(u, lang); - } - return NULL; + if (u->display && u->display[0]) { + return u->display; + } + else if (u_race(u)->describe) { + return u_race(u)->describe(u, lang); + } + return NULL; } const char *uprivate(const unit * u) { - attrib *a = a_find(u->attribs, &at_private); - if (!a) - return NULL; - return a->data.v; + attrib *a = a_find(u->attribs, &at_private); + if (!a) + return NULL; + return a->data.v; } void usetprivate(unit * u, const char *str) { - attrib *a = a_find(u->attribs, &at_private); + attrib *a = a_find(u->attribs, &at_private); - if (str == NULL) { - if (a) - a_remove(&u->attribs, a); - return; - } - if (!a) - a = a_add(&u->attribs, a_new(&at_private)); - if (a->data.v) - free(a->data.v); - a->data.v = _strdup((const char *)str); + if (str == NULL) { + if (a) + a_remove(&u->attribs, a); + return; + } + if (!a) + a = a_add(&u->attribs, a_new(&at_private)); + if (a->data.v) + free(a->data.v); + a->data.v = _strdup((const char *)str); } /*********************/ @@ -479,66 +486,67 @@ void usetprivate(unit * u, const char *str) /*********************/ /* Einheit BENUTZT einen Trank */ attrib_type at_potionuser = { - "potionuser", - DEFAULT_INIT, - DEFAULT_FINALIZE, - DEFAULT_AGE, - NO_WRITE, - NO_READ + "potionuser", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + NO_WRITE, + NO_READ }; void usetpotionuse(unit * u, const potion_type * ptype) { - attrib *a = a_find(u->attribs, &at_potionuser); - if (!a) - a = a_add(&u->attribs, a_new(&at_potionuser)); - a->data.v = (void *)ptype; + attrib *a = a_find(u->attribs, &at_potionuser); + if (!a) + a = a_add(&u->attribs, a_new(&at_potionuser)); + a->data.v = (void *)ptype; } const potion_type *ugetpotionuse(const unit * u) { - attrib *a = a_find(u->attribs, &at_potionuser); - if (!a) - return NULL; - return (const potion_type *)a->data.v; + attrib *a = a_find(u->attribs, &at_potionuser); + if (!a) + return NULL; + return (const potion_type *)a->data.v; } /*********************/ /* at_target */ /*********************/ attrib_type at_target = { - "target", - DEFAULT_INIT, - DEFAULT_FINALIZE, - DEFAULT_AGE, - NO_WRITE, - NO_READ + "target", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + NO_WRITE, + NO_READ }; unit *utarget(const unit * u) { - attrib *a; - if (!fval(u, UFL_TARGET)) - return NULL; - a = a_find(u->attribs, &at_target); - assert(a || !"flag set, but no target found"); - return (unit *) a->data.v; + attrib *a; + if (!fval(u, UFL_TARGET)) + return NULL; + a = a_find(u->attribs, &at_target); + assert(a || !"flag set, but no target found"); + return (unit *)a->data.v; } void usettarget(unit * u, const unit * t) { - attrib *a = a_find(u->attribs, &at_target); - if (!a && t) - a = a_add(&u->attribs, a_new(&at_target)); - if (a) { - if (!t) { - a_remove(&u->attribs, a); - freset(u, UFL_TARGET); - } else { - a->data.v = (void *)t; - fset(u, UFL_TARGET); + attrib *a = a_find(u->attribs, &at_target); + if (!a && t) + a = a_add(&u->attribs, a_new(&at_target)); + if (a) { + if (!t) { + a_remove(&u->attribs, a); + freset(u, UFL_TARGET); + } + else { + a->data.v = (void *)t; + fset(u, UFL_TARGET); + } } - } } /*********************/ @@ -547,93 +555,94 @@ void usettarget(unit * u, const unit * t) void a_writesiege(const attrib * a, const void *owner, struct storage *store) { - struct building *b = (struct building *)a->data.v; - write_building_reference(b, store); + struct building *b = (struct building *)a->data.v; + write_building_reference(b, store); } int a_readsiege(attrib * a, void *owner, struct storage *store) { - int result = read_reference(&a->data.v, store, read_building_reference, - resolve_building); - if (result == 0 && !a->data.v) { - return AT_READ_FAIL; - } - return AT_READ_OK; + int result = read_reference(&a->data.v, store, read_building_reference, + resolve_building); + if (result == 0 && !a->data.v) { + return AT_READ_FAIL; + } + return AT_READ_OK; } attrib_type at_siege = { - "siege", - DEFAULT_INIT, - DEFAULT_FINALIZE, - DEFAULT_AGE, - a_writesiege, - a_readsiege + "siege", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + a_writesiege, + a_readsiege }; struct building *usiege(const unit * u) { - attrib *a; - if (!fval(u, UFL_SIEGE)) - return NULL; - a = a_find(u->attribs, &at_siege); - assert(a || !"flag set, but no siege found"); - return (struct building *)a->data.v; + attrib *a; + if (!fval(u, UFL_SIEGE)) + return NULL; + a = a_find(u->attribs, &at_siege); + assert(a || !"flag set, but no siege found"); + return (struct building *)a->data.v; } void usetsiege(unit * u, const struct building *t) { - attrib *a = a_find(u->attribs, &at_siege); - if (!a && t) - a = a_add(&u->attribs, a_new(&at_siege)); - if (a) { - if (!t) { - a_remove(&u->attribs, a); - freset(u, UFL_SIEGE); - } else { - a->data.v = (void *)t; - fset(u, UFL_SIEGE); + attrib *a = a_find(u->attribs, &at_siege); + if (!a && t) + a = a_add(&u->attribs, a_new(&at_siege)); + if (a) { + if (!t) { + a_remove(&u->attribs, a); + freset(u, UFL_SIEGE); + } + else { + a->data.v = (void *)t; + fset(u, UFL_SIEGE); + } } - } } /*********************/ /* at_contact */ /*********************/ attrib_type at_contact = { - "contact", - DEFAULT_INIT, - DEFAULT_FINALIZE, - DEFAULT_AGE, - NO_WRITE, - NO_READ + "contact", + DEFAULT_INIT, + DEFAULT_FINALIZE, + DEFAULT_AGE, + NO_WRITE, + NO_READ }; void usetcontact(unit * u, const unit * u2) { - attrib *a = a_find(u->attribs, &at_contact); - while (a && a->type == &at_contact && a->data.v != u2) - a = a->next; - if (a && a->type == &at_contact) - return; - a_add(&u->attribs, a_new(&at_contact))->data.v = (void *)u2; + attrib *a = a_find(u->attribs, &at_contact); + while (a && a->type == &at_contact && a->data.v != u2) + a = a->next; + if (a && a->type == &at_contact) + return; + a_add(&u->attribs, a_new(&at_contact))->data.v = (void *)u2; } bool ucontact(const unit * u, const unit * u2) /* Prueft, ob u den Kontaktiere-Befehl zu u2 gesetzt hat. */ { - attrib *ru; - if (u->faction == u2->faction) - return true; + attrib *ru; + if (u->faction == u2->faction) + return true; - /* Explizites KONTAKTIERE */ - for (ru = a_find(u->attribs, &at_contact); ru && ru->type == &at_contact; - ru = ru->next) { - if (((unit *) ru->data.v) == u2) { - return true; + /* Explizites KONTAKTIERE */ + for (ru = a_find(u->attribs, &at_contact); ru && ru->type == &at_contact; + ru = ru->next) { + if (((unit *)ru->data.v) == u2) { + return true; + } } - } - return false; + return false; } /*** @@ -642,280 +651,244 @@ bool ucontact(const unit * u, const unit * u2) void free_units(void) { - while (deleted_units) { - unit *u = deleted_units; - deleted_units = deleted_units->next; - free_unit(u); - free(u); - } + while (deleted_units) { + unit *u = deleted_units; + deleted_units = deleted_units->next; + free_unit(u); + free(u); + } } void write_unit_reference(const unit * u, struct storage *store) { - WRITE_INT(store, (u && u->region) ? u->no : 0); + WRITE_INT(store, (u && u->region) ? u->no : 0); } int resolve_unit(variant id, void *address) { - unit *u = NULL; - if (id.i != 0) { - u = findunit(id.i); - if (u == NULL) { - *(unit **) address = NULL; - return -1; + unit *u = NULL; + if (id.i != 0) { + u = findunit(id.i); + if (u == NULL) { + *(unit **)address = NULL; + return -1; + } } - } - *(unit **) address = u; - return 0; + *(unit **)address = u; + return 0; } variant read_unit_reference(struct storage * store) { - variant var; - READ_INT(store, &var.i); - return var; -} - -attrib_type at_stealth = { - "stealth", NULL, NULL, NULL, a_writeint, a_readint -}; - -void u_seteffstealth(unit * u, int value) -{ - if (skill_enabled(SK_STEALTH)) { - attrib *a = NULL; - if (fval(u, UFL_STEALTH)) { - a = a_find(u->attribs, &at_stealth); - } - if (value < 0) { - if (a != NULL) { - freset(u, UFL_STEALTH); - a_remove(&u->attribs, a); - } - return; - } - if (a == NULL) { - a = a_add(&u->attribs, a_new(&at_stealth)); - fset(u, UFL_STEALTH); - } - a->data.i = value; - } -} - -int u_geteffstealth(const struct unit *u) -{ - if (skill_enabled(SK_STEALTH)) { - if (fval(u, UFL_STEALTH)) { - attrib *a = a_find(u->attribs, &at_stealth); - if (a != NULL) - return a->data.i; - } - } - return -1; + variant var; + READ_INT(store, &var.i); + return var; } int get_level(const unit * u, skill_t id) { - if (skill_enabled(id)) { - skill *sv = u->skills; - while (sv != u->skills + u->skill_size) { - if (sv->id == id) { - return sv->level; - } - ++sv; + if (skill_enabled(id)) { + skill *sv = u->skills; + while (sv != u->skills + u->skill_size) { + if (sv->id == id) { + return sv->level; + } + ++sv; + } } - } - return 0; + return 0; } void set_level(unit * u, skill_t sk, int value) { - skill *sv = u->skills; + skill *sv = u->skills; - if (!skill_enabled(sk)) - return; + if (!skill_enabled(sk)) + return; - if (value == 0) { - remove_skill(u, sk); - return; - } - while (sv != u->skills + u->skill_size) { - if (sv->id == sk) { - sk_set(sv, value); - return; + if (value == 0) { + remove_skill(u, sk); + return; } - ++sv; - } - sk_set(add_skill(u, sk), value); + while (sv != u->skills + u->skill_size) { + if (sv->id == sk) { + sk_set(sv, value); + return; + } + ++sv; + } + sk_set(add_skill(u, sk), value); } static int leftship_age(struct attrib *a) { - /* must be aged, so it doesn't affect report generation (cansee) */ - unused_arg(a); - return AT_AGE_REMOVE; /* remove me */ + /* must be aged, so it doesn't affect report generation (cansee) */ + unused_arg(a); + return AT_AGE_REMOVE; /* remove me */ } static attrib_type at_leftship = { - "leftship", NULL, NULL, leftship_age + "leftship", NULL, NULL, leftship_age }; static attrib *make_leftship(struct ship *leftship) { - attrib *a = a_new(&at_leftship); - a->data.v = leftship; - return a; + attrib *a = a_new(&at_leftship); + a->data.v = leftship; + return a; } void set_leftship(unit * u, ship * sh) { - a_add(&u->attribs, make_leftship(sh)); + a_add(&u->attribs, make_leftship(sh)); } ship *leftship(const unit * u) { - attrib *a = a_find(u->attribs, &at_leftship); + attrib *a = a_find(u->attribs, &at_leftship); - /* Achtung: Es ist nicht garantiert, dass der Rueckgabewert zu jedem - * Zeitpunkt noch auf ein existierendes Schiff zeigt! */ + /* Achtung: Es ist nicht garantiert, dass der Rueckgabewert zu jedem + * Zeitpunkt noch auf ein existierendes Schiff zeigt! */ - if (a) - return (ship *) (a->data.v); + if (a) + return (ship *)(a->data.v); - return NULL; + return NULL; } void u_set_building(unit * u, building * b) { - assert(!u->building); /* you must leave first */ - u->building = b; - if (b && (!b->_owner || b->_owner->number <= 0)) { - building_set_owner(u); - } + assert(!u->building); /* you must leave first */ + u->building = b; + if (b && (!b->_owner || b->_owner->number <= 0)) { + building_set_owner(u); + } } void u_set_ship(unit * u, ship * sh) { - assert(!u->ship); /* you must leave_ship */ - u->ship = sh; - if (sh && (!sh->_owner || sh->_owner->number <= 0)) { - ship_set_owner(u); - } + assert(!u->ship); /* you must leave_ship */ + u->ship = sh; + if (sh && (!sh->_owner || sh->_owner->number <= 0)) { + ship_set_owner(u); + } } void leave_ship(unit * u) { - struct ship *sh = u->ship; + struct ship *sh = u->ship; - u->ship = 0; - if (sh->_owner==u) { - ship_update_owner(sh); - sh->_owner = ship_owner(sh); - } - set_leftship(u, sh); + u->ship = 0; + if (sh->_owner == u) { + ship_update_owner(sh); + sh->_owner = ship_owner(sh); + } + set_leftship(u, sh); } void leave_building(unit * u) { - building * b = u->building; + building * b = u->building; - u->building = 0; - if (b->_owner==u) { - building_update_owner(b); - assert(b->_owner!=u); - } + u->building = 0; + if (b->_owner == u) { + building_update_owner(b); + assert(b->_owner != u); + } } bool can_leave(unit * u) { - static int gamecookie = -1; - static int rule_leave = -1; + static int gamecookie = -1; + static int rule_leave = -1; - if (!u->building) { + if (!u->building) { + return true; + } + + if (rule_leave < 0 || gamecookie != global.cookie) { + gamecookie = global.cookie; + rule_leave = get_param_int(global.parameters, "rules.move.owner_leave", 0); + } + + if (rule_leave && u->building && u == building_owner(u->building)) { + return false; + } return true; - } - - if (rule_leave < 0 || gamecookie != global.cookie) { - gamecookie = global.cookie; - rule_leave = get_param_int(global.parameters, "rules.move.owner_leave", 0); - } - - if (rule_leave && u->building && u == building_owner(u->building)) { - return false; - } - return true; } bool leave(unit * u, bool force) { - if (!force) { - if (!can_leave(u)) { - return false; + if (!force) { + if (!can_leave(u)) { + return false; + } } - } - if (u->building) { - leave_building(u); - } else if (u->ship) { - leave_ship(u); - } - return true; + if (u->building) { + leave_building(u); + } + else if (u->ship) { + leave_ship(u); + } + return true; } const struct race *urace(const struct unit *u) { - return u->race_; + return u->race_; } bool can_survive(const unit * u, const region * r) { - if ((fval(r->terrain, WALK_INTO) && (u_race(u)->flags & RCF_WALK)) - || (fval(r->terrain, SWIM_INTO) && (u_race(u)->flags & RCF_SWIM)) - || (fval(r->terrain, FLY_INTO) && (u_race(u)->flags & RCF_FLY))) { - static const curse_type *ctype = NULL; + if ((fval(r->terrain, WALK_INTO) && (u_race(u)->flags & RCF_WALK)) + || (fval(r->terrain, SWIM_INTO) && (u_race(u)->flags & RCF_SWIM)) + || (fval(r->terrain, FLY_INTO) && (u_race(u)->flags & RCF_FLY))) { + static const curse_type *ctype = NULL; - if (has_horses(u) && !fval(r->terrain, WALK_INTO)) - return false; + if (has_horses(u) && !fval(r->terrain, WALK_INTO)) + return false; - if (!ctype) - ctype = ct_find("holyground"); - if (fval(u_race(u), RCF_UNDEAD) && curse_active(get_curse(r->attribs, ctype))) - return false; + if (!ctype) + ctype = ct_find("holyground"); + if (fval(u_race(u), RCF_UNDEAD) && curse_active(get_curse(r->attribs, ctype))) + return false; - return true; - } - return false; + return true; + } + return false; } void move_unit(unit * u, region * r, unit ** ulist) { - assert(u && r); + assert(u && r); - assert(u->faction || !"this unit is dead"); - if (u->region == r) - return; - if (!ulist) - ulist = (&r->units); - if (u->region) { - setguard(u, GUARD_NONE); - fset(u, UFL_MOVED); - if (u->ship || u->building) { - /* can_leave must be checked in travel_i */ + assert(u->faction || !"this unit is dead"); + if (u->region == r) + return; + if (!ulist) + ulist = (&r->units); + if (u->region) { + setguard(u, GUARD_NONE); + fset(u, UFL_MOVED); + if (u->ship || u->building) { + /* can_leave must be checked in travel_i */ #ifndef NDEBUG - bool result = leave(u, false); - assert(result); + bool result = leave(u, false); + assert(result); #else - leave(u, false); + leave(u, false); #endif + } + translist(&u->region->units, ulist, u); + } + else { + addlist(ulist, u); } - translist(&u->region->units, ulist, u); - } else { - addlist(ulist, u); - } #ifdef SMART_INTERVALS - update_interval(u->faction, r); + update_interval(u->faction, r); #endif - u->region = r; + u->region = r; } /* ist mist, aber wegen nicht skalierender attribute notwendig: */ @@ -923,152 +896,159 @@ void move_unit(unit * u, region * r, unit ** ulist) void transfermen(unit * u, unit * u2, int n) { - const attrib *a; - int hp = u->hp; - region *r = u->region; + const attrib *a; + int hp = u->hp; + region *r = u->region; - if (n == 0) - return; - assert(n > 0); - /* "hat attackiert"-status wird uebergeben */ + if (n == 0) + return; + assert(n > 0); + /* "hat attackiert"-status wird uebergeben */ - if (u2) { - skill *sv, *sn; - skill_t sk; - ship *sh; + if (u2) { + skill *sv, *sn; + skill_t sk; + ship *sh; - assert(u2->number + n > 0); + assert(u2->number + n > 0); - for (sk = 0; sk != MAXSKILLS; ++sk) { - int weeks, level = 0; + for (sk = 0; sk != MAXSKILLS; ++sk) { + int weeks, level = 0; - sv = unit_skill(u, sk); - sn = unit_skill(u2, sk); + sv = unit_skill(u, sk); + sn = unit_skill(u2, sk); - if (sv == NULL && sn == NULL) - continue; - if (sn == NULL && u2->number == 0) { - /* new unit, easy to solve */ - level = sv->level; - weeks = sv->weeks; - } else { - double dlevel = 0.0; + if (sv == NULL && sn == NULL) + continue; + if (sn == NULL && u2->number == 0) { + /* new unit, easy to solve */ + level = sv->level; + weeks = sv->weeks; + } + else { + double dlevel = 0.0; - if (sv && sv->level) { - dlevel += (sv->level + 1 - sv->weeks / (sv->level + 1.0)) * n; - level += sv->level * n; + if (sv && sv->level) { + dlevel += (sv->level + 1 - sv->weeks / (sv->level + 1.0)) * n; + level += sv->level * n; + } + if (sn && sn->level) { + dlevel += + (sn->level + 1 - sn->weeks / (sn->level + 1.0)) * u2->number; + level += sn->level * u2->number; + } + + dlevel = dlevel / (n + u2->number); + level = level / (n + u2->number); + if (level <= dlevel) { + /* apply the remaining fraction to the number of weeks to go. + * subtract the according number of weeks, getting closer to the + * next level */ + level = (int)dlevel; + weeks = (level + 1) - (int)((dlevel - level) * (level + 1)); + } + else { + /* make it harder to reach the next level. + * weeks+level is the max difficulty, 1 - the fraction between + * level and dlevel applied to the number of weeks between this + * and the previous level is the added difficutly */ + level = (int)dlevel + 1; + weeks = 1 + 2 * level - (int)((1 + dlevel - level) * level); + } + } + if (level) { + if (sn == NULL) + sn = add_skill(u2, sk); + sn->level = (unsigned char)level; + sn->weeks = (unsigned char)weeks; + assert(sn->weeks > 0 && sn->weeks <= sn->level * 2 + 1); + assert(u2->number != 0 || (sn->level == sv->level + && sn->weeks == sv->weeks)); + } + else if (sn) { + remove_skill(u2, sk); + sn = NULL; + } } - if (sn && sn->level) { - dlevel += - (sn->level + 1 - sn->weeks / (sn->level + 1.0)) * u2->number; - level += sn->level * u2->number; + a = a_find(u->attribs, &at_effect); + while (a && a->type == &at_effect) { + effect_data *olde = (effect_data *)a->data.v; + if (olde->value) + change_effect(u2, olde->type, olde->value); + a = a->next; } - - dlevel = dlevel / (n + u2->number); - level = level / (n + u2->number); - if (level <= dlevel) { - /* apply the remaining fraction to the number of weeks to go. - * subtract the according number of weeks, getting closer to the - * next level */ - level = (int)dlevel; - weeks = (level + 1) - (int)((dlevel - level) * (level + 1)); - } else { - /* make it harder to reach the next level. - * weeks+level is the max difficulty, 1 - the fraction between - * level and dlevel applied to the number of weeks between this - * and the previous level is the added difficutly */ - level = (int)dlevel + 1; - weeks = 1 + 2 * level - (int)((1 + dlevel - level) * level); + sh = leftship(u); + if (sh != NULL) + set_leftship(u2, sh); + u2->flags |= + u->flags & (UFL_LONGACTION | UFL_NOTMOVING | UFL_HUNGER | UFL_MOVED | + UFL_ENTER); + if (u->attribs) { + transfer_curse(u, u2, n); } - } - if (level) { - if (sn == NULL) - sn = add_skill(u2, sk); - sn->level = (unsigned char)level; - sn->weeks = (unsigned char)weeks; - assert(sn->weeks > 0 && sn->weeks <= sn->level * 2 + 1); - assert(u2->number != 0 || (sn->level == sv->level - && sn->weeks == sv->weeks)); - } else if (sn) { - remove_skill(u2, sk); - sn = NULL; - } } - a = a_find(u->attribs, &at_effect); - while (a && a->type == &at_effect) { - effect_data *olde = (effect_data *) a->data.v; - if (olde->value) - change_effect(u2, olde->type, olde->value); - a = a->next; + scale_number(u, u->number - n); + if (u2) { + set_number(u2, u2->number + n); + hp -= u->hp; + u2->hp += hp; + /* TODO: Das ist schnarchlahm! und gehoert nicht hierhin */ + a = a_find(u2->attribs, &at_effect); + while (a && a->type == &at_effect) { + attrib *an = a->next; + effect_data *olde = (effect_data *)a->data.v; + int e = get_effect(u, olde->type); + if (e != 0) + change_effect(u2, olde->type, -e); + a = an; + } } - sh = leftship(u); - if (sh != NULL) - set_leftship(u2, sh); - u2->flags |= - u->flags & (UFL_LONGACTION | UFL_NOTMOVING | UFL_HUNGER | UFL_MOVED | - UFL_ENTER); - if (u->attribs) { - transfer_curse(u, u2, n); + else if (r->land) { + if ((u_race(u)->ec_flags & ECF_REC_ETHEREAL) == 0) { + const race *rc = u_race(u); + if (rc->ec_flags & ECF_REC_HORSES) { /* Zentauren an die Pferde */ + int h = rhorses(r) + n; + rsethorses(r, h); + } + else { + int p = rpeasants(r); + p += (int)(n * rc->recruit_multi); + rsetpeasants(r, p); + } + } } - } - scale_number(u, u->number - n); - if (u2) { - set_number(u2, u2->number + n); - hp -= u->hp; - u2->hp += hp; - /* TODO: Das ist schnarchlahm! und gehoert nicht hierhin */ - a = a_find(u2->attribs, &at_effect); - while (a && a->type == &at_effect) { - attrib *an = a->next; - effect_data *olde = (effect_data *) a->data.v; - int e = get_effect(u, olde->type); - if (e != 0) - change_effect(u2, olde->type, -e); - a = an; - } - } else if (r->land) { - if ((u_race(u)->ec_flags & ECF_REC_ETHEREAL) == 0) { - const race *rc = u_race(u); - if (rc->ec_flags & ECF_REC_HORSES) { /* Zentauren an die Pferde */ - int h = rhorses(r) + n; - rsethorses(r, h); - } else { - int p = rpeasants(r); - p += (int)(n * rc->recruit_multi); - rsetpeasants(r, p); - } - } - } } struct building *inside_building(const struct unit *u) { - if (u->building == NULL) - return NULL; + if (u->building == NULL) + return NULL; - if (!fval(u->building, BLD_WORKING)) { - /* Unterhalt nicht bezahlt */ - return NULL; - } else if (u->building->size < u->building->type->maxsize) { - /* Gebaeude noch nicht fertig */ - return NULL; - } else { - int p = 0, cap = buildingcapacity(u->building); - const unit *u2; - for (u2 = u->region->units; u2; u2 = u2->next) { - if (u2->building == u->building) { - p += u2->number; - if (u2 == u) { - if (p <= cap) - return u->building; - return NULL; - } - if (p > cap) - return NULL; - } + if (!fval(u->building, BLD_WORKING)) { + /* Unterhalt nicht bezahlt */ + return NULL; } - } - return NULL; + else if (u->building->size < u->building->type->maxsize) { + /* Gebaeude noch nicht fertig */ + return NULL; + } + else { + int p = 0, cap = buildingcapacity(u->building); + const unit *u2; + for (u2 = u->region->units; u2; u2 = u2->next) { + if (u2->building == u->building) { + p += u2->number; + if (u2 == u) { + if (p <= cap) + return u->building; + return NULL; + } + if (p > cap) + return NULL; + } + } + } + return NULL; } void u_setfaction(unit * u, faction * f) @@ -1088,7 +1068,7 @@ void u_setfaction(unit * u, faction * f) } if (u->prevF) { u->prevF->nextF = u->nextF; - } + } else { u->faction->units = u->nextF; } @@ -1121,101 +1101,102 @@ void u_setfaction(unit * u, faction * f) /* vorsicht Sprueche koennen u->number == RS_FARVISION haben! */ void set_number(unit * u, int count) { - assert(count >= 0); - assert(count <= UNIT_MAXSIZE); + assert(count >= 0); + assert(count <= UNIT_MAXSIZE); - if (count == 0) { - u->flags &= ~(UFL_HERO); - } - if (u->faction) { - u->faction->num_people += count - u->number; - } - u->number = (unsigned short)count; + if (count == 0) { + u->flags &= ~(UFL_HERO); + } + if (u->faction) { + u->faction->num_people += count - u->number; + } + u->number = (unsigned short)count; } bool learn_skill(unit * u, skill_t sk, double chance) { - skill *sv = u->skills; - if (chance < 1.0 && rng_int() % 10000 >= chance * 10000) - return false; - while (sv != u->skills + u->skill_size) { - assert(sv->weeks > 0); - if (sv->id == sk) { - if (sv->weeks <= 1) { - sk_set(sv, sv->level + 1); - } else { - sv->weeks--; - } - return true; + skill *sv = u->skills; + if (chance < 1.0 && rng_int() % 10000 >= chance * 10000) + return false; + while (sv != u->skills + u->skill_size) { + assert(sv->weeks > 0); + if (sv->id == sk) { + if (sv->weeks <= 1) { + sk_set(sv, sv->level + 1); + } + else { + sv->weeks--; + } + return true; + } + ++sv; } - ++sv; - } - sv = add_skill(u, sk); - sk_set(sv, 1); - return true; + sv = add_skill(u, sk); + sk_set(sv, 1); + return true; } void remove_skill(unit * u, skill_t sk) { - skill *sv = u->skills; - for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { - if (sv->id == sk) { - skill *sl = u->skills + u->skill_size - 1; - if (sl != sv) { - *sv = *sl; - } - --u->skill_size; - return; + skill *sv = u->skills; + for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { + if (sv->id == sk) { + skill *sl = u->skills + u->skill_size - 1; + if (sl != sv) { + *sv = *sl; + } + --u->skill_size; + return; + } } - } } skill *add_skill(unit * u, skill_t id) { - skill *sv = u->skills; + skill *sv = u->skills; #ifndef NDEBUG - for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { - assert(sv->id != id); - } + for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { + assert(sv->id != id); + } #endif - ++u->skill_size; - u->skills = realloc(u->skills, u->skill_size * sizeof(skill)); - sv = (u->skills + u->skill_size - 1); - sv->level = (unsigned char)0; - sv->weeks = (unsigned char)1; - sv->old = (unsigned char)0; - sv->id = (unsigned char)id; - return sv; + ++u->skill_size; + u->skills = realloc(u->skills, u->skill_size * sizeof(skill)); + sv = (u->skills + u->skill_size - 1); + sv->level = (unsigned char)0; + sv->weeks = (unsigned char)1; + sv->old = (unsigned char)0; + sv->id = (unsigned char)id; + return sv; } skill *unit_skill(const unit * u, skill_t sk) { - skill *sv = u->skills; - while (sv != u->skills + u->skill_size) { - if (sv->id == sk) - return sv; - ++sv; - } - return NULL; + skill *sv = u->skills; + while (sv != u->skills + u->skill_size) { + if (sv->id == sk) + return sv; + ++sv; + } + return NULL; } bool has_skill(const unit * u, skill_t sk) { - skill *sv = u->skills; - while (sv != u->skills + u->skill_size) { - if (sv->id == sk) { - return (sv->level > 0); + skill *sv = u->skills; + while (sv != u->skills + u->skill_size) { + if (sv->id == sk) { + return (sv->level > 0); + } + ++sv; } - ++sv; - } - return false; + return false; } static int item_invis(const unit *u) { const struct resource_type *rring = get_resourcetype(R_RING_OF_INVISIBILITY); const struct resource_type *rsphere = get_resourcetype(R_SPHERE_OF_INVISIBILITY); return (rring ? i_get(u->items, rring->itype) : 0) - + (rsphere ? i_get(u->items, rsphere->itype) * 100 : 0); + + (rsphere ? i_get(u->items, rsphere->itype) * 100 : 0); } static int item_modification(const unit * u, skill_t sk, int val) @@ -1240,123 +1221,124 @@ static int item_modification(const unit * u, skill_t sk, int val) static int att_modification(const unit * u, skill_t sk) { - double result = 0; - static bool init = false; - static const curse_type *skillmod_ct, *gbdream_ct, *worse_ct; - curse *c; + double result = 0; + static bool init = false; + static const curse_type *skillmod_ct, *gbdream_ct, *worse_ct; + curse *c; - if (!init) { - init = true; - skillmod_ct = ct_find("skillmod"); - gbdream_ct = ct_find("gbdream"); - worse_ct = ct_find("worse"); - } + if (!init) { + init = true; + skillmod_ct = ct_find("skillmod"); + gbdream_ct = ct_find("gbdream"); + worse_ct = ct_find("worse"); + } - c = get_curse(u->attribs, worse_ct); - if (c != NULL) - result += curse_geteffect(c); - if (skillmod_ct) { - attrib *a = a_find(u->attribs, &at_curse); - while (a && a->type == &at_curse) { - curse *c = (curse *) a->data.v; - if (c->type == skillmod_ct && c->data.i == sk) { + c = get_curse(u->attribs, worse_ct); + if (c != NULL) result += curse_geteffect(c); - break; - } - a = a->next; - } - } - - /* TODO hier kann nicht mit get/iscursed gearbeitet werden, da nur der - * jeweils erste vom Typ C_GBDREAM zurueckgegen wird, wir aber alle - * durchsuchen und aufaddieren muessen */ - if (u->region) { - double bonus = 0, malus = 0; - attrib *a = a_find(u->region->attribs, &at_curse); - while (a && a->type == &at_curse) { - curse *c = (curse *) a->data.v; - if (curse_active(c) && c->type == gbdream_ct) { - double mod = curse_geteffect(c); - unit *mage = c->magician; - /* wir suchen jeweils den groesten Bonus und den groesten Malus */ - if (mod > bonus) { - if (mage == NULL || mage->number == 0 - || alliedunit(mage, u->faction, HELP_GUARD)) { - bonus = mod; - } - } else if (mod < malus) { - if (mage == NULL || !alliedunit(mage, u->faction, HELP_GUARD)) { - malus = mod; - } + if (skillmod_ct) { + attrib *a = a_find(u->attribs, &at_curse); + while (a && a->type == &at_curse) { + curse *c = (curse *)a->data.v; + if (c->type == skillmod_ct && c->data.i == sk) { + result += curse_geteffect(c); + break; + } + a = a->next; } - } - a = a->next; } - result = result + bonus + malus; - } - return (int)result; + /* TODO hier kann nicht mit get/iscursed gearbeitet werden, da nur der + * jeweils erste vom Typ C_GBDREAM zurueckgegen wird, wir aber alle + * durchsuchen und aufaddieren muessen */ + if (u->region) { + double bonus = 0, malus = 0; + attrib *a = a_find(u->region->attribs, &at_curse); + while (a && a->type == &at_curse) { + curse *c = (curse *)a->data.v; + if (curse_active(c) && c->type == gbdream_ct) { + double mod = curse_geteffect(c); + unit *mage = c->magician; + /* wir suchen jeweils den groesten Bonus und den groesten Malus */ + if (mod > bonus) { + if (mage == NULL || mage->number == 0 + || alliedunit(mage, u->faction, HELP_GUARD)) { + bonus = mod; + } + } + else if (mod < malus) { + if (mage == NULL || !alliedunit(mage, u->faction, HELP_GUARD)) { + malus = mod; + } + } + } + a = a->next; + } + result = result + bonus + malus; + } + + return (int)result; } int get_modifier(const unit * u, skill_t sk, int level, const region * r, - bool noitem) +bool noitem) { - int bskill = level; - int skill = bskill; + int bskill = level; + int skill = bskill; - if (r && sk == SK_STEALTH) { - plane *pl = rplane(r); - if (pl && fval(pl, PFL_NOSTEALTH)) { - return 0; + if (r && sk == SK_STEALTH) { + plane *pl = rplane(r); + if (pl && fval(pl, PFL_NOSTEALTH)) { + return 0; + } } - } - skill += rc_skillmod(u_race(u), r, sk); - skill += att_modification(u, sk); + skill += rc_skillmod(u_race(u), r, sk); + skill += att_modification(u, sk); - if (!noitem) { - skill = item_modification(u, sk, skill); - } - skill = skillmod(u->attribs, u, r, sk, skill, SMF_ALWAYS); + if (!noitem) { + skill = item_modification(u, sk, skill); + } + skill = skillmod(u->attribs, u, r, sk, skill, SMF_ALWAYS); #ifdef HUNGER_REDUCES_SKILL - if (fval(u, UFL_HUNGER)) { - skill = skill / 2; - } + if (fval(u, UFL_HUNGER)) { + skill = skill / 2; + } #endif - return skill - bskill; + return skill - bskill; } int eff_skill(const unit * u, skill_t sk, const region * r) { - if (skill_enabled(sk)) { - int level = get_level(u, sk); - if (level > 0) { - int mlevel = level + get_modifier(u, sk, level, r, false); + if (skill_enabled(sk)) { + int level = get_level(u, sk); + if (level > 0) { + int mlevel = level + get_modifier(u, sk, level, r, false); - if (mlevel > 0) { - int skillcap = SkillCap(sk); - if (skillcap && mlevel > skillcap) { - return skillcap; + if (mlevel > 0) { + int skillcap = SkillCap(sk); + if (skillcap && mlevel > skillcap) { + return skillcap; + } + return mlevel; + } } - return mlevel; - } } - } - return 0; + return 0; } int eff_skill_study(const unit * u, skill_t sk, const region * r) { - int level = get_level(u, sk); - if (level > 0) { - int mlevel = level + get_modifier(u, sk, level, r, true); + int level = get_level(u, sk); + if (level > 0) { + int mlevel = level + get_modifier(u, sk, level, r, true); - if (mlevel > 0) - return mlevel; - } - return 0; + if (mlevel > 0) + return mlevel; + } + return 0; } int invisible(const unit * target, const unit * viewer) @@ -1387,68 +1369,71 @@ int invisible(const unit * target, const unit * viewer) */ void free_unit(unit * u) { - free(u->name); - free(u->display); - free_order(u->thisorder); - free_orders(&u->orders); - if (u->skills) - free(u->skills); - while (u->items) { - item *it = u->items->next; - u->items->next = NULL; - i_free(u->items); - u->items = it; - } - while (u->attribs) - a_remove(&u->attribs, u->attribs); - while (u->reservations) { - struct reservation *res = u->reservations; - u->reservations = res->next; - free(res); - } + free(u->name); + free(u->display); + free_order(u->thisorder); + free_orders(&u->orders); + if (u->skills) + free(u->skills); + while (u->items) { + item *it = u->items->next; + u->items->next = NULL; + i_free(u->items); + u->items = it; + } + while (u->attribs) + a_remove(&u->attribs, u->attribs); + while (u->reservations) { + struct reservation *res = u->reservations; + u->reservations = res->next; + free(res); + } } static void createunitid(unit * u, int id) { - if (id <= 0 || id > MAX_UNIT_NR || ufindhash(id) || dfindhash(id) - || forbiddenid(id)) - u->no = newunitid(); - else - u->no = id; - uhash(u); + if (id <= 0 || id > MAX_UNIT_NR || ufindhash(id) || dfindhash(id) + || forbiddenid(id)) + u->no = newunitid(); + else + u->no = id; + uhash(u); } void name_unit(unit * u) { - if (u_race(u)->generate_name) { - const char *gen_name = u_race(u)->generate_name(u); - if (gen_name) { - unit_setname(u, gen_name); - } else { - unit_setname(u, racename(u->faction->locale, u, u_race(u))); - } - } else { - char name[32]; - const char * result; - const struct locale * lang = u->faction ? u->faction->locale : default_locale; - if (lang) { - static const char * prefix[MAXLOCALES]; - int i = locale_index(lang); - if (!prefix[i]) { - prefix[i] = LOC(lang, "unitdefault"); - if (!prefix[i]) { - prefix[i] = parameters[P_UNIT]; + if (u_race(u)->generate_name) { + const char *gen_name = u_race(u)->generate_name(u); + if (gen_name) { + unit_setname(u, gen_name); + } + else { + unit_setname(u, racename(u->faction->locale, u, u_race(u))); } - } - result = prefix[i]; - } else { - result = parameters[P_UNIT]; } - strlcpy(name, result, sizeof(name)); - strlcat(name, " ", sizeof(name)); - strlcat(name, itoa36(u->no), sizeof(name)); - unit_setname(u, name); - } + else { + char name[32]; + const char * result; + const struct locale * lang = u->faction ? u->faction->locale : default_locale; + if (lang) { + static const char * prefix[MAXLOCALES]; + int i = locale_index(lang); + if (!prefix[i]) { + prefix[i] = LOC(lang, "unitdefault"); + if (!prefix[i]) { + prefix[i] = parameters[P_UNIT]; + } + } + result = prefix[i]; + } + else { + result = parameters[P_UNIT]; + } + strlcpy(name, result, sizeof(name)); + strlcat(name, " ", sizeof(name)); + strlcat(name, itoa36(u->no), sizeof(name)); + unit_setname(u, name); + } } /** creates a new unit. @@ -1457,340 +1442,341 @@ void name_unit(unit * u) * @param creator: unit to inherit stealth, group, building, ship, etc. from */ unit *create_unit(region * r, faction * f, int number, const struct race *urace, - int id, const char *dname, unit * creator) + int id, const char *dname, unit * creator) { - unit *u = (unit *)calloc(1, sizeof(unit)); + unit *u = (unit *)calloc(1, sizeof(unit)); - assert(urace); - if (f) { - assert(f->alive); - u_setfaction(u, f); + assert(urace); + if (f) { + assert(f->alive); + u_setfaction(u, f); - if (f->locale) { - order *deford = default_order(f->locale); - if (deford) { - set_order(&u->thisorder, NULL); - addlist(&u->orders, deford); - } + if (f->locale) { + order *deford = default_order(f->locale); + if (deford) { + set_order(&u->thisorder, NULL); + addlist(&u->orders, deford); + } + } } - } - u_seteffstealth(u, -1); - u_setrace(u, urace); - u->irace = NULL; + u_setrace(u, urace); + u->irace = NULL; - set_number(u, number); + set_number(u, number); - /* die nummer der neuen einheit muss vor name_unit generiert werden, - * da der default name immer noch 'Nummer u->no' ist */ - createunitid(u, id); + /* die nummer der neuen einheit muss vor name_unit generiert werden, + * da der default name immer noch 'Nummer u->no' ist */ + createunitid(u, id); - /* zuerst in die Region setzen, da zb Drachennamen den Regionsnamen - * enthalten */ - if (r) - move_unit(u, r, NULL); + /* zuerst in die Region setzen, da zb Drachennamen den Regionsnamen + * enthalten */ + if (r) + move_unit(u, r, NULL); - /* u->race muss bereits gesetzt sein, wird fuer default-hp gebraucht */ - /* u->region auch */ - u->hp = unit_max_hp(u) * number; + /* u->race muss bereits gesetzt sein, wird fuer default-hp gebraucht */ + /* u->region auch */ + u->hp = unit_max_hp(u) * number; - if (!dname) { - name_unit(u); - } else { - u->name = _strdup(dname); - } - - if (creator) { - attrib *a; - - /* erbt Kampfstatus */ - setstatus(u, creator->status); - - /* erbt Gebaeude/Schiff */ - if (creator->region == r) { - if (creator->building) { - u_set_building(u, creator->building); - } - if (creator->ship && fval(u_race(u), RCF_CANSAIL)) { - u_set_ship(u, creator->ship); - } + if (!dname) { + name_unit(u); + } + else { + u->name = _strdup(dname); } - /* Tarnlimit wird vererbt */ - if (fval(creator, UFL_STEALTH)) { - attrib *a = a_find(creator->attribs, &at_stealth); - if (a) { - int stealth = a->data.i; - a = a_add(&u->attribs, a_new(&at_stealth)); - a->data.i = stealth; - } + if (creator) { + attrib *a; + + /* erbt Kampfstatus */ + setstatus(u, creator->status); + + /* erbt Gebaeude/Schiff */ + if (creator->region == r) { + if (creator->building) { + u_set_building(u, creator->building); + } + if (creator->ship && fval(u_race(u), RCF_CANSAIL)) { + u_set_ship(u, creator->ship); + } + } + + /* Tarnlimit wird vererbt */ + if (fval(creator, UFL_STEALTH)) { + attrib *a = a_find(creator->attribs, &at_stealth); + if (a) { + int stealth = a->data.i; + a = a_add(&u->attribs, a_new(&at_stealth)); + a->data.i = stealth; + } + } + + /* Temps von parteigetarnten Einheiten sind wieder parteigetarnt */ + if (fval(creator, UFL_ANON_FACTION)) { + fset(u, UFL_ANON_FACTION); + } + /* Daemonentarnung */ + set_racename(&u->attribs, get_racename(creator->attribs)); + if (fval(u_race(u), RCF_SHAPESHIFT) && fval(u_race(creator), RCF_SHAPESHIFT)) { + u->irace = creator->irace; + } + + /* Gruppen */ + if (creator->faction == f && fval(creator, UFL_GROUP)) { + a = a_find(creator->attribs, &at_group); + if (a) { + group *g = (group *)a->data.v; + set_group(u, g); + } + } + a = a_find(creator->attribs, &at_otherfaction); + if (a) { + a_add(&u->attribs, make_otherfaction(get_otherfaction(a))); + } + + a = a_add(&u->attribs, a_new(&at_creator)); + a->data.v = creator; } - /* Temps von parteigetarnten Einheiten sind wieder parteigetarnt */ - if (fval(creator, UFL_ANON_FACTION)) { - fset(u, UFL_ANON_FACTION); - } - /* Daemonentarnung */ - set_racename(&u->attribs, get_racename(creator->attribs)); - if (fval(u_race(u), RCF_SHAPESHIFT) && fval(u_race(creator), RCF_SHAPESHIFT)) { - u->irace = creator->irace; - } - - /* Gruppen */ - if (creator->faction == f && fval(creator, UFL_GROUP)) { - a = a_find(creator->attribs, &at_group); - if (a) { - group *g = (group *) a->data.v; - set_group(u, g); - } - } - a = a_find(creator->attribs, &at_otherfaction); - if (a) { - a_add(&u->attribs, make_otherfaction(get_otherfaction(a))); - } - - a = a_add(&u->attribs, a_new(&at_creator)); - a->data.v = creator; - } - - return u; + return u; } int maxheroes(const struct faction *f) { - int nsize = count_all(f); - if (nsize == 0) - return 0; - else { - int nmax = (int)(log10(nsize / 50.0) * 20); - return (nmax < 0) ? 0 : nmax; - } + int nsize = count_all(f); + if (nsize == 0) + return 0; + else { + int nmax = (int)(log10(nsize / 50.0) * 20); + return (nmax < 0) ? 0 : nmax; + } } int countheroes(const struct faction *f) { - const unit *u = f->units; - int n = 0; + const unit *u = f->units; + int n = 0; - while (u) { - if (fval(u, UFL_HERO)) - n += u->number; - u = u->nextF; - } + while (u) { + if (fval(u, UFL_HERO)) + n += u->number; + u = u->nextF; + } #ifdef DEBUG_MAXHEROES - int m = maxheroes(f); - if (n > m) { - log_warning("%s has %d of %d heroes\n", factionname(f), n, m); - } + int m = maxheroes(f); + if (n > m) { + log_warning("%s has %d of %d heroes\n", factionname(f), n, m); + } #endif - return n; + return n; } const char *unit_getname(const unit * u) { - return (const char *)u->name; + return (const char *)u->name; } void unit_setname(unit * u, const char *name) { - free(u->name); - if (name) - u->name = _strdup(name); - else - u->name = NULL; + free(u->name); + if (name) + u->name = _strdup(name); + else + u->name = NULL; } const char *unit_getinfo(const unit * u) { - return (const char *)u->display; + return (const char *)u->display; } void unit_setinfo(unit * u, const char *info) { - free(u->display); - if (info) - u->display = _strdup(info); - else - u->display = NULL; + free(u->display); + if (info) + u->display = _strdup(info); + else + u->display = NULL; } int unit_getid(const unit * u) { - return u->no; + return u->no; } void unit_setid(unit * u, int id) { - unit *nu = findunit(id); - if (nu == NULL) { - uunhash(u); - u->no = id; - uhash(u); - } + unit *nu = findunit(id); + if (nu == NULL) { + uunhash(u); + u->no = id; + uhash(u); + } } int unit_gethp(const unit * u) { - return u->hp; + return u->hp; } void unit_sethp(unit * u, int hp) { - u->hp = hp; + u->hp = hp; } status_t unit_getstatus(const unit * u) { - return u->status; + return u->status; } void unit_setstatus(unit * u, status_t status) { - u->status = status; + u->status = status; } int unit_getweight(const unit * u) { - return weight(u); + return weight(u); } int unit_getcapacity(const unit * u) { - return walkingcapacity(u); + return walkingcapacity(u); } void unit_addorder(unit * u, order * ord) { - order **ordp = &u->orders; - while (*ordp) - ordp = &(*ordp)->next; - *ordp = ord; - u->faction->lastorders = turn; + order **ordp = &u->orders; + while (*ordp) + ordp = &(*ordp)->next; + *ordp = ord; + u->faction->lastorders = turn; } int unit_max_hp(const unit * u) { - static int rules_stamina = -1; - int h; - double p; - static const curse_type *heal_ct = NULL; + static int rules_stamina = -1; + int h; + double p; + static const curse_type *heal_ct = NULL; - if (rules_stamina < 0) { - rules_stamina = - get_param_int(global.parameters, "rules.stamina", STAMINA_AFFECTS_HP); - } - h = u_race(u)->hitpoints; - if (heal_ct == NULL) - heal_ct = ct_find("healingzone"); - - if (rules_stamina & 1) { - p = pow(effskill(u, SK_STAMINA) / 2.0, 1.5) * 0.2; - h += (int)(h * p + 0.5); - } - /* der healing curse veraendert die maximalen hp */ - if (heal_ct) { - curse *c = get_curse(u->region->attribs, heal_ct); - if (c) { - h = (int)(h * (1.0 + (curse_geteffect(c) / 100))); + if (rules_stamina < 0) { + rules_stamina = + get_param_int(global.parameters, "rules.stamina", STAMINA_AFFECTS_HP); } - } + h = u_race(u)->hitpoints; + if (heal_ct == NULL) + heal_ct = ct_find("healingzone"); - return h; + if (rules_stamina & 1) { + p = pow(effskill(u, SK_STAMINA) / 2.0, 1.5) * 0.2; + h += (int)(h * p + 0.5); + } + /* der healing curse veraendert die maximalen hp */ + if (heal_ct) { + curse *c = get_curse(u->region->attribs, heal_ct); + if (c) { + h = (int)(h * (1.0 + (curse_geteffect(c) / 100))); + } + } + + return h; } void scale_number(unit * u, int n) { - skill_t sk; - const attrib *a; - int remain; + const attrib *a; + int remain; - if (n == u->number) - return; - if (n && u->number > 0) { - int full; - remain = ((u->hp % u->number) * (n % u->number)) % u->number; + if (n == u->number) + return; + if (n && u->number > 0) { + int full; + remain = ((u->hp % u->number) * (n % u->number)) % u->number; - full = u->hp / u->number; /* wieviel kriegt jede person mindestens */ - u->hp = full * n + (u->hp - full * u->number) * n / u->number; - assert(u->hp >= 0); - if ((rng_int() % u->number) < remain) - ++u->hp; /* Nachkommastellen */ - } else { - remain = 0; - u->hp = 0; - } - if (u->number > 0) { - for (a = a_find(u->attribs, &at_effect); a && a->type == &at_effect; - a = a->next) { - effect_data *data = (effect_data *) a->data.v; - int snew = data->value / u->number * n; - if (n) { - remain = data->value - snew / n * u->number; - snew += remain * n / u->number; - remain = (remain * n) % u->number; + full = u->hp / u->number; /* wieviel kriegt jede person mindestens */ + u->hp = full * n + (u->hp - full * u->number) * n / u->number; + assert(u->hp >= 0); if ((rng_int() % u->number) < remain) - ++snew; /* Nachkommastellen */ - } - data->value = snew; + ++u->hp; /* Nachkommastellen */ } - } - if (u->number == 0 || n == 0) { - for (sk = 0; sk < MAXSKILLS; sk++) { - remove_skill(u, sk); + else { + remain = 0; + u->hp = 0; + } + if (u->number > 0) { + for (a = a_find(u->attribs, &at_effect); a && a->type == &at_effect; + a = a->next) { + effect_data *data = (effect_data *)a->data.v; + int snew = data->value / u->number * n; + if (n) { + remain = data->value - snew / n * u->number; + snew += remain * n / u->number; + remain = (remain * n) % u->number; + if ((rng_int() % u->number) < remain) + ++snew; /* Nachkommastellen */ + } + data->value = snew; + } + } + if (u->number == 0 || n == 0) { + skill_t sk; + for (sk = 0; sk < MAXSKILLS; sk++) { + remove_skill(u, sk); + } } - } - set_number(u, n); + set_number(u, n); } const struct race *u_irace(const struct unit *u) { - if (u->irace && skill_enabled(SK_STEALTH)) { - return u->irace; - } - return u->race_; + if (u->irace && skill_enabled(SK_STEALTH)) { + return u->irace; + } + return u->race_; } const struct race *u_race(const struct unit *u) { - return u->race_; + return u->race_; } void u_setrace(struct unit *u, const struct race *rc) { - assert(rc); - u->race_ = rc; + assert(rc); + u->race_ = rc; } void unit_add_spell(unit * u, sc_mage * m, struct spell * sp, int level) { - sc_mage *mage = m ? m : get_mage(u); + sc_mage *mage = m ? m : get_mage(u); - if (!mage) { - log_debug("adding new spell %s to a previously non-mage unit %s\n", sp->sname, unitname(u)); - mage = create_mage(u, u->faction?u->faction->magiegebiet:M_GRAY); - } - if (!mage->spellbook) { - mage->spellbook = create_spellbook(0); - } - spellbook_add(mage->spellbook, sp, level); + if (!mage) { + log_debug("adding new spell %s to a previously non-mage unit %s\n", sp->sname, unitname(u)); + mage = create_mage(u, u->faction ? u->faction->magiegebiet : M_GRAY); + } + if (!mage->spellbook) { + mage->spellbook = create_spellbook(0); + } + spellbook_add(mage->spellbook, sp, level); } struct spellbook * unit_get_spellbook(const struct unit * u) { - sc_mage * mage = get_mage(u); - if (mage) { - if (mage->spellbook) { - return mage->spellbook; + sc_mage * mage = get_mage(u); + if (mage) { + if (mage->spellbook) { + return mage->spellbook; + } + if (mage->magietyp != M_GRAY) { + return faction_get_spellbook(u->faction); + } } - if (mage->magietyp!=M_GRAY) { - return faction_get_spellbook(u->faction); - } - } - return 0; + return 0; } int effskill(const unit * u, skill_t sk) { - return eff_skill(u, sk, u->region); + return eff_skill(u, sk, u->region); } diff --git a/src/kernel/unit.h b/src/kernel/unit.h index 4c232da5e..de90db3b2 100644 --- a/src/kernel/unit.h +++ b/src/kernel/unit.h @@ -125,10 +125,6 @@ extern "C" { int ualias(const struct unit *u); - extern struct attrib_type at_stealth; - - void u_seteffstealth(struct unit *u, int value); - int u_geteffstealth(const struct unit *u); const struct race *u_irace(const struct unit *u); const struct race *u_race(const struct unit *u); void u_setrace(struct unit *u, const struct race *); diff --git a/src/kernel/unit.test.c b/src/kernel/unit.test.c new file mode 100644 index 000000000..6f3618e73 --- /dev/null +++ b/src/kernel/unit.test.c @@ -0,0 +1,41 @@ +#include +#include +#include "alchemy.h" +#include "unit.h" +#include "item.h" +#include "region.h" + +#include +#include + +#include +#include + +static void test_scale_number(CuTest *tc) { + unit *u; + const struct potion_type *ptype; + + test_cleanup(); + test_create_world(); + ptype = new_potiontype(it_get_or_create(rt_get_or_create("hodor")), 1); + u = test_create_unit(test_create_faction(test_create_race("human")), findregion(0, 0)); + change_effect(u, ptype, 1); + CuAssertIntEquals(tc, 1, u->number); + CuAssertIntEquals(tc, 1, u->hp); + CuAssertIntEquals(tc, 1, get_effect(u, ptype)); + scale_number(u, 2); + CuAssertIntEquals(tc, 2, u->number); + CuAssertIntEquals(tc, 2, u->hp); + CuAssertIntEquals(tc, 2, get_effect(u, ptype)); + set_level(u, SK_ALCHEMY, 1); + scale_number(u, 0); + CuAssertIntEquals(tc, 0, get_level(u, SK_ALCHEMY)); + test_cleanup(); +} + +CuSuite *get_unit_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_scale_number); + return suite; +} diff --git a/src/kernel/xmlreader.c b/src/kernel/xmlreader.c index bb072d8fa..d93ec6309 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 @@ -459,33 +461,6 @@ static int parse_calendar(xmlDocPtr doc) return rv; } -static int parse_directions(xmlDocPtr doc) -{ - xmlXPathContextPtr xpath = xmlXPathNewContext(doc); - xmlXPathObjectPtr xpathDirections; - xmlNodeSetPtr nsetDirections; - int rv = 0; - - /* reading eressea/directions/dir */ - xpathDirections = - xmlXPathEvalExpression(BAD_CAST "/eressea/directions/dir", xpath); - nsetDirections = xpathDirections->nodesetval; - if (nsetDirections != NULL) { - int k; - for (k = 0; k != nsetDirections->nodeNr; ++k) { - xmlNodePtr dir = nsetDirections->nodeTab[k]; - xmlChar *propValue = xmlGetProp(dir, BAD_CAST "name"); - - register_special_direction((const char *)propValue); - xmlFree(propValue); - } - } - xmlXPathFreeObject(xpathDirections); - xmlXPathFreeContext(xpath); - - return rv; -} - static int parse_ships(xmlDocPtr doc) { xmlXPathContextPtr xpath = xmlXPathNewContext(doc); @@ -2307,6 +2282,5 @@ void register_xmlreader(void) xml_register_callback(parse_equipment); /* requires spells */ xml_register_callback(parse_races); /* requires spells */ xml_register_callback(parse_calendar); - xml_register_callback(parse_directions); } #endif diff --git a/src/laws.c b/src/laws.c index 1c4ba2954..406c37c77 100755 --- a/src/laws.c +++ b/src/laws.c @@ -31,12 +31,13 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "study.h" #include "market.h" #include "keyword.h" +#include "move.h" +#include "battle.h" +#include "alchemy.h" /* kernel includes */ -#include #include #include -#include #include #include #include @@ -46,7 +47,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include -#include #include #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/modules/arena.c b/src/modules/arena.c index ef3adb188..7ec4a91bd 100644 --- a/src/modules/arena.c +++ b/src/modules/arena.c @@ -34,17 +34,17 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include -#include #include #include #include #include #include -#include #include #include #include +#include + /* util include */ #include #include diff --git a/src/modules/autoseed.c b/src/modules/autoseed.c index d06863cd4..fbf4f32ac 100644 --- a/src/modules/autoseed.c +++ b/src/modules/autoseed.c @@ -515,6 +515,7 @@ int autoseed(newfaction ** players, int nsize, int max_agediff) static const terrain_type **terrainarr = 0; static int *distribution; + assert(players); if (nterrains < 0) { int n = 0; const terrain_type *terrain = terrains(); diff --git a/src/modules/gmcmd.c b/src/modules/gmcmd.c index db8cab923..110e418cd 100644 --- a/src/modules/gmcmd.c +++ b/src/modules/gmcmd.c @@ -23,7 +23,6 @@ /* kernel includes */ #include -#include #include #include #include diff --git a/src/modules/museum.c b/src/modules/museum.c index 0c288bd85..ddd7c1c65 100644 --- a/src/modules/museum.c +++ b/src/modules/museum.c @@ -28,7 +28,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include -#include #include #include #include @@ -38,6 +37,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include +#include + /* util includes */ #include #include diff --git a/src/modules/xmas.c b/src/modules/xmas.c index 42dd8c791..d32846f5d 100644 --- a/src/modules/xmas.c +++ b/src/modules/xmas.c @@ -18,11 +18,12 @@ #include #include #include -#include #include #include #include +#include + /* util includes */ #include #include diff --git a/src/monster.c b/src/monster.c index b0d6d897a..c162fe3cd 100644 --- a/src/monster.c +++ b/src/monster.c @@ -23,6 +23,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. /* gamecode includes */ #include "economy.h" #include "give.h" +#include "move.h" /* triggers includes */ #include @@ -37,14 +38,11 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include -#include -#include #include #include #include #include #include -#include #include #include #include diff --git a/src/monsters.c b/src/monsters.c index 1fe54cd26..9bcc87919 100644 --- a/src/monsters.c +++ b/src/monsters.c @@ -40,18 +40,17 @@ #include #include #include -#include -#include #include #include #include #include #include -#include #include #include #include +#include + /* util includes */ #include #include diff --git a/src/kernel/move.c b/src/move.c similarity index 97% rename from src/kernel/move.c rename to src/move.c index c27f40d06..bf87d6fda 100644 --- a/src/kernel/move.c +++ b/src/move.c @@ -20,31 +20,33 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include "move.h" - -#include "alchemy.h" -#include "connection.h" -#include "build.h" -#include "building.h" -#include "calendar.h" -#include "curse.h" -#include "direction.h" -#include "faction.h" -#include "item.h" -#include "magic.h" -#include "messages.h" -#include "order.h" -#include "plane.h" -#include "race.h" -#include "region.h" -#include "render.h" #include "reports.h" -#include "save.h" -#include "ship.h" +#include "alchemy.h" +#include "vortex.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "direction.h" #include "skill.h" -#include "terrain.h" -#include "terrainid.h" -#include "teleport.h" -#include "unit.h" /* util includes */ #include @@ -58,6 +60,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include +#include + #include /* attributes includes */ @@ -540,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; @@ -1035,6 +1069,44 @@ 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: + token = (const char *)get_translation(u->faction->locale, token, UT_SPECDIR); + if (!token) { + return E_MOVE_NOREGION; + } + r2 = find_special_direction(r, token); + 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 new file mode 100644 index 000000000..dd158d224 --- /dev/null +++ b/src/move.h @@ -0,0 +1,92 @@ +/* +Copyright (c) 1998-2010, Enno Rehling +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 +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +**/ + +#ifndef H_KRNL_MOVEMENT +#define H_KRNL_MOVEMENT + +#include "direction.h" + +#ifdef __cplusplus +extern "C" { +#endif + + struct unit; + struct ship; + struct building_type; + + 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. */ +#define SILVERWEIGHT 1 +#define SCALEWEIGHT 100 /* Faktor, um den die Anzeige von gewichten + * * 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. */ + + /* 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); + +#define SA_HARBOUR 2 +#define SA_COAST 1 +#define SA_NO_INSECT -1 +#define SA_NO_COAST -2 + + int check_ship_allowed(struct ship *sh, const struct region * r); +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/kernel/move.test.c b/src/move.test.c similarity index 98% rename from src/kernel/move.test.c rename to src/move.test.c index 100d4fc30..90307586a 100644 --- a/src/kernel/move.test.c +++ b/src/move.test.c @@ -1,8 +1,9 @@ +#include #include #include +#include "move.h" #include -#include #include #include #include diff --git a/src/kernel/names.c b/src/names.c similarity index 98% rename from src/kernel/names.c rename to src/names.c index cba36539c..264beee3c 100644 --- a/src/kernel/names.c +++ b/src/names.c @@ -21,13 +21,13 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "names.h" /* kernel includes */ -#include "unit.h" -#include "region.h" -#include "faction.h" -#include "magic.h" -#include "race.h" -#include "terrain.h" -#include "terrainid.h" +#include +#include +#include +#include +#include +#include +#include /* util includes */ #include diff --git a/src/kernel/names.h b/src/names.h similarity index 100% rename from src/kernel/names.h rename to src/names.h diff --git a/src/randenc.c b/src/randenc.c index 00e9443d3..d52ba1fa8 100644 --- a/src/randenc.c +++ b/src/randenc.c @@ -22,10 +22,10 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "economy.h" #include "monster.h" +#include "move.h" +#include "alchemy.h" /* kernel includes */ -#include -#include #include #include #include @@ -33,8 +33,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include -#include -#include #include #include #include diff --git a/src/report.c b/src/report.c index c6e4bc821..0de33ebec 100644 --- a/src/report.c +++ b/src/report.c @@ -21,6 +21,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include +#include "reports.h" /* modules includes */ #include @@ -35,9 +36,11 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include "economy.h" #include "monster.h" #include "laws.h" +#include "move.h" +#include "alchemy.h" +#include "vortex.h" /* kernel includes */ -#include #include #include #include @@ -48,7 +51,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include -#include #include #include #include @@ -56,7 +58,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include -#include #include #include #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/kernel/reports.c b/src/reports.c similarity index 96% rename from src/kernel/reports.c rename to src/reports.c index 9d8409c3c..1939fa972 100644 --- a/src/kernel/reports.c +++ b/src/reports.c @@ -29,7 +29,6 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include -#include #include #include #include @@ -66,6 +65,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include +#include "move.h" +#include "stealth.h" + bool nocr = false; bool nonr = false; bool noreports = false; @@ -200,37 +202,6 @@ const char **name, const char **basename, int *number, bool singular) } } -int *nmrs = NULL; - -int update_nmrs(void) -{ - int i, newplayers = 0; - faction *f; - int turn = global.data_turn; - - if (nmrs == NULL) - nmrs = malloc(sizeof(int) * (NMRTimeout() + 1)); - for (i = 0; i <= NMRTimeout(); ++i) { - nmrs[i] = 0; - } - - for (f = factions; f; f = f->next) { - if (fval(f, FFL_ISNEW)) { - ++newplayers; - } - else if (!is_monsters(f) && f->alive) { - int nmr = turn - f->lastorders + 1; - if (nmr < 0 || nmr > NMRTimeout()) { - log_error("faction %s has %d NMRS\n", factionid(f), nmr); - nmr = _max(0, nmr); - nmr = _min(nmr, NMRTimeout()); - } - ++nmrs[nmr]; - } - } - return newplayers; -} - #define ORDERS_IN_NR 1 static size_t buforder(char *bufp, size_t size, const order * ord, int mode) { @@ -2379,54 +2350,6 @@ static void log_orders(const struct message *msg) } } -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; -} - void register_reports(void) { /* register datatypes for the different message objects */ diff --git a/src/kernel/reports.h b/src/reports.h similarity index 93% rename from src/kernel/reports.h rename to src/reports.h index 16fa17b93..ea488f80d 100644 --- a/src/kernel/reports.h +++ b/src/reports.h @@ -20,7 +20,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #define H_KRNL_REPORTS #include -#include "objtypes.h" +#include #ifdef __cplusplus extern "C" { @@ -112,9 +112,6 @@ extern "C" { void register_reports(void); - int update_nmrs(void); - int *nmrs; - struct message *msg_curse(const struct curse *c, const void *obj, objtype_t typ, int slef); @@ -141,12 +138,6 @@ extern "C" { const char **rcillusion); void add_seen_faction(struct faction *self, struct faction *seen); -#define ACTION_RESET 0x01 /* reset the one-time-flag FFL_SELECT (on first pass) */ -#define ACTION_CANSEE 0x02 /* to people who can see the actor */ -#define ACTION_CANNOTSEE 0x04 /* to people who can not see the actor */ - int report_action(struct region *r, struct unit *actor, - struct message *msg, int flags); - size_t f_regionid(const struct region *r, const struct faction *f, char *buffer, size_t size); diff --git a/src/kernel/reports.test.c b/src/reports.test.c similarity index 97% rename from src/kernel/reports.test.c rename to src/reports.test.c index f92e35d04..a757183e7 100644 --- a/src/kernel/reports.test.c +++ b/src/reports.test.c @@ -1,9 +1,10 @@ -#include +#include +#include +#include "reports.h" #include #include #include -#include #include #include #include diff --git a/src/spells.c b/src/spells.c new file mode 100644 index 000000000..ba7c9c578 --- /dev/null +++ b/src/spells.c @@ -0,0 +1,6877 @@ +/* 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_special_direction("vortex"); + + register_unitcurse(); + register_regioncurse(); + register_shipcurse(); + register_buildingcurse(); +} diff --git a/src/spells/spells.h b/src/spells.h similarity index 63% rename from src/spells/spells.h rename to src/spells.h index 508e1a18b..6ec0516ed 100644 --- a/src/spells/spells.h +++ b/src/spells.h @@ -21,11 +21,16 @@ extern "C" { struct ship; struct curse; struct unit; + struct message; - extern void register_spells(void); - + void register_spells(void); void set_spelldata(struct spell *sp); +#define ACTION_RESET 0x01 /* reset the one-time-flag FFL_SELECT (on first pass) */ +#define ACTION_CANSEE 0x02 /* to people who can see the actor */ +#define ACTION_CANNOTSEE 0x04 /* to people who can not see the actor */ + int report_action(struct region *r, struct unit *actor, struct message *msg, int flags); + #ifdef __cplusplus } #endif 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/combatspells.c b/src/spells/combatspells.c index f437999bc..16bc4b71a 100644 --- a/src/spells/combatspells.c +++ b/src/spells/combatspells.c @@ -15,7 +15,6 @@ #include "combatspells.h" /* kernel includes */ -#include #include #include #include @@ -27,12 +26,14 @@ #include #include #include -#include #include #include #include #include +#include +#include + /* util includes */ #include #include diff --git a/src/spells/spells.c b/src/spells/spells.c deleted file mode 100644 index 507f07935..000000000 --- a/src/spells/spells.c +++ /dev/null @@ -1,6759 +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 /* fuer lovar */ -#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; -} - -/* ------------------------------------------------------------- */ -/* 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"); - create_special_direction(rt, r, 2, "vortex_desc", "vortex"); - 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/spy.c b/src/spy.c index fc2c5895d..2320e7cb8 100644 --- a/src/spy.c +++ b/src/spy.c @@ -20,14 +20,15 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include "spy.h" #include "laws.h" +#include "stealth.h" +#include "move.h" +#include "reports.h" /* kernel includes */ -#include #include #include #include #include -#include #include #include #include diff --git a/src/stealth.c b/src/stealth.c new file mode 100644 index 000000000..9ad973ca5 --- /dev/null +++ b/src/stealth.c @@ -0,0 +1,64 @@ +#include +#include +#include "stealth.h" +#include +#include +#include +#include + +#include + +attrib_type at_stealth = { + "stealth", NULL, NULL, NULL, a_writeint, a_readint +}; + +void u_seteffstealth(unit * u, int value) +{ + if (skill_enabled(SK_STEALTH)) { + attrib *a = NULL; + if (u->flags & UFL_STEALTH) { + a = a_find(u->attribs, &at_stealth); + } + if (value < 0) { + if (a != NULL) { + u->flags &= ~UFL_STEALTH; + a_remove(&u->attribs, a); + } + return; + } + if (a == NULL) { + a = a_add(&u->attribs, a_new(&at_stealth)); + u->flags |= UFL_STEALTH; + } + a->data.i = value; + } +} + +int u_geteffstealth(const unit *u) +{ + if (skill_enabled(SK_STEALTH)) { + if (u->flags & UFL_STEALTH) { + attrib *a = a_find(u->attribs, &at_stealth); + if (a != NULL) + return a->data.i; + } + } + return -1; +} + +int eff_stealth(const unit * u, const region * r) +{ + int e = 0; + + /* Auf Schiffen keine Tarnung! */ + if (!u->ship && skill_enabled(SK_STEALTH)) { + e = eff_skill(u, SK_STEALTH, r); + + if (u->flags & UFL_STEALTH) { + int es = u_geteffstealth(u); + if (es >= 0 && es < e) + return es; + } + } + return e; +} diff --git a/src/stealth.h b/src/stealth.h new file mode 100644 index 000000000..86bf956d3 --- /dev/null +++ b/src/stealth.h @@ -0,0 +1,19 @@ +#ifndef STEALTH_H +#define STEALTH_H + +#ifdef __cplusplus +extern "C" { +#endif + + struct unit; + struct region; + extern struct attrib_type at_stealth; + int eff_stealth(const struct unit *u, const struct region *r); + void u_seteffstealth(struct unit *u, int value); + int u_geteffstealth(const struct unit *u); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/stealth.test.c b/src/stealth.test.c new file mode 100644 index 000000000..90ee66872 --- /dev/null +++ b/src/stealth.test.c @@ -0,0 +1,40 @@ +#include + +#include "stealth.h" + +#include +#include + +#include +#include + +#include +#include + +void test_stealth(CuTest *tc) { + unit *u; + + test_cleanup(); + test_create_world(); + u = test_create_unit(test_create_faction(test_create_race("human")), findregion(0, 0)); + set_level(u, SK_STEALTH, 2); + CuAssertIntEquals(tc, -1, u_geteffstealth(u)); + CuAssertIntEquals(tc, 2, eff_stealth(u, u->region)); + u_seteffstealth(u, 3); + CuAssertIntEquals(tc, 3, u_geteffstealth(u)); + CuAssertIntEquals(tc, 2, eff_stealth(u, u->region)); + u_seteffstealth(u, 1); + CuAssertIntEquals(tc, 1, u_geteffstealth(u)); + CuAssertIntEquals(tc, 1, eff_stealth(u, u->region)); + u_seteffstealth(u, -1); + CuAssertIntEquals(tc, -1, u_geteffstealth(u)); + CuAssertIntEquals(tc, 2, eff_stealth(u, u->region)); + test_cleanup(); +} + +CuSuite *get_stealth_suite(void) +{ + CuSuite *suite = CuSuiteNew(); + SUITE_ADD_TEST(suite, test_stealth); + return suite; +} diff --git a/src/study.c b/src/study.c index c928c0385..7c733ff87 100644 --- a/src/study.c +++ b/src/study.c @@ -22,15 +22,15 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. #include #include #include "study.h" +#include "move.h" +#include "alchemy.h" -#include #include #include #include #include #include #include -#include #include #include #include diff --git a/src/summary.c b/src/summary.c index f131bbf53..70a908a26 100644 --- a/src/summary.c +++ b/src/summary.c @@ -13,7 +13,6 @@ #include #include "summary.h" - #include "laws.h" #include @@ -22,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -71,6 +69,38 @@ typedef struct summary { } *languages; } summary; + +int *nmrs = NULL; + +int update_nmrs(void) +{ + int i, newplayers = 0; + faction *f; + int turn = global.data_turn; + + if (nmrs == NULL) + nmrs = malloc(sizeof(int) * (NMRTimeout() + 1)); + for (i = 0; i <= NMRTimeout(); ++i) { + nmrs[i] = 0; + } + + for (f = factions; f; f = f->next) { + if (fval(f, FFL_ISNEW)) { + ++newplayers; + } + else if (!is_monsters(f) && f->alive) { + int nmr = turn - f->lastorders + 1; + if (nmr < 0 || nmr > NMRTimeout()) { + log_error("faction %s has %d NMRS\n", factionid(f), nmr); + nmr = _max(0, nmr); + nmr = _min(nmr, NMRTimeout()); + } + ++nmrs[nmr]; + } + } + return newplayers; +} + static char *pcomp(double i, double j) { static char buf[32]; diff --git a/src/summary.h b/src/summary.h index bb2ae04b7..ca65148e9 100644 --- a/src/summary.h +++ b/src/summary.h @@ -16,9 +16,13 @@ extern "C" { #endif struct summary; - extern void report_summary(struct summary *n, struct summary *o, - bool full); - extern struct summary *make_summary(void); + + void report_summary(struct summary *n, struct summary *o, bool full); + struct summary *make_summary(void); + + int update_nmrs(void); + extern int* nmrs; + #ifdef __cplusplus } diff --git a/src/test_eressea.c b/src/test_eressea.c index 8bc2f0dd4..fb78486d4 100644 --- a/src/test_eressea.c +++ b/src/test_eressea.c @@ -36,6 +36,7 @@ int RunAllTests(void) ADD_TESTS(suite, umlaut); ADD_TESTS(suite, strings); /* kernel */ + ADD_TESTS(suite, unit); ADD_TESTS(suite, faction); ADD_TESTS(suite, build); ADD_TESTS(suite, pool); @@ -43,19 +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, 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/util/language.c b/src/util/language.c index 6c7afdba4..e0384e9b6 100644 --- a/src/util/language.c +++ b/src/util/language.c @@ -234,6 +234,15 @@ void ** get_translations(const struct locale *lang, int index) return lstrs[0].tokens + index; } +void *get_translation(const struct locale *lang, const char *str, int index) { + void **tokens = get_translations(lang, index); + variant var; + if (findtoken(*tokens, str, &var) == E_TOK_SUCCESS) { + return var.v; + } + return NULL; +} + void free_locales(void) { while (locales) { diff --git a/src/util/language.h b/src/util/language.h index aeb79ab64..7083dec1f 100644 --- a/src/util/language.h +++ b/src/util/language.h @@ -65,7 +65,8 @@ extern "C" { UT_MAX }; - void ** get_translations(const struct locale *lang, int index); + void ** get_translations(const struct locale *lang, int type); + void * get_translation(const struct locale *lang, const char *str, int type); #ifdef __cplusplus } diff --git a/src/util/translation.c b/src/util/translation.c index 1c93c0bb2..b384acc64 100644 --- a/src/util/translation.c +++ b/src/util/translation.c @@ -48,7 +48,7 @@ void opstack_push(opstack ** stackp, variant data) opstack *stack = *stackp; if (stack == NULL) { stack = (opstack *) malloc(sizeof(opstack)); - stack->size = 1; + stack->size = 2; stack->begin = malloc(sizeof(variant) * stack->size); stack->top = stack->begin; *stackp = stack; diff --git a/src/vortex.c b/src/vortex.c new file mode 100644 index 000000000..a4517f7bb --- /dev/null +++ b/src/vortex.c @@ -0,0 +1,185 @@ +#include +#include +#include "vortex.h" + +#include +#include +#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 == locales) { + 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); + + (void) 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); + + (void)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) +{ + 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 && strcmp(token, 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..905e1d8d5 --- /dev/null +++ b/src/vortex.h @@ -0,0 +1,31 @@ +#ifndef H_VORTEX +#define H_VORTEX +#ifdef __cplusplus +extern "C" { +#endif + + struct region; + struct attrib; + + 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); + 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; +} diff --git a/src/xmlreport.c b/src/xmlreport.c index 6bf30d75f..19b9ff53f 100644 --- a/src/xmlreport.c +++ b/src/xmlreport.c @@ -27,6 +27,7 @@ without prior permission by the authors of Eressea. /* gamecode includes */ #include "laws.h" #include "economy.h" +#include "move.h" /* kernel includes */ #include @@ -40,7 +41,6 @@ without prior permission by the authors of Eressea. #include #include #include -#include #include #include #include