1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Merge pull request #2076 from IvanSavenko/configurable_adventure_map

Implement scalable adventure map window
This commit is contained in:
Ivan Savenko 2023-05-12 17:39:45 +03:00 committed by GitHub
commit 7927470d46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
88 changed files with 4169 additions and 2863 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 228 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 747 B

View File

@ -49,10 +49,13 @@
"vcmi.systemOptions.fullscreenButton.hover" : "Fullscreen",
"vcmi.systemOptions.fullscreenButton.help" : "{Fullscreen}\n\nIf selected, VCMI will run in fullscreen mode, otherwise it will run in windowed mode",
"vcmi.systemOptions.resolutionButton.hover" : "Resolution: %wx%h",
"vcmi.systemOptions.resolutionButton.help" : "{Select Resolution}\n\nChange in-game screen resolution. A game restart is required to apply the new resolution.",
"vcmi.systemOptions.resolutionButton.help" : "{Select Resolution}\n\nChange in-game screen resolution.",
"vcmi.systemOptions.resolutionMenu.hover" : "Select Resolution",
"vcmi.systemOptions.resolutionMenu.help" : "Change in-game screen resolution.",
"vcmi.systemOptions.fullscreenFailed" : "{Fullscreen}\n\nFailed to switch to fullscreen mode! The current resolution is not supported by the display!",
"vcmi.systemOptions.scalingButton.hover" : "Interface Scaling: %p%",
"vcmi.systemOptions.scalingButton.help" : "{Interface Scaling}\n\nChanges scaling of in-game interface",
"vcmi.systemOptions.scalingMenu.hover" : "Select Interface Scaling",
"vcmi.systemOptions.scalingMenu.help" : "Change in-game interface scaling.",
"vcmi.systemOptions.framerateButton.hover" : "Show FPS",
"vcmi.systemOptions.framerateButton.help" : "{Show FPS}\n\nToggle the visibility of the Frames Per Second counter in the corner of the game window",

View File

