using a ring does not increase the spell's cost
adding tests for spell effectiveness revealed many inconsistencies that are now fixed.
This commit is contained in:
Enno Rehling 2021-04-05 19:19:49 +02:00
parent bb12ffa2a2
commit 4d8a3d1947
13 changed files with 313 additions and 247 deletions

View File

@ -4,12 +4,15 @@
- Dämonen können magisch reanimiert werden. - Dämonen können magisch reanimiert werden.
- "Schöne Träume" verliert seine Wirkung, wenn der Zauberer stirbt. - "Schöne Träume" verliert seine Wirkung, wenn der Zauberer stirbt.
- Mit GIB 0 können hungernde Personen an die Bauern gegeben werden. - Mit GIB 0 können hungernde Personen an die Bauern gegeben werden.
- Magieresistenz: Einheiten widerstehen nicht Zaubern der eigenen Partei [2733].
- Zauberkosten steigen durch Ring der Macht nicht an.
- Effektiv gezauberte Stufe von Zauber anhängig von Verfügbarkeit der Materialen.
# 3.27 # 3.27
- Schiffe sind kommentarlos nicht nicht gesegelt [2722] - Schiffe sind kommentarlos nicht gesegelt [2722].
- Meermenschen konnten nicht mehr anschwimmen [2723] - Meermenschen konnten nicht mehr anschwimmen [2723].
- Magieresistenz repariert [2724] - Magieresistenz repariert [2724].
- Kleine Änderung an Samenwachstum. - Kleine Änderung an Samenwachstum.
- Umstellung auf neue Versionen von externen Libraries. - Umstellung auf neue Versionen von externen Libraries.

View File

@ -3807,13 +3807,6 @@
<arg name="command" type="order"/> <arg name="command" type="order"/>
</type> </type>
</message> </message>
<message name="error185" section="errors">
<type>
<arg name="unit" type="unit"/>
<arg name="region" type="region"/>
<arg name="command" type="order"/>
</type>
</message>
<message name="error187" section="errors"> <message name="error187" section="errors">
<type> <type>
<arg name="unit" type="unit"/> <arg name="unit" type="unit"/>

View File

@ -1937,9 +1937,6 @@ msgstr "\"Albträume plagen die Leute. ($int36($id))\""
msgid "error295" msgid "error295"
msgstr "\"$unit($unit) in $region($region): '$order($command)' - Nur ein Magier kann einen Astralkristall benutzen.\"" msgstr "\"$unit($unit) in $region($region): '$order($command)' - Nur ein Magier kann einen Astralkristall benutzen.\""
msgid "error185"
msgstr "\"$unit($unit) in $region($region): '$order($command)' - Der Zauber scheint ungewöhnlich schwach zu sein. Irgendetwas hat die magischen Energien abgeleitet.\""
msgid "error181" msgid "error181"
msgstr "\"$unit($unit) in $region($region): '$order($command)' - Dazu muß sich der Magier in der Burg oder an Bord des Schiffes befinden.\"" msgstr "\"$unit($unit) in $region($region): '$order($command)' - Dazu muß sich der Magier in der Burg oder an Bord des Schiffes befinden.\""

View File

@ -1937,9 +1937,6 @@ msgstr "\"Nightmares plague the population. ($int36($id))\""
msgid "error295" msgid "error295"
msgstr "\"$unit($unit) in $region($region): '$order($command)' - Only mages may use an astralcrystal.\"" msgstr "\"$unit($unit) in $region($region): '$order($command)' - Only mages may use an astralcrystal.\""
msgid "error185"
msgstr "\"$unit($unit) in $region($region): '$order($command)' - The spell seems exceptionally weak. Something has interfred with the magical energies.\""
msgid "error181" msgid "error181"
msgstr "\"$unit($unit) in $region($region): '$order($command)' - To do this, the magician has to be in a castle or on board a ship.\"" msgstr "\"$unit($unit) in $region($region): '$order($command)' - To do this, the magician has to be in a castle or on board a ship.\""

View File

@ -1,3 +1,4 @@
require 'tests.e2.migration'
require 'tests.e2.trolls' require 'tests.e2.trolls'
require 'tests.e2.trees' require 'tests.e2.trees'
require 'tests.e2.buildings' require 'tests.e2.buildings'
@ -5,6 +6,7 @@ require 'tests.e2.movement'
require 'tests.e2.carts' require 'tests.e2.carts'
require 'tests.e2.astral' require 'tests.e2.astral'
require 'tests.e2.spells' require 'tests.e2.spells'
require 'tests.e2.migration'
require 'tests.e2.e2features' require 'tests.e2.e2features'
require 'tests.e2.insects' require 'tests.e2.insects'
require 'tests.e2.production' require 'tests.e2.production'
@ -30,6 +32,5 @@ require 'tests.magicbag'
require 'tests.process' require 'tests.process'
require 'tests.xmas' require 'tests.xmas'
require 'tests.production' require 'tests.production'
require 'tests.spells'
require 'tests.undead' require 'tests.undead'
require 'tests.spells'

View File

