Annotation of brogue-ce/src/brogue/Light.c, Revision 1.1.1.1
1.1 rubenllo 1: /*
2: * Light.c
3: * Brogue
4: *
5: * Created by Brian Walker on 1/21/09.
6: * Copyright 2012. All rights reserved.
7: *
8: * This file is part of Brogue.
9: *
10: * This program is free software: you can redistribute it and/or modify
11: * it under the terms of the GNU Affero General Public License as
12: * published by the Free Software Foundation, either version 3 of the
13: * License, or (at your option) any later version.
14: *
15: * This program is distributed in the hope that it will be useful,
16: * but WITHOUT ANY WARRANTY; without even the implied warranty of
17: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18: * GNU Affero General Public License for more details.
19: *
20: * You should have received a copy of the GNU Affero General Public License
21: * along with this program. If not, see <http://www.gnu.org/licenses/>.
22: */
23:
24: #include "Rogue.h"
25: #include "IncludeGlobals.h"
26:
27: void logLights() {
28:
29: short i, j;
30:
31: printf(" ");
32: for (i=0; i<COLS-2; i++) {
33: printf("%i", i % 10);
34: }
35: printf("\n");
36: for( j=0; j<DROWS-2; j++ ) {
37: if (j < 10) {
38: printf(" ");
39: }
40: printf("%i: ", j);
41: for( i=0; i<DCOLS-2; i++ ) {
42: if (tmap[i][j].light[0] == 0) {
43: printf(" ");
44: } else {
45: printf("%i", max(0, tmap[i][j].light[0] / 10 - 1));
46: }
47: }
48: printf("\n");
49: }
50: printf("\n");
51: }
52:
53: // Returns true if any part of the light hit cells that are in the player's field of view.
54: boolean paintLight(lightSource *theLight, short x, short y, boolean isMinersLight, boolean maintainShadows) {
55: short i, j, k;
56: short colorComponents[3], randComponent, lightMultiplier;
57: short fadeToPercent, radiusRounded;
58: fixpt radius;
59: char grid[DCOLS][DROWS];
60: boolean dispelShadows, overlappedFieldOfView;
61:
62: brogueAssert(rogue.RNG == RNG_SUBSTANTIVE);
63:
64: radius = randClump(theLight->lightRadius) * FP_FACTOR / 100;
65: radiusRounded = fp_round(radius);
66:
67: randComponent = rand_range(0, theLight->lightColor->rand);
68: colorComponents[0] = randComponent + theLight->lightColor->red + rand_range(0, theLight->lightColor->redRand);
69: colorComponents[1] = randComponent + theLight->lightColor->green + rand_range(0, theLight->lightColor->greenRand);
70: colorComponents[2] = randComponent + theLight->lightColor->blue + rand_range(0, theLight->lightColor->blueRand);
71:
72: // the miner's light does not dispel IS_IN_SHADOW,
73: // so the player can be in shadow despite casting his own light.
74: dispelShadows = !maintainShadows && (colorComponents[0] + colorComponents[1] + colorComponents[2]) > 0;
75:
76: fadeToPercent = theLight->radialFadeToPercent;
77:
78: // zero out only the relevant rectangle of the grid
79: for (i = max(0, x - radiusRounded); i < DCOLS && i < x + radiusRounded; i++) {
80: for (j = max(0, y - radiusRounded); j < DROWS && j < y + radiusRounded; j++) {
81: grid[i][j] = 0;
82: }
83: }
84:
85: getFOVMask(grid, x, y, radius, T_OBSTRUCTS_VISION, (theLight->passThroughCreatures ? 0 : (HAS_MONSTER | HAS_PLAYER)),
86: (!isMinersLight));
87:
88: overlappedFieldOfView = false;
89:
90: for (i = max(0, x - radiusRounded); i < DCOLS && i < x + radiusRounded; i++) {
91: for (j = max(0, y - radiusRounded); j < DROWS && j < y + radiusRounded; j++) {
92: if (grid[i][j]) {
93: lightMultiplier = 100 - (100 - fadeToPercent) * fp_sqrt(((i-x) * (i-x) + (j-y) * (j-y)) * FP_FACTOR) / radius;
94: for (k=0; k<3; k++) {
95: tmap[i][j].light[k] += colorComponents[k] * lightMultiplier / 100;;
96: }
97: if (dispelShadows) {
98: pmap[i][j].flags &= ~IS_IN_SHADOW;
99: }
100: if (pmap[i][j].flags & (IN_FIELD_OF_VIEW | ANY_KIND_OF_VISIBLE)) {
101: overlappedFieldOfView = true;
102: }
103: }
104: }
105: }
106:
107: tmap[x][y].light[0] += colorComponents[0];
108: tmap[x][y].light[1] += colorComponents[1];
109: tmap[x][y].light[2] += colorComponents[2];
110:
111: if (dispelShadows) {
112: pmap[x][y].flags &= ~IS_IN_SHADOW;
113: }
114:
115: return overlappedFieldOfView;
116: }
117:
118:
119: // sets miner's light strength and characteristics based on rings of illumination, scrolls of darkness and water submersion
120: void updateMinersLightRadius() {
121: fixpt base_fraction, fraction, lightRadius;
122:
123: lightRadius = 100 * rogue.minersLightRadius;
124:
125: if (rogue.lightMultiplier < 0) {
126: lightRadius = lightRadius / (-1 * rogue.lightMultiplier + 1);
127: } else {
128: lightRadius *= rogue.lightMultiplier;
129: lightRadius = max(lightRadius, (rogue.lightMultiplier * 2 + 2) * FP_FACTOR);
130: }
131:
132: if (player.status[STATUS_DARKNESS]) {
133: base_fraction = FP_FACTOR - player.status[STATUS_DARKNESS] * FP_FACTOR / player.maxStatus[STATUS_DARKNESS];
134: fraction = (base_fraction * base_fraction / FP_FACTOR) * base_fraction / FP_FACTOR;
135: //fraction = (double) pow(1.0 - (((double) player.status[STATUS_DARKNESS]) / player.maxStatus[STATUS_DARKNESS]), 3);
136: if (fraction < FP_FACTOR / 20) {
137: fraction = FP_FACTOR / 20;
138: }
139: lightRadius = lightRadius * fraction / FP_FACTOR;
140: } else {
141: fraction = FP_FACTOR;
142: }
143:
144: if (lightRadius < 2 * FP_FACTOR) {
145: lightRadius = 2 * FP_FACTOR;
146: }
147:
148: if (rogue.inWater && lightRadius > 3 * FP_FACTOR) {
149: lightRadius = max(lightRadius / 2, 3 * FP_FACTOR);
150: }
151:
152: rogue.minersLight.radialFadeToPercent = (35 + max(0, min(65, rogue.lightMultiplier * 5)) * fraction) / FP_FACTOR;
153: rogue.minersLight.lightRadius.upperBound = rogue.minersLight.lightRadius.lowerBound = clamp(lightRadius / FP_FACTOR, -30000, 30000);
154: }
155:
156: void updateDisplayDetail() {
157: short i, j;
158:
159: for (i = 0; i < DCOLS; i++) {
160: for (j = 0; j < DROWS; j++) {
161: if (tmap[i][j].light[0] < -10
162: && tmap[i][j].light[1] < -10
163: && tmap[i][j].light[2] < -10) {
164:
165: displayDetail[i][j] = DV_DARK;
166: } else if (pmap[i][j].flags & IS_IN_SHADOW) {
167: displayDetail[i][j] = DV_UNLIT;
168: } else {
169: displayDetail[i][j] = DV_LIT;
170: }
171: }
172: }
173: }
174:
175: void backUpLighting(short lights[DCOLS][DROWS][3]) {
176: short i, j, k;
177: for (i=0; i<DCOLS; i++) {
178: for (j=0; j<DROWS; j++) {
179: for (k=0; k<3; k++) {
180: lights[i][j][k] = tmap[i][j].light[k];
181: }
182: }
183: }
184: }
185:
186: void restoreLighting(short lights[DCOLS][DROWS][3]) {
187: short i, j, k;
188: for (i=0; i<DCOLS; i++) {
189: for (j=0; j<DROWS; j++) {
190: for (k=0; k<3; k++) {
191: tmap[i][j].light[k] = lights[i][j][k];
192: }
193: }
194: }
195: }
196:
197: void recordOldLights() {
198: short i, j, k;
199: for (i = 0; i < DCOLS; i++) {
200: for (j = 0; j < DROWS; j++) {
201: for (k=0; k<3; k++) {
202: tmap[i][j].oldLight[k] = tmap[i][j].light[k];
203: }
204: }
205: }
206: }
207:
208: void updateLighting() {
209: short i, j, k;
210: enum dungeonLayers layer;
211: enum tileType tile;
212: creature *monst;
213:
214: // Copy Light over oldLight
215: recordOldLights();
216:
217: // and then zero out Light.
218: for (i = 0; i < DCOLS; i++) {
219: for (j = 0; j < DROWS; j++) {
220: for (k=0; k<3; k++) {
221: tmap[i][j].light[k] = 0;
222: }
223: pmap[i][j].flags |= IS_IN_SHADOW;
224: }
225: }
226:
227: // Paint all glowing tiles.
228: for (i = 0; i < DCOLS; i++) {
229: for (j = 0; j < DROWS; j++) {
230: for (layer = 0; layer < NUMBER_TERRAIN_LAYERS; layer++) {
231: tile = pmap[i][j].layers[layer];
232: if (tileCatalog[tile].glowLight) {
233: paintLight(&(lightCatalog[tileCatalog[tile].glowLight]), i, j, false, false);
234: }
235: }
236: }
237: }
238:
239: // Cycle through monsters and paint their lights:
240: CYCLE_MONSTERS_AND_PLAYERS(monst) {
241: if (monst->info.intrinsicLightType) {
242: paintLight(&lightCatalog[monst->info.intrinsicLightType], monst->xLoc, monst->yLoc, false, false);
243: }
244: if (monst->mutationIndex >= 0 && mutationCatalog[monst->mutationIndex].light != NO_LIGHT) {
245: paintLight(&lightCatalog[mutationCatalog[monst->mutationIndex].light], monst->xLoc, monst->yLoc, false, false);
246: }
247:
248: if (monst->status[STATUS_BURNING] && !(monst->info.flags & MONST_FIERY)) {
249: paintLight(&lightCatalog[BURNING_CREATURE_LIGHT], monst->xLoc, monst->yLoc, false, false);
250: }
251:
252: if (monsterRevealed(monst)) {
253: paintLight(&lightCatalog[TELEPATHY_LIGHT], monst->xLoc, monst->yLoc, false, true);
254: }
255: }
256:
257: // Also paint telepathy lights for dormant monsters.
258: for (monst = dormantMonsters->nextCreature; monst != NULL; monst = monst->nextCreature) {
259: if (monsterRevealed(monst)) {
260: paintLight(&lightCatalog[TELEPATHY_LIGHT], monst->xLoc, monst->yLoc, false, true);
261: }
262: }
263:
264: updateDisplayDetail();
265:
266: // Miner's light:
267: paintLight(&rogue.minersLight, player.xLoc, player.yLoc, true, true);
268:
269: if (player.status[STATUS_INVISIBLE]) {
270: player.info.foreColor = &playerInvisibleColor;
271: } else if (playerInDarkness()) {
272: player.info.foreColor = &playerInDarknessColor;
273: } else if (pmap[player.xLoc][player.yLoc].flags & IS_IN_SHADOW) {
274: player.info.foreColor = &playerInShadowColor;
275: } else {
276: player.info.foreColor = &playerInLightColor;
277: }
278: }
279:
280: boolean playerInDarkness() {
281: return (tmap[player.xLoc][player.yLoc].light[0] + 10 < minersLightColor.red
282: && tmap[player.xLoc][player.yLoc].light[1] + 10 < minersLightColor.green
283: && tmap[player.xLoc][player.yLoc].light[2] + 10 < minersLightColor.blue);
284: }
285:
286: #define flarePrecision 1000
287:
288: flare *newFlare(lightSource *light, short x, short y, short changePerFrame, short limit) {
289: flare *theFlare = malloc(sizeof(flare));
290: memset(theFlare, '\0', sizeof(flare));
291: theFlare->light = light;
292: theFlare->xLoc = x;
293: theFlare->yLoc = y;
294: theFlare->coeffChangeAmount = changePerFrame;
295: if (theFlare->coeffChangeAmount == 0) {
296: theFlare->coeffChangeAmount = 1; // no change would mean it lasts forever, which usually breaks things
297: }
298: theFlare->coeffLimit = limit;
299: theFlare->coeff = 100 * flarePrecision;
300: theFlare->turnNumber = rogue.absoluteTurnNumber;
301: return theFlare;
302: }
303:
304: // Creates a new fading flare as described and sticks it into the stack so it will fire at the end of the turn.
305: void createFlare(short x, short y, enum lightType lightIndex) {
306: flare *theFlare;
307:
308: theFlare = newFlare(&(lightCatalog[lightIndex]), x, y, -15, 0);
309:
310: if (rogue.flareCount >= rogue.flareCapacity) {
311: rogue.flareCapacity += 10;
312: rogue.flares = realloc(rogue.flares, sizeof(flare *) * rogue.flareCapacity);
313: }
314: rogue.flares[rogue.flareCount] = theFlare;
315: rogue.flareCount++;
316: }
317:
318: boolean flareIsActive(flare *theFlare) {
319: const boolean increasing = (theFlare->coeffChangeAmount > 0);
320: boolean active = true;
321:
322: if (theFlare->turnNumber > 0 && theFlare->turnNumber < rogue.absoluteTurnNumber - 1) {
323: active = false;
324: }
325: if (increasing) {
326: if ((short) (theFlare->coeff / flarePrecision) > theFlare->coeffLimit) {
327: active = false;
328: }
329: } else {
330: if ((short) (theFlare->coeff / flarePrecision) < theFlare->coeffLimit) {
331: active = false;
332: }
333: }
334: return active;
335: }
336:
337: // Returns true if the flare is still active; false if it's not.
338: boolean updateFlare(flare *theFlare) {
339: if (!flareIsActive(theFlare)) {
340: return false;
341: }
342: theFlare->coeff += (theFlare->coeffChangeAmount) * flarePrecision / 10;
343: theFlare->coeffChangeAmount = theFlare->coeffChangeAmount * 12 / 10;
344: return flareIsActive(theFlare);
345: }
346:
347: // Returns whether it overlaps with the field of view.
348: boolean drawFlareFrame(flare *theFlare) {
349: boolean inView;
350: lightSource tempLight = *(theFlare->light);
351: color tempColor = *(tempLight.lightColor);
352:
353: if (!flareIsActive(theFlare)) {
354: return false;
355: }
356: tempLight.lightRadius.lowerBound = ((long) tempLight.lightRadius.lowerBound) * theFlare->coeff / (flarePrecision * 100);
357: tempLight.lightRadius.upperBound = ((long) tempLight.lightRadius.upperBound) * theFlare->coeff / (flarePrecision * 100);
358: applyColorScalar(&tempColor, theFlare->coeff / flarePrecision);
359: tempLight.lightColor = &tempColor;
360: inView = paintLight(&tempLight, theFlare->xLoc, theFlare->yLoc, false, true);
361:
362: return inView;
363: }
364:
365: // Frees the flares as they expire.
366: void animateFlares(flare **flares, short count) {
367: short lights[DCOLS][DROWS][3];
368: boolean inView, fastForward, atLeastOneFlareStillActive;
369: short i; // i iterates through the flare list
370:
371: brogueAssert(rogue.RNG == RNG_SUBSTANTIVE);
372:
373: backUpLighting(lights);
374: fastForward = rogue.trueColorMode || rogue.playbackFastForward;
375:
376: do {
377: inView = false;
378: atLeastOneFlareStillActive = false;
379: for (i = 0; i < count; i++) {
380: if (flares[i]) {
381: if (updateFlare(flares[i])) {
382: atLeastOneFlareStillActive = true;
383: if (drawFlareFrame(flares[i])) {
384: inView = true;
385: }
386: } else {
387: free(flares[i]);
388: flares[i] = NULL;
389: }
390: }
391: }
392: demoteVisibility();
393: updateFieldOfViewDisplay(false, true);
394: if (!fastForward && (inView || rogue.playbackOmniscience) && atLeastOneFlareStillActive) {
395: fastForward = pauseBrogue(10);
396: }
397: recordOldLights();
398: restoreLighting(lights);
399: } while (atLeastOneFlareStillActive);
400: updateFieldOfViewDisplay(false, true);
401: }
402:
403: void deleteAllFlares() {
404: short i;
405: for (i=0; i<rogue.flareCount; i++) {
406: free(rogue.flares[i]);
407: }
408: rogue.flareCount = 0;
409: }
CVSweb