1 /*
2 * File: spl-util.h *
3 * Summary: data handlers for player-avilable spell list *
4 * Written by: don brodale <dbrodale@bigfootinteractive.com> *
5 * *
6 * Changelog(most recent first): *
7 *
8 * <3> 04oct2001 bwr absorbed spells0.cc
9 * <2> 24jun2000 jmf changed to use new data structure
10 * <1> 12jun2000 dlb created after much thought
11 */
12
13 #include "AppHdr.h"
14 #include "spl-util.h"
15
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <ctype.h>
19 #include <string.h>
20 #include <limits.h>
21
22 #include "externs.h"
23
24 #include "direct.h"
25 #include "debug.h"
26 #include "stuff.h"
27 #include "itemname.h"
28 #include "macro.h"
29 #include "monstuff.h"
30 #include "player.h"
31 #include "spl-book.h"
32 #include "view.h"
33
34
35 #ifdef DOS
36 #include <conio.h>
37 #endif
38
39
40 static struct playerspell spelldata[] = {
41 #include "spl-data.h"
42 };
43
44 static int plyrspell_list[NUM_SPELLS];
45
46 #define PLYRSPELLDATASIZE (sizeof(spelldata)/sizeof(struct playerspell))
47
48 static struct playerspell *seekspell(int spellid);
49 static bool cloud_helper( int (*func) (int, int, int, int), int x, int y,
50 int pow, int ctype );
51
52 /*
53 * BEGIN PUBLIC FUNCTIONS
54 */
55
56 // all this does is merely refresh the internal spell list {dlb}:
init_playerspells(void)57 void init_playerspells(void)
58 {
59 unsigned int x = 0;
60
61 for (x = 0; x < NUM_SPELLS; x++)
62 plyrspell_list[x] = -1;
63
64 // can only use up to PLYRSPELLDATASIZE _MINUS ONE_, or the
65 // last entry tries to set plyrspell_list[SPELL_NO_SPELL]
66 // which corrupts the heap.
67 for (x = 0; x < PLYRSPELLDATASIZE - 1; x++)
68 plyrspell_list[spelldata[x].id] = x;
69
70 for (x = 0; x < NUM_SPELLS; x++)
71 {
72 if (plyrspell_list[x] == -1)
73 plyrspell_list[x] = plyrspell_list[SPELL_NO_SPELL];
74 }
75
76 return; // return value should not matter here {dlb}
77 }; // end init_playerspells()
78
get_spell_slot_by_letter(char letter)79 int get_spell_slot_by_letter( char letter )
80 {
81 ASSERT( isalpha( letter ) );
82
83 const int index = letter_to_index( letter );
84
85 if (you.spell_letter_table[ index ] == -1)
86 return (-1);
87
88 return (you.spell_letter_table[index]);
89 }
90
get_spell_by_letter(char letter)91 int get_spell_by_letter( char letter )
92 {
93 ASSERT( isalpha( letter ) );
94
95 const int slot = get_spell_slot_by_letter( letter );
96
97 return ((slot == -1) ? SPELL_NO_SPELL : you.spells[slot]);
98 }
99
add_spell_to_memory(int spell)100 bool add_spell_to_memory( int spell )
101 {
102 int i, j;
103
104 // first we find a slot in our head:
105 for (i = 0; i < 25; i++)
106 {
107 if (you.spells[i] == SPELL_NO_SPELL)
108 break;
109 }
110
111 you.spells[i] = spell;
112
113 // now we find an available label:
114 for (j = 0; j < 52; j++)
115 {
116 if (you.spell_letter_table[j] == -1)
117 break;
118 }
119
120 you.spell_letter_table[j] = i;
121
122 you.spell_no++;
123
124 return (true);
125 }
126
del_spell_from_memory_by_slot(int slot)127 bool del_spell_from_memory_by_slot( int slot )
128 {
129 int j;
130
131 you.spells[ slot ] = SPELL_NO_SPELL;
132
133 for (j = 0; j < 52; j++)
134 {
135 if (you.spell_letter_table[j] == slot)
136 you.spell_letter_table[j] = -1;
137
138 }
139
140 you.spell_no--;
141
142 return (true);
143 }
144
145
spell_hunger(int which_spell)146 int spell_hunger(int which_spell)
147 {
148 int level = seekspell(which_spell)->level;
149
150 switch (level)
151 {
152 case 1: return 50;
153 case 2: return 95;
154 case 3: return 160;
155 case 4: return 250;
156 case 5: return 350;
157 case 6: return 550;
158 case 7: return 700;
159 case 8: return 850;
160 case 9: return 1000;
161 case 10: return 1000;
162 case 11: return 1100;
163 case 12: return 1250;
164 case 13: return 1380;
165 case 14: return 1500;
166 case 15: return 1600;
167 default: return 1600 + (20 * level);
168 }
169 } // end spell_hunger();
170
171 // applied to spell misfires (more power = worse) and triggers
172 // for Xom acting (more power = more likely to grab his attention) {dlb}
spell_mana(int which_spell)173 int spell_mana(int which_spell)
174 {
175 return (seekspell(which_spell)->level);
176 }
177
178 // applied in naughties (more difficult = higher level knowledge = worse)
179 // and triggers for Sif acting (same reasoning as above, just good) {dlb}
spell_difficulty(int which_spell)180 int spell_difficulty(int which_spell)
181 {
182 return (seekspell(which_spell)->level);
183 }
184
spell_levels_required(int which_spell)185 int spell_levels_required( int which_spell )
186 {
187 int levels = spell_difficulty( which_spell );
188
189 if (which_spell == SPELL_DELAYED_FIREBALL
190 && player_has_spell( SPELL_FIREBALL ))
191 {
192 levels -= spell_difficulty( SPELL_FIREBALL );
193 }
194 else if (which_spell == SPELL_FIREBALL
195 && player_has_spell( SPELL_DELAYED_FIREBALL ))
196 {
197 levels = 0;
198 }
199
200 return (levels);
201 }
202
spell_typematch(int which_spell,unsigned int which_discipline)203 bool spell_typematch(int which_spell, unsigned int which_discipline)
204 {
205 return (seekspell(which_spell)->disciplines & which_discipline);
206 }
207
208 //jmf: next two for simple bit handling
spell_type(int spell)209 unsigned int spell_type(int spell)
210 {
211 return (seekspell(spell)->disciplines);
212 }
213
count_bits(unsigned int bits)214 int count_bits(unsigned int bits)
215 {
216 unsigned int n;
217 int c = 0;
218
219 for (n = 1; n < INT_MAX; n <<= 1)
220 {
221 if (n & bits)
222 c++;
223 }
224
225 return (c);
226 }
227
228 // this will probably be used often, so rather than use malloc/free
229 // (which may lead to memory fragmentation) I'll just use a static
230 // array of characters -- if/when the String changeover takes place,
231 // this will all shift, no doubt {dlb}
232 /*
233 const char *spell_title( int which_spell )
234 {
235 static char this_title[41] = ""; // this is generous, to say the least {dlb}
236 strncpy(this_title, seekspell(which_spell)->title, 41);
237 // truncation better than overrun {dlb}
238 return ( this_title );
239 } // end spell_title()
240 */
241
spell_title(int spell)242 const char *spell_title(int spell) //jmf: ah the joys of driving ms. data
243 {
244 return (seekspell(spell)->title);
245 }
246
247
248 // FUNCTION APPLICATORS: Idea from Juho Snellman <jsnell@lyseo.edu.ouka.fi>
249 // on the Roguelike News pages, Development section.
250 // <URL:http://www.skoardy.demon.co.uk/rlnews/>
251 // Here are some function applicators: sort of like brain-dead,
252 // home-grown iterators for the container "dungeon".
253
254 // Apply a function-pointer to all visible squares
255 // Returns summation of return values from passed in function.
apply_area_visible(int (* func)(int,int,int,int),int power)256 int apply_area_visible( int (*func) (int, int, int, int), int power )
257 {
258 int x, y;
259 int rv = 0;
260
261 //jmf: FIXME: randomly start from other quadrants, like raise_dead?
262 for (x = you.x_pos - 8; x <= you.x_pos + 8; x++)
263 {
264 for (y = you.y_pos - 8; y <= you.y_pos + 8; y++)
265 {
266 if (see_grid(x, y))
267 rv += func(x, y, power, 0);
268 }
269 }
270
271 return (rv);
272 } // end apply_area_visible()
273
274 // Applies the effect to all nine squares around/including the target.
275 // Returns summation of return values from passed in function.
apply_area_square(int (* func)(int,int,int,int),int cx,int cy,int power)276 int apply_area_square( int (*func) (int, int, int, int), int cx, int cy,
277 int power )
278 {
279 int x, y;
280 int rv = 0;
281
282 for (x = cx - 1; x <= cx + 1; x++)
283 {
284 for (y = cy - 1; y <= cy + 1; y++)
285 {
286 rv += func(x, y, power, 0);
287 }
288 }
289
290 return (rv);
291 } // end apply_area_square()
292
293
294 // Applies the effect to the eight squares beside the target.
295 // Returns summation of return values from passed in function.
apply_area_around_square(int (* func)(int,int,int,int),int targ_x,int targ_y,int power)296 int apply_area_around_square( int (*func) (int, int, int, int),
297 int targ_x, int targ_y, int power)
298 {
299 int x, y;
300 int rv = 0;
301
302 for (x = targ_x - 1; x <= targ_x + 1; x++)
303 {
304 for (y = targ_y - 1; y <= targ_y + 1; y++)
305 {
306 if (x == targ_x && y == targ_y)
307 continue;
308 else
309 rv += func(x, y, power, 0);
310 }
311 }
312 return (rv);
313 } // end apply_area_around_square()
314
315 // Effect up to max_targs monsters around a point, chosen randomly
316 // Return varies with the function called; return values will be added up.
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)317 int apply_random_around_square( int (*func) (int, int, int, int),
318 int targ_x, int targ_y,
319 bool hole_in_middle, int power, int max_targs )
320 {
321 int rv = 0;
322
323 if (max_targs <= 0)
324 return 0;
325
326 if (max_targs >= 9 && !hole_in_middle)
327 {
328 return (apply_area_square( func, targ_x, targ_y, power ));
329 }
330
331 if (max_targs >= 8 && hole_in_middle)
332 {
333 return (apply_area_around_square( func, targ_x, targ_y, power ));
334 }
335
336 FixedVector< coord_def, 8 > targs;
337 int count = 0;
338
339 for (int x = targ_x - 1; x <= targ_x + 1; x++)
340 {
341 for (int y = targ_y - 1; y <= targ_y + 1; y++)
342 {
343 if (hole_in_middle && (x == targ_x && y == targ_y))
344 continue;
345
346 if (mgrd[x][y] == NON_MONSTER
347 && !(x == you.x_pos && y == you.y_pos))
348 {
349 continue;
350 }
351
352 // Found target
353 count++;
354
355 // Slight differece here over the basic algorithm...
356 //
357 // For cases where the number of choices <= max_targs it's
358 // obvious (all available choices will be selected).
359 //
360 // For choices > max_targs, here's a brief proof:
361 //
362 // Let m = max_targs, k = choices - max_targs, k > 0.
363 //
364 // Proof, by induction (over k):
365 //
366 // 1) Show n = m + 1 (k = 1) gives uniform distribution,
367 // P(new one not chosen) = 1 / (m + 1).
368 // m 1 1
369 // P(specific previous one replaced) = --- * --- = ---
370 // m+1 m m+1
371 //
372 // So the probablity is uniform (ie. any element has
373 // a 1/(m+1) chance of being in the unchosen slot).
374 //
375 // 2) Assume the distribution is uniform at n = m+k.
376 // (ie. the probablity that any of the found elements
377 // was chosen = m / (m+k) (the slots are symetric,
378 // so it's the sum of the probabilities of being in
379 // any of them)).
380 //
381 // 3) Show n = m + k + 1 gives a uniform distribution.
382 // P(new one chosen) = m / (m + k + 1)
383 // P(any specific previous choice remaining chosen)
384 // = [1 - P(swaped into m+k+1 position)] * P(prev. chosen)
385 // m 1 m
386 // = [ 1 - ----- * --- ] * ---
387 // m+k+1 m m+k
388 //
389 // m+k m m
390 // = ----- * --- = -----
391 // m+k+1 m+k m+k+1
392 //
393 // Therefore, it's uniform for n = m + k + 1. QED
394 //
395 // The important thing to note in calculating the last
396 // probability is that the chosen elements have already
397 // passed tests which verify that they *don't* belong
398 // in slots m+1...m+k, so the only positions an already
399 // chosen element can end up in are it's original
400 // position (in one of the chosen slots), or in the
401 // new slot.
402 //
403 // The new item can, of course, be placed in any slot,
404 // swapping the value there into the new slot... we
405 // just don't care about the non-chosen slots enough
406 // to store them, so it might look like the item
407 // automatically takes the new slot when not chosen
408 // (although, by symetry all the non-chosen slots are
409 // the same... and similarly, by symetry, all chosen
410 // slots are the same).
411 //
412 // Yes, that's a long comment for a short piece of
413 // code, but I want people to have an understanding
414 // of why this works (or at least make them wary about
415 // changing it without proof and breaking this code). -- bwr
416
417 // Accept the first max_targs choices, then when
418 // new choices come up, replace one of the choices
419 // at random, max_targs/count of the time (the rest
420 // of the time it replaces an element in an unchosen
421 // slot -- but we don't care about them).
422 if (count <= max_targs)
423 {
424 targs[ count - 1 ].x = x;
425 targs[ count - 1 ].y = y;
426 }
427 else if (random2( count ) < max_targs)
428 {
429 const int pick = random2( max_targs );
430 targs[ pick ].x = x;
431 targs[ pick ].y = y;
432 }
433 }
434 }
435
436 const int targs_found = (count < max_targs) ? count : max_targs;
437
438 if (targs_found)
439 {
440 // Used to divide the power up among the targets here, but
441 // it's probably better to allow the full power through and
442 // balance the called function. -- bwr
443 for (int i = 0; i < targs_found; i++)
444 {
445 ASSERT( targs[i].x && targs[i].y );
446 rv += func( targs[i].x, targs[i].y, power, 0 );
447 }
448 }
449
450 return (rv);
451 } // end apply_random_around_square()
452
453 // apply func to one square of player's choice beside the player
apply_one_neighbouring_square(int (* func)(int,int,int,int),int power)454 int apply_one_neighbouring_square(int (*func) (int, int, int, int), int power)
455 {
456 struct dist bmove;
457
458 mpr("Which direction? [ESC to cancel]", MSGCH_PROMPT);
459 direction( bmove, DIR_DIR, TARG_ENEMY );
460
461 if (!bmove.isValid)
462 {
463 canned_msg(MSG_SPELL_FIZZLES);
464 return (0);
465 }
466
467 int rv = func(you.x_pos + bmove.dx, you.y_pos + bmove.dy, power, 1);
468
469 if (rv == 0)
470 canned_msg(MSG_NOTHING_HAPPENS);
471
472 return (rv);
473 } // end apply_one_neighbouring_square()
474
apply_area_within_radius(int (* func)(int,int,int,int),int x,int y,int pow,int radius,int ctype)475 int apply_area_within_radius( int (*func) (int, int, int, int),
476 int x, int y, int pow, int radius, int ctype )
477 {
478 int ix, iy;
479 int sq_radius = radius * radius;
480 int sx, sy, ex, ey; // start and end x, y - bounds checked
481 int rv = 0;
482
483 // begin x,y
484 sx = x - radius;
485 sy = y - radius;
486 if (sx < 0) sx = 0;
487 if (sy < 0) sy = 0;
488
489 // end x,y
490 ex = x + radius;
491 ey = y + radius;
492 if (ex > GXM) ex = GXM;
493 if (ey > GYM) ey = GYM;
494
495 for (ix = sx; ix < ex; ix++)
496 {
497 for (iy = sy; iy < ey; iy++)
498 {
499 if (distance(x, y, ix, iy) <= sq_radius)
500 rv += func(ix, iy, pow, ctype);
501 }
502 }
503
504 return (rv);
505 } // end apply_area_within_radius()
506
507 // apply_area_cloud:
508 // Try to make a realistic cloud by expanding from a point, filling empty
509 // floor tiles until we run out of material (passed in as number).
510 // We really need some sort of a queue structure, since ideally I'd like
511 // to do a (shallow) breadth-first-search of the dungeon floor.
512 // This ought to work okay for small clouds.
apply_area_cloud(int (* func)(int,int,int,int),int x,int y,int pow,int number,int ctype)513 void apply_area_cloud( int (*func) (int, int, int, int), int x, int y,
514 int pow, int number, int ctype )
515 {
516 int spread, clouds_left = number;
517 int good_squares = 0, neighbours[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
518 int dx = 1, dy = 1;
519 bool x_first;
520
521 if (clouds_left && cloud_helper(func, x, y, pow, ctype))
522 clouds_left--;
523
524 if (!clouds_left)
525 return;
526
527 if (coinflip())
528 dx *= -1;
529 if (coinflip())
530 dy *= -1;
531
532 x_first = coinflip();
533
534 if (x_first)
535 {
536 if (clouds_left && cloud_helper(func, x + dx, y, pow, ctype))
537 {
538 clouds_left--;
539 good_squares++;
540 neighbours[0]++;
541 }
542
543 if (clouds_left && cloud_helper(func, x - dx, y, pow, ctype))
544 {
545 clouds_left--;
546 good_squares++;
547 neighbours[1]++;
548 }
549
550 if (clouds_left && cloud_helper(func, x, y + dy, pow, ctype))
551 {
552 clouds_left--;
553 good_squares++;
554 neighbours[2]++;
555 }
556
557 if (clouds_left && cloud_helper(func, x, y - dy, pow, ctype))
558 {
559 clouds_left--;
560 good_squares++;
561 neighbours[3]++;
562 }
563 }
564 else
565 {
566 if (clouds_left && cloud_helper(func, x, y + dy, pow, ctype))
567 {
568 clouds_left--;
569 good_squares++;
570 neighbours[2]++;
571 }
572
573 if (clouds_left && cloud_helper(func, x, y - dy, pow, ctype))
574 {
575 clouds_left--;
576 good_squares++;
577 neighbours[3]++;
578 }
579
580 if (clouds_left && cloud_helper(func, x + dx, y, pow, ctype))
581 {
582 clouds_left--;
583 good_squares++;
584 neighbours[0]++;
585 }
586
587 if (clouds_left && cloud_helper(func, x - dx, y, pow, ctype))
588 {
589 clouds_left--;
590 good_squares++;
591 neighbours[1]++;
592 }
593 }
594
595 // now diagonals; we could randomize dx & dy again here
596 if (clouds_left && cloud_helper(func, x + dx, y + dy, pow, ctype))
597 {
598 clouds_left--;
599 good_squares++;
600 neighbours[4]++;
601 }
602
603 if (clouds_left && cloud_helper(func, x - dx, y + dy, pow, ctype))
604 {
605 clouds_left--;
606 good_squares++;
607 neighbours[5]++;
608 }
609
610 if (clouds_left && cloud_helper(func, x + dx, y - dy, pow, ctype))
611 {
612 clouds_left--;
613 good_squares++;
614 neighbours[6]++;
615 }
616
617 if (clouds_left && cloud_helper(func, x - dx, y - dy, pow, ctype))
618 {
619 clouds_left--;
620 good_squares++;
621 neighbours[7]++;
622 }
623
624 if (!(clouds_left && good_squares))
625 return;
626
627 for (int i = 0; i < 8 && clouds_left; i++)
628 {
629 if (neighbours[i] == 0)
630 continue;
631
632 spread = clouds_left / good_squares;
633 clouds_left -= spread;
634 good_squares--;
635
636 switch (i)
637 {
638 case 0:
639 apply_area_cloud(func, x + dx, y, pow, spread, ctype);
640 break;
641 case 1:
642 apply_area_cloud(func, x - dx, y, pow, spread, ctype);
643 break;
644 case 2:
645 apply_area_cloud(func, x, y + dy, pow, spread, ctype);
646 break;
647 case 3:
648 apply_area_cloud(func, x, y - dy, pow, spread, ctype);
649 break;
650 case 4:
651 apply_area_cloud(func, x + dx, y + dy, pow, spread, ctype);
652 break;
653 case 5:
654 apply_area_cloud(func, x - dx, y + dy, pow, spread, ctype);
655 break;
656 case 6:
657 apply_area_cloud(func, x + dx, y - dy, pow, spread, ctype);
658 break;
659 case 7:
660 apply_area_cloud(func, x - dx, y - dy, pow, spread, ctype);
661 break;
662 }
663 }
664 } // end apply_area_cloud()
665
spell_direction(struct dist & spelld,struct bolt & pbolt,int restrict,int mode)666 char spell_direction( struct dist &spelld, struct bolt &pbolt,
667 int restrict, int mode )
668 {
669 if (restrict == DIR_TARGET)
670 mpr( "Choose a target (+/- for next/prev monster)", MSGCH_PROMPT );
671 else
672 mpr( STD_DIRECTION_PROMPT, MSGCH_PROMPT );
673
674 message_current_target();
675
676 direction( spelld, restrict, mode );
677
678 if (!spelld.isValid)
679 {
680 // check for user cancel
681 canned_msg(MSG_SPELL_FIZZLES);
682 return -1;
683 }
684
685 pbolt.target_x = spelld.tx;
686 pbolt.target_y = spelld.ty;
687 pbolt.source_x = you.x_pos;
688 pbolt.source_y = you.y_pos;
689
690 return 1;
691 } // end spell_direction()
692
spelltype_name(unsigned int which_spelltype)693 const char *spelltype_name(unsigned int which_spelltype)
694 {
695 static char bug_string[80];
696
697 switch (which_spelltype)
698 {
699 case SPTYP_CONJURATION:
700 return ("Conjuration");
701 case SPTYP_ENCHANTMENT:
702 return ("Enchantment");
703 case SPTYP_FIRE:
704 return ("Fire");
705 case SPTYP_ICE:
706 return ("Ice");
707 case SPTYP_TRANSMIGRATION:
708 return ("Transmigration");
709 case SPTYP_NECROMANCY:
710 return ("Necromancy");
711 case SPTYP_HOLY:
712 return ("Holy");
713 case SPTYP_SUMMONING:
714 return ("Summoning");
715 case SPTYP_DIVINATION:
716 return ("Divination");
717 case SPTYP_TRANSLOCATION:
718 return ("Translocation");
719 case SPTYP_POISON:
720 return ("Poison");
721 case SPTYP_EARTH:
722 return ("Earth");
723 case SPTYP_AIR:
724 return ("Air");
725 default:
726 snprintf( bug_string, sizeof(bug_string),
727 "invalid(%d)", which_spelltype );
728
729 return (bug_string);
730 }
731 } // end spelltype_name()
732
spell_type2skill(unsigned int spelltype)733 int spell_type2skill(unsigned int spelltype)
734 {
735 char buffer[80];
736
737 switch (spelltype)
738 {
739 case SPTYP_CONJURATION: return (SK_CONJURATIONS);
740 case SPTYP_ENCHANTMENT: return (SK_ENCHANTMENTS);
741 case SPTYP_FIRE: return (SK_FIRE_MAGIC);
742 case SPTYP_ICE: return (SK_ICE_MAGIC);
743 case SPTYP_TRANSMIGRATION: return (SK_TRANSMIGRATION);
744 case SPTYP_NECROMANCY: return (SK_NECROMANCY);
745 case SPTYP_SUMMONING: return (SK_SUMMONINGS);
746 case SPTYP_DIVINATION: return (SK_DIVINATIONS);
747 case SPTYP_TRANSLOCATION: return (SK_TRANSLOCATIONS);
748 case SPTYP_POISON: return (SK_POISON_MAGIC);
749 case SPTYP_EARTH: return (SK_EARTH_MAGIC);
750 case SPTYP_AIR: return (SK_AIR_MAGIC);
751
752 default:
753 case SPTYP_HOLY:
754 snprintf( buffer, sizeof(buffer),
755 "spell_type2skill: called with spelltype %d", spelltype );
756
757 mpr( buffer );
758 return (-1);
759 }
760 } // end spell_type2skill()
761
762 /*
763 **************************************************
764 * *
765 * END PUBLIC FUNCTIONS *
766 * *
767 **************************************************
768 */
769
770 //jmf: simplified; moved init code to top function, init_playerspells()
seekspell(int spell)771 static struct playerspell *seekspell(int spell)
772 {
773 return (&spelldata[plyrspell_list[spell]]);
774 }
775
cloud_helper(int (* func)(int,int,int,int),int x,int y,int pow,int ctype)776 static bool cloud_helper( int (*func) (int, int, int, int), int x, int y,
777 int pow, int ctype )
778 {
779 if (grd[x][y] > DNGN_LAST_SOLID_TILE && env.cgrid[x][y] == EMPTY_CLOUD)
780 {
781 func(x, y, pow, ctype);
782 return true;
783 }
784
785 return false;
786 }
787