#include "builddefines.h" /**************************************************************************************** * JA2 Lighting Module * * Tile-based, ray-casted lighting system. * * Lights are precalculated into linked lists containing offsets from 0,0, and a light * level to add at that tile. Lists are constructed by casting a ray from the origin of * the light, and each tile stopped at is stored as a node in the list. To draw the light * during runtime, you traverse the list, checking at each tile that it isn't of the type * that can obscure light. If it is, you keep traversing the list until you hit a node * with a marker LIGHT_NEW_RAY, which means you're back at the origin, and have skipped * the remainder of the last ray. * * Written by Derek Beland, April 14, 1997 * ***************************************************************************************/ #include "worlddef.h" #include "renderworld.h" #include "sysutil.h" #include "debug.h" #include "wcheck.h" #include "Animation Control.h" #include "Sys Globals.h" #include "lighting.h" #include "structure wrap.h" #include "Shade Table Util.h" #include "rotting corpses.h" #include "PATHAI.H" #define LVL1_L1_PER (50) #define LVL1_L2_PER (50) #define LVL2_L1_PER (30) #define LVL2_L2_PER (70) #define LIGHT_TREE_REVEAL 5 // width of rect // Local-use only prototypes BOOLEAN LightTileBlocked(INT16 iSrcX, INT16 iSrcY, INT16 iX, INT16 iY); BOOLEAN LightDraw(UINT32 uiLightType, INT32 iLight, INT16 iX, INT16 iY, UINT32 uiSprite); BOOLEAN LightDelete(INT32 iLight); INT32 LightGetFree(void); INT32 LinearDistance(INT16 iX1, INT16 iY1, INT16 iX2, INT16 iY2); DOUBLE LinearDistanceDouble(INT16 iX1, INT16 iY1, INT16 iX2, INT16 iY2); BOOLEAN LightAddTile(UINT32 uiLightType, INT16 iSrcX, INT16 iSrcY, INT16 iX, INT16 iY, UINT8 ubShade, UINT32 uiFlags, BOOLEAN fOnlyWalls ); BOOLEAN LightSubtractTile(UINT32 uiLightType, INT16 iSrcX, INT16 iSrcY, INT16 iX, INT16 iY, UINT8 ubShade, UINT32 uiFlags, BOOLEAN fOnlyWalls ); BOOLEAN LightResetTile(INT16 iX, INT16 iY); BOOLEAN LightSetTile(INT16 iX, INT16 iY, UINT8 ubShade, UINT32 uiLightType); BOOLEAN LightSetNaturalTile(INT16 iX, INT16 iY, UINT8 ubShade); UINT16 LightGetLastNode(INT32 iLight); BOOLEAN LightAddNode(INT32 iLight, INT16 iHotSpotX, INT16 iHotSpotY, INT16 iX, INT16 iY, UINT8 ubIntensity, UINT16 uiFlags); BOOLEAN LightInsertNode(INT32 iLight, UINT16 usLightIns, INT16 iHotSpotX, INT16 iHotSpotY, INT16 iX, INT16 iY, UINT8 ubIntensity, UINT16 uiFlags); UINT16 LightFindNextRay(INT32 iLight, UINT16 usIndex); BOOLEAN LightCastRay(INT32 iLight, INT16 iStartX, INT16 iStartY, INT16 iEndPointX, INT16 iEndPointY, UINT8 ubStartIntens, UINT8 ubEndIntens); BOOLEAN LightGenerateElliptical(INT32 iLight, UINT8 iIntensity, INT16 iA, INT16 iB); BOOLEAN LightGenerateBeam(INT32 iLight, UINT8 iIntensity, INT16 iLength, INT16 iRadius, INT16 iDirection); BOOLEAN LightCalcRect(INT32 iLight); BOOLEAN LightIlluminateWall(INT16 iSourceX, INT16 iSourceY, INT16 iTileX, INT16 iTileY, LEVELNODE *pStruct); BOOLEAN LightTileHasWall( INT16 iSrcX, INT16 iSrcY, INT16 iX, INT16 iY ); INT32 LightSpriteGetFree(void); BOOLEAN LightSpriteDirty(INT32 iLight); // Top node of linked lists, NULL = FREE LIGHT_NODE *pLightList[MAX_LIGHT_TEMPLATES]; UINT16 usTemplateSize[MAX_LIGHT_TEMPLATES]; UINT16 *pLightRayList[MAX_LIGHT_TEMPLATES]; UINT16 usRaySize[MAX_LIGHT_TEMPLATES]; INT16 LightHeight[MAX_LIGHT_TEMPLATES]; INT16 LightWidth[MAX_LIGHT_TEMPLATES]; INT16 LightXOffset[MAX_LIGHT_TEMPLATES]; INT16 LightYOffset[MAX_LIGHT_TEMPLATES]; INT16 LightMapLeft[MAX_LIGHT_TEMPLATES]; INT16 LightMapTop[MAX_LIGHT_TEMPLATES]; INT16 LightMapRight[MAX_LIGHT_TEMPLATES]; INT16 LightMapBottom[MAX_LIGHT_TEMPLATES]; STR pLightNames[MAX_LIGHT_TEMPLATES]; // Sprite data LIGHT_SPRITE LightSprites[MAX_LIGHT_SPRITES]; // Lighting system general data UINT8 ubAmbientLightLevel=DEFAULT_SHADE_LEVEL; UINT8 gubNumLightColors=1; // Externed in Rotting Corpses.c SGPPaletteEntry gpLightColors[3]={{0,0,0,0}, {0,0,255,0}, {0,0,0,0}}; SGPPaletteEntry gpOrigLights[2]={{0,0,0,0}, {0,0,255,0}}; /* UINT16 gusShadeLevels[16][3]={{500, 500, 500}, // green table {450, 450, 450}, // bright {350, 350, 350}, {300, 300, 300}, {255, 255, 255}, // normal {227, 227, 227}, {198, 198, 198}, {171, 171, 171}, {143, 143, 143}, {115, 115, 160}, // darkening {87, 87, 176}, {60, 60, 160}, {48, 48, 192}, {36, 36, 208}, {18, 18, 224}, {48, 222, 48}}; */ // Externed in Rotting Corpses.c //Kris' attempt at blue night lights /* UINT16 gusShadeLevels[16][3]={{500, 500, 500}, // green table {450, 450, 450}, // bright {350, 350, 350}, {300, 300, 300}, {255, 255, 255}, // normal {215, 215, 227}, {179, 179, 179}, {149, 149, 149}, {125, 125, 128}, {104, 104, 128}, // darkening {86, 86, 128}, {72, 72, 128}, {60, 60, 128}, {36, 36, 208}, {18, 18, 224}, {48, 222, 48}}; */ /* //Linda's final version UINT16 gusShadeLevels[16][3] = { 500, 500, 500, 450, 450, 450, //bright 350, 350, 350, 300, 300, 300, 255, 255, 255, //normal 222, 200, 200, 174, 167, 167, 150, 137, 137, 122, 116, 116, //darkening 96, 96, 96, 77, 77, 84, 58, 58, 69, 44, 44, 66, //night 36, 36, 244, 18, 18, 224, 48, 222, 48, }; */ // JA2 Gold: UINT16 gusShadeLevels[16][3] = { 500, 500, 500, 450, 450, 450, //bright 350, 350, 350, 300, 300, 300, 255, 255, 255, //normal 231, 199, 199, 209, 185, 185, 187, 171, 171, 165, 157, 157, //darkening 143, 143, 143, 121, 121, 129, 99, 99, 115 , 77, 77, 101, //night 36, 36, 244, 18, 18, 224, 48, 222, 48, }; //Set this true if you want the shadetables to be loaded from the text file. BOOLEAN gfLoadShadeTablesFromTextFile = FALSE; void LoadShadeTablesFromTextFile() { FILE *fp; INT32 i, j; INT32 num; CHAR8 str[10]; if( gfLoadShadeTablesFromTextFile ) { fp = fopen( "ShadeTables.txt", "r" ); Assert( fp ); if( fp ) { for( i = 0; i < 16; i++ ) { for( j = 0; j < 3; j++ ) { fscanf( fp, "%s", str ); sscanf( str, "%d", &num ); gusShadeLevels[i][j] = (UINT16)num; } } fclose( fp ); } } } // Debug variable UINT32 gNodesAdded=0; /**************************************************************************************** InitLightingSystem Initializes the lighting system. ***************************************************************************************/ BOOLEAN InitLightingSystem(void) { UINT32 uiCount; LoadShadeTablesFromTextFile(); // init all light lists for(uiCount=0; uiCount < MAX_LIGHT_TEMPLATES; uiCount++) { pLightList[uiCount]=NULL; pLightNames[uiCount]=NULL; pLightRayList[uiCount]=NULL; usTemplateSize[uiCount]=0; usRaySize[uiCount]=0; } // init all light sprites for(uiCount=0; uiCount < MAX_LIGHT_SPRITES; uiCount++) memset(&LightSprites[uiCount], 0, sizeof(LIGHT_SPRITE)); if(LightLoad("TRANSLUC.LHT")!=0) { DebugMsg(TOPIC_GAME, DBG_LEVEL_0, String("Failed to load translucency template")); return(FALSE); } return(TRUE); } // THIS MUST BE CALLED ONCE ALL SURFACE VIDEO OBJECTS HAVE BEEN LOADED! BOOLEAN SetDefaultWorldLightingColors(void) { SGPPaletteEntry pPal[2]; pPal[0].peRed=0; pPal[0].peGreen=0; pPal[0].peBlue=0; pPal[1].peRed=0; pPal[1].peGreen=0; pPal[1].peBlue=128; LightSetColors(&pPal[0], 1); return(TRUE); } /**************************************************************************************** ShutdownLightingSystem Closes down the lighting system. Any lights that were created are destroyed, and the memory attached to them freed up. ***************************************************************************************/ BOOLEAN ShutdownLightingSystem(void) { UINT32 uiCount; // free up all allocated light nodes for(uiCount=0; uiCount < MAX_LIGHT_TEMPLATES; uiCount++) if(pLightList[uiCount]!=NULL) LightDelete(uiCount); return(TRUE); } /**************************************************************************************** LightReset Removes all currently active lights, without undrawing them. ***************************************************************************************/ BOOLEAN LightReset(void) { UINT32 uiCount; // reset all light lists for(uiCount=0; uiCount < MAX_LIGHT_TEMPLATES; uiCount++) if(pLightList[uiCount]!=NULL) LightDelete(uiCount); // init all light sprites for(uiCount=0; uiCount < MAX_LIGHT_SPRITES; uiCount++) memset(&LightSprites[uiCount], 0, sizeof(LIGHT_SPRITE)); if(LightLoad("TRANSLUC.LHT")!=0) { DebugMsg(TOPIC_GAME, DBG_LEVEL_0, String("Failed to load translucency template")); return(FALSE); } // Loop through mercs and reset light value for ( uiCount = 0; uiCount < MAX_NUM_SOLDIERS; uiCount++ ) { MercPtrs[ uiCount ]->iLight = -1; } return(TRUE); } /**************************************************************************************** LightCreateTemplateNode Creates a new node, and appends it to the template list. The index into the list is returned. ***************************************************************************************/ UINT16 LightCreateTemplateNode(INT32 iLight, INT16 iX, INT16 iY, UINT8 ubLight) { UINT16 usNumNodes; // create a new list if(pLightList[iLight]==NULL) { if((pLightList[iLight]= (LIGHT_NODE *) MemAlloc(sizeof(LIGHT_NODE)))==NULL) return(65535); pLightList[iLight]->iDX=iX; pLightList[iLight]->iDY=iY; pLightList[iLight]->ubLight=ubLight; pLightList[iLight]->uiFlags=0; usTemplateSize[iLight]=1; return(0); } else { usNumNodes=usTemplateSize[iLight]; pLightList[iLight]= (LIGHT_NODE *) MemRealloc(pLightList[iLight], (usNumNodes+1)*sizeof(LIGHT_NODE)); (pLightList[iLight]+usNumNodes)->iDX=iX; (pLightList[iLight]+usNumNodes)->iDY=iY; (pLightList[iLight]+usNumNodes)->ubLight=ubLight; (pLightList[iLight]+usNumNodes)->uiFlags=0; usTemplateSize[iLight]=usNumNodes+1; return(usNumNodes); } } /**************************************************************************************** LightAddTemplateNode Adds a node to the template list. If the node does not exist, it creates a new one. Returns the index into the list. ***************************************************************************************/ UINT16 LightAddTemplateNode(INT32 iLight, INT16 iX, INT16 iY, UINT8 ubLight) { UINT16 usCount; for(usCount=0; usCount < usTemplateSize[iLight]; usCount++) { if(((pLightList[iLight]+usCount)->iDX==iX) && ((pLightList[iLight]+usCount)->iDY==iY)) { return(usCount); } } return(LightCreateTemplateNode(iLight, iX, iY, ubLight)); } /**************************************************************************************** LightAddRayNode Adds a node to the ray casting list. ***************************************************************************************/ UINT16 LightAddRayNode(INT32 iLight, INT16 iX, INT16 iY, UINT8 ubLight, UINT16 usFlags) { UINT16 usNumNodes; // create a new list if(pLightRayList[iLight]==NULL) { if((pLightRayList[iLight]= (UINT16 *) MemAlloc(sizeof(UINT16)))==NULL) return(65535); *pLightRayList[iLight]=(LightAddTemplateNode(iLight, iX, iY, ubLight) | usFlags); usRaySize[iLight]=1; return(0); } else { usNumNodes=usRaySize[iLight]; pLightRayList[iLight]= (UINT16 *) MemRealloc(pLightRayList[iLight], (usNumNodes+1)*sizeof(UINT16)); *(pLightRayList[iLight]+usNumNodes)=(LightAddTemplateNode(iLight, iX, iY, ubLight) | usFlags); usRaySize[iLight]=usNumNodes+1; return(usNumNodes); } } /**************************************************************************************** LightAddRayNode Adds a node to the ray casting list. ***************************************************************************************/ UINT16 LightInsertRayNode(INT32 iLight, UINT16 usIndex, INT16 iX, INT16 iY, UINT8 ubLight, UINT16 usFlags) { UINT16 usNumNodes; // create a new list if(pLightRayList[iLight]==NULL) { if((pLightRayList[iLight]= (UINT16 *) MemAlloc(sizeof(UINT16)))==NULL) return(65535); *pLightRayList[iLight]=(LightAddTemplateNode(iLight, iX, iY, ubLight) | usFlags); usRaySize[iLight]=1; return(0); } else { usNumNodes=usRaySize[iLight]; pLightRayList[iLight]= (UINT16 *) MemRealloc(pLightRayList[iLight], (usNumNodes+1)*sizeof(UINT16)); if(usIndex < usRaySize[iLight]) { memmove(pLightRayList[iLight]+usIndex+1, pLightRayList[iLight]+usIndex, (usRaySize[iLight]-usIndex)*sizeof(UINT16)); } *(pLightRayList[iLight]+usIndex)=(LightAddTemplateNode(iLight, iX, iY, ubLight) | usFlags); usRaySize[iLight]=usNumNodes+1; return(usNumNodes); } } /**************************************************************************************** LightTileBlocked Returns TRUE/FALSE if the tile at the specified tile number can block light. ***************************************************************************************/ BOOLEAN LightTileBlocked(INT16 iSrcX, INT16 iSrcY, INT16 iX, INT16 iY) { UINT32 uiTileNo, uiSrcTileNo; Assert(gpWorldLevelData!=NULL); uiTileNo=MAPROWCOLTOPOS(iY, iX); uiSrcTileNo=MAPROWCOLTOPOS(iSrcY, iSrcX); if (TileIsOutOfBounds(uiTileNo)) { return( FALSE ); } if (TileIsOutOfBounds(uiSrcTileNo)) { return( FALSE ); } if(gpWorldLevelData[ uiTileNo ].sHeight > gpWorldLevelData[ uiSrcTileNo ].sHeight) return(TRUE); { UINT32 uiTileNo; LEVELNODE *pStruct; uiTileNo=MAPROWCOLTOPOS(iY, iX); pStruct = gpWorldLevelData[ uiTileNo ].pStructHead; if ( pStruct != NULL ) { // IF WE ARE A WINDOW, DO NOT BLOCK! if ( FindStructure( uiTileNo, STRUCTURE_WALLNWINDOW ) != NULL ) { return( FALSE ); } } } return(LightTileHasWall( iSrcX, iSrcY, iX, iY)); } /**************************************************************************************** LightTileHasWall Returns TRUE/FALSE if the tile at the specified coordinates contains a wall. ***************************************************************************************/ BOOLEAN LightTileHasWall( INT16 iSrcX, INT16 iSrcY, INT16 iX, INT16 iY) { //LEVELNODE *pStruct; //UINT32 uiType; UINT32 uiTileNo; UINT32 uiSrcTileNo; INT8 bDirection; UINT8 ubTravelCost; //INT8 bWallCount = 0; //UINT16 usWallOrientation; Assert(gpWorldLevelData!=NULL); uiTileNo=MAPROWCOLTOPOS(iY, iX); uiSrcTileNo=MAPROWCOLTOPOS(iSrcY, iSrcX); if ( uiTileNo == uiSrcTileNo ) { return( FALSE ); } if ( TileIsOutOfBounds(uiTileNo)) { return( FALSE ); } if ( TileIsOutOfBounds(uiSrcTileNo)) { return( FALSE ); } // Get direction //bDirection = atan8( iX, iY, iSrcX, iSrcY ); bDirection = atan8( iSrcX, iSrcY, iX, iY ); ubTravelCost = gubWorldMovementCosts[ uiTileNo ][ bDirection ][ 0 ]; if ( ubTravelCost == TRAVELCOST_WALL ) { return( TRUE ); } if ( IS_TRAVELCOST_DOOR( ubTravelCost ) ) { ubTravelCost = DoorTravelCost( NULL, uiTileNo, ubTravelCost, TRUE, NULL ); if ( ubTravelCost == TRAVELCOST_OBSTACLE || ubTravelCost == TRAVELCOST_DOOR ) { return( TRUE ); } } return(FALSE); } /**************************************************************************************** LightDelete Removes a light template from the list, and frees up the associated node memory. ***************************************************************************************/ BOOLEAN LightDelete(INT32 iLight) { if(pLightList[iLight]!=NULL) { if(pLightList[iLight]!=NULL) { MemFree(pLightList[iLight]); pLightList[iLight]=NULL; } if(pLightRayList[iLight]!=NULL) { MemFree(pLightRayList[iLight]); pLightRayList[iLight]=NULL; } if(pLightNames[iLight]!=NULL) { MemFree(pLightNames[iLight]); pLightNames[iLight]=NULL; } usTemplateSize[iLight]=0; usRaySize[iLight]=0; return(TRUE); } else return(FALSE); } /**************************************************************************************** LightGetFree Returns an available slot for a new light template. If none are available, (-1) is returned. ***************************************************************************************/ INT32 LightGetFree(void) { UINT32 uiCount; for(uiCount=0; uiCount < MAX_LIGHT_TEMPLATES; uiCount++) if(pLightList[uiCount]==NULL) return(uiCount); return(-1); } /**************************************************************************************** LinearDistance Calculates the 2D linear distance between two points. ***************************************************************************************/ INT32 LinearDistance(INT16 iX1, INT16 iY1, INT16 iX2, INT16 iY2) { INT32 iDx, iDy; iDx=abs(iX1-iX2); iDx*=iDx; iDy=abs(iY1-iY2); iDy*=iDy; return((INT32)sqrt((DOUBLE)(iDx+iDy))); } /**************************************************************************************** LinearDistanceDouble Calculates the 2D linear distance between two points. Returns the result in a DOUBLE for greater accuracy. ***************************************************************************************/ DOUBLE LinearDistanceDouble(INT16 iX1, INT16 iY1, INT16 iX2, INT16 iY2) { INT32 iDx, iDy; iDx=abs(iX1-iX2); iDx*=iDx; iDy=abs(iY1-iY2); iDy*=iDy; return(sqrt((DOUBLE)(iDx+iDy))); } /**************************************************************************************** LightTrueLevel Returns the light level at a particular level without fake lights ***************************************************************************************/ UINT8 LightTrueLevel( INT32 sGridNo, INT16 bLevel ) { LEVELNODE * pNode; INT32 iSum; if (bLevel == 0) { pNode = gpWorldLevelData[sGridNo].pLandHead; } else { pNode = gpWorldLevelData[sGridNo].pRoofHead; } if (pNode == NULL) { return( ubAmbientLightLevel ); } else { iSum=pNode->ubNaturalShadeLevel - (pNode->ubSumLights - pNode->ubFakeShadeLevel ); if(gGameExternalOptions.fStaticShadowsDecreaseBrightness && ( gRenderFlags & RENDER_FLAG_SHADOWS )) { INT32 sNewGridNo = NewGridNo( sGridNo, (UINT16)DirectionInc( NORTH ) ); STRUCTURE* pStructure = gpWorldLevelData[ sNewGridNo ].pStructureHead; if(pStructure != NULL) { LEVELNODE* pShadowNode = NULL; if(!(pStructure->fFlags & STRUCTURE_BASE_TILE)) { STRUCTURE* pBaseStructure = FindBaseStructure(pStructure); if( pBaseStructure != NULL ) pShadowNode = gpWorldLevelData[ pBaseStructure->sGridNo ].pShadowHead; } else pShadowNode = gpWorldLevelData[ sNewGridNo ].pShadowHead; if(pShadowNode != NULL) { iSum += max( 0, (StructureHeight(pStructure) - 1 ) ); } } } iSum=__min(SHADE_MIN, iSum); iSum=__max(SHADE_MAX, iSum); return( (UINT8) iSum ); } } /**************************************************************************************** LightAddNodeTile Does the addition of light values to individual LEVELNODEs in the world tile list. ***************************************************************************************/ void LightAddTileNode(LEVELNODE *pNode, UINT32 uiLightType, UINT8 ubShadeAdd, BOOLEAN fFake) { INT16 sSum; pNode->ubSumLights = pNode->ubSumLights + ubShadeAdd; if (fFake) { pNode->ubFakeShadeLevel = pNode->ubFakeShadeLevel + ubShadeAdd; } // Now set max pNode->ubMaxLights = __max( pNode->ubMaxLights, ubShadeAdd ); sSum=pNode->ubNaturalShadeLevel - pNode->ubMaxLights; sSum=__min(SHADE_MIN, sSum); sSum=__max(SHADE_MAX, sSum); pNode->ubShadeLevel=(UINT8)sSum; } /**************************************************************************************** LightAddNodeTile Does the subtraction of light values to individual LEVELNODEs in the world tile list. ***************************************************************************************/ void LightSubtractTileNode(LEVELNODE *pNode, UINT32 uiLightType, UINT8 ubShadeSubtract, BOOLEAN fFake) { INT16 sSum; if (ubShadeSubtract > pNode->ubSumLights ) { pNode->ubSumLights = 0; } else { pNode->ubSumLights = pNode->ubSumLights - ubShadeSubtract; } if (fFake) { if (ubShadeSubtract > pNode->ubFakeShadeLevel) { pNode->ubFakeShadeLevel = 0; } else { pNode->ubFakeShadeLevel = pNode->ubFakeShadeLevel - ubShadeSubtract; } } // Now set max pNode->ubMaxLights = __min( pNode->ubMaxLights, pNode->ubSumLights ); sSum=pNode->ubNaturalShadeLevel - pNode->ubMaxLights; sSum=__min(SHADE_MIN, sSum); sSum=__max(SHADE_MAX, sSum); pNode->ubShadeLevel=(UINT8)sSum; } /**************************************************************************************** LightAddTile Adds a specified amount of light to all objects on a given tile. ***************************************************************************************/ BOOLEAN LightAddTile(UINT32 uiLightType, INT16 iSrcX, INT16 iSrcY, INT16 iX, INT16 iY, UINT8 ubShade, UINT32 uiFlags, BOOLEAN fOnlyWalls ) { LEVELNODE *pLand, *pStruct, *pObject, *pMerc, *pRoof, *pOnRoof; UINT8 ubShadeAdd; INT32 uiTile; BOOLEAN fLitWall=FALSE; BOOLEAN fFake; Assert(gpWorldLevelData!=NULL); uiTile= MAPROWCOLTOPOS( iY, iX ); if ( TileIsOutOfBounds( uiTile ) ) { return( FALSE ); } gpWorldLevelData[uiTile].uiFlags|=MAPELEMENT_REDRAW; //if((uiFlags&LIGHT_BACKLIGHT) && !(uiFlags&LIGHT_ROOF_ONLY)) // ubShadeAdd=ubShade*7/10; //else ubShadeAdd=ubShade; // Lesh: it is not recomended due to problems with roof lighting //if ( uiLightType == LIGHT_TYPE_BRIGHT ) // ubShadeAdd += -6; if (uiFlags&LIGHT_FAKE) { fFake = TRUE; } else { fFake = FALSE; } if(!(uiFlags&LIGHT_ROOF_ONLY) || (uiFlags&LIGHT_EVERYTHING)) { pStruct = gpWorldLevelData[uiTile].pStructHead; while(pStruct!=NULL) { if ( pStruct->usIndex < giNumberOfTiles ) { if((gTileDatabase[ pStruct->usIndex ].fType != FIRSTCLIFFHANG) || (uiFlags&LIGHT_EVERYTHING)) { if( (uiFlags&LIGHT_IGNORE_WALLS ) || gfCaves ) LightAddTileNode(pStruct, uiLightType, ubShadeAdd, FALSE); else if(LightIlluminateWall(iSrcX, iSrcY, iX, iY, pStruct)) { if(LightTileHasWall(iSrcX, iSrcY, iX, iY)) fLitWall=TRUE; // ATE: Limit shade for walls if in caves if ( fLitWall && gfCaves ) { LightAddTileNode(pStruct, uiLightType, ( UINT8 ) __min( ubShadeAdd, ( SHADE_MAX + 5 ) ), FALSE); } else if ( fLitWall ) { LightAddTileNode(pStruct, uiLightType, ubShadeAdd, FALSE); } else if ( !fOnlyWalls ) { LightAddTileNode(pStruct, uiLightType, ubShadeAdd, FALSE); } } } } else { LightAddTileNode(pStruct, uiLightType, ubShadeAdd, FALSE); } pStruct=pStruct->pNext; } ubShadeAdd=ubShade; if ( !fOnlyWalls ) { pLand = gpWorldLevelData[uiTile].pLandHead; while( pLand ) { if( gfCaves || !fLitWall ) { LightAddTileNode(pLand, uiLightType, ubShadeAdd, fFake); } pLand=pLand->pNext; } pObject = gpWorldLevelData[uiTile].pObjectHead; while(pObject!=NULL) { if ( pObject->usIndex < giNumberOfTiles ) { LightAddTileNode(pObject, uiLightType, ubShadeAdd, FALSE); } pObject=pObject->pNext; } if(uiFlags&LIGHT_BACKLIGHT) ubShadeAdd=(UINT8)((UINT16)ubShade*7/10); pMerc = gpWorldLevelData[uiTile].pMercHead; while(pMerc!=NULL) { LightAddTileNode(pMerc, uiLightType, ubShadeAdd, FALSE); pMerc=pMerc->pNext; } } } if((uiFlags&LIGHT_ROOF_ONLY) || (uiFlags&LIGHT_EVERYTHING)) { pRoof = gpWorldLevelData[uiTile].pRoofHead; while(pRoof!=NULL) { if ( pRoof->usIndex < giNumberOfTiles ) { LightAddTileNode(pRoof, uiLightType, ubShadeAdd, fFake); } pRoof=pRoof->pNext; } pOnRoof = gpWorldLevelData[uiTile].pOnRoofHead; while(pOnRoof!=NULL) { LightAddTileNode(pOnRoof, uiLightType, ubShadeAdd, FALSE); pOnRoof=pOnRoof->pNext; } } return(TRUE); } /**************************************************************************************** LightSubtractTile Subtracts a specified amount of light to a given tile. ***************************************************************************************/ BOOLEAN LightSubtractTile(UINT32 uiLightType, INT16 iSrcX, INT16 iSrcY, INT16 iX, INT16 iY, UINT8 ubShade, UINT32 uiFlags, BOOLEAN fOnlyWalls ) { LEVELNODE *pLand, *pStruct, *pObject, *pMerc, *pRoof, *pOnRoof; UINT8 ubShadeSubtract; INT32 uiTile; BOOLEAN fLitWall=FALSE; BOOLEAN fFake; // only passed in to land and roof layers; others get fed FALSE Assert(gpWorldLevelData!=NULL); uiTile= MAPROWCOLTOPOS( iY, iX ); if ( TileIsOutOfBounds( uiTile ) ) { return( FALSE ); } gpWorldLevelData[uiTile].uiFlags|=MAPELEMENT_REDRAW; // if((uiFlags&LIGHT_BACKLIGHT) && !(uiFlags&LIGHT_ROOF_ONLY)) // ubShadeSubtract=ubShade*7/10; // else ubShadeSubtract=ubShade; // Lesh: it is not recomended due to problems with roof lighting //if ( uiLightType == LIGHT_TYPE_BRIGHT ) // ubShadeSubtract += -6; if (uiFlags&LIGHT_FAKE) { fFake = TRUE; } else { fFake = FALSE; } if(!(uiFlags&LIGHT_ROOF_ONLY) || (uiFlags&LIGHT_EVERYTHING)) { pStruct = gpWorldLevelData[uiTile].pStructHead; while(pStruct!=NULL) { if ( pStruct->usIndex < giNumberOfTiles ) { if((gTileDatabase[ pStruct->usIndex ].fType != FIRSTCLIFFHANG) || (uiFlags&LIGHT_EVERYTHING)) { if( (uiFlags&LIGHT_IGNORE_WALLS ) || gfCaves ) LightSubtractTileNode(pStruct, uiLightType, ubShadeSubtract, FALSE); else if(LightIlluminateWall(iSrcX, iSrcY, iX, iY, pStruct)) { if(LightTileHasWall( iSrcX, iSrcY, iX, iY)) fLitWall=TRUE; // ATE: Limit shade for walls if in caves if ( fLitWall && gfCaves ) { LightSubtractTileNode(pStruct, uiLightType, ( UINT8 ) __max( ( ubShadeSubtract - 5 ), 0 ), FALSE); } else if ( fLitWall ) { LightSubtractTileNode(pStruct, uiLightType, ubShadeSubtract, FALSE); } else if ( !fOnlyWalls ) { LightSubtractTileNode(pStruct, uiLightType, ubShadeSubtract, FALSE); } } } } else { LightSubtractTileNode(pStruct, uiLightType, ubShadeSubtract, FALSE); } pStruct=pStruct->pNext; } ubShadeSubtract=ubShade; if ( !fOnlyWalls ) { pLand = gpWorldLevelData[uiTile].pLandHead; while( pLand ) { if( gfCaves || !fLitWall ) { LightSubtractTileNode(pLand, uiLightType, ubShadeSubtract, fFake); } pLand=pLand->pNext; } pObject = gpWorldLevelData[uiTile].pObjectHead; while(pObject!=NULL) { if ( pObject->usIndex < giNumberOfTiles ) { LightSubtractTileNode(pObject, uiLightType, ubShadeSubtract, FALSE); } pObject=pObject->pNext; } if(uiFlags&LIGHT_BACKLIGHT) ubShadeSubtract=(UINT8) ((UINT16)ubShade*7/10); pMerc = gpWorldLevelData[uiTile].pMercHead; while(pMerc!=NULL) { LightSubtractTileNode(pMerc, uiLightType, ubShadeSubtract, FALSE); pMerc=pMerc->pNext; } } } if((uiFlags&LIGHT_ROOF_ONLY) || (uiFlags&LIGHT_EVERYTHING)) { pRoof = gpWorldLevelData[uiTile].pRoofHead; while(pRoof!=NULL) { if ( pRoof->usIndex < giNumberOfTiles ) { LightSubtractTileNode(pRoof, uiLightType, ubShadeSubtract, fFake); } pRoof=pRoof->pNext; } pOnRoof = gpWorldLevelData[uiTile].pOnRoofHead; while(pOnRoof!=NULL) { if ( pOnRoof->usIndex < giNumberOfTiles ) { LightSubtractTileNode(pOnRoof, uiLightType, ubShadeSubtract, FALSE); } pOnRoof=pOnRoof->pNext; } } return(TRUE); } /**************************************************************************************** LightSetNaturalTileNode Sets the natural light level (as well as the current) on individual LEVELNODEs. ***************************************************************************************/ void LightSetNaturalTileNode(LEVELNODE *pNode, UINT8 ubShade) { Assert(pNode!=NULL); pNode->ubSumLights=0; pNode->ubMaxLights=0; pNode->ubNaturalShadeLevel = ubShade; pNode->ubShadeLevel = ubShade; //LightAddTileNode(pNode, 0, (INT16)(SHADE_MIN-ubShade)); } /**************************************************************************************** LightSetNaturalTile Sets the natural light value of all objects on a given tile to the specified value. This is the light value a tile has with no artificial lighting affecting it. ***************************************************************************************/ BOOLEAN LightSetNaturalTile(INT16 iX, INT16 iY, UINT8 ubShade) { LEVELNODE *pLand, *pStruct, *pObject, *pRoof, *pOnRoof, *pTopmost, *pMerc; UINT32 uiIndex; CHECKF(gpWorldLevelData!=NULL); uiIndex = MAPROWCOLTOPOS( iY, iX ); Assert(uiIndex!=0xffffffff); ubShade=__max(SHADE_MAX, ubShade); ubShade=__min(SHADE_MIN, ubShade); pLand = gpWorldLevelData[ uiIndex ].pLandHead; while(pLand!=NULL) { LightSetNaturalTileNode(pLand, ubShade); pLand=pLand->pNext; } pStruct = gpWorldLevelData[ uiIndex ].pStructHead; while(pStruct!=NULL) { LightSetNaturalTileNode(pStruct, ubShade); pStruct=pStruct->pNext; } pObject = gpWorldLevelData[ uiIndex ].pObjectHead; while(pObject!=NULL) { LightSetNaturalTileNode(pObject, ubShade); pObject=pObject->pNext; } pRoof = gpWorldLevelData[ uiIndex ].pRoofHead; while(pRoof!=NULL) { LightSetNaturalTileNode(pRoof, ubShade); pRoof=pRoof->pNext; } pOnRoof = gpWorldLevelData[ uiIndex ].pOnRoofHead; while(pOnRoof!=NULL) { LightSetNaturalTileNode(pOnRoof, ubShade); pOnRoof=pOnRoof->pNext; } pTopmost = gpWorldLevelData[ uiIndex ].pTopmostHead; while(pTopmost!=NULL) { LightSetNaturalTileNode(pTopmost, ubShade); pTopmost=pTopmost->pNext; } pMerc = gpWorldLevelData[ uiIndex ].pMercHead; while(pMerc!=NULL) { LightSetNaturalTileNode(pMerc, ubShade); pMerc=pMerc->pNext; } return(TRUE); } /**************************************************************************************** LightResetTileNode Resets the light level of individual LEVELNODEs to the value contained in the natural light level. ***************************************************************************************/ void LightResetTileNode(LEVELNODE *pNode) { pNode->ubSumLights=0; pNode->ubMaxLights=0; pNode->ubShadeLevel = pNode->ubNaturalShadeLevel; pNode->ubFakeShadeLevel = 0; } /**************************************************************************************** LightResetTile Resets the light values of all objects on a given tile to the "natural" light level for that tile. ***************************************************************************************/ BOOLEAN LightResetTile(INT16 iX, INT16 iY) { LEVELNODE *pLand, *pStruct, *pObject, *pRoof, *pOnRoof, *pTopmost, *pMerc; UINT32 uiTile; CHECKF(gpWorldLevelData!=NULL); uiTile = MAPROWCOLTOPOS( iY, iX ); CHECKF(uiTile!=0xffffffff); pLand = gpWorldLevelData[uiTile].pLandHead; while(pLand!=NULL) { LightResetTileNode(pLand); pLand=pLand->pNext; } pStruct = gpWorldLevelData[ uiTile ].pStructHead; while(pStruct!=NULL) { LightResetTileNode(pStruct); pStruct=pStruct->pNext; } pObject = gpWorldLevelData[ uiTile ].pObjectHead; while(pObject!=NULL) { LightResetTileNode(pObject); pObject=pObject->pNext; } pRoof = gpWorldLevelData[ uiTile ].pRoofHead; while(pRoof!=NULL) { LightResetTileNode(pRoof); pRoof=pRoof->pNext; } pOnRoof = gpWorldLevelData[ uiTile ].pOnRoofHead; while(pOnRoof!=NULL) { LightResetTileNode(pOnRoof); pOnRoof=pOnRoof->pNext; } pTopmost = gpWorldLevelData[ uiTile ].pTopmostHead; while(pTopmost!=NULL) { LightResetTileNode(pTopmost); pTopmost=pTopmost->pNext; } pMerc = gpWorldLevelData[ uiTile ].pMercHead; while(pMerc!=NULL) { LightResetTileNode(pMerc); pMerc=pMerc->pNext; } return(TRUE); } /**************************************************************************************** LightResetAllTiles Resets all tiles on the map to their baseline values. ***************************************************************************************/ BOOLEAN LightResetAllTiles(void) { INT16 iCountY, iCountX; for(iCountY=0; iCountY < WORLD_ROWS; iCountY++) for(iCountX=0; iCountX < WORLD_COLS; iCountX++) LightResetTile(iCountX, iCountY); return(TRUE); } /**************************************************************************************** LightSetTile Sets the current light value of all objects on a given tile to the specified value. ***************************************************************************************/ BOOLEAN LightSetTile(INT16 iX, INT16 iY, UINT8 ubShade, UINT32 uiLightType) { /*LEVELNODE *pLand, *pStruct, *pObject, *pRoof, *pOnRoof, *pTopmost, *pFog; UINT32 uiIndex; ubShade=__max(SHADE_MAX, ubShade); ubShade=__min(SHADE_MIN, ubShade); uiIndex = MAPROWCOLTOPOS( iY, iX ); pLand = gpWorldLevelData[ uiIndex ].pLandHead; while(pLand!=NULL) { pLand->sSumLights[uiLightType]= ubShade-SHADE_MAX; pLand->ubShadeLevel = ubShade; pLand=pLand->pNext; } pStruct = gpWorldLevelData[ uiIndex ].pStructHead; while(pStruct!=NULL) { pStruct->sSumLights[uiLightType]= ubShade-SHADE_MAX; pStruct->ubShadeLevel = ubShade; pStruct=pStruct->pNext; } pObject = gpWorldLevelData[ uiIndex ].pObjectHead; while(pObject!=NULL) { pObject->sSumLights[uiLightType]= ubShade-SHADE_MAX; pObject->ubShadeLevel = ubShade; pObject=pObject->pNext; } pRoof = gpWorldLevelData[ uiIndex ].pRoofHead; while(pRoof!=NULL) { pRoof->sSumLights[uiLightType]= ubShade-SHADE_MAX; pRoof->ubShadeLevel = ubShade; pRoof=pRoof->pNext; } pOnRoof = gpWorldLevelData[ uiIndex ].pOnRoofHead; while(pOnRoof!=NULL) { pOnRoof->sSumLights[uiLightType]= ubShade-SHADE_MAX; pOnRoof->ubShadeLevel = ubShade; pOnRoof=pOnRoof->pNext; } pFog = gpWorldLevelData[ uiIndex ].pFogHead; while(pFog!=NULL) { pFog->sSumLights[uiLightType]= ubShade-SHADE_MAX; pFog->ubShadeLevel = ubShade; pFog=pFog->pNext; } pTopmost = gpWorldLevelData[ uiIndex ].pTopmostHead; while(pTopmost!=NULL) { pTopmost->sSumLights[uiLightType]= ubShade-SHADE_MAX; pTopmost->ubShadeLevel = ubShade; pTopmost=pTopmost->pNext; } */ return(TRUE); } /**************************************************************************************** LightGetLastNode Returns a pointer to the last node in a light list. If the list is empty, NULL is returned. ***************************************************************************************/ UINT16 LightGetLastNode(INT32 iLight) { return(usRaySize[iLight]); } /**************************************************************************************** LightAddNode Creates a new node, and adds it to the end of a light list. ***************************************************************************************/ BOOLEAN LightAddNode(INT32 iLight, INT16 iHotSpotX, INT16 iHotSpotY, INT16 iX, INT16 iY, UINT8 ubIntensity, UINT16 uiFlags) { DOUBLE dDistance; UINT8 ubShade; INT32 iLightDecay; dDistance=LinearDistanceDouble(iX, iY, iHotSpotX, iHotSpotY); dDistance/=DISTANCE_SCALE; iLightDecay=(INT32)(dDistance*LIGHT_DECAY); if((iLightDecay >= (INT32)ubIntensity)) ubShade=0; else ubShade=ubIntensity-(UINT8)iLightDecay; iX/=DISTANCE_SCALE; iY/=DISTANCE_SCALE; LightAddRayNode(iLight, iX, iY, ubShade, uiFlags); return(TRUE); } /**************************************************************************************** LightInsertNode Creates a new node, and inserts it after the specified node. ***************************************************************************************/ BOOLEAN LightInsertNode(INT32 iLight, UINT16 usLightIns, INT16 iHotSpotX, INT16 iHotSpotY, INT16 iX, INT16 iY, UINT8 ubIntensity, UINT16 uiFlags) { DOUBLE dDistance; UINT8 ubShade; INT32 iLightDecay; dDistance=LinearDistanceDouble(iX, iY, iHotSpotX, iHotSpotY); dDistance/=DISTANCE_SCALE; iLightDecay=(INT32)(dDistance*LIGHT_DECAY); if((iLightDecay >= (INT32)ubIntensity)) ubShade=0; else ubShade=ubIntensity-(UINT8)iLightDecay; iX/=DISTANCE_SCALE; iY/=DISTANCE_SCALE; LightInsertRayNode(iLight, usLightIns, iX, iY, ubShade, uiFlags); return(TRUE); } /**************************************************************************************** LightFindNextRay Traverses the linked list until a node with the LIGHT_NEW_RAY marker, and returns the pointer. If the end of list is reached, NULL is returned. ***************************************************************************************/ UINT16 LightFindNextRay(INT32 iLight, UINT16 usIndex) { UINT16 usNodeIndex; usNodeIndex=usIndex; while((usNodeIndex < usRaySize[iLight]) && !(*(pLightRayList[iLight]+usNodeIndex)&LIGHT_NEW_RAY)) usNodeIndex++; return(usNodeIndex); } /**************************************************************************************** LightCastRay Casts a ray from an origin to an end point, creating nodes and adding them to the light list. ***************************************************************************************/ BOOLEAN LightCastRay(INT32 iLight, INT16 iStartX, INT16 iStartY, INT16 iEndPointX, INT16 iEndPointY, UINT8 ubStartIntens, UINT8 ubEndIntens) { INT16 AdjUp, AdjDown, ErrorTerm, XAdvance, XDelta, YDelta; INT32 WholeStep, InitialPixelCount, FinalPixelCount, i, j, RunLength; INT16 iXPos, iYPos, iEndY, iEndX; UINT16 usCurNode=0, usFlags=0; BOOLEAN fInsertNodes=FALSE; if((iEndPointX > 0) && (iEndPointY > 0)) usFlags=LIGHT_BACKLIGHT; /* We'll always draw top to bottom, to reduce the number of cases we have to handle, and to make lines between the same endpoints draw the same pixels */ if (iStartY > iEndPointY) { iXPos=iEndPointX; iEndX=iStartX; iYPos=iEndPointY; iEndY=iStartY; fInsertNodes=TRUE; } else { iXPos=iStartX; iEndX=iEndPointX; iYPos=iStartY; iEndY=iEndPointY; } /* Figure out whether we're going left or right, and how far we're going horizontally */ if ((XDelta = (iEndX - iXPos)) < 0) { XAdvance = -1; XDelta = -XDelta; } else { XAdvance = 1; } /* Figure out how far we're going vertically */ YDelta = iEndY - iYPos; // Check for 0 length ray if((XDelta==0) && (YDelta==0)) return(FALSE); //DebugMsg(TOPIC_GAME, DBG_LEVEL_0, String("Drawing (%d,%d) to (%d,%d)", iXPos, iYPos, iEndX, iEndY)); LightAddNode(iLight, 32767, 32767, 32767, 32767, 0, LIGHT_NEW_RAY); if(fInsertNodes) usCurNode=LightGetLastNode(iLight); /* Special-case horizontal, vertical, and diagonal lines, for speed and to avoid nasty boundary conditions and division by 0 */ if (XDelta == 0) { /* Vertical line */ if(fInsertNodes) { for (i=0; i<=YDelta; i++) { LightInsertNode(iLight, usCurNode, iStartX, iStartY, iXPos, iYPos, ubStartIntens, usFlags); iYPos++; } } else { for (i=0; i<=YDelta; i++) { LightAddNode(iLight, iStartX, iStartY, iXPos, iYPos, ubStartIntens, usFlags); iYPos++; } } return(TRUE); } if (YDelta == 0) { /* Horizontal line */ if(fInsertNodes) { for (i=0; i<=XDelta; i++) { LightInsertNode(iLight, usCurNode, iStartX, iStartY, iXPos, iYPos, ubStartIntens, usFlags); iXPos=iXPos+XAdvance; } } else { for (i=0; i<=XDelta; i++) { LightAddNode(iLight, iStartX, iStartY, iXPos, iYPos, ubStartIntens, usFlags); iXPos=iXPos+XAdvance; } } return(TRUE); } if (XDelta == YDelta) { /* Diagonal line */ if(fInsertNodes) { for (i=0; i<=XDelta; i++) { LightInsertNode(iLight, usCurNode, iStartX, iStartY, iXPos, iYPos, ubStartIntens, usFlags); iXPos+=XAdvance; iYPos++; } } else { for (i=0; i<=XDelta; i++) { LightAddNode(iLight, iStartX, iStartY, iXPos, iYPos, ubStartIntens, usFlags); iXPos+=XAdvance; iYPos++; } } return(TRUE); } /* Determine whether the line is X or Y major, and handle accordingly */ if (XDelta >= YDelta) { /* X major line */ /* Minimum # of pixels in a run in this line */ WholeStep = XDelta / YDelta; /* Error term adjust each time Y steps by 1; used to tell when one extra pixel should be drawn as part of a run, to account for fractional steps along the X axis per 1-pixel steps along Y */ AdjUp = (XDelta % YDelta) * 2; /* Error term adjust when the error term turns over, used to factor out the X step made at that time */ AdjDown = YDelta * 2; /* Initial error term; reflects an initial step of 0.5 along the Y axis */ ErrorTerm = (XDelta % YDelta) - (YDelta * 2); /* The initial and last runs are partial, because Y advances only 0.5 for these runs, rather than 1. Divide one full run, plus the initial pixel, between the initial and last runs */ InitialPixelCount = (WholeStep / 2) + 1; FinalPixelCount = InitialPixelCount; /* If the basic run length is even and there's no fractional advance, we have one pixel that could go to either the initial or last partial run, which we'll arbitrarily allocate to the last run */ if ((AdjUp == 0) && ((WholeStep & 0x01) == 0)) { InitialPixelCount--; } /* If there're an odd number of pixels per run, we have 1 pixel that can't be allocated to either the initial or last partial run, so we'll add 0.5 to error term so this pixel will be handled by the normal full-run loop */ if ((WholeStep & 0x01) != 0) { ErrorTerm += YDelta; } /* Draw the first, partial run of pixels */ //DrawHorizontalRun(&ScreenPtr, XAdvance, InitialPixelCount, Color); if(fInsertNodes) { for (i=0; i 0) { RunLength++; ErrorTerm -= AdjDown; /* reset the error term */ } /* Draw this scan line's run */ //DrawHorizontalRun(&ScreenPtr, XAdvance, RunLength, Color); if(fInsertNodes) { for (i=0; i 0) { RunLength++; ErrorTerm -= AdjDown; /* reset the error term */ } /* Draw this scan line's run */ //DrawVerticalRun(&ScreenPtr, XAdvance, RunLength, Color); if(fInsertNodes) { for (i=0; i= 0) WorkingY= (INT32)(sqrt(Temp)+0.5); else WorkingY=0; /* Stop if X is no longer the major axis (the arc has passed the 45-degree point) */ if(((DOUBLE)WorkingY/BSquared) <= ((DOUBLE)WorkingX/ASquared)) break; /* Draw the 4 symmetries of the current point */ LightCastRay(iLight, iX, iY, (INT16)(iX+WorkingX), (INT16)(iY-WorkingY), iIntensity, 1); LightCastRay(iLight, iX, iY, (INT16)(iX-WorkingX), (INT16)(iY-WorkingY), iIntensity, 1); LightCastRay(iLight, iX, iY, (INT16)(iX+WorkingX), (INT16)(iY+WorkingY), iIntensity, 1); LightCastRay(iLight, iX, iY, (INT16)(iX-WorkingX), (INT16)(iY+WorkingY), iIntensity, 1); } /* Draw the four symmetric arcs for which Y advances faster (that is, for which Y is the major axis) */ /* Draw the initial left & right points */ LightCastRay(iLight, iX, iY, (INT16)(iX+iA), iY, iIntensity, 1); LightCastRay(iLight, iX, iY, (INT16)(iX-iA), iY, iIntensity, 1); /* Draw the four arcs */ for (WorkingY = 0; ; ) { /* Advance one pixel along the Y axis */ WorkingY++; /* Calculate the corresponding point along the X axis. Guard against floating-point roundoff making the intermediate term less than 0 */ Temp = ASquared - (ASquared * WorkingY * WorkingY / BSquared); if(Temp >= 0) WorkingX = (INT32)(sqrt(Temp)+0.5); else WorkingX = 0; /* Stop if Y is no longer the major axis (the arc has passed the 45-degree point) */ if (((DOUBLE)WorkingX/ASquared) < ((DOUBLE)WorkingY/BSquared)) break; /* Draw the 4 symmetries of the current point */ LightCastRay(iLight, iX, iY, (INT16)(iX+WorkingX), (INT16)(iY-WorkingY), iIntensity, 1); LightCastRay(iLight, iX, iY, (INT16)(iX-WorkingX), (INT16)(iY-WorkingY), iIntensity, 1); LightCastRay(iLight, iX, iY, (INT16)(iX+WorkingX), (INT16)(iY+WorkingY), iIntensity, 1); LightCastRay(iLight, iX, iY, (INT16)(iX-WorkingX), (INT16)(iY+WorkingY), iIntensity, 1); } return(TRUE); } /**************************************************************************************** LightGenerateSquare Creates an square light, taking two radii. ***************************************************************************************/ BOOLEAN LightGenerateSquare(INT32 iLight, UINT8 iIntensity, INT16 iA, INT16 iB) { INT16 iX, iY; for(iX=0-iA; iX <= 0+iA; iX++) LightCastRay(iLight, 0, 0, iX, (INT16)(0-iB), iIntensity, 1); for(iX=0-iA; iX <= 0+iA; iX++) LightCastRay(iLight, 0, 0, iX, (INT16)(0+iB), iIntensity, 1); for(iY=0-iB; iY <= 0+iB; iY++) LightCastRay(iLight, 0, 0, (INT16)(0-iA), iY, iIntensity, 1); for(iY=0-iB; iY <= 0+iB; iY++) LightCastRay(iLight, 0, 0, (INT16)(0+iA), iY, iIntensity, 1); /*for(iY=0-iB; iY <= 0+iB; iY++) LightCastRay(iLight, 0, iY, (INT16)(0+iA), iY, iIntensity, 1); for(iY=0+iB; iY >= 0-iB; iY--) LightCastRay(iLight, 0, iY, (INT16)(0-iA), iY, iIntensity, 1); for(iX=0-iA; iX <= 0+iA; iX++) LightCastRay(iLight, iX, 0, iX, (INT16)(0+iB), iIntensity, 1); for(iX=0+iA; iX >= 0-iA; iX--) LightCastRay(iLight, iX, 0, iX, (INT16)(0-iB), iIntensity, 1); */ return(TRUE); } /**************************************************************************************** LightGenerateBeam Creates a directional light. ***************************************************************************************/ BOOLEAN LightGenerateBeam(INT32 iLight, UINT8 iIntensity, INT16 iLength, INT16 iRadius, INT16 iDirection) { return(FALSE); } /**************************************************************************************** LightSetBaseLevel Sets the current and natural light settings for all tiles in the world. ***************************************************************************************/ BOOLEAN LightSetBaseLevel(UINT8 iIntensity) { INT16 iCountY, iCountX; UINT32 cnt; SOLDIERTYPE *pSoldier; ubAmbientLightLevel=iIntensity; if( !gfEditMode ) { // Loop for all good guys in tactical map and add a light if required for ( cnt = 0; cnt < guiNumMercSlots; cnt++ ) { pSoldier = MercSlots[ cnt ]; if ( pSoldier != NULL ) { if ( pSoldier->bTeam == gbPlayerNum ) { // Re-create soldier lights pSoldier->ReCreateSoldierLight( ); } } } } for(iCountY=0; iCountY < WORLD_ROWS; iCountY++) for(iCountX=0; iCountX < WORLD_COLS; iCountX++) LightSetNaturalTile(iCountX, iCountY, iIntensity); LightSpriteRenderAll(); if(iIntensity >= LIGHT_DUSK_CUTOFF) RenderSetShadows(FALSE); else RenderSetShadows(TRUE); return(TRUE); } /**************************************************************************************** LightAddBaseLevel Adds a light value to all the tiles in the world ***************************************************************************************/ BOOLEAN LightAddBaseLevel(UINT32 uiLightType, UINT8 iIntensity) { INT16 iCountY, iCountX; ubAmbientLightLevel=__max(SHADE_MAX, ubAmbientLightLevel-iIntensity); for(iCountY=0; iCountY < WORLD_ROWS; iCountY++) for(iCountX=0; iCountX < WORLD_COLS; iCountX++) LightAddTile(uiLightType, iCountX, iCountY, iCountX, iCountY, iIntensity, LIGHT_IGNORE_WALLS|LIGHT_EVERYTHING, FALSE ); if(ubAmbientLightLevel >= LIGHT_DUSK_CUTOFF) RenderSetShadows(FALSE); else RenderSetShadows(TRUE); return(TRUE); } /**************************************************************************************** LightSubtractBaseLevel Sets the current and natural light settings for all tiles in the world. ***************************************************************************************/ BOOLEAN LightSubtractBaseLevel(UINT32 uiLightType, UINT8 iIntensity) { INT16 iCountY, iCountX; ubAmbientLightLevel=__min(SHADE_MIN, ubAmbientLightLevel+iIntensity); for(iCountY=0; iCountY < WORLD_ROWS; iCountY++) for(iCountX=0; iCountX < WORLD_COLS; iCountX++) LightSubtractTile(uiLightType, iCountX, iCountY, iCountX, iCountY, iIntensity, LIGHT_IGNORE_WALLS|LIGHT_EVERYTHING, FALSE ); if(ubAmbientLightLevel >= LIGHT_DUSK_CUTOFF) RenderSetShadows(FALSE); else RenderSetShadows(TRUE); return(TRUE); } /**************************************************************************************** LightCreateOmni Creates a circular light. ***************************************************************************************/ INT32 LightCreateOmni(UINT8 ubIntensity, INT16 iRadius) { INT32 iLight; CHAR8 usName[14]; iLight=LightGetFree(); if(iLight!=(-1)) { LightGenerateElliptical(iLight, ubIntensity, (INT16)(iRadius*DISTANCE_SCALE), (INT16)(iRadius*DISTANCE_SCALE)); sprintf(usName, "LTO%d.LHT", iRadius); pLightNames[iLight]= (STR) MemAlloc(strlen(usName)+1); strcpy(pLightNames[iLight], usName); } return(iLight); } /**************************************************************************************** LightCreateSquare Creates a square light. ***************************************************************************************/ INT32 LightCreateSquare(UINT8 ubIntensity, INT16 iRadius1, INT16 iRadius2) { INT32 iLight; CHAR8 usName[14]; iLight=LightGetFree(); if(iLight!=(-1)) { LightGenerateSquare(iLight, ubIntensity, (INT16)(iRadius1*DISTANCE_SCALE), (INT16)(iRadius2*DISTANCE_SCALE)); sprintf(usName, "LTS%d-%d.LHT", iRadius1, iRadius2); pLightNames[iLight]= (STR) MemAlloc(strlen(usName)+1); strcpy(pLightNames[iLight], usName); } return(iLight); } /**************************************************************************************** LightCreateOval Creates an elliptical light. ***************************************************************************************/ INT32 LightCreateElliptical(UINT8 ubIntensity, INT16 iRadius1, INT16 iRadius2) { INT32 iLight; CHAR8 usName[14]; iLight=LightGetFree(); if(iLight!=(-1)) { LightGenerateElliptical(iLight, ubIntensity, (INT16)(iRadius1*DISTANCE_SCALE), (INT16)(iRadius2*DISTANCE_SCALE)); sprintf(usName, "LTE%d-%d.LHT", iRadius1, iRadius2); pLightNames[iLight]= (STR) MemAlloc(strlen(usName)+1); strcpy(pLightNames[iLight], usName); } return(iLight); } /**************************************************************************************** LightIlluminateWall Renders a light template at the specified X,Y coordinates. ***************************************************************************************/ BOOLEAN LightIlluminateWall(INT16 iSourceX, INT16 iSourceY, INT16 iTileX, INT16 iTileY, LEVELNODE *pStruct) { // return( LightTileHasWall( iSourceX, iSourceY, iTileX, iTileY ) ); return( TRUE ); } /**************************************************************************************** LightDraw Renders a light template at the specified X,Y coordinates. ***************************************************************************************/ BOOLEAN LightDraw(UINT32 uiLightType, INT32 iLight, INT16 iX, INT16 iY, UINT32 uiSprite) { LIGHT_NODE *pLight; UINT16 uiCount; UINT16 usNodeIndex; UINT32 uiFlags; INT32 iOldX, iOldY; BOOLEAN fBlocked = FALSE; BOOLEAN fOnlyWalls; //MAP_ELEMENT * pMapElement; if(pLightList[iLight]==NULL) return(FALSE); // clear out all the flags for(uiCount=0; uiCount < usTemplateSize[iLight]; uiCount++) { pLight=pLightList[iLight]+uiCount; pLight->uiFlags&=(~LIGHT_NODE_DRAWN); } /* if (!(LightSprites[uiSprite].uiFlags & MERC_LIGHT)) { uiFlags |= LIGHT_FAKE pMapElement = &(gpWorldLevelData[]); if (pMapElement->pLand != NULL) { // only do this for visible locations // adjust tile's recorded light level pMapElement->sSumRealLight1+=sShadeAdd; sSum=pMapElement->pLand->ubNaturalShadeLevel - __max( pMapElement->sSumRealLights[0], pMapElement->pLand->sSumLights[1]); sSum=__min(SHADE_MIN, sSum); sSum=__max(SHADE_MAX, sSum); gpWorldLevelData[ ].ubRealShadeLevel = (UINT8) sSum; } } */ iOldX = iX; iOldY = iY; for(uiCount=0; uiCount < usRaySize[iLight]; uiCount++) { usNodeIndex=*(pLightRayList[iLight]+uiCount); if(!(usNodeIndex&LIGHT_NEW_RAY)) { fBlocked = FALSE; fOnlyWalls = FALSE; pLight=pLightList[iLight]+(usNodeIndex&(~LIGHT_BACKLIGHT)); if(!(LightSprites[uiSprite].uiFlags&LIGHT_SPR_ONROOF)) { if(LightTileBlocked( (INT16)iOldX, (INT16)iOldY, (INT16)(iX+pLight->iDX), (INT16)(iY+pLight->iDY))) { uiCount=LightFindNextRay(iLight, uiCount); fOnlyWalls = TRUE; fBlocked = TRUE; } } if(!(pLight->uiFlags&LIGHT_NODE_DRAWN) && (pLight->ubLight) ) { uiFlags=(UINT32)(usNodeIndex&LIGHT_BACKLIGHT); if (LightSprites[uiSprite].uiFlags & MERC_LIGHT) uiFlags |= LIGHT_FAKE; if(LightSprites[uiSprite].uiFlags&LIGHT_SPR_ONROOF) uiFlags|=LIGHT_ROOF_ONLY; LightAddTile(uiLightType, (INT16)iOldX, (INT16)iOldY, (INT16)(iX+pLight->iDX), (INT16)(iY+pLight->iDY), pLight->ubLight, uiFlags, fOnlyWalls ); pLight->uiFlags|=LIGHT_NODE_DRAWN; } if ( fBlocked ) { iOldX = iX; iOldY = iY; } else { iOldX = iX+pLight->iDX; iOldY = iY+pLight->iDY; } } else { iOldX = iX; iOldY = iY; } } return(TRUE); } BOOLEAN LightRevealWall(INT16 sX, INT16 sY, INT16 sSrcX, INT16 sSrcY) { LEVELNODE *pStruct; UINT32 uiTile; BOOLEAN fRerender=FALSE, fHitWall=FALSE, fDoRightWalls=TRUE, fDoLeftWalls=TRUE; TILE_ELEMENT *TileElem; Assert(gpWorldLevelData!=NULL); uiTile=MAPROWCOLTOPOS(sY, sX); if(sX < sSrcX) fDoRightWalls=FALSE; if(sY < sSrcY) fDoLeftWalls=FALSE; // IF A FENCE, RETURN FALSE if ( IsFencePresentAtGridNo( uiTile ) ) { return( FALSE ); } pStruct=gpWorldLevelData[uiTile].pStructHead; //while(pStruct!=NULL) while(pStruct!=NULL && pStruct->usIndexusIndex]); switch(TileElem->usWallOrientation) { case INSIDE_TOP_RIGHT: case OUTSIDE_TOP_RIGHT: if(!fDoRightWalls) fDoLeftWalls=FALSE; break; case INSIDE_TOP_LEFT: case OUTSIDE_TOP_LEFT: if(!fDoLeftWalls) fDoRightWalls=FALSE; break; } pStruct=pStruct->pNext; } pStruct=gpWorldLevelData[uiTile].pStructHead; //while(pStruct!=NULL) while(pStruct!=NULL && pStruct->usIndexusIndex]); switch(TileElem->usWallOrientation) { case NO_ORIENTATION: break; case INSIDE_TOP_RIGHT: case OUTSIDE_TOP_RIGHT: fHitWall=TRUE; if((fDoRightWalls) && (sX >= sSrcX)) { pStruct->uiFlags|=LEVELNODE_REVEAL; fRerender=TRUE; } break; case INSIDE_TOP_LEFT: case OUTSIDE_TOP_LEFT: fHitWall=TRUE; if((fDoLeftWalls) && (sY >= sSrcY)) { pStruct->uiFlags|=LEVELNODE_REVEAL; fRerender=TRUE; } break; } pStruct=pStruct->pNext; } if(fRerender) SetRenderFlags(RENDER_FLAG_FULL); return(fHitWall); } BOOLEAN LightHideWall(INT16 sX, INT16 sY, INT16 sSrcX, INT16 sSrcY) { LEVELNODE *pStruct; UINT32 uiTile; BOOLEAN fRerender=FALSE, fHitWall=FALSE, fDoRightWalls=TRUE, fDoLeftWalls=TRUE; TILE_ELEMENT *TileElem; Assert(gpWorldLevelData!=NULL); uiTile=MAPROWCOLTOPOS(sY, sX); if(sX < sSrcX) fDoRightWalls=FALSE; if(sY < sSrcY) fDoLeftWalls=FALSE; pStruct=gpWorldLevelData[uiTile].pStructHead; //while(pStruct!=NULL) while(pStruct!=NULL && pStruct->usIndexusIndex]); switch(TileElem->usWallOrientation) { case INSIDE_TOP_RIGHT: case OUTSIDE_TOP_RIGHT: if(!fDoRightWalls) fDoLeftWalls=FALSE; break; case INSIDE_TOP_LEFT: case OUTSIDE_TOP_LEFT: if(!fDoLeftWalls) fDoRightWalls=FALSE; break; } pStruct=pStruct->pNext; } pStruct=gpWorldLevelData[uiTile].pStructHead; //while(pStruct!=NULL) while(pStruct!=NULL && pStruct->usIndexusIndex]); switch(TileElem->usWallOrientation) { case NO_ORIENTATION: break; case INSIDE_TOP_RIGHT: case OUTSIDE_TOP_RIGHT: fHitWall=TRUE; if((fDoRightWalls) && (sX >= sSrcX)) { pStruct->uiFlags&=(~LEVELNODE_REVEAL); fRerender=TRUE; } break; case INSIDE_TOP_LEFT: case OUTSIDE_TOP_LEFT: fHitWall=TRUE; if((fDoLeftWalls) && (sY >= sSrcY)) { pStruct->uiFlags&=(~LEVELNODE_REVEAL); fRerender=TRUE; } break; } pStruct=pStruct->pNext; } if(fRerender) SetRenderFlags(RENDER_FLAG_FULL); return(fHitWall); } /**************************************************************************************** CalcTranslucentWalls Tags walls as being translucent using a light template. ***************************************************************************************/ BOOLEAN CalcTranslucentWalls(INT16 iX, INT16 iY) { LIGHT_NODE *pLight; UINT16 uiCount; UINT16 usNodeIndex; if(pLightList[0]==NULL) return(FALSE); for(uiCount=0; uiCount < usRaySize[0]; uiCount++) { usNodeIndex=*(pLightRayList[0]+uiCount); if(!(usNodeIndex&LIGHT_NEW_RAY)) { pLight=pLightList[0]+(usNodeIndex&(~LIGHT_BACKLIGHT)); //Kris: added map boundary checking!!! if(LightRevealWall( (INT16)min(max((iX+pLight->iDX),0),WORLD_COLS-1), (INT16)min(max((iY+pLight->iDY),0),WORLD_ROWS-1), (INT16)min(max(iX,0),WORLD_COLS-1), (INT16)min(max(iY,0),WORLD_ROWS-1) )) { uiCount=LightFindNextRay(0, uiCount); SetRenderFlags(RENDER_FLAG_FULL); } } } return(TRUE); } BOOLEAN LightGreenTile(INT16 sX, INT16 sY, INT16 sSrcX, INT16 sSrcY) { LEVELNODE *pStruct, *pLand; UINT32 uiTile; BOOLEAN fRerender=FALSE, fHitWall=FALSE, fThroughWall=FALSE; TILE_ELEMENT *TileElem; Assert(gpWorldLevelData!=NULL); uiTile=MAPROWCOLTOPOS(sY, sX); pStruct=gpWorldLevelData[uiTile].pStructHead; if((sX < sSrcX) || (sY < sSrcY)) fThroughWall=TRUE; while(pStruct!=NULL) { TileElem = &(gTileDatabase[pStruct->usIndex]); switch(TileElem->usWallOrientation) { case NO_ORIENTATION: break; case INSIDE_TOP_RIGHT: case OUTSIDE_TOP_RIGHT: fHitWall=TRUE; if(sX >= sSrcX) { pStruct->uiFlags|=LEVELNODE_REVEAL; fRerender=TRUE; } break; case INSIDE_TOP_LEFT: case OUTSIDE_TOP_LEFT: fHitWall=TRUE; if(sY >= sSrcY) { pStruct->uiFlags|=LEVELNODE_REVEAL; fRerender=TRUE; } break; } pStruct=pStruct->pNext; } //if(fRerender) //{ pLand=gpWorldLevelData[uiTile].pLandHead; while(pLand!=NULL) { pLand->ubShadeLevel=0; pLand=pLand->pNext; } gpWorldLevelData[uiTile].uiFlags|=MAPELEMENT_REDRAW; SetRenderFlags(RENDER_FLAG_MARKED); //} return(fHitWall); } /**************************************************************************************** LightShowRays Draws a template by making the ground tiles green. Must be polled once for each tile drawn to facilitate animating the drawing process for debugging. ***************************************************************************************/ BOOLEAN LightShowRays(INT16 iX, INT16 iY, BOOLEAN fReset) { LIGHT_NODE *pLight; static UINT16 uiCount=0; UINT16 usNodeIndex; if(fReset) uiCount=0; if(pLightList[0]==NULL) return(FALSE); if(uiCount < usRaySize[0]) { usNodeIndex=*(pLightRayList[0]+uiCount); if(!(usNodeIndex&LIGHT_NEW_RAY)) { pLight=pLightList[0]+(usNodeIndex&(~LIGHT_BACKLIGHT)); if(LightGreenTile((INT16)(iX+pLight->iDX), (INT16)(iY+pLight->iDY), iX, iY)) { uiCount=LightFindNextRay(0, uiCount); SetRenderFlags(RENDER_FLAG_MARKED); } } uiCount++; return(TRUE); } else return(FALSE); } /**************************************************************************************** LightHideGreen Removes the green from the tiles that was drawn to show the path of the rays. ***************************************************************************************/ BOOLEAN LightHideGreen(INT16 sX, INT16 sY, INT16 sSrcX, INT16 sSrcY) { LEVELNODE *pStruct, *pLand; UINT32 uiTile; BOOLEAN fRerender=FALSE, fHitWall=FALSE; TILE_ELEMENT *TileElem; Assert(gpWorldLevelData!=NULL); uiTile=MAPROWCOLTOPOS(sY, sX); pStruct=gpWorldLevelData[uiTile].pStructHead; while(pStruct!=NULL) { TileElem = &(gTileDatabase[pStruct->usIndex]); switch(TileElem->usWallOrientation) { case NO_ORIENTATION: break; case INSIDE_TOP_RIGHT: case OUTSIDE_TOP_RIGHT: fHitWall=TRUE; if(sX >= sSrcX) { pStruct->uiFlags&=(~LEVELNODE_REVEAL); fRerender=TRUE; } break; case INSIDE_TOP_LEFT: case OUTSIDE_TOP_LEFT: fHitWall=TRUE; if(sY >= sSrcY) { pStruct->uiFlags&=(~LEVELNODE_REVEAL); fRerender=TRUE; } break; } pStruct=pStruct->pNext; } //if(fRerender) //{ pLand=gpWorldLevelData[uiTile].pLandHead; while(pLand!=NULL) { pLand->ubShadeLevel=pLand->ubNaturalShadeLevel; pLand=pLand->pNext; } gpWorldLevelData[uiTile].uiFlags|=MAPELEMENT_REDRAW; SetRenderFlags(RENDER_FLAG_MARKED); //} return(fHitWall); } /**************************************************************************************** LightHideRays Hides walls that were revealed by CalcTranslucentWalls. ***************************************************************************************/ BOOLEAN LightHideRays(INT16 iX, INT16 iY) { LIGHT_NODE *pLight; UINT16 uiCount; UINT16 usNodeIndex; if(pLightList[0]==NULL) return(FALSE); for(uiCount=0; uiCount < usRaySize[0]; uiCount++) { usNodeIndex=*(pLightRayList[0]+uiCount); if(!(usNodeIndex&LIGHT_NEW_RAY)) { pLight=pLightList[0]+(usNodeIndex&(~LIGHT_BACKLIGHT)); if(LightHideWall((INT16)(iX+pLight->iDX), (INT16)(iY+pLight->iDY), iX, iY)) { uiCount=LightFindNextRay(0, uiCount); SetRenderFlags(RENDER_FLAG_MARKED); } } } return(TRUE); } /**************************************************************************************** ApplyTranslucencyToWalls Hides walls that were revealed by CalcTranslucentWalls. ***************************************************************************************/ BOOLEAN ApplyTranslucencyToWalls(INT16 iX, INT16 iY) { LIGHT_NODE *pLight; UINT16 uiCount; UINT16 usNodeIndex; if(pLightList[0]==NULL) return(FALSE); for(uiCount=0; uiCount < usRaySize[0]; uiCount++) { usNodeIndex=*(pLightRayList[0]+uiCount); if(!(usNodeIndex&LIGHT_NEW_RAY)) { pLight=pLightList[0]+(usNodeIndex&(~LIGHT_BACKLIGHT)); //Kris: added map boundary checking!!! if(LightHideWall( (INT16)min(max((iX+pLight->iDX),0),WORLD_COLS-1), (INT16)min(max((iY+pLight->iDY),0),WORLD_ROWS-1), (INT16)min(max(iX,0),WORLD_COLS-1), (INT16)min(max(iY,0),WORLD_ROWS-1) )) { uiCount=LightFindNextRay(0, uiCount); SetRenderFlags(RENDER_FLAG_FULL); } } } return(TRUE); } /**************************************************************************************** LightTranslucentTrees Makes all the near-side trees around a given coordinate translucent. ***************************************************************************************/ BOOLEAN LightTranslucentTrees(INT16 iX, INT16 iY) { INT32 iCountX, iCountY; UINT32 uiTile; LEVELNODE *pNode; BOOLEAN fRerender=FALSE; UINT32 fTileFlags; for(iCountY=iY; iCountY < (INT16)(iY+LIGHT_TREE_REVEAL); iCountY++) for(iCountX=iX; iCountX < (INT16)(iX+LIGHT_TREE_REVEAL); iCountX++) { uiTile=MAPROWCOLTOPOS(iCountY, iCountX); pNode=gpWorldLevelData[uiTile].pStructHead; while(pNode!=NULL) { GetTileFlags( pNode->usIndex, &fTileFlags ); if ( fTileFlags & FULL3D_TILE ) { if ( !( pNode->uiFlags & LEVELNODE_REVEALTREES ) ) { //pNode->uiFlags |= ( LEVELNODE_REVEALTREES | LEVELNODE_ERASEZ ); pNode->uiFlags |= ( LEVELNODE_REVEALTREES ); gpWorldLevelData[uiTile].uiFlags |= MAPELEMENT_REDRAW; } fRerender=TRUE; } pNode=pNode->pNext; } } if(fRerender) { //SetRenderFlags(RENDER_FLAG_MARKED); SetRenderFlags(RENDER_FLAG_FULL); return(TRUE); } else return(FALSE); } /**************************************************************************************** LightHideTrees Removes the translucency from any trees in the area. ***************************************************************************************/ BOOLEAN LightHideTrees(INT16 iX, INT16 iY) { INT32 iCountX, iCountY; UINT32 uiTile; LEVELNODE *pNode; BOOLEAN fRerender=FALSE; UINT32 fTileFlags; //Kris: added map boundary checking!!! for(iCountY=(INT16)__max(iY-LIGHT_TREE_REVEAL,0); iCountY < (INT16)__min(iY+LIGHT_TREE_REVEAL,WORLD_ROWS-1); iCountY++) for(iCountX=(INT16)__max(iX-LIGHT_TREE_REVEAL,0); iCountX < (INT16)__min(iX+LIGHT_TREE_REVEAL,WORLD_COLS-1); iCountX++) { uiTile=MAPROWCOLTOPOS(iCountY, iCountX); pNode=gpWorldLevelData[uiTile].pStructHead; while(pNode!=NULL) { GetTileFlags( pNode->usIndex, &fTileFlags ); if ( fTileFlags & FULL3D_TILE ) { if ( ( pNode->uiFlags & LEVELNODE_REVEALTREES ) ) { //pNode->uiFlags &=(~( LEVELNODE_REVEALTREES | LEVELNODE_ERASEZ ) ); pNode->uiFlags &=(~( LEVELNODE_REVEALTREES ) ); gpWorldLevelData[uiTile].uiFlags |= MAPELEMENT_REDRAW; } fRerender=TRUE; } pNode=pNode->pNext; } } if(fRerender) { //SetRenderFlags(RENDER_FLAG_MARKED); SetRenderFlags(RENDER_FLAG_FULL ); return(TRUE); } else return(FALSE); } /**************************************************************************************** LightErase Reverts all tiles a given light affects to their natural light levels. ***************************************************************************************/ BOOLEAN LightErase(UINT32 uiLightType, INT32 iLight, INT16 iX, INT16 iY, UINT32 uiSprite) { LIGHT_NODE *pLight; UINT16 uiCount; UINT16 usNodeIndex; UINT32 uiFlags; INT32 iOldX, iOldY; BOOLEAN fBlocked = FALSE; BOOLEAN fOnlyWalls; if(pLightList[iLight]==NULL) return(FALSE); // clear out all the flags for(uiCount=0; uiCount < usTemplateSize[iLight]; uiCount++) { pLight=pLightList[iLight]+uiCount; pLight->uiFlags&=(~LIGHT_NODE_DRAWN); } iOldX = iX; iOldY = iY; for(uiCount=0; uiCount < usRaySize[iLight]; uiCount++) { usNodeIndex=*(pLightRayList[iLight]+uiCount); if(!(usNodeIndex&LIGHT_NEW_RAY)) { fBlocked = FALSE; fOnlyWalls = FALSE; pLight=pLightList[iLight]+(usNodeIndex&(~LIGHT_BACKLIGHT)); if(!(LightSprites[uiSprite].uiFlags&LIGHT_SPR_ONROOF)) { if(LightTileBlocked( (INT16)iOldX, (INT16)iOldY, (INT16)(iX+pLight->iDX), (INT16)(iY+pLight->iDY))) { uiCount=LightFindNextRay(iLight, uiCount); fOnlyWalls = TRUE; fBlocked = TRUE; } } if(!(pLight->uiFlags&LIGHT_NODE_DRAWN) && (pLight->ubLight) ) { uiFlags=(UINT32)(usNodeIndex&LIGHT_BACKLIGHT); if (LightSprites[uiSprite].uiFlags & MERC_LIGHT) uiFlags |= LIGHT_FAKE; if(LightSprites[uiSprite].uiFlags&LIGHT_SPR_ONROOF) uiFlags|=LIGHT_ROOF_ONLY; LightSubtractTile(uiLightType, (INT16)iOldX, (INT16)iOldY, (INT16)(iX+pLight->iDX), (INT16)(iY+pLight->iDY), pLight->ubLight, uiFlags, fOnlyWalls ); pLight->uiFlags|=LIGHT_NODE_DRAWN; } if ( fBlocked ) { iOldX = iX; iOldY = iY; } else { iOldX = iX+pLight->iDX; iOldY = iY+pLight->iDY; } } else { iOldX = iX; iOldY = iY; } } return(TRUE); } /**************************************************************************************** LightCalcRect Calculates the rect size of a given light, used in dirtying the screen after updating a light. ***************************************************************************************/ BOOLEAN LightCalcRect(INT32 iLight) { SGPRect MaxRect; INT16 sXValue, sYValue, sDummy; UINT32 uiCount; LIGHT_NODE *pLight; if(pLightList[iLight]==NULL) return(FALSE); pLight=pLightList[iLight]; MaxRect.iLeft=99999; MaxRect.iRight=-99999; MaxRect.iTop=99999; MaxRect.iBottom=-99999; for(uiCount=0; uiCount < usTemplateSize[iLight]; uiCount++) { pLight=pLightList[iLight]+uiCount; if(pLight->ubLight) { MaxRect.iLeft=__min(MaxRect.iLeft, pLight->iDX); MaxRect.iRight=__max(MaxRect.iRight, pLight->iDX); MaxRect.iTop=__min(MaxRect.iTop, pLight->iDY); MaxRect.iBottom=__max(MaxRect.iBottom, pLight->iDY); } } FromCellToScreenCoordinates((INT16)(MaxRect.iLeft*CELL_X_SIZE), (INT16)(MaxRect.iTop*CELL_Y_SIZE), &sDummy, &sYValue); LightMapLeft[iLight]=(INT16)MaxRect.iLeft; LightMapTop[iLight]=(INT16)MaxRect.iTop; LightMapRight[iLight]=(INT16)MaxRect.iRight; LightMapBottom[iLight]=(INT16)MaxRect.iBottom; LightHeight[iLight]=-sYValue; LightYOffset[iLight]=sYValue; FromCellToScreenCoordinates((INT16)(MaxRect.iRight*CELL_X_SIZE), (INT16)(MaxRect.iBottom*CELL_Y_SIZE), &sDummy, &sYValue); LightHeight[iLight]+=sYValue; FromCellToScreenCoordinates((INT16)(MaxRect.iLeft*CELL_X_SIZE), (INT16)(MaxRect.iBottom*CELL_Y_SIZE), &sXValue, &sDummy); LightWidth[iLight]=-sXValue; LightXOffset[iLight]=sXValue; FromCellToScreenCoordinates((INT16)(MaxRect.iRight*CELL_X_SIZE), (INT16)(MaxRect.iTop*CELL_Y_SIZE), &sXValue, &sDummy); LightWidth[iLight]+=sXValue; LightHeight[iLight]+=WORLD_TILE_X*2; LightWidth[iLight]+=WORLD_TILE_Y*3; LightXOffset[iLight]-=WORLD_TILE_X*2; LightYOffset[iLight]-=WORLD_TILE_Y*2; return(TRUE); } /**************************************************************************************** LightSave Saves the light list of a given template to a file. Passing in NULL for the filename forces the system to save the light with the internal filename (recommended). ***************************************************************************************/ BOOLEAN LightSave(INT32 iLight, STR pFilename) { HWFILE hFile; STR pName; if(pLightList[iLight]==NULL) return(FALSE); else { if(pFilename==NULL) pName=pLightNames[iLight]; else pName=pFilename; if((hFile=FileOpen(pName, FILE_ACCESS_WRITE|FILE_CREATE_ALWAYS, FALSE))!=0) { FileWrite(hFile, &usTemplateSize[iLight], sizeof(UINT16), NULL); FileWrite(hFile, pLightList[iLight], sizeof(LIGHT_NODE)*usTemplateSize[iLight], NULL); FileWrite(hFile, &usRaySize[iLight], sizeof(UINT16), NULL); FileWrite(hFile, pLightRayList[iLight], sizeof(UINT16)*usRaySize[iLight], NULL); FileClose(hFile); } else return(FALSE); } return(TRUE); } /**************************************************************************************** LightLoad Loads a light template from disk. The light template number is returned, or (-1) if the file wasn't loaded. ***************************************************************************************/ INT32 LightLoad(STR pFilename) { HWFILE hFile; INT32 iLight; iLight=LightGetFree(); if( iLight == -1 ) return (-1); if((hFile=FileOpen(pFilename, FILE_ACCESS_READ, FALSE))!=0) { FileRead(hFile, &usTemplateSize[iLight], sizeof(UINT16), NULL); if((pLightList[iLight]= (LIGHT_NODE *) MemAlloc(usTemplateSize[iLight]*sizeof(LIGHT_NODE)))==NULL) { usTemplateSize[iLight]=0; return(-1); } FileRead(hFile, pLightList[iLight], sizeof(LIGHT_NODE)*usTemplateSize[iLight], NULL); FileRead(hFile, &usRaySize[iLight], sizeof(UINT16), NULL); if((pLightRayList[iLight]= (UINT16 *) MemAlloc(usRaySize[iLight]*sizeof(UINT16)))==NULL) { usTemplateSize[iLight]=0; usRaySize[iLight]=0; MemFree(pLightList[iLight]); return(-1); } FileRead(hFile, pLightRayList[iLight], sizeof(UINT16)*usRaySize[iLight], NULL); FileClose(hFile); pLightNames[iLight]= (STR) MemAlloc(strlen(pFilename)+1); strcpy(pLightNames[iLight], pFilename); } else { return(-1); } LightCalcRect(iLight); return(iLight); } /**************************************************************************************** LightLoadCachedTemplate Figures out whether a light template is already in memory, or needs to be loaded from disk. Returns the index of the template, or (-1) if it couldn't be loaded. ***************************************************************************************/ INT32 LightLoadCachedTemplate(STR pFilename) { INT32 iCount; for(iCount=0; iCount < MAX_LIGHT_TEMPLATES; iCount++) { if((pLightNames[iCount]!=NULL) && !(_stricmp(pFilename, pLightNames[iCount]))) return(iCount); } return(LightLoad(pFilename)); } UINT8 LightGetColors(SGPPaletteEntry *pPal) { if(pPal!=NULL) memcpy(pPal, &gpOrigLights[0], sizeof(SGPPaletteEntry)*gubNumLightColors); return(gubNumLightColors); } /**************************************************************************************** LightSetColors Sets the number of light colors, and the RGB value for each. ***************************************************************************************/ #ifdef JA2EDITOR BOOLEAN gfEditorForceRebuildAllColors = FALSE; #endif extern void SetAllNewTileSurfacesLoaded( BOOLEAN fNew ); BOOLEAN LightSetColors(SGPPaletteEntry *pPal, UINT8 ubNumColors) { INT16 sRed, sGreen, sBlue; Assert( ubNumColors >=1 && ubNumColors <=2 ); Assert( pPal ); if( pPal[0].peRed != gpLightColors[0].peRed || pPal[0].peGreen != gpLightColors[0].peGreen || pPal[0].peBlue != gpLightColors[0].peBlue ) { //Set the entire tileset database so that it reloads everything. It has to because the //colors have changed. SetAllNewTileSurfacesLoaded( TRUE ); } // before doing anything, get rid of all the old palettes DestroyTileShadeTables( ); // we will have at least one light color memcpy(&gpLightColors[0], &pPal[0], sizeof(SGPPaletteEntry)); memcpy(&gpOrigLights[0], &pPal[0], sizeof(SGPPaletteEntry)*2); gubNumLightColors=ubNumColors; // if there are two colors, calculate a third palette that is a mix of the two if(ubNumColors==2) { sRed=__min((((INT16)pPal[0].peRed)*LVL1_L1_PER/100 + ((INT16)pPal[1].peRed)*LVL1_L2_PER/100), 255); sGreen=__min((((INT16)pPal[0].peGreen)*LVL1_L1_PER/100 + ((INT16)pPal[1].peGreen)*LVL1_L2_PER/100), 255); sBlue=__min((((INT16)pPal[0].peBlue)*LVL1_L1_PER/100 + ((INT16)pPal[1].peBlue)*LVL1_L2_PER/100), 255); gpLightColors[1].peRed=(UINT8)(sRed); gpLightColors[1].peGreen=(UINT8)(sGreen); gpLightColors[1].peBlue=(UINT8)(sBlue); sRed=__min((((INT16)pPal[0].peRed)*LVL2_L1_PER/100 + ((INT16)pPal[1].peRed)*LVL2_L2_PER/100), 255); sGreen=__min((((INT16)pPal[0].peGreen)*LVL2_L1_PER/100 + ((INT16)pPal[1].peGreen)*LVL2_L2_PER/100), 255); sBlue=__min((((INT16)pPal[0].peBlue)*LVL2_L1_PER/100 + ((INT16)pPal[1].peBlue)*LVL2_L2_PER/100), 255); gpLightColors[2].peRed=(UINT8)(sRed); gpLightColors[2].peGreen=(UINT8)(sGreen); gpLightColors[2].peBlue=(UINT8)(sBlue); } BuildTileShadeTables( ); // Build all palettes for all soldiers in the world // ( THIS FUNCTION WILL ERASE THEM IF THEY EXIST ) RebuildAllSoldierShadeTables( ); RebuildAllCorpseShadeTables( ); SetRenderFlags(RENDER_FLAG_FULL); return(TRUE); } //--------------------------------------------------------------------------------------- // Light Manipulation Layer //--------------------------------------------------------------------------------------- /******************************************************************************** * LightSpriteGetFree * * Returns the index of the next available sprite. * ********************************************************************************/ INT32 LightSpriteGetFree(void) { INT32 iCount; for(iCount=0; iCount < MAX_LIGHT_SPRITES; iCount++) { if(!(LightSprites[iCount].uiFlags&LIGHT_SPR_ACTIVE)) return(iCount); } return(-1); } /******************************************************************************** * LightSpriteCreate * * Creates an instance of a light. The template is loaded if it isn't already. * If this function fails (out of sprites, or bad template name) it returns (-1). * ********************************************************************************/ INT32 LightSpriteCreate(STR pName, UINT32 uiLightType) { INT32 iSprite; iSprite = LightSpriteGetFree(); if( iSprite != -1 ) { memset(&LightSprites[iSprite], 0, sizeof(LIGHT_SPRITE)); LightSprites[iSprite].iX=WORLD_COLS+1; LightSprites[iSprite].iY=WORLD_ROWS+1; LightSprites[iSprite].iOldX=WORLD_COLS+1; LightSprites[iSprite].iOldY=WORLD_ROWS+1; LightSprites[iSprite].uiLightType=uiLightType; if((LightSprites[iSprite].iTemplate=LightLoadCachedTemplate( pName))==(-1)) return(-1); LightSprites[iSprite].uiFlags|=LIGHT_SPR_ACTIVE; } return(iSprite); } /******************************************************************************** * LightSpriteFake * * Sets the flag of a light sprite to "fake" (in game for merc navig purposes) * ********************************************************************************/ BOOLEAN LightSpriteFake(INT32 iSprite) { if ( (iSprite >= MAX_LIGHT_SPRITES ) || (iSprite < 0) ) { return FALSE; } if(LightSprites[iSprite].uiFlags&LIGHT_SPR_ACTIVE) { LightSprites[iSprite].uiFlags|=MERC_LIGHT; return( TRUE ); } else { return( FALSE ); } } /******************************************************************************** * LightSpriteDestroy * * Removes an instance of a light. If it was on, it is erased from the scene. * ********************************************************************************/ BOOLEAN LightSpriteDestroy(INT32 iSprite) { if ( (iSprite >= MAX_LIGHT_SPRITES ) || (iSprite < 0) ) { return FALSE; } if(LightSprites[iSprite].uiFlags&LIGHT_SPR_ACTIVE) { if(LightSprites[iSprite].uiFlags&LIGHT_SPR_ERASE) { if((LightSprites[iSprite].iX < WORLD_COLS) && (LightSprites[iSprite].iY < WORLD_ROWS)) { LightErase(LightSprites[iSprite].uiLightType, LightSprites[iSprite].iTemplate, LightSprites[iSprite].iX, LightSprites[iSprite].iY, iSprite); LightSpriteDirty(iSprite); } LightSprites[iSprite].uiFlags&=(~LIGHT_SPR_ERASE); } LightSprites[iSprite].uiFlags&=(~LIGHT_SPR_ACTIVE); return(TRUE); } return(FALSE); } /******************************************************************************** * LightSpriteRender * * Currently unused. * ********************************************************************************/ BOOLEAN LightSpriteRender(void) { //INT32 iCount; //BOOLEAN fRenderLights=FALSE; return(FALSE); /* for(iCount=0; iCount < MAX_LIGHT_SPRITES; iCount++) { if(LightSprites[iCount].uiFlags&LIGHT_SPR_ACTIVE) { if((LightSprites[iCount].iX!=LightSprites[iCount].iOldX) || (LightSprites[iCount].iY!=LightSprites[iCount].iOldY) || (LightSprites[iCount].uiFlags&LIGHT_SPR_REDRAW)) { if(LightSprites[iCount].iOldX < WORLD_COLS) { fRenderLights=TRUE; LightSpriteDirty(iCount); } LightSprites[iCount].iOldX=LightSprites[iCount].iX; LightSprites[iCount].iOldY=LightSprites[iCount].iY; if(LightSprites[iCount].uiFlags&LIGHT_SPR_ON) { LightSpriteDirty(iCount); fRenderLights=TRUE; } LightSprites[iCount].uiFlags&=(~LIGHT_SPR_REDRAW); } } } if(fRenderLights) { LightResetAllTiles(); for(iCount=0; iCount < MAX_LIGHT_SPRITES; iCount++) if((LightSprites[iCount].uiFlags&LIGHT_SPR_ACTIVE) && (LightSprites[iCount].uiFlags&LIGHT_SPR_ON)) LightDraw(LightSprites[iCount].iTemplate, LightSprites[iCount].iX, LightSprites[iCount].iY, iCount); return(TRUE); } return(FALSE); */ } /******************************************************************************** * LightSpriteRenderAll * * Resets all tiles in the world to the ambient light level, and redraws all * active lights. * ********************************************************************************/ BOOLEAN LightSpriteRenderAll(void) { INT32 iCount; LightResetAllTiles(); for(iCount=0; iCount < MAX_LIGHT_SPRITES; iCount++) { LightSprites[iCount].uiFlags&=(~LIGHT_SPR_ERASE); if((LightSprites[iCount].uiFlags&LIGHT_SPR_ACTIVE) && (LightSprites[iCount].uiFlags&LIGHT_SPR_ON)) { LightDraw(LightSprites[iCount].uiLightType, LightSprites[iCount].iTemplate, LightSprites[iCount].iX, LightSprites[iCount].iY, iCount); LightSprites[iCount].uiFlags|=LIGHT_SPR_ERASE; LightSpriteDirty(iCount); } LightSprites[iCount].iOldX=LightSprites[iCount].iX; LightSprites[iCount].iOldY=LightSprites[iCount].iY; } return(TRUE); } /******************************************************************************** * LightSpritePosition * * Sets the X,Y position (IN TILES) of a light instance. * ********************************************************************************/ BOOLEAN LightSpritePosition(INT32 iSprite, INT16 iX, INT16 iY) { if ( (iSprite >= MAX_LIGHT_SPRITES ) || (iSprite < 0) ) { return FALSE; } if(LightSprites[iSprite].uiFlags&LIGHT_SPR_ACTIVE) { if((LightSprites[iSprite].iX==iX) && (LightSprites[iSprite].iY==iY)) return(TRUE); if(LightSprites[iSprite].uiFlags&LIGHT_SPR_ERASE) { if((LightSprites[iSprite].iX < WORLD_COLS) && (LightSprites[iSprite].iY < WORLD_ROWS)) { LightErase(LightSprites[iSprite].uiLightType, LightSprites[iSprite].iTemplate, LightSprites[iSprite].iX, LightSprites[iSprite].iY, iSprite); LightSpriteDirty(iSprite); } } //LightSprites[iSprite].iOldX=LightSprites[iSprite].iX; //LightSprites[iSprite].iOldY=LightSprites[iSprite].iY; LightSprites[iSprite].iX=iX; LightSprites[iSprite].iY=iY; if(LightSprites[iSprite].uiFlags&LIGHT_SPR_ON) { if((LightSprites[iSprite].iX < WORLD_COLS) && (LightSprites[iSprite].iY < WORLD_ROWS)) { LightDraw(LightSprites[iSprite].uiLightType, LightSprites[iSprite].iTemplate, iX, iY, iSprite); LightSprites[iSprite].uiFlags|=LIGHT_SPR_ERASE; LightSpriteDirty(iSprite); } } } else return(FALSE); return(TRUE); } /******************************************************************************** * LightSpriteRoofStatus * * Determines whether a light is on a roof or not. * ********************************************************************************/ BOOLEAN LightSpriteRoofStatus(INT32 iSprite, BOOLEAN fOnRoof) { if ( (iSprite >= MAX_LIGHT_SPRITES ) || (iSprite < 0) ) { return FALSE; } if(fOnRoof && (LightSprites[iSprite].uiFlags&LIGHT_SPR_ONROOF)) return(FALSE); if(!fOnRoof && !(LightSprites[iSprite].uiFlags&LIGHT_SPR_ONROOF)) return(FALSE); if(LightSprites[iSprite].uiFlags&LIGHT_SPR_ACTIVE) { if(LightSprites[iSprite].uiFlags&LIGHT_SPR_ERASE) { if((LightSprites[iSprite].iX < WORLD_COLS) && (LightSprites[iSprite].iY < WORLD_ROWS)) { LightErase(LightSprites[iSprite].uiLightType, LightSprites[iSprite].iTemplate, LightSprites[iSprite].iX, LightSprites[iSprite].iY, iSprite); LightSpriteDirty(iSprite); } } if(fOnRoof) LightSprites[iSprite].uiFlags|=LIGHT_SPR_ONROOF; else LightSprites[iSprite].uiFlags&=(~LIGHT_SPR_ONROOF); if(LightSprites[iSprite].uiFlags&LIGHT_SPR_ON) { if((LightSprites[iSprite].iX < WORLD_COLS) && (LightSprites[iSprite].iY < WORLD_ROWS)) { LightDraw(LightSprites[iSprite].uiLightType, LightSprites[iSprite].iTemplate, LightSprites[iSprite].iX, LightSprites[iSprite].iY, iSprite); LightSprites[iSprite].uiFlags|=LIGHT_SPR_ERASE; LightSpriteDirty(iSprite); } } } else return(FALSE); return(TRUE); } /******************************************************************************** * LightSpritePower * * Turns on or off a light, based on the BOOLEAN. * ********************************************************************************/ BOOLEAN LightSpritePower(INT32 iSprite, BOOLEAN fOn) { if ( (iSprite >= MAX_LIGHT_SPRITES ) || (iSprite < 0) ) { return FALSE; } if(fOn) { //LightSprites[iSprite].uiFlags|=(LIGHT_SPR_ON|LIGHT_SPR_REDRAW); LightSprites[iSprite].uiFlags|=(LIGHT_SPR_ON); //LightSprites[iSprite].uiFlags&=(~LIGHT_SPR_ERASE); LightSprites[iSprite].iOldX=WORLD_COLS; } else { LightSprites[iSprite].uiFlags&=(~LIGHT_SPR_ON); //LightSprites[iSprite].uiFlags&=(~LIGHT_SPR_ERASE); LightSprites[iSprite].uiFlags|=(LIGHT_SPR_ERASE); } if(LightSprites[iSprite].uiFlags&LIGHT_SPR_ACTIVE) { if(LightSprites[iSprite].uiFlags&LIGHT_SPR_ERASE) { if((LightSprites[iSprite].iX < WORLD_COLS) && (LightSprites[iSprite].iY < WORLD_ROWS)) { LightErase(LightSprites[iSprite].uiLightType, LightSprites[iSprite].iTemplate, LightSprites[iSprite].iX, LightSprites[iSprite].iY, iSprite); LightSpriteDirty(iSprite); } } if(LightSprites[iSprite].uiFlags&LIGHT_SPR_ON) { if((LightSprites[iSprite].iX < WORLD_COLS) && (LightSprites[iSprite].iY < WORLD_ROWS)) { LightDraw(LightSprites[iSprite].uiLightType, LightSprites[iSprite].iTemplate, LightSprites[iSprite].iX, LightSprites[iSprite].iY, iSprite); LightSprites[iSprite].uiFlags|=LIGHT_SPR_ERASE; LightSpriteDirty(iSprite); } } } return(TRUE); } /******************************************************************************** * LightSpriteDirty * * Sets the flag for the renderer to draw all marked tiles. * ********************************************************************************/ BOOLEAN LightSpriteDirty(INT32 iSprite) { //INT16 iLeft_s, iTop_s; //INT16 iMapLeft, iMapTop, iMapRight, iMapBottom; //CellXYToScreenXY((INT16)(LightSprites[iSprite].iX*CELL_X_SIZE), // (INT16)(LightSprites[iSprite].iY*CELL_Y_SIZE), &iLeft_s, &iTop_s); //iLeft_s+=LightXOffset[LightSprites[iSprite].iTemplate]; //iTop_s+=LightYOffset[LightSprites[iSprite].iTemplate]; //iMapLeft=LightSprites[iSprite].iX+LightMapLeft[LightSprites[iSprite].iTemplate]; //iMapTop=LightSprites[iSprite].iY+LightMapTop[LightSprites[iSprite].iTemplate]; //iMapRight=LightSprites[iSprite].iX+LightMapRight[LightSprites[iSprite].iTemplate]; //iMapBottom=LightSprites[iSprite].iY+LightMapBottom[LightSprites[iSprite].iTemplate]; //ReRenderWorld(iMapLeft, iMapTop, iMapRight, iMapBottom); //UpdateSaveBuffer(); //AddBaseDirtyRect(gsVIEWPORT_START_X, gsVIEWPORT_START_Y, gsVIEWPORT_END_X, gsVIEWPORT_END_Y ); //AddBaseDirtyRect(iLeft_s, iTop_s, // (INT16)(iLeft_s+LightWidth[LightSprites[iSprite].iTemplate]), // (INT16)(iTop_s+LightHeight[LightSprites[iSprite].iTemplate])); SetRenderFlags(RENDER_FLAG_MARKED); return(TRUE); } BOOLEAN CreateObjectPalette(HVOBJECT pObj, UINT32 uiBase, SGPPaletteEntry *pShadePal) { UINT32 uiCount; pObj->pShades[uiBase]=Create16BPPPaletteShaded( pShadePal, gusShadeLevels[0][0], gusShadeLevels[0][1], gusShadeLevels[0][2], TRUE); for(uiCount=1; uiCount < 16; uiCount++) { pObj->pShades[uiBase+uiCount]=Create16BPPPaletteShaded( pShadePal, gusShadeLevels[uiCount][0], gusShadeLevels[uiCount][1], gusShadeLevels[uiCount][2], FALSE); } return(TRUE); } BOOLEAN CreateSoldierShadedPalette( SOLDIERTYPE *pSoldier, UINT32 uiBase, SGPPaletteEntry *pShadePal) { UINT32 uiCount; pSoldier->pShades[uiBase]=Create16BPPPaletteShaded( pShadePal, gusShadeLevels[0][0], gusShadeLevels[0][1], gusShadeLevels[0][2], TRUE); for(uiCount=1; uiCount < 16; uiCount++) { pSoldier->pShades[uiBase+uiCount]=Create16BPPPaletteShaded( pShadePal, gusShadeLevels[uiCount][0], gusShadeLevels[uiCount][1], gusShadeLevels[uiCount][2], FALSE); } return(TRUE); } /********************************************************************************************** CreateObjectPaletteTables Creates the shading tables for 8-bit brushes. One highlight table is created, based on the object-type, 3 brightening tables, 1 normal, and 11 darkening tables. The entries are created iteratively, rather than in a loop to allow hand-tweaking of the values. If you change the HVOBJECT_SHADE_TABLES symbol, remember to add/delete entries here, it won't adjust automagically. **********************************************************************************************/ #ifdef JA2TESTVERSION extern UINT32 uiNumTablesSaved; #endif UINT16 CreateTilePaletteTables(HVOBJECT pObj, UINT32 uiTileIndex, BOOLEAN fForce ) { UINT32 uiCount; SGPPaletteEntry LightPal[256]; BOOLEAN fLoaded = FALSE; Assert(pObj!=NULL); // create the basic shade table if( !gfForceBuildShadeTables && !fForce ) { //The overwhelming majority of maps use the neutral 0,0,0 light for outdoors. These shadetables //are extremely time consuming to generate, so we will attempt to load them. If we do, then //we skip the generation process altogether. if( LoadShadeTable( pObj, uiTileIndex ) ) { fLoaded = TRUE; } } if( !fLoaded ) { //This is expensive as hell to call! for(uiCount=0; uiCount < 256; uiCount++) { // combine the rgb of the light color with the object's palette LightPal[uiCount].peRed=(UINT8)(__min((UINT16)pObj->pPaletteEntry[uiCount].peRed+(UINT16)gpLightColors[0].peRed, 255)); LightPal[uiCount].peGreen=(UINT8)(__min((UINT16)pObj->pPaletteEntry[uiCount].peGreen+(UINT16)gpLightColors[0].peGreen, 255)); LightPal[uiCount].peBlue=(UINT8)(__min((UINT16)pObj->pPaletteEntry[uiCount].peBlue+(UINT16)gpLightColors[0].peBlue, 255)); } // build the shade tables CreateObjectPalette(pObj, 0, LightPal); //We paid to generate the shade table, so now save it, so we don't have to regenerate it ever //again! if( !gfForceBuildShadeTables && !gpLightColors[0].peRed && !gpLightColors[0].peGreen && !gpLightColors[0].peBlue ) { SaveShadeTable( pObj, uiTileIndex ); } #ifdef JA2TESTVERSION else uiNumTablesSaved++; #endif } // if two lights are active if(gubNumLightColors==2) { // build the second light's palette and table for(uiCount=0; uiCount < 256; uiCount++) { LightPal[uiCount].peRed=(UINT8)(__min((UINT16)pObj->pPaletteEntry[uiCount].peRed+(UINT16)gpLightColors[1].peRed, 255)); LightPal[uiCount].peGreen=(UINT8)(__min((UINT16)pObj->pPaletteEntry[uiCount].peGreen+(UINT16)gpLightColors[1].peGreen, 255)); LightPal[uiCount].peBlue=(UINT8)(__min((UINT16)pObj->pPaletteEntry[uiCount].peBlue+(UINT16)gpLightColors[1].peBlue, 255)); } CreateObjectPalette(pObj, 16, LightPal); // build a table that is a mix of the first two for(uiCount=0; uiCount < 256; uiCount++) { LightPal[uiCount].peRed=(UINT8)(__min((UINT16)pObj->pPaletteEntry[uiCount].peRed+(UINT16)gpLightColors[2].peRed, 255)); LightPal[uiCount].peGreen=(UINT8)(__min((UINT16)pObj->pPaletteEntry[uiCount].peGreen+(UINT16)gpLightColors[2].peGreen, 255)); LightPal[uiCount].peBlue=(UINT8)(__min((UINT16)pObj->pPaletteEntry[uiCount].peBlue+(UINT16)gpLightColors[2].peBlue, 255)); } CreateObjectPalette(pObj, 32, LightPal); } // build neutral palette as well! // Set current shade table to neutral color pObj->pShadeCurrent=pObj->pShades[4]; pObj->pGlow=pObj->pShades[0]; return(TRUE); } UINT16 CreateSoldierPaletteTables(SOLDIERTYPE *pSoldier, UINT32 uiType) { SGPPaletteEntry LightPal[256]; UINT32 uiCount; // create the basic shade table for(uiCount=0; uiCount < 256; uiCount++) { // combine the rgb of the light color with the object's palette LightPal[uiCount].peRed=(UINT8)(__min((UINT16)pSoldier->p8BPPPalette[uiCount].peRed+(UINT16)gpLightColors[0].peRed, 255)); LightPal[uiCount].peGreen=(UINT8)(__min((UINT16)pSoldier->p8BPPPalette[uiCount].peGreen+(UINT16)gpLightColors[0].peGreen, 255)); LightPal[uiCount].peBlue=(UINT8)(__min((UINT16)pSoldier->p8BPPPalette[uiCount].peBlue+(UINT16)gpLightColors[0].peBlue, 255)); } // build the shade tables CreateSoldierShadedPalette(pSoldier, 0, LightPal); // if two lights are active if(gubNumLightColors==2) { // build the second light's palette and table for(uiCount=0; uiCount < 256; uiCount++) { LightPal[uiCount].peRed=(UINT8)(__min((UINT16)pSoldier->p8BPPPalette[uiCount].peRed+(UINT16)gpLightColors[1].peRed, 255)); LightPal[uiCount].peGreen=(UINT8)(__min((UINT16)pSoldier->p8BPPPalette[uiCount].peGreen+(UINT16)gpLightColors[1].peGreen, 255)); LightPal[uiCount].peBlue=(UINT8)(__min((UINT16)pSoldier->p8BPPPalette[uiCount].peBlue+(UINT16)gpLightColors[1].peBlue, 255)); } CreateSoldierShadedPalette(pSoldier, 16, LightPal); // build a table that is a mix of the first two for(uiCount=0; uiCount < 256; uiCount++) { LightPal[uiCount].peRed=(UINT8)(__min((UINT16)pSoldier->p8BPPPalette[uiCount].peRed+(UINT16)gpLightColors[2].peRed, 255)); LightPal[uiCount].peGreen=(UINT8)(__min((UINT16)pSoldier->p8BPPPalette[uiCount].peGreen+(UINT16)gpLightColors[2].peGreen, 255)); LightPal[uiCount].peBlue=(UINT8)(__min((UINT16)pSoldier->p8BPPPalette[uiCount].peBlue+(UINT16)gpLightColors[2].peBlue, 255)); } CreateSoldierShadedPalette(pSoldier, 32, LightPal); } // build neutral palette as well! // Set current shade table to neutral color pSoldier->pCurrentShade=pSoldier->pShades[4]; //pSoldier->pGlow=pSoldier->pShades[0]; return(TRUE); }