@ -7,61 +7,48 @@
* Full text of license available in license.txt file, in main folder
*
*/
// CMT.cpp : Defines the entry point for the console application.
//
#include "StdInc.h"
#include "CMT.h"
#include "CGameInfo.h"
#include "mainmenu/CMainMenu.h"
#include "lobby/CSelectionBase.h"
#include "windows/CCastleInterface.h"
#include "mainmenu/CPrologEpilogVideo.h"
#include "gui/CursorHandler.h"
#include "CPlayerInterface.h"
#include "CVideoHandler.h"
#include "CMusicHandler.h"
#include "Client.h"
#include "gui/CGuiHandler.h"
#include "CServerHandler.h"
#include "gui/NotificationHandler.h"
#include "ClientCommandManager.h"
#include "windows/CMessage.h"
#include "renderSDL/SDL_Extensions.h"
#include "render/IScreenHandler.h"
#include "../lib/filesystem/Filesystem.h"
#include "../lib/filesystem/FileStream.h"
#include "../lib/CConsoleHandler.h"
#include "../lib/CGameState.h"
#include "../lib/CBuildingHandler.h"
#include "../CCallback.h"
#include "../lib/CHeroHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/serializer/BinaryDeserializer.h"
#include "../lib/serializer/BinarySerializer.h"
#include "../lib/VCMIDirs.h"
#include "../lib/NetPacks.h"
#include "../lib/CModHandler.h"
#include "../lib/CTownHandler.h"
#include "../lib/mapping/CCampaignHandler.h"
#include "../lib/logging/CBasicLogConfigurator.h"
#include "../lib/CPlayerState.h"
#include "../lib/serializer/Connection.h"
#include <boost/asio.hpp>
#include <boost/program_options.hpp>
#include "mainmenu/CPrologEpilogVideo.h"
#include <vstd/StringUtils.h>
#include <SDL.h>
#include <SDL_events.h>
#include <SDL_hints.h>
#include <SDL_main.h>
#ifdef VCMI_WINDOWS
#include <SDL_syswm.h>
#endif
#ifdef VCMI_ANDROID
#include "../lib/CAndroidVMHelper.h"
#include <SDL_system.h>
#endif
#include "CMT.h"
#if __MINGW32__
#undef main
#endif
@ -70,44 +57,21 @@ namespace po = boost::program_options;
namespace po_style = boost::program_options::command_line_style;
namespace bfs = boost::filesystem;
std::string NAME_AFFIX = "client";
std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
CGuiHandler GH;
int preferredDriverIndex = -1;
SDL_Window * mainWindow = nullptr;
SDL_Renderer * mainRenderer = nullptr;
SDL_Texture * screenTexture = nullptr;
extern boost::thread_specific_ptr<bool> inGuiThread;
SDL_Surface *screen = nullptr, //main screen surface
*screen2 = nullptr, //and hlp surface (used to store not-active interfaces layer)
*screenBuf = screen; //points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
std::queue<SDL_Event> SDLEventsQueue;
boost::mutex eventsM;
static po::variables_map vm;
//static bool setResolution = false; //set by event handling thread after resolution is adjusted
#ifndef VCMI_IOS
void processCommand(const std::string &message);
#endif
static void setScreenRes(int w, int h, int bpp, bool fullscreen, int displayIndex, bool resetVideo=true);
void playIntro();
static void mainLoop();
static CBasicLogConfigurator *logConfig;
#ifndef VCMI_WINDOWS
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <getopt.h>
#endif
void init()
{
CStopWatch tmh;
@ -139,17 +103,6 @@ static void prog_help(const po::options_description &opts)
std::cout << opts;
}
static void SDLLogCallback(void* userdata,
int category,
SDL_LogPriority priority,
const char* message)
{
//todo: convert SDL log priority to vcmi log priority
//todo: make separate log domain for SDL
logGlobal->debug("SDL(category %d; priority %d) %s", category, priority, message);
}
#if defined(VCMI_WINDOWS) && !defined(__GNUC__) && defined(VCMI_WITH_DEBUG_CONSOLE)
int wmain(int argc, wchar_t* argv[])
#elif defined(VCMI_MOBILE)
@ -255,7 +208,7 @@ int main(int argc, char * argv[])
const bfs::path logPath = VCMIDirs::get().userLogsPath() / "VCMI_Client_log.txt";
logConfig = new CBasicLogConfigurator(logPath, console);
logConfig->configureDefault();
logGlobal->info(NAME);
logGlobal->info("Starting client of '%s'", GameConstants::VCMI_VERSION);
logGlobal->info("Creating console and configuring logger: %d ms", pomtime.getDiff());
logGlobal->info("The log file will be saved to %s", logPath);
@ -338,73 +291,11 @@ int main(int argc, char * argv[])
testFile("VIDEO/GOOD1A.SMK", "campaign movies");
testFile("SOUNDS/G1A.WAV", "campaign music"); //technically not a music but voiced intro sounds
conf.init();
logGlobal->info("Loading settings: %d ms", pomtime.getDiff());
srand ( (unsigned int)time(nullptr) );
const JsonNode& video = settings["video"];
const JsonNode& res = video["screenRes"];
//something is really wrong...
if (res["width"].Float() < 100 || res["height"].Float() < 100)
{
logGlobal->error("Fatal error: failed to load settings!");
logGlobal->error("Possible reasons:");
logGlobal->error("\tCorrupted local configuration file at %s/settings.json", VCMIDirs::get().userConfigPath());
logGlobal->error("\tMissing or corrupted global configuration file at %s/schemas/settings.json", VCMIDirs::get().userConfigPath());
logGlobal->error("VCMI will now exit...");
exit(EXIT_FAILURE);
}
if(!settings["session"]["headless"].Bool())
{
if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_AUDIO|SDL_INIT_NOPARACHUTE))
{
logGlobal->error("Something was wrong: %s", SDL_GetError());
exit(-1);
}
#ifdef VCMI_ANDROID
// manually setting egl pixel format, as a possible solution for sdl2<->android problem
// https://bugzilla.libsdl.org/show_bug.cgi?id=2291
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
#endif // VCMI_ANDROID
//(!)init here AFTER SDL_Init() while using SDL for FPS management
GH.init();
SDL_LogSetOutputFunction(&SDLLogCallback, nullptr);
int driversCount = SDL_GetNumRenderDrivers();
std::string preferredDriverName = video["driver"].String();
logGlobal->info("Found %d render drivers", driversCount);
for(int it = 0; it < driversCount; it++)
{
SDL_RendererInfo info;
SDL_GetRenderDriverInfo(it,&info);
std::string driverName(info.name);
if(!preferredDriverName.empty() && driverName == preferredDriverName)
{
preferredDriverIndex = it;
logGlobal->info("\t%s (active)", driverName);
}
else
logGlobal->info("\t%s", driverName);
}
setScreenRes((int)res["width"].Float(), (int)res["height"].Float(), (int)video["bitsPerPixel"].Float(), video["fullscreen"].Bool(), (int)video["displayIndex"].Float());
logGlobal->info("\tInitializing screen: %d ms", pomtime.getDiff());
}
CCS = new CClientState();
CGI = new CGameInfo(); //contains all global informations about game (texts, lodHandlers, map handler etc.)
CSH = new CServerHandler();
@ -457,9 +348,7 @@ int main(int argc, char * argv[])
{
if(!vm.count("battle") && !vm.count("nointro") && settings["video"]["showIntro"].Bool())
playIntro();
SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 255);
SDL_RenderClear(mainRenderer);
SDL_RenderPresent(mainRenderer);
GH.screenHandler().clearScreen();
}
@ -490,7 +379,6 @@ int main(int argc, char * argv[])
CCS->curh->show();
}
logGlobal->info("Initialization of VCMI (together): %d ms", total.getDiff());
session["autoSkip"].Bool() = vm.count("autoSkip");
@ -580,350 +468,6 @@ void playIntro()
}
}
#if !defined(VCMI_MOBILE)
static bool checkVideoMode(int monitorIndex, int w, int h)
{
//we only check that our desired window size fits on screen
SDL_DisplayMode mode;
if (0 != SDL_GetDesktopDisplayMode(monitorIndex, &mode))
{
logGlobal->error("SDL_GetDesktopDisplayMode failed");
logGlobal->error(SDL_GetError());
return false;
}
logGlobal->info("Check display mode: requested %d x %d; available up to %d x %d ", w, h, mode.w, mode.h);
if (!mode.w || !mode.h || (w <= mode.w && h <= mode.h))
{
return true;
}
return false;
}
#endif
static void cleanupRenderer()
{
screenBuf = nullptr; //it`s a link - just nullify
if(nullptr != screen2)
{
SDL_FreeSurface(screen2);
screen2 = nullptr;
}
if(nullptr != screen)
{
SDL_FreeSurface(screen);
screen = nullptr;
}
if(nullptr != screenTexture)
{
SDL_DestroyTexture(screenTexture);
screenTexture = nullptr;
}
}
static bool recreateWindow(int w, int h, int bpp, bool fullscreen, int displayIndex)
{
// VCMI will only work with 2 or 4 bytes per pixel
vstd::amax(bpp, 16);
vstd::amin(bpp, 32);
if(bpp>16)
bpp = 32;
if(displayIndex < 0)
{
if (mainWindow != nullptr)
displayIndex = SDL_GetWindowDisplayIndex(mainWindow);
if (displayIndex < 0)
displayIndex = 0;
}
#if defined(VCMI_MOBILE)
SDL_GetWindowSize(mainWindow, &w, &h);
#else
if(!checkVideoMode(displayIndex, w, h))
{
logGlobal->error("Error: SDL says that %dx%d resolution is not available!", w, h);
}
#endif
bool bufOnScreen = (screenBuf == screen);
bool realFullscreen = settings["video"]["realFullscreen"].Bool();
/* match best rendering resolution */
int renderWidth = 0, renderHeight = 0;
auto aspectRatio = (float)w / (float)h;
auto minDiff = 10.f;
for (const auto& pair : conf.guiOptions)
{
int pWidth, pHeight;
std::tie(pWidth, pHeight) = pair.first;
/* filter out resolution which is larger than window */
if (pWidth > w || pHeight > h)
{
continue;
}
auto ratio = (float)pWidth / (float)pHeight;
auto diff = fabs(aspectRatio - ratio);
/* select closest aspect ratio */
if (diff < minDiff)
{
renderWidth = pWidth;
renderHeight = pHeight;
minDiff = diff;
}
/* select largest resolution meets prior conditions.
* since there are resolutions like 1366x768(not exactly 16:9), a deviation of 0.005 is allowed. */
else if (fabs(diff - minDiff) < 0.005f && pWidth > renderWidth)
{
renderWidth = pWidth;
renderHeight = pHeight;
}
}
if (renderWidth == 0)
{
// no matching resolution for upscaling - complain & fallback to default resolution.
logGlobal->error("Failed to match rendering resolution for %dx%d!", w, h);
Settings newRes = settings.write["video"]["screenRes"];
std::tie(w, h) = conf.guiOptions.begin()->first;
newRes["width"].Float() = w;
newRes["height"].Float() = h;
conf.SetResolution(w, h);
logGlobal->error("Falling back to %dx%d", w, h);
renderWidth = w;
renderHeight = h;
}
else
{
logGlobal->info("Set logical rendering resolution to %dx%d", renderWidth, renderHeight);
}
cleanupRenderer();
if(nullptr == mainWindow)
{
#if defined(VCMI_MOBILE)
auto createWindow = [displayIndex](uint32_t extraFlags) -> bool {
mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), 0, 0, SDL_WINDOW_FULLSCREEN | extraFlags);
return mainWindow != nullptr;
};
# ifdef VCMI_IOS
SDL_SetHint(SDL_HINT_IOS_HIDE_HOME_INDICATOR, "1");
SDL_SetHint(SDL_HINT_RETURN_KEY_HIDES_IME, "1");
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
uint32_t windowFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI;
if(!createWindow(windowFlags | SDL_WINDOW_METAL))
{
logGlobal->warn("Metal unavailable, using OpenGLES");
createWindow(windowFlags);
}
# else
createWindow(0);
# endif // VCMI_IOS
// SDL on mobile doesn't do proper letterboxing, and will show an annoying flickering in the blank space in case you're not using the full screen estate
// That's why we need to make sure our width and height we'll use below have the same aspect ratio as the screen itself to ensure we fill the full screen estate
SDL_Rect screenRect;
if(SDL_GetDisplayBounds(0, &screenRect) == 0)
{
const auto screenWidth = screenRect.w;
const auto screenHeight = screenRect.h;
const auto aspect = static_cast<double>(screenWidth) / screenHeight;
logGlobal->info("Screen size and aspect ratio: %dx%d (%lf)", screenWidth, screenHeight, aspect);
if((double)w / aspect > (double)h)
{
h = (int)round((double)w / aspect);
}
else
{
w = (int)round((double)h * aspect);
}
logGlobal->info("Changing logical screen size to %dx%d", w, h);
}
else
{
logGlobal->error("Can't fix aspect ratio for screen");
}
#else
if(fullscreen)
{
if(realFullscreen)
mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), renderWidth, renderHeight, SDL_WINDOW_FULLSCREEN);
else //in windowed full-screen mode use desktop resolution
mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex),SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), 0, 0, SDL_WINDOW_FULLSCREEN_DESKTOP);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
}
else
{
mainWindow = SDL_CreateWindow(NAME.c_str(), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex),SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), w, h, 0);
}
#endif // defined(VCMI_MOBILE)
if(nullptr == mainWindow)
{
throw std::runtime_error("Unable to create window\n");
}
//create first available renderer if preferred not set. Use no flags, so HW accelerated will be preferred but SW renderer also will possible
mainRenderer = SDL_CreateRenderer(mainWindow,preferredDriverIndex,0);
if(nullptr == mainRenderer)
{
throw std::runtime_error("Unable to create renderer\n");
}
SDL_RendererInfo info;
SDL_GetRendererInfo(mainRenderer, &info);
logGlobal->info("Created renderer %s", info.name);
}
else
{
#if !defined(VCMI_MOBILE)
if(fullscreen)
{
if(realFullscreen)
{
SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN);
SDL_DisplayMode mode;
SDL_GetDesktopDisplayMode(displayIndex, &mode);
mode.w = renderWidth;
mode.h = renderHeight;
SDL_SetWindowDisplayMode(mainWindow, &mode);
}
else
{
SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
}
SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex));
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
}
else
{
SDL_SetWindowFullscreen(mainWindow, 0);
SDL_SetWindowSize(mainWindow, w, h);
SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex));
}
#endif
}
if(!(fullscreen && realFullscreen))
{
SDL_RenderSetLogicalSize(mainRenderer, renderWidth, renderHeight);
//following line is bugged not only on android, do not re-enable without checking
//#ifndef VCMI_ANDROID
// // on android this stretches the game to fit the screen, not preserving aspect and apparently this also breaks coordinates scaling in mouse events
// SDL_RenderSetViewport(mainRenderer, nullptr);
//#endif
}
#ifdef VCMI_ENDIAN_BIG
int bmask = 0xff000000;
int gmask = 0x00ff0000;
int rmask = 0x0000ff00;
int amask = 0x000000ff;
#else
int bmask = 0x000000ff;
int gmask = 0x0000ff00;
int rmask = 0x00ff0000;
int amask = 0xFF000000;
#endif
screen = SDL_CreateRGBSurface(0,renderWidth,renderHeight,bpp,rmask,gmask,bmask,amask);
if(nullptr == screen)
{
logGlobal->error("Unable to create surface %dx%d with %d bpp: %s", renderWidth, renderHeight, bpp, SDL_GetError());
throw std::runtime_error("Unable to create surface");
}
//No blending for screen itself. Required for proper cursor rendering.
SDL_SetSurfaceBlendMode(screen, SDL_BLENDMODE_NONE);
screenTexture = SDL_CreateTexture(mainRenderer,
SDL_PIXELFORMAT_ARGB8888,
SDL_TEXTUREACCESS_STREAMING,
renderWidth, renderHeight);
if(nullptr == screenTexture)
{
logGlobal->error("Unable to create screen texture");
logGlobal->error(SDL_GetError());
throw std::runtime_error("Unable to create screen texture");
}
screen2 = CSDL_Ext::copySurface(screen);
if(nullptr == screen2)
{
throw std::runtime_error("Unable to copy surface\n");
}
screenBuf = bufOnScreen ? screen : screen2;
SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 0);
SDL_RenderClear(mainRenderer);
SDL_RenderPresent(mainRenderer);
if(!settings["session"]["headless"].Bool() && settings["general"]["notifications"].Bool())
{
NotificationHandler::init(mainWindow);
}
return true;
}
//used only once during initialization
static void setScreenRes(int w, int h, int bpp, bool fullscreen, int displayIndex, bool resetVideo)
{
if(!recreateWindow(w, h, bpp, fullscreen, displayIndex))
{
throw std::runtime_error("Requested screen resolution is not available\n");
}
}
static void fullScreenChanged()
{
boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
Settings full = settings.write["video"]["fullscreen"];
const bool toFullscreen = full->Bool();
auto bitsPerPixel = screen->format->BitsPerPixel;
auto w = screen->w;
auto h = screen->h;
if(!recreateWindow(w, h, bitsPerPixel, toFullscreen, -1))
{
//will return false and report error if video mode is not supported
return;
}
GH.totalRedraw();
}
static void handleEvent(SDL_Event & ev)
{
if((ev.type==SDL_QUIT) ||(ev.type == SDL_KEYDOWN && ev.key.keysym.sym==SDLK_F4 && (ev.key.keysym.mod & KMOD_ALT)))
@ -1001,8 +545,11 @@ static void handleEvent(SDL_Event & ev)
CMM->menu->switchToTab("load");
break;
case EUserEvent::FULLSCREEN_TOGGLED:
fullScreenChanged();
break;
{
boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
GH.screenHandler().onScreenResize();
break;
}
default:
logGlobal->error("Unknown user event. Code %d", ev.user.code);
break;
@ -1015,7 +562,10 @@ static void handleEvent(SDL_Event & ev)
switch (ev.window.event) {
case SDL_WINDOWEVENT_RESTORED:
#ifndef VCMI_IOS
fullScreenChanged();
{
boost::unique_lock<boost::recursive_mutex> lock(*CPlayerInterface::pim);
GH.screenHandler().onScreenResize();
}
#endif
break;
}
@ -1039,14 +589,14 @@ static void handleEvent(SDL_Event & ev)
boost::unique_lock<boost::mutex> lock(eventsM);
SDLEventsQueue.push(ev);
}
}
static void mainLoop()
{
SettingsListener resChanged = settings.listen["video"]["fullscreen"];
SettingsListener resChanged = settings.listen["video"]["resolution"];
SettingsListener fsChanged = settings.listen["video"]["fullscreen"];
resChanged([](const JsonNode &newState){ CGuiHandler::pushUserEvent(EUserEvent::FULLSCREEN_TOGGLED); });
fsChanged([](const JsonNode &newState){ CGuiHandler::pushUserEvent(EUserEvent::FULLSCREEN_TOGGLED); });
inGuiThread.reset(new bool(true));
assert(GH.mainFPSmng);
@ -1063,7 +613,6 @@ static void mainLoop()
CSH->applyPacksOnLobbyScreen();
GH.renderFrame();
}
}
@ -1100,29 +649,9 @@ static void quitApplication()
vstd::clear_pointer(console);// should be removed after everything else since used by logging
boost::this_thread::sleep(boost::posix_time::milliseconds(750));//???
if(!settings["session"]["headless"].Bool())
{
if(settings["general"]["notifications"].Bool())
{
NotificationHandler::destroy();
}
cleanupRenderer();
if(nullptr != mainRenderer)
{
SDL_DestroyRenderer(mainRenderer);
mainRenderer = nullptr;
}
if(nullptr != mainWindow)
{
SDL_DestroyWindow(mainWindow);
mainWindow = nullptr;
}
SDL_Quit();
}
GH.screenHandler().close();
if(logConfig != nullptr)
{
@ -1137,7 +666,6 @@ static void quitApplication()
void handleQuit(bool ask)
{
if(CSH->client && LOCPLINT && ask)
{
CCS->curh->set(Cursor::Map::POINTER);

View File

@ -2,9 +2,10 @@ set(client_SRCS
StdInc.cpp
../CCallback.cpp
adventureMap/CAdvMapPanel.cpp
adventureMap/CAdventureMapInterface.cpp
adventureMap/CAdventureOptions.cpp
adventureMap/AdventureMapInterface.cpp
adventureMap/AdventureMapShortcuts.cpp
adventureMap/AdventureMapWidget.cpp
adventureMap/AdventureOptions.cpp
adventureMap/CInGameConsole.cpp
adventureMap/CInfoBar.cpp
adventureMap/CList.cpp
@ -75,6 +76,7 @@ set(client_SRCS
renderSDL/SDLImage.cpp
renderSDL/SDLImageLoader.cpp
renderSDL/SDLRWwrapper.cpp
renderSDL/ScreenHandler.cpp
renderSDL/SDL_Extensions.cpp
widgets/Buttons.cpp
@ -129,9 +131,11 @@ set(client_SRCS
set(client_HEADERS
StdInc.h
adventureMap/CAdvMapPanel.h
adventureMap/CAdventureMapInterface.h
adventureMap/CAdventureOptions.h
adventureMap/AdventureMapInterface.h
adventureMap/AdventureMapShortcuts.h
adventureMap/AdventureMapWidget.h
adventureMap/AdventureState.h
adventureMap/AdventureOptions.h
adventureMap/CInGameConsole.h
adventureMap/CInfoBar.h
adventureMap/CList.h
@ -202,6 +206,7 @@ set(client_HEADERS
render/IFont.h
render/IImage.h
render/IImageLoader.h
render/IScreenHandler.h
renderSDL/CBitmapFont.h
renderSDL/CBitmapHanFont.h
@ -211,6 +216,7 @@ set(client_HEADERS
renderSDL/SDLImage.h
renderSDL/SDLImageLoader.h
renderSDL/SDLRWwrapper.h
renderSDL/ScreenHandler.h
renderSDL/SDL_Extensions.h
renderSDL/SDL_PixelAccess.h

View File

@ -12,10 +12,9 @@
#include <vcmi/Artifact.h>
#include "adventureMap/CAdventureMapInterface.h"
#include "adventureMap/AdventureMapInterface.h"
#include "mapView/mapHandler.h"
#include "adventureMap/CList.h"
#include "adventureMap/CInfoBar.h"
#include "battle/BattleInterface.h"
#include "battle/BattleEffectsController.h"
#include "battle/BattleFieldController.h"
@ -163,7 +162,7 @@ void CPlayerInterface::initGameInterface(std::shared_ptr<Environment> ENV, std::
initializeHeroTownList();
// always recreate advmap interface to avoid possible memory-corruption bugs
adventureInt.reset(new CAdventureMapInterface());
adventureInt.reset(new AdventureMapInterface());
}
void CPlayerInterface::playerStartsTurn(PlayerColor player)

View File

@ -31,7 +31,7 @@ struct CPathsInfo;
VCMI_LIB_NAMESPACE_END
class CButton;
class CAdventureMapInterface;
class AdventureMapInterface;
class CCastleInterface;
class BattleInterface;
class CComponent;

View File

@ -123,7 +123,8 @@ public:
}
};
extern std::string NAME;
static const std::string NAME_AFFIX = "client";
static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
CServerHandler::CServerHandler()
: state(EClientState::NONE), mx(std::make_shared<boost::recursive_mutex>()), client(nullptr), loadMode(0), campaignStateToSend(nullptr), campaignServerRestartLock(false)

View File

@ -15,7 +15,7 @@
#include "CPlayerInterface.h"
#include "CServerHandler.h"
#include "ClientNetPackVisitors.h"
#include "adventureMap/CAdventureMapInterface.h"
#include "adventureMap/AdventureMapInterface.h"
#include "battle/BattleInterface.h"
#include "gui/CGuiHandler.h"
#include "mapView/mapHandler.h"

View File

@ -1,9 +0,0 @@
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings
xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
</assembly>

View File

@ -15,7 +15,7 @@
#include "../lib/mapObjects/CGHeroInstance.h"
#include "../lib/mapObjects/CGTownInstance.h"
#include "CPlayerInterface.h"
#include "adventureMap/CAdventureMapInterface.h"
#include "adventureMap/AdventureMapInterface.h"
PlayerLocalState::PlayerLocalState(CPlayerInterface & owner)
: owner(owner)

View File

@ -0,0 +1,815 @@
/*
* AdventureMapInterface.cpp, part of VCMI engine
*
* 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"
#include "AdventureMapInterface.h"
#include "AdventureOptions.h"
#include "AdventureState.h"
#include "CInGameConsole.h"
#include "CMinimap.h"
#include "CList.h"
#include "CInfoBar.h"
#include "MapAudioPlayer.h"
#include "AdventureMapWidget.h"
#include "AdventureMapShortcuts.h"
#include "../mapView/mapHandler.h"
#include "../mapView/MapView.h"
#include "../windows/InfoWindows.h"
#include "../CGameInfo.h"
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../CMT.h"
#include "../PlayerLocalState.h"
#include "../CPlayerInterface.h"
#include "../../CCallback.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/spells/CSpellHandler.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/CPathfinder.h"
#include "../../lib/mapping/CMap.h"
std::shared_ptr<AdventureMapInterface> adventureInt;
AdventureMapInterface::AdventureMapInterface():
mapAudio(new MapAudioPlayer()),
spellBeingCasted(nullptr),
scrollingWasActive(false),
scrollingWasBlocked(false)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos.x = pos.y = 0;
pos.w = GH.screenDimensions().x;
pos.h = GH.screenDimensions().y;
strongInterest = true; // handle all mouse move events to prevent dead mouse move space in fullscreen mode
shortcuts = std::make_shared<AdventureMapShortcuts>(*this);
widget = std::make_shared<AdventureMapWidget>(shortcuts);
shortcuts->setState(EAdventureState::MAKING_TURN);
widget->getMapView()->onViewMapActivated();
addUsedEvents(KEYBOARD);
}
void AdventureMapInterface::onMapViewMoved(const Rect & visibleArea, int mapLevel)
{
shortcuts->onMapViewMoved(visibleArea, mapLevel);
widget->getMinimap()->onMapViewMoved(visibleArea, mapLevel);
widget->onMapViewMoved(visibleArea, mapLevel);
}
void AdventureMapInterface::onAudioResumed()
{
mapAudio->onAudioResumed();
}
void AdventureMapInterface::onAudioPaused()
{
mapAudio->onAudioPaused();
}
void AdventureMapInterface::onHeroMovementStarted(const CGHeroInstance * hero)
{
widget->getInfoBar()->popAll();
widget->getInfoBar()->showSelection();
}
void AdventureMapInterface::onHeroChanged(const CGHeroInstance *h)
{
widget->getHeroList()->update(h);
if (h && h == LOCPLINT->localState->getCurrentHero() && !widget->getInfoBar()->showingComponents())
widget->getInfoBar()->showSelection();
widget->updateActiveState();
}
void AdventureMapInterface::onTownChanged(const CGTownInstance * town)
{
widget->getTownList()->update(town);
if (town && town == LOCPLINT->localState->getCurrentTown() && !widget->getInfoBar()->showingComponents())
widget->getInfoBar()->showSelection();
}
void AdventureMapInterface::showInfoBoxMessage(const std::vector<Component> & components, std::string message, int timer)
{
widget->getInfoBar()->pushComponents(components, message, timer);
}
void AdventureMapInterface::activate()
{
CIntObject::activate();
adjustActiveness();
screenBuf = screen;
if(LOCPLINT)
{
LOCPLINT->cingconsole->activate();
LOCPLINT->cingconsole->pos = this->pos;
}
GH.fakeMouseMove(); //to restore the cursor
}
void AdventureMapInterface::deactivate()
{
CIntObject::deactivate();
CCS->curh->set(Cursor::Map::POINTER);
}
void AdventureMapInterface::showAll(SDL_Surface * to)
{
CIntObject::showAll(to);
LOCPLINT->cingconsole->show(to);
}
void AdventureMapInterface::show(SDL_Surface * to)
{
handleMapScrollingUpdate();
CIntObject::show(to);
LOCPLINT->cingconsole->show(to);
}
void AdventureMapInterface::handleMapScrollingUpdate()
{
/// Width of window border, in pixels, that triggers map scrolling
static constexpr uint32_t borderScrollWidth = 15;
uint32_t timePassed = GH.mainFPSmng->getElapsedMilliseconds();
uint32_t scrollSpeedPixels = settings["adventure"]["scrollSpeedPixels"].Float();
uint32_t scrollDistance = scrollSpeedPixels * timePassed / 1000;
Point cursorPosition = GH.getCursorPosition();
Point scrollDirection;
if (cursorPosition.x < borderScrollWidth)
scrollDirection.x = -1;
if (cursorPosition.x > GH.screenDimensions().x - borderScrollWidth)
scrollDirection.x = +1;
if (cursorPosition.y < borderScrollWidth)
scrollDirection.y = -1;
if (cursorPosition.y > GH.screenDimensions().y - borderScrollWidth)
scrollDirection.y = +1;
Point scrollDelta = scrollDirection * scrollDistance;
bool cursorInScrollArea = scrollDelta != Point(0,0);
bool scrollingActive = cursorInScrollArea && active && shortcuts->optionSidePanelActive() && !scrollingWasBlocked;
bool scrollingBlocked = GH.isKeyboardCtrlDown();
if (!scrollingWasActive && scrollingBlocked)
{
scrollingWasBlocked = true;
return;
}
if (!cursorInScrollArea && scrollingWasBlocked)
{
scrollingWasBlocked = false;
return;
}
if (scrollingActive)
widget->getMapView()->onMapScrolled(scrollDelta);
if (!scrollingActive && !scrollingWasActive)
return;
if(scrollDelta.x > 0)
{
if(scrollDelta.y < 0)
CCS->curh->set(Cursor::Map::SCROLL_NORTHEAST);
if(scrollDelta.y > 0)
CCS->curh->set(Cursor::Map::SCROLL_SOUTHEAST);
if(scrollDelta.y == 0)
CCS->curh->set(Cursor::Map::SCROLL_EAST);
}
if(scrollDelta.x < 0)
{
if(scrollDelta.y < 0)
CCS->curh->set(Cursor::Map::SCROLL_NORTHWEST);
if(scrollDelta.y > 0)
CCS->curh->set(Cursor::Map::SCROLL_SOUTHWEST);
if(scrollDelta.y == 0)
CCS->curh->set(Cursor::Map::SCROLL_WEST);
}
if (scrollDelta.x == 0)
{
if(scrollDelta.y < 0)
CCS->curh->set(Cursor::Map::SCROLL_NORTH);
if(scrollDelta.y > 0)
CCS->curh->set(Cursor::Map::SCROLL_SOUTH);
if(scrollDelta.y == 0)
CCS->curh->set(Cursor::Map::POINTER);
}
scrollingWasActive = scrollingActive;
}
void AdventureMapInterface::centerOnTile(int3 on)
{
widget->getMapView()->onCenteredTile(on);
}
void AdventureMapInterface::centerOnObject(const CGObjectInstance * obj)
{
widget->getMapView()->onCenteredObject(obj);
}
void AdventureMapInterface::keyPressed(EShortcut key)
{
if (key == EShortcut::GLOBAL_CANCEL && spellBeingCasted)
hotkeyAbortCastingMode();
//fake mouse use to trigger onTileHovered()
GH.fakeMouseMove();
}
void AdventureMapInterface::onSelectionChanged(const CArmedInstance *sel)
{
assert(sel);
widget->getInfoBar()->popAll();
mapAudio->onSelectionChanged(sel);
bool centerView = !settings["session"]["autoSkip"].Bool();
if (centerView)
centerOnObject(sel);
if(sel->ID==Obj::TOWN)
{
auto town = dynamic_cast<const CGTownInstance*>(sel);
widget->getInfoBar()->showTownSelection(town);
widget->getTownList()->select(town);
widget->getHeroList()->select(nullptr);
onHeroChanged(nullptr);
}
else //hero selected
{
auto hero = dynamic_cast<const CGHeroInstance*>(sel);
widget->getInfoBar()->showHeroSelection(hero);
widget->getHeroList()->select(hero);
widget->getTownList()->select(nullptr);
LOCPLINT->localState->verifyPath(hero);
onHeroChanged(hero);
}
widget->updateActiveState();
widget->getHeroList()->redraw();
widget->getTownList()->redraw();
}
void AdventureMapInterface::onMapTilesChanged(boost::optional<std::unordered_set<int3>> positions)
{
if (positions)
widget->getMinimap()->updateTiles(*positions);
else
widget->getMinimap()->update();
}
void AdventureMapInterface::onHotseatWaitStarted(PlayerColor playerID)
{
onCurrentPlayerChanged(playerID);
setState(EAdventureState::HOTSEAT_WAIT);
}
void AdventureMapInterface::onEnemyTurnStarted(PlayerColor playerID)
{
if(settings["session"]["spectate"].Bool())
return;
mapAudio->onEnemyTurnStarted();
widget->getMinimap()->setAIRadar(true);
widget->getInfoBar()->startEnemyTurn(LOCPLINT->cb->getCurrentPlayer());
setState(EAdventureState::ENEMY_TURN);
}
void AdventureMapInterface::setState(EAdventureState state)
{
shortcuts->setState(state);
adjustActiveness();
widget->updateActiveState();
}
void AdventureMapInterface::adjustActiveness()
{
bool widgetMustBeActive = active && shortcuts->optionSidePanelActive();
bool mapViewMustBeActive = active && (shortcuts->optionMapViewActive());
if (widgetMustBeActive && !widget->active)
widget->activate();
if (!widgetMustBeActive && widget->active)
widget->deactivate();
if (mapViewMustBeActive && !widget->getMapView()->active)
widget->getMapView()->activate();
if (!mapViewMustBeActive && widget->getMapView()->active)
widget->getMapView()->deactivate();
}
void AdventureMapInterface::onCurrentPlayerChanged(PlayerColor playerID)
{
LOCPLINT->localState->setSelection(nullptr);
if (playerID == currentPlayerID)
return;
currentPlayerID = playerID;
widget->setPlayer(playerID);
}
void AdventureMapInterface::onPlayerTurnStarted(PlayerColor playerID)
{
onCurrentPlayerChanged(playerID);
setState(EAdventureState::MAKING_TURN);
if(LOCPLINT->cb->getCurrentPlayer() == LOCPLINT->playerID
|| settings["session"]["spectate"].Bool())
{
widget->getMinimap()->setAIRadar(false);
widget->getInfoBar()->showSelection();
}
widget->getHeroList()->update();
widget->getTownList()->update();
const CGHeroInstance * heroToSelect = nullptr;
// find first non-sleeping hero
for (auto hero : LOCPLINT->localState->getWanderingHeroes())
{
if (!LOCPLINT->localState->isHeroSleeping(hero))
{
heroToSelect = hero;
break;
}
}
//select first hero if available.
if (heroToSelect != nullptr)
{
LOCPLINT->localState->setSelection(heroToSelect);
}
else if (LOCPLINT->localState->getOwnedTowns().size())
{
LOCPLINT->localState->setSelection(LOCPLINT->localState->getOwnedTown(0));
}
else
{
LOCPLINT->localState->setSelection(LOCPLINT->localState->getWanderingHero(0));
}
//show new day animation and sound on infobar
widget->getInfoBar()->showDate();
onHeroChanged(nullptr);
showAll(screen);
mapAudio->onPlayerTurnStarted();
if(settings["session"]["autoSkip"].Bool() && !GH.isKeyboardShiftDown())
{
if(CInfoWindow *iw = dynamic_cast<CInfoWindow *>(GH.topInt().get()))
iw->close();
hotkeyEndingTurn();
}
}
void AdventureMapInterface::hotkeyEndingTurn()
{
if(settings["session"]["spectate"].Bool())
return;
LOCPLINT->makingTurn = false;
LOCPLINT->cb->endTurn();
mapAudio->onPlayerTurnEnded();
}
const CGObjectInstance* AdventureMapInterface::getActiveObject(const int3 &mapPos)
{
std::vector < const CGObjectInstance * > bobjs = LOCPLINT->cb->getBlockingObjs(mapPos); //blocking objects at tile
if (bobjs.empty())
return nullptr;
return *boost::range::max_element(bobjs, &CMapHandler::compareObjectBlitOrder);
}
void AdventureMapInterface::onTileLeftClicked(const int3 &mapPos)
{
if(!shortcuts->optionMapViewActive())
return;
//FIXME: this line breaks H3 behavior for Dimension Door
if(!LOCPLINT->cb->isVisible(mapPos))
return;
if(!LOCPLINT->makingTurn)
return;
const TerrainTile *tile = LOCPLINT->cb->getTile(mapPos);
const CGObjectInstance *topBlocking = getActiveObject(mapPos);
int3 selPos = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
if(spellBeingCasted)
{
assert(shortcuts->optionSpellcasting());
if (!isInScreenRange(selPos, mapPos))
return;
const TerrainTile *heroTile = LOCPLINT->cb->getTile(selPos);
switch(spellBeingCasted->id)
{
case SpellID::SCUTTLE_BOAT: //Scuttle Boat
if(topBlocking && topBlocking->ID == Obj::BOAT)
performSpellcasting(mapPos);
break;
case SpellID::DIMENSION_DOOR:
if(!tile || tile->isClear(heroTile))
performSpellcasting(mapPos);
break;
}
return;
}
//check if we can select this object
bool canSelect = topBlocking && topBlocking->ID == Obj::HERO && topBlocking->tempOwner == LOCPLINT->playerID;
canSelect |= topBlocking && topBlocking->ID == Obj::TOWN && LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, topBlocking->tempOwner);
bool isHero = false;
if(LOCPLINT->localState->getCurrentArmy()->ID != Obj::HERO) //hero is not selected (presumably town)
{
if(LOCPLINT->localState->getCurrentArmy() == topBlocking) //selected town clicked
LOCPLINT->openTownWindow(static_cast<const CGTownInstance*>(topBlocking));
else if(canSelect)
LOCPLINT->localState->setSelection(static_cast<const CArmedInstance*>(topBlocking));
}
else if(const CGHeroInstance * currentHero = LOCPLINT->localState->getCurrentHero()) //hero is selected
{
isHero = true;
const CGPathNode *pn = LOCPLINT->cb->getPathsInfo(currentHero)->getPathInfo(mapPos);
if(currentHero == topBlocking) //clicked selected hero
{
LOCPLINT->openHeroWindow(currentHero);
return;
}
else if(canSelect && pn->turns == 255 ) //selectable object at inaccessible tile
{
LOCPLINT->localState->setSelection(static_cast<const CArmedInstance*>(topBlocking));
return;
}
else //still here? we need to move hero if we clicked end of already selected path or calculate a new path otherwise
{
if(LOCPLINT->localState->hasPath(currentHero) &&
LOCPLINT->localState->getPath(currentHero).endPos() == mapPos)//we'll be moving
{
if(!CGI->mh->hasOngoingAnimations())
LOCPLINT->moveHero(currentHero, LOCPLINT->localState->getPath(currentHero));
return;
}
else //remove old path and find a new one if we clicked on accessible tile
{
LOCPLINT->localState->setPath(currentHero, mapPos);
onHeroChanged(currentHero);
}
}
} //end of hero is selected "case"
else
{
throw std::runtime_error("Nothing is selected...");
}
const auto shipyard = ourInaccessibleShipyard(topBlocking);
if(isHero && shipyard != nullptr)
{
LOCPLINT->showShipyardDialogOrProblemPopup(shipyard);
}
}
void AdventureMapInterface::onTileHovered(const int3 &mapPos)
{
if(!shortcuts->optionMapViewActive())
return;
//may occur just at the start of game (fake move before full intiialization)
if(!LOCPLINT->localState->getCurrentArmy())
return;
if(!LOCPLINT->cb->isVisible(mapPos))
{
CCS->curh->set(Cursor::Map::POINTER);
GH.statusbar->clear();
return;
}
auto objRelations = PlayerRelations::ALLIES;
const CGObjectInstance *objAtTile = getActiveObject(mapPos);
if(objAtTile)
{
objRelations = LOCPLINT->cb->getPlayerRelations(LOCPLINT->playerID, objAtTile->tempOwner);
std::string text = LOCPLINT->localState->getCurrentHero() ? objAtTile->getHoverText(LOCPLINT->localState->getCurrentHero()) : objAtTile->getHoverText(LOCPLINT->playerID);
boost::replace_all(text,"\n"," ");
GH.statusbar->write(text);
}
else
{
std::string hlp = CGI->mh->getTerrainDescr(mapPos, false);
GH.statusbar->write(hlp);
}
if(spellBeingCasted)
{
switch(spellBeingCasted->id)
{
case SpellID::SCUTTLE_BOAT:
{
int3 hpos = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
if(objAtTile && objAtTile->ID == Obj::BOAT && isInScreenRange(hpos, mapPos))
CCS->curh->set(Cursor::Map::SCUTTLE_BOAT);
else
CCS->curh->set(Cursor::Map::POINTER);
return;
}
case SpellID::DIMENSION_DOOR:
{
const TerrainTile * t = LOCPLINT->cb->getTile(mapPos, false);
int3 hpos = LOCPLINT->localState->getCurrentArmy()->getSightCenter();
if((!t || t->isClear(LOCPLINT->cb->getTile(hpos))) && isInScreenRange(hpos, mapPos))
CCS->curh->set(Cursor::Map::TELEPORT);
else
CCS->curh->set(Cursor::Map::POINTER);
return;
}
}
}
if(LOCPLINT->localState->getCurrentArmy()->ID == Obj::TOWN)
{
if(objAtTile)
{
if(objAtTile->ID == Obj::TOWN && objRelations != PlayerRelations::ENEMIES)
CCS->curh->set(Cursor::Map::TOWN);
else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
CCS->curh->set(Cursor::Map::HERO);
else
CCS->curh->set(Cursor::Map::POINTER);
}
else
CCS->curh->set(Cursor::Map::POINTER);
}
else if(const CGHeroInstance * hero = LOCPLINT->localState->getCurrentHero())
{
std::array<Cursor::Map, 4> cursorMove = { Cursor::Map::T1_MOVE, Cursor::Map::T2_MOVE, Cursor::Map::T3_MOVE, Cursor::Map::T4_MOVE, };
std::array<Cursor::Map, 4> cursorAttack = { Cursor::Map::T1_ATTACK, Cursor::Map::T2_ATTACK, Cursor::Map::T3_ATTACK, Cursor::Map::T4_ATTACK, };
std::array<Cursor::Map, 4> cursorSail = { Cursor::Map::T1_SAIL, Cursor::Map::T2_SAIL, Cursor::Map::T3_SAIL, Cursor::Map::T4_SAIL, };
std::array<Cursor::Map, 4> cursorDisembark = { Cursor::Map::T1_DISEMBARK, Cursor::Map::T2_DISEMBARK, Cursor::Map::T3_DISEMBARK, Cursor::Map::T4_DISEMBARK, };
std::array<Cursor::Map, 4> cursorExchange = { Cursor::Map::T1_EXCHANGE, Cursor::Map::T2_EXCHANGE, Cursor::Map::T3_EXCHANGE, Cursor::Map::T4_EXCHANGE, };
std::array<Cursor::Map, 4> cursorVisit = { Cursor::Map::T1_VISIT, Cursor::Map::T2_VISIT, Cursor::Map::T3_VISIT, Cursor::Map::T4_VISIT, };
std::array<Cursor::Map, 4> cursorSailVisit = { Cursor::Map::T1_SAIL_VISIT, Cursor::Map::T2_SAIL_VISIT, Cursor::Map::T3_SAIL_VISIT, Cursor::Map::T4_SAIL_VISIT, };
const CGPathNode * pathNode = LOCPLINT->cb->getPathsInfo(hero)->getPathInfo(mapPos);
assert(pathNode);
if((GH.isKeyboardAltDown() || settings["gameTweaks"]["forceMovementInfo"].Bool()) && pathNode->reachable()) //overwrite status bar text with movement info
{
showMoveDetailsInStatusbar(*hero, *pathNode);
}
int turns = pathNode->turns;
vstd::amin(turns, 3);
switch(pathNode->action)
{
case CGPathNode::NORMAL:
case CGPathNode::TELEPORT_NORMAL:
if(pathNode->layer == EPathfindingLayer::LAND)
CCS->curh->set(cursorMove[turns]);
else
CCS->curh->set(cursorSailVisit[turns]);
break;
case CGPathNode::VISIT:
case CGPathNode::BLOCKING_VISIT:
case CGPathNode::TELEPORT_BLOCKING_VISIT:
if(objAtTile && objAtTile->ID == Obj::HERO)
{
if(LOCPLINT->localState->getCurrentArmy() == objAtTile)
CCS->curh->set(Cursor::Map::HERO);
else
CCS->curh->set(cursorExchange[turns]);
}
else if(pathNode->layer == EPathfindingLayer::LAND)
CCS->curh->set(cursorVisit[turns]);
else
CCS->curh->set(cursorSailVisit[turns]);
break;
case CGPathNode::BATTLE:
case CGPathNode::TELEPORT_BATTLE:
CCS->curh->set(cursorAttack[turns]);
break;
case CGPathNode::EMBARK:
CCS->curh->set(cursorSail[turns]);
break;
case CGPathNode::DISEMBARK:
CCS->curh->set(cursorDisembark[turns]);
break;
default:
if(objAtTile && objRelations != PlayerRelations::ENEMIES)
{
if(objAtTile->ID == Obj::TOWN)
CCS->curh->set(Cursor::Map::TOWN);
else if(objAtTile->ID == Obj::HERO && objRelations == PlayerRelations::SAME_PLAYER)
CCS->curh->set(Cursor::Map::HERO);
else
CCS->curh->set(Cursor::Map::POINTER);
}
else
CCS->curh->set(Cursor::Map::POINTER);
break;
}
}
if(ourInaccessibleShipyard(objAtTile))
{
CCS->curh->set(Cursor::Map::T1_SAIL);
}
}
void AdventureMapInterface::showMoveDetailsInStatusbar(const CGHeroInstance & hero, const CGPathNode & pathNode)
{
const int maxMovementPointsAtStartOfLastTurn = pathNode.turns > 0 ? hero.maxMovePoints(pathNode.layer == EPathfindingLayer::LAND) : hero.movement;
const int movementPointsLastTurnCost = maxMovementPointsAtStartOfLastTurn - pathNode.moveRemains;
const int remainingPointsAfterMove = pathNode.turns == 0 ? pathNode.moveRemains : 0;
std::string result = VLC->generaltexth->translate("vcmi.adventureMap", pathNode.turns > 0 ? "moveCostDetails" : "moveCostDetailsNoTurns");
boost::replace_first(result, "%TURNS", std::to_string(pathNode.turns));
boost::replace_first(result, "%POINTS", std::to_string(movementPointsLastTurnCost));
boost::replace_first(result, "%REMAINING", std::to_string(remainingPointsAfterMove));
GH.statusbar->write(result);
}
void AdventureMapInterface::onTileRightClicked(const int3 &mapPos)
{
if(!shortcuts->optionMapViewActive())
return;
if(spellBeingCasted)
{
hotkeyAbortCastingMode();
return;
}
if(!LOCPLINT->cb->isVisible(mapPos))
{
CRClickPopup::createAndPush(VLC->generaltexth->allTexts[61]); //Uncharted Territory
return;
}
const CGObjectInstance * obj = getActiveObject(mapPos);
if(!obj)
{
// Bare or undiscovered terrain
const TerrainTile * tile = LOCPLINT->cb->getTile(mapPos);
if(tile)
{
std::string hlp = CGI->mh->getTerrainDescr(mapPos, true);
CRClickPopup::createAndPush(hlp);
}
return;
}
CRClickPopup::createAndPush(obj, GH.getCursorPosition(), ETextAlignment::CENTER);
}
void AdventureMapInterface::enterCastingMode(const CSpell * sp)
{
assert(sp->id == SpellID::SCUTTLE_BOAT || sp->id == SpellID::DIMENSION_DOOR);
spellBeingCasted = sp;
Settings config = settings.write["session"]["showSpellRange"];
config->Bool() = true;
setState(EAdventureState::CASTING_SPELL);
}
void AdventureMapInterface::exitCastingMode()
{
assert(spellBeingCasted);
spellBeingCasted = nullptr;
setState(EAdventureState::MAKING_TURN);
Settings config = settings.write["session"]["showSpellRange"];
config->Bool() = false;
}
void AdventureMapInterface::hotkeyAbortCastingMode()
{
exitCastingMode();
LOCPLINT->showInfoDialog(CGI->generaltexth->allTexts[731]); //Spell cancelled
}
void AdventureMapInterface::performSpellcasting(const int3 & dest)
{
SpellID id = spellBeingCasted->id;
exitCastingMode();
LOCPLINT->cb->castSpell(LOCPLINT->localState->getCurrentHero(), id, dest);
}
Rect AdventureMapInterface::terrainAreaPixels() const
{
return widget->getMapView()->pos;
}
const IShipyard * AdventureMapInterface::ourInaccessibleShipyard(const CGObjectInstance *obj) const
{
const IShipyard *ret = IShipyard::castFrom(obj);
if(!ret ||
obj->tempOwner != currentPlayerID ||
(CCS->curh->get<Cursor::Map>() != Cursor::Map::T1_SAIL && CCS->curh->get<Cursor::Map>() != Cursor::Map::POINTER))
return nullptr;
return ret;
}
void AdventureMapInterface::hotkeyExitWorldView()
{
setState(EAdventureState::MAKING_TURN);
widget->getMapView()->onViewMapActivated();
}
void AdventureMapInterface::openWorldView(int tileSize)
{
setState(EAdventureState::WORLD_VIEW);
widget->getMapView()->onViewWorldActivated(tileSize);
}
void AdventureMapInterface::openWorldView()
{
openWorldView(11);
}
void AdventureMapInterface::openWorldView(const std::vector<ObjectPosInfo>& objectPositions, bool showTerrain)
{
openWorldView(11);
widget->getMapView()->onViewSpellActivated(11, objectPositions, showTerrain);
}
void AdventureMapInterface::hotkeyNextTown()
{
widget->getTownList()->selectNext();
}
void AdventureMapInterface::hotkeySwitchMapLevel()
{
widget->getMapView()->onMapLevelSwitched();
}
void AdventureMapInterface::onScreenResize()
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
widget.reset();
pos.x = pos.y = 0;
pos.w = GH.screenDimensions().x;
pos.h = GH.screenDimensions().y;
widget = std::make_shared<AdventureMapWidget>(shortcuts);
widget->getMapView()->onViewMapActivated();
widget->setPlayer(currentPlayerID);
widget->updateActiveState();
widget->getMinimap()->update();
widget->getInfoBar()->showSelection();
adjustActiveness();
}

View File

@ -1,5 +1,5 @@
/*
* CAdvMapInt.h, part of VCMI engine
* AdventureMapInterface.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
@ -29,8 +29,8 @@ class CButton;
class IImage;
class CAnimImage;
class CGStatusBar;
class CAdvMapPanel;
class CAdvMapWorldViewPanel;
class AdventureMapWidget;
class AdventureMapShortcuts;
class CAnimation;
class MapView;
class CResDataBar;
@ -39,111 +39,56 @@ class CTownList;
class CInfoBar;
class CMinimap;
class MapAudioPlayer;
enum class EAdventureState;
struct MapDrawingInfo;
/// That's a huge class which handles general adventure map actions and
/// shows the right menu(questlog, spellbook, end turn,..) from where you
/// can get to the towns and heroes.
class CAdventureMapInterface : public CIntObject
class AdventureMapInterface : public CIntObject
{
private:
enum class EGameState
{
NOT_INITIALIZED,
HOTSEAT_WAIT,
MAKING_TURN,
ENEMY_TURN,
WORLD_VIEW
};
EGameState state;
/// currently acting player
PlayerColor currentPlayerID;
/// uses EDirections enum
bool scrollingCursorSet;
/// if true, cursor was changed to scrolling and must be reset back once scroll is over
bool scrollingWasActive;
const CSpell *spellBeingCasted; //nullptr if none
/// if true, then scrolling was blocked via ctrl and should not restart until player move cursor outside scrolling area
bool scrollingWasBlocked;
std::vector<std::shared_ptr<CAnimImage>> gems;
std::shared_ptr<IImage> bg;
std::shared_ptr<IImage> bgWorldView;
std::shared_ptr<CButton> kingOverview;
std::shared_ptr<CButton> sleepWake;
std::shared_ptr<CButton> underground;
std::shared_ptr<CButton> questlog;
std::shared_ptr<CButton> moveHero;
std::shared_ptr<CButton> spellbook;
std::shared_ptr<CButton> advOptions;
std::shared_ptr<CButton> sysOptions;
std::shared_ptr<CButton> nextHero;
std::shared_ptr<CButton> endTurn;
std::shared_ptr<CButton> worldViewUnderground;
std::shared_ptr<MapView> terrain;
std::shared_ptr<CMinimap> minimap;
std::shared_ptr<CHeroList> heroList;
std::shared_ptr<CTownList> townList;
std::shared_ptr<CInfoBar> infoBar;
std::shared_ptr<CGStatusBar> statusbar;
std::shared_ptr<CResDataBar> resdatabar;
std::shared_ptr<CAdvMapPanel> panelMain; // panel that holds all right-side buttons in normal view
std::shared_ptr<CAdvMapWorldViewPanel> panelWorldView; // panel that holds all buttons and other ui in world view
std::shared_ptr<CAdvMapPanel> activeMapPanel; // currently active panel (either main or world view, depending on current mode)
std::shared_ptr<CAnimation> worldViewIcons;// images for world view overlay
/// spell for which player is selecting target, or nullptr if none
const CSpell *spellBeingCasted;
std::shared_ptr<MapAudioPlayer> mapAudio;
std::shared_ptr<AdventureMapWidget> widget;
std::shared_ptr<AdventureMapShortcuts> shortcuts;
private:
//functions bound to buttons
void fshowOverview();
void fworldViewBack();
void fworldViewScale1x();
void fworldViewScale2x();
void fworldViewScale4x();
void fswitchLevel();
void fshowQuestlog();
void fsleepWake();
void fmoveHero();
void fshowSpellbok();
void fadventureOPtions();
void fsystemOptions();
void fnextHero();
void fendTurn();
void setState(EAdventureState state);
void hotkeyMoveHeroDirectional(Point direction);
/// updates active state of game window whenever game state changes
void adjustActiveness();
bool isActive();
void adjustActiveness(bool aiTurnStart); //should be called every time at AI/human turn transition; blocks GUI during AI turn
const IShipyard * ourInaccessibleShipyard(const CGObjectInstance *obj) const; //checks if obj is our ashipyard and cursor is 0,0 -> returns shipyard or nullptr else
// update locked state of buttons
void updateButtons();
/// checks if obj is our ashipyard and cursor is 0,0 -> returns shipyard or nullptr else
const IShipyard * ourInaccessibleShipyard(const CGObjectInstance *obj) const;
/// check and if necessary reacts on scrolling by moving cursor to screen edge
void handleMapScrollingUpdate();
void showMoveDetailsInStatusbar(const CGHeroInstance & hero, const CGPathNode & pathNode);
const CGObjectInstance *getActiveObject(const int3 &tile);
std::optional<Point> keyToMoveDirection(EShortcut key);
void endingTurn();
/// exits currently opened world view mode and returns to normal map
void exitWorldView();
void exitCastingMode();
void leaveCastingMode(const int3 & castTarget);
void abortCastingMode();
/// casts current spell at specified location
void performSpellcasting(const int3 & castTarget);
protected:
// CIntObject interface implementation
/// CIntObject interface implementation
void activate() override;
void deactivate() override;
@ -153,8 +98,16 @@ protected:
void keyPressed(EShortcut key) override;
void onScreenResize() override;
public:
CAdventureMapInterface();
AdventureMapInterface();
void hotkeyAbortCastingMode();
void hotkeyExitWorldView();
void hotkeyEndingTurn();
void hotkeyNextTown();
void hotkeySwitchMapLevel();
/// Called by PlayerInterface when specified player is ready to start his turn
void onHotseatWaitStarted(PlayerColor playerID);
@ -225,4 +178,4 @@ public:
void openWorldView(const std::vector<ObjectPosInfo>& objectPositions, bool showTerrain);
};
extern std::shared_ptr<CAdventureMapInterface> adventureInt;
extern std::shared_ptr<AdventureMapInterface> adventureInt;

View File

@ -0,0 +1,446 @@
/*
* AdventureMapShortcuts.cpp, part of VCMI engine
*
* 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"
#include "AdventureMapShortcuts.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../lobby/CSavingScreen.h"
#include "../mapView/mapHandler.h"
#include "../windows/CKingdomInterface.h"
#include "../windows/CSpellWindow.h"
#include "../windows/CTradeWindow.h"
#include "../windows/settings/SettingsMainWindow.h"
#include "AdventureMapInterface.h"
#include "AdventureOptions.h"
#include "AdventureState.h"
#include "../../CCallback.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CPathfinder.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/mapping/CMap.h"
AdventureMapShortcuts::AdventureMapShortcuts(AdventureMapInterface & owner)
: owner(owner)
, state(EAdventureState::NOT_INITIALIZED)
, mapLevel(0)
{}
void AdventureMapShortcuts::setState(EAdventureState newState)
{
state = newState;
}
void AdventureMapShortcuts::onMapViewMoved(const Rect & visibleArea, int newMapLevel)
{
mapLevel = newMapLevel;
}
std::vector<AdventureMapShortcutState> AdventureMapShortcuts::getShortcuts()
{
std::vector<AdventureMapShortcutState> result = {
{ EShortcut::ADVENTURE_KINGDOM_OVERVIEW, optionInMapView(), [this]() { this->showOverview(); } },
{ EShortcut::ADVENTURE_EXIT_WORLD_VIEW, optionInWorldView(), [this]() { this->worldViewBack(); } },
{ EShortcut::ADVENTURE_VIEW_WORLD, optionInMapView(), [this]() { this->worldViewScale1x(); } },
{ EShortcut::ADVENTURE_VIEW_WORLD_X1, optionInWorldView(), [this]() { this->worldViewScale1x(); } },
{ EShortcut::ADVENTURE_VIEW_WORLD_X2, optionInWorldView(), [this]() { this->worldViewScale2x(); } },
{ EShortcut::ADVENTURE_VIEW_WORLD_X4, optionInWorldView(), [this]() { this->worldViewScale4x(); } },
{ EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL, optionCanToggleLevel(), [this]() { this->switchMapLevel(); } },
{ EShortcut::ADVENTURE_QUEST_LOG, optionCanViewQuests(), [this]() { this->showQuestlog(); } },
{ EShortcut::ADVENTURE_TOGGLE_SLEEP, optionHeroSelected(), [this]() { this->toggleSleepWake(); } },
{ EShortcut::ADVENTURE_SET_HERO_ASLEEP, optionHeroAwake(), [this]() { this->setHeroSleeping(); } },
{ EShortcut::ADVENTURE_SET_HERO_AWAKE, optionHeroSleeping(), [this]() { this->setHeroAwake(); } },
{ EShortcut::ADVENTURE_MOVE_HERO, optionHeroCanMove(), [this]() { this->moveHeroAlongPath(); } },
{ EShortcut::ADVENTURE_CAST_SPELL, optionHeroSelected(), [this]() { this->showSpellbook(); } },
{ EShortcut::ADVENTURE_GAME_OPTIONS, optionInMapView(), [this]() { this->adventureOptions(); } },
{ EShortcut::GLOBAL_OPTIONS, optionInMapView(), [this]() { this->systemOptions(); } },
{ EShortcut::ADVENTURE_NEXT_HERO, optionHasNextHero(), [this]() { this->nextHero(); } },
{ EShortcut::GAME_END_TURN, optionInMapView(), [this]() { this->endTurn(); } },
{ EShortcut::ADVENTURE_THIEVES_GUILD, optionInMapView(), [this]() { this->showThievesGuild(); } },
{ EShortcut::ADVENTURE_VIEW_SCENARIO, optionInMapView(), [this]() { this->showScenarioInfo(); } },
{ EShortcut::GAME_SAVE_GAME, optionInMapView(), [this]() { this->saveGame(); } },
{ EShortcut::GAME_LOAD_GAME, optionInMapView(), [this]() { this->loadGame(); } },
{ EShortcut::ADVENTURE_DIG_GRAIL, optionHeroSelected(), [this]() { this->digGrail(); } },
{ EShortcut::ADVENTURE_VIEW_PUZZLE, optionSidePanelActive(),[this]() { this->viewPuzzleMap(); } },
{ EShortcut::GAME_RESTART_GAME, optionInMapView(), [this]() { this->restartGame(); } },
{ EShortcut::ADVENTURE_VISIT_OBJECT, optionHeroSelected(), [this]() { this->visitObject(); } },
{ EShortcut::ADVENTURE_VIEW_SELECTED, optionInMapView(), [this]() { this->openObject(); } },
{ EShortcut::GAME_OPEN_MARKETPLACE, optionInMapView(), [this]() { this->showMarketplace(); } },
{ EShortcut::ADVENTURE_NEXT_TOWN, optionInMapView(), [this]() { this->nextTown(); } },
{ EShortcut::ADVENTURE_NEXT_OBJECT, optionInMapView(), [this]() { this->nextObject(); } },
{ EShortcut::ADVENTURE_MOVE_HERO_SW, optionHeroSelected(), [this]() { this->moveHeroDirectional({-1, +1}); } },
{ EShortcut::ADVENTURE_MOVE_HERO_SS, optionHeroSelected(), [this]() { this->moveHeroDirectional({ 0, +1}); } },
{ EShortcut::ADVENTURE_MOVE_HERO_SE, optionHeroSelected(), [this]() { this->moveHeroDirectional({+1, +1}); } },
{ EShortcut::ADVENTURE_MOVE_HERO_WW, optionHeroSelected(), [this]() { this->moveHeroDirectional({-1, 0}); } },
{ EShortcut::ADVENTURE_MOVE_HERO_EE, optionHeroSelected(), [this]() { this->moveHeroDirectional({+1, 0}); } },
{ EShortcut::ADVENTURE_MOVE_HERO_NW, optionHeroSelected(), [this]() { this->moveHeroDirectional({-1, -1}); } },
{ EShortcut::ADVENTURE_MOVE_HERO_NN, optionHeroSelected(), [this]() { this->moveHeroDirectional({ 0, -1}); } },
{ EShortcut::ADVENTURE_MOVE_HERO_NE, optionHeroSelected(), [this]() { this->moveHeroDirectional({+1, -1}); } }
};
return result;
}
void AdventureMapShortcuts::showOverview()
{
GH.pushIntT<CKingdomInterface>();
}
void AdventureMapShortcuts::worldViewBack()
{
owner.hotkeyExitWorldView();
auto hero = LOCPLINT->localState->getCurrentHero();
if (hero)
owner.centerOnObject(hero);
}
void AdventureMapShortcuts::worldViewScale1x()
{
// TODO set corresponding scale button to "selected" mode
owner.openWorldView(7);
}
void AdventureMapShortcuts::worldViewScale2x()
{
owner.openWorldView(11);
}
void AdventureMapShortcuts::worldViewScale4x()
{
owner.openWorldView(16);
}
void AdventureMapShortcuts::switchMapLevel()
{
int maxLevels = LOCPLINT->cb->getMapSize().z;
if (maxLevels < 2)
return;
owner.hotkeySwitchMapLevel();
}
void AdventureMapShortcuts::showQuestlog()
{
LOCPLINT->showQuestLog();
}
void AdventureMapShortcuts::toggleSleepWake()
{
if (!optionHeroSelected())
return;
if (optionHeroAwake())
setHeroSleeping();
else
setHeroAwake();
}
void AdventureMapShortcuts::setHeroSleeping()
{
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
if (h)
{
LOCPLINT->localState->setHeroAsleep(h);
owner.onHeroChanged(h);
nextHero();
}
}
void AdventureMapShortcuts::setHeroAwake()
{
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
if (h)
{
LOCPLINT->localState->setHeroAwaken(h);
owner.onHeroChanged(h);
}
}
void AdventureMapShortcuts::moveHeroAlongPath()
{
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
if (!h || !LOCPLINT->localState->hasPath(h))
return;
LOCPLINT->moveHero(h, LOCPLINT->localState->getPath(h));
}
void AdventureMapShortcuts::showSpellbook()
{
if (!LOCPLINT->localState->getCurrentHero())
return;
owner.centerOnObject(LOCPLINT->localState->getCurrentHero());
GH.pushIntT<CSpellWindow>(LOCPLINT->localState->getCurrentHero(), LOCPLINT, false);
}
void AdventureMapShortcuts::adventureOptions()
{
GH.pushIntT<AdventureOptions>();
}
void AdventureMapShortcuts::systemOptions()
{
GH.pushIntT<SettingsMainWindow>();
}
void AdventureMapShortcuts::nextHero()
{
const auto * currHero = LOCPLINT->localState->getCurrentHero();
const auto * nextHero = LOCPLINT->localState->getNextWanderingHero(currHero);
if (nextHero)
{
LOCPLINT->localState->setSelection(nextHero);
owner.centerOnObject(nextHero);
}
}
void AdventureMapShortcuts::endTurn()
{
if(!LOCPLINT->makingTurn)
return;
if(settings["adventure"]["heroReminder"].Bool())
{
for(auto hero : LOCPLINT->localState->getWanderingHeroes())
{
if(!LOCPLINT->localState->isHeroSleeping(hero) && hero->movement > 0)
{
// Only show hero reminder if conditions met:
// - There still movement points
// - Hero don't have a path or there not points for first step on path
LOCPLINT->localState->verifyPath(hero);
if(!LOCPLINT->localState->hasPath(hero))
{
LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], [this](){ owner.hotkeyEndingTurn(); }, nullptr);
return;
}
auto path = LOCPLINT->localState->getPath(hero);
if (path.nodes.size() < 2 || path.nodes[path.nodes.size() - 2].turns)
{
LOCPLINT->showYesNoDialog( CGI->generaltexth->allTexts[55], [this](){ owner.hotkeyEndingTurn(); }, nullptr);
return;
}
}
}
}
owner.hotkeyEndingTurn();
}
void AdventureMapShortcuts::showThievesGuild()
{
//find first town with tavern
auto itr = range::find_if(LOCPLINT->localState->getOwnedTowns(), [](const CGTownInstance * town)
{
return town->hasBuilt(BuildingID::TAVERN);
});
if(itr != LOCPLINT->localState->getOwnedTowns().end())
LOCPLINT->showThievesGuildWindow(*itr);
else
LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithTavern"));
}
void AdventureMapShortcuts::showScenarioInfo()
{
AdventureOptions::showScenarioInfo();
}
void AdventureMapShortcuts::saveGame()
{
GH.pushIntT<CSavingScreen>();
}
void AdventureMapShortcuts::loadGame()
{
LOCPLINT->proposeLoadingGame();
}
void AdventureMapShortcuts::digGrail()
{
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
if(h && LOCPLINT->makingTurn)
LOCPLINT->tryDiggging(h);
}
void AdventureMapShortcuts::viewPuzzleMap()
{
LOCPLINT->showPuzzleMap();
}
void AdventureMapShortcuts::restartGame()
{
LOCPLINT->showYesNoDialog(CGI->generaltexth->translate("vcmi.adventureMap.confirmRestartGame"),
[](){ GH.pushUserEvent(EUserEvent::RESTART_GAME); }, nullptr);
}
void AdventureMapShortcuts::visitObject()
{
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
if(h)
LOCPLINT->cb->moveHero(h,h->pos);
}
void AdventureMapShortcuts::openObject()
{
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
const CGTownInstance *t = LOCPLINT->localState->getCurrentTown();
if(h)
LOCPLINT->openHeroWindow(h);
if(t)
LOCPLINT->openTownWindow(t);
}
void AdventureMapShortcuts::showMarketplace()
{
//check if we have any marketplace
const CGTownInstance *townWithMarket = nullptr;
for(const CGTownInstance *t : LOCPLINT->cb->getTownsInfo())
{
if(t->hasBuilt(BuildingID::MARKETPLACE))
{
townWithMarket = t;
break;
}
}
if(townWithMarket) //if any town has marketplace, open window
GH.pushIntT<CMarketplaceWindow>(townWithMarket);
else //if not - complain
LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.adventureMap.noTownWithMarket"));
}
void AdventureMapShortcuts::nextTown()
{
owner.hotkeyNextTown();
}
void AdventureMapShortcuts::nextObject()
{
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero();
const CGTownInstance *t = LOCPLINT->localState->getCurrentTown();
if(h)
nextHero();
if(t)
nextTown();
}
void AdventureMapShortcuts::moveHeroDirectional(const Point & direction)
{
const CGHeroInstance *h = LOCPLINT->localState->getCurrentHero(); //selected hero
if(!h)
return;
if (CGI->mh->hasOngoingAnimations())
return;
int3 dst = h->visitablePos() + int3(direction.x, direction.y, 0);
if (!CGI->mh->isInMap((dst)))
return;
if ( !LOCPLINT->localState->setPath(h, dst))
return;
const CGPath & path = LOCPLINT->localState->getPath(h);
if (path.nodes.size() > 2)
owner.onHeroChanged(h);
else
if(path.nodes[0].turns == 0)
LOCPLINT->moveHero(h, path);
}
bool AdventureMapShortcuts::optionCanViewQuests()
{
return optionInMapView() && CGI->mh->getMap()->quests.empty();
}
bool AdventureMapShortcuts::optionCanToggleLevel()
{
return optionInMapView() && LOCPLINT->cb->getMapSize().z > 0;
}
bool AdventureMapShortcuts::optionMapLevelSurface()
{
return mapLevel == 0;
}
bool AdventureMapShortcuts::optionHeroSleeping()
{
const CGHeroInstance *hero = LOCPLINT->localState->getCurrentHero();
return optionInMapView() && hero && LOCPLINT->localState->isHeroSleeping(hero);
}
bool AdventureMapShortcuts::optionHeroAwake()
{
const CGHeroInstance *hero = LOCPLINT->localState->getCurrentHero();
return optionInMapView() && hero && !LOCPLINT->localState->isHeroSleeping(hero);
}
bool AdventureMapShortcuts::optionHeroSelected()
{
return optionInMapView() && LOCPLINT->localState->getCurrentHero() != nullptr;
}
bool AdventureMapShortcuts::optionHeroCanMove()
{
const auto * hero = LOCPLINT->localState->getCurrentHero();
return optionInMapView() && hero && hero->movement != 0 && LOCPLINT->localState->hasPath(hero);
}
bool AdventureMapShortcuts::optionHasNextHero()
{
const auto * hero = LOCPLINT->localState->getCurrentHero();
const auto * nextSuitableHero = LOCPLINT->localState->getNextWanderingHero(hero);
return optionInMapView() && nextSuitableHero != nullptr;
}
bool AdventureMapShortcuts::optionSpellcasting()
{
return state == EAdventureState::CASTING_SPELL;
}
bool AdventureMapShortcuts::optionInMapView()
{
return state == EAdventureState::MAKING_TURN;
}
bool AdventureMapShortcuts::optionInWorldView()
{
return state == EAdventureState::WORLD_VIEW;
}
bool AdventureMapShortcuts::optionSidePanelActive()
{
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW;
}
bool AdventureMapShortcuts::optionMapViewActive()
{
return state == EAdventureState::MAKING_TURN || state == EAdventureState::WORLD_VIEW || state == EAdventureState::CASTING_SPELL;
}

View File

@ -0,0 +1,87 @@
/*
* AdventureMapShortcuts.h, part of VCMI engine
*
* 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
*
*/
#pragma once
VCMI_LIB_NAMESPACE_BEGIN
class Point;
class Rect;
VCMI_LIB_NAMESPACE_END
enum class EShortcut;
class AdventureMapInterface;
enum class EAdventureState;
struct AdventureMapShortcutState
{
EShortcut shortcut;
bool isEnabled;
std::function<void()> callback;
};
/// Class that contains list of functions for shortcuts available from adventure map
class AdventureMapShortcuts
{
AdventureMapInterface & owner;
EAdventureState state;
int mapLevel;
void showOverview();
void worldViewBack();
void worldViewScale1x();
void worldViewScale2x();
void worldViewScale4x();
void switchMapLevel();
void showQuestlog();
void toggleSleepWake();
void setHeroSleeping();
void setHeroAwake();
void moveHeroAlongPath();
void showSpellbook();
void adventureOptions();
void systemOptions();
void nextHero();
void endTurn();
void showThievesGuild();
void showScenarioInfo();
void saveGame();
void loadGame();
void digGrail();
void viewPuzzleMap();
void restartGame();
void visitObject();
void openObject();
void showMarketplace();
void nextTown();
void nextObject();
void moveHeroDirectional(const Point & direction);
public:
explicit AdventureMapShortcuts(AdventureMapInterface & owner);
std::vector<AdventureMapShortcutState> getShortcuts();
bool optionCanViewQuests();
bool optionCanToggleLevel();
bool optionMapLevelSurface();
bool optionHeroSleeping();
bool optionHeroAwake();
bool optionHeroSelected();
bool optionHeroCanMove();
bool optionHasNextHero();
bool optionSpellcasting();
bool optionInMapView();
bool optionInWorldView();
bool optionSidePanelActive();
bool optionMapViewActive();
void setState(EAdventureState newState);
void onMapViewMoved(const Rect & visibleArea, int mapLevel);
};

View File

@ -0,0 +1,455 @@
/*
* CAdventureMapWidget.cpp, part of VCMI engine
*
* 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"
#include "AdventureMapWidget.h"
#include "AdventureMapShortcuts.h"
#include "CInfoBar.h"
#include "CList.h"
#include "CMinimap.h"
#include "CResDataBar.h"
#include "AdventureState.h"
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../mapView/MapView.h"
#include "../render/CAnimation.h"
#include "../render/IImage.h"
#include "../widgets/Buttons.h"
#include "../widgets/Images.h"
#include "../widgets/TextControls.h"
#include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../../lib/StringConstants.h"
#include "../../lib/filesystem/ResourceID.h"
AdventureMapWidget::AdventureMapWidget( std::shared_ptr<AdventureMapShortcuts> shortcuts )
: shortcuts(shortcuts)
, mapLevel(0)
{
pos.x = pos.y = 0;
pos.w = GH.screenDimensions().x;
pos.h = GH.screenDimensions().y;
REGISTER_BUILDER("adventureInfobar", &AdventureMapWidget::buildInfobox );
REGISTER_BUILDER("adventureMapImage", &AdventureMapWidget::buildMapImage );
REGISTER_BUILDER("adventureMapButton", &AdventureMapWidget::buildMapButton );
REGISTER_BUILDER("adventureMapContainer", &AdventureMapWidget::buildMapContainer );
REGISTER_BUILDER("adventureMapGameArea", &AdventureMapWidget::buildMapGameArea );
REGISTER_BUILDER("adventureMapHeroList", &AdventureMapWidget::buildMapHeroList );
REGISTER_BUILDER("adventureMapIcon", &AdventureMapWidget::buildMapIcon );
REGISTER_BUILDER("adventureMapTownList", &AdventureMapWidget::buildMapTownList );
REGISTER_BUILDER("adventureMinimap", &AdventureMapWidget::buildMinimap );
REGISTER_BUILDER("adventureResourceDateBar", &AdventureMapWidget::buildResourceDateBar );
REGISTER_BUILDER("adventureStatusBar", &AdventureMapWidget::buildStatusBar );
REGISTER_BUILDER("adventurePlayerTexture", &AdventureMapWidget::buildTexturePlayerColored);
for (const auto & entry : shortcuts->getShortcuts())
addShortcut(entry.shortcut, entry.callback);
const JsonNode config(ResourceID("config/widgets/adventureMap.json"));
for(const auto & entry : config["options"]["imagesPlayerColored"].Vector())
{
ResourceID resourceName(entry.String(), EResType::IMAGE);
playerColorerImages.push_back(resourceName.getName());
}
build(config);
addUsedEvents(KEYBOARD);
}
void AdventureMapWidget::onMapViewMoved(const Rect & visibleArea, int newMapLevel)
{
if(mapLevel == newMapLevel)
return;
mapLevel = newMapLevel;
updateActiveState();
}
Rect AdventureMapWidget::readSourceArea(const JsonNode & source, const JsonNode & sourceCommon)
{
const auto & input = source.isNull() ? sourceCommon : source;
return readArea(input, Rect(Point(0, 0), Point(800, 600)));
}
Rect AdventureMapWidget::readTargetArea(const JsonNode & source)
{
if(subwidgetSizes.empty())
return readArea(source, pos);
return readArea(source, subwidgetSizes.back());
}
Rect AdventureMapWidget::readArea(const JsonNode & source, const Rect & boundingBox)
{
const auto & object = source.Struct();
if(object.count("left") + object.count("width") + object.count("right") != 2)
logGlobal->error("Invalid area definition in widget! Unable to load width!");
if(object.count("top") + object.count("height") + object.count("bottom") != 2)
logGlobal->error("Invalid area definition in widget! Unable to load height!");
int left = source["left"].Integer();
int width = source["width"].Integer();
int right = source["right"].Integer();
int top = source["top"].Integer();
int height = source["height"].Integer();
int bottom = source["bottom"].Integer();
Point topLeft(left, top);
Point dimensions(width, height);
if(source["left"].isNull())
topLeft.x = boundingBox.w - right - width;
if(source["width"].isNull())
dimensions.x = boundingBox.w - right - left;
if(source["top"].isNull())
topLeft.y = boundingBox.h - bottom - height;
if(source["height"].isNull())
dimensions.y = boundingBox.h - bottom - top;
return Rect(topLeft + boundingBox.topLeft(), dimensions);
}
std::shared_ptr<IImage> AdventureMapWidget::loadImage(const std::string & name)
{
ResourceID resource(name, EResType::IMAGE);
if(images.count(resource.getName()) == 0)
images[resource.getName()] = IImage::createFromFile(resource.getName());
return images[resource.getName()];
}
std::shared_ptr<CAnimation> AdventureMapWidget::loadAnimation(const std::string & name)
{
ResourceID resource(name, EResType::ANIMATION);
if(animations.count(resource.getName()) == 0)
animations[resource.getName()] = std::make_shared<CAnimation>(resource.getName());
return animations[resource.getName()];
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildInfobox(const JsonNode & input)
{
Rect area = readTargetArea(input["area"]);
infoBar = std::make_shared<CInfoBar>(area);
return infoBar;
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildMapImage(const JsonNode & input)
{
Rect targetArea = readTargetArea(input["area"]);
Rect sourceArea = readSourceArea(input["sourceArea"], input["area"]);
std::string image = input["image"].String();
return std::make_shared<CFilledTexture>(loadImage(image), targetArea, sourceArea);
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildMapButton(const JsonNode & input)
{
auto position = readTargetArea(input["area"]);
auto image = input["image"].String();
auto help = readHintText(input["help"]);
bool playerColored = input["playerColored"].Bool();
auto button = std::make_shared<CButton>(position.topLeft(), image, help, 0, EShortcut::NONE, playerColored);
loadButtonBorderColor(button, input["borderColor"]);
loadButtonHotkey(button, input["hotkey"]);
return button;
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildMapContainer(const JsonNode & input)
{
auto position = readTargetArea(input["area"]);
std::shared_ptr<CAdventureMapContainerWidget> result;
if (!input["exists"].isNull())
{
if (!input["exists"]["heightMin"].isNull() && input["exists"]["heightMin"].Integer() >= pos.h)
return nullptr;
if (!input["exists"]["heightMax"].isNull() && input["exists"]["heightMax"].Integer() < pos.h)
return nullptr;
if (!input["exists"]["widthMin"].isNull() && input["exists"]["widthMin"].Integer() >= pos.w)
return nullptr;
if (!input["exists"]["widthMax"].isNull() && input["exists"]["widthMax"].Integer() < pos.w)
return nullptr;
}
if (input["overlay"].Bool())
result = std::make_shared<CAdventureMapOverlayWidget>();
else
result = std::make_shared<CAdventureMapContainerWidget>();
result->disableCondition = input["hideWhen"].String();
result->moveBy(position.topLeft());
subwidgetSizes.push_back(position);
for(const auto & entry : input["items"].Vector())
{
auto widget = buildWidget(entry);
addWidget(entry["name"].String(), widget);
result->ownedChildren.push_back(widget);
// FIXME: remove cast and replace it with better check
if (std::dynamic_pointer_cast<CLabel>(widget) || std::dynamic_pointer_cast<CLabelGroup>(widget))
result->addChild(widget.get(), true);
else
result->addChild(widget.get(), false);
}
subwidgetSizes.pop_back();
return result;
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildMapGameArea(const JsonNode & input)
{
Rect area = readTargetArea(input["area"]);
mapView = std::make_shared<MapView>(area.topLeft(), area.dimensions());
return mapView;
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildMapHeroList(const JsonNode & input)
{
Rect area = readTargetArea(input["area"]);
subwidgetSizes.push_back(area);
Rect item = readTargetArea(input["item"]);
Point itemOffset(input["itemsOffset"]["x"].Integer(), input["itemsOffset"]["y"].Integer());
int itemsCount = input["itemsCount"].Integer();
auto result = std::make_shared<CHeroList>(itemsCount, area, item.topLeft() - area.topLeft(), itemOffset, LOCPLINT->localState->getWanderingHeroes().size());
if(!input["scrollUp"].isNull())
result->setScrollUpButton(std::dynamic_pointer_cast<CButton>(buildMapButton(input["scrollUp"])));
if(!input["scrollDown"].isNull())
result->setScrollDownButton(std::dynamic_pointer_cast<CButton>(buildMapButton(input["scrollDown"])));
subwidgetSizes.pop_back();
heroList = result;
return result;
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildMapIcon(const JsonNode & input)
{
Rect area = readTargetArea(input["area"]);
size_t index = input["index"].Integer();
size_t perPlayer = input["perPlayer"].Integer();
std::string image = input["image"].String();
return std::make_shared<CAdventureMapIcon>(area.topLeft(), loadAnimation(image), index, perPlayer);
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildMapTownList(const JsonNode & input)
{
Rect area = readTargetArea(input["area"]);
subwidgetSizes.push_back(area);
Rect item = readTargetArea(input["item"]);
Point itemOffset(input["itemsOffset"]["x"].Integer(), input["itemsOffset"]["y"].Integer());
int itemsCount = input["itemsCount"].Integer();
auto result = std::make_shared<CTownList>(itemsCount, area, item.topLeft() - area.topLeft(), itemOffset, LOCPLINT->localState->getOwnedTowns().size());
if(!input["scrollUp"].isNull())
result->setScrollUpButton(std::dynamic_pointer_cast<CButton>(buildMapButton(input["scrollUp"])));
if(!input["scrollDown"].isNull())
result->setScrollDownButton(std::dynamic_pointer_cast<CButton>(buildMapButton(input["scrollDown"])));
subwidgetSizes.pop_back();
townList = result;
return result;
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildMinimap(const JsonNode & input)
{
Rect area = readTargetArea(input["area"]);
minimap = std::make_shared<CMinimap>(area);
return minimap;
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildResourceDateBar(const JsonNode & input)
{
Rect area = readTargetArea(input["area"]);
std::string image = input["image"].String();
auto result = std::make_shared<CResDataBar>(image, area.topLeft());
for(auto i = 0; i < GameConstants::RESOURCE_QUANTITY; i++)
{
const auto & node = input[GameConstants::RESOURCE_NAMES[i]];
if(node.isNull())
continue;
result->setResourcePosition(GameResID(i), Point(node["x"].Integer(), node["y"].Integer()));
}
result->setDatePosition(Point(input["date"]["x"].Integer(), input["date"]["y"].Integer()));
return result;
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildStatusBar(const JsonNode & input)
{
Rect area = readTargetArea(input["area"]);
std::string image = input["image"].String();
auto background = std::make_shared<CFilledTexture>(image, area);
return CGStatusBar::create(background);
}
std::shared_ptr<CIntObject> AdventureMapWidget::buildTexturePlayerColored(const JsonNode & input)
{
logGlobal->debug("Building widget CFilledTexture");
auto image = input["image"].String();
Rect area = readTargetArea(input["area"]);
return std::make_shared<FilledTexturePlayerColored>(image, area);
}
std::shared_ptr<CHeroList> AdventureMapWidget::getHeroList()
{
return heroList;
}
std::shared_ptr<CTownList> AdventureMapWidget::getTownList()
{
return townList;
}
std::shared_ptr<CMinimap> AdventureMapWidget::getMinimap()
{
return minimap;
}
std::shared_ptr<MapView> AdventureMapWidget::getMapView()
{
return mapView;
}
std::shared_ptr<CInfoBar> AdventureMapWidget::getInfoBar()
{
return infoBar;
}
void AdventureMapWidget::setPlayer(const PlayerColor & player)
{
setPlayerChildren(this, player);
}
void AdventureMapWidget::setPlayerChildren(CIntObject * widget, const PlayerColor & player)
{
for(auto & entry : widget->children)
{
auto container = dynamic_cast<CAdventureMapContainerWidget *>(entry);
auto icon = dynamic_cast<CAdventureMapIcon *>(entry);
auto button = dynamic_cast<CButton *>(entry);
auto texture = dynamic_cast<FilledTexturePlayerColored *>(entry);
if(button)
button->setPlayerColor(player);
if(icon)
icon->setPlayer(player);
if(container)
setPlayerChildren(container, player);
if (texture)
texture->playerColored(player);
}
for(const auto & entry : playerColorerImages)
{
if(images.count(entry))
images[entry]->playerColored(player);
}
redraw();
}
CAdventureMapIcon::CAdventureMapIcon(const Point & position, std::shared_ptr<CAnimation> animation, size_t index, size_t iconsPerPlayer)
: index(index)
, iconsPerPlayer(iconsPerPlayer)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
pos += position;
image = std::make_shared<CAnimImage>(animation, index);
}
void CAdventureMapIcon::setPlayer(const PlayerColor & player)
{
image->setFrame(index + player.getNum() * iconsPerPlayer);
}
void CAdventureMapOverlayWidget::show(SDL_Surface * to)
{
CIntObject::showAll(to);
}
void AdventureMapWidget::updateActiveStateChildden(CIntObject * widget)
{
for(auto & entry : widget->children)
{
auto container = dynamic_cast<CAdventureMapContainerWidget *>(entry);
if (container)
{
if (container->disableCondition == "heroAwake")
container->setEnabled(!shortcuts->optionHeroSleeping());
if (container->disableCondition == "heroSleeping")
container->setEnabled(shortcuts->optionHeroSleeping());
if (container->disableCondition == "mapLayerSurface")
container->setEnabled(shortcuts->optionMapLevelSurface());
if (container->disableCondition == "mapLayerUnderground")
container->setEnabled(!shortcuts->optionMapLevelSurface());
if (container->disableCondition == "mapViewMode")
container->setEnabled(shortcuts->optionInWorldView());
if (container->disableCondition == "worldViewMode")
container->setEnabled(!shortcuts->optionInWorldView());
updateActiveStateChildden(container);
}
}
}
void AdventureMapWidget::updateActiveState()
{
updateActiveStateChildden(this);
for (auto entry: shortcuts->getShortcuts())
setShortcutBlocked(entry.shortcut, !entry.isEnabled);
}

View File

@ -0,0 +1,109 @@
/*
* CAdventureMapWidget.h, part of VCMI engine
*
* 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
*
*/
#pragma once
#include "../gui/InterfaceObjectConfigurable.h"
class CHeroList;
class CTownList;
class CMinimap;
class MapView;
class CInfoBar;
class IImage;
class AdventureMapShortcuts;
enum class EAdventureState;
/// Internal class of AdventureMapInterface that contains actual UI elements
class AdventureMapWidget : public InterfaceObjectConfigurable
{
int mapLevel;
/// temporary stack of sizes of currently building widgets
std::vector<Rect> subwidgetSizes;
/// list of images on which player-colored palette will be applied
std::vector<std::string> playerColorerImages;
/// list of named images shared between widgets
std::map<std::string, std::shared_ptr<IImage>> images;
std::map<std::string, std::shared_ptr<CAnimation>> animations;
/// Widgets that require access from adventure map
std::shared_ptr<CHeroList> heroList;
std::shared_ptr<CTownList> townList;
std::shared_ptr<CMinimap> minimap;
std::shared_ptr<MapView> mapView;
std::shared_ptr<CInfoBar> infoBar;
std::shared_ptr<AdventureMapShortcuts> shortcuts;
Rect readTargetArea(const JsonNode & source);
Rect readSourceArea(const JsonNode & source, const JsonNode & sourceCommon);
Rect readArea(const JsonNode & source, const Rect & boundingBox);
std::shared_ptr<IImage> loadImage(const std::string & name);
std::shared_ptr<CAnimation> loadAnimation(const std::string & name);
std::shared_ptr<CIntObject> buildInfobox(const JsonNode & input);
std::shared_ptr<CIntObject> buildMapImage(const JsonNode & input);
std::shared_ptr<CIntObject> buildMapButton(const JsonNode & input);
std::shared_ptr<CIntObject> buildMapContainer(const JsonNode & input);
std::shared_ptr<CIntObject> buildMapGameArea(const JsonNode & input);
std::shared_ptr<CIntObject> buildMapHeroList(const JsonNode & input);
std::shared_ptr<CIntObject> buildMapIcon(const JsonNode & input);
std::shared_ptr<CIntObject> buildMapTownList(const JsonNode & input);
std::shared_ptr<CIntObject> buildMinimap(const JsonNode & input);
std::shared_ptr<CIntObject> buildResourceDateBar(const JsonNode & input);
std::shared_ptr<CIntObject> buildStatusBar(const JsonNode & input);
std::shared_ptr<CIntObject> buildTexturePlayerColored(const JsonNode &);
void setPlayerChildren(CIntObject * widget, const PlayerColor & player);
void updateActiveStateChildden(CIntObject * widget);
public:
explicit AdventureMapWidget( std::shared_ptr<AdventureMapShortcuts> shortcuts );
std::shared_ptr<CHeroList> getHeroList();
std::shared_ptr<CTownList> getTownList();
std::shared_ptr<CMinimap> getMinimap();
std::shared_ptr<MapView> getMapView();
std::shared_ptr<CInfoBar> getInfoBar();
void setPlayer(const PlayerColor & player);
void onMapViewMoved(const Rect & visibleArea, int mapLevel);
void updateActiveState();
};
/// Small helper class that provides ownership for shared_ptr's of child elements
class CAdventureMapContainerWidget : public CIntObject
{
friend class AdventureMapWidget;
std::vector<std::shared_ptr<CIntObject>> ownedChildren;
std::string disableCondition;
};
class CAdventureMapOverlayWidget : public CAdventureMapContainerWidget
{
public:
void show(SDL_Surface * to) override;
};
/// Small helper class that provides player-colorable icon using animation file
class CAdventureMapIcon : public CIntObject
{
std::shared_ptr<CAnimImage> image;
size_t index;
size_t iconsPerPlayer;
public:
CAdventureMapIcon(const Point & position, std::shared_ptr<CAnimation> image, size_t index, size_t iconsPerPlayer);
void setPlayer(const PlayerColor & player);
};

View File

@ -9,7 +9,7 @@
*/
#include "StdInc.h"
#include "CAdventureOptions.h"
#include "AdventureOptions.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
@ -23,7 +23,7 @@
#include "../../CCallback.h"
#include "../../lib/StartInfo.h"
CAdventureOptions::CAdventureOptions()
AdventureOptions::AdventureOptions()
: CWindowObject(PLAYER_COLORED, "ADVOPTS")
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
@ -31,10 +31,10 @@ CAdventureOptions::CAdventureOptions()
viewWorld = std::make_shared<CButton>(Point(24, 23), "ADVVIEW.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_WORLD);
viewWorld->addCallback( [] { LOCPLINT->viewWorldMap(); });
exit = std::make_shared<CButton>(Point(204, 313), "IOK6432.DEF", CButton::tooltip(), std::bind(&CAdventureOptions::close, this), EShortcut::GLOBAL_RETURN);
exit = std::make_shared<CButton>(Point(204, 313), "IOK6432.DEF", CButton::tooltip(), std::bind(&AdventureOptions::close, this), EShortcut::GLOBAL_RETURN);
scenInfo = std::make_shared<CButton>(Point(24, 198), "ADVINFO.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_SCENARIO);
scenInfo->addCallback(CAdventureOptions::showScenarioInfo);
scenInfo->addCallback(AdventureOptions::showScenarioInfo);
puzzle = std::make_shared<CButton>(Point(24, 81), "ADVPUZ.DEF", CButton::tooltip(), [&](){ close(); }, EShortcut::ADVENTURE_VIEW_PUZZLE);
puzzle->addCallback(std::bind(&CPlayerInterface::showPuzzleMap, LOCPLINT));
@ -46,7 +46,7 @@ CAdventureOptions::CAdventureOptions()
dig->block(true);
}
void CAdventureOptions::showScenarioInfo()
void AdventureOptions::showScenarioInfo()
{
if(LOCPLINT->cb->getStartInfo()->campState)
{

View File

@ -14,7 +14,7 @@
class CButton;
/// Adventure options dialog where you can view the world, dig, play the replay of the last turn,...
class CAdventureOptions : public CWindowObject
class AdventureOptions : public CWindowObject
{
std::shared_ptr<CButton> exit;
std::shared_ptr<CButton> viewWorld;
@ -24,7 +24,7 @@ class CAdventureOptions : public CWindowObject
/*std::shared_ptr<CButton> replay*/
public:
CAdventureOptions();
AdventureOptions();
static void showScenarioInfo();
};

View File

@ -0,0 +1,20 @@
/*
* AdventureState.h, part of VCMI engine
*
* 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
*
*/
#pragma once
enum class EAdventureState
{
NOT_INITIALIZED,
HOTSEAT_WAIT,
MAKING_TURN,
ENEMY_TURN,
CASTING_SPELL,
WORLD_VIEW
};

View File

@ -1,94 +0,0 @@
/*
* CAdvMapPanel.cpp, part of VCMI engine
*
* 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"
#include "CAdvMapPanel.h"
#include "../widgets/Buttons.h"
#include "../widgets/Images.h"
#include "../render/CAnimation.h"
#include "../render/IImage.h"
#include "../gui/CGuiHandler.h"
CAdvMapPanel::CAdvMapPanel(std::shared_ptr<IImage> bg, Point position)
: CIntObject()
, background(bg)
{
defActions = 255;
recActions = 255;
pos.x += position.x;
pos.y += position.y;
if (bg)
{
pos.w = bg->width();
pos.h = bg->height();
}
}
void CAdvMapPanel::addChildColorableButton(std::shared_ptr<CButton> button)
{
colorableButtons.push_back(button);
addChildToPanel(button, ACTIVATE | DEACTIVATE);
}
void CAdvMapPanel::setPlayerColor(const PlayerColor & clr)
{
for(auto & button : colorableButtons)
{
button->setPlayerColor(clr);
}
}
void CAdvMapPanel::showAll(SDL_Surface * to)
{
if(background)
background->draw(to, pos.x, pos.y);
CIntObject::showAll(to);
}
void CAdvMapPanel::addChildToPanel(std::shared_ptr<CIntObject> obj, ui8 actions)
{
otherObjects.push_back(obj);
obj->recActions |= actions | SHOWALL;
obj->recActions &= ~DISPOSE;
addChild(obj.get(), false);
}
CAdvMapWorldViewPanel::CAdvMapWorldViewPanel(std::shared_ptr<CAnimation> _icons, std::shared_ptr<IImage> bg, Point position, int spaceBottom, const PlayerColor &color)
: CAdvMapPanel(bg, position), icons(_icons)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
int fillerHeight = bg ? spaceBottom - pos.y - pos.h : 0;
if(fillerHeight > 0)
{
backgroundFiller = std::make_shared<CFilledTexture>("DIBOXBCK", Rect(0, pos.h, pos.w, fillerHeight));
}
}
void CAdvMapWorldViewPanel::recolorIcons(const PlayerColor & color, int indexOffset)
{
assert(iconsData.size() == currentIcons.size());
for(size_t idx = 0; idx < iconsData.size(); idx++)
{
const auto & data = iconsData.at(idx);
currentIcons[idx]->setFrame(data.first + indexOffset);
}
}
void CAdvMapWorldViewPanel::addChildIcon(std::pair<int, Point> data, int indexOffset)
{
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
iconsData.push_back(data);
currentIcons.push_back(std::make_shared<CAnimImage>(icons, data.first + indexOffset, 0, data.second.x, data.second.y));
}

View File

@ -1,60 +0,0 @@
/*
* CAdvMapPanel.h, part of VCMI engine
*
* 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
*
*/
#pragma once
#include "../gui/CIntObject.h"
VCMI_LIB_NAMESPACE_BEGIN
class PlayerColor;
VCMI_LIB_NAMESPACE_END
class CAnimation;
class CAnimImage;
class CFilledTexture;
class CButton;
class IImage;
/// simple panel that contains other displayable elements; used to separate groups of controls
class CAdvMapPanel : public CIntObject
{
std::vector<std::shared_ptr<CButton>> colorableButtons;
std::vector<std::shared_ptr<CIntObject>> otherObjects;
/// the surface passed to this obj will be freed in dtor
std::shared_ptr<IImage> background;
public:
CAdvMapPanel(std::shared_ptr<IImage> bg, Point position);
void addChildToPanel(std::shared_ptr<CIntObject> obj, ui8 actions = 0);
void addChildColorableButton(std::shared_ptr<CButton> button);
/// recolors all buttons to given player color
void setPlayerColor(const PlayerColor & clr);
void showAll(SDL_Surface * to) override;
};
/// specialized version of CAdvMapPanel that handles recolorable def-based pictures for world view info panel
class CAdvMapWorldViewPanel : public CAdvMapPanel
{
/// data that allows reconstruction of panel info icons
std::vector<std::pair<int, Point>> iconsData;
/// ptrs to child-pictures constructed from iconsData
std::vector<std::shared_ptr<CAnimImage>> currentIcons;
/// surface drawn below world view panel on higher resolutions (won't be needed when world view panel is configured for extraResolutions mod)
std::shared_ptr<CFilledTexture> backgroundFiller;
std::shared_ptr<CAnimation> icons;
public:
CAdvMapWorldViewPanel(std::shared_ptr<CAnimation> _icons, std::shared_ptr<IImage> bg, Point position, int spaceBottom, const PlayerColor &color);
void addChildIcon(std::pair<int, Point> data, int indexOffset);
/// recreates all pictures from given def to recolor them according to current player color
void recolorIcons(const PlayerColor & color, int indexOffset);
};

File diff suppressed because it is too large Load Diff

View File

@ -19,6 +19,8 @@
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../render/Colors.h"
#include "../adventureMap/AdventureMapInterface.h"
#include "../windows/CMessage.h"
#include "../../CCallback.h"
#include "../../lib/CConfigHandler.h"
@ -83,24 +85,17 @@ void CInGameConsole::print(const std::string & txt)
// boost::unique_lock scope
{
boost::unique_lock<boost::mutex> lock(texts_mx);
int lineLen = conf.go()->ac.outputLineLength;
if(txt.size() < lineLen)
{
texts.push_back({txt, 0});
}
else
{
assert(lineLen);
for(int g = 0; g < txt.size() / lineLen + 1; ++g)
{
std::string part = txt.substr(g * lineLen, lineLen);
if(part.empty())
break;
// Maximum width for a text line is limited by:
// 1) width of adventure map terrain area, for when in-game console is on top of advmap
// 2) width of castle/battle window (fixed to 800) when this window is open
// 3) arbitrary selected left and right margins
int maxWidth = std::min( 800, adventureInt->terrainAreaPixels().w) - 100;
texts.push_back({part, 0});
}
}
auto splitText = CMessage::breakText(txt, maxWidth, FONT_MEDIUM);
for (auto const & entry : splitText)
texts.push_back({entry, 0});
while(texts.size() > maxDisplayedTexts)
texts.erase(texts.begin());

View File

@ -11,7 +11,7 @@
#include "StdInc.h"
#include "CInfoBar.h"
#include "CAdventureMapInterface.h"
#include "AdventureMapInterface.h"
#include "../widgets/CComponent.h"
#include "../widgets/Images.h"

View File

@ -11,15 +11,17 @@
#include "StdInc.h"
#include "CList.h"
#include "CAdventureMapInterface.h"
#include "AdventureMapInterface.h"
#include "../widgets/Images.h"
#include "../widgets/Buttons.h"
#include "../widgets/ObjectLists.h"
#include "../windows/InfoWindows.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../PlayerLocalState.h"
#include "../gui/CGuiHandler.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CHeroHandler.h"
@ -81,24 +83,44 @@ void CList::CListItem::onSelect(bool on)
redraw();
}
CList::CList(int Size, Point position, std::string btnUp, std::string btnDown, size_t listAmount, int helpUp, int helpDown, CListBox::CreateFunc create)
: CIntObject(0, position),
CList::CList(int Size, Rect widgetDimensions)
: CIntObject(0, widgetDimensions.topLeft()),
size(Size),
selected(nullptr)
{
pos.w = widgetDimensions.w;
pos.h = widgetDimensions.h;
}
void CList::showAll(SDL_Surface * to)
{
CSDL_Ext::fillRect(to, pos, Colors::BLACK);
CIntObject::showAll(to);
}
void CList::createList(Point firstItemPosition, Point itemPositionDelta, size_t listAmount)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
scrollUp = std::make_shared<CButton>(Point(0, 0), btnUp, CGI->generaltexth->zelp[helpUp]);
scrollDown = std::make_shared<CButton>(Point(0, scrollUp->pos.h + 32*(int)size), btnDown, CGI->generaltexth->zelp[helpDown]);
listBox = std::make_shared<CListBox>(std::bind(&CList::createItem, this, _1), firstItemPosition, itemPositionDelta, size, listAmount);
}
listBox = std::make_shared<CListBox>(create, Point(1,scrollUp->pos.h), Point(0, 32), size, listAmount);
void CList::setScrollUpButton(std::shared_ptr<CButton> button)
{
addChild(button.get());
//assign callback only after list was created
scrollUp = button;
scrollUp->addCallback(std::bind(&CListBox::moveToPrev, listBox));
scrollDown->addCallback(std::bind(&CListBox::moveToNext, listBox));
scrollUp->addCallback(std::bind(&CList::update, this));
scrollDown->addCallback(std::bind(&CList::update, this));
update();
}
void CList::setScrollDownButton(std::shared_ptr<CButton> button)
{
addChild(button.get());
scrollDown = button;
scrollDown->addCallback(std::bind(&CList::update, this));
scrollDown->addCallback(std::bind(&CListBox::moveToNext, listBox));
update();
}
@ -107,8 +129,11 @@ void CList::update()
bool onTop = listBox->getPos() == 0;
bool onBottom = listBox->getPos() + size >= listBox->size();
scrollUp->block(onTop);
scrollDown->block(onBottom);
if (scrollUp)
scrollUp->block(onTop);
if (scrollDown)
scrollDown->block(onBottom);
}
void CList::select(std::shared_ptr<CListItem> which)
@ -223,16 +248,17 @@ std::string CHeroList::CHeroItem::getHoverText()
return boost::str(boost::format(CGI->generaltexth->allTexts[15]) % hero->getNameTranslated() % hero->type->heroClass->getNameTranslated());
}
std::shared_ptr<CIntObject> CHeroList::createHeroItem(size_t index)
std::shared_ptr<CIntObject> CHeroList::createItem(size_t index)
{
if (LOCPLINT->localState->getWanderingHeroes().size() > index)
return std::make_shared<CHeroItem>(this, LOCPLINT->localState->getWanderingHero(index));
return std::make_shared<CEmptyHeroItem>();
}
CHeroList::CHeroList(int size, Point position, std::string btnUp, std::string btnDown):
CList(size, position, btnUp, btnDown, LOCPLINT->localState->getWanderingHeroes().size(), 303, 304, std::bind(&CHeroList::createHeroItem, this, _1))
CHeroList::CHeroList(int visibleItemsCount, Rect widgetPosition, Point firstItemOffset, Point itemOffsetDelta, size_t initialItemsCount)
: CList(visibleItemsCount, widgetPosition)
{
createList(firstItemOffset, itemOffsetDelta, initialItemsCount);
}
void CHeroList::select(const CGHeroInstance * hero)
@ -261,7 +287,7 @@ void CHeroList::update(const CGHeroInstance * hero)
CList::update();
}
std::shared_ptr<CIntObject> CTownList::createTownItem(size_t index)
std::shared_ptr<CIntObject> CTownList::createItem(size_t index)
{
if (LOCPLINT->localState->getOwnedTowns().size() > index)
return std::make_shared<CTownItem>(this, LOCPLINT->localState->getOwnedTown(index));
@ -312,9 +338,10 @@ std::string CTownList::CTownItem::getHoverText()
return town->getObjectName();
}
CTownList::CTownList(int size, Point position, std::string btnUp, std::string btnDown):
CList(size, position, btnUp, btnDown, LOCPLINT->localState->getOwnedTowns().size(), 306, 307, std::bind(&CTownList::createTownItem, this, _1))
CTownList::CTownList(int visibleItemsCount, Rect widgetPosition, Point firstItemOffset, Point itemOffsetDelta, size_t initialItemsCount)
: CList(visibleItemsCount, widgetPosition)
{
createList(firstItemOffset, itemOffsetDelta, initialItemsCount);
}
void CTownList::select(const CGTownInstance * town)

View File

@ -10,8 +10,6 @@
#pragma once
#include "../gui/CIntObject.h"
#include "../widgets/ObjectLists.h"
#include "../../lib/FunctionList.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -21,7 +19,9 @@ class CGTownInstance;
VCMI_LIB_NAMESPACE_END
class CListBox;
class CButton;
class CAnimImage;
/// Base UI Element for hero\town lists
class CList : public CIntObject
@ -53,23 +53,9 @@ protected:
virtual std::string getHoverText()=0;
};
std::shared_ptr<CListBox> listBox;
private:
const size_t size;
/**
* @brief CList - protected constructor
* @param size - maximal amount of visible at once items
* @param position - cordinates
* @param btnUp - path to image to use as top button
* @param btnDown - path to image to use as bottom button
* @param listAmount - amount of items in the list
* @param helpUp - index in zelp.txt for button help tooltip
* @param helpDown - index in zelp.txt for button help tooltip
* @param create - function for creating items in listbox
* @param destroy - function for deleting items in listbox
*/
CList(int size, Point position, std::string btnUp, std::string btnDown, size_t listAmount, int helpUp, int helpDown, CListBox::CreateFunc create);
//for selection\deselection
std::shared_ptr<CListItem> selected;
void select(std::shared_ptr<CListItem> which);
@ -78,8 +64,14 @@ protected:
std::shared_ptr<CButton> scrollUp;
std::shared_ptr<CButton> scrollDown;
/// should be called when list is invalidated
void update();
protected:
std::shared_ptr<CListBox> listBox;
CList(int size, Rect widgetDimensions);
void createList(Point firstItemPosition, Point itemPositionDelta, size_t listAmount);
virtual std::shared_ptr<CIntObject> createItem(size_t index) = 0;
public:
/// functions that will be called when selection changes
@ -88,10 +80,18 @@ public:
/// return index of currently selected element
int getSelectedIndex();
void setScrollUpButton(std::shared_ptr<CButton> button);
void setScrollDownButton(std::shared_ptr<CButton> button);
/// should be called when list is invalidated
void update();
/// set of methods to switch selection
void selectIndex(int which);
void selectNext();
void selectPrev();
void showAll(SDL_Surface * to) override;
};
/// List of heroes which is shown at the right of the adventure map screen
@ -125,13 +125,9 @@ class CHeroList : public CList
std::string getHoverText() override;
};
std::shared_ptr<CIntObject> createHeroItem(size_t index);
std::shared_ptr<CIntObject> createItem(size_t index);
public:
/**
* @brief CHeroList
* @param size, position, btnUp, btnDown @see CList::CList
*/
CHeroList(int size, Point position, std::string btnUp, std::string btnDown);
CHeroList(int visibleItemsCount, Rect widgetPosition, Point firstItemOffset, Point itemOffsetDelta, size_t initialItemsCount);
/// Select specific hero and scroll if needed
void select(const CGHeroInstance * hero = nullptr);
@ -159,13 +155,9 @@ class CTownList : public CList
std::string getHoverText() override;
};
std::shared_ptr<CIntObject> createTownItem(size_t index);
std::shared_ptr<CIntObject> createItem(size_t index) override;
public:
/**
* @brief CTownList
* @param size, position, btnUp, btnDown @see CList::CList
*/
CTownList(int size, Point position, std::string btnUp, std::string btnDown);
CTownList(int visibleItemsCount, Rect widgetPosition, Point firstItemOffset, Point itemOffsetDelta, size_t initialItemsCount);
/// Select specific town and scroll if needed
void select(const CGTownInstance * town = nullptr);

View File

@ -11,7 +11,7 @@
#include "StdInc.h"
#include "CMinimap.h"
#include "CAdventureMapInterface.h"
#include "AdventureMapInterface.h"
#include "../widgets/Images.h"
#include "../CGameInfo.h"

View File

@ -19,50 +19,40 @@
#include "../../CCallback.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/ResourceSet.h"
#define ADVOPT (conf.go()->ac)
CResDataBar::CResDataBar(const std::string & defname, int x, int y, int offx, int offy, int resdist, int datedist)
CResDataBar::CResDataBar(const std::string & imageName, const Point & position)
{
pos.x += x;
pos.y += y;
pos.x += position.x;
pos.y += position.y;
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
background = std::make_shared<CPicture>(defname, 0, 0);
background = std::make_shared<CPicture>(imageName, 0, 0);
background->colorize(LOCPLINT->playerID);
pos.w = background->pos.w;
pos.h = background->pos.h;
txtpos.resize(8);
for (int i = 0; i < 8 ; i++)
{
txtpos[i].first = pos.x + offx + resdist*i;
txtpos[i].second = pos.y + offy;
}
txtpos[7].first = txtpos[6].first + datedist;
addUsedEvents(RCLICK);
}
CResDataBar::CResDataBar()
CResDataBar::CResDataBar(const std::string & defname, int x, int y, int offx, int offy, int resdist, int datedist):
CResDataBar(defname, Point(x,y))
{
pos.x += ADVOPT.resdatabarX;
pos.y += ADVOPT.resdatabarY;
for (int i = 0; i < 7 ; i++)
resourcePositions[GameResID(i)] = Point( offx + resdist*i, offy );
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
background = std::make_shared<CPicture>(ADVOPT.resdatabarG, 0, 0);
background->colorize(LOCPLINT->playerID);
datePosition = resourcePositions[EGameResID::GOLD] + Point(datedist, 0);
}
pos.w = background->pos.w;
pos.h = background->pos.h;
txtpos.resize(8);
for (int i = 0; i < 8 ; i++)
{
txtpos[i].first = pos.x + ADVOPT.resOffsetX + ADVOPT.resDist*i;
txtpos[i].second = pos.y + ADVOPT.resOffsetY;
}
txtpos[7].first = txtpos[6].first + ADVOPT.resDateDist;
void CResDataBar::setDatePosition(const Point & position)
{
datePosition = position;
}
void CResDataBar::setResourcePosition(const GameResID & resource, const Point & position)
{
resourcePositions[resource] = position;
}
std::string CResDataBar::buildDateString()
@ -80,13 +70,15 @@ std::string CResDataBar::buildDateString()
void CResDataBar::draw(SDL_Surface * to)
{
//TODO: all this should be labels, but they require proper text update on change
for (GameResID i=EGameResID::WOOD; i <= GameResID(EGameResID::GOLD); ++i)
for (auto & entry : resourcePositions)
{
std::string text = std::to_string(LOCPLINT->cb->getResourceAmount(i));
std::string text = std::to_string(LOCPLINT->cb->getResourceAmount(entry.first));
graphics->fonts[FONT_SMALL]->renderTextLeft(to, text, Colors::WHITE, Point(txtpos[i].first, txtpos[i].second));
graphics->fonts[FONT_SMALL]->renderTextLeft(to, text, Colors::WHITE, pos.topLeft() + entry.second);
}
graphics->fonts[FONT_SMALL]->renderTextLeft(to, buildDateString(), Colors::WHITE, Point(txtpos[7].first, txtpos[7].second));
if (datePosition)
graphics->fonts[FONT_SMALL]->renderTextLeft(to, buildDateString(), Colors::WHITE, pos.topLeft() + *datePosition);
}
void CResDataBar::showAll(SDL_Surface * to)

View File

@ -11,6 +11,11 @@
#include "../gui/CIntObject.h"
VCMI_LIB_NAMESPACE_BEGIN
enum class EGameResID : int8_t;
using GameResID = Identifier<EGameResID>;
VCMI_LIB_NAMESPACE_END
/// Resources bar which shows information about how many gold, crystals,... you have
/// Current date is displayed too
class CResDataBar : public CIntObject
@ -19,14 +24,21 @@ class CResDataBar : public CIntObject
std::shared_ptr<CPicture> background;
std::vector<std::pair<int,int> > txtpos;
std::map<GameResID, Point> resourcePositions;
std::optional<Point> datePosition;
void draw(SDL_Surface * to);
public:
CResDataBar();
/// For dynamically-sized UI windows, e.g. adventure map interface
CResDataBar(const std::string & imageName, const Point & position);
/// For fixed-size UI windows, e.g. CastleInterface
CResDataBar(const std::string &defname, int x, int y, int offx, int offy, int resdist, int datedist);
void setDatePosition(const Point & position);
void setResourcePosition(const GameResID & resource, const Point & position);
void colorize(PlayerColor player);
void showAll(SDL_Surface * to) override;
};

View File

@ -29,7 +29,7 @@
#include "../gui/CursorHandler.h"
#include "../gui/CGuiHandler.h"
#include "../render/Canvas.h"
#include "../adventureMap/CAdventureMapInterface.h"
#include "../adventureMap/AdventureMapInterface.h"
#include "../../CCallback.h"
#include "../../lib/CStack.h"

View File

@ -72,7 +72,6 @@ BattleWindow::BattleWindow(BattleInterface & owner):
console = widget<BattleConsole>("console");
GH.statusbar = console;
owner.console = console;
owner.fieldController.reset( new BattleFieldController(owner));
@ -187,6 +186,11 @@ void BattleWindow::deactivate()
LOCPLINT->cingconsole->deactivate();
}
bool BattleWindow::captureThisKey(EShortcut key)
{
return owner.openingPlaying();
}
void BattleWindow::keyPressed(EShortcut key)
{
if (owner.openingPlaying())
@ -359,7 +363,7 @@ void BattleWindow::showAlternativeActionIcon(PossiblePlayerBattleAction action)
}
auto anim = std::make_shared<CAnimation>(iconName);
w->setImage(anim, false);
w->setImage(anim);
w->redraw();
}

