Mercurial > hg > early-roguelike
diff urogue/magic.c @ 256:c495a4f288c6
Import UltraRogue from the Roguelike Restoration Project (r1490)
author | John "Elwin" Edwards |
---|---|
date | Tue, 31 Jan 2017 19:56:04 -0500 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/urogue/magic.c Tue Jan 31 19:56:04 2017 -0500 @@ -0,0 +1,878 @@ +/* + 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; + }