# pragma region License
/*
License ( OLC - 3 )
~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
Copyright 2024 Joshua Sigona < sigonasr2 @ gmail . com >
Redistribution and use in source and binary forms , with or without modification ,
are permitted provided that the following conditions are met :
1. Redistributions or derivations of source code must retain the above copyright
notice , this list of conditions and the following disclaimer .
2. Redistributions or derivative works in binary form must reproduce the above
copyright notice . This list of conditions and the following disclaimer must be
reproduced in the documentation and / or other materials provided with the distribution .
3. Neither the name of the copyright holder nor the names of its contributors may
be used to endorse or promote products derived from this software without specific
prior written permission .
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS " AND ANY
EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED . IN NO EVENT
SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT , INDIRECT ,
INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL DAMAGES ( INCLUDING , BUT NOT LIMITED
TO , PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR
BUSINESS INTERRUPTION ) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN
CONTRACT , STRICT LIABILITY , OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE .
Portions of this software are copyright © 2024 The FreeType
Project ( www . freetype . org ) . Please see LICENSE_FT . txt for more information .
All rights reserved .
*/
# pragma endregion
# include "AdventuresInLestoria.h"
# include "DEFINES.h"
# include "Menu.h"
# include "Unlock.h"
# include "ConnectionPoint.h"
# include "util.h"
# include "drawutil.h"
# include "MenuLabel.h"
# include "EncountersSpawnListScrollableWindowComponent.h"
# include "VisualNovel.h"
# include "State_OverworldMap.h"
# include "SaveFile.h"
# include "Audio.h"
INCLUDE_MONSTER_LIST
INCLUDE_game
INCLUDE_GFX
std : : vector < ConnectionPoint > State_OverworldMap : : connections ;
ConnectionPoint * State_OverworldMap : : currentConnectionPoint = nullptr ;
State_OverworldMap : : State_OverworldMap ( ) { }
void State_OverworldMap : : OnStateChange ( GameState * prevState ) {
Component < MenuComponent > ( MenuType : : PAUSE , " Return to Camp Button " ) - > SetGrayedOut ( false ) ;
SaveFile : : SaveGame ( ) ;
game - > ResetCompletedStageFlag ( ) ;
if ( Unlock : : IsUnlocked ( " STORY_1_3 " ) ) {
game - > SetChapter ( 2 ) ;
}
game - > LoadLevel ( " WORLD_MAP " ) ;
} ;
void State_OverworldMap : : OnLevelLoad ( ) {
if ( Menu : : IsMenuOpen ( ) ) {
Menu : : CloseAllMenus ( ) ;
}
game - > GetPlayer ( ) - > ForceSetPos ( currentConnectionPoint - > rect . pos + currentConnectionPoint - > rect . size / 2 + vf2d { 0 , 16 } ) ;
playerTargetPos = currentConnectionPoint - > rect . pos + currentConnectionPoint - > rect . size / 2 + vf2d { 0 , 16 } ;
game - > GetPlayer ( ) - > UpdateWalkingAnimation ( DOWN ) ;
game - > GetPlayer ( ) - > SetState ( State : : FORCE_WALK ) ;
game - > GetPlayer ( ) - > SetSizeMult ( 1 ) ;
game - > camera . MoveCamera ( currentConnectionPoint - > rect . middle ( ) + vf2d { game - > GetScreenSize ( ) . x / 6.0f , 0 } ) ;
Component < MenuLabel > ( OVERWORLD_LEVEL_SELECT , " Chapter Label " ) - > SetLabel ( " Chapter " + std : : to_string ( game - > GetCurrentChapter ( ) ) ) ;
Component < MenuLabel > ( OVERWORLD_LEVEL_SELECT , " Stage Label " ) - > SetLabel ( currentConnectionPoint - > name ) ;
Component < EncountersSpawnListScrollableWindowComponent > ( OVERWORLD_LEVEL_SELECT , " Spawns List " ) - > UpdateSpawns ( currentConnectionPoint - > spawns ) ;
if ( currentConnectionPoint - > levelDataExists ) {
Component < MenuComponent > ( OVERWORLD_LEVEL_SELECT , " Enter Button " ) - > Enable ( ) ;
} else {
Component < MenuComponent > ( OVERWORLD_LEVEL_SELECT , " Enter Button " ) - > Disable ( ) ;
}
Menu : : OpenMenu ( OVERWORLD_LEVEL_SELECT , false ) ;
game - > UpdateDiscordStatus ( " Overworld Map " , game - > GetPlayer ( ) - > GetClassName ( ) ) ;
}
void State_OverworldMap : : OnUserUpdate ( AiL * game ) {
if ( Menu : : stack . size ( ) > 1 ) return ;
game - > camera . SetTarget ( currentConnectionPoint - > rect . middle ( ) + vf2d { game - > GetScreenSize ( ) . x / 6.0f , 0 } ) ;
game - > UpdateCamera ( game - > GetElapsedTime ( ) ) ;
game - > GetPlayer ( ) - > Update ( game - > GetElapsedTime ( ) ) ;
if ( game - > GetPlayer ( ) - > GetPos ( ) ! = playerTargetPos ) {
if ( geom2d : : line < float > ( game - > GetPlayer ( ) - > GetPos ( ) , playerTargetPos ) . length ( ) < 2 ) {
game - > GetPlayer ( ) - > SetPos ( playerTargetPos ) ;
} else {
game - > GetPlayer ( ) - > SetPos ( game - > GetPlayer ( ) - > GetPos ( ) + util : : pointTo ( game - > GetPlayer ( ) - > GetPos ( ) , playerTargetPos ) * playerMoveSpd * game - > GetElapsedTime ( ) ) ;
}
}
# pragma region Handle Connection Point Clicking and Movement
for ( ConnectionPoint & cp : connections ) {
if ( game - > GetMouse ( Mouse : : LEFT ) . bPressed & & geom2d : : overlaps ( game - > GetWorldMousePos ( ) , cp . rect )
| | game - > KEY_LEFT . Pressed ( ) | | game - > KEY_RIGHT . Pressed ( ) | | game - > KEY_UP . Pressed ( ) | | game - > KEY_DOWN . Pressed ( )
| | ( ! analogMove & & ( abs ( game - > KEY_SCROLLHORZ_L . Analog ( ) ) > = 0.2f | | abs ( game - > KEY_SCROLLVERT_L . Analog ( ) ) > = 0.2f ) ) ) {
bool mouseUsed = game - > GetMouse ( Mouse : : LEFT ) . bPressed & & geom2d : : overlaps ( game - > GetWorldMousePos ( ) , cp . rect ) ;
for ( int directionInd = 0 ; int neighborInd : currentConnectionPoint - > neighbors ) {
int targetDirection = - 1 ;
if ( game - > KEY_LEFT . Pressed ( ) | | game - > KEY_SCROLLHORZ_L . Analog ( ) < = - 0.2f ) targetDirection = ConnectionPoint : : WEST ;
if ( game - > KEY_RIGHT . Pressed ( ) | | game - > KEY_SCROLLHORZ_L . Analog ( ) > = 0.2f ) targetDirection = ConnectionPoint : : EAST ;
if ( game - > KEY_UP . Pressed ( ) | | game - > KEY_SCROLLVERT_L . Analog ( ) < = - 0.2f ) targetDirection = ConnectionPoint : : NORTH ;
if ( game - > KEY_DOWN . Pressed ( ) | | game - > KEY_SCROLLVERT_L . Analog ( ) > = 0.2f ) targetDirection = ConnectionPoint : : SOUTH ;
if ( neighborInd = = - 1 ) {
directionInd + + ;
continue ;
}
ConnectionPoint & neighbor = ConnectionPointFromIndex ( neighborInd ) ;
if ( Unlock : : IsUnlocked ( neighbor . unlockCondition ) & & & cp = = & neighbor
& & ( mouseUsed | | targetDirection = = directionInd ) ) {
UpdateCurrentConnectionPoint ( neighbor ) ;
playerTargetPos = currentConnectionPoint - > rect . pos + currentConnectionPoint - > rect . size / 2 + vf2d { 0 , 16 } ;
float angleTo = util : : angleTo ( game - > GetPlayer ( ) - > GetPos ( ) , playerTargetPos ) ;
if ( angleTo > = - 3 * PI / 4 & & angleTo < - PI / 4 ) {
game - > GetPlayer ( ) - > UpdateWalkingAnimation ( UP ) ;
} else
if ( angleTo < PI / 4 & & angleTo > = - PI / 4 ) {
game - > GetPlayer ( ) - > UpdateWalkingAnimation ( RIGHT ) ;
} else
if ( angleTo > = PI / 4 & & angleTo < 3 * PI / 4 ) {
game - > GetPlayer ( ) - > UpdateWalkingAnimation ( DOWN ) ;
} else {
game - > GetPlayer ( ) - > UpdateWalkingAnimation ( LEFT ) ;
}
if ( abs ( game - > KEY_SCROLLHORZ_L . Analog ( ) ) > = 0.2f | | abs ( game - > KEY_SCROLLVERT_L . Analog ( ) > = 0.2f ) ) analogMove = true ;
goto doneNavigating ;
}
directionInd + + ;
}
}
}
# pragma endregion
doneNavigating :
if ( abs ( game - > KEY_SCROLLVERT_L . Analog ( ) ) < 0.2f & & abs ( game - > KEY_SCROLLHORZ_L . Analog ( ) ) < 0.2f ) {
analogMove = false ;
}
game - > ClearTimedOutGarbage ( ) ;
} ;
void State_OverworldMap : : Draw ( AiL * game ) {
game - > RenderWorld ( game - > GetElapsedTime ( ) ) ;
currentTime + = game - > GetElapsedTime ( ) ;
for ( ConnectionPoint & cp : connections ) {
if ( ! Unlock : : IsUnlocked ( cp ) ) {
game - > view . FillRectDecal ( cp . rect . pos , cp . rect . size , { 0 , 0 , 0 , 128 } ) ;
} else {
float exclamationScale = fmod ( game - > GetRunTime ( ) , 1.0f ) < 0.2f ? 1.f : 1.2f ;
if ( ! cp . Visited ( ) ) {
game - > view . DrawDecal ( cp . rect . pos + vf2d { - 1.f , 0.f } , GFX [ " exclamation.png " ] . Decal ( ) , { exclamationScale , exclamationScale } , BLACK ) ;
game - > view . DrawDecal ( cp . rect . pos + vf2d { - 1.f , - 1.f } , GFX [ " exclamation.png " ] . Decal ( ) , { exclamationScale , exclamationScale } ) ;
}
}
}
bool highlightedAStage = false ;
for ( ConnectionPoint & cp : connections ) {
if ( Unlock : : IsUnlocked ( cp ) & & geom2d : : overlaps ( game - > GetWorldMousePos ( ) , cp . rect ) & & ( & cp = = currentConnectionPoint | | cp . IsNeighbor ( * currentConnectionPoint ) ) ) {
drawutil : : DrawCrosshairDecalTransformedView ( game - > view , cp . rect , currentTime ) ;
highlightedAStage = true ;
break ;
}
}
if ( ! highlightedAStage ) drawutil : : DrawCrosshairDecalTransformedView ( game - > view , currentConnectionPoint - > rect , currentTime ) ; //Highlight the current stage instead then if we haven't moused over a new one.
//In radians.
using AngleTotal = float ;
using Count = uint8_t ;
using MedianAngle = std : : pair < AngleTotal , Count > ;
using ConnectionPointIndex = int ;
auto GetAngle = [ ] ( MedianAngle angle ) {
return angle . first / angle . second ;
} ;
std : : map < ConnectionPointIndex , MedianAngle > neighbors ;
for ( int direction = 0 ; int index : currentConnectionPoint - > neighbors ) {
if ( index ! = - 1 ) {
ConnectionPoint & neighbor = connections [ index ] ;
if ( ! Unlock : : IsUnlocked ( neighbor ) ) {
direction + + ;
continue ;
}
float arrowAngle = direction * 0.5f * PI - 0.5f * PI ;
if ( neighbors . count ( index ) ) {
if ( arrowAngle = = 0.f ) neighbors [ index ] . first + = 2 * PI ;
neighbors [ index ] . first + = circ_add ( 0 , 2 * PI + arrowAngle , 0 , 2 * PI ) ;
neighbors [ index ] . second + + ;
} else {
neighbors [ index ] = { circ_add ( 0 , arrowAngle , 0 , 2 * PI ) , 1 } ;
}
}
direction + + ;
}
float arrowDist = fmod ( game - > GetRunTime ( ) , 1.0f ) < 0.5f ? 14 : 18 ;
for ( auto & [ index , medianAngle ] : neighbors ) {
game - > view . DrawRotatedDecal ( game - > GetPlayer ( ) - > GetPos ( ) + vf2d { arrowDist , GetAngle ( medianAngle ) } . cart ( ) + vf2d { 0.f , 1.f } , GFX [ " overworld_arrow.png " ] . Decal ( ) , GetAngle ( medianAngle ) , GFX [ " overworld_arrow.png " ] . Sprite ( ) - > Size ( ) / 2 , { 1.f , 1.f } , { 0 , 0 , 0 } ) ;
game - > view . DrawRotatedDecal ( game - > GetPlayer ( ) - > GetPos ( ) + vf2d { arrowDist , GetAngle ( medianAngle ) } . cart ( ) , GFX [ " overworld_arrow.png " ] . Decal ( ) , GetAngle ( medianAngle ) , GFX [ " overworld_arrow.png " ] . Sprite ( ) - > Size ( ) / 2 , { 1.f , 1.f } , { 199 , 48 , 55 } ) ;
}
} ;
void State_OverworldMap : : DrawOverlay ( AiL * game ) { }
bool State_OverworldMap : : HasStageMarker ( std : : string connectionName ) {
for ( ConnectionPoint & connection : connections ) {
if ( connection . name = = connectionName ) {
return true ;
}
}
return false ;
}
void State_OverworldMap : : SetStageMarker ( std : : string connectionName ) {
for ( ConnectionPoint & connection : connections ) {
if ( connection . name = = connectionName ) {
currentConnectionPoint = & connection ;
return ;
}
}
ERR ( " WARNING! Could not find a connection point with name " < < connectionName < < " ! " ) ;
}
ConnectionPoint & State_OverworldMap : : ConnectionPointFromIndex ( int ind ) {
return connections . at ( ind ) ;
}
ConnectionPoint & State_OverworldMap : : GetCurrentConnectionPoint ( ) {
return * ( ( State_OverworldMap * ) ( GameState : : states . at ( States : : OVERWORLD_MAP ) ) ) - > currentConnectionPoint ;
}
void State_OverworldMap : : StartLevel ( ) {
game - > UpdateDiscordStatus ( State_OverworldMap : : GetCurrentConnectionPoint ( ) . name , game - > GetPlayer ( ) - > GetClassName ( ) ) ;
SaveFile : : SaveGame ( ) ;
State_OverworldMap : : GetCurrentConnectionPoint ( ) . SetVisited ( ) ;
if ( State_OverworldMap : : GetCurrentConnectionPoint ( ) . type . starts_with ( " STORY " ) ) {
VisualNovel : : LoadVisualNovel ( State_OverworldMap : : GetCurrentConnectionPoint ( ) . map ) ;
} else
if ( State_OverworldMap : : GetCurrentConnectionPoint ( ) . type . starts_with ( " HUB " ) ) {
GameState : : ChangeState ( States : : GAME_HUB , 0.3f , 10U ) ;
} else {
GameState : : ChangeState ( States : : GAME_RUN , 0.3f , 10U ) ;
}
}
void State_OverworldMap : : UpdateCurrentConnectionPoint ( const ConnectionPoint & connection ) {
currentConnectionPoint = const_cast < ConnectionPoint * > ( & connection ) ;
Component < MenuLabel > ( OVERWORLD_LEVEL_SELECT , " Stage Label " ) - > SetLabel ( currentConnectionPoint - > name ) ;
Component < EncountersSpawnListScrollableWindowComponent > ( OVERWORLD_LEVEL_SELECT , " Spawns List " ) - > UpdateSpawns ( currentConnectionPoint - > spawns ) ;
if ( currentConnectionPoint - > levelDataExists ) {
Component < MenuComponent > ( OVERWORLD_LEVEL_SELECT , " Enter Button " ) - > Enable ( ) ;
} else {
Component < MenuComponent > ( OVERWORLD_LEVEL_SELECT , " Enter Button " ) - > Disable ( ) ;
}
}
std : : optional < ConnectionPoint * > State_OverworldMap : : ConnectionPointFromString ( std : : string_view mapName ) {
auto foundItem = std : : find_if ( connections . begin ( ) , connections . end ( ) , [ & ] ( ConnectionPoint & cp ) { return cp . map = = mapName ; } ) ;
if ( foundItem ! = connections . end ( ) ) return & * foundItem ;
return { } ;
}
void State_OverworldMap : : ResetConnectionPoints ( ) {
for ( ConnectionPoint & cp : connections ) {
cp . visited = false ;
}
}