@ -0,0 +1,131 @@
local tcname = 'tests.e2.migration'
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_mage(f, r)
local u = unit.create(f, r)
u.magic = 'tybied'
u:set_skill('magic', 10)
u:add_spell('migration')
return u
end
function test_migration_success()
local r = region.create(0, 0, "plain")
local f = faction.create('human')
local u = setup_mage(f, r)
local u2 = unit.create(faction.create('human'), r)
u2:add_order('KONTAKTIERE ' .. itoa36(u.id))
u:add_order('ZAUBERE STUFE 1 "Ritual der Aufnahme" ' .. itoa36(u2.id))
u.aura = 9
u.aura_max = 9
process_orders()
assert_equal(f, u2.faction)
assert_equal(6, u.aura)
assert_equal(8, u.aura_max)
end
function test_migration_no_contact()
local r = region.create(0, 0, "plain")
local f = faction.create('human')
local u = setup_mage(f, r)
local u2 = unit.create(faction.create('human'), r)
u:add_order('ZAUBERE STUFE 1 "Ritual der Aufnahme" ' .. itoa36(u2.id))
u.aura = 9
u.aura_max = 9
process_orders()
assert_not_equal(f, u2.faction)
assert_equal(9, u.aura)
assert_equal(9, u.aura_max)
end
function test_migration_too_many()
local r = region.create(0, 0, "plain")
local f = faction.create('human')
local u = setup_mage(f, r)
local u2 = unit.create(faction.create('human'), r)
u2:add_order('KONTAKTIERE ' .. itoa36(u.id))
u2.number = 2
u:add_order('ZAUBERE STUFE 1 "Ritual der Aufnahme" ' .. itoa36(u2.id))
u.aura = 9
u.aura_max = 9
process_orders()
assert_not_equal(f, u2.faction)
assert_equal(9, u.aura)
assert_equal(9, u.aura_max)
end
function test_migration_with_ring()
local r = region.create(0, 0, "plain")
local f = faction.create('human')
local u = setup_mage(f, r)
local u2 = unit.create(faction.create('human'), r)
u2:add_order('KONTAKTIERE ' .. itoa36(u.id))
u2.number = 2
u:add_item('rop', 1)
u:add_order('ZAUBERE STUFE 1 "Ritual der Aufnahme" ' .. itoa36(u2.id))
u.aura = 9
u.aura_max = 9
process_orders()
assert_equal(f, u2.faction)
assert_equal(6, u.aura)
assert_equal(8, u.aura_max)
end
function test_migration_insufficient_aura()
-- if unit cannot pay full costs, it casts at a lower level.
local r = region.create(0, 0, "plain")
local f = faction.create('human')
local u = setup_mage(f, r)
local u2 = unit.create(faction.create('human'), r)
u2:add_order('KONTAKTIERE ' .. itoa36(u.id))
u2.number = 2
u:add_order('ZAUBERE STUFE 2 "Ritual der Aufnahme" ' .. itoa36(u2.id))
u.aura = 3
u.aura_max = 9
process_orders()
-- spell fails, costs nothing:
assert_not_equal(f, u2.faction)
assert_equal(3, u.aura)
assert_equal(9, u.aura_max)
end
function test_migration_reduced_cost()
-- if unit cannot pay full costs, it casts at a lower level.
local r = region.create(0, 0, "plain")
local f = faction.create('human')
local u = setup_mage(f, r)
local u2 = unit.create(faction.create('human'), r)
u2:add_order('KONTAKTIERE ' .. itoa36(u.id))
u:add_order('ZAUBERE STUFE 2 "Ritual der Aufnahme" ' .. itoa36(u2.id))
u.aura = 3
u.aura_max = 9
process_orders()
-- spell is cast at level 1:
assert_equal(f, u2.faction)
assert_equal(0, u.aura)
assert_equal(7, u.aura_max)
end
--[[
additional tests:
- not enough aura, casting at lower level
- no aura, ring does not grant level 1
- magic tower, like ring, cumulative
]]--

View File

@ -163,11 +163,10 @@ local function create_cp_front(f, r)
end end
function test_confusion_and_panic() function test_confusion_and_panic()
f = faction.create('demon', "confusion@eressea.de", "de") f = faction.create('demon')
f2 = faction.create('demon') f2 = faction.create('demon')
for y = 1, 10 do
local u1, u2, u3, u4, r local u1, u2, u3, u4, r
r = region.create(0, 2*y, 'plain') r = region.create(0, 0, 'plain')
u1 = create_cp_front(f, r) u1 = create_cp_front(f, r)
u2 = create_cp_mage(f, r) u2 = create_cp_mage(f, r)
u3 = create_cp_mage(f, r) u3 = create_cp_mage(f, r)
@ -181,18 +180,11 @@ function test_confusion_and_panic()
create_cp_mage(f2, r) create_cp_mage(f2, r)
for ux in r.units do for ux in r.units do
for uy in r.units do for uy in r.units do
if ux.faction ~= uy.faction then if ux.faction ~= uy.faction then
ux:add_order("ATTACKIERE " .. itoa36(uy.id)) ux:add_order("ATTACKIERE " .. itoa36(uy.id))
end
end end
end
end end
end
for i = 1,10 do
process_orders() process_orders()
end
-- should not produce "select_enemies has a bug"
-- init_reports()
-- write_reports()
end end

View File

