[BACK]Return to Light.c CVS log [TXT][DIR] Up to [contributed] / brogue-ce / src / brogue

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