View File

@ -85,6 +85,7 @@ public:
void activate() override;
void deactivate() override;
void keyPressed(EShortcut key) override;
bool captureThisKey(EShortcut key) override;
void clickRight(tribool down, bool previousState) override;
void show(SDL_Surface *to) override;
void showAll(SDL_Surface *to) override;

View File

@ -18,6 +18,7 @@
#include "../CGameInfo.h"
#include "../render/Colors.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../renderSDL/ScreenHandler.h"
#include "../CMT.h"
#include "../CPlayerInterface.h"
#include "../battle/BattleInterface.h"
@ -38,6 +39,8 @@
#include "ios/utils.h"
#endif
CGuiHandler GH;
extern std::queue<SDL_Event> SDLEventsQueue;
extern boost::mutex eventsM;
@ -95,9 +98,10 @@ void CGuiHandler::processLists(const ui16 activityFlag, std::function<void (std:
void CGuiHandler::init()
{
screenHandlerInstance = std::make_unique<ScreenHandler>();
shortcutsHandlerInstance = std::make_unique<ShortcutHandler>();
mainFPSmng = new CFramerateManager();
mainFPSmng->init(settings["video"]["targetfps"].Integer());
mainFPSmng = new CFramerateManager(settings["video"]["targetfps"].Integer());
isPointerRelativeMode = settings["general"]["userRelativePointer"].Bool();
pointerSpeedMultiplier = settings["general"]["relativePointerSpeedMultiplier"].Float();
}
@ -180,9 +184,8 @@ std::shared_ptr<IShowActivatable> CGuiHandler::topInt()
void CGuiHandler::totalRedraw()
{
#ifdef VCMI_ANDROID
SDL_FillRect(screen2, NULL, SDL_MapRGB(screen2->format, 0, 0, 0));
#endif
CSDL_Ext::fillSurface( screen2, Colors::BLACK);
for(auto & elem : objsToBlit)
elem->showAll(screen2);
CSDL_Ext::blitAt(screen2,0,0,screen);
@ -803,7 +806,26 @@ void CGuiHandler::pushUserEvent(EUserEvent usercode, void * userdata)
SDL_PushEvent(&event);
}
CFramerateManager::CFramerateManager()
IScreenHandler & CGuiHandler::screenHandler()
{
return *screenHandlerInstance;
}
void CGuiHandler::onScreenResize()
{
for (auto const & entry : listInt)
{
auto intObject = std::dynamic_pointer_cast<CIntObject>(entry);
if (intObject)
intObject->onScreenResize();
}
totalRedraw();
}
CFramerateManager::CFramerateManager(int newRate)
: rate(0)
, rateticks(0)
, fps(0)
@ -811,7 +833,9 @@ CFramerateManager::CFramerateManager()
, accumulatedTime(0)
, lastticks(0)
, timeElapsed(0)
{}
{
init(newRate);
}
void CFramerateManager::init(int newRate)
{

View File

@ -29,6 +29,7 @@ class CIntObject;
class IUpdateable;
class IShowActivatable;
class IShowable;
class IScreenHandler;
// TODO: event handling need refactoring
enum class EUserEvent
@ -56,7 +57,7 @@ private:
ui32 accumulatedFrames;
public:
CFramerateManager(); // initializes the manager with a given fps rate
CFramerateManager(int newRate); // initializes the manager with a given fps rate
void init(int newRate); // needs to be called directly before the main game loop to reset the internal timer
void framerateDelay(); // needs to be called every game update cycle
ui32 getElapsedMilliseconds() const {return this->timeElapsed;}
@ -95,6 +96,7 @@ private:
CIntObjectList doubleClickInterested;
CIntObjectList textInterested;
std::unique_ptr<IScreenHandler> screenHandlerInstance;
void handleMouseButtonClick(CIntObjectList & interestedObjs, MouseButton btn, bool isPressed);
void processLists(const ui16 activityFlag, std::function<void (std::list<CIntObject*> *)> cb);
@ -135,6 +137,8 @@ public:
/// moves mouse pointer into specified position inside vcmi window
void moveCursorToPosition(const Point & position);
IScreenHandler & screenHandler();
IUpdateable *curInt;
Point lastClick;
@ -156,6 +160,9 @@ public:
void totalRedraw(); //forces total redraw (using showAll), sets a flag, method gets called at the end of the rendering
void simpleRedraw(); //update only top interface and draw background from buffer, sets a flag, method gets called at the end of the rendering
/// called whenever user selects different resolution, requiring to center/resize all windows
void onScreenResize();
void pushInt(std::shared_ptr<IShowActivatable> newInt); //deactivate old top interface, activates this one and pushes to the top
template <typename T, typename ... Args>
void pushIntT(Args && ... args)

View File

@ -159,16 +159,6 @@ void CIntObject::printAtMiddleLoc(const std::string & text, const Point &p, EFon
graphics->fonts[font]->renderTextCenter(dst, text, kolor, pos.topLeft() + p);
}
void CIntObject::blitAtLoc( SDL_Surface * src, int x, int y, SDL_Surface * dst )
{
CSDL_Ext::blitAt(src, pos.x + x, pos.y + y, dst);
}
void CIntObject::blitAtLoc(SDL_Surface * src, const Point &p, SDL_Surface * dst)
{
blitAtLoc(src, p.x, p.y, dst);
}
void CIntObject::printAtMiddleWBLoc( const std::string & text, int x, int y, EFonts font, int charpr, SDL_Color kolor, SDL_Surface * dst)
{
graphics->fonts[font]->renderTextLinesCenter(dst, CMessage::breakText(text, charpr, font), kolor, Point(pos.x + x, pos.y + y));
@ -199,11 +189,22 @@ void CIntObject::disable()
void CIntObject::enable()
{
if(!active_m && (!parent_m || parent_m->active))
{
activate();
redraw();
}
recActions = 255;
}
void CIntObject::setEnabled(bool on)
{
if (on)
enable();
else
disable();
}
void CIntObject::fitToScreen(int borderWidth, bool propagate)
{
Point newPos = pos.topLeft();
@ -242,7 +243,7 @@ void CIntObject::addChild(CIntObject * child, bool adjustPosition)
children.push_back(child);
child->parent_m = this;
if(adjustPosition)
child->pos += pos.topLeft();
child->moveBy(pos.topLeft(), adjustPosition);
if (!active && child->active)
child->deactivate();
@ -286,6 +287,11 @@ void CIntObject::redraw()
}
}
void CIntObject::onScreenResize()
{
center(pos, true);
}
const Rect & CIntObject::center( const Rect &r, bool propagate )
{
pos.w = r.w;

View File

@ -53,7 +53,7 @@ class IShowActivatable : public IShowable, public IActivatable
{
public:
//redraw parent flag - this int may be semi-transparent and require redraw of parent window
enum {BLOCK_ADV_HOTKEYS = 2, REDRAW_PARENT=8};
enum {REDRAW_PARENT=8};
int type; //bin flags using etype
IShowActivatable();
virtual ~IShowActivatable(){};
@ -140,8 +140,13 @@ public:
ui8 defActions; //which calls will be tried to be redirected to children
ui8 recActions; //which calls we allow to receive from parent
void disable(); //deactivates if needed, blocks all automatic activity, allows only disposal
void enable(); //activates if needed, all activity enabled (Warning: may not be symetric with disable if recActions was limited!)
/// deactivates if needed, blocks all automatic activity, allows only disposal
void disable();
/// activates if needed, all activity enabled (Warning: may not be symetric with disable if recActions was limited!)
void enable();
/// deactivates or activates UI element based on flag
void setEnabled(bool on);
// activate or deactivate object. Inactive object won't receive any input events (keyboard\mouse)
// usually used automatically by parent
@ -155,6 +160,10 @@ public:
//request complete redraw of this object
void redraw() override;
/// called only for windows whenever screen size changes
/// default behavior is to re-center, can be overriden
virtual void onScreenResize();
const Rect & center(const Rect &r, bool propagate = true); //sets pos so that r will be in the center of screen, assigns sizes of r to pos, returns new position
const Rect & center(const Point &p, bool propagate = true); //moves object so that point p will be in its center
const Rect & center(bool propagate = true); //centers when pos.w and pos.h are set, returns new position
@ -177,10 +186,6 @@ public:
void printAtMiddleLoc(const std::string & text, const Point &p, EFonts font, SDL_Color color, SDL_Surface * dst);
void printAtMiddleWBLoc(const std::string & text, int x, int y, EFonts font, int charsPerLine, SDL_Color color, SDL_Surface * dst);
//image blitting. If possible use CPicture or CAnimImage instead
void blitAtLoc(SDL_Surface * src, int x, int y, SDL_Surface * dst);
void blitAtLoc(SDL_Surface * src, const Point &p, SDL_Surface * dst);
friend class CGuiHandler;
};

View File

@ -82,15 +82,26 @@ void InterfaceObjectConfigurable::build(const JsonNode &config)
items = &config["items"];
}
const std::string unnamedObjectPrefix = "__widget_";
for(const auto & item : items->Vector())
{
std::string name = item["name"].isNull()
? unnamedObjectPrefix + std::to_string(unnamedObjectId++)
: item["name"].String();
logGlobal->debug("Building widget with name %s", name);
widgets[name] = buildWidget(item);
}
addWidget(item["name"].String(), buildWidget(item));
}
void InterfaceObjectConfigurable::addWidget(const std::string & namePreferred, std::shared_ptr<CIntObject> widget)
{
static const std::string unnamedObjectPrefix = "__widget_";
std::string nameActual;
if (widgets.count(namePreferred) == 0)
nameActual = namePreferred;
else
logGlobal->error("Duplicated widget name: '%s'", namePreferred);
if (nameActual.empty())
nameActual = unnamedObjectPrefix + std::to_string(unnamedObjectId++);
logGlobal->debug("Building widget with name %s", nameActual);
widgets[nameActual] = widget;
}
std::string InterfaceObjectConfigurable::readText(const JsonNode & config) const
@ -174,6 +185,8 @@ EFonts InterfaceObjectConfigurable::readFont(const JsonNode & config) const
return EFonts::FONT_SMALL;
if(config.String() == "tiny")
return EFonts::FONT_TINY;
if(config.String() == "calisto")
return EFonts::FONT_CALLI;
}
logGlobal->debug("Uknown font attribute");
return EFonts::FONT_TIMES;
@ -287,15 +300,7 @@ std::shared_ptr<CToggleButton> InterfaceObjectConfigurable::buildToggleButton(co
assert(imgOrder.size() >= 4);
button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
}
if(!config["callback"].isNull())
{
std::string callbackName = config["callback"].String();
if (callbacks.count(callbackName))
button->addCallback(callbacks.at(callbackName));
else
logGlobal->error("Invalid callback '%s' in widget", callbackName );
}
loadButtonCallback(button, config["callback"]);
return button;
}
@ -319,32 +324,56 @@ std::shared_ptr<CButton> InterfaceObjectConfigurable::buildButton(const JsonNode
assert(imgOrder.size() >= 4);
button->setImageOrder(imgOrder[0].Integer(), imgOrder[1].Integer(), imgOrder[2].Integer(), imgOrder[3].Integer());
}
if(!config["callback"].isNull())
{
std::string callbackName = config["callback"].String();
if (callbacks.count(callbackName) > 0)
button->addCallback(std::bind(callbacks.at(callbackName), 0));
else
logGlobal->error("Invalid callback '%s' in widget", callbackName );
}
if(!config["hotkey"].isNull())
{
if(config["hotkey"].getType() == JsonNode::JsonType::DATA_STRING)
{
button->assignedKey = readHotkey(config["hotkey"]);
auto target = shortcuts.find(button->assignedKey);
if (target != shortcuts.end())
{
button->addCallback(target->second.callback);
target->second.assignedToButton = true;
}
}
}
loadButtonBorderColor(button, config["borderColor"]);
loadButtonCallback(button, config["callback"]);
loadButtonHotkey(button, config["hotkey"]);
return button;
}
void InterfaceObjectConfigurable::loadButtonBorderColor(std::shared_ptr<CButton> button, const JsonNode & config) const
{
if (config.isNull())
return;
auto color = readColor(config);
button->setBorderColor(color);
}
void InterfaceObjectConfigurable::loadButtonCallback(std::shared_ptr<CButton> button, const JsonNode & config) const
{
if(config.isNull())
return;
std::string callbackName = config.String();
if (callbacks.count(callbackName) > 0)
button->addCallback(std::bind(callbacks.at(callbackName), 0));
else
logGlobal->error("Invalid callback '%s' in widget", callbackName );
}
void InterfaceObjectConfigurable::loadButtonHotkey(std::shared_ptr<CButton> button, const JsonNode & config) const
{
if(config.isNull())
return;
if(config.getType() != JsonNode::JsonType::DATA_STRING)
{
logGlobal->error("Invalid shortcut format - string expected!");
return;
}
button->assignedKey = readHotkey(config);
auto target = shortcuts.find(button->assignedKey);
if (target == shortcuts.end())
return;
button->addCallback(target->second.callback);
target->second.assignedToButton = true;
}
std::shared_ptr<CLabelGroup> InterfaceObjectConfigurable::buildLabelGroup(const JsonNode & config) const
{
logGlobal->debug("Building widget CLabelGroup");
@ -374,7 +403,8 @@ std::shared_ptr<CSlider> InterfaceObjectConfigurable::buildSlider(const JsonNode
auto itemsTotal = config["itemsTotal"].Integer();
auto value = config["selected"].Integer();
bool horizontal = config["orientation"].String() == "horizontal";
auto const & result = std::make_shared<CSlider>(position, length, callbacks.at(config["callback"].String()), itemsVisible, itemsTotal, value, horizontal, style);
const auto & result =
std::make_shared<CSlider>(position, length, callbacks.at(config["callback"].String()), itemsVisible, itemsTotal, value, horizontal, style);
if (!config["scrollBounds"].isNull())
{

View File

@ -48,6 +48,8 @@ protected:
//must be called after adding callbacks
void build(const JsonNode & config);
void addWidget(const std::string & name, std::shared_ptr<CIntObject> widget);
void addCallback(const std::string & callbackName, std::function<void(int)> callback);
JsonNode variables;
@ -73,6 +75,10 @@ protected:
std::pair<std::string, std::string> readHintText(const JsonNode &) const;
EShortcut readHotkey(const JsonNode &) const;
void loadButtonCallback(std::shared_ptr<CButton> button, const JsonNode & config) const;
void loadButtonHotkey(std::shared_ptr<CButton> button, const JsonNode & config) const;
void loadButtonBorderColor(std::shared_ptr<CButton> button, const JsonNode & config) const;
//basic widgets
std::shared_ptr<CPicture> buildPicture(const JsonNode &) const;
std::shared_ptr<CLabel> buildLabel(const JsonNode &) const;

View File

@ -81,7 +81,9 @@ enum class EShortcut
// Adventure map screen
ADVENTURE_GAME_OPTIONS, // 'o', Open CAdventureOptions window
ADVENTURE_TOGGLE_GRID, // F6, Toggles map grid
ADVENTURE_TOGGLE_SLEEP, // z,w, Toggles hero sleep status
ADVENTURE_TOGGLE_SLEEP, // Toggles hero sleep status
ADVENTURE_SET_HERO_ASLEEP, // Moves hero to sleep state
ADVENTURE_SET_HERO_AWAKE, // Move hero to awake state
ADVENTURE_MOVE_HERO, // Moves hero alongside set path
ADVENTURE_VISIT_OBJECT, // Revisits object hero is standing on
ADVENTURE_VIEW_SELECTED,// Open window with currently selected hero/town
@ -94,12 +96,15 @@ enum class EShortcut
ADVENTURE_DIG_GRAIL,
ADVENTURE_VIEW_PUZZLE,
ADVENTURE_VIEW_WORLD,
ADVENTURE_VIEW_WORLD_X1,
ADVENTURE_VIEW_WORLD_X2,
ADVENTURE_VIEW_WORLD_X4,
ADVENTURE_TOGGLE_MAP_LEVEL,
ADVENTURE_KINGDOM_OVERVIEW,
ADVENTURE_QUEST_LOG,
ADVENTURE_CAST_SPELL,
ADVENTURE_END_TURN,
ADVENTURE_THIEVES_GUILD,
ADVENTURE_EXIT_WORLD_VIEW,
// Move hero one tile in specified direction. Bound to cursors & numpad buttons
ADVENTURE_MOVE_HERO_SW,
@ -153,4 +158,3 @@ enum class EShortcut
AFTER_LAST
};

View File

@ -64,6 +64,8 @@ std::vector<EShortcut> ShortcutHandler::translateKeycode(SDL_Keycode key) const
{SDLK_RETURN, EShortcut::LOBBY_LOAD_GAME },
{SDLK_KP_ENTER, EShortcut::LOBBY_LOAD_GAME },
{SDLK_s, EShortcut::LOBBY_SAVE_GAME },
{SDLK_RETURN, EShortcut::LOBBY_SAVE_GAME },
{SDLK_KP_ENTER, EShortcut::LOBBY_SAVE_GAME },
{SDLK_r, EShortcut::LOBBY_RANDOM_MAP },
{SDLK_h, EShortcut::LOBBY_HIDE_CHAT },
{SDLK_a, EShortcut::LOBBY_ADDITIONAL_OPTIONS },
@ -79,8 +81,8 @@ std::vector<EShortcut> ShortcutHandler::translateKeycode(SDL_Keycode key) const
{SDLK_TAB, EShortcut::GAME_ACTIVATE_CONSOLE },
{SDLK_o, EShortcut::ADVENTURE_GAME_OPTIONS },
{SDLK_F6, EShortcut::ADVENTURE_TOGGLE_GRID },
{SDLK_z, EShortcut::ADVENTURE_TOGGLE_SLEEP },
{SDLK_w, EShortcut::ADVENTURE_TOGGLE_SLEEP },
{SDLK_z, EShortcut::ADVENTURE_SET_HERO_ASLEEP },
{SDLK_w, EShortcut::ADVENTURE_SET_HERO_AWAKE },
{SDLK_m, EShortcut::ADVENTURE_MOVE_HERO },
{SDLK_SPACE, EShortcut::ADVENTURE_VISIT_OBJECT },
{SDLK_KP_1, EShortcut::ADVENTURE_MOVE_HERO_SW },
@ -106,11 +108,13 @@ std::vector<EShortcut> ShortcutHandler::translateKeycode(SDL_Keycode key) const
{SDLK_d, EShortcut::ADVENTURE_DIG_GRAIL },
{SDLK_p, EShortcut::ADVENTURE_VIEW_PUZZLE },
{SDLK_v, EShortcut::ADVENTURE_VIEW_WORLD },
{SDLK_1, EShortcut::ADVENTURE_VIEW_WORLD_X1 },
{SDLK_2, EShortcut::ADVENTURE_VIEW_WORLD_X2 },
{SDLK_4, EShortcut::ADVENTURE_VIEW_WORLD_X4 },
{SDLK_u, EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL},
{SDLK_k, EShortcut::ADVENTURE_KINGDOM_OVERVIEW},
{SDLK_q, EShortcut::ADVENTURE_QUEST_LOG },
{SDLK_c, EShortcut::ADVENTURE_CAST_SPELL },
{SDLK_e, EShortcut::ADVENTURE_END_TURN },
{SDLK_g, EShortcut::ADVENTURE_THIEVES_GUILD },
{SDLK_q, EShortcut::BATTLE_TOGGLE_QUEUE },
{SDLK_c, EShortcut::BATTLE_USE_CREATURE_SPELL },
@ -218,6 +222,8 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
{"adventureGameOptions", EShortcut::ADVENTURE_GAME_OPTIONS },
{"adventureToggleGrid", EShortcut::ADVENTURE_TOGGLE_GRID },
{"adventureToggleSleep", EShortcut::ADVENTURE_TOGGLE_SLEEP },
{"adventureSetHeroAsleep", EShortcut::ADVENTURE_SET_HERO_ASLEEP },
{"adventureSetHeroAwake", EShortcut::ADVENTURE_SET_HERO_AWAKE },
{"adventureMoveHero", EShortcut::ADVENTURE_MOVE_HERO },
{"adventureVisitObject", EShortcut::ADVENTURE_VISIT_OBJECT },
{"adventureMoveHeroSW", EShortcut::ADVENTURE_MOVE_HERO_SW },
@ -238,12 +244,15 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
{"adventureDigGrail", EShortcut::ADVENTURE_DIG_GRAIL },
{"adventureViewPuzzle", EShortcut::ADVENTURE_VIEW_PUZZLE },
{"adventureViewWorld", EShortcut::ADVENTURE_VIEW_WORLD },
{"adventureViewWorld1", EShortcut::ADVENTURE_VIEW_WORLD_X1 },
{"adventureViewWorld2", EShortcut::ADVENTURE_VIEW_WORLD_X2 },
{"adventureViewWorld4", EShortcut::ADVENTURE_VIEW_WORLD_X4 },
{"adventureToggleMapLevel", EShortcut::ADVENTURE_TOGGLE_MAP_LEVEL},
{"adventureKingdomOverview", EShortcut::ADVENTURE_KINGDOM_OVERVIEW},
{"adventureQuestLog", EShortcut::ADVENTURE_QUEST_LOG },
{"adventureCastSpell", EShortcut::ADVENTURE_CAST_SPELL },
{"adventureEndTurn", EShortcut::ADVENTURE_END_TURN },
{"adventureThievesGuild", EShortcut::ADVENTURE_THIEVES_GUILD },
{"adventureExitWorldView", EShortcut::ADVENTURE_EXIT_WORLD_VIEW },
{"battleToggleQueue", EShortcut::BATTLE_TOGGLE_QUEUE },
{"battleUseCreatureSpell", EShortcut::BATTLE_USE_CREATURE_SPELL },
{"battleSurrender", EShortcut::BATTLE_SURRENDER },
@ -278,85 +287,3 @@ EShortcut ShortcutHandler::findShortcut(const std::string & identifier ) const
return shortcutNames.at(identifier);
return EShortcut::NONE;
}

View File

@ -51,7 +51,7 @@ CLobbyScreen::CLobbyScreen(ESelectionScreen screenType)
buttonOptions = std::make_shared<CButton>(Point(411, 510), "GSPBUTT.DEF", CGI->generaltexth->zelp[46], std::bind(&CLobbyScreen::toggleTab, this, tabOpt), EShortcut::LOBBY_ADDITIONAL_OPTIONS);
};
buttonChat = std::make_shared<CButton>(Point(619, 83), "GSPBUT2.DEF", CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_HIDE_CHAT);
buttonChat = std::make_shared<CButton>(Point(619, 80), "GSPBUT2.DEF", CGI->generaltexth->zelp[48], std::bind(&CLobbyScreen::toggleChat, this), EShortcut::LOBBY_HIDE_CHAT);
buttonChat->addTextOverlay(CGI->generaltexth->allTexts[532], FONT_SMALL);
switch(screenType)

