mirror of
https://git.themanaworld.org/mana/verse
synced 2026-08-16 04:23:07 -04:00
2000 lines
62 KiB
C++
2000 lines
62 KiB
C++
/*
|
|
* The ManaPlus Client
|
|
* Copyright (C) 2004-2009 The Mana World Development Team
|
|
* Copyright (C) 2009-2010 The Mana Developers
|
|
* Copyright (C) 2011-2017 The ManaPlus Developers
|
|
*
|
|
* This file is part of The ManaPlus 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 "client.h"
|
|
|
|
#include "chatlogger.h"
|
|
#include "configmanager.h"
|
|
#include "dirs.h"
|
|
#include "eventsmanager.h"
|
|
#include "game.h"
|
|
#include "graphicsmanager.h"
|
|
#include "main.h"
|
|
#include "party.h"
|
|
#include "settings.h"
|
|
#include "soundmanager.h"
|
|
#include "spellmanager.h"
|
|
|
|
#include "being/localplayer.h"
|
|
#include "being/playerinfo.h"
|
|
#include "being/playerrelations.h"
|
|
|
|
#include "const/net/net.h"
|
|
|
|
#include "enums/being/attributesstrings.h"
|
|
|
|
#include "fs/virtfs.h"
|
|
#include "fs/virtfstools.h"
|
|
|
|
#include "gui/dialogsmanager.h"
|
|
#include "gui/gui.h"
|
|
#include "gui/skin.h"
|
|
#include "gui/popupmanager.h"
|
|
#include "gui/windowmanager.h"
|
|
|
|
#include "gui/shortcut/dropshortcut.h"
|
|
#include "gui/shortcut/emoteshortcut.h"
|
|
#include "gui/shortcut/itemshortcut.h"
|
|
#include "gui/shortcut/spellshortcut.h"
|
|
|
|
#include "gui/windows/changeemaildialog.h"
|
|
#include "gui/windows/changepassworddialog.h"
|
|
#include "gui/windows/charselectdialog.h"
|
|
#include "gui/windows/connectiondialog.h"
|
|
#include "gui/windows/equipmentwindow.h"
|
|
#include "gui/windows/logindialog.h"
|
|
#include "gui/windows/npcdialog.h"
|
|
#include "gui/windows/okdialog.h"
|
|
#include "gui/windows/registerdialog.h"
|
|
#include "gui/windows/serverdialog.h"
|
|
#include "gui/windows/setupwindow.h"
|
|
#include "gui/windows/updaterwindow.h"
|
|
#include "gui/windows/quitdialog.h"
|
|
#include "gui/windows/worldselectdialog.h"
|
|
|
|
#include "gui/widgets/button.h"
|
|
#include "gui/widgets/createwidget.h"
|
|
#include "gui/widgets/desktop.h"
|
|
#include "gui/widgets/windowcontainer.h"
|
|
|
|
#include "input/inputmanager.h"
|
|
#include "input/joystick.h"
|
|
#include "input/keyboardconfig.h"
|
|
|
|
#include "input/touch/touchmanager.h"
|
|
|
|
#include "net/charserverhandler.h"
|
|
#include "net/chathandler.h"
|
|
#include "net/download.h"
|
|
#include "net/gamehandler.h"
|
|
#include "net/generalhandler.h"
|
|
#include "net/guildhandler.h"
|
|
#include "net/inventoryhandler.h"
|
|
#include "net/ipc.h"
|
|
#include "net/loginhandler.h"
|
|
#include "net/net.h"
|
|
#include "net/updatetypeoperators.h"
|
|
#include "net/packetlimiter.h"
|
|
#include "net/partyhandler.h"
|
|
|
|
#ifdef TMWA_SUPPORT
|
|
#include "net/tmwa/guildmanager.h"
|
|
#endif // TMWA_SUPPORT
|
|
|
|
#include "particle/particleengine.h"
|
|
|
|
#include "resources/imagehelper.h"
|
|
|
|
#include "resources/db/avatardb.h"
|
|
#include "resources/db/badgesdb.h"
|
|
#include "resources/db/chardb.h"
|
|
#include "resources/db/colordb.h"
|
|
#include "resources/db/deaddb.h"
|
|
#include "resources/db/elementaldb.h"
|
|
#include "resources/db/emotedb.h"
|
|
#include "resources/db/homunculusdb.h"
|
|
#include "resources/db/horsedb.h"
|
|
#include "resources/db/itemdb.h"
|
|
#include "resources/db/itemfielddb.h"
|
|
#include "resources/db/sounddb.h"
|
|
#include "resources/db/mapdb.h"
|
|
#include "resources/db/mercenarydb.h"
|
|
#include "resources/db/moddb.h"
|
|
#include "resources/db/monsterdb.h"
|
|
#include "resources/db/networkdb.h"
|
|
#include "resources/db/npcdb.h"
|
|
#include "resources/db/npcdialogdb.h"
|
|
#include "resources/db/palettedb.h"
|
|
#include "resources/db/petdb.h"
|
|
#include "resources/db/skillunitdb.h"
|
|
#include "resources/db/statdb.h"
|
|
#include "resources/db/statuseffectdb.h"
|
|
#include "resources/db/unitsdb.h"
|
|
#include "resources/db/weaponsdb.h"
|
|
|
|
#include "resources/resourcemanager/resourcemanager.h"
|
|
|
|
#include "resources/sprite/spritereference.h"
|
|
|
|
#include "utils/checkutils.h"
|
|
#include "utils/cpu.h"
|
|
#include "utils/delete2.h"
|
|
#include "utils/dumplibs.h"
|
|
#include "utils/env.h"
|
|
#include "utils/fuzzer.h"
|
|
#include "utils/gettext.h"
|
|
#include "utils/gettexthelper.h"
|
|
#include "utils/mrand.h"
|
|
#ifdef ANDROID
|
|
#include "fs/paths.h"
|
|
#endif // ANDROID
|
|
#include "utils/sdlcheckutils.h"
|
|
#include "utils/timer.h"
|
|
|
|
#include "utils/translation/translationmanager.h"
|
|
|
|
#include "listeners/assertlistener.h"
|
|
#include "listeners/errorlistener.h"
|
|
|
|
#ifdef USE_OPENGL
|
|
#include "test/testlauncher.h"
|
|
#include "test/testmain.h"
|
|
#else // USE_OPENGL
|
|
#include "configuration.h"
|
|
#endif // USE_OPENGL
|
|
|
|
#ifdef WIN32
|
|
#include <SDL_syswm.h>
|
|
#include "fs/specialfolder.h"
|
|
#undef ERROR
|
|
#endif // WIN32
|
|
|
|
#ifdef ANDROID
|
|
#ifndef USE_SDL2
|
|
#include <SDL_screenkeyboard.h>
|
|
#include <fstream>
|
|
#endif // USE_SDL2
|
|
#endif // ANDROID
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#ifdef USE_MUMBLE
|
|
#include "mumblemanager.h"
|
|
#endif // USE_MUMBLE
|
|
|
|
#ifdef USE_SDL2
|
|
#include <SDL2_framerate.h>
|
|
#else // USE_SDL2
|
|
#include <SDL_framerate.h>
|
|
#endif // USE_SDL2
|
|
|
|
#include "debug.h"
|
|
|
|
std::string errorMessage;
|
|
LoginData loginData;
|
|
|
|
Client *client = nullptr;
|
|
|
|
extern FPSmanager fpsManager;
|
|
|
|
volatile bool runCounters;
|
|
bool isSafeMode = false;
|
|
int serverVersion = 0;
|
|
int packetVersion = 0;
|
|
unsigned int tmwServerVersion = 0;
|
|
time_t start_time;
|
|
unsigned int mLastHost = 0;
|
|
unsigned long mSearchHash = 0;
|
|
int textures_count = 0;
|
|
|
|
namespace
|
|
{
|
|
class AccountListener final : public ActionListener
|
|
{
|
|
public:
|
|
AccountListener()
|
|
{ }
|
|
|
|
A_DELETE_COPY(AccountListener)
|
|
|
|
void action(const ActionEvent &) override final
|
|
{
|
|
client->setState(State::CHAR_SELECT);
|
|
}
|
|
} accountListener;
|
|
|
|
class LoginListener final : public ActionListener
|
|
{
|
|
public:
|
|
LoginListener()
|
|
{ }
|
|
|
|
A_DELETE_COPY(LoginListener)
|
|
|
|
void action(const ActionEvent &) override final
|
|
{
|
|
client->setState(State::PRE_LOGIN);
|
|
}
|
|
} loginListener;
|
|
} // namespace
|
|
|
|
Client::Client() :
|
|
ActionListener(),
|
|
mCurrentServer(),
|
|
mGame(nullptr),
|
|
mCurrentDialog(nullptr),
|
|
mQuitDialog(nullptr),
|
|
mSetupButton(nullptr),
|
|
mVideoButton(nullptr),
|
|
mHelpButton(nullptr),
|
|
mAboutButton(nullptr),
|
|
mThemesButton(nullptr),
|
|
mPerfomanceButton(nullptr),
|
|
#ifdef ANDROID
|
|
mCloseButton(nullptr),
|
|
#endif // ANDROID
|
|
mState(State::CHOOSE_SERVER),
|
|
mOldState(State::START),
|
|
mSkin(nullptr),
|
|
mButtonPadding(1),
|
|
mButtonSpacing(3),
|
|
mPing(0),
|
|
mConfigAutoSaved(false)
|
|
{
|
|
WindowManager::init();
|
|
}
|
|
|
|
void Client::testsInit()
|
|
{
|
|
if (!settings.options.test.empty() &&
|
|
settings.options.test != "99")
|
|
{
|
|
gameInit();
|
|
}
|
|
else
|
|
{
|
|
initRand();
|
|
logger = new Logger;
|
|
Dirs::initLocalDataDir();
|
|
Dirs::initTempDir();
|
|
Dirs::initConfigDir();
|
|
GettextHelper::initLang();
|
|
}
|
|
}
|
|
|
|
void Client::gameInit()
|
|
{
|
|
logger = new Logger;
|
|
|
|
initRand();
|
|
|
|
assertListener = new AssertListener;
|
|
// Load branding information
|
|
if (!settings.options.brandingPath.empty())
|
|
branding.init(settings.options.brandingPath);
|
|
branding.setDefaultValues(getBrandingDefaults());
|
|
|
|
Dirs::initRootDir();
|
|
Dirs::initHomeDir();
|
|
|
|
// Configure logger
|
|
if (!settings.options.logFileName.empty())
|
|
settings.logFileName = settings.options.logFileName;
|
|
else
|
|
settings.logFileName = settings.localDataDir + "/manaplus.log";
|
|
logger->log("Log file: " + settings.logFileName);
|
|
logger->setLogFile(settings.logFileName);
|
|
|
|
#ifdef USE_FUZZER
|
|
Fuzzer::init();
|
|
#endif // USE_FUZZER
|
|
|
|
if (settings.options.test.empty())
|
|
ConfigManager::backupConfig("config.xml");
|
|
ConfigManager::initConfiguration();
|
|
settings.init();
|
|
Net::loadIgnorePackets();
|
|
paths.setDefaultValues(getPathsDefaults());
|
|
initFeatures();
|
|
initPaths();
|
|
logger->log("init 4");
|
|
logger->setDebugLog(config.getBoolValue("debugLog"));
|
|
logger->setReportUnimplemented(config.getBoolValue("unimplimentedLog"));
|
|
|
|
config.incValue("runcount");
|
|
|
|
#ifndef ANDROID
|
|
if (settings.options.test.empty())
|
|
ConfigManager::storeSafeParameters();
|
|
#endif // ANDROID
|
|
|
|
ResourceManager::init();
|
|
if (!VirtFs::setWriteDir(settings.localDataDir))
|
|
{
|
|
logger->error(strprintf("%s couldn't be set as home directory! "
|
|
"Exiting.", settings.localDataDir.c_str()));
|
|
}
|
|
|
|
GettextHelper::initLang();
|
|
|
|
chatLogger = new ChatLogger;
|
|
if (settings.options.chatLogDir.empty())
|
|
{
|
|
chatLogger->setBaseLogDir(settings.localDataDir
|
|
+ std::string("/logs/"));
|
|
}
|
|
else
|
|
{
|
|
chatLogger->setBaseLogDir(settings.options.chatLogDir);
|
|
}
|
|
|
|
logger->setLogToStandardOut(config.getBoolValue("logToStandardOut"));
|
|
|
|
// Log the client version
|
|
logger->log1(FULL_VERSION);
|
|
logger->log("Start configPath: " + config.getConfigPath());
|
|
|
|
Dirs::initScreenshotDir();
|
|
|
|
updateEnv();
|
|
dumpLibs();
|
|
|
|
// Initialize SDL
|
|
logger->log1("Initializing SDL...");
|
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0)
|
|
{
|
|
logger->safeError(strprintf("Could not initialize SDL: %s",
|
|
SDL_GetError()));
|
|
}
|
|
atexit(SDL_Quit);
|
|
|
|
PacketLimiter::initPacketLimiter();
|
|
#ifndef USE_SDL2
|
|
SDL_EnableUNICODE(1);
|
|
#endif // USE_SDL2
|
|
|
|
WindowManager::applyKeyRepeat();
|
|
|
|
// disable unused SDL events
|
|
#ifndef USE_SDL2
|
|
SDL_EventState(SDL_VIDEOEXPOSE, SDL_IGNORE);
|
|
#endif // USE_SDL2
|
|
|
|
SDL_EventState(SDL_SYSWMEVENT, SDL_IGNORE);
|
|
SDL_EventState(SDL_USEREVENT, SDL_IGNORE);
|
|
|
|
#ifdef WIN32
|
|
Dirs::extractDataDir();
|
|
Dirs::mountDataDir();
|
|
#endif // WIN32
|
|
|
|
WindowManager::setIcon();
|
|
ConfigManager::checkConfigVersion();
|
|
logVars();
|
|
Cpu::detect();
|
|
#if defined(USE_OPENGL)
|
|
#if !defined(ANDROID) && !defined(__APPLE__) && \
|
|
!defined(__native_client__) && !defined(UNITTESTS)
|
|
if (!settings.options.safeMode &&
|
|
settings.options.test.empty() &&
|
|
!config.getBoolValue("videodetected"))
|
|
{
|
|
graphicsManager.detectVideoSettings();
|
|
}
|
|
#endif // !defined(ANDROID) && !defined(__APPLE__) &&
|
|
// !defined(__native_client__) && !defined(UNITTESTS)
|
|
#endif // defined(USE_OPENGL)
|
|
|
|
initGraphics();
|
|
|
|
touchManager.init();
|
|
|
|
#ifndef WIN32
|
|
Dirs::extractDataDir();
|
|
Dirs::mountDataDir();
|
|
#endif // WIN32
|
|
|
|
Dirs::updateDataPath();
|
|
|
|
// Add the main data directories to our PhysicsFS search path
|
|
if (!settings.options.dataPath.empty())
|
|
{
|
|
VirtFs::mountDir(settings.options.dataPath,
|
|
Append_false);
|
|
}
|
|
|
|
// Add the local data directory to PhysicsFS search path
|
|
VirtFs::mountDir(settings.localDataDir,
|
|
Append_false);
|
|
TranslationManager::loadCurrentLang();
|
|
#ifdef ENABLE_CUSTOMNLS
|
|
TranslationManager::loadGettextLang();
|
|
#endif // ENABLE_CUSTOMNLS
|
|
|
|
WindowManager::initTitle();
|
|
|
|
mainGraphics->postInit();
|
|
|
|
theme = new Theme;
|
|
Theme::selectSkin();
|
|
ActorSprite::load();
|
|
touchManager.init();
|
|
|
|
// Initialize the item and emote shortcuts.
|
|
for (unsigned f = 0; f < SHORTCUT_TABS; f ++)
|
|
itemShortcut[f] = new ItemShortcut(f);
|
|
emoteShortcut = new EmoteShortcut;
|
|
dropShortcut = new DropShortcut;
|
|
|
|
gui = new Gui;
|
|
gui->postInit(mainGraphics);
|
|
dialogsManager = new DialogsManager;
|
|
popupManager = new PopupManager;
|
|
|
|
initSoundManager();
|
|
eventsManager.init();
|
|
|
|
// Initialize keyboard
|
|
keyboard.init();
|
|
inputManager.init();
|
|
|
|
// Initialise player relations
|
|
player_relations.init();
|
|
Joystick::init();
|
|
WindowManager::createWindows();
|
|
|
|
keyboard.update();
|
|
if (joystick)
|
|
joystick->update();
|
|
|
|
// Initialize default server
|
|
mCurrentServer.hostname = settings.options.serverName;
|
|
mCurrentServer.port = settings.options.serverPort;
|
|
if (!settings.options.serverType.empty())
|
|
{
|
|
mCurrentServer.type = ServerInfo::parseType(
|
|
settings.options.serverType);
|
|
}
|
|
|
|
loginData.username = settings.options.username;
|
|
loginData.password = settings.options.password;
|
|
LoginDialog::savedPassword = settings.options.password;
|
|
loginData.remember = serverConfig.getValue("remember", 1);
|
|
loginData.registerLogin = false;
|
|
|
|
if (mCurrentServer.hostname.empty())
|
|
{
|
|
mCurrentServer.hostname = branding.getValue("defaultServer", "");
|
|
settings.options.serverName = mCurrentServer.hostname;
|
|
}
|
|
|
|
if (mCurrentServer.port == 0)
|
|
{
|
|
mCurrentServer.port = CAST_U16(branding.getValue(
|
|
"defaultPort", CAST_S32(DEFAULT_PORT)));
|
|
mCurrentServer.type = ServerInfo::parseType(
|
|
branding.getValue("defaultServerType", "tmwathena"));
|
|
}
|
|
|
|
chatLogger->setServerName(mCurrentServer.hostname);
|
|
|
|
if (loginData.username.empty() && loginData.remember)
|
|
loginData.username = serverConfig.getValue("username", "");
|
|
|
|
if (mState != State::ERROR)
|
|
mState = State::CHOOSE_SERVER;
|
|
|
|
startTimers();
|
|
|
|
const int fpsLimit = config.getIntValue("fpslimit");
|
|
settings.limitFps = fpsLimit > 0;
|
|
|
|
SDL_initFramerate(&fpsManager);
|
|
WindowManager::setFramerate(fpsLimit);
|
|
initConfigListeners();
|
|
|
|
settings.guiAlpha = config.getFloatValue("guialpha");
|
|
optionChanged("fpslimit");
|
|
|
|
start_time = time(nullptr);
|
|
|
|
PlayerInfo::init();
|
|
|
|
#ifdef ANDROID
|
|
#ifndef USE_SDL2
|
|
WindowManager::updateScreenKeyboard(SDL_GetScreenKeyboardHeight(nullptr));
|
|
#endif // USE_SDL2
|
|
#endif // ANDROID
|
|
|
|
#ifdef USE_MUMBLE
|
|
if (!mumbleManager)
|
|
mumbleManager = new MumbleManager;
|
|
#endif // USE_MUMBLE
|
|
|
|
mSkin = theme->load("windowmenu.xml", "");
|
|
if (mSkin)
|
|
{
|
|
mButtonPadding = mSkin->getPadding();
|
|
mButtonSpacing = mSkin->getOption("spacing", 3);
|
|
}
|
|
}
|
|
|
|
Client::~Client()
|
|
{
|
|
if (!settings.options.testMode)
|
|
gameClear();
|
|
else
|
|
testsClear();
|
|
CHECKLISTENERS
|
|
}
|
|
|
|
void Client::initConfigListeners()
|
|
{
|
|
config.addListener("fpslimit", this);
|
|
config.addListener("guialpha", this);
|
|
config.addListener("gamma", this);
|
|
config.addListener("enableGamma", this);
|
|
config.addListener("particleEmitterSkip", this);
|
|
config.addListener("vsync", this);
|
|
config.addListener("repeateDelay", this);
|
|
config.addListener("repeateInterval", this);
|
|
config.addListener("logInput", this);
|
|
}
|
|
|
|
void Client::initSoundManager()
|
|
{
|
|
// Initialize sound engine
|
|
try
|
|
{
|
|
if (config.getBoolValue("sound"))
|
|
soundManager.init();
|
|
|
|
soundManager.setSfxVolume(config.getIntValue("sfxVolume"));
|
|
soundManager.setMusicVolume(config.getIntValue("musicVolume"));
|
|
}
|
|
catch (const char *const err)
|
|
{
|
|
mState = State::ERROR;
|
|
errorMessage = err;
|
|
logger->log("Warning: %s", err);
|
|
}
|
|
soundManager.playMusic(branding.getValue(
|
|
"loginMusic",
|
|
"keprohm.ogg"),
|
|
SkipError_true);
|
|
}
|
|
|
|
void Client::initGraphics()
|
|
{
|
|
WindowManager::applyVSync();
|
|
runCounters = config.getBoolValue("packetcounters");
|
|
|
|
graphicsManager.initGraphics();
|
|
|
|
imageHelper->postInit();
|
|
getConfigDefaults2(config.getDefaultValues());
|
|
WindowManager::applyGrabMode();
|
|
WindowManager::applyGamma();
|
|
|
|
mainGraphics->beginDraw();
|
|
}
|
|
|
|
void Client::testsClear()
|
|
{
|
|
if (!settings.options.test.empty())
|
|
gameClear();
|
|
else
|
|
BeingInfo::clear();
|
|
}
|
|
|
|
void Client::gameClear()
|
|
{
|
|
if (logger)
|
|
logger->log1("Quitting1");
|
|
config.removeListeners(this);
|
|
|
|
delete2(assertListener);
|
|
|
|
eventsManager.shutdown();
|
|
WindowManager::deleteWindows();
|
|
if (windowContainer)
|
|
windowContainer->slowLogic();
|
|
|
|
stopTimers();
|
|
|
|
// Unload XML databases
|
|
CharDB::unload();
|
|
StatDb::unload();
|
|
DeadDB::unload();
|
|
ColorDB::unload();
|
|
SoundDB::unload();
|
|
EmoteDB::unload();
|
|
ItemDB::unload();
|
|
ItemFieldDb::unload();
|
|
const ServerTypeT type = Net::getNetworkType();
|
|
if (type == ServerType::EATHENA ||
|
|
type == ServerType::EVOL2)
|
|
{
|
|
MercenaryDB::unload();
|
|
HomunculusDB::unload();
|
|
ElementalDb::unload();
|
|
SkillUnitDb::unload();
|
|
HorseDB::unload();
|
|
NetworkDb::unload();
|
|
}
|
|
MonsterDB::unload();
|
|
NPCDB::unload();
|
|
NpcDialogDB::unload();
|
|
AvatarDB::unload();
|
|
BadgesDB::unload();
|
|
WeaponsDB::unload();
|
|
PaletteDB::unload();
|
|
PETDB::unload();
|
|
StatusEffectDB::unload();
|
|
ModDB::unload();
|
|
|
|
if (loginHandler)
|
|
loginHandler->clearWorlds();
|
|
|
|
if (chatHandler)
|
|
chatHandler->clear();
|
|
|
|
if (charServerHandler)
|
|
charServerHandler->clear();
|
|
|
|
delete2(ipc);
|
|
|
|
#ifdef USE_MUMBLE
|
|
delete2(mumbleManager);
|
|
#endif // USE_MUMBLE
|
|
|
|
PlayerInfo::deinit();
|
|
|
|
// Before config.write() since it writes the shortcuts to the config
|
|
for (unsigned f = 0; f < SHORTCUT_TABS; f ++)
|
|
delete2(itemShortcut[f])
|
|
delete2(emoteShortcut);
|
|
delete2(dropShortcut);
|
|
|
|
player_relations.store();
|
|
|
|
if (logger)
|
|
logger->log1("Quitting2");
|
|
|
|
delete2(mCurrentDialog);
|
|
delete2(popupManager);
|
|
delete2(dialogsManager);
|
|
delete2(gui);
|
|
|
|
if (inventoryHandler)
|
|
inventoryHandler->clear();
|
|
|
|
if (logger)
|
|
logger->log1("Quitting3");
|
|
|
|
touchManager.clear();
|
|
|
|
graphicsManager.deleteRenderers();
|
|
|
|
if (logger)
|
|
logger->log1("Quitting4");
|
|
|
|
XML::cleanupXML();
|
|
|
|
if (logger)
|
|
logger->log1("Quitting5");
|
|
|
|
BeingInfo::clear();
|
|
|
|
// Shutdown sound
|
|
soundManager.close();
|
|
|
|
if (logger)
|
|
logger->log1("Quitting6");
|
|
|
|
ActorSprite::unload();
|
|
|
|
ResourceManager::deleteInstance();
|
|
|
|
if (logger)
|
|
logger->log1("Quitting8");
|
|
|
|
WindowManager::deleteIcon();
|
|
|
|
if (logger)
|
|
logger->log1("Quitting9");
|
|
|
|
delete2(joystick);
|
|
|
|
keyboard.deinit();
|
|
|
|
if (logger)
|
|
logger->log1("Quitting10");
|
|
|
|
soundManager.shutdown();
|
|
touchManager.shutdown();
|
|
|
|
#ifdef DEBUG_CONFIG
|
|
config.enableKeyLogging();
|
|
#endif // DEBUG_CONFIG
|
|
|
|
config.removeOldKeys();
|
|
config.write();
|
|
serverConfig.write();
|
|
|
|
config.clear();
|
|
serverConfig.clear();
|
|
|
|
if (logger)
|
|
logger->log1("Quitting11");
|
|
|
|
#ifdef USE_PROFILER
|
|
Perfomance::clear();
|
|
#endif // USE_PROFILER
|
|
|
|
#ifdef DEBUG_OPENGL_LEAKS
|
|
if (logger)
|
|
logger->log("textures left: %d", textures_count);
|
|
#endif // DEBUG_OPENGL_LEAKS
|
|
|
|
delete2(chatLogger);
|
|
TranslationManager::close();
|
|
}
|
|
|
|
int Client::testsExec()
|
|
{
|
|
#ifdef USE_OPENGL
|
|
if (settings.options.test.empty())
|
|
{
|
|
TestMain test;
|
|
return test.exec();
|
|
}
|
|
else
|
|
{
|
|
TestLauncher launcher(settings.options.test);
|
|
return launcher.exec();
|
|
}
|
|
#else // USE_OPENGL
|
|
|
|
return 0;
|
|
#endif // USE_OPENGL
|
|
}
|
|
|
|
#define ADDBUTTON(var, object) var = object; \
|
|
x -= var->getWidth() + mButtonSpacing; \
|
|
var->setPosition(x, mButtonPadding); \
|
|
top->add(var);
|
|
|
|
void Client::stateConnectGame1()
|
|
{
|
|
if (gameHandler &&
|
|
loginHandler &&
|
|
gameHandler->isConnected())
|
|
{
|
|
loginHandler->disconnect();
|
|
}
|
|
}
|
|
|
|
void Client::stateConnectServer1()
|
|
{
|
|
if (mOldState == State::CHOOSE_SERVER)
|
|
{
|
|
settings.serverName = mCurrentServer.hostname;
|
|
ConfigManager::initServerConfig(mCurrentServer.hostname);
|
|
PacketLimiter::initPacketLimiter();
|
|
initTradeFilter();
|
|
Dirs::initUsersDir();
|
|
player_relations.init();
|
|
|
|
// Initialize the item and emote shortcuts.
|
|
for (unsigned f = 0; f < SHORTCUT_TABS; f ++)
|
|
{
|
|
delete itemShortcut[f];
|
|
itemShortcut[f] = new ItemShortcut(f);
|
|
}
|
|
delete emoteShortcut;
|
|
emoteShortcut = new EmoteShortcut;
|
|
|
|
// Initialize the drop shortcuts.
|
|
delete dropShortcut;
|
|
dropShortcut = new DropShortcut;
|
|
|
|
initFeatures();
|
|
PlayerInfo::loadData();
|
|
loginData.registerUrl = mCurrentServer.registerUrl;
|
|
loginData.packetVersion = mCurrentServer.packetVersion;
|
|
if (!mCurrentServer.onlineListUrl.empty())
|
|
settings.onlineListUrl = mCurrentServer.onlineListUrl;
|
|
else
|
|
settings.onlineListUrl = settings.serverName;
|
|
settings.persistentIp = mCurrentServer.persistentIp;
|
|
settings.supportUrl = mCurrentServer.supportUrl;
|
|
settings.updateMirrors = mCurrentServer.updateMirrors;
|
|
settings.enableRemoteCommands = serverConfig.getValue(
|
|
"enableRemoteCommands", 1);
|
|
|
|
if (settings.options.username.empty())
|
|
{
|
|
if (loginData.remember)
|
|
loginData.username = serverConfig.getValue("username", "");
|
|
else
|
|
loginData.username.clear();
|
|
}
|
|
else
|
|
{
|
|
loginData.username = settings.options.username;
|
|
}
|
|
settings.login = loginData.username;
|
|
WindowManager::updateTitle();
|
|
|
|
loginData.remember = serverConfig.getValue("remember", 1);
|
|
Net::connectToServer(mCurrentServer);
|
|
|
|
#ifdef USE_MUMBLE
|
|
if (mumbleManager)
|
|
mumbleManager->setServer(mCurrentServer.hostname);
|
|
#endif // USE_MUMBLE
|
|
|
|
#ifdef TMWA_SUPPORT
|
|
GuildManager::init();
|
|
#endif // TMWA_SUPPORT
|
|
|
|
if (!mConfigAutoSaved)
|
|
{
|
|
mConfigAutoSaved = true;
|
|
config.write();
|
|
}
|
|
}
|
|
else if (mOldState != State::CHOOSE_SERVER &&
|
|
loginHandler &&
|
|
loginHandler->isConnected())
|
|
{
|
|
mState = State::PRE_LOGIN;
|
|
}
|
|
}
|
|
|
|
void Client::stateWorldSelect1()
|
|
{
|
|
if (mOldState == State::UPDATE &&
|
|
loginHandler)
|
|
{
|
|
if (loginHandler->getWorlds().size() < 2)
|
|
mState = State::PRE_LOGIN;
|
|
}
|
|
}
|
|
|
|
void Client::stateGame1()
|
|
{
|
|
if (!gui)
|
|
return;
|
|
|
|
BasicContainer2 *const top = static_cast<BasicContainer2*>(
|
|
gui->getTop());
|
|
|
|
if (!top)
|
|
return;
|
|
|
|
CREATEWIDGETV(desktop, Desktop, nullptr);
|
|
top->add(desktop);
|
|
int x = top->getWidth() - mButtonPadding;
|
|
ADDBUTTON(mSetupButton, new Button(desktop,
|
|
// TRANSLATORS: setup tab quick button
|
|
_("Setup"), "Setup", this))
|
|
ADDBUTTON(mPerfomanceButton, new Button(desktop,
|
|
// TRANSLATORS: perfoamance tab quick button
|
|
_("Performance"), "Perfomance", this))
|
|
ADDBUTTON(mVideoButton, new Button(desktop,
|
|
// TRANSLATORS: video tab quick button
|
|
_("Video"), "Video", this))
|
|
ADDBUTTON(mThemesButton, new Button(desktop,
|
|
// TRANSLATORS: theme tab quick button
|
|
_("Theme"), "Themes", this))
|
|
ADDBUTTON(mAboutButton, new Button(desktop,
|
|
// TRANSLATORS: theme tab quick button
|
|
_("About"), "about", this))
|
|
ADDBUTTON(mHelpButton, new Button(desktop,
|
|
// TRANSLATORS: theme tab quick button
|
|
_("Help"), "help", this))
|
|
#ifdef ANDROID
|
|
ADDBUTTON(mCloseButton, new Button(desktop,
|
|
// TRANSLATORS: close quick button
|
|
_("Close"), "close", this))
|
|
#endif // ANDROID
|
|
|
|
desktop->setSize(mainGraphics->getWidth(),
|
|
mainGraphics->getHeight());
|
|
}
|
|
|
|
void Client::stateSwitchLogin1()
|
|
{
|
|
if (mOldState == State::GAME &&
|
|
gameHandler)
|
|
{
|
|
gameHandler->disconnect();
|
|
}
|
|
}
|
|
|
|
int Client::gameExec()
|
|
{
|
|
int lastTickTime = tick_time;
|
|
|
|
while (mState != State::EXIT)
|
|
{
|
|
PROFILER_START();
|
|
if (eventsManager.handleEvents())
|
|
continue;
|
|
|
|
BLOCK_START("Client::gameExec 3")
|
|
if (generalHandler)
|
|
generalHandler->flushNetwork();
|
|
BLOCK_END("Client::gameExec 3")
|
|
|
|
BLOCK_START("Client::gameExec 4")
|
|
if (gui)
|
|
gui->logic();
|
|
cur_time = time(nullptr);
|
|
int k = 0;
|
|
while (lastTickTime != tick_time &&
|
|
k < 40)
|
|
{
|
|
if (mGame)
|
|
mGame->logic();
|
|
else if (gui)
|
|
gui->handleInput();
|
|
|
|
++lastTickTime;
|
|
k ++;
|
|
}
|
|
soundManager.logic();
|
|
|
|
logic_count += k;
|
|
if (gui)
|
|
gui->slowLogic();
|
|
if (mGame)
|
|
mGame->slowLogic();
|
|
slowLogic();
|
|
BLOCK_END("Client::gameExec 4")
|
|
|
|
// This is done because at some point tick_time will wrap.
|
|
lastTickTime = tick_time;
|
|
|
|
// Update the screen when application is visible, delay otherwise.
|
|
if (!WindowManager::getIsMinimized())
|
|
{
|
|
frame_count++;
|
|
if (gui)
|
|
gui->draw();
|
|
mainGraphics->updateScreen();
|
|
}
|
|
else
|
|
{
|
|
SDL_Delay(100);
|
|
}
|
|
|
|
BLOCK_START("~Client::SDL_framerateDelay")
|
|
if (settings.limitFps)
|
|
SDL_framerateDelay(&fpsManager);
|
|
BLOCK_END("~Client::SDL_framerateDelay")
|
|
|
|
BLOCK_START("Client::gameExec 6")
|
|
if (mState == State::CONNECT_GAME)
|
|
{
|
|
stateConnectGame1();
|
|
}
|
|
else if (mState == State::CONNECT_SERVER)
|
|
{
|
|
stateConnectServer1();
|
|
}
|
|
else if (mState == State::WORLD_SELECT)
|
|
{
|
|
stateWorldSelect1();
|
|
}
|
|
else if (mOldState == State::START ||
|
|
(mOldState == State::GAME && mState != State::GAME))
|
|
{
|
|
stateGame1();
|
|
}
|
|
else if (mState == State::SWITCH_LOGIN)
|
|
{
|
|
stateSwitchLogin1();
|
|
}
|
|
BLOCK_END("Client::gameExec 6")
|
|
|
|
if (mState != mOldState)
|
|
{
|
|
BLOCK_START("Client::gameExec 7")
|
|
PlayerInfo::stateChange(mState);
|
|
|
|
if (mOldState == State::GAME)
|
|
{
|
|
delete2(mGame);
|
|
assertListener = new AssertListener;
|
|
Game::clearInstance();
|
|
resourceManager->cleanOrphans();
|
|
Party::clearParties();
|
|
Guild::clearGuilds();
|
|
NpcDialog::clearDialogs();
|
|
if (guildHandler)
|
|
guildHandler->clear();
|
|
if (partyHandler)
|
|
partyHandler->clear();
|
|
if (chatLogger)
|
|
chatLogger->clear();
|
|
if (!settings.options.dataPath.empty())
|
|
UpdaterWindow::unloadMods(settings.options.dataPath);
|
|
else
|
|
UpdaterWindow::unloadMods(settings.oldUpdates);
|
|
if (!settings.options.skipUpdate)
|
|
UpdaterWindow::unloadMods(settings.oldUpdates + "/fix/");
|
|
}
|
|
else if (mOldState == State::CHAR_SELECT)
|
|
{
|
|
if (mState != State::CHANGEPASSWORD && charServerHandler)
|
|
charServerHandler->clear();
|
|
}
|
|
|
|
mOldState = mState;
|
|
|
|
// Get rid of the dialog of the previous state
|
|
delete2(mCurrentDialog);
|
|
|
|
// State has changed, while the quitDialog was active, it might
|
|
// not be correct anymore
|
|
if (mQuitDialog)
|
|
{
|
|
mQuitDialog->scheduleDelete();
|
|
mQuitDialog = nullptr;
|
|
}
|
|
BLOCK_END("Client::gameExec 7")
|
|
|
|
BLOCK_START("Client::gameExec 8")
|
|
switch (mState)
|
|
{
|
|
case State::CHOOSE_SERVER:
|
|
{
|
|
BLOCK_START("Client::gameExec STATE_CHOOSE_SERVER")
|
|
logger->log1("State: CHOOSE SERVER");
|
|
mCurrentServer.supportUrl.clear();
|
|
settings.supportUrl.clear();
|
|
if (settings.options.dataPath.empty())
|
|
{
|
|
// Add customdata directory
|
|
VirtFs::searchAndRemoveArchives(
|
|
"customdata/",
|
|
"zip");
|
|
}
|
|
|
|
if (!settings.oldUpdates.empty())
|
|
{
|
|
UpdaterWindow::unloadUpdates(settings.oldUpdates);
|
|
settings.oldUpdates.clear();
|
|
}
|
|
|
|
if (!settings.options.skipUpdate)
|
|
{
|
|
VirtFs::searchAndRemoveArchives(
|
|
settings.updatesDir +
|
|
"/local/",
|
|
"zip");
|
|
|
|
VirtFs::unmountDirSilent(
|
|
settings.localDataDir +
|
|
dirSeparator +
|
|
settings.updatesDir +
|
|
"/local/");
|
|
}
|
|
|
|
resourceManager->clearCache();
|
|
|
|
loginData.clearUpdateHost();
|
|
serverVersion = 0;
|
|
packetVersion = 0;
|
|
tmwServerVersion = 0;
|
|
|
|
// Allow changing this using a server choice dialog
|
|
// We show the dialog box only if the command-line
|
|
// options weren't set.
|
|
if (settings.options.serverName.empty() &&
|
|
settings.options.serverPort == 0 &&
|
|
!branding.getValue("onlineServerList", "a").empty())
|
|
{
|
|
// Don't allow an alpha opacity
|
|
// lower than the default value
|
|
theme->setMinimumOpacity(0.8F);
|
|
|
|
CREATEWIDGETV(mCurrentDialog, ServerDialog,
|
|
&mCurrentServer,
|
|
settings.configDir);
|
|
}
|
|
else
|
|
{
|
|
mState = State::CONNECT_SERVER;
|
|
|
|
// Reset options so that cancelling or connect
|
|
// timeout will show the server dialog.
|
|
settings.options.serverName.clear();
|
|
settings.options.serverPort = 0;
|
|
}
|
|
BLOCK_END("Client::gameExec STATE_CHOOSE_SERVER")
|
|
break;
|
|
}
|
|
|
|
case State::CONNECT_SERVER:
|
|
BLOCK_START("Client::gameExec State::CONNECT_SERVER")
|
|
logger->log1("State: CONNECT SERVER");
|
|
loginData.updateHosts.clear();
|
|
CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
|
|
// TRANSLATORS: connection dialog header
|
|
_("Connecting to server"),
|
|
State::SWITCH_SERVER);
|
|
TranslationManager::loadCurrentLang();
|
|
BLOCK_END("Client::gameExec State::CONNECT_SERVER")
|
|
break;
|
|
|
|
case State::PRE_LOGIN:
|
|
logger->log1("State: PRE_LOGIN");
|
|
break;
|
|
|
|
case State::LOGIN:
|
|
BLOCK_START("Client::gameExec State::LOGIN")
|
|
logger->log1("State: LOGIN");
|
|
// Don't allow an alpha opacity
|
|
// lower than the default value
|
|
theme->setMinimumOpacity(0.8F);
|
|
|
|
if (packetVersion == 0)
|
|
{
|
|
packetVersion = loginData.packetVersion;
|
|
if (packetVersion != 0)
|
|
{
|
|
loginHandler->updatePacketVersion();
|
|
logger->log("Preconfigured packet version: %d",
|
|
packetVersion);
|
|
}
|
|
}
|
|
|
|
loginData.updateType = static_cast<UpdateTypeT>(
|
|
serverConfig.getValue("updateType", 0));
|
|
|
|
mSearchHash = Net::Download::adlerBuffer(
|
|
const_cast<char*>(mCurrentServer.hostname.c_str()),
|
|
CAST_S32(mCurrentServer.hostname.size()));
|
|
if (settings.options.username.empty() ||
|
|
settings.options.password.empty())
|
|
{
|
|
CREATEWIDGETV(mCurrentDialog, LoginDialog,
|
|
loginData,
|
|
&mCurrentServer,
|
|
&settings.options.updateHost);
|
|
}
|
|
else
|
|
{
|
|
mState = State::LOGIN_ATTEMPT;
|
|
// Clear the password so that when login fails, the
|
|
// dialog will show up next time.
|
|
settings.options.password.clear();
|
|
}
|
|
BLOCK_END("Client::gameExec State::LOGIN")
|
|
break;
|
|
|
|
case State::LOGIN_ATTEMPT:
|
|
BLOCK_START("Client::gameExec State::LOGIN_ATTEMPT")
|
|
logger->log1("State: LOGIN ATTEMPT");
|
|
CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
|
|
// TRANSLATORS: connection dialog header
|
|
_("Logging in"),
|
|
State::SWITCH_SERVER);
|
|
if (loginHandler)
|
|
loginHandler->loginOrRegister(&loginData);
|
|
BLOCK_END("Client::gameExec State::LOGIN_ATTEMPT")
|
|
break;
|
|
|
|
case State::WORLD_SELECT:
|
|
BLOCK_START("Client::gameExec State::WORLD_SELECT")
|
|
logger->log1("State: WORLD SELECT");
|
|
{
|
|
TranslationManager::loadCurrentLang();
|
|
if (!loginHandler)
|
|
{
|
|
BLOCK_END("Client::gameExec State::WORLD_SELECT")
|
|
break;
|
|
}
|
|
Worlds worlds = loginHandler->getWorlds();
|
|
|
|
if (worlds.empty())
|
|
{
|
|
// Trust that the netcode knows what it's doing
|
|
mState = State::UPDATE;
|
|
}
|
|
else if (worlds.size() == 1)
|
|
{
|
|
loginHandler->chooseServer(
|
|
0, mCurrentServer.persistentIp);
|
|
mState = State::UPDATE;
|
|
}
|
|
else
|
|
{
|
|
CREATEWIDGETV(mCurrentDialog, WorldSelectDialog,
|
|
worlds);
|
|
if (settings.options.chooseDefault)
|
|
{
|
|
static_cast<WorldSelectDialog*>(mCurrentDialog)
|
|
->action(ActionEvent(nullptr, "ok"));
|
|
}
|
|
}
|
|
}
|
|
BLOCK_END("Client::gameExec State::WORLD_SELECT")
|
|
break;
|
|
|
|
case State::WORLD_SELECT_ATTEMPT:
|
|
BLOCK_START("Client::gameExec State::WORLD_SELECT_ATTEMPT")
|
|
logger->log1("State: WORLD SELECT ATTEMPT");
|
|
CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
|
|
// TRANSLATORS: connection dialog header
|
|
_("Entering game world"),
|
|
State::WORLD_SELECT);
|
|
BLOCK_END("Client::gameExec State::WORLD_SELECT_ATTEMPT")
|
|
break;
|
|
|
|
case State::UPDATE:
|
|
BLOCK_START("Client::gameExec State::UPDATE")
|
|
logger->log1("State: UPDATE");
|
|
|
|
// Determine which source to use for the update host
|
|
if (!settings.options.updateHost.empty())
|
|
settings.updateHost = settings.options.updateHost;
|
|
else
|
|
settings.updateHost = loginData.updateHost;
|
|
Dirs::initUpdatesDir();
|
|
|
|
if (!settings.oldUpdates.empty())
|
|
UpdaterWindow::unloadUpdates(settings.oldUpdates);
|
|
|
|
if (settings.options.skipUpdate)
|
|
{
|
|
mState = State::LOAD_DATA;
|
|
settings.oldUpdates.clear();
|
|
UpdaterWindow::loadDirMods(settings.options.dataPath);
|
|
}
|
|
else if (loginData.updateType & UpdateType::Skip)
|
|
{
|
|
settings.oldUpdates = settings.localDataDir
|
|
+ dirSeparator + settings.updatesDir;
|
|
UpdaterWindow::loadLocalUpdates(settings.oldUpdates);
|
|
mState = State::LOAD_DATA;
|
|
}
|
|
else
|
|
{
|
|
settings.oldUpdates = settings.localDataDir +
|
|
dirSeparator +
|
|
settings.updatesDir;
|
|
CREATEWIDGETV(mCurrentDialog, UpdaterWindow,
|
|
settings.updateHost,
|
|
settings.oldUpdates,
|
|
settings.options.dataPath.empty(),
|
|
loginData.updateType);
|
|
}
|
|
BLOCK_END("Client::gameExec State::UPDATE")
|
|
break;
|
|
|
|
case State::LOAD_DATA:
|
|
{
|
|
BLOCK_START("Client::gameExec State::LOAD_DATA")
|
|
logger->log1("State: LOAD DATA");
|
|
|
|
// If another data path has been set,
|
|
// we don't load any other files...
|
|
if (settings.options.dataPath.empty())
|
|
{
|
|
// Add customdata directory
|
|
VirtFs::searchAndAddArchives(
|
|
"customdata/",
|
|
"zip",
|
|
Append_false);
|
|
}
|
|
|
|
if (!settings.options.skipUpdate)
|
|
{
|
|
VirtFs::searchAndAddArchives(
|
|
settings.updatesDir + "/local/",
|
|
"zip",
|
|
Append_false);
|
|
|
|
VirtFs::mountDir(
|
|
settings.localDataDir +
|
|
dirSeparator +
|
|
settings.updatesDir +
|
|
"/local/",
|
|
Append_false);
|
|
}
|
|
|
|
logger->log("Init paths");
|
|
paths.init("paths.xml", UseVirtFs_true);
|
|
paths.setDefaultValues(getPathsDefaults());
|
|
initPaths();
|
|
if (!SpriteReference::Empty)
|
|
{
|
|
SpriteReference::Empty = new SpriteReference(
|
|
paths.getStringValue("spriteErrorFile"),
|
|
0);
|
|
}
|
|
|
|
if (!BeingInfo::unknown)
|
|
BeingInfo::unknown = new BeingInfo;
|
|
|
|
initFeatures();
|
|
TranslationManager::loadCurrentLang();
|
|
PlayerInfo::stateChange(mState);
|
|
|
|
delete spellManager;
|
|
spellManager = new SpellManager;
|
|
delete spellShortcut;
|
|
spellShortcut = new SpellShortcut;
|
|
|
|
AttributesEnum::init();
|
|
// Load XML databases
|
|
CharDB::load();
|
|
StatDb::load();
|
|
DeadDB::load();
|
|
PaletteDB::load();
|
|
ColorDB::load();
|
|
SoundDB::load();
|
|
MapDB::load();
|
|
ItemFieldDb::load();
|
|
ItemDB::load();
|
|
Being::load();
|
|
const ServerTypeT type = Net::getNetworkType();
|
|
if (type == ServerType::EATHENA ||
|
|
type == ServerType::EVOL2)
|
|
{
|
|
NetworkDb::load();
|
|
if (loginHandler)
|
|
loginHandler->updatePacketVersion();
|
|
MercenaryDB::load();
|
|
HomunculusDB::load();
|
|
ElementalDb::load();
|
|
SkillUnitDb::load();
|
|
HorseDB::load();
|
|
}
|
|
MonsterDB::load();
|
|
AvatarDB::load();
|
|
BadgesDB::load();
|
|
WeaponsDB::load();
|
|
UnitsDb::loadUnits();
|
|
NPCDB::load();
|
|
NpcDialogDB::load();
|
|
PETDB::load();
|
|
EmoteDB::load();
|
|
// ModDB::load();
|
|
StatusEffectDB::load();
|
|
EquipmentWindow::prepareSlotNames();
|
|
|
|
ActorSprite::load();
|
|
|
|
if (desktop)
|
|
desktop->reloadWallpaper();
|
|
|
|
mState = State::GET_CHARACTERS;
|
|
BLOCK_END("Client::gameExec State::LOAD_DATA")
|
|
break;
|
|
}
|
|
case State::GET_CHARACTERS:
|
|
BLOCK_START("Client::gameExec State::GET_CHARACTERS")
|
|
logger->log1("State: GET CHARACTERS");
|
|
CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
|
|
// TRANSLATORS: connection dialog header
|
|
_("Requesting characters"),
|
|
State::SWITCH_SERVER);
|
|
if (charServerHandler)
|
|
charServerHandler->requestCharacters();
|
|
BLOCK_END("Client::gameExec State::GET_CHARACTERS")
|
|
break;
|
|
|
|
case State::CHAR_SELECT:
|
|
BLOCK_START("Client::gameExec State::CHAR_SELECT")
|
|
logger->log1("State: CHAR SELECT");
|
|
// Don't allow an alpha opacity
|
|
// lower than the default value
|
|
theme->setMinimumOpacity(0.8F);
|
|
|
|
settings.login = loginData.username;
|
|
WindowManager::updateTitle();
|
|
|
|
CREATEWIDGETV(mCurrentDialog, CharSelectDialog,
|
|
loginData);
|
|
|
|
if (!(static_cast<CharSelectDialog*>(mCurrentDialog))
|
|
->selectByName(settings.options.character,
|
|
CharSelectDialog::Choose))
|
|
{
|
|
(static_cast<CharSelectDialog*>(mCurrentDialog))
|
|
->selectByName(
|
|
serverConfig.getValue("lastCharacter", ""),
|
|
settings.options.chooseDefault ?
|
|
CharSelectDialog::Choose :
|
|
CharSelectDialog::Focus);
|
|
}
|
|
|
|
// Choosing character on the command line should work only
|
|
// once, clear it so that 'switch character' works.
|
|
settings.options.character.clear();
|
|
BLOCK_END("Client::gameExec State::CHAR_SELECT")
|
|
break;
|
|
|
|
case State::CONNECT_GAME:
|
|
BLOCK_START("Client::gameExec State::CONNECT_GAME")
|
|
logger->log1("State: CONNECT GAME");
|
|
CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
|
|
// TRANSLATORS: connection dialog header
|
|
_("Connecting to the game server"),
|
|
State::CHOOSE_SERVER);
|
|
if (gameHandler)
|
|
gameHandler->connect();
|
|
BLOCK_END("Client::gameExec State::CONNECT_GAME")
|
|
break;
|
|
|
|
case State::CHANGE_MAP:
|
|
BLOCK_START("Client::gameExec State::CHANGE_MAP")
|
|
logger->log1("State: CHANGE_MAP");
|
|
CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
|
|
// TRANSLATORS: connection dialog header
|
|
_("Changing game servers"),
|
|
State::SWITCH_CHARACTER);
|
|
if (gameHandler)
|
|
gameHandler->connect();
|
|
BLOCK_END("Client::gameExec State::CHANGE_MAP")
|
|
break;
|
|
|
|
case State::GAME:
|
|
BLOCK_START("Client::gameExec State::GAME")
|
|
if (localPlayer)
|
|
{
|
|
logger->log("Memorizing selected character %s",
|
|
localPlayer->getName().c_str());
|
|
serverConfig.setValue("lastCharacter",
|
|
localPlayer->getName());
|
|
#ifdef USE_MUMBLE
|
|
if (mumbleManager)
|
|
mumbleManager->setPlayer(localPlayer->getName());
|
|
#endif // USE_MUMBLE
|
|
}
|
|
|
|
// Fade out logon-music here too to give the desired effect
|
|
// of "flowing" into the game.
|
|
soundManager.fadeOutMusic(1000);
|
|
|
|
// Allow any alpha opacity
|
|
theme->setMinimumOpacity(-1.0F);
|
|
|
|
if (chatLogger)
|
|
chatLogger->setServerName(settings.serverName);
|
|
|
|
#ifdef ANDROID
|
|
delete2(mCloseButton);
|
|
#endif // ANDROID
|
|
|
|
delete2(mSetupButton);
|
|
delete2(mVideoButton);
|
|
delete2(mThemesButton);
|
|
delete2(mAboutButton);
|
|
delete2(mHelpButton);
|
|
delete2(mPerfomanceButton);
|
|
delete2(desktop);
|
|
|
|
mCurrentDialog = nullptr;
|
|
|
|
logger->log1("State: GAME");
|
|
if (generalHandler)
|
|
generalHandler->reloadPartially();
|
|
mGame = new Game;
|
|
BLOCK_END("Client::gameExec State::GAME")
|
|
break;
|
|
|
|
case State::LOGIN_ERROR:
|
|
BLOCK_START("Client::gameExec State::LOGIN_ERROR")
|
|
logger->log1("State: LOGIN ERROR");
|
|
CREATEWIDGETV(mCurrentDialog, OkDialog,
|
|
// TRANSLATORS: error dialog header
|
|
_("Error"),
|
|
errorMessage,
|
|
// TRANSLATORS: ok dialog button
|
|
_("Close"),
|
|
DialogType::ERROR,
|
|
Modal_true,
|
|
ShowCenter_true,
|
|
nullptr,
|
|
260);
|
|
mCurrentDialog->addActionListener(&loginListener);
|
|
mCurrentDialog = nullptr; // OkDialog deletes itself
|
|
BLOCK_END("Client::gameExec State::LOGIN_ERROR")
|
|
break;
|
|
|
|
case State::ACCOUNTCHANGE_ERROR:
|
|
BLOCK_START("Client::gameExec State::ACCOUNTCHANGE_ERROR")
|
|
logger->log1("State: ACCOUNT CHANGE ERROR");
|
|
CREATEWIDGETV(mCurrentDialog, OkDialog,
|
|
// TRANSLATORS: error dialog header
|
|
_("Error"),
|
|
errorMessage,
|
|
// TRANSLATORS: ok dialog button
|
|
_("Close"),
|
|
DialogType::ERROR,
|
|
Modal_true,
|
|
ShowCenter_true,
|
|
nullptr,
|
|
260);
|
|
mCurrentDialog->addActionListener(&accountListener);
|
|
mCurrentDialog = nullptr; // OkDialog deletes itself
|
|
BLOCK_END("Client::gameExec State::ACCOUNTCHANGE_ERROR")
|
|
break;
|
|
|
|
case State::REGISTER_PREP:
|
|
BLOCK_START("Client::gameExec State::REGISTER_PREP")
|
|
logger->log1("State: REGISTER_PREP");
|
|
CREATEWIDGETV(mCurrentDialog, ConnectionDialog,
|
|
// TRANSLATORS: connection dialog header
|
|
_("Requesting registration details"),
|
|
State::LOGIN);
|
|
loginHandler->getRegistrationDetails();
|
|
BLOCK_END("Client::gameExec State::REGISTER_PREP")
|
|
break;
|
|
|
|
case State::REGISTER:
|
|
logger->log1("State: REGISTER");
|
|
CREATEWIDGETV(mCurrentDialog, RegisterDialog,
|
|
loginData);
|
|
break;
|
|
|
|
case State::REGISTER_ATTEMPT:
|
|
BLOCK_START("Client::gameExec State::REGISTER_ATTEMPT")
|
|
logger->log("Username is %s", loginData.username.c_str());
|
|
if (loginHandler)
|
|
loginHandler->registerAccount(&loginData);
|
|
BLOCK_END("Client::gameExec State::REGISTER_ATTEMPT")
|
|
break;
|
|
|
|
case State::CHANGEPASSWORD:
|
|
BLOCK_START("Client::gameExec State::CHANGEPASSWORD")
|
|
logger->log1("State: CHANGE PASSWORD");
|
|
CREATEWIDGETV(mCurrentDialog, ChangePasswordDialog,
|
|
loginData);
|
|
mCurrentDialog->setVisible(Visible_true);
|
|
BLOCK_END("Client::gameExec State::CHANGEPASSWORD")
|
|
break;
|
|
|
|
case State::CHANGEPASSWORD_ATTEMPT:
|
|
BLOCK_START("Client::gameExec "
|
|
"State::CHANGEPASSWORD_ATTEMPT")
|
|
logger->log1("State: CHANGE PASSWORD ATTEMPT");
|
|
if (loginHandler)
|
|
{
|
|
loginHandler->changePassword(loginData.password,
|
|
loginData.newPassword);
|
|
}
|
|
BLOCK_END("Client::gameExec State::CHANGEPASSWORD_ATTEMPT")
|
|
break;
|
|
|
|
case State::CHANGEPASSWORD_SUCCESS:
|
|
BLOCK_START("Client::gameExec "
|
|
"State::CHANGEPASSWORD_SUCCESS")
|
|
logger->log1("State: CHANGE PASSWORD SUCCESS");
|
|
CREATEWIDGETV(mCurrentDialog, OkDialog,
|
|
// TRANSLATORS: password change message header
|
|
_("Password Change"),
|
|
// TRANSLATORS: password change message text
|
|
_("Password changed successfully!"),
|
|
// TRANSLATORS: ok dialog button
|
|
_("OK"),
|
|
DialogType::ERROR,
|
|
Modal_true,
|
|
ShowCenter_true,
|
|
nullptr,
|
|
260);
|
|
mCurrentDialog->addActionListener(&accountListener);
|
|
mCurrentDialog = nullptr; // OkDialog deletes itself
|
|
loginData.password = loginData.newPassword;
|
|
loginData.newPassword.clear();
|
|
BLOCK_END("Client::gameExec State::CHANGEPASSWORD_SUCCESS")
|
|
break;
|
|
|
|
case State::CHANGEEMAIL:
|
|
logger->log1("State: CHANGE EMAIL");
|
|
CREATEWIDGETV(mCurrentDialog,
|
|
ChangeEmailDialog,
|
|
loginData);
|
|
mCurrentDialog->setVisible(Visible_true);
|
|
break;
|
|
|
|
case State::CHANGEEMAIL_ATTEMPT:
|
|
logger->log1("State: CHANGE EMAIL ATTEMPT");
|
|
if (loginHandler)
|
|
loginHandler->changeEmail(loginData.email);
|
|
break;
|
|
|
|
case State::CHANGEEMAIL_SUCCESS:
|
|
logger->log1("State: CHANGE EMAIL SUCCESS");
|
|
CREATEWIDGETV(mCurrentDialog, OkDialog,
|
|
// TRANSLATORS: email change message header
|
|
_("Email Change"),
|
|
// TRANSLATORS: email change message text
|
|
_("Email changed successfully!"),
|
|
// TRANSLATORS: ok dialog button
|
|
_("OK"),
|
|
DialogType::ERROR,
|
|
Modal_true,
|
|
ShowCenter_true,
|
|
nullptr,
|
|
260);
|
|
mCurrentDialog->addActionListener(&accountListener);
|
|
mCurrentDialog = nullptr; // OkDialog deletes itself
|
|
break;
|
|
|
|
case State::SWITCH_SERVER:
|
|
BLOCK_START("Client::gameExec State::SWITCH_SERVER")
|
|
logger->log1("State: SWITCH SERVER");
|
|
|
|
if (loginHandler)
|
|
loginHandler->disconnect();
|
|
if (gameHandler)
|
|
{
|
|
gameHandler->disconnect();
|
|
gameHandler->clear();
|
|
}
|
|
settings.serverName.clear();
|
|
settings.login.clear();
|
|
WindowManager::updateTitle();
|
|
serverConfig.write();
|
|
serverConfig.unload();
|
|
if (setupWindow)
|
|
setupWindow->externalUnload();
|
|
|
|
mState = State::CHOOSE_SERVER;
|
|
BLOCK_END("Client::gameExec State::SWITCH_SERVER")
|
|
break;
|
|
|
|
case State::SWITCH_LOGIN:
|
|
BLOCK_START("Client::gameExec State::SWITCH_LOGIN")
|
|
logger->log1("State: SWITCH LOGIN");
|
|
|
|
if (loginHandler)
|
|
{
|
|
loginHandler->logout();
|
|
loginHandler->disconnect();
|
|
}
|
|
if (gameHandler)
|
|
gameHandler->disconnect();
|
|
if (loginHandler)
|
|
loginHandler->connect();
|
|
|
|
settings.login.clear();
|
|
WindowManager::updateTitle();
|
|
mState = State::LOGIN;
|
|
BLOCK_END("Client::gameExec State::SWITCH_LOGIN")
|
|
break;
|
|
|
|
case State::SWITCH_CHARACTER:
|
|
BLOCK_START("Client::gameExec State::SWITCH_CHARACTER")
|
|
logger->log1("State: SWITCH CHARACTER");
|
|
|
|
// Done with game
|
|
if (gameHandler)
|
|
gameHandler->disconnect();
|
|
|
|
settings.login.clear();
|
|
WindowManager::updateTitle();
|
|
mState = State::GET_CHARACTERS;
|
|
BLOCK_END("Client::gameExec State::SWITCH_CHARACTER")
|
|
break;
|
|
|
|
case State::LOGOUT_ATTEMPT:
|
|
logger->log1("State: LOGOUT ATTEMPT");
|
|
break;
|
|
|
|
case State::WAIT:
|
|
logger->log1("State: WAIT");
|
|
break;
|
|
|
|
case State::EXIT:
|
|
BLOCK_START("Client::gameExec State::EXIT")
|
|
logger->log1("State: EXIT");
|
|
Net::unload();
|
|
BLOCK_END("Client::gameExec State::EXIT")
|
|
break;
|
|
|
|
case State::FORCE_QUIT:
|
|
BLOCK_START("Client::gameExec State::FORCE_QUIT")
|
|
logger->log1("State: FORCE QUIT");
|
|
if (generalHandler)
|
|
generalHandler->unload();
|
|
mState = State::EXIT;
|
|
BLOCK_END("Client::gameExec State::FORCE_QUIT")
|
|
break;
|
|
|
|
case State::ERROR:
|
|
BLOCK_START("Client::gameExec State::ERROR")
|
|
config.write();
|
|
if (mOldState == State::GAME)
|
|
serverConfig.write();
|
|
logger->log1("State: ERROR");
|
|
logger->log("Error: %s\n", errorMessage.c_str());
|
|
mCurrentDialog = DialogsManager::openErrorDialog(
|
|
// TRANSLATORS: error message header
|
|
_("Error"),
|
|
errorMessage,
|
|
Modal_true);
|
|
mCurrentDialog->addActionListener(&errorListener);
|
|
mCurrentDialog = nullptr; // OkDialog deletes itself
|
|
gameHandler->disconnect();
|
|
BLOCK_END("Client::gameExec State::ERROR")
|
|
break;
|
|
|
|
case State::AUTORECONNECT_SERVER:
|
|
// ++++++
|
|
break;
|
|
|
|
case State::START:
|
|
default:
|
|
mState = State::FORCE_QUIT;
|
|
break;
|
|
}
|
|
BLOCK_END("Client::gameExec 8")
|
|
}
|
|
PROFILER_END();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Client::optionChanged(const std::string &name)
|
|
{
|
|
if (name == "fpslimit")
|
|
{
|
|
const int fpsLimit = config.getIntValue("fpslimit");
|
|
settings.limitFps = fpsLimit > 0;
|
|
WindowManager::setFramerate(fpsLimit);
|
|
}
|
|
else if (name == "guialpha" ||
|
|
name == "enableGuiOpacity")
|
|
{
|
|
const float alpha = config.getFloatValue("guialpha");
|
|
settings.guiAlpha = alpha;
|
|
ImageHelper::setEnableAlpha(alpha != 1.0F &&
|
|
config.getBoolValue("enableGuiOpacity"));
|
|
}
|
|
else if (name == "gamma" ||
|
|
name == "enableGamma")
|
|
{
|
|
WindowManager::applyGamma();
|
|
}
|
|
else if (name == "particleEmitterSkip")
|
|
{
|
|
ParticleEngine::emitterSkip =
|
|
config.getIntValue("particleEmitterSkip") + 1;
|
|
}
|
|
else if (name == "vsync")
|
|
{
|
|
WindowManager::applyVSync();
|
|
}
|
|
else if (name == "repeateInterval" ||
|
|
name == "repeateDelay")
|
|
{
|
|
WindowManager::applyKeyRepeat();
|
|
}
|
|
}
|
|
|
|
void Client::action(const ActionEvent &event)
|
|
{
|
|
std::string tab;
|
|
const std::string &eventId = event.getId();
|
|
|
|
if (eventId == "close")
|
|
{
|
|
setState(State::FORCE_QUIT);
|
|
return;
|
|
}
|
|
if (eventId == "Setup")
|
|
{
|
|
tab.clear();
|
|
}
|
|
else if (eventId == "help")
|
|
{
|
|
inputManager.executeAction(InputAction::WINDOW_HELP);
|
|
return;
|
|
}
|
|
else if (eventId == "about")
|
|
{
|
|
inputManager.executeAction(InputAction::WINDOW_ABOUT);
|
|
return;
|
|
}
|
|
else if (eventId == "Video")
|
|
{
|
|
tab = "Video";
|
|
}
|
|
else if (eventId == "Themes")
|
|
{
|
|
tab = "Theme";
|
|
}
|
|
else if (eventId == "Perfomance")
|
|
{
|
|
tab = "Perfomance";
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (setupWindow)
|
|
{
|
|
setupWindow->setVisible(fromBool(
|
|
!setupWindow->isWindowVisible(), Visible));
|
|
if (setupWindow->isWindowVisible())
|
|
{
|
|
if (!tab.empty())
|
|
setupWindow->activateTab(tab);
|
|
setupWindow->requestMoveToTop();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Client::initFeatures()
|
|
{
|
|
features.init(paths.getStringValue("featuresFile"),
|
|
UseVirtFs_true,
|
|
SkipError_true);
|
|
features.setDefaultValues(getFeaturesDefaults());
|
|
settings.fixDeadAnimation = features.getBoolValue("fixDeadAnimation");
|
|
}
|
|
|
|
void Client::initPaths()
|
|
{
|
|
settings.gmCommandSymbol = paths.getStringValue("gmCommandSymbol");
|
|
settings.gmCharCommandSymbol = paths.getStringValue("gmCharCommandSymbol");
|
|
settings.linkCommandSymbol = paths.getStringValue("linkCommandSymbol");
|
|
if (settings.linkCommandSymbol.empty())
|
|
settings.linkCommandSymbol = "=";
|
|
settings.overweightPercent = paths.getIntValue("overweightPercent");
|
|
}
|
|
|
|
void Client::initTradeFilter()
|
|
{
|
|
const std::string tradeListName =
|
|
settings.serverConfigDir + "/tradefilter.txt";
|
|
|
|
std::ofstream tradeFile;
|
|
struct stat statbuf;
|
|
|
|
if (stat(tradeListName.c_str(), &statbuf) ||
|
|
!S_ISREG(statbuf.st_mode))
|
|
{
|
|
tradeFile.open(tradeListName.c_str(),
|
|
std::ios::out);
|
|
if (tradeFile.is_open())
|
|
{
|
|
tradeFile << ": sell" << std::endl;
|
|
tradeFile << ": buy" << std::endl;
|
|
tradeFile << ": trade" << std::endl;
|
|
tradeFile << "i sell" << std::endl;
|
|
tradeFile << "i buy" << std::endl;
|
|
tradeFile << "i trade" << std::endl;
|
|
tradeFile << "i trading" << std::endl;
|
|
tradeFile << "i am buy" << std::endl;
|
|
tradeFile << "i am sell" << std::endl;
|
|
tradeFile << "i am trade" << std::endl;
|
|
tradeFile << "i am trading" << std::endl;
|
|
tradeFile << "i'm buy" << std::endl;
|
|
tradeFile << "i'm sell" << std::endl;
|
|
tradeFile << "i'm trade" << std::endl;
|
|
tradeFile << "i'm trading" << std::endl;
|
|
}
|
|
else
|
|
{
|
|
reportAlways("Error opening file for writing: %s",
|
|
tradeListName.c_str());
|
|
}
|
|
tradeFile.close();
|
|
}
|
|
}
|
|
|
|
bool Client::isTmw()
|
|
{
|
|
const std::string &name = settings.serverName;
|
|
if (name == "server.themanaworld.org" ||
|
|
name == "themanaworld.org" ||
|
|
name == "167.114.129.72")
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Client::moveButtons(const int width)
|
|
{
|
|
if (mSetupButton)
|
|
{
|
|
int x = width - mSetupButton->getWidth() - mButtonPadding;
|
|
mSetupButton->setPosition(x, mButtonPadding);
|
|
#ifndef WIN32
|
|
x -= mPerfomanceButton->getWidth() + mButtonSpacing;
|
|
mPerfomanceButton->setPosition(x, mButtonPadding);
|
|
|
|
x -= mVideoButton->getWidth() + mButtonSpacing;
|
|
mVideoButton->setPosition(x, mButtonPadding);
|
|
|
|
x -= mThemesButton->getWidth() + mButtonSpacing;
|
|
mThemesButton->setPosition(x, mButtonPadding);
|
|
|
|
x -= mAboutButton->getWidth() + mButtonSpacing;
|
|
mAboutButton->setPosition(x, mButtonPadding);
|
|
|
|
x -= mHelpButton->getWidth() + mButtonSpacing;
|
|
mHelpButton->setPosition(x, mButtonPadding);
|
|
#ifdef ANDROID
|
|
x -= mCloseButton->getWidth() + mButtonSpacing;
|
|
mCloseButton->setPosition(x, mButtonPadding);
|
|
#endif // ANDROID
|
|
#endif // WIN32
|
|
}
|
|
}
|
|
|
|
void Client::windowRemoved(const Window *const window)
|
|
{
|
|
if (mCurrentDialog == window)
|
|
mCurrentDialog = nullptr;
|
|
}
|
|
|
|
void Client::logVars()
|
|
{
|
|
#ifdef ANDROID
|
|
logger->log("APPDIR: %s", getenv("APPDIR"));
|
|
logger->log("DATADIR2: %s", getSdStoragePath().c_str());
|
|
#endif // ANDROID
|
|
}
|
|
|
|
void Client::slowLogic()
|
|
{
|
|
if (!gameHandler ||
|
|
!gameHandler->mustPing())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (get_elapsed_time1(mPing) > 1500)
|
|
{
|
|
mPing = tick_time;
|
|
if (mState == State::UPDATE ||
|
|
mState == State::LOGIN ||
|
|
mState == State::LOGIN_ATTEMPT)
|
|
{
|
|
if (loginHandler)
|
|
loginHandler->ping();
|
|
if (generalHandler)
|
|
generalHandler->flushSend();
|
|
}
|
|
else if (mState == State::CHAR_SELECT)
|
|
{
|
|
if (charServerHandler)
|
|
charServerHandler->ping();
|
|
if (generalHandler)
|
|
generalHandler->flushSend();
|
|
}
|
|
}
|
|
}
|