Annotation of brogue-ce/src/brogue/Light.c, Revision 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