diff --git a/scripts/tests/common.lua b/scripts/tests/common.lua index d5b70a22c..14cdcf7b3 100644 --- a/scripts/tests/common.lua +++ b/scripts/tests/common.lua @@ -136,11 +136,9 @@ function test_fleeing_units_can_be_transported() u1.number = 100 u1:add_order("ATTACKIEREN " .. itoa36(u2.id)) u2.number = 100 - u2.name = 'Passagier' u2:add_order("FAHREN " .. itoa36(u3.id)) u2:add_order("KAEMPFE FLIEHE") u3.number = 100 - u3.name = 'Transporter' u3:add_order("KAEMPFE FLIEHE") u3:add_order("TRANSPORT " .. itoa36(u2.id)) u3:add_order("NACH O ") @@ -642,7 +640,6 @@ function test_laen2() u1:set_skill("mining", 15) u1:clear_orders() u1:add_order("MACHEN Laen") - u1.name = "Laenmeister" local b = building.create(r, "mine") b.size = 10 diff --git a/scripts/tests/e2/e2features.lua b/scripts/tests/e2/e2features.lua index 3034c7ed1..0926f4cf8 100644 --- a/scripts/tests/e2/e2features.lua +++ b/scripts/tests/e2/e2features.lua @@ -115,7 +115,6 @@ function test_dwarf_bonus() local u = unit.create(faction.create("dwarf"), r) assert_equal("dwarf", u.faction.race) assert_equal("dwarf", u.race) - u.faction.name = "Zwerge" u.number = 10 u:set_skill("mining", 1) u:add_order("MACHE EISEN") diff --git a/scripts/tests/e2/familiars.lua b/scripts/tests/e2/familiars.lua new file mode 100644 index 000000000..c60ccd3ad --- /dev/null +++ b/scripts/tests/e2/familiars.lua @@ -0,0 +1,84 @@ +local tcname = 'tests.e2.familiars' +local lunit = require('lunit') +if _VERSION >= 'Lua 5.2' then + _ENV = module(tcname, 'seeall') +else + module(tcname, lunit.testcase, package.seeall) +end + +function setup() + eressea.game.reset() + eressea.settings.set("nmr.removenewbie", "0") + eressea.settings.set("nmr.timeout", "0") + eressea.settings.set("NewbieImmunity", "0") + eressea.settings.set("rules.food.flags", "4") + eressea.settings.set("rules.peasants.growth.factor", "0") + eressea.settings.set("magic.resist.enable", "0") + eressea.settings.set("magic.fumble.enable", "0") + eressea.settings.set("magic.regeneration.enable", "0") +end + +local function setup_familiars(f, r) + f.magic = 'gwyrrd' + local uf = unit.create(f, r) + uf.magic = 'gray' + local u = unit.create(f, r) + u.magic = 'gwyrrd' + u:set_skill('magic', 9) + u.familiar = uf + return u, uf +end + +function test_moneyspell() + local r = region.create(0, 0, "plain") + local f = faction.create('human') + local um, uf = setup_familiars(f, r) + um.aura = 9 + um:add_order('ZAUBERE STUFE 9 Viehheilung') + process_orders() + assert_equal(0, um.aura) + assert_equal(450, um:get_item('money')) +end + +function test_moneyspell_through_familiar() + -- casting magician's spell with the familiar: double cost + local r = region.create(0, 0, "plain") + local f = faction.create('human') + local um, uf = setup_familiars(f, r) + um.aura = 12 + uf:add_order('ZAUBERE STUFE 3 Viehheilung') + process_orders() + assert_equal(0, uf:get_skill('magic')) + assert_equal(12, um.aura) -- cannot cast, familiar needs magic skill + assert_equal(0, uf:get_item('money')) + assert_equal(0, um:get_item('money')) + + uf:set_skill('magic', 2) -- can cast no higher than level 2 + process_orders() + assert_equal(8, um.aura) -- double the cost + assert_equal(100, uf:get_item('money')) + assert_equal(0, um:get_item('money')) + + um:set_skill('magic', 4) -- use at most half of skill + uf:set_skill('magic', 1) -- too low for level 2 spell, cast at level 1 + process_orders() + assert_equal(6, um.aura) -- double cost of level 1 + assert_equal(150, uf:get_item('money')) + assert_equal(0, um:get_item('money')) +end + +function test_moneyspell_as_familiar() + -- familiar has the spell and has magic skills: regular spellcasting rules apply + local r = region.create(0, 0, "plain") + local f = faction.create('human') + local um, uf = setup_familiars(f, r) + um.aura = 9 + uf.aura = 20 + uf:set_skill('magic', 10) + uf:add_spell('earn_silver#gwyrrd') + uf:add_order('ZAUBERE STUFE 10 Viehheilung') + process_orders() + assert_equal(500, uf:get_item('money')) + assert_equal(10, uf.aura) + assert_equal(9, um.aura) +end diff --git a/scripts/tests/e2/ships.lua b/scripts/tests/e2/ships.lua index c9f3272ea..825aeeef7 100644 --- a/scripts/tests/e2/ships.lua +++ b/scripts/tests/e2/ships.lua @@ -22,7 +22,6 @@ function test_ship_requires_skill() assert_not_nil(r2) local f = faction.create("human", "fake@eressea.de", "de") local u1 = unit.create(f, r1, 1) - u1.name = "fake" u1.ship = ship.create(r1, "longboat") u1:clear_orders() u1:add_order("NACH O") diff --git a/scripts/tests/e2/stealth.lua b/scripts/tests/e2/stealth.lua index 6a8fcd9f9..65fe1dd64 100644 --- a/scripts/tests/e2/stealth.lua +++ b/scripts/tests/e2/stealth.lua @@ -45,7 +45,6 @@ function test_stealth_faction_on() end function test_stealth_faction_other() - u.name = "Enno" u:clear_orders() u:add_order("TARNEN PARTEI NUMMER " .. itoa36(f.id)) diff --git a/scripts/tests/parser.lua b/scripts/tests/parser.lua index 7cd42b17f..446c13cfe 100644 --- a/scripts/tests/parser.lua +++ b/scripts/tests/parser.lua @@ -256,7 +256,6 @@ function test_promote_after_recruit() local r1 = region.create(0, 0, 'plain') local r2 = region.create(1, 0, 'plain') local u1 = unit.create(f, r1, 1) - u1.name = 'Xolgrim' local u2 = unit.create(f, r2, 55) u2:add_order('REKRUTIERE 1') u1:add_order('BEFOERDERE') diff --git a/src/magic.c b/src/magic.c index f902e63d6..4b512710a 100644 --- a/src/magic.c +++ b/src/magic.c @@ -267,16 +267,12 @@ bool FactionSpells(void) return rule != 0; } -int mage_get_spell_level(const sc_mage *mage, const spell *sp) { - spellbook *book = mage_get_spellbook(mage); - spellbook_entry *sbe = spellbook_get(book, sp); - return sbe ? sbe->level : 0; -} - int get_spell_level_mage(const spell * sp, void * cbdata) { sc_mage *mage = (sc_mage *)cbdata; - return mage_get_spell_level(mage, sp); + spellbook *book = mage_get_spellbook(mage); + spellbook_entry *sbe = spellbook_get(book, sp); + return sbe ? sbe->level : 0; } /* ------------------------------------------------------------- */ @@ -502,12 +498,21 @@ sc_mage *create_mage(unit * u, magic_t mtyp) /* ------------------------------------------------------------- */ /* Funktionen fuer die Bearbeitung der List-of-known-spells */ +int unit_spell_level(const unit *u, const struct spell *sp) +{ + spellbook *book = unit_get_spellbook(u); + spellbook_entry *sbe = book ? spellbook_get(book, sp) : NULL; + if (sbe) { + return sbe->level; + } + return 0; +} + bool u_hasspell(const unit *u, const struct spell *sp) { - spellbook * book = unit_get_spellbook(u); - spellbook_entry * sbe = book ? spellbook_get(book, sp) : NULL; - if (sbe) { - return sbe->level <= effskill(u, SK_MAGIC, NULL); + int level = unit_spell_level(u, sp); + if (level > 0) { + return level <= effskill(u, SK_MAGIC, NULL); } return false; } @@ -826,10 +831,9 @@ void pay_spell(unit * mage, const unit *caster, const spell * sp, int cast_level */ bool knowsspell(const region * r, const unit * u, const spell * sp) { - UNUSED_ARG(r); - assert(sp); + int level = unit_spell_level(u, sp); /* steht der Spruch in der Spruchliste? */ - return u_hasspell(u, sp); + return level > 0; } /* Um einen Spruch zu beherrschen, muss der Magier die Stufe des @@ -2479,16 +2483,16 @@ static bool is_moving_ship(ship * sh) return false; } -static int default_spell_level(const sc_mage *mage, const spell *sp) { +static int default_spell_level(const unit *u, const spell *sp) { if (sp && sp->components) { const struct spell_component *spc; for (spc = sp->components; spc->type; ++spc) { if (spc->cost != SPC_FIX) { - return -1; + return 0; } } } - return mage_get_spell_level(mage, sp); + return unit_spell_level(u, sp); } #define MAX_PARAMETERS 48 @@ -2503,7 +2507,7 @@ static castorder *cast_cmd(unit * u, order * ord) spell *sp = NULL; plane *pl; spellparameter *args = NULL; - unit * mage = u; + unit * mage = NULL; param_t param; if (LongHunger(u)) { @@ -2553,40 +2557,50 @@ static castorder *cast_cmd(unit * u, order * ord) return 0; } + /** + * skill = der maximale durch das Magietalent erlaubte Level. + * Entspricht dem Talent des Zaubernden, oder im Falle, dass ein + * Vertrauter einen Spruch seines Magiers zaubert, dessen halbes Talent. + */ + skill = effskill(u, SK_MAGIC, NULL); sp = unit_getspell(u, s, u->faction->locale); - /* Vertraute koennen auch Zauber sprechen, die sie selbst nicht - * koennen. unit_getspell findet aber nur jene Sprueche, die - * die Einheit beherrscht. */ - if (!sp && is_familiar(u)) { + + /* + * u = Die Einheit, die den Befehl gegeben hat. + * mage = Die Einheit, deren Spruchliste und Aura benutzt wird. + * + * Vertraute koennen auch Zauber sprechen, die sie selbst nicht + * koennen. `unit_getspell` findet aber nur jene Sprueche, die + * die Einheit beherrscht. In diesem Fall ist `familiar` der Vertraute. + */ + if (sp) { + /* wir zaubern selbst */ + mage = u; + } + else if (skill > 0) { + /* als Vertrauter suchen wir einen Spender-Magier mit dem Spruch */ mage = get_familiar_mage(u); if (mage) { - familiar = u; + int limit = effskill(mage, SK_MAGIC, NULL) / 2; + if (limit < skill) { + skill = limit; + } sp = unit_getspell(mage, s, mage->faction->locale); - } - else { - /* somehow, this familiar has no mage! */ - log_error("cast_cmd: familiar %s is without a mage?\n", unitname(u)); - mage = u; + if (sp->sptyp & NOTFAMILIARCAST) { + /* Fehler: "Diesen Spruch kann der Vertraute nicht zaubern" */ + cmistake(u, ord, 177, MSG_MAGIC); + return 0; + } + familiar = u; } } - + /* OBS: hier kein else! */ if (!sp) { /* Fehler 'Spell not found' */ cmistake(u, ord, 173, MSG_MAGIC); return 0; } - /* 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 Sicherheitspruefung kommen */ - if (!knowsspell(r, u, sp)) { - /* vorsicht! u kann der familiar sein */ - if (!familiar) { - cmistake(u, ord, 173, MSG_MAGIC); - return 0; - } - } if (sp->sptyp & ISCOMBATSPELL) { /* Fehler: "Dieser Zauber ist nur im Kampf sinnvoll" */ cmistake(u, ord, 174, MSG_MAGIC); @@ -2627,19 +2641,45 @@ static castorder *cast_cmd(unit * u, order * ord) cmistake(u, ord, 176, MSG_MAGIC); return 0; } - if (range > 1024) { /* (2^10) weiter als 10 Regionen entfernt */ + if (familiar) { + /* Magier zaubert durch Vertrauten: keine Fernzauber erlaubt */ + ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "familiar_farcast", + "mage", mage)); + return 0; + } + if (range > 1024) { + /* (2^10) weiter als 10 Regionen entfernt */ ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "spellfail::nocontact", "target", target_r)); return 0; } } + if (familiar) { + /* + * Magier zaubert durch Vertrauten: Doppelte Kosten. + * + * Das hier über range zu machen ist ein Hack, deshalb passiert es erst + * nach allen anderen Range-Checks. + */ + range *= 2; + } - skill = effskill(mage, SK_MAGIC, NULL); + /** + * level = Die Stufe, auf der gezaubert werden soll. + * Kann nicht höher sein als das Talent des Zaubernden, oder im + * Falle, dass ein Vertrauter einen Spruch seines Magiers zaubert, + * nicht höher als dessen halbes Talent. + */ if (level < 0) { - level = default_spell_level(get_mage(mage), sp); - if (level < 0) { + level = default_spell_level(mage, sp); + if (level <= 0) { level = skill; } + if (level > skill) { + /* die Einheit ist nicht erfahren genug fuer diesen Zauber */ + cmistake(u, ord, 169, MSG_MAGIC); + return 0; + } } else if (!(sp->sptyp & SPELLLEVEL)) { /* Stufenangabe bei nicht Stufenvariierbaren Spruechen abfangen */ @@ -2649,45 +2689,12 @@ static castorder *cast_cmd(unit * u, order * ord) "mage region command", u, u->region, ord)); } } + if (level > skill) { - /* die Einheit ist nicht erfahren genug fuer diesen Zauber */ - cmistake(u, ord, 169, MSG_MAGIC); - return 0; + /* STUFE kann nicht mehr als das erlaubte Maximum sein */ + level = skill; } - /* Vertrautenmagie */ - /* Kennt der Vertraute den Spruch, so zaubert er ganz normal. - * Ansonsten zaubert der Magier durch seinen Vertrauten, dh - * zahlt Komponenten und Aura. Dabei ist die maximale Stufe - * die des Vertrauten! - * Der Spruch wirkt dann auf die Region des Vertrauten und - * gilt nicht als Farcasting. */ - if (familiar) { - if ((sp->sptyp & NOTFAMILIARCAST)) { - /* Fehler: "Diesen Spruch kann der Vertraute nicht zaubern" */ - cmistake(u, ord, 177, MSG_MAGIC); - return 0; - } - if (mage != familiar) { /* Magier zaubert durch Vertrauten */ - int sk; - if (range > 1) { /* Fehler! Versucht zu Farcasten */ - ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "familiar_farcast", - "mage", mage)); - return 0; - } - sk = effskill(mage, SK_MAGIC, NULL); - if (distance(mage->region, r) > sk) { - ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "familiar_toofar", - "mage", mage)); - return 0; - } - /* mage auf magier setzen, level anpassen, range fuer Erhoehung - * der Spruchkosten nutzen */ - range *= 2; - sk /= 2; - if (level > sk) level = sk; - } - } /* Weitere Argumente zusammenbasteln */ if (sp->parameter) { char *params[MAX_PARAMETERS]; diff --git a/src/magic.h b/src/magic.h index 89c09d4f6..4b82a2e33 100644 --- a/src/magic.h +++ b/src/magic.h @@ -192,7 +192,6 @@ extern "C" { enum magic_t mage_get_type(const struct sc_mage *mage); const struct spell *mage_get_combatspell(const struct sc_mage *mage, int nr, int *level); struct spellbook * mage_get_spellbook(const struct sc_mage * mage); - int mage_get_spell_level(const struct sc_mage *mage, const struct spell *sp); int mage_get_spellpoints(const struct sc_mage *m); void mage_set_spellpoints(struct sc_mage *m, int aura); int mage_change_spellpoints(struct sc_mage *m, int delta); @@ -201,6 +200,7 @@ extern "C" { void unit_set_magic(struct unit *u, enum magic_t mtype); struct spellbook * unit_get_spellbook(const struct unit * u); void unit_add_spell(struct unit * u, struct spell * sp, int level); + int unit_spell_level(const struct unit *u, const struct spell *sp); bool is_mage(const struct unit *u); /* gibt true, wenn u->mage gesetzt. */ @@ -222,7 +222,9 @@ extern "C" { bool u_hasspell(const struct unit *u, const struct spell *sp); /* prueft, ob der Spruch in der Spruchliste der Einheit steht. */ void pick_random_spells(struct faction *f, int level, struct spellbook * book, int num_spells); - bool knowsspell(const struct region *r, const struct unit *u, + bool knowsspell( + const struct region *r, + const struct unit *u, const struct spell * sp); /* prueft, ob die Einheit diesen Spruch gerade beherrscht, dh * mindestens die erforderliche Stufe hat. Hier koennen auch Abfragen