/* vi: set ts=2: * * Eressea PB(E)M host Copyright (C) 1998-2003 * Christian Schlittchen (corwin@amber.kn-bremen.de) * Katja Zedel (katze@felidae.kn-bremen.de) * Henning Peters (faroul@beyond.kn-bremen.de) * Enno Rehling (enno@eressea-pbem.de) * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) * * based on: * * Atlantis v1.0 13 September 1993 Copyright 1993 by Russell Wallace * Atlantis v1.7 Copyright 1996 by Alex Schröder * * This program may not be used, modified or distributed without * prior permission by the authors of Eressea. * This program may not be sold or used commercially without prior written * permission from the authors. */ #include #include "eressea.h" #include "magic.h" #include "item.h" #include "pool.h" #include "message.h" #include "faction.h" #include "race.h" #include "spell.h" #include "ship.h" #include "curse.h" #include "building.h" #include "region.h" #include "goodies.h" #include "plane.h" #include "objtypes.h" #include "unit.h" #include "skill.h" #include "pathfinder.h" #include "karma.h" #include #include #include #include #include #include /* util includes */ #include #include #include #include /* libc includes */ #include #include #include #include #include #include #include /* ------------------------------------------------------------- */ const char *magietypen[MAXMAGIETYP] = { "nomagic", "illaun", "tybied", "cerddor", "gwyrrd", "draig" }; attrib_type at_reportspell = { "reportspell", NULL, NULL, NULL, NO_WRITE, NO_READ }; /** ** at_icastle ** TODO: separate castle-appearance from illusion-effects **/ static ship * findshipr(const region *r, int n) /* Ein Schiff in einer bestimmten Region finden: */ { ship * sh; for (sh = r->ships; sh; sh = sh->next) { if (sh->no == n) { assert(sh->region == r); return sh; } } return 0; } static building * findbuildingr(const region *r, int n) /* Ein Gebäude in einer bestimmten Region finden: */ { building *b; for (b = rbuildings(r); b; b = b->next) { if (b->no == n) { return b; } } return 0; } static int a_readicastle(attrib * a, FILE * f) { icastle_data * data = (icastle_data*)a->data.v; if (global.data_versiontime = 0; data->type = NULL; return AT_READ_FAIL; } else { int bno; fscanf(f, "%s %d %d", buf, &bno, &data->time); data->building = findbuilding(bno); if (!data->building) { /* this shouldn't happen, but just in case it does: */ ur_add((void*)bno, (void**)&data->building, resolve_building); } data->type = bt_find(buf); return AT_READ_OK; } } static void a_writeicastle(const attrib * a, FILE * f) { icastle_data * data = (icastle_data*)a->data.v; fprintf(f, "%s %d %d", data->type->_name, data->building->no, data->time); } static int a_ageicastle(struct attrib * a) { icastle_data * data = (icastle_data*)a->data.v; if (data->time<=0) { building * b = data->building; region * r = b->region; sprintf(buf, "Plötzlich löst sich %s in kleine Traumwolken auf.", buildingname(b)); addmessage(r, 0, buf, MSG_EVENT, ML_INFO); /* destroy_building lets units leave the building */ destroy_building(b); return 0; } else data->time--; return 1; } static void a_initicastle(struct attrib * a) { a->data.v = calloc(sizeof(icastle_data), 1); } static void a_finalizeicastle(struct attrib * a) { free(a->data.v); } attrib_type at_icastle = { "zauber_icastle", a_initicastle, a_finalizeicastle, a_ageicastle, a_writeicastle, a_readicastle }; /* ------------------------------------------------------------- */ extern int dice(int count, int value); /* ------------------------------------------------------------- */ /* aus dem alten System übriggebliegene Funktionen, die bei der * Umwandlung von alt nach neu gebraucht werden */ /* ------------------------------------------------------------- */ static void init_mage(attrib * a) { a->data.v = calloc(sizeof(sc_mage), 1); } static void free_mage(attrib * a) { sc_mage * mage = (sc_mage*)a->data.v; freelist(mage->spellptr); free(mage); } static int read_mage(attrib * a, FILE * F) { int i, mtype; sc_mage * mage = (sc_mage*)a->data.v; spell_ptr ** sp = &mage->spellptr; fscanf(F, "%d %d %d", &mtype, &mage->spellpoints, &mage->spchange); mage->magietyp = (magic_t)mtype; for (i=0;i!=MAXCOMBATSPELLS;++i) { int spid; fscanf (F, "%d %d", &spid, &mage->combatspelllevel[i]); mage->combatspell[i] = (spellid_t)spid; } for (;;) { int spid; fscanf (F, "%d", &spid); if (spid < 0) break; *sp = calloc (sizeof(spell_ptr), 1); (*sp)->spellid = (spellid_t)spid; sp = &(*sp)->next; } return AT_READ_OK; } static void write_mage(const attrib * a, FILE * F) { int i; sc_mage *mage = (sc_mage*)a->data.v; spell_ptr *sp = mage->spellptr; fprintf (F, "%d %d %d ", mage->magietyp, mage->spellpoints, mage->spchange); for (i=0;i!=MAXCOMBATSPELLS;++i) { fprintf (F, "%d %d ", mage->combatspell[i], mage->combatspelllevel[i]); } while (sp) { fprintf (F, "%d ", sp->spellid); sp = sp->next; } fprintf (F, "-1\n"); } attrib_type at_mage = { "mage", init_mage, free_mage, NULL, write_mage, read_mage, ATF_UNIQUE }; boolean is_mage(const unit * u) { return i2b(get_mage(u) != NULL); } sc_mage * get_mage(const unit * u) { if (has_skill(u, SK_MAGIC)) { attrib * a = a_find(u->attribs, &at_mage); if (a) return a->data.v; } return (sc_mage *) NULL; } magic_t find_magetype(const unit * u) { sc_mage *m; /* Null abfangen! */ m = get_mage(u); if (!m) return 0; return m->magietyp; } /* ------------------------------------------------------------- */ /* ein 'neuer' Magier bekommt von Anfang an alle Sprüche seines * Magiegebietes mit einer Stufe kleiner oder gleich seinem Magietalent * in seine List-of-known-spells. Ausgenommen mtyp 0 (M_GRAU) */ static void createspelllist(unit *u, magic_t mtyp) { int sk, i; if (mtyp == M_GRAU) return; sk = effskill(u, SK_MAGIC); if (sk == 0) return; for (i = 0; spelldaten[i].id != SPL_NOSPELL; i++) { if (spelldaten[i].magietyp == mtyp && spelldaten[i].level <= sk) { if (!getspell(u, spelldaten[i].id)) addspell(u, spelldaten[i].id); } } } /* ------------------------------------------------------------- */ /* Erzeugen eines neuen Magiers */ sc_mage * create_mage(unit * u, magic_t mtyp) { sc_mage *mage; attrib *a; int i; mage = calloc(1, sizeof(sc_mage)); mage->magietyp = mtyp; mage->spellpoints = 0; mage->spchange = 0; mage->spellcount = 0; for (i=0;icombatspell[i] = SPL_NOSPELL; } mage->spellptr = NULL; a = a_add(&u->attribs, a_new(&at_mage)); a->data.v = mage; createspelllist(u, mtyp); return mage; } /* ------------------------------------------------------------- */ /* Ausgabe der Spruchbeschreibungen * Anzeige des Spruchs nur, wenn die Stufe des besten Magiers vorher * kleiner war (u->faction->seenspells). Ansonsten muss nur geprüft * werden, ob dieser Magier den Spruch schon kennt, und andernfalls der * Spruch zu seiner List-of-known-spells hinzugefügt werden. */ attrib_type at_seenspell = { "seenspell", NULL, NULL, NULL, DEFAULT_WRITE, DEFAULT_READ }; static boolean already_seen(const faction * f, spellid_t id) { attrib *a; for (a = a_find(f->attribs, &at_seenspell); a; a=a->nexttype) if (a->data.i==id) return true; return false; } void updatespelllist(unit * u) { int max = eff_skill(u, SK_MAGIC, u->region); int i, sk = max; magic_t gebiet = find_magetype(u); if (u->faction->no==MONSTER_FACTION) return; /* Magier mit keinem bzw M_GRAU bekommen weder Sprüche angezeigt noch * neue Sprüche in ihre List-of-known-spells. Das sind zb alle alten * Drachen, die noch den Skill Magie haben */ if (gebiet == M_GRAU) return; for (i = 0; spelldaten[i].id != SPL_NOSPELL; i++) { boolean know = getspell(u, spelldaten[i].id); if (know || (spelldaten[i].magietyp == gebiet && spelldaten[i].level <= sk)) { if (!know) addspell(u, spelldaten[i].id); if (!already_seen(u->faction, spelldaten[i].id)) { a_add(&u->faction->attribs, a_new(&at_reportspell))->data.i = spelldaten[i].id; a_add(&u->faction->attribs, a_new(&at_seenspell))->data.i = spelldaten[i].id; } } } /* Nur Orkmagier bekommen den Keuschheitsamulettzauber */ if (old_race(u->race) == RC_ORC && !getspell(u, SPL_ARTEFAKT_CHASTITYBELT) && find_spellbyid(SPL_ARTEFAKT_CHASTITYBELT)->level <= max) { addspell(u,SPL_ARTEFAKT_CHASTITYBELT); if (!already_seen(u->faction, spelldaten[i].id)) { a_add(&u->faction->attribs, a_new(&at_reportspell))->data.i = SPL_ARTEFAKT_CHASTITYBELT; a_add(&u->faction->attribs, a_new(&at_seenspell))->data.i = SPL_ARTEFAKT_CHASTITYBELT; } } /* Nur Wyrm-Magier bekommen den Wyrmtransformationszauber */ if (fspecial(u->faction, FS_WYRM) && !getspell(u, SPL_BECOMEWYRM) && find_spellbyid(SPL_BECOMEWYRM)->level <= max) { addspell(u, SPL_BECOMEWYRM); if (!already_seen(u->faction, spelldaten[i].id)) { a_add(&u->faction->attribs, a_new(&at_reportspell))->data.i = SPL_BECOMEWYRM; a_add(&u->faction->attribs, a_new(&at_seenspell))->data.i = SPL_BECOMEWYRM; } } /* Transformierte Wyrm-Magier bekommen Drachenodem */ if (dragonrace(u->race)) { race_t urc = old_race(u->race); switch (urc) { /* keine breaks! Wyrme sollen alle drei Zauber können.*/ case RC_WYRM: { if(!getspell(u, SPL_WYRMODEM) && find_spellbyid(SPL_WYRMODEM)->level <= max) { addspell(u, SPL_WYRMODEM); if (!already_seen(u->faction, spelldaten[i].id)) { a_add(&u->faction->attribs, a_new(&at_reportspell))->data.i = SPL_WYRMODEM; a_add(&u->faction->attribs, a_new(&at_seenspell))->data.i = SPL_WYRMODEM; } } } case RC_DRAGON: { if(!getspell(u, SPL_DRAGONODEM) && find_spellbyid(SPL_DRAGONODEM)->level <= max) { addspell(u, SPL_DRAGONODEM); if (!already_seen(u->faction, spelldaten[i].id)) { a_add(&u->faction->attribs, a_new(&at_reportspell))->data.i = SPL_DRAGONODEM; a_add(&u->faction->attribs, a_new(&at_seenspell))->data.i = SPL_DRAGONODEM; } } } case RC_FIREDRAGON: { if(!getspell(u, SPL_FIREDRAGONODEM) && find_spellbyid(SPL_FIREDRAGONODEM)->level <= max) { addspell(u, SPL_FIREDRAGONODEM); if (!already_seen(u->faction, spelldaten[i].id)) { a_add(&u->faction->attribs, a_new(&at_reportspell))->data.i = SPL_FIREDRAGONODEM; a_add(&u->faction->attribs, a_new(&at_seenspell))->data.i = SPL_FIREDRAGONODEM; } } } } } return; } /* ------------------------------------------------------------- */ /* Funktionen für die Bearbeitung der List-of-known-spells */ void addspell(unit *u, spellid_t spellid) { sc_mage *m; spell_ptr *newsp; m = get_mage(u); if (!m) { return; } newsp = calloc(1, sizeof(spell_ptr)); newsp->spellid = spellid; addlist(&m->spellptr, newsp); return; } boolean getspell(const unit *u, spellid_t spellid) { sc_mage *m; spell_ptr *spt; m = get_mage(u); if (!m) { return false; } for (spt = m->spellptr; spt; spt = spt->next) { if (spt->spellid == spellid) { return true; } } return false; } /* ------------------------------------------------------------- */ /* Spruch identifizieren */ #include "umlaut.h" typedef struct spell_names { struct spell_names * next; const struct locale * lang; magic_t mtype; struct tnode names; } spell_names; static spell_names * spellnames; static spell_names * init_spellnames(const struct locale * lang, magic_t mtype) { int i; spell_names * sn = calloc(sizeof(spell_names), 1); sn->next = spellnames; sn->lang = lang; sn->mtype = mtype; for (i=0; spelldaten[i].id != SPL_NOSPELL; i++) { const char * n = spelldaten[i].sname; if (spelldaten[i].magietyp!=mtype) continue; if (spelldaten[i].info==NULL) n = locale_string(lang, mkname("spell", n)); addtoken(&sn->names, n, (void*)(spelldaten+i)); } return spellnames = sn; } static spell_names * get_spellnames(const struct locale * lang, magic_t mtype) { spell_names * sn = spellnames; while (sn) { if (sn->mtype==mtype && sn->lang==lang) break; sn=sn->next; } if (!sn) return init_spellnames(lang, mtype); return sn; } spell * find_spellbyname(unit *u, const char *name, const struct locale * lang) { spell_ptr *spt; sc_mage * m = get_mage(u); spell * sp = NULL; spell_names * sn; if (!m) return NULL; sn = get_spellnames(lang, m->magietyp); if (findtoken(&sn->names, name, (void**)&sp)==E_TOK_NOMATCH) { magic_t mtype; for(mtype=0;mtype!=MAXMAGIETYP;++mtype) { sn = get_spellnames(lang, mtype); if (findtoken(&sn->names, name, (void**)&sp)!=E_TOK_NOMATCH) break; } } if (sp!=NULL) { for (spt = m->spellptr; spt; spt = spt->next) { if (sp->id==spt->spellid) return sp; } } if (lang==default_locale) return NULL; return find_spellbyname(u, name, default_locale); } spell * find_spellbyid(spellid_t id) { int i; for (i = 0; spelldaten[i].id != SPL_NOSPELL; i++) { if (spelldaten[i].id == id) { return &spelldaten[i]; } } return (spell *) NULL; } /* ------------------------------------------------------------- */ /* Eingestellte Kampfzauberstufe ermitteln */ int get_combatspelllevel(const unit *u, int nr) { sc_mage *m; assert(nr < MAXCOMBATSPELLS); m = get_mage(u); if (!m) { return -1; } return min(m->combatspelllevel[nr], eff_skill(u, SK_MAGIC, u->region)); } /* ------------------------------------------------------------- */ /* Kampfzauber ermitteln, setzen oder löschen */ spell* get_combatspell(const unit *u, int nr) { sc_mage *m; assert(nr < MAXCOMBATSPELLS); m = get_mage(u); if (m) { return find_spellbyid(m->combatspell[nr]); } else if(u->race->precombatspell != NO_SPELL) { return find_spellbyid(u->race->precombatspell); } return NULL; } void set_combatspell(unit *u, spell *sp, const char * cmd, int level) { sc_mage *m; m = get_mage(u); if (!m) return; /* knowsspell prüft auf ist_magier, ist_spruch, kennt_spruch */ if (knowsspell(u->region, u, sp) == false){ /* Fehler 'Spell not found' */ cmistake(u, cmd, 173, MSG_MAGIC); return; } if (getspell(u, sp->id) == false) { /* Diesen Zauber kennt die Einheit nicht */ cmistake(u, cmd, 169, MSG_MAGIC); return; } if (!(sp->sptyp & ISCOMBATSPELL)) { /* Diesen Kampfzauber gibt es nicht */ cmistake(u, cmd, 171, MSG_MAGIC); return; } if (sp->sptyp & PRECOMBATSPELL) { m->combatspell[0] = sp->id; m->combatspelllevel[0] = level; } else if (sp->sptyp & COMBATSPELL) { m->combatspell[1] = sp->id; m->combatspelllevel[1] = level; } else if (sp->sptyp & POSTCOMBATSPELL) { m->combatspell[2] = sp->id; m->combatspelllevel[2] = level; } return; } void unset_combatspell(unit *u, spell *sp) { sc_mage *m; int nr = 0; int i; m = get_mage(u); if (!m) return; /* Kampfzauber löschen */ if (!sp) { for (i=0;icombatspell[i] = SPL_NOSPELL; } } else if (sp->sptyp & PRECOMBATSPELL) { if (sp != get_combatspell(u,0)) return; } else if (sp->sptyp & COMBATSPELL) { if (sp != get_combatspell(u,1)) { return; } else { nr = 1; } } else if (sp->sptyp & POSTCOMBATSPELL) { if (sp != get_combatspell(u,2)) { return; } else { nr = 2; } } m->combatspell[nr] = SPL_NOSPELL; m->combatspelllevel[nr] = 0; return; } /* ------------------------------------------------------------- */ /* Gibt die aktuelle Anzahl der Magiepunkte der Einheit zurück */ int get_spellpoints(const unit * u) { sc_mage *m; m = get_mage(u); if (!m) return 0; return m->spellpoints; } void set_spellpoints(unit * u, int sp) { sc_mage *m; m = get_mage(u); if (!m) return; m->spellpoints = sp; return; } /* * verändert die Anzahl der Magiepunkte der Einheit um +mp */ int change_spellpoints(unit * u, int mp) { sc_mage *m; int sp; m = get_mage(u); if (!m) return 0; /* verhindere negative Magiepunkte */ sp = max(m->spellpoints + mp, 0); m->spellpoints = sp; return sp; } /* bietet die Möglichkeit, die maximale Anzahl der Magiepunkte mit * Regionszaubern oder Attributen zu beinflussen */ static int get_spchange(const unit * u) { sc_mage *m; m = get_mage(u); if (!m) return 0; return m->spchange; } /* ein Magier kann normalerweise maximal Stufe^2.1/1.2+1 Magiepunkte * haben. * Manche Rassen haben einen zusätzlichen Multiplikator * Durch Talentverlust (zB Insekten im Berg) können negative Werte * entstehen */ /* Artefakt der Stärke * Ermöglicht dem Magier mehr Magiepunkte zu 'speichern' */ /** TODO: at_skillmod daraus machen */ static int use_item_aura(const region * r, const unit * u) { int sk, n; sk = eff_skill(u, SK_MAGIC, r); n = (int)(sk * sk * u->race->maxaura / 4); return n; } int max_spellpoints(const region * r, const unit * u) { int sk, n; double msp; double potenz = 2.1; double divisor = 1.2; sk = eff_skill(u, SK_MAGIC, r); msp = u->race->maxaura*(pow(sk, potenz)/divisor+1) + get_spchange(u); if (get_item(u,I_AURAKULUM) > 0) { msp += use_item_aura(r, u); } n = get_curseeffect(u->attribs, C_AURA, 0); if (n>0) msp = (msp*n)/100; return max((int)msp, 0); } int change_maxspellpoints(unit * u, int csp) { sc_mage *m; m = get_mage(u); if (!m) return 0; m->spchange += csp; return max_spellpoints(u->region, u); } /* ------------------------------------------------------------- */ /* Counter für die bereits gezauberte Anzahl Sprüche pro Runde. * Um nur die Zahl der bereits gezauberten Sprüche zu ermitteln mit * step = 0 aufrufen. */ int countspells(unit *u, int step) { sc_mage *m; int count; m = get_mage(u); if (!m) return 0; if (step == 0) return m->spellcount; count = m->spellcount + step; /* negative Werte abfangen. */ m->spellcount = max(0,count); return m->spellcount; } /* ------------------------------------------------------------- */ /* Die für den Spruch benötigte Aura pro Stufe. * Die Grundkosten pro Stufe werden hier um 2^count erhöht. Der * Parameter count ist dabei die Anzahl der bereits gezauberten Sprüche */ int spellcost(unit *u, spell * sp) { int k, aura = 0; int count; count = countspells(u,0); for (k = 0; k < MAXINGREDIENT; k++) { if (sp->komponenten[k][0] == R_AURA) { aura = sp->komponenten[k][1]; } } aura *= (int)pow(2.0,(double)count); return aura; } /* ------------------------------------------------------------- */ /* SPC_LINEAR ist am höchstwertigen, dann müssen Komponenten für die * Stufe des Magiers vorhanden sein. * SPC_LINEAR hat die gewünschte Stufe als multiplikator, * nur SPC_FIX muss nur einmal vorhanden sein, ist also am * niedrigstwertigen und sollte von den beiden anderen Typen * überschrieben werden */ static int spl_costtyp(spell * sp) { int k; int costtyp = SPC_FIX; for (k = 0; k < MAXINGREDIENT; k++) { if (costtyp == SPC_LINEAR) return SPC_LINEAR; if (sp->komponenten[k][2] == SPC_LINEAR) { return SPC_LINEAR; } /* wenn keine Fixkosten, Typ übernehmen */ if (sp->komponenten[k][2] != SPC_FIX) { costtyp = sp->komponenten[k][2]; } } return costtyp; } /* ------------------------------------------------------------- */ /* durch Komponenten und cast_level begrenzter maximal möglicher * Level * Da die Funktion nicht alle Komponenten durchprobiert sondern beim * ersten Fehler abbricht, muss die Fehlermeldung später mit cancast() * generiert werden. * */ int eff_spelllevel(unit *u, spell * sp, int cast_level, int range) { int k; int maxlevel; int needplevel; int costtyp = SPC_FIX; for (k = 0; k < MAXINGREDIENT; k++) { if (cast_level == 0) return 0; if (sp->komponenten[k][1] > 0) { /* Die Kosten für Aura sind auch von der Zahl der bereits * gezauberten Sprüche abhängig */ if (sp->komponenten[k][0] == R_AURA) { needplevel = spellcost(u, sp) * range; } else { needplevel = sp->komponenten[k][1] * range; } maxlevel = get_pooled(u, u->region, sp->komponenten[k][0])/needplevel; /* sind die Kosten fix, so muss die Komponente nur einmal vorhanden * sein und der cast_level ändert sich nicht */ if (sp->komponenten[k][2] == SPC_FIX) { if (maxlevel < 1) cast_level = 0; /* ansonsten wird das Minimum aus maximal möglicher Stufe und der * gewünschten gebildet */ } else if (sp->komponenten[k][2] == SPC_LEVEL) { costtyp = SPC_LEVEL; cast_level = min(cast_level, maxlevel); /* bei Typ Linear müssen die Kosten in Höhe der Stufe vorhanden * sein, ansonsten schlägt der Spruch fehl */ } else if (sp->komponenten[k][2] == SPC_LINEAR) { costtyp = SPC_LINEAR; if (maxlevel < cast_level) cast_level = 0; } } } /* Ein Spruch mit Fixkosten wird immer mit der Stufe des Spruchs und * nicht auf der Stufe des Magiers gezaubert */ if (costtyp == SPC_FIX) { cast_level = min(cast_level, sp->level); } return cast_level; } /* ------------------------------------------------------------- */ /* Die Spruchgrundkosten werden mit der Entfernung (Farcasting) * multipliziert, wobei die Aurakosten ein Sonderfall sind, da sie sich * auch durch die Menge der bereits gezauberten Sprüche erhöht. * Je nach Kostenart werden dann die Komponenten noch mit cast_level * multipliziert. */ void pay_spell(unit * u, spell * sp, int cast_level, int range) { int k; int resuse; for (k = 0; k != MAXINGREDIENT; k++) { if (sp->komponenten[k][0] == R_AURA) { resuse = spellcost(u, sp) * range; } else { resuse = sp->komponenten[k][1] * range; } if (sp->komponenten[k][2] == SPC_LINEAR || sp->komponenten[k][2] == SPC_LEVEL) { resuse *= cast_level; } use_pooled(u, u->region, sp->komponenten[k][0], resuse); } } /* ------------------------------------------------------------- */ /* Ein Magier kennt den Spruch und kann sich die Beschreibung anzeigen * lassen, wenn diese in seiner Spruchliste steht. Zaubern muss er ihn * aber dann immer noch nicht können, vieleicht ist seine Stufe derzeit * nicht ausreichend oder die Komponenten fehlen. */ boolean knowsspell(const region * r, const unit * u, const spell * sp) { /* Ist überhaupt ein gültiger Spruch angegeben? */ if (!sp || (sp->id == SPL_NOSPELL)) { return false; } /* Magier? */ if (get_mage(u) == NULL) { log_warning(("%s ist kein Magier, versucht aber zu zaubern.\n", unitname(u))); return false; } /* steht der Spruch in der Spruchliste? */ if (getspell(u, sp->id) == false){ /* ist der Spruch aus einem anderen Magiegebiet? */ if (find_magetype(u) != sp->magietyp){ return false; } if (eff_skill(u, SK_MAGIC, u->region) >= sp->level){ log_warning(("%s ist hat die erforderliche Stufe, kennt aber %s nicht.\n", unitname(u), spell_name(sp, default_locale))); } return false; } /* hier sollten alle potentiellen Fehler abgefangen sein */ return true; } /* Um einen Spruch zu beherrschen, muss der Magier die Stufe des * Spruchs besitzen, nicht nur wissen, das es ihn gibt (also den Spruch * in seiner Spruchliste haben). * Kosten für einen Spruch können Magiepunkte, Silber, Kraeuter * und sonstige Gegenstaende sein. */ boolean cancast(unit * u, spell * sp, int level, int range, char * cmd) { int k; resource_t res; int itemanz; boolean b = true; if (knowsspell(u->region, u, sp) == false) { /* Diesen Zauber kennt die Einheit nicht */ cmistake(u, strdup(cmd), 173, MSG_MAGIC); return false; } /* reicht die Stufe aus? */ if (eff_skill(u, SK_MAGIC, u->region) < sp->level) { /* die Einheit ist nicht erfahren genug für diesen Zauber */ cmistake(u, strdup(cmd), 169, MSG_MAGIC); return false; } for (k = 0; k < MAXINGREDIENT; k++) { if (sp->komponenten[k][1] > 0) { res = sp->komponenten[k][0]; /* Die Kosten für Aura sind auch von der Zahl der bereits * gezauberten Sprüche abhängig */ if (sp->komponenten[k][0] == R_AURA) { itemanz = spellcost(u, sp) * range; } else { itemanz = sp->komponenten[k][1] * range; } /* sind die Kosten stufenabhängig, so muss itemanz noch mit dem * level multipliziert werden */ switch(sp->komponenten[k][2]) { case SPC_LEVEL: case SPC_LINEAR: itemanz *= level; break; case SPC_FIX: default: break; } if (get_pooled(u, u->region, res) < itemanz) { if (b == false) { /* es fehlte schon eine andere Komponente, wir basteln die * Meldung weiter zusammen */ scat(", "); icat(itemanz); scat(locale_string(u->faction->locale, resname(res, (itemanz == 1 ? 0 : 1)))); } else { /* Noch fehlte keine Komponente, wir generieren den Anfang der * Fehlermeldung */ sprintf(buf, "%s in %s: 'ZAUBER %s' Für diesen Zauber fehlen " "noch %d ", unitname(u), regionid(u->region), spell_name(sp, u->faction->locale), itemanz); scat(locale_string(u->faction->locale, resname(res, (itemanz == 1 ? 0 : 1)))); b = false; } } } } if (b == false) { scat("."); addmessage(0, u->faction, buf, MSG_MAGIC, ML_MISTAKE); return false; } return true; } /* ------------------------------------------------------------- */ /* generische Spruchstaerke, * * setzt sich derzeit aus der Stufe des Magiers, Magieturmeffekt, * Spruchitems und Antimagiefeldern zusammen. Es koennen noch die * Stufe des Spruchs und Magiekosten mit einfliessen. * * Die effektive Spruchstärke und ihre Auswirkungen werden in der * Spruchfunktionsroutine ermittelt. */ double spellpower(region * r, unit * u, spell * sp, int cast_level) { curse * c; double force = cast_level; if (sp==NULL) { return 0; } else { /* Bonus durch Magieturm und gesegneten Steinkreis */ struct building * b = inside_building(u); const struct building_type * btype = b?b->type:NULL; if (btype && btype->flags & BTF_MAGIC) ++force; } if (get_item(u, I_RING_OF_POWER) > 0) ++force; /* Antimagie in der Zielregion */ c = get_curse(r->attribs, ct_find("antimagiczone")); if (curse_active(c)) { force -= curse_geteffect(c); curse_changevigour(&r->attribs, c, -cast_level); cmistake(u, findorder(u, u->thisorder), 185, MSG_MAGIC); } /* Patzerfluch-Effekt: */ c = get_curse(r->attribs, ct_find("fumble")); if (curse_active(c)) { force -= curse_geteffect(c); curse_changevigour(&u->attribs, c, -1); cmistake(u, findorder(u, u->thisorder), 185, MSG_MAGIC); } #ifdef MAGICPOWER force = force * MAGICPOWER; #endif return max(force, 0); } /* ------------------------------------------------------------- */ /* farcasting() == 1 -> gleiche Region, da man mit Null nicht vernünfigt * rechnen kann */ static int farcasting(unit *magician, region *r) { int dist; int mult; if (!r) { return 1025; } dist = koor_distance(r->x, r->y, magician->region->x, magician->region->y); if (dist > 24) return 1025; mult = (int)pow(2.0,(double)dist); if (dist > 1) { if (!path_exists(magician->region, r, dist*2, allowed_fly)) mult = 1025; } return mult; } /* ------------------------------------------------------------- */ /* Antimagie - Magieresistenz */ /* ------------------------------------------------------------- */ /* allgemeine Magieresistenz einer Einheit, * reduziert magischen Schaden */ double magic_resistance(unit *target) { attrib * a; curse *c; int n; /* Bonus durch Rassenmagieresistenz */ double probability = target->race->magres; /* Magier haben einen Resistenzbonus vom Magietalent * 5%*/ probability += effskill(target, SK_MAGIC)*0.05; /* Auswirkungen von Zaubern auf der Einheit */ c = get_curse(target->attribs, ct_find("magicresistance")); if (c) { probability += 0.01 * curse_geteffect(c) * get_cursedmen(target, c); } /* Unicorn +10 */ n = get_item(target, I_UNICORN); if (n) probability += n*0.1/target->number; /* Auswirkungen von Zaubern auf der Region */ a = a_find(target->region->attribs, &at_curse); while (a) { curse *c = (curse*)a->data.v; unit *mage = c->magician; if (mage!=NULL) { if (c->type == ct_find("goodmagicresistancezone")) { if (alliedunit(mage, target->faction, HELP_GUARD)) { probability += curse_geteffect(c)*0.01; break; } } else if (c->type == ct_find("badmagicresistancezone")) { if (alliedunit(mage, target->faction, HELP_GUARD)) { a = a->nexttype; continue; } } } a = a->nexttype; } /* Bonus durch Artefakte */ /* TODO (noch gibs keine)*/ /* Bonus durch Gebäude */ { struct building * b = inside_building(target); const struct building_type * btype = b?b->type:NULL; /* gesegneter Steinkreis gibt 30% dazu */ if (btype) probability += btype->magresbonus * 0.01; } return probability; } /* ------------------------------------------------------------- */ /* Prüft, ob das Objekt dem Zauber widerstehen kann. * Objekte können Regionen, Units, Gebäude oder Schiffe sein. * TYP_UNIT: * Das höchste Talent des Ziels ist sein 'Magieresistenz-Talent', Magier * bekommen einen Bonus. Grundchance ist 50%, für jede Stufe * Unterschied gibt es 5%, minimalchance ist 5% für jeden (5-95%) * Scheitert der Spruch an der Magieresistenz, so gibt die Funktion * true zurück */ boolean target_resists_magic(unit *magician, void *obj, int objtyp, int t_bonus) { double probability = 0.0; if (magician == NULL) return true; if (obj == NULL) return true; switch(objtyp) { case TYP_UNIT: { int at, pa = 0; skill * sv; unit * u = (unit*)obj; if (fspecial(u->faction, FS_MAGICIMMUNE)) return true; at = effskill(magician, SK_MAGIC); for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) { int sk = effskill(u, sv->id); if (pa < sk) pa = sk; } /* Contest */ probability = 0.05 * (10 + pa - at); probability += magic_resistance((unit *)obj); break; } case TYP_REGION: /* Bonus durch Zauber */ probability += 0.01 * get_curseeffect(((region *)obj)->attribs, C_RESIST_MAGIC, 0); break; case TYP_BUILDING: /* Bonus durch Zauber */ probability += 0.01 * get_curseeffect(((building *)obj)->attribs, C_RESIST_MAGIC, 0); /* Bonus durch Typ */ probability += 0.01 * ((building *)obj)->type->magres; break; case TYP_SHIP: /* Bonus durch Zauber */ probability += 0.01 * get_curseeffect(((ship *)obj)->attribs, C_RESIST_MAGIC, 0); break; } probability = max(0.02, probability + t_bonus*0.01); probability = min(0.98, probability); /* gibt true, wenn die Zufallszahl kleiner als die chance ist und * false, wenn sie gleich oder größer ist, dh je größer die * Magieresistenz (chance) desto eher gibt die Funktion true zurück */ return chance(probability); } /* ------------------------------------------------------------- */ boolean is_magic_resistant(unit *magician, unit *target, int resist_bonus) { return (boolean)target_resists_magic(magician, target, TYP_UNIT, resist_bonus); } /* ------------------------------------------------------------- */ /* Patzer */ /* ------------------------------------------------------------- */ /* allgemeine Zauberpatzer: * Treten auf, wenn die Stufe des Magieres zu niedrig ist. * Abhaengig von Staerke des Spruchs. * * Die Warscheinlichkeit reicht von 20% (beherrscht den Spruch gerade * eben) bis zu etwa 1% bei doppelt so gut wie notwendig */ boolean fumble(region * r, unit * u, spell * sp, int cast_grade) { /* X ergibt Zahl zwischen 1 und 0, je kleiner, desto besser der Magier. * 0,5*40-20=0, dh wenn der Magier doppelt so gut ist, wie der Spruch * benötigt, gelingt er immer, ist er gleich gut, gelingt der Spruch mit * 20% Warscheinlichkeit nicht * */ int rnd = 0; double x = (double) cast_grade / (double) eff_skill(u, SK_MAGIC, r); int patzer = (int) (((double) x * 40.0) - 20.0); struct building * b = inside_building(u); const struct building_type * btype = b?b->type:NULL; if (btype) patzer -= btype->fumblebonus; /* CHAOSPATZERCHANCE 10 : +10% Chance zu Patzern */ if (sp->magietyp == M_CHAOS) { patzer += CHAOSPATZERCHANCE; } if (is_cursed(u->attribs, C_MBOOST, 0) == true) { patzer += CHAOSPATZERCHANCE; } if (is_cursed(u->attribs, C_FUMBLE, 0) == true) { patzer += CHAOSPATZERCHANCE; } /* wenn die Chance kleiner als 0 ist, können wir gleich false * zurückgeben */ if (patzer <= 0) { #ifdef PATZERDEBUG printf("%s: Zauber Stufe %d, Talent %d, Patzerchance %d\n", unitname(u), cast_grade, eff_skill(u, SK_MAGIC, r), patzer); #endif return false; } rnd = rand()%100; #ifdef PATZERDEBUG printf("%s: Zauber Stufe %d, Talent %d, Patzerchance %d, rand: %d\n", unitname(u), cast_grade, eff_skill(u, SK_MAGIC, r), patzer, rnd); #endif if (rnd > patzer) { /* Glück gehabt, kein Patzer */ return false; } return true; } /* Die normalen Spruchkosten müssen immer bezahlt werden, hier noch * alle weiteren Folgen eines Patzers */ static void do_fumble(castorder *co) { curse * c; region * r = co->rt; unit * u = (unit*)co->magician; spell * sp = co->sp; int level = co->level; int duration; switch (rand() % 10) { /* wenn vorhanden spezieller Patzer, ansonsten nix */ case 0: sp->patzer(co); break; case 1: /* Kröte */ duration = rand()%level/2; if (duration<2) duration = 2; { /* one or two things will happen: the toad changes her race back, * and may or may not get toadslime. * The list of things to happen are attached to a timeout * trigger and that's added to the triggerlit of the mage gone toad. */ trigger * trestore = trigger_changerace(u, u->race, u->irace); if (rand()%10>2) t_add(&trestore, trigger_giveitem(u, olditemtype[I_TOADSLIME], 1)); add_trigger(&u->attribs, "timer", trigger_timeout(duration, trestore)); } u->race = new_race[RC_TOAD]; u->irace = new_race[RC_TOAD]; sprintf(buf, "Eine Botschaft von %s: 'Ups! Quack, Quack!'", unitname(u)); addmessage(r, 0, buf, MSG_MAGIC, ML_MISTAKE); break; case 2: /* temporärer Stufenverlust */ duration = max(rand()%level/2, 2); c = create_curse(u, &u->attribs, ct_find("skil"), level, duration, -(level/2), 1); c->data = (void*)SK_MAGIC; add_message(&u->faction->msgs, new_message(u->faction, "patzer2%u:unit%r:region", u, r)); break; case 3: case 4: /* Spruch schlägt fehl, alle Magiepunkte weg */ set_spellpoints(u, 0); add_message(&u->faction->msgs, msg_message("patzer3", "unit region command", u, r, spell_name(sp, u->faction->locale))); break; case 5: case 6: /* Spruch gelingt, aber alle Magiepunkte weg */ ((nspell_f)sp->sp_function)(co); set_spellpoints(u, 0); sprintf(buf, "Als %s versucht, '%s' zu zaubern erhebt sich " "plötzlich ein dunkler Wind. Bizarre geisterhafte " "Gestalten kreisen um den Magier und scheinen sich von " "den magischen Energien des Zaubers zu ernähren. Mit letzter " "Kraft gelingt es %s dennoch den Spruch zu zaubern.", unitname(u), spell_name(sp, u->faction->locale), unitname(u)); addmessage(0, u->faction, buf, MSG_MAGIC, ML_WARN); break; case 7: case 8: case 9: default: /* Spruch gelingt, alle nachfolgenden Sprüche werden 2^4 so teuer */ ((nspell_f)sp->sp_function)(co); sprintf(buf, "%s fühlt sich nach dem Zaubern von %s viel erschöpfter " "als sonst und hat das Gefühl, dass alle weiteren Zauber deutlich " "mehr Kraft als normalerweise kosten werden.", unitname(u), spell_name(sp, u->faction->locale)); addmessage(0, u->faction, buf, MSG_MAGIC, ML_WARN); countspells(u,3); } return; } /* ------------------------------------------------------------- */ /* Regeneration von Aura */ /* ------------------------------------------------------------- */ /* Ein Magier regeneriert pro Woche W(Stufe^1.5/2+1), mindestens 1 * Zwerge nur die Hälfte */ static int regeneration(unit * u) { int sk, aura, d; double potenz = 1.5; double divisor = 2.0; if (fspecial(u->faction, FS_MAGICIMMUNE)) return 0; sk = effskill(u, SK_MAGIC); /* Rassenbonus/-malus */ d = (int)(pow(sk, potenz) * u->race->regaura / divisor); d++; /* Einfluss von Artefakten */ /* TODO (noch gibs keine)*/ /* Würfeln */ aura = (rand() % d + rand() % d)/2 + 1; #ifdef MAGICREGEN aura = (int)(aura * MAGICREGEN); #endif return aura; } void regeneration_magiepunkte(void) { region *r; unit *u; int aura, auramax; double reg_aura; int n; for (r = regions; r; r = r->next) { for (u = r->units; u; u = u->next) { if (is_mage(u)) { aura = get_spellpoints(u); auramax = max_spellpoints(r, u); if (aura < auramax) { struct building * b = inside_building(u); const struct building_type * btype = b?b->type:NULL; reg_aura = (double)regeneration(u); /* Magierturm erhöht die Regeneration um 75% */ /* Steinkreis erhöht die Regeneration um 50% */ if (btype) reg_aura *= btype->auraregen; /* Bonus/Malus durch Zauber */ n = get_curseeffect(u->attribs, C_AURA, 0); if (n>0) { reg_aura = (reg_aura*n)/100; } /* Einfluss von Artefakten */ /* TODO (noch gibs keine)*/ /* maximal Differenz bis Maximale-Aura regenerieren * mindestens 1 Aura pro Monat */ reg_aura = max(1,reg_aura); reg_aura = min((auramax - aura), reg_aura); aura += (int)reg_aura; ADDMSG(&u->faction->msgs, msg_message( "regenaura", "unit region amount", u, r, (int)reg_aura)); } set_spellpoints(u, min(aura, auramax)); /* Zum letzten Mal Spruchliste aktualisieren */ updatespelllist(u); } } } } /* ------------------------------------------------------------- */ /* ------------------------------------------------------------- */ /* Zuerst wird versucht alle noch nicht gefundenen Objekte zu finden * oder zu prüfen, ob das gefundene Objekt wirklich hätte gefunden * werden dürfen (nicht alle Zauber wirken global). Dabei zählen wir die * Misserfolge (failed). * Dann folgen die Tests der gefundenen Objekte auf Magieresistenz und * Sichtbarkeit. Dabei zählen wir die magieresistenten (resists) * Objekte. Alle anderen werten wir als Erfolge (success) */ /* gibt bei Misserfolg 0 zurück, bei Magieresistenz zumindeste eines * Objektes 1 und bei Erfolg auf ganzer Linie 2 */ static int verify_targets(castorder *co) { unit *mage = (unit *)co->magician; spell *sp = co->sp; region *target_r = co->rt; spellparameter *sa = co->par; spllprm *spobj; int failed = 0; int resists = 0; int success = 0; int i; if (sa) { /* zuerst versuchen wir vorher nicht gefundene Objekte zu finden. * Wurde ein Objekt durch globalsuche gefunden, obwohl der Zauber * gar nicht global hätte suchen dürften, setzen wir das Objekt * zurück. */ for (i=0;ilength;i++) { spobj = sa->param[i]; switch(spobj->typ) { case SPP_UNIT_ID: { unit *u; if (sp->sptyp & SEARCHGLOBAL) { u = findunitg(spobj->data.i, target_r); } else { u = findunitr(target_r, spobj->data.i); } if (!u){ /* Einheit nicht gefunden */ spobj->flag = TARGET_NOTFOUND; failed++; add_message(&mage->faction->msgs, new_message(mage->faction, "spellunitnotfound%u:unit%r:region%s:command%s:id", mage, mage->region, strdup(co->order), strdup(itoa36(spobj->data.i)))); break; } else { /* Einheit wurde nun gefunden, pointer umsetzen */ spobj->flag = 0; spobj->typ = SPP_UNIT; spobj->data.u = u; } break; } /* TEMP-Einheit */ case SPP_TUNIT_ID: { unit *u; /* Versuch 1 : Region der Zauberwirkung */ u = findnewunit(target_r, mage->faction, spobj->data.i); if (!u){ /* Versuch 2 : Region des Magiers */ u = findnewunit(mage->region, mage->faction, spobj->data.i); } if (!u && sp->sptyp & SEARCHGLOBAL) { /* Fehler: TEMP-Einheiten kann man nicht global suchen. der * TEMP-Name gilt immer nur jeweils für die Region */ } if (!u){ /* Einheit nicht gefunden */ char tbuf[1024]; spobj->flag = TARGET_NOTFOUND; failed++; /* Meldung zusammenbauen, sonst steht dort anstatt * "TEMP " nur "" */ sprintf(tbuf, "%s %s", LOC(mage->faction->locale, parameters[P_TEMP]), itoa36(spobj->data.i)); add_message(&mage->faction->msgs, new_message(mage->faction, "spellunitnotfound%u:unit%r:region%s:command%s:id", mage, mage->region, strdup(co->order), strdup(tbuf))); break; } else { /* Einheit wurde nun gefunden, pointer umsetzen */ spobj->flag = 0; spobj->typ = SPP_UNIT; spobj->data.u = u; } break; } case SPP_UNIT: { unit *u = spobj->data.u; if (!(sp->sptyp & SEARCHGLOBAL) && !findunitr(target_r, u->no)) { /* die Einheit befindet sich nicht in der Zielregion */ spobj->typ = SPP_UNIT_ID; spobj->data.i = u->no; spobj->flag = TARGET_NOTFOUND; failed++; add_message(&mage->faction->msgs, new_message(mage->faction, "spellunitnotfound%u:unit%r:region%s:command%s:id", mage, mage->region, strdup(co->order), strdup(itoa36(spobj->data.i)))); break; } break; } case SPP_BUILDING_ID: { building *b; if (sp->sptyp & SEARCHGLOBAL) { b = findbuilding(spobj->data.i); } else { b = findbuildingr(target_r, spobj->data.i); } if (!b) { /* Burg nicht gefunden */ spobj->flag = TARGET_NOTFOUND; failed++; add_message(&mage->faction->msgs, new_message(mage->faction, "spellbuildingnotfound%u:unit%r:region%s:command%i:id", mage, mage->region, strdup(co->order), spobj->data.i)); break; } else { spobj->typ = SPP_BUILDING; spobj->flag = 0; spobj->data.b = b; } break; } case SPP_BUILDING: { building *b; b = spobj->data.b; if (!(sp->sptyp & SEARCHGLOBAL) && !findbuildingr(target_r, b->no)) { /* die Burg befindet sich nicht in der Zielregion */ spobj->typ = SPP_BUILDING_ID; spobj->data.i = b->no; spobj->flag = TARGET_NOTFOUND; failed++; add_message(&mage->faction->msgs, new_message(mage->faction, "spellbuildingnotfound%u:unit%r:region%s:command%i:id", mage, mage->region, strdup(co->order), spobj->data.i)); break; } break; } case SPP_SHIP_ID: { ship *sh; if (sp->sptyp & SEARCHGLOBAL) { sh = findship(spobj->data.i); } else { sh = findshipr(target_r, spobj->data.i); } if (!sh) { /* Schiff nicht gefunden */ spobj->flag = TARGET_NOTFOUND; failed++; add_message(&mage->faction->msgs, new_message(mage->faction, "spellshipnotfound%u:unit%r:region%s:command%i:id", mage, mage->region, strdup(co->order), spobj->data.i)); break; } else { spobj->typ = SPP_SHIP; spobj->flag = 0; spobj->data.sh = sh; } break; } case SPP_SHIP: { ship *sh; sh = spobj->data.sh; if (!(sp->sptyp & SEARCHGLOBAL) && !findshipr(target_r, sh->no)) { /* das Schiff befindet sich nicht in der Zielregion */ spobj->typ = SPP_SHIP_ID; spobj->data.i = sh->no; spobj->flag = TARGET_NOTFOUND; failed++; add_message(&mage->faction->msgs, new_message(mage->faction, "spellshipnotfound%u:unit%r:region%s:command%i:id", mage, mage->region, strdup(co->order), spobj->data.i)); break; } break; } case SPP_REGION: case SPP_INT: case SPP_STRING: default: break; } } /* Nun folgen die Tests auf cansee und Magieresistenz */ for (i=0;ilength;i++) { spobj = sa->param[i]; switch(spobj->typ) { case SPP_UNIT: { unit *u; u = spobj->data.u; /* Wenn auf Sichtbarkeit geprüft werden soll und die Einheit * nicht gesehen wird und auch nicht kontaktiert, dann melde * 'Einheit nicht gefunden' */ if ((sp->sptyp & TESTCANSEE) && !cansee(mage->faction, target_r, u, 0) && !ucontact(u, mage)) { spobj->typ = SPP_UNIT_ID; spobj->data.i = u->no; spobj->flag = TARGET_NOTFOUND; add_message(&mage->faction->msgs, new_message(mage->faction, "spellunitnotfound%u:unit%r:region%s:command%s:id", mage, mage->region, strdup(co->order), strdup(itoa36(spobj->data.i)))); failed++; break; } if ((sp->sptyp & TESTRESISTANCE) && target_resists_magic(mage, u, TYP_UNIT, 0)) { /* Fehlermeldung */ spobj->typ = SPP_UNIT_ID; spobj->data.i = u->no; spobj->flag = TARGET_RESISTS; resists++; add_message(&mage->faction->msgs, new_message(mage->faction, "spellunitresists%u:unit%r:region%s:command%d:id", mage, mage->region, strdup(co->order), spobj->data.i)); break; } /* TODO: Test auf Parteieigenschaft Magieresistsenz */ success++; break; } case SPP_BUILDING: { building *b; b = spobj->data.b; if ((sp->sptyp & TESTRESISTANCE) && target_resists_magic(mage, b, TYP_BUILDING, 0)) { /* Fehlermeldung */ spobj->typ = SPP_BUILDING_ID; spobj->data.i = b->no; spobj->flag = TARGET_RESISTS; resists++; add_message(&mage->faction->msgs, new_message(mage->faction, "spellbuildingresists%u:unit%r:region%s:command%d:id", mage, mage->region, strdup(co->order), spobj->data.i)); break; } success++; break; } case SPP_SHIP: { ship *sh; sh = spobj->data.sh; if ((sp->sptyp & TESTRESISTANCE) && target_resists_magic(mage, sh, TYP_SHIP, 0)) { /* Fehlermeldung */ spobj->typ = SPP_SHIP_ID; spobj->data.i = sh->no; spobj->flag = TARGET_RESISTS; resists++; add_message(&mage->faction->msgs, new_message(mage->faction, "spellshipresists%u:unit%r:region%s:command%d:id", mage, mage->region, strdup(co->order), spobj->data.i)); break; } success++; break; } case SPP_REGION: { /* haben wir ein Regionsobjekt, dann wird auch dieses und nicht target_r überprüft. */ region *tr; tr = spobj->data.r; if (!tr) { /* Fehlermeldung */ failed++; add_message(&mage->faction->msgs, new_message(mage->faction, "spellregionresists%u:unit%r:region%s:command", mage, mage->region, strdup(co->order))); break; } if ((sp->sptyp & TESTRESISTANCE) && target_resists_magic(mage, tr, TYP_REGION, 0)) { /* Fehlermeldung */ spobj->flag = TARGET_RESISTS; resists++; add_message(&mage->faction->msgs, new_message(mage->faction, "spellregionresists%u:unit%r:region%s:command", mage, mage->region, strdup(co->order))); break; } success++; break; } case SPP_INT: case SPP_STRING: success++; break; case SPP_SHIP_ID: case SPP_TUNIT_ID: case SPP_UNIT_ID: case SPP_BUILDING_ID: default: break; } } } else { /* der Zauber hat keine expliziten Parameter/Ziele, es kann sich * aber um einen Regionszauber handeln. Wenn notwendig hier die * Magieresistenz der Region prüfen. */ if ((sp->sptyp & REGIONSPELL)) { /* Zielobjekt Region anlegen */ sa = calloc(1, sizeof(spellparameter)); sa->length = 1; sa->param = calloc(sa->length, sizeof(spllprm *)); spobj = calloc(1, sizeof(spllprm)); spobj->typ = SPP_REGION; spobj->data.r = target_r; sa->param[0] = spobj; co->par = sa; if ((sp->sptyp & TESTRESISTANCE)) { if (target_resists_magic(mage, target_r, TYP_REGION, 0)) { /* Fehlermeldung */ add_message(&mage->faction->msgs, new_message(mage->faction, "spellregionresists%u:unit%r:region%s:command", mage, mage->region, strdup(co->order))); spobj->flag = TARGET_RESISTS; resists++; } else { success++; } } else { success++; } } } if (failed > 0) { /* mindestens ein Ziel wurde nicht gefunden */ if (sa->length <= failed) { return 0; } if (success == 0) { /* kein Ziel war ein Erfolg */ if (resists > 0){ /* aber zumindest ein Ziel wurde gefunden, hat nur wiederstanden */ return 1; } else { /* nur Fehlschläge */ return 0; } } } /* kein Fehlschlag gefunden */ if (resists > 0){ return 1; } return 2; } /* ------------------------------------------------------------- */ /* Hilfsstrukturen für ZAUBERE */ /* ------------------------------------------------------------- */ static void free_spellparameter(spellparameter *pa) { int i; /* Elemente free'en */ for (i=0; i < pa->length; i++) { switch(pa->param[i]->typ) { case SPP_STRING: free(pa->param[i]->data.s); break; default: break; } free(pa->param[i]); } if (pa->param) free(pa->param); /* struct free'en */ free(pa); } static spellparameter * add_spellparameter(region *target_r, unit *u, const char *syntax, char *s, int skip) { int i = 1; int c = 0; int l = 0; char *tbuf, *token; spellparameter *par; spllprm *spobj; par = calloc(1, sizeof(spellparameter)); /* Temporären Puffer initialisieren */ tbuf = strdup(s); /* Tokens zählen */ token = strtok(tbuf, " "); while(token) { par->length++; token = strtok(NULL, " "); } /* length sollte nun nur noch die Anzahl der für den Zauber relevanten * Elemente enthalten */ par->length -= skip; /* Anzahl der Elemente ('temp 123' sind zwei!) */ /* mindestens ein Ziel (Ziellose Zauber werden nicht * geparst) */ if (par->length < 1) { /* Fehler: Ziel vergessen */ cmistake(u, strdup(s), 203, MSG_MAGIC); /* Aufräumen */ free(tbuf); free(par); return 0; } /* Pointer allozieren */ par->param = calloc(par->length, sizeof(spllprm *)); /* Tokens zuweisen */ strcpy(tbuf, s); token = strtok (tbuf, " "); while(token && syntax[c] != 0) { if (i > skip) { if (syntax[c] == '+') { /* das vorhergehende Element kommt ein oder mehrmals vor, wir * springen zum key zurück */ c--; } if (syntax[c] == '?') { /* optionales Element vom Typ des nachfolgenden Zeichen, wir * gehen ein Element weiter */ c++; } spobj = calloc(1, sizeof(spllprm)); switch(syntax[c]) { case 'u': { /* Parameter ist eine Unitid */ unit *ut; int unitname; /* Zuerst feststellen, ob es sich um eine Temp-Einheit * handelt. Steht dort das Schlüsselwort EINHEIT, so hat * garantiert jemand die Syntax falsch verstanden und die * Einheitennummer kommt dahinter. In beiden Fällen wird der * Befehl um ein token weiter eingelesen und es muß geprüft * werden, ob der Befehlsstring überhaupt lang genug wäre. */ switch (findparam(token, u->faction->locale)) { case P_TEMP: i++; if (par->length < i - skip) { /* Fehler: Ziel vergessen */ cmistake(u, strdup(s), 203, MSG_MAGIC); /* Aufräumen */ free(tbuf); free(spobj); par->length = l; free_spellparameter(par); return 0; } token = strtok(NULL, " "); unitname = atoi36(token); spobj->typ = SPP_TUNIT_ID; ut = findnewunit(u->region, u->faction, unitname); break; case P_UNIT: i++; if (par->length < i - skip) { /* Fehler: Ziel vergessen */ cmistake(u, strdup(s), 203, MSG_MAGIC); /* Aufräumen */ free(tbuf); free(spobj); par->length = l; free_spellparameter(par); return 0; } token = strtok(NULL, " "); default: unitname = atoi36(token); ut = findunitr(target_r, unitname); } if (ut) { spobj->typ = SPP_UNIT; spobj->data.u = ut; } else if (spobj->typ && spobj->typ == SPP_TUNIT_ID){ /* die TEMP Einheit wurde nicht gefunden */ spobj->data.i = unitname; } else { /* eine Zieleinheit wurde nicht gefunden, wir merken uns die * Unitid für später. */ spobj->typ = SPP_UNIT_ID; spobj->data.i = unitname; } break; } case 'r': { /* Parameter sind zwei Regionskoordinaten */ region *rt; int x, y, tx, ty; i++; if (par->length < i - skip) { /* Fehler: Zielregion vergessen */ cmistake(u, strdup(s), 194, MSG_MAGIC); /* Aufräumen */ free(tbuf); free(spobj); par->length = l; free_spellparameter(par); return 0; } tx = atoi(token); token = strtok(NULL, " "); ty = atoi(token); /* die relativen Koordinaten ins absolute Koordinatensystem * umrechnen */ x = rel_to_abs(0,u->faction,tx,0); y = rel_to_abs(0,u->faction,ty,1); rt = findregion(x,y); if (rt) { spobj->typ = SPP_REGION; spobj->data.r = rt; } else { /* Fehler: Zielregion vergessen */ cmistake(u, strdup(s), 194, MSG_MAGIC); /* Aufräumen */ free(tbuf); free(spobj); par->length = l; free_spellparameter(par); return 0; } break; } case 'b': { /* Parameter ist eine Burgnummer */ building *b; int b_id; #ifdef FULL_BASE36 b_id = atoi36(token); #else b_id = atoi(token); #endif b = findbuilding(b_id); if (b) { spobj->typ = SPP_BUILDING; spobj->data.b = b; } else { /* eine Burg mit der Nummer b_id wurde in dieser Region * nicht gefunden, wir merken uns die b_id für später. */ spobj->typ = SPP_BUILDING_ID; spobj->data.i = b_id; } break; } case 's': { /* Parameter ist eine Schiffsnummer */ ship *sh; int shid; #ifdef FULL_BASE36 shid = atoi36(token); #else shid = atoi(token); #endif sh = findshipr(target_r, shid); if (sh) { spobj->typ = SPP_SHIP; spobj->data.sh = sh; } else { /* ein Schiff mit der Nummer shipname wurde in dieser Region * nicht gefunden, wir merken uns die shipname für später. */ spobj->typ = SPP_SHIP_ID; spobj->data.i = shid; } break; } case 'c': /* Text, wird im Spruch ausgewertet */ spobj->typ = SPP_STRING; spobj->data.s = strdup(token); break; case 'i': /* Zahl */ spobj->typ = SPP_INT; spobj->data.i = atoi(token); break; case 'k': { /* keyword, dieses Element beschreibt was für ein Typ nachfolgt */ c++; /* das nächste Zeichen ist immer ein c für einen variablen Stringparameter */ switch (findparam(token, u->faction->locale)) { case P_REGION: { spobj->typ = SPP_REGION; spobj->data.r = target_r; break; } case P_UNIT: { unit *ut; int unitname; i++; if (par->length < i - skip) { /* Fehler: Ziel vergessen */ cmistake(u, strdup(s), 203, MSG_MAGIC); /* Aufräumen */ free(tbuf); free(spobj); par->length = l; free_spellparameter(par); return 0; } token = strtok(NULL, " "); if (findparam(token, u->faction->locale) == P_TEMP) { i++; if (par->length < i - skip) { /* Fehler: Ziel vergessen */ cmistake(u, strdup(s), 203, MSG_MAGIC); /* Aufräumen */ free(tbuf); free(spobj); par->length = l; free_spellparameter(par); return 0; } token = strtok(NULL, " "); unitname = atoi36(token); ut = findnewunit(u->region, u->faction, unitname); } else { unitname = atoi36(token); ut = findunitr(target_r, unitname); } if (ut) { spobj->typ = SPP_UNIT; spobj->data.u = ut; } else { /* eine Zieleinheit wurde nicht gefunden, wir merken uns die * Unitid für später. */ spobj->typ = SPP_UNIT_ID; spobj->data.i = unitname; } break; } case P_BUILDING: case P_GEBAEUDE: { building *b; int b_id; i++; if (par->length < i - skip) { /* Fehler: Ziel vergessen */ cmistake(u, strdup(s), 203, MSG_MAGIC); /* Aufräumen */ free(tbuf); free(spobj); par->length = l; free_spellparameter(par); return 0; } token = strtok(NULL, " "); #ifdef FULL_BASE36 b_id = atoi36(token); #else b_id = atoi(token); #endif b = findbuilding(b_id); if (b) { spobj->typ = SPP_BUILDING; spobj->data.b = b; } else { /* eine Burg mit der Nummer b_id wurde in dieser Region * nicht gefunden, wir merken uns die b_id für später. */ spobj->typ = SPP_BUILDING_ID; spobj->data.i = b_id; } break; } case P_SHIP: { ship *sh; int shid; i++; if (par->length < i - skip) { /* Fehler: Ziel vergessen */ cmistake(u, strdup(s), 203, MSG_MAGIC); /* Aufräumen */ free(tbuf); free(spobj); par->length = l; free_spellparameter(par); return 0; } token = strtok(NULL, " "); #ifdef FULL_BASE36 shid = atoi36(token); #else shid = atoi(token); #endif sh = findshipr(target_r, shid); if (sh) { spobj->typ = SPP_SHIP; spobj->data.sh = sh; } else { /* ein Schiff mit der Nummer shipname wurde in dieser Region * nicht gefunden, wir merken uns die shipname für später. */ spobj->typ = SPP_SHIP_ID; spobj->data.i = shid; } break; } default: /* Syntax Error. */ cmistake(u, strdup(s), 209, MSG_MAGIC); /* Aufräumen */ free(tbuf); free(spobj); par->length = l; free_spellparameter(par); return 0; } break; } default: /* Syntax Error. */ cmistake(u, strdup(s), 209, MSG_MAGIC); /* Aufräumen */ free(tbuf); free(spobj); par->length = l; free_spellparameter(par); return 0; } par->param[l] = spobj; l++; c++; } i++; token = strtok(NULL, " "); } /* im Endeffekt waren es nur l parameter */ par->length = l; /* das letzte Zeichen des Syntaxstrings sollte 0 oder ein + * sein, ansonsten fehlte ein Parameter */ if (syntax[c] != 0 && syntax[c] != '+' && syntax[c] != '?') { /* Syntax Error. */ cmistake(u, strdup(s), 209, MSG_MAGIC); /* Aufräumen */ free(tbuf); free_spellparameter(par); return 0; } /* Aufräumen */ free(tbuf); return par; } /* ------------------------------------------------------------- */ castorder * new_castorder(void *u, unit *u2, spell *sp, region *r, int lev, double force, int range, char *cmd, spellparameter *p) { castorder *corder; corder = calloc(1, sizeof(castorder)); corder->magician = u; corder->familiar = u2; corder->sp = sp; corder->level = lev; corder->force = force; corder->rt = r; corder->distance = range; corder->order = cmd; corder->par = p; return corder; } /* Hänge c-order co an die letze c-order von cll an */ void add_castorder(castorder **cll, castorder *co) { castorder *co2; /* Anfang der Liste? */ if (*cll == NULL) { *cll = co; return; } /* suche letztes element */ for (co2 = *cll; co2->next != NULL; co2 = co2->next) { } co2->next = co; return; } void free_castorders(castorder *co) { castorder *co2; while(co) { co2 = co; co = co->next; if (co2->par) { free_spellparameter(co2->par); } if (co2->order) free(co2->order); free(co2); } return; } /* ------------------------------------------------------------- */ /*** ** at_familiarmage **/ typedef struct familiar_data { unit * mage; unit * familiar; } famililar_data; static void write_unit(const attrib * a, FILE * F) { unit * u = (unit*)a->data.v; write_unit_reference(u, F); } static int sm_familiar(const unit * u, const region * r, skill_t sk, int value) /* skillmod */ { if (sk==SK_MAGIC) return value; else { int mod; unit * familiar = get_familiar(u); if (familiar==NULL) { log_error(("[sm_familiar] %s has a familiar-skillmod, but no familiar\n", unitname(u))); return value; } mod = eff_skill(familiar, sk, r)/2; if (r != familiar->region) { mod /= distance(r, familiar->region); } return value + mod; } } static void set_familiar(unit * mage, unit * familiar) { /* if the skill modifier for the mage does not yet exist, add it */ attrib * a = a_find(mage->attribs, &at_skillmod); while (a) { skillmod_data * smd = (skillmod_data *)a->data.v; if (smd->special==sm_familiar) break; a = a->nexttype; } if (a==NULL) { attrib * an = a_add(&mage->attribs, a_new(&at_skillmod)); skillmod_data * smd = (skillmod_data *)an->data.v; smd->special = sm_familiar; smd->skill=NOSKILL; } a = a_find(mage->attribs, &at_familiar); if (a==NULL) { a = a_add(&mage->attribs, a_new(&at_familiar)); a->data.v = familiar; } else assert(!a->data.v || a->data.v == familiar); /* TODO: Diese Attribute beim Tod des Familiars entfernen: */ a = a_find(familiar->attribs, &at_familiarmage); if (a==NULL) { a = a_add(&familiar->attribs, a_new(&at_familiarmage)); a->data.v = mage; } else assert(!a->data.v || a->data.v == mage); } void remove_familiar(unit *mage) { attrib *a = a_find(mage->attribs, &at_familiar); attrib *an; skillmod_data *smd; a_remove(&mage->attribs, a); a = a_find(mage->attribs, &at_skillmod); while (a) { an = a->nexttype; smd = (skillmod_data *)a->data.v; if (smd->special==sm_familiar) a_remove(&mage->attribs, a); a = an; } } void create_newfamiliar(unit * mage, unit * familiar) { /* if the skill modifier for the mage does not yet exist, add it */ attrib * a = a_find(mage->attribs, &at_skillmod); while (a) { skillmod_data * smd = (skillmod_data *)a->data.v; if (smd->special==sm_familiar) break; a = a->nexttype; } if (a==NULL) { attrib * an = a_add(&mage->attribs, a_new(&at_skillmod)); skillmod_data * smd = (skillmod_data *)an->data.v; smd->special = sm_familiar; smd->skill=NOSKILL; } a = a_find(mage->attribs, &at_familiar); if (a==NULL) { a = a_add(&mage->attribs, a_new(&at_familiar)); a->data.v = familiar; } else assert(!a->data.v || a->data.v == familiar); /* TODO: Diese Attribute beim Tod des Familiars entfernen: */ a = a_find(familiar->attribs, &at_familiarmage); if (a==NULL) { a = a_add(&familiar->attribs, a_new(&at_familiarmage)); a->data.v = mage; } else assert(!a->data.v || a->data.v == mage); /* Wenn der Magier stirbt, dann auch der Vertraute */ add_trigger(&mage->attribs, "destroy", trigger_killunit(familiar)); /* Wenn der Vertraute stirbt, dann bekommt der Magier einen Schock */ add_trigger(&familiar->attribs, "destroy", trigger_shock(mage)); } static void * resolve_familiar(void * data) { unit * familiar = resolve_unit(data); if (familiar) { attrib * a = a_find(familiar->attribs, &at_familiarmage); if (a!=NULL && a->data.v) { unit * mage = (unit *)a->data.v; set_familiar(mage, familiar); } } return familiar; } static int read_familiar(attrib * a, FILE * F) { int i; fscanf(F, "%s", buf); i = atoi36(buf); ur_add((void*)i, &a->data.v, resolve_familiar); return AT_READ_OK; } /* clones */ void create_newclone(unit * mage, unit * clone) { attrib *a; a = a_find(mage->attribs, &at_clone); if (a == NULL) { a = a_add(&mage->attribs, a_new(&at_clone)); a->data.v = clone; } else assert(!a->data.v || a->data.v == clone); /* TODO: Diese Attribute beim Tod des Klons entfernen: */ a = a_find(clone->attribs, &at_clonemage); if (a == NULL) { a = a_add(&clone->attribs, a_new(&at_clonemage)); a->data.v = mage; } else assert(!a->data.v || a->data.v == mage); /* Wenn der Magier stirbt, wird das in destroy_unit abgefangen. * Kein Trigger, zu kompliziert. */ /* Wenn der Klon stirbt, dann bekommt der Magier einen Schock */ add_trigger(&clone->attribs, "destroy", trigger_clonedied(mage)); } static void set_clone(unit * mage, unit * clone) { attrib *a; a = a_find(mage->attribs, &at_clone); if (a==NULL) { a = a_add(&mage->attribs, a_new(&at_clone)); a->data.v = clone; } else assert(!a->data.v || a->data.v == clone); a = a_find(clone->attribs, &at_clonemage); if (a==NULL) { a = a_add(&clone->attribs, a_new(&at_clonemage)); a->data.v = mage; } else assert(!a->data.v || a->data.v == mage); } unit * has_clone(unit *mage) { attrib *a = a_find(mage->attribs, &at_clone); if(a) return (unit *)a->data.v; return NULL; } static void * resolve_clone(void * data) { unit * clone = resolve_unit(data); if (clone) { attrib * a = a_find(clone->attribs, &at_clonemage); if (a!=NULL && a->data.v) { unit * mage = (unit *)a->data.v; set_clone(mage, clone); } } return clone; } static int read_clone(attrib * a, FILE * F) { int i; fscanf(F, "%s", buf); i = atoi36(buf); ur_add((void*)i, &a->data.v, resolve_clone); return AT_READ_OK; } /* mages */ static void * resolve_mage(void * data) { unit * mage = resolve_unit(data); if (mage) { attrib * a = a_find(mage->attribs, &at_familiar); if (a!=NULL && a->data.v) { unit * familiar = (unit *)a->data.v; set_familiar(mage, familiar); } } return mage; } static int read_magician(attrib * a, FILE * F) { int i; fscanf(F, "%s", buf); i = atoi36(buf); ur_add((void*)i, &a->data.v, resolve_mage); return AT_READ_OK; } static int age_unit(attrib * a) /* if unit is gone or dead, remove the attribute */ { unit * u = (unit*)a->data.v; return (u!=NULL && u->number>0); } attrib_type at_familiarmage = { "familiarmage", NULL, NULL, age_unit, write_unit, read_magician, ATF_UNIQUE }; attrib_type at_familiar = { "familiar", NULL, NULL, age_unit, write_unit, read_familiar, ATF_UNIQUE }; attrib_type at_clonemage = { "clonemage", NULL, NULL, age_unit, write_unit, read_magician, ATF_UNIQUE }; attrib_type at_clone = { "clone", NULL, NULL, age_unit, write_unit, read_clone, ATF_UNIQUE }; unit * get_familiar(const unit *u) { attrib * a = a_find(u->attribs, &at_familiar); if (a!=NULL) return (unit*)a->data.v; return NULL; } unit * get_familiar_mage(const unit *u) { attrib * a = a_find(u->attribs, &at_familiarmage); if (a!=NULL) return (unit*)a->data.v; return NULL; } unit * get_clone(const unit *u) { attrib * a = a_find(u->attribs, &at_clone); if (a!=NULL) return (unit*)a->data.v; return NULL; } unit * get_clone_mage(const unit *u) { attrib * a = a_find(u->attribs, &at_clonemage); if (a!=NULL) return (unit*)a->data.v; return NULL; } static boolean is_moving_ship(const region * r, const ship *sh) { const unit *u; int todo; u = shipowner(r, sh); todo = igetkeyword(u->thisorder, u->faction->locale); if (todo == K_ROUTE || todo == K_MOVE || todo == K_FOLLOW){ return true; } return false; } /* ------------------------------------------------------------- */ /* Damit man keine Rituale in fremden Gebiet machen kann, diese vor * Bewegung zaubern. Magier sind also in einem fremden Gebiet eine Runde * lang verletzlich, da sie es betreten, und angegriffen werden können, * bevor sie ein Ritual machen können. * * Syntax: ZAUBER [REGION X Y] [STUFE ] "Spruchname" [Einheit-1 * Einheit-2 ..] * * Nach Priorität geordnet die Zauber global auswerten. * * Die Kosten für Farcasting multiplizieren sich mit der Entfernung, * cast_level gibt die virtuelle Stufe an, die den durch das Farcasten * entstandenen Spruchkosten entspricht. Sind die Spruchkosten nicht * levelabhängig, so sind die Kosten nur von der Entfernung bestimmt, * die Stärke/Level durch den realen Skill des Magiers */ void magic(void) { region *r; region *target_r; unit *u; /* Aktuelle unit in Region */ unit *familiar; /* wenn u ein Vertrauter ist */ unit *mage; /* derjenige, der den Spruch am Ende zaubert */ spell *sp; const char *s; strlist *so; int spellrank; int level, success; int range, t_x, t_y; double force; int skiptokens; castorder *co; castorder *cll[MAX_SPELLRANK]; spellparameter *args; for (spellrank = 0; spellrank < MAX_SPELLRANK; spellrank++) { cll[spellrank] = (castorder*)NULL; } for (r = regions; r; r = r->next) { for (u = r->units; u; u = u->next) { boolean casted = false; if (old_race(u->race) == RC_SPELL || fval(u, UFL_LONGACTION)) continue; if (old_race(u->race) == RC_INSECT && r_insectstalled(r) && !is_cursed(u->attribs, C_KAELTESCHUTZ,0)) continue; if(fval(u, UFL_WERE)) { continue; } if (attacked(u)) { continue; } for (so = u->orders; so; so = so->next) { if (igetkeyword(so->s, u->faction->locale) == K_CAST) { if (LongHunger() && fval(u, UFL_HUNGER)) { cmistake(u, so->s, 224, MSG_MAGIC); continue; } if (r->planep && fval(r->planep, PFL_NOMAGIC)) { cmistake(u, so->s, 269, MSG_MAGIC); continue; } casted = true; target_r = r; mage = u; level = eff_skill(u, SK_MAGIC, r); familiar = NULL; skiptokens = 1; s = getstrtoken(); /* für Syntax ' STUFE x REGION y z ' */ if (findparam(s, u->faction->locale) == P_LEVEL) { s = getstrtoken(); level = min(atoip(s), level); s = getstrtoken(); skiptokens += 2; if (level < 1) { /* Fehler "Das macht wenig Sinn" */ cmistake(u, so->s, 10, MSG_MAGIC); continue; } } if (findparam(s, u->faction->locale) == P_REGION) { t_x = atoi(getstrtoken()); t_x = rel_to_abs(getplane(u->region),u->faction,t_x,0); t_y = atoi(getstrtoken()); t_y = rel_to_abs(getplane(u->region),u->faction,t_y,1); target_r = findregion(t_x, t_y); s = getstrtoken(); skiptokens += 3; if (!target_r) { /* Fehler "Es wurde kein Zauber angegeben" */ cmistake(u, so->s, 172, MSG_MAGIC); continue; } } /* für Syntax ' REGION x y STUFE z ' * hier nach REGION nochmal auf STUFE prüfen */ if (findparam(s, u->faction->locale) == P_LEVEL) { s = getstrtoken(); level = min(atoip(s), level); s = getstrtoken(); skiptokens += 2; if (level < 1) { /* Fehler "Das macht wenig Sinn" */ cmistake(u, so->s, 10, MSG_MAGIC); continue; } } if (!s[0] || strlen(s) == 0) { /* Fehler "Es wurde kein Zauber angegeben" */ cmistake(u, so->s, 172, MSG_MAGIC); continue; } sp = find_spellbyname(u, s, u->faction->locale); /* Vertraute können auch Zauber sprechen, die sie selbst nicht * können. find_spellbyname findet aber nur jene Sprüche, die * die Einheit beherrscht. */ if (sp == NULL && is_familiar(u)) { familiar = u; mage = get_familiar_mage(u); sp = find_spellbyname(mage, s, mage->faction->locale); } if (sp == NULL) { /* Fehler 'Spell not found' */ cmistake(u, so->s, 173, MSG_MAGIC); continue; } /* um testen auf spruchnamen zu unterbinden sollte vor allen * fehlermeldungen die anzeigen das der magier diesen Spruch * nur in diese Situation nicht anwenden kann, noch eine * einfache Sicherheitsprüfung kommen */ if (knowsspell(r, u, sp) == false){ /* vorsicht! u kann der familiar sein */ if (!familiar){ cmistake(u, so->s, 173, MSG_MAGIC); continue; } } if (sp->sptyp & ISCOMBATSPELL) { /* Fehler: "Dieser Zauber ist nur im Kampf sinnvoll" */ cmistake(u, so->s, 174, MSG_MAGIC); continue; } /* Auf dem Ozean Zaubern als quasi-langer Befehl können * normalerweise nur Meermenschen, ausgenommen explizit als * OCEANCASTABLE deklarierte Sprüche */ if (rterrain(r) == T_OCEAN) { if (old_race(u->race) != RC_AQUARIAN && !fval(u->race, RCF_SWIM) && !(sp->sptyp & OCEANCASTABLE)) { /* Fehlermeldung */ ADDMSG(&u->faction->msgs, msg_message("spellfail_onocean", "unit region command", u, u->region, so->s)); continue; } /* Auf bewegenden Schiffen kann man nur explizit als * ONSHIPCAST deklarierte Zauber sprechen */ } else if (u->ship) { if (is_moving_ship(r, u->ship)) { if (!(sp->sptyp & ONSHIPCAST)) { /* Fehler: "Diesen Spruch kann man nicht auf einem sich * bewegenden Schiff stehend zaubern" */ cmistake(u, so->s, 175, MSG_MAGIC); continue; } } } /* Farcasting bei nicht farcastbaren Sprüchen abfangen */ range = farcasting(u, target_r); if (range > 1) { if (!(sp->sptyp & FARCASTING)) { /* Fehler "Diesen Spruch kann man nicht in die Ferne * richten" */ cmistake(u, so->s, 176, MSG_MAGIC); continue; } if (range > 1024) { /* (2^10) weiter als 10 Regionen entfernt */ sprintf(buf, "%s in %s: 'ZAUBER %s' Zu der Region %s kann keine " "Verbindung hergestellt werden", unitname(u), regionid(u->region), spell_name(sp, u->faction->locale), regionid(target_r)); addmessage(0, u->faction, buf, MSG_MAGIC, ML_MISTAKE); continue; } } /* Stufenangabe bei nicht Stufenvariierbaren Sprüchen abfangen */ if (!(sp->sptyp & SPELLLEVEL)) { int ilevel = eff_skill(u, SK_MAGIC, u->region); if (ilevel!=level) { level = ilevel; sprintf(buf, "%s in %s: 'ZAUBER %s' Dieser Zauber kann nicht " "mit Stufenangabe gezaubert werden.", unitname(u), regionid(u->region), spell_name(sp, u->faction->locale)); addmessage(0, u->faction, buf, MSG_MAGIC, ML_WARN); } } /* Vertrautenmagie */ /* Kennt der Vertraute den Spruch, so zaubert er ganz normal. * Ansonsten zaubert der Magier durch seinen Vertrauten, dh * zahlt Komponenten und Aura. Dabei ist die maximale Stufe * die des Vertrauten! * Der Spruch wirkt dann auf die Region des Vertrauten und * gilt nicht als Farcasting. */ if (familiar || is_familiar(u)) { if ((sp->sptyp & NOTFAMILIARCAST)) { /* Fehler: "Diesen Spruch kann der Vertraute nicht zaubern" */ cmistake(u, so->s, 177, MSG_MAGIC); continue; } if (!knowsspell(r, u, sp)) { /* Magier zaubert durch Vertrauten */ mage = get_familiar_mage(u); if (range > 1) { /* Fehler! Versucht zu Farcasten */ sprintf(buf, "%s kann Sprüche, die durch %s wirken, nicht " "zusätzlich nochmal in die Ferne richten.", unitname(mage), unitname(u)); addmessage(0, u->faction, buf, MSG_MAGIC, ML_MISTAKE); continue; } if (distance(mage->region, r) > eff_skill(mage, SK_MAGIC, mage->region)) { sprintf(buf, "%s kann nicht genug Kraft aufbringen, um " "durch %s zu wirken", unitname(mage), unitname(u)); addmessage(0, u->faction, buf, MSG_MAGIC, ML_MISTAKE); continue; } /* mage auf magier setzen, level anpassen, range für Erhöhung * der Spruchkosten nutzen, langen Befehl des Magiers * löschen, zaubern kann er noch */ range *= 2; set_string(&mage->thisorder, ""); level = min(level, eff_skill(mage, SK_MAGIC, mage->region)/2); familiar = u; } } /* Weitere Argumente zusammenbasten */ if (sp->parameter) { ++skiptokens; args = add_spellparameter(target_r, mage, sp->parameter, so->s, skiptokens); if (!args) { /* Syntax war falsch */ continue; } } else { args = (spellparameter *) NULL; } co = new_castorder(mage, familiar, sp, target_r, level, 0, range, strdup(so->s), args); add_castorder(&cll[(int)(sp->rank)], co); } } if (casted) fset(u, UFL_LONGACTION); } } for (spellrank = 0; spellrank < MAX_SPELLRANK; spellrank++) { for (co = cll[spellrank]; co; co = co->next) { char *cmd = co->order; u = (unit *)co->magician; sp = co->sp; level = co->level; /* Talent des Magiers oder gewünschte Stufe */ target_r = co->rt; /* Da sich die Aura und Komponenten in der Zwischenzeit verändert * haben können und sich durch vorherige Sprüche das Zaubern * erschwert haben kann, muss hier erneut geprüft werden, ob der * Spruch überhaupt gezaubert werden kann. * (level) die effektive Stärke des Spruchs (= Stufe, auf der der * Spruch gezaubert wird) */ /* reichen die Komponenten nicht, wird der Level reduziert. */ level = eff_spelllevel(u, sp, level, co->distance); if (level < 1) { /* Fehlermeldung mit Komponenten generieren */ cancast(u, sp, co->level, co->distance, cmd); continue; } if (level < co->level){ /* Sprüche mit Fixkosten werden immer auf Stufe des Spruchs * gezaubert, co->level ist aber defaultmäßig Stufe des Magiers */ if (spl_costtyp(sp) != SPC_FIX) { sprintf(buf, "%s hat nur genügend Komponenten um %s auf Stufe %d " "zu zaubern.", unitname(u), spell_name(sp, u->faction->locale), level); addmessage(0, u->faction, buf, MSG_MAGIC, ML_INFO); } } co->level = level; /* Prüfen, ob die realen Kosten für die gewünschten Stufe bezahlt * werden können */ if (cancast(u, sp, level, co->distance, cmd) == false) { /* die Fehlermeldung wird in cancast generiert */ continue; } /* die Stärke kann durch Antimagie auf 0 sinken */ force = spellpower(target_r, u, sp, level); if (force <= 0) { sprintf(buf, "%s schafft es nicht genügend Kraft aufzubringen " "um %s dennoch zu zaubern.", unitname(u), spell_name(sp, u->faction->locale)); addmessage(0, u->faction, buf, MSG_MAGIC, ML_MISTAKE); continue; } co->force = force; /* Ziele auf Existenz prüfen und Magieresistenz feststellen. Wurde * kein Ziel gefunden, so ist verify_targets=0. Scheitert der * Spruch an der Magieresistenz, so ist verify_targets = 1, bei * Erfolg auf ganzer Linie ist verify_targets= 2 */ switch (verify_targets(co)){ case 0: /* kein Ziel gefunden, Fehlermeldungen sind in verify_targets */ continue; /* äußere Schleife, nächster Zauberer */ case 1: { /* einige oder alle Ziele waren magieresistent */ spellparameter *pa = co->par; int n; for (n=0; n!=pa->length;++n) { if(pa->param[n]->flag != TARGET_RESISTS && pa->param[n]->flag != TARGET_NOTFOUND) { /* mindestens ein erfolgreicher Zauberversuch, wir machen normal weiter */ break; } } if (n==pa->length) { /* zwar wurde mindestens ein Ziel gefunden, das widerstand * jedoch dem Zauber. Kosten abziehen und abbrechen. */ pay_spell(u, sp, level, co->distance); countspells(u, 1); sprintf(buf, "%s gelingt es %s zu zaubern, doch der Spruch zeigt " "keine Wirkung.", unitname(u), spell_name(sp, u->faction->locale)); addmessage(0, u->faction, buf, MSG_MAGIC, ML_MISTAKE); continue; /* äußere Schleife, nächster Zauberer */ } break; } case 2: default: /* Zauber war erfolgreich */ break; } /* Auch für Patzer gibt es Erfahrung, müssen die Spruchkosten * bezahlt werden und die nachfolgenden Sprüche werden teurer */ if (fumble(target_r, u, sp, level) == true) { /* zuerst bezahlen, dann evt in do_fumble alle Aura verlieren */ pay_spell(u, sp, level, co->distance); do_fumble(co); countspells(u, 1); continue; } success = ((nspell_f)sp->sp_function)(co); if (success > 0) { /* Kosten nur für real benötige Stufe berechnen */ pay_spell(u, sp, success, co->distance); /* erst bezahlen, dann Kostenzähler erhöhen */ countspells(u,1); } } } /* Sind alle Zauber gesprochen gibts Erfahrung */ for (r = regions; r; r = r->next) { for (u = r->units; u; u = u->next) { if (is_mage(u) && countspells(u,0) > 0) { produceexp(u, SK_MAGIC, u->number); /* Spruchlistenaktualiesierung ist in Regeneration */ } } } for (spellrank = 0; spellrank < MAX_SPELLRANK; spellrank++) { free_castorders(cll[spellrank]); } } attrib * create_special_direction(region *r, int x, int y, 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->x = x; d->y = y; d->duration = duration; d->desc = strdup(desc); d->keyword = strdup(keyword); return a; } const char * spell_info(const struct spell * sp, const struct locale * lang) { if (sp->info==NULL) { return LOC(lang, mkname("spellinfo", sp->sname)); } return sp->info; } const char * spell_name(const struct spell * sp, const struct locale * lang) { if (sp->info==NULL) { return LOC(lang, mkname("spell", sp->sname)); } return sp->sname; }