diff --git a/Tactical/Soldier Control.cpp b/Tactical/Soldier Control.cpp index 7439c1bdf..0c1441a59 100644 --- a/Tactical/Soldier Control.cpp +++ b/Tactical/Soldier Control.cpp @@ -13,6 +13,7 @@ #include "Animation Data.h" #include "Animation Control.h" #include "container.h" +#define _USE_MATH_DEFINES // for C #include #include "pathai.h" #include "Random.h" @@ -17688,78 +17689,13 @@ void SOLDIERTYPE::HandleFlashLights( ) fLightChanged = TRUE; } - // not possible to get this bonus on a roof, due to our lighting system - if ( !this->pathing.bLevel ) - { - UINT8 flashlightrange = this->GetBestEquippedFlashLightRange( ); - - // if no flashlight is found, this will be 0 - if ( flashlightrange ) - { - // the range at which we create additional light sources to the side - UINT8 firstexpand = 8; - UINT8 secondexpand = 12; - - // depending on our direction, alter range - if ( this->ubDirection == NORTHEAST || this->ubDirection == NORTHWEST || this->ubDirection == SOUTHEAST || this->ubDirection == SOUTHWEST ) - { - flashlightrange = sqrt( (FLOAT)flashlightrange*(FLOAT)flashlightrange / 2.0f ); - firstexpand = sqrt( (FLOAT)firstexpand*(FLOAT)firstexpand / 2.0f ); - secondexpand = sqrt( (FLOAT)secondexpand*(FLOAT)secondexpand / 2.0f ); - } - - // we determine the height of the next tile in our direction. Because of the way structures are handled, we sometimes have to take the very tile we're occupying right now - INT32 nextGridNoinSight = this->sGridNo; - - for ( UINT8 i = 0; i < flashlightrange; ++i ) - { - nextGridNoinSight = NewGridNo( nextGridNoinSight, DirectionInc( this->ubDirection ) ); - - if ( SoldierToVirtualSoldierLineOfSightTest( this, nextGridNoinSight, this->pathing.bLevel, gAnimControl[this->usAnimState].ubEndHeight, FALSE ) ) - CreatePersonalLight( nextGridNoinSight, this->ubID ); - - // after a certain range, add new lights to the side to simulate a light cone - if ( i > firstexpand ) - { - INT8 sidedir1 = (this->ubDirection + 2) % NUM_WORLD_DIRECTIONS; - INT8 sidedir2 = (this->ubDirection - 2) % NUM_WORLD_DIRECTIONS; - - INT32 sideGridNo1 = NewGridNo( nextGridNoinSight, DirectionInc( sidedir1 ) ); - sideGridNo1 = NewGridNo( sideGridNo1, DirectionInc( sidedir1 ) ); - - if ( SoldierToVirtualSoldierLineOfSightTest( this, sideGridNo1, this->pathing.bLevel, gAnimControl[this->usAnimState].ubEndHeight, FALSE, NO_DISTANCE_LIMIT ) ) - CreatePersonalLight( sideGridNo1, this->ubID ); - - if ( i > secondexpand ) - { - sideGridNo1 = NewGridNo( sideGridNo1, DirectionInc( sidedir1 ) ); - - if ( SoldierToVirtualSoldierLineOfSightTest( this, sideGridNo1, this->pathing.bLevel, gAnimControl[this->usAnimState].ubEndHeight, FALSE, NO_DISTANCE_LIMIT ) ) - CreatePersonalLight( sideGridNo1, this->ubID ); - } - - INT32 sideGridNo2 = NewGridNo( nextGridNoinSight, DirectionInc( sidedir2 ) ); - sideGridNo2 = NewGridNo( sideGridNo2, DirectionInc( sidedir2 ) ); + if ( AddBestFlashLight() ) + { + // take note: we own a light source + this->usSoldierFlagMask |= SOLDIER_LIGHT_OWNER; - if ( SoldierToVirtualSoldierLineOfSightTest( this, sideGridNo2, this->pathing.bLevel, gAnimControl[this->usAnimState].ubEndHeight, FALSE, NO_DISTANCE_LIMIT ) ) - CreatePersonalLight( sideGridNo2, this->ubID ); - - if ( i > secondexpand ) - { - sideGridNo2 = NewGridNo( sideGridNo2, DirectionInc( sidedir2 ) ); - - if ( SoldierToVirtualSoldierLineOfSightTest( this, sideGridNo2, this->pathing.bLevel, gAnimControl[this->usAnimState].ubEndHeight, FALSE, NO_DISTANCE_LIMIT ) ) - CreatePersonalLight( sideGridNo2, this->ubID ); - } - } - } - - // take note: we own a light source - this->usSoldierFlagMask |= SOLDIER_LIGHT_OWNER; - - fLightChanged = TRUE; - } - } + fLightChanged = TRUE; + } if ( fLightChanged ) { @@ -17805,6 +17741,162 @@ UINT8 SOLDIERTYPE::GetBestEquippedFlashLightRange( ) return(bestrange); } +bool SOLDIERTYPE::AddBestFlashLight() +{ + // not possible to get this bonus on a roof, due to our lighting system + if ( this->pathing.bLevel != 0 ) + { + return false; + } + + UINT8 maxRange = this->GetBestEquippedFlashLightRange(); + if ( maxRange < 1 ) + { + return false; + } + + // we don't use the flashlight to run better at night (light up our shoes), we use it to find enemies! + UINT8 minRange = 4; + if ( minRange > maxRange ) + { + minRange = maxRange; + } + + float maxAngle = 45; + maxAngle *= PI / 180 / 2; // convert to rad and halven + + auto forward = DirectionInc(this->ubDirection); + auto left = DirectionInc(DirectionIfTurnedClockwise(this->ubDirection, 6)); + auto leftLeft = DirectionInc(DirectionIfTurnedClockwise(this->ubDirection, 5)); + auto right = DirectionInc(DirectionIfTurnedClockwise(this->ubDirection, 2)); + auto rightRight = DirectionInc(DirectionIfTurnedClockwise(this->ubDirection, 3)); + + bool isDiagonal = this->ubDirection == NORTHEAST || this->ubDirection == NORTHWEST || this->ubDirection == SOUTHEAST || this->ubDirection == SOUTHWEST; + + struct position_2d + { + INT16 x, y; + + position_2d(INT32 gridNo) + { + ConvertGridNoToXY(gridNo, &x, &y); + } + position_2d(INT16 _x, INT16 _y) : x{_x}, y{_y} + { + } + }; + struct vector_2d + { + INT16 dx, dy; + float length; + + vector_2d(INT8 direction) + { + ConvertDirectionToVectorInXY(direction, &dx, &dy); + length = CalcLength(dx, dy); + } + vector_2d(position_2d from, position_2d to) + { + dx = to.x - from.x; + dy = to.y - from.y; + length = CalcLength(dx, dy); + } + vector_2d(INT16 _dx, INT16 _dy) : dx{_dx}, dy{_dy} + { + length = CalcLength(dx, dy); + } + + float GetAngle( vector_2d other ) + { + auto dot = dx * other.dx + dy * other.dy; + return acos(dot / (length * other.length)); + } + + static float CalcLength(float dx, float dy) + { + return sqrt(powf(dx, 2) + powf(dy, 2)); + } + }; + + position_2d soldierPos(this->sGridNo); + vector_2d soldierDir(this->ubDirection); + + auto is_in_area = [&](INT32 sGridNoToTest) -> bool + { + vector_2d v(soldierPos, position_2d(sGridNoToTest)); + + if (v.length > maxRange) + { + return false; + } + + if (v.length < minRange) + { + return false; + } + + auto coneAngle = soldierDir.GetAngle( v ); + if (coneAngle > maxAngle) + { + return false; + } + + return true; + }; + + auto add_light_if_in_line_of_sight = [&, this]( INT32 sGridNoToTest, bool allowSkip ) -> void + { + if (allowSkip) // improve performance by skipping 3/4 of the lights + { + INT16 sXPos, sYPos; + ConvertGridNoToXY( sGridNoToTest, &sXPos, &sYPos ); + if (!(sXPos % 2 == 0 && sYPos % 2 == 0)) + { + return; + } + } + + if ( SoldierToVirtualSoldierLineOfSightTest( this, sGridNoToTest, this->pathing.bLevel, gAnimControl[this->usAnimState].ubEndHeight, false, NO_DISTANCE_LIMIT ) ) + { + CreatePersonalLight( sGridNoToTest, this->ubID ); + } + }; + + auto travel_direction_to_add_light = [&]( INT32 startingGridNo, INT16 directionIncrementer ) + { + for ( auto currentGridNo = startingGridNo; !OutOfBounds( currentGridNo, -1 ) && is_in_area( currentGridNo ); currentGridNo += directionIncrementer ) + { + add_light_if_in_line_of_sight( currentGridNo, true); + } + }; + + for ( auto currentGridNo = this->sGridNo; !OutOfBounds( currentGridNo, -1 ); currentGridNo += forward ) + { + vector_2d v(soldierPos, position_2d(currentGridNo)); + if ( v.length < minRange ) + { + continue; + } + else if (v.length > maxRange) + { + break; + } + + add_light_if_in_line_of_sight( currentGridNo, false ); + + travel_direction_to_add_light( currentGridNo, left ); + travel_direction_to_add_light( currentGridNo, right ); + + if ( isDiagonal ) + { + travel_direction_to_add_light( NewGridNo( currentGridNo, leftLeft ), left ); + travel_direction_to_add_light( NewGridNo( currentGridNo, rightRight ), right ); + } + } + + return true; +} + // Flugente: soldier profiles // retrieves the correct sub-array INT8 SOLDIERTYPE::GetSoldierProfileType( UINT8 usTeam ) diff --git a/Tactical/Soldier Control.h b/Tactical/Soldier Control.h index eff711d09..12945d309 100644 --- a/Tactical/Soldier Control.h +++ b/Tactical/Soldier Control.h @@ -1935,7 +1935,8 @@ class SOLDIERTYPE//last edited at version 102 //void AddDrugValues(UINT8 uDrugType, UINT8 usEffect, UINT8 usTravelRate, UINT8 usSideEffect ); void HandleFlashLights(); - UINT8 GetBestEquippedFlashLightRange(); + bool AddBestFlashLight(); + UINT8 GetBestEquippedFlashLightRange(); // Flugente: soldier profiles INT8 GetSoldierProfileType(UINT8 usTeam); // retrieves the correct sub-array diff --git a/TileEngine/Isometric Utils.cpp b/TileEngine/Isometric Utils.cpp index 1315070b9..0f59940eb 100644 --- a/TileEngine/Isometric Utils.cpp +++ b/TileEngine/Isometric Utils.cpp @@ -91,6 +91,11 @@ UINT8 gOneCCDirection[ NUM_WORLD_DIRECTIONS ] = WEST }; +UINT8 DirectionIfTurnedClockwise(UINT8 facing, UINT8 times) +{ + return (facing + times) % NUM_WORLD_DIRECTIONS; +} + // DIRECTION FACING DIRECTION WE WANT TO GOTO UINT8 gPurpendicularDirection[ NUM_WORLD_DIRECTIONS ][ NUM_WORLD_DIRECTIONS ] = { @@ -912,6 +917,15 @@ INT16 sDeltaScreenX, sDeltaScreenY; } +INT16 DirectionToVectorX[NUM_WORLD_DIRECTIONS] = {0, 1, 1, 1, 0, -1, -1, -1}; +INT16 DirectionToVectorY[NUM_WORLD_DIRECTIONS] = {-1, -1, 0, 1, 1, 1, 0, -1}; +void ConvertDirectionToVectorInXY( UINT8 ubDirection, INT16* sXDir, INT16* sYDir ) +{ + Assert(ubDirection >= 0 && ubDirection < NUM_WORLD_DIRECTIONS); + *sXDir = DirectionToVectorX[ubDirection]; + *sYDir = DirectionToVectorY[ubDirection]; +} + void ConvertGridNoToXY( INT32 sGridNo, INT16 *sXPos, INT16 *sYPos ) { *sYPos = sGridNo / WORLD_COLS; diff --git a/TileEngine/Isometric Utils.h b/TileEngine/Isometric Utils.h index eaa5f2b38..7da2f67e4 100644 --- a/TileEngine/Isometric Utils.h +++ b/TileEngine/Isometric Utils.h @@ -30,6 +30,8 @@ extern UINT8 gTwoCDirection[ NUM_WORLD_DIRECTIONS ]; extern UINT8 gOneCDirection[ NUM_WORLD_DIRECTIONS ]; extern UINT8 gOneCCDirection[ NUM_WORLD_DIRECTIONS ]; +UINT8 DirectionIfTurnedClockwise(UINT8 facing, UINT8 times); + extern UINT8 gPurpendicularDirection[ NUM_WORLD_DIRECTIONS ][ NUM_WORLD_DIRECTIONS ]; // Macros @@ -40,6 +42,7 @@ extern UINT8 gPurpendicularDirection[ NUM_WORLD_DIRECTIONS ][ NUM_WORLD_DIRECTIO #define GETWORLDINDEXFROMWORLDCOORDS( y, x ) ( (INT32) ( x / CELL_X_SIZE ) ) + WORLD_COLS * ( (INT32) ( y / CELL_Y_SIZE ) ) +void ConvertDirectionToVectorInXY(UINT8 ubDirection, INT16* sXDir, INT16* sYDir); void ConvertGridNoToXY( INT32 sGridNo, INT16 *sXPos, INT16 *sYPos ); void ConvertGridNoToCellXY( INT32 sGridNo, INT16 *sXPos, INT16 *sYPos ); void ConvertGridNoToCenterCellXY( INT32 sGridNo, INT16 *sXPos, INT16 *sYPos );