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"
|
|
|
|
|
|
|
|
#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"
|
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-03-03 20:30:15 +00:00
|
|
|
if (settings["video"]["showfps"].Bool())
|
|
|
|
drawFPSCounter();
|
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-02-10 21:49:23 +00:00
|
|
|
void GameEngine::drawFPSCounter()
|
2023-10-19 16:19:09 +02:00
|
|
|
{
|
2025-02-10 21:49:23 +00:00
|
|
|
Canvas target = screenHandler().getScreenCanvas();
|
2025-02-10 17:23:20 +00:00
|
|
|
Rect targetArea(0, screenDimensions().y - 20, 48, 11);
|
2023-11-13 00:04:42 +01:00
|
|
|
std::string fps = std::to_string(framerate().getFramerate())+" FPS";
|
|
|
|
|
2025-02-10 17:23:20 +00:00
|
|
|
target.drawColor(targetArea, ColorRGBA(10, 10, 10));
|
|
|
|
target.drawText(targetArea.center(), EFonts::FONT_SMALL, Colors::WHITE, ETextAlignment::CENTER, fps);
|
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-02-10 21:49:23 +00:00
|
|
|
void GameEngine::onScreenResize(bool resolutionChanged)
|
2023-10-19 16:19:09 +02:00
|
|
|
{
|
2024-02-03 15:14:59 +01:00
|
|
|
if(resolutionChanged)
|
|
|
|
screenHandler().onScreenResize();
|
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;
|
|
|
|
}
|