Annotation of brogue-ce/src/brogue/Movement.c, Revision 1.1.1.1
1.1 rubenllo 1: /*
2: * Movement.c
3: * Brogue
4: *
5: * Created by Brian Walker on 1/10/09.
6: * Copyright 2012. All rights reserved.
7: *
8: * This file is part of Brogue.
9: *
10: * This program is free software: you can redistribute it and/or modify
11: * it under the terms of the GNU Affero General Public License as
12: * published by the Free Software Foundation, either version 3 of the
13: * License, or (at your option) any later version.
14: *
15: * This program is distributed in the hope that it will be useful,
16: * but WITHOUT ANY WARRANTY; without even the implied warranty of
17: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18: * GNU Affero General Public License for more details.
19: *
20: * You should have received a copy of the GNU Affero General Public License
21: * along with this program. If not, see <http://www.gnu.org/licenses/>.
22: */
23:
24: #include "Rogue.h"
25: #include "IncludeGlobals.h"
26:
27: void playerRuns(short direction) {
28: short newX, newY, dir;
29: boolean cardinalPassability[4];
30:
31: rogue.disturbed = (player.status[STATUS_CONFUSED] ? true : false);
32:
33: for (dir = 0; dir < 4; dir++) {
34: newX = player.xLoc + nbDirs[dir][0];
35: newY = player.yLoc + nbDirs[dir][1];
36: cardinalPassability[dir] = monsterAvoids(&player, newX, newY);
37: }
38:
39: while (!rogue.disturbed) {
40: if (!playerMoves(direction)) {
41: rogue.disturbed = true;
42: break;
43: }
44:
45: newX = player.xLoc + nbDirs[direction][0];
46: newY = player.yLoc + nbDirs[direction][1];
47: if (!coordinatesAreInMap(newX, newY)
48: || monsterAvoids(&player, newX, newY)) {
49:
50: rogue.disturbed = true;
51: }
52: if (isDisturbed(player.xLoc, player.yLoc)) {
53: rogue.disturbed = true;
54: } else if (direction < 4) {
55: for (dir = 0; dir < 4; dir++) {
56: newX = player.xLoc + nbDirs[dir][0];
57: newY = player.yLoc + nbDirs[dir][1];
58: if (cardinalPassability[dir] != monsterAvoids(&player, newX, newY)
59: && !(nbDirs[dir][0] + nbDirs[direction][0] == 0 &&
60: nbDirs[dir][1] + nbDirs[direction][1] == 0)) {
61: // dir is not the x-opposite or y-opposite of direction
62: rogue.disturbed = true;
63: }
64: }
65: }
66: }
67: updateFlavorText();
68: }
69:
70: enum dungeonLayers highestPriorityLayer(short x, short y, boolean skipGas) {
71: short bestPriority = 10000;
72: enum dungeonLayers tt, best = 0;
73:
74: for (tt = 0; tt < NUMBER_TERRAIN_LAYERS; tt++) {
75: if (tt == GAS && skipGas) {
76: continue;
77: }
78: if (pmap[x][y].layers[tt] && tileCatalog[pmap[x][y].layers[tt]].drawPriority < bestPriority) {
79: bestPriority = tileCatalog[pmap[x][y].layers[tt]].drawPriority;
80: best = tt;
81: }
82: }
83: return best;
84: }
85:
86: enum dungeonLayers layerWithTMFlag(short x, short y, unsigned long flag) {
87: enum dungeonLayers layer;
88:
89: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
90: if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & flag) {
91: return layer;
92: }
93: }
94: return NO_LAYER;
95: }
96:
97: enum dungeonLayers layerWithFlag(short x, short y, unsigned long flag) {
98: enum dungeonLayers layer;
99:
100: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
101: if (tileCatalog[pmap[x][y].layers[layer]].flags & flag) {
102: return layer;
103: }
104: }
105: return NO_LAYER;
106: }
107:
108: // Retrieves a pointer to the flavor text of the highest-priority terrain at the given location
109: char *tileFlavor(short x, short y) {
110: return tileCatalog[pmap[x][y].layers[highestPriorityLayer(x, y, false)]].flavorText;
111: }
112:
113: // Retrieves a pointer to the description text of the highest-priority terrain at the given location
114: char *tileText(short x, short y) {
115: return tileCatalog[pmap[x][y].layers[highestPriorityLayer(x, y, false)]].description;
116: }
117:
118: void describedItemBasedOnParameters(short theCategory, short theKind, short theQuantity, short theOriginDepth, char *buf) {
119: item *tempItem = initializeItem();
120: tempItem->category = theCategory;
121: tempItem->kind = theKind;
122: tempItem->quantity = theQuantity;
123: tempItem->originDepth = theOriginDepth;
124: itemName(tempItem, buf, false, true, NULL);
125: free(tempItem);
126: return;
127: }
128:
129: // Describes the item in question either by naming it if the player has already seen its name,
130: // or by tersely identifying its category otherwise.
131: void describedItemName(item *theItem, char *buf) {
132: if (rogue.playbackOmniscience || (!player.status[STATUS_HALLUCINATING])) {
133: itemName(theItem, buf, (theItem->category & (WEAPON | ARMOR) ? false : true), true, NULL);
134: } else {
135: describeHallucinatedItem(buf);
136: }
137: }
138:
139: void describeLocation(char *buf, short x, short y) {
140: creature *monst;
141: item *theItem, *magicItem;
142: boolean standsInTerrain;
143: boolean subjectMoving;
144: boolean prepositionLocked = false;
145: boolean monsterDormant;
146:
147: char subject[COLS * 3];
148: char verb[COLS * 3];
149: char preposition[COLS * 3];
150: char object[COLS * 3];
151: char adjective[COLS * 3];
152:
153: assureCosmeticRNG;
154:
155: if (x == player.xLoc && y == player.yLoc) {
156: if (player.status[STATUS_LEVITATING]) {
157: sprintf(buf, "you are hovering above %s.", tileText(x, y));
158: } else {
159: strcpy(buf, tileFlavor(x, y));
160: }
161: restoreRNG;
162: return;
163: }
164:
165: monst = NULL;
166: standsInTerrain = ((tileCatalog[pmap[x][y].layers[highestPriorityLayer(x, y, false)]].mechFlags & TM_STAND_IN_TILE) ? true : false);
167: theItem = itemAtLoc(x, y);
168: monsterDormant = false;
169: if (pmap[x][y].flags & HAS_MONSTER) {
170: monst = monsterAtLoc(x, y);
171: } else if (pmap[x][y].flags & HAS_DORMANT_MONSTER) {
172: monst = dormantMonsterAtLoc(x, y);
173: monsterDormant = true;
174: }
175:
176: // detecting magical items
177: magicItem = NULL;
178: if (theItem && !playerCanSeeOrSense(x, y)
179: && (theItem->flags & ITEM_MAGIC_DETECTED)
180: && itemMagicPolarity(theItem)) {
181: magicItem = theItem;
182: } else if (monst && !canSeeMonster(monst)
183: && monst->carriedItem
184: && (monst->carriedItem->flags & ITEM_MAGIC_DETECTED)
185: && itemMagicPolarity(monst->carriedItem)) {
186: magicItem = monst->carriedItem;
187: }
188: if (magicItem && !(pmap[x][y].flags & DISCOVERED)) {
189: switch (itemMagicPolarity(magicItem)) {
190: case 1:
191: strcpy(object, magicItem->category == AMULET ? "the Amulet of Yendor" : "benevolent magic");
192: break;
193: case -1:
194: strcpy(object, "malevolent magic");
195: break;
196: default:
197: strcpy(object, "mysterious magic");
198: break;
199: }
200: sprintf(buf, "you can detect the aura of %s here.", object);
201: restoreRNG;
202: return;
203: }
204:
205: // telepathy
206: if (monst
207: && !canSeeMonster(monst)
208: && monsterRevealed(monst)) {
209:
210: strcpy(adjective, (((!player.status[STATUS_HALLUCINATING] || rogue.playbackOmniscience) && !monst->info.isLarge)
211: || (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience && rand_range(0, 1)) ? "small" : "large"));
212: if (pmap[x][y].flags & DISCOVERED) {
213: strcpy(object, tileText(x, y));
214: if (monst->bookkeepingFlags & MB_SUBMERGED) {
215: strcpy(preposition, "under ");
216: } else if (monsterDormant) {
217: strcpy(preposition, "coming from within ");
218: } else if (standsInTerrain) {
219: strcpy(preposition, "in ");
220: } else {
221: strcpy(preposition, "over ");
222: }
223: } else {
224: strcpy(object, "here");
225: strcpy(preposition, "");
226: }
227:
228: sprintf(buf, "you can sense a %s psychic emanation %s%s.", adjective, preposition, object);
229: restoreRNG;
230: return;
231: }
232:
233: if (monst && !canSeeMonster(monst) && !rogue.playbackOmniscience) {
234: // Monster is not visible.
235: monst = NULL;
236: }
237:
238: if (!playerCanSeeOrSense(x, y)) {
239: if (pmap[x][y].flags & DISCOVERED) { // memory
240: if (pmap[x][y].rememberedItemCategory) {
241: if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) {
242: describeHallucinatedItem(object);
243: } else {
244: describedItemBasedOnParameters(pmap[x][y].rememberedItemCategory, pmap[x][y].rememberedItemKind,
245: pmap[x][y].rememberedItemQuantity, pmap[x][y].rememberedItemOriginDepth, object);
246: }
247: } else {
248: strcpy(object, tileCatalog[pmap[x][y].rememberedTerrain].description);
249: }
250: sprintf(buf, "you remember seeing %s here.", object);
251: restoreRNG;
252: return;
253: } else if (pmap[x][y].flags & MAGIC_MAPPED) { // magic mapped
254: sprintf(buf, "you expect %s to be here.", tileCatalog[pmap[x][y].rememberedTerrain].description);
255: restoreRNG;
256: return;
257: }
258: strcpy(buf, "");
259: restoreRNG;
260: return;
261: }
262:
263: if (monst) {
264:
265: monsterName(subject, monst, true);
266:
267: if (pmap[x][y].layers[GAS] && monst->status[STATUS_INVISIBLE]) { // phantoms in gas
268: sprintf(buf, "you can perceive the faint outline of %s in %s.", subject, tileCatalog[pmap[x][y].layers[GAS]].description);
269: restoreRNG;
270: return;
271: }
272:
273: subjectMoving = (monst->turnsSpentStationary == 0
274: && !(monst->info.flags & (MONST_GETS_TURN_ON_ACTIVATION | MONST_IMMOBILE))
275: && monst->creatureState != MONSTER_SLEEPING
276: && !(monst->bookkeepingFlags & (MB_SEIZED | MB_CAPTIVE)));
277: if ((monst->info.flags & MONST_ATTACKABLE_THRU_WALLS)
278: && cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) {
279: strcpy(verb, "is embedded");
280: } else if (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) {
281: strcpy(verb, "is trapped");
282: subjectMoving = false;
283: } else if (monst->bookkeepingFlags & MB_CAPTIVE) {
284: strcpy(verb, "is shackled in place");
285: subjectMoving = false;
286: } else if (monst->status[STATUS_PARALYZED]) {
287: strcpy(verb, "is frozen in place");
288: subjectMoving = false;
289: } else if (monst->status[STATUS_STUCK]) {
290: strcpy(verb, "is entangled");
291: subjectMoving = false;
292: } else if (monst->status[STATUS_LEVITATING]) {
293: strcpy(verb, (subjectMoving ? "is flying" : "is hovering"));
294: strcpy(preposition, "over");
295: prepositionLocked = true;
296: } else if (monsterCanSubmergeNow(monst)) {
297: strcpy(verb, (subjectMoving ? "is gliding" : "is drifting"));
298: } else if (cellHasTerrainFlag(x, y, T_MOVES_ITEMS) && !(monst->info.flags & MONST_SUBMERGES)) {
299: strcpy(verb, (subjectMoving ? "is swimming" : "is struggling"));
300: } else if (cellHasTerrainFlag(x, y, T_AUTO_DESCENT)) {
301: strcpy(verb, "is suspended in mid-air");
302: strcpy(preposition, "over");
303: prepositionLocked = true;
304: subjectMoving = false;
305: } else if (monst->status[STATUS_CONFUSED]) {
306: strcpy(verb, "is staggering");
307: } else if ((monst->info.flags & MONST_RESTRICTED_TO_LIQUID)
308: && !cellHasTMFlag(monst->xLoc, monst->yLoc, TM_ALLOWS_SUBMERGING)) {
309: strcpy(verb, "is lying");
310: subjectMoving = false;
311: } else if (monst->info.flags & MONST_IMMOBILE) {
312: strcpy(verb, "is resting");
313: } else {
314: switch (monst->creatureState) {
315: case MONSTER_SLEEPING:
316: strcpy(verb, "is sleeping");
317: subjectMoving = false;
318: break;
319: case MONSTER_WANDERING:
320: strcpy(verb, subjectMoving ? "is wandering" : "is standing");
321: break;
322: case MONSTER_FLEEING:
323: strcpy(verb, subjectMoving ? "is fleeing" : "is standing");
324: break;
325: case MONSTER_TRACKING_SCENT:
326: strcpy(verb, subjectMoving ? "is charging" : "is standing");
327: break;
328: case MONSTER_ALLY:
329: strcpy(verb, subjectMoving ? "is following you" : "is standing");
330: break;
331: default:
332: strcpy(verb, "is standing");
333: break;
334: }
335: }
336: if (monst->status[STATUS_BURNING] && !(monst->info.flags & MONST_FIERY)) {
337: strcat(verb, ", burning,");
338: }
339:
340: if (theItem) {
341: strcpy(preposition, "over");
342: describedItemName(theItem, object);
343: } else {
344: if (!prepositionLocked) {
345: strcpy(preposition, subjectMoving ? (standsInTerrain ? "through" : "across")
346: : (standsInTerrain ? "in" : "on"));
347: }
348:
349: strcpy(object, tileText(x, y));
350:
351: }
352: } else { // no monster
353: strcpy(object, tileText(x, y));
354: if (theItem) {
355: describedItemName(theItem, subject);
356: subjectMoving = cellHasTerrainFlag(x, y, T_MOVES_ITEMS);
357: if (player.status[STATUS_HALLUCINATING] && !rogue.playbackOmniscience) {
358: strcpy(verb, "is");
359: } else {
360: strcpy(verb, (theItem->quantity > 1 || (theItem->category & GOLD)) ? "are" : "is");
361: }
362: if (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) {
363: strcat(verb, " enclosed");
364: } else {
365: strcat(verb, subjectMoving ? " drifting" : " lying");
366: }
367: strcpy(preposition, standsInTerrain ? (subjectMoving ? "through" : "in")
368: : (subjectMoving ? "across" : "on"));
369:
370:
371: } else { // no item
372: sprintf(buf, "you %s %s.", (playerCanDirectlySee(x, y) ? "see" : "sense"), object);
373: restoreRNG;
374: return;
375: }
376: }
377:
378: sprintf(buf, "%s %s %s %s.", subject, verb, preposition, object);
379: restoreRNG;
380: }
381:
382: void printLocationDescription(short x, short y) {
383: char buf[DCOLS*3];
384: describeLocation(buf, x, y);
385: flavorMessage(buf);
386: }
387:
388: void useKeyAt(item *theItem, short x, short y) {
389: short layer, i;
390: creature *monst;
391: char buf[COLS], buf2[COLS], terrainName[COLS], preposition[10];
392: boolean disposable;
393:
394: strcpy(terrainName, "unknown terrain"); // redundant failsafe
395: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
396: if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_PROMOTES_WITH_KEY) {
397: if (tileCatalog[pmap[x][y].layers[layer]].description[0] == 'a'
398: && tileCatalog[pmap[x][y].layers[layer]].description[1] == ' ') {
399: sprintf(terrainName, "the %s", &(tileCatalog[pmap[x][y].layers[layer]].description[2]));
400: } else {
401: strcpy(terrainName, tileCatalog[pmap[x][y].layers[layer]].description);
402: }
403: if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_STAND_IN_TILE) {
404: strcpy(preposition, "in");
405: } else {
406: strcpy(preposition, "on");
407: }
408: promoteTile(x, y, layer, false);
409: }
410: }
411:
412: disposable = false;
413: for (i=0; i < KEY_ID_MAXIMUM && (theItem->keyLoc[i].x || theItem->keyLoc[i].machine); i++) {
414: if (theItem->keyLoc[i].x == x && theItem->keyLoc[i].y == y && theItem->keyLoc[i].disposableHere) {
415: disposable = true;
416: } else if (theItem->keyLoc[i].machine == pmap[x][y].machineNumber && theItem->keyLoc[i].disposableHere) {
417: disposable = true;
418: }
419: }
420:
421: if (disposable) {
422: if (removeItemFromChain(theItem, packItems)) {
423: itemName(theItem, buf2, true, false, NULL);
424: sprintf(buf, "you use your %s %s %s.",
425: buf2,
426: preposition,
427: terrainName);
428: messageWithColor(buf, &itemMessageColor, false);
429: deleteItem(theItem);
430: } else if (removeItemFromChain(theItem, floorItems)) {
431: deleteItem(theItem);
432: pmap[x][y].flags &= ~HAS_ITEM;
433: } else if (pmap[x][y].flags & HAS_MONSTER) {
434: monst = monsterAtLoc(x, y);
435: if (monst->carriedItem && monst->carriedItem == theItem) {
436: monst->carriedItem = NULL;
437: deleteItem(theItem);
438: }
439: }
440: }
441: }
442:
443: short randValidDirectionFrom(creature *monst, short x, short y, boolean respectAvoidancePreferences) {
444: short i, newX, newY, validDirections[8], count = 0;
445:
446: brogueAssert(rogue.RNG == RNG_SUBSTANTIVE);
447: for (i=0; i<8; i++) {
448: newX = x + nbDirs[i][0];
449: newY = y + nbDirs[i][1];
450: if (coordinatesAreInMap(newX, newY)
451: && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)
452: && !diagonalBlocked(x, y, newX, newY, false)
453: && (!respectAvoidancePreferences
454: || (!monsterAvoids(monst, newX, newY))
455: || ((pmap[newX][newY].flags & HAS_PLAYER) && monst->creatureState != MONSTER_ALLY))) {
456: validDirections[count++] = i;
457: }
458: }
459: if (count == 0) {
460: // Rare, and important in this case that the function returns BEFORE a random roll is made to avoid OOS.
461: return NO_DIRECTION;
462: }
463: return validDirections[rand_range(0, count - 1)];
464: }
465:
466: void vomit(creature *monst) {
467: char buf[COLS], monstName[COLS];
468: spawnDungeonFeature(monst->xLoc, monst->yLoc, &dungeonFeatureCatalog[DF_VOMIT], true, false);
469:
470: if (canDirectlySeeMonster(monst)
471: && !rogue.automationActive) {
472:
473: monsterName(monstName, monst, true);
474: sprintf(buf, "%s vomit%s profusely", monstName, (monst == &player ? "" : "s"));
475: combatMessage(buf, NULL);
476: }
477: }
478:
479: void moveEntrancedMonsters(enum directions dir) {
480: creature *monst, *nextMonst;
481:
482: dir = oppositeDirection(dir);
483:
484: if (rogue.patchVersion >= 3) {
485: for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
486: monst->bookkeepingFlags &= ~MB_HAS_ENTRANCED_MOVED;
487: }
488:
489: for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
490: if (!(monst->bookkeepingFlags & MB_HAS_ENTRANCED_MOVED)
491: && monst->status[STATUS_ENTRANCED]
492: && !monst->status[STATUS_STUCK]
493: && !monst->status[STATUS_PARALYZED]
494: && !(monst->bookkeepingFlags & MB_CAPTIVE)) {
495:
496: moveMonster(monst, nbDirs[dir][0], nbDirs[dir][1]);
497: monst->bookkeepingFlags |= MB_HAS_ENTRANCED_MOVED;
498: monst = monsters; // loop through from the beginning to be safe
499: }
500: }
501:
502: } else {
503: for (monst = monsters->nextCreature; monst != NULL; monst = nextMonst) {
504: nextMonst = monst->nextCreature;
505: if (monst->status[STATUS_ENTRANCED]
506: && !monst->status[STATUS_STUCK]
507: && !monst->status[STATUS_PARALYZED]
508: && !(monst->bookkeepingFlags & MB_CAPTIVE)) {
509:
510: moveMonster(monst, nbDirs[dir][0], nbDirs[dir][1]);
511: }
512: }
513: }
514:
515: }
516:
517: void becomeAllyWith(creature *monst) {
518: demoteMonsterFromLeadership(monst);
519: // Drop your item.
520: if (monst->carriedItem) {
521: makeMonsterDropItem(monst);
522: }
523: // If you're going to change into something, it should be friendly.
524: if (monst->carriedMonster) {
525: becomeAllyWith(monst->carriedMonster);
526: }
527: monst->creatureState = MONSTER_ALLY;
528: monst->bookkeepingFlags |= MB_FOLLOWER;
529: monst->leader = &player;
530: monst->bookkeepingFlags &= ~(MB_CAPTIVE | MB_SEIZED);
531: refreshDungeonCell(monst->xLoc, monst->yLoc);
532: }
533:
534: void freeCaptive(creature *monst) {
535: char buf[COLS * 3], monstName[COLS];
536:
537: becomeAllyWith(monst);
538: monsterName(monstName, monst, false);
539: sprintf(buf, "you free the grateful %s and gain a faithful ally.", monstName);
540: message(buf, false);
541: }
542:
543: boolean freeCaptivesEmbeddedAt(short x, short y) {
544: creature *monst;
545:
546: if (pmap[x][y].flags & HAS_MONSTER) {
547: // Free any captives trapped in the tunnelized terrain.
548: monst = monsterAtLoc(x, y);
549: if ((monst->bookkeepingFlags & MB_CAPTIVE)
550: && !(monst->info.flags & MONST_ATTACKABLE_THRU_WALLS)
551: && (cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY))) {
552: freeCaptive(monst);
553: return true;
554: }
555: }
556: return false;
557: }
558:
559: // Do we need confirmation so we don't accidently hit an acid mound?
560: boolean abortAttackAgainstAcidicTarget(creature *hitList[8]) {
561: short i;
562: char monstName[COLS], weaponName[COLS];
563: char buf[COLS*3];
564:
565: if (rogue.weapon
566: && !(rogue.weapon->flags & ITEM_PROTECTED)
567: && !player.status[STATUS_HALLUCINATING]
568: && !player.status[STATUS_CONFUSED]) {
569:
570: for (i=0; i<8; i++) {
571: if (hitList[i]
572: && (hitList[i]->info.flags & MONST_DEFEND_DEGRADE_WEAPON)
573: && canSeeMonster(hitList[i])
574: && (!(rogue.weapon->flags & ITEM_RUNIC)
575: || !(rogue.weapon->flags & ITEM_RUNIC_IDENTIFIED)
576: || rogue.weapon->enchant2 != W_SLAYING
577: || !monsterIsInClass(hitList[i], rogue.weapon->vorpalEnemy))) {
578:
579: monsterName(monstName, hitList[i], true);
580: itemName(rogue.weapon, weaponName, false, false, NULL);
581: sprintf(buf, "Degrade your %s by attacking %s?", weaponName, monstName);
582: if (confirm(buf, false)) {
583: return false; // Fire when ready!
584: } else {
585: return true; // Abort!
586: }
587: }
588: }
589: }
590: return false;
591: }
592:
593: // Returns true if a whip attack was launched.
594: // If "aborted" pointer is provided, sets it to true if it was aborted because
595: // the player opted not to attack an acid mound (in which case the whole turn
596: // should be aborted), as opposed to there being no valid whip attack available
597: // (in which case the player/monster should move instead).
598: boolean handleWhipAttacks(creature *attacker, enum directions dir, boolean *aborted) {
599: bolt theBolt;
600: creature *defender, *hitList[8] = {0};
601: short strikeLoc[2], originLoc[2], targetLoc[2];
602:
603: const char boltChar[DIRECTION_COUNT] = "||~~\\//\\";
604:
605: brogueAssert(dir > NO_DIRECTION && dir < DIRECTION_COUNT);
606:
607: if (attacker == &player) {
608: if (!rogue.weapon || !(rogue.weapon->flags & ITEM_ATTACKS_EXTEND)) {
609: return false;
610: }
611: } else if (!(attacker->info.abilityFlags & MA_ATTACKS_EXTEND)) {
612: return false;
613: }
614: originLoc[0] = attacker->xLoc;
615: originLoc[1] = attacker->yLoc;
616: targetLoc[0] = attacker->xLoc + nbDirs[dir][0];
617: targetLoc[1] = attacker->yLoc + nbDirs[dir][1];
618: getImpactLoc(strikeLoc, originLoc, targetLoc, 5, false);
619:
620: defender = monsterAtLoc(strikeLoc[0], strikeLoc[1]);
621: if (defender
622: && (attacker != &player || canSeeMonster(defender))
623: && !monsterIsHidden(defender, attacker)
624: && monsterWillAttackTarget(attacker, defender)) {
625:
626: if (attacker == &player) {
627: hitList[0] = defender;
628: if (abortAttackAgainstAcidicTarget(hitList)) {
629: if (aborted) {
630: *aborted = true;
631: }
632: return false;
633: }
634: }
635: attacker->bookkeepingFlags &= ~MB_SUBMERGED;
636: theBolt = boltCatalog[BOLT_WHIP];
637: theBolt.theChar = boltChar[dir];
638: zap(originLoc, targetLoc, &theBolt, false);
639: return true;
640: }
641: return false;
642: }
643:
644: // Returns true if a spear attack was launched.
645: // If "aborted" pointer is provided, sets it to true if it was aborted because
646: // the player opted not to attack an acid mound (in which case the whole turn
647: // should be aborted), as opposed to there being no valid spear attack available
648: // (in which case the player/monster should move instead).
649: boolean handleSpearAttacks(creature *attacker, enum directions dir, boolean *aborted) {
650: creature *defender, *hitList[8] = {0};
651: short targetLoc[2], range = 2, i = 0, h = 0;
652: boolean proceed = false, visualEffect = false;
653:
654: const char boltChar[DIRECTION_COUNT] = "||--\\//\\";
655:
656: brogueAssert(dir > NO_DIRECTION && dir < DIRECTION_COUNT);
657:
658: if (attacker == &player) {
659: if (!rogue.weapon || !(rogue.weapon->flags & ITEM_ATTACKS_PENETRATE)) {
660: return false;
661: }
662: } else if (!(attacker->info.abilityFlags & MA_ATTACKS_PENETRATE)) {
663: return false;
664: }
665:
666: for (i = 0; i < range; i++) {
667: targetLoc[0] = attacker->xLoc + (1 + i) * nbDirs[dir][0];
668: targetLoc[1] = attacker->yLoc + (1 + i) * nbDirs[dir][1];
669: if (!coordinatesAreInMap(targetLoc[0], targetLoc[1])) {
670: break;
671: }
672:
673: /* Add creatures that we are willing to attack to the potential
674: hitlist. Any of those that are either right by us or visible will
675: trigger the attack. */
676: defender = monsterAtLoc(targetLoc[0], targetLoc[1]);
677: if (defender
678: && (!cellHasTerrainFlag(targetLoc[0], targetLoc[1], T_OBSTRUCTS_PASSABILITY)
679: || (defender->info.flags & MONST_ATTACKABLE_THRU_WALLS))
680: && monsterWillAttackTarget(attacker, defender)) {
681:
682: hitList[h++] = defender;
683:
684: /* We check if i=0, i.e. the defender is right next to us, because
685: we have to do "normal" attacking here. We can't just return
686: false and leave to playerMoves/moveMonster due to the collateral hitlist. */
687: if (i == 0 || !monsterIsHidden(defender, attacker)
688: && (attacker != &player || canSeeMonster(defender))) {
689: // We'll attack.
690: proceed = true;
691: }
692: }
693:
694: if (cellHasTerrainFlag(targetLoc[0], targetLoc[1], (T_OBSTRUCTS_PASSABILITY | T_OBSTRUCTS_VISION))) {
695: break;
696: }
697: }
698: range = i;
699: if (proceed) {
700: if (attacker == &player) {
701: if (abortAttackAgainstAcidicTarget(hitList)) {
702: if (aborted) {
703: *aborted = true;
704: }
705: return false;
706: }
707: }
708: if (!rogue.playbackFastForward) {
709: for (i = 0; i < range; i++) {
710: targetLoc[0] = attacker->xLoc + (1 + i) * nbDirs[dir][0];
711: targetLoc[1] = attacker->yLoc + (1 + i) * nbDirs[dir][1];
712: if (coordinatesAreInMap(targetLoc[0], targetLoc[1])
713: && playerCanSeeOrSense(targetLoc[0], targetLoc[1])) {
714:
715: visualEffect = true;
716: plotForegroundChar(boltChar[dir], targetLoc[0], targetLoc[1], &lightBlue, true);
717: }
718: }
719: }
720: attacker->bookkeepingFlags &= ~MB_SUBMERGED;
721: // Artificially reverse the order of the attacks,
722: // so that spears of force can send both monsters flying.
723: for (i = h - 1; i >= 0; i--) {
724: attack(attacker, hitList[i], false);
725: }
726: if (visualEffect) {
727: pauseBrogue(16);
728: for (i = 0; i < range; i++) {
729: targetLoc[0] = attacker->xLoc + (1 + i) * nbDirs[dir][0];
730: targetLoc[1] = attacker->yLoc + (1 + i) * nbDirs[dir][1];
731: if (coordinatesAreInMap(targetLoc[0], targetLoc[1])) {
732: refreshDungeonCell(targetLoc[0], targetLoc[1]);
733: }
734: }
735: }
736: return true;
737: }
738: return false;
739: }
740:
741: void buildFlailHitList(const short x, const short y, const short newX, const short newY, creature *hitList[16]) {
742: creature *monst;
743: short mx, my;
744: short i = 0;
745:
746: for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
747: mx = monst->xLoc;
748: my = monst->yLoc;
749: if (distanceBetween(x, y, mx, my) == 1
750: && distanceBetween(newX, newY, mx, my) == 1
751: && canSeeMonster(monst)
752: && monstersAreEnemies(&player, monst)
753: && monst->creatureState != MONSTER_ALLY
754: && !(monst->bookkeepingFlags & MB_IS_DYING)
755: && (!cellHasTerrainFlag(monst->xLoc, monst->yLoc, T_OBSTRUCTS_PASSABILITY) || (monst->info.flags & MONST_ATTACKABLE_THRU_WALLS))) {
756:
757: while (hitList[i]) {
758: i++;
759: }
760: hitList[i] = monst;
761: }
762: }
763: }
764:
765: boolean diagonalBlocked(const short x1, const short y1, const short x2, const short y2, const boolean limitToPlayerKnowledge) {
766: unsigned long tFlags;
767: if (x1 == x2 || y1 == y2) {
768: return false; // If it's not a diagonal, it's not diagonally blocked.
769: }
770: getLocationFlags(x1, y2, &tFlags, NULL, NULL, limitToPlayerKnowledge);
771: if (tFlags & T_OBSTRUCTS_DIAGONAL_MOVEMENT) {
772: return true;
773: }
774: getLocationFlags(x2, y1, &tFlags, NULL, NULL, limitToPlayerKnowledge);
775: if (tFlags & T_OBSTRUCTS_DIAGONAL_MOVEMENT) {
776: return true;
777: }
778: return false;
779: }
780:
781: // Called whenever the player voluntarily tries to move in a given direction.
782: // Can be called from movement keys, exploration, or auto-travel.
783: boolean playerMoves(short direction) {
784: short initialDirection = direction, i, layer;
785: short x = player.xLoc, y = player.yLoc;
786: short newX, newY, newestX, newestY;
787: boolean playerMoved = false, alreadyRecorded = false, specialAttackAborted = false, anyAttackHit = false;
788: creature *defender = NULL, *tempMonst = NULL, *hitList[16] = {NULL};
789: char monstName[COLS];
790: char buf[COLS*3];
791: const int directionKeys[8] = {UP_KEY, DOWN_KEY, LEFT_KEY, RIGHT_KEY, UPLEFT_KEY, DOWNLEFT_KEY, UPRIGHT_KEY, DOWNRIGHT_KEY};
792:
793: brogueAssert(direction >= 0 && direction < DIRECTION_COUNT);
794:
795: newX = x + nbDirs[direction][0];
796: newY = y + nbDirs[direction][1];
797:
798: if (!coordinatesAreInMap(newX, newY)) {
799: return false;
800: }
801:
802: if (player.status[STATUS_CONFUSED]) {
803: // Confirmation dialog if you're moving while confused and you're next to lava and not levitating or immune to fire.
804: if (player.status[STATUS_LEVITATING] <= 1
805: && player.status[STATUS_IMMUNE_TO_FIRE] <= 1) {
806:
807: for (i=0; i<8; i++) {
808: newestX = x + nbDirs[i][0];
809: newestY = y + nbDirs[i][1];
810: if (coordinatesAreInMap(newestX, newestY)
811: && (pmap[newestX][newestY].flags & (DISCOVERED | MAGIC_MAPPED))
812: && !diagonalBlocked(x, y, newestX, newestY, false)
813: && cellHasTerrainFlag(newestX, newestY, T_LAVA_INSTA_DEATH)
814: && !cellHasTerrainFlag(newestX, newestY, T_OBSTRUCTS_PASSABILITY | T_ENTANGLES)
815: && !((pmap[newestX][newestY].flags & HAS_MONSTER)
816: && canSeeMonster(monsterAtLoc(newestX, newestY))
817: && monsterAtLoc(newestX, newestY)->creatureState != MONSTER_ALLY)) {
818:
819: if (!confirm("Risk stumbling into lava?", false)) {
820: return false;
821: } else {
822: break;
823: }
824: }
825: }
826: }
827:
828: direction = randValidDirectionFrom(&player, x, y, false);
829: if (direction == -1) {
830: return false;
831: } else {
832: newX = x + nbDirs[direction][0];
833: newY = y + nbDirs[direction][1];
834: if (!coordinatesAreInMap(newX, newY)) {
835: return false;
836: }
837: if (!alreadyRecorded) {
838: recordKeystroke(directionKeys[initialDirection], false, false);
839: alreadyRecorded = true;
840: }
841: }
842: }
843:
844: if (pmap[newX][newY].flags & HAS_MONSTER) {
845: defender = monsterAtLoc(newX, newY);
846: }
847:
848: // If there's no enemy at the movement location that the player is aware of, consider terrain promotions.
849: if (!defender
850: || (!canSeeMonster(defender) && !monsterRevealed(defender))
851: || !monstersAreEnemies(&player, defender)) {
852:
853: if (cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY) && cellHasTMFlag(newX, newY, TM_PROMOTES_ON_PLAYER_ENTRY)) {
854: layer = layerWithTMFlag(newX, newY, TM_PROMOTES_ON_PLAYER_ENTRY);
855: if (tileCatalog[pmap[newX][newY].layers[layer]].flags & T_OBSTRUCTS_PASSABILITY) {
856: if (!alreadyRecorded) {
857: recordKeystroke(directionKeys[initialDirection], false, false);
858: alreadyRecorded = true;
859: }
860: message(tileCatalog[pmap[newX][newY].layers[layer]].flavorText, false);
861: promoteTile(newX, newY, layer, false);
862: playerTurnEnded();
863: return true;
864: }
865: }
866:
867: if (rogue.patchVersion < 1 && player.status[STATUS_STUCK] && cellHasTerrainFlag(x, y, T_ENTANGLES)) {
868: // Don't interrupt exploration with this message.
869: if (--player.status[STATUS_STUCK]) {
870: if (!rogue.automationActive) {
871: message("you struggle but cannot free yourself.", false);
872: }
873: } else {
874: if (!rogue.automationActive) {
875: message("you break free!", false);
876: }
877: if (tileCatalog[pmap[x][y].layers[SURFACE]].flags & T_ENTANGLES) {
878: pmap[x][y].layers[SURFACE] = NOTHING;
879: }
880: }
881: moveEntrancedMonsters(direction);
882: if (!alreadyRecorded) {
883: recordKeystroke(directionKeys[initialDirection], false, false);
884: alreadyRecorded = true;
885: }
886: if (player.status[STATUS_STUCK]) {
887: playerTurnEnded();
888: return true;
889: }
890: }
891: }
892:
893: if (((!cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY) || (cellHasTMFlag(newX, newY, TM_PROMOTES_WITH_KEY) && keyInPackFor(newX, newY)))
894: && !diagonalBlocked(x, y, newX, newY, false)
895: && (!cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY) || (cellHasTMFlag(x, y, TM_PROMOTES_WITH_KEY) && keyInPackFor(x, y))))
896: || (defender && defender->info.flags & MONST_ATTACKABLE_THRU_WALLS)) {
897: // if the move is not blocked
898:
899: if (handleWhipAttacks(&player, direction, &specialAttackAborted)
900: || handleSpearAttacks(&player, direction, &specialAttackAborted)) {
901:
902: if (!alreadyRecorded) {
903: recordKeystroke(directionKeys[initialDirection], false, false);
904: alreadyRecorded = true;
905: }
906: playerRecoversFromAttacking(true);
907: moveEntrancedMonsters(direction);
908: playerTurnEnded();
909: return true;
910: } else if (specialAttackAborted) { // Canceled an attack against an acid mound.
911: brogueAssert(!alreadyRecorded);
912: rogue.disturbed = true;
913: return false;
914: }
915:
916: if (defender) {
917: // if there is a monster there
918:
919: if (defender->bookkeepingFlags & MB_CAPTIVE) {
920: monsterName(monstName, defender, false);
921: sprintf(buf, "Free the captive %s?", monstName);
922: if (alreadyRecorded || confirm(buf, false)) {
923: if (!alreadyRecorded) {
924: recordKeystroke(directionKeys[initialDirection], false, false);
925: alreadyRecorded = true;
926: }
927: if (cellHasTMFlag(newX, newY, TM_PROMOTES_WITH_KEY) && keyInPackFor(newX, newY)) {
928: useKeyAt(keyInPackFor(newX, newY), newX, newY);
929: }
930: freeCaptive(defender);
931: player.ticksUntilTurn += player.attackSpeed;
932: playerTurnEnded();
933: return true;
934: } else {
935: return false;
936: }
937: }
938:
939: if (defender->creatureState != MONSTER_ALLY) {
940: // Make a hit list of monsters the player is attacking this turn.
941: // We separate this tallying phase from the actual attacking phase because sometimes the attacks themselves
942: // create more monsters, and those shouldn't be attacked in the same turn.
943:
944: buildHitList(hitList, &player, defender,
945: rogue.weapon && (rogue.weapon->flags & ITEM_ATTACKS_ALL_ADJACENT));
946:
947: if (abortAttackAgainstAcidicTarget(hitList)) { // Acid mound attack confirmation.
948: brogueAssert(!alreadyRecorded);
949: rogue.disturbed = true;
950: return false;
951: }
952:
953: if (player.status[STATUS_NAUSEOUS]) {
954: if (!alreadyRecorded) {
955: recordKeystroke(directionKeys[initialDirection], false, false);
956: alreadyRecorded = true;
957: }
958: if (rand_percent(25)) {
959: vomit(&player);
960: playerTurnEnded();
961: return false;
962: }
963: }
964:
965: // Proceeding with the attack.
966:
967: if (!alreadyRecorded) {
968: recordKeystroke(directionKeys[initialDirection], false, false);
969: alreadyRecorded = true;
970: }
971:
972: // Attack!
973: for (i=0; i<16; i++) {
974: if (hitList[i]
975: && monsterWillAttackTarget(&player, hitList[i])
976: && !(hitList[i]->bookkeepingFlags & MB_IS_DYING)
977: && !rogue.gameHasEnded) {
978:
979: if (attack(&player, hitList[i], false)) {
980: anyAttackHit = true;
981: }
982: }
983: }
984:
985: playerRecoversFromAttacking(anyAttackHit);
986: moveEntrancedMonsters(direction);
987: playerTurnEnded();
988: return true;
989: }
990: }
991:
992: if (player.bookkeepingFlags & MB_SEIZED) {
993: for (tempMonst = monsters->nextCreature; tempMonst != NULL; tempMonst = tempMonst->nextCreature) {
994: if ((tempMonst->bookkeepingFlags & MB_SEIZING)
995: && monstersAreEnemies(&player, tempMonst)
996: && distanceBetween(player.xLoc, player.yLoc, tempMonst->xLoc, tempMonst->yLoc) == 1
997: && !diagonalBlocked(player.xLoc, player.yLoc, tempMonst->xLoc, tempMonst->yLoc, false)
998: && !tempMonst->status[STATUS_ENTRANCED]) {
999:
1000: monsterName(monstName, tempMonst, true);
1001: if (alreadyRecorded || !canSeeMonster(tempMonst)) {
1002: if (!alreadyRecorded) {
1003: recordKeystroke(directionKeys[initialDirection], false, false);
1004: alreadyRecorded = true;
1005: }
1006: sprintf(buf, "you struggle but %s is holding your legs!", monstName);
1007: moveEntrancedMonsters(direction);
1008: message(buf, false);
1009: playerTurnEnded();
1010: return true;
1011: } else {
1012: sprintf(buf, "you cannot move; %s is holding your legs!", monstName);
1013: message(buf, false);
1014: return false;
1015: }
1016: }
1017: }
1018: player.bookkeepingFlags &= ~MB_SEIZED; // failsafe
1019: }
1020:
1021: if (pmap[newX][newY].flags & (DISCOVERED | MAGIC_MAPPED)
1022: && player.status[STATUS_LEVITATING] <= 1
1023: && !player.status[STATUS_CONFUSED]
1024: && cellHasTerrainFlag(newX, newY, T_LAVA_INSTA_DEATH)
1025: && player.status[STATUS_IMMUNE_TO_FIRE] <= 1
1026: && !cellHasTerrainFlag(newX, newY, T_ENTANGLES)
1027: && !cellHasTMFlag(newX, newY, TM_IS_SECRET)) {
1028: message("that would be certain death!", false);
1029: return false; // player won't willingly step into lava
1030: } else if (pmap[newX][newY].flags & (DISCOVERED | MAGIC_MAPPED)
1031: && player.status[STATUS_LEVITATING] <= 1
1032: && !player.status[STATUS_CONFUSED]
1033: && cellHasTerrainFlag(newX, newY, T_AUTO_DESCENT)
1034: && !cellHasTerrainFlag(newX, newY, T_ENTANGLES)
1035: && !cellHasTMFlag(newX, newY, TM_IS_SECRET)
1036: && !confirm("Dive into the depths?", false)) {
1037: return false;
1038: } else if (playerCanSee(newX, newY)
1039: && !player.status[STATUS_CONFUSED]
1040: && !player.status[STATUS_BURNING]
1041: && player.status[STATUS_IMMUNE_TO_FIRE] <= 1
1042: && cellHasTerrainFlag(newX, newY, T_IS_FIRE)
1043: && !cellHasTMFlag(newX, newY, TM_EXTINGUISHES_FIRE)
1044: && !confirm("Venture into flame?", false)) {
1045: return false;
1046: } else if (playerCanSee(newX, newY)
1047: && !player.status[STATUS_CONFUSED]
1048: && !player.status[STATUS_BURNING]
1049: && cellHasTerrainFlag(newX, newY, T_CAUSES_CONFUSION | T_CAUSES_PARALYSIS)
1050: && (!rogue.armor || !(rogue.armor->flags & ITEM_RUNIC) || !(rogue.armor->flags & ITEM_RUNIC_IDENTIFIED) || rogue.armor->enchant2 != A_RESPIRATION)
1051: && !confirm("Venture into dangerous gas?", false)) {
1052: return false;
1053: } else if (pmap[newX][newY].flags & (ANY_KIND_OF_VISIBLE | MAGIC_MAPPED)
1054: && player.status[STATUS_LEVITATING] <= 1
1055: && !player.status[STATUS_CONFUSED]
1056: && cellHasTerrainFlag(newX, newY, T_IS_DF_TRAP)
1057: && !(pmap[newX][newY].flags & PRESSURE_PLATE_DEPRESSED)
1058: && !cellHasTMFlag(newX, newY, TM_IS_SECRET)
1059: && !confirm("Step onto the pressure plate?", false)) {
1060: return false;
1061: }
1062:
1063: if (rogue.weapon && (rogue.weapon->flags & ITEM_LUNGE_ATTACKS)) {
1064: newestX = player.xLoc + 2*nbDirs[direction][0];
1065: newestY = player.yLoc + 2*nbDirs[direction][1];
1066: if (coordinatesAreInMap(newestX, newestY) && (pmap[newestX][newestY].flags & HAS_MONSTER)) {
1067: tempMonst = monsterAtLoc(newestX, newestY);
1068: if (tempMonst
1069: && canSeeMonster(tempMonst)
1070: && monstersAreEnemies(&player, tempMonst)
1071: && tempMonst->creatureState != MONSTER_ALLY
1072: && !(tempMonst->bookkeepingFlags & MB_IS_DYING)
1073: && (!cellHasTerrainFlag(tempMonst->xLoc, tempMonst->yLoc, T_OBSTRUCTS_PASSABILITY) || (tempMonst->info.flags & MONST_ATTACKABLE_THRU_WALLS))) {
1074:
1075: hitList[0] = tempMonst;
1076: if (abortAttackAgainstAcidicTarget(hitList)) { // Acid mound attack confirmation.
1077: brogueAssert(!alreadyRecorded);
1078: rogue.disturbed = true;
1079: return false;
1080: }
1081: }
1082: }
1083: }
1084: if (rogue.weapon && (rogue.weapon->flags & ITEM_PASS_ATTACKS)) {
1085: buildFlailHitList(x, y, newX, newY, hitList);
1086: if (abortAttackAgainstAcidicTarget(hitList)) { // Acid mound attack confirmation.
1087: brogueAssert(!alreadyRecorded);
1088: rogue.disturbed = true;
1089: return false;
1090: }
1091: }
1092:
1093: if (rogue.patchVersion >= 1 && player.status[STATUS_STUCK] && cellHasTerrainFlag(x, y, T_ENTANGLES)) {
1094: // Don't interrupt exploration with this message.
1095: if (--player.status[STATUS_STUCK]) {
1096: if (!rogue.automationActive) {
1097: message("you struggle but cannot free yourself.", false);
1098: }
1099: moveEntrancedMonsters(direction);
1100: if (!alreadyRecorded) {
1101: recordKeystroke(directionKeys[initialDirection], false, false);
1102: alreadyRecorded = true;
1103: }
1104: playerTurnEnded();
1105: return true;
1106: } else {
1107: if (!rogue.automationActive) {
1108: message("you break free!", false);
1109: }
1110: if (tileCatalog[pmap[x][y].layers[SURFACE]].flags & T_ENTANGLES) {
1111: pmap[x][y].layers[SURFACE] = NOTHING;
1112: }
1113: }
1114: }
1115:
1116: if (player.status[STATUS_NAUSEOUS]) {
1117: if (!alreadyRecorded) {
1118: recordKeystroke(directionKeys[initialDirection], false, false);
1119: alreadyRecorded = true;
1120: }
1121: if (rand_percent(25)) {
1122: vomit(&player);
1123: playerTurnEnded();
1124: return true;
1125: }
1126: }
1127:
1128: // Are we taking the stairs?
1129: if (rogue.downLoc[0] == newX && rogue.downLoc[1] == newY) {
1130: if (!alreadyRecorded) {
1131: recordKeystroke(directionKeys[initialDirection], false, false);
1132: alreadyRecorded = true;
1133: }
1134: useStairs(1);
1135: } else if (rogue.upLoc[0] == newX && rogue.upLoc[1] == newY) {
1136: if (!alreadyRecorded) {
1137: recordKeystroke(directionKeys[initialDirection], false, false);
1138: alreadyRecorded = true;
1139: }
1140: useStairs(-1);
1141: } else {
1142: // Okay, we're finally moving!
1143: if (!alreadyRecorded) {
1144: recordKeystroke(directionKeys[initialDirection], false, false);
1145: alreadyRecorded = true;
1146: }
1147:
1148: player.xLoc += nbDirs[direction][0];
1149: player.yLoc += nbDirs[direction][1];
1150: pmap[x][y].flags &= ~HAS_PLAYER;
1151: pmap[player.xLoc][player.yLoc].flags |= HAS_PLAYER;
1152: pmap[player.xLoc][player.yLoc].flags &= ~IS_IN_PATH;
1153: if (defender && defender->creatureState == MONSTER_ALLY) { // Swap places with ally.
1154: pmap[defender->xLoc][defender->yLoc].flags &= ~HAS_MONSTER;
1155: defender->xLoc = x;
1156: defender->yLoc = y;
1157: if (monsterAvoids(defender, x, y)) {
1158: getQualifyingPathLocNear(&(defender->xLoc), &(defender->yLoc), player.xLoc, player.yLoc, true, forbiddenFlagsForMonster(&(defender->info)), 0, 0, (HAS_PLAYER | HAS_MONSTER | HAS_STAIRS), false);
1159: }
1160: //getQualifyingLocNear(loc, player.xLoc, player.yLoc, true, NULL, forbiddenFlagsForMonster(&(defender->info)) & ~(T_IS_DF_TRAP | T_IS_DEEP_WATER | T_SPONTANEOUSLY_IGNITES), HAS_MONSTER, false, false);
1161: //defender->xLoc = loc[0];
1162: //defender->yLoc = loc[1];
1163: pmap[defender->xLoc][defender->yLoc].flags |= HAS_MONSTER;
1164: }
1165:
1166: if (pmap[player.xLoc][player.yLoc].flags & HAS_ITEM) {
1167: pickUpItemAt(player.xLoc, player.yLoc);
1168: rogue.disturbed = true;
1169: }
1170: refreshDungeonCell(x, y);
1171: refreshDungeonCell(player.xLoc, player.yLoc);
1172: playerMoved = true;
1173:
1174: checkForMissingKeys(x, y);
1175: if (monsterShouldFall(&player)) {
1176: player.bookkeepingFlags |= MB_IS_FALLING;
1177: }
1178: moveEntrancedMonsters(direction);
1179:
1180: // Perform a lunge or flail attack if appropriate.
1181: for (i=0; i<16; i++) {
1182: if (hitList[i]) {
1183: if (attack(&player, hitList[i], (rogue.weapon && (rogue.weapon->flags & ITEM_LUNGE_ATTACKS)))) {
1184: anyAttackHit = true;
1185: }
1186: }
1187: }
1188: if (hitList[0]) {
1189: playerRecoversFromAttacking(anyAttackHit);
1190: }
1191:
1192: playerTurnEnded();
1193: }
1194: } else if (cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)) {
1195: i = pmap[newX][newY].layers[layerWithFlag(newX, newY, T_OBSTRUCTS_PASSABILITY)];
1196: if ((tileCatalog[i].flags & T_OBSTRUCTS_PASSABILITY)
1197: && (!diagonalBlocked(x, y, newX, newY, false) || !cellHasTMFlag(newX, newY, TM_PROMOTES_WITH_KEY))) {
1198:
1199: if (!(pmap[newX][newY].flags & DISCOVERED)) {
1200: if (!alreadyRecorded) {
1201: recordKeystroke(directionKeys[initialDirection], false, false);
1202: alreadyRecorded = true;
1203: }
1204: discoverCell(newX, newY);
1205: refreshDungeonCell(newX, newY);
1206: }
1207:
1208: messageWithColor(tileCatalog[i].flavorText, &backgroundMessageColor, false);
1209: }
1210: }
1211: return playerMoved;
1212: }
1213:
1214: // replaced in Dijkstra.c:
1215: /*
1216: // returns true if the cell value changed
1217: boolean updateDistanceCell(short **distanceMap, short x, short y) {
1218: short dir, newX, newY;
1219: boolean somethingChanged = false;
1220:
1221: if (distanceMap[x][y] >= 0 && distanceMap[x][y] < 30000) {
1222: for (dir=0; dir< DIRECTION_COUNT; dir++) {
1223: newX = x + nbDirs[dir][0];
1224: newY = y + nbDirs[dir][1];
1225: if (coordinatesAreInMap(newX, newY)
1226: && distanceMap[newX][newY] >= distanceMap[x][y] + 2
1227: && !diagonalBlocked(x, y, newX, newY)) {
1228: distanceMap[newX][newY] = distanceMap[x][y] + 1;
1229: somethingChanged = true;
1230: }
1231: }
1232: }
1233: return somethingChanged;
1234: }
1235:
1236: void dijkstraScan(short **distanceMap, char passMap[DCOLS][DROWS], boolean allowDiagonals) {
1237: short i, j, maxDir;
1238: enum directions dir;
1239: boolean somethingChanged;
1240:
1241: maxDir = (allowDiagonals ? 8 : 4);
1242:
1243: do {
1244: somethingChanged = false;
1245: for (i=1; i<DCOLS-1; i++) {
1246: for (j=1; j<DROWS-1; j++) {
1247: if (!passMap || passMap[i][j]) {
1248: for (dir = 0; dir < maxDir; dir++) {
1249: if (coordinatesAreInMap(i + nbDirs[dir][0], j + nbDirs[dir][1])
1250: && (!passMap || passMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]])
1251: && distanceMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]] >= distanceMap[i][j] + 2) {
1252: distanceMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]] = distanceMap[i][j] + 1;
1253: somethingChanged = true;
1254: }
1255: }
1256: }
1257: }
1258: }
1259:
1260:
1261: for (i = DCOLS - 1; i >= 0; i--) {
1262: for (j = DROWS - 1; j >= 0; j--) {
1263: if (!passMap || passMap[i][j]) {
1264: for (dir = 0; dir < maxDir; dir++) {
1265: if (coordinatesAreInMap(i + nbDirs[dir][0], j + nbDirs[dir][1])
1266: && (!passMap || passMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]])
1267: && distanceMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]] >= distanceMap[i][j] + 2) {
1268: distanceMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]] = distanceMap[i][j] + 1;
1269: somethingChanged = true;
1270: }
1271: }
1272: }
1273: }
1274: }
1275: } while (somethingChanged);
1276: }*/
1277:
1278: /*void enqueue(short x, short y, short val, distanceQueue *dQ) {
1279: short *qX2, *qY2, *qVal2;
1280:
1281: // if we need to allocate more memory:
1282: if (dQ->qLen + 1 > dQ->qMaxLen) {
1283: dQ->qMaxLen *= 2;
1284: qX2 = realloc(dQ->qX, dQ->qMaxLen);
1285: if (qX2) {
1286: free(dQ->qX);
1287: dQ->qX = qX2;
1288: } else {
1289: // out of memory
1290: }
1291: qY2 = realloc(dQ->qY, dQ->qMaxLen);
1292: if (qY2) {
1293: free(dQ->qY);
1294: dQ->qY = qY2;
1295: } else {
1296: // out of memory
1297: }
1298: qVal2 = realloc(dQ->qVal, dQ->qMaxLen);
1299: if (qVal2) {
1300: free(dQ->qVal);
1301: dQ->qVal = qVal2;
1302: } else {
1303: // out of memory
1304: }
1305: }
1306:
1307: dQ->qX[dQ->qLen] = x;
1308: dQ->qY[dQ->qLen] = y;
1309: (dQ->qVal)[dQ->qLen] = val;
1310:
1311: dQ->qLen++;
1312:
1313: if (val < dQ->qMinVal) {
1314: dQ->qMinVal = val;
1315: dQ->qMinCount = 1;
1316: } else if (val == dQ->qMinVal) {
1317: dQ->qMinCount++;
1318: }
1319: }
1320:
1321: void updateQueueMinCache(distanceQueue *dQ) {
1322: short i;
1323: dQ->qMinCount = 0;
1324: dQ->qMinVal = 30001;
1325: for (i = 0; i < dQ->qLen; i++) {
1326: if (dQ->qVal[i] < dQ->qMinVal) {
1327: dQ->qMinVal = dQ->qVal[i];
1328: dQ->qMinCount = 1;
1329: } else if (dQ->qVal[i] == dQ->qMinVal) {
1330: dQ->qMinCount++;
1331: }
1332: }
1333: }
1334:
1335: // removes the lowest value from the queue, populates x/y/value variables and updates min caching
1336: void dequeue(short *x, short *y, short *val, distanceQueue *dQ) {
1337: short i, minIndex;
1338:
1339: if (dQ->qMinCount <= 0) {
1340: updateQueueMinCache(dQ);
1341: }
1342:
1343: *val = dQ->qMinVal;
1344:
1345: // find the last instance of the minVal
1346: for (minIndex = dQ->qLen - 1; minIndex >= 0 && dQ->qVal[minIndex] != *val; minIndex--);
1347:
1348: // populate the return variables
1349: *x = dQ->qX[minIndex];
1350: *y = dQ->qY[minIndex];
1351:
1352: dQ->qLen--;
1353:
1354: // delete the minValue queue entry
1355: for (i = minIndex; i < dQ->qLen; i++) {
1356: dQ->qX[i] = dQ->qX[i+1];
1357: dQ->qY[i] = dQ->qY[i+1];
1358: dQ->qVal[i] = dQ->qVal[i+1];
1359: }
1360:
1361: // update min values
1362: dQ->qMinCount--;
1363: if (!dQ->qMinCount && dQ->qLen) {
1364: updateQueueMinCache(dQ);
1365: }
1366:
1367: }
1368:
1369: void dijkstraScan(short **distanceMap, char passMap[DCOLS][DROWS], boolean allowDiagonals) {
1370: short i, j, maxDir, val;
1371: enum directions dir;
1372: distanceQueue dQ;
1373:
1374: dQ.qMaxLen = DCOLS * DROWS * 1.5;
1375: dQ.qX = (short *) malloc(dQ.qMaxLen * sizeof(short));
1376: dQ.qY = (short *) malloc(dQ.qMaxLen * sizeof(short));
1377: dQ.qVal = (short *) malloc(dQ.qMaxLen * sizeof(short));
1378: dQ.qLen = 0;
1379: dQ.qMinVal = 30000;
1380: dQ.qMinCount = 0;
1381:
1382: maxDir = (allowDiagonals ? 8 : 4);
1383:
1384: // seed the queue with the entire map
1385: for (i=0; i<DCOLS; i++) {
1386: for (j=0; j<DROWS; j++) {
1387: if (!passMap || passMap[i][j]) {
1388: enqueue(i, j, distanceMap[i][j], &dQ);
1389: }
1390: }
1391: }
1392:
1393: // iterate through queue updating lowest entries until the queue is empty
1394: while (dQ.qLen) {
1395: dequeue(&i, &j, &val, &dQ);
1396: if (distanceMap[i][j] == val) { // if it hasn't been improved since joining the queue
1397: for (dir = 0; dir < maxDir; dir++) {
1398: if (coordinatesAreInMap(i + nbDirs[dir][0], j + nbDirs[dir][1])
1399: && (!passMap || passMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]])
1400: && distanceMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]] >= distanceMap[i][j] + 2) {
1401:
1402: distanceMap[i + nbDirs[dir][0]][j + nbDirs[dir][1]] = distanceMap[i][j] + 1;
1403:
1404: enqueue(i + nbDirs[dir][0], j + nbDirs[dir][1], distanceMap[i][j] + 1, &dQ);
1405: }
1406: }
1407: }
1408: }
1409:
1410: free(dQ.qX);
1411: free(dQ.qY);
1412: free(dQ.qVal);
1413: }*/
1414:
1415: /*
1416: void calculateDistances(short **distanceMap, short destinationX, short destinationY, unsigned long blockingTerrainFlags, creature *traveler) {
1417: short i, j;
1418: boolean somethingChanged;
1419:
1420: for (i=0; i<DCOLS; i++) {
1421: for (j=0; j<DROWS; j++) {
1422: distanceMap[i][j] = ((traveler && traveler == &player && !(pmap[i][j].flags & (DISCOVERED | MAGIC_MAPPED)))
1423: || ((traveler && monsterAvoids(traveler, i, j))
1424: || cellHasTerrainFlag(i, j, blockingTerrainFlags))) ? -1 : 30000;
1425: }
1426: }
1427:
1428: distanceMap[destinationX][destinationY] = 0;
1429:
1430: // dijkstraScan(distanceMap);
1431: do {
1432: somethingChanged = false;
1433: for (i=0; i<DCOLS; i++) {
1434: for (j=0; j<DROWS; j++) {
1435: if (updateDistanceCell(distanceMap, i, j)) {
1436: somethingChanged = true;
1437: }
1438: }
1439: }
1440:
1441:
1442: for (i = DCOLS - 1; i >= 0; i--) {
1443: for (j = DROWS - 1; j >= 0; j--) {
1444: if (updateDistanceCell(distanceMap, i, j)) {
1445: somethingChanged = true;
1446: }
1447: }
1448: }
1449: } while (somethingChanged);
1450: }*/
1451:
1452: // Returns -1 if there are no beneficial moves.
1453: // If preferDiagonals is true, we will prefer diagonal moves.
1454: // Always rolls downhill on the distance map.
1455: // If monst is provided, do not return a direction pointing to
1456: // a cell that the monster avoids.
1457: short nextStep(short **distanceMap, short x, short y, creature *monst, boolean preferDiagonals) {
1458: short newX, newY, bestScore;
1459: enum directions dir, bestDir;
1460: creature *blocker;
1461: boolean blocked;
1462:
1463: brogueAssert(coordinatesAreInMap(x, y));
1464:
1465: bestScore = 0;
1466: bestDir = NO_DIRECTION;
1467:
1468: for (dir = (preferDiagonals ? 7 : 0);
1469: (preferDiagonals ? dir >= 0 : dir < DIRECTION_COUNT);
1470: (preferDiagonals ? dir-- : dir++)) {
1471:
1472: newX = x + nbDirs[dir][0];
1473: newY = y + nbDirs[dir][1];
1474:
1475: brogueAssert(coordinatesAreInMap(newX, newY));
1476: if (coordinatesAreInMap(newX, newY)) {
1477: blocked = false;
1478: blocker = monsterAtLoc(newX, newY);
1479: if (monst
1480: && monsterAvoids(monst, newX, newY)) {
1481:
1482: blocked = true;
1483: } else if (monst
1484: && blocker
1485: && !canPass(monst, blocker)
1486: && !monstersAreTeammates(monst, blocker)
1487: && !monstersAreEnemies(monst, blocker)) {
1488: blocked = true;
1489: }
1490: if ((distanceMap[x][y] - distanceMap[newX][newY]) > bestScore
1491: && !diagonalBlocked(x, y, newX, newY, monst == &player)
1492: && knownToPlayerAsPassableOrSecretDoor(newX, newY)
1493: && !blocked) {
1494:
1495: bestDir = dir;
1496: bestScore = distanceMap[x][y] - distanceMap[newX][newY];
1497: }
1498: }
1499: }
1500: return bestDir;
1501: }
1502:
1503: void displayRoute(short **distanceMap, boolean removeRoute) {
1504: short currentX = player.xLoc, currentY = player.yLoc, dir, newX, newY;
1505: boolean advanced;
1506:
1507: if (distanceMap[player.xLoc][player.yLoc] < 0 || distanceMap[player.xLoc][player.yLoc] == 30000) {
1508: return;
1509: }
1510: do {
1511: if (removeRoute) {
1512: refreshDungeonCell(currentX, currentY);
1513: } else {
1514: hiliteCell(currentX, currentY, &hiliteColor, 50, true);
1515: }
1516: advanced = false;
1517: for (dir = 7; dir >= 0; dir--) {
1518: newX = currentX + nbDirs[dir][0];
1519: newY = currentY + nbDirs[dir][1];
1520: if (coordinatesAreInMap(newX, newY)
1521: && distanceMap[newX][newY] >= 0 && distanceMap[newX][newY] < distanceMap[currentX][currentY]
1522: && !diagonalBlocked(currentX, currentY, newX, newY, true)) {
1523:
1524: currentX = newX;
1525: currentY = newY;
1526: advanced = true;
1527: break;
1528: }
1529: }
1530: } while (advanced);
1531: }
1532:
1533: void travelRoute(short path[1000][2], short steps) {
1534: short i, j;
1535: short dir;
1536: creature *monst;
1537:
1538: brogueAssert(!rogue.playbackMode);
1539:
1540: rogue.disturbed = false;
1541: rogue.automationActive = true;
1542:
1543: for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
1544: if (canSeeMonster(monst)) {
1545: monst->bookkeepingFlags |= MB_ALREADY_SEEN;
1546: } else {
1547: monst->bookkeepingFlags &= ~MB_ALREADY_SEEN;
1548: }
1549: }
1550:
1551: for (i=0; i < steps && !rogue.disturbed; i++) {
1552: for (j = i + 1; j < steps - 1; j++) {
1553: // Check to see if the path has become obstructed or avoided since the last time we saw it.
1554: if (diagonalBlocked(path[j-1][0], path[j-1][1], path[j][0], path[j][1], true)
1555: || monsterAvoids(&player, path[j][0], path[j][1])) {
1556:
1557: rogue.disturbed = true;
1558: break;
1559: }
1560: }
1561: for (dir = 0; dir < DIRECTION_COUNT && !rogue.disturbed; dir++) {
1562: if (player.xLoc + nbDirs[dir][0] == path[i][0]
1563: && player.yLoc + nbDirs[dir][1] == path[i][1]) {
1564:
1565: if (!playerMoves(dir)) {
1566: rogue.disturbed = true;
1567: }
1568: if (pauseBrogue(25)) {
1569: rogue.disturbed = true;
1570: }
1571: break;
1572: }
1573: }
1574: }
1575: rogue.disturbed = true;
1576: rogue.automationActive = false;
1577: updateFlavorText();
1578: }
1579:
1580: void travelMap(short **distanceMap) {
1581: short currentX = player.xLoc, currentY = player.yLoc, dir, newX, newY;
1582: boolean advanced;
1583:
1584: rogue.disturbed = false;
1585: rogue.automationActive = true;
1586:
1587: if (distanceMap[player.xLoc][player.yLoc] < 0 || distanceMap[player.xLoc][player.yLoc] == 30000) {
1588: return;
1589: }
1590: do {
1591: advanced = false;
1592: for (dir = 7; dir >= 0; dir--) {
1593: newX = currentX + nbDirs[dir][0];
1594: newY = currentY + nbDirs[dir][1];
1595: if (coordinatesAreInMap(newX, newY)
1596: && distanceMap[newX][newY] >= 0
1597: && distanceMap[newX][newY] < distanceMap[currentX][currentY]
1598: && !diagonalBlocked(currentX, currentY, newX, newY, true)) {
1599:
1600: if (!playerMoves(dir)) {
1601: rogue.disturbed = true;
1602: }
1603: if (pauseBrogue(500)) {
1604: rogue.disturbed = true;
1605: }
1606: currentX = newX;
1607: currentY = newY;
1608: advanced = true;
1609: break;
1610: }
1611: }
1612: } while (advanced && !rogue.disturbed);
1613: rogue.disturbed = true;
1614: rogue.automationActive = false;
1615: updateFlavorText();
1616: }
1617:
1618: void travel(short x, short y, boolean autoConfirm) {
1619: short **distanceMap, i;
1620: rogueEvent theEvent;
1621: unsigned short staircaseConfirmKey;
1622:
1623: confirmMessages();
1624:
1625: if (D_WORMHOLING) {
1626: recordMouseClick(mapToWindowX(x), mapToWindowY(y), true, false);
1627: pmap[player.xLoc][player.yLoc].flags &= ~HAS_PLAYER;
1628: refreshDungeonCell(player.xLoc, player.yLoc);
1629: player.xLoc = x;
1630: player.yLoc = y;
1631: pmap[x][y].flags |= HAS_PLAYER;
1632: updatePlayerUnderwaterness();
1633: refreshDungeonCell(x, y);
1634: updateVision(true);
1635: return;
1636: }
1637:
1638: if (abs(player.xLoc - x) + abs(player.yLoc - y) == 1) {
1639: // targeting a cardinal neighbor
1640: for (i=0; i<4; i++) {
1641: if (nbDirs[i][0] == (x - player.xLoc) && nbDirs[i][1] == (y - player.yLoc)) {
1642: playerMoves(i);
1643: break;
1644: }
1645: }
1646: return;
1647: }
1648:
1649: if (!(pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED))) {
1650: message("You have not explored that location.", false);
1651: return;
1652: }
1653:
1654: distanceMap = allocGrid();
1655:
1656: calculateDistances(distanceMap, x, y, 0, &player, false, false);
1657: if (distanceMap[player.xLoc][player.yLoc] < 30000) {
1658: if (autoConfirm) {
1659: travelMap(distanceMap);
1660: //refreshSideBar(-1, -1, false);
1661: } else {
1662: if (rogue.upLoc[0] == x && rogue.upLoc[1] == y) {
1663: staircaseConfirmKey = ASCEND_KEY;
1664: } else if (rogue.downLoc[0] == x && rogue.downLoc[1] == y) {
1665: staircaseConfirmKey = DESCEND_KEY;
1666: } else {
1667: staircaseConfirmKey = 0;
1668: }
1669: displayRoute(distanceMap, false);
1670: message("Travel this route? (y/n)", false);
1671:
1672: do {
1673: nextBrogueEvent(&theEvent, true, false, false);
1674: } while (theEvent.eventType != MOUSE_UP && theEvent.eventType != KEYSTROKE);
1675:
1676: displayRoute(distanceMap, true); // clear route display
1677: confirmMessages();
1678:
1679: if ((theEvent.eventType == MOUSE_UP && windowToMapX(theEvent.param1) == x && windowToMapY(theEvent.param2) == y)
1680: || (theEvent.eventType == KEYSTROKE && (theEvent.param1 == 'Y' || theEvent.param1 == 'y'
1681: || theEvent.param1 == RETURN_KEY
1682: || (theEvent.param1 == staircaseConfirmKey
1683: && theEvent.param1 != 0)))) {
1684: travelMap(distanceMap);
1685: //refreshSideBar(-1, -1, false);
1686: commitDraws();
1687: } else if (theEvent.eventType == MOUSE_UP) {
1688: executeMouseClick(&theEvent);
1689: }
1690: }
1691: // if (player.xLoc == x && player.yLoc == y) {
1692: // rogue.cursorLoc[0] = rogue.cursorLoc[1] = 0;
1693: // } else {
1694: // rogue.cursorLoc[0] = x;
1695: // rogue.cursorLoc[1] = y;
1696: // }
1697: } else {
1698: rogue.cursorLoc[0] = rogue.cursorLoc[1] = -1;
1699: message("No path is available.", false);
1700: }
1701: freeGrid(distanceMap);
1702: }
1703:
1704: void populateGenericCostMap(short **costMap) {
1705: short i, j;
1706:
1707: for (i=0; i<DCOLS; i++) {
1708: for (j=0; j<DROWS; j++) {
1709: if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)
1710: && (!cellHasTMFlag(i, j, TM_IS_SECRET) || (discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY))) {
1711:
1712: costMap[i][j] = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN;
1713: } else if (cellHasTerrainFlag(i, j, T_PATHING_BLOCKER & ~T_OBSTRUCTS_PASSABILITY)) {
1714: costMap[i][j] = PDS_FORBIDDEN;
1715: } else {
1716: costMap[i][j] = 1;
1717: }
1718: }
1719: }
1720: }
1721:
1722: void getLocationFlags(const short x, const short y,
1723: unsigned long *tFlags, unsigned long *TMFlags, unsigned long *cellFlags,
1724: const boolean limitToPlayerKnowledge) {
1725: if (limitToPlayerKnowledge
1726: && (pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED))
1727: && !playerCanSee(x, y)) {
1728:
1729: if (tFlags) {
1730: *tFlags = pmap[x][y].rememberedTerrainFlags;
1731: }
1732: if (TMFlags) {
1733: *TMFlags = pmap[x][y].rememberedTMFlags;
1734: }
1735: if (cellFlags) {
1736: *cellFlags = pmap[x][y].rememberedCellFlags;
1737: }
1738: } else {
1739: if (tFlags) {
1740: *tFlags = terrainFlags(x, y);
1741: }
1742: if (TMFlags) {
1743: *TMFlags = terrainMechFlags(x, y);
1744: }
1745: if (cellFlags) {
1746: *cellFlags = pmap[x][y].flags;
1747: }
1748: }
1749: }
1750:
1751: void populateCreatureCostMap(short **costMap, creature *monst) {
1752: short i, j, unexploredCellCost;
1753: creature *currentTenant;
1754: item *theItem;
1755: unsigned long tFlags, cFlags;
1756:
1757: unexploredCellCost = 10 + (clamp(rogue.depthLevel, 5, 15) - 5) * 2;
1758:
1759: for (i=0; i<DCOLS; i++) {
1760: for (j=0; j<DROWS; j++) {
1761: if (monst == &player && !(pmap[i][j].flags & (DISCOVERED | MAGIC_MAPPED))) {
1762: costMap[i][j] = PDS_OBSTRUCTION;
1763: continue;
1764: }
1765:
1766: getLocationFlags(i, j, &tFlags, NULL, &cFlags, monst == &player);
1767:
1768: if ((tFlags & T_OBSTRUCTS_PASSABILITY)
1769: && (!cellHasTMFlag(i, j, TM_IS_SECRET) || (discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY) || monst == &player)) {
1770:
1771: costMap[i][j] = (tFlags & T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN;
1772: continue;
1773: }
1774:
1775: if ((tFlags & T_LAVA_INSTA_DEATH)
1776: && !(monst->info.flags & (MONST_IMMUNE_TO_FIRE | MONST_FLIES | MONST_INVULNERABLE))
1777: && (monst->status[STATUS_LEVITATING] || monst->status[STATUS_IMMUNE_TO_FIRE])
1778: && max(monst->status[STATUS_LEVITATING], monst->status[STATUS_IMMUNE_TO_FIRE]) < (rogue.mapToShore[i][j] + distanceBetween(i, j, monst->xLoc, monst->yLoc) * monst->movementSpeed / 100)) {
1779: // Only a temporary effect will permit the monster to survive the lava, and the remaining duration either isn't
1780: // enough to get it to the spot, or it won't suffice to let it return to shore if it does get there.
1781: // Treat these locations as obstacles.
1782: costMap[i][j] = PDS_FORBIDDEN;
1783: continue;
1784: }
1785:
1786: if (((tFlags & T_AUTO_DESCENT) || (tFlags & T_IS_DEEP_WATER) && !(monst->info.flags & MONST_IMMUNE_TO_WATER))
1787: && !(monst->info.flags & MONST_FLIES)
1788: && (monst->status[STATUS_LEVITATING])
1789: && monst->status[STATUS_LEVITATING] < (rogue.mapToShore[i][j] + distanceBetween(i, j, monst->xLoc, monst->yLoc) * monst->movementSpeed / 100)) {
1790: // Only a temporary effect will permit the monster to levitate over the chasm/water, and the remaining duration either isn't
1791: // enough to get it to the spot, or it won't suffice to let it return to shore if it does get there.
1792: // Treat these locations as obstacles.
1793: costMap[i][j] = PDS_FORBIDDEN;
1794: continue;
1795: }
1796:
1797: if (monsterAvoids(monst, i, j)) {
1798: costMap[i][j] = PDS_FORBIDDEN;
1799: continue;
1800: }
1801:
1802: if (cFlags & HAS_MONSTER) {
1803: currentTenant = monsterAtLoc(i, j);
1804: if (currentTenant
1805: && (currentTenant->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE))
1806: && !canPass(monst, currentTenant)) {
1807:
1808: costMap[i][j] = PDS_FORBIDDEN;
1809: continue;
1810: }
1811: }
1812:
1813: if ((cFlags & KNOWN_TO_BE_TRAP_FREE)
1814: || (monst != &player && monst->creatureState != MONSTER_ALLY)) {
1815:
1816: costMap[i][j] = 10;
1817: } else {
1818: // Player and allies give locations that are known to be free of traps
1819: // an advantage that increases with depth level, based on the depths
1820: // at which traps are generated.
1821: costMap[i][j] = unexploredCellCost;
1822: }
1823:
1824: if (!(monst->info.flags & MONST_INVULNERABLE)) {
1825: if ((tFlags & T_CAUSES_NAUSEA)
1826: || cellHasTMFlag(i, j, TM_PROMOTES_ON_ITEM_PICKUP)
1827: || (tFlags & T_ENTANGLES) && !(monst->info.flags & MONST_IMMUNE_TO_WEBS)) {
1828:
1829: costMap[i][j] += 20;
1830: }
1831: }
1832:
1833: if (monst == &player) {
1834: theItem = itemAtLoc(i, j);
1835: if (theItem && (theItem->flags & ITEM_PLAYER_AVOIDS)) {
1836: costMap[i][j] += 10;
1837: }
1838: }
1839: }
1840: }
1841: }
1842:
1843: enum directions adjacentFightingDir() {
1844: short newX, newY;
1845: enum directions dir;
1846: creature *monst;
1847:
1848: if (cellHasTerrainFlag(player.xLoc, player.yLoc, T_OBSTRUCTS_PASSABILITY)) {
1849: return NO_DIRECTION;
1850: }
1851: for (dir = 0; dir < DIRECTION_COUNT; dir++) {
1852: newX = player.xLoc + nbDirs[dir][0];
1853: newY = player.yLoc + nbDirs[dir][1];
1854: monst = monsterAtLoc(newX, newY);
1855: if (monst
1856: && canSeeMonster(monst)
1857: && (!diagonalBlocked(player.xLoc, player.yLoc, newX, newY, false) || (monst->info.flags & MONST_ATTACKABLE_THRU_WALLS))
1858: && monstersAreEnemies(&player, monst)
1859: && !(monst->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE))) {
1860:
1861: return dir;
1862: }
1863: }
1864: return NO_DIRECTION;
1865: }
1866:
1867: #define exploreGoalValue(x, y) (0 - abs((x) - DCOLS / 2) / 3 - abs((x) - DCOLS / 2) / 4)
1868:
1869: void getExploreMap(short **map, boolean headingToStairs) {// calculate explore map
1870: short i, j;
1871: short **costMap;
1872: item *theItem;
1873:
1874: costMap = allocGrid();
1875: populateCreatureCostMap(costMap, &player);
1876:
1877: for (i=0; i<DCOLS; i++) {
1878: for (j=0; j<DROWS; j++) {
1879: map[i][j] = 30000; // Can be overridden later.
1880: theItem = itemAtLoc(i, j);
1881: if (!(pmap[i][j].flags & DISCOVERED)) {
1882: if ((pmap[i][j].flags & MAGIC_MAPPED)
1883: && (tileCatalog[pmap[i][j].layers[DUNGEON]].flags | tileCatalog[pmap[i][j].layers[LIQUID]].flags) & T_PATHING_BLOCKER) {
1884: // Magic-mapped cells revealed as obstructions should be treated as such even though they're not discovered.
1885: costMap[i][j] = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN;
1886: } else {
1887: costMap[i][j] = 1;
1888: map[i][j] = exploreGoalValue(i, j);
1889: }
1890: } else if (theItem
1891: && !monsterAvoids(&player, i, j)) {
1892: if (theItem->flags & ITEM_PLAYER_AVOIDS) {
1893: costMap[i][j] = 20;
1894: } else {
1895: costMap[i][j] = 1;
1896: map[i][j] = exploreGoalValue(i, j) - 10;
1897: }
1898: }
1899: }
1900: }
1901:
1902: costMap[rogue.downLoc[0]][rogue.downLoc[1]] = 100;
1903: costMap[rogue.upLoc[0]][rogue.upLoc[1]] = 100;
1904:
1905: if (headingToStairs) {
1906: map[rogue.downLoc[0]][rogue.downLoc[1]] = 0; // head to the stairs
1907: }
1908:
1909: dijkstraScan(map, costMap, true);
1910:
1911: //displayGrid(costMap);
1912: freeGrid(costMap);
1913: }
1914:
1915: boolean explore(short frameDelay) {
1916: short **distanceMap;
1917: short path[1000][2], steps;
1918: boolean madeProgress, headingToStairs;
1919: enum directions dir;
1920: creature *monst;
1921:
1922: // Explore commands should never be written to a recording.
1923: // Instead, the elemental movement commands that compose it
1924: // should be written individually.
1925: brogueAssert(!rogue.playbackMode);
1926:
1927: clearCursorPath();
1928:
1929: madeProgress = false;
1930: headingToStairs = false;
1931:
1932: if (player.status[STATUS_CONFUSED]) {
1933: message("Not while you're confused.", false);
1934: return false;
1935: }
1936: if (cellHasTerrainFlag(player.xLoc, player.yLoc, T_OBSTRUCTS_PASSABILITY)) {
1937: message("Not while you're trapped.", false);
1938: return false;
1939: }
1940:
1941: for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
1942: if (canSeeMonster(monst)) {
1943: monst->bookkeepingFlags |= MB_ALREADY_SEEN;
1944: } else {
1945: monst->bookkeepingFlags &= ~MB_ALREADY_SEEN;
1946: }
1947: }
1948:
1949: // fight any adjacent enemies
1950: dir = adjacentFightingDir();
1951: if (dir != NO_DIRECTION
1952: && startFighting(dir, (player.status[STATUS_HALLUCINATING] ? true : false))) {
1953:
1954: return true;
1955: }
1956:
1957: if (!rogue.autoPlayingLevel) {
1958: message(KEYBOARD_LABELS ? "Exploring... press any key to stop." : "Exploring... touch anywhere to stop.",
1959: false);
1960: // A little hack so the exploring message remains bright while exploring and then auto-dims when
1961: // another message is displayed:
1962: confirmMessages();
1963: printString(KEYBOARD_LABELS ? "Exploring... press any key to stop." : "Exploring... touch anywhere to stop.",
1964: mapToWindowX(0), mapToWindowY(-1), &white, &black, NULL);
1965: }
1966: rogue.disturbed = false;
1967: rogue.automationActive = true;
1968:
1969: distanceMap = allocGrid();
1970: do {
1971: // fight any adjacent enemies
1972: dir = adjacentFightingDir();
1973: if (dir != NO_DIRECTION) {
1974: startFighting(dir, (player.status[STATUS_HALLUCINATING] ? true : false));
1975: if (rogue.disturbed) {
1976: madeProgress = true;
1977: continue;
1978: }
1979: }
1980: if (rogue.disturbed) {
1981: continue;
1982: }
1983:
1984: getExploreMap(distanceMap, headingToStairs);
1985:
1986: // hilite path
1987: steps = getPlayerPathOnMap(path, distanceMap, player.xLoc, player.yLoc);
1988: hilitePath(path, steps, false);
1989:
1990: // take a step
1991: dir = nextStep(distanceMap, player.xLoc, player.yLoc, NULL, false);
1992:
1993: if (!headingToStairs && rogue.autoPlayingLevel && dir == NO_DIRECTION) {
1994: headingToStairs = true;
1995: continue;
1996: }
1997:
1998: refreshSideBar(-1, -1, false);
1999:
2000: if (dir == NO_DIRECTION) {
2001: rogue.disturbed = true;
2002: } else if (!playerMoves(dir)) {
2003: rogue.disturbed = true;
2004: } else {
2005: madeProgress = true;
2006: if (pauseBrogue(frameDelay)) {
2007: rogue.disturbed = true;
2008: rogue.autoPlayingLevel = false;
2009: }
2010: }
2011: hilitePath(path, steps, true);
2012: } while (!rogue.disturbed);
2013: //clearCursorPath();
2014: rogue.automationActive = false;
2015: refreshSideBar(-1, -1, false);
2016: freeGrid(distanceMap);
2017: return madeProgress;
2018: }
2019:
2020: void autoPlayLevel(boolean fastForward) {
2021: boolean madeProgress;
2022:
2023: rogue.autoPlayingLevel = true;
2024:
2025: confirmMessages();
2026: message(KEYBOARD_LABELS ? "Playing... press any key to stop." : "Playing... touch anywhere to stop.", false);
2027:
2028: // explore until we are not making progress
2029: do {
2030: madeProgress = explore(fastForward ? 1 : 50);
2031: //refreshSideBar(-1, -1, false);
2032:
2033: if (!madeProgress && rogue.downLoc[0] == player.xLoc && rogue.downLoc[1] == player.yLoc) {
2034: useStairs(1);
2035: madeProgress = true;
2036: }
2037: } while (madeProgress && rogue.autoPlayingLevel);
2038:
2039: confirmMessages();
2040:
2041: rogue.autoPlayingLevel = false;
2042: }
2043:
2044: short directionOfKeypress(unsigned short ch) {
2045: switch (ch) {
2046: case LEFT_KEY:
2047: case LEFT_ARROW:
2048: case NUMPAD_4:
2049: return LEFT;
2050: case RIGHT_KEY:
2051: case RIGHT_ARROW:
2052: case NUMPAD_6:
2053: return RIGHT;
2054: case UP_KEY:
2055: case UP_ARROW:
2056: case NUMPAD_8:
2057: return UP;
2058: case DOWN_KEY:
2059: case DOWN_ARROW:
2060: case NUMPAD_2:
2061: return DOWN;
2062: case UPLEFT_KEY:
2063: case NUMPAD_7:
2064: return UPLEFT;
2065: case UPRIGHT_KEY:
2066: case NUMPAD_9:
2067: return UPRIGHT;
2068: case DOWNLEFT_KEY:
2069: case NUMPAD_1:
2070: return DOWNLEFT;
2071: case DOWNRIGHT_KEY:
2072: case NUMPAD_3:
2073: return DOWNRIGHT;
2074: default:
2075: return -1;
2076: }
2077: }
2078:
2079: boolean startFighting(enum directions dir, boolean tillDeath) {
2080: short x, y, expectedDamage;
2081: creature *monst;
2082:
2083: x = player.xLoc + nbDirs[dir][0];
2084: y = player.yLoc + nbDirs[dir][1];
2085: monst = monsterAtLoc(x, y);
2086: if (monst->info.flags & (MONST_IMMUNE_TO_WEAPONS | MONST_INVULNERABLE)) {
2087: return false;
2088: }
2089: expectedDamage = monst->info.damage.upperBound * monsterDamageAdjustmentAmount(monst) / FP_FACTOR;
2090: if (rogue.easyMode) {
2091: expectedDamage /= 5;
2092: }
2093: rogue.blockCombatText = true;
2094: rogue.disturbed = false;
2095: do {
2096: if (!playerMoves(dir)) {
2097: break;
2098: }
2099: if (pauseBrogue(1)) {
2100: break;
2101: }
2102: } while (!rogue.disturbed && !rogue.gameHasEnded && (tillDeath || player.currentHP > expectedDamage)
2103: && (pmap[x][y].flags & HAS_MONSTER) && monsterAtLoc(x, y) == monst);
2104:
2105: rogue.blockCombatText = false;
2106: return rogue.disturbed;
2107: }
2108:
2109: boolean isDisturbed(short x, short y) {
2110: short i;
2111: creature *monst;
2112: for (i=0; i< DIRECTION_COUNT; i++) {
2113: monst = monsterAtLoc(x + nbDirs[i][0], y + nbDirs[i][1]);
2114: if (pmap[x + nbDirs[i][0]][y + nbDirs[i][1]].flags & (HAS_ITEM)) {
2115: // Do not trigger for submerged or invisible or unseen monsters.
2116: return true;
2117: }
2118: if (monst
2119: && !(monst->creatureState == MONSTER_ALLY)
2120: && (canSeeMonster(monst) || monsterRevealed(monst))) {
2121: // Do not trigger for submerged or invisible or unseen monsters.
2122: return true;
2123: }
2124: }
2125: return false;
2126: }
2127:
2128: void discover(short x, short y) {
2129: enum dungeonLayers layer;
2130: dungeonFeature *feat;
2131: if (cellHasTMFlag(x, y, TM_IS_SECRET)) {
2132:
2133: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
2134: if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_IS_SECRET) {
2135: feat = &dungeonFeatureCatalog[tileCatalog[pmap[x][y].layers[layer]].discoverType];
2136: pmap[x][y].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING);
2137: spawnDungeonFeature(x, y, feat, true, false);
2138: }
2139: }
2140: refreshDungeonCell(x, y);
2141:
2142: if (playerCanSee(x, y)) {
2143: rogue.disturbed = true;
2144: }
2145: }
2146: }
2147:
2148: // returns true if found anything
2149: boolean search(short searchStrength) {
2150: short i, j, radius, x, y, percent;
2151: boolean foundSomething = false;
2152:
2153: radius = searchStrength / 10;
2154: x = player.xLoc;
2155: y = player.yLoc;
2156:
2157: for (i = x - radius; i <= x + radius; i++) {
2158: for (j = y - radius; j <= y + radius; j++) {
2159: if (coordinatesAreInMap(i, j)
2160: && playerCanDirectlySee(i, j)) {
2161:
2162: percent = searchStrength - distanceBetween(x, y, i, j) * 10;
2163: if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)) {
2164: percent = percent * 2/3;
2165: }
2166: if (percent >= 100) {
2167: pmap[i][j].flags |= KNOWN_TO_BE_TRAP_FREE;
2168: }
2169: percent = min(percent, 100);
2170: if (cellHasTMFlag(i, j, TM_IS_SECRET)) {
2171: if (rand_percent(percent)) {
2172: discover(i, j);
2173: foundSomething = true;
2174: }
2175: }
2176: }
2177: }
2178: }
2179: return foundSomething;
2180: }
2181:
2182: boolean proposeOrConfirmLocation(short x, short y, char *failureMessage) {
2183: boolean retval = false;
2184: if (player.xLoc == x && player.yLoc == y) {
2185: message("you are already there.", false);
2186: } else if (pmap[x][y].flags & (DISCOVERED | MAGIC_MAPPED)) {
2187: if (rogue.cursorLoc[0] == x && rogue.cursorLoc[1] == y) {
2188: retval = true;
2189: } else {
2190: rogue.cursorLoc[0] = x;
2191: rogue.cursorLoc[1] = y;
2192: }
2193: } else {
2194: message(failureMessage, false);
2195: }
2196: return retval;
2197: }
2198:
2199: boolean useStairs(short stairDirection) {
2200: boolean succeeded = false;
2201: //cellDisplayBuffer fromBuf[COLS][ROWS], toBuf[COLS][ROWS];
2202:
2203: if (stairDirection == 1) {
2204: if (rogue.depthLevel < DEEPEST_LEVEL) {
2205: //copyDisplayBuffer(fromBuf, displayBuffer);
2206: rogue.cursorLoc[0] = rogue.cursorLoc[1] = -1;
2207: rogue.depthLevel++;
2208: message("You descend.", false);
2209: startLevel(rogue.depthLevel - 1, stairDirection);
2210: if (rogue.depthLevel > rogue.deepestLevel) {
2211: rogue.deepestLevel = rogue.depthLevel;
2212: }
2213: //copyDisplayBuffer(toBuf, displayBuffer);
2214: //irisFadeBetweenBuffers(fromBuf, toBuf, mapToWindowX(player.xLoc), mapToWindowY(player.yLoc), 20, false);
2215: } else if (numberOfMatchingPackItems(AMULET, 0, 0, false)) {
2216: victory(true);
2217: } else {
2218: confirmMessages();
2219: messageWithColor("the crystal archway repels you with a mysterious force!", &lightBlue, false);
2220: messageWithColor("(Only the bearer of the Amulet of Yendor may pass.)", &backgroundMessageColor, false);
2221: }
2222: succeeded = true;
2223: } else {
2224: if (rogue.depthLevel > 1 || numberOfMatchingPackItems(AMULET, 0, 0, false)) {
2225: rogue.cursorLoc[0] = rogue.cursorLoc[1] = -1;
2226: rogue.depthLevel--;
2227: if (rogue.depthLevel == 0) {
2228: victory(false);
2229: } else {
2230: //copyDisplayBuffer(fromBuf, displayBuffer);
2231: message("You ascend.", false);
2232: startLevel(rogue.depthLevel + 1, stairDirection);
2233: //copyDisplayBuffer(toBuf, displayBuffer);
2234: //irisFadeBetweenBuffers(fromBuf, toBuf, mapToWindowX(player.xLoc), mapToWindowY(player.yLoc), 20, true);
2235: }
2236: succeeded = true;
2237: } else {
2238: confirmMessages();
2239: messageWithColor("The dungeon exit is magically sealed!", &lightBlue, false);
2240: messageWithColor("(Only the bearer of the Amulet of Yendor may pass.)", &backgroundMessageColor, false);
2241: }
2242: }
2243:
2244: if (succeeded) {
2245: updatePlayerUnderwaterness();
2246: }
2247:
2248: return succeeded;
2249: }
2250:
2251: void storeMemories(const short x, const short y) {
2252: pmap[x][y].rememberedTerrainFlags = terrainFlags(x, y);
2253: pmap[x][y].rememberedTMFlags = terrainMechFlags(x, y);
2254: pmap[x][y].rememberedCellFlags = pmap[x][y].flags;
2255: pmap[x][y].rememberedTerrain = pmap[x][y].layers[highestPriorityLayer(x, y, false)];
2256: }
2257:
2258: void updateFieldOfViewDisplay(boolean updateDancingTerrain, boolean refreshDisplay) {
2259: short i, j;
2260: item *theItem;
2261: char buf[COLS*3], name[COLS*3];
2262:
2263: assureCosmeticRNG;
2264:
2265: for (i=0; i<DCOLS; i++) {
2266: for (j = DROWS-1; j >= 0; j--) {
2267: if (pmap[i][j].flags & IN_FIELD_OF_VIEW
2268: && (max(0, tmap[i][j].light[0])
2269: + max(0, tmap[i][j].light[1])
2270: + max(0, tmap[i][j].light[2]) > VISIBILITY_THRESHOLD)
2271: && !(pmap[i][j].flags & CLAIRVOYANT_DARKENED)) {
2272:
2273: pmap[i][j].flags |= VISIBLE;
2274: }
2275:
2276: if ((pmap[i][j].flags & VISIBLE) && !(pmap[i][j].flags & WAS_VISIBLE)) { // if the cell became visible this move
2277: if (!(pmap[i][j].flags & DISCOVERED) && rogue.automationActive) {
2278: if (pmap[i][j].flags & HAS_ITEM) {
2279: theItem = itemAtLoc(i, j);
2280: if (theItem && (theItem->category & KEY)) {
2281: itemName(theItem, name, false, true, NULL);
2282: sprintf(buf, "you see %s.", name);
2283: messageWithColor(buf, &itemMessageColor, false);
2284: }
2285: }
2286: if (!(pmap[i][j].flags & MAGIC_MAPPED)
2287: && cellHasTMFlag(i, j, TM_INTERRUPT_EXPLORATION_WHEN_SEEN)) {
2288:
2289: strcpy(name, tileCatalog[pmap[i][j].layers[layerWithTMFlag(i, j, TM_INTERRUPT_EXPLORATION_WHEN_SEEN)]].description);
2290: sprintf(buf, "you see %s.", name);
2291: messageWithColor(buf, &backgroundMessageColor, false);
2292: }
2293: }
2294: discoverCell(i, j);
2295: if (refreshDisplay) {
2296: refreshDungeonCell(i, j);
2297: }
2298: } else if (!(pmap[i][j].flags & VISIBLE) && (pmap[i][j].flags & WAS_VISIBLE)) { // if the cell ceased being visible this move
2299: storeMemories(i, j);
2300: if (refreshDisplay) {
2301: refreshDungeonCell(i, j);
2302: }
2303: } else if (!(pmap[i][j].flags & CLAIRVOYANT_VISIBLE) && (pmap[i][j].flags & WAS_CLAIRVOYANT_VISIBLE)) { // ceased being clairvoyantly visible
2304: storeMemories(i, j);
2305: if (refreshDisplay) {
2306: refreshDungeonCell(i, j);
2307: }
2308: } else if (!(pmap[i][j].flags & WAS_CLAIRVOYANT_VISIBLE) && (pmap[i][j].flags & CLAIRVOYANT_VISIBLE)) { // became clairvoyantly visible
2309: pmap[i][j].flags &= ~STABLE_MEMORY;
2310: if (refreshDisplay) {
2311: refreshDungeonCell(i, j);
2312: }
2313: } else if (!(pmap[i][j].flags & TELEPATHIC_VISIBLE) && (pmap[i][j].flags & WAS_TELEPATHIC_VISIBLE)) { // ceased being telepathically visible
2314: storeMemories(i, j);
2315: if (refreshDisplay) {
2316: refreshDungeonCell(i, j);
2317: }
2318: } else if (!(pmap[i][j].flags & WAS_TELEPATHIC_VISIBLE) && (pmap[i][j].flags & TELEPATHIC_VISIBLE)) { // became telepathically visible
2319: if (!(pmap[i][j].flags & DISCOVERED)
2320: && !cellHasTerrainFlag(i, j, T_PATHING_BLOCKER)) {
2321: rogue.xpxpThisTurn++;
2322: }
2323:
2324: pmap[i][j].flags &= ~STABLE_MEMORY;
2325: if (refreshDisplay) {
2326: refreshDungeonCell(i, j);
2327: }
2328: } else if (playerCanSeeOrSense(i, j)
2329: && (tmap[i][j].light[0] != tmap[i][j].oldLight[0] ||
2330: tmap[i][j].light[1] != tmap[i][j].oldLight[1] ||
2331: tmap[i][j].light[2] != tmap[i][j].oldLight[2])) { // if the cell's light color changed this move
2332:
2333: if (refreshDisplay) {
2334: refreshDungeonCell(i, j);
2335: }
2336: } else if (updateDancingTerrain
2337: && playerCanSee(i, j)
2338: && (!rogue.automationActive || !(rogue.playerTurnNumber % 5))
2339: && ((tileCatalog[pmap[i][j].layers[DUNGEON]].backColor) && tileCatalog[pmap[i][j].layers[DUNGEON]].backColor->colorDances
2340: || (tileCatalog[pmap[i][j].layers[DUNGEON]].foreColor) && tileCatalog[pmap[i][j].layers[DUNGEON]].foreColor->colorDances
2341: || (tileCatalog[pmap[i][j].layers[LIQUID]].backColor) && tileCatalog[pmap[i][j].layers[LIQUID]].backColor->colorDances
2342: || (tileCatalog[pmap[i][j].layers[LIQUID]].foreColor) && tileCatalog[pmap[i][j].layers[LIQUID]].foreColor->colorDances
2343: || (tileCatalog[pmap[i][j].layers[SURFACE]].backColor) && tileCatalog[pmap[i][j].layers[SURFACE]].backColor->colorDances
2344: || (tileCatalog[pmap[i][j].layers[SURFACE]].foreColor) && tileCatalog[pmap[i][j].layers[SURFACE]].foreColor->colorDances
2345: || (tileCatalog[pmap[i][j].layers[GAS]].backColor) && tileCatalog[pmap[i][j].layers[GAS]].backColor->colorDances
2346: || (tileCatalog[pmap[i][j].layers[GAS]].foreColor) && tileCatalog[pmap[i][j].layers[GAS]].foreColor->colorDances
2347: || player.status[STATUS_HALLUCINATING])) {
2348:
2349: pmap[i][j].flags &= ~STABLE_MEMORY;
2350: if (refreshDisplay) {
2351: refreshDungeonCell(i, j);
2352: }
2353: }
2354: }
2355: }
2356: restoreRNG;
2357: }
2358:
2359: // Octants: //
2360: // \7|8/ //
2361: // 6\|/1 //
2362: // --@-- //
2363: // 5/|\2 //
2364: // /4|3\ //
2365:
2366: void betweenOctant1andN(short *x, short *y, short x0, short y0, short n) {
2367: short x1 = *x, y1 = *y;
2368: short dx = x1 - x0, dy = y1 - y0;
2369: switch (n) {
2370: case 1:
2371: return;
2372: case 2:
2373: *y = y0 - dy;
2374: return;
2375: case 5:
2376: *x = x0 - dx;
2377: *y = y0 - dy;
2378: return;
2379: case 6:
2380: *x = x0 - dx;
2381: return;
2382: case 8:
2383: *x = x0 - dy;
2384: *y = y0 - dx;
2385: return;
2386: case 3:
2387: *x = x0 - dy;
2388: *y = y0 + dx;
2389: return;
2390: case 7:
2391: *x = x0 + dy;
2392: *y = y0 - dx;
2393: return;
2394: case 4:
2395: *x = x0 + dy;
2396: *y = y0 + dx;
2397: return;
2398: }
2399: }
2400:
2401: // Returns a boolean grid indicating whether each square is in the field of view of (xLoc, yLoc).
2402: // forbiddenTerrain is the set of terrain flags that will block vision (but the blocking cell itself is
2403: // illuminated); forbiddenFlags is the set of map flags that will block vision.
2404: // If cautiousOnWalls is set, we will not illuminate blocking tiles unless the tile one space closer to the origin
2405: // is visible to the player; this is to prevent lights from illuminating a wall when the player is on the other
2406: // side of the wall.
2407: void getFOVMask(char grid[DCOLS][DROWS], short xLoc, short yLoc, fixpt maxRadius,
2408: unsigned long forbiddenTerrain, unsigned long forbiddenFlags, boolean cautiousOnWalls) {
2409: short i;
2410:
2411: for (i=1; i<=8; i++) {
2412: scanOctantFOV(grid, xLoc, yLoc, i, maxRadius, 1, LOS_SLOPE_GRANULARITY * -1, 0,
2413: forbiddenTerrain, forbiddenFlags, cautiousOnWalls);
2414: }
2415: }
2416:
2417: // This is a custom implementation of recursive shadowcasting.
2418: void scanOctantFOV(char grid[DCOLS][DROWS], short xLoc, short yLoc, short octant, fixpt maxRadius,
2419: short columnsRightFromOrigin, long startSlope, long endSlope, unsigned long forbiddenTerrain,
2420: unsigned long forbiddenFlags, boolean cautiousOnWalls) {
2421:
2422: if (columnsRightFromOrigin * FP_FACTOR >= maxRadius) return;
2423:
2424: short i, a, b, iStart, iEnd, x, y, x2, y2; // x and y are temporary variables on which we do the octant transform
2425: long newStartSlope, newEndSlope;
2426: boolean cellObstructed;
2427:
2428: newStartSlope = startSlope;
2429:
2430: a = ((LOS_SLOPE_GRANULARITY / -2 + 1) + startSlope * columnsRightFromOrigin) / LOS_SLOPE_GRANULARITY;
2431: b = ((LOS_SLOPE_GRANULARITY / -2 + 1) + endSlope * columnsRightFromOrigin) / LOS_SLOPE_GRANULARITY;
2432:
2433: iStart = min(a, b);
2434: iEnd = max(a, b);
2435:
2436: // restrict vision to a circle of radius maxRadius
2437: if ((columnsRightFromOrigin*columnsRightFromOrigin + iEnd*iEnd) >= maxRadius*maxRadius / FP_FACTOR / FP_FACTOR) {
2438: return;
2439: }
2440: if ((columnsRightFromOrigin*columnsRightFromOrigin + iStart*iStart) >= maxRadius*maxRadius / FP_FACTOR / FP_FACTOR) {
2441: iStart = (int) (-1 * fp_sqrt((maxRadius*maxRadius / FP_FACTOR) - (columnsRightFromOrigin*columnsRightFromOrigin * FP_FACTOR)) / FP_FACTOR);
2442: }
2443:
2444: x = xLoc + columnsRightFromOrigin;
2445: y = yLoc + iStart;
2446: betweenOctant1andN(&x, &y, xLoc, yLoc, octant);
2447: boolean currentlyLit = coordinatesAreInMap(x, y) && !(cellHasTerrainFlag(x, y, forbiddenTerrain) ||
2448: (pmap[x][y].flags & forbiddenFlags));
2449: for (i = iStart; i <= iEnd; i++) {
2450: x = xLoc + columnsRightFromOrigin;
2451: y = yLoc + i;
2452: betweenOctant1andN(&x, &y, xLoc, yLoc, octant);
2453: if (!coordinatesAreInMap(x, y)) {
2454: // We're off the map -- here there be memory corruption.
2455: continue;
2456: }
2457: cellObstructed = (cellHasTerrainFlag(x, y, forbiddenTerrain) || (pmap[x][y].flags & forbiddenFlags));
2458: // if we're cautious on walls and this is a wall:
2459: if (cautiousOnWalls && cellObstructed) {
2460: // (x2, y2) is the tile one space closer to the origin from the tile we're on:
2461: x2 = xLoc + columnsRightFromOrigin - 1;
2462: y2 = yLoc + i;
2463: if (i < 0) {
2464: y2++;
2465: } else if (i > 0) {
2466: y2--;
2467: }
2468: betweenOctant1andN(&x2, &y2, xLoc, yLoc, octant);
2469:
2470: if (pmap[x2][y2].flags & IN_FIELD_OF_VIEW) {
2471: // previous tile is visible, so illuminate
2472: grid[x][y] = 1;
2473: }
2474: } else {
2475: // illuminate
2476: grid[x][y] = 1;
2477: }
2478: if (!cellObstructed && !currentlyLit) { // next column slope starts here
2479: newStartSlope = (long int) ((LOS_SLOPE_GRANULARITY * (i) - LOS_SLOPE_GRANULARITY / 2) / (columnsRightFromOrigin * 2 + 1) * 2);
2480: currentlyLit = true;
2481: } else if (cellObstructed && currentlyLit) { // next column slope ends here
2482: newEndSlope = (long int) ((LOS_SLOPE_GRANULARITY * (i) - LOS_SLOPE_GRANULARITY / 2)
2483: / (columnsRightFromOrigin * 2 - 1) * 2);
2484: if (newStartSlope <= newEndSlope) {
2485: // run next column
2486: scanOctantFOV(grid, xLoc, yLoc, octant, maxRadius, columnsRightFromOrigin + 1, newStartSlope, newEndSlope,
2487: forbiddenTerrain, forbiddenFlags, cautiousOnWalls);
2488: }
2489: currentlyLit = false;
2490: }
2491: }
2492: if (currentlyLit) { // got to the bottom of the scan while lit
2493: newEndSlope = endSlope;
2494: if (newStartSlope <= newEndSlope) {
2495: // run next column
2496: scanOctantFOV(grid, xLoc, yLoc, octant, maxRadius, columnsRightFromOrigin + 1, newStartSlope, newEndSlope,
2497: forbiddenTerrain, forbiddenFlags, cautiousOnWalls);
2498: }
2499: }
2500: }
2501:
2502: void addScentToCell(short x, short y, short distance) {
2503: unsigned short value;
2504: if (!cellHasTerrainFlag(x, y, T_OBSTRUCTS_SCENT) || !cellHasTerrainFlag(x, y, T_OBSTRUCTS_PASSABILITY)) {
2505: value = rogue.scentTurnNumber - distance;
2506: scentMap[x][y] = max(value, (unsigned short) scentMap[x][y]);
2507: }
2508: }
CVSweb