@ -1711,26 +1711,20 @@ void do_combatmagic(battle * b, combatmagic_t was)
/* Fehler! */ /* Fehler! */
return; return;
} }
if (sp == NULL) if (sp == NULL || !u_hasspell(mage, sp))
continue; continue;
ord = create_order(K_CAST, lang, "'%s'", spell_name(mkname_spell(sp), lang)); level = max_spell_level(mage, caster, sp, level, 1, NULL);
if (!cancast(mage, sp, 1, 1, ord)) {
free_order(ord);
continue;
}
level = eff_spelllevel(mage, caster, sp, level, 1);
if (sl > 0 && sl < level) { if (sl > 0 && sl < level) {
level = sl; level = sl;
} }
if (level < 0) { if (level < 1) {
report_failed_spell(b, mage, sp); report_failed_spell(b, mage, sp);
free_order(ord);
continue; continue;
} }
power = spellpower(r, mage, sp, level, ord); ord = create_order(K_CAST, lang, "'%s'", spell_name(mkname_spell(sp), lang));
power = spellpower(r, mage, sp, level);
free_order(ord); free_order(ord);
if (power <= 0) { /* Effekt von Antimagie */ if (power <= 0) { /* Effekt von Antimagie */
report_failed_spell(b, mage, sp); report_failed_spell(b, mage, sp);
@ -1787,25 +1781,23 @@ static void do_combatspell(troop at)
int level, qi; int level, qi;
double power; double power;
int fumblechance = 0; int fumblechance = 0;
order *ord;
int sl;
const struct locale *lang = mage->faction->locale; const struct locale *lang = mage->faction->locale;
sp = get_combatspell(mage, 1); sp = get_combatspell(mage, 1);
if (sp == NULL) { if (sp == NULL || !u_hasspell(mage, sp)) {
fi->magic = 0; /* Hat keinen Kampfzauber, kaempft nichtmagisch weiter */ fi->magic = 0; /* Hat keinen Kampfzauber, kaempft nichtmagisch weiter */
return; return;
} }
ord = create_order(K_CAST, lang, "'%s'", spell_name(mkname_spell(sp), lang)); level = max_spell_level(mage, mage, sp, fi->magic, 1, NULL);
if (!cancast(mage, sp, 1, 1, ord)) { if (level < 1) {
fi->magic = 0; /* Kann nicht mehr Zaubern, kaempft nichtmagisch weiter */ fi->magic = 0; /* Kann nicht mehr Zaubern, kaempft nichtmagisch weiter */
return; return;
} }
else {
level = eff_spelllevel(mage, mage, sp, fi->magic, 1); int sl = get_combatspelllevel(mage, 1);
sl = get_combatspelllevel(mage, 1); if (sl < level) {
if (sl > 0 && sl < level) { level = sl;
level = sl; }
} }
if (fumble(r, mage, sp, level)) { if (fumble(r, mage, sp, level)) {
@ -1828,11 +1820,9 @@ static void do_combatspell(troop at)
if (rng_int() % 100 < fumblechance) { if (rng_int() % 100 < fumblechance) {
report_failed_spell(b, mage, sp); report_failed_spell(b, mage, sp);
pay_spell(mage, NULL, sp, level, 1); pay_spell(mage, NULL, sp, level, 1);
free_order(ord);
return; return;
} }
power = spellpower(r, mage, sp, level, ord); power = spellpower(r, mage, sp, level);
free_order(ord);
if (power <= 0) { /* Effekt von Antimagie */ if (power <= 0) { /* Effekt von Antimagie */
report_failed_spell(b, mage, sp); report_failed_spell(b, mage, sp);
pay_spell(mage, NULL, sp, level, 1); pay_spell(mage, NULL, sp, level, 1);

View File

@ -194,7 +194,7 @@ static int tolua_unit_get_id(lua_State * L)
static int tolua_unit_set_id(lua_State * L) static int tolua_unit_set_id(lua_State * L)
{ {
unit *u = (unit *)tolua_tousertype(L, 1, 0); unit *u = (unit *)tolua_tousertype(L, 1, 0);
unit_setid(u, (int)tolua_tonumber(L, 2, 0)); unit_setid(u, (int)lua_tointeger(L, 2));
return 0; return 0;
} }
@ -205,6 +205,17 @@ static int tolua_unit_get_auramax(lua_State * L)
return 1; return 1;
} }
static int tolua_unit_set_auramax(lua_State * L)
{
unit *u = (unit *)tolua_tousertype(L, 1, 0);
int aura = (int)lua_tointeger(L, 2);
int now = max_spellpoints(u, u->region);
aura = change_maxspellpoints(u, aura - now);
lua_pushinteger(L, aura);
return 1;
}
static int tolua_unit_get_hpmax(lua_State * L) static int tolua_unit_get_hpmax(lua_State * L)
{ {
unit *u = (unit *)tolua_tousertype(L, 1, 0); unit *u = (unit *)tolua_tousertype(L, 1, 0);
@ -1055,7 +1066,7 @@ void tolua_unit_open(lua_State * L)
tolua_variable(L, TOLUA_CAST "race", tolua_unit_get_race, tolua_variable(L, TOLUA_CAST "race", tolua_unit_get_race,
tolua_unit_set_race); tolua_unit_set_race);
tolua_variable(L, TOLUA_CAST "hp_max", tolua_unit_get_hpmax, 0); tolua_variable(L, TOLUA_CAST "hp_max", tolua_unit_get_hpmax, 0);
tolua_variable(L, TOLUA_CAST "aura_max", tolua_unit_get_auramax, 0); tolua_variable(L, TOLUA_CAST "aura_max", tolua_unit_get_auramax, tolua_unit_set_auramax);
tolua_function(L, TOLUA_CAST "equip", tolua_equipunit); tolua_function(L, TOLUA_CAST "equip", tolua_equipunit);
tolua_function(L, TOLUA_CAST "show", tolua_bufunit); tolua_function(L, TOLUA_CAST "show", tolua_bufunit);

View File

@ -1374,9 +1374,10 @@ static void end_spells(parseinfo *pi, const XML_Char *el) {
else if (xml_strequal(el, "spell")) { else if (xml_strequal(el, "spell")) {
spell *sp = (spell *)pi->object; spell *sp = (spell *)pi->object;
if (ncomponents > 0) { if (ncomponents > 0) {
sp->components = (spell_component *)calloc(ncomponents + 1, sizeof(spell_component)); sp->components = malloc((1 + (size_t)ncomponents) * sizeof(spell_component));
if (!sp->components) abort(); if (!sp->components) abort();
memcpy(sp->components, components, sizeof(spell_component) * ncomponents); memcpy(sp->components, components, sizeof(spell_component) * ncomponents);
sp->components[ncomponents].type = NULL;
ncomponents = 0; ncomponents = 0;
} }
pi->object = NULL; pi->object = NULL;

View File

@ -267,12 +267,16 @@ bool FactionSpells(void)
return rule != 0; 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) int get_spell_level_mage(const spell * sp, void * cbdata)
{ {
sc_mage *mage = (sc_mage *)cbdata; sc_mage *mage = (sc_mage *)cbdata;
spellbook *book = get_spellbook(magic_school[mage->magietyp]); return mage_get_spell_level(mage, sp);
spellbook_entry *sbe = spellbook_get(book, sp);
return sbe ? sbe->level : 0;
} }
/* ------------------------------------------------------------- */ /* ------------------------------------------------------------- */
@ -491,14 +495,14 @@ sc_mage *create_mage(unit * u, magic_t mtyp)
/* ------------------------------------------------------------- */ /* ------------------------------------------------------------- */
/* Funktionen fuer die Bearbeitung der List-of-known-spells */ /* Funktionen fuer die Bearbeitung der List-of-known-spells */
int u_hasspell(const unit *u, const struct spell *sp) bool u_hasspell(const unit *u, const struct spell *sp)
{ {
spellbook * book = unit_get_spellbook(u); spellbook * book = unit_get_spellbook(u);
spellbook_entry * sbe = book ? spellbook_get(book, sp) : 0; spellbook_entry * sbe = book ? spellbook_get(book, sp) : 0;
if (sbe) { if (sbe) {
return sbe->level <= effskill(u, SK_MAGIC, NULL); return sbe->level <= effskill(u, SK_MAGIC, NULL);
} }
return 0; return false;
} }
/* ------------------------------------------------------------- */ /* ------------------------------------------------------------- */
@ -721,100 +725,67 @@ int auracost(const unit *caster, const spell *sp) {
return 0; return 0;
} }
/* ------------------------------------------------------------- */ static void add_missing_component(resource **reslist_p, const struct spell_component *spc, int n) {
/* SPC_LINEAR ist am hoechstwertigen, dann muessen Komponenten fuer die resource *res = malloc(sizeof(resource));
* Stufe des Magiers vorhanden sein. if (res) {
* SPC_LINEAR hat die gewuenschte Stufe als multiplikator, res->number = n;
* nur SPC_FIX muss nur einmal vorhanden sein, ist also am res->type = spc->type;
* niedrigstwertigen und sollte von den beiden anderen Typen res->next = *reslist_p;
* ueberschrieben werden */ *reslist_p = res;
static int spl_costtyp(const spell * sp)
{
int k;
int costtyp = SPC_FIX;
for (k = 0; sp->components && sp->components[k].type; k++) {
if (costtyp == SPC_LINEAR)
return SPC_LINEAR;
if (sp->components[k].cost == SPC_LINEAR) {
return SPC_LINEAR;
}
/* wenn keine Fixkosten, Typ uebernehmen */
if (sp->components[k].cost != SPC_FIX) {
costtyp = sp->components[k].cost;
}
} }
return costtyp;
} }
/** /**
* Durch Komponenten und cast_level begrenzter maximal moeglicher Level. * Durch Komponenten und cast_level begrenzter maximal moeglicher Level.
*
* Da die Funktion nicht alle Komponenten durchprobiert sondern beim
* ersten Fehler abbricht, muss die Fehlermeldung spaeter mit cancast()
* generiert werden.
*/ */
int eff_spelllevel(unit * u, unit *caster, const spell * sp, int cast_level, int range) int max_spell_level(unit * u, unit *caster, const spell * sp, int cast_level, int range, resource **reslist_p)
{ {
const resource_type *r_aura = get_resourcetype(R_AURA); const resource_type *r_aura = get_resourcetype(R_AURA);
int k, maxlevel; const struct spell_component *spc;
int costtyp = SPC_FIX; int maxlevel = cast_level;
for (k = 0; sp->components && sp->components[k].type; k++) { if (!sp->components) {
if (cast_level == 0) return cast_level;
return 0; }
if (sp->components[k].amount > 0) { for (spc = sp->components; spc->type; ++spc) {
int level_cost = sp->components[k].amount * range; if (spc->amount > 0) {
if (sp->components[k].type == r_aura) { int need = 0, have, level_cost = spellcost(u, spc) * range;
/* Die Kosten fuer Aura sind auch von der Zahl der bereits if (level_cost <= 0) continue;
* gezauberten Sprueche abhaengig */
level_cost *= aura_multiplier(caster); have = get_pooled(u, spc->type, GET_DEFAULT,
} level_cost * cast_level);
maxlevel =
get_pooled(u, sp->components[k].type, GET_DEFAULT,
level_cost * cast_level) / level_cost;
/* sind die Kosten fix, so muss die Komponente nur einmal vorhanden /* sind die Kosten fix, so muss die Komponente nur einmal vorhanden
* sein und der cast_level aendert sich nicht */ * sein und der cast_level aendert sich nicht,
if (sp->components[k].cost == SPC_FIX) { * ansonsten wird das Minimum aus maximal moeglicher Stufe und der
if (maxlevel < 1) * gewuenschten gebildet */
cast_level = 0; if (spc->cost == SPC_FIX) {
/* ansonsten wird das Minimum aus maximal moeglicher Stufe und der if (have < level_cost) {
* gewuenschten gebildet */ maxlevel = 0;
} need = level_cost - have;
else if (sp->components[k].cost == SPC_LEVEL) {
costtyp = SPC_LEVEL;
if (maxlevel < cast_level) {
cast_level = maxlevel;
} }
/* bei Typ Linear muessen die Kosten in Hoehe der Stufe vorhanden
* sein, ansonsten schlaegt der Spruch fehl */
} }
else if (sp->components[k].cost == SPC_LINEAR) { else {
costtyp = SPC_LINEAR; need = level_cost * cast_level;
if (maxlevel < cast_level) if (have < need) {
cast_level = 0; /* bei Typ Linear muessen die Kosten in Hoehe der Stufe vorhanden
* sein, ansonsten schlaegt der Spruch fehl */
if (spc->cost == SPC_LINEAR) {
maxlevel = 0;
}
else {
maxlevel = have / level_cost;
}
need -= have;
}
}
if (reslist_p && need > 0) {
add_missing_component(reslist_p, spc, need);
} }
} }
} }
/* Ein Spruch mit Fixkosten wird immer mit der Stufe des Spruchs und return maxlevel;
* nicht auf der Stufe des Magiers gezaubert */
if (costtyp == SPC_FIX) {
spellbook * sb = unit_get_spellbook(u);
if (sb) {
spellbook_entry * sbe = spellbook_get(sb, sp);
if (sbe && cast_level > sbe->level) {
return sbe->level;
}
}
else {
log_error("spell %s is not in the spellbook for %s\n", sp->sname, unitname(u));
}
}
return cast_level;
} }
/* ------------------------------------------------------------- */ /* ------------------------------------------------------------- */
@ -852,7 +823,7 @@ bool knowsspell(const region * r, const unit * u, const spell * sp)
UNUSED_ARG(r); UNUSED_ARG(r);
assert(sp); assert(sp);
/* steht der Spruch in der Spruchliste? */ /* steht der Spruch in der Spruchliste? */
return u_hasspell(u, sp) != 0; return u_hasspell(u, sp);
} }
/* Um einen Spruch zu beherrschen, muss der Magier die Stufe des /* Um einen Spruch zu beherrschen, muss der Magier die Stufe des
@ -862,13 +833,28 @@ bool knowsspell(const region * r, const unit * u, const spell * sp)
* und sonstige Gegenstaende sein. * und sonstige Gegenstaende sein.
*/ */
static void
report_missing_components(unit *u, order *ord, resource *reslist) {
assert(reslist);
assert(u);
assert(u->faction);
assert(ord);
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "missing_components_list",
"list", reslist));
}
static void free_components(resource *reslist) {
while (reslist) {
resource *res = reslist->next;
free(reslist);
reslist = res;
}
}
bool bool
cancast(unit * u, const spell * sp, int level, int range, struct order * ord) cancast(unit * u, const spell * sp, int level, int range, struct order * ord)
{ {
int k; if (u_hasspell(u, sp)) {
resource *reslist = NULL;
if (!knowsspell(u->region, u, sp)) {
/* Diesen Zauber kennt die Einheit nicht */ /* Diesen Zauber kennt die Einheit nicht */
cmistake(u, ord, 173, MSG_MAGIC); cmistake(u, ord, 173, MSG_MAGIC);
return false; return false;
@ -880,49 +866,6 @@ cancast(unit * u, const spell * sp, int level, int range, struct order * ord)
return false; return false;
} }
for (k = 0; sp->components && sp->components[k].type; ++k) {
const struct spell_component *spc = sp->components + k;
if (spc->amount > 0) {
const resource_type *rtype = spc->type;
int itemhave, itemanz;
/* Die Kosten fuer Aura sind auch von der Zahl der bereits
* gezauberten Sprueche abhaengig */
itemanz = spellcost(u, spc) * range;
/* sind die Kosten stufenabhaengig, so muss itemanz noch mit dem
* level multipliziert werden */
switch (spc->cost) {
case SPC_LEVEL:
case SPC_LINEAR:
itemanz *= level;
break;
case SPC_FIX:
default:
break;
}
itemhave = get_pooled(u, rtype, GET_DEFAULT, itemanz);
if (itemhave < itemanz) {
resource *res = malloc(sizeof(resource));
assert(res);
res->number = itemanz - itemhave;
res->type = rtype;
res->next = reslist;
reslist = res;
}
}
}
if (reslist != NULL) {
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "missing_components_list",
"list", reslist));
while (reslist) {
resource *res = reslist->next;
free(reslist);
reslist = res;
}
return false;
}
return true; return true;
} }
@ -938,7 +881,7 @@ cancast(unit * u, const spell * sp, int level, int range, struct order * ord)
*/ */
double double
spellpower(region * r, unit * u, const spell * sp, int cast_level, struct order *ord) spellpower(region * r, unit * u, const spell * sp, int cast_level)
{ {
double force = cast_level; double force = cast_level;
static int elf_power, config; static int elf_power, config;
@ -981,7 +924,6 @@ spellpower(region * r, unit * u, const spell * sp, int cast_level, struct order
unit *mage = c->magician; unit *mage = c->magician;
force -= curse_geteffect(c); force -= curse_geteffect(c);
curse_changevigour(&r->attribs, c, -cast_level); curse_changevigour(&r->attribs, c, -cast_level);
cmistake(u, ord, 185, MSG_MAGIC);
if (mage != NULL && mage->faction != NULL) { if (mage != NULL && mage->faction != NULL) {
if (force > 0) { if (force > 0) {
ADDMSG(&mage->faction->msgs, msg_message("reduce_spell", ADDMSG(&mage->faction->msgs, msg_message("reduce_spell",
@ -1000,7 +942,6 @@ spellpower(region * r, unit * u, const spell * sp, int cast_level, struct order
unit *mage = c->magician; unit *mage = c->magician;
force -= curse_geteffect(c); force -= curse_geteffect(c);
curse_changevigour(&u->attribs, c, -1); curse_changevigour(&u->attribs, c, -1);
cmistake(u, ord, 185, MSG_MAGIC);
if (mage != NULL && mage->faction != NULL) { if (mage != NULL && mage->faction != NULL) {
if (force > 0) { if (force > 0) {
ADDMSG(&mage->faction->msgs, msg_message("reduce_spell", ADDMSG(&mage->faction->msgs, msg_message("reduce_spell",
@ -2532,16 +2473,28 @@ static bool is_moving_ship(ship * sh)
return false; return false;
} }
static int default_spell_level(const sc_mage *mage, 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 mage_get_spell_level(mage, sp);
}
#define MAX_PARAMETERS 48 #define MAX_PARAMETERS 48
static castorder *cast_cmd(unit * u, order * ord) static castorder *cast_cmd(unit * u, order * ord)
{ {
char token[128]; char token[128];
region *r = u->region; region *r = u->region;
region *target_r = r; region *target_r = r;
int level, range; int level = -1, range, skill;
unit *familiar = NULL; unit *familiar = NULL;
const char *s; const char *s;
spell *sp = 0; spell *sp = NULL;
plane *pl; plane *pl;
spellparameter *args = NULL; spellparameter *args = NULL;
unit * mage = u; unit * mage = u;
@ -2556,20 +2509,13 @@ static castorder *cast_cmd(unit * u, order * ord)
cmistake(u, ord, 269, MSG_MAGIC); cmistake(u, ord, 269, MSG_MAGIC);
return 0; return 0;
} }
level = effskill(u, SK_MAGIC, NULL);
init_order(ord, NULL); init_order(ord, NULL);
s = gettoken(token, sizeof(token)); s = gettoken(token, sizeof(token));
param = findparam(s, u->faction->locale); param = findparam(s, u->faction->locale);
/* fuer Syntax ' STUFE x REGION y z ' */ /* fuer Syntax ' STUFE x REGION y z ' */
if (param == P_LEVEL) { if (param == P_LEVEL) {
int p = getint(); level = getint();
if (level > p) level = p;
if (level < 1) {
/* Fehler "Das macht wenig Sinn" */
syntax_error(u, ord);
return 0;
}
s = gettoken(token, sizeof(token)); s = gettoken(token, sizeof(token));
param = findparam(s, u->faction->locale); param = findparam(s, u->faction->locale);
} }
@ -2590,16 +2536,9 @@ static castorder *cast_cmd(unit * u, order * ord)
s = gettoken(token, sizeof(token)); s = gettoken(token, sizeof(token));
param = findparam(s, u->faction->locale); param = findparam(s, u->faction->locale);
} }
/* fuer Syntax ' REGION x y STUFE z ' /* fuer Syntax `REGION x y STUFE z` hier nach REGION nochmal pruefen */
* hier nach REGION nochmal auf STUFE pruefen */
if (param == P_LEVEL) { if (param == P_LEVEL) {
int p = getint(); level = getint();
if (level > p) level = p;
if (level < 1) {
/* Fehler "Das macht wenig Sinn" */
syntax_error(u, ord);
return 0;
}
s = gettoken(token, sizeof(token)); s = gettoken(token, sizeof(token));
} }
if (!s || !s[0]) { if (!s || !s[0]) {
@ -2609,7 +2548,6 @@ static castorder *cast_cmd(unit * u, order * ord)
} }
sp = unit_getspell(u, s, u->faction->locale); sp = unit_getspell(u, s, u->faction->locale);
/* Vertraute koennen auch Zauber sprechen, die sie selbst nicht /* Vertraute koennen auch Zauber sprechen, die sie selbst nicht
* koennen. unit_getspell findet aber nur jene Sprueche, die * koennen. unit_getspell findet aber nur jene Sprueche, die
* die Einheit beherrscht. */ * die Einheit beherrscht. */
@ -2631,6 +2569,7 @@ static castorder *cast_cmd(unit * u, order * ord)
cmistake(u, ord, 173, MSG_MAGIC); cmistake(u, ord, 173, MSG_MAGIC);
return 0; return 0;
} }
/* um testen auf spruchnamen zu unterbinden sollte vor allen /* um testen auf spruchnamen zu unterbinden sollte vor allen
* fehlermeldungen die anzeigen das der magier diesen Spruch * fehlermeldungen die anzeigen das der magier diesen Spruch
* nur in diese Situation nicht anwenden kann, noch eine * nur in diese Situation nicht anwenden kann, noch eine
@ -2647,6 +2586,7 @@ static castorder *cast_cmd(unit * u, order * ord)
cmistake(u, ord, 174, MSG_MAGIC); cmistake(u, ord, 174, MSG_MAGIC);
return 0; return 0;
} }
/* Auf dem Ozean Zaubern als quasi-langer Befehl koennen /* Auf dem Ozean Zaubern als quasi-langer Befehl koennen
* normalerweise nur Meermenschen, ausgenommen explizit als * normalerweise nur Meermenschen, ausgenommen explizit als
* OCEANCASTABLE deklarierte Sprueche */ * OCEANCASTABLE deklarierte Sprueche */
@ -2687,15 +2627,28 @@ static castorder *cast_cmd(unit * u, order * ord)
return 0; return 0;
} }
} }
/* Stufenangabe bei nicht Stufenvariierbaren Spruechen abfangen */
if (!(sp->sptyp & SPELLLEVEL)) { skill = effskill(mage, SK_MAGIC, NULL);
int ilevel = effskill(u, SK_MAGIC, NULL); if (level < 0) {
if (ilevel != level) { level = default_spell_level(get_mage(mage), sp);
level = ilevel; if (level < 0) {
level = skill;
}
}
else if (!(sp->sptyp & SPELLLEVEL)) {
/* Stufenangabe bei nicht Stufenvariierbaren Spruechen abfangen */
if (skill != level) {
level = skill;
ADDMSG(&u->faction->msgs, msg_message("spellfail::nolevel", ADDMSG(&u->faction->msgs, msg_message("spellfail::nolevel",
"mage region command", u, u->region, 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;
}
/* Vertrautenmagie */ /* Vertrautenmagie */
/* Kennt der Vertraute den Spruch, so zaubert er ganz normal. /* Kennt der Vertraute den Spruch, so zaubert er ganz normal.
* Ansonsten zaubert der Magier durch seinen Vertrauten, dh * Ansonsten zaubert der Magier durch seinen Vertrauten, dh
@ -2832,33 +2785,28 @@ void magic(void)
unit *caster = co_get_caster(co); unit *caster = co_get_caster(co);
const spell *sp = co->sp; const spell *sp = co->sp;
region *target_r = co_get_region(co); region *target_r = co_get_region(co);
resource *reslist = NULL;
/* reichen die Komponenten nicht, wird der Level reduziert. */ /* reichen die Komponenten nicht, kann der Level reduziert werden. */
co->level = eff_spelllevel(mage, caster, sp, cast_level, co->distance); co->level = max_spell_level(mage, caster, sp, cast_level, co->distance, &reslist);
if (co->level < 1) { if (co->level < 1) {
/* Fehlermeldung mit Komponenten generieren */ /* Es fehlt eine Komponente vollständig: */
cancast(mage, sp, cast_level, co->distance, ord); assert(reslist);
report_missing_components(mage, ord, reslist);
free_components(reslist);
continue; continue;
} }
if (cast_level > co->level) { if (cast_level > co->level) {
/* Sprueche mit Fixkosten werden immer auf Stufe des Spruchs /* Es gibt von einer Komponente zu wenig, deshalb
* gezaubert, co->level ist aber defaultmaessig Stufe des Magiers */ * wird mit reduzierter Stufe gezaubert: */
if (spl_costtyp(sp) != SPC_FIX) { ADDMSG(&caster->faction->msgs, msg_message("missing_components",
ADDMSG(&caster->faction->msgs, msg_message("missing_components", "unit spell level", caster, sp, cast_level));
"unit spell level", caster, sp, cast_level)); free_components(reslist);
}
} }
/* Pruefen, ob die realen Kosten fuer die gewuenschten Stufe bezahlt co->force = MagicPower(spellpower(target_r, mage, sp, co->level));
* werden koennen */
if (!cancast(mage, sp, co->level, co->distance, ord)) {
/* die Fehlermeldung wird in cancast generiert */
continue;
}
co->force = MagicPower(spellpower(target_r, mage, sp, co->level, ord));
/* die Staerke kann durch Antimagie auf 0 sinken */ /* die Staerke kann durch Antimagie auf 0 sinken */
if (co->force <= 0) { if (co->force <= 0) {
co->force = 0; co->force = 0;
@ -2905,7 +2853,7 @@ void magic(void)
} }
/* erst bezahlen, dann Kostenzaehler erhoehen */ /* erst bezahlen, dann Kostenzaehler erhoehen */
if (co->level > 0) { if (co->level > 0) {
pay_spell(mage, caster, sp, co->level, co->distance); pay_spell(mage, caster, sp, cast_level, co->distance);
} }
if (fumbled) { if (fumbled) {
do_fumble(co); do_fumble(co);

View File

@ -112,7 +112,7 @@ extern "C" {
typedef struct spell_component { typedef struct spell_component {
const struct resource_type *type; const struct resource_type *type;
int amount; int amount;
int cost; enum spellcost_t cost;
} spell_component; } spell_component;
/* ------------------------------------------------------------- */ /* ------------------------------------------------------------- */
@ -146,7 +146,7 @@ extern "C" {
#define ANYTARGET (UNITSPELL|REGIONSPELL|BUILDINGSPELL|SHIPSPELL) /* wirkt auf alle objekttypen (unit, ship, building, region) */ #define ANYTARGET (UNITSPELL|REGIONSPELL|BUILDINGSPELL|SHIPSPELL) /* wirkt auf alle objekttypen (unit, ship, building, region) */
/* Flag Spruchkostenberechnung: */ /* Flag Spruchkostenberechnung: */
enum { enum spellcost_t {
SPC_FIX, /* Fixkosten */ SPC_FIX, /* Fixkosten */
SPC_LEVEL, /* Komponenten pro Level */ SPC_LEVEL, /* Komponenten pro Level */
SPC_LINEAR /* Komponenten pro Level und muessen vorhanden sein */ SPC_LINEAR /* Komponenten pro Level und muessen vorhanden sein */
@ -191,6 +191,7 @@ extern "C" {
enum magic_t mage_get_type(const struct sc_mage *mage); 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); 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); 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); int mage_get_spellpoints(const struct sc_mage *m);
void mage_set_spellpoints(struct sc_mage *m, int aura); void mage_set_spellpoints(struct sc_mage *m, int aura);
int mage_change_spellpoints(struct sc_mage *m, int delta); int mage_change_spellpoints(struct sc_mage *m, int delta);
@ -217,7 +218,7 @@ extern "C" {
void unset_combatspell(struct unit *u, struct spell * sp); void unset_combatspell(struct unit *u, struct spell * sp);
/* loescht Kampfzauber */ /* loescht Kampfzauber */
/* fuegt den Spruch mit der Id spellid der Spruchliste der Einheit hinzu. */ /* fuegt den Spruch mit der Id spellid der Spruchliste der Einheit hinzu. */
int u_hasspell(const struct unit *u, const struct spell *sp); bool u_hasspell(const struct unit *u, const struct spell *sp);
/* prueft, ob der Spruch in der Spruchliste der Einheit steht. */ /* 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); 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,
@ -242,7 +243,7 @@ extern "C" {
/* Zaubern */ /* Zaubern */
double spellpower(struct region *r, struct unit *u, const struct spell * sp, double spellpower(struct region *r, struct unit *u, const struct spell * sp,
int cast_level, struct order *ord); int cast_level);
/* ermittelt die Staerke eines Spruchs */ /* ermittelt die Staerke eines Spruchs */
bool fumble(struct region *r, struct unit *u, const struct spell * sp, bool fumble(struct region *r, struct unit *u, const struct spell * sp,
int cast_level); int cast_level);
@ -282,8 +283,9 @@ extern "C" {
/* zieht die Komponenten des Zaubers aus dem Inventory der Einheit /* zieht die Komponenten des Zaubers aus dem Inventory der Einheit
* ab. Die effektive Stufe des gezauberten Spruchs ist wichtig fuer * ab. Die effektive Stufe des gezauberten Spruchs ist wichtig fuer
* die korrekte Bestimmung der Magiepunktkosten */ * die korrekte Bestimmung der Magiepunktkosten */
int eff_spelllevel(struct unit *mage, struct unit *caster, int max_spell_level(struct unit *mage, struct unit *caster,
const struct spell * sp, int cast_level, int distance); const struct spell * sp, int cast_level, int distance,
struct resource **reslist_p);
/* ermittelt die effektive Stufe des Zaubers. Dabei ist cast_level /* ermittelt die effektive Stufe des Zaubers. Dabei ist cast_level
* die gewuenschte maximale Stufe (im Normalfall Stufe des Magiers, * die gewuenschte maximale Stufe (im Normalfall Stufe des Magiers,
* bei Farcasting Stufe*2^Entfernung) */ * bei Farcasting Stufe*2^Entfernung) */

View File

@ -127,7 +127,7 @@ void test_pay_spell(CuTest * tc)
change_resource(u, get_resourcetype(R_AURA), 3); change_resource(u, get_resourcetype(R_AURA), 3);
change_resource(u, get_resourcetype(R_HORSE), 3); change_resource(u, get_resourcetype(R_HORSE), 3);
level = eff_spelllevel(u, u, sp, 3, 1); level = max_spell_level(u, u, sp, 3, 1, NULL);
CuAssertIntEquals(tc, 3, level); CuAssertIntEquals(tc, 3, level);
pay_spell(u, NULL, sp, level, 1); pay_spell(u, NULL, sp, level, 1);
CuAssertIntEquals(tc, 0, get_resource(u, get_resourcetype(R_SILVER))); CuAssertIntEquals(tc, 0, get_resource(u, get_resourcetype(R_SILVER)));
@ -161,16 +161,16 @@ void test_pay_spell_failure(CuTest * tc)
CuAssertIntEquals(tc, 2, change_resource(u, get_resourcetype(R_AURA), 2)); CuAssertIntEquals(tc, 2, change_resource(u, get_resourcetype(R_AURA), 2));
CuAssertIntEquals(tc, 3, change_resource(u, get_resourcetype(R_HORSE), 3)); CuAssertIntEquals(tc, 3, change_resource(u, get_resourcetype(R_HORSE), 3));
level = eff_spelllevel(u, u, sp, 3, 1); level = max_spell_level(u, u, sp, 3, 1, NULL);
CuAssertIntEquals(tc, 2, level); CuAssertIntEquals(tc, 2, level);
pay_spell(u, NULL, sp, level, 1); pay_spell(u, NULL, sp, level, 1);
CuAssertIntEquals(tc, 1, change_resource(u, get_resourcetype(R_SILVER), 1)); CuAssertIntEquals(tc, 1, change_resource(u, get_resourcetype(R_SILVER), 1));
CuAssertIntEquals(tc, 3, change_resource(u, get_resourcetype(R_AURA), 3)); CuAssertIntEquals(tc, 3, change_resource(u, get_resourcetype(R_AURA), 3));
CuAssertIntEquals(tc, 2, change_resource(u, get_resourcetype(R_HORSE), 1)); CuAssertIntEquals(tc, 2, change_resource(u, get_resourcetype(R_HORSE), 1));
CuAssertIntEquals(tc, 0, eff_spelllevel(u, u, sp, 3, 1)); CuAssertIntEquals(tc, 0, max_spell_level(u, u, sp, 3, 1, NULL));
CuAssertIntEquals(tc, 0, change_resource(u, get_resourcetype(R_SILVER), -1)); CuAssertIntEquals(tc, 0, change_resource(u, get_resourcetype(R_SILVER), -1));
CuAssertIntEquals(tc, 0, eff_spelllevel(u, u, sp, 2, 1)); CuAssertIntEquals(tc, 0, max_spell_level(u, u, sp, 2, 1, NULL));
test_teardown(); test_teardown();
} }