View File

@ -71,7 +71,6 @@ CSelectionBase::CSelectionBase(ESelectionScreen type)
: CWindowObject(BORDERED | SHADOW_DISABLED), ISelectionScreenInfo(type)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
IShowActivatable::type = BLOCK_ADV_HOTKEYS;
pos.w = 762;
pos.h = 584;
if(screenType == ESelectionScreen::campaignList)

View File

@ -31,6 +31,7 @@
#include "../../lib/NetPacksLobby.h"
#include "../../lib/CGeneralTextHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/CModHandler.h"
#include "../../lib/GameSettings.h"
#include "../../lib/filesystem/Filesystem.h"

View File

@ -299,6 +299,26 @@ CMainMenu::~CMainMenu()
GH.curInt = nullptr;
}
void CMainMenu::activate()
{
// check if screen was resized while main menu was inactive - e.g. in gameplay mode
if (pos.dimensions() != GH.screenDimensions())
onScreenResize();
CIntObject::activate();
}
void CMainMenu::onScreenResize()
{
pos.w = GH.screenDimensions().x;
pos.h = GH.screenDimensions().y;
menu = nullptr;
menu = std::make_shared<CMenuScreen>(CMainMenuConfig::get().getConfig()["window"]);
backgroundAroundMenu->pos = pos;
}
void CMainMenu::update()
{
if(CMM != this->shared_from_this()) //don't update if you are not a main interface

View File

@ -141,6 +141,8 @@ public:
std::shared_ptr<CMenuScreen> menu;
~CMainMenu();
void activate() override;
void onScreenResize() override;
void update() override;
static void openLobby(ESelectionScreen screenType, bool host, const std::vector<std::string> * names, ELoadMode loadMode);
static void openCampaignLobby(const std::string & campaignFileName);

View File

@ -17,7 +17,7 @@
#include "../../CCallback.h"
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../adventureMap/CAdventureMapInterface.h"
#include "../adventureMap/AdventureMapInterface.h"
#include "../../lib/mapObjects/CGHeroInstance.h"
#include "../../lib/mapping/CMap.h"

View File

@ -19,7 +19,7 @@
#include "../CGameInfo.h"
#include "../CPlayerInterface.h"
#include "../adventureMap/CAdventureMapInterface.h"
#include "../adventureMap/AdventureMapInterface.h"
#include "../gui/CGuiHandler.h"
#include "../render/CAnimation.h"
#include "../render/Canvas.h"

View File

@ -15,7 +15,7 @@
#include "MapViewModel.h"
#include "../CGameInfo.h"
#include "../adventureMap/CAdventureMapInterface.h"
#include "../adventureMap/AdventureMapInterface.h"
#include "../gui/CGuiHandler.h"
#include "../gui/CursorHandler.h"

View File

@ -17,7 +17,7 @@
#include "MapViewModel.h"
#include "../CPlayerInterface.h"
#include "../adventureMap/CAdventureMapInterface.h"
#include "../adventureMap/AdventureMapInterface.h"
#include "../gui/CGuiHandler.h"
#include "../../lib/CConfigHandler.h"

View File

@ -0,0 +1,36 @@
/*
* IScreenHandler.h, part of VCMI engine
*
* 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
*
*/
#pragma once
VCMI_LIB_NAMESPACE_BEGIN
class Point;
VCMI_LIB_NAMESPACE_END
class IScreenHandler
{
public:
virtual ~IScreenHandler() = default;
/// Updates window state after fullscreen state has been changed in settings
virtual void onScreenResize() = 0;
/// De-initializes window state
virtual void close() = 0;
/// Fills screen with black color, erasing any existing content
virtual void clearScreen() = 0;
/// Returns list of resolutions supported by current screen
virtual std::vector<Point> getSupportedResolutions() const = 0;
/// Returns <min, max> range of possible values for screen scaling percentage
virtual std::tuple<int, int> getSupportedScalingRange() const = 0;
};

View File

@ -861,43 +861,6 @@ void CSDL_Ext::getClipRect(SDL_Surface * src, Rect & other)
other = CSDL_Ext::fromSDL(rect);
}
bool CSDL_Ext::isResolutionSupported(const std::vector<Point> & resolutions, const Point toTest )
{
#if defined(VCMI_MOBILE)
// ios can use any resolution
// presumably, same goes for Android
return true;
#else
// in fullscreen only resolutions supported by monitor can be used
return vstd::contains(resolutions, toTest);
#endif
}
std::vector<Point> CSDL_Ext::getSupportedResolutions()
{
int displayID = SDL_GetWindowDisplayIndex(mainWindow);
return getSupportedResolutions(displayID);
}
std::vector<Point> CSDL_Ext::getSupportedResolutions( int displayIndex)
{
std::vector<Point> result;
int modesCount = SDL_GetNumDisplayModes(displayIndex);
for (int i =0; i < modesCount; ++i)
{
SDL_DisplayMode mode;
if (SDL_GetDisplayMode(displayIndex, i, &mode) != 0)
continue;
Point resolution(mode.w, mode.h);
result.push_back(resolution);
}
return result;
}
template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<2>(int, int);
template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<3>(int, int);
template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<4>(int, int);

