1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 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;
}
|