// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) 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 Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "stdpch.h" #include "nel/3d/u_driver.h" #include "far_tp.h" #include "net_manager.h" #include "connection.h" #include "interface_v3/interface_manager.h" #include "login_patch.h" #include "init_main_loop.h" #include "weather.h" #include "time_client.h" #include "timed_fx_manager.h" #include "world_database_manager.h" #include "continent_manager.h" #include "user_entity.h" #include "entities.h" #include "interface_v3/input_handler_manager.h" #include "interface_v3/bot_chat_page_all.h" #include "sound_manager.h" #include "actions_client.h" #include "r2/editor.h" #include "global.h" #include "release.h" #include "nel/misc/string_conversion.h" #include "debug_client.h" #include "session_browser_impl.h" #include "game_share/security_check.h" #include "client_chat_manager.h" #include "bg_downloader_access.h" #include "login_progress_post_thread.h" #include "interface_v3/action_handler_base.h" using namespace NLMISC; using namespace NLNET; using namespace NL3D; using namespace RSMGR; using namespace R2; extern CClientChatManager ChatMngr; extern CVariable SBSPortOffset; // *************************************************************************** // Login state machine // *************************************************************************** // Adapted from "nel/misc/string_conversion.h" #define NL_BEGIN_CLASS_STRING_CONVERSION_TABLE(__class, __type) \ static const NLMISC::CStringConversion<__class::__type>::CPair __type##_nl_string_conversion_table[] = \ { #define NL_END_CLASS_STRING_CONVERSION_TABLE(__class, __type, __tableName, __defaultValue) \ }; \ NLMISC::CStringConversion<__class::__type> \ __tableName(__type##_nl_string_conversion_table, sizeof(__type##_nl_string_conversion_table) \ / sizeof(__type##_nl_string_conversion_table[0]), __class::__defaultValue); #define NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(__class, val) { #val, __class::val}, NL_BEGIN_CLASS_STRING_CONVERSION_TABLE (CLoginStateMachine, TState) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_start) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_login) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_auto_login) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_shard_list) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_start_config) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_scan_data) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_display_eula) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_check_patch) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_display_cat) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_patch) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_close_client) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_reboot_screen) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_restart_client) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_connect) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_browser_screen) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_ingame) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_leave_shard) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_enter_far_tp_main_loop) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_disconnect) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_reconnect_fs) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_reconnect_select_char) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_reconnect_ready) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_exit_global_menu) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_reconnect_error) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_create_account) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_end) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, st_unknown) NL_END_CLASS_STRING_CONVERSION_TABLE(CLoginStateMachine, TState, StateConversion, st_unknown) NL_BEGIN_CLASS_STRING_CONVERSION_TABLE (CLoginStateMachine, TEvent) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_quit) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_skip_all_login) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_init_done) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_game_conf) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_data_scan) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_close_data_scan) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_login_ok) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_bad_login) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_shard_selected) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_patch_needed) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_no_patch) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_run_patch) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_close_patch) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_accept_eula) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_decline_eula) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_reboot) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_ingame_return) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_far_tp_main_loop_entered) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_connect) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_conn_failed) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_conn_dropped) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_enter_game) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_self_reconnected) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_chars_received) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_no_user_char) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_ready_received) // NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_reselect_char) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_reconnect_ok_received) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_global_menu_exited) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_relog) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_create_account) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_close_create_account) NL_CLASS_STRING_CONVERSION_TABLE_ENTRY(CLoginStateMachine, ev_unknown) NL_END_CLASS_STRING_CONVERSION_TABLE(CLoginStateMachine, TEvent, EventConversion, ev_unknown) //----------------------------------------------- // toString : //----------------------------------------------- const std::string& CLoginStateMachine::toString(CLoginStateMachine::TState state) { return StateConversion.toString(state); } //----------------------------------------------- // toString : //----------------------------------------------- const std::string& CLoginStateMachine::toString(CLoginStateMachine::TEvent event) { return EventConversion.toString(event); } #define SM_BEGIN_EVENT_TABLE \ for(;!isTerminationRequested();) \ { \ TEvent ev = waitEvent(); \ #define SM_END_EVENT_TABLE \ } \ /* #define SM_EVENT(eventId, stateId) \ if (ev == eventId) \ { \ try \ { \ COFile outputF; \ string sLog = NLMISC::toString("[%s] %s -> %s\n", CLoginStateMachine::toString(ev).c_str(), CLoginStateMachine::toString(_CurrentState).c_str(), CLoginStateMachine::toString(stateId).c_str()); \ if ( outputF.open( getLogDirectory() + "error_join.log", true, true ) ) \ { \ outputF.serialBuffer( (uint8*)(&sLog[0]), (uint)sLog.size() ); \ outputF.close(); \ } \ } \ catch (const Exception &) \ {} \ _CurrentState = stateId; \ break; \ } \ */ #define SM_EVENT(eventId, stateId) \ if (ev == eventId) \ { \ _CurrentState = stateId; \ break; \ } \ extern std::string LoginLogin, LoginPassword, LoginCustomParameters; extern bool noUserChar; extern bool userChar; extern bool serverReceivedReady; extern bool CharNameValidArrived; extern bool FirstFrame; extern bool IsInRingSession; extern void selectTipsOfTheDay (uint tips); #define BAR_STEP_TP 2 CLoginStateMachine::TEvent CLoginStateMachine::waitEvent() { nlassert(CCoTask::getCurrentTask() == this); while (_NextEvents.empty() && !isTerminationRequested()) { // wait until someone push an event on the state machine yield(); if (isTerminationRequested()) return ev_quit; } TEvent ev = _NextEvents.front(); _NextEvents.pop_front(); return ev; } void CLoginStateMachine::pushEvent(TEvent eventId) { // set the next event _NextEvents.push_back(eventId); if (CCoTask::getCurrentTask() != this) { /// resume the state machine to advance state resume(); } } void CLoginStateMachine::run() { // state machine coroutine while (_CurrentState != st_end && !isTerminationRequested()) { switch(_CurrentState) { case st_start: /// initial state if (!ClientCfg.TestBrowser) { if (LoginLogin.empty()) { if (LoginCustomParameters.empty()) { // standard procedure SM_BEGIN_EVENT_TABLE SM_EVENT(ev_init_done, st_login); SM_EVENT(ev_skip_all_login, st_ingame); SM_EVENT(ev_quit, st_end); SM_END_EVENT_TABLE } else { // alternate login procedure SM_BEGIN_EVENT_TABLE SM_EVENT(ev_init_done, st_alt_login); SM_EVENT(ev_skip_all_login, st_ingame); SM_EVENT(ev_quit, st_end); SM_END_EVENT_TABLE } } else { // login 2, bypass the login screen SM_BEGIN_EVENT_TABLE SM_EVENT(ev_init_done, st_auto_login); SM_EVENT(ev_skip_all_login, st_ingame); SM_EVENT(ev_quit, st_end); SM_END_EVENT_TABLE } } // else // { // // browser test mode // SM_BEGIN_EVENT_TABLE // SM_EVENT(ev_init_done, st_browser_screen); // SM_EVENT(ev_quit, st_end); // SM_END_EVENT_TABLE // } break; case st_login: /// display login screen and options { initLoginScreen(); if (ClientCfg.R2Mode) { // r2 mode SM_BEGIN_EVENT_TABLE SM_EVENT(ev_login_ok, st_check_patch); SM_EVENT(ev_game_conf, st_start_config); SM_EVENT(ev_data_scan, st_scan_data); SM_EVENT(ev_create_account, st_create_account); SM_EVENT(ev_bad_login, st_login); SM_EVENT(ev_quit, st_end); SM_END_EVENT_TABLE } else { // legacy mode SM_BEGIN_EVENT_TABLE SM_EVENT(ev_login_ok, st_shard_list); SM_EVENT(ev_game_conf, st_start_config); SM_EVENT(ev_data_scan, st_scan_data); SM_EVENT(ev_create_account, st_create_account); SM_EVENT(ev_bad_login, st_login); SM_EVENT(ev_quit, st_end); SM_END_EVENT_TABLE } } break; case st_auto_login: initAutoLogin(); // if (ClientCfg.R2Mode) { // r2 mode SM_BEGIN_EVENT_TABLE SM_EVENT(ev_login_ok, st_check_patch); SM_EVENT(ev_quit, st_end); SM_END_EVENT_TABLE } // else // { // // legacy mode // SM_BEGIN_EVENT_TABLE // SM_EVENT(ev_login_ok, st_check_patch); // SM_EVENT(ev_quit, st_end); // SM_END_EVENT_TABLE // } break; case st_alt_login: initAltLogin(); // if (ClientCfg.R2Mode) { // r2 mode SM_BEGIN_EVENT_TABLE SM_EVENT(ev_login_not_alt, st_login); SM_EVENT(ev_login_ok, st_check_patch); SM_EVENT(ev_quit, st_end); SM_END_EVENT_TABLE } // else // { // // legacy mode // SM_BEGIN_EVENT_TABLE // SM_EVENT(ev_login_ok, st_check_patch); // SM_EVENT(ev_quit, st_end); // SM_END_EVENT_TABLE // } break; case st_shard_list: /// display the shard list initShardDisplay(); SM_BEGIN_EVENT_TABLE SM_EVENT(ev_shard_selected, st_check_patch); SM_EVENT(ev_quit, st_end); SM_END_EVENT_TABLE break; case st_start_config: /// launch the configurator and close ryzom break; case st_scan_data: /// run the check data thread initDataScan(); SM_BEGIN_EVENT_TABLE SM_EVENT(ev_close_data_scan, st_login); SM_EVENT(ev_quit, st_end); SM_END_EVENT_TABLE break; case st_create_account: if (initCreateAccount()) { SM_BEGIN_EVENT_TABLE SM_EVENT(ev_login_ok, st_check_patch); SM_EVENT(ev_close_create_account, st_login); SM_EVENT(ev_quit, st_end); SM_END_EVENT_TABLE } else { // return to login menu if an error occured _CurrentState = st_login; } break; case st_display_eula: { /// display the eula and wait for validation if (ClientCfg.SkipEULA) { // we don't want to see eula, auto accept pushEvent(ev_accept_eula); } bool mustReboot = false; if (isBGDownloadEnabled()) { mustReboot = CBGDownloaderAccess::getInstance().mustLaunchBatFile(); } else { mustReboot = CPatchManager::getInstance()->mustLaunchBatFile(); } if (mustReboot) { // skip eula and show reboot screen _CurrentState = st_reboot_screen; break; } initEula(); // Login sequence was reordered // if (ClientCfg.R2Mode) // { // // ring mode // SM_BEGIN_EVENT_TABLE // SM_EVENT(ev_accept_eula, st_browser_screen); // SM_EVENT(ev_quit, st_end); // SM_END_EVENT_TABLE // } // else // { // // legacy mode SM_BEGIN_EVENT_TABLE SM_EVENT(ev_accept_eula, st_connect); SM_EVENT(ev_quit, st_end); SM_END_EVENT_TABLE // } } break; case st_check_patch: /// check the data to check if patch needed CLoginProgressPostThread::getInstance().step(CLoginStep(LoginStep_PostLogin, "login_step_post_login")); if (!ClientCfg.PatchWanted) { // client don't want to be patched ! _CurrentState = st_display_eula; break; } initPatchCheck(); SM_BEGIN_EVENT_TABLE if (isBGDownloadEnabled()) { SM_EVENT(ev_patch_needed, st_patch); // no choice for patch content when background downloader is used } else { SM_EVENT(ev_patch_needed, st_display_cat); } SM_EVENT(ev_no_patch, st_display_eula); SM_EVENT(ev_quit, st_end); SM_END_EVENT_TABLE break; case st_display_cat: initCatDisplay(); SM_BEGIN_EVENT_TABLE SM_EVENT(ev_run_patch, st_patch); SM_EVENT(ev_quit, st_end); SM_END_EVENT_TABLE break; case st_patch: /// run the patch process and display progress initPatch(); SM_BEGIN_EVENT_TABLE SM_EVENT(ev_close_patch, st_display_eula); SM_EVENT(ev_quit, st_end); SM_END_EVENT_TABLE break; case st_close_client: /// terminate the client and quit break; case st_reboot_screen: /// display the reboot screen and wait validation initReboot(); SM_BEGIN_EVENT_TABLE SM_EVENT(ev_reboot, st_end); SM_EVENT(ev_quit, st_end); SM_END_EVENT_TABLE break; case st_restart_client: /// restart the client with login bypass params break; case st_connect: /// connect to the FS (start the 'in game' mode) ConnectToShard(); if (ClientCfg.R2Mode) { SM_BEGIN_EVENT_TABLE SM_EVENT(ev_enter_game, st_ingame); SM_END_EVENT_TABLE } else { SM_BEGIN_EVENT_TABLE SM_EVENT(ev_enter_game, st_end); SM_EVENT(ev_conn_failed, st_shard_list); SM_END_EVENT_TABLE } break; // case st_browser_screen: // /// show the outgame browser // // if (!ClientCfg.TestBrowser) // { // // in test browser mode, the browser is already init // initWebBrowser(); // } // // SM_BEGIN_EVENT_TABLE // SM_EVENT(ev_connect, st_connect); // SM_EVENT(ev_relog, st_login); // SM_EVENT(ev_quit, st_end); // SM_END_EVENT_TABLE // // break; case st_ingame: SM_BEGIN_EVENT_TABLE //SM_EVENT(ev_chars_received, st_ingame); // ignored for normal login procedure //SM_EVENT(ev_no_user_char, st_ingame); // " //SM_EVENT(ev_ready_received, st_ingame); // " //SM_EVENT(ev_global_menu_exited, st_ingame); " SM_EVENT(ev_connect, st_leave_shard); // connect to another shard SM_END_EVENT_TABLE break; case st_leave_shard: /* * Far TP / Server Hop / Reselect character entry point */ // Server Hop part 1: We are not in main loop but still at character selection // => make a hop to the specified server without doing anything with interface // Far TP part 1.1: From the ingame main loop, the admin html box gives us an event ev_connect for the destination shard. // Note: the admin html box is run by CInputHandlerManager::getInstance()->pumpEvents() in the main loop. // Tip: to see where a co-task is resumed from, just add a breakpoint on the end of CCoTask::resume(). CAHManager::getInstance()->runActionHandler("quit_ryzom", NULL, ""); if (!FarTP.isIngame()) // assumes there is no Far TP starting between char selection and main loop, see below { crashLogAddServerHopEvent(); FarTP.onServerQuitOk(); // don't wait for onServerQuitOK() because the EGS will no longer send it if it requests a Far TP during "select char" (not sending USER_CHAR) SM_BEGIN_EVENT_TABLE SM_EVENT(ev_ingame_return, st_disconnect); SM_END_EVENT_TABLE } else { if(FarTP.isReselectingChar()) crashLogAddReselectPersoEvent(); else crashLogAddFarTpEvent(); if (NetMngr.getConnectionState() == CNetworkConnection::Disconnect) FarTP.onServerQuitOk(); // don't wait for onServerQuitOK() because the EGS is down SM_BEGIN_EVENT_TABLE SM_EVENT(ev_ingame_return, st_enter_far_tp_main_loop); SM_EVENT(ev_enter_game, st_ingame); SM_END_EVENT_TABLE } break; case st_enter_far_tp_main_loop: // if bgdownloader is used, then pause it pauseBGDownloader(); // Far TP part 1.2: let the main loop finish the current frame. // This is called when CONNECTION:SERVER_QUIT_OK is received (from NetMngr.update() in main loop). SM_BEGIN_EVENT_TABLE SM_EVENT(ev_far_tp_main_loop_entered, st_disconnect); SM_END_EVENT_TABLE break; case st_disconnect: // Far TP part 2: disconnect from the FS and unload shard-specific data (called from farTPmainLoop()) // FarTP.disconnectFromPreviousShard(); SM_BEGIN_EVENT_TABLE SM_EVENT(ev_connect, st_reconnect_fs); SM_END_EVENT_TABLE break; case st_reconnect_fs: // Server Hop part 3.1: connect to the new shard (called from globalMenu()) // Far TP part 3.1: connect to the new shard (called from farTPmainLoop()) FarTP.connectToNewShard(); SM_BEGIN_EVENT_TABLE SM_EVENT(ev_self_reconnected, st_ingame); SM_EVENT(ev_chars_received, st_reconnect_select_char); SM_EVENT(ev_no_user_char, st_reconnect_error); SM_EVENT(ev_conn_failed, st_reconnect_error); SM_EVENT(ev_conn_dropped, st_reconnect_error); SM_END_EVENT_TABLE break; case st_reconnect_select_char: // Server Hop part 3.2: bypass character selection ui & select the same character. // Far TP part 3.2: bypass character selection ui & select the same character. // This is called from farTPmainloop(), when CONNECTION:USER_CHARS is received. if( !FarTP.isReselectingChar() ) { FarTP.selectCharAndEnter(); } // Far TP part 3.2bis: see in farTPmainLoop() if (!FarTP.isIngame()) { SM_BEGIN_EVENT_TABLE SM_EVENT(ev_ready_received, st_exit_global_menu); SM_EVENT(ev_conn_dropped, st_reconnect_error); SM_END_EVENT_TABLE } else { SM_BEGIN_EVENT_TABLE SM_EVENT(ev_ready_received, st_reconnect_ready); SM_EVENT(ev_conn_dropped, st_reconnect_error); if ( FarTP.isReselectingChar() && (ev == ev_connect) ) { // Inside this Character Reselect we embed a new Server Hop FarTP.beginEmbeddedServerHop(); _CurrentState = st_leave_shard; break; } SM_END_EVENT_TABLE } break; case st_exit_global_menu: // Server Hop part 3.3 // Stay in Server Hop state until the global menu has been exited SM_BEGIN_EVENT_TABLE SM_EVENT(ev_global_menu_exited, FarTP.isServerHopEmbedded() ? st_reconnect_ready : st_ingame); SM_END_EVENT_TABLE break; case st_reconnect_ready: // Far TP part 3.3: send ready. // This is called from farTPmainloop(), when CONNECTION:READY is received. if ( FarTP.isServerHopEmbedded() ) FarTP.endEmbeddedServerHop(); SM_BEGIN_EVENT_TABLE SM_EVENT(ev_enter_game, st_ingame); SM_EVENT(ev_conn_dropped, st_reconnect_error); SM_END_EVENT_TABLE break; case st_reconnect_error: // Far TP failed FarTP.onFailure(); SM_BEGIN_EVENT_TABLE SM_EVENT(ev_quit, st_end); SM_END_EVENT_TABLE break; default: nlwarning("Unhandeled state"); break; } } } // *************************************************************************** // Far TP // *************************************************************************** CFarTP FarTP; extern std::string CurrentCookie; extern string ClientApp; namespace R2 { extern bool ReloadUIFlag; } /* * requestFarTPToSession */ bool CFarTP::requestFarTPToSession(TSessionId sessionId, uint8 charSlot, CFarTP::TJoinMode joinMode, bool bailOutIfSessionVanished) { if (_HookedForEditor) { // Redirect Far TP to editor instead of following instructions _HookedForEditor = false; return FarTP.requestFarTPToSession((TSessionId)0, charSlot, CFarTP::LaunchEditor, true); // allow bailing out in case of edition session not reachable } // call the join session using the session browser interface uint32 charId = (NetMngr.getLoginCookie().getUserId()<<4)+(0xf&charSlot); // Clean out for next Far TP _SessionIdToJoinFast = 0; CSessionBrowserImpl &sb = CSessionBrowserImpl::getInstance(); sb.init(NULL); // sb.setAuthInfo(NetMngr.getLoginCookie()); // sb.connectItf(CInetAddress("borisb", 80)); sb.CurrentJoinMode = joinMode; // send the join session // _JoinSessionResultReceived = false; try { switch (joinMode) { case LaunchEditor: { retryJoinEdit: TSessionId sessionId(0); sb.joinEditSession(charId, ClientApp); bool ret = sb.waitOneMessage(CSessionBrowserImpl::getMessageName("on_joinSessionResult")); if (!ret) throw "Protocol error"; if (sb._LastJoinSessionResult == 2) { // the edit session did not exist, create a new one sb.scheduleSession(charId, TSessionType::st_edit, "", "", TSessionLevel::sl_a, /*TAccessType::at_private, */TRuleType::rt_strict, TEstimatedDuration::et_long, 0, TAnimMode(), TRaceFilter(), TReligionFilter(), TGuildFilter(), TShardFilter(), TLevelFilter(), std::string(), TSessionOrientation(), true, true); ret = sb.waitOneMessage(CSessionBrowserImpl::getMessageName("on_scheduleSessionResult")); if (!ret || sb._LastScheduleSessionResult!= 0) throw "schedule error"; sessionId = sb._LastScheduleSessionId; // invite the char in the session sb.inviteCharacter(charId, sessionId, charId, TSessionPartStatus::sps_edit_invited); ret = sb.waitOneMessage(CSessionBrowserImpl::getMessageName("on_invokeResult")); if (!ret || sb._LastInvokeResult != 0) throw "Invitation Error"; // now, start the edit session sb.startSession(charId, sessionId); ret = sb.waitOneMessage(CSessionBrowserImpl::getMessageName("on_invokeResult")); if (!ret || sb._LastInvokeResult != 0) throw "start session error"; // retry to join goto retryJoinEdit; } else if (sb._LastJoinSessionResult == 15) { sessionId = sb._LastJoinSessionId; // the session was closed, the SU has put it in planned state, start it sb.startSession(charId, sessionId); ret = sb.waitOneMessage(CSessionBrowserImpl::getMessageName("on_invokeResult")); if (!ret || sb._LastInvokeResult != 0) throw "start session error (2), no DSS available"; // retry to join goto retryJoinEdit; } else if (sb._LastJoinSessionResult != 0) { // any other error are not recoverable from here throw "join session error"; } // ok, the join is accepted !, the other far TP work is done in the callback } break; case JoinSession: { sb.joinSession(charId, sessionId, ClientApp); bool ret = sb.waitOneMessage(CSessionBrowserImpl::getMessageName("on_joinSessionResult")); if (!ret) throw "Protocol error"; if (sb._LastJoinSessionResult == 16) { // #pragma message (NL_LOC_WRN "inform the player that he is banned from the ring") throw "User ban from the ring"; } if (sb._LastJoinSessionResult != 0) { throw "Join session error"; } // ok, the join is accepted !, the other far TP work is done in the callback } break; case JoinMainland: //retryJoinMainland: { TSessionId sessionId(0); sb.joinMainland(charId, ClientApp); bool ret = sb.waitOneMessage(CSessionBrowserImpl::getMessageName("on_joinSessionResult")); if (!ret) throw "Protocol error"; if (sb._LastJoinSessionResult != 0) { // some error during the join throw "Error joining session"; } // ok, the join is accepted !, the other far TP work is done in the callback } break; default: nlstop; } return true; } // // const string url = _URLBase + string(_URLTable[joinMode]); // string res; // try // { // // Get the address of a front-end service via http // if (!HttpClient.connect(url)) // throw 1; // switch (joinMode) // { // case CFarTP::LaunchEditor: // if (!HttpClient.sendGetWithCookie(url, "ryzomId", CurrentCookie, toString("charSlot=%u", charSlot))) // throw 2; // break; // case CFarTP::JoinSession: // if (!HttpClient.sendPostWithCookie(url, "ryzomId", CurrentCookie, toString("sessionId=%u&charSlot=%u", sessionId, charSlot))) // throw 2; // break; // case CFarTP::JoinMainland: // if (!HttpClient.sendPostWithCookie(url, "ryzomId", CurrentCookie, "ml=")) // throw 2; // break; // } // if (!HttpClient.receive(res)) // throw 3; // HttpClient.disconnect(); // string::size_type luaPos = res.find(""); // if (luaPos == string::npos) // throw 4; // res = res.substr(luaPos + 5); // res = res.substr(0, res.find("")); // nlinfo("Found server for %ssession %u", joinMode==CFarTP::LaunchEditor ? "editing " : "", sessionId); // // // Begin Far TP // CInterfaceManager *pIM = CInterfaceManager::getInstance(); // pIM->executeLuaScript(res, true); // return true; // } catch ( char *errorMsg ) { // // Get more details on the error // string httpErrorStr; // if (errorNum < 4) // we didn't receive an answer // httpErrorStr = toString(" (HTTP error %u)", errorNum); // if ( errorNum == 4 ) // { // string::size_type posEndOfFirstLine = res.find( "\n" ); // if ( posEndOfFirstLine == string::npos ) // { // // Invalid http answer // httpErrorStr = toString(" (HTTP invalid)"); // } // else // { // // Http status not OK (e.g. "404 Not Found") // httpErrorStr = res.substr( 0, posEndOfFirstLine ); // if ( httpErrorStr.find( "200 OK" ) == string::npos ) // httpErrorStr = " (" + httpErrorStr + ")"; // else // httpErrorStr.clear(); // } // } bool requestRetToMainland = /*httpErrorStr.empty() &&*/ bailOutIfSessionVanished; bool isAttemptingEmbeddedServerHop = isReselectingChar() && (joinMode != CFarTP::JoinSession); bool letReturnToCharSelect = (!isIngame()) || isAttemptingEmbeddedServerHop; // User-friendly message CInterfaceManager *pIM = CInterfaceManager::getInstance(); if ( letReturnToCharSelect ) { // // Hide all buttons except Quit. If !requestRetToMainland, we will show them back at the end of connectToNewShard(). // CAHManager::getInstance()->runActionHandler( "proc", NULL, "charsel_disable_buttons" ); // CAHManager::getInstance()->runActionHandler( "set", NULL, "target_property=ui:outgame:charsel:quit_but:active|value=1" ); CInterfaceElement *btnOk = CWidgetManager::getInstance()->getElementFromId("ui:outgame:charsel:message_box:ok"); if (btnOk) btnOk->setActive( ! requestRetToMainland ); // Hide the black screen i.e. force showing the interface CInterfaceElement *charSelBlackScreen = CWidgetManager::getInstance()->getElementFromId("ui:outgame:charsel:black_screen"); if (charSelBlackScreen) { CViewBase *charSelBlackScreenBitmap = dynamic_cast(charSelBlackScreen); if (charSelBlackScreenBitmap) charSelBlackScreenBitmap->setAlpha(0); } } pIM->messageBoxWithHelp( CI18N::get(requestRetToMainland ? "uiSessionVanishedFarTP" : "uiSessionUnreachable") + ucstring(errorMsg), letReturnToCharSelect ? "ui:outgame:charsel" : "ui:interface"); // Info in the log string outErrorMsg = toString("Could not join %ssession %u: '%s' error\n", joinMode==CFarTP::LaunchEditor ? "editing " : "", sessionId.asInt(), errorMsg ); nlwarning( outErrorMsg.c_str() ); // if ( httpErrorStr.empty() ) // { // string delimiter = "Content-Type: text/html"; // string::size_type delimPos = res.find( delimiter ); // if ( delimPos != string::npos ) // res = res.substr( delimPos + delimiter.size() ); // } // uint pos = 0; // do // { // WarningLog->displayRaw( res.substr( pos, 200 ).c_str() ); // truncates long strings // pos += 200; // } // while ( pos < res.size() ); WarningLog->displayRawNL( "" ); // Save this error (regular log file is deleted at every startup) // res = res + "\n\n"; /*try { COFile outputF; if ( outputF.open( getLogDirectory() + "error_join.log", true, true ) ) { time_t currentTime; time( ¤tTime ); string headerS = NLMISC::toString( "\n\n%s%s\n\n", asctime(localtime(¤tTime)), outErrorMsg.c_str() ); outputF.serialBuffer( (uint8*)(&headerS[0]), (uint)headerS.size() ); // outputF.serialBuffer( (uint8*)(&res[0]), res.size() ); outputF.close(); } } catch (const Exception &) {} */ // If the session is not a permanent session and has vanished, pop the position if ( requestRetToMainland ) { requestReturnToPreviousSession( sessionId ); LastGameCycle = NetMngr.getCurrentServerTick(); NetMngr.send(NetMngr.getCurrentServerTick()); } else if ( letReturnToCharSelect ) { // Show all buttons except 'New character' so that the character can retry entering game or choose another character. CAHManager::getInstance()->runActionHandler( "proc", NULL, "charsel_enable_buttons" ); CAHManager::getInstance()->runActionHandler( "set", NULL, "target_property=ui:outgame:charsel:create_new_but:active|value=0" ); CInterfaceGroup* charselGroup = dynamic_cast(CWidgetManager::getInstance()->getElementFromId("ui:outgame:charsel")); if(charselGroup) CAHManager::getInstance()->runActionHandler( "proc", charselGroup, "charsel_init_buttons" ); } return false; } } /* * hookNextFarTPForEditor */ void CFarTP::hookNextFarTPForEditor() { _HookedForEditor = true; } /* * requestReturnToPreviousSession */ void CFarTP::requestReturnToPreviousSession(TSessionId rejectedSessionId) { const string msgName = "CONNECTION:RET_MAINLAND"; CBitMemStream out; nlverify(GenericMsgHeaderMngr.pushNameToStream(msgName, out)); out.serial(PlayerSelectedSlot); out.serial(rejectedSessionId); NetMngr.push(out); nlinfo("%s sent", msgName.c_str()); } /* * requestReconnection */ void CFarTP::requestReconnection() { _ReselectingChar = true; if (!requestFarTPToSession(TSessionId(std::numeric_limits::max()), std::numeric_limits::max(), CFarTP::JoinMainland, false)) _ReselectingChar = false; } const char * CFarTP::_URLTable[CFarTP::NbJoinModes] = { "edit_session.php", "join_session.php", "join_shard.php" }; /* * States */ bool CFarTP::isFarTPInProgress() const { CLoginStateMachine::TState state = LoginSM.getCurrentState(); return (state == CLoginStateMachine::st_leave_shard || state == CLoginStateMachine::st_enter_far_tp_main_loop || state == CLoginStateMachine::st_disconnect || state == CLoginStateMachine::st_reconnect_fs || state == CLoginStateMachine::st_reconnect_select_char || state == CLoginStateMachine::st_reconnect_ready || state == CLoginStateMachine::st_reconnect_error); } bool CFarTP::isServerHopInProgress() const { CLoginStateMachine::TState state = LoginSM.getCurrentState(); return (state == CLoginStateMachine::st_leave_shard || state == CLoginStateMachine::st_reconnect_fs || state == CLoginStateMachine::st_reconnect_select_char || state == CLoginStateMachine::st_exit_global_menu || state == CLoginStateMachine::st_reconnect_error); // TODO: error handling } // from begin of Far TP to disconnection bool CFarTP::isLeavingShard() const { CLoginStateMachine::TState state = LoginSM.getCurrentState(); return (state == CLoginStateMachine::st_leave_shard || state == CLoginStateMachine::st_enter_far_tp_main_loop || state == CLoginStateMachine::st_disconnect); } // from reconnection to game entering bool CFarTP::isJoiningShard() const { CLoginStateMachine::TState state = LoginSM.getCurrentState(); return (state == CLoginStateMachine::st_reconnect_fs || state == CLoginStateMachine::st_reconnect_select_char || state == CLoginStateMachine::st_reconnect_ready || state == CLoginStateMachine::st_reconnect_error); } /* * Events */ void CFarTP::onServerQuitOk() { game_exit_request = false; ryzom_exit_request = false; if (LoginSM.getCurrentState() == CLoginStateMachine::st_leave_shard) LoginSM.pushEvent(CLoginStateMachine::ev_ingame_return); } void CFarTP::onServerQuitAbort() { game_exit_request = false; ryzom_exit_request = false; if (LoginSM.getCurrentState() == CLoginStateMachine::st_leave_shard) LoginSM.pushEvent(CLoginStateMachine::ev_enter_game); _ReselectingChar = false; } void CFarTP::disconnectFromPreviousShard() { CInterfaceManager *pIM = CInterfaceManager::getInstance(); if (isIngame()) { // Display background (TODO: not Kami) beginLoading (StartBackground); UseEscapeDuringLoading = false; // Play music and fade out the Game Sound if (SoundMngr) { // Loading Music Loop.ogg LoadingMusic = ClientCfg.SoundOutGameMusic; SoundMngr->playEventMusic(LoadingMusic, CSoundManager::LoadingMusicXFade, true); SoundMngr->fadeOutGameSound(ClientCfg.SoundTPFade); } // Change the tips selectTipsOfTheDay (rand()); // Start progress bar and display background ProgressBar.reset (BAR_STEP_TP); ucstring nmsg("Loading..."); ProgressBar.newMessage ( ClientCfg.buildLoadingString(nmsg) ); ProgressBar.progress(0); } // Disconnect from the FS NetMngr.disconnect(); if (isIngame()) { // Save the R2EDEnabled flag to know if we are switching it when we reconnect _PreviousR2EdEnabled = ClientCfg.R2EDEnabled; // Release R2 editor if applicable R2::getEditor().autoConfigRelease(IsInRingSession); } if( isReselectingChar() ) { releaseMainLoopReselect(); } else { // String manager: remove all waiting callbacks and removers // (if some interface stuff has not received its string yet, its remover will get useless) STRING_MANAGER::CStringManagerClient::release( false ); // Ugly globals userChar = false; noUserChar = false; serverReceivedReady = false; CharNameValidArrived = false; UserCharPosReceived = false; SabrinaPhraseBookLoaded = false; } // Restart the network manager, the interface sync counter and the entities pIM->resetShardSpecificData(); /* ServerToLocal autocopy stuff: When reinit will reset server counters to 0 in the server database, onServerChange() will be called and data will be copied since CInterfaceManager::_LocalSyncActionCounter==0 (done in resetShardSpecificData() above) If we do the resetShardSpecificData() after, scenari could arise where Local database could not be reseted */ NetMngr.reinit(); if (isIngame() && !isReselectingChar()) { nlinfo("FarTP: calling EntitiesMngr.reinit()"); EntitiesMngr.reinit(); } LoginSM.pushEvent(CLoginStateMachine::ev_connect); } void CFarTP::connectToNewShard() { // TODO: start commands? // Connect to the next FS NetMngr.initCookie(Cookie, FSAddr); // connect the session browser to the new shard NLNET::CInetAddress sbsAddress(CSessionBrowserImpl::getInstance().getFrontEndAddress()); sbsAddress.setPort(sbsAddress.port()+SBSPortOffset); CSessionBrowserImpl::getInstance().connectItf(sbsAddress); string result; NetMngr.connect(result); if (!result.empty()) { _Reason = new string(result); LoginSM.pushEvent(CLoginStateMachine::ev_conn_failed); return; } // Reinit the string manager cache. STRING_MANAGER::CStringManagerClient::instance()->initCache(FSAddr, ClientCfg.LanguageCode); // reset the chat mode ChatMngr.resetChatMode(); // The next step will be triggered by the CONNECTION:USER_CHARS msg from the server } // return to character selection screen bool CFarTP::reselectCharacter() { if ( ! reconnection() ) { // The user clicked the Quit button releaseOutGame(); return false; } return true; } void CFarTP::selectCharAndEnter() { CBitMemStream out; nlverify (GenericMsgHeaderMngr.pushNameToStream("CONNECTION:SELECT_CHAR", out)); CSelectCharMsg SelectCharMsg; SelectCharMsg.c = (uint8)PlayerSelectedSlot; // PlayerSelectedSlot has not been reset out.serial(SelectCharMsg); NetMngr.push(out); /*//Obsolete CBitMemStream out2; nlverify(GenericMsgHeaderMngr.pushNameToStream("CONNECTION:ENTER", out2)); NetMngr.push(out2); */ LastGameCycle = NetMngr.getCurrentServerTick(); NetMngr.send(NetMngr.getCurrentServerTick()); // if (!isIngame()) // globalMenu will exit when WaitServerAnswer and serverReceivedReady (triggered by the CONNECTION:READY msg) are true // else // Next step will be triggered by the CONNECTION:READY msg from the server } void CFarTP::sendReady() { if ( isReselectingChar() ) { initMainLoop(); } else { // Set season RT.updateRyzomClock(NetMngr.getCurrentServerTick(), ryzomGetLocalTime() * 0.001); DayNightCycleHour = (float)RT.getRyzomTime(); CurrSeason = RT.getRyzomSeason(); RT.updateRyzomClock(NetMngr.getCurrentServerTick(), ryzomGetLocalTime() * 0.001); DayNightCycleHour = (float)RT.getRyzomTime(); ManualSeasonValue = RT.getRyzomSeason(); // Reset all fx (no need to CTimedFXManager::getInstance().reset() and EntitiesMngr.getGroundFXManager().reset() because the entities have been removed) CProjectileManager::getInstance().reset(); FXMngr.reset(); // (must be done after EntitiesMngr.release()) EntitiesMngr.getGroundFXManager().init(Scene, ClientCfg.GroundFXMaxDist, ClientCfg.GroundFXMaxNB, ClientCfg.GroundFXCacheSize); // Get the sheet for the user from the CFG. // Initialize the user and add him into the entity manager. // DO IT AFTER: Database, Collision Manager, PACS, scene, animations loaded. CSheetId userSheet(ClientCfg.UserSheet); nlinfo("FarTP: calling EntitiesMngr.create(0, userSheet.asInt())"); TNewEntityInfo emptyEntityInfo; emptyEntityInfo.reset(); EntitiesMngr.create(0, userSheet.asInt(), emptyEntityInfo); // Wait for the start position (USER_CHAR) and set the continent waitForUserCharReceived(); CInterfaceManager *pIM = CInterfaceManager::getInstance(); if ( ClientCfg.R2EDEnabled != _PreviousR2EdEnabled ) { // Reload textures, keys and interface config if we are switch between playing and r2 mode // Must be done after receiving the current R2EDEnabled flag (USER_CHAR) pIM->loadIngameInterfaceTextures(); // Unload (and save if leaving normal playing mode) keys and interface config // Instead of doing it in disconnectFromPreviousShard(), we do it here, only when it's needed ClientCfg.R2EDEnabled = ! ClientCfg.R2EDEnabled; pIM->uninitInGame0(); ClientCfg.R2EDEnabled = ! ClientCfg.R2EDEnabled; ActionsContext.removeAllCombos(); if ( ! ClientCfg.R2EDEnabled ) { // Remove all existing keys and load them back, and load new interface config pIM->loadKeys(); CWidgetManager::getInstance()->hideAllWindows(); pIM->loadInterfaceConfig(); } else { R2::ReloadUIFlag = true; // in R2ED mode the CEditor class deals with it } } pIM->configureQuitDialogBox(); // must be called after waitForUserCharReceived() to know the ring config ContinentMngr.select(UserEntity->pos(), ProgressBar); // IMPORTANT : must select continent after ui init, because ui init also load landmarks (located in the icfg file) // landmarks would be invisible else (RT 12239) // Update Network until current tick increase. LastGameCycle = NetMngr.getCurrentServerTick(); while (LastGameCycle == NetMngr.getCurrentServerTick()) { // Event server get events CInputHandlerManager::getInstance()->pumpEventsNoIM(); // Update Network. NetMngr.update(); IngameDbMngr.flushObserverCalls(); NLGUI::CDBManager::getInstance()->flushObserverCalls(); // Be nice to the system nlSleep(100); } LastGameCycle = NetMngr.getCurrentServerTick(); ProgressBar.progress(1); // Create the message for the server to create the character. CBitMemStream out; if(GenericMsgHeaderMngr.pushNameToStream("CONNECTION:READY", out)) { out.serial(ClientCfg.LanguageCode); NetMngr.push(out); NetMngr.send(NetMngr.getCurrentServerTick()); } // To be sure server crash is not fault of client ConnectionReadySent = true; // must be called before BotChatPageAll->initAfterConnectionReady() // To reset the inputs. CInputHandlerManager::getInstance()->pumpEventsNoIM(); if(BotChatPageAll && (! ClientCfg.R2EDEnabled)) BotChatPageAll->initAfterConnectionReady(); // Transition from background to game FirstFrame = true; } ProgressBar.finish(); LoginSM.pushEvent(CLoginStateMachine::ev_enter_game); if (_DSSDown) { _DSSDown = false; onDssDown(true); } } void CFarTP::onFailure() { // Display message string reason; if (_Reason) { reason += ": " + (*_Reason); delete _Reason; _Reason = NULL; } else if (noUserChar) { reason += ": no characters found!"; } Driver->systemMessageBox(("Unable to join shard"+reason).c_str(), "Error", UDriver::okType, UDriver::exclamationIcon); // TODO: recover from error LoginSM.pushEvent(CLoginStateMachine::ev_quit); } void CFarTP::onDssDown(bool forceReturn) { if (!forceReturn) { // If leaving shard, don't bother with DSS "downitude" if (isLeavingShard()) return; // If joining shard, store event and launch it at the end of reconnection if (isJoiningShard()) { _DSSDown = true; // note: still, some cases will make the client hang or abort, e.g. DSS down before the client receives the start pos (in ring shard) return; } } CInterfaceManager *pIM= CInterfaceManager::getInstance(); pIM->messageBoxWithHelp(CI18N::get("uiDisconnected")); requestReturnToPreviousSession(); } extern bool loginFinished; void setLoginFinished( bool f ); extern bool loginOK; void CFarTP::joinSessionResult(uint32 /* userId */, TSessionId /* sessionId */, uint32 /* result */, const std::string &/* shardAddr */, const std::string &/* participantStatus */) { // _LastJoinSessionResultMsg = result; // // _JoinSessionResultReceived = true; // // if (result == 0) // { // // ok, the join is successful // // FSAddr = shardAddr; // // setLoginFinished( true ); // loginOK = true; // // LoginSM.pushEvent(CLoginStateMachine::ev_connect); // // } // else // { // // TODO : display the error message on client screen and log // nlstop; // } } void CFarTP::setJoinSessionResult(TSessionId sessionId, const CSecurityCode& securityCode) { _SessionIdToJoinFast = sessionId; _SecurityCodeForDisconnection = securityCode; } void CFarTP::writeSecurityCodeForDisconnection(NLMISC::IStream& msgout) { CSecurityCheckForFastDisconnection::forwardSecurityCode(msgout, _SessionIdToJoinFast, _SecurityCodeForDisconnection); } // Not run by the cotask but within the main loop void CFarTP::farTPmainLoop() { ConnectionReadySent = false; LoginSM.pushEvent(CLoginStateMachine::ev_far_tp_main_loop_entered); disconnectFromPreviousShard(); uint nbRecoSelectCharReceived = 0; bool welcomeWindow = true; // Update network until the end of the FarTP process, before resuming the main loop while (!ConnectionReadySent) { // Event server get events CInputHandlerManager::getInstance()->pumpEventsNoIM(); // Update Network. NetMngr.update(); IngameDbMngr.flushObserverCalls(); NLGUI::CDBManager::getInstance()->flushObserverCalls(); // TODO: resend in case the last datagram sent was lost? // // check if we can send another dated block // if (NetMngr.getCurrentServerTick() != serverTick) // { // // // serverTick = NetMngr.getCurrentServerTick(); // NetMngr.send(serverTick); // } // else // { // // Send dummy info // NetMngr.send(); // } if (LoginSM.getCurrentState() == CLoginStateMachine::st_reconnect_select_char) { // Far TP part 3.2bis: go to character selection dialog // This is done outside the co-routine because it may need to co-routine to do an embedded server hop if ( FarTP.isReselectingChar() ) { ++nbRecoSelectCharReceived; if ( nbRecoSelectCharReceived <= 1 ) { ClientCfg.SelectCharacter = -1; // turn off character autoselection if ( ! FarTP.reselectCharacter() ) // it should not return here in farTPmainLoop() in the same state otherwise this would be called twice return; } else nlwarning( "Received more than one st_reconnect_select_char event" ); } } else if (LoginSM.getCurrentState() == CLoginStateMachine::st_reconnect_ready) { // Don't call sendReady() within the cotask but within the main loop, as it contains // event/network loops that could trigger a global exit(). sendReady(); welcomeWindow = !isReselectingChar(); } // Be nice to the system nlSleep(100); } // active/desactive welcome window if(welcomeWindow) initWelcomeWindow(); }