Annotation of brogue-ce/src/brogue/Time.c, Revision 1.1.1.1
1.1 rubenllo 1: /*
2: * Time.c
3: * Brogue
4: *
5: * Created by Brian Walker on 6/21/13.
6: * Copyright 2013. 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 exposeCreatureToFire(creature *monst) {
28: char buf[COLS], buf2[COLS];
29: if ((monst->bookkeepingFlags & MB_IS_DYING)
30: || monst->status[STATUS_IMMUNE_TO_FIRE]
31: || (monst->info.flags & MONST_INVULNERABLE)
32: || (monst->bookkeepingFlags & MB_SUBMERGED)
33: || ((!monst->status[STATUS_LEVITATING]) && cellHasTMFlag(monst->xLoc, monst->yLoc, TM_EXTINGUISHES_FIRE))) {
34: return;
35: }
36: if (monst->status[STATUS_BURNING] == 0) {
37: if (monst == &player) {
38: rogue.minersLight.lightColor = &fireForeColor;
39: player.info.foreColor = &torchLightColor;
40: refreshDungeonCell(player.xLoc, player.yLoc);
41: //updateVision(); // this screws up the firebolt visual effect by erasing it while a message is displayed
42: combatMessage("you catch fire", &badMessageColor);
43: } else if (canDirectlySeeMonster(monst)) {
44: monsterName(buf, monst, true);
45: sprintf(buf2, "%s catches fire", buf);
46: combatMessage(buf2, messageColorFromVictim(monst));
47: }
48: }
49: monst->status[STATUS_BURNING] = monst->maxStatus[STATUS_BURNING] = max(monst->status[STATUS_BURNING], 7);
50: }
51:
52: void updateFlavorText() {
53: char buf[DCOLS * 3];
54: if (rogue.disturbed && !rogue.gameHasEnded) {
55: if (rogue.armor
56: && (rogue.armor->flags & ITEM_RUNIC)
57: && rogue.armor->enchant2 == A_RESPIRATION
58: && tileCatalog[pmap[player.xLoc][player.yLoc].layers[highestPriorityLayer(player.xLoc, player.yLoc, false)]].flags & T_RESPIRATION_IMMUNITIES) {
59:
60: flavorMessage("A pocket of cool, clean air swirls around you.");
61: } else if (player.status[STATUS_LEVITATING]) {
62: describeLocation(buf, player.xLoc, player.yLoc);
63: flavorMessage(buf);
64: } else {
65: flavorMessage(tileFlavor(player.xLoc, player.yLoc));
66: }
67: }
68: }
69:
70: void updatePlayerUnderwaterness() {
71: if (rogue.inWater) {
72: if (!cellHasTerrainFlag(player.xLoc, player.yLoc, T_IS_DEEP_WATER) || player.status[STATUS_LEVITATING]
73: || cellHasTerrainFlag(player.xLoc, player.yLoc, (T_ENTANGLES | T_OBSTRUCTS_PASSABILITY))) {
74:
75: rogue.inWater = false;
76: updateMinersLightRadius();
77: updateVision(true);
78: displayLevel();
79: }
80: } else {
81: if (cellHasTerrainFlag(player.xLoc, player.yLoc, T_IS_DEEP_WATER) && !player.status[STATUS_LEVITATING]
82: && !cellHasTerrainFlag(player.xLoc, player.yLoc, (T_ENTANGLES | T_OBSTRUCTS_PASSABILITY))) {
83:
84: rogue.inWater = true;
85: updateMinersLightRadius();
86: updateVision(true);
87: displayLevel();
88: }
89: }
90: }
91:
92: boolean monsterShouldFall(creature *monst) {
93: return (!(monst->status[STATUS_LEVITATING])
94: && cellHasTerrainFlag(monst->xLoc, monst->yLoc, T_AUTO_DESCENT)
95: && !cellHasTerrainFlag(monst->xLoc, monst->yLoc, T_ENTANGLES | T_OBSTRUCTS_PASSABILITY)
96: && !(monst->bookkeepingFlags & MB_PREPLACED));
97: }
98:
99: // Called at least every 100 ticks; may be called more frequently.
100: void applyInstantTileEffectsToCreature(creature *monst) {
101: char buf[COLS], buf2[COLS], buf3[COLS];
102: short *x = &(monst->xLoc), *y = &(monst->yLoc), damage;
103: enum dungeonLayers layer;
104: item *theItem;
105:
106: if (monst->bookkeepingFlags & MB_IS_DYING) {
107: return; // the monster is already dead.
108: }
109:
110: if (monst == &player) {
111: if (!player.status[STATUS_LEVITATING]) {
112: pmap[*x][*y].flags |= KNOWN_TO_BE_TRAP_FREE;
113: }
114: } else if (!player.status[STATUS_HALLUCINATING]
115: && !monst->status[STATUS_LEVITATING]
116: && canSeeMonster(monst)
117: && !(cellHasTerrainFlag(*x, *y, T_IS_DF_TRAP))) {
118: pmap[*x][*y].flags |= KNOWN_TO_BE_TRAP_FREE;
119: }
120:
121: // You will discover the secrets of any tile you stand on.
122: if (monst == &player
123: && !(monst->status[STATUS_LEVITATING])
124: && cellHasTMFlag(*x, *y, TM_IS_SECRET)
125: && playerCanSee(*x, *y)) {
126:
127: discover(*x, *y);
128: }
129:
130: // Submerged monsters in terrain that doesn't permit submersion should immediately surface.
131: if ((monst->bookkeepingFlags & MB_SUBMERGED) && !cellHasTMFlag(*x, *y, TM_ALLOWS_SUBMERGING)) {
132: monst->bookkeepingFlags &= ~MB_SUBMERGED;
133: }
134:
135: // Visual effect for submersion in water.
136: if (monst == &player) {
137: updatePlayerUnderwaterness();
138: }
139:
140: // Obstructed krakens can't seize their prey.
141: if ((monst->bookkeepingFlags & MB_SEIZING)
142: && (cellHasTerrainFlag(*x, *y, T_OBSTRUCTS_PASSABILITY))
143: && !(monst->info.flags & MONST_ATTACKABLE_THRU_WALLS)) {
144:
145: monst->bookkeepingFlags &= ~MB_SEIZING;
146: }
147:
148: // Creatures plunge into chasms and through trap doors.
149: if (monsterShouldFall(monst)) {
150: if (monst == &player) {
151: // player falling takes place at the end of the turn
152: if (!(monst->bookkeepingFlags & MB_IS_FALLING)) {
153: monst->bookkeepingFlags |= MB_IS_FALLING;
154: }
155: return;
156: } else { // it's a monster
157: monst->bookkeepingFlags |= MB_IS_FALLING; // handled at end of turn
158: }
159: }
160:
161: // lava
162: if (!(monst->status[STATUS_LEVITATING])
163: && !(monst->status[STATUS_IMMUNE_TO_FIRE])
164: && !(monst->info.flags & MONST_INVULNERABLE)
165: && !cellHasTerrainFlag(*x, *y, (T_ENTANGLES | T_OBSTRUCTS_PASSABILITY))
166: && !cellHasTMFlag(*x, *y, TM_EXTINGUISHES_FIRE)
167: && cellHasTerrainFlag(*x, *y, T_LAVA_INSTA_DEATH)) {
168:
169: if (monst == &player) {
170: sprintf(buf, "you plunge into %s!",
171: tileCatalog[pmap[*x][*y].layers[layerWithFlag(*x, *y, T_LAVA_INSTA_DEATH)]].description);
172: message(buf, true);
173: sprintf(buf, "Killed by %s",
174: tileCatalog[pmap[*x][*y].layers[layerWithFlag(*x, *y, T_LAVA_INSTA_DEATH)]].description);
175: gameOver(buf, true);
176: return;
177: } else { // it's a monster
178: if (canSeeMonster(monst)) {
179: monsterName(buf, monst, true);
180: sprintf(buf2, "%s is consumed by the %s instantly!", buf,
181: tileCatalog[pmap[*x][*y].layers[layerWithFlag(*x, *y, T_LAVA_INSTA_DEATH)]].description);
182: messageWithColor(buf2, messageColorFromVictim(monst), false);
183: }
184: killCreature(monst, false);
185: spawnDungeonFeature(*x, *y, &(dungeonFeatureCatalog[DF_CREATURE_FIRE]), true, false);
186: refreshDungeonCell(*x, *y);
187: return;
188: }
189: }
190:
191: // Water puts out fire.
192: if (cellHasTMFlag(*x, *y, TM_EXTINGUISHES_FIRE)
193: && monst->status[STATUS_BURNING]
194: && !monst->status[STATUS_LEVITATING]
195: && !(monst->info.flags & MONST_ATTACKABLE_THRU_WALLS)
196: && !(monst->info.flags & MONST_FIERY)) {
197: extinguishFireOnCreature(monst);
198: }
199:
200: // If you see a monster use a secret door, you discover it.
201: if (playerCanSee(*x, *y)
202: && cellHasTMFlag(*x, *y, TM_IS_SECRET)
203: && (cellHasTerrainFlag(*x, *y, T_OBSTRUCTS_PASSABILITY))) {
204: discover(*x, *y);
205: }
206:
207: // Pressure plates.
208: if (!(monst->status[STATUS_LEVITATING])
209: && !(monst->bookkeepingFlags & MB_SUBMERGED)
210: && (!cellHasTMFlag(*x, *y, TM_ALLOWS_SUBMERGING) || !(monst->info.flags & MONST_SUBMERGES))
211: && cellHasTerrainFlag(*x, *y, T_IS_DF_TRAP)
212: && !(pmap[*x][*y].flags & PRESSURE_PLATE_DEPRESSED)) {
213:
214: pmap[*x][*y].flags |= PRESSURE_PLATE_DEPRESSED;
215: if (playerCanSee(*x, *y) && cellHasTMFlag(*x, *y, TM_IS_SECRET)) {
216: discover(*x, *y);
217: refreshDungeonCell(*x, *y);
218: }
219: if (canSeeMonster(monst)) {
220: monsterName(buf, monst, true);
221: sprintf(buf2, "a pressure plate clicks underneath %s!", buf);
222: message(buf2, true);
223: } else if (playerCanSee(*x, *y)) {
224: // usually means an invisible monster
225: message("a pressure plate clicks!", false);
226: }
227: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
228: if (tileCatalog[pmap[*x][*y].layers[layer]].flags & T_IS_DF_TRAP) {
229: spawnDungeonFeature(*x, *y, &(dungeonFeatureCatalog[tileCatalog[pmap[*x][*y].layers[layer]].fireType]), true, false);
230: promoteTile(*x, *y, layer, false);
231: }
232: }
233: }
234:
235: if (cellHasTMFlag(*x, *y, TM_PROMOTES_ON_STEP)) { // flying creatures activate too
236: // Because this uses no pressure plate to keep track of whether it's already depressed,
237: // it will trigger every time this function is called while the monster or player is on the tile.
238: // Because this function can be called several times per turn, multiple promotions can
239: // happen unpredictably if the tile does not promote to a tile without the T_PROMOTES_ON_STEP
240: // attribute. That's acceptable for some effects, e.g. doors opening,
241: // but not for others, e.g. magical glyphs activating.
242: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
243: if (tileCatalog[pmap[*x][*y].layers[layer]].mechFlags & TM_PROMOTES_ON_STEP) {
244: promoteTile(*x, *y, layer, false);
245: }
246: }
247: }
248:
249: if (cellHasTMFlag(*x, *y, TM_PROMOTES_ON_PLAYER_ENTRY) && monst == &player) {
250: // Subject to same caveats as T_PROMOTES_ON_STEP above.
251: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
252: if (tileCatalog[pmap[*x][*y].layers[layer]].mechFlags & TM_PROMOTES_ON_PLAYER_ENTRY) {
253: promoteTile(*x, *y, layer, false);
254: }
255: }
256: }
257:
258: if (cellHasTMFlag(*x, *y, TM_PROMOTES_ON_SACRIFICE_ENTRY)
259: && monst->machineHome == pmap[*x][*y].machineNumber
260: && (monst->bookkeepingFlags & MB_MARKED_FOR_SACRIFICE)) {
261: // Subject to same caveats as T_PROMOTES_ON_STEP above.
262: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
263: if (tileCatalog[pmap[*x][*y].layers[layer]].mechFlags & TM_PROMOTES_ON_SACRIFICE_ENTRY) {
264: promoteTile(*x, *y, layer, false);
265: }
266: }
267: }
268:
269: // spiderwebs
270: if (cellHasTerrainFlag(*x, *y, T_ENTANGLES) && !monst->status[STATUS_STUCK]
271: && !(monst->info.flags & (MONST_IMMUNE_TO_WEBS | MONST_INVULNERABLE))
272: && !(monst->bookkeepingFlags & MB_SUBMERGED)) {
273:
274: monst->status[STATUS_STUCK] = monst->maxStatus[STATUS_STUCK] = rand_range(3, 7);
275: if (monst == &player) {
276: if (!rogue.automationActive) {
277: // Don't interrupt exploration with this message.
278: sprintf(buf2, "you are stuck fast in %s!",
279: tileCatalog[pmap[*x][*y].layers[layerWithFlag(*x, *y, T_ENTANGLES)]].description);
280: message(buf2, false);
281: }
282: } else if (canDirectlySeeMonster(monst)) { // it's a monster
283: if (!rogue.automationActive) {
284: monsterName(buf, monst, true);
285: sprintf(buf2, "%s is stuck fast in %s!", buf,
286: tileCatalog[pmap[*x][*y].layers[layerWithFlag(*x, *y, T_ENTANGLES)]].description);
287: message(buf2, false);
288: }
289: }
290: }
291:
292: // explosions
293: if (cellHasTerrainFlag(*x, *y, T_CAUSES_EXPLOSIVE_DAMAGE) && !monst->status[STATUS_EXPLOSION_IMMUNITY]
294: && !(monst->bookkeepingFlags & MB_SUBMERGED)) {
295: damage = rand_range(15, 20);
296: damage = max(damage, monst->info.maxHP / 2);
297: monst->status[STATUS_EXPLOSION_IMMUNITY] = 5;
298: if (monst == &player) {
299: rogue.disturbed = true;
300: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS && !(tileCatalog[pmap[*x][*y].layers[layer]].flags & T_CAUSES_EXPLOSIVE_DAMAGE); layer++);
301: message(tileCatalog[pmap[*x][*y].layers[layer]].flavorText, false);
302: if (rogue.armor && (rogue.armor->flags & ITEM_RUNIC) && rogue.armor->enchant2 == A_DAMPENING) {
303: itemName(rogue.armor, buf2, false, false, NULL);
304: sprintf(buf, "Your %s pulses and absorbs the damage.", buf2);
305: messageWithColor(buf, &goodMessageColor, false);
306: autoIdentify(rogue.armor);
307: } else if (inflictDamage(NULL, &player, damage, &yellow, false)) {
308: strcpy(buf2, tileCatalog[pmap[*x][*y].layers[layerWithFlag(*x, *y, T_CAUSES_EXPLOSIVE_DAMAGE)]].description);
309: sprintf(buf, "Killed by %s", buf2);
310: gameOver(buf, true);
311: return;
312: }
313: } else { // it's a monster
314: if (monst->creatureState == MONSTER_SLEEPING) {
315: monst->creatureState = MONSTER_TRACKING_SCENT;
316: }
317: monsterName(buf, monst, true);
318:
319: // Get explosive layer before damage in case a death DF replaces the explosion
320: strcpy(buf3, tileCatalog[pmap[*x][*y].layers[layerWithFlag(*x, *y, T_CAUSES_EXPLOSIVE_DAMAGE)]].description);
321: if (inflictDamage(NULL, monst, damage, &yellow, false)) {
322: // if killed
323: sprintf(buf2, "%s %s %s.", buf,
324: (monst->info.flags & MONST_INANIMATE) ? "is destroyed by" : "dies in",
325: buf3);
326: messageWithColor(buf2, messageColorFromVictim(monst), false);
327: refreshDungeonCell(*x, *y);
328: return;
329: } else {
330: // if survived
331: sprintf(buf2, "%s engulfs %s.",
332: tileCatalog[pmap[*x][*y].layers[layerWithFlag(*x, *y, T_CAUSES_EXPLOSIVE_DAMAGE)]].description, buf);
333: messageWithColor(buf2, messageColorFromVictim(monst), false);
334: }
335: }
336: }
337:
338: // Toxic gases!
339: // If it's the player, and he's wearing armor of respiration, then no effect from toxic gases.
340: if (monst == &player
341: && cellHasTerrainFlag(*x, *y, T_RESPIRATION_IMMUNITIES)
342: && rogue.armor
343: && (rogue.armor->flags & ITEM_RUNIC)
344: && rogue.armor->enchant2 == A_RESPIRATION) {
345: if (!(rogue.armor->flags & ITEM_RUNIC_IDENTIFIED)) {
346: message("Your armor trembles and a pocket of clean air swirls around you.", false);
347: autoIdentify(rogue.armor);
348: }
349: } else {
350:
351: // zombie gas
352: if (cellHasTerrainFlag(*x, *y, T_CAUSES_NAUSEA)
353: && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))
354: && !(monst->bookkeepingFlags & MB_SUBMERGED)) {
355: if (monst == &player) {
356: rogue.disturbed = true;
357: }
358: if (canDirectlySeeMonster(monst) && !(monst->status[STATUS_NAUSEOUS])) {
359: if (monst->creatureState == MONSTER_SLEEPING) {
360: monst->creatureState = MONSTER_TRACKING_SCENT;
361: }
362: flashMonster(monst, &brown, 100);
363: monsterName(buf, monst, true);
364: sprintf(buf2, "%s choke%s and gag%s on the overpowering stench of decay.", buf,
365: (monst == &player ? "": "s"), (monst == &player ? "": "s"));
366: message(buf2, false);
367: }
368: monst->status[STATUS_NAUSEOUS] = monst->maxStatus[STATUS_NAUSEOUS] = max(monst->status[STATUS_NAUSEOUS], 20);
369: }
370:
371: // confusion gas
372: if (cellHasTerrainFlag(*x, *y, T_CAUSES_CONFUSION) && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))) {
373: if (monst == &player) {
374: rogue.disturbed = true;
375: }
376: if (canDirectlySeeMonster(monst) && !(monst->status[STATUS_CONFUSED])) {
377: if (monst->creatureState == MONSTER_SLEEPING) {
378: monst->creatureState = MONSTER_TRACKING_SCENT;
379: }
380: flashMonster(monst, &confusionGasColor, 100);
381: monsterName(buf, monst, true);
382: sprintf(buf2, "%s %s very confused!", buf, (monst == &player ? "feel": "looks"));
383: message(buf2, false);
384: }
385: monst->status[STATUS_CONFUSED] = monst->maxStatus[STATUS_CONFUSED] = max(monst->status[STATUS_CONFUSED], 25);
386: }
387:
388: // paralysis gas
389: if (cellHasTerrainFlag(*x, *y, T_CAUSES_PARALYSIS)
390: && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))
391: && !(monst->bookkeepingFlags & MB_SUBMERGED)) {
392:
393: if (canDirectlySeeMonster(monst) && !monst->status[STATUS_PARALYZED]) {
394: flashMonster(monst, &pink, 100);
395: monsterName(buf, monst, true);
396: sprintf(buf2, "%s %s paralyzed!", buf, (monst == &player ? "are": "is"));
397: message(buf2, (monst == &player));
398: }
399: monst->status[STATUS_PARALYZED] = monst->maxStatus[STATUS_PARALYZED] = max(monst->status[STATUS_PARALYZED], 20);
400: if (monst == &player) {
401: rogue.disturbed = true;
402: }
403: }
404: }
405:
406: // poisonous lichen
407: if (cellHasTerrainFlag(*x, *y, T_CAUSES_POISON)
408: && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))
409: && !monst->status[STATUS_LEVITATING]) {
410:
411: if (monst == &player && !player.status[STATUS_POISONED]) {
412: rogue.disturbed = true;
413: }
414: if (canDirectlySeeMonster(monst) && !(monst->status[STATUS_POISONED])) {
415: if (monst->creatureState == MONSTER_SLEEPING) {
416: monst->creatureState = MONSTER_TRACKING_SCENT;
417: }
418: flashMonster(monst, &green, 100);
419: monsterName(buf, monst, true);
420: sprintf(buf2, "the lichen's grasping tendrils poison %s.", buf);
421: messageWithColor(buf2, messageColorFromVictim(monst), false);
422: }
423: damage = max(0, 5 - monst->status[STATUS_POISONED]);
424: addPoison(monst, damage, 0); // Lichen doesn't increase poison concentration above 1.
425: }
426:
427: // fire
428: if (cellHasTerrainFlag(*x, *y, T_IS_FIRE)) {
429: exposeCreatureToFire(monst);
430: } else if (cellHasTerrainFlag(*x, *y, T_IS_FLAMMABLE)
431: // We should only expose to fire if it is flammable and not on fire. However, when
432: // gas burns, it only sets the volume to 0 and doesn't clear the layer (for visual
433: // reasons). This can cause crashes if the fire tile fails to spawn, so we also exclude it.
434: && !(pmap[*x][*y].layers[GAS] != NOTHING && pmap[*x][*y].volume == 0)
435: && !cellHasTerrainFlag(*x, *y, T_IS_FIRE)
436: && monst->status[STATUS_BURNING]
437: && !(monst->bookkeepingFlags & (MB_SUBMERGED | MB_IS_FALLING))) {
438: exposeTileToFire(*x, *y, true);
439: }
440:
441: // keys
442: if (cellHasTMFlag(*x, *y, TM_PROMOTES_WITH_KEY) && (theItem = keyOnTileAt(*x, *y))) {
443: useKeyAt(theItem, *x, *y);
444: }
445: }
446:
447: void applyGradualTileEffectsToCreature(creature *monst, short ticks) {
448: short itemCandidates, randItemIndex;
449: short x = monst->xLoc, y = monst->yLoc, damage;
450: char buf[COLS * 5], buf2[COLS * 3];
451: item *theItem;
452: enum dungeonLayers layer;
453:
454: if (!(monst->status[STATUS_LEVITATING])
455: && cellHasTerrainFlag(x, y, T_IS_DEEP_WATER)
456: && !cellHasTerrainFlag(x, y, (T_ENTANGLES | T_OBSTRUCTS_PASSABILITY))
457: && !(monst->info.flags & MONST_IMMUNE_TO_WATER)) {
458: if (monst == &player) {
459: if (!(pmap[x][y].flags & HAS_ITEM) && rand_percent(ticks * 50 / 100)) {
460: itemCandidates = numberOfMatchingPackItems(ALL_ITEMS, 0, (ITEM_EQUIPPED), false);
461: if (itemCandidates) {
462: randItemIndex = rand_range(1, itemCandidates);
463: for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
464: if (!(theItem->flags & (ITEM_EQUIPPED))) {
465: if (randItemIndex == 1) {
466: break;
467: } else {
468: randItemIndex--;
469: }
470: }
471: }
472: theItem = dropItem(theItem);
473: if (theItem) {
474: itemName(theItem, buf2, false, true, NULL);
475: sprintf(buf, "%s float%s away in the current!",
476: buf2,
477: (theItem->quantity == 1 ? "s" : ""));
478: messageWithColor(buf, &itemMessageColor, false);
479: }
480: }
481: }
482: } else if (monst->carriedItem && !(pmap[x][y].flags & HAS_ITEM) && rand_percent(ticks * 50 / 100)) { // it's a monster with an item
483: makeMonsterDropItem(monst);
484: }
485: }
486:
487: if (cellHasTerrainFlag(x, y, T_CAUSES_DAMAGE)
488: && !(monst->info.flags & (MONST_INANIMATE | MONST_INVULNERABLE))
489: && !(monst->bookkeepingFlags & MB_SUBMERGED)) {
490:
491: damage = (monst->info.maxHP / 15) * ticks / 100;
492: damage = max(1, damage);
493: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS && !(tileCatalog[pmap[x][y].layers[layer]].flags & T_CAUSES_DAMAGE); layer++);
494: if (monst == &player) {
495: if (rogue.armor && (rogue.armor->flags & ITEM_RUNIC) && rogue.armor->enchant2 == A_RESPIRATION) {
496: if (!(rogue.armor->flags & ITEM_RUNIC_IDENTIFIED)) {
497: message("Your armor trembles and a pocket of clean air swirls around you.", false);
498: autoIdentify(rogue.armor);
499: }
500: } else {
501: rogue.disturbed = true;
502: messageWithColor(tileCatalog[pmap[x][y].layers[layer]].flavorText, &badMessageColor, false);
503: if (inflictDamage(NULL, &player, damage, tileCatalog[pmap[x][y].layers[layer]].backColor, true)) {
504: sprintf(buf, "Killed by %s", tileCatalog[pmap[x][y].layers[layer]].description);
505: gameOver(buf, true);
506: return;
507: }
508: }
509: } else { // it's a monster
510: if (monst->creatureState == MONSTER_SLEEPING) {
511: monst->creatureState = MONSTER_TRACKING_SCENT;
512: }
513: if (inflictDamage(NULL, monst, damage, tileCatalog[pmap[x][y].layers[layer]].backColor, true)) {
514: if (canSeeMonster(monst)) {
515: monsterName(buf, monst, true);
516: sprintf(buf2, "%s dies.", buf);
517: messageWithColor(buf2, messageColorFromVictim(monst), false);
518: }
519: refreshDungeonCell(x, y);
520: return;
521: }
522: }
523: }
524:
525: if (cellHasTerrainFlag(x, y, T_CAUSES_HEALING)
526: && !(monst->info.flags & MONST_INANIMATE)
527: && !(monst->bookkeepingFlags & MB_SUBMERGED)) {
528:
529: damage = (monst->info.maxHP / 15) * ticks / 100;
530: damage = max(1, damage);
531: if (monst->currentHP < monst->info.maxHP) {
532: monst->currentHP = min(monst->currentHP + damage, monst->info.maxHP);
533: if (monst == &player) {
534: messageWithColor("you feel much better.", &goodMessageColor, false);
535: }
536: }
537: }
538: }
539:
540: void updateClairvoyance() {
541: short i, j, clairvoyanceRadius, dx, dy;
542: boolean cursed;
543: unsigned long cFlags;
544:
545: for (i=0; i<DCOLS; i++) {
546: for (j=0; j<DROWS; j++) {
547:
548: pmap[i][j].flags &= ~WAS_CLAIRVOYANT_VISIBLE;
549:
550: if (pmap[i][j].flags & CLAIRVOYANT_VISIBLE) {
551: pmap[i][j].flags |= WAS_CLAIRVOYANT_VISIBLE;
552: }
553:
554: pmap[i][j].flags &= ~(CLAIRVOYANT_VISIBLE | CLAIRVOYANT_DARKENED);
555: }
556: }
557:
558: cursed = (rogue.clairvoyance < 0);
559: if (cursed) {
560: clairvoyanceRadius = (rogue.clairvoyance - 1) * -1;
561: cFlags = CLAIRVOYANT_DARKENED;
562: } else {
563: clairvoyanceRadius = (rogue.clairvoyance > 0) ? rogue.clairvoyance + 1 : 0;
564: cFlags = CLAIRVOYANT_VISIBLE | DISCOVERED;
565: }
566:
567: for (i = max(0, player.xLoc - clairvoyanceRadius); i < min(DCOLS, player.xLoc + clairvoyanceRadius + 1); i++) {
568: for (j = max(0, player.yLoc - clairvoyanceRadius); j < min(DROWS, player.yLoc + clairvoyanceRadius + 1); j++) {
569:
570: dx = (player.xLoc - i);
571: dy = (player.yLoc - j);
572:
573: if (dx*dx + dy*dy < clairvoyanceRadius*clairvoyanceRadius + clairvoyanceRadius
574: && (pmap[i][j].layers[DUNGEON] != GRANITE || pmap[i][j].flags & DISCOVERED)) {
575:
576: if (cFlags & DISCOVERED) {
577: discoverCell(i, j);
578: }
579: pmap[i][j].flags |= cFlags;
580: if (!(pmap[i][j].flags & HAS_PLAYER) && !cursed) {
581: pmap[i][j].flags &= ~STABLE_MEMORY;
582: }
583: }
584: }
585: }
586: }
587:
588: void updateTelepathy() {
589: short i, j;
590: creature *monst;
591: boolean grid[DCOLS][DROWS];
592:
593: for (i=0; i<DCOLS; i++) {
594: for (j=0; j<DROWS; j++) {
595: pmap[i][j].flags &= ~WAS_TELEPATHIC_VISIBLE;
596: if (pmap[i][j].flags & TELEPATHIC_VISIBLE) {
597: pmap[i][j].flags |= WAS_TELEPATHIC_VISIBLE;
598: }
599: pmap[i][j].flags &= ~(TELEPATHIC_VISIBLE);
600: }
601: }
602:
603: zeroOutGrid(grid);
604: for (monst = monsters->nextCreature; monst; monst = monst->nextCreature) {
605: if (monsterRevealed(monst)) {
606: getFOVMask(grid, monst->xLoc, monst->yLoc, 2 * FP_FACTOR, T_OBSTRUCTS_VISION, 0, false);
607: pmap[monst->xLoc][monst->yLoc].flags |= TELEPATHIC_VISIBLE;
608: discoverCell(monst->xLoc, monst->yLoc);
609: }
610: }
611: for (monst = dormantMonsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
612: if (monsterRevealed(monst)) {
613: getFOVMask(grid, monst->xLoc, monst->yLoc, 2 * FP_FACTOR, T_OBSTRUCTS_VISION, 0, false);
614: pmap[monst->xLoc][monst->yLoc].flags |= TELEPATHIC_VISIBLE;
615: discoverCell(monst->xLoc, monst->yLoc);
616: }
617: }
618: for (i = 0; i < DCOLS; i++) {
619: for (j = 0; j < DROWS; j++) {
620: if (grid[i][j]) {
621: pmap[i][j].flags |= TELEPATHIC_VISIBLE;
622: discoverCell(i, j);
623: }
624: }
625: }
626: }
627:
628: short scentDistance(short x1, short y1, short x2, short y2) {
629: if (abs(x1 - x2) > abs(y1 - y2)) {
630: return 2 * abs(x1 - x2) + abs(y1 - y2);
631: } else {
632: return abs(x1 - x2) + 2 * abs(y1 - y2);
633: }
634: }
635:
636: void updateScent() {
637: short i, j;
638: char grid[DCOLS][DROWS];
639:
640: zeroOutGrid(grid);
641:
642: getFOVMask(grid, player.xLoc, player.yLoc, DCOLS * FP_FACTOR, T_OBSTRUCTS_SCENT, 0, false);
643:
644: for (i=0; i<DCOLS; i++) {
645: for (j=0; j<DROWS; j++) {
646: if (grid[i][j]) {
647: addScentToCell(i, j, scentDistance(player.xLoc, player.yLoc, i, j));
648: }
649: }
650: }
651: addScentToCell(player.xLoc, player.yLoc, 0);
652: }
653:
654: short armorAggroAdjustment(item *theArmor) {
655: if (!theArmor
656: || !(theArmor->category & ARMOR)) {
657:
658: return 0;
659: }
660: return max(0, armorTable[theArmor->kind].strengthRequired - 12);
661: }
662:
663: short currentAggroValue() {
664: // Default value of 14 in the light.
665: short stealthVal = 14;
666:
667: if (player.status[STATUS_INVISIBLE]) {
668: stealthVal = 1; // Invisibility means stealth range of 1, no matter what.
669: } else {
670: if (playerInDarkness()) {
671: // In darkness, halve, rounded down.
672: stealthVal = stealthVal / 2;
673: }
674: if (pmap[player.xLoc][player.yLoc].flags & IS_IN_SHADOW) {
675: // When not standing in a lit area, halve, rounded down (stacks with darkness halving).
676: stealthVal = stealthVal / 2;
677: }
678:
679: // Add 1 for each point of your armor's natural (unenchanted) strength requirement above 12.
680: stealthVal += armorAggroAdjustment(rogue.armor);
681:
682: // Halve (rounded up) if you just rested.
683: if (rogue.justRested) {
684: stealthVal = (stealthVal + 1) / 2;
685: }
686:
687: if (player.status[STATUS_AGGRAVATING] > 0) {
688: stealthVal += player.status[STATUS_AGGRAVATING];
689: }
690:
691: // Subtract your bonuses from rings of stealth.
692: // (Cursed rings of stealth will end up adding here.)
693: stealthVal -= rogue.stealthBonus;
694:
695: // Can't go below 2 unless you just rested.
696: if (stealthVal < 2 && !rogue.justRested) {
697: stealthVal = 2;
698: } else if (stealthVal < 1) { // Can't go below 1, ever.
699: stealthVal = 1;
700: }
701: }
702: return stealthVal;
703: }
704:
705: void demoteVisibility() {
706: short i, j;
707:
708: for (i=0; i<DCOLS; i++) {
709: for (j=0; j<DROWS; j++) {
710: pmap[i][j].flags &= ~WAS_VISIBLE;
711: if (pmap[i][j].flags & VISIBLE) {
712: pmap[i][j].flags &= ~VISIBLE;
713: pmap[i][j].flags |= WAS_VISIBLE;
714: }
715: }
716: }
717: }
718:
719: void discoverCell(const short x, const short y) {
720: pmap[x][y].flags &= ~STABLE_MEMORY;
721: if (!(pmap[x][y].flags & DISCOVERED)) {
722: pmap[x][y].flags |= DISCOVERED;
723: if (!cellHasTerrainFlag(x, y, T_PATHING_BLOCKER)) {
724: rogue.xpxpThisTurn++;
725: }
726: }
727: }
728:
729: void updateVision(boolean refreshDisplay) {
730: short i, j;
731: char grid[DCOLS][DROWS];
732: item *theItem;
733: creature *monst;
734:
735: demoteVisibility();
736: for (i=0; i<DCOLS; i++) {
737: for (j=0; j<DROWS; j++) {
738: pmap[i][j].flags &= ~IN_FIELD_OF_VIEW;
739: }
740: }
741:
742: // Calculate player's field of view (distinct from what is visible, as lighting hasn't been done yet).
743: zeroOutGrid(grid);
744: getFOVMask(grid, player.xLoc, player.yLoc, (DCOLS + DROWS) * FP_FACTOR, (T_OBSTRUCTS_VISION), 0, false);
745: for (i=0; i<DCOLS; i++) {
746: for (j=0; j<DROWS; j++) {
747: if (grid[i][j]) {
748: pmap[i][j].flags |= IN_FIELD_OF_VIEW;
749: }
750: }
751: }
752: pmap[player.xLoc][player.yLoc].flags |= IN_FIELD_OF_VIEW | VISIBLE;
753:
754: if (rogue.clairvoyance < 0) {
755: discoverCell(player.xLoc, player.yLoc);
756: }
757:
758: if (rogue.clairvoyance != 0) {
759: updateClairvoyance();
760: }
761:
762: updateTelepathy();
763: updateLighting();
764: updateFieldOfViewDisplay(true, refreshDisplay);
765:
766: // for (i=0; i<DCOLS; i++) {
767: // for (j=0; j<DROWS; j++) {
768: // if (pmap[i][j].flags & VISIBLE) {
769: // plotCharWithColor(' ', mapToWindowX(i), mapToWindowY(j), &yellow, &yellow);
770: // } else if (pmap[i][j].flags & IN_FIELD_OF_VIEW) {
771: // plotCharWithColor(' ', mapToWindowX(i), mapToWindowY(j), &blue, &blue);
772: // }
773: // }
774: // }
775: // displayMoreSign();
776:
777: if (player.status[STATUS_HALLUCINATING] > 0) {
778: for (theItem = floorItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
779: if ((pmap[theItem->xLoc][theItem->yLoc].flags & DISCOVERED) && refreshDisplay) {
780: refreshDungeonCell(theItem->xLoc, theItem->yLoc);
781: }
782: }
783: for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
784: if ((pmap[monst->xLoc][monst->yLoc].flags & DISCOVERED) && refreshDisplay) {
785: refreshDungeonCell(monst->xLoc, monst->yLoc);
786: }
787: }
788: }
789: }
790:
791: void checkNutrition() {
792: item *theItem;
793: char buf[DCOLS*3], foodWarning[DCOLS*3];
794:
795: if (numberOfMatchingPackItems(FOOD, 0, 0, false) == 0) {
796: sprintf(foodWarning, " and have no food");
797: } else {
798: foodWarning[0] = '\0';
799: }
800:
801: if (player.status[STATUS_NUTRITION] == HUNGER_THRESHOLD) {
802: player.status[STATUS_NUTRITION]--;
803: sprintf(buf, "you are hungry%s.", foodWarning);
804: message(buf, foodWarning[0]);
805: } else if (player.status[STATUS_NUTRITION] == WEAK_THRESHOLD) {
806: player.status[STATUS_NUTRITION]--;
807: sprintf(buf, "you feel weak with hunger%s.", foodWarning);
808: message(buf, true);
809: } else if (player.status[STATUS_NUTRITION] == FAINT_THRESHOLD) {
810: player.status[STATUS_NUTRITION]--;
811: sprintf(buf, "you feel faint with hunger%s.", foodWarning);
812: message(buf, true);
813: } else if (player.status[STATUS_NUTRITION] <= 1) {
814: // Force the player to eat something if he has it
815: for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
816: if (theItem->category == FOOD) {
817: sprintf(buf, "unable to control your hunger, you eat a %s.", (theItem->kind == FRUIT ? "mango" : "ration of food"));
818: messageWithColor(buf, &itemMessageColor, true);
819: apply(theItem, false);
820: break;
821: }
822: }
823: }
824:
825: if (player.status[STATUS_NUTRITION] == 1) { // Didn't manage to eat any food above.
826: player.status[STATUS_NUTRITION] = 0; // So the status bar changes in time for the message:
827: message("you are starving to death!", true);
828: }
829: }
830:
831: void burnItem(item *theItem) {
832: short x, y;
833: char buf1[COLS * 3], buf2[COLS * 3];
834: itemName(theItem, buf1, false, true, NULL);
835: sprintf(buf2, "%s burn%s up!",
836: buf1,
837: theItem->quantity == 1 ? "s" : "");
838: x = theItem->xLoc;
839: y = theItem->yLoc;
840: removeItemFromChain(theItem, floorItems);
841: deleteItem(theItem);
842: pmap[x][y].flags &= ~(HAS_ITEM | ITEM_DETECTED);
843: if (pmap[x][y].flags & (ANY_KIND_OF_VISIBLE | DISCOVERED | ITEM_DETECTED)) {
844: refreshDungeonCell(x, y);
845: }
846: if (playerCanSee(x, y)) {
847: messageWithColor(buf2, &itemMessageColor, false);
848: }
849: spawnDungeonFeature(x, y, &(dungeonFeatureCatalog[DF_ITEM_FIRE]), true, false);
850: }
851:
852: void flashCreatureAlert(creature *monst, char msg[200], color *foreColor, color *backColor) {
853: short x, y;
854: if (monst->yLoc > DROWS / 2) {
855: y = mapToWindowY(monst->yLoc - 2);
856: } else {
857: y = mapToWindowY(monst->yLoc + 2);
858: }
859: x = mapToWindowX(monst->xLoc - strLenWithoutEscapes(msg) / 2);
860: if (x > COLS - strLenWithoutEscapes(msg)) {
861: x = COLS - strLenWithoutEscapes(msg);
862: }
863: flashMessage(msg, x, y, (rogue.playbackMode ? 100 : 1000), foreColor, backColor);
864: rogue.disturbed = true;
865: }
866:
867: void handleHealthAlerts() {
868: short i, currentPercent, previousPercent,
869: thresholds[] = {5, 10, 25, 40},
870: pThresholds[] = {100, 90, 50};
871: char buf[DCOLS];
872:
873: const short healthThresholdsCount = 4,
874: poisonThresholdsCount = 3;
875:
876: assureCosmeticRNG;
877:
878: currentPercent = player.currentHP * 100 / player.info.maxHP;
879: previousPercent = player.previousHealthPoints * 100 / player.info.maxHP;
880:
881: if (currentPercent < previousPercent && !rogue.gameHasEnded) {
882: for (i=0; i < healthThresholdsCount; i++) {
883: if (currentPercent < thresholds[i] && previousPercent >= thresholds[i]) {
884: sprintf(buf, " <%i%% health ", thresholds[i]);
885: flashCreatureAlert(&player, buf, &badMessageColor, &darkRed);
886: break;
887: }
888: }
889: }
890:
891: if (!rogue.gameHasEnded) {
892: currentPercent = player.status[STATUS_POISONED] * player.poisonAmount * 100 / player.currentHP;
893:
894: if (currentPercent > rogue.previousPoisonPercent && !rogue.gameHasEnded) {
895: for (i=0; i < poisonThresholdsCount; i++) {
896: if (currentPercent > pThresholds[i] && rogue.previousPoisonPercent <= pThresholds[i]) {
897: if (currentPercent < 100) {
898: sprintf(buf, " >%i%% poisoned ", pThresholds[i]);
899: } else {
900: strcpy(buf, " Fatally poisoned ");
901: }
902: flashCreatureAlert(&player, buf, &yellow, &darkGreen);
903: break;
904: }
905: }
906: }
907: rogue.previousPoisonPercent = currentPercent;
908: }
909:
910: restoreRNG;
911: }
912:
913: void addXPXPToAlly(short XPXP, creature *monst) {
914: char theMonsterName[100], buf[200];
915: if (!(monst->info.flags & (MONST_INANIMATE | MONST_IMMOBILE))
916: && !(monst->bookkeepingFlags & MB_TELEPATHICALLY_REVEALED)
917: && monst->creatureState == MONSTER_ALLY
918: && monst->spawnDepth <= rogue.depthLevel
919: && rogue.depthLevel <= AMULET_LEVEL) {
920:
921: monst->xpxp += XPXP;
922: //printf("\n%i xpxp added to your %s this turn.", rogue.xpxpThisTurn, monst->info.monsterName);
923: if (monst->xpxp >= XPXP_NEEDED_FOR_TELEPATHIC_BOND
924: && !(monst->bookkeepingFlags & MB_TELEPATHICALLY_REVEALED)) {
925:
926: monst->bookkeepingFlags |= MB_TELEPATHICALLY_REVEALED;
927: updateVision(true);
928: monsterName(theMonsterName, monst, false);
929: sprintf(buf, "you have developed a telepathic bond with your %s.", theMonsterName);
930: messageWithColor(buf, &advancementMessageColor, false);
931: }
932: if (monst->xpxp > 1500 * 20) {
933: rogue.featRecord[FEAT_COMPANION] = true;
934: }
935: }
936: }
937:
938: void handleXPXP() {
939: creature *monst;
940: //char buf[DCOLS*2], theMonsterName[50];
941:
942: for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
943: addXPXPToAlly(rogue.xpxpThisTurn, monst);
944: }
945: if (rogue.depthLevel > 1) {
946: for (monst = levels[rogue.depthLevel - 2].monsters; monst != NULL; monst = monst->nextCreature) {
947: addXPXPToAlly(rogue.xpxpThisTurn, monst);
948: }
949: }
950: if (rogue.depthLevel < DEEPEST_LEVEL) {
951: for (monst = levels[rogue.depthLevel].monsters; monst != NULL; monst = monst->nextCreature) {
952: addXPXPToAlly(rogue.xpxpThisTurn, monst);
953: }
954: }
955: rogue.xpxpThisTurn = 0;
956: }
957:
958: void playerFalls() {
959: short damage;
960: short layer;
961:
962: if (cellHasTMFlag(player.xLoc, player.yLoc, TM_IS_SECRET)
963: && playerCanSee(player.xLoc, player.yLoc)) {
964:
965: discover(player.xLoc, player.yLoc);
966: }
967:
968: monstersFall(); // Monsters must fall with the player rather than getting suspended on the previous level.
969: updateFloorItems(); // Likewise, items should fall with the player rather than getting suspended above.
970:
971: layer = layerWithFlag(player.xLoc, player.yLoc, T_AUTO_DESCENT);
972: if (layer >= 0) {
973: message(tileCatalog[pmap[player.xLoc][player.yLoc].layers[layer]].flavorText, true);
974: } else if (layer == -1) {
975: message("You plunge downward!", true);
976: }
977:
978: player.bookkeepingFlags &= ~(MB_IS_FALLING | MB_SEIZED | MB_SEIZING);
979: rogue.disturbed = true;
980:
981: if (rogue.depthLevel < DEEPEST_LEVEL) {
982: rogue.depthLevel++;
983: startLevel(rogue.depthLevel - 1, 0);
984: damage = randClumpedRange(FALL_DAMAGE_MIN, FALL_DAMAGE_MAX, 2);
985: messageWithColor("You are damaged by the fall.", &badMessageColor, false);
986: if (inflictDamage(NULL, &player, damage, &red, false)) {
987: gameOver("Killed by a fall", true);
988: } else if (rogue.depthLevel > rogue.deepestLevel) {
989: rogue.deepestLevel = rogue.depthLevel;
990: }
991: } else {
992: message("A strange force seizes you as you fall.", false);
993: teleport(&player, -1, -1, true);
994: }
995: createFlare(player.xLoc, player.yLoc, GENERIC_FLASH_LIGHT);
996: animateFlares(rogue.flares, rogue.flareCount);
997: rogue.flareCount = 0;
998: }
999:
1000:
1001:
1002: void activateMachine(short machineNumber) {
1003: short i, j, x, y, layer, sRows[DROWS], sCols[DCOLS], monsterCount, maxMonsters;
1004: creature **activatedMonsterList, *monst;
1005:
1006: fillSequentialList(sCols, DCOLS);
1007: shuffleList(sCols, DCOLS);
1008: fillSequentialList(sRows, DROWS);
1009: shuffleList(sRows, DROWS);
1010:
1011: for (i=0; i<DCOLS; i++) {
1012: for (j=0; j<DROWS; j++) {
1013: x = sCols[i];
1014: y = sRows[j];
1015: if ((pmap[x][y].flags & IS_IN_MACHINE)
1016: && pmap[x][y].machineNumber == machineNumber
1017: && !(pmap[x][y].flags & IS_POWERED)
1018: && cellHasTMFlag(x, y, TM_IS_WIRED)) {
1019:
1020: pmap[x][y].flags |= IS_POWERED;
1021: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
1022: if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_IS_WIRED) {
1023: promoteTile(x, y, layer, false);
1024: }
1025: }
1026: }
1027: }
1028: }
1029:
1030: monsterCount = maxMonsters = 0;
1031: activatedMonsterList = NULL;
1032: for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
1033: if (monst->machineHome == machineNumber
1034: && monst->spawnDepth == rogue.depthLevel
1035: && (monst->info.flags & MONST_GETS_TURN_ON_ACTIVATION)) {
1036:
1037: monsterCount++;
1038:
1039: if (monsterCount > maxMonsters) {
1040: maxMonsters += 10;
1041: activatedMonsterList = realloc(activatedMonsterList, sizeof(creature *) * maxMonsters);
1042: }
1043: activatedMonsterList[monsterCount - 1] = monst;
1044: }
1045: }
1046: for (i=0; i<monsterCount; i++) {
1047: if (!(activatedMonsterList[i]->bookkeepingFlags & MB_IS_DYING)) {
1048: monstersTurn(activatedMonsterList[i]);
1049: }
1050: }
1051:
1052: if (activatedMonsterList) {
1053: free(activatedMonsterList);
1054: }
1055: }
1056:
1057: boolean circuitBreakersPreventActivation(short machineNumber) {
1058: short i, j;
1059: for (i=0; i<DCOLS; i++) {
1060: for (j=0; j<DROWS; j++) {
1061: if (pmap[i][j].machineNumber == machineNumber
1062: && cellHasTMFlag(i, j, TM_IS_CIRCUIT_BREAKER)) {
1063:
1064: return true;
1065: }
1066: }
1067: }
1068: return false;
1069: }
1070:
1071: void promoteTile(short x, short y, enum dungeonLayers layer, boolean useFireDF) {
1072: short i, j;
1073: enum dungeonFeatureTypes DFType;
1074: floorTileType *tile;
1075:
1076: tile = &(tileCatalog[pmap[x][y].layers[layer]]);
1077:
1078: DFType = (useFireDF ? tile->fireType : tile->promoteType);
1079:
1080: if ((tile->mechFlags & TM_VANISHES_UPON_PROMOTION)) {
1081: if (tileCatalog[pmap[x][y].layers[layer]].flags & T_PATHING_BLOCKER) {
1082: rogue.staleLoopMap = true;
1083: }
1084: pmap[x][y].layers[layer] = (layer == DUNGEON ? FLOOR : NOTHING); // even the dungeon layer implicitly has floor underneath it
1085: if (layer == GAS) {
1086: pmap[x][y].volume = 0;
1087: }
1088: refreshDungeonCell(x, y);
1089: }
1090: if (DFType) {
1091: spawnDungeonFeature(x, y, &dungeonFeatureCatalog[DFType], true, false);
1092: }
1093:
1094: if (!useFireDF && (tile->mechFlags & TM_IS_WIRED)
1095: && !(pmap[x][y].flags & IS_POWERED)
1096: && !circuitBreakersPreventActivation(pmap[x][y].machineNumber)) {
1097: // Send power through all cells in the same machine that are not IS_POWERED,
1098: // and on any such cell, promote each terrain layer that is T_IS_WIRED.
1099: // Note that machines need not be contiguous.
1100: pmap[x][y].flags |= IS_POWERED;
1101: activateMachine(pmap[x][y].machineNumber); // It lives!!!
1102:
1103: // Power fades from the map immediately after we finish.
1104: for (i=0; i<DCOLS; i++) {
1105: for (j=0; j<DROWS; j++) {
1106: pmap[i][j].flags &= ~IS_POWERED;
1107: }
1108: }
1109: }
1110: }
1111:
1112: boolean exposeTileToElectricity(short x, short y) {
1113: enum dungeonLayers layer;
1114: boolean promotedSomething = false;
1115:
1116: if (!cellHasTMFlag(x, y, TM_PROMOTES_ON_ELECTRICITY)) {
1117: return false;
1118: }
1119: for (layer=0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
1120: if (tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_PROMOTES_ON_ELECTRICITY) {
1121: promoteTile(x, y, layer, false);
1122: promotedSomething = true;
1123: }
1124: }
1125: return promotedSomething;
1126: }
1127:
1128: boolean exposeTileToFire(short x, short y, boolean alwaysIgnite) {
1129: enum dungeonLayers layer;
1130: short ignitionChance = 0, bestExtinguishingPriority = 1000, explosiveNeighborCount = 0;
1131: short newX, newY;
1132: enum directions dir;
1133: boolean fireIgnited = false, explosivePromotion = false;
1134:
1135: if (!cellHasTerrainFlag(x, y, T_IS_FLAMMABLE)) {
1136: return false;
1137: }
1138:
1139: // Pick the extinguishing layer with the best priority.
1140: for (layer=0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
1141: if ((tileCatalog[pmap[x][y].layers[layer]].mechFlags & TM_EXTINGUISHES_FIRE)
1142: && tileCatalog[pmap[x][y].layers[layer]].drawPriority < bestExtinguishingPriority) {
1143: bestExtinguishingPriority = tileCatalog[pmap[x][y].layers[layer]].drawPriority;
1144: }
1145: }
1146:
1147: // Pick the fire type of the most flammable layer that is either gas or equal-or-better priority than the best extinguishing layer.
1148: for (layer=0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
1149: if ((tileCatalog[pmap[x][y].layers[layer]].flags & T_IS_FLAMMABLE)
1150: && (layer == GAS || tileCatalog[pmap[x][y].layers[layer]].drawPriority <= bestExtinguishingPriority)
1151: && tileCatalog[pmap[x][y].layers[layer]].chanceToIgnite > ignitionChance) {
1152: ignitionChance = tileCatalog[pmap[x][y].layers[layer]].chanceToIgnite;
1153: }
1154: }
1155:
1156: if (alwaysIgnite || (ignitionChance && rand_percent(ignitionChance))) { // If it ignites...
1157: fireIgnited = true;
1158:
1159: // Count explosive neighbors.
1160: if (cellHasTMFlag(x, y, TM_EXPLOSIVE_PROMOTE)) {
1161: for (dir = 0, explosiveNeighborCount = 0; dir < DIRECTION_COUNT; dir++) {
1162: newX = x + nbDirs[dir][0];
1163: newY = y + nbDirs[dir][1];
1164: if (coordinatesAreInMap(newX, newY)
1165: && (cellHasTerrainFlag(newX, newY, T_IS_FIRE | T_OBSTRUCTS_GAS) || cellHasTMFlag(newX, newY, TM_EXPLOSIVE_PROMOTE))) {
1166:
1167: explosiveNeighborCount++;
1168: }
1169: }
1170: if (explosiveNeighborCount >= 8) {
1171: explosivePromotion = true;
1172: }
1173: }
1174:
1175: // Flammable layers are consumed.
1176: for (layer=0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
1177: if (tileCatalog[pmap[x][y].layers[layer]].flags & T_IS_FLAMMABLE) {
1178: // pmap[x][y].layers[GAS] is not cleared here, which is a bug.
1179: // We preserve the layer anyways because this results in nicer gas burning behavior.
1180: if (layer == GAS) {
1181: pmap[x][y].volume = 0; // Flammable gas burns its volume away.
1182: }
1183: promoteTile(x, y, layer, !explosivePromotion);
1184: }
1185: }
1186: refreshDungeonCell(x, y);
1187: }
1188: return fireIgnited;
1189: }
1190:
1191: // Only the gas layer can be volumetric.
1192: void updateVolumetricMedia() {
1193: short i, j, newX, newY, numSpaces;
1194: unsigned long highestNeighborVolume;
1195: unsigned long sum;
1196: enum tileType gasType;
1197: enum directions dir;
1198: unsigned short newGasVolume[DCOLS][DROWS];
1199:
1200: for (i=0; i<DCOLS; i++) {
1201: for (j=0; j<DROWS; j++) {
1202: newGasVolume[i][j] = 0;
1203: }
1204: }
1205:
1206: for (i=0; i<DCOLS; i++) {
1207: for (j=0; j<DROWS; j++) {
1208: if (!cellHasTerrainFlag(i, j, T_OBSTRUCTS_GAS)) {
1209: sum = pmap[i][j].volume;
1210: numSpaces = 1;
1211: highestNeighborVolume = pmap[i][j].volume;
1212: gasType = pmap[i][j].layers[GAS];
1213: for (dir=0; dir< DIRECTION_COUNT; dir++) {
1214: newX = i + nbDirs[dir][0];
1215: newY = j + nbDirs[dir][1];
1216: if (coordinatesAreInMap(newX, newY)
1217: && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_GAS)) {
1218:
1219: sum += pmap[newX][newY].volume;
1220: numSpaces++;
1221: if (pmap[newX][newY].volume > highestNeighborVolume) {
1222: highestNeighborVolume = pmap[newX][newY].volume;
1223: gasType = pmap[newX][newY].layers[GAS];
1224: }
1225: }
1226: }
1227: if (cellHasTerrainFlag(i, j, T_AUTO_DESCENT)) { // if it's a chasm tile or trap door,
1228: numSpaces++; // this will allow gas to escape from the level entirely
1229: }
1230: newGasVolume[i][j] += sum / max(1, numSpaces);
1231: if ((unsigned) rand_range(0, numSpaces - 1) < (sum % numSpaces)) {
1232: newGasVolume[i][j]++; // stochastic rounding
1233: }
1234: if (pmap[i][j].layers[GAS] != gasType && newGasVolume[i][j] > 3) {
1235: if (pmap[i][j].layers[GAS] != NOTHING) {
1236: newGasVolume[i][j] = min(3, newGasVolume[i][j]); // otherwise interactions between gases are crazy
1237: }
1238: pmap[i][j].layers[GAS] = gasType;
1239: } else if (pmap[i][j].layers[GAS] && newGasVolume[i][j] < 1) {
1240: pmap[i][j].layers[GAS] = NOTHING;
1241: refreshDungeonCell(i, j);
1242: }
1243: if (pmap[i][j].volume > 0) {
1244: if (tileCatalog[pmap[i][j].layers[GAS]].mechFlags & TM_GAS_DISSIPATES_QUICKLY) {
1245: newGasVolume[i][j] -= (rand_percent(50) ? 1 : 0);
1246: } else if (tileCatalog[pmap[i][j].layers[GAS]].mechFlags & TM_GAS_DISSIPATES) {
1247: newGasVolume[i][j] -= (rand_percent(20) ? 1 : 0);
1248: }
1249: }
1250: } else if (pmap[i][j].volume > 0) { // if has gas but can't hold gas,
1251: // disperse gas instantly into neighboring tiles that can hold gas
1252: numSpaces = 0;
1253: for (dir = 0; dir < DIRECTION_COUNT; dir++) {
1254: newX = i + nbDirs[dir][0];
1255: newY = j + nbDirs[dir][1];
1256: if (coordinatesAreInMap(newX, newY)
1257: && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_GAS)) {
1258:
1259: numSpaces++;
1260: }
1261: }
1262: if (numSpaces > 0) {
1263: for (dir = 0; dir < DIRECTION_COUNT; dir++) {
1264: newX = i + nbDirs[dir][0];
1265: newY = j + nbDirs[dir][1];
1266: if (coordinatesAreInMap(newX, newY)
1267: && !cellHasTerrainFlag(newX, newY, T_OBSTRUCTS_GAS)) {
1268:
1269: newGasVolume[newX][newY] += (pmap[i][j].volume / numSpaces);
1270: if (pmap[i][j].volume / numSpaces) {
1271: pmap[newX][newY].layers[GAS] = pmap[i][j].layers[GAS];
1272: }
1273: }
1274: }
1275: }
1276: newGasVolume[i][j] = 0;
1277: pmap[i][j].layers[GAS] = NOTHING;
1278: }
1279: }
1280: }
1281:
1282: for (i=0; i<DCOLS; i++) {
1283: for (j=0; j<DROWS; j++) {
1284: if (pmap[i][j].volume != newGasVolume[i][j]) {
1285: pmap[i][j].volume = newGasVolume[i][j];
1286: refreshDungeonCell(i, j);
1287: }
1288: }
1289: }
1290: }
1291:
1292: void updateYendorWardenTracking() {
1293: creature *prevMonst;
1294: short n;
1295:
1296: if (!rogue.yendorWarden) {
1297: return;
1298: }
1299: if (rogue.yendorWarden->depth == rogue.depthLevel) {
1300: return;
1301: }
1302: if (!(rogue.yendorWarden->bookkeepingFlags & MB_PREPLACED)) {
1303: levels[rogue.yendorWarden->depth - 1].mapStorage[rogue.yendorWarden->xLoc][rogue.yendorWarden->yLoc].flags &= ~HAS_MONSTER;
1304: }
1305: n = rogue.yendorWarden->depth - 1;
1306:
1307: // remove traversing monster from other level monster chain
1308: if (rogue.yendorWarden == levels[n].monsters) {
1309: levels[n].monsters = rogue.yendorWarden->nextCreature;
1310: } else {
1311: for (prevMonst = levels[n].monsters; prevMonst->nextCreature != rogue.yendorWarden; prevMonst = prevMonst->nextCreature);
1312: prevMonst->nextCreature = rogue.yendorWarden->nextCreature;
1313: }
1314:
1315: if (rogue.yendorWarden->depth > rogue.depthLevel) {
1316: rogue.yendorWarden->depth = rogue.depthLevel + 1;
1317: n = rogue.yendorWarden->depth - 1;
1318: rogue.yendorWarden->bookkeepingFlags |= MB_APPROACHING_UPSTAIRS;
1319: rogue.yendorWarden->xLoc = levels[n].downStairsLoc[0];
1320: rogue.yendorWarden->yLoc = levels[n].downStairsLoc[1];
1321: } else {
1322: rogue.yendorWarden->depth = rogue.depthLevel - 1;
1323: n = rogue.yendorWarden->depth - 1;
1324: rogue.yendorWarden->bookkeepingFlags |= MB_APPROACHING_DOWNSTAIRS;
1325: rogue.yendorWarden->xLoc = levels[n].upStairsLoc[0];
1326: rogue.yendorWarden->yLoc = levels[n].upStairsLoc[1];
1327: }
1328: rogue.yendorWarden->nextCreature = levels[rogue.yendorWarden->depth - 1].monsters;
1329: levels[rogue.yendorWarden->depth - 1].monsters = rogue.yendorWarden;
1330: rogue.yendorWarden->bookkeepingFlags |= MB_PREPLACED;
1331: rogue.yendorWarden->status[STATUS_ENTERS_LEVEL_IN] = 50;
1332: }
1333:
1334: // Monsters who are over chasms or other descent tiles won't fall until this is called.
1335: // This is to avoid having the monster chain change unpredictably in the middle of a turn.
1336: void monstersFall() {
1337: creature *monst, *previousCreature, *nextCreature;
1338: short x, y;
1339: char buf[DCOLS], buf2[DCOLS];
1340:
1341: // monsters plunge into chasms at the end of the turn
1342: for (monst = monsters->nextCreature; monst != NULL; monst = nextCreature) {
1343: nextCreature = monst->nextCreature;
1344: if ((monst->bookkeepingFlags & MB_IS_FALLING) || monsterShouldFall(monst)) {
1345: if (rogue.patchVersion >= 3) monst->bookkeepingFlags |= MB_IS_FALLING;
1346:
1347: x = monst->xLoc;
1348: y = monst->yLoc;
1349:
1350: if (canSeeMonster(monst)) {
1351: monsterName(buf, monst, true);
1352: sprintf(buf2, "%s plunges out of sight!", buf);
1353: messageWithColor(buf2, messageColorFromVictim(monst), false);
1354: }
1355:
1356: if (rogue.patchVersion < 3) {
1357: monst->status[STATUS_ENTRANCED] = 0;
1358: monst->bookkeepingFlags |= MB_PREPLACED;
1359: monst->bookkeepingFlags &= ~(MB_IS_FALLING | MB_SEIZED | MB_SEIZING);
1360: monst->targetCorpseLoc[0] = monst->targetCorpseLoc[1] = 0;
1361: }
1362:
1363: if (monst->info.flags & MONST_GETS_TURN_ON_ACTIVATION) {
1364: // Guardians and mirrored totems never survive the fall. If they did, they might block the level below.
1365: killCreature(monst, false);
1366: } else if (!inflictDamage(NULL, monst, randClumpedRange(6, 12, 2), &red, false)) {
1367: demoteMonsterFromLeadership(monst);
1368:
1369: if (rogue.patchVersion >= 3) {
1370: monst->status[STATUS_ENTRANCED] = 0;
1371: monst->bookkeepingFlags |= MB_PREPLACED;
1372: monst->bookkeepingFlags &= ~(MB_IS_FALLING | MB_SEIZED | MB_SEIZING);
1373: monst->targetCorpseLoc[0] = monst->targetCorpseLoc[1] = 0;
1374: }
1375:
1376: // remove from monster chain
1377: for (previousCreature = monsters;
1378: previousCreature->nextCreature != monst;
1379: previousCreature = previousCreature->nextCreature);
1380: previousCreature->nextCreature = monst->nextCreature;
1381:
1382: // add to next level's chain
1383: monst->nextCreature = levels[rogue.depthLevel-1 + 1].monsters;
1384: levels[rogue.depthLevel-1 + 1].monsters = monst;
1385:
1386: monst->depth = rogue.depthLevel + 1;
1387:
1388: if (monst == rogue.yendorWarden) {
1389: updateYendorWardenTracking();
1390: }
1391: }
1392:
1393: pmap[x][y].flags &= ~HAS_MONSTER;
1394: refreshDungeonCell(x, y);
1395: }
1396: }
1397: }
1398:
1399: void updateEnvironment() {
1400: short i, j, direction, newX, newY, promotions[DCOLS][DROWS];
1401: long promoteChance;
1402: enum dungeonLayers layer;
1403: floorTileType *tile;
1404: boolean isVolumetricGas = false;
1405:
1406: monstersFall();
1407:
1408: // update gases twice
1409: for (i=0; i<DCOLS && !isVolumetricGas; i++) {
1410: for (j=0; j<DROWS && !isVolumetricGas; j++) {
1411: if (!isVolumetricGas && pmap[i][j].layers[GAS]) {
1412: isVolumetricGas = true;
1413: }
1414: }
1415: }
1416: if (isVolumetricGas) {
1417: updateVolumetricMedia();
1418: updateVolumetricMedia();
1419: }
1420:
1421: // Do random tile promotions in two passes to keep generations distinct.
1422: // First pass, make a note of each terrain layer at each coordinate that is going to promote:
1423: for (i=0; i<DCOLS; i++) {
1424: for (j=0; j<DROWS; j++) {
1425: promotions[i][j] = 0;
1426: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
1427: tile = &(tileCatalog[pmap[i][j].layers[layer]]);
1428: if (tile->promoteChance < 0) {
1429: promoteChance = 0;
1430: for (direction = 0; direction < 4; direction++) {
1431: if (coordinatesAreInMap(i + nbDirs[direction][0], j + nbDirs[direction][1])
1432: && !cellHasTerrainFlag(i + nbDirs[direction][0], j + nbDirs[direction][1], T_OBSTRUCTS_PASSABILITY)
1433: && pmap[i + nbDirs[direction][0]][j + nbDirs[direction][1]].layers[layer] != pmap[i][j].layers[layer]
1434: && !(pmap[i][j].flags & CAUGHT_FIRE_THIS_TURN)) {
1435: promoteChance += -1 * tile->promoteChance;
1436: }
1437: }
1438: } else {
1439: promoteChance = tile->promoteChance;
1440: }
1441: if (promoteChance
1442: && !(pmap[i][j].flags & CAUGHT_FIRE_THIS_TURN)
1443: && rand_range(0, 10000) < promoteChance) {
1444: promotions[i][j] |= Fl(layer);
1445: //promoteTile(i, j, layer, false);
1446: }
1447: }
1448: }
1449: }
1450: // Second pass, do the promotions:
1451: for (i=0; i<DCOLS; i++) {
1452: for (j=0; j<DROWS; j++) {
1453: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
1454: if ((promotions[i][j] & Fl(layer))) {
1455: //&& (tileCatalog[pmap[i][j].layers[layer]].promoteChance != 0)){
1456: // make sure that it's still a promotable layer
1457: promoteTile(i, j, layer, false);
1458: }
1459: }
1460: }
1461: }
1462:
1463: // Bookkeeping for fire, pressure plates and key-activated tiles.
1464: for (i=0; i<DCOLS; i++) {
1465: for (j=0; j<DROWS; j++) {
1466: pmap[i][j].flags &= ~(CAUGHT_FIRE_THIS_TURN);
1467: if (!(pmap[i][j].flags & (HAS_PLAYER | HAS_MONSTER | HAS_ITEM))
1468: && (pmap[i][j].flags & PRESSURE_PLATE_DEPRESSED)) {
1469:
1470: pmap[i][j].flags &= ~PRESSURE_PLATE_DEPRESSED;
1471: }
1472: if (cellHasTMFlag(i, j, TM_PROMOTES_WITHOUT_KEY) && !keyOnTileAt(i, j)) {
1473: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
1474: if (tileCatalog[pmap[i][j].layers[layer]].mechFlags & TM_PROMOTES_WITHOUT_KEY) {
1475: promoteTile(i, j, layer, false);
1476: }
1477: }
1478: }
1479: }
1480: }
1481:
1482: // Update fire.
1483: for (i=0; i<DCOLS; i++) {
1484: for (j=0; j<DROWS; j++) {
1485: if (cellHasTerrainFlag(i, j, T_IS_FIRE) && !(pmap[i][j].flags & CAUGHT_FIRE_THIS_TURN)) {
1486: exposeTileToFire(i, j, false);
1487: for (direction=0; direction<4; direction++) {
1488: newX = i + nbDirs[direction][0];
1489: newY = j + nbDirs[direction][1];
1490: if (coordinatesAreInMap(newX, newY)) {
1491: exposeTileToFire(newX, newY, false);
1492: }
1493: }
1494: }
1495: }
1496: }
1497:
1498: // Terrain that affects items and vice versa
1499: updateFloorItems();
1500: }
1501:
1502: void updateAllySafetyMap() {
1503: short i, j;
1504: short **playerCostMap, **monsterCostMap;
1505:
1506: rogue.updatedAllySafetyMapThisTurn = true;
1507:
1508: playerCostMap = allocGrid();
1509: monsterCostMap = allocGrid();
1510:
1511: for (i=0; i<DCOLS; i++) {
1512: for (j=0; j<DROWS; j++) {
1513: allySafetyMap[i][j] = 30000;
1514:
1515: playerCostMap[i][j] = monsterCostMap[i][j] = 1;
1516:
1517: if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)
1518: && (!cellHasTMFlag(i, j, TM_IS_SECRET) || (discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY))) {
1519:
1520: playerCostMap[i][j] = monsterCostMap[i][j] = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN;
1521: } else if (cellHasTerrainFlag(i, j, T_PATHING_BLOCKER & ~T_OBSTRUCTS_PASSABILITY)) {
1522: playerCostMap[i][j] = monsterCostMap[i][j] = PDS_FORBIDDEN;
1523: } else if (cellHasTerrainFlag(i, j, T_SACRED)) {
1524: playerCostMap[i][j] = 1;
1525: monsterCostMap[i][j] = PDS_FORBIDDEN;
1526: } else if ((pmap[i][j].flags & HAS_MONSTER) && monstersAreEnemies(&player, monsterAtLoc(i, j))) {
1527: playerCostMap[i][j] = 1;
1528: monsterCostMap[i][j] = PDS_FORBIDDEN;
1529: allySafetyMap[i][j] = 0;
1530: }
1531: }
1532: }
1533:
1534: playerCostMap[player.xLoc][player.yLoc] = PDS_FORBIDDEN;
1535: monsterCostMap[player.xLoc][player.yLoc] = PDS_FORBIDDEN;
1536:
1537: dijkstraScan(allySafetyMap, playerCostMap, false);
1538:
1539: for (i=0; i<DCOLS; i++) {
1540: for (j=0; j<DROWS; j++) {
1541: if (monsterCostMap[i][j] < 0) {
1542: continue;
1543: }
1544:
1545: if (allySafetyMap[i][j] == 30000) {
1546: allySafetyMap[i][j] = 150;
1547: }
1548:
1549: allySafetyMap[i][j] = 50 * allySafetyMap[i][j] / (50 + allySafetyMap[i][j]);
1550:
1551: allySafetyMap[i][j] *= -3;
1552:
1553: if (pmap[i][j].flags & IN_LOOP) {
1554: allySafetyMap[i][j] -= 10;
1555: }
1556: }
1557: }
1558: dijkstraScan(allySafetyMap, monsterCostMap, false);
1559:
1560: freeGrid(playerCostMap);
1561: freeGrid(monsterCostMap);
1562: }
1563:
1564: void resetDistanceCellInGrid(short **grid, short x, short y) {
1565: enum directions dir;
1566: short newX, newY;
1567: for (dir = 0; dir < 4; dir++) {
1568: newX = x + nbDirs[dir][0];
1569: newY = y + nbDirs[dir][1];
1570: if (coordinatesAreInMap(newX, newY)
1571: && grid[x][y] > grid[newX][newY] + 1) {
1572:
1573: grid[x][y] = grid[newX][newY] + 1;
1574: }
1575: }
1576: }
1577:
1578: void updateSafetyMap() {
1579: short i, j;
1580: short **playerCostMap, **monsterCostMap;
1581: creature *monst;
1582:
1583: rogue.updatedSafetyMapThisTurn = true;
1584:
1585: playerCostMap = allocGrid();
1586: monsterCostMap = allocGrid();
1587:
1588: for (i=0; i<DCOLS; i++) {
1589: for (j=0; j<DROWS; j++) {
1590: safetyMap[i][j] = 30000;
1591:
1592: playerCostMap[i][j] = monsterCostMap[i][j] = 1; // prophylactic
1593:
1594: if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)
1595: && (!cellHasTMFlag(i, j, TM_IS_SECRET) || (discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY))) {
1596:
1597: playerCostMap[i][j] = monsterCostMap[i][j] = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN;
1598: } else if (cellHasTerrainFlag(i, j, T_SACRED)) {
1599: playerCostMap[i][j] = 1;
1600: monsterCostMap[i][j] = PDS_FORBIDDEN;
1601: } else if (cellHasTerrainFlag(i, j, T_LAVA_INSTA_DEATH)) {
1602: monsterCostMap[i][j] = PDS_FORBIDDEN;
1603: if (player.status[STATUS_LEVITATING] || !player.status[STATUS_IMMUNE_TO_FIRE]) {
1604: playerCostMap[i][j] = 1;
1605: } else {
1606: playerCostMap[i][j] = PDS_FORBIDDEN;
1607: }
1608: } else {
1609: if (pmap[i][j].flags & HAS_MONSTER) {
1610: monst = monsterAtLoc(i, j);
1611: if ((monst->creatureState == MONSTER_SLEEPING
1612: || monst->turnsSpentStationary > 2
1613: || (monst->info.flags & MONST_GETS_TURN_ON_ACTIVATION)
1614: || monst->creatureState == MONSTER_ALLY)
1615: && monst->creatureState != MONSTER_FLEEING) {
1616:
1617: playerCostMap[i][j] = 1;
1618: monsterCostMap[i][j] = PDS_FORBIDDEN;
1619: continue;
1620: }
1621: }
1622:
1623: if (cellHasTerrainFlag(i, j, (T_AUTO_DESCENT | T_IS_DF_TRAP))) {
1624: monsterCostMap[i][j] = PDS_FORBIDDEN;
1625: if (player.status[STATUS_LEVITATING]) {
1626: playerCostMap[i][j] = 1;
1627: } else {
1628: playerCostMap[i][j] = PDS_FORBIDDEN;
1629: }
1630: } else if (cellHasTerrainFlag(i, j, T_IS_FIRE)) {
1631: monsterCostMap[i][j] = PDS_FORBIDDEN;
1632: if (player.status[STATUS_IMMUNE_TO_FIRE]) {
1633: playerCostMap[i][j] = 1;
1634: } else {
1635: playerCostMap[i][j] = PDS_FORBIDDEN;
1636: }
1637: } else if (cellHasTerrainFlag(i, j, (T_IS_DEEP_WATER | T_SPONTANEOUSLY_IGNITES))) {
1638: if (player.status[STATUS_LEVITATING]) {
1639: playerCostMap[i][j] = 1;
1640: } else {
1641: playerCostMap[i][j] = 5;
1642: }
1643: monsterCostMap[i][j] = 5;
1644: } else if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)
1645: && cellHasTMFlag(i, j, TM_IS_SECRET) && !(discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY)
1646: && !(pmap[i][j].flags & IN_FIELD_OF_VIEW)) {
1647: // Secret door that the player can't currently see
1648: playerCostMap[i][j] = 100;
1649: monsterCostMap[i][j] = 1;
1650: } else {
1651: playerCostMap[i][j] = monsterCostMap[i][j] = 1;
1652: }
1653: }
1654: }
1655: }
1656:
1657: safetyMap[player.xLoc][player.yLoc] = 0;
1658: playerCostMap[player.xLoc][player.yLoc] = 1;
1659: monsterCostMap[player.xLoc][player.yLoc] = PDS_FORBIDDEN;
1660:
1661: playerCostMap[rogue.upLoc[0]][rogue.upLoc[1]] = PDS_FORBIDDEN;
1662: monsterCostMap[rogue.upLoc[0]][rogue.upLoc[1]] = PDS_FORBIDDEN;
1663: playerCostMap[rogue.downLoc[0]][rogue.downLoc[1]] = PDS_FORBIDDEN;
1664: monsterCostMap[rogue.downLoc[0]][rogue.downLoc[1]] = PDS_FORBIDDEN;
1665:
1666: dijkstraScan(safetyMap, playerCostMap, false);
1667:
1668: for (i=0; i<DCOLS; i++) {
1669: for (j=0; j<DROWS; j++) {
1670: if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)
1671: && cellHasTMFlag(i, j, TM_IS_SECRET) && !(discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY)
1672: && !(pmap[i][j].flags & IN_FIELD_OF_VIEW)) {
1673:
1674: // Secret doors that the player can't see are not particularly safe themselves;
1675: // the areas behind them are.
1676: resetDistanceCellInGrid(safetyMap, i, j);
1677: }
1678: }
1679: }
1680:
1681: for (i=0; i<DCOLS; i++) {
1682: for (j=0; j<DROWS; j++) {
1683: if (monsterCostMap[i][j] < 0) {
1684: continue;
1685: }
1686:
1687: if (safetyMap[i][j] == 30000) {
1688: safetyMap[i][j] = 150;
1689: }
1690:
1691: safetyMap[i][j] = 50 * safetyMap[i][j] / (50 + safetyMap[i][j]);
1692:
1693: safetyMap[i][j] *= -3;
1694:
1695: if (pmap[i][j].flags & IN_LOOP) {
1696: safetyMap[i][j] -= 10;
1697: }
1698: }
1699: }
1700: dijkstraScan(safetyMap, monsterCostMap, false);
1701: for (i=0; i<DCOLS; i++) {
1702: for (j=0; j<DROWS; j++) {
1703: if (monsterCostMap[i][j] < 0) {
1704: safetyMap[i][j] = 30000;
1705: }
1706: }
1707: }
1708: freeGrid(playerCostMap);
1709: freeGrid(monsterCostMap);
1710: }
1711:
1712: void updateSafeTerrainMap() {
1713: short i, j;
1714: short **costMap;
1715: creature *monst;
1716:
1717: rogue.updatedMapToSafeTerrainThisTurn = true;
1718: costMap = allocGrid();
1719:
1720: for (i=0; i<DCOLS; i++) {
1721: for (j=0; j<DROWS; j++) {
1722: monst = monsterAtLoc(i, j);
1723: if (cellHasTerrainFlag(i, j, T_OBSTRUCTS_PASSABILITY)
1724: && (!cellHasTMFlag(i, j, TM_IS_SECRET) || (discoveredTerrainFlagsAtLoc(i, j) & T_OBSTRUCTS_PASSABILITY))) {
1725:
1726: costMap[i][j] = cellHasTerrainFlag(i, j, T_OBSTRUCTS_DIAGONAL_MOVEMENT) ? PDS_OBSTRUCTION : PDS_FORBIDDEN;
1727: rogue.mapToSafeTerrain[i][j] = 30000; // OOS prophylactic
1728: } else if ((monst && (monst->turnsSpentStationary > 1 || (monst->info.flags & MONST_GETS_TURN_ON_ACTIVATION)))
1729: || (cellHasTerrainFlag(i, j, T_PATHING_BLOCKER & ~T_HARMFUL_TERRAIN) && !cellHasTMFlag(i, j, TM_IS_SECRET))) {
1730:
1731: costMap[i][j] = PDS_FORBIDDEN;
1732: rogue.mapToSafeTerrain[i][j] = 30000;
1733: } else if (cellHasTerrainFlag(i, j, T_HARMFUL_TERRAIN) || pmap[i][j].layers[DUNGEON] == DOOR) {
1734: // The door thing is an aesthetically offensive but necessary hack to make sure
1735: // that monsters trying to find their way out of caustic gas do not sprint for
1736: // the doors. Doors are superficially free of gas, but as soon as they are opened,
1737: // gas will fill their tile, so they are not actually safe. Without this fix,
1738: // allies will fidget back and forth in a doorway while they asphyxiate.
1739: // This will have to do. It's a difficult problem to solve elegantly.
1740: costMap[i][j] = 1;
1741: rogue.mapToSafeTerrain[i][j] = 30000;
1742: } else {
1743: costMap[i][j] = 1;
1744: rogue.mapToSafeTerrain[i][j] = 0;
1745: }
1746: }
1747: }
1748: dijkstraScan(rogue.mapToSafeTerrain, costMap, false);
1749: freeGrid(costMap);
1750: }
1751:
1752: void processIncrementalAutoID() {
1753: item *theItem, *autoIdentifyItems[3] = {rogue.armor, rogue.ringLeft, rogue.ringRight};
1754: char buf[DCOLS*3], theItemName[DCOLS*3];
1755: short i;
1756:
1757: for (i=0; i<3; i++) {
1758: theItem = autoIdentifyItems[i];
1759: if (theItem
1760: && theItem->charges > 0
1761: && (!(theItem->flags & ITEM_IDENTIFIED) || ((theItem->category & RING) && !ringTable[theItem->kind].identified))) {
1762:
1763: theItem->charges--;
1764: if (theItem->charges <= 0) {
1765: itemName(theItem, theItemName, false, false, NULL);
1766: sprintf(buf, "you are now familiar enough with your %s to identify it.", theItemName);
1767: messageWithColor(buf, &itemMessageColor, false);
1768:
1769: if (theItem->category & ARMOR) {
1770: // Don't necessarily reveal the armor's runic specifically, just that it has one.
1771: theItem->flags |= ITEM_IDENTIFIED;
1772: } else if (theItem->category & RING) {
1773: identify(theItem);
1774: }
1775: updateIdentifiableItems();
1776:
1777: itemName(theItem, theItemName, true, true, NULL);
1778: sprintf(buf, "%s %s.", (theItem->quantity > 1 ? "they are" : "it is"), theItemName);
1779: messageWithColor(buf, &itemMessageColor, false);
1780: }
1781: }
1782: }
1783: }
1784:
1785: short staffChargeDuration(const item *theItem) {
1786: // staffs of blinking and obstruction recharge half as fast so they're less powerful
1787: return (theItem->kind == STAFF_BLINKING || theItem->kind == STAFF_OBSTRUCTION ? 10000 : 5000) / theItem->enchant1;
1788: }
1789:
1790: // Multiplier can be negative, in which case staffs and charms will be drained instead of recharged.
1791: void rechargeItemsIncrementally(short multiplier) {
1792: item *theItem;
1793: char buf[DCOLS*3], theItemName[DCOLS*3];
1794: short rechargeIncrement, staffRechargeDuration;
1795:
1796: if (rogue.wisdomBonus) {
1797: // at level 27, you recharge anything to full in one turn
1798: rechargeIncrement = 10 * ringWisdomMultiplier(rogue.wisdomBonus * FP_FACTOR) / FP_FACTOR;
1799: } else {
1800: rechargeIncrement = 10;
1801: }
1802:
1803: rechargeIncrement *= multiplier;
1804:
1805: for (theItem = packItems->nextItem; theItem != NULL; theItem = theItem->nextItem) {
1806: if (theItem->category & STAFF) {
1807: if (theItem->charges < theItem->enchant1 && rechargeIncrement > 0
1808: || theItem->charges > 0 && rechargeIncrement < 0) {
1809:
1810: theItem->enchant2 -= rechargeIncrement;
1811: }
1812: staffRechargeDuration = staffChargeDuration(theItem);
1813: while (theItem->enchant2 <= 0) {
1814: // if it's time to add a staff charge
1815: if (theItem->charges < theItem->enchant1) {
1816: theItem->charges++;
1817: }
1818: theItem->enchant2 += randClumpedRange(max(staffRechargeDuration / 3, 1), staffRechargeDuration * 5 / 3, 3);
1819: }
1820: while (theItem->enchant2 > staffRechargeDuration * 5 / 3) {
1821: // if it's time to drain a staff charge
1822: if (theItem->charges > 0) {
1823: theItem->charges--;
1824: }
1825: theItem->enchant2 -= staffRechargeDuration;
1826: }
1827: } else if ((theItem->category & CHARM) && (theItem->charges > 0)) {
1828: theItem->charges = clamp(theItem->charges - multiplier, 0, charmRechargeDelay(theItem->kind, theItem->enchant1));
1829: if (theItem->charges == 0) {
1830: itemName(theItem, theItemName, false, false, NULL);
1831: sprintf(buf, "your %s has recharged.", theItemName);
1832: message(buf, false);
1833: }
1834: }
1835: }
1836: }
1837:
1838: void extinguishFireOnCreature(creature *monst) {
1839:
1840: monst->status[STATUS_BURNING] = 0;
1841: if (monst == &player) {
1842: player.info.foreColor = &white;
1843: rogue.minersLight.lightColor = &minersLightColor;
1844: refreshDungeonCell(player.xLoc, player.yLoc);
1845: updateVision(true);
1846: message("you are no longer on fire.", false);
1847: }
1848: }
1849:
1850: // n is the monster's depthLevel - 1.
1851: void monsterEntersLevel(creature *monst, short n) {
1852: creature *prevMonst;
1853: char monstName[COLS], buf[COLS];
1854: boolean pit = false;
1855:
1856: if (rogue.patchVersion >= 3) {
1857: levels[n].mapStorage[monst->xLoc][monst->yLoc].flags &= ~HAS_MONSTER;
1858: }
1859:
1860: // place traversing monster near the stairs on this level
1861: if (monst->bookkeepingFlags & MB_APPROACHING_DOWNSTAIRS) {
1862: monst->xLoc = rogue.upLoc[0];
1863: monst->yLoc = rogue.upLoc[1];
1864: } else if (monst->bookkeepingFlags & MB_APPROACHING_UPSTAIRS) {
1865: monst->xLoc = rogue.downLoc[0];
1866: monst->yLoc = rogue.downLoc[1];
1867: } else if (monst->bookkeepingFlags & MB_APPROACHING_PIT) { // jumping down pit
1868: pit = true;
1869: monst->xLoc = levels[n].playerExitedVia[0];
1870: monst->yLoc = levels[n].playerExitedVia[1];
1871: } else {
1872: brogueAssert(false);
1873: }
1874: monst->depth = rogue.depthLevel;
1875: monst->targetCorpseLoc[0] = monst->targetCorpseLoc[1] = 0;
1876:
1877: if (!pit) {
1878: getQualifyingPathLocNear(&(monst->xLoc), &(monst->yLoc), monst->xLoc, monst->yLoc, true,
1879: T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(monst->info)), 0,
1880: avoidedFlagsForMonster(&(monst->info)), HAS_STAIRS, false);
1881: }
1882: if (!pit
1883: && (pmap[monst->xLoc][monst->yLoc].flags & (HAS_PLAYER | HAS_MONSTER))
1884: && !(terrainFlags(monst->xLoc, monst->yLoc) & avoidedFlagsForMonster(&(monst->info)))) {
1885: // Monsters using the stairs will displace any creatures already located there, to thwart stair-dancing.
1886: prevMonst = monsterAtLoc(monst->xLoc, monst->yLoc);
1887: brogueAssert(prevMonst);
1888: getQualifyingPathLocNear(&(prevMonst->xLoc), &(prevMonst->yLoc), monst->xLoc, monst->yLoc, true,
1889: T_DIVIDES_LEVEL & avoidedFlagsForMonster(&(prevMonst->info)), 0,
1890: avoidedFlagsForMonster(&(prevMonst->info)), (HAS_MONSTER | HAS_PLAYER | HAS_STAIRS), false);
1891: pmap[monst->xLoc][monst->yLoc].flags &= ~(HAS_PLAYER | HAS_MONSTER);
1892: pmap[prevMonst->xLoc][prevMonst->yLoc].flags |= (prevMonst == &player ? HAS_PLAYER : HAS_MONSTER);
1893: refreshDungeonCell(prevMonst->xLoc, prevMonst->yLoc);
1894: //DEBUG printf("\nBumped a creature (%s) from (%i, %i) to (%i, %i).", prevMonst->info.monsterName, monst->xLoc, monst->yLoc, prevMonst->xLoc, prevMonst->yLoc);
1895: }
1896:
1897: // remove traversing monster from other level monster chain
1898: if (monst == levels[n].monsters) {
1899: levels[n].monsters = monst->nextCreature;
1900: } else {
1901: for (prevMonst = levels[n].monsters; prevMonst->nextCreature != monst; prevMonst = prevMonst->nextCreature);
1902: prevMonst->nextCreature = monst->nextCreature;
1903: }
1904:
1905: // prepend traversing monster to current level monster chain
1906: monst->nextCreature = monsters->nextCreature;
1907: monsters->nextCreature = monst;
1908:
1909: monst->status[STATUS_ENTERS_LEVEL_IN] = 0;
1910: monst->bookkeepingFlags |= MB_PREPLACED;
1911: monst->bookkeepingFlags &= ~MB_IS_FALLING;
1912: restoreMonster(monst, NULL, NULL);
1913: //DEBUG printf("\nPlaced a creature (%s) at (%i, %i).", monst->info.monsterName, monst->xLoc, monst->yLoc);
1914: monst->ticksUntilTurn = monst->movementSpeed;
1915: refreshDungeonCell(monst->xLoc, monst->yLoc);
1916:
1917: if (pit) {
1918: monsterName(monstName, monst, true);
1919: if (!monst->status[STATUS_LEVITATING]) {
1920: if (inflictDamage(NULL, monst, randClumpedRange(6, 12, 2), &red, false)) {
1921: if (canSeeMonster(monst)) {
1922: sprintf(buf, "%s plummets from above and splatters against the ground!", monstName);
1923: messageWithColor(buf, messageColorFromVictim(monst), false);
1924: }
1925: } else {
1926: if (canSeeMonster(monst)) {
1927: sprintf(buf, "%s falls from above and crashes to the ground!", monstName);
1928: message(buf, false);
1929: }
1930: }
1931: } else if (canSeeMonster(monst)) {
1932: sprintf(buf, "%s swoops into the cavern from above.", monstName);
1933: message(buf, false);
1934: }
1935: }
1936: }
1937:
1938: void monstersApproachStairs() {
1939: creature *monst, *nextMonst;
1940: short n;
1941:
1942: for (n = rogue.depthLevel - 2; n <= rogue.depthLevel; n += 2) { // cycle through previous and next level
1943: if (n >= 0 && n < DEEPEST_LEVEL && levels[n].visited) {
1944: for (monst = levels[n].monsters; monst != NULL;) {
1945: nextMonst = monst->nextCreature;
1946: if (monst->status[STATUS_ENTERS_LEVEL_IN] > 1) {
1947: monst->status[STATUS_ENTERS_LEVEL_IN]--;
1948: } else if (monst->status[STATUS_ENTERS_LEVEL_IN] == 1) {
1949: monsterEntersLevel(monst, n);
1950: }
1951: monst = nextMonst;
1952: }
1953: }
1954: }
1955:
1956: if (rogue.yendorWarden
1957: && abs(rogue.depthLevel - rogue.yendorWarden->depth) > 1) {
1958:
1959: updateYendorWardenTracking();
1960: }
1961: }
1962:
1963: void decrementPlayerStatus() {
1964: // Handle hunger.
1965: if (!player.status[STATUS_PARALYZED]) {
1966: // No nutrition is expended while paralyzed.
1967: if (player.status[STATUS_NUTRITION] > 0) {
1968: if (!numberOfMatchingPackItems(AMULET, 0, 0, false) || rand_percent(20)) {
1969: player.status[STATUS_NUTRITION]--;
1970: }
1971: }
1972: checkNutrition();
1973: }
1974:
1975: if (player.status[STATUS_TELEPATHIC] > 0 && !--player.status[STATUS_TELEPATHIC]) {
1976: updateVision(true);
1977: message("your preternatural mental sensitivity fades.", false);
1978: }
1979:
1980: if (player.status[STATUS_DARKNESS] > 0) {
1981: player.status[STATUS_DARKNESS]--;
1982: updateMinersLightRadius();
1983: //updateVision();
1984: }
1985:
1986: if (player.status[STATUS_HALLUCINATING] > 0 && !--player.status[STATUS_HALLUCINATING]) {
1987: displayLevel();
1988: message("your hallucinations fade.", false);
1989: }
1990:
1991: if (player.status[STATUS_LEVITATING] > 0 && !--player.status[STATUS_LEVITATING]) {
1992: message("you are no longer levitating.", false);
1993: }
1994:
1995: if (player.status[STATUS_CONFUSED] > 0 && !--player.status[STATUS_CONFUSED]) {
1996: message("you no longer feel confused.", false);
1997: }
1998:
1999: if (player.status[STATUS_NAUSEOUS] > 0 && !--player.status[STATUS_NAUSEOUS]) {
2000: message("you feel less nauseous.", false);
2001: }
2002:
2003: if (player.status[STATUS_PARALYZED] > 0 && !--player.status[STATUS_PARALYZED]) {
2004: message("you can move again.", false);
2005: }
2006:
2007: if (player.status[STATUS_HASTED] > 0 && !--player.status[STATUS_HASTED]) {
2008: player.movementSpeed = player.info.movementSpeed;
2009: player.attackSpeed = player.info.attackSpeed;
2010: synchronizePlayerTimeState();
2011: message("your supernatural speed fades.", false);
2012: }
2013:
2014: if (player.status[STATUS_SLOWED] > 0 && !--player.status[STATUS_SLOWED]) {
2015: player.movementSpeed = player.info.movementSpeed;
2016: player.attackSpeed = player.info.attackSpeed;
2017: synchronizePlayerTimeState();
2018: message("your normal speed resumes.", false);
2019: }
2020:
2021: if (player.status[STATUS_WEAKENED] > 0 && !--player.status[STATUS_WEAKENED]) {
2022: player.weaknessAmount = 0;
2023: message("strength returns to your muscles as the weakening toxin wears off.", false);
2024: updateEncumbrance();
2025: }
2026:
2027: if (player.status[STATUS_DONNING]) {
2028: player.status[STATUS_DONNING]--;
2029: recalculateEquipmentBonuses();
2030: }
2031:
2032: if (player.status[STATUS_IMMUNE_TO_FIRE] > 0 && !--player.status[STATUS_IMMUNE_TO_FIRE]) {
2033: message("you no longer feel immune to fire.", false);
2034: }
2035:
2036: if (player.status[STATUS_STUCK] && !cellHasTerrainFlag(player.xLoc, player.yLoc, T_ENTANGLES)) {
2037: player.status[STATUS_STUCK] = 0;
2038: }
2039:
2040: if (player.status[STATUS_EXPLOSION_IMMUNITY]) {
2041: player.status[STATUS_EXPLOSION_IMMUNITY]--;
2042: }
2043:
2044: if (player.status[STATUS_DISCORDANT]) {
2045: player.status[STATUS_DISCORDANT]--;
2046: }
2047:
2048: if (player.status[STATUS_AGGRAVATING]) {
2049: player.status[STATUS_AGGRAVATING]--;
2050: }
2051:
2052: if (player.status[STATUS_SHIELDED]) {
2053: player.status[STATUS_SHIELDED] -= player.maxStatus[STATUS_SHIELDED] / 20;
2054: if (player.status[STATUS_SHIELDED] <= 0) {
2055: player.status[STATUS_SHIELDED] = player.maxStatus[STATUS_SHIELDED] = 0;
2056: }
2057: }
2058:
2059: if (player.status[STATUS_INVISIBLE] > 0 && !--player.status[STATUS_INVISIBLE]) {
2060: message("you are no longer invisible.", false);
2061: }
2062:
2063: if (rogue.monsterSpawnFuse <= 0) {
2064: spawnPeriodicHorde();
2065: rogue.monsterSpawnFuse = rand_range(125, 175);
2066: }
2067: }
2068:
2069: boolean dangerChanged(boolean danger[4]) {
2070: enum directions dir;
2071: short newX, newY;
2072: for (dir = 0; dir < 4; dir++) {
2073: newX = player.xLoc + nbDirs[dir][0];
2074: newY = player.yLoc + nbDirs[dir][1];
2075: if (danger[dir] != monsterAvoids(&player, newX, newY)) {
2076: return true;
2077: }
2078: }
2079: return false;
2080: }
2081:
2082: void autoRest() {
2083: short i = 0;
2084: boolean initiallyEmbedded; // Stop as soon as we're free from crystal.
2085: boolean danger[4];
2086: short newX, newY;
2087: enum directions dir;
2088:
2089: for (dir = 0; dir < 4; dir++) {
2090: newX = player.xLoc + nbDirs[dir][0];
2091: newY = player.yLoc + nbDirs[dir][1];
2092: danger[dir] = monsterAvoids(&player, newX, newY);
2093: }
2094:
2095: rogue.disturbed = false;
2096: rogue.automationActive = true;
2097: initiallyEmbedded = cellHasTerrainFlag(player.xLoc, player.yLoc, T_OBSTRUCTS_PASSABILITY);
2098:
2099: if ((player.currentHP < player.info.maxHP
2100: || player.status[STATUS_HALLUCINATING]
2101: || player.status[STATUS_CONFUSED]
2102: || player.status[STATUS_NAUSEOUS]
2103: || player.status[STATUS_POISONED]
2104: || player.status[STATUS_DARKNESS]
2105: || initiallyEmbedded)
2106: && !rogue.disturbed) {
2107: while (i++ < TURNS_FOR_FULL_REGEN
2108: && (player.currentHP < player.info.maxHP
2109: || player.status[STATUS_HALLUCINATING]
2110: || player.status[STATUS_CONFUSED]
2111: || player.status[STATUS_NAUSEOUS]
2112: || player.status[STATUS_POISONED]
2113: || player.status[STATUS_DARKNESS]
2114: || cellHasTerrainFlag(player.xLoc, player.yLoc, T_OBSTRUCTS_PASSABILITY))
2115: && !rogue.disturbed
2116: && (!initiallyEmbedded || cellHasTerrainFlag(player.xLoc, player.yLoc, T_OBSTRUCTS_PASSABILITY))) {
2117:
2118: recordKeystroke(REST_KEY, false, false);
2119: rogue.justRested = true;
2120: playerTurnEnded();
2121: if (dangerChanged(danger) || pauseBrogue(1)) {
2122: rogue.disturbed = true;
2123: }
2124: }
2125: } else {
2126: for (i=0; i<100 && !rogue.disturbed; i++) {
2127: recordKeystroke(REST_KEY, false, false);
2128: rogue.justRested = true;
2129: playerTurnEnded();
2130: if (dangerChanged(danger) || pauseBrogue(1)) {
2131: rogue.disturbed = true;
2132: }
2133: }
2134: }
2135: rogue.automationActive = false;
2136: }
2137:
2138: void manualSearch() {
2139: recordKeystroke(SEARCH_KEY, false, false);
2140:
2141: if (player.status[STATUS_SEARCHING] <= 0) {
2142: player.status[STATUS_SEARCHING] = 0;
2143: player.maxStatus[STATUS_SEARCHING] = 5;
2144: }
2145:
2146: player.status[STATUS_SEARCHING] += 1;
2147:
2148: /* The search strength values were chosen based on equating the expected
2149: number of cells discovered by 5x 80 searches (1.7.4) and 1x 200 search
2150: (1.7.5). 1x200 discovers an average of 932 cells; 5.65 times more cells than
2151: the 165 of 5x80. This factor is intepreted as the advantage of undelayed
2152: searching. Hence, we chose a short radius r and a long radius s such that
2153:
2154: 4 * 5.65 * E_r + E_s ~= 932
2155:
2156: where E_x is the expected no. of cells discovered with radius x. We choose
2157: r=60, s=160, giving 852 < 932 (under to account for removal of 1.7.5 stealth
2158: range doubling).
2159: */
2160: short searchStrength = 0;
2161: if (player.status[STATUS_SEARCHING] < 5) {
2162: searchStrength = (rogue.awarenessBonus >= 0 ? 60 : 30);
2163: } else {
2164: // Do a final, larger-radius search on the fifth search in a row
2165: searchStrength = 160;
2166: message("you finish your detailed search of the area.", false);
2167: player.status[STATUS_SEARCHING] = 0;
2168: }
2169:
2170: // ensure our search is no weaker than the current passive search
2171: search(max(searchStrength, rogue.awarenessBonus + 30));
2172:
2173: rogue.justSearched = true;
2174: playerTurnEnded();
2175: }
2176:
2177: // Call this periodically (when haste/slow wears off and when moving between depths)
2178: // to keep environmental updates in sync with player turns.
2179: void synchronizePlayerTimeState() {
2180: rogue.ticksTillUpdateEnvironment = player.ticksUntilTurn;
2181: }
2182:
2183: void playerRecoversFromAttacking(boolean anAttackHit) {
2184: if (player.ticksUntilTurn >= 0) {
2185: // Don't do this if the player's weapon of speed just fired.
2186: if (rogue.weapon && (rogue.weapon->flags & ITEM_ATTACKS_STAGGER) && anAttackHit) {
2187: player.ticksUntilTurn += 2 * player.attackSpeed;
2188: } else if (rogue.weapon && (rogue.weapon->flags & ITEM_ATTACKS_QUICKLY)) {
2189: player.ticksUntilTurn += player.attackSpeed / 2;
2190: } else {
2191: player.ticksUntilTurn += player.attackSpeed;
2192: }
2193: }
2194: }
2195:
2196:
2197: static void recordCurrentCreatureHealths() {
2198: creature *monst;
2199: CYCLE_MONSTERS_AND_PLAYERS(monst) {
2200: monst->previousHealthPoints = monst->currentHP;
2201: }
2202: }
2203:
2204: // This is the dungeon schedule manager, called every time the player's turn comes to an end.
2205: // It hands control over to monsters until they've all expended their accumulated ticks,
2206: // updating the environment (gas spreading, flames spreading and burning out, etc.) every
2207: // 100 ticks.
2208: void playerTurnEnded() {
2209: short soonestTurn, damage, turnsRequiredToShore, turnsToShore;
2210: char buf[COLS], buf2[COLS];
2211: creature *monst, *monst2, *nextMonst;
2212: boolean fastForward = false;
2213: short oldRNG;
2214:
2215: brogueAssert(rogue.RNG == RNG_SUBSTANTIVE);
2216:
2217: handleXPXP();
2218: resetDFMessageEligibility();
2219: recordCurrentCreatureHealths();
2220:
2221: if (player.bookkeepingFlags & MB_IS_FALLING) {
2222: playerFalls();
2223: if (!rogue.gameHasEnded) {
2224: handleHealthAlerts();
2225: }
2226: return;
2227: }
2228:
2229: // This happens in updateEnvironment, but some monsters move faster than the
2230: // environment updates in the loop below. This means they need to fall at
2231: // the start of the turn to avoid them being able to act while suspended
2232: // over a chasm
2233: monstersFall();
2234:
2235: do {
2236: if (rogue.gameHasEnded) {
2237: return;
2238: }
2239:
2240: if (!player.status[STATUS_PARALYZED]) {
2241: rogue.playerTurnNumber++; // So recordings don't register more turns than you actually have.
2242: }
2243: rogue.absoluteTurnNumber++;
2244:
2245: if (player.status[STATUS_INVISIBLE]) {
2246: rogue.scentTurnNumber += 10; // Your scent fades very quickly while you are invisible.
2247: } else {
2248: rogue.scentTurnNumber += 3; // this must happen per subjective player time
2249: }
2250: if (rogue.scentTurnNumber > 20000) {
2251: resetScentTurnNumber();
2252: }
2253:
2254: //updateFlavorText();
2255:
2256: // Regeneration/starvation:
2257: if (player.status[STATUS_NUTRITION] <= 0) {
2258: player.currentHP--;
2259: if (player.currentHP <= 0) {
2260: gameOver("Starved to death", true);
2261: return;
2262: }
2263: } else if (player.currentHP < player.info.maxHP
2264: && !player.status[STATUS_POISONED]) {
2265: if ((player.turnsUntilRegen -= 1000) <= 0) {
2266: player.currentHP++;
2267: if (player.previousHealthPoints < player.currentHP) {
2268: player.previousHealthPoints++; // Regeneration doesn't display on the status bar.
2269: }
2270: player.turnsUntilRegen += player.info.turnsBetweenRegen;
2271: }
2272: if (player.regenPerTurn) {
2273: player.currentHP += player.regenPerTurn;
2274: if (player.previousHealthPoints < player.currentHP) {
2275: player.previousHealthPoints = min(player.currentHP, player.previousHealthPoints + player.regenPerTurn);
2276: }
2277: }
2278: }
2279:
2280: if (rogue.awarenessBonus > -30 && !(pmap[player.xLoc][player.yLoc].flags & SEARCHED_FROM_HERE)) {
2281: // Low-grade auto-search wherever you step, but only once per tile.
2282: search(rogue.awarenessBonus + 30);
2283: pmap[player.xLoc][player.yLoc].flags |= SEARCHED_FROM_HERE;
2284: }
2285: if (!rogue.justSearched && player.status[STATUS_SEARCHING] > 0) {
2286: // Searching only "charges up" when done on consecutive turns
2287: player.status[STATUS_SEARCHING] = 0;
2288: }
2289: if (rogue.staleLoopMap) {
2290: analyzeMap(false); // Don't need to update the chokemap.
2291: }
2292:
2293: for (monst = monsters->nextCreature; monst != NULL; monst = nextMonst) {
2294: nextMonst = monst->nextCreature;
2295: if ((monst->bookkeepingFlags & MB_BOUND_TO_LEADER)
2296: && (!monst->leader || !(monst->bookkeepingFlags & MB_FOLLOWER))
2297: && (monst->creatureState != MONSTER_ALLY)) {
2298:
2299: killCreature(monst, false);
2300: if (canSeeMonster(monst)) {
2301: monsterName(buf2, monst, true);
2302: sprintf(buf, "%s dissipates into thin air", buf2);
2303: combatMessage(buf, messageColorFromVictim(monst));
2304: }
2305: }
2306: }
2307:
2308: if (player.status[STATUS_BURNING] > 0) {
2309: damage = rand_range(1, 3);
2310: if (!(player.status[STATUS_IMMUNE_TO_FIRE]) && inflictDamage(NULL, &player, damage, &orange, true)) {
2311: gameOver("Burned to death", true);
2312: }
2313: if (!--player.status[STATUS_BURNING]) {
2314: extinguishFireOnCreature(&player);
2315: }
2316: }
2317:
2318: if (player.status[STATUS_POISONED] > 0) {
2319: player.status[STATUS_POISONED]--;
2320: if (inflictDamage(NULL, &player, player.poisonAmount, &green, true)) {
2321: gameOver("Died from poison", true);
2322: }
2323: if (!player.status[STATUS_POISONED]) {
2324: player.poisonAmount = 0;
2325: }
2326: }
2327:
2328: if (player.ticksUntilTurn == 0) { // attacking adds ticks elsewhere
2329: player.ticksUntilTurn += player.movementSpeed;
2330: } else if (player.ticksUntilTurn < 0) { // if he gets a free turn
2331: player.ticksUntilTurn = 0;
2332: }
2333:
2334: updateScent();
2335: // updateVision(true);
2336: // rogue.aggroRange = currentAggroValue();
2337: // if (rogue.displayAggroRangeMode) {
2338: // displayLevel();
2339: // }
2340: rogue.updatedSafetyMapThisTurn = false;
2341: rogue.updatedAllySafetyMapThisTurn = false;
2342: rogue.updatedMapToSafeTerrainThisTurn = false;
2343:
2344: for (monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
2345: if (D_SAFETY_VISION || monst->creatureState == MONSTER_FLEEING && pmap[monst->xLoc][monst->yLoc].flags & IN_FIELD_OF_VIEW) {
2346: updateSafetyMap(); // only if there is a fleeing monster who can see the player
2347: break;
2348: }
2349: }
2350:
2351: if (D_BULLET_TIME && !rogue.justRested) {
2352: player.ticksUntilTurn = 0;
2353: }
2354:
2355: applyGradualTileEffectsToCreature(&player, player.ticksUntilTurn);
2356:
2357: if (rogue.gameHasEnded) {
2358: return;
2359: }
2360:
2361: rogue.heardCombatThisTurn = false;
2362:
2363: while (player.ticksUntilTurn > 0) {
2364: soonestTurn = 10000;
2365: for(monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
2366: soonestTurn = min(soonestTurn, monst->ticksUntilTurn);
2367: }
2368: soonestTurn = min(soonestTurn, player.ticksUntilTurn);
2369: soonestTurn = min(soonestTurn, rogue.ticksTillUpdateEnvironment);
2370: for(monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
2371: monst->ticksUntilTurn -= soonestTurn;
2372: }
2373: rogue.ticksTillUpdateEnvironment -= soonestTurn;
2374: if (rogue.ticksTillUpdateEnvironment <= 0) {
2375: rogue.ticksTillUpdateEnvironment += 100;
2376:
2377: // stuff that happens periodically according to an objective time measurement goes here:
2378: rechargeItemsIncrementally(1); // staffs recharge every so often
2379: processIncrementalAutoID(); // become more familiar with worn armor and rings
2380: rogue.monsterSpawnFuse--; // monsters spawn in the level every so often
2381:
2382: for (monst = monsters->nextCreature; monst != NULL;) {
2383: nextMonst = monst->nextCreature;
2384: applyInstantTileEffectsToCreature(monst);
2385: monst = nextMonst; // this weirdness is in case the monster dies in the previous step
2386: }
2387:
2388: for (monst = monsters->nextCreature; monst != NULL;) {
2389: nextMonst = monst->nextCreature;
2390: decrementMonsterStatus(monst);
2391: monst = nextMonst;
2392: }
2393:
2394: // monsters with a dungeon feature spawn it every so often
2395: for (monst = monsters->nextCreature; monst != NULL;) {
2396: nextMonst = monst->nextCreature;
2397: if (monst->info.DFChance
2398: && !(monst->info.flags & MONST_GETS_TURN_ON_ACTIVATION)
2399: && rand_percent(monst->info.DFChance)) {
2400:
2401: spawnDungeonFeature(monst->xLoc, monst->yLoc, &dungeonFeatureCatalog[monst->info.DFType], true, false);
2402: }
2403: monst = nextMonst;
2404: }
2405:
2406: updateEnvironment(); // Update fire and gas, items floating around in water, monsters falling into chasms, etc.
2407: decrementPlayerStatus();
2408: applyInstantTileEffectsToCreature(&player);
2409: if (rogue.gameHasEnded) { // caustic gas, lava, trapdoor, etc.
2410: return;
2411: }
2412: monstersApproachStairs();
2413:
2414: if (player.ticksUntilTurn > 100 && !fastForward) {
2415: fastForward = rogue.playbackFastForward || pauseBrogue(25);
2416: }
2417:
2418: // Rolling waypoint refresh:
2419: rogue.wpRefreshTicker++;
2420: if (rogue.wpRefreshTicker >= rogue.wpCount) {
2421: rogue.wpRefreshTicker = 0;
2422: }
2423: refreshWaypoint(rogue.wpRefreshTicker);
2424: }
2425:
2426: for (monst = monsters->nextCreature; (monst != NULL) && (rogue.gameHasEnded == false); monst = monst->nextCreature) {
2427: if (monst->ticksUntilTurn <= 0) {
2428: if (monst->currentHP > monst->info.maxHP) {
2429: monst->currentHP = monst->info.maxHP;
2430: }
2431:
2432: if ((monst->info.flags & MONST_GETS_TURN_ON_ACTIVATION)
2433: || monst->status[STATUS_PARALYZED]
2434: || monst->status[STATUS_ENTRANCED]
2435: || (monst->bookkeepingFlags & MB_CAPTIVE)) {
2436:
2437: // Do not pass go; do not collect 200 gold.
2438: monst->ticksUntilTurn = monst->movementSpeed;
2439: } else {
2440: monstersTurn(monst);
2441: }
2442:
2443: for(monst2 = monsters->nextCreature; monst2 != NULL; monst2 = monst2->nextCreature) {
2444: if (monst2 == monst) { // monst still alive and on the level
2445: applyGradualTileEffectsToCreature(monst, monst->ticksUntilTurn);
2446: break;
2447: }
2448: }
2449: monst = monsters; // loop through from the beginning to be safe
2450: }
2451: }
2452:
2453: player.ticksUntilTurn -= soonestTurn;
2454:
2455: if (rogue.gameHasEnded) {
2456: return;
2457: }
2458: }
2459: // DEBUG displayLevel();
2460: //checkForDungeonErrors();
2461:
2462: updateVision(true);
2463: rogue.aggroRange = currentAggroValue();
2464: if (rogue.displayAggroRangeMode) {
2465: displayLevel();
2466: }
2467:
2468: for(monst = monsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
2469: if (canSeeMonster(monst) && !(monst->bookkeepingFlags & (MB_WAS_VISIBLE | MB_ALREADY_SEEN))) {
2470: monst->bookkeepingFlags |= MB_WAS_VISIBLE;
2471: if (monst->creatureState != MONSTER_ALLY) {
2472: rogue.disturbed = true;
2473: if (rogue.cautiousMode || rogue.automationActive) {
2474: oldRNG = rogue.RNG;
2475: rogue.RNG = RNG_COSMETIC;
2476: //assureCosmeticRNG;
2477: monsterName(buf2, monst, false);
2478: sprintf(buf, "you %s a%s %s",
2479: playerCanDirectlySee(monst->xLoc, monst->yLoc) ? "see" : "sense",
2480: (isVowelish(buf2) ? "n" : ""),
2481: buf2);
2482: if (rogue.cautiousMode) {
2483: strcat(buf, ".");
2484: message(buf, true);
2485: } else {
2486: combatMessage(buf, 0);
2487: }
2488: restoreRNG;
2489: }
2490: }
2491: if (cellHasTerrainFlag(monst->xLoc, monst->yLoc, T_OBSTRUCTS_PASSABILITY)
2492: && cellHasTMFlag(monst->xLoc, monst->yLoc, TM_IS_SECRET)) {
2493:
2494: discover(monst->xLoc, monst->yLoc);
2495: }
2496: if (canDirectlySeeMonster(monst)) {
2497: if (rogue.weapon && rogue.weapon->flags & ITEM_RUNIC
2498: && rogue.weapon->enchant2 == W_SLAYING
2499: && !(rogue.weapon->flags & ITEM_RUNIC_HINTED)
2500: && monsterIsInClass(monst, rogue.weapon->vorpalEnemy)) {
2501:
2502: rogue.weapon->flags |= ITEM_RUNIC_HINTED;
2503: itemName(rogue.weapon, buf2, false, false, NULL);
2504: sprintf(buf, "the runes on your %s gleam balefully.", buf2);
2505: messageWithColor(buf, &itemMessageColor, true);
2506: }
2507: if (rogue.armor && rogue.armor->flags & ITEM_RUNIC
2508: && rogue.armor->enchant2 == A_IMMUNITY
2509: && !(rogue.armor->flags & ITEM_RUNIC_HINTED)
2510: && monsterIsInClass(monst, rogue.armor->vorpalEnemy)) {
2511:
2512: rogue.armor->flags |= ITEM_RUNIC_HINTED;
2513: itemName(rogue.armor, buf2, false, false, NULL);
2514: sprintf(buf, "the runes on your %s glow protectively.", buf2);
2515: messageWithColor(buf, &itemMessageColor, true);
2516: }
2517: }
2518: } else if (!canSeeMonster(monst)
2519: && (monst->bookkeepingFlags & MB_WAS_VISIBLE)
2520: && !(monst->bookkeepingFlags & MB_CAPTIVE)) {
2521: monst->bookkeepingFlags &= ~MB_WAS_VISIBLE;
2522: }
2523: }
2524:
2525: displayCombatText();
2526:
2527: if (player.status[STATUS_PARALYZED]) {
2528: if (!fastForward) {
2529: fastForward = rogue.playbackFastForward || pauseBrogue(25);
2530: }
2531: }
2532:
2533: //checkNutrition(); // Now handled within decrementPlayerStatus().
2534: if (!rogue.playbackFastForward) {
2535: shuffleTerrainColors(100, false);
2536: }
2537:
2538: displayAnnotation();
2539:
2540: refreshSideBar(-1, -1, false);
2541:
2542: applyInstantTileEffectsToCreature(&player);
2543: if (rogue.gameHasEnded) { // caustic gas, lava, trapdoor, etc.
2544: return;
2545: }
2546:
2547: if (player.currentHP > player.info.maxHP) {
2548: player.currentHP = player.info.maxHP;
2549: }
2550:
2551: if (player.bookkeepingFlags & MB_IS_FALLING) {
2552: playerFalls();
2553: handleHealthAlerts();
2554: return;
2555: }
2556:
2557: } while (player.status[STATUS_PARALYZED]);
2558:
2559: rogue.justRested = false;
2560: rogue.justSearched = false;
2561: updateFlavorText();
2562:
2563: if (!rogue.updatedMapToShoreThisTurn) {
2564: updateMapToShore();
2565: }
2566:
2567: // "point of no return" check
2568: if ((player.status[STATUS_LEVITATING] && cellHasTerrainFlag(player.xLoc, player.yLoc, T_LAVA_INSTA_DEATH | T_IS_DEEP_WATER | T_AUTO_DESCENT))
2569: || (player.status[STATUS_IMMUNE_TO_FIRE] && cellHasTerrainFlag(player.xLoc, player.yLoc, T_LAVA_INSTA_DEATH))) {
2570: if (!rogue.receivedLevitationWarning) {
2571: turnsRequiredToShore = rogue.mapToShore[player.xLoc][player.yLoc] * player.movementSpeed / 100;
2572: if (cellHasTerrainFlag(player.xLoc, player.yLoc, T_LAVA_INSTA_DEATH)) {
2573: turnsToShore = max(player.status[STATUS_LEVITATING], player.status[STATUS_IMMUNE_TO_FIRE]) * 100 / player.movementSpeed;
2574: } else {
2575: turnsToShore = player.status[STATUS_LEVITATING] * 100 / player.movementSpeed;
2576: }
2577: if (turnsRequiredToShore == turnsToShore || turnsRequiredToShore + 1 == turnsToShore) {
2578: message("better head back to solid ground!", true);
2579: rogue.receivedLevitationWarning = true;
2580: } else if (turnsRequiredToShore > turnsToShore
2581: && turnsRequiredToShore < 10000) {
2582: message("you're past the point of no return!", true);
2583: rogue.receivedLevitationWarning = true;
2584: }
2585: }
2586: } else {
2587: rogue.receivedLevitationWarning = false;
2588: }
2589:
2590: emptyGraveyard();
2591: rogue.playbackBetweenTurns = true;
2592: RNGCheck();
2593: handleHealthAlerts();
2594:
2595: if (rogue.flareCount > 0) {
2596: animateFlares(rogue.flares, rogue.flareCount);
2597: rogue.flareCount = 0;
2598: }
2599: }
2600:
2601: void resetScentTurnNumber() { // don't want player.scentTurnNumber to roll over the short maxint!
2602: short i, j, d;
2603: rogue.scentTurnNumber -= 15000;
2604: for (d = 0; d < DEEPEST_LEVEL; d++) {
2605: if (levels[d].visited) {
2606: for (i=0; i<DCOLS; i++) {
2607: for (j=0; j<DROWS; j++) {
2608: if (levels[d].scentMap[i][j] > 15000) {
2609: levels[d].scentMap[i][j] -= 15000;
2610: } else {
2611: levels[d].scentMap[i][j] = 0;
2612: }
2613: }
2614: }
2615: }
2616: }
2617: }
CVSweb