summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt7
-rw-r--r--src/actorsprite.cpp2
-rw-r--r--src/being.cpp8
-rw-r--r--src/being.h2
-rw-r--r--src/chatlogger.cpp115
-rw-r--r--src/chatlogger.h9
-rw-r--r--src/client.cpp950
-rw-r--r--src/client.h25
-rw-r--r--src/configuration.cpp11
-rw-r--r--src/configuration.h1
-rw-r--r--src/effectmanager.cpp8
-rw-r--r--src/event.h6
-rw-r--r--src/game.cpp881
-rw-r--r--src/game.h2
-rw-r--r--src/graphics.cpp90
-rw-r--r--src/graphics.h76
-rw-r--r--src/gui/abilitieswindow.cpp6
-rw-r--r--src/gui/buydialog.cpp2
-rw-r--r--src/gui/changeemaildialog.cpp4
-rw-r--r--src/gui/changepassworddialog.cpp4
-rw-r--r--src/gui/charselectdialog.cpp2
-rw-r--r--src/gui/chatwindow.cpp2
-rw-r--r--src/gui/connectiondialog.cpp2
-rw-r--r--src/gui/equipmentwindow.cpp2
-rw-r--r--src/gui/gui.cpp36
-rw-r--r--src/gui/helpwindow.cpp4
-rw-r--r--src/gui/inventorywindow.cpp2
-rw-r--r--src/gui/npcdialog.cpp2
-rw-r--r--src/gui/popupmenu.cpp2
-rw-r--r--src/gui/questswindow.cpp290
-rw-r--r--src/gui/questswindow.h71
-rw-r--r--src/gui/recorder.cpp2
-rw-r--r--src/gui/register.cpp2
-rw-r--r--src/gui/selldialog.cpp2
-rw-r--r--src/gui/serverdialog.cpp18
-rw-r--r--src/gui/setup_audio.cpp2
-rw-r--r--src/gui/shortcutwindow.cpp9
-rw-r--r--src/gui/skilldialog.cpp8
-rw-r--r--src/gui/tradewindow.cpp2
-rw-r--r--src/gui/truetypefont.cpp145
-rw-r--r--src/gui/truetypefont.h25
-rw-r--r--src/gui/unregisterdialog.cpp4
-rw-r--r--src/gui/updaterwindow.cpp20
-rw-r--r--src/gui/viewport.cpp2
-rw-r--r--src/gui/widgets/avatarlistbox.cpp6
-rw-r--r--src/gui/widgets/browserbox.cpp24
-rw-r--r--src/gui/widgets/button.cpp20
-rw-r--r--src/gui/widgets/chattab.h1
-rw-r--r--src/gui/widgets/checkbox.cpp18
-rw-r--r--src/gui/widgets/desktop.cpp2
-rw-r--r--src/gui/widgets/itemcontainer.cpp17
-rw-r--r--src/gui/widgets/itemlinkhandler.cpp27
-rw-r--r--src/gui/widgets/itemlinkhandler.h6
-rw-r--r--src/gui/widgets/itemshortcutcontainer.cpp47
-rw-r--r--src/gui/widgets/itemshortcutcontainer.h2
-rw-r--r--src/gui/widgets/label.cpp24
-rw-r--r--src/gui/widgets/listbox.cpp6
-rw-r--r--src/gui/widgets/popup.cpp4
-rw-r--r--src/gui/widgets/progressbar.cpp7
-rw-r--r--src/gui/widgets/radiobutton.cpp20
-rw-r--r--src/gui/widgets/shoplistbox.cpp3
-rw-r--r--src/gui/widgets/tab.cpp15
-rw-r--r--src/gui/widgets/textbox.cpp24
-rw-r--r--src/gui/widgets/textpreview.cpp21
-rw-r--r--src/gui/widgets/window.cpp86
-rw-r--r--src/gui/widgets/window.h21
-rw-r--r--src/gui/windowmenu.cpp49
-rw-r--r--src/inventory.cpp2
-rw-r--r--src/itemshortcut.cpp7
-rw-r--r--src/itemshortcut.h2
-rw-r--r--src/joystick.cpp14
-rw-r--r--src/keyboardconfig.cpp1
-rw-r--r--src/keyboardconfig.h7
-rw-r--r--src/log.cpp114
-rw-r--r--src/log.h77
-rw-r--r--src/map.cpp2
-rw-r--r--src/net/download.cpp18
-rw-r--r--src/net/manaserv/charhandler.cpp8
-rw-r--r--src/net/manaserv/chathandler.cpp6
-rw-r--r--src/net/manaserv/connection.cpp8
-rw-r--r--src/net/manaserv/effecthandler.cpp4
-rw-r--r--src/net/manaserv/guildhandler.cpp22
-rw-r--r--src/net/manaserv/inventoryhandler.cpp13
-rw-r--r--src/net/manaserv/loginhandler.cpp2
-rw-r--r--src/net/manaserv/network.cpp20
-rw-r--r--src/net/manaserv/partyhandler.cpp4
-rw-r--r--src/net/manaserv/playerhandler.cpp30
-rw-r--r--src/net/net.cpp5
-rw-r--r--src/net/playerhandler.h7
-rw-r--r--src/net/tmwa/abilityhandler.cpp2
-rw-r--r--src/net/tmwa/beinghandler.cpp12
-rw-r--r--src/net/tmwa/buysellhandler.cpp2
-rw-r--r--src/net/tmwa/charserverhandler.cpp4
-rw-r--r--src/net/tmwa/gamehandler.cpp4
-rw-r--r--src/net/tmwa/generalhandler.cpp2
-rw-r--r--src/net/tmwa/inventoryhandler.cpp22
-rw-r--r--src/net/tmwa/inventoryhandler.h6
-rw-r--r--src/net/tmwa/loginhandler.cpp16
-rw-r--r--src/net/tmwa/messagein.cpp2
-rw-r--r--src/net/tmwa/messageout.cpp4
-rw-r--r--src/net/tmwa/network.cpp34
-rw-r--r--src/net/tmwa/partyhandler.cpp4
-rw-r--r--src/net/tmwa/playerhandler.cpp116
-rw-r--r--src/net/tmwa/playerhandler.h13
-rw-r--r--src/openglgraphics.cpp5
-rw-r--r--src/particle.cpp4
-rw-r--r--src/particleemitter.cpp5
-rw-r--r--src/playerinfo.cpp4
-rw-r--r--src/resources/abilitydb.cpp4
-rw-r--r--src/resources/animation.cpp8
-rw-r--r--src/resources/attributes.cpp42
-rw-r--r--src/resources/beinginfo.cpp8
-rw-r--r--src/resources/chardb.cpp4
-rw-r--r--src/resources/dye.cpp12
-rw-r--r--src/resources/dye.h2
-rw-r--r--src/resources/emotedb.cpp16
-rw-r--r--src/resources/hairdb.cpp10
-rw-r--r--src/resources/image.cpp24
-rw-r--r--src/resources/imageset.cpp2
-rw-r--r--src/resources/imagewriter.cpp10
-rw-r--r--src/resources/itemdb.cpp42
-rw-r--r--src/resources/mapreader.cpp39
-rw-r--r--src/resources/monsterdb.cpp12
-rw-r--r--src/resources/music.cpp2
-rw-r--r--src/resources/npcdb.cpp4
-rw-r--r--src/resources/questdb.cpp232
-rw-r--r--src/resources/questdb.h139
-rw-r--r--src/resources/resource.cpp2
-rw-r--r--src/resources/resourcemanager.cpp18
-rw-r--r--src/resources/settingsmanager.cpp20
-rw-r--r--src/resources/settingsmanager.h4
-rw-r--r--src/resources/soundeffect.cpp2
-rw-r--r--src/resources/spritedef.cpp32
-rw-r--r--src/resources/theme.cpp75
-rw-r--r--src/resources/theme.h10
-rw-r--r--src/sdlgraphics.cpp38
-rw-r--r--src/sdlgraphics.h3
-rw-r--r--src/sound.cpp28
-rw-r--r--src/text.cpp6
-rw-r--r--src/textparticle.cpp4
-rw-r--r--src/textrenderer.h139
-rw-r--r--src/units.cpp6
-rw-r--r--src/utils/mutex.h4
-rw-r--r--src/utils/sha256.cpp2
-rw-r--r--src/utils/stringutils.cpp26
-rw-r--r--src/utils/stringutils.h15
-rw-r--r--src/utils/xml.cpp16
-rw-r--r--src/utils/zlib.cpp8
-rw-r--r--src/video.cpp43
-rw-r--r--src/video.h4
150 files changed, 3022 insertions, 2044 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 4a6a93db..8738a41b 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -4,7 +4,7 @@ pkg_check_modules(SDL2 REQUIRED
SDL2_image
SDL2_mixer
SDL2_net
- SDL2_ttf)
+ SDL2_ttf>=2.0.12)
find_package(PhysFS REQUIRED)
find_package(CURL REQUIRED)
find_package(LibXml2 REQUIRED)
@@ -230,6 +230,8 @@ set(SRCS
gui/palette.h
gui/popupmenu.cpp
gui/popupmenu.h
+ gui/questswindow.cpp
+ gui/questswindow.h
gui/quitdialog.cpp
gui/quitdialog.h
gui/recorder.cpp
@@ -346,6 +348,8 @@ set(SRCS
resources/music.h
resources/npcdb.cpp
resources/npcdb.h
+ resources/questdb.cpp
+ resources/questdb.h
resources/resource.cpp
resources/resource.h
resources/resourcemanager.cpp
@@ -495,7 +499,6 @@ set(SRCS
textmanager.h
textparticle.cpp
textparticle.h
- textrenderer.h
tileset.h
units.cpp
units.h
diff --git a/src/actorsprite.cpp b/src/actorsprite.cpp
index 7cb46bd4..f31297e9 100644
--- a/src/actorsprite.cpp
+++ b/src/actorsprite.cpp
@@ -213,7 +213,7 @@ void ActorSprite::loadTargetCursor(const std::string &filename,
auto currentImageSet = resman->getImageSet(filename, width, height);
if (!currentImageSet)
{
- logger->log("Error loading target cursor: %s", filename.c_str());
+ Log::info("Error loading target cursor: %s", filename.c_str());
return;
}
diff --git a/src/being.cpp b/src/being.cpp
index 46b67f4b..91fe4bc0 100644
--- a/src/being.cpp
+++ b/src/being.cpp
@@ -481,15 +481,15 @@ void Being::setShowName(bool doShowName)
void Being::setGuildName(const std::string &name)
{
- logger->log("Got guild name \"%s\" for being %s(%i)", name.c_str(),
- mName.c_str(), mId);
+ Log::info("Got guild name \"%s\" for being %s(%i)", name.c_str(),
+ mName.c_str(), mId);
}
void Being::setGuildPos(const std::string &pos)
{
- logger->log("Got guild position \"%s\" for being %s(%i)", pos.c_str(),
- mName.c_str(), mId);
+ Log::info("Got guild position \"%s\" for being %s(%i)", pos.c_str(),
+ mName.c_str(), mId);
}
void Being::addGuild(Guild *guild)
diff --git a/src/being.h b/src/being.h
index 80620e71..43cc546c 100644
--- a/src/being.h
+++ b/src/being.h
@@ -166,7 +166,7 @@ class Being : public ActorSprite, public EventListener, public gcn::DeathListene
* Puts a damage bubble above this being.
*
* @param attacker the attacking being
- * @param damage the amount of damage recieved (0 means miss)
+ * @param damage the amount of damage received (0 means miss)
* @param type the attack type
* @param attackId the attack id (used for monsters)
*/
diff --git a/src/chatlogger.cpp b/src/chatlogger.cpp
index 119d3fc1..854f9780 100644
--- a/src/chatlogger.cpp
+++ b/src/chatlogger.cpp
@@ -38,6 +38,51 @@
#include "utils/stringutils.h"
+static std::string getDateString()
+{
+ time_t t = time(nullptr);
+ struct tm *now = localtime(&t);
+ char buffer[11];
+ strftime(buffer, sizeof(buffer), "%Y-%m-%d", now);
+ return buffer;
+}
+
+static std::string &secureName(std::string &name)
+{
+ const size_t sz = name.length();
+ for (size_t f = 0; f < sz; f ++)
+ {
+ const unsigned char ch = name[f];
+ if ((ch < '0' || ch > '9') &&
+ (ch < 'a' || ch > 'z') &&
+ (ch < 'A' || ch > 'Z') &&
+ ch != '-' &&
+ ch != '+' &&
+ ch != '=' &&
+ ch != '.' &&
+ ch != ',' &&
+ ch != ')' &&
+ ch != '(' &&
+ ch != '[' &&
+ ch != ']' &&
+ ch != '#')
+ {
+ name[f] = '_';
+ }
+ }
+ return name;
+}
+
+static void makeDir(const std::string &dir)
+{
+#ifdef _WIN32
+ mkdir(dir.c_str());
+#else
+ mkdir(dir.c_str(), S_IRWXU | S_IRGRP | S_IXGRP);
+#endif
+}
+
+
ChatLogger::~ChatLogger() = default;
void ChatLogger::setLogFile(const std::string &logFilename)
@@ -74,69 +119,32 @@ void ChatLogger::log(std::string str)
if (!mLogFile.is_open() || dateStr != mLogDate)
{
mLogDate = dateStr;
- setLogFile(strprintf("%s/%s/#General_%s.log", mLogDir.c_str(),
- mServerName.c_str(), dateStr.c_str()));
+ setLogFile(strprintf("%s/%s/#General_%s.log",
+ mLogDir.c_str(),
+ mServerName.c_str(),
+ dateStr.c_str()));
}
removeColors(str);
- writeTo(mLogFile, str);
+ mLogFile << str << std::endl;
}
void ChatLogger::log(std::string name, std::string str)
{
std::ofstream logFile;
- logFile.open(strprintf("%s/%s/%s_%s.log", mLogDir.c_str(), mServerName.c_str(),
- secureName(name).c_str(), getDateString().c_str()).c_str(),
+ logFile.open(strprintf("%s/%s/%s_%s.log",
+ mLogDir.c_str(),
+ mServerName.c_str(),
+ secureName(name).c_str(),
+ getDateString().c_str())
+ .c_str(),
std::ios_base::app);
if (!logFile.is_open())
return;
removeColors(str);
- writeTo(logFile, str);
-
- if (logFile.is_open())
- logFile.close();
-}
-
-std::string ChatLogger::getDateString()
-{
- time_t t = time(nullptr);
- struct tm *now = localtime(&t);
- char buffer[11];
- strftime(buffer, sizeof(buffer), "%Y-%m-%d", now);
- return buffer;
-}
-
-std::string ChatLogger::secureName(std::string &name)
-{
- const size_t sz = name.length();
- for (size_t f = 0; f < sz; f ++)
- {
- const unsigned char ch = name[f];
- if ((ch < '0' || ch > '9') &&
- (ch < 'a' || ch > 'z') &&
- (ch < 'A' || ch > 'Z') &&
- ch != '-' &&
- ch != '+' &&
- ch != '=' &&
- ch != '.' &&
- ch != ',' &&
- ch != ')' &&
- ch != '(' &&
- ch != '[' &&
- ch != ']' &&
- ch != '#')
- {
- name[f] = '_';
- }
- }
- return name;
-}
-
-void ChatLogger::writeTo(std::ofstream &file, const std::string &str)
-{
- file << str << std::endl;
+ logFile << str << std::endl;
}
void ChatLogger::setServerName(const std::string &serverName)
@@ -158,12 +166,3 @@ void ChatLogger::setServerName(const std::string &serverName)
closedir(dir);
}
}
-
-void ChatLogger::makeDir(const std::string &dir)
-{
-#ifdef _WIN32
- mkdir(dir.c_str());
-#else
- mkdir(dir.c_str(), S_IRWXU | S_IRGRP | S_IXGRP);
-#endif
-}
diff --git a/src/chatlogger.h b/src/chatlogger.h
index 59d9f28c..8e4118dc 100644
--- a/src/chatlogger.h
+++ b/src/chatlogger.h
@@ -35,13 +35,8 @@ class ChatLogger
* Enters a message in the log. The message will be timestamped.
*/
void log(std::string str);
-
void log(std::string name, std::string str);
- static std::string getDateString();
-
- static std::string secureName(std::string &str);
-
void setServerName(const std::string &serverName);
private:
@@ -50,10 +45,6 @@ class ChatLogger
*/
void setLogFile(const std::string &logFilename);
- static void writeTo(std::ofstream &file, const std::string &str);
-
- static void makeDir(const std::string &dir);
-
std::ofstream mLogFile;
std::string mLogDir;
std::string mServerName;
diff --git a/src/client.cpp b/src/client.cpp
index f0254d50..7c8763a1 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -29,6 +29,7 @@
#include "game.h"
#include "itemshortcut.h"
#include "keyboardconfig.h"
+#include "log.h"
#include "playerrelations.h"
#include "sound.h"
@@ -87,6 +88,8 @@
#include <sys/stat.h>
#include <cassert>
+#include <guichan/exception.hpp>
+
// TODO: Get rid of these globals
std::string errorMessage;
LoginData loginData;
@@ -94,7 +97,6 @@ LoginData loginData;
Config config; /**< Global settings (config.xml) */
Configuration branding; /**< XML branding information reader */
Configuration paths; /**< XML default paths information reader */
-Logger *logger; /**< Log object */
ChatLogger *chatLogger; /**< Chat log object */
KeyboardConfig keyboard;
@@ -113,7 +115,7 @@ volatile int frame_count = 0; /**< Counts the frames during one second */
* Updates fps.
* Called every seconds by SDL_AddTimer()
*/
-Uint32 nextSecond(Uint32 interval, void *param)
+static Uint32 nextSecond(Uint32 interval, void *param)
{
fps = frame_count;
frame_count = 0;
@@ -174,8 +176,6 @@ Client::Client(const Options &options):
assert(!mInstance);
mInstance = this;
- logger = new Logger;
-
// Set default values for configuration files
branding.setDefaultValues(getBrandingDefaults());
paths.setDefaultValues(getPathsDefaults());
@@ -190,19 +190,18 @@ Client::Client(const Options &options):
initHomeDir();
initConfiguration();
+ // Configure logger
+ Log::init();
+ Log::setLogFile(mLocalDataDir + "/mana.log");
+ Log::setLogToStandardOut(config.logToStandardOut);
+ Log::info("%s", FULL_VERSION);
+
chatLogger = new ChatLogger;
if (options.chatLogDir.empty())
chatLogger->setLogDir(mLocalDataDir + "/logs/");
else
chatLogger->setLogDir(options.chatLogDir);
- // Configure logger
- logger->setLogFile(mLocalDataDir + "/mana.log");
- logger->setLogToStandardOut(config.logToStandardOut);
-
- // Log the mana version
- logger->log("%s", FULL_VERSION);
-
initScreenshotDir();
#if SDL_VERSION_ATLEAST(2, 24, 0)
@@ -210,10 +209,10 @@ Client::Client(const Options &options):
#endif
// Initialize SDL
- logger->log("Initializing SDL...");
+ Log::info("Initializing SDL...");
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0)
{
- logger->error(strprintf("Could not initialize SDL: %s",
+ Log::critical(strprintf("Could not initialize SDL: %s",
SDL_GetError()));
}
atexit(SDL_Quit);
@@ -222,7 +221,7 @@ Client::Client(const Options &options):
if (!FS::setWriteDir(mLocalDataDir))
{
- logger->error(strprintf("%s couldn't be set as write directory! "
+ Log::critical(strprintf("%s couldn't be set as write directory! "
"Exiting.", mLocalDataDir.c_str()));
}
@@ -285,7 +284,7 @@ Client::Client(const Options &options):
iconFile += ".png";
#endif
iconFile = ResourceManager::getPath(iconFile);
- logger->log("Loading icon from file: %s", iconFile.c_str());
+ Log::info("Loading icon from file: %s", iconFile.c_str());
#ifdef _WIN32
static SDL_SysWMinfo pInfo;
SDL_GetWindowWMInfo(mVideo.window(), &pInfo);
@@ -327,7 +326,7 @@ Client::Client(const Options &options):
{
mState = STATE_ERROR;
errorMessage = err;
- logger->log("Warning: %s", err);
+ Log::warn("%s", err);
}
// Initialize keyboard
@@ -423,15 +422,13 @@ Client::~Client()
SDL_FreeSurface(mIcon);
- logger->log("Quitting");
+ Log::info("Quitting");
delete userPalette;
XML::Writer writer(mConfigDir + "/config.xml");
if (writer.isValid())
serialize(writer, config);
- delete logger;
-
mInstance = nullptr;
}
@@ -441,506 +438,533 @@ int Client::exec()
while (mState != STATE_EXIT)
{
- Time::beginFrame();
-
- if (mGame)
+ // Handle SDL events
+ SDL_Event event;
+ while (SDL_PollEvent(&event))
{
- // Let the game handle the events while it is active
- mGame->handleInput();
- }
- else
- {
- // Handle SDL events
- SDL_Event event;
- while (SDL_PollEvent(&event))
+ switch (event.type)
{
- switch (event.type)
- {
- case SDL_QUIT:
- mState = STATE_EXIT;
- break;
-
- case SDL_WINDOWEVENT:
- switch (event.window.event) {
- case SDL_WINDOWEVENT_SIZE_CHANGED:
- handleWindowSizeChanged(event.window.data1,
- event.window.data2);
- break;
- }
+ case SDL_QUIT:
+ mState = STATE_EXIT;
+ break;
+
+ case SDL_WINDOWEVENT:
+ switch (event.window.event) {
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ handleWindowSizeChanged(event.window.data1,
+ event.window.data2);
break;
+ }
+ break;
- case SDL_KEYDOWN:
- if (keyboard.isEnabled())
+ case SDL_KEYDOWN:
+ if (keyboard.isEnabled())
+ {
+ const int tKey = keyboard.getKeyIndex(event.key.keysym.sym);
+ if (tKey == KeyboardConfig::KEY_WINDOW_SETUP)
{
- const int tKey = keyboard.getKeyIndex(event.key.keysym.sym);
- if (tKey == KeyboardConfig::KEY_WINDOW_SETUP)
- {
- setupWindow->setVisible(!setupWindow->isVisible());
- if (setupWindow->isVisible())
- setupWindow->requestMoveToTop();
- continue;
- }
+ setupWindow->setVisible(!setupWindow->isVisible());
+ if (setupWindow->isVisible())
+ setupWindow->requestMoveToTop();
+ continue;
}
- break;
}
- guiInput->pushInput(event);
- }
- }
-
- if (Net::getGeneralHandler())
- Net::getGeneralHandler()->flushNetwork();
-
- gui->logic();
- if (mGame)
- mGame->logic();
-
- sound.logic();
-
- // Update the screen when application is active, delay otherwise.
- if (isActive())
- {
- frame_count++;
- gui->draw();
- graphics->updateScreen();
- mFpsManager.limitFps(config.fpsLimit);
- }
- else
- {
- mFpsManager.limitFps(10);
- }
-
- // TODO: Add connect timeouts
- if (mState == STATE_CONNECT_GAME &&
- Net::getGameHandler()->isConnected())
- {
- Net::getLoginHandler()->disconnect();
- }
- else if (mState == STATE_CONNECT_SERVER &&
- mOldState == STATE_CONNECT_SERVER &&
- Net::getLoginHandler()->isConnected())
- {
- mState = STATE_LOGIN;
- }
- else if (mState == STATE_WORLD_SELECT && mOldState == STATE_UPDATE)
- {
- if (Net::getLoginHandler()->getWorlds().size() < 2)
- {
- mState = STATE_LOGIN;
- }
- }
- else if (mOldState == STATE_START ||
- (mOldState == STATE_GAME && mState != STATE_GAME))
- {
- auto *top = static_cast<gcn::Container*>(gui->getTop());
-
- mDesktop = new Desktop;
- top->add(mDesktop);
- mSetupButton = new Button("", "Setup", this);
- mSetupButton->setButtonPopupText(_("Setup"));
- mSetupButton->setButtonIcon("button-icon-setup.png");
- mSetupButton->setPosition(top->getWidth() - mSetupButton->getWidth()
- - 3, 3);
- top->add(mSetupButton);
-
- mDesktop->setSize(graphics->getWidth(), graphics->getHeight());
- }
+ if (setupWindow->isVisible() &&
+ keyboard.getNewKeyIndex() > KeyboardConfig::KEY_NO_VALUE)
+ {
+ keyboard.setNewKey(event.key.keysym.sym);
+ keyboard.callbackNewKey();
+ keyboard.setNewKeyIndex(KeyboardConfig::KEY_NO_VALUE);
+ continue;
+ }
- if (mState == STATE_SWITCH_LOGIN && mOldState == STATE_GAME)
- {
- Net::getGameHandler()->disconnect();
- }
+ // Check whether the game will handle the event
+ if (mGame && mGame->keyDownEvent(event.key))
+ continue;
- if (mState != mOldState)
- {
- {
- Event event(Event::StateChange);
- event.setInt("oldState", mOldState);
- event.setInt("newState", mState);
- event.trigger(Event::ClientChannel);
+ break;
}
- if (mOldState == STATE_GAME)
+ // Push input to GUI when not used
+ try
{
- delete mGame;
- mGame = nullptr;
+ guiInput->pushInput(event);
}
-
- mOldState = mState;
-
- // Get rid of the dialog of the previous state
- if (mCurrentDialog)
+ catch (gcn::Exception e)
{
- delete mCurrentDialog;
- mCurrentDialog = nullptr;
+ const char *err = e.getMessage().c_str();
+ Log::warn("Guichan input exception: %s", err);
}
- // State has changed, while the quitDialog was active, it might
- // not be correct anymore
- if (mQuitDialog)
- mQuitDialog->scheduleDelete();
+ }
- switch (mState)
- {
- case STATE_CHOOSE_SERVER:
- logger->log("State: CHOOSE SERVER");
+ update();
+ }
- // Don't allow an alpha opacity
- // lower than the default value
- gui->getTheme()->setMinimumOpacity(0.8f);
+ Net::unload();
- mCurrentDialog = new ServerDialog(&mCurrentServer,
- mConfigDir);
- break;
+ return 0;
+}
- case STATE_CONNECT_SERVER:
- logger->log("State: CONNECT SERVER");
+void Client::update()
+{
+ Time::beginFrame();
- Net::connectToServer(mCurrentServer);
+ mVideo.updateWindowSize();
+ checkGraphicsSize();
- mCurrentDialog = new ConnectionDialog(
- _("Connecting to server"), STATE_SWITCH_SERVER);
- break;
+ // Let the game handle continuous input while it is active
+ if (mGame)
+ mGame->handleInput();
- case STATE_LOGIN:
- logger->log("State: LOGIN");
- // Don't allow an alpha opacity
- // lower than the default value
- gui->getTheme()->setMinimumOpacity(0.8f);
+ if (Net::getGeneralHandler())
+ Net::getGeneralHandler()->flushNetwork();
- if (mOptions.username.empty() || mOptions.password.empty())
- {
- mCurrentDialog = new LoginDialog(&loginData);
- }
- else
- {
- mState = STATE_LOGIN_ATTEMPT;
- // Clear the password so that when login fails, the
- // dialog will show up next time.
- mOptions.password.clear();
- }
- break;
+ gui->logic();
+ if (mGame)
+ mGame->logic();
- case STATE_LOGIN_ATTEMPT:
- logger->log("State: LOGIN ATTEMPT");
- accountLogin(&loginData);
- mCurrentDialog = new ConnectionDialog(
- _("Logging in"), STATE_SWITCH_SERVER);
- break;
+ sound.logic();
- case STATE_WORLD_SELECT:
- logger->log("State: WORLD SELECT");
- {
- Worlds worlds = Net::getLoginHandler()->getWorlds();
-
- if (worlds.empty())
- {
- // Trust that the netcode knows what it's doing
- mState = STATE_UPDATE;
- }
- else if (worlds.size() == 1 || mOptions.chooseDefault)
- {
- Net::getLoginHandler()->chooseServer(0);
- mState = STATE_UPDATE;
- }
- else
- {
- mCurrentDialog = new WorldSelectDialog(std::move(worlds));
- }
- }
- break;
+ // Update the screen when application is active, delay otherwise.
+ if (isActive())
+ {
+ frame_count++;
+ gui->draw();
+ graphics->updateScreen();
+ mFpsManager.limitFps(config.fpsLimit);
+ }
+ else
+ {
+ mFpsManager.limitFps(10);
+ }
- case STATE_WORLD_SELECT_ATTEMPT:
- logger->log("State: WORLD SELECT ATTEMPT");
- mCurrentDialog = new ConnectionDialog(
- _("Entering game world"), STATE_WORLD_SELECT);
- break;
+ // TODO: Add connect timeouts
+ if (mState == STATE_CONNECT_GAME &&
+ Net::getGameHandler()->isConnected())
+ {
+ Net::getLoginHandler()->disconnect();
+ }
+ else if (mState == STATE_CONNECT_SERVER &&
+ mOldState == STATE_CONNECT_SERVER &&
+ Net::getLoginHandler()->isConnected())
+ {
+ mState = STATE_LOGIN;
+ }
+ else if (mState == STATE_WORLD_SELECT && mOldState == STATE_UPDATE)
+ {
+ if (Net::getLoginHandler()->getWorlds().size() < 2)
+ {
+ mState = STATE_LOGIN;
+ }
+ }
+ else if (mOldState == STATE_START ||
+ (mOldState == STATE_GAME && mState != STATE_GAME))
+ {
+ auto *top = static_cast<gcn::Container*>(gui->getTop());
+
+ mDesktop = new Desktop;
+ top->add(mDesktop);
+ mSetupButton = new Button("", "Setup", this);
+ mSetupButton->setButtonPopupText(_("Setup"));
+ mSetupButton->setButtonIcon("button-icon-setup.png");
+ mSetupButton->setPosition(top->getWidth() - mSetupButton->getWidth()
+ - 3, 3);
+ top->add(mSetupButton);
+
+ mDesktop->setSize(graphics->getWidth(), graphics->getHeight());
+ }
- case STATE_UPDATE:
- logger->log("State: UPDATE");
+ if (mState == STATE_SWITCH_LOGIN && mOldState == STATE_GAME)
+ {
+ Net::getGameHandler()->disconnect();
+ }
- if (mOptions.skipUpdate)
- {
- mState = STATE_LOAD_DATA;
- }
- else if (initUpdatesDir())
- {
- mCurrentDialog = new UpdaterWindow(mUpdateHost,
- mLocalDataDir + "/" + mUpdatesDir,
- mOptions.dataPath.empty());
- }
- break;
+ if (mState != mOldState)
+ {
+ {
+ Event event(Event::StateChange);
+ event.setInt("oldState", mOldState);
+ event.setInt("newState", mState);
+ event.trigger(Event::ClientChannel);
+ }
- case STATE_LOAD_DATA:
- logger->log("State: LOAD DATA");
+ if (mOldState == STATE_GAME)
+ {
+ delete mGame;
+ mGame = nullptr;
+ }
- // If another data path has been set,
- // we don't load any other files...
- if (mOptions.dataPath.empty())
- {
- // Add customdata directory
- ResourceManager::searchAndAddArchives(
- "customdata/",
- "zip",
- false);
- }
+ mOldState = mState;
- // TODO remove this as soon as inventoryhandler stops using this event
- Event::trigger(Event::ClientChannel, Event::LoadingDatabases);
+ // Get rid of the dialog of the previous state
+ if (mCurrentDialog)
+ {
+ delete mCurrentDialog;
+ mCurrentDialog = nullptr;
+ }
+ // State has changed, while the quitDialog was active, it might
+ // not be correct anymore
+ if (mQuitDialog)
+ mQuitDialog->scheduleDelete();
- // Load XML databases
- CharDB::load();
+ switch (mState)
+ {
+ case STATE_CHOOSE_SERVER:
+ Log::info("State: CHOOSE SERVER");
- delete itemDb;
+ // Don't allow an alpha opacity
+ // lower than the default value
+ gui->getTheme()->setMinimumOpacity(0.8f);
- switch (Net::getNetworkType())
- {
- case ServerType::TmwAthena:
- itemDb = new TmwAthena::TaItemDB;
- break;
- case ServerType::ManaServ:
- itemDb = new ManaServ::ManaServItemDB;
- break;
- default:
- // Nothing
- itemDb = nullptr;
- break;
- }
- assert(itemDb);
+ mCurrentDialog = new ServerDialog(&mCurrentServer,
+ mConfigDir);
+ break;
- // load settings.xml
- SettingsManager::load();
+ case STATE_CONNECT_SERVER:
+ Log::info("State: CONNECT SERVER");
- ActorSprite::load();
+ Net::connectToServer(mCurrentServer);
- mDesktop->reloadWallpaper();
+ mCurrentDialog = new ConnectionDialog(
+ _("Connecting to server"), STATE_SWITCH_SERVER);
+ break;
- mState = STATE_GET_CHARACTERS;
- break;
+ case STATE_LOGIN:
+ Log::info("State: LOGIN");
+ // Don't allow an alpha opacity
+ // lower than the default value
+ gui->getTheme()->setMinimumOpacity(0.8f);
- case STATE_GET_CHARACTERS:
- logger->log("State: GET CHARACTERS");
- Net::getCharHandler()->requestCharacters();
- mCurrentDialog = new ConnectionDialog(
- _("Requesting characters"),
- STATE_SWITCH_SERVER);
- break;
+ if (mOptions.username.empty() || mOptions.password.empty())
+ {
+ mCurrentDialog = new LoginDialog(&loginData);
+ }
+ else
+ {
+ mState = STATE_LOGIN_ATTEMPT;
+ // Clear the password so that when login fails, the
+ // dialog will show up next time.
+ mOptions.password.clear();
+ }
+ break;
- case STATE_CHAR_SELECT:
- logger->log("State: CHAR SELECT");
- // Don't allow an alpha opacity
- // lower than the default value
- gui->getTheme()->setMinimumOpacity(0.8f);
+ case STATE_LOGIN_ATTEMPT:
+ Log::info("State: LOGIN ATTEMPT");
+ accountLogin(&loginData);
+ mCurrentDialog = new ConnectionDialog(
+ _("Logging in"), STATE_SWITCH_SERVER);
+ break;
- mCurrentDialog = new CharSelectDialog(&loginData);
+ case STATE_WORLD_SELECT:
+ Log::info("State: WORLD SELECT");
+ {
+ Worlds worlds = Net::getLoginHandler()->getWorlds();
- if (!((CharSelectDialog*) mCurrentDialog)->selectByName(
- mOptions.character, CharSelectDialog::Choose))
+ if (worlds.empty())
{
- ((CharSelectDialog*) mCurrentDialog)->selectByName(
- config.lastCharacter,
- mOptions.chooseDefault ?
- CharSelectDialog::Choose :
- CharSelectDialog::Focus);
+ // Trust that the netcode knows what it's doing
+ mState = STATE_UPDATE;
}
+ else if (worlds.size() == 1 || mOptions.chooseDefault)
+ {
+ Net::getLoginHandler()->chooseServer(0);
+ mState = STATE_UPDATE;
+ }
+ else
+ {
+ mCurrentDialog = new WorldSelectDialog(std::move(worlds));
+ }
+ }
+ break;
- // Choosing character on the command line should work only
- // once, clear it so that 'switch character' works.
- mOptions.character.clear();
- mOptions.chooseDefault = false;
-
- break;
-
- case STATE_CONNECT_GAME:
- logger->log("State: CONNECT GAME");
-
- Net::getGameHandler()->connect();
- mCurrentDialog = new ConnectionDialog(
- _("Connecting to the game server"),
- Net::getNetworkType() == ServerType::TmwAthena ?
- STATE_CHOOSE_SERVER : STATE_SWITCH_CHARACTER);
- break;
-
- case STATE_CHANGE_MAP:
- logger->log("State: CHANGE_MAP");
-
- Net::getGameHandler()->connect();
- mCurrentDialog = new ConnectionDialog(
- _("Changing game servers"),
- STATE_SWITCH_CHARACTER);
- break;
-
- case STATE_GAME:
- logger->log("Memorizing selected character %s",
- local_player->getName().c_str());
- config.lastCharacter = local_player->getName();
-
- // Fade out logon-music here too to give the desired effect
- // of "flowing" into the game.
- sound.fadeOutMusic(1000);
-
- // Allow any alpha opacity
- gui->getTheme()->setMinimumOpacity(0.0f);
-
- delete mSetupButton;
- delete mDesktop;
- mSetupButton = nullptr;
- mDesktop = nullptr;
-
- mCurrentDialog = nullptr;
-
- logger->log("State: GAME");
- mGame = new Game;
- break;
-
- case STATE_LOGIN_ERROR:
- logger->log("State: LOGIN ERROR");
- showErrorDialog(errorMessage, STATE_LOGIN);
- break;
-
- case STATE_ACCOUNTCHANGE_ERROR:
- logger->log("State: ACCOUNT CHANGE ERROR");
- showErrorDialog(errorMessage, STATE_CHAR_SELECT);
- break;
+ case STATE_WORLD_SELECT_ATTEMPT:
+ Log::info("State: WORLD SELECT ATTEMPT");
+ mCurrentDialog = new ConnectionDialog(
+ _("Entering game world"), STATE_WORLD_SELECT);
+ break;
- case STATE_REGISTER_PREP:
- logger->log("State: REGISTER_PREP");
- Net::getLoginHandler()->getRegistrationDetails();
- mCurrentDialog = new ConnectionDialog(
- _("Requesting registration details"), STATE_LOGIN);
- break;
+ case STATE_UPDATE:
+ Log::info("State: UPDATE");
- case STATE_REGISTER:
- logger->log("State: REGISTER");
- mCurrentDialog = new RegisterDialog(&loginData);
- break;
+ if (mOptions.skipUpdate)
+ {
+ mState = STATE_LOAD_DATA;
+ }
+ else if (initUpdatesDir())
+ {
+ mCurrentDialog = new UpdaterWindow(mUpdateHost,
+ mLocalDataDir + "/" + mUpdatesDir,
+ mOptions.dataPath.empty());
+ }
+ break;
- case STATE_REGISTER_ATTEMPT:
- logger->log("Username is %s", loginData.username.c_str());
- Net::getLoginHandler()->registerAccount(&loginData);
- loginData.password.clear();
- break;
+ case STATE_LOAD_DATA:
+ Log::info("State: LOAD DATA");
- case STATE_CHANGEPASSWORD:
- logger->log("State: CHANGE PASSWORD");
- mCurrentDialog = new ChangePasswordDialog(&loginData);
- break;
+ // If another data path has been set,
+ // we don't load any other files...
+ if (mOptions.dataPath.empty())
+ {
+ // Add customdata directory
+ ResourceManager::searchAndAddArchives(
+ "customdata/",
+ "zip",
+ false);
+ }
- case STATE_CHANGEPASSWORD_ATTEMPT:
- logger->log("State: CHANGE PASSWORD ATTEMPT");
- Net::getLoginHandler()->changePassword(loginData.username,
- loginData.password,
- loginData.newPassword);
- break;
+ // TODO remove this as soon as inventoryhandler stops using this event
+ Event::trigger(Event::ClientChannel, Event::LoadingDatabases);
- case STATE_CHANGEPASSWORD_SUCCESS:
- logger->log("State: CHANGE PASSWORD SUCCESS");
- showOkDialog(_("Password Change"),
- _("Password changed successfully!"),
- STATE_CHAR_SELECT);
- loginData.password.clear();
- loginData.newPassword.clear();
- break;
+ // Load XML databases
+ CharDB::load();
- case STATE_CHANGEEMAIL:
- logger->log("State: CHANGE EMAIL");
- mCurrentDialog = new ChangeEmailDialog(&loginData);
- break;
+ delete itemDb;
- case STATE_CHANGEEMAIL_ATTEMPT:
- logger->log("State: CHANGE EMAIL ATTEMPT");
- Net::getLoginHandler()->changeEmail(loginData.email);
- break;
+ switch (Net::getNetworkType())
+ {
+ case ServerType::TmwAthena:
+ itemDb = new TmwAthena::TaItemDB;
+ break;
+ case ServerType::ManaServ:
+ itemDb = new ManaServ::ManaServItemDB;
+ break;
+ default:
+ // Nothing
+ itemDb = nullptr;
+ break;
+ }
+ assert(itemDb);
- case STATE_CHANGEEMAIL_SUCCESS:
- logger->log("State: CHANGE EMAIL SUCCESS");
- showOkDialog(_("Email Change"),
- _("Email changed successfully!"),
- STATE_CHAR_SELECT);
- break;
+ // load settings.xml
+ SettingsManager::load();
- case STATE_UNREGISTER:
- logger->log("State: UNREGISTER");
- mCurrentDialog = new UnRegisterDialog(&loginData);
- break;
+ ActorSprite::load();
- case STATE_UNREGISTER_ATTEMPT:
- logger->log("State: UNREGISTER ATTEMPT");
- Net::getLoginHandler()->unregisterAccount(
- loginData.username, loginData.password);
- break;
+ mDesktop->reloadWallpaper();
- case STATE_UNREGISTER_SUCCESS:
- logger->log("State: UNREGISTER SUCCESS");
- Net::getLoginHandler()->disconnect();
+ mState = STATE_GET_CHARACTERS;
+ break;
- showOkDialog(_("Unregister Successful"),
- _("Farewell, come back any time..."),
- STATE_CHOOSE_SERVER);
- loginData.clear();
- break;
+ case STATE_GET_CHARACTERS:
+ Log::info("State: GET CHARACTERS");
+ Net::getCharHandler()->requestCharacters();
+ mCurrentDialog = new ConnectionDialog(
+ _("Requesting characters"),
+ STATE_SWITCH_SERVER);
+ break;
- case STATE_SWITCH_SERVER:
- logger->log("State: SWITCH SERVER");
+ case STATE_CHAR_SELECT:
+ Log::info("State: CHAR SELECT");
+ // Don't allow an alpha opacity
+ // lower than the default value
+ gui->getTheme()->setMinimumOpacity(0.8f);
- Net::getLoginHandler()->disconnect();
- Net::getGameHandler()->disconnect();
+ mCurrentDialog = new CharSelectDialog(&loginData);
- mCurrentServer.hostname.clear();
- mState = STATE_CHOOSE_SERVER;
- break;
+ if (!((CharSelectDialog*) mCurrentDialog)->selectByName(
+ mOptions.character, CharSelectDialog::Choose))
+ {
+ ((CharSelectDialog*) mCurrentDialog)->selectByName(
+ config.lastCharacter,
+ mOptions.chooseDefault ?
+ CharSelectDialog::Choose :
+ CharSelectDialog::Focus);
+ }
- case STATE_SWITCH_LOGIN:
- logger->log("State: SWITCH LOGIN");
+ // Choosing character on the command line should work only
+ // once, clear it so that 'switch character' works.
+ mOptions.character.clear();
+ mOptions.chooseDefault = false;
- Net::getLoginHandler()->disconnect();
+ break;
- mState = STATE_CONNECT_SERVER;
- break;
+ case STATE_CONNECT_GAME:
+ Log::info("State: CONNECT GAME");
- case STATE_SWITCH_CHARACTER:
- logger->log("State: SWITCH CHARACTER");
+ Net::getGameHandler()->connect();
+ mCurrentDialog = new ConnectionDialog(
+ _("Connecting to the game server"),
+ Net::getNetworkType() == ServerType::TmwAthena ?
+ STATE_CHOOSE_SERVER : STATE_SWITCH_CHARACTER);
+ break;
- // Done with game
- Net::getGameHandler()->disconnect();
+ case STATE_CHANGE_MAP:
+ Log::info("State: CHANGE_MAP");
- mState = STATE_GET_CHARACTERS;
- break;
+ Net::getGameHandler()->connect();
+ mCurrentDialog = new ConnectionDialog(
+ _("Changing game servers"),
+ STATE_SWITCH_CHARACTER);
+ break;
- case STATE_LOGOUT_ATTEMPT:
- logger->log("State: LOGOUT ATTEMPT");
- // TODO
- break;
+ case STATE_GAME:
+ Log::info("Memorizing selected character %s",
+ local_player->getName().c_str());
+ config.lastCharacter = local_player->getName();
- case STATE_WAIT:
- logger->log("State: WAIT");
- break;
+ // Fade out logon-music here too to give the desired effect
+ // of "flowing" into the game.
+ sound.fadeOutMusic(1000);
- case STATE_EXIT:
- logger->log("State: EXIT");
- break;
+ // Allow any alpha opacity
+ gui->getTheme()->setMinimumOpacity(0.0f);
- case STATE_FORCE_QUIT:
- logger->log("State: FORCE QUIT");
- mState = STATE_EXIT;
- break;
+ delete mSetupButton;
+ delete mDesktop;
+ mSetupButton = nullptr;
+ mDesktop = nullptr;
- case STATE_ERROR:
- logger->log("State: ERROR");
- logger->log("Error: %s", errorMessage.c_str());
- showErrorDialog(errorMessage, STATE_CHOOSE_SERVER);
- Net::getGameHandler()->disconnect();
- break;
+ mCurrentDialog = nullptr;
- default:
- mState = STATE_FORCE_QUIT;
- break;
- }
+ Log::info("State: GAME");
+ mGame = new Game;
+ break;
+
+ case STATE_LOGIN_ERROR:
+ Log::info("State: LOGIN ERROR");
+ showErrorDialog(errorMessage, STATE_LOGIN);
+ break;
+
+ case STATE_ACCOUNTCHANGE_ERROR:
+ Log::info("State: ACCOUNT CHANGE ERROR");
+ showErrorDialog(errorMessage, STATE_CHAR_SELECT);
+ break;
+
+ case STATE_REGISTER_PREP:
+ Log::info("State: REGISTER_PREP");
+ Net::getLoginHandler()->getRegistrationDetails();
+ mCurrentDialog = new ConnectionDialog(
+ _("Requesting registration details"), STATE_LOGIN);
+ break;
+
+ case STATE_REGISTER:
+ Log::info("State: REGISTER");
+ mCurrentDialog = new RegisterDialog(&loginData);
+ break;
+
+ case STATE_REGISTER_ATTEMPT:
+ Log::info("Username is %s", loginData.username.c_str());
+ Net::getLoginHandler()->registerAccount(&loginData);
+ loginData.password.clear();
+ break;
+
+ case STATE_CHANGEPASSWORD:
+ Log::info("State: CHANGE PASSWORD");
+ mCurrentDialog = new ChangePasswordDialog(&loginData);
+ break;
+
+ case STATE_CHANGEPASSWORD_ATTEMPT:
+ Log::info("State: CHANGE PASSWORD ATTEMPT");
+ Net::getLoginHandler()->changePassword(loginData.username,
+ loginData.password,
+ loginData.newPassword);
+ break;
+
+ case STATE_CHANGEPASSWORD_SUCCESS:
+ Log::info("State: CHANGE PASSWORD SUCCESS");
+ showOkDialog(_("Password Change"),
+ _("Password changed successfully!"),
+ STATE_CHAR_SELECT);
+ loginData.password.clear();
+ loginData.newPassword.clear();
+ break;
+
+ case STATE_CHANGEEMAIL:
+ Log::info("State: CHANGE EMAIL");
+ mCurrentDialog = new ChangeEmailDialog(&loginData);
+ break;
+
+ case STATE_CHANGEEMAIL_ATTEMPT:
+ Log::info("State: CHANGE EMAIL ATTEMPT");
+ Net::getLoginHandler()->changeEmail(loginData.email);
+ break;
+
+ case STATE_CHANGEEMAIL_SUCCESS:
+ Log::info("State: CHANGE EMAIL SUCCESS");
+ showOkDialog(_("Email Change"),
+ _("Email changed successfully!"),
+ STATE_CHAR_SELECT);
+ break;
+
+ case STATE_UNREGISTER:
+ Log::info("State: UNREGISTER");
+ mCurrentDialog = new UnRegisterDialog(&loginData);
+ break;
+
+ case STATE_UNREGISTER_ATTEMPT:
+ Log::info("State: UNREGISTER ATTEMPT");
+ Net::getLoginHandler()->unregisterAccount(
+ loginData.username, loginData.password);
+ break;
+
+ case STATE_UNREGISTER_SUCCESS:
+ Log::info("State: UNREGISTER SUCCESS");
+ Net::getLoginHandler()->disconnect();
+
+ showOkDialog(_("Unregister Successful"),
+ _("Farewell, come back any time..."),
+ STATE_CHOOSE_SERVER);
+ loginData.clear();
+ break;
+
+ case STATE_SWITCH_SERVER:
+ Log::info("State: SWITCH SERVER");
+
+ Net::getLoginHandler()->disconnect();
+ Net::getGameHandler()->disconnect();
+
+ mCurrentServer.hostname.clear();
+ mState = STATE_CHOOSE_SERVER;
+ break;
+
+ case STATE_SWITCH_LOGIN:
+ Log::info("State: SWITCH LOGIN");
+
+ Net::getLoginHandler()->disconnect();
+
+ mState = STATE_CONNECT_SERVER;
+ break;
+
+ case STATE_SWITCH_CHARACTER:
+ Log::info("State: SWITCH CHARACTER");
+
+ // Done with game
+ Net::getGameHandler()->disconnect();
+
+ mState = STATE_GET_CHARACTERS;
+ break;
+
+ case STATE_LOGOUT_ATTEMPT:
+ Log::info("State: LOGOUT ATTEMPT");
+ // TODO
+ break;
+
+ case STATE_WAIT:
+ Log::info("State: WAIT");
+ break;
+
+ case STATE_EXIT:
+ Log::info("State: EXIT");
+ break;
+
+ case STATE_FORCE_QUIT:
+ Log::info("State: FORCE QUIT");
+ mState = STATE_EXIT;
+ break;
+
+ case STATE_ERROR:
+ Log::info("State: ERROR");
+ Log::error("%s", errorMessage.c_str());
+ showErrorDialog(errorMessage, STATE_CHOOSE_SERVER);
+ Net::getGameHandler()->disconnect();
+ break;
+
+ default:
+ mState = STATE_FORCE_QUIT;
+ break;
}
}
-
- Net::unload();
-
- return 0;
}
void Client::showOkDialog(const std::string &title,
@@ -989,7 +1013,7 @@ void Client::initRootDir()
Configuration portable;
portable.init(portableName);
- logger->log("Portable file: %s", portableName.c_str());
+ Log::info("Portable file: %s", portableName.c_str());
if (mOptions.localDataDir.empty())
{
@@ -997,8 +1021,8 @@ void Client::initRootDir()
if (!dir.empty())
{
mOptions.localDataDir = mRootDir + dir;
- logger->log("Portable data dir: %s",
- mOptions.localDataDir.c_str());
+ Log::info("Portable data dir: %s",
+ mOptions.localDataDir.c_str());
}
}
@@ -1008,8 +1032,8 @@ void Client::initRootDir()
if (!dir.empty())
{
mOptions.configDir = mRootDir + dir;
- logger->log("Portable config dir: %s",
- mOptions.configDir.c_str());
+ Log::info("Portable config dir: %s",
+ mOptions.configDir.c_str());
}
}
@@ -1019,8 +1043,8 @@ void Client::initRootDir()
if (!dir.empty())
{
mOptions.screenshotDir = mRootDir + dir;
- logger->log("Portable screenshot dir: %s",
- mOptions.screenshotDir.c_str());
+ Log::info("Portable screenshot dir: %s",
+ mOptions.screenshotDir.c_str());
}
}
}
@@ -1052,7 +1076,7 @@ void Client::initHomeDir()
if (mkdir_r(mLocalDataDir.c_str()))
{
- logger->error(strprintf(_("%s doesn't exist and can't be created! "
+ Log::critical(strprintf(_("%s doesn't exist and can't be created! "
"Exiting."), mLocalDataDir.c_str()));
}
@@ -1075,7 +1099,7 @@ void Client::initHomeDir()
if (mkdir_r(mConfigDir.c_str()))
{
- logger->error(strprintf(_("%s doesn't exist and can't be created! "
+ Log::critical(strprintf(_("%s doesn't exist and can't be created! "
"Exiting."), mConfigDir.c_str()));
}
}
@@ -1094,7 +1118,7 @@ void Client::initConfiguration()
if (doc.rootNode() && doc.rootNode().name() == "configuration")
deserialize(doc.rootNode(), config);
else
- logger->log("Couldn't read configuration file: %s", configPath.c_str());
+ Log::info("Couldn't read configuration file: %s", configPath.c_str());
}
/**
@@ -1117,7 +1141,7 @@ bool Client::initUpdatesDir()
if (mUpdateHost.empty())
{
- logger->log("No update host provided");
+ Log::info("No update host provided");
mUpdatesDir.clear();
mState = STATE_LOAD_DATA;
return false;
@@ -1125,8 +1149,8 @@ bool Client::initUpdatesDir()
mUpdatesDir = "updates/" + getDirectoryFromURL(mUpdateHost);
- logger->log("Update host: %s", mUpdateHost.c_str());
- logger->log("Updates dir: %s", mUpdatesDir.c_str());
+ Log::info("Update host: %s", mUpdateHost.c_str());
+ Log::info("Updates dir: %s", mUpdatesDir.c_str());
// Verify that the updates directory exists. Create if necessary.
if (!FS::isDirectory(mUpdatesDir))
@@ -1146,16 +1170,16 @@ bool Client::initUpdatesDir()
if (!CreateDirectory(newDir.c_str(), 0) &&
GetLastError() != ERROR_ALREADY_EXISTS)
{
- logger->log("Error: %s can't be made, but doesn't exist!",
- newDir.c_str());
+ Log::error("%s can't be made, but doesn't exist!",
+ newDir.c_str());
errorMessage =
strprintf(_("Error creating updates directory!\n(%s)"),
newDir.c_str());
mState = STATE_ERROR;
}
#else
- logger->log("Error: %s/%s can't be made, but doesn't exist!",
- mLocalDataDir.c_str(), mUpdatesDir.c_str());
+ Log::error("%s/%s can't be made, but doesn't exist!",
+ mLocalDataDir.c_str(), mUpdatesDir.c_str());
errorMessage =
strprintf(_("Error creating updates directory!\n(%s/%s)"),
mLocalDataDir.c_str(), mUpdatesDir.c_str());
@@ -1201,7 +1225,7 @@ void Client::initScreenshotDir()
void Client::accountLogin(LoginData *loginData)
{
- logger->log("Username is %s", loginData->username.c_str());
+ Log::info("Username is %s", loginData->username.c_str());
// Send login infos
if (loginData->registerLogin)
@@ -1224,10 +1248,6 @@ void Client::handleWindowSizeChanged(int width, int height)
// Store the new size in the configuration.
config.screenWidth = width;
config.screenHeight = height;
-
- mVideo.windowSizeChanged(width, height);
-
- checkGraphicsSize();
}
void Client::checkGraphicsSize()
diff --git a/src/client.h b/src/client.h
index a3e9c572..b9e16af4 100644
--- a/src/client.h
+++ b/src/client.h
@@ -150,6 +150,8 @@ public:
int exec();
+ void update();
+
/**
* Pops up an OkDialog with the given \a title and \a message, and
* switches to the given \a state when Ok is pressed.
@@ -236,4 +238,27 @@ private:
SDL_TimerID mSecondsCounterId = 0;
FpsManager mFpsManager;
+
+#if defined(_WIN32) || defined(__APPLE__)
+ /**
+ * This class triggers an update on the window expose event, which allows
+ * the application to draw while Windows is in a modal move/resize loop
+ * as well as while resizing on macOS.
+ */
+ class ExposeEventWatcher
+ {
+ public:
+ ExposeEventWatcher() { SDL_AddEventWatch(&watch, nullptr); }
+ ~ExposeEventWatcher() { SDL_DelEventWatch(&watch, nullptr); }
+
+ static int watch(void *, SDL_Event *event)
+ {
+ if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_EXPOSED)
+ Client::instance()->update();
+ return 0;
+ }
+ };
+
+ ExposeEventWatcher mExposeEventWatcher;
+#endif
};
diff --git a/src/configuration.cpp b/src/configuration.cpp
index b27fbe31..e220f4c6 100644
--- a/src/configuration.cpp
+++ b/src/configuration.cpp
@@ -107,9 +107,9 @@ VariableData *Configuration::getDefault(const std::string &key,
return itdef->second;
}
- logger->log("%s: No value in registry for key %s",
- mConfigPath.c_str(),
- key.c_str());
+ Log::info("%s: No value in registry for key %s",
+ mConfigPath.c_str(),
+ key.c_str());
}
return nullptr;
@@ -214,13 +214,13 @@ void Configuration::init(const std::string &filename, bool useResManager)
if (!rootNode)
{
- logger->log("Couldn't open configuration file: %s", filename.c_str());
+ Log::info("Couldn't open configuration file: %s", filename.c_str());
return;
}
if (rootNode.name() != "configuration")
{
- logger->log("Warning: No configuration file (%s)", filename.c_str());
+ Log::warn("No configuration file (%s)", filename.c_str());
return;
}
@@ -351,6 +351,7 @@ void serdeOptions(T option)
option("showgender", &Config::showGender);
option("showMonstersTakedDamage", &Config::showMonstersTakedDamage);
option("showWarps", &Config::showWarps);
+ option("hideCompletedQuests", &Config::hideCompletedQuests);
option("particleMaxCount", &Config::particleMaxCount);
option("particleFastPhysics", &Config::particleFastPhysics);
option("particleEmitterSkip", &Config::particleEmitterSkip);
diff --git a/src/configuration.h b/src/configuration.h
index 3ed3c77e..9e00cb74 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -181,6 +181,7 @@ struct Config
bool showGender = false;
bool showMonstersTakedDamage = false;
bool showWarps = true;
+ bool hideCompletedQuests = false;
int particleMaxCount = 3000;
int particleFastPhysics = 0;
int particleEmitterSkip = 1;
diff --git a/src/effectmanager.cpp b/src/effectmanager.cpp
index 32280555..d1b3a037 100644
--- a/src/effectmanager.cpp
+++ b/src/effectmanager.cpp
@@ -38,13 +38,13 @@ EffectManager::EffectManager()
// Handle old naming until the 0.5.x versions are obsolete.
if (!root || root.name() != "being-effects")
{
- logger->log("Error loading being effects file: effects.xml");
+ Log::info("Error loading being effects file: effects.xml");
return;
}
}
else
{
- logger->log("Effects are now loading");
+ Log::info("Effects are now loading");
}
for (auto node : root.children())
@@ -67,7 +67,7 @@ bool EffectManager::trigger(int id, Being *being, int rotation)
auto it = mEffects.find(id);
if (it == mEffects.end())
{
- logger->log("EffectManager::trigger: effect %d not found", id);
+ Log::warn("EffectManager::trigger: effect %d not found", id);
return false;
}
@@ -90,7 +90,7 @@ bool EffectManager::trigger(int id, int x, int y, int rotation)
auto it = mEffects.find(id);
if (it == mEffects.end())
{
- logger->log("EffectManager::trigger: effect %d not found", id);
+ Log::warn("EffectManager::trigger: effect %d not found", id);
return false;
}
diff --git a/src/event.h b/src/event.h
index d3fdede9..3cbd9123 100644
--- a/src/event.h
+++ b/src/event.h
@@ -53,7 +53,8 @@ public:
ItemChannel,
NoticesChannel,
NpcChannel,
- StorageChannel
+ StorageChannel,
+ QuestsChannel
};
enum Type
@@ -99,7 +100,8 @@ public:
UpdateStat,
UpdateStatusEffect,
Whisper,
- WhisperError
+ WhisperError,
+ QuestVarsChanged,
};
/**
diff --git a/src/game.cpp b/src/game.cpp
index a6764fcf..7e2496a1 100644
--- a/src/game.cpp
+++ b/src/game.cpp
@@ -21,15 +21,15 @@
#include "game.h"
-#include "actorspritemanager.h"
#include "actorsprite.h"
+#include "actorspritemanager.h"
#include "channelmanager.h"
#include "client.h"
#include "commandhandler.h"
#include "configuration.h"
#include "effectmanager.h"
-#include "event.h"
#include "emoteshortcut.h"
+#include "event.h"
#include "graphics.h"
#include "itemshortcut.h"
#include "joystick.h"
@@ -41,24 +41,24 @@
#include "playerrelations.h"
#include "sound.h"
+#include "gui/abilitieswindow.h"
#include "gui/chatwindow.h"
#include "gui/debugwindow.h"
#include "gui/equipmentwindow.h"
#include "gui/gui.h"
#include "gui/helpwindow.h"
#include "gui/inventorywindow.h"
-#include "gui/shortcutwindow.h"
#include "gui/minimap.h"
#include "gui/ministatuswindow.h"
#include "gui/npcdialog.h"
#include "gui/okdialog.h"
#include "gui/outfitwindow.h"
+#include "gui/questswindow.h"
#include "gui/quitdialog.h"
-#include "gui/sdlinput.h"
#include "gui/setup.h"
-#include "gui/socialwindow.h"
-#include "gui/abilitieswindow.h"
+#include "gui/shortcutwindow.h"
#include "gui/skilldialog.h"
+#include "gui/socialwindow.h"
#include "gui/statuswindow.h"
#include "gui/textdialog.h"
#include "gui/tradewindow.h"
@@ -79,7 +79,6 @@
#include "utils/gettext.h"
#include "utils/mkdir.h"
-#include <guichan/exception.hpp>
#include <guichan/focushandler.hpp>
#include <fstream>
@@ -96,6 +95,7 @@ StatusWindow *statusWindow;
MiniStatusWindow *miniStatusWindow;
InventoryWindow *inventoryWindow;
SkillDialog *skillDialog;
+QuestsWindow *questsWindow;
Minimap *minimap;
EquipmentWindow *equipmentWindow;
TradeWindow *tradeWindow;
@@ -152,6 +152,7 @@ static void createGuiWindows()
statusWindow = new StatusWindow;
inventoryWindow = new InventoryWindow(PlayerInfo::getInventory());
skillDialog = new SkillDialog;
+ questsWindow = new QuestsWindow;
helpWindow = new HelpWindow;
debugWindow = new DebugWindow;
itemShortcutWindow = new ShortcutWindow("ItemShortcut",
@@ -184,6 +185,7 @@ static void destroyGuiWindows()
del_0(miniStatusWindow)
del_0(inventoryWindow)
del_0(skillDialog)
+ del_0(questsWindow)
del_0(minimap)
del_0(equipmentWindow)
del_0(tradeWindow)
@@ -287,7 +289,7 @@ static bool saveScreenshot()
if (!screenshot)
{
serverNotice(_("Could not take screenshot!"));
- logger->log("Error: could not take screenshot.");
+ Log::error("Could not take screenshot.");
return false;
}
@@ -300,9 +302,9 @@ static bool saveScreenshot()
if (mkdir_r(screenshotDirectory.c_str()) != 0)
{
- logger->log("Directory %s doesn't exist and can't be created! "
- "Setting screenshot directory to home.",
- screenshotDirectory.c_str());
+ Log::info("Directory %s doesn't exist and can't be created! "
+ "Setting screenshot directory to home.",
+ screenshotDirectory.c_str());
screenshotDirectory = FS::getUserDir();
}
@@ -324,13 +326,21 @@ static bool saveScreenshot()
if (success)
{
+ std::string screenshotLink;
+#if SDL_VERSION_ATLEAST(2, 0, 14)
+ screenshotLink = strprintf("@@screenshot:%s|%s@@",
+ filenameSuffix.str().c_str(),
+ filenameSuffix.str().c_str());
+#else
+ screenshotLink = filenameSuffix.str();
+#endif
serverNotice(strprintf(_("Screenshot saved as %s"),
- filenameSuffix.str().c_str()));
+ screenshotLink.c_str()));
}
else
{
serverNotice(_("Saving screenshot failed!"));
- logger->log("Error: could not save screenshot.");
+ Log::error("Could not save screenshot.");
}
SDL_FreeSurface(screenshot);
@@ -387,516 +397,457 @@ static void handleItemPickUp()
}
/**
- * The huge input handling method.
+ * Handles an SDL_KEYDOWN event and returns whether it was consumed.
*/
-void Game::handleInput()
+bool Game::keyDownEvent(SDL_KeyboardEvent &event)
{
- if (joystick)
- joystick->update();
+ gcn::Window *requestedWindow = nullptr;
+
+ // send straight to gui for certain windows
+ if (quitDialog || TextDialog::isActive() ||
+ PlayerInfo::getNPCPostCount() > 0)
+ {
+ return false;
+ }
- // Events
- SDL_Event event;
- while (SDL_PollEvent(&event))
+ if (keyboard.isKeyActive(KeyboardConfig::KEY_EMOTE))
{
- bool used = false;
+ int emotion = keyboard.getKeyEmoteOffset(event.keysym.sym);
+ if (emotion != -1)
+ {
+ emoteShortcut->useEmote(emotion);
+ return true;
+ }
+ }
- if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
+ if (!chatWindow->isInputFocused()
+ && !gui->getFocusHandler()->getModalFocused())
+ {
+ NpcDialog *dialog = NpcDialog::getActive();
+ if (keyboard.isKeyActive(KeyboardConfig::KEY_OK)
+ && (!dialog || !dialog->isTextInputFocused()))
{
- // Let the client deal with this one (it'll pass down from there)
- Client::instance()->handleWindowSizeChanged(event.window.data1,
- event.window.data2);
+ // Close the Browser if opened
+ if (helpWindow->isVisible())
+ helpWindow->setVisible(false);
+ // Close the config window, cancelling changes if opened
+ else if (setupWindow->isVisible())
+ setupWindow->action(gcn::ActionEvent(nullptr, "cancel"));
+ else if (dialog)
+ dialog->action(gcn::ActionEvent(nullptr, "ok"));
}
- // Keyboard events (for discontinuous keys)
- else if (event.type == SDL_KEYDOWN)
+ if (keyboard.isKeyActive(KeyboardConfig::KEY_TOGGLE_CHAT))
{
- gcn::Window *requestedWindow = nullptr;
+ if (chatWindow->requestChatFocus())
+ return true;
+ }
+ if (dialog)
+ {
+ if (keyboard.isKeyActive(KeyboardConfig::KEY_MOVE_UP))
+ dialog->move(1);
+ else if (keyboard.isKeyActive(KeyboardConfig::KEY_MOVE_DOWN))
+ dialog->move(-1);
+ }
+ }
- if (setupWindow->isVisible() &&
- keyboard.getNewKeyIndex() > KeyboardConfig::KEY_NO_VALUE)
- {
- keyboard.setNewKey(event.key.keysym.sym);
- keyboard.callbackNewKey();
- keyboard.setNewKeyIndex(KeyboardConfig::KEY_NO_VALUE);
- return;
- }
+ if (!chatWindow->isInputFocused() || (event.keysym.mod & KMOD_ALT))
+ {
+ if (keyboard.isKeyActive(KeyboardConfig::KEY_PREV_CHAT_TAB))
+ {
+ chatWindow->prevTab();
+ return true;
+ }
+ if (keyboard.isKeyActive(KeyboardConfig::KEY_NEXT_CHAT_TAB))
+ {
+ chatWindow->nextTab();
+ return true;
+ }
+ }
- // send straight to gui for certain windows
- if (quitDialog || TextDialog::isActive() ||
- PlayerInfo::getNPCPostCount() > 0)
- {
- try
- {
- guiInput->pushInput(event);
- }
- catch (gcn::Exception e)
- {
- const char* err = e.getMessage().c_str();
- logger->log("Warning: guichan input exception: %s", err);
- }
- return;
- }
+ if (!chatWindow->isInputFocused())
+ {
+ bool wearOutfit = false;
+ bool copyOutfit = false;
- // Mode switch to emotes
- if (keyboard.isKeyActive(KeyboardConfig::KEY_EMOTE))
- {
- // Emotions
- int emotion = keyboard.getKeyEmoteOffset(event.key.keysym.sym);
- if (emotion != -1)
- {
- emoteShortcut->useEmote(emotion);
- used = true;
- return;
- }
- }
-
- if (!chatWindow->isInputFocused()
- && !gui->getFocusHandler()->getModalFocused())
- {
- NpcDialog *dialog = NpcDialog::getActive();
- if (keyboard.isKeyActive(KeyboardConfig::KEY_OK)
- && (!dialog || !dialog->isTextInputFocused()))
- {
- // Close the Browser if opened
- if (helpWindow->isVisible())
- helpWindow->setVisible(false);
- // Close the config window, cancelling changes if opened
- else if (setupWindow->isVisible())
- setupWindow->action(gcn::ActionEvent(nullptr, "cancel"));
- else if (dialog)
- dialog->action(gcn::ActionEvent(nullptr, "ok"));
- }
- if (keyboard.isKeyActive(KeyboardConfig::KEY_TOGGLE_CHAT))
- {
- if (chatWindow->requestChatFocus())
- used = true;
- }
- if (dialog)
- {
- if (keyboard.isKeyActive(KeyboardConfig::KEY_MOVE_UP))
- dialog->move(1);
- else if (keyboard.isKeyActive(KeyboardConfig::KEY_MOVE_DOWN))
- dialog->move(-1);
- }
- }
+ if (keyboard.isKeyActive(KeyboardConfig::KEY_WEAR_OUTFIT))
+ wearOutfit = true;
+ if (keyboard.isKeyActive(KeyboardConfig::KEY_COPY_OUTFIT))
+ copyOutfit = true;
- if (!chatWindow->isInputFocused() || (event.key.keysym.mod &
- KMOD_ALT))
+ if (wearOutfit || copyOutfit)
+ {
+ int outfitNum = -1;
+ switch (event.keysym.sym)
{
- if (keyboard.isKeyActive(KeyboardConfig::KEY_PREV_CHAT_TAB))
- {
- chatWindow->prevTab();
- return;
- }
- else if (keyboard.isKeyActive(KeyboardConfig::KEY_NEXT_CHAT_TAB))
- {
- chatWindow->nextTab();
- return;
- }
+ case SDLK_1:
+ case SDLK_2:
+ case SDLK_3:
+ case SDLK_4:
+ case SDLK_5:
+ case SDLK_6:
+ case SDLK_7:
+ case SDLK_8:
+ case SDLK_9:
+ outfitNum = event.keysym.sym - SDLK_1;
+ break;
+
+ case SDLK_0:
+ outfitNum = 9;
+ break;
+
+ case SDLK_MINUS:
+ outfitNum = 10;
+ break;
+
+ case SDLK_EQUALS:
+ outfitNum = 11;
+ break;
+
+ case SDLK_BACKSPACE:
+ outfitNum = 12;
+ break;
+
+ case SDLK_INSERT:
+ outfitNum = 13;
+ break;
+
+ case SDLK_HOME:
+ outfitNum = 14;
+ break;
+
+ default:
+ break;
}
-
- if (!chatWindow->isInputFocused())
+ if (outfitNum >= 0)
{
- bool wearOutfit = false;
- bool copyOutfit = false;
+ if (wearOutfit)
+ outfitWindow->wearOutfit(outfitNum);
+ else if (copyOutfit)
+ outfitWindow->copyOutfit(outfitNum);
+ return true;
+ }
+ }
+ }
- if (keyboard.isKeyActive(KeyboardConfig::KEY_WEAR_OUTFIT))
- wearOutfit = true;
+ const int tKey = keyboard.getKeyIndex(event.keysym.sym);
+ switch (tKey)
+ {
+ case KeyboardConfig::KEY_SCROLL_CHAT_UP:
+ if (chatWindow->isVisible())
+ {
+ chatWindow->scroll(-DEFAULT_CHAT_WINDOW_SCROLL);
+ return true;
+ }
+ break;
+ case KeyboardConfig::KEY_SCROLL_CHAT_DOWN:
+ if (chatWindow->isVisible())
+ {
+ chatWindow->scroll(DEFAULT_CHAT_WINDOW_SCROLL);
+ return true;
+ }
+ break;
+ case KeyboardConfig::KEY_WINDOW_HELP:
+ // In-game Help
+ if (helpWindow->isVisible())
+ {
+ helpWindow->setVisible(false);
+ }
+ else
+ {
+ helpWindow->loadHelp("index");
+ helpWindow->requestMoveToTop();
+ }
+ return true;
- if (keyboard.isKeyActive(KeyboardConfig::KEY_COPY_OUTFIT))
- copyOutfit = true;
+ case KeyboardConfig::KEY_QUIT:
+ {
+ // Close possible stuck NPC dialogs.
+ NpcDialog *npcDialog = NpcDialog::getActive();
+ if (npcDialog && npcDialog->isWaitingForTheServer())
+ {
+ npcDialog->close();
+ return true;
+ }
- if (wearOutfit || copyOutfit)
- {
- int outfitNum = -1;
- switch (event.key.keysym.sym)
- {
- case SDLK_1:
- case SDLK_2:
- case SDLK_3:
- case SDLK_4:
- case SDLK_5:
- case SDLK_6:
- case SDLK_7:
- case SDLK_8:
- case SDLK_9:
- outfitNum = event.key.keysym.sym - SDLK_1;
- break;
-
- case SDLK_0:
- outfitNum = 9;
- break;
-
- case SDLK_MINUS:
- outfitNum = 10;
- break;
-
- case SDLK_EQUALS:
- outfitNum = 11;
- break;
-
- case SDLK_BACKSPACE:
- outfitNum = 12;
- break;
-
- case SDLK_INSERT:
- outfitNum = 13;
- break;
-
- case SDLK_HOME:
- outfitNum = 14;
- break;
-
- default:
- break;
- }
- if (outfitNum >= 0)
- {
- used = true;
- if (wearOutfit)
- outfitWindow->wearOutfit(outfitNum);
- else if (copyOutfit)
- outfitWindow->copyOutfit(outfitNum);
- }
- }
- }
+ // Otherwise, show the quit confirmation dialog.
+ quitDialog = new QuitDialog(&quitDialog);
+ quitDialog->requestMoveToTop();
+ return true;
+ }
+ default:
+ break;
+ }
- const int tKey = keyboard.getKeyIndex(event.key.keysym.sym);
- switch (tKey)
- {
- case KeyboardConfig::KEY_SCROLL_CHAT_UP:
- if (chatWindow->isVisible())
- {
- chatWindow->scroll(-DEFAULT_CHAT_WINDOW_SCROLL);
- used = true;
- }
- break;
- case KeyboardConfig::KEY_SCROLL_CHAT_DOWN:
- if (chatWindow->isVisible())
- {
- chatWindow->scroll(DEFAULT_CHAT_WINDOW_SCROLL);
- used = true;
- return;
- }
- break;
- case KeyboardConfig::KEY_WINDOW_HELP:
- // In-game Help
- if (helpWindow->isVisible())
- helpWindow->setVisible(false);
- else
- {
- helpWindow->loadHelp("index");
- helpWindow->requestMoveToTop();
- }
- used = true;
- break;
- case KeyboardConfig::KEY_QUIT:
- {
- // Close possible stuck NPC dialogs.
- NpcDialog *npcDialog = NpcDialog::getActive();
- if (npcDialog && npcDialog->isWaitingForTheServer())
- {
- npcDialog->close();
- return;
- }
-
- // Otherwise, show the quit confirmation dialog.
- quitDialog = new QuitDialog(&quitDialog);
- quitDialog->requestMoveToTop();
- return;
- }
- default:
- break;
- }
- if (keyboard.isEnabled() && !chatWindow->isInputFocused() &&
- !NpcDialog::isAnyInputFocused() && !InventoryWindow::isAnyInputFocused())
+ if (keyboard.isEnabled() && !chatWindow->isInputFocused() &&
+ !NpcDialog::isAnyInputFocused() && !InventoryWindow::isAnyInputFocused())
+ {
+ // Do not activate shortcuts if tradewindow is visible
+ if (!tradeWindow->isVisible() && !setupWindow->isVisible())
+ {
+ // Checks if any item shortcut is pressed.
+ for (int i = KeyboardConfig::KEY_SHORTCUT_1;
+ i <= KeyboardConfig::KEY_SHORTCUT_12;
+ i++)
{
- // Do not activate shortcuts if tradewindow is visible
- if (!tradeWindow->isVisible() && !setupWindow->isVisible())
+ if (tKey == i)
{
- // Checks if any item shortcut is pressed.
- for (int i = KeyboardConfig::KEY_SHORTCUT_1;
- i <= KeyboardConfig::KEY_SHORTCUT_12;
- i++)
- {
- if (tKey == i && !used)
- {
- itemShortcut->useItem(
- i - KeyboardConfig::KEY_SHORTCUT_1);
- break;
- }
- }
- }
-
- switch (tKey)
- {
- case KeyboardConfig::KEY_PICKUP:
- {
- handleItemPickUp();
-
- used = true;
- }
- break;
- case KeyboardConfig::KEY_SIT:
- // Player sit action
- local_player->toggleSit();
- used = true;
- break;
- case KeyboardConfig::KEY_HIDE_WINDOWS:
- // Hide certain windows
- if (!chatWindow->isInputFocused())
- {
- statusWindow->setVisible(false);
- inventoryWindow->setVisible(false);
- skillDialog->setVisible(false);
- setupWindow->setVisible(false);
- equipmentWindow->setVisible(false);
- helpWindow->setVisible(false);
- debugWindow->setVisible(false);
- socialWindow->setVisible(false);
- }
- break;
- case KeyboardConfig::KEY_WINDOW_STATUS:
- requestedWindow = statusWindow;
- break;
- case KeyboardConfig::KEY_WINDOW_INVENTORY:
- requestedWindow = inventoryWindow;
- break;
- case KeyboardConfig::KEY_WINDOW_EQUIPMENT:
- requestedWindow = equipmentWindow;
- break;
- case KeyboardConfig::KEY_WINDOW_SKILL:
- requestedWindow = skillDialog;
- break;
- case KeyboardConfig::KEY_WINDOW_MINIMAP:
- minimap->toggle();
- break;
- case KeyboardConfig::KEY_WINDOW_CHAT:
- requestedWindow = chatWindow;
- break;
- case KeyboardConfig::KEY_WINDOW_SHORTCUT:
- requestedWindow = itemShortcutWindow;
- break;
- case KeyboardConfig::KEY_WINDOW_SETUP:
- requestedWindow = setupWindow;
- break;
- case KeyboardConfig::KEY_WINDOW_DEBUG:
- requestedWindow = debugWindow;
- break;
- case KeyboardConfig::KEY_WINDOW_SOCIAL:
- requestedWindow = socialWindow;
- break;
- case KeyboardConfig::KEY_WINDOW_EMOTE_SHORTCUT:
- requestedWindow = emoteShortcutWindow;
- break;
- case KeyboardConfig::KEY_WINDOW_OUTFIT:
- requestedWindow = outfitWindow;
- break;
- case KeyboardConfig::KEY_SCREENSHOT:
- // Screenshot (picture, hence the p)
- saveScreenshot();
- used = true;
- break;
- case KeyboardConfig::KEY_TRADE:
- // Toggle accepting of incoming trade requests
- unsigned int deflt = player_relations.getDefault();
- if (deflt & PlayerPermissions::TRADE)
- {
- serverNotice(_("Ignoring incoming trade requests"));
- deflt &= ~PlayerPermissions::TRADE;
- }
- else
- {
- serverNotice(_("Accepting incoming trade requests"));
- deflt |= PlayerPermissions::TRADE;
- }
-
- player_relations.setDefault(deflt);
-
- used = true;
- break;
+ itemShortcut->useItem(
+ i - KeyboardConfig::KEY_SHORTCUT_1);
+ return true;
}
}
-
- if (requestedWindow)
- {
- requestedWindow->setVisible(!requestedWindow->isVisible());
- if (requestedWindow->isVisible())
- requestedWindow->requestMoveToTop();
- used = true;
- }
- }
- // Quit event
- else if (event.type == SDL_QUIT)
- {
- Client::setState(STATE_EXIT);
}
- // Push input to GUI when not used
- if (!used)
+ switch (tKey)
+ {
+ case KeyboardConfig::KEY_PICKUP:
+ handleItemPickUp();
+ return true;
+
+ case KeyboardConfig::KEY_SIT:
+ local_player->toggleSit();
+ return true;
+
+ case KeyboardConfig::KEY_HIDE_WINDOWS:
+ // Hide certain windows
+ statusWindow->setVisible(false);
+ inventoryWindow->setVisible(false);
+ skillDialog->setVisible(false);
+ questsWindow->setVisible(false);
+ setupWindow->setVisible(false);
+ equipmentWindow->setVisible(false);
+ helpWindow->setVisible(false);
+ debugWindow->setVisible(false);
+ socialWindow->setVisible(false);
+ break;
+
+ case KeyboardConfig::KEY_WINDOW_STATUS:
+ requestedWindow = statusWindow;
+ break;
+ case KeyboardConfig::KEY_WINDOW_INVENTORY:
+ requestedWindow = inventoryWindow;
+ break;
+ case KeyboardConfig::KEY_WINDOW_EQUIPMENT:
+ requestedWindow = equipmentWindow;
+ break;
+ case KeyboardConfig::KEY_WINDOW_SKILL:
+ requestedWindow = skillDialog;
+ break;
+ case KeyboardConfig::KEY_WINDOW_QUESTS:
+ requestedWindow = questsWindow;
+ break;
+ case KeyboardConfig::KEY_WINDOW_MINIMAP:
+ minimap->toggle();
+ return true;
+ case KeyboardConfig::KEY_WINDOW_CHAT:
+ requestedWindow = chatWindow;
+ break;
+ case KeyboardConfig::KEY_WINDOW_SHORTCUT:
+ requestedWindow = itemShortcutWindow;
+ break;
+ case KeyboardConfig::KEY_WINDOW_SETUP:
+ requestedWindow = setupWindow;
+ break;
+ case KeyboardConfig::KEY_WINDOW_DEBUG:
+ requestedWindow = debugWindow;
+ break;
+ case KeyboardConfig::KEY_WINDOW_SOCIAL:
+ requestedWindow = socialWindow;
+ break;
+ case KeyboardConfig::KEY_WINDOW_EMOTE_SHORTCUT:
+ requestedWindow = emoteShortcutWindow;
+ break;
+ case KeyboardConfig::KEY_WINDOW_OUTFIT:
+ requestedWindow = outfitWindow;
+ break;
+ case KeyboardConfig::KEY_SCREENSHOT:
+ saveScreenshot();
+ return true;
+
+ case KeyboardConfig::KEY_TRADE:
{
- try
+ // Toggle accepting of incoming trade requests
+ unsigned int deflt = player_relations.getDefault();
+ if (deflt & PlayerPermissions::TRADE)
{
- guiInput->pushInput(event);
+ serverNotice(_("Ignoring incoming trade requests"));
+ deflt &= ~PlayerPermissions::TRADE;
}
- catch (gcn::Exception e)
+ else
{
- const char *err = e.getMessage().c_str();
- logger->log("Warning: guichan input exception: %s", err);
+ serverNotice(_("Accepting incoming trade requests"));
+ deflt |= PlayerPermissions::TRADE;
}
+
+ player_relations.setDefault(deflt);
+
+ return true;
+ }
+
+ case KeyboardConfig::KEY_TALK:
+ if (Being *target = local_player->getTarget())
+ if (target->canTalk())
+ target->talkTo();
+ return true;
}
+ }
+
+ if (requestedWindow)
+ {
+ requestedWindow->setVisible(!requestedWindow->isVisible());
+ if (requestedWindow->isVisible())
+ requestedWindow->requestMoveToTop();
+ return true;
+ }
- } // End while
+ return false;
+}
+
+/**
+ * Continuous input handling.
+ */
+void Game::handleInput()
+{
+ if (joystick)
+ joystick->update();
// If the user is configuring the keys then don't respond.
if (!keyboard.isEnabled())
return;
+ if (!local_player->isAlive())
+ return;
+ if (PlayerInfo::isTalking())
+ return;
+ if (chatWindow->isInputFocused() || quitDialog || TextDialog::isActive())
+ return;
// Moving player around
- if (local_player->isAlive() && !PlayerInfo::isTalking() &&
- !chatWindow->isInputFocused() && !quitDialog && !TextDialog::isActive())
- {
- // Get the state of the keyboard keys
- keyboard.refreshActiveKeys();
- // Ignore input if either "ignore" key is pressed
- // Stops the character moving about if the user's window manager
- // uses "ignore+arrow key" to switch virtual desktops.
- if (keyboard.isKeyActive(KeyboardConfig::KEY_IGNORE_INPUT_1) ||
- keyboard.isKeyActive(KeyboardConfig::KEY_IGNORE_INPUT_2))
- {
- return;
- }
+ // Get the state of the keyboard keys
+ keyboard.refreshActiveKeys();
- unsigned char direction = 0;
+ // Ignore input if either "ignore" key is pressed
+ // Stops the character moving about if the user's window manager
+ // uses "ignore+arrow key" to switch virtual desktops.
+ if (keyboard.isKeyActive(KeyboardConfig::KEY_IGNORE_INPUT_1) ||
+ keyboard.isKeyActive(KeyboardConfig::KEY_IGNORE_INPUT_2))
+ {
+ return;
+ }
- // Translate pressed keys to movement and direction
- if (keyboard.isKeyActive(KeyboardConfig::KEY_MOVE_UP) ||
- (joystick && joystick->isUp()))
- {
- direction |= Being::UP;
- }
- else if (keyboard.isKeyActive(KeyboardConfig::KEY_MOVE_DOWN) ||
- (joystick && joystick->isDown()))
- {
- direction |= Being::DOWN;
- }
+ unsigned char direction = 0;
- if (keyboard.isKeyActive(KeyboardConfig::KEY_MOVE_LEFT) ||
- (joystick && joystick->isLeft()))
- {
- direction |= Being::LEFT;
- }
- else if (keyboard.isKeyActive(KeyboardConfig::KEY_MOVE_RIGHT) ||
- (joystick && joystick->isRight()))
- {
- direction |= Being::RIGHT;
- }
+ // Translate pressed keys to movement and direction
+ if (keyboard.isKeyActive(KeyboardConfig::KEY_MOVE_UP) ||
+ (joystick && joystick->isUp()))
+ {
+ direction |= Being::UP;
+ }
+ else if (keyboard.isKeyActive(KeyboardConfig::KEY_MOVE_DOWN) ||
+ (joystick && joystick->isDown()))
+ {
+ direction |= Being::DOWN;
+ }
- if (keyboard.isKeyActive(KeyboardConfig::KEY_EMOTE) && direction != 0)
- {
- if (local_player->getDirection() != direction)
- {
- local_player->setDirection(direction);
- Net::getPlayerHandler()->setDirection(direction);
- }
- direction = 0;
- }
- else
- {
- local_player->setWalkingDir(direction);
- }
+ if (keyboard.isKeyActive(KeyboardConfig::KEY_MOVE_LEFT) ||
+ (joystick && joystick->isLeft()))
+ {
+ direction |= Being::LEFT;
+ }
+ else if (keyboard.isKeyActive(KeyboardConfig::KEY_MOVE_RIGHT) ||
+ (joystick && joystick->isRight()))
+ {
+ direction |= Being::RIGHT;
+ }
- // Attacking monsters
- if (keyboard.isKeyActive(KeyboardConfig::KEY_ATTACK) ||
- (joystick && joystick->buttonPressed(0)))
+ if (keyboard.isKeyActive(KeyboardConfig::KEY_EMOTE) && direction != 0)
+ {
+ if (local_player->getDirection() != direction)
{
- if (local_player->getTarget())
- local_player->attack(local_player->getTarget(), true);
+ local_player->setDirection(direction);
+ Net::getPlayerHandler()->setDirection(direction);
}
+ }
+ else
+ {
+ local_player->setWalkingDir(direction);
+ }
- if (keyboard.isKeyActive(KeyboardConfig::KEY_TARGET_ATTACK))
- {
- Being *target = local_player->getTarget();
+ // Attacking monsters
+ if (keyboard.isKeyActive(KeyboardConfig::KEY_ATTACK) ||
+ (joystick && joystick->buttonPressed(0)))
+ {
+ if (local_player->getTarget())
+ local_player->attack(local_player->getTarget(), true);
+ }
- bool newTarget = !keyboard.isKeyActive(KeyboardConfig::KEY_TARGET);
- // A set target has highest priority
- if (!target)
- {
- // Only auto target Monsters
- target = actorSpriteManager->findNearestLivingBeing(local_player,
- 20, ActorSprite::MONSTER);
- }
- local_player->attack(target, newTarget);
- }
+ if (keyboard.isKeyActive(KeyboardConfig::KEY_TARGET_ATTACK))
+ {
+ Being *target = local_player->getTarget();
- // Target the nearest player/monster/npc
- if ((keyboard.isKeyActive(KeyboardConfig::KEY_TARGET_PLAYER) ||
- keyboard.isKeyActive(KeyboardConfig::KEY_TARGET_CLOSEST) ||
- keyboard.isKeyActive(KeyboardConfig::KEY_TARGET_NPC) ||
- (joystick && joystick->buttonPressed(3))) &&
- !keyboard.isKeyActive(KeyboardConfig::KEY_TARGET))
+ bool newTarget = !keyboard.isKeyActive(KeyboardConfig::KEY_TARGET);
+ // A set target has highest priority
+ if (!target)
{
- ActorSprite::Type currentTarget = ActorSprite::UNKNOWN;
- if (keyboard.isKeyActive(KeyboardConfig::KEY_TARGET_CLOSEST) ||
- (joystick && joystick->buttonPressed(3)))
- currentTarget = ActorSprite::MONSTER;
- else if (keyboard.isKeyActive(KeyboardConfig::KEY_TARGET_PLAYER))
- currentTarget = ActorSprite::PLAYER;
- else if (keyboard.isKeyActive(KeyboardConfig::KEY_TARGET_NPC))
- currentTarget = ActorSprite::NPC;
-
- Being *target = actorSpriteManager->findNearestLivingBeing(local_player,
- 20, currentTarget);
-
- if (target && (target != local_player->getTarget() ||
- currentTarget != mLastTarget))
- {
- local_player->setTarget(target);
- mLastTarget = currentTarget;
- }
- }
- else
- {
- mLastTarget = ActorSprite::UNKNOWN; // Reset last target
+ // Only auto target Monsters
+ target = actorSpriteManager->findNearestLivingBeing(local_player,
+ 20, ActorSprite::MONSTER);
}
+ local_player->attack(target, newTarget);
+ }
- // Talk to the nearest NPC if 't' pressed
- if (event.type == SDL_KEYDOWN &&
- keyboard.getKeyIndex(event.key.keysym.sym) == KeyboardConfig::KEY_TALK)
+ // Target the nearest player/monster/npc
+ if ((keyboard.isKeyActive(KeyboardConfig::KEY_TARGET_PLAYER) ||
+ keyboard.isKeyActive(KeyboardConfig::KEY_TARGET_CLOSEST) ||
+ keyboard.isKeyActive(KeyboardConfig::KEY_TARGET_NPC) ||
+ (joystick && joystick->buttonPressed(3))) &&
+ !keyboard.isKeyActive(KeyboardConfig::KEY_TARGET))
+ {
+ ActorSprite::Type currentTarget = ActorSprite::UNKNOWN;
+ if (keyboard.isKeyActive(KeyboardConfig::KEY_TARGET_CLOSEST) ||
+ (joystick && joystick->buttonPressed(3)))
+ currentTarget = ActorSprite::MONSTER;
+ else if (keyboard.isKeyActive(KeyboardConfig::KEY_TARGET_PLAYER))
+ currentTarget = ActorSprite::PLAYER;
+ else if (keyboard.isKeyActive(KeyboardConfig::KEY_TARGET_NPC))
+ currentTarget = ActorSprite::NPC;
+
+ Being *target = actorSpriteManager->findNearestLivingBeing(local_player,
+ 20, currentTarget);
+
+ if (target && (target != local_player->getTarget() ||
+ currentTarget != mLastTarget))
{
- Being *target = local_player->getTarget();
-
- if (target)
- {
- if (target->canTalk())
- target->talkTo();
- }
+ local_player->setTarget(target);
+ mLastTarget = currentTarget;
}
+ }
+ else
+ {
+ mLastTarget = ActorSprite::UNKNOWN; // Reset last target
+ }
- // Stop attacking if the right key is pressed
- if (!keyboard.isKeyActive(KeyboardConfig::KEY_ATTACK)
- && keyboard.isKeyActive(KeyboardConfig::KEY_TARGET))
- {
- local_player->stopAttack();
- }
+ // Stop attacking if the right key is pressed
+ if (!keyboard.isKeyActive(KeyboardConfig::KEY_ATTACK)
+ && keyboard.isKeyActive(KeyboardConfig::KEY_TARGET))
+ {
+ local_player->stopAttack();
+ }
- if (joystick)
+ if (joystick)
+ {
+ if (joystick->buttonPressed(1))
{
- if (joystick->buttonPressed(1))
- {
- const int x = local_player->getTileX();
- const int y = local_player->getTileY();
-
- FloorItem *item = actorSpriteManager->findItem(x, y);
+ const int x = local_player->getTileX();
+ const int y = local_player->getTileY();
- if (item)
- local_player->pickUp(item);
- }
- else if (joystick->buttonPressed(2))
- {
- local_player->toggleSit();
- }
+ if (FloorItem *item = actorSpriteManager->findItem(x, y))
+ local_player->pickUp(item);
+ }
+ else if (joystick->buttonPressed(2))
+ {
+ local_player->toggleSit();
}
}
}
@@ -931,7 +882,7 @@ void Game::changeMap(const std::string &mapPath)
if (!newMap)
{
- logger->log("Error while loading %s", fullMap.c_str());
+ Log::info("Error while loading %s", fullMap.c_str());
new OkDialog(_("Could Not Load Map"),
strprintf(_("Error while loading %s"), fullMap.c_str()));
}
diff --git a/src/game.h b/src/game.h
index c7ff720e..f105d802 100644
--- a/src/game.h
+++ b/src/game.h
@@ -58,6 +58,8 @@ class Game
*/
void logic();
+ bool keyDownEvent(SDL_KeyboardEvent &event);
+
void handleInput();
void changeMap(const std::string &mapName);
diff --git a/src/graphics.cpp b/src/graphics.cpp
index 5455f61a..68fc4e46 100644
--- a/src/graphics.cpp
+++ b/src/graphics.cpp
@@ -21,27 +21,11 @@
#include "graphics.h"
-#include "resources/image.h"
+#include "gui/truetypefont.h"
+#include "resources/theme.h"
#include <guichan/exception.hpp>
-#include <utility>
-
-ImageRect::ImageRect(ImageRect &&r)
-{
- image = std::exchange(r.image, nullptr);
- top = r.top;
- left = r.left;
- bottom = r.bottom;
- right = r.right;
- fillMode = r.fillMode;
-}
-
-ImageRect::~ImageRect()
-{
- delete image;
-}
-
void Graphics::updateSize(int width, int height, float /*scale*/)
{
@@ -166,7 +150,7 @@ void Graphics::drawImageRect(const ImageRect &imgRect, int x, int y, int w, int
switch (imgRect.fillMode)
{
case FillMode::Stretch:
- drawRescaledImage(imgRect.image,
+ drawRescaledImage(imgRect.image.get(),
srcGridX[ix],
srcGridY[iy],
dstGridX[ix],
@@ -175,7 +159,7 @@ void Graphics::drawImageRect(const ImageRect &imgRect, int x, int y, int w, int
dstW, dstH);
break;
case FillMode::Repeat:
- drawRescaledImagePattern(imgRect.image,
+ drawRescaledImagePattern(imgRect.image.get(),
srcGridX[ix],
srcGridY[iy],
srcW, srcH,
@@ -189,6 +173,72 @@ void Graphics::drawImageRect(const ImageRect &imgRect, int x, int y, int w, int
}
}
+void Graphics::drawText(const std::string &text,
+ int x, int y,
+ gcn::Graphics::Alignment alignment,
+ const gcn::Color &color,
+ gcn::Font *font,
+ bool outline,
+ bool shadow,
+ const std::optional<gcn::Color> &outlineColor,
+ const std::optional<gcn::Color> &shadowColor)
+{
+ switch (alignment)
+ {
+ case gcn::Graphics::LEFT:
+ break;
+ case gcn::Graphics::CENTER:
+ x -= font->getWidth(text) / 2;
+ break;
+ case gcn::Graphics::RIGHT:
+ x -= font->getWidth(text);
+ break;
+ default:
+ throw GCN_EXCEPTION("Unknown alignment.");
+ }
+
+ auto realOutlineColor = outlineColor;
+ auto realShadowColor = shadowColor;
+
+ if (shadow && !realShadowColor)
+ {
+ auto sc = Theme::getThemeColor(Theme::SHADOW);
+ sc.a = color.a / 2;
+ realShadowColor = sc;
+ }
+
+ if (outline && !realOutlineColor)
+ {
+ auto oc = Theme::getThemeColor(Theme::OUTLINE);
+ oc.a = color.a;
+ realOutlineColor = oc;
+ }
+
+ setColor(color);
+ static_cast<TrueTypeFont*>(font)->drawString(graphics, text, x, y,
+ realOutlineColor,
+ realShadowColor);
+}
+
+void Graphics::drawText(const std::string &text,
+ int x,
+ int y,
+ gcn::Graphics::Alignment align,
+ gcn::Font *font,
+ const TextFormat &format)
+{
+ drawText(text,
+ x,
+ y,
+ align,
+ format.color,
+ font,
+ format.outlineColor.has_value(),
+ format.shadowColor.has_value(),
+ format.outlineColor,
+ format.shadowColor);
+}
+
void Graphics::_beginDraw()
{
pushClipArea(gcn::Rectangle(0, 0, mWidth, mHeight));
diff --git a/src/graphics.h b/src/graphics.h
index 038b0c6b..b6508753 100644
--- a/src/graphics.h
+++ b/src/graphics.h
@@ -21,12 +21,17 @@
#pragma once
+#include "resources/image.h"
+
#include <SDL.h>
#include <guichan/color.hpp>
#include <guichan/graphics.hpp>
-class Image;
+#include <memory>
+#include <optional>
+
+struct TextFormat;
enum class FillMode
{
@@ -35,47 +40,27 @@ enum class FillMode
};
/**
- * 9 images defining a rectangle. 4 corners, 4 sides and a middle area. The
- * topology is as follows:
+ * An image reference along with the margins specifying how to render this
+ * image at different sizes. The margins divide the image into 9 sections as
+ * follows:
*
* <pre>
- * !-----!-----------------!-----!
- * ! 0 ! 1 ! 2 !
- * !-----!-----------------!-----!
- * ! 3 ! 4 ! 5 !
- * !-----!-----------------!-----!
- * ! 6 ! 7 ! 8 !
- * !-----!-----------------!-----!
+ * !------!--------------!-------!
+ * ! ! top ! !
+ * !------!--------------!-------!
+ * ! left ! ! right !
+ * !------!--------------!-------!
+ * ! ! bottom ! !
+ * !------!--------------!-------!
* </pre>
*
- * Sections 0, 2, 6 and 8 will remain as is. 1, 3, 4, 5 and 7 will be
- * repeated to fit the size of the widget.
+ * The corner sections will remain as is. The edges and the center sections
+ * will be repeated or stretched to fit the target size, depending on the fill
+ * mode.
*/
-class ImageRect
+struct ImageRect
{
-public:
- enum ImagePosition
- {
- UPPER_LEFT = 0,
- UPPER_CENTER = 1,
- UPPER_RIGHT = 2,
- LEFT = 3,
- CENTER = 4,
- RIGHT = 5,
- LOWER_LEFT = 6,
- LOWER_CENTER = 7,
- LOWER_RIGHT = 8
- };
-
- ImageRect() = default;
- ImageRect(const ImageRect &) = delete;
- ImageRect(ImageRect &&);
- ~ImageRect();
-
- ImageRect &operator=(const ImageRect &) = delete;
- ImageRect &operator=(ImageRect &&r) = delete;
-
- Image *image = nullptr;
+ std::unique_ptr<Image> image;
int top = 0;
int left = 0;
int bottom = 0;
@@ -205,6 +190,25 @@ class Graphics : public gcn::Graphics
drawImageRect(imgRect, area.x, area.y, area.width, area.height);
}
+ using gcn::Graphics::drawText;
+
+ void drawText(const std::string &text,
+ int x, int y,
+ gcn::Graphics::Alignment alignment,
+ const gcn::Color &color,
+ gcn::Font *font,
+ bool outline = false,
+ bool shadow = false,
+ const std::optional<gcn::Color> &outlineColor = {},
+ const std::optional<gcn::Color> &shadowColor = {});
+
+ void drawText(const std::string &text,
+ int x,
+ int y,
+ gcn::Graphics::Alignment align,
+ gcn::Font *font,
+ const TextFormat &format);
+
/**
* Updates the screen. This is done by either copying the buffer to the
* screen or swapping pages.
diff --git a/src/gui/abilitieswindow.cpp b/src/gui/abilitieswindow.cpp
index 700fa7ff..d8122bf3 100644
--- a/src/gui/abilitieswindow.cpp
+++ b/src/gui/abilitieswindow.cpp
@@ -145,13 +145,13 @@ void AbilitiesWindow::draw(gcn::Graphics *graphics)
void AbilitiesWindow::rebuild(const std::map<int, Ability> &abilityData)
{
delete_all(mEntries);
-
+
mEntries.clear();
int vPos = 0; //vertical position of next placed element
for (auto &[id, ability] : abilityData)
{
- logger->log("Updating ability GUI for %d", id);
+ Log::info("Updating ability GUI for %d", id);
AbilityInfo *info = AbilityDB::get(id);
if (info)
@@ -166,7 +166,7 @@ void AbilitiesWindow::rebuild(const std::map<int, Ability> &abilityData)
}
else
{
- logger->log("Warning: No info available of ability %d", id);
+ Log::warn("No info available of ability %d", id);
}
}
}
diff --git a/src/gui/buydialog.cpp b/src/gui/buydialog.cpp
index 135c2119..fb316722 100644
--- a/src/gui/buydialog.cpp
+++ b/src/gui/buydialog.cpp
@@ -54,7 +54,7 @@ BuyDialog::BuyDialog(int npcId):
setCloseButton(true);
setMinWidth(260);
setMinHeight(230);
- setDefaultSize(260, 230, ImageRect::CENTER);
+ setDefaultSize(260, 230, WindowAlignment::Center);
mShopItems = new ShopItems;
diff --git a/src/gui/changeemaildialog.cpp b/src/gui/changeemaildialog.cpp
index ce83087b..e10a0d18 100644
--- a/src/gui/changeemaildialog.cpp
+++ b/src/gui/changeemaildialog.cpp
@@ -112,8 +112,8 @@ void ChangeEmailDialog::action(const gcn::ActionEvent &event)
const std::string username = mLoginData->username.c_str();
const std::string newFirstEmail = mFirstEmailField->getText();
const std::string newSecondEmail = mSecondEmailField->getText();
- logger->log("ChangeEmailDialog::Email change, Username is %s",
- username.c_str());
+ Log::info("ChangeEmailDialog::Email change, Username is %s",
+ username.c_str());
std::stringstream errorMessage;
int error = 0;
diff --git a/src/gui/changepassworddialog.cpp b/src/gui/changepassworddialog.cpp
index 437a8c90..91d7721d 100644
--- a/src/gui/changepassworddialog.cpp
+++ b/src/gui/changepassworddialog.cpp
@@ -94,8 +94,8 @@ void ChangePasswordDialog::action(const gcn::ActionEvent &event)
const std::string oldPassword = mOldPassField->getText();
const std::string newFirstPass = mFirstPassField->getText();
const std::string newSecondPass = mSecondPassField->getText();
- logger->log("ChangePasswordDialog::Password change, Username is %s",
- username.c_str());
+ Log::info("ChangePasswordDialog::Password change, Username is %s",
+ username.c_str());
std::stringstream errorMessage;
int error = 0;
diff --git a/src/gui/charselectdialog.cpp b/src/gui/charselectdialog.cpp
index 2485be69..1ed353dd 100644
--- a/src/gui/charselectdialog.cpp
+++ b/src/gui/charselectdialog.cpp
@@ -271,7 +271,7 @@ void CharSelectDialog::setCharacters(const Net::Characters &characters)
if (characterSlot >= (int)mCharacterEntries.size())
{
- logger->log("Warning: slot out of range: %d", character->slot);
+ Log::warn("Slot out of range: %d", character->slot);
continue;
}
diff --git a/src/gui/chatwindow.cpp b/src/gui/chatwindow.cpp
index d006097c..d19231ab 100644
--- a/src/gui/chatwindow.cpp
+++ b/src/gui/chatwindow.cpp
@@ -102,7 +102,7 @@ ChatWindow::ChatWindow():
setResizable(true);
setDefaultVisible(true);
setSaveVisible(true);
- setDefaultSize(600, 123, ImageRect::LOWER_LEFT);
+ setDefaultSize(600, 123, WindowAlignment::BottomLeft);
setMinWidth(150);
setMinHeight(90);
diff --git a/src/gui/connectiondialog.cpp b/src/gui/connectiondialog.cpp
index 2ff68a15..e6435269 100644
--- a/src/gui/connectiondialog.cpp
+++ b/src/gui/connectiondialog.cpp
@@ -54,7 +54,7 @@ ConnectionDialog::ConnectionDialog(const std::string &text,
void ConnectionDialog::action(const gcn::ActionEvent &)
{
- logger->log("Cancel pressed");
+ Log::info("Cancel pressed");
Client::setState(mCancelState);
}
diff --git a/src/gui/equipmentwindow.cpp b/src/gui/equipmentwindow.cpp
index b9d3b0fd..8523e7b2 100644
--- a/src/gui/equipmentwindow.cpp
+++ b/src/gui/equipmentwindow.cpp
@@ -63,7 +63,7 @@ EquipmentWindow::EquipmentWindow(Equipment *equipment):
setCloseButton(true);
setSaveVisible(true);
setContentSize(175, 290);
- setDefaultSize(getWidth(), getHeight(), ImageRect::CENTER);
+ setDefaultSize(getWidth(), getHeight(), WindowAlignment::Center);
loadWindowState();
mUnequip = new Button(_("Unequip"), "unequip", this);
diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp
index 0c3d78eb..768aeb2b 100644
--- a/src/gui/gui.cpp
+++ b/src/gui/gui.cpp
@@ -71,7 +71,7 @@ Gui::Gui(Graphics *graphics, const std::string &themePath)
setTheme(themeIt != mAvailableThemes.end() ? *themeIt : mAvailableThemes.front());
- logger->log("Initializing GUI...");
+ Log::info("Initializing GUI...");
// Set graphics
setGraphics(graphics);
@@ -106,8 +106,8 @@ Gui::Gui(Graphics *graphics, const std::string &themePath)
}
catch (gcn::Exception e)
{
- logger->error(std::string("Unable to load '") + fontFile +
- std::string("': ") + e.getMessage());
+ Log::critical(std::string("Unable to load '") + fontFile +
+ "': " + e.getMessage());
}
// Set bold font
@@ -119,8 +119,8 @@ Gui::Gui(Graphics *graphics, const std::string &themePath)
}
catch (gcn::Exception e)
{
- logger->error(std::string("Unable to load '") + fontFile +
- std::string("': ") + e.getMessage());
+ Log::critical(std::string("Unable to load '") + fontFile +
+ "': " + e.getMessage());
}
// Set mono font
@@ -132,8 +132,8 @@ Gui::Gui(Graphics *graphics, const std::string &themePath)
}
catch (gcn::Exception e)
{
- logger->error(std::string("Unable to load '") + fontFile +
- std::string("': ") + e.getMessage());
+ Log::critical(std::string("Unable to load '") + fontFile +
+ "': " + e.getMessage());
}
loadCustomCursors();
@@ -288,32 +288,20 @@ void Gui::loadCustomCursors()
SDL_Surface *mouseSurface = loadSurface(cursorPath);
if (!mouseSurface)
{
- logger->log("Warning: Unable to load mouse cursor file (%s): %s",
- cursorPath.c_str(), SDL_GetError());
+ Log::warn("Unable to load mouse cursor file (%s): %s",
+ cursorPath.c_str(), SDL_GetError());
return;
}
SDL_SetSurfaceBlendMode(mouseSurface, SDL_BLENDMODE_NONE);
-#if SDL_BYTEORDER == SDL_BIG_ENDIAN
- const Uint32 rmask = 0xff000000;
- const Uint32 gmask = 0x00ff0000;
- const Uint32 bmask = 0x0000ff00;
- const Uint32 amask = 0x000000ff;
-#else
- const Uint32 rmask = 0x000000ff;
- const Uint32 gmask = 0x0000ff00;
- const Uint32 bmask = 0x00ff0000;
- const Uint32 amask = 0xff000000;
-#endif
-
constexpr int cursorSize = 40;
const int targetCursorSize = cursorSize * mCustomCursorScale;
const int columns = mouseSurface->w / cursorSize;
- SDL_Surface *cursorSurface = SDL_CreateRGBSurface(
+ SDL_Surface *cursorSurface = SDL_CreateRGBSurfaceWithFormat(
0, targetCursorSize, targetCursorSize, 32,
- rmask, gmask, bmask, amask);
+ SDL_PIXELFORMAT_RGBA32);
for (int i = 0; i < static_cast<int>(Cursor::Count); ++i)
{
@@ -329,7 +317,7 @@ void Gui::loadCustomCursors()
17 * mCustomCursorScale);
if (!cursor)
{
- logger->log("Warning: Unable to create cursor: %s", SDL_GetError());
+ Log::warn("Unable to create cursor: %s", SDL_GetError());
}
mCustomMouseCursors.push_back(cursor);
diff --git a/src/gui/helpwindow.cpp b/src/gui/helpwindow.cpp
index e0e21610..7c7c5d4c 100644
--- a/src/gui/helpwindow.cpp
+++ b/src/gui/helpwindow.cpp
@@ -45,7 +45,7 @@ HelpWindow::HelpWindow():
setResizable(true);
setupWindow->registerWindowForReset(this);
- setDefaultSize(500, 400, ImageRect::CENTER);
+ setDefaultSize(500, 400, WindowAlignment::Center);
mBrowserBox = new BrowserBox;
mScrollArea = new ScrollArea(mBrowserBox);
@@ -98,7 +98,7 @@ void HelpWindow::loadFile(const std::string &file)
char *fileContents = (char *) FS::loadFile(fileName, contentsLength);
if (!fileContents)
{
- logger->log("Couldn't load text file: %s", fileName.c_str());
+ Log::info("Couldn't load text file: %s", fileName.c_str());
return;
}
diff --git a/src/gui/inventorywindow.cpp b/src/gui/inventorywindow.cpp
index ab2e9c86..0125700c 100644
--- a/src/gui/inventorywindow.cpp
+++ b/src/gui/inventorywindow.cpp
@@ -65,7 +65,7 @@ InventoryWindow::InventoryWindow(Inventory *inventory):
setCloseButton(true);
setSaveVisible(true);
- setDefaultSize(387, 307, ImageRect::CENTER);
+ setDefaultSize(387, 307, WindowAlignment::Center);
setMinWidth(316);
setMinHeight(179);
addKeyListener(this);
diff --git a/src/gui/npcdialog.cpp b/src/gui/npcdialog.cpp
index 16e7db94..033d01cc 100644
--- a/src/gui/npcdialog.cpp
+++ b/src/gui/npcdialog.cpp
@@ -82,7 +82,7 @@ NpcDialog::NpcDialog(int npcId)
setMinWidth(200);
setMinHeight(150);
- setDefaultSize(260, 200, ImageRect::CENTER);
+ setDefaultSize(260, 200, WindowAlignment::Center);
// Setup output text box
mTextBox = new BrowserBox(BrowserBox::AUTO_WRAP);
diff --git a/src/gui/popupmenu.cpp b/src/gui/popupmenu.cpp
index 4bafc074..16b16af9 100644
--- a/src/gui/popupmenu.cpp
+++ b/src/gui/popupmenu.cpp
@@ -318,7 +318,7 @@ void PopupMenu::handleLink(const std::string &link)
// Unknown actions
else if (link != "cancel")
{
- logger->log("PopupMenu: Warning, unknown action '%s'", link.c_str());
+ Log::info("PopupMenu: Warning, unknown action '%s'", link.c_str());
}
setVisible(false);
diff --git a/src/gui/questswindow.cpp b/src/gui/questswindow.cpp
new file mode 100644
index 00000000..6769815e
--- /dev/null
+++ b/src/gui/questswindow.cpp
@@ -0,0 +1,290 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2025 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "questswindow.h"
+
+#include "configuration.h"
+
+#include "gui/setup.h"
+
+#include "gui/widgets/browserbox.h"
+#include "gui/widgets/checkbox.h"
+#include "gui/widgets/itemlinkhandler.h"
+#include "gui/widgets/layout.h"
+#include "gui/widgets/listbox.h"
+#include "gui/widgets/scrollarea.h"
+
+#include "net/net.h"
+#include "net/playerhandler.h"
+
+#include "resources/questdb.h"
+
+#include "utils/gettext.h"
+
+#include <guichan/font.hpp>
+
+#include <algorithm>
+
+class QuestsModel final : public gcn::ListModel
+{
+public:
+ int getNumberOfElements() override
+ { return mQuests.size(); }
+
+ std::string getElementAt(int i) override
+ { return mQuests[i].name(); }
+
+ const std::vector<QuestEntry> &getQuests() const
+ { return mQuests; }
+
+ void setQuests(const std::vector<QuestEntry> &quests)
+ { mQuests = quests; }
+
+private:
+ std::vector<QuestEntry> mQuests;
+};
+
+
+class QuestsListBox final : public ListBox
+{
+public:
+ QuestsListBox(QuestsModel *model)
+ : ListBox(model)
+ {}
+
+ unsigned getRowHeight() const override;
+
+ void draw(gcn::Graphics *graphics) override;
+};
+
+unsigned QuestsListBox::getRowHeight() const
+{
+ auto rowHeight = ListBox::getRowHeight();
+
+ if (auto icon = gui->getTheme()->getIcon("complete"))
+ rowHeight = std::max<unsigned>(rowHeight, icon->getHeight() + 2);
+
+ return rowHeight;
+}
+
+void QuestsListBox::draw(gcn::Graphics *gcnGraphics)
+{
+ if (!mListModel)
+ return;
+
+ auto *graphics = static_cast<Graphics *>(gcnGraphics);
+ auto *model = static_cast<QuestsModel *>(getListModel());
+
+ const int rowHeight = getRowHeight();
+
+ auto theme = gui->getTheme();
+ auto completeIcon = theme->getIcon("complete");
+ auto incompleteIcon = theme->getIcon("incomplete");
+
+ // Draw filled rectangle around the selected list element
+ if (mSelected >= 0)
+ {
+ auto highlightColor = Theme::getThemeColor(Theme::HIGHLIGHT);
+ highlightColor.a = gui->getTheme()->getGuiAlpha();
+ graphics->setColor(highlightColor);
+ graphics->fillRectangle(gcn::Rectangle(0, rowHeight * mSelected,
+ getWidth(), rowHeight));
+ }
+
+ // Draw the list elements
+ graphics->setFont(getFont());
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+
+ const int fontHeight = getFont()->getHeight();
+
+ for (int i = 0, y = 0; i < model->getNumberOfElements();
+ ++i, y += rowHeight)
+ {
+ if (mSelected == i)
+ graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT_TEXT));
+ else
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+
+ auto &quest = model->getQuests()[i];
+ int x = 1;
+
+ if (const Image *icon = quest.completed ? completeIcon : incompleteIcon)
+ {
+ graphics->drawImage(icon, x, y + (rowHeight - icon->getHeight()) / 2);
+ x += icon->getWidth() + 4;
+ }
+
+ graphics->drawText(quest.name(), x, y + (rowHeight - fontHeight) / 2);
+ }
+}
+
+
+QuestsWindow::QuestsWindow()
+ : Window(_("Quests"))
+ , mQuestsModel(std::make_unique<QuestsModel>())
+ , mQuestsListBox(new QuestsListBox(mQuestsModel.get()))
+ , mHideCompletedCheckBox(new CheckBox(_("Hide completed"), config.hideCompletedQuests))
+ , mQuestDetails(new BrowserBox(BrowserBox::AUTO_WRAP))
+ , mLinkHandler(std::make_unique<ItemLinkHandler>())
+{
+ setWindowName("Quests");
+ setupWindow->registerWindowForReset(this);
+ setResizable(true);
+ setCloseButton(true);
+ setSaveVisible(true);
+
+ setDefaultSize(387, 307, WindowAlignment::Center);
+ setMinWidth(316);
+ setMinHeight(179);
+
+ mQuestsListBox->addSelectionListener(this);
+ mHideCompletedCheckBox->setActionEventId("hideCompleted");
+ mHideCompletedCheckBox->addActionListener(this);
+
+ auto questListScrollArea = new ScrollArea(mQuestsListBox);
+ questListScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ mQuestDetails->setLinkHandler(mLinkHandler.get());
+ mQuestDetailsScrollArea = new ScrollArea(mQuestDetails);
+ mQuestDetailsScrollArea->setHorizontalScrollPolicy(gcn::ScrollArea::SHOW_NEVER);
+
+ auto place = getPlacer(0, 0);
+ place(0, 0, questListScrollArea, 2, 2).setPadding(2);
+ place(2, 0, mQuestDetailsScrollArea, 3, 2).setPadding(2);
+ place = getPlacer(0, 1);
+ place(0, 0, mHideCompletedCheckBox);
+
+ getLayout().setRowHeight(1, 0); // Don't scale up the bottom row
+
+ listen(Event::QuestsChannel);
+
+ refreshQuestList();
+ loadWindowState();
+}
+
+QuestsWindow::~QuestsWindow() = default;
+
+void QuestsWindow::action(const gcn::ActionEvent &event)
+{
+ if (event.getId() == "hideCompleted")
+ {
+ config.hideCompletedQuests = mHideCompletedCheckBox->isSelected();
+ refreshQuestList();
+ }
+}
+
+void QuestsWindow::valueChanged(const gcn::SelectionEvent &event)
+{
+ if (mSelectedQuestIndex != mQuestsListBox->getSelected())
+ updateQuestDetails();
+}
+
+void QuestsWindow::event(Event::Channel channel, const Event &event)
+{
+ if (channel == Event::QuestsChannel)
+ {
+ if (event.getType() == Event::QuestVarsChanged)
+ refreshQuestList();
+ }
+}
+
+void QuestsWindow::refreshQuestList()
+{
+ // Store the currently selected quest state and varId to preserve selection
+ const QuestState *selectedQuestState = nullptr;
+ int selectedVarId = -1;
+ if (mSelectedQuestIndex >= 0 && mSelectedQuestIndex < mQuestsModel->getNumberOfElements())
+ {
+ const auto &selectedQuest = mQuestsModel->getQuests().at(mSelectedQuestIndex);
+ selectedQuestState = selectedQuest.state;
+ selectedVarId = selectedQuest.varId;
+ }
+
+ auto &questVars = Net::getPlayerHandler()->getQuestVars();
+ auto newQuests = QuestDB::getQuestsEntries(questVars, config.hideCompletedQuests);
+
+ // Put completed quests at the top
+ std::stable_sort(newQuests.begin(), newQuests.end(), [](const QuestEntry &a, const QuestEntry &b) {
+ return a.completed > b.completed;
+ });
+
+ mQuestsModel->setQuests(newQuests);
+
+ if (!selectedQuestState)
+ return;
+
+ // Try to find and reselect the same quest, preferring exact state match
+ int newSelectedIndex = -1;
+
+ for (int i = 0; i < static_cast<int>(newQuests.size()); ++i)
+ {
+ if (newQuests[i].state == selectedQuestState)
+ {
+ newSelectedIndex = i;
+ break;
+ }
+ else if (newSelectedIndex == -1 && newQuests[i].varId == selectedVarId)
+ {
+ newSelectedIndex = i;
+ // Don't break here - continue looking for exact state match
+ }
+ }
+
+ if (mSelectedQuestIndex != newSelectedIndex)
+ mQuestsListBox->setSelected(newSelectedIndex);
+ else
+ updateQuestDetails();
+}
+
+void QuestsWindow::updateQuestDetails()
+{
+ mQuestDetails->clearRows();
+
+ mSelectedQuestIndex = mQuestsListBox->getSelected();
+ if (mSelectedQuestIndex < 0 || mSelectedQuestIndex >= mQuestsModel->getNumberOfElements())
+ return;
+
+ const QuestEntry &quest = mQuestsModel->getQuests().at(mSelectedQuestIndex);
+ for (const auto &row : quest.rows())
+ {
+ switch (row.type)
+ {
+ case QuestRowType::Text:
+ mQuestDetails->addRow(row.text);
+ break;
+ case QuestRowType::Name:
+ mQuestDetails->addRow("[" + row.text + "]");
+ break;
+ case QuestRowType::Reward:
+ mQuestDetails->addRow(strprintf(_("Reward: %s"), row.text.c_str()));
+ break;
+ case QuestRowType::Giver:
+ mQuestDetails->addRow(strprintf(_("Quest Giver: %s"), row.text.c_str()));
+ break;
+ case QuestRowType::Coordinates:
+ mQuestDetails->addRow(strprintf(_("Coordinates: %s (%d, %d)"),
+ row.text.c_str(), row.x, row.y));
+ break;
+ case QuestRowType::NPC:
+ mQuestDetails->addRow(strprintf(_("NPC: %s"), row.text.c_str()));
+ break;
+ }
+ }
+}
diff --git a/src/gui/questswindow.h b/src/gui/questswindow.h
new file mode 100644
index 00000000..a479f826
--- /dev/null
+++ b/src/gui/questswindow.h
@@ -0,0 +1,71 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2025 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "eventlistener.h"
+
+#include "gui/widgets/window.h"
+
+#include <guichan/actionlistener.hpp>
+#include <guichan/keylistener.hpp>
+#include <guichan/selectionlistener.hpp>
+
+class BrowserBox;
+class CheckBox;
+class LinkHandler;
+class QuestsListBox;
+class QuestsModel;
+class ScrollArea;
+
+/**
+ * Quests window.
+ *
+ * \ingroup Interface
+ */
+class QuestsWindow final : public Window,
+ public gcn::ActionListener,
+ public gcn::SelectionListener,
+ public EventListener
+{
+public:
+ QuestsWindow();
+ ~QuestsWindow();
+
+ void action(const gcn::ActionEvent &event) override;
+
+ void valueChanged(const gcn::SelectionEvent &event) override;
+
+ void event(Event::Channel channel, const Event &event) override;
+
+private:
+ void refreshQuestList();
+ void updateQuestDetails();
+
+ int mSelectedQuestIndex = -1;
+ std::unique_ptr<QuestsModel> mQuestsModel;
+ QuestsListBox *mQuestsListBox;
+ CheckBox *mHideCompletedCheckBox;
+ BrowserBox *mQuestDetails;
+ ScrollArea *mQuestDetailsScrollArea;
+ std::unique_ptr<LinkHandler> mLinkHandler;
+};
+
+extern QuestsWindow *questsWindow;
diff --git a/src/gui/recorder.cpp b/src/gui/recorder.cpp
index 894e3631..96e458ff 100644
--- a/src/gui/recorder.cpp
+++ b/src/gui/recorder.cpp
@@ -47,7 +47,7 @@ Recorder::Recorder(ChatWindow *chat,
// 123 is the default chat window height. If you change this in Chat, please
// change it here as well
setDefaultSize(button->getWidth() + offsetX, button->getHeight() +
- offsetY, ImageRect::LOWER_LEFT, 0, 123);
+ offsetY, WindowAlignment::BottomLeft, 0, 123);
place(0, 0, button);
diff --git a/src/gui/register.cpp b/src/gui/register.cpp
index 62114c10..d7924021 100644
--- a/src/gui/register.cpp
+++ b/src/gui/register.cpp
@@ -147,7 +147,7 @@ void RegisterDialog::action(const gcn::ActionEvent &event)
else if (event.getId() == "register" && canSubmit())
{
const std::string user = mUserField->getText();
- logger->log("RegisterDialog::register Username is %s", user.c_str());
+ Log::info("RegisterDialog::register Username is %s", user.c_str());
std::string errorMessage;
int error = 0;
diff --git a/src/gui/selldialog.cpp b/src/gui/selldialog.cpp
index 4aeacd6f..4fa12e53 100644
--- a/src/gui/selldialog.cpp
+++ b/src/gui/selldialog.cpp
@@ -56,7 +56,7 @@ SellDialog::SellDialog(int npcId):
setCloseButton(true);
setMinWidth(260);
setMinHeight(230);
- setDefaultSize(260, 230, ImageRect::CENTER);
+ setDefaultSize(260, 230, WindowAlignment::Center);
// Create a ShopItems instance, that is aware of duplicate entries.
mShopItems = new ShopItems(true);
diff --git a/src/gui/serverdialog.cpp b/src/gui/serverdialog.cpp
index f5418625..e41c0bbe 100644
--- a/src/gui/serverdialog.cpp
+++ b/src/gui/serverdialog.cpp
@@ -115,7 +115,10 @@ public:
{
const ServerInfo &info = model->getServer(i);
- graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+ if (mSelected == i)
+ graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT_TEXT));
+ else
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
if (!info.name.empty())
{
@@ -201,7 +204,7 @@ ServerDialog::ServerDialog(ServerInfo *serverInfo, const std::string &dir):
setMinWidth(getWidth());
setMinHeight(getHeight());
- setDefaultSize(getWidth(), getHeight(), ImageRect::CENTER);
+ setDefaultSize(getWidth(), getHeight(), WindowAlignment::Center);
setResizable(true);
addKeyListener(this);
@@ -361,7 +364,7 @@ void ServerDialog::logic()
case DownloadStatus::Canceled:
case DownloadStatus::Error:
mDownloadDone = true;
- logger->log("Error retrieving server list: %s", mDownload->getError());
+ Log::info("Error retrieving server list: %s", mDownload->getError());
mDownloadText->setCaption(_("Error retrieving server list!"));
break;
@@ -406,15 +409,14 @@ void ServerDialog::loadServers()
if (!rootNode || rootNode.name() != "serverlist")
{
- logger->log("Error loading server list!");
+ Log::info("Error loading server list!");
return;
}
int version = rootNode.getProperty("version", 0);
if (version != 1)
{
- logger->log("Error: unsupported online server list version: %d",
- version);
+ Log::error("Unsupported online server list version: %d", version);
return;
}
@@ -440,8 +442,8 @@ void ServerDialog::loadServer(XML::Node serverNode)
#endif
)
{
- logger->log("Ignoring server entry with unknown type: %s",
- type.c_str());
+ Log::info("Ignoring server entry with unknown type: %s",
+ type.c_str());
return;
}
diff --git a/src/gui/setup_audio.cpp b/src/gui/setup_audio.cpp
index 43b132d8..0031f9bc 100644
--- a/src/gui/setup_audio.cpp
+++ b/src/gui/setup_audio.cpp
@@ -108,7 +108,7 @@ void Setup_Audio::apply()
catch (const char *err)
{
new OkDialog(_("Sound Engine"), err);
- logger->log("Warning: %s", err);
+ Log::warn("%s", err);
}
}
else
diff --git a/src/gui/shortcutwindow.cpp b/src/gui/shortcutwindow.cpp
index 2cffbb81..c33fcf01 100644
--- a/src/gui/shortcutwindow.cpp
+++ b/src/gui/shortcutwindow.cpp
@@ -49,10 +49,13 @@ ShortcutWindow::ShortcutWindow(const std::string &title,
const int border = (getPadding() + content->getFrameSize()) * 2;
setMinWidth(content->getBoxWidth() + border);
setMinHeight(content->getBoxHeight() + border + GRAB_MARGIN);
- setMaxWidth(content->getBoxWidth() * content->getMaxItems() + border);
- setMaxHeight(content->getBoxHeight() * content->getMaxItems() + border + GRAB_MARGIN);
- setDefaultSize(getMinWidth(), getMaxHeight(), ImageRect::LOWER_RIGHT);
+ const int maxContentWidth = content->getBoxWidth() * content->getMaxItems();
+ const int maxContentHeight = content->getBoxHeight() * content->getMaxItems();
+ setMaxWidth(std::max(getMinWidth(), maxContentWidth + border));
+ setMaxHeight(std::max(getMinHeight(), maxContentHeight + border + GRAB_MARGIN));
+
+ setDefaultSize(getMinWidth(), getMaxHeight(), WindowAlignment::BottomRight);
place(0, 0, scrollArea, 5, 5).setPadding(0);
diff --git a/src/gui/skilldialog.cpp b/src/gui/skilldialog.cpp
index 40421daf..49552421 100644
--- a/src/gui/skilldialog.cpp
+++ b/src/gui/skilldialog.cpp
@@ -147,11 +147,15 @@ public:
}
// Draw the list elements
- graphics->setColor(Theme::getThemeColor(Theme::TEXT));
for (int i = 0, y = 1;
i < model->getNumberOfElements();
++i, y += getRowHeight())
{
+ if (mSelected == i)
+ graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT_TEXT));
+ else
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+
if (SkillInfo *e = model->getSkillAt(i))
e->draw(graphics, y, getWidth());
}
@@ -290,7 +294,7 @@ void SkillDialog::loadSkills()
if (!root || root.name() != "skills")
{
- logger->log("Error loading skills file: %s", SKILLS_FILE);
+ Log::info("Error loading skills file: %s", SKILLS_FILE);
if (Net::getNetworkType() == ServerType::TmwAthena)
{
diff --git a/src/gui/tradewindow.cpp b/src/gui/tradewindow.cpp
index e24845b7..6610bd43 100644
--- a/src/gui/tradewindow.cpp
+++ b/src/gui/tradewindow.cpp
@@ -60,7 +60,7 @@ TradeWindow::TradeWindow():
setWindowName("Trade");
setResizable(true);
setCloseButton(true);
- setDefaultSize(386, 180, ImageRect::CENTER);
+ setDefaultSize(386, 180, WindowAlignment::Center);
setMinWidth(386);
setMinHeight(180);
setupWindow->registerWindowForReset(this);
diff --git a/src/gui/truetypefont.cpp b/src/gui/truetypefont.cpp
index 444641b5..c85d08c0 100644
--- a/src/gui/truetypefont.cpp
+++ b/src/gui/truetypefont.cpp
@@ -53,6 +53,7 @@ bool operator==(SDL_Color lhs, SDL_Color rhs)
lhs.a == rhs.a);
}
+
class TextChunk
{
public:
@@ -60,24 +61,49 @@ public:
: text(text)
{}
- void generate(TTF_Font *font)
+ void render(Graphics *graphics,
+ int x, int y,
+ TTF_Font *font,
+ std::unique_ptr<Image> &img,
+ float scale);
+
+ const std::string text;
+ std::unique_ptr<Image> regular;
+ std::unique_ptr<Image> outlined;
+};
+
+void TextChunk::render(Graphics *graphics,
+ int x, int y,
+ TTF_Font *font,
+ std::unique_ptr<Image> &img,
+ float scale)
+{
+ if (!img)
{
// Always render in white, we'll use color modulation when rendering
+ constexpr SDL_Color white = { 255, 255, 255, 255 };
SDL_Surface *surface = TTF_RenderUTF8_Blended(font,
getSafeUtf8String(text),
- SDL_Color { 255, 255, 255, 255 });
+ white);
- if (!surface)
- return;
+ if (surface)
+ {
+ img.reset(Image::load(surface));
+ SDL_FreeSurface(surface);
+ }
+ }
- img.reset(Image::load(surface));
+ if (img)
+ {
+ graphics->drawRescaledImageF(img.get(), 0, 0, x, y,
+ img->getWidth(),
+ img->getHeight(),
+ img->getWidth() / scale,
+ img->getHeight() / scale, true);
- SDL_FreeSurface(surface);
}
+}
- std::unique_ptr<Image> img;
- const std::string text;
-};
std::list<TrueTypeFont*> TrueTypeFont::mFonts;
float TrueTypeFont::mScale = 1.0f;
@@ -85,6 +111,7 @@ float TrueTypeFont::mScale = 1.0f;
TrueTypeFont::TrueTypeFont(const std::string &filename, int size, int style)
: mFilename(filename)
, mPointSize(size)
+ , mStyle(style)
{
if (TTF_Init() == -1)
{
@@ -93,14 +120,18 @@ TrueTypeFont::TrueTypeFont(const std::string &filename, int size, int style)
}
mFont = TTF_OpenFont(filename.c_str(), size * mScale);
+ mFontOutline = TTF_OpenFont(filename.c_str(), size * mScale);
- if (!mFont)
+ if (!mFont || !mFontOutline)
{
throw GCN_EXCEPTION("SDLTrueTypeFont::SDLTrueTypeFont: " +
std::string(TTF_GetError()));
}
TTF_SetFontStyle(mFont, style);
+ TTF_SetFontStyle(mFontOutline, style);
+
+ TTF_SetFontOutline(mFontOutline, static_cast<int>(mScale));
mFonts.push_back(this);
}
@@ -112,6 +143,9 @@ TrueTypeFont::~TrueTypeFont()
if (mFont)
TTF_CloseFont(mFont);
+ if (mFontOutline)
+ TTF_CloseFont(mFontOutline);
+
TTF_Quit();
}
@@ -123,37 +157,41 @@ void TrueTypeFont::drawString(gcn::Graphics *graphics,
return;
auto *g = static_cast<Graphics *>(graphics);
+ TextChunk &chunk = getChunk(text);
- bool found = false;
+ chunk.render(g, x, y, mFont, chunk.regular, mScale);
+}
- for (auto i = mCache.begin(); i != mCache.end(); ++i)
- {
- auto &chunk = *i;
- if (chunk.text == text)
- {
- // Raise priority: move it to front
- mCache.splice(mCache.begin(), mCache, i);
- found = true;
- break;
- }
- }
+void TrueTypeFont::drawString(Graphics *graphics,
+ const std::string &text,
+ int x, int y,
+ const std::optional<gcn::Color> &outlineColor,
+ const std::optional<gcn::Color> &shadowColor)
+{
+ if (text.empty())
+ return;
+
+ auto *g = static_cast<Graphics *>(graphics);
+ auto color = graphics->getColor();
+ TextChunk &chunk = getChunk(text);
- if (!found)
+ if (shadowColor)
{
- if (mCache.size() >= CACHE_SIZE)
- mCache.pop_back();
- mCache.emplace_front(text);
- mCache.front().generate(mFont);
+ g->setColor(*shadowColor);
+ if (outlineColor)
+ chunk.render(g, x, y, mFontOutline, chunk.outlined, mScale);
+ else
+ chunk.render(g, x + 1, y + 1, mFont, chunk.regular, mScale);
}
- if (auto img = mCache.front().img.get())
+ if (outlineColor)
{
- g->drawRescaledImageF(img, 0, 0, x, y,
- img->getWidth(),
- img->getHeight(),
- img->getWidth() / mScale,
- img->getHeight() / mScale, true);
+ g->setColor(*outlineColor);
+ chunk.render(g, x - 1, y - 1, mFontOutline, chunk.outlined, mScale);
}
+
+ g->setColor(color);
+ chunk.render(g, x, y, mFont, chunk.regular, mScale);
}
void TrueTypeFont::updateFontScale(float scale)
@@ -167,9 +205,16 @@ void TrueTypeFont::updateFontScale(float scale)
{
#if SDL_TTF_VERSION_ATLEAST(2, 0, 18)
TTF_SetFontSize(font->mFont, font->mPointSize * mScale);
+ TTF_SetFontSize(font->mFontOutline, font->mPointSize * mScale);
+ TTF_SetFontOutline(font->mFontOutline, mScale);
#else
TTF_CloseFont(font->mFont);
+ TTF_CloseFont(font->mFontOutline);
font->mFont = TTF_OpenFont(font->mFilename.c_str(), font->mPointSize * mScale);
+ font->mFontOutline = TTF_OpenFont(font->mFilename.c_str(), font->mPointSize * mScale);
+ TTF_SetFontStyle(font->mFont, font->mStyle);
+ TTF_SetFontStyle(font->mFontOutline, font->mStyle);
+ TTF_SetFontOutline(font->mFontOutline, mScale);
#endif
font->mCache.clear();
@@ -178,19 +223,11 @@ void TrueTypeFont::updateFontScale(float scale)
int TrueTypeFont::getWidth(const std::string &text) const
{
- for (auto i = mCache.begin(); i != mCache.end(); i++)
- {
- if (i->text == text)
- {
- // Raise priority: move it to front
- // Assumption is that TTF::draw will be called next
- mCache.splice(mCache.begin(), mCache, i);
- if (i->img)
- return std::ceil(i->img->getWidth() / mScale);
- return 0;
- }
- }
+ TextChunk &chunk = getChunk(text);
+ if (auto img = chunk.regular.get())
+ return std::ceil(img->getWidth() / mScale);
+ // If the image wasn't created yet, just calculate the width of the text
int w, h;
TTF_SizeUTF8(mFont, getSafeUtf8String(text), &w, &h);
return std::ceil(w / mScale);
@@ -205,3 +242,21 @@ int TrueTypeFont::getLineHeight() const
{
return std::ceil(TTF_FontLineSkip(mFont) / mScale);
}
+
+TextChunk &TrueTypeFont::getChunk(const std::string &text) const
+{
+ for (auto i = mCache.begin(); i != mCache.end(); i++)
+ {
+ if (i->text == text)
+ {
+ // Raise priority: move it to front
+ mCache.splice(mCache.begin(), mCache, i);
+ return *i;
+ }
+ }
+
+ if (mCache.size() >= CACHE_SIZE)
+ mCache.pop_back();
+
+ return mCache.emplace_front(text);
+}
diff --git a/src/gui/truetypefont.h b/src/gui/truetypefont.h
index a479537d..f88938ff 100644
--- a/src/gui/truetypefont.h
+++ b/src/gui/truetypefont.h
@@ -22,13 +22,16 @@
#pragma once
+#include <guichan/color.hpp>
#include <guichan/font.hpp>
#include <SDL_ttf.h>
#include <list>
+#include <optional>
#include <string>
+class Graphics;
class TextChunk;
/**
@@ -46,11 +49,13 @@ class TrueTypeFont : public gcn::Font
* @param size Font size.
*/
TrueTypeFont(const std::string &filename, int size, int style = 0);
-
~TrueTypeFont() override;
- int getWidth(const std::string &text) const override;
+ const std::string &filename() const { return mFilename; }
+ int pointSize() const { return mPointSize; }
+ int style() const { return mStyle; }
+ int getWidth(const std::string &text) const override;
int getHeight() const override;
/**
@@ -67,13 +72,27 @@ class TrueTypeFont : public gcn::Font
const std::string &text,
int x, int y) override;
+ /**
+ * Extended version of drawString that allows for rendering text with
+ * outline and/or shadow.
+ */
+ void drawString(Graphics *graphics,
+ const std::string &text,
+ int x, int y,
+ const std::optional<gcn::Color> &outlineColor,
+ const std::optional<gcn::Color> &shadowColor);
+
static void updateFontScale(float scale);
private:
+ TextChunk &getChunk(const std::string &text) const;
+
const std::string mFilename;
- TTF_Font *mFont;
+ TTF_Font *mFont = nullptr;
+ TTF_Font *mFontOutline = nullptr;
const int mPointSize;
+ const int mStyle;
// Word surfaces cache
mutable std::list<TextChunk> mCache;
diff --git a/src/gui/unregisterdialog.cpp b/src/gui/unregisterdialog.cpp
index 94f6ef62..2abbebf7 100644
--- a/src/gui/unregisterdialog.cpp
+++ b/src/gui/unregisterdialog.cpp
@@ -98,8 +98,8 @@ void UnRegisterDialog::action(const gcn::ActionEvent &event)
else if (event.getId() == "unregister")
{
const std::string &password = mPasswordField->getText();
- logger->log("UnregisterDialog::unregistered, Username is %s",
- mLoginData->username.c_str());
+ Log::info("UnregisterDialog::unregistered, Username is %s",
+ mLoginData->username.c_str());
std::stringstream errorMessage;
bool error = false;
diff --git a/src/gui/updaterwindow.cpp b/src/gui/updaterwindow.cpp
index 5cfb45cd..c16c3e04 100644
--- a/src/gui/updaterwindow.cpp
+++ b/src/gui/updaterwindow.cpp
@@ -60,7 +60,7 @@ std::vector<UpdateFile> loadXMLFile(const std::string &fileName)
if (!rootNode || rootNode.name() != "updates")
{
- logger->log("Error loading update file: %s", fileName.c_str());
+ Log::info("Error loading update file: %s", fileName.c_str());
return files;
}
@@ -110,7 +110,7 @@ std::vector<UpdateFile> loadTxtFile(const std::string &fileName)
}
else
{
- logger->log("Error loading update file: %s", fileName.c_str());
+ Log::info("Error loading update file: %s", fileName.c_str());
}
fileHandler.close();
@@ -128,7 +128,7 @@ UpdaterWindow::UpdaterWindow(const std::string &updateHost,
{
setWindowName("UpdaterWindow");
setResizable(true);
- setDefaultSize(450, 400, ImageRect::CENTER);
+ setDefaultSize(450, 400, WindowAlignment::Center);
setMinWidth(320);
setMinHeight(240);
@@ -258,9 +258,9 @@ void UpdaterWindow::loadUpdates()
mUpdateFiles = loadXMLFile(mUpdatesDir + "/" + xmlUpdateFile);
if (mUpdateFiles.empty())
{
- logger->log("Warning this server does not have a"
- " %s file falling back to %s", xmlUpdateFile,
- txtUpdateFile);
+ Log::warn("This server does not have a"
+ " %s file falling back to %s", xmlUpdateFile,
+ txtUpdateFile);
mUpdateFiles = loadTxtFile(mUpdatesDir + "/" + txtUpdateFile);
}
}
@@ -336,9 +336,9 @@ void UpdaterWindow::downloadCompleted()
mUpdateFiles = loadXMLFile(mUpdatesDir + "/" + xmlUpdateFile);
if (mUpdateFiles.empty())
{
- logger->log("Warning this server does not have a %s"
- " file falling back to %s",
- xmlUpdateFile, txtUpdateFile);
+ Log::warn("This server does not have a %s"
+ " file falling back to %s",
+ xmlUpdateFile, txtUpdateFile);
// If the resources.xml file fails, fall back onto a older version
mDialogState = DialogState::DownloadList;
@@ -383,7 +383,7 @@ void UpdaterWindow::downloadCompleted()
else
{
fclose(file);
- logger->log("%s already here", thisFile.name.c_str());
+ Log::info("%s already here", thisFile.name.c_str());
}
mUpdateIndex++;
}
diff --git a/src/gui/viewport.cpp b/src/gui/viewport.cpp
index 9e529063..3d90846e 100644
--- a/src/gui/viewport.cpp
+++ b/src/gui/viewport.cpp
@@ -278,7 +278,7 @@ void Viewport::_followMouse()
mMouseY = static_cast<int>(logicalY);
// If the left button is dragged
- if (mPlayerFollowMouse && button & SDL_BUTTON(1))
+ if (mPlayerFollowMouse && button & SDL_BUTTON_LMASK)
{
// We create a mouse event and send it to mouseDragged.
const Uint8 *keys = SDL_GetKeyboardState(nullptr);
diff --git a/src/gui/widgets/avatarlistbox.cpp b/src/gui/widgets/avatarlistbox.cpp
index 4a806d04..a5109267 100644
--- a/src/gui/widgets/avatarlistbox.cpp
+++ b/src/gui/widgets/avatarlistbox.cpp
@@ -76,11 +76,15 @@ void AvatarListBox::draw(gcn::Graphics *gcnGraphics)
auto offlineIcon = theme->getIcon("offline");
// Draw the list elements
- graphics->setColor(Theme::getThemeColor(Theme::TEXT));
for (int i = 0, y = 0;
i < model->getNumberOfElements();
++i, y += rowHeight)
{
+ if (mSelected == i)
+ graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT_TEXT));
+ else
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+
Avatar *a = model->getAvatarAt(i);
int x = 1;
diff --git a/src/gui/widgets/browserbox.cpp b/src/gui/widgets/browserbox.cpp
index 4fe85ae5..bf337a0f 100644
--- a/src/gui/widgets/browserbox.cpp
+++ b/src/gui/widgets/browserbox.cpp
@@ -23,7 +23,6 @@
#include "gui/widgets/browserbox.h"
#include "keyboardconfig.h"
-#include "textrenderer.h"
#include "gui/gui.h"
#include "gui/truetypefont.h"
@@ -228,7 +227,7 @@ void BrowserBox::addRow(std::string_view row)
void BrowserBox::clearRows()
{
mTextRows.clear();
- setSize(0, 0);
+ setSize(mMode == AUTO_SIZE ? 0 : getWidth(), 0);
mHoveredLink.reset();
maybeRelayoutText();
}
@@ -292,6 +291,8 @@ void BrowserBox::draw(gcn::Graphics *graphics)
}
}
+ auto g = static_cast<Graphics*>(graphics);
+
for (const auto &row : mTextRows)
{
for (const auto &part : row.parts)
@@ -301,16 +302,15 @@ void BrowserBox::draw(gcn::Graphics *graphics)
if (part.y > yEnd)
return;
- TextRenderer::renderText(graphics,
- part.text,
- part.x,
- part.y,
- Graphics::LEFT,
- part.color,
- part.font,
- part.outlineColor.has_value() || mOutline,
- mShadows,
- part.outlineColor);
+ g->drawText(part.text,
+ part.x,
+ part.y,
+ Graphics::LEFT,
+ part.color,
+ part.font,
+ part.outlineColor.has_value() || mOutline,
+ mShadows,
+ part.outlineColor);
}
}
}
diff --git a/src/gui/widgets/button.cpp b/src/gui/widgets/button.cpp
index 31c3a677..604f5dc8 100644
--- a/src/gui/widgets/button.cpp
+++ b/src/gui/widgets/button.cpp
@@ -28,7 +28,6 @@
#include "resources/image.h"
#include "resources/theme.h"
-#include "textrenderer.h"
#include <guichan/exception.hpp>
#include <guichan/font.hpp>
@@ -132,8 +131,9 @@ void Button::draw(gcn::Graphics *graphics)
if (isPressed())
widgetState.flags |= STATE_SELECTED;
+ auto g = static_cast<Graphics *>(graphics);
auto &skin = gui->getTheme()->getSkin(SkinType::Button);
- skin.draw(static_cast<Graphics *>(graphics), widgetState);
+ skin.draw(g, widgetState);
auto skinState = skin.getState(widgetState.flags);
auto font = (skinState && skinState->textFormat.bold) ? boldFont : getFont();
@@ -196,18 +196,16 @@ void Button::draw(gcn::Graphics *graphics)
}
if (btnIconWidth)
- static_cast<Graphics *>(graphics)->drawImage(icon, btnIconX, btnIconY);
+ g->drawImage(icon, btnIconX, btnIconY);
if (auto skinState = skin.getState(widgetState.flags))
{
- auto &textFormat = skinState->textFormat;
- TextRenderer::renderText(static_cast<Graphics *>(graphics),
- getCaption(),
- textX,
- textY,
- getAlignment(),
- font,
- textFormat);
+ g->drawText(getCaption(),
+ textX,
+ textY,
+ getAlignment(),
+ font,
+ skinState->textFormat);
}
}
diff --git a/src/gui/widgets/chattab.h b/src/gui/widgets/chattab.h
index dfc07638..5232392f 100644
--- a/src/gui/widgets/chattab.h
+++ b/src/gui/widgets/chattab.h
@@ -24,7 +24,6 @@
#include "gui/chatwindow.h"
#include "gui/widgets/tab.h"
-#include "gui/widgets/textfield.h"
class BrowserBox;
class Recorder;
diff --git a/src/gui/widgets/checkbox.cpp b/src/gui/widgets/checkbox.cpp
index e6079f2f..4bb0bb72 100644
--- a/src/gui/widgets/checkbox.cpp
+++ b/src/gui/widgets/checkbox.cpp
@@ -21,8 +21,6 @@
#include "gui/widgets/checkbox.h"
-#include "textrenderer.h"
-
#include "gui/gui.h"
#include "resources/theme.h"
@@ -44,19 +42,19 @@ void CheckBox::draw(gcn::Graphics* graphics)
if (isSelected())
widgetState.flags |= STATE_SELECTED;
+ auto g = static_cast<Graphics *>(graphics);
auto &skin = gui->getTheme()->getSkin(SkinType::CheckBox);
- skin.draw(static_cast<Graphics *>(graphics), widgetState);
+ skin.draw(g, widgetState);
if (auto skinState = skin.getState(widgetState.flags))
{
auto &textFormat = skinState->textFormat;
- TextRenderer::renderText(static_cast<Graphics *>(graphics),
- getCaption(),
- skin.getMinWidth() + skin.padding + skin.spacing,
- skin.padding,
- Graphics::LEFT,
- textFormat.bold ? boldFont : getFont(),
- textFormat);
+ g->drawText(getCaption(),
+ skin.getMinWidth() + skin.padding + skin.spacing,
+ skin.padding,
+ Graphics::LEFT,
+ textFormat.bold ? boldFont : getFont(),
+ textFormat);
}
}
diff --git a/src/gui/widgets/desktop.cpp b/src/gui/widgets/desktop.cpp
index e424beec..b21c235a 100644
--- a/src/gui/widgets/desktop.cpp
+++ b/src/gui/widgets/desktop.cpp
@@ -113,6 +113,6 @@ void Desktop::setBestFittingWallpaper()
}
else
{
- logger->log("Couldn't load %s as wallpaper", wallpaperName.c_str());
+ Log::info("Couldn't load %s as wallpaper", wallpaperName.c_str());
}
}
diff --git a/src/gui/widgets/itemcontainer.cpp b/src/gui/widgets/itemcontainer.cpp
index 57e8a973..63388213 100644
--- a/src/gui/widgets/itemcontainer.cpp
+++ b/src/gui/widgets/itemcontainer.cpp
@@ -155,7 +155,7 @@ void ItemContainer::draw(gcn::Graphics *graphics)
if (item->isEquipped())
g->setColor(theme->getColor(Theme::ITEM_EQUIPPED));
else
- g->setColor(gcn::Color(0, 0, 0));
+ g->setColor(theme->getColor(Theme::TEXT));
g->drawText(caption, itemX + slotSkin.width / 2,
itemY + slotSkin.height - 14, gcn::Graphics::CENTER);
@@ -354,9 +354,7 @@ void ItemContainer::mouseReleased(gcn::MouseEvent &event)
// Show ItemTooltip
void ItemContainer::mouseMoved(gcn::MouseEvent &event)
{
- Item *item = getItemAt(getSlotIndex(event.getX(), event.getY()));
-
- if (item)
+ if (Item *item = getItemAt(getSlotIndex(event.getX(), event.getY())))
{
mItemPopup->setItem(item->getInfo());
mItemPopup->position(viewport->getMouseX(), viewport->getMouseY());
@@ -394,12 +392,17 @@ void ItemContainer::adjustHeight()
int ItemContainer::getSlotIndex(int x, int y) const
{
+ if (x >= getWidth() || y >= getHeight())
+ return Inventory::NO_SLOT_INDEX;
+
auto &slotSkin = gui->getTheme()->getSkin(SkinType::ItemSlot);
+ const auto row = y / slotSkin.height;
+ const auto column = x / slotSkin.width;
- if (x < getWidth() && y < getHeight())
- return (y / slotSkin.height) * mGridColumns + (x / slotSkin.width);
+ if (row < 0 || row >= mGridRows || column < 0 || column >= mGridColumns)
+ return Inventory::NO_SLOT_INDEX;
- return Inventory::NO_SLOT_INDEX;
+ return (row * mGridColumns) + column;
}
void ItemContainer::keyAction()
diff --git a/src/gui/widgets/itemlinkhandler.cpp b/src/gui/widgets/itemlinkhandler.cpp
index f596fc82..d9d6f506 100644
--- a/src/gui/widgets/itemlinkhandler.cpp
+++ b/src/gui/widgets/itemlinkhandler.cpp
@@ -33,6 +33,7 @@
#include "gui/widgets/itemlinkhandler.h"
+#include "client.h"
#include "resources/iteminfo.h"
#include "resources/itemdb.h"
#include "utils/gettext.h"
@@ -42,6 +43,7 @@ ItemLinkHandler::ItemLinkHandler(Window *parent)
: mParent(parent)
{
mItemPopup = std::make_unique<ItemPopup>();
+ mItemPopup->addDeathListener(this);
}
ItemLinkHandler::~ItemLinkHandler() = default;
@@ -55,6 +57,24 @@ static bool isUrl(const std::string &link)
void ItemLinkHandler::handleLink(const std::string &link)
{
+#if SDL_VERSION_ATLEAST(2, 0, 14)
+ // Handle screenshots by constructing full file path
+ if (startsWith(link, "screenshot:"))
+ {
+ std::string filename = link.substr(11); // Remove "screenshot:" prefix
+
+ // Prevent directory traversal attacks or opening malicious files
+ if (filename.find("..") == std::string::npos && endsWith(filename, ".png"))
+ {
+ std::string fileUrl = "file://" + Client::getScreenshotDirectory() + "/" + filename;
+ if (SDL_OpenURL(fileUrl.c_str()) == -1)
+ new OkDialog(_("Open URL Failed"), SDL_GetError(), true, mParent);
+ }
+
+ return;
+ }
+#endif
+
if (isUrl(link))
{
mLink = link;
@@ -97,3 +117,10 @@ void ItemLinkHandler::action(const gcn::ActionEvent &actionEvent)
#endif
}
}
+
+void ItemLinkHandler::death(const gcn::Event &event)
+{
+ // If somebody else killed the PopupUp, make sure we don't also try to delete it
+ if (event.getSource() == mItemPopup.get())
+ mItemPopup.release();
+}
diff --git a/src/gui/widgets/itemlinkhandler.h b/src/gui/widgets/itemlinkhandler.h
index 58202d33..637482bd 100644
--- a/src/gui/widgets/itemlinkhandler.h
+++ b/src/gui/widgets/itemlinkhandler.h
@@ -24,13 +24,14 @@
#include "gui/widgets/linkhandler.h"
#include <guichan/actionlistener.hpp>
+#include <guichan/deathlistener.hpp>
#include <memory>
class ItemPopup;
class Window;
-class ItemLinkHandler : public LinkHandler, gcn::ActionListener
+class ItemLinkHandler : public LinkHandler, gcn::ActionListener, public gcn::DeathListener
{
public:
ItemLinkHandler(Window *parent = nullptr);
@@ -42,6 +43,9 @@ class ItemLinkHandler : public LinkHandler, gcn::ActionListener
// ActionListener interface
void action(const gcn::ActionEvent &actionEvent) override;
+ // DeathListener interface
+ void death(const gcn::Event &event) override;
+
private:
std::unique_ptr<ItemPopup> mItemPopup;
diff --git a/src/gui/widgets/itemshortcutcontainer.cpp b/src/gui/widgets/itemshortcutcontainer.cpp
index b7924a4b..b47fa29d 100644
--- a/src/gui/widgets/itemshortcutcontainer.cpp
+++ b/src/gui/widgets/itemshortcutcontainer.cpp
@@ -49,6 +49,7 @@ void ItemShortcutContainer::draw(gcn::Graphics *graphics)
{
auto *g = static_cast<Graphics*>(graphics);
auto theme = gui->getTheme();
+ auto &skin = theme->getSkin(SkinType::ShortcutBox);
graphics->setFont(getFont());
@@ -57,21 +58,22 @@ void ItemShortcutContainer::draw(gcn::Graphics *graphics)
WidgetState state;
state.x = (i % mGridWidth) * mBoxWidth;
state.y = (i / mGridWidth) * mBoxHeight;
- theme->drawSkin(g, SkinType::ShortcutBox, state);
+ skin.draw(g, state);
// Draw item keyboard shortcut.
const char *key = SDL_GetKeyName(
keyboard.getKeyValue(KeyboardConfig::KEY_SHORTCUT_1 + i));
graphics->setColor(Theme::getThemeColor(Theme::TEXT));
- g->drawText(key, state.x + 2, state.y + 2, gcn::Graphics::LEFT);
+ g->drawText(key,
+ state.x + skin.padding + 2,
+ state.y + skin.padding + 2,
+ gcn::Graphics::LEFT);
- if (itemShortcut->getItem(i) < 0)
+ const int itemId = itemShortcut->getItem(i);
+ if (itemId < 0)
continue;
- Item *item =
- PlayerInfo::getInventory()->findItem(itemShortcut->getItem(i));
-
- if (item)
+ if (Item *item = PlayerInfo::getInventory()->findItem(itemId))
{
// Draw item icon.
if (Image *image = item->getImage())
@@ -83,11 +85,13 @@ void ItemShortcutContainer::draw(gcn::Graphics *graphics)
caption = "Eq.";
image->setAlpha(1.0f);
- g->drawImage(image, state.x, state.y);
+ g->drawImage(image, state.x + skin.padding, state.y + skin.padding);
if (item->isEquipped())
g->setColor(Theme::getThemeColor(Theme::ITEM_EQUIPPED));
- g->drawText(caption, state.x + mBoxWidth / 2,
- state.y + mBoxHeight - 14, gcn::Graphics::CENTER);
+ g->drawText(caption,
+ state.x + mBoxWidth / 2,
+ state.y + mBoxHeight - 14,
+ gcn::Graphics::CENTER);
}
}
}
@@ -200,15 +204,7 @@ void ItemShortcutContainer::mouseReleased(gcn::MouseEvent &event)
// Show ItemTooltip
void ItemShortcutContainer::mouseMoved(gcn::MouseEvent &event)
{
- const int index = getIndexFromGrid(event.getX(), event.getY());
- if (index == -1)
- return;
-
- const int itemId = itemShortcut->getItem(index);
- if (itemId < 0)
- return;
-
- if (Item *item = PlayerInfo::getInventory()->findItem(itemId))
+ if (Item *item = getItemAt(event.getX(), event.getY()))
{
mItemPopup->setItem(item->getInfo());
mItemPopup->position(viewport->getMouseX(), viewport->getMouseY());
@@ -219,6 +215,19 @@ void ItemShortcutContainer::mouseMoved(gcn::MouseEvent &event)
}
}
+Item *ItemShortcutContainer::getItemAt(int x, int y) const
+{
+ const int index = getIndexFromGrid(x, y);
+ if (index == -1)
+ return nullptr;
+
+ const int itemId = itemShortcut->getItem(index);
+ if (itemId < 0)
+ return nullptr;
+
+ return PlayerInfo::getInventory()->findItem(itemId);
+}
+
// Hide ItemTooltip
void ItemShortcutContainer::mouseExited(gcn::MouseEvent &event)
{
diff --git a/src/gui/widgets/itemshortcutcontainer.h b/src/gui/widgets/itemshortcutcontainer.h
index 63d9e0ef..a01857db 100644
--- a/src/gui/widgets/itemshortcutcontainer.h
+++ b/src/gui/widgets/itemshortcutcontainer.h
@@ -67,6 +67,8 @@ class ItemShortcutContainer : public ShortcutContainer
void mouseExited(gcn::MouseEvent &event) override;
void mouseMoved(gcn::MouseEvent &event) override;
+ Item *getItemAt(int x, int y) const;
+
bool mItemClicked = false;
Item *mItemMoved = nullptr;
diff --git a/src/gui/widgets/label.cpp b/src/gui/widgets/label.cpp
index a2ed8820..9c0fd3cd 100644
--- a/src/gui/widgets/label.cpp
+++ b/src/gui/widgets/label.cpp
@@ -21,8 +21,6 @@
#include "gui/widgets/label.h"
-#include "textrenderer.h"
-
#include "resources/theme.h"
#include <guichan/exception.hpp>
@@ -59,15 +57,15 @@ void Label::draw(gcn::Graphics *graphics)
throw GCN_EXCEPTION("Unknown alignment.");
}
- TextRenderer::renderText(static_cast<Graphics *>(graphics),
- getCaption(),
- textX,
- textY,
- getAlignment(),
- getForegroundColor(),
- getFont(),
- mOutlineColor.has_value(),
- mShadowColor.has_value(),
- mOutlineColor,
- mShadowColor);
+ auto g = static_cast<Graphics *>(graphics);
+ g->drawText(getCaption(),
+ textX,
+ textY,
+ getAlignment(),
+ getForegroundColor(),
+ getFont(),
+ mOutlineColor.has_value(),
+ mShadowColor.has_value(),
+ mOutlineColor,
+ mShadowColor);
}
diff --git a/src/gui/widgets/listbox.cpp b/src/gui/widgets/listbox.cpp
index 112de232..612e785f 100644
--- a/src/gui/widgets/listbox.cpp
+++ b/src/gui/widgets/listbox.cpp
@@ -56,10 +56,14 @@ void ListBox::draw(gcn::Graphics *graphics)
}
// Draw the list elements
- graphics->setColor(Theme::getThemeColor(Theme::TEXT));
for (int i = 0, y = 0; i < mListModel->getNumberOfElements();
++i, y += height)
{
+ if (mSelected == i)
+ graphics->setColor(Theme::getThemeColor(Theme::HIGHLIGHT_TEXT));
+ else
+ graphics->setColor(Theme::getThemeColor(Theme::TEXT));
+
graphics->drawText(mListModel->getElementAt(i), 1, y);
}
}
diff --git a/src/gui/widgets/popup.cpp b/src/gui/widgets/popup.cpp
index b245b9e6..c4c6f652 100644
--- a/src/gui/widgets/popup.cpp
+++ b/src/gui/widgets/popup.cpp
@@ -40,7 +40,7 @@ Popup::Popup(const std::string &name, SkinType skinType)
, mMaxHeight(graphics->getHeight())
, mSkinType(skinType)
{
- logger->log("Popup::Popup(\"%s\")", name.c_str());
+ Log::debug("Popup::Popup(\"%s\")", name.c_str());
if (!windowContainer)
throw GCN_EXCEPTION("Popup::Popup(): no windowContainer set");
@@ -58,7 +58,7 @@ Popup::Popup(const std::string &name, SkinType skinType)
Popup::~Popup()
{
- logger->log("Popup::~Popup(\"%s\")", mPopupName.c_str());
+ Log::debug("Popup::~Popup(\"%s\")", mPopupName.c_str());
}
void Popup::setWindowContainer(WindowContainer *wc)
diff --git a/src/gui/widgets/progressbar.cpp b/src/gui/widgets/progressbar.cpp
index 5cf1b05a..502ab686 100644
--- a/src/gui/widgets/progressbar.cpp
+++ b/src/gui/widgets/progressbar.cpp
@@ -83,11 +83,16 @@ void ProgressBar::draw(gcn::Graphics *graphics)
rect.x = 0;
rect.y = 0;
+ Theme::ProgressPalette palette = Theme::THEME_PROG_END;
+ if (mProgressPalette >= 0)
+ palette = static_cast<Theme::ProgressPalette>(mProgressPalette);
+
gui->getTheme()->drawProgressBar(static_cast<Graphics *>(graphics),
rect,
mColor,
mProgress,
- mText);
+ mText,
+ palette);
}
void ProgressBar::setProgress(float progress)
diff --git a/src/gui/widgets/radiobutton.cpp b/src/gui/widgets/radiobutton.cpp
index ceba78eb..3474bbd8 100644
--- a/src/gui/widgets/radiobutton.cpp
+++ b/src/gui/widgets/radiobutton.cpp
@@ -21,11 +21,11 @@
#include "gui/widgets/radiobutton.h"
-#include "textrenderer.h"
-
#include "gui/gui.h"
#include "resources/theme.h"
+#include <guichan/font.hpp>
+
RadioButton::RadioButton(const std::string &caption,
const std::string &group,
bool marked)
@@ -44,19 +44,19 @@ void RadioButton::draw(gcn::Graphics* graphics)
if (isSelected())
widgetState.flags |= STATE_SELECTED;
+ auto g = static_cast<Graphics *>(graphics);
auto &skin = gui->getTheme()->getSkin(SkinType::RadioButton);
- skin.draw(static_cast<Graphics *>(graphics), widgetState);
+ skin.draw(g, widgetState);
if (auto skinState = skin.getState(widgetState.flags))
{
auto &textFormat = skinState->textFormat;
- TextRenderer::renderText(static_cast<Graphics *>(graphics),
- getCaption(),
- skin.getMinWidth() + skin.padding + skin.spacing,
- skin.padding,
- Graphics::LEFT,
- textFormat.bold ? boldFont : getFont(),
- textFormat);
+ g->drawText(getCaption(),
+ skin.getMinWidth() + skin.padding + skin.spacing,
+ skin.padding,
+ Graphics::LEFT,
+ textFormat.bold ? boldFont : getFont(),
+ textFormat);
}
}
diff --git a/src/gui/widgets/shoplistbox.cpp b/src/gui/widgets/shoplistbox.cpp
index e2313c85..d745d74d 100644
--- a/src/gui/widgets/shoplistbox.cpp
+++ b/src/gui/widgets/shoplistbox.cpp
@@ -75,6 +75,7 @@ void ShopListBox::draw(gcn::Graphics *gcnGraphics)
auto backgroundColor = Theme::getThemeColor(Theme::BACKGROUND);
auto warningColor = Theme::getThemeColor(Theme::SHOP_WARNING);
auto textColor = Theme::getThemeColor(Theme::TEXT);
+ auto highlightTextColor = Theme::getThemeColor(Theme::HIGHLIGHT_TEXT);
highlightColor.a = alpha;
backgroundColor.a = alpha;
warningColor.a = alpha;
@@ -136,7 +137,7 @@ void ShopListBox::draw(gcn::Graphics *gcnGraphics)
}
}
- graphics->setColor(textColor);
+ graphics->setColor(i == mSelected ? highlightTextColor : textColor);
graphics->drawText(mListModel->getElementAt(i),
ITEM_ICON_SIZE + 5,
y + (ITEM_ICON_SIZE - fontHeight) / 2);
diff --git a/src/gui/widgets/tab.cpp b/src/gui/widgets/tab.cpp
index b2779c4f..0f6ca4e5 100644
--- a/src/gui/widgets/tab.cpp
+++ b/src/gui/widgets/tab.cpp
@@ -73,18 +73,27 @@ void Tab::draw(gcn::Graphics *graphics)
if (mTabbedArea && mTabbedArea->isTabSelected(this))
flags |= STATE_SELECTED;
- auto &skin = gui->getTheme()->getSkin(SkinType::Tab);
+ auto theme = gui->getTheme();
+ auto &palette = theme->getPalette(0);
+ auto &skin = theme->getSkin(SkinType::Tab);
+
if (auto state = skin.getState(flags))
{
gcn::Color foregroundColor = state->textFormat.color;
+ auto outlineColor = state->textFormat.outlineColor;
if (mFlash)
- foregroundColor = Theme::getThemeColor(Theme::TAB_FLASH);
+ {
+ foregroundColor = palette.getColor(Theme::TAB_FLASH);
+ outlineColor = palette.getOutlineColor(Theme::TAB_FLASH);
+ }
else if (mTabColor)
+ {
foregroundColor = *mTabColor;
+ }
auto label = static_cast<Label*>(mLabel);
label->setForegroundColor(foregroundColor);
- label->setOutlineColor(state->textFormat.outlineColor);
+ label->setOutlineColor(outlineColor);
label->setShadowColor(state->textFormat.shadowColor);
}
diff --git a/src/gui/widgets/textbox.cpp b/src/gui/widgets/textbox.cpp
index 6cc514fe..7fd7d626 100644
--- a/src/gui/widgets/textbox.cpp
+++ b/src/gui/widgets/textbox.cpp
@@ -23,7 +23,6 @@
#include "gui/gui.h"
#include "resources/theme.h"
-#include "textrenderer.h"
#include <guichan/font.hpp>
@@ -96,7 +95,7 @@ void TextBox::setTextWrapped(const std::string &text, int minDimension)
xpos = width;
wrappedStream << word;
}
- else if (xpos != 0 && xpos + getFont()->getWidth(" ") + width <=
+ else if (xpos != 0 && xpos + getFont()->getWidth(" ") + width <=
mMinWidth)
{
xpos += getFont()->getWidth(" ") + width;
@@ -176,18 +175,19 @@ void TextBox::draw(gcn::Graphics *graphics)
graphics->setColor(*mTextColor);
graphics->setFont(getFont());
+ auto g = static_cast<Graphics*>(graphics);
+
for (i = 0; i < mTextRows.size(); i++)
{
// Move the text one pixel so we can have a caret before a letter.
- TextRenderer::renderText(graphics,
- mTextRows[i],
- 1,
- i * getFont()->getHeight(),
- gcn::Graphics::LEFT,
- *mTextColor,
- getFont(),
- mOutlineColor.has_value(),
- false,
- mOutlineColor);
+ g->drawText(mTextRows[i],
+ 1,
+ i * getFont()->getHeight(),
+ gcn::Graphics::LEFT,
+ *mTextColor,
+ getFont(),
+ mOutlineColor.has_value(),
+ false,
+ mOutlineColor);
}
}
diff --git a/src/gui/widgets/textpreview.cpp b/src/gui/widgets/textpreview.cpp
index 2f80bd23..aed04853 100644
--- a/src/gui/widgets/textpreview.cpp
+++ b/src/gui/widgets/textpreview.cpp
@@ -21,13 +21,7 @@
#include "gui/widgets/textpreview.h"
-#include "configuration.h"
-#include "textrenderer.h"
-
#include "gui/gui.h"
-#include "gui/truetypefont.h"
-
-#include <typeinfo>
TextPreview::TextPreview(const std::string &text)
: mText(text)
@@ -36,12 +30,13 @@ TextPreview::TextPreview(const std::string &text)
mTextColor = &Theme::getThemeColor(Theme::TEXT);
}
-void TextPreview::draw(gcn::Graphics* graphics)
+void TextPreview::draw(gcn::Graphics *graphics)
{
- TextRenderer::renderText(graphics, mText, 2, 2, gcn::Graphics::LEFT,
- gcn::Color(mTextColor->r,
- mTextColor->g,
- mTextColor->b,
- 255),
- mFont, mOutline, mShadow);
+ auto g = static_cast<Graphics*>(graphics);
+ g->drawText(mText, 2, 2, gcn::Graphics::LEFT,
+ gcn::Color(mTextColor->r,
+ mTextColor->g,
+ mTextColor->b,
+ 255),
+ mFont, mOutline, mShadow);
}
diff --git a/src/gui/widgets/window.cpp b/src/gui/widgets/window.cpp
index e57918b7..14d91af1 100644
--- a/src/gui/widgets/window.cpp
+++ b/src/gui/widgets/window.cpp
@@ -23,7 +23,6 @@
#include "configuration.h"
#include "log.h"
-#include "textrenderer.h"
#include "gui/gui.h"
#include "gui/viewport.h"
@@ -53,7 +52,7 @@ Window::Window(SkinType skinType, const std::string &caption, bool modal, Window
, mMaxWinWidth(graphics->getWidth())
, mMaxWinHeight(graphics->getHeight())
{
- logger->log("Window::Window(\"%s\")", caption.c_str());
+ Log::debug("Window::Window(\"%s\")", caption.c_str());
if (!windowContainer)
throw GCN_EXCEPTION("Window::Window(): no windowContainer set");
@@ -82,7 +81,7 @@ Window::Window(SkinType skinType, const std::string &caption, bool modal, Window
Window::~Window()
{
- logger->log("Window::~Window(\"%s\")", getCaption().c_str());
+ Log::debug("Window::~Window(\"%s\")", getCaption().c_str());
saveWindowState();
@@ -139,13 +138,12 @@ void Window::drawFrame(gcn::Graphics *graphics)
if (auto skinState = skin.getState(widgetState.flags))
{
auto &textFormat = skinState->textFormat;
- TextRenderer::renderText(g,
- getCaption(),
- getFrameSize() + skin.titleOffsetX,
- getFrameSize() + skin.titleOffsetY,
- gcn::Graphics::LEFT,
- textFormat.bold ? boldFont : getFont(),
- textFormat);
+ g->drawText(getCaption(),
+ getFrameSize() + skin.titleOffsetX,
+ getFrameSize() + skin.titleOffsetY,
+ gcn::Graphics::LEFT,
+ textFormat.bold ? boldFont : getFont(),
+ textFormat);
}
}
}
@@ -191,52 +189,6 @@ void Window::setLocationRelativeTo(gcn::Widget *widget)
getY() + (wy + (widget->getHeight() - getHeight()) / 2 - y));
}
-void Window::setLocationRelativeTo(ImageRect::ImagePosition position,
- int offsetX, int offsetY)
-{
- if (position == ImageRect::UPPER_LEFT)
- {
- }
- else if (position == ImageRect::UPPER_CENTER)
- {
- offsetX += (graphics->getWidth() - getWidth()) / 2;
- }
- else if (position == ImageRect::UPPER_RIGHT)
- {
- offsetX += graphics->getWidth() - getWidth();
- }
- else if (position == ImageRect::LEFT)
- {
- offsetY += (graphics->getHeight() - getHeight()) / 2;
- }
- else if (position == ImageRect::CENTER)
- {
- offsetX += (graphics->getWidth() - getWidth()) / 2;
- offsetY += (graphics->getHeight() - getHeight()) / 2;
- }
- else if (position == ImageRect::RIGHT)
- {
- offsetX += graphics->getWidth() - getWidth();
- offsetY += (graphics->getHeight() - getHeight()) / 2;
- }
- else if (position == ImageRect::LOWER_LEFT)
- {
- offsetY += graphics->getHeight() - getHeight();
- }
- else if (position == ImageRect::LOWER_CENTER)
- {
- offsetX += (graphics->getWidth() - getWidth()) / 2;
- offsetY += graphics->getHeight() - getHeight();
- }
- else if (position == ImageRect::LOWER_RIGHT)
- {
- offsetX += graphics->getWidth() - getWidth();
- offsetY += graphics->getHeight() - getHeight();
- }
-
- setPosition(offsetX, offsetY);
-}
-
void Window::setMinWidth(int width)
{
mMinWinWidth = std::max(getSkin().getMinWidth(), width);
@@ -599,41 +551,41 @@ void Window::setDefaultSize()
}
void Window::setDefaultSize(int defaultWidth, int defaultHeight,
- ImageRect::ImagePosition position,
+ WindowAlignment alignment,
int offsetX, int offsetY)
{
int x = 0;
int y = 0;
- switch (position)
+ switch (alignment)
{
- case ImageRect::UPPER_LEFT:
+ case WindowAlignment::TopLeft:
break;
- case ImageRect::UPPER_CENTER:
+ case WindowAlignment::Top:
x = (graphics->getWidth() - defaultWidth) / 2;
break;
- case ImageRect::UPPER_RIGHT:
+ case WindowAlignment::TopRight:
x = graphics->getWidth() - defaultWidth;
break;
- case ImageRect::LEFT:
+ case WindowAlignment::Left:
y = (graphics->getHeight() - defaultHeight) / 2;
break;
- case ImageRect::CENTER:
+ case WindowAlignment::Center:
x = (graphics->getWidth() - defaultWidth) / 2;
y = (graphics->getHeight() - defaultHeight) / 2;
break;
- case ImageRect::RIGHT:
+ case WindowAlignment::Right:
x = graphics->getWidth() - defaultWidth;
y = (graphics->getHeight() - defaultHeight) / 2;
break;
- case ImageRect::LOWER_LEFT:
+ case WindowAlignment::BottomLeft:
y = graphics->getHeight() - defaultHeight;
break;
- case ImageRect::LOWER_CENTER:
+ case WindowAlignment::Bottom:
x = (graphics->getWidth() - defaultWidth) / 2;
y = graphics->getHeight() - defaultHeight;
break;
- case ImageRect::LOWER_RIGHT:
+ case WindowAlignment::BottomRight:
x = graphics->getWidth() - defaultWidth;
y = graphics->getHeight() - defaultHeight;
break;
diff --git a/src/gui/widgets/window.h b/src/gui/widgets/window.h
index 6331a715..2a47b0b9 100644
--- a/src/gui/widgets/window.h
+++ b/src/gui/widgets/window.h
@@ -36,6 +36,19 @@ class ResizeGrip;
class Skin;
class WindowContainer;
+enum class WindowAlignment
+{
+ TopLeft,
+ Top,
+ TopRight,
+ Left,
+ Center,
+ Right,
+ BottomLeft,
+ Bottom,
+ BottomRight
+};
+
/**
* A window. This window can be dragged around and has a title bar. Windows are
* invisible by default.
@@ -103,12 +116,6 @@ class Window : public gcn::Window, gcn::WidgetListener
void setLocationRelativeTo(gcn::Widget *widget);
/**
- * Sets the location relative to the given enumerated position.
- */
- void setLocationRelativeTo(ImageRect::ImagePosition position,
- int offsetX = 0, int offsetY = 0);
-
- /**
* Sets whether or not the window can be resized.
*/
void setResizable(bool resize);
@@ -303,7 +310,7 @@ class Window : public gcn::Window, gcn::WidgetListener
* on a relative enumerated position, rather than a coordinate position.
*/
void setDefaultSize(int defaultWidth, int defaultHeight,
- ImageRect::ImagePosition position,
+ WindowAlignment alignment,
int offsetx = 0, int offsetY = 0);
/**
diff --git a/src/gui/windowmenu.cpp b/src/gui/windowmenu.cpp
index 0b2d126f..2c1b6211 100644
--- a/src/gui/windowmenu.cpp
+++ b/src/gui/windowmenu.cpp
@@ -23,9 +23,10 @@
#include "graphics.h"
+#include "gui/abilitieswindow.h"
#include "gui/emotepopup.h"
+#include "gui/questswindow.h"
#include "gui/skilldialog.h"
-#include "gui/abilitieswindow.h"
#include "gui/widgets/button.h"
#include "gui/widgets/window.h"
@@ -34,6 +35,8 @@
#include "net/net.h"
#include "net/playerhandler.h"
+#include "resources/questdb.h"
+
#include "utils/gettext.h"
#include <string>
@@ -64,6 +67,9 @@ WindowMenu::WindowMenu()
if (abilitiesWindow->hasAbilities())
addButton(N_("Abilities"), x, h, "button-icon-abilities.png");
+ if (QuestDB::hasQuests())
+ addButton(N_("Quests"), x, h, "button-icon-quests.png");
+
addButton(N_("Social"), x, h, "button-icon-social.png",
KeyboardConfig::KEY_WINDOW_SOCIAL);
addButton(N_("Shortcuts"), x, h, "button-icon-shortcut.png",
@@ -122,6 +128,10 @@ void WindowMenu::action(const gcn::ActionEvent &event)
{
window = skillDialog;
}
+ else if (event.getId() == "Quests")
+ {
+ window = questsWindow;
+ }
else if (event.getId() == "Abilities")
{
window = abilitiesWindow;
@@ -166,12 +176,18 @@ static std::string createShortcutCaption(const std::string &text,
KeyboardConfig::KeyAction key)
{
std::string caption = gettext(text.c_str());
+
if (key != KeyboardConfig::KEY_NO_VALUE)
{
- caption += " (";
- caption += SDL_GetKeyName(keyboard.getKeyValue(key));
- caption += ")";
+ auto keyValue = keyboard.getKeyValue(key);
+ if (keyValue > 0)
+ {
+ caption += " (";
+ caption += SDL_GetKeyName(keyValue);
+ caption += ")";
+ }
}
+
return caption;
}
@@ -179,7 +195,7 @@ void WindowMenu::addButton(const std::string &text, int &x, int &h,
const std::string &iconPath,
KeyboardConfig::KeyAction key)
{
- auto *btn = new Button("", text, this);
+ auto *btn = new Button(std::string(), text, this);
if (!iconPath.empty() && btn->setButtonIcon(iconPath))
{
btn->setButtonPopupText(createShortcutCaption(text, key));
@@ -187,7 +203,7 @@ void WindowMenu::addButton(const std::string &text, int &x, int &h,
else
{
btn->setCaption(gettext(text.c_str()));
- btn->setButtonPopupText(createShortcutCaption("", key));
+ btn->setButtonPopupText(createShortcutCaption(std::string(), key));
}
btn->setPosition(x, 0);
@@ -204,40 +220,45 @@ void WindowMenu::updatePopUpCaptions()
if (!button)
continue;
- std::string eventId = button->getActionEventId();
+ const std::string &eventId = button->getActionEventId();
if (eventId == "Status")
{
- button->setButtonPopupText(createShortcutCaption("Status",
+ button->setButtonPopupText(createShortcutCaption(eventId,
KeyboardConfig::KEY_WINDOW_STATUS));
}
else if (eventId == "Equipment")
{
- button->setButtonPopupText(createShortcutCaption("Equipment",
+ button->setButtonPopupText(createShortcutCaption(eventId,
KeyboardConfig::KEY_WINDOW_EQUIPMENT));
}
else if (eventId == "Inventory")
{
- button->setButtonPopupText(createShortcutCaption("Inventory",
+ button->setButtonPopupText(createShortcutCaption(eventId,
KeyboardConfig::KEY_WINDOW_INVENTORY));
}
else if (eventId == "Skills")
{
- button->setButtonPopupText(createShortcutCaption("Skills",
+ button->setButtonPopupText(createShortcutCaption(eventId,
KeyboardConfig::KEY_WINDOW_SKILL));
}
+ else if (eventId == "Quests")
+ {
+ button->setButtonPopupText(
+ createShortcutCaption(eventId, KeyboardConfig::KEY_WINDOW_QUESTS));
+ }
else if (eventId == "Social")
{
- button->setButtonPopupText(createShortcutCaption("Social",
+ button->setButtonPopupText(createShortcutCaption(eventId,
KeyboardConfig::KEY_WINDOW_SOCIAL));
}
else if (eventId == "Shortcuts")
{
- button->setButtonPopupText(createShortcutCaption("Shortcuts",
+ button->setButtonPopupText(createShortcutCaption(eventId,
KeyboardConfig::KEY_WINDOW_SHORTCUT));
}
else if (eventId == "Setup")
{
- button->setButtonPopupText(createShortcutCaption("Setup",
+ button->setButtonPopupText(createShortcutCaption(eventId,
KeyboardConfig::KEY_WINDOW_SETUP));
}
}
diff --git a/src/inventory.cpp b/src/inventory.cpp
index 51eb24e1..e45e094c 100644
--- a/src/inventory.cpp
+++ b/src/inventory.cpp
@@ -67,7 +67,7 @@ void Inventory::setItem(int index, int id, int quantity)
{
if (index < 0 || index >= getSize())
{
- logger->log("Warning: invalid inventory index: %d", index);
+ Log::warn("Invalid inventory index: %d", index);
return;
}
diff --git a/src/itemshortcut.cpp b/src/itemshortcut.cpp
index 643a594f..4d2cec8a 100644
--- a/src/itemshortcut.cpp
+++ b/src/itemshortcut.cpp
@@ -29,8 +29,7 @@
ItemShortcut *itemShortcut;
-ItemShortcut::ItemShortcut():
- mItemSelected(-1)
+ItemShortcut::ItemShortcut()
{
load();
}
@@ -42,8 +41,8 @@ ItemShortcut::~ItemShortcut()
void ItemShortcut::load()
{
- for (int i = 0; i < SHORTCUT_ITEMS; i++)
- mItems[i] = -1;
+ for (int &item : mItems)
+ item = -1;
for (auto &shortcut : config.itemShortcuts)
{
diff --git a/src/itemshortcut.h b/src/itemshortcut.h
index fcec3d7d..0849a8d1 100644
--- a/src/itemshortcut.h
+++ b/src/itemshortcut.h
@@ -109,7 +109,7 @@ class ItemShortcut
void save();
int mItems[SHORTCUT_ITEMS]; /**< The items stored. */
- int mItemSelected; /**< The item held by cursor. */
+ int mItemSelected = -1; /**< The item held by cursor. */
};
diff --git a/src/joystick.cpp b/src/joystick.cpp
index c6e106bf..070e9dcf 100644
--- a/src/joystick.cpp
+++ b/src/joystick.cpp
@@ -36,9 +36,9 @@ void Joystick::init()
SDL_JoystickEventState(SDL_ENABLE);
joystickCount = SDL_NumJoysticks();
- logger->log("%i joysticks/gamepads found", joystickCount);
+ Log::info("%i joysticks/gamepads found", joystickCount);
for (int i = 0; i < joystickCount; i++)
- logger->log("- %s", SDL_JoystickNameForIndex(i));
+ Log::info("- %s", SDL_JoystickNameForIndex(i));
}
Joystick::Joystick(int no)
@@ -57,14 +57,14 @@ Joystick::Joystick(int no)
// TODO Bail out!
if (!mJoystick)
{
- logger->log("Couldn't open joystick: %s", SDL_GetError());
+ Log::info("Couldn't open joystick: %s", SDL_GetError());
return;
}
- logger->log("Axes: %i ", SDL_JoystickNumAxes(mJoystick));
- logger->log("Balls: %i", SDL_JoystickNumBalls(mJoystick));
- logger->log("Hats: %i", SDL_JoystickNumHats(mJoystick));
- logger->log("Buttons: %i", SDL_JoystickNumButtons(mJoystick));
+ Log::info("Axes: %i ", SDL_JoystickNumAxes(mJoystick));
+ Log::info("Balls: %i", SDL_JoystickNumBalls(mJoystick));
+ Log::info("Hats: %i", SDL_JoystickNumHats(mJoystick));
+ Log::info("Buttons: %i", SDL_JoystickNumButtons(mJoystick));
}
Joystick::~Joystick()
diff --git a/src/keyboardconfig.cpp b/src/keyboardconfig.cpp
index ec5b5e37..92d37ace 100644
--- a/src/keyboardconfig.cpp
+++ b/src/keyboardconfig.cpp
@@ -71,6 +71,7 @@ static KeyData const keyData[KeyboardConfig::KEY_TOTAL] = {
{ "WindowInventory", SDLK_F3, _("Inventory Window") },
{ "WindowEquipment", SDLK_F4, _("Equipment Window") },
{ "WindowSkill", SDLK_F5, _("Skill Window") },
+ { "WindowQuests", KeyboardConfig::KEY_NO_VALUE, _("Quest Window") },
{ "WindowMinimap", SDLK_F6, _("Minimap Window") },
{ "WindowChat", SDLK_F7, _("Chat Window") },
{ "WindowShortcut", SDLK_F8, _("Item Shortcut Window") },
diff --git a/src/keyboardconfig.h b/src/keyboardconfig.h
index 6fc79ced..be643b9b 100644
--- a/src/keyboardconfig.h
+++ b/src/keyboardconfig.h
@@ -32,9 +32,9 @@
*/
struct KeyFunction
{
- const char* configField; /** Field index that is in the config file. */
- int defaultValue; /** The default key value used. */
- SDL_Keycode value; /** The actual value that is used. */
+ const char* configField; /**< Field index that is in the config file. */
+ int defaultValue; /**< The default key value used. */
+ SDL_Keycode value; /**< The actual value that is used. */
};
class Setup_Keyboard;
@@ -191,6 +191,7 @@ class KeyboardConfig
KEY_WINDOW_INVENTORY,
KEY_WINDOW_EQUIPMENT,
KEY_WINDOW_SKILL,
+ KEY_WINDOW_QUESTS,
KEY_WINDOW_MINIMAP,
KEY_WINDOW_CHAT,
KEY_WINDOW_SHORTCUT,
diff --git a/src/log.cpp b/src/log.cpp
index 24cb6e9d..eeb26b00 100644
--- a/src/log.cpp
+++ b/src/log.cpp
@@ -1,7 +1,7 @@
/*
* The Mana Client
* Copyright (C) 2004-2009 The Mana World Development Team
- * Copyright (C) 2009-2012 The Mana Developers
+ * Copyright (C) 2009-2025 The Mana Developers
*
* This file is part of The Mana Client.
*
@@ -23,47 +23,36 @@
#include <SDL.h>
-#include <sys/time.h>
+#include <cstdarg>
+#include <fstream>
#include <iostream>
#include <sstream>
-#include <cstdarg>
-#include <cstdio>
+#include <sys/time.h>
-Logger::Logger() = default;
-Logger::~Logger() = default;
+static std::ofstream logFile;
+static bool logToStandardOut = true;
-void Logger::setLogFile(const std::string &logFilename)
+static const char *getLogPriorityPrefix(SDL_LogPriority priority)
{
- mLogFile.open(logFilename, std::ios_base::trunc);
-
- if (!mLogFile.is_open())
- {
- std::cout << "Warning: error while opening " << logFilename <<
- " for writing.\n";
+ switch (priority) {
+ case SDL_LOG_PRIORITY_WARN:
+ return "Warning: ";
+ case SDL_LOG_PRIORITY_ERROR:
+ return "Error: ";
+ case SDL_LOG_PRIORITY_CRITICAL:
+ return "Critical Error: ";
+ default:
+ return "";
}
}
-void Logger::log(const char *log_text, ...)
-{
- va_list ap;
- va_start(ap, log_text);
- vlog(log_text, ap);
- va_end(ap);
-}
-
-void Logger::vlog(const char *log_text, va_list ap)
+static void logOutputFunction(void *userdata, int category, SDL_LogPriority priority, const char *message)
{
- const size_t bufSize = 1024;
- char* buf = new char[bufSize];
-
- // Use a temporary buffer to fill in the variables
- vsnprintf(buf, bufSize, log_text, ap);
-
// Get the current system time
timeval tv;
gettimeofday(&tv, nullptr);
- // Print the log entry
+ // Create timestamp string
std::stringstream timeStr;
timeStr << "["
<< ((((tv.tv_sec / 60) / 60) % 24 < 10) ? "0" : "")
@@ -79,24 +68,71 @@ void Logger::vlog(const char *log_text, va_list ap)
<< (int)((tv.tv_usec / 10000) % 100)
<< "] ";
- if (mLogFile.is_open())
+ const char *prefix = getLogPriorityPrefix(priority);
+
+ if (logToStandardOut)
+ {
+ std::cout << timeStr.str() << prefix << message << std::endl;
+ }
+
+ if (logFile.is_open())
{
- mLogFile << timeStr.str() << buf << std::endl;
+ logFile << timeStr.str() << prefix << message << std::endl;
}
+}
+
+void Log::init()
+{
+ SDL_LogSetOutputFunction(logOutputFunction, nullptr);
+}
- if (mLogToStandardOut)
+void Log::setLogFile(const std::string &logFilename)
+{
+ logFile.open(logFilename, std::ios_base::trunc);
+
+ if (!logFile.is_open())
{
- std::cout << timeStr.str() << buf << std::endl;
+ std::cout << "Warning: error while opening " << logFilename
+ << " for writing.\n";
}
+}
- // Delete temporary buffer
- delete[] buf;
+void Log::setLogToStandardOut(bool value)
+{
+ logToStandardOut = value;
}
-void Logger::error(const std::string &error_text)
+#define DEFINE_LOG_FUNCTION(name, priority) \
+ void Log::name(const char *fmt, ...) \
+ { \
+ va_list ap; \
+ va_start(ap, fmt); \
+ SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, priority, fmt, ap); \
+ va_end(ap); \
+ }
+
+DEFINE_LOG_FUNCTION(verbose, SDL_LOG_PRIORITY_VERBOSE)
+DEFINE_LOG_FUNCTION(debug, SDL_LOG_PRIORITY_DEBUG)
+DEFINE_LOG_FUNCTION(info, SDL_LOG_PRIORITY_INFO)
+DEFINE_LOG_FUNCTION(warn, SDL_LOG_PRIORITY_WARN)
+DEFINE_LOG_FUNCTION(error, SDL_LOG_PRIORITY_ERROR)
+
+#undef DEFINE_LOG_FUNCTION
+
+void Log::vinfo(const char *fmt, va_list ap)
+{
+ SDL_LogMessageV(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO, fmt, ap);
+}
+
+void Log::critical(const std::string &message)
{
- log("Error: %s", error_text.c_str());
- std::cerr << "Error: " << error_text << std::endl;
- SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", error_text.c_str(), nullptr);
+ SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "%s", message.c_str());
+
+ if (!logToStandardOut)
+ {
+ std::cerr << getLogPriorityPrefix(SDL_LOG_PRIORITY_CRITICAL) << message << std::endl;
+ }
+
+ SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", message.c_str(), nullptr);
exit(1);
}
diff --git a/src/log.h b/src/log.h
index f4f78fa1..1457bb3a 100644
--- a/src/log.h
+++ b/src/log.h
@@ -1,7 +1,7 @@
/*
* The Mana Client
* Copyright (C) 2004-2009 The Mana World Development Team
- * Copyright (C) 2009-2012 The Mana Developers
+ * Copyright (C) 2009-2025 The Mana Developers
*
* This file is part of The Mana Client.
*
@@ -21,52 +21,51 @@
#pragma once
-#include <fstream>
+#include <string>
+#include <cstdarg>
+
+#ifdef __GNUC__
+# define LOG_PRINTF_ATTR __attribute__((__format__(__printf__, 1, 2)))
+#else
+# define LOG_PRINTF_ATTR
+#endif
/**
- * The Log Class : Useful to write debug or info messages
+ * The Log namespace: Useful to write debug or info messages to the log file
+ * and/or console. The messages will be timestamped.
*/
-class Logger
+namespace Log
{
- public:
- Logger();
+ /**
+ * Initializes the log system.
+ */
+ void init();
- /**
- * Destructor, closes log file.
- */
- ~Logger();
+ /**
+ * Sets the file to log to and opens it.
+ */
+ void setLogFile(const std::string &logFilename);
- /**
- * Sets the file to log to and opens it
- */
- void setLogFile(const std::string &logFilename);
+ /**
+ * Sets whether the log should be written to standard output.
+ */
+ void setLogToStandardOut(bool value);
- /**
- * Sets whether the log should be written to standard output.
- */
- void setLogToStandardOut(bool value) { mLogToStandardOut = value; }
-
- /**
- * Enters a message in the log. The message will be timestamped.
- */
- void log(const char *log_text, ...)
-#ifdef __GNUC__
- __attribute__((__format__(__printf__, 2, 3)))
-#endif
- ;
+ void verbose(const char *log_text, ...) LOG_PRINTF_ATTR;
+ void debug(const char *log_text, ...) LOG_PRINTF_ATTR;
+ void info(const char *log_text, ...) LOG_PRINTF_ATTR;
+ void warn(const char *log_text, ...) LOG_PRINTF_ATTR;
+ void error(const char *log_text, ...) LOG_PRINTF_ATTR;
- void vlog(const char *log_text, va_list ap);
+ void vinfo(const char *log_text, va_list ap);
- /**
- * Log an error and quit. The error will be printed to standard error
- * and showm in a simple message box.
- */
- __attribute__((noreturn))
- void error(const std::string &error_text);
+ /**
+ * Log an error and quit. The error will be printed to standard error
+ * and shown in a simple message box.
+ */
+ __attribute__((noreturn))
+ void critical(const std::string &error_text);
- private:
- std::ofstream mLogFile;
- bool mLogToStandardOut = true;
-};
+} // namespace Log
-extern Logger *logger;
+#undef LOG_PRINTF_ATTR
diff --git a/src/map.cpp b/src/map.cpp
index 908d6178..8dbc7c28 100644
--- a/src/map.cpp
+++ b/src/map.cpp
@@ -983,7 +983,7 @@ void Map::addAnimation(int gid, TileAnimation animation)
auto const [_, inserted] = mTileAnimations.try_emplace(gid, std::move(animation));
if (!inserted)
{
- logger->error(strprintf("Duplicate tile animation for gid %d", gid));
+ Log::warn("Duplicate tile animation for gid %d", gid);
}
}
diff --git a/src/net/download.cpp b/src/net/download.cpp
index 7aab3b2f..571af7a4 100644
--- a/src/net/download.cpp
+++ b/src/net/download.cpp
@@ -108,13 +108,13 @@ bool Download::start()
{
assert(!mThread); // Download already started
- logger->log("Starting download: %s", mUrl.c_str());
+ Log::info("Starting download: %s", mUrl.c_str());
mThread = SDL_CreateThread(downloadThread, "Download", this);
if (!mThread)
{
- logger->log("%s", DOWNLOAD_ERROR_MESSAGE_THREAD);
+ Log::info("%s", DOWNLOAD_ERROR_MESSAGE_THREAD);
strncpy(mError, DOWNLOAD_ERROR_MESSAGE_THREAD, CURL_ERROR_SIZE - 1);
mState.lock()->status = DownloadStatus::Error;
return false;
@@ -125,7 +125,7 @@ bool Download::start()
void Download::cancel()
{
- logger->log("Canceling download: %s", mUrl.c_str());
+ Log::info("Canceling download: %s", mUrl.c_str());
mCancel = true;
}
@@ -186,7 +186,7 @@ int Download::downloadThread(void *ptr)
if (!curl)
break;
- logger->log("Downloading: %s", d->mUrl.c_str());
+ Log::info("Downloading: %s", d->mUrl.c_str());
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, d->mHeaders);
@@ -236,8 +236,8 @@ int Download::downloadThread(void *ptr)
if (res != CURLE_OK)
{
- logger->log("curl error %d: %s host: %s",
- res, d->mError, d->mUrl.c_str());
+ Log::info("curl error %d: %s host: %s",
+ res, d->mError, d->mUrl.c_str());
if (file)
{
@@ -262,9 +262,9 @@ int Download::downloadThread(void *ptr)
// Remove the corrupted file
::remove(outFilename.c_str());
- logger->log("Checksum for file %s failed: (%lx/%lx)",
- d->mFileName.c_str(),
- adler, *d->mAdler);
+ Log::info("Checksum for file %s failed: (%lx/%lx)",
+ d->mFileName.c_str(),
+ adler, *d->mAdler);
continue; // Bail out here to avoid the renaming
}
diff --git a/src/net/manaserv/charhandler.cpp b/src/net/manaserv/charhandler.cpp
index 98591669..cc0ea17e 100644
--- a/src/net/manaserv/charhandler.cpp
+++ b/src/net/manaserv/charhandler.cpp
@@ -264,10 +264,10 @@ void CharHandler::handleCharacterSelectResponse(MessageIn &msg)
chatServer.hostname.assign(msg.readString());
chatServer.port = msg.readInt16();
- logger->log("Game server: %s:%d", gameServer.hostname.c_str(),
- gameServer.port);
- logger->log("Chat server: %s:%d", chatServer.hostname.c_str(),
- chatServer.port);
+ Log::info("Game server: %s:%d", gameServer.hostname.c_str(),
+ gameServer.port);
+ Log::info("Chat server: %s:%d", chatServer.hostname.c_str(),
+ chatServer.port);
// Prevent the selected local player from being deleted
local_player = mSelectedCharacter->dummy;
diff --git a/src/net/manaserv/chathandler.cpp b/src/net/manaserv/chathandler.cpp
index dca556c2..436da820 100644
--- a/src/net/manaserv/chathandler.cpp
+++ b/src/net/manaserv/chathandler.cpp
@@ -158,8 +158,8 @@ void ChatHandler::handleGameChatMessage(MessageIn &msg)
if (!being)
{
- logger->log("Warning: Received GPMSG_SAY for unknown being with id %i."
- " (Message is: %s)", id, chatMsg.c_str());
+ Log::warn("Received GPMSG_SAY for unknown being with id %i."
+ " (Message is: %s)", id, chatMsg.c_str());
return;
}
@@ -262,7 +262,7 @@ void ChatHandler::handleChatMessage(MessageIn &msg)
else
{
// Can't find channel
- logger->log("Couldn't find chat channel id: %hi", channelId);
+ Log::info("Couldn't find chat channel id: %hi", channelId);
}
}
diff --git a/src/net/manaserv/connection.cpp b/src/net/manaserv/connection.cpp
index 1b6f757a..2eb5b4bf 100644
--- a/src/net/manaserv/connection.cpp
+++ b/src/net/manaserv/connection.cpp
@@ -44,13 +44,13 @@ Connection::~Connection()
bool Connection::connect(const std::string &address, enet_uint16 port)
{
- logger->log("Net::Connection::connect(%s, %i)", address.c_str(), port);
+ Log::info("Net::Connection::connect(%s, %i)", address.c_str(), port);
if (mConnection)
disconnect();
if (address.empty())
{
- logger->log("Net::Connection::connect() got empty address!");
+ Log::info("Net::Connection::connect() got empty address!");
mState = NET_ERROR;
return false;
}
@@ -65,7 +65,7 @@ bool Connection::connect(const std::string &address, enet_uint16 port)
if (!mConnection)
{
- logger->log("Unable to initiate connection to the server.");
+ Log::info("Unable to initiate connection to the server.");
mState = NET_ERROR;
return false;
}
@@ -96,7 +96,7 @@ void Connection::send(const ManaServ::MessageOut &msg)
{
if (!isConnected())
{
- logger->log("Warning: cannot send message to not connected server!");
+ Log::warn("Cannot send message to not connected server!");
return;
}
diff --git a/src/net/manaserv/effecthandler.cpp b/src/net/manaserv/effecthandler.cpp
index 22d1f9cf..afd7cd5c 100644
--- a/src/net/manaserv/effecthandler.cpp
+++ b/src/net/manaserv/effecthandler.cpp
@@ -82,7 +82,7 @@ void EffectHandler::handleCreateEffectBeing(MessageIn &msg)
if (b)
effectManager->trigger(eid, b);
else
- logger->log("Warning: CreateEffect called for unknown being #%d", bid);
+ Log::warn("CreateEffect called for unknown being #%d", bid);
}
void EffectHandler::handleCreateTextParticle(MessageIn &msg)
@@ -120,7 +120,7 @@ void EffectHandler::handleShake(MessageIn &msg)
viewport->shakeScreen(intensityX, intensityY, decay, duration);
break;
default:
- logger->log("Warning: Received GPMSG_SHAKE message with unexpected length of %d bytes", msg.getUnreadLength());
+ Log::warn("Received GPMSG_SHAKE message with unexpected length of %d bytes", msg.getUnreadLength());
}
}
diff --git a/src/net/manaserv/guildhandler.cpp b/src/net/manaserv/guildhandler.cpp
index 9fdbafc2..bae38c37 100644
--- a/src/net/manaserv/guildhandler.cpp
+++ b/src/net/manaserv/guildhandler.cpp
@@ -73,7 +73,7 @@ void GuildHandler::handleMessage(MessageIn &msg)
{
case CPMSG_GUILD_CREATE_RESPONSE:
{
- logger->log("Received CPMSG_GUILD_CREATE_RESPONSE");
+ Log::info("Received CPMSG_GUILD_CREATE_RESPONSE");
if (msg.readInt8() == ERRMSG_OK)
{
// TODO - Acknowledge guild was created
@@ -88,7 +88,7 @@ void GuildHandler::handleMessage(MessageIn &msg)
case CPMSG_GUILD_INVITE_RESPONSE:
{
- logger->log("Received CPMSG_GUILD_INVITE_RESPONSE");
+ Log::info("Received CPMSG_GUILD_INVITE_RESPONSE");
const unsigned char response = msg.readInt8();
if (response == ERRMSG_OK)
{
@@ -111,7 +111,7 @@ void GuildHandler::handleMessage(MessageIn &msg)
case CPMSG_GUILD_ACCEPT_RESPONSE:
{
- logger->log("Received CPMSG_GUILD_ACCEPT_RESPONSE");
+ Log::info("Received CPMSG_GUILD_ACCEPT_RESPONSE");
if (msg.readInt8() == ERRMSG_OK)
{
// TODO - Acknowledge accepted into guild
@@ -121,7 +121,7 @@ void GuildHandler::handleMessage(MessageIn &msg)
case CPMSG_GUILD_GET_MEMBERS_RESPONSE:
{
- logger->log("Received CPMSG_GUILD_GET_MEMBERS_RESPONSE");
+ Log::info("Received CPMSG_GUILD_GET_MEMBERS_RESPONSE");
if (msg.readInt8() == ERRMSG_OK)
{
std::string name;
@@ -152,7 +152,7 @@ void GuildHandler::handleMessage(MessageIn &msg)
case CPMSG_GUILD_UPDATE_LIST:
{
- logger->log("Received CPMSG_GUILD_UPDATE_LIST");
+ Log::info("Received CPMSG_GUILD_UPDATE_LIST");
short guildId = msg.readInt16();
std::string name = msg.readString();
char eventId = msg.readInt8();
@@ -189,14 +189,14 @@ void GuildHandler::handleMessage(MessageIn &msg)
break;
default:
- logger->log("Invalid guild event");
+ Log::info("Invalid guild event");
}
}
} break;
case CPMSG_GUILD_INVITED:
{
- logger->log("Received CPMSG_GUILD_INVITED");
+ Log::info("Received CPMSG_GUILD_INVITED");
std::string inviterName = msg.readString();
std::string guildName = msg.readString();
int guildId = msg.readInt16();
@@ -207,7 +207,7 @@ void GuildHandler::handleMessage(MessageIn &msg)
case CPMSG_GUILD_PROMOTE_MEMBER_RESPONSE:
{
- logger->log("Received CPMSG_GUILD_PROMOTE_MEMBER_RESPONSE");
+ Log::info("Received CPMSG_GUILD_PROMOTE_MEMBER_RESPONSE");
if (msg.readInt8() == ERRMSG_OK)
{
@@ -223,14 +223,14 @@ void GuildHandler::handleMessage(MessageIn &msg)
case CPMSG_GUILD_REJOIN:
{
- logger->log("Received CPMSG_GUILD_REJOIN");
+ Log::info("Received CPMSG_GUILD_REJOIN");
joinedGuild(msg);
} break;
case CPMSG_GUILD_QUIT_RESPONSE:
{
- logger->log("Received CPMSG_GUILD_QUIT_RESPONSE");
+ Log::info("Received CPMSG_GUILD_QUIT_RESPONSE");
if (msg.readInt8() == ERRMSG_OK)
{
@@ -247,7 +247,7 @@ void GuildHandler::handleMessage(MessageIn &msg)
} break;
case CPMSG_GUILD_KICK_NOTIFICATION:
{
- logger->log("Received CPMSG_GUILD_KICK_NOTIFICATION");
+ Log::info("Received CPMSG_GUILD_KICK_NOTIFICATION");
const int guildId = msg.readInt16();
std::string player = msg.readString();
diff --git a/src/net/manaserv/inventoryhandler.cpp b/src/net/manaserv/inventoryhandler.cpp
index 58a495af..fa31b32a 100644
--- a/src/net/manaserv/inventoryhandler.cpp
+++ b/src/net/manaserv/inventoryhandler.cpp
@@ -88,9 +88,8 @@ void EquipBackend::equip(int inventorySlot, int equipmentSlot)
auto slotIt = mSlots.find(equipmentSlot);
if (slotIt == mSlots.end())
{
- logger->log("ManaServ::EquipBackend: Equipment slot %i"
- " is not existing.",
- equipmentSlot);
+ Log::info("ManaServ::EquipBackend: Equipment slot %i"
+ " is not existing.", equipmentSlot);
return;
}
@@ -115,8 +114,8 @@ void EquipBackend::unequip(int inventorySlot)
}
}
- logger->log("ManaServ::EquipBackend: No equipped item found at inventory "
- "slot %i!", inventorySlot);
+ Log::info("ManaServ::EquipBackend: No equipped item found at inventory "
+ "slot %i!", inventorySlot);
}
void EquipBackend::event(Event::Channel, const Event &event)
@@ -134,8 +133,8 @@ void EquipBackend::readEquipFile()
if (!rootNode || rootNode.name() != "equip-slots")
{
- logger->log("ManaServ::EquipBackend: Error while reading "
- EQUIP_FILE "!");
+ Log::info("ManaServ::EquipBackend: Error while reading "
+ EQUIP_FILE "!");
return;
}
diff --git a/src/net/manaserv/loginhandler.cpp b/src/net/manaserv/loginhandler.cpp
index 1c398990..9828af29 100644
--- a/src/net/manaserv/loginhandler.cpp
+++ b/src/net/manaserv/loginhandler.cpp
@@ -342,7 +342,7 @@ void LoginHandler::readServerInfo(MessageIn &msg)
if (!updateHost.empty())
mLoginData->updateHost = updateHost;
else
- logger->log("Warning: server does not have an update host set!");
+ Log::warn("Server does not have an update host set!");
// Read the client data folder for dynamic data loading.
// This is only used by the Qt client.
diff --git a/src/net/manaserv/network.cpp b/src/net/manaserv/network.cpp
index d69d3397..cb9f76a0 100644
--- a/src/net/manaserv/network.cpp
+++ b/src/net/manaserv/network.cpp
@@ -48,14 +48,14 @@ void initialize()
{
if (enet_initialize())
{
- logger->error("Failed to initialize ENet.");
+ Log::critical("Failed to initialize ENet.");
}
client = enet_host_create(nullptr, 3, 0, 0, 0);
if (!client)
{
- logger->error("Failed to create the local host.");
+ Log::critical("Failed to create the local host.");
}
}
@@ -66,7 +66,7 @@ void finalize()
if (connections)
{
- logger->error("Tried to shutdown the network subsystem while there "
+ Log::critical("Tried to shutdown the network subsystem while there "
"are network connections left!");
}
@@ -78,7 +78,7 @@ Connection *getConnection()
{
if (!client)
{
- logger->error("Tried to instantiate a network object before "
+ Log::critical("Tried to instantiate a network object before "
"initializing the network subsystem!");
}
@@ -117,14 +117,14 @@ namespace
if (iter != mMessageHandlers.end())
{
- //logger->log("Received packet %x (%i B)",
- // msg.getId(), msg.getLength());
+ //Log::info("Received packet %x (%i B)",
+ // msg.getId(), msg.getLength());
iter->second->handleMessage(msg);
}
else
{
- logger->log("Unhandled packet %x (%i B)",
- msg.getId(), msg.getLength());
+ Log::info("Unhandled packet %x (%i B)",
+ msg.getId(), msg.getLength());
}
// Clean up the packet now that we're done using it.
@@ -142,7 +142,7 @@ void flush()
switch (event.type)
{
case ENET_EVENT_TYPE_CONNECT:
- logger->log("Connected to port %d.", event.peer->address.port);
+ Log::info("Connected to port %d.", event.peer->address.port);
// Store any relevant server information here.
event.peer->data = nullptr;
break;
@@ -152,7 +152,7 @@ void flush()
break;
case ENET_EVENT_TYPE_DISCONNECT:
- logger->log("Disconnected.");
+ Log::info("Disconnected.");
// Reset the server information.
event.peer->data = nullptr;
break;
diff --git a/src/net/manaserv/partyhandler.cpp b/src/net/manaserv/partyhandler.cpp
index e2a021cd..23ea7aa5 100644
--- a/src/net/manaserv/partyhandler.cpp
+++ b/src/net/manaserv/partyhandler.cpp
@@ -103,7 +103,7 @@ void PartyHandler::handleMessage(MessageIn &msg)
"inviter has left the game."));
break;
default:
- logger->log("Unknown CPMSG_PARTY_INVITE_ANSWER_RESPONSE.");
+ Log::info("Unknown CPMSG_PARTY_INVITE_ANSWER_RESPONSE.");
break;
}
} break;
@@ -161,7 +161,7 @@ void PartyHandler::handleMessage(MessageIn &msg)
name.c_str()));
break;
default:
- logger->log("Unknown CPMSG_PARTY_REJECTED.");
+ Log::info("Unknown CPMSG_PARTY_REJECTED.");
break;
}
} break;
diff --git a/src/net/manaserv/playerhandler.cpp b/src/net/manaserv/playerhandler.cpp
index 8ee9ed80..edae6be6 100644
--- a/src/net/manaserv/playerhandler.cpp
+++ b/src/net/manaserv/playerhandler.cpp
@@ -96,7 +96,7 @@ void PlayerHandler::handleMessage(MessageIn &msg)
netToken = msg.readString(32);
std::string address = msg.readString();
int port = msg.readInt16();
- logger->log("Changing server to %s:%d", address.c_str(), port);
+ Log::info("Changing server to %s:%d", address.c_str(), port);
gameServer.hostname = address;
gameServer.port = port;
@@ -147,14 +147,14 @@ void PlayerHandler::handleMessage(MessageIn &msg)
} break;
case ATTRIBMOD_INVALID_ATTRIBUTE:
{
- logger->log("Warning: Server denied increase of attribute %d (unknown attribute) ", attrNum);
+ Log::warn("Server denied increase of attribute %d (unknown attribute) ", attrNum);
} break;
case ATTRIBMOD_NO_POINTS_LEFT:
{
// when the server says "you got no points" it
// has to be correct. The server is always right!
// undo attribute change and set points to 0
- logger->log("Warning: Server denied increase of attribute %d (no points left) ", attrNum);
+ Log::warn("Server denied increase of attribute %d (no points left) ", attrNum);
int attrValue = PlayerInfo::getStatBase(attrNum) - 1;
PlayerInfo::setAttribute(CHAR_POINTS, 0);
PlayerInfo::setStatBase(attrNum, attrValue);
@@ -162,7 +162,7 @@ void PlayerHandler::handleMessage(MessageIn &msg)
case ATTRIBMOD_DENIED:
{
// undo attribute change
- logger->log("Warning: Server denied increase of attribute %d (reason unknown) ", attrNum);
+ Log::warn("Server denied increase of attribute %d (reason unknown) ", attrNum);
int points = PlayerInfo::getAttribute(CHAR_POINTS) - 1;
PlayerInfo::setAttribute(CHAR_POINTS, points);
@@ -184,14 +184,14 @@ void PlayerHandler::handleMessage(MessageIn &msg)
} break;
case ATTRIBMOD_INVALID_ATTRIBUTE:
{
- logger->log("Warning: Server denied reduction of attribute %d (unknown attribute) ", attrNum);
+ Log::warn("Server denied reduction of attribute %d (unknown attribute) ", attrNum);
} break;
case ATTRIBMOD_NO_POINTS_LEFT:
{
// when the server says "you got no points" it
// has to be correct. The server is always right!
// undo attribute change and set points to 0
- logger->log("Warning: Server denied reduction of attribute %d (no points left) ", attrNum);
+ Log::warn("Server denied reduction of attribute %d (no points left) ", attrNum);
int attrValue = PlayerInfo::getStatBase(attrNum) + 1;
// TODO are these right?
PlayerInfo::setAttribute(CHAR_POINTS, 0);
@@ -201,7 +201,7 @@ void PlayerHandler::handleMessage(MessageIn &msg)
case ATTRIBMOD_DENIED:
{
// undo attribute change
- logger->log("Warning: Server denied reduction of attribute %d (reason unknown) ", attrNum);
+ Log::warn("Server denied reduction of attribute %d (reason unknown) ", attrNum);
int charaPoints = PlayerInfo::getAttribute(CHAR_POINTS) - 1;
PlayerInfo::setAttribute(CHAR_POINTS, charaPoints);
@@ -245,7 +245,7 @@ void PlayerHandler::handleMessage(MessageIn &msg)
BY_SERVER);
break;
default:
- logger->log("0x013b: Unhandled message %i", type);
+ Log::info("0x013b: Unhandled message %i", type);
break;
}
}
@@ -263,7 +263,7 @@ void PlayerHandler::handleMapChangeMessage(MessageIn &msg)
Game *game = Game::instance();
const bool sameMap = (game->getCurrentMapName() == mapName);
- logger->log("Changing map to %s (%d, %d)", mapName.c_str(), x, y);
+ Log::info("Changing map to %s (%d, %d)", mapName.c_str(), x, y);
// Switch the actual map, deleting the previous one
game->changeMap(mapName);
@@ -285,8 +285,8 @@ void PlayerHandler::handleMapChangeMessage(MessageIn &msg)
local_player->setPosition(x, y);
local_player->setDestination(x, y);
- logger->log("Adjust scrolling by %d,%d", (int) scrollOffsetX,
- (int) scrollOffsetY);
+ Log::info("Adjust scrolling by %d,%d", (int) scrollOffsetX,
+ (int) scrollOffsetY);
viewport->scrollBy(scrollOffsetX, scrollOffsetY);
}
@@ -295,7 +295,7 @@ void PlayerHandler::attack(int id)
auto ability = AbilityDB::find("Strike");
if (!ability)
{
- logger->log("PlayerHandler::attack: 'Strike' ability not found.");
+ Log::info("PlayerHandler::attack: 'Strike' ability not found.");
return;
}
@@ -304,7 +304,7 @@ void PlayerHandler::attack(int id)
abilityHandler->useOn(ability->id, id);
break;
case AbilityInfo::TARGET_POINT:
- logger->log("PlayerHandler::attack: Unsupported target mode 'point' for 'Strike' ability.");
+ Log::info("PlayerHandler::attack: Unsupported target mode 'point' for 'Strike' ability.");
break;
case AbilityInfo::TARGET_DIRECTION:
abilityHandler->useInDirection(ability->id, local_player->getDirection());
@@ -420,8 +420,8 @@ Vector PlayerHandler::getPixelsPerSecondMoveSpeed(const Vector &speed, Map *map)
if (!map)
{
- logger->log("Manaserv::PlayerHandler: Speed wasn't given back"
- " because Map not initialized.");
+ Log::info("Manaserv::PlayerHandler: Speed wasn't given back"
+ " because Map not initialized.");
return speedInPixels;
}
diff --git a/src/net/net.cpp b/src/net/net.cpp
index 1d157b3d..443a739c 100644
--- a/src/net/net.cpp
+++ b/src/net/net.cpp
@@ -138,7 +138,7 @@ void connectToServer(ServerInfo &server)
else if (server.port == 9601)
server.type = ServerType::ManaServ;
else
- logger->error(_("Unknown Server Type! Exiting."));
+ Log::critical(_("Unknown Server Type! Exiting."));
}
if (networkType == server.type && getGeneralHandler() != nullptr)
@@ -160,7 +160,7 @@ void connectToServer(ServerInfo &server)
generalHandler = new TmwAthena::GeneralHandler;
break;
default:
- logger->error(_("Server protocol unsupported"));
+ Log::critical(_("Server protocol unsupported"));
break;
}
@@ -203,4 +203,3 @@ ServerType getNetworkType()
}
} // namespace Net
-
diff --git a/src/net/playerhandler.h b/src/net/playerhandler.h
index b9cf1abf..e5b86b2e 100644
--- a/src/net/playerhandler.h
+++ b/src/net/playerhandler.h
@@ -24,6 +24,8 @@
#include "being.h"
#include "flooritem.h"
+#include "resources/questdb.h"
+
namespace Net {
class PlayerHandler
@@ -80,6 +82,11 @@ class PlayerHandler
* Return false when tiles-center positions only are to be used.
*/
virtual bool usePixelPrecision() = 0;
+
+ const QuestVars &getQuestVars() const { return mQuestVars; }
+
+ protected:
+ QuestVars mQuestVars;
};
} // namespace Net
diff --git a/src/net/tmwa/abilityhandler.cpp b/src/net/tmwa/abilityhandler.cpp
index ab891b40..fea492ef 100644
--- a/src/net/tmwa/abilityhandler.cpp
+++ b/src/net/tmwa/abilityhandler.cpp
@@ -129,7 +129,7 @@ void AbilityHandler::handleMessage(MessageIn &msg)
auto type = msg.readInt8();
if (btype == BSKILL_EMOTE)
{
- logger->log("Action: %d", btype);
+ Log::info("Action: %d", btype);
}
std::string msg;
diff --git a/src/net/tmwa/beinghandler.cpp b/src/net/tmwa/beinghandler.cpp
index c5979e9f..690b0d87 100644
--- a/src/net/tmwa/beinghandler.cpp
+++ b/src/net/tmwa/beinghandler.cpp
@@ -33,9 +33,9 @@
#include "playerrelations.h"
#include "net/net.h"
-#include "net/playerhandler.h"
#include "net/tmwa/messagein.h"
#include "net/tmwa/messageout.h"
+#include "net/tmwa/playerhandler.h"
#include "net/tmwa/protocol.h"
#include "resources/emotedb.h"
@@ -107,6 +107,12 @@ static Being *createBeing(int id, short job)
outMsg.writeInt32(id);
}
+ if (type == ActorSprite::NPC)
+ {
+ auto playerHandler = static_cast<TmwAthena::PlayerHandler*>(Net::getPlayerHandler());
+ playerHandler->applyQuestStatusEffects(being);
+ }
+
return being;
}
@@ -525,8 +531,8 @@ void BeingHandler::handleMessage(MessageIn &msg)
dstBeing->setSprite(SPRITE_MISC2, id);
break;
default:
- logger->log("SMSG_BEING_CHANGE_LOOKS2: unsupported type: "
- "%d, id: %d", static_cast<int>(type), id);
+ Log::info("SMSG_BEING_CHANGE_LOOKS2: unsupported type: "
+ "%d, id: %d", static_cast<int>(type), id);
break;
}
}
diff --git a/src/net/tmwa/buysellhandler.cpp b/src/net/tmwa/buysellhandler.cpp
index 1fdf1ffe..d7acd674 100644
--- a/src/net/tmwa/buysellhandler.cpp
+++ b/src/net/tmwa/buysellhandler.cpp
@@ -99,7 +99,7 @@ void BuySellHandler::handleMessage(MessageIn &msg)
Item *item = PlayerInfo::getInventory()->getItem(index);
- if (item && !(item->isEquipped()))
+ if (item && !item->isEquipped())
dialog->addItem(item, value);
}
}
diff --git a/src/net/tmwa/charserverhandler.cpp b/src/net/tmwa/charserverhandler.cpp
index 0ecbb135..fcd47b74 100644
--- a/src/net/tmwa/charserverhandler.cpp
+++ b/src/net/tmwa/charserverhandler.cpp
@@ -89,8 +89,8 @@ void CharServerHandler::handleMessage(MessageIn &msg)
auto *character = new Net::Character;
readPlayerData(msg, character);
mCharacters.push_back(character);
- logger->log("CharServer: Player: %s (%d)",
- character->dummy->getName().c_str(), character->slot);
+ Log::info("CharServer: Player: %s (%d)",
+ character->dummy->getName().c_str(), character->slot);
}
Client::setState(STATE_CHAR_SELECT);
diff --git a/src/net/tmwa/gamehandler.cpp b/src/net/tmwa/gamehandler.cpp
index 0a3bb9d9..a88e377e 100644
--- a/src/net/tmwa/gamehandler.cpp
+++ b/src/net/tmwa/gamehandler.cpp
@@ -68,8 +68,8 @@ void GameHandler::handleMessage(MessageIn &msg)
msg.readInt32(); // server tick
msg.readCoordinates(x, y, direction);
msg.skip(2); // unknown
- logger->log("Protocol: Player start position: (%d, %d), Direction: %d",
- x, y, direction);
+ Log::info("Protocol: Player start position: (%d, %d), Direction: %d",
+ x, y, direction);
// Switch now or we'll have problems
Client::setState(STATE_GAME);
// Stores the position until the map is loaded.
diff --git a/src/net/tmwa/generalhandler.cpp b/src/net/tmwa/generalhandler.cpp
index d6eb3b34..1f52c3db 100644
--- a/src/net/tmwa/generalhandler.cpp
+++ b/src/net/tmwa/generalhandler.cpp
@@ -117,7 +117,7 @@ void GeneralHandler::handleMessage(MessageIn &msg)
{
case SMSG_CONNECTION_PROBLEM:
code = msg.readInt8();
- logger->log("Connection problem: %i", code);
+ Log::info("Connection problem: %i", code);
switch (code)
{
diff --git a/src/net/tmwa/inventoryhandler.cpp b/src/net/tmwa/inventoryhandler.cpp
index 0d8e3005..73d967a6 100644
--- a/src/net/tmwa/inventoryhandler.cpp
+++ b/src/net/tmwa/inventoryhandler.cpp
@@ -157,10 +157,10 @@ void InventoryHandler::handleMessage(MessageIn &msg)
if (debugInventory)
{
- logger->log("Index: %d, ID: %d, Type: %d, Identified: %d, "
- "Qty: %d, Cards: %d, %d, %d, %d",
- index, itemId, itemType, identified, amount,
- cards[0], cards[1], cards[2], cards[3]);
+ Log::info("Index: %d, ID: %d, Type: %d, Identified: %d, "
+ "Qty: %d, Cards: %d, %d, %d, %d",
+ index, itemId, itemType, identified, amount,
+ cards[0], cards[1], cards[2], cards[3]);
}
if (msg.getId() == SMSG_PLAYER_INVENTORY)
@@ -191,10 +191,10 @@ void InventoryHandler::handleMessage(MessageIn &msg)
if (debugInventory)
{
- logger->log("Index: %d, ID: %d, Type: %d, Identified: %d, "
- "Qty: %d, Cards: %d, %d, %d, %d",
- index, itemId, itemType, identified, amount,
- cards[0], cards[1], cards[2], cards[3]);
+ Log::info("Index: %d, ID: %d, Type: %d, Identified: %d, "
+ "Qty: %d, Cards: %d, %d, %d, %d",
+ index, itemId, itemType, identified, amount,
+ cards[0], cards[1], cards[2], cards[3]);
}
mInventoryItems.push_back(
@@ -419,8 +419,8 @@ void InventoryHandler::handleMessage(MessageIn &msg)
}
else
{
- logger->log("Couldn't set attacke range due to the lack"
- "of an initialized map.");
+ Log::info("Couldn't set attacke range due to the lack"
+ "of an initialized map.");
local_player->setAttackRange(-1);
}
}
@@ -434,7 +434,7 @@ void InventoryHandler::handleMessage(MessageIn &msg)
index -= INVENTORY_OFFSET;
- logger->log("Arrows equipped: %i", index);
+ Log::info("Arrows equipped: %i", index);
mEquips.setEquipment(EQUIP_PROJECTILE_SLOT, index);
break;
}
diff --git a/src/net/tmwa/inventoryhandler.h b/src/net/tmwa/inventoryhandler.h
index f5ef4492..cde1235b 100644
--- a/src/net/tmwa/inventoryhandler.h
+++ b/src/net/tmwa/inventoryhandler.h
@@ -108,9 +108,9 @@ class EquipBackend final : public Equipment::Backend
if (!newItem && inventoryIndex >= 0)
{
- logger->log("EquipBackend: Warning, trying to equip "
- "non-existing item from inventory index %i at "
- "equipment slot %i.", inventoryIndex, index);
+ Log::info("EquipBackend: Warning, trying to equip "
+ "non-existing item from inventory index %i at "
+ "equipment slot %i.", inventoryIndex, index);
return;
}
diff --git a/src/net/tmwa/loginhandler.cpp b/src/net/tmwa/loginhandler.cpp
index a7162ee6..b6e3d518 100644
--- a/src/net/tmwa/loginhandler.cpp
+++ b/src/net/tmwa/loginhandler.cpp
@@ -108,7 +108,7 @@ void LoginHandler::handleMessage(MessageIn &msg)
mUpdateHost = msg.readString(len);
loginData.updateHost = mUpdateHost;
- logger->log("Received update host \"%s\" from login server.",
+ Log::info("Received update host \"%s\" from login server.",
mUpdateHost.c_str());
break;
}
@@ -138,10 +138,10 @@ void LoginHandler::handleMessage(MessageIn &msg)
msg.readInt16(); // maintenance
msg.readInt16(); // is_new
- logger->log("Network: Server: %s (%s:%d)",
- world->name.c_str(),
- ipToString(world->address),
- world->port);
+ Log::info("Network: Server: %s (%s:%d)",
+ world->name.c_str(),
+ ipToString(world->address),
+ world->port);
mWorlds.push_back(world);
}
@@ -150,7 +150,7 @@ void LoginHandler::handleMessage(MessageIn &msg)
case SMSG_LOGIN_ERROR:
code = msg.readInt8();
- logger->log("Login::error code: %i", code);
+ Log::info("Login::error code: %i", code);
switch (code)
{
@@ -212,9 +212,9 @@ void LoginHandler::handleMessage(MessageIn &msg)
mServerVersion = 0;
if (mServerVersion > 0)
- logger->log("TMW server version: x%06x", mServerVersion);
+ Log::info("TMW server version: x%06x", mServerVersion);
else
- logger->log("Server without version");
+ Log::info("Server without version");
mRegistrationEnabled = (options & FLAG_REGISTRATION);
diff --git a/src/net/tmwa/messagein.cpp b/src/net/tmwa/messagein.cpp
index c0db0fca..2630c511 100644
--- a/src/net/tmwa/messagein.cpp
+++ b/src/net/tmwa/messagein.cpp
@@ -26,9 +26,11 @@
#include <SDL_endian.h>
+#ifndef MAKEWORD
#define MAKEWORD(low,high) \
((unsigned short)(((unsigned char)(low)) | \
((unsigned short)((unsigned char)(high))) << 8))
+#endif
namespace TmwAthena {
diff --git a/src/net/tmwa/messageout.cpp b/src/net/tmwa/messageout.cpp
index a886fb4d..7758e306 100644
--- a/src/net/tmwa/messageout.cpp
+++ b/src/net/tmwa/messageout.cpp
@@ -23,6 +23,8 @@
#include "net/tmwa/network.h"
+#include "log.h"
+
#include <SDL_endian.h>
#include <cstring>
@@ -32,7 +34,7 @@ namespace TmwAthena {
MessageOut::MessageOut(uint16_t id)
{
#ifdef DEBUG
- logger->log("Sending %s (0x%x)", Network::mInstance->messageName(id), id);
+ Log::info("Sending %s (0x%x)", Network::mInstance->messageName(id), id);
#endif
writeInt16(id);
}
diff --git a/src/net/tmwa/network.cpp b/src/net/tmwa/network.cpp
index b448dc4f..9e010f7c 100644
--- a/src/net/tmwa/network.cpp
+++ b/src/net/tmwa/network.cpp
@@ -279,7 +279,7 @@ bool Network::connect(const ServerInfo &server)
{
if (mState != IDLE && mState != NET_ERROR)
{
- logger->log("Tried to connect an already connected socket!");
+ Log::info("Tried to connect an already connected socket!");
assert(false);
return false;
}
@@ -290,8 +290,8 @@ bool Network::connect(const ServerInfo &server)
return false;
}
- logger->log("Network::Connecting to %s:%i", server.hostname.c_str(),
- server.port);
+ Log::info("Network::Connecting to %s:%i", server.hostname.c_str(),
+ server.port);
mServer.hostname = server.hostname;
mServer.port = server.port;
@@ -376,8 +376,7 @@ void Network::dispatchMessages()
auto packetInfoIt = mPacketInfo.find(msgId);
if (packetInfoIt == mPacketInfo.end())
{
- auto error = strprintf("Unknown packet 0x%x received.", msgId);
- logger->error(error);
+ Log::critical(strprintf("Unknown packet 0x%x received.", msgId));
break;
}
@@ -395,9 +394,8 @@ void Network::dispatchMessages()
if (len < 4)
{
- auto error = strprintf("Variable length packet 0x%x has invalid length %d.",
- msgId, len);
- logger->error(error);
+ Log::critical(strprintf("Variable length packet 0x%x has invalid length %d.",
+ msgId, len));
break;
}
}
@@ -413,14 +411,14 @@ void Network::dispatchMessages()
if (iter != mMessageHandlers.end())
{
#ifdef DEBUG
- logger->log("Handling %s (0x%x) of length %d", packetInfo->name, msgId, len);
+ Log::info("Handling %s (0x%x) of length %d", packetInfo->name, msgId, len);
#endif
iter->second->handleMessage(message);
}
else
{
- logger->log("Unhandled %s (0x%x) of length %d", packetInfo->name, msgId, len);
+ Log::info("Unhandled %s (0x%x) of length %d", packetInfo->name, msgId, len);
}
skip(len);
@@ -474,7 +472,7 @@ bool Network::realConnect()
std::string errorMessage = strprintf(_("Unable to resolve host \"%s\""),
mServer.hostname.c_str());
setError(errorMessage);
- logger->log("SDLNet_ResolveHost: %s", errorMessage.c_str());
+ Log::info("SDLNet_ResolveHost: %s", errorMessage.c_str());
return false;
}
@@ -483,13 +481,13 @@ bool Network::realConnect()
mSocket = SDLNet_TCP_Open(&ipAddress);
if (!mSocket)
{
- logger->log("Error in SDLNet_TCP_Open(): %s", SDLNet_GetError());
+ Log::info("Error in SDLNet_TCP_Open(): %s", SDLNet_GetError());
setError(SDLNet_GetError());
return false;
}
- logger->log("Network::Started session with %s:%i",
- ipToString(ipAddress.host), ipAddress.port);
+ Log::info("Network::Started session with %s:%i",
+ ipToString(ipAddress.host), ipAddress.port);
mState = CONNECTED;
@@ -522,7 +520,7 @@ void Network::receive()
switch (numReady)
{
case -1:
- logger->log("Error: SDLNet_CheckSockets");
+ Log::error("SDLNet_CheckSockets");
// FALLTHROUGH
case 0:
break;
@@ -537,7 +535,7 @@ void Network::receive()
{
// We got disconnected
mState = IDLE;
- logger->log("Disconnected.");
+ Log::info("Disconnected.");
}
else if (ret < 0)
{
@@ -578,7 +576,7 @@ void Network::receive()
if (SDLNet_TCP_DelSocket(set, mSocket) == -1)
{
- logger->log("Error in SDLNet_DelSocket(): %s", SDLNet_GetError());
+ Log::info("Error in SDLNet_DelSocket(): %s", SDLNet_GetError());
}
SDLNet_FreeSocketSet(set);
@@ -586,7 +584,7 @@ void Network::receive()
void Network::setError(const std::string &error)
{
- logger->log("Network error: %s", error.c_str());
+ Log::info("Network error: %s", error.c_str());
mError = error;
mState = NET_ERROR;
}
diff --git a/src/net/tmwa/partyhandler.cpp b/src/net/tmwa/partyhandler.cpp
index 2b256cd5..78dfd7a0 100644
--- a/src/net/tmwa/partyhandler.cpp
+++ b/src/net/tmwa/partyhandler.cpp
@@ -184,7 +184,7 @@ void PartyHandler::handleMessage(MessageIn &msg)
partyTab->chatLog(_("Experience sharing not possible."), BY_SERVER);
break;
default:
- logger->log("Unknown party exp option: %d", exp);
+ Log::info("Unknown party exp option: %d", exp);
}
switch (item)
@@ -208,7 +208,7 @@ void PartyHandler::handleMessage(MessageIn &msg)
partyTab->chatLog(_("Item sharing not possible."), BY_SERVER);
break;
default:
- logger->log("Unknown party item option: %d", exp);
+ Log::info("Unknown party item option: %d", exp);
}
break;
}
diff --git a/src/net/tmwa/playerhandler.cpp b/src/net/tmwa/playerhandler.cpp
index f6f6ef41..44b0efd3 100644
--- a/src/net/tmwa/playerhandler.cpp
+++ b/src/net/tmwa/playerhandler.cpp
@@ -21,8 +21,11 @@
#include "net/tmwa/playerhandler.h"
+#include "actorspritemanager.h"
+#include "being.h"
#include "client.h"
#include "configuration.h"
+#include "effectmanager.h"
#include "game.h"
#include "localplayer.h"
#include "log.h"
@@ -55,7 +58,7 @@ const int MAP_TELEPORT_SCROLL_DISTANCE = 8;
namespace {
/**
- * Listener used for handling the overweigth message.
+ * Listener used for handling the overweight message.
*/
struct WeightListener : public gcn::ActionListener
{
@@ -126,7 +129,7 @@ static const char *randomDeathMessage()
N_("You're off the twig."),
N_("You've kicked the bucket."),
N_("You've shuffled off your mortal coil, run down the "
- "curtain and joined the bleedin' choir invisibile."),
+ "curtain and joined the bleedin' choir invisible."),
N_("You are an ex-player."),
N_("You're pining for the fjords.")
};
@@ -152,10 +155,14 @@ PlayerHandler::PlayerHandler()
SMSG_PLAYER_STAT_UPDATE_6,
SMSG_PLAYER_ARROW_MESSAGE,
SMSG_MAP_MASK,
+ SMSG_QUEST_SET_VAR,
+ SMSG_QUEST_PLAYER_VARS,
0
};
handledMessages = _messages;
playerHandler = this;
+
+ listen(Event::GameChannel);
}
void PlayerHandler::handleMessage(MessageIn &msg)
@@ -179,7 +186,7 @@ void PlayerHandler::handleMessage(MessageIn &msg)
int x = msg.readInt16();
int y = msg.readInt16();
- logger->log("Warping to %s (%d, %d)", mapPath.c_str(), x, y);
+ Log::info("Warping to %s (%d, %d)", mapPath.c_str(), x, y);
/*
* We must clear the local player's target *before* the call
@@ -217,8 +224,8 @@ void PlayerHandler::handleMessage(MessageIn &msg)
// Stop movement
local_player->setDestination(pos.x, pos.y);
- logger->log("Adjust scrolling by %d:%d", (int) scrollOffsetX,
- (int) scrollOffsetY);
+ Log::info("Adjust scrolling by %d:%d", (int) scrollOffsetX,
+ (int) scrollOffsetY);
viewport->scrollBy(scrollOffsetX, scrollOffsetY);
}
@@ -499,7 +506,7 @@ void PlayerHandler::handleMessage(MessageIn &msg)
serverNotice(_("Equip arrows first."));
break;
default:
- logger->log("0x013b: Unhandled message %i", type);
+ Log::info("0x013b: Unhandled message %i", type);
break;
}
}
@@ -514,6 +521,49 @@ void PlayerHandler::handleMessage(MessageIn &msg)
map->setMask(mask);
}
break;
+
+ case SMSG_QUEST_SET_VAR:
+ {
+ int variable = msg.readInt16();
+ int value = msg.readInt32();
+ int oldValue = mQuestVars.get(variable);
+
+ mQuestVars.set(variable, value);
+ updateQuestStatusEffects();
+ Event::trigger(Event::QuestsChannel, Event::QuestVarsChanged);
+
+ if (effectManager && local_player)
+ {
+ switch (QuestDB::questChange(variable, oldValue, value))
+ {
+ case QuestChange::None:
+ break;
+ case QuestChange::New:
+ effectManager->trigger(paths.getIntValue("newQuestEffectId"), local_player);
+ break;
+ case QuestChange::Completed:
+ effectManager->trigger(paths.getIntValue("completeQuestEffectId"), local_player);
+ break;
+ }
+ }
+ break;
+ }
+
+ case SMSG_QUEST_PLAYER_VARS:
+ {
+ msg.readInt16(); // length
+ mQuestVars.clear();
+ unsigned int count = (msg.getLength() - 4) / 6;
+ for (unsigned int i = 0; i < count; ++i)
+ {
+ int variable = msg.readInt16();
+ int value = msg.readInt32();
+ mQuestVars.set(variable, value);
+ }
+ updateQuestStatusEffects();
+ Event::trigger(Event::QuestsChannel, Event::QuestVarsChanged);
+ break;
+ }
}
}
@@ -649,8 +699,8 @@ Vector PlayerHandler::getPixelsPerSecondMoveSpeed(const Vector &speed, Map *map)
if (!map || speed.x == 0 || speed.y == 0)
{
- logger->log("TmwAthena::PlayerHandler: Speed set to default: "
- "Map not yet initialized or invalid speed.");
+ Log::info("TmwAthena::PlayerHandler: Speed set to default: "
+ "Map not yet initialized or invalid speed.");
return getDefaultMoveSpeed();
}
@@ -664,4 +714,54 @@ Vector PlayerHandler::getPixelsPerSecondMoveSpeed(const Vector &speed, Map *map)
return pixelsPerSecond;
}
+void PlayerHandler::event(Event::Channel channel, const Event &event)
+{
+ if (channel == Event::GameChannel)
+ {
+ if (event.getType() == Event::MapLoaded)
+ {
+ updateQuestStatusEffects();
+ }
+ }
+}
+
+void PlayerHandler::applyQuestStatusEffects(Being *npc)
+{
+ const auto npcId = npc->getSubType();
+ const auto effect = mActiveQuestEffects.get(npcId);
+ if (effect != 0)
+ npc->setStatusEffect(effect, true);
+}
+
+void PlayerHandler::updateQuestStatusEffects()
+{
+ auto game = Game::instance();
+ if (!game)
+ return;
+
+ const auto &currentMapName = game->getCurrentMapName();
+ auto updatedQuestEffects = QuestDB::getActiveEffects(mQuestVars, currentMapName);
+
+ // Loop over all NPCs, disabling no longer active effects and enabling new ones
+ for (auto actor : actorSpriteManager->getAll()) {
+ if (actor->getType() != ActorSprite::NPC)
+ continue;
+
+ auto *npc = static_cast<Being *>(actor);
+ const auto npcId = npc->getSubType();
+ const auto oldEffect = mActiveQuestEffects.get(npcId);
+ const auto newEffect = updatedQuestEffects.get(npcId);
+
+ if (oldEffect != newEffect)
+ {
+ if (oldEffect != 0)
+ npc->setStatusEffect(oldEffect, false);
+ if (newEffect != 0)
+ npc->setStatusEffect(newEffect, true);
+ }
+ }
+
+ std::swap(mActiveQuestEffects, updatedQuestEffects);
+}
+
} // namespace TmwAthena
diff --git a/src/net/tmwa/playerhandler.h b/src/net/tmwa/playerhandler.h
index f1a67e94..49990d85 100644
--- a/src/net/tmwa/playerhandler.h
+++ b/src/net/tmwa/playerhandler.h
@@ -28,7 +28,8 @@
namespace TmwAthena {
-class PlayerHandler final : public MessageHandler, public Net::PlayerHandler
+class PlayerHandler final : public MessageHandler, public Net::PlayerHandler,
+ public EventListener
{
public:
PlayerHandler();
@@ -63,6 +64,16 @@ class PlayerHandler final : public MessageHandler, public Net::PlayerHandler
bool usePixelPrecision() override
{ return false; }
+
+ // EventListener
+ void event(Event::Channel channel, const Event &event) override;
+
+ void applyQuestStatusEffects(Being *npc);
+
+ private:
+ void updateQuestStatusEffects();
+
+ QuestEffectMap mActiveQuestEffects;
};
} // namespace TmwAthena
diff --git a/src/openglgraphics.cpp b/src/openglgraphics.cpp
index ecd98ea1..667c91e8 100644
--- a/src/openglgraphics.cpp
+++ b/src/openglgraphics.cpp
@@ -91,8 +91,8 @@ OpenGLGraphics::OpenGLGraphics(SDL_Window *window, SDL_GLContext glContext)
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &texSize);
}
Image::mTextureSize = texSize;
- logger->log("OpenGL texture size: %d pixels%s", Image::mTextureSize,
- rectTex ? " (rectangle textures)" : "");
+ Log::info("OpenGL texture size: %d pixels%s", Image::mTextureSize,
+ rectTex ? " (rectangle textures)" : "");
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
@@ -542,6 +542,7 @@ void OpenGLGraphics::drawRescaledImagePattern(const Image *image,
void OpenGLGraphics::updateScreen()
{
SDL_GL_SwapWindow(mWindow);
+ SDL_ShowWindow(mWindow);
/*
* glFinish flushes all OpenGL commands and makes sure they have been
diff --git a/src/particle.cpp b/src/particle.cpp
index cb79c86f..5051672b 100644
--- a/src/particle.cpp
+++ b/src/particle.cpp
@@ -74,7 +74,7 @@ void Particle::setupEngine()
Particle::fastPhysics = config.particleFastPhysics;
Particle::emitterSkip = config.particleEmitterSkip + 1;
Particle::enabled = config.particleEffects;
- logger->log("Particle engine set up");
+ Log::info("Particle engine set up");
}
bool Particle::draw(Graphics *, int, int) const
@@ -261,7 +261,7 @@ Particle *Particle::addEffect(const std::string &particleEffectFile,
if (!rootNode || rootNode.name() != "effect")
{
- logger->log("Error loading particle: %s", particleEffectFile.c_str());
+ Log::info("Error loading particle: %s", particleEffectFile.c_str());
return nullptr;
}
diff --git a/src/particleemitter.cpp b/src/particleemitter.cpp
index 4954d317..e898b2fb 100644
--- a/src/particleemitter.cpp
+++ b/src/particleemitter.cpp
@@ -179,9 +179,8 @@ ParticleEmitter::ParticleEmitter(XML::Node emitterNode, Particle *target,
}
else
{
- logger->log("Particle Engine: Warning, unknown emitter property \"%s\"",
- name.c_str()
- );
+ Log::info("Particle Engine: Warning, unknown emitter property \"%s\"",
+ name.c_str());
}
}
else if (propertyNode.name() == "emitter")
diff --git a/src/playerinfo.cpp b/src/playerinfo.cpp
index a05ffaaa..a96471a5 100644
--- a/src/playerinfo.cpp
+++ b/src/playerinfo.cpp
@@ -287,8 +287,8 @@ void clearAbilityStatus(int id)
void setAbilityStatus(int id, int current, int max, int recharge)
{
- logger->log("AbilityUpdate Skill #%d -- (%d/%d) -> %d", id, current, max,
- recharge);
+ Log::info("AbilityUpdate Skill #%d -- (%d/%d) -> %d", id, current, max,
+ recharge);
mAbilities[id].currentMana = current;
mAbilities[id].neededMana = max;
mAbilities[id].recharge = recharge;
diff --git a/src/resources/abilitydb.cpp b/src/resources/abilitydb.cpp
index 311ee9eb..cb596ea8 100644
--- a/src/resources/abilitydb.cpp
+++ b/src/resources/abilitydb.cpp
@@ -41,7 +41,7 @@ static AbilityInfo::TargetMode targetModeFromString(const std::string& str)
if (str == "direction")
return AbilityInfo::TARGET_DIRECTION;
- logger->log("AbilityDB: Warning, unknown target mode \"%s\"", str.c_str() );
+ Log::info("AbilityDB: Warning, unknown target mode \"%s\"", str.c_str() );
return AbilityInfo::TARGET_BEING;
}
@@ -68,7 +68,7 @@ void AbilityDB::readAbilityNode(XML::Node node, const std::string &filename)
info->rechargeCurrent = 0;
if (mAbilityInfos.find(id) != mAbilityInfos.end())
- logger->log("AbilityDB: Duplicate ability ID %d in %s, ignoring", id, filename.c_str());
+ Log::info("AbilityDB: Duplicate ability ID %d in %s, ignoring", id, filename.c_str());
else
mAbilityInfos[id] = info;
}
diff --git a/src/resources/animation.cpp b/src/resources/animation.cpp
index 91c22236..c529400f 100644
--- a/src/resources/animation.cpp
+++ b/src/resources/animation.cpp
@@ -84,7 +84,7 @@ Animation Animation::fromXML(XML::Node node, const std::string &dyePalettes)
if (index < 0)
{
- logger->log("No valid value for 'index'");
+ Log::info("No valid value for 'index'");
continue;
}
@@ -92,7 +92,7 @@ Animation Animation::fromXML(XML::Node node, const std::string &dyePalettes)
if (!img)
{
- logger->log("No image at index %d", index);
+ Log::info("No image at index %d", index);
continue;
}
@@ -105,7 +105,7 @@ Animation Animation::fromXML(XML::Node node, const std::string &dyePalettes)
if (start < 0 || end < 0)
{
- logger->log("No valid value for 'start' or 'end'");
+ Log::info("No valid value for 'start' or 'end'");
continue;
}
@@ -115,7 +115,7 @@ Animation Animation::fromXML(XML::Node node, const std::string &dyePalettes)
if (!img)
{
- logger->log("No image at index %d", start);
+ Log::info("No image at index %d", start);
continue;
}
diff --git a/src/resources/attributes.cpp b/src/resources/attributes.cpp
index 7ec6b516..5b1018ee 100644
--- a/src/resources/attributes.cpp
+++ b/src/resources/attributes.cpp
@@ -240,22 +240,22 @@ namespace Attributes {
int id = node.getProperty("id", 0);
if (!id)
{
- logger->log("Attributes: Invalid or missing stat ID in "
- DEFAULT_ATTRIBUTESDB_FILE "!");
+ Log::info("Attributes: Invalid or missing stat ID in "
+ DEFAULT_ATTRIBUTESDB_FILE "!");
return;
}
if (attributes.find(id) != attributes.end())
{
- logger->log("Attributes: Redefinition of stat ID %d", id);
+ Log::info("Attributes: Redefinition of stat ID %d", id);
}
std::string name = node.getProperty("name", "");
if (name.empty())
{
- logger->log("Attributes: Invalid or missing stat name in "
- DEFAULT_ATTRIBUTESDB_FILE "!");
+ Log::info("Attributes: Invalid or missing stat name in "
+ DEFAULT_ATTRIBUTESDB_FILE "!");
return;
}
@@ -280,10 +280,10 @@ namespace Attributes {
{
if (name.empty())
{
- logger->log("Attribute modifier in attribute %u:%s: "
- "Empty name definition "
- "on empty tag definition, skipping.",
- a.id, a.name.c_str());
+ Log::info("Attribute modifier in attribute %u:%s: "
+ "Empty name definition "
+ "on empty tag definition, skipping.",
+ a.id, a.name.c_str());
--count;
continue;
}
@@ -296,10 +296,10 @@ namespace Attributes {
{
if (name.empty())
{
- logger->log("Attribute modifier in attribute %u:%s: "
- "Empty name definition "
- "on empty effect definition, skipping.",
- a.id, a.name.c_str());
+ Log::info("Attribute modifier in attribute %u:%s: "
+ "Empty name definition "
+ "on empty effect definition, skipping.",
+ a.id, a.name.c_str());
--count;
continue;
}
@@ -308,7 +308,7 @@ namespace Attributes {
}
tags.insert(std::make_pair(tag, effect));
}
- logger->log("Found %d tags for attribute %d.", count, id);
+ Log::info("Found %d tags for attribute %d.", count, id);
}
/**
@@ -321,8 +321,8 @@ namespace Attributes {
DEFAULT_MIN_PTS);
attributeMaximum = node.getProperty("maximum",
DEFAULT_MAX_PTS);
- logger->log("Loaded points: start: %i, min: %i, max: %i.",
- creationPoints, attributeMinimum, attributeMaximum);
+ Log::info("Loaded points: start: %i, min: %i, max: %i.",
+ creationPoints, attributeMinimum, attributeMaximum);
}
/**
@@ -330,8 +330,8 @@ namespace Attributes {
*/
void checkStatus()
{
- logger->log("Found %d tags for %d attributes.", int(tags.size()),
- int(attributes.size()));
+ Log::info("Found %d tags for %d attributes.", int(tags.size()),
+ int(attributes.size()));
if (attributes.size() == 0)
{
@@ -346,9 +346,9 @@ namespace Attributes {
if (averageValue > attributeMaximum || averageValue < attributeMinimum
|| creationPoints < 1)
{
- logger->log("Attributes: Character's point values make "
- "the character's creation impossible. "
- "Switch back to defaults.");
+ Log::info("Attributes: Character's point values make "
+ "the character's creation impossible. "
+ "Switch back to defaults.");
creationPoints = DEFAULT_POINTS;
attributeMinimum = DEFAULT_MIN_PTS;
attributeMaximum = DEFAULT_MAX_PTS;
diff --git a/src/resources/beinginfo.cpp b/src/resources/beinginfo.cpp
index 20c24d4f..f2edf1d8 100644
--- a/src/resources/beinginfo.cpp
+++ b/src/resources/beinginfo.cpp
@@ -73,8 +73,8 @@ void BeingInfo::setTargetCursorSize(const std::string &size)
const auto cursorSize = targetCursorSizeFromString(size);
if (!cursorSize)
{
- logger->log("Unknown targetCursor value \"%s\" for %s",
- size.c_str(), name.c_str());
+ Log::info("Unknown targetCursor value \"%s\" for %s",
+ size.c_str(), name.c_str());
}
targetCursorSize = cursorSize.value_or(ActorSprite::TC_MEDIUM);
}
@@ -84,8 +84,8 @@ void BeingInfo::setHoverCursor(const std::string &cursorName)
const auto cursor = cursorFromString(cursorName);
if (!cursor)
{
- logger->log("Unknown hoverCursor value \"%s\" for %s",
- cursorName.c_str(), name.c_str());
+ Log::info("Unknown hoverCursor value \"%s\" for %s",
+ cursorName.c_str(), name.c_str());
}
hoverCursor = cursor.value_or(Cursor::Pointer);
}
diff --git a/src/resources/chardb.cpp b/src/resources/chardb.cpp
index 9001b6c2..97f86d3a 100644
--- a/src/resources/chardb.cpp
+++ b/src/resources/chardb.cpp
@@ -54,7 +54,7 @@ void CharDB::load()
if (!root || root.name() != "chars")
{
- logger->log("CharDB: Failed to parse charcreation.xml.");
+ Log::info("CharDB: Failed to parse charcreation.xml.");
return;
}
@@ -86,7 +86,7 @@ void CharDB::load()
void CharDB::unload()
{
- logger->log("Unloading chars database...");
+ Log::info("Unloading chars database...");
mLoaded = false;
}
diff --git a/src/resources/dye.cpp b/src/resources/dye.cpp
index 0ecc9fd6..136c9334 100644
--- a/src/resources/dye.cpp
+++ b/src/resources/dye.cpp
@@ -63,8 +63,8 @@ DyePalette::DyePalette(const std::string &description)
}
else
{
- logger->log("Error, invalid embedded palette: %s",
- description.c_str());
+ Log::info("Error, invalid embedded palette: %s",
+ description.c_str());
return;
}
@@ -82,7 +82,7 @@ DyePalette::DyePalette(const std::string &description)
++pos;
}
- logger->log("Error, invalid embedded palette: %s", description.c_str());
+ Log::info("Error, invalid embedded palette: %s", description.c_str());
}
void DyePalette::getColor(int intensity, int color[3]) const
@@ -195,7 +195,7 @@ Dye::Dye(const std::string &description)
if (next_pos <= pos + 3 || description[pos + 1] != ':')
{
- logger->log("Error, invalid dye: %s", description.c_str());
+ Log::info("Error, invalid dye: %s", description.c_str());
return;
}
@@ -211,7 +211,7 @@ Dye::Dye(const std::string &description)
case 'C': i = 5; break;
case 'W': i = 6; break;
default:
- logger->log("Error, invalid dye: %s", description.c_str());
+ Log::info("Error, invalid dye: %s", description.c_str());
return;
}
mDyePalettes[i] = new DyePalette(description.substr(pos + 2,
@@ -289,7 +289,7 @@ void Dye::instantiate(std::string &target, const std::string &palettes)
}
else
{
- logger->log("Error, invalid dye placeholder: %s", target.c_str());
+ Log::info("Error, invalid dye placeholder: %s", target.c_str());
return;
}
s << target[next_pos];
diff --git a/src/resources/dye.h b/src/resources/dye.h
index 0fe68f07..1730d2fd 100644
--- a/src/resources/dye.h
+++ b/src/resources/dye.h
@@ -35,7 +35,7 @@ class DyePalette
* The string is either a file name or a sequence of hexadecimal RGB
* values separated by ',' and starting with '#'.
*/
- DyePalette(const std::string &pallete);
+ DyePalette(const std::string &description);
/**
* Gets a pixel color depending on its intensity. First color is
diff --git a/src/resources/emotedb.cpp b/src/resources/emotedb.cpp
index d29483d1..c0f5f777 100644
--- a/src/resources/emotedb.cpp
+++ b/src/resources/emotedb.cpp
@@ -51,7 +51,7 @@ void EmoteDB::readEmoteNode(XML::Node node, const std::string &filename)
const int id = node.getProperty("id", -1);
if (id == -1)
{
- logger->log("Emote Database: Emote with missing ID in %s!", filename.c_str());
+ Log::info("Emote Database: Emote with missing ID in %s!", filename.c_str());
return;
}
@@ -63,8 +63,8 @@ void EmoteDB::readEmoteNode(XML::Node node, const std::string &filename)
if (emote.effectId == -1)
{
- logger->log("Emote Database: Warning: Emote %s has no attached effect in %s!",
- emote.name.c_str(), filename.c_str());
+ Log::info("Emote Database: Warning: Emote %s has no attached effect in %s!",
+ emote.name.c_str(), filename.c_str());
return;
}
@@ -74,8 +74,8 @@ void EmoteDB::readEmoteNode(XML::Node node, const std::string &filename)
if (imageName.empty() || width <= 0 || height <= 0)
{
- logger->log("Emote Database: Warning: Emote %s has bad imageset values in %s",
- emote.name.c_str(), filename.c_str());
+ Log::info("Emote Database: Warning: Emote %s has bad imageset values in %s",
+ emote.name.c_str(), filename.c_str());
return;
}
@@ -85,8 +85,8 @@ void EmoteDB::readEmoteNode(XML::Node node, const std::string &filename)
if (!emote.is || emote.is->size() == 0)
{
- logger->log("Emote Database: Error loading imageset for emote %s in %s",
- emote.name.c_str(), filename.c_str());
+ Log::info("Emote Database: Error loading imageset for emote %s in %s",
+ emote.name.c_str(), filename.c_str());
return;
}
@@ -119,7 +119,7 @@ const Emote &EmoteDB::get(int id)
if (i == mEmotes.end())
{
- logger->log("EmoteDB: Warning, unknown emote ID %d requested", id);
+ Log::info("EmoteDB: Warning, unknown emote ID %d requested", id);
return mUnknown;
}
diff --git a/src/resources/hairdb.cpp b/src/resources/hairdb.cpp
index 6b88a4df..312188d6 100644
--- a/src/resources/hairdb.cpp
+++ b/src/resources/hairdb.cpp
@@ -40,7 +40,7 @@ void HairDB::readHairColorNode(XML::Node node, const std::string &filename)
int id = node.getProperty("id", 0);
if (mHairColors.find(id) != mHairColors.end())
- logger->log("HairDb: Redefinition of color Id %d in %s", id, filename.c_str());
+ Log::info("HairDb: Redefinition of color Id %d in %s", id, filename.c_str());
mHairColors[id] = node.getProperty("value", COLOR_WHITE);
}
@@ -55,7 +55,7 @@ void HairDB::unload()
if (!mLoaded)
return;
- logger->log("Unloading hair style and color database...");
+ Log::info("Unloading hair style and color database...");
mHairColors.clear();
mHairStyles.clear();
@@ -71,7 +71,7 @@ void HairDB::addHairStyle(int id)
id = -id;
if (mHairStyles.find(id) != mHairStyles.end())
- logger->log("Warning: Redefinition of hairstyle id %i:", id);
+ Log::warn("Redefinition of hairstyle id %i:", id);
mHairStyles.insert(id);
}
@@ -81,13 +81,13 @@ const std::string &HairDB::getHairColor(int id) const
if (!mLoaded)
{
// no idea if this can happen, but that check existed before
- logger->log("WARNING: HairDB::getHairColor() called before settings were loaded!");
+ Log::warn("HairDB::getHairColor() called before settings were loaded!");
}
auto it = mHairColors.find(id);
if (it != mHairColors.end())
return it->second;
- logger->log("HairDb: Error, unknown color Id# %d", id);
+ Log::info("HairDb: Error, unknown color Id# %d", id);
return mHairColors.at(0);
}
diff --git a/src/resources/image.cpp b/src/resources/image.cpp
index b36aea01..11d5c275 100644
--- a/src/resources/image.cpp
+++ b/src/resources/image.cpp
@@ -54,8 +54,7 @@ Image::Image(SDL_Texture *texture, int width, int height):
if (!texture)
{
- logger->log(
- "Image::Image(SDL_Texture*, ...): Couldn't load invalid Surface!");
+ Log::info("Image::Image(SDL_Texture*, ...): Couldn't load invalid Surface!");
}
}
@@ -72,8 +71,7 @@ Image::Image(GLuint glimage, int width, int height, int texWidth, int texHeight)
if (glimage == 0)
{
- logger->log(
- "Image::Image(GLuint, ...): Couldn't load invalid Surface!");
+ Log::info("Image::Image(GLuint, ...): Couldn't load invalid Surface!");
}
}
#endif
@@ -101,7 +99,7 @@ Resource *Image::load(SDL_RWops *rw)
if (!tmpImage)
{
- logger->log("Error, image load failed: %s", IMG_GetError());
+ Log::info("Error, image load failed: %s", IMG_GetError());
return nullptr;
}
@@ -117,20 +115,20 @@ Resource *Image::load(SDL_RWops *rw, const Dye &dye)
if (!surf)
{
- logger->log("Error, image load failed: %s", IMG_GetError());
+ Log::info("Error, image load failed: %s", IMG_GetError());
return nullptr;
}
if (surf->format->format != SDL_PIXELFORMAT_RGBA32)
{
- logger->log("Warning: image format is %s, not SDL_PIXELFORMAT_RGBA32. Converting...",
- SDL_GetPixelFormatName(surf->format->format));
+ Log::warn("Image format is %s, not SDL_PIXELFORMAT_RGBA32. Converting...",
+ SDL_GetPixelFormatName(surf->format->format));
SDL_Surface *convertedSurf = SDL_ConvertSurfaceFormat(surf, SDL_PIXELFORMAT_RGBA32, 0);
SDL_FreeSurface(surf);
if (!convertedSurf)
{
- logger->log("Error, image convert failed: %s", SDL_GetError());
+ Log::info("Error, image convert failed: %s", SDL_GetError());
return nullptr;
}
surf = convertedSurf;
@@ -219,8 +217,8 @@ Image *Image::_GLload(SDL_Surface *image)
if (realWidth < width || realHeight < height)
{
- logger->log("Warning: image too large, cropping to %dx%d texture!",
- realWidth, realHeight);
+ Log::warn("Image too large, cropping to %dx%d texture!",
+ realWidth, realHeight);
}
// Determine 32-bit masks based on byte order
@@ -253,7 +251,7 @@ Image *Image::_GLload(SDL_Surface *image)
if (!image)
{
- logger->log("Error, image convert failed: out of memory");
+ Log::info("Error, image convert failed: out of memory");
return nullptr;
}
@@ -308,7 +306,7 @@ Image *Image::_GLload(SDL_Surface *image)
errmsg = "GL_OUT_OF_MEMORY";
break;
}
- logger->log("Error: Image GL import failed: %s", errmsg);
+ Log::error("Image GL import failed: %s", errmsg);
return nullptr;
}
diff --git a/src/resources/imageset.cpp b/src/resources/imageset.cpp
index 34cf1fd8..1f194b4f 100644
--- a/src/resources/imageset.cpp
+++ b/src/resources/imageset.cpp
@@ -50,7 +50,7 @@ Image *ImageSet::get(size_t i) const
{
if (i >= mImages.size())
{
- logger->log("Warning: No sprite %d in this image set", (int) i);
+ Log::warn("No sprite %d in this image set", (int) i);
return nullptr;
}
diff --git a/src/resources/imagewriter.cpp b/src/resources/imagewriter.cpp
index ddf1fbee..cf4c6803 100644
--- a/src/resources/imagewriter.cpp
+++ b/src/resources/imagewriter.cpp
@@ -42,7 +42,7 @@ bool ImageWriter::writePNG(SDL_Surface *surface, const std::string &filename)
png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_ptr)
{
- logger->log("Had trouble creating png_structp");
+ Log::info("Had trouble creating png_structp");
return false;
}
@@ -50,21 +50,21 @@ bool ImageWriter::writePNG(SDL_Surface *surface, const std::string &filename)
if (!info_ptr)
{
png_destroy_write_struct(&png_ptr, (png_infopp)nullptr);
- logger->log("Could not create png_info");
+ Log::info("Could not create png_info");
return false;
}
if (setjmp(png_jmpbuf(png_ptr)))
{
png_destroy_write_struct(&png_ptr, (png_infopp)nullptr);
- logger->log("problem writing to %s", filename.c_str());
+ Log::info("problem writing to %s", filename.c_str());
return false;
}
FILE *fp = fopen(filename.c_str(), "wb");
if (!fp)
{
- logger->log("could not open file %s for writing", filename.c_str());
+ Log::info("could not open file %s for writing", filename.c_str());
return false;
}
@@ -83,7 +83,7 @@ bool ImageWriter::writePNG(SDL_Surface *surface, const std::string &filename)
row_pointers = new png_bytep[surface->h];
if (!row_pointers)
{
- logger->log("Had trouble converting surface to row pointers");
+ Log::info("Had trouble converting surface to row pointers");
fclose(fp);
return false;
}
diff --git a/src/resources/itemdb.cpp b/src/resources/itemdb.cpp
index 05f6ad0b..1d217fc2 100644
--- a/src/resources/itemdb.cpp
+++ b/src/resources/itemdb.cpp
@@ -148,7 +148,7 @@ const ItemInfo &ItemDB::get(int id) const
auto i = mItemInfos.find(id);
if (i == mItemInfos.end())
{
- logger->log("ItemDB: Warning, unknown item ID# %d", id);
+ Log::info("ItemDB: Warning, unknown item ID# %d", id);
return *mUnknown;
}
@@ -164,8 +164,8 @@ const ItemInfo &ItemDB::get(const std::string &name) const
{
if (!name.empty())
{
- logger->log("ItemDB: Warning, unknown item name \"%s\"",
- name.c_str());
+ Log::info("ItemDB: Warning, unknown item name \"%s\"",
+ name.c_str());
}
return *mUnknown;
}
@@ -202,8 +202,8 @@ void ItemDB::loadSoundRef(ItemInfo &itemInfo, XML::Node node)
}
else
{
- logger->log("ItemDB: Ignoring unknown sound event '%s'",
- event.c_str());
+ Log::info("ItemDB: Ignoring unknown sound event '%s'",
+ event.c_str());
}
}
@@ -237,15 +237,15 @@ void ItemDB::loadReplacement(ItemInfo &info, XML::Node replaceNode)
if (sprite == SPRITE_UNKNOWN)
{
- logger->log("ItemDB: Invalid sprite name '%s' in replace tag",
- spriteString.data());
+ Log::info("ItemDB: Invalid sprite name '%s' in replace tag",
+ spriteString.data());
return;
}
if (direction == DIRECTION_UNKNOWN)
{
- logger->log("ItemDB: Invalid direction name '%s' in replace tag",
- directionString.data());
+ Log::info("ItemDB: Invalid direction name '%s' in replace tag",
+ directionString.data());
return;
}
@@ -266,7 +266,7 @@ void ItemDB::loadReplacement(ItemInfo &info, XML::Node replaceNode)
void ItemDB::unload()
{
- logger->log("Unloading item database...");
+ Log::info("Unloading item database...");
delete mUnknown;
mUnknown = nullptr;
@@ -283,12 +283,12 @@ void ItemDB::loadCommonRef(ItemInfo &itemInfo, XML::Node node, const std::string
if (!itemInfo.id)
{
- logger->log("ItemDB: Invalid or missing item Id in %s!", filename.c_str());
+ Log::info("ItemDB: Invalid or missing item Id in %s!", filename.c_str());
return;
}
else if (mItemInfos.find(itemInfo.id) != mItemInfos.end())
{
- logger->log("ItemDB: Redefinition of item Id %d in %s", itemInfo.id, filename.c_str());
+ Log::info("ItemDB: Redefinition of item Id %d in %s", itemInfo.id, filename.c_str());
}
itemInfo.mView = node.getProperty("view", 0);
@@ -346,8 +346,8 @@ void ItemDB::addItem(ItemInfo *itemInfo)
if (itr == mNamedItemInfos.end())
mNamedItemInfos[temp] = itemInfo;
else
- logger->log("ItemDB: Duplicate name (%s) for item id %d found.",
- temp.c_str(), itemInfo->id);
+ Log::info("ItemDB: Duplicate name (%s) for item id %d found.",
+ temp.c_str(), itemInfo->id);
}
}
@@ -359,7 +359,7 @@ static void checkParameter(int id, const T param, const T errorValue)
std::stringstream errMsg;
errMsg << "ItemDB: Missing " << param << " attribute for item id "
<< id << "!";
- logger->log("%s", errMsg.str().c_str());
+ Log::info("%s", errMsg.str().c_str());
}
}
@@ -368,7 +368,7 @@ void ItemDB::checkItemInfo(ItemInfo &itemInfo)
int id = itemInfo.id;
if (!itemInfo.attackAction.empty())
if (itemInfo.attackRange == 0)
- logger->log("ItemDB: Missing attack range from weapon %i!", id);
+ Log::info("ItemDB: Missing attack range from weapon %i!", id);
if (id >= 0)
{
@@ -504,7 +504,7 @@ void ManaServItemDB::readItemNode(XML::Node node, const std::string &filename)
std::string trigger = itemChild.getProperty("trigger", std::string());
if (trigger.empty())
{
- logger->log("Found empty trigger effect label in %s, skipping.", filename.c_str());
+ Log::info("Found empty trigger effect label in %s, skipping.", filename.c_str());
continue;
}
@@ -514,8 +514,8 @@ void ManaServItemDB::readItemNode(XML::Node node, const std::string &filename)
auto triggerLabel = triggerTable.find(trigger);
if (triggerLabel == triggerTable.end())
{
- logger->log("Warning: unknown trigger %s in item %d!",
- trigger.c_str(), itemInfo->id);
+ Log::warn("Unknown trigger %s in item %d!",
+ trigger.c_str(), itemInfo->id);
continue;
}
@@ -528,7 +528,7 @@ void ManaServItemDB::readItemNode(XML::Node node, const std::string &filename)
int duration = effectChild.getProperty("duration", 0);
if (attribute.empty() || !value)
{
- logger->log("Warning: incomplete modifier definition in %s, skipping.", filename.c_str());
+ Log::warn("Incomplete modifier definition in %s, skipping.", filename.c_str());
continue;
}
auto it = extraStats.cbegin();
@@ -537,7 +537,7 @@ void ManaServItemDB::readItemNode(XML::Node node, const std::string &filename)
++it;
if (it == extraStats.end())
{
- logger->log("Warning: unknown modifier tag %s in %s, skipping.", attribute.c_str(), filename.c_str());
+ Log::warn("Unknown modifier tag %s in %s, skipping.", attribute.c_str(), filename.c_str());
continue;
}
effect.push_back(
diff --git a/src/resources/mapreader.cpp b/src/resources/mapreader.cpp
index b952cdcc..3ca5763c 100644
--- a/src/resources/mapreader.cpp
+++ b/src/resources/mapreader.cpp
@@ -77,7 +77,7 @@ static std::string resolveRelativePath(std::string base, std::string relative)
Map *MapReader::readMap(const std::string &filename)
{
- logger->log("Attempting to read map %s", filename.c_str());
+ Log::info("Attempting to read map %s", filename.c_str());
Map *map = nullptr;
XML::Document doc(filename);
@@ -89,7 +89,7 @@ Map *MapReader::readMap(const std::string &filename)
{
if (node.name() != "map")
{
- logger->log("Error: Not a map file (%s)!", filename.c_str());
+ Log::error("Not a map file (%s)!", filename.c_str());
}
else
{
@@ -98,7 +98,7 @@ Map *MapReader::readMap(const std::string &filename)
}
else
{
- logger->log("Error while parsing map file (%s)!", filename.c_str());
+ Log::info("Error while parsing map file (%s)!", filename.c_str());
}
if (map)
@@ -119,9 +119,9 @@ Map *MapReader::readMap(XML::Node node, const std::string &path)
if (tilew < 0 || tileh < 0)
{
- logger->log("MapReader: Warning: "
- "Unitialized tile width or height value for map: %s",
- path.c_str());
+ Log::info("MapReader: Warning: "
+ "Unitialized tile width or height value for map: %s",
+ path.c_str());
return nullptr;
}
@@ -174,15 +174,15 @@ Map *MapReader::readMap(XML::Node node, const std::string &path)
const int objW = objectNode.getProperty("width", 0);
const int objH = objectNode.getProperty("height", 0);
- logger->log("- Loading object name: %s type: %s at %d:%d",
- objName.c_str(), objType.c_str(),
- objX, objY);
+ Log::info("- Loading object name: %s type: %s at %d:%d",
+ objName.c_str(), objType.c_str(),
+ objX, objY);
if (objType == "PARTICLE_EFFECT")
{
if (objName.empty())
{
- logger->log(" Warning: No particle file given");
+ Log::info(" Warning: No particle file given");
continue;
}
@@ -203,7 +203,7 @@ Map *MapReader::readMap(XML::Node node, const std::string &path)
}
else
{
- logger->log(" Warning: Unknown object type");
+ Log::info(" Warning: Unknown object type");
}
}
}
@@ -293,7 +293,7 @@ static void readLayer(XML::Node node, Map *map)
map->addLayer(layer);
}
- logger->log("- Loading layer \"%s\"", name.c_str());
+ Log::info("- Loading layer \"%s\"", name.c_str());
int x = 0;
int y = 0;
@@ -333,8 +333,8 @@ static void readLayer(XML::Node node, Map *map)
if (!compression.empty() && compression != "gzip"
&& compression != "zlib")
{
- logger->log("Warning: only gzip or zlib layer "
- "compression supported!");
+ Log::warn("Only gzip or zlib layer "
+ "compression supported!");
return;
}
@@ -383,7 +383,7 @@ static void readLayer(XML::Node node, Map *map)
if (!inflated)
{
- logger->log("Error: Could not decompress layer!");
+ Log::error("Could not decompress layer!");
return;
}
}
@@ -415,7 +415,7 @@ static void readLayer(XML::Node node, Map *map)
const auto data = childNode.textContent();
if (data.empty())
{
- logger->log("Error: CSV layer data is empty!");
+ Log::error("CSV layer data is empty!");
continue;
}
@@ -432,7 +432,7 @@ static void readLayer(XML::Node node, Map *map)
if (errno == ERANGE)
{
- logger->log("Error: Range error in tile layer data!");
+ Log::error("Range error in tile layer data!");
break;
}
@@ -452,7 +452,7 @@ static void readLayer(XML::Node node, Map *map)
pos = strchr(end, ',');
if (!pos)
{
- logger->log("Error: CSV layer data too short!");
+ Log::error("CSV layer data too short!");
break;
}
++pos;
@@ -536,8 +536,7 @@ static Tileset *readTileset(XML::Node node, const std::string &path,
}
else
{
- logger->log("Warning: Failed to load tileset (%s)",
- source.c_str());
+ Log::warn("Failed to load tileset (%s)", source.c_str());
}
}
}
diff --git a/src/resources/monsterdb.cpp b/src/resources/monsterdb.cpp
index 4963f93f..7f092a0e 100644
--- a/src/resources/monsterdb.cpp
+++ b/src/resources/monsterdb.cpp
@@ -111,11 +111,11 @@ void MonsterDB::readMonsterNode(XML::Node node, const std::string &filename)
}
else
{
- logger->log("MonsterDB: Warning, sound effect %s for "
- "unknown event %s of monster %s in %s",
- soundFile.c_str(), event.c_str(),
- currentInfo->name.c_str(),
- filename.c_str());
+ Log::info("MonsterDB: Warning, sound effect %s for "
+ "unknown event %s of monster %s in %s",
+ soundFile.c_str(), event.c_str(),
+ currentInfo->name.c_str(),
+ filename.c_str());
}
}
else if (spriteNode.name() == "attack")
@@ -168,7 +168,7 @@ BeingInfo *MonsterDB::get(int id)
if (i == mMonsterInfos.end())
{
- logger->log("MonsterDB: Warning, unknown monster ID %d requested", id);
+ Log::info("MonsterDB: Warning, unknown monster ID %d requested", id);
return BeingInfo::Unknown;
}
diff --git a/src/resources/music.cpp b/src/resources/music.cpp
index 069af588..b73d89ce 100644
--- a/src/resources/music.cpp
+++ b/src/resources/music.cpp
@@ -40,7 +40,7 @@ Music *Music::load(SDL_RWops *rw)
return new Music(music);
}
- logger->log("Error, failed to load music: %s", Mix_GetError());
+ Log::info("Error, failed to load music: %s", Mix_GetError());
return nullptr;
}
diff --git a/src/resources/npcdb.cpp b/src/resources/npcdb.cpp
index 6b1c3150..44292525 100644
--- a/src/resources/npcdb.cpp
+++ b/src/resources/npcdb.cpp
@@ -46,7 +46,7 @@ void NPCDB::readNPCNode(XML::Node node, const std::string &filename)
int id = node.getProperty("id", 0);
if (id == 0)
{
- logger->log("NPC Database: NPC with missing ID in %s", filename.c_str());
+ Log::info("NPC Database: NPC with missing ID in %s", filename.c_str());
return;
}
@@ -94,7 +94,7 @@ BeingInfo *NPCDB::get(int id)
if (i == mNPCInfos.end())
{
- logger->log("NPCDB: Warning, unknown NPC ID %d requested", id);
+ Log::info("NPCDB: Warning, unknown NPC ID %d requested", id);
return BeingInfo::Unknown;
}
diff --git a/src/resources/questdb.cpp b/src/resources/questdb.cpp
new file mode 100644
index 00000000..1424c20e
--- /dev/null
+++ b/src/resources/questdb.cpp
@@ -0,0 +1,232 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2025 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "resources/questdb.h"
+#include "log.h"
+
+#include <algorithm>
+#include <unordered_map>
+#include <utility>
+
+namespace QuestDB {
+
+// The quests are stored in a map using their variable ID as the key
+static std::unordered_map<int, Quest> quests;
+
+// Helper function to check if a container contains a value
+template<typename Container, typename Value>
+static bool contains(const Container &container, const Value &value)
+{
+ return std::find(container.begin(), container.end(), value) != container.end();
+}
+
+void init()
+{
+ unload();
+}
+
+void readQuestVarNode(XML::Node node, const std::string &filename)
+{
+ int varId = 0;
+ if (!node.attribute("id", varId))
+ return;
+
+ Quest &quest = quests[varId];
+
+ for (auto child : node.children())
+ {
+ if (child.name() == "effect")
+ {
+ QuestEffect &effect = quest.effects.emplace_back();
+ child.attribute("map", effect.map);
+ child.attribute("npc", effect.npcId);
+ child.attribute("effect", effect.statusEffectId);
+ child.attribute("value", effect.values);
+
+ if (effect.map.empty() || effect.npcId == 0 || effect.statusEffectId == 0 || effect.values.empty())
+ {
+ Log::warn("effect node for var %d is missing required attributes", varId);
+ }
+ }
+ else if (child.name() == "quest")
+ {
+ QuestState &state = quest.states.emplace_back();
+ child.attribute("name", state.name);
+ child.attribute("group", state.group);
+ child.attribute("incomplete", state.incomplete);
+ child.attribute("complete", state.complete);
+
+ if (state.incomplete.empty() && state.complete.empty())
+ {
+ Log::warn("quest node for var %d ('%s') has neither 'complete' nor 'incomplete' values",
+ varId, state.name.c_str());
+ continue;
+ }
+
+ for (auto questChild : child.children())
+ {
+ QuestRowType rowType;
+ std::string_view tag = questChild.name();
+ if (tag == "text")
+ rowType = QuestRowType::Text;
+ else if (tag == "name")
+ rowType = QuestRowType::Name;
+ else if (tag == "reward")
+ rowType = QuestRowType::Reward;
+ else if (tag == "questgiver" || tag == "giver")
+ rowType = QuestRowType::Giver;
+ else if (tag == "coordinates")
+ rowType = QuestRowType::Coordinates;
+ else if (tag == "npc")
+ rowType = QuestRowType::NPC;
+ else
+ {
+ Log::warn("unknown quest row type '%s' for var %d ('%s')",
+ tag.data(), varId, state.name.c_str());
+ continue;
+ }
+
+ QuestRow &row = state.rows.emplace_back(rowType);
+ row.text = questChild.textContent();
+
+ if (rowType == QuestRowType::Coordinates)
+ {
+ questChild.attribute("x", row.x);
+ questChild.attribute("y", row.y);
+ }
+ }
+ }
+ }
+}
+
+void unload()
+{
+ quests.clear();
+}
+
+bool hasQuests()
+{
+ return !quests.empty();
+}
+
+// In quests, the map name may include the file extension. This is discouraged
+// but supported for compatibility.
+static std::string_view baseName(const std::string &fileName)
+{
+ auto pos = fileName.find_last_of('.');
+ return pos == std::string::npos ? fileName : std::string_view(fileName.data(), pos);
+}
+
+QuestEffectMap getActiveEffects(const QuestVars &questVars,
+ const std::string &mapName)
+{
+ QuestEffectMap activeEffects;
+
+ for (auto &[var, quest] : std::as_const(quests))
+ {
+ auto value = questVars.get(var);
+
+ for (auto &effect : quest.effects)
+ {
+ if (baseName(effect.map) != mapName)
+ continue;
+ if (!contains(effect.values, value))
+ continue;
+
+ activeEffects.set(effect.npcId, effect.statusEffectId);
+ }
+ }
+
+ return activeEffects;
+}
+
+std::vector<QuestEntry> getQuestsEntries(const QuestVars &questVars,
+ bool skipCompleted)
+{
+ std::vector<QuestEntry> activeQuests;
+
+ for (auto &[varId, quest] : std::as_const(quests))
+ {
+ auto value = questVars.get(varId);
+
+ for (auto &state : quest.states)
+ {
+ bool matchesIncomplete = contains(state.incomplete, value);
+ bool matchesComplete = contains(state.complete, value);
+
+ if (skipCompleted && matchesComplete)
+ continue;
+
+ if (matchesIncomplete || matchesComplete)
+ {
+ QuestEntry &entry = activeQuests.emplace_back();
+ entry.varId = varId;
+ entry.completed = matchesComplete;
+ entry.state = &state;
+ }
+ }
+ }
+
+ return activeQuests;
+}
+
+static std::pair<int, int> countQuestEntries(const Quest &quest, int value)
+{
+ int totalEntries = 0;
+ int completedEntries = 0;
+
+ for (const auto &state : quest.states)
+ {
+ bool matchesIncomplete = contains(state.incomplete, value);
+ bool matchesComplete = contains(state.complete, value);
+
+ if (matchesIncomplete || matchesComplete)
+ {
+ totalEntries++;
+ if (matchesComplete)
+ completedEntries++;
+ }
+ }
+
+ return { totalEntries, completedEntries };
+}
+
+QuestChange questChange(int varId, int oldValue, int newValue)
+{
+ if (newValue == oldValue)
+ return QuestChange::None;
+
+ auto questIt = quests.find(varId);
+ if (questIt == quests.end())
+ return QuestChange::None;
+
+ const Quest &quest = questIt->second;
+
+ auto [oldQuestEntries, oldCompletedEntries] = countQuestEntries(quest, oldValue);
+ auto [newQuestEntries, newCompletedEntries] = countQuestEntries(quest, newValue);
+
+ if (newCompletedEntries > oldCompletedEntries)
+ return QuestChange::Completed;
+ if (newQuestEntries > oldQuestEntries)
+ return QuestChange::New;
+ return QuestChange::None;
+}
+
+} // namespace QuestDB
diff --git a/src/resources/questdb.h b/src/resources/questdb.h
new file mode 100644
index 00000000..43996b0b
--- /dev/null
+++ b/src/resources/questdb.h
@@ -0,0 +1,139 @@
+/*
+ * The Mana Client
+ * Copyright (C) 2025 The Mana Developers
+ *
+ * This file is part of The Mana Client.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "utils/xml.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+/**
+ * A map that returns a default value for non-existent keys.
+ */
+template<typename Key, typename Value, Value def = Value()>
+class MapWithDefault
+{
+public:
+ void set(Key key, Value value)
+ {
+ mVars[key] = value;
+ }
+
+ Value get(Key key) const
+ {
+ auto it = mVars.find(key);
+ return it != mVars.end() ? it->second : def;
+ }
+
+ void clear()
+ {
+ mVars.clear();
+ }
+
+private:
+ std::map<Key, Value> mVars;
+};
+
+struct QuestEffect
+{
+ std::vector<int> values; // Quest variable values to which the effect applies
+ std::string map; // Map name the NPC is located on
+ int npcId = 0;
+ int statusEffectId = 0;
+};
+
+// Map of quest variables, from variable ID to value
+using QuestVars = MapWithDefault<int, int>;
+
+// Map of quest effects, from NPC ID to status effect ID
+using QuestEffectMap = MapWithDefault<int, int>;
+
+enum class QuestRowType
+{
+ Text,
+ Name,
+ Reward,
+ Giver,
+ Coordinates,
+ NPC
+};
+
+struct QuestRow
+{
+ QuestRow(QuestRowType type)
+ : type(type)
+ {}
+
+ QuestRowType type;
+ std::string text;
+ int x = 0;
+ int y = 0;
+};
+
+struct QuestState
+{
+ std::string name; // Name of the quest in this state
+ std::string group; // Group name of the quest in this state
+ std::vector<int> incomplete; // Quest variable values for this state (quest incomplete)
+ std::vector<int> complete; // Quest variable values for this state (quest complete)
+ std::vector<QuestRow> rows; // Rows of text in the Quests window for this state
+};
+
+struct Quest
+{
+ std::vector<QuestEffect> effects;
+ std::vector<QuestState> states;
+};
+
+struct QuestEntry
+{
+ int varId;
+ bool completed;
+ const QuestState *state;
+
+ const std::string &name() const { return state->name; }
+ const std::vector<QuestRow> &rows() const { return state->rows; }
+};
+
+enum class QuestChange
+{
+ None,
+ New,
+ Completed
+};
+
+namespace QuestDB
+{
+ void init();
+ void readQuestVarNode(XML::Node node, const std::string &filename);
+ void unload();
+
+ bool hasQuests();
+
+ QuestEffectMap getActiveEffects(const QuestVars &questVars,
+ const std::string &mapName);
+
+ std::vector<QuestEntry> getQuestsEntries(const QuestVars &questVars,
+ bool skipCompleted = false);
+
+ QuestChange questChange(int varId, int oldValue, int newValue);
+};
diff --git a/src/resources/resource.cpp b/src/resources/resource.cpp
index cdff8060..17864cf5 100644
--- a/src/resources/resource.cpp
+++ b/src/resources/resource.cpp
@@ -31,7 +31,7 @@ void Resource::decRef(OrphanPolicy orphanPolicy)
{
// Reference may not already have reached zero
if (mRefCount == 0) {
- logger->log("Warning: mRefCount already zero for %s", mIdPath.c_str());
+ Log::warn("mRefCount already zero for %s", mIdPath.c_str());
assert(false);
}
diff --git a/src/resources/resourcemanager.cpp b/src/resources/resourcemanager.cpp
index e62407e3..2857c0df 100644
--- a/src/resources/resourcemanager.cpp
+++ b/src/resources/resourcemanager.cpp
@@ -45,7 +45,7 @@ ResourceManager *ResourceManager::instance = nullptr;
ResourceManager::ResourceManager()
{
- logger->log("Initializing resource manager...");
+ Log::info("Initializing resource manager...");
}
ResourceManager::~ResourceManager()
@@ -86,11 +86,11 @@ void ResourceManager::cleanUp(Resource *res)
{
if (res->mRefCount > 0)
{
- logger->log("ResourceManager::~ResourceManager() cleaning up %d "
- "reference%s to %s",
- res->mRefCount,
- (res->mRefCount == 1) ? "" : "s",
- res->mIdPath.c_str());
+ Log::info("ResourceManager::~ResourceManager() cleaning up %d "
+ "reference%s to %s",
+ res->mRefCount,
+ (res->mRefCount == 1) ? "" : "s",
+ res->mIdPath.c_str());
}
delete res;
@@ -118,7 +118,7 @@ void ResourceManager::cleanOrphans()
}
else
{
- logger->log("ResourceManager::release(%s)", res->mIdPath.c_str());
+ Log::info("ResourceManager::release(%s)", res->mIdPath.c_str());
iter = mOrphanedResources.erase(iter);
delete res; // delete only after removal from list, to avoid issues in recursion
}
@@ -129,12 +129,12 @@ void ResourceManager::cleanOrphans()
bool ResourceManager::addToSearchPath(const std::string &path, bool append)
{
- logger->log("Adding to PhysicsFS: %s", path.c_str());
if (!FS::addToSearchPath(path, append))
{
- logger->log("Error: %s", FS::getLastError());
+ Log::error("Couldn't add search path: %s (%s)", path.c_str(), FS::getLastError());
return false;
}
+ Log::info("Added search path: %s", path.c_str());
return true;
}
diff --git a/src/resources/settingsmanager.cpp b/src/resources/settingsmanager.cpp
index 9323d4d1..3dfb5eb4 100644
--- a/src/resources/settingsmanager.cpp
+++ b/src/resources/settingsmanager.cpp
@@ -27,6 +27,7 @@
#include "resources/monsterdb.h"
#include "resources/npcdb.h"
#include "resources/abilitydb.h"
+#include "resources/questdb.h"
#include "resources/statuseffectdb.h"
#include "net/net.h"
@@ -38,6 +39,9 @@
#include "log.h"
#include "units.h"
+#include <string>
+#include <set>
+
namespace SettingsManager
{
static std::string mSettingsFile;
@@ -53,6 +57,7 @@ namespace SettingsManager
hairDB.init();
itemDb->init();
MonsterDB::init();
+ QuestDB::init();
AbilityDB::init();
NPCDB::init();
EmoteDB::init();
@@ -96,6 +101,7 @@ namespace SettingsManager
NPCDB::unload();
AbilityDB::unload();
MonsterDB::unload();
+ QuestDB::unload();
if (itemDb)
itemDb->unload();
hairDB.unload();
@@ -107,7 +113,7 @@ namespace SettingsManager
*/
static bool loadFile(const std::string &filename)
{
- logger->log("Loading game settings from %s", filename.c_str());
+ Log::info("Loading game settings from %s", filename.c_str());
XML::Document doc(filename);
XML::Node node = doc.rootNode();
@@ -118,7 +124,7 @@ namespace SettingsManager
// FIXME: check root node's name when bjorn decides it's time
if (!node /*|| node.name() != "settings" */)
{
- logger->log("Settings Manager: %s is not a valid settings file!", filename.c_str());
+ Log::info("Settings Manager: %s is not a valid settings file!", filename.c_str());
return false;
}
@@ -156,7 +162,7 @@ namespace SettingsManager
// check if we're not entering a loop
if (mIncludedFiles.find(includeFile) != mIncludedFiles.end())
{
- logger->log("Warning: Circular include loop detecting while including %s from %s", includeFile.c_str(), filename.c_str());
+ Log::warn("Circular include loop detecting while including %s from %s", includeFile.c_str(), filename.c_str());
}
else
{
@@ -165,7 +171,7 @@ namespace SettingsManager
}
else
{
- logger->log("Warning: <include> element without 'file' or 'name' attribute in %s", filename.c_str());
+ Log::warn("<include> element without 'file' or 'name' attribute in %s", filename.c_str());
}
}
else if (childNode.name() == "option")
@@ -177,7 +183,7 @@ namespace SettingsManager
if (!name.empty())
paths.setValue(name, value);
else
- logger->log("Warning: option without a name found in %s", filename.c_str());
+ Log::warn("option without a name found in %s", filename.c_str());
}
else if (childNode.name() == "attribute")
{
@@ -221,6 +227,10 @@ namespace SettingsManager
{
NPCDB::readNPCNode(childNode, filename);
}
+ else if (childNode.name() == "var")
+ {
+ QuestDB::readQuestVarNode(childNode, filename);
+ }
else if (childNode.name() == "emote")
{
EmoteDB::readEmoteNode(childNode, filename);
diff --git a/src/resources/settingsmanager.h b/src/resources/settingsmanager.h
index 5b70f865..d9a6994c 100644
--- a/src/resources/settingsmanager.h
+++ b/src/resources/settingsmanager.h
@@ -20,10 +20,6 @@
#pragma once
-#include <string>
-#include <list>
-#include <set>
-
namespace SettingsManager
{
void load();
diff --git a/src/resources/soundeffect.cpp b/src/resources/soundeffect.cpp
index 19d7a820..1b0492d7 100644
--- a/src/resources/soundeffect.cpp
+++ b/src/resources/soundeffect.cpp
@@ -36,7 +36,7 @@ SoundEffect *SoundEffect::load(SDL_RWops *rw)
return new SoundEffect(soundEffect);
}
- logger->log("Error, failed to load sound effect: %s", Mix_GetError());
+ Log::info("Error, failed to load sound effect: %s", Mix_GetError());
return nullptr;
}
diff --git a/src/resources/spritedef.cpp b/src/resources/spritedef.cpp
index 85e5e566..0e7f12dd 100644
--- a/src/resources/spritedef.cpp
+++ b/src/resources/spritedef.cpp
@@ -44,7 +44,7 @@ Action *SpriteDef::getAction(const std::string &action) const
if (i == mActions.end())
{
- logger->log("Warning: no action \"%s\" defined!", action.c_str());
+ Log::warn("No action \"%s\" defined!", action.c_str());
return nullptr;
}
@@ -66,7 +66,7 @@ SpriteDef *SpriteDef::load(const std::string &animationFile, int variant)
if (!rootNode || rootNode.name() != "sprite")
{
- logger->log("Error, failed to parse %s", animationFile.c_str());
+ Log::info("Error, failed to parse %s", animationFile.c_str());
std::string errorFile = paths.getStringValue("sprites")
+ paths.getStringValue("spriteErrorFile");
@@ -157,7 +157,7 @@ void SpriteDef::loadImageSet(XML::Node node, const std::string &palettes)
auto imageSet = resman->getImageSet(imageSrc, width, height);
if (!imageSet)
{
- logger->error(strprintf("Couldn't load imageset (%s)!",
+ Log::critical(strprintf("Couldn't load imageset (%s)!",
imageSrc.c_str()));
}
@@ -174,16 +174,16 @@ void SpriteDef::loadAction(XML::Node node, int variant_offset)
auto si = mImageSets.find(imageSetName);
if (si == mImageSets.end())
{
- logger->log("Warning: imageset \"%s\" not defined in %s",
- imageSetName.c_str(), getIdPath().c_str());
+ Log::warn("imageset \"%s\" not defined in %s",
+ imageSetName.c_str(), getIdPath().c_str());
return;
}
ImageSet *imageSet = si->second;
if (actionName == SpriteAction::INVALID)
{
- logger->log("Warning: Unknown action \"%s\" defined in %s",
- actionName.c_str(), getIdPath().c_str());
+ Log::warn("Unknown action \"%s\" defined in %s",
+ actionName.c_str(), getIdPath().c_str());
return;
}
auto *action = new Action;
@@ -215,8 +215,8 @@ void SpriteDef::loadAnimation(XML::Node animationNode,
if (directionType == DIRECTION_INVALID)
{
- logger->log("Warning: Unknown direction \"%s\" used in %s",
- directionName.c_str(), getIdPath().c_str());
+ Log::warn("Unknown direction \"%s\" used in %s",
+ directionName.c_str(), getIdPath().c_str());
return;
}
@@ -239,7 +239,7 @@ void SpriteDef::loadAnimation(XML::Node animationNode,
if (index < 0)
{
- logger->log("No valid value for 'index'");
+ Log::info("No valid value for 'index'");
continue;
}
@@ -247,7 +247,7 @@ void SpriteDef::loadAnimation(XML::Node animationNode,
if (!img)
{
- logger->log("No image at index %d", index + variant_offset);
+ Log::info("No image at index %d", index + variant_offset);
continue;
}
@@ -260,7 +260,7 @@ void SpriteDef::loadAnimation(XML::Node animationNode,
if (start < 0 || end < 0)
{
- logger->log("No valid value for 'start' or 'end'");
+ Log::info("No valid value for 'start' or 'end'");
continue;
}
@@ -270,7 +270,7 @@ void SpriteDef::loadAnimation(XML::Node animationNode,
if (!img)
{
- logger->log("No image at index %d", start + variant_offset);
+ Log::info("No image at index %d", start + variant_offset);
break;
}
@@ -295,8 +295,8 @@ void SpriteDef::includeSprite(XML::Node includeNode)
if (processedFiles.find(filename) != processedFiles.end())
{
- logger->log("Error, Tried to include %s which already is included.",
- filename.c_str());
+ Log::info("Error, Tried to include %s which already is included.",
+ filename.c_str());
return;
}
processedFiles.insert(filename);
@@ -306,7 +306,7 @@ void SpriteDef::includeSprite(XML::Node includeNode)
if (!rootNode || rootNode.name() != "sprite")
{
- logger->log("Error, no sprite root node in %s", filename.c_str());
+ Log::info("Error, no sprite root node in %s", filename.c_str());
return;
}
diff --git a/src/resources/theme.cpp b/src/resources/theme.cpp
index 8e4a07be..0c332902 100644
--- a/src/resources/theme.cpp
+++ b/src/resources/theme.cpp
@@ -25,7 +25,6 @@
#include "configuration.h"
#include "log.h"
-#include "textrenderer.h"
#include "resources/dye.h"
#include "resources/image.h"
@@ -75,7 +74,7 @@ ThemeInfo::ThemeInfo(const std::string &path)
if (rootNode.attribute("name", name) && !name.empty())
this->doc = std::move(doc);
else
- logger->log("Error: Theme '%s' has no name!", path.c_str());
+ Log::error("Theme '%s' has no name!", path.c_str());
}
std::string ThemeInfo::getFullPath() const
@@ -228,15 +227,14 @@ void Skin::updateAlpha(float alpha)
Theme::Theme(const ThemeInfo &themeInfo)
: mThemePath(themeInfo.getFullPath())
- , mProgressColors(THEME_PROG_END)
{
listen(Event::ConfigChannel);
readTheme(themeInfo);
if (mPalettes.empty())
{
- logger->log("Error, theme did not define any palettes: %s",
- themeInfo.getPath().c_str());
+ Log::info("Error, theme did not define any palettes: %s",
+ themeInfo.getPath().c_str());
// Avoid crashing
mPalettes.emplace_back(THEME_COLORS_END);
@@ -377,9 +375,12 @@ void Theme::drawSkin(Graphics *graphics, SkinType type, const WidgetState &state
getSkin(type).draw(graphics, state);
}
-void Theme::drawProgressBar(Graphics *graphics, const gcn::Rectangle &area,
- const gcn::Color &color, float progress,
- const std::string &text) const
+void Theme::drawProgressBar(Graphics *graphics,
+ const gcn::Rectangle &area,
+ const gcn::Color &color,
+ float progress,
+ const std::string &text,
+ ProgressPalette progressType) const
{
gcn::Font *oldFont = graphics->getFont();
gcn::Color oldColor = graphics->getColor();
@@ -408,17 +409,21 @@ void Theme::drawProgressBar(Graphics *graphics, const gcn::Rectangle &area,
{
if (auto skinState = skin.getState(widgetState.flags))
{
- auto font = skinState->textFormat.bold ? boldFont : gui->getFont();
+ const TextFormat *textFormat = &skinState->textFormat;
+
+ if (progressType < THEME_PROG_END && mProgressTextFormats[progressType])
+ textFormat = &(*mProgressTextFormats[progressType]);
+
+ auto font = textFormat->bold ? boldFont : gui->getFont();
const int textX = area.x + area.width / 2;
const int textY = area.y + (area.height - font->getHeight()) / 2;
- TextRenderer::renderText(graphics,
- text,
- textX,
- textY,
- gcn::Graphics::CENTER,
- font,
- skinState->textFormat);
+ graphics->drawText(text,
+ textX,
+ textY,
+ gcn::Graphics::CENTER,
+ font,
+ *textFormat);
}
}
@@ -479,7 +484,7 @@ static bool check(bool value, const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
- logger->vlog(msg, ap);
+ Log::vinfo(msg, ap);
va_end(ap);
}
return !value;
@@ -487,9 +492,9 @@ static bool check(bool value, const char *msg, ...)
bool Theme::readTheme(const ThemeInfo &themeInfo)
{
- logger->log("Loading %s theme from '%s'...",
- themeInfo.getName().c_str(),
- themeInfo.getPath().c_str());
+ Log::info("Loading %s theme from '%s'...",
+ themeInfo.getName().c_str(),
+ themeInfo.getPath().c_str());
XML::Node rootNode = themeInfo.getDocument().rootNode();
@@ -507,10 +512,10 @@ bool Theme::readTheme(const ThemeInfo &themeInfo)
else if (childNode.name() == "icon")
readIconNode(childNode);
else
- logger->log("Theme: Unknown node '%s'!", childNode.name().data());
+ Log::info("Theme: Unknown node '%s'!", childNode.name().data());
}
- logger->log("Finished loading theme.");
+ Log::info("Finished loading theme.");
for (auto &[_, skin] : mSkins)
skin.updateAlpha(mAlpha);
@@ -588,9 +593,8 @@ static void readSkinStateRectNode(XML::Node node, SkinState &state)
node.attribute("fill", rect.filled);
}
-static void readSkinStateTextNode(XML::Node node, SkinState &state)
+static void readTextNode(XML::Node node, TextFormat &textFormat)
{
- auto &textFormat = state.textFormat;
node.attribute("bold", textFormat.bold);
node.attribute("color", textFormat.color);
node.attribute("outlineColor", textFormat.outlineColor);
@@ -625,7 +629,7 @@ void Theme::readSkinStateNode(XML::Node node, Skin &skin) const
else if (childNode.name() == "rect")
readSkinStateRectNode(childNode, state);
else if (childNode.name() == "text")
- readSkinStateTextNode(childNode, state);
+ readTextNode(childNode, state.textFormat);
}
skin.addState(std::move(state));
@@ -689,7 +693,7 @@ void Theme::readSkinStateImgNode(XML::Node node, SkinState &state) const
border.right = right;
border.top = top;
border.bottom = bottom;
- border.image = image->getSubImage(x, y, width, height);
+ border.image.reset(image->getSubImage(x, y, width, height));
node.attribute("fill", border.fillMode);
}
@@ -705,7 +709,7 @@ inline void fromString(const char *str, gcn::Color &value)
if (strlen(str) < 7 || str[0] != '#')
{
error:
- logger->log("Error, invalid theme color palette: %s", str);
+ Log::info("Error, invalid theme color palette: %s", str);
value = gcn::Color(0, 0, 0);
return;
}
@@ -788,6 +792,7 @@ static int readColorId(const std::string &id)
"WHISPER_TAB",
"BACKGROUND",
"HIGHLIGHT",
+ "HIGHLIGHT_TEXT",
"TAB_FLASH",
"SHOP_WARNING",
"ITEM_EQUIPPED",
@@ -873,7 +878,7 @@ void Theme::readPaletteNode(XML::Node node)
{
int paletteId;
if (node.attribute("id", paletteId) && static_cast<size_t>(paletteId) != mPalettes.size())
- logger->log("Theme: Non-consecutive palette 'id' attribute with value %d!", paletteId);
+ Log::info("Theme: Non-consecutive palette 'id' attribute with value %d!", paletteId);
Palette &palette = mPalettes.emplace_back(THEME_COLORS_END);
@@ -882,7 +887,7 @@ void Theme::readPaletteNode(XML::Node node)
if (childNode.name() == "color")
readColorNode(childNode, palette);
else
- logger->log("Theme: Unknown node '%s'!", childNode.name().data());
+ Log::info("Theme: Unknown node '%s'!", childNode.name().data());
}
}
@@ -916,5 +921,15 @@ void Theme::readProgressBarNode(XML::Node node)
if (check(id >= 0, "Theme: 'progress' element has unknown 'id' attribute: '%s'!", idStr.c_str()))
return;
- mProgressColors[id] = std::make_unique<DyePalette>(node.getProperty("color", std::string()));
+ std::string color;
+ if (node.attribute("color", color))
+ mProgressColors[id] = std::make_unique<DyePalette>(color);
+
+ for (auto childNode : node.children())
+ {
+ if (childNode.name() == "text")
+ readTextNode(childNode, mProgressTextFormats[id].emplace());
+ else
+ Log::info("Theme: Unknown node '%s' in progressbar!", childNode.name().data());
+ }
}
diff --git a/src/resources/theme.h b/src/resources/theme.h
index bfd3b33a..fbcb263b 100644
--- a/src/resources/theme.h
+++ b/src/resources/theme.h
@@ -30,6 +30,7 @@
#include "resources/image.h"
#include "utils/xml.h"
+#include <array>
#include <map>
#include <memory>
#include <optional>
@@ -227,6 +228,7 @@ class Theme : public EventListener
WHISPER_TAB,
BACKGROUND,
HIGHLIGHT,
+ HIGHLIGHT_TEXT,
TAB_FLASH,
SHOP_WARNING,
ITEM_EQUIPPED,
@@ -283,7 +285,7 @@ class Theme : public EventListener
static const gcn::Color &getThemeColor(int type);
static gcn::Color getProgressColor(int type, float progress);
-
+
const Palette &getPalette(size_t index) const;
/**
@@ -305,7 +307,8 @@ class Theme : public EventListener
const gcn::Rectangle &area,
const gcn::Color &color,
float progress,
- const std::string &text = std::string()) const;
+ const std::string &text = std::string(),
+ ProgressPalette progressType = ProgressPalette::THEME_PROG_END) const;
const Skin &getSkin(SkinType skinType) const;
@@ -357,5 +360,6 @@ class Theme : public EventListener
float mAlpha = 1.0;
std::vector<Palette> mPalettes;
- std::vector<std::unique_ptr<DyePalette>> mProgressColors;
+ std::array<std::unique_ptr<DyePalette>, THEME_PROG_END> mProgressColors;
+ std::array<std::optional<TextFormat>, THEME_PROG_END> mProgressTextFormats;
};
diff --git a/src/sdlgraphics.cpp b/src/sdlgraphics.cpp
index 9256471a..7d75e7f2 100644
--- a/src/sdlgraphics.cpp
+++ b/src/sdlgraphics.cpp
@@ -72,16 +72,17 @@ std::unique_ptr<Graphics> SDLGraphics::create(SDL_Window *window, const VideoSet
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, rendererFlags);
if (!renderer)
{
- logger->error(strprintf("Failed to create renderer: %s",
+ Log::critical(strprintf("Failed to create renderer: %s",
SDL_GetError()));
return {};
}
- return std::make_unique<SDLGraphics>(renderer);
+ return std::make_unique<SDLGraphics>(window, renderer);
}
-SDLGraphics::SDLGraphics(SDL_Renderer *renderer)
- : mRenderer(renderer)
+SDLGraphics::SDLGraphics(SDL_Window *window, SDL_Renderer *renderer)
+ : mWindow(window)
+ , mRenderer(renderer)
{
Image::setRenderer(mRenderer);
@@ -90,25 +91,25 @@ SDLGraphics::SDLGraphics(SDL_Renderer *renderer)
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
if (const char *driver = SDL_GetCurrentVideoDriver())
- logger->log("Using video driver: %s", driver);
+ Log::info("Using video driver: %s", driver);
else
- logger->log("Using video driver: not initialized");
+ Log::info("Using video driver: not initialized");
SDL_RendererInfo info;
if (SDL_GetRendererInfo(renderer, &info) == 0) {
- logger->log("Using renderer: %s", info.name);
-
- logger->log("The renderer is a software fallback: %s",
- (info.flags & SDL_RENDERER_SOFTWARE) ? "yes" : "no");
- logger->log("The renderer is hardware accelerated: %s",
- (info.flags & SDL_RENDERER_ACCELERATED) ? "yes" : "no");
- logger->log("Vsync: %s",
- (info.flags & SDL_RENDERER_PRESENTVSYNC) ? "on" : "off");
- logger->log("Renderer supports rendering to texture: %s",
- (info.flags & SDL_RENDERER_TARGETTEXTURE) ? "yes" : "no");
- logger->log("Max texture size: %dx%d",
- info.max_texture_width, info.max_texture_height);
+ Log::info("Using renderer: %s", info.name);
+
+ Log::info("The renderer is a software fallback: %s",
+ (info.flags & SDL_RENDERER_SOFTWARE) ? "yes" : "no");
+ Log::info("The renderer is hardware accelerated: %s",
+ (info.flags & SDL_RENDERER_ACCELERATED) ? "yes" : "no");
+ Log::info("Vsync: %s",
+ (info.flags & SDL_RENDERER_PRESENTVSYNC) ? "on" : "off");
+ Log::info("Renderer supports rendering to texture: %s",
+ (info.flags & SDL_RENDERER_TARGETTEXTURE) ? "yes" : "no");
+ Log::info("Max texture size: %dx%d",
+ info.max_texture_width, info.max_texture_height);
}
}
@@ -245,6 +246,7 @@ void SDLGraphics::drawRescaledImagePattern(const Image *image,
void SDLGraphics::updateScreen()
{
SDL_RenderPresent(mRenderer);
+ SDL_ShowWindow(mWindow);
}
void SDLGraphics::windowToLogical(int windowX, int windowY,
diff --git a/src/sdlgraphics.h b/src/sdlgraphics.h
index 7cdea414..057fcdc3 100644
--- a/src/sdlgraphics.h
+++ b/src/sdlgraphics.h
@@ -33,7 +33,7 @@ public:
static std::unique_ptr<Graphics> create(SDL_Window *window,
const VideoSettings &settings);
- SDLGraphics(SDL_Renderer *renderer);
+ SDLGraphics(SDL_Window *window, SDL_Renderer *renderer);
~SDLGraphics() override;
void setVSync(bool sync) override;
@@ -83,5 +83,6 @@ protected:
void updateClipRect() override;
private:
+ SDL_Window *mWindow = nullptr;
SDL_Renderer *mRenderer = nullptr;
};
diff --git a/src/sound.cpp b/src/sound.cpp
index c97951a3..20d3810a 100644
--- a/src/sound.cpp
+++ b/src/sound.cpp
@@ -64,11 +64,11 @@ void Sound::init()
if (mInstalled)
return;
- logger->log("Sound::init() Initializing sound...");
+ Log::info("Sound::init() Initializing sound...");
if (SDL_InitSubSystem(SDL_INIT_AUDIO) == -1)
{
- logger->log("Sound::init() Failed to initialize audio subsystem");
+ Log::info("Sound::init() Failed to initialize audio subsystem");
return;
}
@@ -78,8 +78,8 @@ void Sound::init()
MIX_DEFAULT_CHANNELS, audioBuffer);
if (res < 0)
{
- logger->log("Sound::init Could not initialize audio: %s",
- Mix_GetError());
+ Log::info("Sound::init Could not initialize audio: %s",
+ Mix_GetError());
return;
}
@@ -122,18 +122,18 @@ void Sound::info()
case AUDIO_S16MSB: format = "S16MSB"; break;
}
- logger->log("Sound::info() SDL_mixer: %i.%i.%i (compiled)",
+ Log::info("Sound::info() SDL_mixer: %i.%i.%i (compiled)",
compiledVersion.major,
compiledVersion.minor,
compiledVersion.patch);
- logger->log("Sound::info() SDL_mixer: %i.%i.%i (linked)",
+ Log::info("Sound::info() SDL_mixer: %i.%i.%i (linked)",
linkedVersion->major,
linkedVersion->minor,
linkedVersion->patch);
- logger->log("Sound::info() Driver: %s", driver);
- logger->log("Sound::info() Format: %s", format);
- logger->log("Sound::info() Rate: %i", rate);
- logger->log("Sound::info() Channels: %i", channels);
+ Log::info("Sound::info() Driver: %s", driver);
+ Log::info("Sound::info() Format: %s", format);
+ Log::info("Sound::info() Rate: %i", rate);
+ Log::info("Sound::info() Channels: %i", channels);
}
void Sound::setMusicVolume(int volume)
@@ -173,7 +173,7 @@ void Sound::stopMusic()
if (!mInstalled)
return;
- logger->log("Sound::stopMusic()");
+ Log::info("Sound::stopMusic()");
haltMusic();
}
@@ -201,7 +201,7 @@ void Sound::fadeOutMusic(int ms)
if (!mInstalled)
return;
- logger->log("Sound::fadeOutMusic() Fading-out (%i ms)", ms);
+ Log::info("Sound::fadeOutMusic() Fading-out (%i ms)", ms);
if (mMusic)
{
@@ -260,7 +260,7 @@ void Sound::playSfx(const std::string &path, int x, int y)
if (ResourceRef<SoundEffect> sound = resman->getSoundEffect(tmpPath))
{
- logger->log("Sound::playSfx() Playing: %s", path.c_str());
+ Log::info("Sound::playSfx() Playing: %s", path.c_str());
int vol = 120;
if (local_player && (x > 0 || y > 0))
@@ -299,7 +299,7 @@ void Sound::close()
return;
haltMusic();
- logger->log("Sound::close() Shutting down sound...");
+ Log::info("Sound::close() Shutting down sound...");
Mix_CloseAudio();
mInstalled = false;
diff --git a/src/text.cpp b/src/text.cpp
index 4698aa87..14ffed18 100644
--- a/src/text.cpp
+++ b/src/text.cpp
@@ -23,7 +23,6 @@
#include "text.h"
#include "textmanager.h"
-#include "textrenderer.h"
#include "gui/gui.h"
@@ -94,6 +93,7 @@ void Text::adviseXY(int x, int y)
void Text::draw(gcn::Graphics *graphics, int xOff, int yOff)
{
+ auto g = static_cast<Graphics *>(graphics);
if (mIsSpeech)
{
WidgetState state;
@@ -103,7 +103,7 @@ void Text::draw(gcn::Graphics *graphics, int xOff, int yOff)
state.height = mHeight + 10;
auto theme = gui->getTheme();
- theme->drawSkin(static_cast<Graphics *>(graphics), SkinType::SpeechBubble, state);
+ theme->drawSkin(g, SkinType::SpeechBubble, state);
/*
if (mWidth >= 15)
@@ -115,7 +115,7 @@ void Text::draw(gcn::Graphics *graphics, int xOff, int yOff)
*/
}
- TextRenderer::renderText(graphics, mText,
+ g->drawText(mText,
mX - xOff, mY - yOff, gcn::Graphics::LEFT,
*mColor, mFont, !mIsSpeech, true);
}
diff --git a/src/textparticle.cpp b/src/textparticle.cpp
index eb477217..4f6f1df4 100644
--- a/src/textparticle.cpp
+++ b/src/textparticle.cpp
@@ -21,7 +21,7 @@
#include "textparticle.h"
-#include "textrenderer.h"
+#include "graphics.h"
#include <guichan/color.hpp>
@@ -47,7 +47,7 @@ bool TextParticle::draw(Graphics *graphics, int offsetX, int offsetY) const
gcn::Color color = *mColor;
color.a = getCurrentAlpha() * 255;
- TextRenderer::renderText(graphics, mText,
+ graphics->drawText(mText,
screenX, screenY, gcn::Graphics::CENTER,
color, mTextFont, mOutline, false);
diff --git a/src/textrenderer.h b/src/textrenderer.h
deleted file mode 100644
index de4fcff2..00000000
--- a/src/textrenderer.h
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Text Renderer
- * Copyright (C) 2009 The Mana World Development Team
- * Copyright (C) 2009-2012 The Mana Developers
- *
- * This file is part of The Mana Client.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include "resources/theme.h"
-
-#include <guichan/exception.hpp>
-#include <guichan/font.hpp>
-
-/**
- * Class for text rendering which can apply an outline and shadow.
- */
-class TextRenderer
-{
-public:
- /**
- * Renders a specified text.
- */
- static void renderText(gcn::Graphics *graphics,
- const std::string &text,
- int x, int y,
- gcn::Graphics::Alignment alignment,
- const gcn::Color &color,
- gcn::Font *font,
- bool outline = false,
- bool shadow = false,
- const std::optional<gcn::Color> &outlineColor = {},
- const std::optional<gcn::Color> &shadowColor = {})
- {
- switch (alignment)
- {
- case gcn::Graphics::LEFT:
- break;
- case gcn::Graphics::CENTER:
- x -= font->getWidth(text) / 2;
- break;
- case gcn::Graphics::RIGHT:
- x -= font->getWidth(text);
- break;
- default:
- throw GCN_EXCEPTION("Unknown alignment.");
- }
-
- // Text shadow
- if (shadow)
- {
- if (shadowColor)
- {
- graphics->setColor(*shadowColor);
- }
- else
- {
- auto sc = Theme::getThemeColor(Theme::SHADOW);
- sc.a = color.a / 2;
- graphics->setColor(sc);
- }
-
- if (outline)
- font->drawString(graphics, text, x + 2, y + 2);
- else
- font->drawString(graphics, text, x + 1, y + 1);
- }
-
- if (outline)
- {
- /*
- graphics->setColor(guiPalette->getColor(Palette::OUTLINE,
- alpha/4));
- // TODO: Reanable when we can draw it nicely in software mode
- font->drawString(graphics, text, x + 2, y + 2);
- font->drawString(graphics, text, x + 1, y + 2);
- font->drawString(graphics, text, x + 2, y + 1);
- */
-
- // Text outline
- if (outlineColor)
- {
- graphics->setColor(*outlineColor);
- }
- else
- {
- auto oc = Theme::getThemeColor(Theme::OUTLINE);
- oc.a = color.a;
- graphics->setColor(oc);
- }
-
- font->drawString(graphics, text, x + 1, y);
- font->drawString(graphics, text, x - 1, y);
- font->drawString(graphics, text, x, y + 1);
- font->drawString(graphics, text, x, y - 1);
- }
-
- graphics->setColor(color);
- font->drawString(graphics, text, x, y);
- }
-
- /**
- * Renders a specified text.
- */
- static void renderText(gcn::Graphics *graphics,
- const std::string &text,
- int x,
- int y,
- gcn::Graphics::Alignment align,
- gcn::Font *font,
- const TextFormat &format)
- {
- renderText(graphics,
- text,
- x,
- y,
- align,
- format.color,
- font,
- format.outlineColor.has_value(),
- format.shadowColor.has_value(),
- format.outlineColor,
- format.shadowColor);
- }
-};
diff --git a/src/units.cpp b/src/units.cpp
index ee8fa6ea..1ec81cb0 100644
--- a/src/units.cpp
+++ b/src/units.cpp
@@ -123,8 +123,8 @@ void Units::readUnitNode(XML::Node node, const std::string &filename)
}
else
{
- logger->log("Error bad unit count: %d for %s in %s",
- ul.count, ul.symbol.c_str(), bu.symbol.c_str());
+ Log::info("Error bad unit count: %d for %s in %s",
+ ul.count, ul.symbol.c_str(), bu.symbol.c_str());
}
}
}
@@ -139,7 +139,7 @@ void Units::readUnitNode(XML::Node node, const std::string &filename)
else if (type == "currency")
units[UNIT_CURRENCY] = ud;
else
- logger->log("Error unknown unit type: %s in %s", type.c_str(), filename.c_str());
+ Log::info("Error unknown unit type: %s in %s", type.c_str(), filename.c_str());
}
diff --git a/src/utils/mutex.h b/src/utils/mutex.h
index a0c72e95..b6c4e88d 100644
--- a/src/utils/mutex.h
+++ b/src/utils/mutex.h
@@ -75,13 +75,13 @@ inline Mutex::~Mutex()
inline void Mutex::lock()
{
if (SDL_mutexP(mMutex) == -1)
- logger->log("Mutex locking failed: %s", SDL_GetError());
+ Log::info("Mutex locking failed: %s", SDL_GetError());
}
inline void Mutex::unlock()
{
if (SDL_mutexV(mMutex) == -1)
- logger->log("Mutex unlocking failed: %s", SDL_GetError());
+ Log::info("Mutex unlocking failed: %s", SDL_GetError());
}
diff --git a/src/utils/sha256.cpp b/src/utils/sha256.cpp
index 0e44af7a..a1339d14 100644
--- a/src/utils/sha256.cpp
+++ b/src/utils/sha256.cpp
@@ -3,7 +3,7 @@
* Copyright (C) 2008-2009 The Mana World Development Team
* Copyright (C) 2009-2012 The Mana Developers
*
- * This file has been slighly modified as part of The Mana Client.
+ * This file has been slightly modified as part of The Mana Client.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2, as
diff --git a/src/utils/stringutils.cpp b/src/utils/stringutils.cpp
index 05b6982c..7b05b365 100644
--- a/src/utils/stringutils.cpp
+++ b/src/utils/stringutils.cpp
@@ -152,6 +152,32 @@ bool getBoolFromString(std::string text, bool def)
return def;
}
+// Overload for std::vector<int> to parse comma-separated integers
+void fromString(const char *str, std::vector<int> &value)
+{
+ value.clear();
+
+ const char *p = str;
+ while (*p)
+ {
+ while (*p == ' ' || *p == ',')
+ ++p; // skip spaces and commas
+ if (!*p)
+ break;
+ char *end = nullptr;
+ int v = static_cast<int>(strtol(p, &end, 10));
+ if (end != p)
+ {
+ value.push_back(v);
+ p = end;
+ }
+ else
+ {
+ ++p; // skip invalid character to avoid infinite loop
+ }
+ }
+}
+
std::string autocomplete(const std::vector<std::string> &candidates,
std::string base)
{
diff --git a/src/utils/stringutils.h b/src/utils/stringutils.h
index 53c36bc6..885ddcb8 100644
--- a/src/utils/stringutils.h
+++ b/src/utils/stringutils.h
@@ -117,9 +117,18 @@ std::string &removeColors(std::string &msg);
/**
* Returns whether a string starts with a given prefix.
*/
-inline bool startsWith(const std::string &str, const char *prefix)
+inline bool startsWith(std::string_view str, std::string_view prefix)
{
- return str.rfind(prefix, 0) == 0;
+ return str.substr(0, prefix.size()) == prefix;
+}
+
+/**
+ * Returns whether a string ends with a given suffix.
+ */
+inline bool endsWith(std::string_view str, std::string_view suffix)
+{
+ return str.size() >= suffix.size() &&
+ str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
}
/**
@@ -193,6 +202,8 @@ inline void fromString(const char *str, bool &value)
value = getBoolFromString(str);
}
+void fromString(const char *str, std::vector<int> &value);
+
template<typename T>
struct FromString<T, std::enable_if_t<std::is_enum_v<T>>>
{
diff --git a/src/utils/xml.cpp b/src/utils/xml.cpp
index c408c9c2..34bcc52b 100644
--- a/src/utils/xml.cpp
+++ b/src/utils/xml.cpp
@@ -43,13 +43,13 @@ namespace XML
auto *context = static_cast<XMLContext*>(ctx);
if (context)
- logger->log("Error in XML file '%s' on line %d",
- context->file.c_str(), error->line);
+ Log::info("Error in XML file '%s' on line %d",
+ context->file.c_str(), error->line);
else
- logger->log("Error in unknown XML file on line %d",
- error->line);
+ Log::info("Error in unknown XML file on line %d",
+ error->line);
- logger->log("%s", error->message);
+ Log::info("%s", error->message);
// No need to keep errors around
xmlCtxtResetLastError(error->ctxt);
@@ -76,11 +76,11 @@ namespace XML
SDL_free(data);
if (!mDoc)
- logger->log("Error parsing XML file %s", filename.c_str());
+ Log::info("Error parsing XML file %s", filename.c_str());
}
else
{
- logger->log("Error loading %s: %s", filename.c_str(), SDL_GetError());
+ Log::info("Error loading %s: %s", filename.c_str(), SDL_GetError());
}
xmlSetStructuredErrorFunc(nullptr, xmlLogger);
@@ -109,7 +109,7 @@ namespace XML
mWriter = xmlNewTextWriterFilename(fileName.c_str(), 0);
if (!mWriter)
{
- logger->log("Error creating XML writer for file %s", fileName.c_str());
+ Log::info("Error creating XML writer for file %s", fileName.c_str());
return;
}
diff --git a/src/utils/zlib.cpp b/src/utils/zlib.cpp
index f78b235e..08281bcc 100644
--- a/src/utils/zlib.cpp
+++ b/src/utils/zlib.cpp
@@ -107,19 +107,19 @@ int inflateMemory(unsigned char *in, unsigned int inLength,
{
if (ret == Z_MEM_ERROR)
{
- logger->log("Error: Out of memory while decompressing data!");
+ Log::error("Out of memory while decompressing data!");
}
else if (ret == Z_VERSION_ERROR)
{
- logger->log("Error: Incompatible zlib version!");
+ Log::error("Incompatible zlib version!");
}
else if (ret == Z_DATA_ERROR)
{
- logger->log("Error: Incorrect zlib compressed data!");
+ Log::error("Incorrect zlib compressed data!");
}
else
{
- logger->log("Error: Unknown error while decompressing data!");
+ Log::error("Unknown error while decompressing data!");
}
free(out);
diff --git a/src/video.cpp b/src/video.cpp
index 7ab21d12..c71a9097 100644
--- a/src/video.cpp
+++ b/src/video.cpp
@@ -65,7 +65,7 @@ Graphics *Video::initialize(const VideoSettings &settings)
if (!initDisplayModes())
{
- logger->log("Failed to initialize display modes: %s", SDL_GetError());
+ Log::info("Failed to initialize display modes: %s", SDL_GetError());
}
SDL_DisplayMode displayMode;
@@ -81,7 +81,7 @@ Graphics *Video::initialize(const VideoSettings &settings)
if (SDL_GetClosestDisplayMode(mSettings.display, &requestedMode, &displayMode) == nullptr)
{
- logger->log("SDL_GetClosestDisplayMode failed: %s, falling back to borderless mode", SDL_GetError());
+ Log::info("SDL_GetClosestDisplayMode failed: %s, falling back to borderless mode", SDL_GetError());
mSettings.windowMode = WindowMode::WindowedFullscreen;
}
}
@@ -92,6 +92,10 @@ Graphics *Video::initialize(const VideoSettings &settings)
switch (mSettings.windowMode)
{
case WindowMode::Windowed:
+ // In windowed mode, the window is initially created hidden, we'll show
+ // it once we have finished setting it up and done an initial paint to
+ // avoid startup flicker on X11 and Windows.
+ windowFlags |= SDL_WINDOW_HIDDEN;
break;
case WindowMode::Fullscreen:
windowFlags |= SDL_WINDOW_FULLSCREEN;
@@ -99,6 +103,10 @@ Graphics *Video::initialize(const VideoSettings &settings)
break;
case WindowMode::WindowedFullscreen:
windowFlags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
+ // On Windows, fullscreen desktop with OpenGL actually flickers worse
+ // when the window is initially hidden.
+ if (!mSettings.openGL)
+ windowFlags |= SDL_WINDOW_HIDDEN;
videoMode = "windowed fullscreen";
break;
}
@@ -106,10 +114,10 @@ Graphics *Video::initialize(const VideoSettings &settings)
if (mSettings.openGL)
windowFlags |= SDL_WINDOW_OPENGL;
- logger->log("Setting video mode %dx%d %s",
- mSettings.width,
- mSettings.height,
- videoMode);
+ Log::info("Setting video mode %dx%d %s",
+ mSettings.width,
+ mSettings.height,
+ videoMode);
mWindow = SDL_CreateWindow("Mana",
SDL_WINDOWPOS_UNDEFINED,
@@ -120,8 +128,7 @@ Graphics *Video::initialize(const VideoSettings &settings)
if (!mWindow)
{
- logger->error(strprintf("Failed to create window: %s",
- SDL_GetError()));
+ Log::critical(strprintf("Failed to create window: %s", SDL_GetError()));
return nullptr;
}
@@ -131,7 +138,7 @@ Graphics *Video::initialize(const VideoSettings &settings)
{
if (SDL_SetWindowDisplayMode(mWindow, &displayMode) != 0)
{
- logger->log("SDL_SetWindowDisplayMode failed: %s", SDL_GetError());
+ Log::info("SDL_SetWindowDisplayMode failed: %s", SDL_GetError());
}
}
@@ -144,7 +151,7 @@ Graphics *Video::initialize(const VideoSettings &settings)
mGraphics = OpenGLGraphics::create(mWindow, mSettings);
if (!mGraphics)
{
- logger->log("Failed to create OpenGL context, falling back to SDL renderer: %s",
+ Log::info("Failed to create OpenGL context, falling back to SDL renderer: %s",
SDL_GetError());
mSettings.openGL = false;
}
@@ -170,7 +177,7 @@ bool Video::apply(const VideoSettings &settings)
SDL_DisplayMode displayMode;
if (SDL_GetWindowDisplayMode(mWindow, &displayMode) != 0)
{
- logger->error(strprintf("SDL_GetCurrentDisplayMode failed: %s", SDL_GetError()));
+ Log::critical(strprintf("SDL_GetCurrentDisplayMode failed: %s", SDL_GetError()));
return false;
}
@@ -188,7 +195,7 @@ bool Video::apply(const VideoSettings &settings)
if (SDL_SetWindowDisplayMode(mWindow, &displayMode) != 0)
{
- logger->error(strprintf("SDL_SetWindowDisplayMode failed: %s", SDL_GetError()));
+ Log::critical(strprintf("SDL_SetWindowDisplayMode failed: %s", SDL_GetError()));
return false;
}
}
@@ -209,7 +216,7 @@ bool Video::apply(const VideoSettings &settings)
if (SDL_SetWindowFullscreen(mWindow, windowFlags) != 0)
{
- logger->error(strprintf("SDL_SetWindowFullscreen failed: %s", SDL_GetError()));
+ Log::critical(strprintf("SDL_SetWindowFullscreen failed: %s", SDL_GetError()));
return false;
}
@@ -235,12 +242,12 @@ bool Video::apply(const VideoSettings &settings)
return true;
}
-void Video::windowSizeChanged(int width, int height)
+void Video::updateWindowSize()
{
- mSettings.width = width;
- mSettings.height = height;
-
- mGraphics->updateSize(width, height, mSettings.scale());
+ SDL_GetWindowSize(mWindow, &mSettings.width, &mSettings.height);
+ mGraphics->updateSize(mSettings.width,
+ mSettings.height,
+ mSettings.scale());
}
bool Video::initDisplayModes()
diff --git a/src/video.h b/src/video.h
index 47aed627..03e0dc4f 100644
--- a/src/video.h
+++ b/src/video.h
@@ -89,9 +89,9 @@ public:
bool apply(const VideoSettings &settings);
/**
- * Handle a change in window size, possibly adjusting the scale.
+ * Handles a change in window size, possibly adjusting the scale.
*/
- void windowSizeChanged(int width, int height);
+ void updateWindowSize();
const DisplayMode &desktopDisplayMode() const
{