View File

@ -105,11 +105,6 @@ using TColorPutterAlpha = void (*)(uint8_t *&, const uint8_t &, const uint8_t &,
void convertToGrayscaleBpp(SDL_Surface * surf, const Rect & rect);
void convertToGrayscale(SDL_Surface * surf, const Rect & rect);
bool isResolutionSupported(const std::vector<Point> & resolutions, const Point toTest);
std::vector<Point> getSupportedResolutions();
std::vector<Point> getSupportedResolutions(int displayIndex);
void setColorKey(SDL_Surface * surface, SDL_Color color);
///set key-color to 0,255,255

View File

@ -0,0 +1,510 @@
/*
* ScreenHandler.cpp, part of VCMI engine
*
* 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"
#include "ScreenHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/NotificationHandler.h"
#include "CMT.h"
#include "SDL_Extensions.h"
#ifdef VCMI_ANDROID
#include "../lib/CAndroidVMHelper.h"
#endif
#include <SDL.h>
// TODO: should be made into a private members of ScreenHandler
SDL_Window * mainWindow = nullptr;
SDL_Renderer * mainRenderer = nullptr;
SDL_Texture * screenTexture = nullptr;
SDL_Surface * screen = nullptr; //main screen surface
SDL_Surface * screen2 = nullptr; //and hlp surface (used to store not-active interfaces layer)
SDL_Surface * screenBuf = screen; //points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed
static const std::string NAME_AFFIX = "client";
static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
std::tuple<int, int> ScreenHandler::getSupportedScalingRange() const
{
// H3 resolution, any resolution smaller than that is not correctly supported
static const Point minResolution = {800, 600};
// arbitrary limit on *downscaling*. Allow some downscaling, if requested by user. Should be generally limited to 100+ for all but few devices
static const double minimalScaling = 50;
Point renderResolution = getPreferredRenderingResolution();
double maximalScalingWidth = 100.0 * renderResolution.x / minResolution.x;
double maximalScalingHeight = 100.0 * renderResolution.y / minResolution.y;
double maximalScaling = std::min(maximalScalingWidth, maximalScalingHeight);
return { minimalScaling, maximalScaling };
}
Point ScreenHandler::getPreferredLogicalResolution() const
{
Point renderResolution = getPreferredRenderingResolution();
auto [minimalScaling, maximalScaling] = getSupportedScalingRange();
int userScaling = settings["video"]["resolution"]["scaling"].Integer();
int scaling = std::clamp(userScaling, minimalScaling, maximalScaling);
Point logicalResolution = renderResolution * 100.0 / scaling;
return logicalResolution;
}
Point ScreenHandler::getPreferredRenderingResolution() const
{
if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED)
{
SDL_Rect bounds;
if (SDL_GetDisplayBounds(getPreferredDisplayIndex(), &bounds) == 0)
return Point(bounds.w, bounds.h);
}
const JsonNode & video = settings["video"];
int width = video["resolution"]["width"].Integer();
int height = video["resolution"]["height"].Integer();
return Point(width, height);
}
int ScreenHandler::getPreferredDisplayIndex() const
{
#ifdef VCMI_MOBILE
// Assuming no multiple screens on Android / ios?
return 0;
#else
if (mainWindow != nullptr)
{
int result = SDL_GetWindowDisplayIndex(mainWindow);
if (result >= 0)
return result;
}
return settings["video"]["displayIndex"].Integer();
#endif
}
EWindowMode ScreenHandler::getPreferredWindowMode() const
{
#ifdef VCMI_MOBILE
// On Android / ios game will always render to screen size
return EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED;
#else
const JsonNode & video = settings["video"];
bool fullscreen = video["fullscreen"].Bool();
bool realFullscreen = settings["video"]["realFullscreen"].Bool();
if (!fullscreen)
return EWindowMode::WINDOWED;
if (realFullscreen)
return EWindowMode::FULLSCREEN_EXCLUSIVE;
else
return EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED;
#endif
}
ScreenHandler::ScreenHandler()
{
#ifdef VCMI_WINDOWS
// set VCMI as "per-monitor DPI awareness". This completely disables any DPI-scaling by system.
// Might not be the best solution since VCMI can't automatically adjust to DPI changes (including moving to monitors with different DPI scaling)
// However this fixed unintuitive bug where player selects specific resolution for windowed mode, but ends up with completely different one due to scaling
// NOTE: requires SDL 2.24.
SDL_SetHint(SDL_HINT_WINDOWS_DPI_AWARENESS, "permonitor");
#endif
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_AUDIO))
{
logGlobal->error("Something was wrong: %s", SDL_GetError());
exit(-1);
}
const auto & logCallback = [](void * userdata, int category, SDL_LogPriority priority, const char * message)
{
logGlobal->debug("SDL(category %d; priority %d) %s", category, priority, message);
};
SDL_LogSetOutputFunction(logCallback, nullptr);
#ifdef VCMI_ANDROID
// manually setting egl pixel format, as a possible solution for sdl2<->android problem
// https://bugzilla.libsdl.org/show_bug.cgi?id=2291
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 6);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
#endif // VCMI_ANDROID
validateSettings();
recreateWindowAndScreenBuffers();
}
void ScreenHandler::recreateWindowAndScreenBuffers()
{
destroyScreenBuffers();
if(mainWindow == nullptr)
initializeWindow();
else
updateWindowState();
initializeScreenBuffers();
if(!settings["session"]["headless"].Bool() && settings["general"]["notifications"].Bool())
{
NotificationHandler::init(mainWindow);
}
}
void ScreenHandler::updateWindowState()
{
#ifndef VCMI_MOBILE
int displayIndex = getPreferredDisplayIndex();
switch(getPreferredWindowMode())
{
case EWindowMode::FULLSCREEN_EXCLUSIVE:
{
SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN);
SDL_DisplayMode mode;
SDL_GetDesktopDisplayMode(displayIndex, &mode);
Point resolution = getPreferredRenderingResolution();
mode.w = resolution.x;
mode.h = resolution.y;
SDL_SetWindowDisplayMode(mainWindow, &mode);
SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex));
return;
}
case EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED:
{
SDL_SetWindowFullscreen(mainWindow, SDL_WINDOW_FULLSCREEN_DESKTOP);
SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex), SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex));
return;
}
case EWindowMode::WINDOWED:
{
Point resolution = getPreferredRenderingResolution();
SDL_SetWindowFullscreen(mainWindow, 0);
SDL_SetWindowSize(mainWindow, resolution.x, resolution.y);
SDL_SetWindowPosition(mainWindow, SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex), SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex));
return;
}
}
#endif
}
void ScreenHandler::initializeWindow()
{
mainWindow = createWindow();
if(mainWindow == nullptr)
throw std::runtime_error("Unable to create window\n");
//create first available renderer if preferred not set. Use no flags, so HW accelerated will be preferred but SW renderer also will possible
mainRenderer = SDL_CreateRenderer(mainWindow, getPreferredRenderingDriver(), 0);
if(mainRenderer == nullptr)
throw std::runtime_error("Unable to create renderer\n");
SDL_RendererInfo info;
SDL_GetRendererInfo(mainRenderer, &info);
SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "best");
logGlobal->info("Created renderer %s", info.name);
}
void ScreenHandler::initializeScreenBuffers()
{
#ifdef VCMI_ENDIAN_BIG
int bmask = 0xff000000;
int gmask = 0x00ff0000;
int rmask = 0x0000ff00;
int amask = 0x000000ff;
#else
int bmask = 0x000000ff;
int gmask = 0x0000ff00;
int rmask = 0x00ff0000;
int amask = 0xFF000000;
#endif
auto logicalSize = getPreferredLogicalResolution();
SDL_RenderSetLogicalSize(mainRenderer, logicalSize.x, logicalSize.y);
screen = SDL_CreateRGBSurface(0, logicalSize.x, logicalSize.y, 32, rmask, gmask, bmask, amask);
if(nullptr == screen)
{
logGlobal->error("Unable to create surface %dx%d with %d bpp: %s", logicalSize.x, logicalSize.y, 32, SDL_GetError());
throw std::runtime_error("Unable to create surface");
}
//No blending for screen itself. Required for proper cursor rendering.
SDL_SetSurfaceBlendMode(screen, SDL_BLENDMODE_NONE);
screenTexture = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, logicalSize.x, logicalSize.y);
if(nullptr == screenTexture)
{
logGlobal->error("Unable to create screen texture");
logGlobal->error(SDL_GetError());
throw std::runtime_error("Unable to create screen texture");
}
screen2 = CSDL_Ext::copySurface(screen);
if(nullptr == screen2)
{
throw std::runtime_error("Unable to copy surface\n");
}
if (GH.listInt.size() > 1)
screenBuf = screen2;
else
screenBuf = screen;
clearScreen();
}
SDL_Window * ScreenHandler::createWindowImpl(Point dimensions, int flags, bool center)
{
int displayIndex = getPreferredDisplayIndex();
int positionFlags = center ? SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex) : SDL_WINDOWPOS_UNDEFINED_DISPLAY(displayIndex);
return SDL_CreateWindow(NAME.c_str(), positionFlags, positionFlags, dimensions.x, dimensions.y, flags);
}
SDL_Window * ScreenHandler::createWindow()
{
#ifndef VCMI_MOBILE
Point dimensions = getPreferredRenderingResolution();
switch(getPreferredWindowMode())
{
case EWindowMode::FULLSCREEN_EXCLUSIVE:
return createWindowImpl(dimensions, SDL_WINDOW_FULLSCREEN, false);
case EWindowMode::FULLSCREEN_BORDERLESS_WINDOWED:
return createWindowImpl(Point(), SDL_WINDOW_FULLSCREEN_DESKTOP, false);
case EWindowMode::WINDOWED:
return createWindowImpl(dimensions, SDL_WINDOW_RESIZABLE, true);
default:
return nullptr;
};
#endif
#ifdef VCMI_IOS
SDL_SetHint(SDL_HINT_IOS_HIDE_HOME_INDICATOR, "1");
SDL_SetHint(SDL_HINT_RETURN_KEY_HIDES_IME, "1");
uint32_t windowFlags = SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI;
SDL_Window * result = createWindowImpl(Point(), windowFlags | SDL_WINDOW_METAL, false);
if(result != nullptr)
return result;
logGlobal->warn("Metal unavailable, using OpenGLES");
return createWindowImpl(Point(), windowFlags, false);
#endif
#ifdef VCMI_ANDROID
return createWindowImpl(Point(), SDL_WINDOW_FULLSCREEN, false);
#endif
}
void ScreenHandler::onScreenResize()
{
recreateWindowAndScreenBuffers();
GH.onScreenResize();
}
void ScreenHandler::validateSettings()
{
#ifndef VCMI_MOBILE
{
int displayIndex = settings["video"]["displayIndex"].Integer();
int displaysCount = SDL_GetNumVideoDisplays();
if (displayIndex >= displaysCount)
{
Settings writer = settings.write["video"]["displayIndex"];
writer->Float() = 0;
}
}
if (getPreferredWindowMode() == EWindowMode::WINDOWED)
{
//we only check that our desired window size fits on screen
int displayIndex = getPreferredDisplayIndex();
Point resolution = getPreferredRenderingResolution();
SDL_DisplayMode mode;
if (SDL_GetDesktopDisplayMode(displayIndex, &mode) == 0)
{
if(resolution.x > mode.w || resolution.y > mode.h)
{
Settings writer = settings.write["video"]["resolution"];
writer["width"].Float() = mode.w;
writer["height"].Float() = mode.h;
}
}
}
if (getPreferredWindowMode() == EWindowMode::FULLSCREEN_EXCLUSIVE)
{
auto legalOptions = getSupportedResolutions();
Point selectedResolution = getPreferredRenderingResolution();
if(!vstd::contains(legalOptions, selectedResolution))
{
// resolution selected for fullscreen mode is not supported by display
// try to find current display resolution and use it instead as "reasonable default"
SDL_DisplayMode mode;
if (SDL_GetDesktopDisplayMode(getPreferredDisplayIndex(), &mode) == 0)
{
Settings writer = settings.write["video"]["resolution"];
writer["width"].Float() = mode.w;
writer["height"].Float() = mode.h;
}
}
}
#endif
}
int ScreenHandler::getPreferredRenderingDriver() const
{
int result = -1;
const JsonNode & video = settings["video"];
int driversCount = SDL_GetNumRenderDrivers();
std::string preferredDriverName = video["driver"].String();
logGlobal->info("Found %d render drivers", driversCount);
for(int it = 0; it < driversCount; it++)
{
SDL_RendererInfo info;
if (SDL_GetRenderDriverInfo(it, &info) == 0)
{
std::string driverName(info.name);
if(!preferredDriverName.empty() && driverName == preferredDriverName)
{
result = it;
logGlobal->info("\t%s (active)", driverName);
}
else
logGlobal->info("\t%s", driverName);
}
else
logGlobal->info("\t(error)");
}
return result;
}
void ScreenHandler::destroyScreenBuffers()
{
// screenBuf is not a separate surface, but points to either screen or screen2 - just set to null
screenBuf = nullptr;
if(nullptr != screen2)
{
SDL_FreeSurface(screen2);
screen2 = nullptr;
}
if(nullptr != screen)
{
SDL_FreeSurface(screen);
screen = nullptr;
}
if(nullptr != screenTexture)
{
SDL_DestroyTexture(screenTexture);
screenTexture = nullptr;
}
}
void ScreenHandler::destroyWindow()
{
if(nullptr != mainRenderer)
{
SDL_DestroyRenderer(mainRenderer);
mainRenderer = nullptr;
}
if(nullptr != mainWindow)
{
SDL_DestroyWindow(mainWindow);
mainWindow = nullptr;
}
}
void ScreenHandler::close()
{
if(settings["general"]["notifications"].Bool())
NotificationHandler::destroy();
destroyScreenBuffers();
destroyWindow();
SDL_Quit();
}
void ScreenHandler::clearScreen()
{
SDL_SetRenderDrawColor(mainRenderer, 0, 0, 0, 255);
SDL_RenderClear(mainRenderer);
SDL_RenderPresent(mainRenderer);
}
std::vector<Point> ScreenHandler::getSupportedResolutions() const
{
int displayID = SDL_GetWindowDisplayIndex(mainWindow);
return getSupportedResolutions(displayID);
}
std::vector<Point> ScreenHandler::getSupportedResolutions( int displayIndex) const
{
//NOTE: this method is never called on Android/iOS, only on desktop systems
std::vector<Point> result;
int modesCount = SDL_GetNumDisplayModes(displayIndex);
for (int i =0; i < modesCount; ++i)
{
SDL_DisplayMode mode;
if (SDL_GetDisplayMode(displayIndex, i, &mode) == 0)
{
Point resolution(mode.w, mode.h);
result.push_back(resolution);
}
}
boost::range::sort(result, [](const auto & left, const auto & right)
{
return left.x * left.y < right.x * right.y;
});
// erase potential duplicates, e.g. resolutions with different framerate / bits per pixel
result.erase(boost::unique(result).end(), result.end());
return result;
}

View File

@ -0,0 +1,89 @@
/*
* ScreenHandler.h, part of VCMI engine
*
* 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
*
*/
#pragma once
struct SDL_Texture;
struct SDL_Window;
struct SDL_Renderer;
struct SDL_Surface;
#include "../../lib/Point.h"
#include "../render/IScreenHandler.h"
enum class EWindowMode
{
// game runs in a window that covers part of the screen
WINDOWED,
// game runs in a 'window' that always covers entire screen and uses unmodified desktop resolution
// The only mode that is available on mobile devices
FULLSCREEN_BORDERLESS_WINDOWED,
// game runs in a fullscreen mode with resolution selected by player
FULLSCREEN_EXCLUSIVE
};
/// This class is responsible for management of game window and its main rendering surface
class ScreenHandler : public IScreenHandler
{
/// Dimensions of target surfaces/textures, this value is what game logic views as screen size
Point getPreferredLogicalResolution() const;
/// Dimensions of output window, if different from logicalResolution SDL will perform scaling
/// This value is what player views as window size
Point getPreferredRenderingResolution() const;
EWindowMode getPreferredWindowMode() const;
/// Returns index of display on which window should be created
int getPreferredDisplayIndex() const;
/// Returns index of rendering driver preferred by player or -1 if no preference
int getPreferredRenderingDriver() const;
/// Creates SDL window with specified parameters
SDL_Window * createWindowImpl(Point dimensions, int flags, bool center);
/// Creates SDL window using OS-specific settings & user-specific config
SDL_Window * createWindow();
/// Manages window and SDL renderer
void initializeWindow();
void destroyWindow();
/// Manages surfaces & textures used for
void initializeScreenBuffers();
void destroyScreenBuffers();
/// Updates state (e.g. position) of game window after resolution/fullscreen change
void updateWindowState();
/// Initializes or reiniitalizes all screen state
void recreateWindowAndScreenBuffers();
/// Performs validation of settings and updates them to valid values if necessary
void validateSettings();
public:
/// Creates and initializes screen, window and SDL state
ScreenHandler();
/// Updates and potentially recreates target screen to match selected fullscreen status
void onScreenResize() final;
/// De-initializes and destroys screen, window and SDL state
void close() final;
/// Fills screen with black color, erasing any existing content
void clearScreen() final;
std::vector<Point> getSupportedResolutions() const final;
std::vector<Point> getSupportedResolutions(int displayIndex) const;
std::tuple<int, int> getSupportedScalingRange() const final;
};

View File

@ -249,32 +249,32 @@ CButton::CButton(Point position, const std::string &defName, const std::pair<std
if (!defName.empty())
{
imageNames.push_back(defName);
setIndex(0, playerColoredButton);
setIndex(0);
if (playerColoredButton)
image->playerColored(LOCPLINT->playerID);
}
}
void CButton::setIndex(size_t index, bool playerColoredButton)
void CButton::setIndex(size_t index)
{
if (index == currentImage || index>=imageNames.size())
return;
currentImage = index;
auto anim = std::make_shared<CAnimation>(imageNames[index]);
setImage(anim, playerColoredButton);
setImage(anim);
}
void CButton::setImage(std::shared_ptr<CAnimation> anim, bool playerColoredButton, int animFlags)
void CButton::setImage(std::shared_ptr<CAnimation> anim, int animFlags)
{
OBJECT_CONSTRUCTION_CUSTOM_CAPTURING(255-DISPOSE);
image = std::make_shared<CAnimImage>(anim, getState(), 0, 0, 0, animFlags);
if (playerColoredButton)
image->playerColored(LOCPLINT->playerID);
pos = image->pos;
}
void CButton::setPlayerColor(PlayerColor player)
{
if (image)
if (image && image->isPlayerColored())
image->playerColored(player);
}

View File

@ -16,13 +16,7 @@
#include <SDL_pixels.h>
VCMI_LIB_NAMESPACE_BEGIN
namespace config
{
struct ButtonInfo;
}
class Rect;
VCMI_LIB_NAMESPACE_END
struct SDL_Surface;
@ -104,8 +98,8 @@ public:
CFunctionList<void()> Callback = 0, EShortcut key = {}, bool playerColoredButton = false );
/// Appearance modifiers
void setIndex(size_t index, bool playerColoredButton=false);
void setImage(std::shared_ptr<CAnimation> anim, bool playerColoredButton=false, int animFlags=0);
void setIndex(size_t index);
void setImage(std::shared_ptr<CAnimation> anim, int animFlags=0);
void setPlayerColor(PlayerColor player);
/// CIntObject overrides

