[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     ! 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