From ff55845733c2459048cb9faf6f4724984fd93cc9 Mon Sep 17 00:00:00 2001 From: CptMoore <39010654+CptMoore@users.noreply.github.com> Date: Mon, 16 Jan 2023 00:00:22 +0100 Subject: [PATCH 1/2] Flashlight changes: - Fixed the lighted area being way too large in the diagonal directions. - Added a minimum range to simulate a flashlight user not illuminating its feet. - Added angle based calculations instead of the hardcoded one. --- Tactical/Soldier Control.cpp | 231 +++++++++++++++++++++++---------- Tactical/Soldier Control.h | 3 +- TileEngine/Isometric Utils.cpp | 13 ++ TileEngine/Isometric Utils.h | 3 + 4 files changed, 178 insertions(+), 72 deletions(-) diff --git a/Tactical/Soldier Control.cpp b/Tactical/Soldier Control.cpp index 7439c1bdf..cb5d78e6e 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 ); - } + if ( AddBestFlashLight() ) + { + // take note: we own a light source + this->usSoldierFlagMask |= SOLDIER_LIGHT_OWNER; - INT32 sideGridNo2 = NewGridNo( nextGridNoinSight, DirectionInc( sidedir2 ) ); - sideGridNo2 = NewGridNo( sideGridNo2, DirectionInc( sidedir2 ) ); - - 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,159 @@ 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 + { + position_2d(INT32 gridNo) + { + ConvertGridNoToXY(gridNo, &x, &y); + } + position_2d(INT16 _x, INT16 _y) + { + x = _x; + y = _y; + } + INT16 x, y; + }; + struct vector_2d + { + vector_2d(INT8 direction) + { + ConvertDirectionToVectorInXY(direction, &dx, &dy); + length = sqrt(pow(dx, 2) + pow(dy, 2)); + } + vector_2d(position_2d from, position_2d to) + { + dx = to.x - from.x; + dy = to.y - from.y; + length = sqrt(pow(dx, 2) + pow(dy, 2)); + } + vector_2d(INT16 _dx, INT16 _dy) + { + dx = _dx; + dy = _dy; + length = sqrt(pow(dx, 2) + pow(dy, 2)); + } + INT16 dx, dy; + float length; + + float GetAngle( vector_2d other ) + { + auto dot = dx * other.dx + dy * other.dy; + return acos(dot / (length * other.length)); + } + }; + + 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..d2af0f136 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,14 @@ INT16 sDeltaScreenX, sDeltaScreenY; } +INT16 DirectionToVectorX[] = {0, 1, 1, 1, 0, -1, -1, -1, 0}; +INT16 DirectionToVectorY[] = {-1, -1, 0, 1, 1, 1, 0, -1, 0}; +void ConvertDirectionToVectorInXY( UINT8 ubDirection, INT16* sXDir, INT16* sYDir ) +{ + *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 ); From 65831218f70123e5d7459eea7a8527379b4627ff Mon Sep 17 00:00:00 2001 From: CptMoore <39010654+CptMoore@users.noreply.github.com> Date: Tue, 17 Jan 2023 22:38:30 +0100 Subject: [PATCH 2/2] Flashlight changes, MR feedback. --- Tactical/Soldier Control.cpp | 31 +++++++++++++++++-------------- TileEngine/Isometric Utils.cpp | 5 +++-- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Tactical/Soldier Control.cpp b/Tactical/Soldier Control.cpp index cb5d78e6e..0c1441a59 100644 --- a/Tactical/Soldier Control.cpp +++ b/Tactical/Soldier Control.cpp @@ -17775,44 +17775,47 @@ bool SOLDIERTYPE::AddBestFlashLight() struct position_2d { + INT16 x, y; + position_2d(INT32 gridNo) { ConvertGridNoToXY(gridNo, &x, &y); } - position_2d(INT16 _x, INT16 _y) - { - x = _x; - y = _y; - } - INT16 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 = sqrt(pow(dx, 2) + pow(dy, 2)); + length = CalcLength(dx, dy); } vector_2d(position_2d from, position_2d to) { dx = to.x - from.x; dy = to.y - from.y; - length = sqrt(pow(dx, 2) + pow(dy, 2)); + length = CalcLength(dx, dy); } - vector_2d(INT16 _dx, INT16 _dy) + vector_2d(INT16 _dx, INT16 _dy) : dx{_dx}, dy{_dy} { - dx = _dx; - dy = _dy; - length = sqrt(pow(dx, 2) + pow(dy, 2)); + length = CalcLength(dx, dy); } - INT16 dx, dy; - float length; 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); diff --git a/TileEngine/Isometric Utils.cpp b/TileEngine/Isometric Utils.cpp index d2af0f136..0f59940eb 100644 --- a/TileEngine/Isometric Utils.cpp +++ b/TileEngine/Isometric Utils.cpp @@ -917,10 +917,11 @@ INT16 sDeltaScreenX, sDeltaScreenY; } -INT16 DirectionToVectorX[] = {0, 1, 1, 1, 0, -1, -1, -1, 0}; -INT16 DirectionToVectorY[] = {-1, -1, 0, 1, 1, 1, 0, -1, 0}; +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]; }