/* 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 #include #include #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))); }