View File

@ -16,6 +16,7 @@
#include "../renderSDL/SDL_Extensions.h"
#include "../render/IImage.h"
#include "../render/CAnimation.h"
#include "../render/ColorFilter.h"
#include "../battle/BattleInterface.h"
#include "../battle/BattleInterfaceClasses.h"
@ -109,6 +110,16 @@ void CPicture::colorize(PlayerColor player)
CFilledTexture::CFilledTexture(std::string imageName, Rect position):
CIntObject(0, position.topLeft()),
texture(IImage::createFromFile(imageName))
{
pos.w = position.w;
pos.h = position.h;
imageArea = Rect(Point(), texture->dimensions());
}
CFilledTexture::CFilledTexture(std::shared_ptr<IImage> image, Rect position, Rect imageArea)
: CIntObject(0, position.topLeft())
, texture(image)
, imageArea(imageArea)
{
pos.w = position.w;
pos.h = position.h;
@ -118,17 +129,45 @@ void CFilledTexture::showAll(SDL_Surface *to)
{
CSDL_Ext::CClipRectGuard guard(to, pos);
for (int y=pos.top(); y < pos.bottom(); y+= texture->height())
for (int y=pos.top(); y < pos.bottom(); y+= imageArea.h)
{
for (int x=pos.left(); x < pos.right(); x+=texture->width())
texture->draw(to, x, y);
for (int x=pos.left(); x < pos.right(); x+= imageArea.w)
texture->draw(to, x, y, &imageArea);
}
}
FilledTexturePlayerColored::FilledTexturePlayerColored(std::string imageName, Rect position)
: CFilledTexture(imageName, position)
{
}
void FilledTexturePlayerColored::playerColored(PlayerColor player)
{
// Color transform to make color of brown DIBOX.PCX texture match color of specified player
std::array<ColorFilter, PlayerColor::PLAYER_LIMIT_I> filters = {
ColorFilter::genRangeShifter( 0.25, 0, 0, 1.25, 0.00, 0.00 ), // red
ColorFilter::genRangeShifter( 0, 0, 0, 0.45, 1.20, 4.50 ), // blue
ColorFilter::genRangeShifter( 0.40, 0.27, 0.23, 1.10, 1.20, 1.15 ), // tan
ColorFilter::genRangeShifter( -0.27, 0.10, -0.27, 0.70, 1.70, 0.70 ), // green
ColorFilter::genRangeShifter( 0.47, 0.17, -0.27, 1.60, 1.20, 0.70 ), // orange
ColorFilter::genRangeShifter( 0.12, -0.1, 0.25, 1.15, 1.20, 2.20 ), // purple
ColorFilter::genRangeShifter( -0.13, 0.23, 0.23, 0.90, 1.20, 2.20 ), // teal
ColorFilter::genRangeShifter( 0.44, 0.15, 0.25, 1.00, 1.00, 1.75 ) // pink
};
assert(player.isValidPlayer());
if (!player.isValidPlayer())
{
logGlobal->error("Unable to colorize to invalid player color %d!", static_cast<int>(player.getNum()));
return;
}
texture->adjustPalette(filters[player.getNum()], 0);
}
CAnimImage::CAnimImage(const std::string & name, size_t Frame, size_t Group, int x, int y, ui8 Flags):
frame(Frame),
group(Group),
player(-1),
flags(Flags)
{
pos.x += x;
@ -141,7 +180,6 @@ CAnimImage::CAnimImage(std::shared_ptr<CAnimation> Anim, size_t Frame, size_t Gr
anim(Anim),
frame(Frame),
group(Group),
player(-1),
flags(Flags)
{
pos.x += x;
@ -153,7 +191,6 @@ CAnimImage::CAnimImage(std::shared_ptr<CAnimation> Anim, size_t Frame, Rect targ
anim(Anim),
frame(Frame),
group(Group),
player(-1),
flags(Flags),
scaledSize(targetPos.w, targetPos.h)
{
@ -242,8 +279,8 @@ void CAnimImage::setFrame(size_t Frame, size_t Group)
group = Group;
if(auto img = anim->getImage(frame, group))
{
if (flags & CShowableAnim::PLAYER_COLORED)
img->playerColored(player);
if (player.has_value())
img->playerColored(*player);
setSizeFromImage(*img);
}
}
@ -254,10 +291,14 @@ void CAnimImage::setFrame(size_t Frame, size_t Group)
void CAnimImage::playerColored(PlayerColor currPlayer)
{
player = currPlayer;
flags |= CShowableAnim::PLAYER_COLORED;
anim->getImage(frame, group)->playerColored(player);
anim->getImage(frame, group)->playerColored(*player);
if (flags & CShowableAnim::BASE)
anim->getImage(0, group)->playerColored(player);
anim->getImage(0, group)->playerColored(*player);
}
bool CAnimImage::isPlayerColored() const
{
return player.has_value();
}
CShowableAnim::CShowableAnim(int x, int y, std::string name, ui8 Flags, ui32 frameTime, size_t Group, uint8_t alpha):

View File

@ -68,14 +68,25 @@ public:
/// area filled with specific texture
class CFilledTexture : public CIntObject
{
protected:
std::shared_ptr<IImage> texture;
Rect imageArea;
public:
CFilledTexture(std::string imageName, Rect position);
CFilledTexture(std::shared_ptr<IImage> image, Rect position, Rect imageArea);
void showAll(SDL_Surface *to) override;
};
class FilledTexturePlayerColored : public CFilledTexture
{
public:
FilledTexturePlayerColored(std::string imageName, Rect position);
void playerColored(PlayerColor player);
};
/// Class for displaying one image from animation
class CAnimImage: public CIntObject
{
@ -84,10 +95,12 @@ private:
//displayed frame/group
size_t frame;
size_t group;
PlayerColor player;
ui8 flags;
const Point scaledSize;
/// If set, then image is colored using player-specific palette
std::optional<PlayerColor> player;
bool isScaled() const;
void setSizeFromImage(const IImage &img);
void init();
@ -99,15 +112,18 @@ public:
CAnimImage(std::shared_ptr<CAnimation> Anim, size_t Frame, Rect targetPos, size_t Group=0, ui8 Flags=0);
~CAnimImage();
//size of animation
/// size of animation
size_t size();
//change displayed frame on this one
/// change displayed frame on this one
void setFrame(size_t Frame, size_t Group=0);
//makes image player-colored
/// makes image player-colored to specific player
void playerColored(PlayerColor player);
/// returns true if image has player-colored effect applied
bool isPlayerColored() const;
void showAll(SDL_Surface * to) override;
};
@ -120,7 +136,6 @@ public:
BASE=1, //base frame will be blitted before current one
HORIZONTAL_FLIP=2, //TODO: will be displayed rotated
VERTICAL_FLIP=4, //TODO: will be displayed rotated
PLAYER_COLORED=16, //TODO: all loaded images will be player-colored
PLAY_ONCE=32 //play animation only once and stop at last frame
};
protected:

View File

@ -399,7 +399,7 @@ void CGStatusBar::clear()
write({});
}
CGStatusBar::CGStatusBar(std::shared_ptr<CPicture> background_, EFonts Font, ETextAlignment Align, const SDL_Color & Color)
CGStatusBar::CGStatusBar(std::shared_ptr<CIntObject> background_, EFonts Font, ETextAlignment Align, const SDL_Color & Color)
: CLabel(background_->pos.x, background_->pos.y, Font, Align, Color, "")
, enteringText(false)
{
@ -419,28 +419,31 @@ CGStatusBar::CGStatusBar(int x, int y, std::string name, int maxw)
addUsedEvents(LCLICK);
OBJECT_CONSTRUCTION_CAPTURING(255 - DISPOSE);
background = std::make_shared<CPicture>(name);
auto backgroundImage = std::make_shared<CPicture>(name);
background = backgroundImage;
pos = background->pos;
if((unsigned)maxw < (unsigned)pos.w) //(insigned)-1 > than any correct value of pos.w
{
//execution of this block when maxw is incorrect breaks text centralization (issue #3151)
vstd::amin(pos.w, maxw);
background->srcRect = Rect(0, 0, maxw, pos.h);
backgroundImage->srcRect = Rect(0, 0, maxw, pos.h);
}
autoRedraw = false;
}
CGStatusBar::~CGStatusBar()
{
assert(GH.statusbar.get() != this || GH.statusbar == nullptr);
if (GH.statusbar.get() == this)
GH.statusbar = nullptr;
}
void CGStatusBar::show(SDL_Surface * to)
{
showAll(to);
}
void CGStatusBar::init()
{
GH.statusbar = shared_from_this();
}
void CGStatusBar::clickLeft(tribool down, bool previousState)
{
if(!down)
@ -450,8 +453,16 @@ void CGStatusBar::clickLeft(tribool down, bool previousState)
}
}
void CGStatusBar::activate()
{
GH.statusbar = shared_from_this();
CIntObject::deactivate();
}
void CGStatusBar::deactivate()
{
assert(GH.statusbar.get() == this);
if (enteringText)
LOCPLINT->cingconsole->endEnteringText(false);

View File

@ -45,7 +45,7 @@ protected:
Point getBorderSize() override;
virtual std::string visibleText();
std::shared_ptr<CPicture> background;
std::shared_ptr<CIntObject> background;
std::string text;
bool autoRedraw; //whether control will redraw itself on setTxt
@ -125,9 +125,7 @@ class CGStatusBar : public CLabel, public std::enable_shared_from_this<CGStatusB
std::string consoleText;
bool enteringText;
void init();
CGStatusBar(std::shared_ptr<CPicture> background_, EFonts Font = FONT_SMALL, ETextAlignment Align = ETextAlignment::CENTER, const SDL_Color & Color = Colors::WHITE);
CGStatusBar(std::shared_ptr<CIntObject> background_, EFonts Font = FONT_SMALL, ETextAlignment Align = ETextAlignment::CENTER, const SDL_Color & Color = Colors::WHITE);
CGStatusBar(int x, int y, std::string name, int maxw = -1);
//make CLabel API private
@ -143,15 +141,17 @@ protected:
void clickLeft(tribool down, bool previousState) override;
public:
~CGStatusBar();
template<typename ...Args>
static std::shared_ptr<CGStatusBar> create(Args... args)
{
std::shared_ptr<CGStatusBar> ret{new CGStatusBar{args...}};
ret->init();
return ret;
}
void show(SDL_Surface * to) override;
void activate() override;
void deactivate() override;
// IStatusBar interface

View File

@ -29,7 +29,7 @@
#include "../renderSDL/SDL_Extensions.h"
#include "../render/IImage.h"
#include "../render/ColorFilter.h"
#include "../adventureMap/CAdventureMapInterface.h"
#include "../adventureMap/AdventureMapInterface.h"
#include "../adventureMap/CList.h"
#include "../adventureMap/CResDataBar.h"
@ -1198,9 +1198,12 @@ CCastleInterface::CCastleInterface(const CGTownInstance * Town, const CGTownInst
Rect barRect(9, 182, 732, 18);
auto statusbarBackground = std::make_shared<CPicture>(panel->getSurface(), barRect, 9, 555);
statusbar = CGStatusBar::create(statusbarBackground);
resdatabar = std::make_shared<CResDataBar>("ARESBAR", 3, 575, 32, 2, 85, 85);
resdatabar = std::make_shared<CResDataBar>("ARESBAR", 3, 575, 37, 3, 84, 78);
townlist = std::make_shared<CTownList>(3, Rect(Point(743, 414), Point(48, 128)), Point(1,16), Point(0, 32), LOCPLINT->localState->getOwnedTowns().size() );
townlist->setScrollUpButton( std::make_shared<CButton>( Point(744, 414), "IAM014", CButton::tooltipLocalized("core.help.306")));
townlist->setScrollDownButton( std::make_shared<CButton>( Point(744, 526), "IAM015", CButton::tooltipLocalized("core.help.307")));
townlist = std::make_shared<CTownList>(3, Point(744, 414), "IAM014", "IAM015");
if(from)
townlist->select(from);

View File

@ -39,6 +39,9 @@
#include "../../lib/mapObjects/CGTownInstance.h"
#include "../../lib/mapObjects/MiscObjects.h"
static const std::string OVERVIEW_BACKGROUND = "OvCast.pcx";
static const size_t OVERVIEW_SIZE = 4;
InfoBox::InfoBox(Point position, InfoPos Pos, InfoSize Size, std::shared_ptr<IInfoBoxData> Data):
size(Size),
infoPos(Pos),
@ -468,10 +471,10 @@ void InfoBoxCustom::prepareMessage(std::string & text, std::shared_ptr<CComponen
}
CKingdomInterface::CKingdomInterface()
: CWindowObject(PLAYER_COLORED | BORDERED, conf.go()->ac.overviewBg)
: CWindowObject(PLAYER_COLORED | BORDERED, OVERVIEW_BACKGROUND)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
ui32 footerPos = conf.go()->ac.overviewSize * 116;
ui32 footerPos = OVERVIEW_SIZE * 116;
tabArea = std::make_shared<CTabbedInt>(std::bind(&CKingdomInterface::createMainTab, this, _1), Point(4,4));
@ -481,12 +484,12 @@ CKingdomInterface::CKingdomInterface()
generateButtons();
statusbar = CGStatusBar::create(std::make_shared<CPicture>("KSTATBAR", 10,pos.h - 45));
resdatabar = std::make_shared<CResDataBar>("KRESBAR", 3, 111+footerPos, 32, 2, 76, 76);
resdatabar = std::make_shared<CResDataBar>("KRESBAR", 7, 111+footerPos, 29, 5, 76, 81);
}
void CKingdomInterface::generateObjectsList(const std::vector<const CGObjectInstance * > &ownedObjects)
{
ui32 footerPos = conf.go()->ac.overviewSize * 116;
ui32 footerPos = OVERVIEW_SIZE * 116;
size_t dwellSize = (footerPos - 64)/57;
//Map used to determine image number for several objects
@ -550,7 +553,7 @@ std::shared_ptr<CIntObject> CKingdomInterface::createOwnedObject(size_t index)
std::shared_ptr<CIntObject> CKingdomInterface::createMainTab(size_t index)
{
size_t size = conf.go()->ac.overviewSize;
size_t size = OVERVIEW_SIZE;
switch(index)
{
case 0:
@ -564,7 +567,7 @@ std::shared_ptr<CIntObject> CKingdomInterface::createMainTab(size_t index)
void CKingdomInterface::generateMinesList(const std::vector<const CGObjectInstance *> & ownedObjects)
{
ui32 footerPos = conf.go()->ac.overviewSize * 116;
ui32 footerPos = OVERVIEW_SIZE * 116;
TResources minesCount(GameConstants::RESOURCE_QUANTITY, 0);
int totalIncome=0;
@ -610,7 +613,7 @@ void CKingdomInterface::generateMinesList(const std::vector<const CGObjectInstan
void CKingdomInterface::generateButtons()
{
ui32 footerPos = conf.go()->ac.overviewSize * 116;
ui32 footerPos = OVERVIEW_SIZE * 116;
//Main control buttons
btnHeroes = std::make_shared<CButton>(Point(748, 28+footerPos), "OVBUTN1.DEF", CButton::tooltip(CGI->generaltexth->overview[11], CGI->generaltexth->overview[6]),
@ -689,7 +692,7 @@ CKingdHeroList::CKingdHeroList(size_t maxSize)
skillsLabel = std::make_shared<CLabel>(500, 10, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[1]);
ui32 townCount = LOCPLINT->cb->howManyHeroes(false);
ui32 size = conf.go()->ac.overviewSize*116 + 19;
ui32 size = OVERVIEW_SIZE*116 + 19;
heroes = std::make_shared<CListBox>(std::bind(&CKingdHeroList::createHeroItem, this, _1),
Point(19,21), Point(0,116), maxSize, townCount, 0, 1, Rect(-19, -21, size, size));
}
@ -705,7 +708,7 @@ void CKingdHeroList::updateGarrisons()
std::shared_ptr<CIntObject> CKingdHeroList::createHeroItem(size_t index)
{
ui32 picCount = conf.go()->ac.overviewPics;
ui32 picCount = 4; // OVSLOT contains 4 images
size_t heroesCount = LOCPLINT->cb->howManyHeroes(false);
if(index < heroesCount)
@ -730,7 +733,7 @@ CKingdTownList::CKingdTownList(size_t maxSize)
visitHeroLabel = std::make_shared<CLabel>(608, 10, FONT_MEDIUM, ETextAlignment::CENTER, Colors::WHITE, CGI->generaltexth->overview[5]);
ui32 townCount = LOCPLINT->cb->howManyTowns();
ui32 size = conf.go()->ac.overviewSize*116 + 19;
ui32 size = OVERVIEW_SIZE*116 + 19;
towns = std::make_shared<CListBox>(std::bind(&CKingdTownList::createTownItem, this, _1),
Point(19,21), Point(0,116), maxSize, townCount, 0, 1, Rect(-19, -21, size, size));
}
@ -756,7 +759,7 @@ void CKingdTownList::updateGarrisons()
std::shared_ptr<CIntObject> CKingdTownList::createTownItem(size_t index)
{
ui32 picCount = conf.go()->ac.overviewPics;
ui32 picCount = 4; // OVSLOT contains 4 images
size_t townsCount = LOCPLINT->cb->howManyTowns();
if(index < townsCount)

View File

@ -16,7 +16,7 @@
#include "../gui/CGuiHandler.h"
#include "../gui/Shortcut.h"
#include "../widgets/CComponent.h"
#include "../adventureMap/CAdventureMapInterface.h"
#include "../adventureMap/AdventureMapInterface.h"
#include "../widgets/Buttons.h"
#include "../adventureMap/CMinimap.h"
#include "../renderSDL/SDL_Extensions.h"

View File

@ -27,7 +27,7 @@
#include "../widgets/MiscWidgets.h"
#include "../widgets/CComponent.h"
#include "../widgets/TextControls.h"
#include "../adventureMap/CAdventureMapInterface.h"
#include "../adventureMap/AdventureMapInterface.h"
#include "../render/CAnimation.h"
#include "../renderSDL/SDL_Extensions.h"

View File

@ -332,7 +332,6 @@ CTradeWindow::CTradeWindow(std::string bgName, const IMarket *Market, const CGHe
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
type |= BLOCK_ADV_HOTKEYS;
mode = Mode;
initTypes();
}

View File

@ -248,9 +248,3 @@ CStatusbarWindow::CStatusbarWindow(int options, std::string imageName, Point cen
CStatusbarWindow::CStatusbarWindow(int options, std::string imageName) : CWindowObject(options, imageName)
{
}
void CStatusbarWindow::activate()
{
CIntObject::activate();
GH.statusbar = statusbar;
}

View File

@ -58,7 +58,6 @@ class CStatusbarWindow : public CWindowObject
public:
CStatusbarWindow(int options, std::string imageName, Point centerAt);
CStatusbarWindow(int options, std::string imageName = "");
void activate() override;
protected:
std::shared_ptr<CGStatusBar> statusbar;
};

View File

@ -1619,7 +1619,6 @@ CThievesGuildWindow::CThievesGuildWindow(const CGObjectInstance * _owner):
owner(_owner)
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
type |= BLOCK_ADV_HOTKEYS;
SThievesGuildInfo tgi; //info to be displayed
LOCPLINT->cb->getThievesGuildInfo(tgi, owner);

View File

@ -12,7 +12,6 @@
#include "CWindowObject.h"
#include "../lib/GameConstants.h"
#include "../lib/ResourceSet.h"
#include "../lib/CConfigHandler.h"
#include "../lib/int3.h"
#include "../widgets/CWindowWithArtifacts.h"
#include "../widgets/CGarrisonInt.h"

View File

@ -22,7 +22,7 @@
#include "../gui/CGuiHandler.h"
#include "../battle/BattleInterface.h"
#include "../battle/BattleInterfaceClasses.h"
#include "../adventureMap/CAdventureMapInterface.h"
#include "../adventureMap/AdventureMapInterface.h"
#include "../windows/CMessage.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../gui/CursorHandler.h"
@ -119,7 +119,6 @@ CInfoWindow::CInfoWindow(std::string Text, PlayerColor player, const TCompsInfo
{
OBJECT_CONSTRUCTION_CAPTURING(255-DISPOSE);
type |= BLOCK_ADV_HOTKEYS;
ID = QueryID(-1);
for(auto & Button : Buttons)
{

View File

@ -22,7 +22,7 @@
#include "CPlayerInterface.h"
#include "windows/GUIClasses.h"
#include "CServerHandler.h"
#include "renderSDL/SDL_Extensions.h"
#include "render/IScreenHandler.h"
static void setIntSetting(std::string group, std::string field, int value)
@ -37,6 +37,19 @@ static void setBoolSetting(std::string group, std::string field, bool value)
entry->Bool() = value;
}
static std::string scalingToEntryString( int scaling)
{
return std::to_string(scaling) + '%';
}
static std::string scalingToLabelString( int scaling)
{
std::string string = CGI->generaltexth->translate("vcmi.systemOptions.scalingButton.hover");
boost::replace_all(string, "%p", std::to_string(scaling));
return string;
}
static std::string resolutionToEntryString( int w, int h)
{
std::string string = "%wx%h";
@ -96,6 +109,10 @@ GeneralOptionsTab::GeneralOptionsTab()
{
selectGameResolution();
});
addCallback("setGameScaling", [this](int dummyValue)
{
selectGameScaling();
});
addCallback("framerateChanged", [](bool value)
{
setBoolSetting("video", "showfps", value);
@ -114,10 +131,14 @@ GeneralOptionsTab::GeneralOptionsTab()
build(config);
const auto & currentResolution = settings["video"]["resolution"];
std::shared_ptr<CLabel> resolutionLabel = widget<CLabel>("resolutionLabel");
const auto & currentResolution = settings["video"]["screenRes"];
resolutionLabel->setText(resolutionToLabelString(currentResolution["width"].Integer(), currentResolution["height"].Integer()));
std::shared_ptr<CLabel> scalingLabel = widget<CLabel>("scalingLabel");
scalingLabel->setText(scalingToLabelString(currentResolution["scaling"].Integer()));
std::shared_ptr<CToggleButton> spellbookAnimationCheckbox = widget<CToggleButton>("spellbookAnimationCheckbox");
spellbookAnimationCheckbox->setSelected(settings["video"]["spellbookAnimation"].Bool());
@ -149,32 +170,25 @@ GeneralOptionsTab::GeneralOptionsTab()
std::shared_ptr<CLabel> soundVolumeLabel = widget<CLabel>("soundValueLabel");
soundVolumeLabel->setText(std::to_string(CCS->soundh->getVolume()) + "%");
}
#ifdef VCMI_MOBILE
// On mobile platforms, VCMI always uses OS screen resolutions
// Players can control UI size via "Interface Scaling" option instead
std::shared_ptr<CButton> resolutionButton = widget<CButton>("resolutionButton");
bool GeneralOptionsTab::isResolutionSupported(const Point & resolution)
{
return isResolutionSupported( resolution, settings["video"]["fullscreen"].Bool());
}
bool GeneralOptionsTab::isResolutionSupported(const Point & resolution, bool fullscreen)
{
if (!fullscreen)
return true;
auto supportedList = CSDL_Ext::getSupportedResolutions();
return CSDL_Ext::isResolutionSupported(supportedList, resolution);
resolutionButton->disable();
resolutionLabel->disable();
fullscreenCheckbox->block(true);
#endif
}
void GeneralOptionsTab::selectGameResolution()
{
fillSelectableResolutions();
supportedResolutions = GH.screenHandler().getSupportedResolutions();
std::vector<std::string> items;
size_t currentResolutionIndex = 0;
size_t i = 0;
for(const auto & it : selectableResolutions)
for(const auto & it : supportedResolutions)
{
auto resolutionStr = resolutionToEntryString(it.x, it.y);
if(widget<CLabel>("resolutionLabel")->getText() == resolutionToLabelString(it.x, it.y))
@ -195,14 +209,14 @@ void GeneralOptionsTab::selectGameResolution()
void GeneralOptionsTab::setGameResolution(int index)
{
assert(index >= 0 && index < selectableResolutions.size());
assert(index >= 0 && index < supportedResolutions.size());
if ( index < 0 || index >= selectableResolutions.size() )
if ( index < 0 || index >= supportedResolutions.size() )
return;
Point resolution = selectableResolutions[index];
Point resolution = supportedResolutions[index];
Settings gameRes = settings.write["video"]["screenRes"];
Settings gameRes = settings.write["video"]["resolution"];
gameRes["width"].Float() = resolution.x;
gameRes["height"].Float() = resolution.y;
@ -211,48 +225,57 @@ void GeneralOptionsTab::setGameResolution(int index)
void GeneralOptionsTab::setFullscreenMode(bool on)
{
fillSelectableResolutions();
const auto & screenRes = settings["video"]["screenRes"];
const Point desiredResolution(screenRes["width"].Integer(), screenRes["height"].Integer());
const Point currentResolution = GH.screenDimensions();
if (!isResolutionSupported(currentResolution, on))
{
widget<CToggleButton>("fullscreenCheckbox")->setSelected(!on);
LOCPLINT->showInfoDialog(CGI->generaltexth->translate("vcmi.systemOptions.fullscreenFailed"));
return;
}
setBoolSetting("video", "fullscreen", on);
if (!isResolutionSupported(desiredResolution, on))
{
// user changed his desired resolution and switched to fullscreen
// however resolution he selected before is not available in fullscreen
// so reset it back to currect resolution which is confirmed to be supported earlier
Settings gameRes = settings.write["video"]["screenRes"];
gameRes["width"].Float() = currentResolution.x;
gameRes["height"].Float() = currentResolution.y;
widget<CLabel>("resolutionLabel")->setText(resolutionToLabelString(currentResolution.x, currentResolution.y));
}
}
void GeneralOptionsTab::fillSelectableResolutions()
void GeneralOptionsTab::selectGameScaling()
{
selectableResolutions.clear();
supportedScaling.clear();
for(const auto & it : conf.guiOptions)
auto [minimalScaling, maximalScaling] = GH.screenHandler().getSupportedScalingRange();
for (int i = 0; i <= maximalScaling; i += 10)
{
const Point dimensions(it.first.first, it.first.second);
if(isResolutionSupported(dimensions))
selectableResolutions.push_back(dimensions);
if (i >= minimalScaling)
supportedScaling.push_back(i);
}
boost::range::sort(selectableResolutions, [](const auto & left, const auto & right)
std::vector<std::string> items;
size_t currentIndex = 0;
size_t i = 0;
for(const auto & it : supportedScaling)
{
return left.x * left.y < right.x * right.y;
});
auto resolutionStr = scalingToEntryString(it);
if(widget<CLabel>("scalingLabel")->getText() == scalingToLabelString(it))
currentIndex = i;
items.push_back(std::move(resolutionStr));
++i;
}
GH.pushIntT<CObjectListWindow>(
items,
nullptr,
CGI->generaltexth->translate("vcmi.systemOptions.scalingMenu.hover"),
CGI->generaltexth->translate("vcmi.systemOptions.scalingMenu.help"),
[this](int index)
{
setGameScaling(index);
},
currentIndex
);
}
void GeneralOptionsTab::setGameScaling(int index)
{
assert(index >= 0 && index < supportedScaling.size());
if ( index < 0 || index >= supportedScaling.size() )
return;
int scaling = supportedScaling[index];
Settings gameRes = settings.write["video"]["resolution"];
gameRes["scaling"].Float() = scaling;
widget<CLabel>("scalingLabel")->setText(scalingToLabelString(scaling));
}

View File

@ -19,16 +19,16 @@ private:
SettingsListener onFullscreenChanged;
std::vector<Point> supportedResolutions;
std::vector<Point> selectableResolutions;
std::vector<int> supportedScaling;
void setFullscreenMode( bool on);
void fillSelectableResolutions();
bool isResolutionSupported(const Point & resolution);
bool isResolutionSupported(const Point & resolution, bool fullscreen);
void selectGameResolution();
void setGameResolution(int index);
void selectGameScaling();
void setGameScaling(int index);
public:
GeneralOptionsTab();
};
};

View File

@ -1,33 +0,0 @@
{
"GUISettings":
[
{
"resolution": { "x": 800, "y": 600 },
"InGameConsole": { "maxInputPerLine": 60, "maxOutputPerLine": 60 },
"AdvMap": { "x": 7, "y": 7, "width": 594, "height": 546, "smoothMove": 1, "puzzleSepia": 1, "objectFading" : 1, "screenFading" : 1 },
"InfoBox": { "x": 605, "y": 389 },
"gem0": { "x": 6, "y": 508, "graphic": "agemLL.def" },
"gem1": { "x": 556, "y": 508, "graphic": "agemLR.def" },
"gem2": { "x": 6, "y": 6, "graphic": "agemUL.def" },
"gem3": { "x": 556, "y": 6, "graphic": "agemUR.def" },
"background": "AdvMap.bmp",
"backgroundWorldView": "VWorld.bmp",
"HeroList": { "size": 5, "x": 609, "y": 196, "movePoints": "IMOBIL.DEF", "manaPoints": "IMANA.DEF", "arrowUp": "IAM012.DEF", "arrowDown": "IAM013.DEF" },
"TownList": { "size": 5, "x": 747, "y": 196, "arrowUp": "IAM014.DEF", "arrowDown": "IAM015.DEF" },
"Minimap": { "width": 144, "height": 144, "x": 630, "y": 26 },
"Overview": { "pics": 4, "size": 4, "graphic": "OvCast.pcx" },
"Statusbar": { "x": 7, "y": 556, "graphic": "AdRollvr.bmp" },
"ResDataBar": { "x": 3, "y": 575, "graphic": "ARESBAR.bmp", "offsetX": 32, "offsetY": 2, "resSpace": 85, "resDateSpace": 85 },
"ButtonKingdomOv": { "x": 679, "y": 196, "graphic": "IAM002.DEF", "playerColoured": 1 },
"ButtonUnderground": { "x": 711, "y": 196, "graphic": "IAM010.DEF", "playerColoured": 1, "additionalDefs": [ "IAM003.DEF" ] },
"ButtonQuestLog": { "x": 679, "y": 228, "graphic": "IAM004.DEF", "playerColoured": 1 },
"ButtonSleepWake": { "x": 711, "y": 228, "graphic": "IAM005.DEF", "playerColoured": 1, "additionalDefs":["IAM011.DEF"] },
"ButtonMoveHero": { "x": 679, "y": 260, "graphic": "IAM006.DEF", "playerColoured": 1 },
"ButtonSpellbook": { "x": 711, "y": 260, "graphic": "IAM007.DEF", "playerColoured": 1 },
"ButtonAdvOptions": { "x": 679, "y": 292, "graphic": "IAM008.DEF", "playerColoured": 1 },
"ButtonSysOptions": { "x": 711, "y": 292, "graphic": "IAM009.DEF", "playerColoured": 1 },
"ButtonNextHero": { "x": 679, "y": 324, "graphic": "IAM000.DEF", "playerColoured": 1 },
"ButtonEndTurn": { "x": 679, "y": 356, "graphic": "IAM001.DEF", "playerColoured": 1 }
}
]
}

View File

@ -111,7 +111,7 @@
"additionalProperties" : false,
"default": {},
"required" : [
"screenRes",
"resolution",
"bitsPerPixel",
"fullscreen",
"realFullscreen",
@ -124,15 +124,16 @@
"targetfps"
],
"properties" : {
"screenRes" : {
"resolution" : {
"type" : "object",
"additionalProperties" : false,
"required" : [ "width", "height" ],
"required" : [ "width", "height", "scaling" ],
"properties" : {
"width" : { "type" : "number" },
"height" : { "type" : "number" }
"height" : { "type" : "number" },
"scaling" : { "type" : "number" }
},
"default": {"width" : 800, "height": 600 }
"default": {"width" : 800, "height": 600, "scaling" : 100 }
},
"bitsPerPixel" : {
"type" : "number",

View File

@ -0,0 +1,869 @@
{
"options" : {
// player-colored images used for background
"imagesPlayerColored" : [ "AdvMap.pcx" ],
},
"items":
[
// Background sections - left side
{
"type": "adventureMapImage",
"name" : "backgroundLeftTop",
"image" : "AdvMap.pcx",
"area" : { "left": 0, "top" : 0, "width" : 7, "height" : 52 }
},
{
"type": "adventureMapImage",
"name" : "backgroundLeftCenter",
"image" : "AdvMap.pcx",
"area" : { "left": 0, "top" : 52, "width" : 7, "bottom" : 91 }
},
{
"type": "adventureMapImage",
"name" : "backgroundLeftBottom",
"image" : "AdvMap.pcx",
"area" : { "left": 0, "bottom" : 0, "width" : 7, "height" : 91 }
},
// Background sections - top side
{
"type": "adventureMapImage",
"name" : "backgroundTopLeft",
"image" : "AdvMap.pcx",
"area" : { "left": 7, "top" : 0, "width" : 193, "height" : 7 }
},
{
"type": "adventureMapImage",
"name" : "backgroundTopCenter",
"image" : "AdvMap.pcx",
"area" : { "left": 200, "top" : 0, "right" : 244, "height" : 7 }
},
{
"type": "adventureMapImage",
"name" : "backgroundTopRight",
"image" : "AdvMap.pcx",
"area" : { "width": 45, "top" : 0, "right" : 199, "height" : 7 }
},
// Background sections - bottom side
{
"type": "adventureMapImage",
"name" : "backgroundBottomLeft",
"image" : "AdvMap.pcx",
"area" : { "left": 7, "bottom" : 0, "width" : 44, "height" : 47 }
},
{
"type": "adventureMapImage",
"name" : "backgroundBottomCenter",
"image" : "AdvMap.pcx",
"area" : { "left": 51, "bottom" : 0, "right" : 244, "height" : 47 }
},
{
"type": "adventureMapImage",
"name" : "backgroundBottomRight",
"image" : "AdvMap.pcx",
"area" : { "width": 45, "bottom" : 0, "right" : 199, "height" : 47 }
},
// Background sections - side panel
{
"type": "adventureMapImage",
"name" : "backgroundRightMinimap",
"image" : "AdvMap.pcx",
"area" : { "right": 0, "top" : 0, "width" : 199, "height" : 196 }
},
{
"type": "adventureMapImage",
"name": "backgroundHeroListBorderLeft",
"image" : "AdvMap.pcx",
"area": { "top": 196, "bottom" : 211, "right" : 191, "width" : 8 }
},
{
"type": "adventureMapImage",
"name": "backgroundTownListBorderRight",
"image" : "AdvMap.pcx",
"area": { "top": 196, "bottom" : 211, "right" : 0, "width" : 5 }
},
// Game area
{
"type": "adventureMapGameArea",
"name": "mapView",
"area": { "top": 7, "bottom" : 47, "left" : 7, "right" : 199 }
},
// Minimap
{
"type": "adventureMinimap",
"name": "minimap",
"area": { "top": 26, "right" : 26, "width" : 144, "height" : 144 }
},
// Adventure map buttons
{
"type": "adventureMapContainer",
"name" : "buttonsContainer",
"hideWhen" : "worldViewMode",
"area": { "top": 196, "right" : 57, "width" : 64, "height" : 192 },
"items" : [
{
"type" : "adventureMapButton",
"name" : "buttonKingdomOverview",
"image" : "IAM002.DEF",
"help" : "core.help.293",
"hotkey": "adventureKingdomOverview",
"playerColored" : true,
"area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapContainer",
"hideWhen" : "mapLayerSurface",
"area": { "top" : 0, "left": 32, "width" : 32, "height" : 32 },
"items" : [
{
"type": "adventureMapButton",
"name": "buttonUnderground",
"image" : "IAM010.DEF",
"help" : "core.help.294",
"hotkey": "adventureToggleMapLevel",
"playerColored" : true,
"area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 }
}
],
},
{
"type": "adventureMapContainer",
"hideWhen" : "mapLayerUnderground",
"area": { "top" : 0, "left": 32, "width" : 32, "height" : 32 },
"items" : [
{
"type": "adventureMapButton",
"name": "buttonSurface",
"image" : "IAM003.DEF",
"help" : "core.help.294",
"hotkey": "adventureToggleMapLevel",
"playerColored" : true,
"area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 }
}
],
},
{
"type": "adventureMapButton",
"name": "buttonQuestLog",
"image" : "IAM004.DEF",
"help" : "core.help.295",
"hotkey": "adventureQuestLog",
"playerColored" : true,
"area": { "top" : 32, "left": 0, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapContainer",
"hideWhen" : "heroAwake",
"area": { "top" : 32, "left": 32, "width" : 32, "height" : 32 },
"items" : [
{
"type": "adventureMapButton",
"name": "buttonSleep",
"image" : "IAM005.DEF",
"help" : "core.help.296",
"hotkey": "adventureSetHeroAsleep",
"playerColored" : true,
"area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 }
}
]
},
{
"type": "adventureMapContainer",
"hideWhen" : "heroSleeping",
"area": { "top" : 32, "left": 32, "width" : 32, "height" : 32 },
"items" : [
{
"type": "adventureMapButton",
"name": "buttonWake",
"image" : "IAM011.DEF",
"help" : "core.help.296",
"hotkey": "adventureSetHeroAwake",
"playerColored" : true,
"area": { "top" : 0, "left": 0, "width" : 32, "height" : 32 }
}
]
},
{
"type": "adventureMapButton",
"name": "buttonMove",
"image" : "IAM006.DEF",
"help" : "core.help.297",
"hotkey": "adventureMoveHero",
"playerColored" : true,
"area": { "top" : 64, "left": 0, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapButton",
"name": "buttonCast",
"image" : "IAM007.DEF",
"help" : "core.help.298",
"hotkey": "adventureCastSpell",
"playerColored" : true,
"area": { "top" : 64, "left": 32, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapButton",
"name": "buttonAdventureOptions",
"image" : "IAM008.DEF",
"help" : "core.help.299",
"hotkey": "adventureGameOptions",
"playerColored" : true,
"area": { "top" : 96, "left": 0, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapButton",
"name": "buttonSystemOptions",
"image" : "IAM009.DEF",
"help" : "core.help.300",
"hotkey": "globalOptions",
"playerColored" : true,
"area": { "top" : 96, "left": 32, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapButton",
"name": "buttonNextHero",
"image" : "IAM000.DEF",
"help" : "core.help.301",
"hotkey": "adventureNextHero",
"playerColored" : true,
"area": { "top" : 128, "left": 0, "width" : 64, "height" : 32 }
},
{
"type": "adventureMapButton",
"name": "buttonEndTurn",
"image" : "IAM001.DEF",
"hotkey": "gameEndTurn",
"help" : "core.help.302",
"playerColored" : true,
"area": { "top" : 160, "left": 0, "width" : 64, "height" : 32 }
}
]
},
// Town / Hero lists for small (600-664) vertical resolution
{
"type": "adventureMapContainer",
"name" : "listContainerSmall",
"hideWhen" : "worldViewMode",
"area": { "top": 196, "right" : 0, "width" : 193, "height" : 196 },
"exists" : { "heightMax" : 664 },
"items" : [
{
"type": "adventureMapImage",
"name": "backgroundHeroListBorderRight",
"image" : "AdvMap.pcx",
"area": { "top": 0, "bottom" : 0, "right" : 121, "width" : 6 },
"sourceArea": { "top": 196, "bottom" : 211, "right" : 121, "width" : 6 }
},
{
"type": "adventureMapImage",
"name": "backgroundTownListBorderLeft",
"image" : "AdvMap.pcx",
"area": { "top": 0, "bottom" : 0, "right" : 53, "width" : 4 },
"sourceArea": { "top": 196, "bottom" : 211, "right" : 53, "width" : 4 }
},
{
"type": "adventureMapImage",
"name" : "backgroundBelowHeroTownList",
"image" : "AdvMap.pcx",
"area" : { "right": 0, "left" : 0, "bottom" : 0, "height" : 4 },
"sourceArea": { "bottom" : 208, "height" : 4, "right" : 0, "width" : 193 }
},
// Hero List
{
"type": "adventureMapHeroList",
"name" : "heroList",
"area": { "top": 0, "right" : 125, "width" : 68, "height" : 193 },
"scrollUp" : {
"type": "adventureMapButton",
"name": "heroListScrollUp",
"image" : "IAM012.DEF",
"help" : "core.help.303",
"area": { "top" : 0, "left": 2, "width" : 64, "height" : 16 }
},
"scrollDown" : {
"type": "adventureMapButton",
"name": "heroListScrollDown",
"image" : "IAM013.DEF",
"help" : "core.help.304",
"area": { "bottom" : 0, "left": 2, "width" : 64, "height" : 16 }
},
"item" : { "top" : 16, "left": 3, "width" : 62, "height" : 32 },
"itemsOffset" : { "x" : 0, "y" : 32 },
"itemsCount" : 5
},
// Town List
{
"type": "adventureMapTownList",
"name" : "townList",
"area": { "top": 0, "right" : 3, "width" : 51, "height" : 193 },
"scrollUp" : {
"type": "adventureMapButton",
"name": "townListScrollUp",
"image" : "IAM014.DEF",
"help" : "core.help.306",
"area": { "top" : 0, "left": 1, "width" : 48, "height" : 16 }
},
"scrollDown" : {
"type": "adventureMapButton",
"name": "townListScrollDown",
"image" : "IAM015.DEF",
"help" : "core.help.307",
"area": { "bottom" : 0, "left": 1, "width" : 48, "height" : 16 }
},
"item" : { "top" : 16, "left": 2, "width" : 48, "height" : 32 },
"itemsOffset" : { "x" : 0, "y" : 32 },
"itemsCount" : 5
},
]
},
{
"type": "adventureMapContainer",
"name" : "emptyAreaFillSmall",
"hideWhen" : "worldViewMode",
"area": { "top": 392, "right" : 3, "width" : 190, "bottom" : 211 },
"exists" : { "heightMax" : 664 },
"items" : [
{
"type": "adventureMapImage",
"name": "emptyAreaFillSmallImage",
"image" : "DiBoxBck.pcx",
"area": { "top": 0, "bottom" : 0, "left" : 0, "right" : 0 },
"sourceArea": { "left" : 0, "top" : 0, "width" : 256, "height" : 256 }
},
]
},
// Town / Hero lists for large (664+) vertical resolution
{
"type": "adventureMapContainer",
"name" : "listContainerLarge",
"hideWhen" : "worldViewMode",
"area": { "top": 196, "right" : 0, "width" : 193, "height" : 260 },
"exists" : { "heightMin" : 664 },
"items" : [
{
"type": "adventureMapImage",
"name": "backgroundHeroListBorderRight",
"image" : "AdvMap.pcx",
"area": { "top": 0, "bottom" : 0, "right" : 121, "width" : 6 },
"sourceArea": { "top": 196, "bottom" : 211, "right" : 121, "width" : 6 }
},
{
"type": "adventureMapImage",
"name": "backgroundTownListBorderLeft",
"image" : "AdvMap.pcx",
"area": { "top": 0, "bottom" : 0, "right" : 53, "width" : 4 },
"sourceArea": { "top": 196, "bottom" : 211, "right" : 53, "width" : 4 }
},
{
"type": "adventureMapImage",
"name" : "backgroundBelowHeroTownList",
"image" : "AdvMap.pcx",
"area" : { "right": 0, "left" : 0, "bottom" : 0, "height" : 4 },
"sourceArea": { "bottom" : 208, "height" : 4, "right" : 0, "width" : 193 }
},
// Hero List
{
"type": "adventureMapHeroList",
"name" : "heroList",
"area": { "top": 0, "right" : 125, "width" : 68, "height" : 257 },
"item" : { "top" : 1, "left": 3, "width" : 62, "height" : 32 },
"itemsOffset" : { "x" : 0, "y" : 32 },
"itemsCount" : 8
},
// Town List
{
"type": "adventureMapTownList",
"name" : "townList",
"area": { "top": 0, "right" : 3, "width" : 51, "height" : 257 },
"scrollUp" : {
"type": "adventureMapButton",
"name": "townListScrollUp",
"image" : "IAM014.DEF",
"help" : "core.help.306",
"area": { "top" : 0, "left": 1, "width" : 48, "height" : 16 }
},
"scrollDown" : {
"type": "adventureMapButton",
"name": "townListScrollDown",
"image" : "IAM015.DEF",
"help" : "core.help.307",
"area": { "bottom" : 0, "left": 1, "width" : 48, "height" : 16 }
},
"item" : { "top" : 16, "left": 1, "width" : 48, "height" : 32 },
"itemsOffset" : { "x" : 0, "y" : 32 },
"itemsCount" : 7
},
// Fill empty area below buttons
{
"type": "adventureMapImage",
"name" : "backgroundBelowButtons",
"image" : "DiBoxBck.pcx",
"area": { "top": 192, "bottom" : 3, "right" : 57, "width" : 64 },
"sourceArea": { "left" : 0, "top" : 0, "width" : 256, "height" : 256 }
},
]
},
{
"type": "adventureMapContainer",
"name" : "emptyAreaFillLarge",
"hideWhen" : "worldViewMode",
"area": { "top": 456, "right" : 3, "width" : 190, "bottom" : 211 },
"exists" : { "heightMin" : 664 },
"items" : [
{
"type": "adventureMapImage",
"name": "emptyAreaFillLargeImage",
"image" : "DiBoxBck.pcx",
"area": { "top": 0, "bottom" : 0, "left" : 0, "right" : 0 },
"sourceArea": { "left" : 0, "top" : 0, "width" : 256, "height" : 256 }
},
]
},
{
"type": "adventureMapContainer",
"name" : "adventureInfobarContainer",
"hideWhen" : "worldViewMode",
"area" : { "bottom": 0, "right" : 0, "width" : 199, "height" : 211 },
"items" : [
// Infobar
{
"type": "adventureMapImage",
"name" : "backgroundRightInfobar",
"image" : "AdvMap.pcx",
"area": { "top": 0, "bottom" : 0, "left" : 0, "right" : 0 },
"sourceArea" : { "bottom": 0, "right" : 0, "width" : 199, "height" : 211 }
},
{
"type": "adventureInfobar",
"name": "infoBar",
"area": { "bottom": 44, "right" : 19, "width" : 175, "height" : 168 }
}
]
},
// Status bar
{
"type": "adventureStatusBar",
"name": "statusBar",
"image" : "DiBoxBck.pcx",
"area": { "left": 8, "bottom" : 26, "right" : 199, "height" : 18 }
},
// Resource & Data bar
{
"type": "adventurePlayerTexture",
"name" : "backgroundLeftOfResourceDateBar",
"image" : "DiBoxBck.pcx",
"area" : { "left": 3, "bottom" : 4, "right" : 797, "height" : 21 }
},
{
"type": "adventureResourceDateBar",
"name": "resourceDataBar",
"image" : "AResBar.pcx",
"area": { "bottom" : 3, "right" : 3, "height" : 22, "width" : 794 },
"wood" : { "x" : 37, "y" : 3 },
"mercury" : { "x" : 121, "y" : 3 },
"ore" : { "x" : 205, "y" : 3 },
"sulfur" : { "x" : 289, "y" : 3 },
"crystal" : { "x" : 373, "y" : 3 },
"gems" : { "x" : 457, "y" : 3 },
"gold" : { "x" : 541, "y" : 3 },
"date" : { "x" : 619, "y" : 3 }
},
// World view mode widgets
{
"type": "adventureMapContainer",
"name" : "worldViewContainer",
"hideWhen" : "mapViewMode",
"area": { "top": 195, "right" : 3, "width" : 190, "bottom" : 26 },
"items" : [
{
"type": "adventureMapImage",
"name": "worldViewBackground",
"image" : "VWorld.pcx",
"area": { "left" : 0, "right" : 0, "top" : 0, "height" : 381 },
"sourceArea": { "left" : 0, "right" : 0, "top" : 0, "bottom" : 0 }
},
{
"type": "adventureMapButton",
"name": "worldViewZoom1",
"image" : "VWMAG1.DEF",
"hotkey": "adventureViewWorld1",
"area": { "top" : 23, "left": 1, "width" : 60, "height" : 32 }
},
{
"type": "adventureMapButton",
"name": "worldViewZoom2",
"image" : "VWMAG2.DEF",
"hotkey": "adventureViewWorld2",
"area": { "top" : 23, "left": 64, "width" : 60, "height" : 32 }
},
{
"type": "adventureMapButton",
"name": "worldViewZoom4",
"image" : "VWMAG4.DEF",
"hotkey": "adventureViewWorld4",
"area": { "top" : 23, "left": 128, "width" : 60, "height" : 32 }
},
{
"type": "adventureMapButton",
"name": "worldViewSurface",
"image" : "IAM003.DEF",
"hotkey": "adventureToggleMapLevel",
"playerColored" : true,
"area": { "top" : 79, "left": 343, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapButton",
"name": "worldViewPuzzle",
"borderColor" : "gold",
"image" : "VWPUZ.DEF",
"hotkey": "adventureViewPuzzle",
"area": { "top" : 343, "left": 5, "width" : 66, "height" : 32 }
},
{
"type": "adventureMapButton",
"name": "worldViewUnderground",
"image" : "IAM010.DEF",
"playerColored" : true,
"hotkey": "adventureToggleMapLevel",
"area": { "top" : 343, "left": 79, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapButton",
"name": "worldViewExit",
"borderColor" : "gold",
"image" : "IOKAY32.DEF",
"hotkey": "adventureExitWorldView",
"area": { "top" : 343, "left": 119, "width" : 66, "height" : 32 }
},
// World view - objects icons
{
"type": "adventureMapIcon",
"name": "worldViewIconTown",
"image" : "VwSymbol.def",
"index" : 0,
"perPlayer" : 19,
"area": { "top" : 59, "left": 5, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapIcon",
"name": "worldViewIconHero",
"image" : "VwSymbol.def",
"index" : 1,
"perPlayer" : 19,
"area": { "top" : 79, "left": 5, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapIcon",
"name": "worldViewIconArtifact",
"image" : "VwSymbol.def",
"index" : 2,
"perPlayer" : 19,
"area": { "top" : 99, "left": 5, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapIcon",
"name": "worldViewIconTeleporter",
"image" : "VwSymbol.def",
"index" : 3,
"perPlayer" : 19,
"area": { "top" : 119, "left": 5, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapIcon",
"name": "worldViewIconGate",
"image" : "VwSymbol.def",
"index" : 4,
"perPlayer" : 19,
"area": { "top" : 139, "left": 5, "width" : 32, "height" : 32 }
},
// World view - mines icons
{
"type": "adventureMapIcon",
"name": "worldViewIconMineWood",
"image" : "VwSymbol.def",
"index" : 5,
"perPlayer" : 19,
"area": { "top" : 183, "left": 5, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapIcon",
"name": "worldViewIconMineMercury",
"image" : "VwSymbol.def",
"index" : 6,
"perPlayer" : 19,
"area": { "top" : 203, "left": 5, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapIcon",
"name": "worldViewIconMineOre",
"image" : "VwSymbol.def",
"index" : 7,
"perPlayer" : 19,
"area": { "top" : 223, "left": 5, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapIcon",
"name": "worldViewIconMineSulfur",
"image" : "VwSymbol.def",
"index" : 8,
"perPlayer" : 19,
"area": { "top" : 243, "left": 5, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapIcon",
"name": "worldViewIconMineCrystal",
"image" : "VwSymbol.def",
"index" : 9,
"perPlayer" : 19,
"area": { "top" : 263, "left": 5, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapIcon",
"name": "worldViewIconMineGems",
"image" : "VwSymbol.def",
"index" : 10,
"perPlayer" : 19,
"area": { "top" : 283, "left": 5, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapIcon",
"name": "worldViewIconMineGold",
"image" : "VwSymbol.def",
"index" : 11,
"perPlayer" : 19,
"area": { "top" : 303, "left": 5, "width" : 32, "height" : 32 }
},
// World view - resources icons
{
"type": "adventureMapIcon",
"name": "worldViewIconResourceWood",
"image" : "VwSymbol.def",
"index" : 12,
"perPlayer" : 19,
"area": { "top" : 183, "left": 154, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapIcon",
"name": "worldViewIconResourceMercury",
"image" : "VwSymbol.def",
"index" : 13,
"perPlayer" : 19,
"area": { "top" : 203, "left": 154, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapIcon",
"name": "worldViewIconResourceOre",
"image" : "VwSymbol.def",
"index" : 14,
"perPlayer" : 19,
"area": { "top" : 223, "left": 154, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapIcon",
"name": "worldViewIconResourceSulfur",
"image" : "VwSymbol.def",
"index" : 15,
"perPlayer" : 19,
"area": { "top" : 243, "left": 154, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapIcon",
"name": "worldViewIconResourceCrystal",
"image" : "VwSymbol.def",
"index" : 16,
"perPlayer" : 19,
"area": { "top" : 263, "left": 154, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapIcon",
"name": "worldViewIconResourceGems",
"image" : "VwSymbol.def",
"index" : 17,
"perPlayer" : 19,
"area": { "top" : 283, "left": 154, "width" : 32, "height" : 32 }
},
{
"type": "adventureMapIcon",
"name": "worldViewIconResourceGold",
"image" : "VwSymbol.def",
"index" : 18,
"perPlayer" : 19,
"area": { "top" : 303, "left": 154, "width" : 32, "height" : 32 }
},
{
"name": "worldViewLabelTitle",
"type": "label",
"font": "big",
"alignment": "center",
"color": "yellow",
"position": {"x": 94, "y": 11},
"text": "core.genrltxt.611"
},
{
"name": "worldViewLabelMine",
"type": "label",
"font": "calisto",
"alignment": "left",
"color": "white",
"position": {"x": 7, "y": 173},
"text": "core.genrltxt.617"
},
{
"name": "worldViewLabelResource",
"type": "label",
"font": "calisto",
"alignment": "right",
"color": "white",
"position": {"x": 185, "y": 190},
"text": "core.genrltxt.618"
},
{
"name": "worldViewLabelsObjects",
"type": "labelGroup",
"font": "calisto",
"alignment": "left",
"color": "white",
"items":
[
{
"position": {"x": 43, "y": 66},
"text": "core.genrltxt.612"
},
{
"position": {"x": 43, "y": 86},
"text": "core.genrltxt.613"
},
{
"position": {"x": 43, "y": 106},
"text": "core.genrltxt.614"
},
{
"position": {"x": 43, "y": 126},
"text": "core.genrltxt.615"
},
{
"position": {"x": 43, "y": 146},
"text": "core.genrltxt.616"
}
]
},
{
"name": "worldViewLabelsResources",
"type": "labelGroup",
"font": "calisto",
"alignment": "center",
"color": "white",
"items":
[
{
"position": {"x": 101, "y": 198},
"text": "core.genrltxt.619"
},
{
"position": {"x": 101, "y": 218},
"text": "core.genrltxt.620"
},
{
"position": {"x": 101, "y": 238},
"text": "core.genrltxt.621"
},
{
"position": {"x": 101, "y": 258},
"text": "core.genrltxt.622"
},
{
"position": {"x": 101, "y": 278},
"text": "core.genrltxt.623"
},
{
"position": {"x": 101, "y": 298},
"text": "core.genrltxt.624"
},
{
"position": {"x": 101, "y": 318},
"text": "core.genrltxt.625"
}
]
},
{
"type": "adventureMapImage",
"name" : "backgroundBelowWorldView",
"image" : "DiBoxBck.pcx",
"area": { "top": 381, "bottom" : 0, "left" : 0, "right" : 0 },
"sourceArea": { "left" : 0, "top" : 0, "width" : 256, "height" : 256 }
}
]
},
// GEMS - set of images with different image for each player
{
"type": "adventureMapContainer",
"name" : "overlayGemTopLeft",
"overlay" : true,
"area": { "left": 6, "top" : 6, "width" : 46, "height" : 46 },
"items" : [
{
"type": "adventureMapIcon",
"name" : "gemTopLeft",
"image" : "agemUL.def",
"index" : 0,
"perPlayer" : 1,
"area" : { "left": 0, "top" : 0, "right" : 0, "bottom" : 0 }
}
]
},
{
"type": "adventureMapContainer",
"name" : "overlayGemTopRight",
"overlay" : true,
"area": { "right": 198, "top" : 6, "width" : 46, "height" : 46 },
"items" : [
{
"type": "adventureMapIcon",
"name" : "gemTopRight",
"image" : "agemUR.def",
"index" : 0,
"perPlayer" : 1,
"area" : { "left": 0, "top" : 0, "right" : 0, "bottom" : 0 }
}
]
},
{
"type": "adventureMapContainer",
"name" : "overlayGemBottomLeft",
"overlay" : true,
"area": { "left": 6, "bottom" : 46, "width" : 46, "height" : 46 },
"items" : [
{
"type": "adventureMapIcon",
"name" : "gemBottomLeft",
"image" : "agemLL.def",
"index" : 0,
"perPlayer" : 1,
"area" : { "left": 0, "top" : 0, "right" : 0, "bottom" : 0 }
}
]
},
{
"type": "adventureMapContainer",
"name" : "overlayGemBottomRight",
"overlay" : true,
"area": { "right": 198, "bottom" : 46, "width" : 46, "height" : 46 },
"items" : [
{
"type": "adventureMapIcon",
"name" : "gemBottomRight",
"image" : "agemLR.def",
"index" : 0,
"perPlayer" : 1,
"area" : { "left": 0, "top" : 0, "right" : 0, "bottom" : 0 }
}
]
}
]
}

View File

@ -149,10 +149,6 @@
"text": "core.genrltxt.406",
"position": {"x": 45, "y": 145}
},
{
"text": "core.genrltxt.407",
"position": {"x": 45, "y": 175}
},
{
"text": "vcmi.battleOptions.skipBattleIntroMusic.hover",
"position": {"x": 45, "y": 175}
@ -196,18 +192,12 @@
"position": {"x": 10, "y": 143},
"callback": "mouseShadowChanged"
},
{
"name": "battleFieldCasualtiesPlaceholder",
"type": "picture",
"image": "settingsWindow/checkBoxEmpty",
"position": {"x": 10, "y": 173},
},
{
"name": "skipBattleIntroMusicCheckbox",
"type": "toggleButton",
"image": "sysopchk.def",
"help": "vcmi.battleOptions.skipBattleIntroMusic",
"position": {"x": 10, "y": 203},
"position": {"x": 10, "y": 173},
"callback": "skipBattleIntroMusicChanged"
},
{

View File

@ -58,6 +58,33 @@
]
},
{
"name": "scalingLabel",
"type": "label",
"font": "medium",
"alignment": "left",
"color": "white",
"position": {"x": 45, "y": 115},
"text": "vcmi.systemOptions.scalingButton.hover"
},
{
"name": "resolutionButton",
"type": "button",
"position": {"x": 10, "y": 113},
"image": "settingsWindow/button32",
"help": "vcmi.systemOptions.scalingButton",
"callback": "setGameScaling",
"items":
[
{
"name": "gearIcon",
"type": "picture",
"image": "settingsWindow/gear",
"position": {"x": 0, "y": 0 }
}
]
},
{
"name": "topCheckboxesLabels",
"type": "labelGroup",
@ -67,15 +94,15 @@
"items":
[
{
"position": {"x": 45, "y": 115},
"position": {"x": 45, "y": 145},
"text": "vcmi.systemOptions.fullscreenButton.hover"
},
{
"position": {"x": 45, "y": 145},
"position": {"x": 45, "y": 175},
"text": "vcmi.systemOptions.framerateButton.hover"
},
{
"position": {"x": 45, "y": 175},
"position": {"x": 45, "y": 205},
"text": "core.genrltxt.577"
},
@ -86,7 +113,7 @@
"type": "toggleButton",
"image": "sysopchk.def",
"help": "vcmi.systemOptions.fullscreenButton",
"position": {"x": 10, "y": 113},
"position": {"x": 10, "y": 143},
"callback": "fullscreenChanged"
},
{
@ -94,7 +121,7 @@
"type": "toggleButton",
"image": "sysopchk.def",
"help": "vcmi.systemOptions.framerateButton",
"position": {"x": 10, "y": 143},
"position": {"x": 10, "y": 173},
"callback": "framerateChanged"
},
@ -103,7 +130,7 @@
"type": "toggleButton",
"image": "sysopchk.def",
"help": "core.help.364",
"position": {"x": 10, "y": 173},
"position": {"x": 10, "y": 203},
"callback": "spellbookAnimationChanged"
},

View File

@ -133,6 +133,10 @@ if(APPLE)
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER vcmilauncher)
endif()
if (NOT APPLE_IOS AND NOT ANDROID)
target_link_libraries(vcmilauncher SDL2::SDL2)
endif()
target_link_libraries(vcmilauncher ${VCMI_LIB_TARGET} Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network)
target_include_directories(vcmilauncher
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}

View File

@ -110,7 +110,6 @@ MainWindow::MainWindow(QWidget * parent)
else
enterSetup();
ui->settingsView->isExtraResolutionsModEnabled = ui->modlistView->isExtraResolutionsModEnabled();
ui->settingsView->setDisplayList();
connect(ui->modlistView, &CModListView::extraResolutionsEnabledChanged,
ui->settingsView, &CSettingsView::fillValidResolutions);

View File

@ -25,6 +25,10 @@
#include "../../lib/CConfigHandler.h"
#include "../../lib/VCMIDirs.h"
#ifndef VCMI_MOBILE
#include <SDL2/SDL.h>
#endif
namespace
{
QString resolutionToString(const QSize & resolution)
@ -67,9 +71,9 @@ void CSettingsView::loadSettings()
{
ui->comboBoxShowIntro->setCurrentIndex(settings["video"]["showIntro"].Bool());
#ifdef Q_OS_IOS
ui->comboBoxFullScreen->setCurrentIndex(1);
ui->comboBoxFullScreen->setDisabled(true);
#ifdef VCMI_MOBILE
ui->comboBoxFullScreen->hide();
ui->labelFullScreen->hide();
#else
if (settings["video"]["realFullscreen"].Bool())
ui->comboBoxFullScreen->setCurrentIndex(2);
@ -106,66 +110,74 @@ void CSettingsView::loadSettings()
ui->comboBoxCursorType->setCurrentIndex((int)cursorTypeIndex);
}
void CSettingsView::fillValidResolutions(bool isExtraResolutionsModEnabled)
void CSettingsView::fillValidResolutions()
{
this->isExtraResolutionsModEnabled = isExtraResolutionsModEnabled;
fillValidResolutionsForScreen(ui->comboBoxDisplayIndex->isVisible() ? ui->comboBoxDisplayIndex->currentIndex() : 0);
}
#ifndef VCMI_MOBILE
static QVector<QSize> findAvailableResolutions(int displayIndex)
{
// Ugly workaround since we don't actually need SDL in Launcher
// However Qt at the moment provides no way to query list of available resolutions
QVector<QSize> result;
SDL_Init(SDL_INIT_VIDEO);
int modesCount = SDL_GetNumDisplayModes(displayIndex);
for (int i =0; i < modesCount; ++i)
{
SDL_DisplayMode mode;
if (SDL_GetDisplayMode(displayIndex, i, &mode) != 0)
continue;
QSize resolution(mode.w, mode.h);
result.push_back(resolution);
}
boost::range::sort(result, [](const auto & left, const auto & right)
{
return left.height() * left.width() < right.height() * right.width();
});
result.erase(boost::unique(result).end(), result.end());
SDL_Quit();
return result;
}
void CSettingsView::fillValidResolutionsForScreen(int screenIndex)
{
ui->comboBoxResolution->blockSignals(true); // avoid saving wrong resolution after adding first item from the list
ui->comboBoxResolution->clear();
// TODO: read available resolutions from all mods
QVariantList resolutions;
if(isExtraResolutionsModEnabled)
{
const auto extrasResolutionsPath = settings["launcher"]["extraResolutionsModPath"].String().c_str();
const auto extrasResolutionsJson = JsonUtils::JsonFromFile(CLauncherDirs::get().modsPath() + extrasResolutionsPath);
resolutions = extrasResolutionsJson.toMap().value(QLatin1String{"GUISettings"}).toList();
}
if(resolutions.isEmpty())
{
ui->comboBoxResolution->blockSignals(false);
ui->comboBoxResolution->addItem(resolutionToString({800, 600}));
return;
}
const auto screens = qGuiApp->screens();
const auto currentScreen = screenIndex < screens.size() ? screens[screenIndex] : qGuiApp->primaryScreen();
[[maybe_unused]] const auto screenSize = currentScreen->size();
QVector<QSize> resolutions = findAvailableResolutions(screenIndex);
for(const auto & entry : resolutions)
{
const auto resolutionMap = entry.toMap().value(QLatin1String{"resolution"}).toMap();
if(resolutionMap.isEmpty())
continue;
ui->comboBoxResolution->addItem(resolutionToString(entry));
const auto widthValue = resolutionMap[QLatin1String{"x"}];
const auto heightValue = resolutionMap[QLatin1String{"y"}];
if(!widthValue.isValid() || !heightValue.isValid())
continue;
const QSize resolution{widthValue.toInt(), heightValue.toInt()};
#ifndef VCMI_IOS
if(screenSize.width() < resolution.width() || screenSize.height() < resolution.height())
continue;
#endif
ui->comboBoxResolution->addItem(resolutionToString(resolution));
}
int resX = settings["video"]["screenRes"]["width"].Integer();
int resY = settings["video"]["screenRes"]["height"].Integer();
int resX = settings["video"]["resolution"]["width"].Integer();
int resY = settings["video"]["resolution"]["height"].Integer();
int resIndex = ui->comboBoxResolution->findText(resolutionToString({resX, resY}));
ui->comboBoxResolution->setCurrentIndex(resIndex);
ui->comboBoxResolution->blockSignals(false);
// if selected resolution no longer exists, force update value to the first resolution
// if selected resolution no longer exists, force update value to the largest (last) resolution
if(resIndex == -1)
ui->comboBoxResolution->setCurrentIndex(0);
ui->comboBoxResolution->setCurrentIndex(ui->comboBoxResolution->count() - 1);
}
#else
void CSettingsView::fillValidResolutionsForScreen(int screenIndex)
{
// resolutions are not selectable on mobile platforms
ui->comboBoxResolution->hide();
ui->labelResolution->hide();
}
#endif
CSettingsView::CSettingsView(QWidget * parent)
: QWidget(parent), ui(new Ui::CSettingsView)
@ -186,7 +198,7 @@ void CSettingsView::on_comboBoxResolution_currentTextChanged(const QString & arg
{
QStringList list = arg1.split("x");
Settings node = settings.write["video"]["screenRes"];
Settings node = settings.write["video"]["resolution"];
node["width"].Float() = list[0].toInt();
node["height"].Float() = list[1].toInt();
}

View File

@ -29,10 +29,8 @@ public:
void changeEvent(QEvent *event) override;
void showEvent(QShowEvent * event) override;
bool isExtraResolutionsModEnabled{};
public slots:
void fillValidResolutions(bool isExtraResolutionsModEnabled);
void fillValidResolutions();
private slots:
void on_comboBoxResolution_currentTextChanged(const QString & arg1);

View File

@ -112,9 +112,9 @@
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<y>-107</y>
<width>620</width>
<height>793</height>
<height>745</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="3,0,0,0">
@ -253,17 +253,17 @@
</property>
<item>
<property name="text">
<string>Off</string>
<string>Windowed</string>
</property>
</item>
<item>
<property name="text">
<string>On</string>
<string>Borderless fullscreen</string>
</property>
</item>
<item>
<property name="text">
<string>Real</string>
<string>Exclusive fullscreen</string>
</property>
</item>
</widget>

View File

@ -17,10 +17,7 @@
VCMI_LIB_NAMESPACE_BEGIN
using namespace config;
SettingsStorage settings;
CConfigHandler conf;
template<typename Accessor>
SettingsStorage::NodeAccessor<Accessor>::NodeAccessor(SettingsStorage & _parent, std::vector<std::string> _path):
@ -178,123 +175,6 @@ JsonNode & Settings::operator[](const std::string & value)
{
return node[value];
}
//
// template DLL_LINKAGE struct SettingsStorage::NodeAccessor<SettingsListener>;
// template DLL_LINKAGE struct SettingsStorage::NodeAccessor<Settings>;
static void setButton(ButtonInfo &button, const JsonNode &g)
{
button.x = static_cast<int>(g["x"].Float());
button.y = static_cast<int>(g["y"].Float());
button.playerColoured = g["playerColoured"].Float();
button.defName = g["graphic"].String();
if (!g["additionalDefs"].isNull()) {
const JsonVector &defs_vec = g["additionalDefs"].Vector();
for(const JsonNode &def : defs_vec) {
button.additionalDefs.push_back(def.String());
}
}
}
static void setGem(AdventureMapConfig &ac, const int gem, const JsonNode &g)
{
ac.gemX[gem] = static_cast<int>(g["x"].Float());
ac.gemY[gem] = static_cast<int>(g["y"].Float());
ac.gemG.push_back(g["graphic"].String());
}
CConfigHandler::CConfigHandler()
: current(nullptr)
{
}
void config::CConfigHandler::init()
{
/* Read resolutions. */
const JsonNode config(ResourceID("config/resolutions.json"));
const JsonVector &guisettings_vec = config["GUISettings"].Vector();
for(const JsonNode &g : guisettings_vec)
{
std::pair<int, int> curRes(static_cast<int>(g["resolution"]["x"].Float()), static_cast<int>(g["resolution"]["y"].Float()));
GUIOptions *current = &conf.guiOptions[curRes];
current->ac.inputLineLength = static_cast<int>(g["InGameConsole"]["maxInputPerLine"].Float());
current->ac.outputLineLength = static_cast<int>(g["InGameConsole"]["maxOutputPerLine"].Float());
current->ac.advmapX = static_cast<int>(g["AdvMap"]["x"].Float());
current->ac.advmapY = static_cast<int>(g["AdvMap"]["y"].Float());
current->ac.advmapW = static_cast<int>(g["AdvMap"]["width"].Float());
current->ac.advmapH = static_cast<int>(g["AdvMap"]["height"].Float());
current->ac.smoothMove = g["AdvMap"]["smoothMove"].Float();
current->ac.puzzleSepia = g["AdvMap"]["puzzleSepia"].Float();
current->ac.screenFading = g["AdvMap"]["screenFading"].isNull() ? true : g["AdvMap"]["screenFading"].Float(); // enabled by default
current->ac.objectFading = g["AdvMap"]["objectFading"].isNull() ? true : g["AdvMap"]["objectFading"].Float();
current->ac.infoboxX = static_cast<int>(g["InfoBox"]["x"].Float());
current->ac.infoboxY = static_cast<int>(g["InfoBox"]["y"].Float());
setGem(current->ac, 0, g["gem0"]);
setGem(current->ac, 1, g["gem1"]);
setGem(current->ac, 2, g["gem2"]);
setGem(current->ac, 3, g["gem3"]);
current->ac.mainGraphic = g["background"].String();
current->ac.worldViewGraphic = g["backgroundWorldView"].String();
current->ac.hlistX = static_cast<int>(g["HeroList"]["x"].Float());
current->ac.hlistY = static_cast<int>(g["HeroList"]["y"].Float());
current->ac.hlistSize = static_cast<int>(g["HeroList"]["size"].Float());
current->ac.hlistMB = g["HeroList"]["movePoints"].String();
current->ac.hlistMN = g["HeroList"]["manaPoints"].String();
current->ac.hlistAU = g["HeroList"]["arrowUp"].String();
current->ac.hlistAD = g["HeroList"]["arrowDown"].String();
current->ac.tlistX = static_cast<int>(g["TownList"]["x"].Float());
current->ac.tlistY = static_cast<int>(g["TownList"]["y"].Float());
current->ac.tlistSize = static_cast<int>(g["TownList"]["size"].Float());
current->ac.tlistAU = g["TownList"]["arrowUp"].String();
current->ac.tlistAD = g["TownList"]["arrowDown"].String();
current->ac.minimapW = static_cast<int>(g["Minimap"]["width"].Float());
current->ac.minimapH = static_cast<int>(g["Minimap"]["height"].Float());
current->ac.minimapX = static_cast<int>(g["Minimap"]["x"].Float());
current->ac.minimapY = static_cast<int>(g["Minimap"]["y"].Float());
current->ac.overviewPics = static_cast<int>(g["Overview"]["pics"].Float());
current->ac.overviewSize = static_cast<int>(g["Overview"]["size"].Float());
current->ac.overviewBg = g["Overview"]["graphic"].String();
current->ac.statusbarX = static_cast<int>(g["Statusbar"]["x"].Float());
current->ac.statusbarY = static_cast<int>(g["Statusbar"]["y"].Float());
current->ac.statusbarG = g["Statusbar"]["graphic"].String();
current->ac.resdatabarX = static_cast<int>(g["ResDataBar"]["x"].Float());
current->ac.resdatabarY = static_cast<int>(g["ResDataBar"]["y"].Float());
current->ac.resOffsetX = static_cast<int>(g["ResDataBar"]["offsetX"].Float());
current->ac.resOffsetY = static_cast<int>(g["ResDataBar"]["offsetY"].Float());
current->ac.resDist = static_cast<int>(g["ResDataBar"]["resSpace"].Float());
current->ac.resDateDist = static_cast<int>(g["ResDataBar"]["resDateSpace"].Float());
current->ac.resdatabarG = g["ResDataBar"]["graphic"].String();
setButton(current->ac.kingOverview, g["ButtonKingdomOv"]);
setButton(current->ac.underground, g["ButtonUnderground"]);
setButton(current->ac.questlog, g["ButtonQuestLog"]);
setButton(current->ac.sleepWake, g["ButtonSleepWake"]);
setButton(current->ac.moveHero, g["ButtonMoveHero"]);
setButton(current->ac.spellbook, g["ButtonSpellbook"]);
setButton(current->ac.advOptions, g["ButtonAdvOptions"]);
setButton(current->ac.sysOptions, g["ButtonSysOptions"]);
setButton(current->ac.nextHero, g["ButtonNextHero"]);
setButton(current->ac.endTurn, g["ButtonEndTurn"]);
}
const JsonNode& screenRes = settings["video"]["screenRes"];
SetResolution(static_cast<int>(screenRes["width"].Float()), static_cast<int>(screenRes["height"].Float()));
}
// Force instantiation of the SettingsStorage::NodeAccessor class template.
// That way method definitions can sit in the cpp file

View File

@ -112,83 +112,6 @@ public:
friend class SettingsStorage;
};
namespace config
{
struct DLL_LINKAGE ButtonInfo
{
std::string defName;
std::vector<std::string> additionalDefs;
int x, y; //position on the screen
bool playerColoured; //if true button will be colored to main player's color (works properly only for appropriate 8bpp graphics)
};
/// Struct which holds data about position of several GUI elements at the adventure map screen
struct DLL_LINKAGE AdventureMapConfig
{
//minimap properties
int minimapX, minimapY, minimapW, minimapH;
//statusbar
int statusbarX, statusbarY; //pos
std::string statusbarG; //graphic name
//resdatabar
int resdatabarX, resdatabarY, resDist, resDateDist, resOffsetX, resOffsetY; //pos
std::string resdatabarG; //graphic name
//infobox
int infoboxX, infoboxY;
//advmap
int advmapX, advmapY, advmapW, advmapH;
bool smoothMove;
bool puzzleSepia;
bool screenFading;
bool objectFading;
//general properties
std::string mainGraphic;
std::string worldViewGraphic;
//buttons
ButtonInfo kingOverview, underground, questlog, sleepWake, moveHero, spellbook, advOptions,
sysOptions, nextHero, endTurn;
//hero list
int hlistX, hlistY, hlistSize;
std::string hlistMB, hlistMN, hlistAU, hlistAD;
//town list
int tlistX, tlistY, tlistSize;
std::string tlistAU, tlistAD;
//gems
int gemX[4], gemY[4];
std::vector<std::string> gemG;
//in-game console
int inputLineLength, outputLineLength;
//kingdom overview
int overviewPics, overviewSize; //pic count in def and count of visible slots
std::string overviewBg; //background name
};
struct DLL_LINKAGE GUIOptions
{
AdventureMapConfig ac;
};
/// Handles adventure map screen settings
class DLL_LINKAGE CConfigHandler
{
GUIOptions *current; // pointer to current gui options
public:
using GuiOptionsMap = std::map<std::pair<int, int>, GUIOptions>;
GuiOptionsMap guiOptions;
void init();
CConfigHandler();
GUIOptions *go() { return current; };
void SetResolution(int x, int y)
{
std::pair<int,int> index(x, y);
if (guiOptions.count(index) == 0)
current = nullptr;
else
current = &guiOptions.at(index);
}
};
}
extern DLL_LINKAGE SettingsStorage settings;
extern DLL_LINKAGE config::CConfigHandler conf;
VCMI_LIB_NAMESPACE_END

View File

@ -191,9 +191,6 @@ MainWindow::MainWindow(QWidget* parent) :
QApplication::quit();
}
conf.init();
logGlobal->info("Loading settings");
loadTranslation();
ui->setupUi(this);