878 lines
24 KiB
C
878 lines
24 KiB
C
/*
|
|
magic.c - This file contains functions for casting magic spells
|
|
|
|
UltraRogue: The Ultimate Adventure in the Dungeons of Doom
|
|
Copyright (C) 1986, 1992, 1993, 1995 Herb Chong
|
|
All rights reserved.
|
|
|
|
Based on "Advanced Rogue"
|
|
Copyright (C) 1984, 1985 Michael Morgan, Ken Dalka
|
|
All rights reserved.
|
|
|
|
Based on "Rogue: Exploring the Dungeons of Doom"
|
|
Copyright (C) 1980, 1981 Michael Toy, Ken Arnold and Glenn Wichman
|
|
All rights reserved.
|
|
|
|
See the file LICENSE.TXT for full copyright and licensing information.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include "rogue.h"
|
|
|
|
/*
|
|
Cost for each level of spells level:
|
|
*/
|
|
|
|
static const int spell_cost[] = {1, 5, 17, 29, 53, 91, 159, 247, 396};
|
|
|
|
static struct spells monst_spells[] =
|
|
{
|
|
{5, S_SELFTELEP, SCR_MAGIC},
|
|
{4, P_HEALING, POT_MAGIC | _TWO_},
|
|
{3, P_REGENERATE, POT_MAGIC},
|
|
{2, P_HEALING, POT_MAGIC},
|
|
{4, P_HASTE, POT_MAGIC},
|
|
{2, P_SEEINVIS, POT_MAGIC},
|
|
{3, P_SHERO, POT_MAGIC},
|
|
{5, P_PHASE, POT_MAGIC},
|
|
{4, P_INVIS, POT_MAGIC},
|
|
{4, WS_CANCEL, ZAP_MAGIC},
|
|
|
|
/* In reverse order of damage ability */
|
|
{6, WS_ELECT, ZAP_MAGIC | _TWO_},
|
|
{6, WS_FIRE, ZAP_MAGIC | _TWO_},
|
|
{6, WS_COLD, ZAP_MAGIC | _TWO_},
|
|
{6, WS_MISSILE, ZAP_MAGIC | _TWO_},
|
|
{5, WS_ELECT, ZAP_MAGIC},
|
|
{5, WS_FIRE, ZAP_MAGIC},
|
|
{5, WS_COLD, ZAP_MAGIC},
|
|
{4, WS_ELECT, ZAP_MAGIC | ISCURSED},
|
|
{4, WS_FIRE, ZAP_MAGIC | ISCURSED},
|
|
{4, WS_COLD, ZAP_MAGIC | ISCURSED},
|
|
{3, WS_MISSILE, ZAP_MAGIC},
|
|
{1, WS_MISSILE, ZAP_MAGIC | ISCURSED},
|
|
|
|
{-1, -1, 0}
|
|
};
|
|
|
|
/*
|
|
Spells that a player can cast Non-mus only know ISKNOW spells until found
|
|
in the dungeon. Special classes know their spells one level lower, and
|
|
blessed one above.
|
|
*/
|
|
|
|
static struct spells player_spells[] =
|
|
{
|
|
{1, WS_KNOCK, ZAP_MAGIC | ISKNOW},
|
|
{1, S_SUMFAMILIAR, SCR_MAGIC | SP_DRUID | SP_MAGIC | SP_CLERIC, SP_ILLUSION },
|
|
{1, S_GFIND, SCR_MAGIC | ISKNOW},
|
|
{1, P_MONSTDET, POT_MAGIC | ISKNOW | SP_DRUID},
|
|
{1, P_TREASDET, POT_MAGIC | ISKNOW | SP_MAGIC},
|
|
{1, S_FOODDET, SCR_MAGIC | ISKNOW | SP_CLERIC},
|
|
{1, S_LIGHT, SCR_MAGIC | ISKNOW | SP_ILLUSION},
|
|
|
|
{2, WS_CLOSE, ZAP_MAGIC | ISKNOW},
|
|
{2, S_IDENTIFY, SCR_MAGIC | ISKNOW},
|
|
{2, WS_HIT, ZAP_MAGIC | ISKNOW | SP_PRAYER},
|
|
{2, P_SHIELD, POT_MAGIC | ISKNOW | SP_MAGIC},
|
|
{2, P_COLDRESIST, POT_MAGIC | SP_WIZARD},
|
|
{2, P_SEEINVIS, POT_MAGIC | SP_ILLUSION},
|
|
{2, S_CONFUSE, SCR_MAGIC | SP_CLERIC},
|
|
{2, P_SMELL, POT_MAGIC | SP_DRUID},
|
|
{2, WS_MISSILE, ZAP_MAGIC | SP_MAGIC},
|
|
{2, P_HEAR, POT_MAGIC},
|
|
|
|
{3, P_CLEAR, POT_MAGIC | ISKNOW},
|
|
{3, P_HEALING, POT_MAGIC | ISKNOW | SP_PRAYER},
|
|
{3, S_CURING, SCR_MAGIC | ISKNOW | SP_PRAYER},
|
|
{3, WS_MONSTELEP, ZAP_MAGIC | SP_MAGIC},
|
|
{3, WS_CANCEL, ZAP_MAGIC | SP_WIZARD},
|
|
{3, S_SELFTELEP, SCR_MAGIC | SP_WIZARD},
|
|
{3, P_FIRERESIST, POT_MAGIC | SP_WIZARD | SP_DRUID},
|
|
{3, S_MAP, SCR_MAGIC | SP_ILLUSION | SP_DRUID},
|
|
{3, S_REMOVECURSE, SCR_MAGIC | SP_PRAYER},
|
|
{3, S_HOLD, SCR_MAGIC | SP_CLERIC},
|
|
{3, S_SLEEP, SCR_MAGIC | SP_DRUID},
|
|
{3, P_HASOXYGEN, POT_MAGIC | SP_DRUID},
|
|
{3, WS_XENOHEALING, ZAP_MAGIC | SP_DRUID},
|
|
{3, P_RESTORE, POT_MAGIC},
|
|
|
|
{4, S_MSHIELD, SCR_MAGIC | ISKNOW | SP_ILLUSION},
|
|
{4, P_INVIS, POT_MAGIC | SP_ILLUSION},
|
|
{4, S_REFLECT, SCR_MAGIC | SP_ILLUSION},
|
|
{4, P_TRUESEE, POT_MAGIC | SP_ILLUSION},
|
|
{4, P_REGENERATE, POT_MAGIC | SP_CLERIC},
|
|
{4, WS_DRAIN, ZAP_MAGIC | SP_CLERIC},
|
|
{4, P_HASTE, POT_MAGIC | SP_ILLUSION | SP_CLERIC},
|
|
{4, P_LEVITATION, POT_MAGIC | SP_WIZARD | SP_DRUID},
|
|
{4, WS_WEB, ZAP_MAGIC | SP_MAGIC},
|
|
{4, P_PHASE, POT_MAGIC},
|
|
|
|
{5, P_SHERO, POT_MAGIC | ISKNOW},
|
|
{5, S_PETRIFY, SCR_MAGIC | SP_MAGIC},
|
|
{5, S_SCARE, SCR_MAGIC | _TWO_ | SP_PRAYER},
|
|
{5, WS_COLD, ZAP_MAGIC | SP_DRUID},
|
|
{5, WS_FIRE, ZAP_MAGIC | SP_CLERIC},
|
|
{5, WS_ELECT, ZAP_MAGIC | SP_WIZARD},
|
|
{5, WS_ANTIMATTER, ZAP_MAGIC | SP_ILLUSION},
|
|
{5, S_ELECTRIFY, SCR_MAGIC | SP_ILLUSION},
|
|
|
|
{6, WS_DISINTEGRATE, ZAP_MAGIC | ISKNOW},
|
|
{6, S_OWNERSHIP, SCR_MAGIC | SP_ALL},
|
|
|
|
{7, S_ENCHANT, SCR_MAGIC | SP_MAGIC},
|
|
|
|
{-1, -1, 0}
|
|
};
|
|
|
|
/*
|
|
incant()
|
|
Cast a spell
|
|
*/
|
|
|
|
void
|
|
incant(struct thing *caster, coord dir)
|
|
{
|
|
int i;
|
|
struct stats *curp;
|
|
struct stats *maxp;
|
|
int is_player = (caster == &player);
|
|
int points_casters;
|
|
char *casters_name = (on(player, ISBLIND)) ? "it" :
|
|
monsters[caster->t_index].m_name;
|
|
struct spells *sp;
|
|
char *cast_name; /* = spell_name(sp) */
|
|
char *spell_type; /* spell or prayer */
|
|
int casting_cost; /* from spell_cost[] */
|
|
int spell_roll; /* sucess/fail 1D100 die roll */
|
|
int fumble_chance; /* Spell fumble chance */
|
|
int num_fumbles = 0; /* for fumble_spell() */
|
|
int bless_or_curse = ISNORMAL; /* blessed or cursed? */
|
|
int message_flags = CAST_NORMAL; /* which message to print out */
|
|
int class_casters; /* For determining ISKNOW */
|
|
int stat_casters; /* s_intel or s_wisdom */
|
|
int level_casters; /* spellcasting level */
|
|
char buf[2 * LINELEN];
|
|
struct spells sorted_spells[MAX_SPELLS];
|
|
char spellbuf[2 * LINELEN];
|
|
char spellbuf2[2 * LINELEN];
|
|
|
|
curp = &(caster->t_stats);
|
|
maxp = &(caster->maxstats);
|
|
points_casters = curp->s_power;
|
|
|
|
if (points_casters <= 0)
|
|
{
|
|
if (is_player)
|
|
msg("You don't have any spell points.");
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Paladins, Rangers, ringwearers, and monsters cast at 4 levels
|
|
* below. Other non-specialists at 8 below
|
|
*/
|
|
|
|
level_casters = curp->s_lvl;
|
|
|
|
switch (caster->t_ctype)
|
|
{
|
|
case C_PALADIN:
|
|
level_casters -= 4;
|
|
/* fallthrough */
|
|
case C_CLERIC:
|
|
class_casters = SP_CLERIC;
|
|
stat_casters = curp->s_wisdom;
|
|
break;
|
|
case C_RANGER:
|
|
level_casters -= 4;
|
|
/* fallthrough */
|
|
case C_DRUID:
|
|
class_casters = SP_DRUID;
|
|
stat_casters = curp->s_wisdom;
|
|
break;
|
|
case C_MAGICIAN:
|
|
class_casters = SP_WIZARD;
|
|
stat_casters = curp->s_intel;
|
|
break;
|
|
case C_ILLUSION:
|
|
class_casters = SP_ILLUSION;
|
|
stat_casters = curp->s_intel;
|
|
break;
|
|
case C_MONSTER:
|
|
if (off(*caster, ISUNIQUE))
|
|
level_casters -= 4;
|
|
class_casters = 0x0;
|
|
stat_casters = curp->s_intel;
|
|
break;
|
|
|
|
default:
|
|
if (is_wearing(R_WIZARD))
|
|
{
|
|
level_casters -= 4;
|
|
class_casters = (rnd(4) ? SP_WIZARD : SP_ILLUSION);
|
|
stat_casters = curp->s_intel;
|
|
}
|
|
else if (is_wearing(R_PIETY))
|
|
{
|
|
level_casters -= 4;
|
|
class_casters = (rnd(4) ? SP_CLERIC : SP_DRUID);
|
|
stat_casters = curp->s_wisdom;
|
|
}
|
|
else
|
|
{
|
|
level_casters -= 8;
|
|
class_casters = 0x0;
|
|
stat_casters = (rnd(2) ? curp->s_wisdom : curp->s_intel);
|
|
}
|
|
}
|
|
|
|
/* Bug - What about when WIS == INT? */
|
|
|
|
spell_type = (stat_casters == curp->s_intel) ? "spell" : "prayer";
|
|
|
|
if (!is_player && (sp = pick_monster_spell(caster)) == NULL)
|
|
return;
|
|
else if (is_player)
|
|
{
|
|
int num_spells = -1; /* num of spells cheap enough */
|
|
|
|
sorted_spells[0].sp_cost = -1;
|
|
|
|
for (sp = player_spells; sp->sp_level != -1; sp++)
|
|
{
|
|
if (sp->sp_flags & class_casters) /* Does class know spell? */
|
|
{
|
|
int rnd_number = rnd(2 * sp->sp_level) - sp->sp_level;
|
|
|
|
/* Knows normal spell one level below others */
|
|
|
|
casting_cost = spell_cost[sp->sp_level - 1] + rnd_number;
|
|
|
|
if (points_casters >= casting_cost)
|
|
{
|
|
sorted_spells[++num_spells] = *sp;
|
|
sorted_spells[num_spells].sp_cost = casting_cost;
|
|
sorted_spells[num_spells].sp_level = sp->sp_level - 1;
|
|
}
|
|
|
|
/* Knows blessed spell one level above others */
|
|
|
|
casting_cost = spell_cost[sp->sp_level + 1] + rnd_number;
|
|
|
|
if (points_casters >= casting_cost)
|
|
{
|
|
sorted_spells[++num_spells] = *sp;
|
|
sorted_spells[num_spells].sp_level = sp->sp_level + 1;
|
|
sorted_spells[num_spells].sp_cost = casting_cost;
|
|
sorted_spells[num_spells].sp_flags |= ISBLESSED;
|
|
}
|
|
} /* If class doesn't know spell, see if its a ISKNOW */
|
|
else if (sp->sp_flags & ISKNOW)
|
|
{
|
|
int rnd_number = rnd(4 * sp->sp_level) - sp->sp_level;
|
|
|
|
casting_cost = spell_cost[sp->sp_level] + rnd_number;
|
|
|
|
if (points_casters >= casting_cost)
|
|
{
|
|
sorted_spells[++num_spells] = *sp;
|
|
sorted_spells[num_spells].sp_cost = casting_cost;
|
|
}
|
|
}
|
|
/* else this spell is unknown */
|
|
}
|
|
|
|
if (sorted_spells[0].sp_cost == -1)
|
|
{
|
|
msg("You don't have enough %s points.", spell_type);
|
|
after = FALSE;
|
|
return;
|
|
}
|
|
|
|
qsort(sorted_spells,num_spells + 1,sizeof(struct spells),sort_spells);
|
|
|
|
do /* Prompt for spells */
|
|
{
|
|
struct spells *which_spell = NULL;
|
|
|
|
buf[0] = '\0';
|
|
msg("");/* Get rid of --More-- */
|
|
msg("Which %s are you casting [%d points left] (* for list)? ",
|
|
spell_type, points_casters);
|
|
|
|
switch(get_string(buf, cw))
|
|
{
|
|
case NORM: break;
|
|
case QUIT: return; /* ESC - lose turn */
|
|
default: continue;
|
|
}
|
|
|
|
if (buf[0] == '*') /* print list */
|
|
{
|
|
add_line("Cost Abbreviation Full Name");
|
|
|
|
for (i = 0; i <= num_spells; i++)
|
|
{
|
|
sp = &sorted_spells[i];
|
|
sprintf(buf, "[%3d] %-12s\t%s",
|
|
sp->sp_cost, spell_abrev(sp,spellbuf2),
|
|
spell_name(sp,spellbuf));
|
|
add_line(buf);
|
|
}
|
|
end_line();
|
|
sp = NULL;
|
|
continue;
|
|
}
|
|
|
|
if (isupper(buf[0])) /* Uppercase Abbreviation */
|
|
{
|
|
for (i = 0; i <= num_spells; i++)
|
|
{
|
|
sp = &sorted_spells[i];
|
|
|
|
if ((strcmp(spell_abrev(sp,spellbuf2), buf) == 0))
|
|
{
|
|
which_spell = sp;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else /* Full Spell Name */
|
|
{
|
|
for (i = 0; i <= num_spells; i++)
|
|
{
|
|
sp = &sorted_spells[i];
|
|
|
|
if ((strcmp(spell_name(sp,spellbuf), buf) == 0))
|
|
{
|
|
which_spell = sp;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
sp = which_spell;
|
|
}
|
|
while (sp == NULL);
|
|
}
|
|
|
|
/* Common monster and player code */
|
|
|
|
cast_name = spell_name(sp,spellbuf);
|
|
|
|
fumble_chance = (10 * sp->sp_level / 4 - 10 * level_casters / 13) * 5;
|
|
|
|
if (cur_weapon != NULL && wield_ok(caster, cur_weapon, FALSE) == FALSE)
|
|
{
|
|
switch (caster->t_ctype)
|
|
{
|
|
case C_MAGICIAN:
|
|
case C_ILLUSION:
|
|
msg("You should have both hands free.");
|
|
fumble_chance += rnd(level_casters) * 5;
|
|
break;
|
|
|
|
case C_CLERIC:
|
|
case C_DRUID:
|
|
case C_PALADIN:
|
|
msg("Your god looks askance at the weapon you wield.");
|
|
fumble_chance += rnd(level_casters) * 5;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (fumble_chance >= MAX_FUMBLE_CHANCE)
|
|
fumble_chance = MAX_FUMBLE_CHANCE;
|
|
else if (fumble_chance <= MIN_FUMBLE_CHANCE + sp->sp_level)
|
|
fumble_chance = MIN_FUMBLE_CHANCE + sp->sp_level;
|
|
|
|
if (fumble_chance > (30 + rnd(50)))
|
|
{
|
|
if (is_player)
|
|
{
|
|
int answer;
|
|
|
|
msg("Are you sure you want to try for that hard a %s? [n]",
|
|
spell_type);
|
|
|
|
answer = readchar();
|
|
|
|
if (tolower(answer) != 'y')
|
|
{
|
|
after = FALSE;
|
|
return;
|
|
}
|
|
else
|
|
msg("Here goes...");
|
|
}
|
|
else /* Only if the monster is desperate */
|
|
{
|
|
if (curp->s_hpt > maxp->s_hpt / 2)
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* casting costs food energy */
|
|
|
|
food_left -= sp->sp_cost;
|
|
|
|
spell_roll = rnd(100);
|
|
|
|
debug("%s(%d) cast '%s' fumble %%%d (rolled %d) ",
|
|
monsters[caster->t_index].m_name, curp->s_power, cast_name,
|
|
fumble_chance, spell_roll);
|
|
|
|
caster->t_rest_hpt = caster->t_rest_pow = 0;
|
|
|
|
if (!is_player) /* Stop running. */
|
|
{
|
|
running = FALSE;
|
|
msg("The %s is casting '%s'.", casters_name, cast_name);
|
|
}
|
|
|
|
/* The Crown of Might insures that your spells never fumble */
|
|
|
|
if (spell_roll < fumble_chance)
|
|
{
|
|
if (is_carrying(TR_CROWN))
|
|
message_flags |= CAST_CROWN;
|
|
else
|
|
{
|
|
message_flags |= CAST_CURSED;
|
|
|
|
curp->s_power -= min(curp->s_power,
|
|
(2 * sp->sp_cost)); /* 2x cost */
|
|
num_fumbles = rnd(((fumble_chance - spell_roll) / 10)
|
|
+ 1) + rnd(sp->sp_level) + rnd(curp->s_lvl);
|
|
num_fumbles = min(10, max(0, num_fumbles));
|
|
|
|
if (num_fumbles >= 6 && rnd(1) == 0)
|
|
bless_or_curse = ISCURSED;
|
|
else if (num_fumbles < 4)
|
|
{
|
|
if (is_player)
|
|
msg("Your %s fails.", spell_type);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else if (spell_roll > MAX_FUMBLE_CHANCE)
|
|
{
|
|
if (is_player)
|
|
{
|
|
message_flags |= CAST_BLESSED;
|
|
pstats.s_exp += 3 * sp->sp_cost * curp->s_lvl;
|
|
check_level();
|
|
}
|
|
|
|
maxp->s_power += sp->sp_cost;
|
|
bless_or_curse = ISBLESSED;
|
|
}
|
|
else
|
|
{
|
|
if (is_player) /* extra exp for sucessful spells */
|
|
{
|
|
if (player.t_ctype == C_MAGICIAN || player.t_ctype == C_ILLUSION)
|
|
{
|
|
pstats.s_exp += sp->sp_cost * curp->s_lvl;
|
|
check_level();
|
|
}
|
|
}
|
|
|
|
bless_or_curse = sp->sp_flags & ISBLESSED;
|
|
curp->s_power -= sp->sp_cost;
|
|
}
|
|
|
|
/* The Sceptre of Might blesses all your spells */
|
|
|
|
if (is_player && ((bless_or_curse & ISBLESSED) == 0) &&
|
|
is_carrying(TR_SCEPTRE))
|
|
{
|
|
message_flags |= CAST_SEPTRE;
|
|
bless_or_curse = ISBLESSED;
|
|
}
|
|
|
|
if (sp->sp_flags & POT_MAGIC)
|
|
quaff(caster, sp->sp_which, bless_or_curse);
|
|
else if (sp->sp_flags & SCR_MAGIC)
|
|
read_scroll(caster, sp->sp_which, bless_or_curse);
|
|
else if (sp->sp_flags & ZAP_MAGIC)
|
|
{
|
|
if (is_player)
|
|
{
|
|
do /* Must pick a direction */
|
|
{
|
|
msg("Which direction?");
|
|
}
|
|
while (get_dir() == FALSE);
|
|
}
|
|
else
|
|
{
|
|
delta.x = dir.x;
|
|
delta.y = dir.y;
|
|
}
|
|
do_zap(caster, sp->sp_which, bless_or_curse);
|
|
}
|
|
else
|
|
msg("What a strange %s!", spell_type);
|
|
|
|
/*
|
|
* Print messages and take fumbles *after* spell has gone off. This
|
|
* makes ENCHANT, etc more dangerous
|
|
*/
|
|
|
|
if (is_player)
|
|
{
|
|
if (message_flags & CAST_SEPTRE)
|
|
msg("The Sceptre enhanced your %s.", spell_type);
|
|
if (message_flags & CAST_CROWN)
|
|
msg("The Crown wordlessly corrected your %s.",
|
|
spell_type);
|
|
|
|
switch (message_flags & 0x1)
|
|
{
|
|
case CAST_CURSED:
|
|
msg("You botched your '%s' %s.", cast_name,
|
|
spell_type);
|
|
fumble_spell(caster, num_fumbles);
|
|
break;
|
|
case CAST_NORMAL:
|
|
msg("You sucessfully cast your '%s' %s.",
|
|
cast_name, spell_type);
|
|
break;
|
|
|
|
case CAST_BLESSED:
|
|
msg("Your '%s' %s went superbly.", cast_name,
|
|
spell_type);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
spell_name()
|
|
returns pointer to spell name
|
|
*/
|
|
|
|
char *
|
|
spell_name(struct spells *sp, char *buf)
|
|
{
|
|
if (buf == NULL)
|
|
return("UltraRogue Bug #105");
|
|
|
|
if (sp->sp_flags & POT_MAGIC)
|
|
strcpy(buf, p_magic[sp->sp_which].mi_name);
|
|
else if (sp->sp_flags & SCR_MAGIC)
|
|
strcpy(buf, s_magic[sp->sp_which].mi_name);
|
|
else if (sp->sp_flags & ZAP_MAGIC)
|
|
strcpy(buf, ws_magic[sp->sp_which].mi_name);
|
|
else
|
|
strcpy(buf, "unknown spell type");
|
|
|
|
if (sp->sp_flags & ISBLESSED)
|
|
strcat(buf, " 2");
|
|
|
|
return(buf);
|
|
}
|
|
|
|
/*
|
|
spell_abrev()
|
|
returns pointer to capital letter spell abbreviation
|
|
*/
|
|
|
|
char *
|
|
spell_abrev(struct spells *sp, char *buf)
|
|
{
|
|
if (buf == NULL)
|
|
return("UltraRogue Bug #106");
|
|
|
|
if (sp->sp_flags & POT_MAGIC)
|
|
strcpy(buf, p_magic[sp->sp_which].mi_abrev);
|
|
else if (sp->sp_flags & SCR_MAGIC)
|
|
strcpy(buf, s_magic[sp->sp_which].mi_abrev);
|
|
else if (sp->sp_flags & ZAP_MAGIC)
|
|
strcpy(buf, ws_magic[sp->sp_which].mi_abrev);
|
|
else
|
|
strcpy(buf, "?????");
|
|
|
|
if (sp->sp_flags & ISBLESSED)
|
|
strcat(buf, " 2");
|
|
|
|
return(buf);
|
|
}
|
|
|
|
/*
|
|
fumble_spell()
|
|
he blew it. Make him pay
|
|
*/
|
|
|
|
void
|
|
fumble_spell(struct thing *caster, int num_fumbles)
|
|
{
|
|
struct stats *curp = &(caster->t_stats);
|
|
struct stats *maxp = &(caster->maxstats);
|
|
int is_player = (caster == &player);
|
|
|
|
debug("Taking %d fumbles.", num_fumbles);
|
|
|
|
switch (num_fumbles)
|
|
{
|
|
case 10: /* Lose ability */
|
|
if (rnd(5) == 0)
|
|
quaff(caster, P_GAINABIL, ISCURSED);
|
|
break;
|
|
|
|
case 9: /* Lose max spell points */
|
|
|
|
if (rnd(4) == 0)
|
|
{
|
|
maxp->s_power -= rnd(10);
|
|
|
|
if (maxp->s_power <= 5)
|
|
maxp->s_power = 5;
|
|
}
|
|
break;
|
|
|
|
case 8: /* Lose all current spell points */
|
|
|
|
if (rnd(3) == 0)
|
|
curp->s_power = 0;
|
|
else
|
|
curp->s_power /= 2;
|
|
break;
|
|
|
|
case 7: /* Freeze */
|
|
|
|
if (rnd(2) == 0)
|
|
{
|
|
if (is_player)
|
|
no_command++;
|
|
else
|
|
caster->t_no_move++;
|
|
}
|
|
break;
|
|
|
|
case 6: /* Cast a cursed spell - see below */
|
|
break;
|
|
|
|
case 5: /* Become dazed and confused */
|
|
|
|
if (rnd(5) == 0)
|
|
quaff(caster, P_CLEAR, ISCURSED);
|
|
break;
|
|
|
|
case 4: /* Lose hit points */
|
|
|
|
if (is_player)
|
|
feel_message();
|
|
if ((curp->s_hpt -= rnd(10)) <= 0)
|
|
{
|
|
if (is_player)
|
|
death(D_SPELLFUMBLE);
|
|
else
|
|
killed(caster, find_mons(caster->t_pos.y, caster->t_pos.x),
|
|
NOMESSAGE, NOPOINTS);
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case 3: /* Spell fails */
|
|
break;
|
|
|
|
case 2: /* Freeze */
|
|
|
|
if (is_player)
|
|
no_command++;
|
|
else
|
|
caster->t_no_move++;
|
|
|
|
break;
|
|
|
|
default:
|
|
case 1: /* Take double spell points - handled in incant() */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
learn_new_spells()
|
|
go through player_spells and ISKNOW identified potions,
|
|
scrolls, and sticks
|
|
*/
|
|
|
|
void
|
|
learn_new_spells(void)
|
|
{
|
|
struct spells *sp;
|
|
int kludge = 0;
|
|
char spellbuf[2*LINELEN];
|
|
|
|
for (sp = player_spells; sp->sp_level != -1; sp++)
|
|
{
|
|
if (sp->sp_flags & POT_MAGIC)
|
|
kludge = TYP_POTION;
|
|
else if (sp->sp_flags & SCR_MAGIC)
|
|
kludge = TYP_SCROLL;
|
|
else if (sp->sp_flags & ZAP_MAGIC)
|
|
kludge = TYP_STICK;
|
|
|
|
if (know_items[kludge][sp->sp_which])
|
|
{
|
|
if ((sp->sp_flags & ISKNOW) == FALSE)
|
|
debug("Learned new spell '%s'", spell_name(sp,spellbuf));
|
|
sp->sp_flags |= ISKNOW;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
pick_monster_spell()
|
|
decide which spell from monst_spells will be cast
|
|
returns pointer to spell in monst_spells
|
|
*/
|
|
|
|
struct spells *
|
|
pick_monster_spell(struct thing *caster)
|
|
{
|
|
struct spells *sp = NULL;
|
|
struct stats *curp = &(caster->t_stats);
|
|
int points_casters = curp->s_power;
|
|
|
|
/* Discover castable spells */
|
|
|
|
for (sp = monst_spells; sp->sp_level != -1; sp++)
|
|
{
|
|
int rnd_number = rnd(2 * sp->sp_level) - sp->sp_level;
|
|
int casting_cost = spell_cost[sp->sp_level] + rnd_number;
|
|
|
|
if (points_casters >= casting_cost)
|
|
sp->sp_flags |= ISKNOW;
|
|
}
|
|
|
|
/* Decide which spell to cast */
|
|
|
|
if (curp->s_hpt < rnd(caster->maxstats.s_hpt)) /* think defense */
|
|
{
|
|
int i;
|
|
static const int run_or_heal[NUM_RUN] =
|
|
{ M_SELFTELEP, M_HLNG2, M_HLNG, M_REGENERATE };
|
|
|
|
for (i = 0; i < NUM_RUN; i++)
|
|
{
|
|
sp = &monst_spells[run_or_heal[i]];
|
|
|
|
if ((sp->sp_flags & ISKNOW) && rnd(1))
|
|
return(sp);
|
|
}
|
|
}
|
|
|
|
if (on(*caster, ISSLOW)) /* cancel a slow */
|
|
{
|
|
sp = &monst_spells[M_HASTE];
|
|
|
|
if (sp->sp_flags & ISKNOW)
|
|
return (sp);
|
|
}
|
|
|
|
if (on(*caster, ISFLEE)) /* stop running away */
|
|
{
|
|
sp = &monst_spells[M_SHERO];
|
|
|
|
if (sp->sp_flags & ISKNOW)
|
|
return (sp);
|
|
}
|
|
|
|
if (on(player, ISINVIS) || on(player, ISDISGUISE))
|
|
{
|
|
if (off(*caster, CANSEE)) /* look for him */
|
|
{
|
|
sp = &monst_spells[M_SEEINVIS];
|
|
|
|
if (sp->sp_flags & ISKNOW)
|
|
return (sp);
|
|
}
|
|
else if (off(*caster, ISINVIS)) /* go invisible */
|
|
{
|
|
sp = &monst_spells[M_INVIS];
|
|
|
|
if (sp->sp_flags & ISKNOW)
|
|
return (sp);
|
|
}
|
|
}
|
|
|
|
if (on(player, CANINWALL) && (off(*caster, CANINWALL)) &&
|
|
(rnd(5) == 0))
|
|
{
|
|
sp = &monst_spells[M_PHASE];
|
|
|
|
if (sp->sp_flags & ISKNOW)
|
|
return (sp);
|
|
}
|
|
|
|
if (rnd(5) == 0 && has_defensive_spell(player))
|
|
{
|
|
sp = &monst_spells[M_CANCEL];
|
|
|
|
if (sp->sp_flags & ISKNOW)
|
|
return (sp);
|
|
}
|
|
|
|
/* Cast an offensive spell */
|
|
|
|
for (sp = &monst_spells[M_OFFENSE]; sp->sp_level != 1; sp++)
|
|
{
|
|
if ((rnd(3) == 0) && (sp->sp_flags & ISKNOW))
|
|
return (sp);
|
|
|
|
if ((rnd(3) == 0) && (sp->sp_flags & ISKNOW))
|
|
{
|
|
if (sp->sp_which != WS_MISSILE &&
|
|
DISTANCE(caster->t_pos, hero) > BOLT_LENGTH)
|
|
continue;
|
|
else
|
|
return(sp);
|
|
}
|
|
}
|
|
|
|
return(NULL);
|
|
}
|
|
|
|
/*
|
|
sort_spells()
|
|
called by qsort()
|
|
*/
|
|
|
|
int
|
|
sort_spells(const void *a, const void *b)
|
|
{
|
|
struct spells *sp1, *sp2;
|
|
int diff;
|
|
char spellbuf[2 * LINELEN];
|
|
char spellbuf2[2 * LINELEN];
|
|
|
|
union /* hack to prevent 'lint' from complaining */
|
|
{
|
|
struct spells *s;
|
|
const void *vptr;
|
|
} s1,s2;
|
|
|
|
s1.vptr = a;
|
|
s2.vptr = b;
|
|
|
|
sp1 = s1.s;
|
|
sp2 = s2.s;
|
|
|
|
diff = sp1->sp_cost - sp2->sp_cost;
|
|
|
|
if (diff != 0)
|
|
return(diff);
|
|
else
|
|
return(strcmp(spell_name(sp1,spellbuf), spell_name(sp1,spellbuf2)));
|
|
}
|