diff options
author | peterb12 <peterb12@c06c8d41-db1a-0410-9941-cceddc491573> | 2005-07-21 02:34:44 +0000 |
---|---|---|
committer | peterb12 <peterb12@c06c8d41-db1a-0410-9941-cceddc491573> | 2005-07-21 02:34:44 +0000 |
commit | 673bdae75485d14f759af597c3c62b99601f9a43 (patch) | |
tree | 368103f29fe0ce5dcf98060d9b5faa04590085fb /trunk/source/spl-util.cc | |
parent | 7e900be770db24b0405fd2162491c405a425873e (diff) | |
download | crawl-ref-673bdae75485d14f759af597c3c62b99601f9a43.tar.gz crawl-ref-673bdae75485d14f759af597c3c62b99601f9a43.zip |
Initial revision
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@3 c06c8d41-db1a-0410-9941-cceddc491573
Diffstat (limited to 'trunk/source/spl-util.cc')
-rw-r--r-- | trunk/source/spl-util.cc | 786 |
1 files changed, 786 insertions, 0 deletions
diff --git a/trunk/source/spl-util.cc b/trunk/source/spl-util.cc new file mode 100644 index 0000000000..fa6e2d5885 --- /dev/null +++ b/trunk/source/spl-util.cc @@ -0,0 +1,786 @@ +/* + * File: spl-util.h * + * Summary: data handlers for player-avilable spell list * + * Written by: don brodale <dbrodale@bigfootinteractive.com> * + * * + * Changelog(most recent first): * + * + * <3> 04oct2001 bwr absorbed spells0.cc + * <2> 24jun2000 jmf changed to use new data structure + * <1> 12jun2000 dlb created after much thought + */ + +#include "AppHdr.h" +#include "spl-util.h" + +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <limits.h> + +#include "externs.h" + +#include "direct.h" +#include "debug.h" +#include "stuff.h" +#include "itemname.h" +#include "macro.h" +#include "monstuff.h" +#include "player.h" +#include "spl-book.h" +#include "view.h" + + +#ifdef DOS +#include <conio.h> +#endif + + +static struct playerspell spelldata[] = { +#include "spl-data.h" +}; + +static int plyrspell_list[NUM_SPELLS]; + +#define PLYRSPELLDATASIZE (sizeof(spelldata)/sizeof(struct playerspell)) + +static struct playerspell *seekspell(int spellid); +static bool cloud_helper( int (*func) (int, int, int, int), int x, int y, + int pow, int ctype ); + +/* + * BEGIN PUBLIC FUNCTIONS + */ + +// all this does is merely refresh the internal spell list {dlb}: +void init_playerspells(void) +{ + unsigned int x = 0; + + for (x = 0; x < NUM_SPELLS; x++) + plyrspell_list[x] = -1; + + // can only use up to PLYRSPELLDATASIZE _MINUS ONE_, or the + // last entry tries to set plyrspell_list[SPELL_NO_SPELL] + // which corrupts the heap. + for (x = 0; x < PLYRSPELLDATASIZE - 1; x++) + plyrspell_list[spelldata[x].id] = x; + + for (x = 0; x < NUM_SPELLS; x++) + { + if (plyrspell_list[x] == -1) + plyrspell_list[x] = plyrspell_list[SPELL_NO_SPELL]; + } + + return; // return value should not matter here {dlb} +}; // end init_playerspells() + +int get_spell_slot_by_letter( char letter ) +{ + ASSERT( isalpha( letter ) ); + + const int index = letter_to_index( letter ); + + if (you.spell_letter_table[ index ] == -1) + return (-1); + + return (you.spell_letter_table[index]); +} + +int get_spell_by_letter( char letter ) +{ + ASSERT( isalpha( letter ) ); + + const int slot = get_spell_slot_by_letter( letter ); + + return ((slot == -1) ? SPELL_NO_SPELL : you.spells[slot]); +} + +bool add_spell_to_memory( int spell ) +{ + int i, j; + + // first we find a slot in our head: + for (i = 0; i < 25; i++) + { + if (you.spells[i] == SPELL_NO_SPELL) + break; + } + + you.spells[i] = spell; + + // now we find an available label: + for (j = 0; j < 52; j++) + { + if (you.spell_letter_table[j] == -1) + break; + } + + you.spell_letter_table[j] = i; + + you.spell_no++; + + return (true); +} + +bool del_spell_from_memory_by_slot( int slot ) +{ + int j; + + you.spells[ slot ] = SPELL_NO_SPELL; + + for (j = 0; j < 52; j++) + { + if (you.spell_letter_table[j] == slot) + you.spell_letter_table[j] = -1; + + } + + you.spell_no--; + + return (true); +} + + +int spell_hunger(int which_spell) +{ + int level = seekspell(which_spell)->level; + + switch (level) + { + case 1: return 50; + case 2: return 95; + case 3: return 160; + case 4: return 250; + case 5: return 350; + case 6: return 550; + case 7: return 700; + case 8: return 850; + case 9: return 1000; + case 10: return 1000; + case 11: return 1100; + case 12: return 1250; + case 13: return 1380; + case 14: return 1500; + case 15: return 1600; + default: return 1600 + (20 * level); + } +} // end spell_hunger(); + +// applied to spell misfires (more power = worse) and triggers +// for Xom acting (more power = more likely to grab his attention) {dlb} +int spell_mana(int which_spell) +{ + return (seekspell(which_spell)->level); +} + +// applied in naughties (more difficult = higher level knowledge = worse) +// and triggers for Sif acting (same reasoning as above, just good) {dlb} +int spell_difficulty(int which_spell) +{ + return (seekspell(which_spell)->level); +} + +int spell_levels_required( int which_spell ) +{ + int levels = spell_difficulty( which_spell ); + + if (which_spell == SPELL_DELAYED_FIREBALL + && player_has_spell( SPELL_FIREBALL )) + { + levels -= spell_difficulty( SPELL_FIREBALL ); + } + else if (which_spell == SPELL_FIREBALL + && player_has_spell( SPELL_DELAYED_FIREBALL )) + { + levels = 0; + } + + return (levels); +} + +bool spell_typematch(int which_spell, unsigned int which_discipline) +{ + return (seekspell(which_spell)->disciplines & which_discipline); +} + +//jmf: next two for simple bit handling +unsigned int spell_type(int spell) +{ + return (seekspell(spell)->disciplines); +} + +int count_bits(unsigned int bits) +{ + unsigned int n; + int c = 0; + + for (n = 1; n < INT_MAX; n <<= 1) + { + if (n & bits) + c++; + } + + return (c); +} + +// this will probably be used often, so rather than use malloc/free +// (which may lead to memory fragmentation) I'll just use a static +// array of characters -- if/when the String changeover takes place, +// this will all shift, no doubt {dlb} +/* + const char *spell_title( int which_spell ) + { + static char this_title[41] = ""; // this is generous, to say the least {dlb} + strncpy(this_title, seekspell(which_spell)->title, 41); + // truncation better than overrun {dlb} + return ( this_title ); + } // end spell_title() +*/ + +const char *spell_title(int spell) //jmf: ah the joys of driving ms. data +{ + return (seekspell(spell)->title); +} + + +// FUNCTION APPLICATORS: Idea from Juho Snellman <jsnell@lyseo.edu.ouka.fi> +// on the Roguelike News pages, Development section. +// <URL:http://www.skoardy.demon.co.uk/rlnews/> +// Here are some function applicators: sort of like brain-dead, +// home-grown iterators for the container "dungeon". + +// Apply a function-pointer to all visible squares +// Returns summation of return values from passed in function. +int apply_area_visible( int (*func) (int, int, int, int), int power ) +{ + int x, y; + int rv = 0; + + //jmf: FIXME: randomly start from other quadrants, like raise_dead? + for (x = you.x_pos - 8; x <= you.x_pos + 8; x++) + { + for (y = you.y_pos - 8; y <= you.y_pos + 8; y++) + { + if (see_grid(x, y)) + rv += func(x, y, power, 0); + } + } + + return (rv); +} // end apply_area_visible() + +// Applies the effect to all nine squares around/including the target. +// Returns summation of return values from passed in function. +int apply_area_square( int (*func) (int, int, int, int), int cx, int cy, + int power ) +{ + int x, y; + int rv = 0; + + for (x = cx - 1; x <= cx + 1; x++) + { + for (y = cy - 1; y <= cy + 1; y++) + { + rv += func(x, y, power, 0); + } + } + + return (rv); +} // end apply_area_square() + + +// Applies the effect to the eight squares beside the target. +// Returns summation of return values from passed in function. +int apply_area_around_square( int (*func) (int, int, int, int), + int targ_x, int targ_y, int power) +{ + int x, y; + int rv = 0; + + for (x = targ_x - 1; x <= targ_x + 1; x++) + { + for (y = targ_y - 1; y <= targ_y + 1; y++) + { + if (x == targ_x && y == targ_y) + continue; + else + rv += func(x, y, power, 0); + } + } + return (rv); +} // end apply_area_around_square() + +// Effect up to max_targs monsters around a point, chosen randomly +// Return varies with the function called; return values will be added up. +int apply_random_around_square( int (*func) (int, int, int, int), + int targ_x, int targ_y, + bool hole_in_middle, int power, int max_targs ) +{ + int rv = 0; + + if (max_targs <= 0) + return 0; + + if (max_targs >= 9 && !hole_in_middle) + { + return (apply_area_square( func, targ_x, targ_y, power )); + } + + if (max_targs >= 8 && hole_in_middle) + { + return (apply_area_around_square( func, targ_x, targ_y, power )); + } + + FixedVector< coord_def, 8 > targs; + int count = 0; + + for (int x = targ_x - 1; x <= targ_x + 1; x++) + { + for (int y = targ_y - 1; y <= targ_y + 1; y++) + { + if (hole_in_middle && (x == targ_x && y == targ_y)) + continue; + + if (mgrd[x][y] == NON_MONSTER + && !(x == you.x_pos && y == you.y_pos)) + { + continue; + } + + // Found target + count++; + + // Slight differece here over the basic algorithm... + // + // For cases where the number of choices <= max_targs it's + // obvious (all available choices will be selected). + // + // For choices > max_targs, here's a brief proof: + // + // Let m = max_targs, k = choices - max_targs, k > 0. + // + // Proof, by induction (over k): + // + // 1) Show n = m + 1 (k = 1) gives uniform distribution, + // P(new one not chosen) = 1 / (m + 1). + // m 1 1 + // P(specific previous one replaced) = --- * --- = --- + // m+1 m m+1 + // + // So the probablity is uniform (ie. any element has + // a 1/(m+1) chance of being in the unchosen slot). + // + // 2) Assume the distribution is uniform at n = m+k. + // (ie. the probablity that any of the found elements + // was chosen = m / (m+k) (the slots are symetric, + // so it's the sum of the probabilities of being in + // any of them)). + // + // 3) Show n = m + k + 1 gives a uniform distribution. + // P(new one chosen) = m / (m + k + 1) + // P(any specific previous choice remaining chosen) + // = [1 - P(swaped into m+k+1 position)] * P(prev. chosen) + // m 1 m + // = [ 1 - ----- * --- ] * --- + // m+k+1 m m+k + // + // m+k m m + // = ----- * --- = ----- + // m+k+1 m+k m+k+1 + // + // Therefore, it's uniform for n = m + k + 1. QED + // + // The important thing to note in calculating the last + // probability is that the chosen elements have already + // passed tests which verify that they *don't* belong + // in slots m+1...m+k, so the only positions an already + // chosen element can end up in are it's original + // position (in one of the chosen slots), or in the + // new slot. + // + // The new item can, of course, be placed in any slot, + // swapping the value there into the new slot... we + // just don't care about the non-chosen slots enough + // to store them, so it might look like the item + // automatically takes the new slot when not chosen + // (although, by symetry all the non-chosen slots are + // the same... and similarly, by symetry, all chosen + // slots are the same). + // + // Yes, that's a long comment for a short piece of + // code, but I want people to have an understanding + // of why this works (or at least make them wary about + // changing it without proof and breaking this code). -- bwr + + // Accept the first max_targs choices, then when + // new choices come up, replace one of the choices + // at random, max_targs/count of the time (the rest + // of the time it replaces an element in an unchosen + // slot -- but we don't care about them). + if (count <= max_targs) + { + targs[ count - 1 ].x = x; + targs[ count - 1 ].y = y; + } + else if (random2( count ) < max_targs) + { + const int pick = random2( max_targs ); + targs[ pick ].x = x; + targs[ pick ].y = y; + } + } + } + + const int targs_found = (count < max_targs) ? count : max_targs; + + if (targs_found) + { + // Used to divide the power up among the targets here, but + // it's probably better to allow the full power through and + // balance the called function. -- bwr + for (int i = 0; i < targs_found; i++) + { + ASSERT( targs[i].x && targs[i].y ); + rv += func( targs[i].x, targs[i].y, power, 0 ); + } + } + + return (rv); +} // end apply_random_around_square() + +// apply func to one square of player's choice beside the player +int apply_one_neighbouring_square(int (*func) (int, int, int, int), int power) +{ + struct dist bmove; + + mpr("Which direction? [ESC to cancel]", MSGCH_PROMPT); + direction( bmove, DIR_DIR, TARG_ENEMY ); + + if (!bmove.isValid) + { + canned_msg(MSG_SPELL_FIZZLES); + return (0); + } + + int rv = func(you.x_pos + bmove.dx, you.y_pos + bmove.dy, power, 1); + + if (rv == 0) + canned_msg(MSG_NOTHING_HAPPENS); + + return (rv); +} // end apply_one_neighbouring_square() + +int apply_area_within_radius( int (*func) (int, int, int, int), + int x, int y, int pow, int radius, int ctype ) +{ + int ix, iy; + int sq_radius = radius * radius; + int sx, sy, ex, ey; // start and end x, y - bounds checked + int rv = 0; + + // begin x,y + sx = x - radius; + sy = y - radius; + if (sx < 0) sx = 0; + if (sy < 0) sy = 0; + + // end x,y + ex = x + radius; + ey = y + radius; + if (ex > GXM) ex = GXM; + if (ey > GYM) ey = GYM; + + for (ix = sx; ix < ex; ix++) + { + for (iy = sy; iy < ey; iy++) + { + if (distance(x, y, ix, iy) <= sq_radius) + rv += func(ix, iy, pow, ctype); + } + } + + return (rv); +} // end apply_area_within_radius() + +// apply_area_cloud: +// Try to make a realistic cloud by expanding from a point, filling empty +// floor tiles until we run out of material (passed in as number). +// We really need some sort of a queue structure, since ideally I'd like +// to do a (shallow) breadth-first-search of the dungeon floor. +// This ought to work okay for small clouds. +void apply_area_cloud( int (*func) (int, int, int, int), int x, int y, + int pow, int number, int ctype ) +{ + int spread, clouds_left = number; + int good_squares = 0, neighbours[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; + int dx = 1, dy = 1; + bool x_first; + + if (clouds_left && cloud_helper(func, x, y, pow, ctype)) + clouds_left--; + + if (!clouds_left) + return; + + if (coinflip()) + dx *= -1; + if (coinflip()) + dy *= -1; + + x_first = coinflip(); + + if (x_first) + { + if (clouds_left && cloud_helper(func, x + dx, y, pow, ctype)) + { + clouds_left--; + good_squares++; + neighbours[0]++; + } + + if (clouds_left && cloud_helper(func, x - dx, y, pow, ctype)) + { + clouds_left--; + good_squares++; + neighbours[1]++; + } + + if (clouds_left && cloud_helper(func, x, y + dy, pow, ctype)) + { + clouds_left--; + good_squares++; + neighbours[2]++; + } + + if (clouds_left && cloud_helper(func, x, y - dy, pow, ctype)) + { + clouds_left--; + good_squares++; + neighbours[3]++; + } + } + else + { + if (clouds_left && cloud_helper(func, x, y + dy, pow, ctype)) + { + clouds_left--; + good_squares++; + neighbours[2]++; + } + + if (clouds_left && cloud_helper(func, x, y - dy, pow, ctype)) + { + clouds_left--; + good_squares++; + neighbours[3]++; + } + + if (clouds_left && cloud_helper(func, x + dx, y, pow, ctype)) + { + clouds_left--; + good_squares++; + neighbours[0]++; + } + + if (clouds_left && cloud_helper(func, x - dx, y, pow, ctype)) + { + clouds_left--; + good_squares++; + neighbours[1]++; + } + } + + // now diagonals; we could randomize dx & dy again here + if (clouds_left && cloud_helper(func, x + dx, y + dy, pow, ctype)) + { + clouds_left--; + good_squares++; + neighbours[4]++; + } + + if (clouds_left && cloud_helper(func, x - dx, y + dy, pow, ctype)) + { + clouds_left--; + good_squares++; + neighbours[5]++; + } + + if (clouds_left && cloud_helper(func, x + dx, y - dy, pow, ctype)) + { + clouds_left--; + good_squares++; + neighbours[6]++; + } + + if (clouds_left && cloud_helper(func, x - dx, y - dy, pow, ctype)) + { + clouds_left--; + good_squares++; + neighbours[7]++; + } + + if (!(clouds_left && good_squares)) + return; + + for (int i = 0; i < 8 && clouds_left; i++) + { + if (neighbours[i] == 0) + continue; + + spread = clouds_left / good_squares; + clouds_left -= spread; + good_squares--; + + switch (i) + { + case 0: + apply_area_cloud(func, x + dx, y, pow, spread, ctype); + break; + case 1: + apply_area_cloud(func, x - dx, y, pow, spread, ctype); + break; + case 2: + apply_area_cloud(func, x, y + dy, pow, spread, ctype); + break; + case 3: + apply_area_cloud(func, x, y - dy, pow, spread, ctype); + break; + case 4: + apply_area_cloud(func, x + dx, y + dy, pow, spread, ctype); + break; + case 5: + apply_area_cloud(func, x - dx, y + dy, pow, spread, ctype); + break; + case 6: + apply_area_cloud(func, x + dx, y - dy, pow, spread, ctype); + break; + case 7: + apply_area_cloud(func, x - dx, y - dy, pow, spread, ctype); + break; + } + } +} // end apply_area_cloud() + +char spell_direction( struct dist &spelld, struct bolt &pbolt, + int restrict, int mode ) +{ + if (restrict == DIR_TARGET) + mpr( "Choose a target (+/- for next/prev monster)", MSGCH_PROMPT ); + else + mpr( STD_DIRECTION_PROMPT, MSGCH_PROMPT ); + + message_current_target(); + + direction( spelld, restrict, mode ); + + if (!spelld.isValid) + { + // check for user cancel + canned_msg(MSG_SPELL_FIZZLES); + return -1; + } + + pbolt.target_x = spelld.tx; + pbolt.target_y = spelld.ty; + pbolt.source_x = you.x_pos; + pbolt.source_y = you.y_pos; + + return 1; +} // end spell_direction() + +const char *spelltype_name(unsigned int which_spelltype) +{ + static char bug_string[80]; + + switch (which_spelltype) + { + case SPTYP_CONJURATION: + return ("Conjuration"); + case SPTYP_ENCHANTMENT: + return ("Enchantment"); + case SPTYP_FIRE: + return ("Fire"); + case SPTYP_ICE: + return ("Ice"); + case SPTYP_TRANSMIGRATION: + return ("Transmigration"); + case SPTYP_NECROMANCY: + return ("Necromancy"); + case SPTYP_HOLY: + return ("Holy"); + case SPTYP_SUMMONING: + return ("Summoning"); + case SPTYP_DIVINATION: + return ("Divination"); + case SPTYP_TRANSLOCATION: + return ("Translocation"); + case SPTYP_POISON: + return ("Poison"); + case SPTYP_EARTH: + return ("Earth"); + case SPTYP_AIR: + return ("Air"); + default: + snprintf( bug_string, sizeof(bug_string), + "invalid(%d)", which_spelltype ); + + return (bug_string); + } +} // end spelltype_name() + +int spell_type2skill(unsigned int spelltype) +{ + char buffer[80]; + + switch (spelltype) + { + case SPTYP_CONJURATION: return (SK_CONJURATIONS); + case SPTYP_ENCHANTMENT: return (SK_ENCHANTMENTS); + case SPTYP_FIRE: return (SK_FIRE_MAGIC); + case SPTYP_ICE: return (SK_ICE_MAGIC); + case SPTYP_TRANSMIGRATION: return (SK_TRANSMIGRATION); + case SPTYP_NECROMANCY: return (SK_NECROMANCY); + case SPTYP_SUMMONING: return (SK_SUMMONINGS); + case SPTYP_DIVINATION: return (SK_DIVINATIONS); + case SPTYP_TRANSLOCATION: return (SK_TRANSLOCATIONS); + case SPTYP_POISON: return (SK_POISON_MAGIC); + case SPTYP_EARTH: return (SK_EARTH_MAGIC); + case SPTYP_AIR: return (SK_AIR_MAGIC); + + default: + case SPTYP_HOLY: + snprintf( buffer, sizeof(buffer), + "spell_type2skill: called with spelltype %d", spelltype ); + + mpr( buffer ); + return (-1); + } +} // end spell_type2skill() + +/* + ************************************************** + * * + * END PUBLIC FUNCTIONS * + * * + ************************************************** + */ + +//jmf: simplified; moved init code to top function, init_playerspells() +static struct playerspell *seekspell(int spell) +{ + return (&spelldata[plyrspell_list[spell]]); +} + +static bool cloud_helper( int (*func) (int, int, int, int), int x, int y, + int pow, int ctype ) +{ + if (grd[x][y] > DNGN_LAST_SOLID_TILE && env.cgrid[x][y] == EMPTY_CLOUD) + { + func(x, y, pow, ctype); + return true; + } + + return false; +} |