2023-10-19 16:19:09 +02:00
/*
2025-02-10 21:49:23 +00:00
* GameEngine . cpp , part of VCMI engine
2023-10-19 16:19:09 +02:00
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
# include "StdInc.h"
2025-02-10 21:49:23 +00:00
# include "GameEngine.h"
2025-07-17 20:42:27 +02:00
# include "GameLibrary.h"
2025-02-10 21:49:23 +00:00
# include "gui/CIntObject.h"
# include "gui/CursorHandler.h"
# include "gui/ShortcutHandler.h"
# include "gui/FramerateManager.h"
# include "gui/WindowHandler.h"
# include "gui/EventDispatcher.h"
# include "eventsSDL/InputHandler.h"
2025-02-10 22:08:50 +00:00
# include "media/CMusicHandler.h"
# include "media/CSoundHandler.h"
# include "media/CVideoHandler.h"
# include "media/CEmptyVideoPlayer.h"
2025-02-10 21:49:23 +00:00
# include "adventureMap/AdventureMapInterface.h"
# include "render/Canvas.h"
# include "render/Colors.h"
# include "render/IFont.h"
# include "render/EFont.h"
# include "renderSDL/ScreenHandler.h"
# include "renderSDL/RenderHandler.h"
2025-02-27 22:18:31 +00:00
# include "GameEngineUser.h"
2025-02-10 21:49:23 +00:00
# include "battle/BattleInterface.h"
2025-03-12 14:18:44 +00:00
# include "../lib/AsyncRunner.h"
2025-02-10 21:49:23 +00:00
# include "../lib/CConfigHandler.h"
2025-07-14 20:38:03 +02:00
# include "../lib/texts/TextOperations.h"
2025-07-17 20:42:27 +02:00
# include "../lib/texts/CGeneralTextHandler.h"
2023-10-19 16:19:09 +02:00
# include <SDL_render.h>
2025-02-10 21:49:23 +00:00
std : : unique_ptr < GameEngine > ENGINE ;
2023-10-19 16:19:09 +02:00
static thread_local bool inGuiThread = false ;
2024-08-09 15:30:04 +00:00
ObjectConstruction : : ObjectConstruction ( CIntObject * obj )
2023-10-19 16:19:09 +02:00
{
2025-02-10 21:49:23 +00:00
ENGINE - > createdObj . push_front ( obj ) ;
ENGINE - > captureChildren = true ;
2023-10-19 16:19:09 +02:00
}
2024-08-09 15:30:04 +00:00
ObjectConstruction : : ~ ObjectConstruction ( )
2023-10-19 16:19:09 +02:00
{
2025-02-10 21:49:23 +00:00
assert ( ! ENGINE - > createdObj . empty ( ) ) ;
ENGINE - > createdObj . pop_front ( ) ;
ENGINE - > captureChildren = ! ENGINE - > createdObj . empty ( ) ;
2023-10-19 16:19:09 +02:00
}
2025-03-12 13:32:22 +00:00
GameEngine : : GameEngine ( )
: captureChildren ( false )
, fakeStatusBar ( std : : make_shared < EmptyStatusBar > ( ) )
2023-10-19 16:19:09 +02:00
{
inGuiThread = true ;
eventDispatcherInstance = std : : make_unique < EventDispatcher > ( ) ;
windowHandlerInstance = std : : make_unique < WindowHandler > ( ) ;
screenHandlerInstance = std : : make_unique < ScreenHandler > ( ) ;
renderHandlerInstance = std : : make_unique < RenderHandler > ( ) ;
shortcutsHandlerInstance = std : : make_unique < ShortcutHandler > ( ) ;
2024-04-30 12:01:03 +03:00
inputHandlerInstance = std : : make_unique < InputHandler > ( ) ; // Must be after windowHandlerInstance and shortcutsHandlerInstance
2023-10-19 16:19:09 +02:00
framerateManagerInstance = std : : make_unique < FramerateManager > ( settings [ " video " ] [ " targetfps " ] . Integer ( ) ) ;
2025-02-10 22:08:50 +00:00
# ifndef ENABLE_VIDEO
videoPlayerInstance = std : : make_unique < CEmptyVideoPlayer > ( ) ;
# else
2025-02-10 22:52:48 +00:00
if ( settings [ " session " ] [ " disableVideo " ] . Bool ( ) )
2025-02-10 22:08:50 +00:00
videoPlayerInstance = std : : make_unique < CEmptyVideoPlayer > ( ) ;
2025-02-10 22:52:48 +00:00
else
videoPlayerInstance = std : : make_unique < CVideoPlayer > ( ) ;
2025-02-10 22:08:50 +00:00
# endif
2025-02-10 22:52:48 +00:00
soundPlayerInstance = std : : make_unique < CSoundHandler > ( ) ;
musicPlayerInstance = std : : make_unique < CMusicHandler > ( ) ;
2025-03-12 15:57:06 +00:00
sound ( ) . setVolume ( settings [ " general " ] [ " sound " ] . Integer ( ) ) ;
music ( ) . setVolume ( settings [ " general " ] [ " music " ] . Integer ( ) ) ;
2025-02-10 22:08:50 +00:00
cursorHandlerInstance = std : : make_unique < CursorHandler > ( ) ;
2025-03-12 14:18:44 +00:00
asyncTasks = std : : make_unique < AsyncRunner > ( ) ;
2023-10-19 16:19:09 +02:00
}
2025-02-10 21:49:23 +00:00
void GameEngine : : handleEvents ( )
2023-10-19 16:19:09 +02:00
{
events ( ) . dispatchTimer ( framerate ( ) . getElapsedMilliseconds ( ) ) ;
//player interface may want special event handling
2025-02-27 22:18:31 +00:00
if ( engineUser - > capturedAllEvents ( ) )
2023-10-19 16:19:09 +02:00
return ;
input ( ) . processEvents ( ) ;
}
2025-02-10 21:49:23 +00:00
void GameEngine : : fakeMouseMove ( )
2023-10-19 16:19:09 +02:00
{
dispatchMainThread ( [ ] ( ) {
2025-02-10 21:49:23 +00:00
ENGINE - > events ( ) . dispatchMouseMoved ( Point ( 0 , 0 ) , ENGINE - > getCursorPosition ( ) ) ;
2023-10-19 16:19:09 +02:00
} ) ;
}
2025-03-03 20:30:15 +00:00
[[noreturn]] void GameEngine : : mainLoop ( )
2023-10-19 16:19:09 +02:00
{
2025-03-03 20:30:15 +00:00
for ( ; ; )
2023-10-19 16:19:09 +02:00
{
2025-03-03 20:30:15 +00:00
input ( ) . fetchEvents ( ) ;
updateFrame ( ) ;
screenHandlerInstance - > presentScreenTexture ( ) ;
framerate ( ) . framerateDelay ( ) ; // holds a constant FPS
}
}
2023-10-19 16:19:09 +02:00
2025-03-03 20:30:15 +00:00
void GameEngine : : updateFrame ( )
{
std : : scoped_lock interfaceLock ( ENGINE - > interfaceMutex ) ;
2025-02-27 22:18:31 +00:00
2025-03-03 20:30:15 +00:00
engineUser - > onUpdate ( ) ;
2023-10-19 16:19:09 +02:00
2025-03-03 20:30:15 +00:00
handleEvents ( ) ;
windows ( ) . simpleRedraw ( ) ;
2023-11-18 19:41:00 +02:00
2025-07-17 20:42:27 +02:00
if ( settings [ " video " ] [ " performanceOverlay " ] [ " show " ] . Bool ( ) )
drawPerformanceOverlay ( ) ;
2023-10-19 16:19:09 +02:00
2025-03-03 20:30:15 +00:00
screenHandlerInstance - > updateScreenTexture ( ) ;
2023-10-19 16:19:09 +02:00
2025-03-03 20:30:15 +00:00
windows ( ) . onFrameRendered ( ) ;
ENGINE - > cursor ( ) . update ( ) ;
2023-10-19 16:19:09 +02:00
}
2025-02-10 21:49:23 +00:00
GameEngine : : ~ GameEngine ( )
2024-08-02 15:02:37 +00:00
{
// enforce deletion order on shutdown
// all UI elements including adventure map must be destroyed before Gui Handler
// proper solution would be removal of adventureInt global
adventureInt . reset ( ) ;
}
2023-10-19 16:19:09 +02:00
2025-02-10 21:49:23 +00:00
ShortcutHandler & GameEngine : : shortcuts ( )
2023-10-19 16:19:09 +02:00
{
assert ( shortcutsHandlerInstance ) ;
return * shortcutsHandlerInstance ;
}
2025-02-10 21:49:23 +00:00
FramerateManager & GameEngine : : framerate ( )
2023-10-19 16:19:09 +02:00
{
assert ( framerateManagerInstance ) ;
return * framerateManagerInstance ;
}
2025-02-10 21:49:23 +00:00
bool GameEngine : : isKeyboardCtrlDown ( ) const
2023-10-19 16:19:09 +02:00
{
return inputHandlerInstance - > isKeyboardCtrlDown ( ) ;
}
2025-02-10 21:49:23 +00:00
bool GameEngine : : isKeyboardCmdDown ( ) const
2024-05-12 17:09:31 +00:00
{
return inputHandlerInstance - > isKeyboardCmdDown ( ) ;
}
2025-02-10 21:49:23 +00:00
bool GameEngine : : isKeyboardAltDown ( ) const
2023-10-19 16:19:09 +02:00
{
return inputHandlerInstance - > isKeyboardAltDown ( ) ;
}
2025-02-10 21:49:23 +00:00
bool GameEngine : : isKeyboardShiftDown ( ) const
2023-10-19 16:19:09 +02:00
{
return inputHandlerInstance - > isKeyboardShiftDown ( ) ;
}
2025-02-10 21:49:23 +00:00
const Point & GameEngine : : getCursorPosition ( ) const
2023-10-19 16:19:09 +02:00
{
return inputHandlerInstance - > getCursorPosition ( ) ;
}
2025-02-10 21:49:23 +00:00
Point GameEngine : : screenDimensions ( ) const
2023-10-19 16:19:09 +02:00
{
2024-07-21 21:11:02 +00:00
return screenHandlerInstance - > getLogicalResolution ( ) ;
2023-10-19 16:19:09 +02:00
}
2025-07-17 20:42:27 +02:00
void GameEngine : : drawPerformanceOverlay ( )
2023-10-19 16:19:09 +02:00
{
2025-07-14 20:38:03 +02:00
auto font = EFonts : : FONT_SMALL ;
const auto & fontPtr = ENGINE - > renderHandler ( ) . loadFont ( font ) ;
2025-02-10 21:49:23 +00:00
Canvas target = screenHandler ( ) . getScreenCanvas ( ) ;
2025-07-14 20:38:03 +02:00
auto powerState = ENGINE - > input ( ) . getPowerState ( ) ;
std : : string powerSymbol = " " ; // add symbol if emoji are supported (e.g. VCMI extras)
2025-07-17 20:42:27 +02:00
if ( powerState . powerState = = PowerStateMode : : ON_BATTERY )
powerSymbol = fontPtr - > canRepresentCharacter ( " 🔋 " ) ? " 🔋 " : ( LIBRARY - > generaltexth - > translate ( " vcmi.overlay.battery " ) + " " ) ;
else if ( powerState . powerState = = PowerStateMode : : CHARGING )
powerSymbol = fontPtr - > canRepresentCharacter ( " 🔌 " ) ? " 🔌 " : ( LIBRARY - > generaltexth - > translate ( " vcmi.overlay.charging " ) + " " ) ;
2025-07-14 20:38:03 +02:00
2023-11-13 00:04:42 +01:00
std : : string fps = std : : to_string ( framerate ( ) . getFramerate ( ) ) + " FPS " ;
2025-07-14 20:38:03 +02:00
std : : string time = TextOperations : : getFormattedTimeLocal ( std : : time ( nullptr ) ) ;
std : : string power = powerState . powerState = = PowerStateMode : : UNKNOWN ? " " : powerSymbol + std : : to_string ( powerState . percent ) + " % " ;
std : : string textToDisplay = time + ( power . empty ( ) ? " " : " | " + power ) + " | " + fps ;
2025-07-17 20:42:27 +02:00
maxPerformanceOverlayTextWidth = std : : max ( maxPerformanceOverlayTextWidth , static_cast < int > ( fontPtr - > getStringWidth ( textToDisplay ) ) ) ; // do not get smaller (can cause graphical glitches)
2025-07-14 20:38:03 +02:00
Rect targetArea ;
2025-07-17 20:42:27 +02:00
std : : string edge = settings [ " video " ] [ " performanceOverlay " ] [ " edge " ] . String ( ) ;
int marginTopBottom = settings [ " video " ] [ " performanceOverlay " ] [ " marginTopBottom " ] . Integer ( ) ;
int marginLeftRight = settings [ " video " ] [ " performanceOverlay " ] [ " marginLeftRight " ] . Integer ( ) ;
2025-07-14 20:38:03 +02:00
2025-07-17 20:42:27 +02:00
Point boxSize ( maxPerformanceOverlayTextWidth + 6 , fontPtr - > getLineHeight ( ) + 2 ) ;
2025-07-14 20:38:03 +02:00
if ( edge = = " topleft " )
2025-07-17 20:42:27 +02:00
targetArea = Rect ( marginLeftRight , marginTopBottom , boxSize . x , boxSize . y ) ;
2025-07-14 20:38:03 +02:00
else if ( edge = = " topright " )
2025-07-17 20:42:27 +02:00
targetArea = Rect ( screenDimensions ( ) . x - marginLeftRight - boxSize . x , marginTopBottom , boxSize . x , boxSize . y ) ;
2025-07-14 20:38:03 +02:00
else if ( edge = = " bottomleft " )
2025-07-17 20:42:27 +02:00
targetArea = Rect ( marginLeftRight , screenDimensions ( ) . y - marginTopBottom - boxSize . y , boxSize . x , boxSize . y ) ;
2025-07-14 20:38:03 +02:00
else if ( edge = = " bottomright " )
2025-07-17 20:42:27 +02:00
targetArea = Rect ( screenDimensions ( ) . x - marginLeftRight - boxSize . x , screenDimensions ( ) . y - marginTopBottom - boxSize . y , boxSize . x , boxSize . y ) ;
2023-11-13 00:04:42 +01:00
2025-07-14 20:38:03 +02:00
target . drawColor ( targetArea . resize ( 1 ) , Colors : : BRIGHT_YELLOW ) ;
target . drawColor ( targetArea , ColorRGBA ( 0 , 0 , 0 ) ) ;
target . drawText ( targetArea . center ( ) , font , Colors : : WHITE , ETextAlignment : : CENTER , textToDisplay ) ;
2023-10-19 16:19:09 +02:00
}
2025-02-10 21:49:23 +00:00
bool GameEngine : : amIGuiThread ( )
2023-10-19 16:19:09 +02:00
{
return inGuiThread ;
}
2025-02-10 21:49:23 +00:00
void GameEngine : : dispatchMainThread ( const std : : function < void ( ) > & functor )
2023-10-19 16:19:09 +02:00
{
inputHandlerInstance - > dispatchMainThread ( functor ) ;
}
2025-02-10 21:49:23 +00:00
IScreenHandler & GameEngine : : screenHandler ( )
2023-10-19 16:19:09 +02:00
{
return * screenHandlerInstance ;
}
2025-02-10 21:49:23 +00:00
IRenderHandler & GameEngine : : renderHandler ( )
2023-10-19 16:19:09 +02:00
{
return * renderHandlerInstance ;
}
2025-02-10 21:49:23 +00:00
EventDispatcher & GameEngine : : events ( )
2023-10-19 16:19:09 +02:00
{
return * eventDispatcherInstance ;
}
2025-02-10 21:49:23 +00:00
InputHandler & GameEngine : : input ( )
2023-10-19 16:19:09 +02:00
{
return * inputHandlerInstance ;
}
2025-02-10 21:49:23 +00:00
WindowHandler & GameEngine : : windows ( )
2023-10-19 16:19:09 +02:00
{
assert ( windowHandlerInstance ) ;
return * windowHandlerInstance ;
}
2025-02-10 21:49:23 +00:00
std : : shared_ptr < IStatusBar > GameEngine : : statusbar ( )
2023-10-19 16:19:09 +02:00
{
auto locked = currentStatusBar . lock ( ) ;
if ( ! locked )
return fakeStatusBar ;
return locked ;
}
2025-03-12 15:57:06 +00:00
void GameEngine : : setStatusbar ( const std : : shared_ptr < IStatusBar > & newStatusBar )
2023-10-19 16:19:09 +02:00
{
currentStatusBar = newStatusBar ;
}
2025-08-31 13:15:55 +02:00
void GameEngine : : onScreenResize ( bool resolutionChanged , bool windowResized )
2023-10-19 16:19:09 +02:00
{
2024-02-03 15:14:59 +01:00
if ( resolutionChanged )
2025-08-31 13:15:55 +02:00
if ( ! screenHandler ( ) . onScreenResize ( windowResized ) )
return ;
2024-10-01 19:40:12 +00:00
2023-10-19 16:19:09 +02:00
windows ( ) . onScreenResize ( ) ;
2025-02-10 22:08:50 +00:00
ENGINE - > cursor ( ) . onScreenResize ( ) ;
2023-10-19 16:19:09 +02:00
}
2025-02-27 22:18:31 +00:00
void GameEngine : : setEngineUser ( IGameEngineUser * user )
{
engineUser = user ;
}