From d4aa7b69ad042bc819e5604a35ab9e2a55e34283 Mon Sep 17 00:00:00 2001 From: Qwerty Chouskie Date: Fri, 26 Jan 2018 14:41:41 -0800 Subject: [PATCH 001/137] New skidding particle effect (and a small fix for the SFX) The textures are here: https://jacobspctuneup.tk/STK/skid-particle1.png https://jacobspctuneup.tk/STK/skid-particle2.png --- data/gfx/skid1.xml | 20 ++++++++++---------- data/gfx/skid2.xml | 20 ++++++++++---------- src/karts/skidding.cpp | 3 +++ 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/data/gfx/skid1.xml b/data/gfx/skid1.xml index 34d0ceab0..fa4f944b7 100644 --- a/data/gfx/skid1.xml +++ b/data/gfx/skid1.xml @@ -1,27 +1,27 @@ - + - + + y="0.002" + z="0.0" /> - + - + - + diff --git a/data/gfx/skid2.xml b/data/gfx/skid2.xml index 1b94634b0..e4bb9e85d 100644 --- a/data/gfx/skid2.xml +++ b/data/gfx/skid2.xml @@ -1,27 +1,27 @@ - + - + + y="0.002" + z="0.0" /> - + - + - + diff --git a/src/karts/skidding.cpp b/src/karts/skidding.cpp index ceb0d2d79..50cafd15b 100644 --- a/src/karts/skidding.cpp +++ b/src/karts/skidding.cpp @@ -349,6 +349,9 @@ void Skidding::update(float dt, bool is_on_ground, * 0.5f * kp->getSkidGraphicalJumpTime(); m_remaining_jump_time = kp->getSkidGraphicalJumpTime(); + // Don't "play-sound-than-pause-sound-when-jumping-then-play-sound-when-jump-done" + m_gfx_jump_offset = 0.001f; + #ifdef SKID_DEBUG #define SPEED 20.0f m_real_steering = updateSteering(steering, dt); From 89a8219ec95f1ada20e883a52710b3976df379bf Mon Sep 17 00:00:00 2001 From: Qwerty Chouskie Date: Sun, 28 Jan 2018 13:36:45 -0800 Subject: [PATCH 002/137] Tweak particle emission rate The boost-specific rate was never really noticeable anyways. --- data/gfx/skid1.xml | 2 +- data/gfx/skid2.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/gfx/skid1.xml b/data/gfx/skid1.xml index fa4f944b7..8073ebff3 100644 --- a/data/gfx/skid1.xml +++ b/data/gfx/skid1.xml @@ -13,7 +13,7 @@ is used to show that the skidding bonus is now available, the maximum is used when the skid bonus is applied to the kart --> + max="1250" /> + max="1250" /> Date: Sun, 11 Feb 2018 19:01:42 -0800 Subject: [PATCH 003/137] Update skidding effect Also fix nitro when skiddding --- data/gfx/nitro.xml | 2 +- data/gfx/skid1.xml | 20 ++++++++++---------- data/gfx/skid2.xml | 20 ++++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/data/gfx/nitro.xml b/data/gfx/nitro.xml index bccbdce4d..aaa3785b0 100644 --- a/data/gfx/nitro.xml +++ b/data/gfx/nitro.xml @@ -1,5 +1,5 @@ - + diff --git a/data/gfx/skid1.xml b/data/gfx/skid1.xml index 8073ebff3..1808e6be9 100644 --- a/data/gfx/skid1.xml +++ b/data/gfx/skid1.xml @@ -1,27 +1,27 @@ - + - + + y="0.006" + z="-0.015" /> - + - + - + diff --git a/data/gfx/skid2.xml b/data/gfx/skid2.xml index 230decad0..e60476fca 100644 --- a/data/gfx/skid2.xml +++ b/data/gfx/skid2.xml @@ -1,27 +1,27 @@ - + - + + y="0.006" + z="-0.015" /> - + - + - + From 8a51cae73daf13e0e95b5895bf4c8a9cc9d3315c Mon Sep 17 00:00:00 2001 From: Qwerty Chouskie Date: Sun, 11 Feb 2018 19:16:23 -0800 Subject: [PATCH 004/137] Final(?) tweak --- data/gfx/skid1.xml | 2 +- data/gfx/skid2.xml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/data/gfx/skid1.xml b/data/gfx/skid1.xml index 1808e6be9..8310bed8d 100644 --- a/data/gfx/skid1.xml +++ b/data/gfx/skid1.xml @@ -4,7 +4,7 @@ diff --git a/data/gfx/skid2.xml b/data/gfx/skid2.xml index e60476fca..a48baab3f 100644 --- a/data/gfx/skid2.xml +++ b/data/gfx/skid2.xml @@ -4,7 +4,7 @@ @@ -12,8 +12,8 @@ - + Date: Wed, 18 Apr 2018 16:26:58 -0700 Subject: [PATCH 005/137] Update again --- data/gfx/skid1.xml | 16 ++++++++-------- data/gfx/skid2.xml | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/data/gfx/skid1.xml b/data/gfx/skid1.xml index 8310bed8d..6fb0ccc5f 100644 --- a/data/gfx/skid1.xml +++ b/data/gfx/skid1.xml @@ -1,7 +1,7 @@ - + - + - + - + - + diff --git a/data/gfx/skid2.xml b/data/gfx/skid2.xml index a48baab3f..9c3cf52af 100644 --- a/data/gfx/skid2.xml +++ b/data/gfx/skid2.xml @@ -1,7 +1,7 @@ - + - + - + - + - + From b770fa19fbfbf229b519f7983ab597da3e73d1eb Mon Sep 17 00:00:00 2001 From: Deve Date: Wed, 25 Apr 2018 20:26:23 +0200 Subject: [PATCH 006/137] Auto refresh for lan server detection --- src/network/network.cpp | 135 ---------------- src/network/network.hpp | 7 - src/network/protocols/connect_to_server.cpp | 2 +- src/network/servers_manager.cpp | 171 +++++++++++++++++++- src/network/servers_manager.hpp | 12 +- src/states_screens/server_selection.cpp | 24 ++- src/states_screens/server_selection.hpp | 4 +- 7 files changed, 201 insertions(+), 154 deletions(-) diff --git a/src/network/network.cpp b/src/network/network.cpp index 6d9b6d27a..4eb275463 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -230,138 +230,3 @@ void Network::closeLog() m_log_file.unlock(); } } // closeLog - -// ---------------------------------------------------------------------------- -/** Sets a list of default broadcast addresses which is used in case no valid - * broadcast address is found. This list includes default private network - * addresses. - */ -void Network::setDefaultBroadcastAddresses() -{ - // Add some common LAN addresses - m_broadcast_address.emplace_back(std::string("192.168.255.255")); - m_broadcast_address.emplace_back(std::string("192.168.0.255") ); - m_broadcast_address.emplace_back(std::string("192.168.1.255") ); - m_broadcast_address.emplace_back(std::string("172.31.255.255") ); - m_broadcast_address.emplace_back(std::string("172.16.255.255") ); - m_broadcast_address.emplace_back(std::string("172.16.0.255") ); - m_broadcast_address.emplace_back(std::string("10.255.255.255") ); - m_broadcast_address.emplace_back(std::string("10.0.255.255") ); - m_broadcast_address.emplace_back(std::string("10.0.0.255") ); - m_broadcast_address.emplace_back(std::string("255.255.255.255")); - m_broadcast_address.emplace_back(std::string("127.0.0.255") ); - m_broadcast_address.emplace_back(std::string("127.0.0.1") ); -} // setDefaultBroadcastAddresses - -// ---------------------------------------------------------------------------- -/** This masks various possible broadcast addresses. For example, in a /16 - * network it would first use *.*.255.255, then *.*.*.255. Also if the - * length of the mask is not a multiple of 8, the original value will - * be used, before multiple of 8 are create: /22 (*.3f.ff.ff), then - * /16 (*.*.ff.ff), /8 (*.*.*.ff). While this is usually an overkill, - * it can help in the case that the router does not forward a broadcast - * as expected (this problem often happens with 255.255.255.255, which is - * why this broadcast address creation code was added). - * \param a The transport address for which the broadcast addresses need - * to be created. - * \param len Number of bits to be or'ed. - */ -void Network::addAllBroadcastAddresses(const TransportAddress &a, int len) -{ - // Try different broadcast addresses - by masking on - // byte boundaries - while (len > 0) - { - unsigned int mask = (1 << len) - 1; - TransportAddress bcast(a.getIP() | mask, - NetworkConfig::get()->getServerDiscoveryPort()); - Log::info("Broadcast", "address %s length %d mask %x --> %s", - a.toString().c_str(), - len, mask, - bcast.toString().c_str()); - m_broadcast_address.push_back(bcast); - if (len % 8 != 0) - len -= (len % 8); - else - len = len - 8; - } // while len > 0 -} // addAllBroadcastAddresses - -// ---------------------------------------------------------------------------- -/** Returns a list of all possible broadcast addresses on this machine. - * It queries all adapters for active IPV4 interfaces, determines their - * netmask to create the broadcast addresses. It will also add 'smaller' - * broadcast addesses, e.g. in a /16 network, it will add *.*.255.255 and - * *.*.*.255, since it was sometimes observed that routers would not let - * all broadcast addresses through. Duplicated answers (from the same server - * to different addersses) will be filtered out in ServersManager. - */ -const std::vector& Network::getBroadcastAddresses() -{ - if (m_broadcast_address.size() > 0) return m_broadcast_address; - -#ifdef WIN32 - IP_ADAPTER_ADDRESSES *addresses; - int count = 100, return_code; - - int iteration = 0; - do - { - addresses = new IP_ADAPTER_ADDRESSES[count]; - ULONG buf_len = sizeof(IP_ADAPTER_ADDRESSES)*count; - long flags = 0; - return_code = GetAdaptersAddresses(AF_INET, flags, NULL, addresses, - &buf_len); - iteration++; - } while (return_code == ERROR_BUFFER_OVERFLOW && iteration<10); - - if (return_code == ERROR_BUFFER_OVERFLOW) - { - Log::warn("NetworkConfig", "Can not get broadcast addresses."); - setDefaultBroadcastAddresses(); - return m_broadcast_address; - } - - for (IP_ADAPTER_ADDRESSES *p = addresses; p; p = p->Next) - { - // Check all operational IP4 adapters - if (p->OperStatus == IfOperStatusUp && - p->FirstUnicastAddress->Address.lpSockaddr->sa_family == AF_INET) - { - const sockaddr_in *sa = (sockaddr_in*)p->FirstUnicastAddress->Address.lpSockaddr; - // Use sa->sin_addr.S_un.S_addr and htonl? - TransportAddress ta(sa->sin_addr.S_un.S_un_b.s_b1, - sa->sin_addr.S_un.S_un_b.s_b2, - sa->sin_addr.S_un.S_un_b.s_b3, - sa->sin_addr.S_un.S_un_b.s_b4); - int len = 32 - p->FirstUnicastAddress->OnLinkPrefixLength; - addAllBroadcastAddresses(ta, len); - } - } -#else - struct ifaddrs *addresses, *p; - - getifaddrs(&addresses); - for (p = addresses; p; p = p->ifa_next) - { - if (p->ifa_addr->sa_family == AF_INET) - { - struct sockaddr_in *sa = (struct sockaddr_in *) p->ifa_addr; - TransportAddress ta(htonl(sa->sin_addr.s_addr), 0); - uint32_t u = ((sockaddr_in*)(p->ifa_netmask))->sin_addr.s_addr; - // Convert mask to #bits: SWAT algorithm - u = u - ((u >> 1) & 0x55555555); - u = (u & 0x33333333) + ((u >> 2) & 0x33333333); - u = (((u + (u >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24; - - printf("Interface: %s\tAddress: %s\tmask: %x\n", p->ifa_name, - ta.toString().c_str(), u); - addAllBroadcastAddresses(ta, u); - } - } - - -#endif - return m_broadcast_address; -} // getBroadcastAddresses - diff --git a/src/network/network.hpp b/src/network/network.hpp index d6d0a5fb8..c50038bcc 100644 --- a/src/network/network.hpp +++ b/src/network/network.hpp @@ -51,12 +51,6 @@ private: /** Where to log packets. If NULL for FILE* logging is disabled. */ static Synchronised m_log_file; - /** List of broadcast addresses to use. */ - std::vector m_broadcast_address; - - void setDefaultBroadcastAddresses(); - void addAllBroadcastAddresses(const TransportAddress &a, int len); - public: Network(int peer_count, int channel_limit, uint32_t max_incoming_bandwidth, @@ -75,7 +69,6 @@ public: TransportAddress* sender, int max_tries = -1); void broadcastPacket(NetworkString *data, bool reliable = true); - const std::vector& getBroadcastAddresses(); // ------------------------------------------------------------------------ /** Returns a pointer to the ENet host object. */ diff --git a/src/network/protocols/connect_to_server.cpp b/src/network/protocols/connect_to_server.cpp index bed2b1a84..ad82b50f0 100644 --- a/src/network/protocols/connect_to_server.cpp +++ b/src/network/protocols/connect_to_server.cpp @@ -82,7 +82,7 @@ void ConnectToServer::asynchronousUpdate() { if (!m_server) { - while (!ServersManager::get()->refresh()) + while (!ServersManager::get()->refresh(false)) StkTime::sleep(1); while (!ServersManager::get()->listUpdated()) StkTime::sleep(1); diff --git a/src/network/servers_manager.cpp b/src/network/servers_manager.cpp index 76ad6d9da..e74fbc447 100644 --- a/src/network/servers_manager.cpp +++ b/src/network/servers_manager.cpp @@ -33,6 +33,14 @@ #include #include +#if defined(WIN32) +# undef _WIN32_WINNT +# define _WIN32_WINNT 0x600 +# include +#else +# include +#endif + #define SERVER_REFRESH_INTERVAL 5.0f static ServersManager* g_manager_singleton(NULL); @@ -136,7 +144,7 @@ Online::XMLRequest* ServersManager::getLANRefreshRequest() const addr.port = STKHost::PORT_ANY; Network *broadcast = new Network(1, 1, 0, 0, &addr); const std::vector &all_bcast = - broadcast->getBroadcastAddresses(); + ServersManager::get()->getBroadcastAddresses(); for (auto &bcast_addr : all_bcast) { Log::info("Server Discovery", "Broadcasting to %s", @@ -225,7 +233,7 @@ void ServersManager::setLanServers(const std::mapisWAN()) - Online::RequestManager::get()->addRequest(getWANRefreshRequest()); + { + Online::RequestManager::get()->addRequest(getWANRefreshRequest()); + } else - Online::RequestManager::get()->addRequest(getLANRefreshRequest()); + { + if (full_refresh) + { + updateBroadcastAddresses(); + } + + Online::RequestManager::get()->addRequest(getLANRefreshRequest()); + } + return true; } // refresh @@ -275,3 +294,147 @@ void ServersManager::setWanServers(bool success, const XMLNode* input) m_last_load_time.store((float)StkTime::getRealTime()); m_list_updated = true; } // refresh + +// ---------------------------------------------------------------------------- +/** Sets a list of default broadcast addresses which is used in case no valid + * broadcast address is found. This list includes default private network + * addresses. + */ +void ServersManager::setDefaultBroadcastAddresses() +{ + // Add some common LAN addresses + m_broadcast_address.emplace_back(std::string("192.168.255.255")); + m_broadcast_address.emplace_back(std::string("192.168.0.255") ); + m_broadcast_address.emplace_back(std::string("192.168.1.255") ); + m_broadcast_address.emplace_back(std::string("172.31.255.255") ); + m_broadcast_address.emplace_back(std::string("172.16.255.255") ); + m_broadcast_address.emplace_back(std::string("172.16.0.255") ); + m_broadcast_address.emplace_back(std::string("10.255.255.255") ); + m_broadcast_address.emplace_back(std::string("10.0.255.255") ); + m_broadcast_address.emplace_back(std::string("10.0.0.255") ); + m_broadcast_address.emplace_back(std::string("255.255.255.255")); + m_broadcast_address.emplace_back(std::string("127.0.0.255") ); + m_broadcast_address.emplace_back(std::string("127.0.0.1") ); +} // setDefaultBroadcastAddresses + +// ---------------------------------------------------------------------------- +/** This masks various possible broadcast addresses. For example, in a /16 + * network it would first use *.*.255.255, then *.*.*.255. Also if the + * length of the mask is not a multiple of 8, the original value will + * be used, before multiple of 8 are create: /22 (*.3f.ff.ff), then + * /16 (*.*.ff.ff), /8 (*.*.*.ff). While this is usually an overkill, + * it can help in the case that the router does not forward a broadcast + * as expected (this problem often happens with 255.255.255.255, which is + * why this broadcast address creation code was added). + * \param a The transport address for which the broadcast addresses need + * to be created. + * \param len Number of bits to be or'ed. + */ +void ServersManager::addAllBroadcastAddresses(const TransportAddress &a, int len) +{ + // Try different broadcast addresses - by masking on + // byte boundaries + while (len > 0) + { + unsigned int mask = (1 << len) - 1; + TransportAddress bcast(a.getIP() | mask, + NetworkConfig::get()->getServerDiscoveryPort()); + Log::info("Broadcast", "address %s length %d mask %x --> %s", + a.toString().c_str(), + len, mask, + bcast.toString().c_str()); + m_broadcast_address.push_back(bcast); + if (len % 8 != 0) + len -= (len % 8); + else + len = len - 8; + } // while len > 0 +} // addAllBroadcastAddresses + +// ---------------------------------------------------------------------------- +/** Updates a list of all possible broadcast addresses on this machine. + * It queries all adapters for active IPV4 interfaces, determines their + * netmask to create the broadcast addresses. It will also add 'smaller' + * broadcast addesses, e.g. in a /16 network, it will add *.*.255.255 and + * *.*.*.255, since it was sometimes observed that routers would not let + * all broadcast addresses through. Duplicated answers (from the same server + * to different addersses) will be filtered out in ServersManager. + */ +void ServersManager::updateBroadcastAddresses() +{ + m_broadcast_address.clear(); + +#ifdef WIN32 + IP_ADAPTER_ADDRESSES *addresses; + int count = 100, return_code; + + int iteration = 0; + do + { + addresses = new IP_ADAPTER_ADDRESSES[count]; + ULONG buf_len = sizeof(IP_ADAPTER_ADDRESSES)*count; + long flags = 0; + return_code = GetAdaptersAddresses(AF_INET, flags, NULL, addresses, + &buf_len); + iteration++; + } while (return_code == ERROR_BUFFER_OVERFLOW && iteration<10); + + if (return_code == ERROR_BUFFER_OVERFLOW) + { + Log::warn("NetworkConfig", "Can not get broadcast addresses."); + setDefaultBroadcastAddresses(); + return; + } + + for (IP_ADAPTER_ADDRESSES *p = addresses; p; p = p->Next) + { + // Check all operational IP4 adapters + if (p->OperStatus == IfOperStatusUp && + p->FirstUnicastAddress->Address.lpSockaddr->sa_family == AF_INET) + { + const sockaddr_in *sa = (sockaddr_in*)p->FirstUnicastAddress->Address.lpSockaddr; + // Use sa->sin_addr.S_un.S_addr and htonl? + TransportAddress ta(sa->sin_addr.S_un.S_un_b.s_b1, + sa->sin_addr.S_un.S_un_b.s_b2, + sa->sin_addr.S_un.S_un_b.s_b3, + sa->sin_addr.S_un.S_un_b.s_b4); + int len = 32 - p->FirstUnicastAddress->OnLinkPrefixLength; + addAllBroadcastAddresses(ta, len); + } + } +#else + struct ifaddrs *addresses, *p; + + getifaddrs(&addresses); + for (p = addresses; p; p = p->ifa_next) + { + if (p->ifa_addr->sa_family == AF_INET) + { + struct sockaddr_in *sa = (struct sockaddr_in *) p->ifa_addr; + TransportAddress ta(htonl(sa->sin_addr.s_addr), 0); + uint32_t u = ((sockaddr_in*)(p->ifa_netmask))->sin_addr.s_addr; + // Convert mask to #bits: SWAT algorithm + u = u - ((u >> 1) & 0x55555555); + u = (u & 0x33333333) + ((u >> 2) & 0x33333333); + u = (((u + (u >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24; + + printf("Interface: %s\tAddress: %s\tmask: %x\n", p->ifa_name, + ta.toString().c_str(), u); + addAllBroadcastAddresses(ta, u); + } + } +#endif +} // updateBroadcastAddresses + +// ---------------------------------------------------------------------------- +/** Returns a list of all possible broadcast addresses on this machine. + */ +const std::vector& ServersManager::getBroadcastAddresses() +{ + if (m_broadcast_address.empty()) + { + updateBroadcastAddresses(); + } + + return m_broadcast_address; +} // getBroadcastAddresses diff --git a/src/network/servers_manager.hpp b/src/network/servers_manager.hpp index cafde353a..40f4a7179 100644 --- a/src/network/servers_manager.hpp +++ b/src/network/servers_manager.hpp @@ -29,6 +29,7 @@ namespace Online { class XMLRequest; } class Server; +class TransportAddress; class XMLNode; /** @@ -40,6 +41,9 @@ class ServersManager private: /** List of servers */ std::vector > m_servers; + + /** List of broadcast addresses to use. */ + std::vector m_broadcast_address; std::atomic m_last_load_time; @@ -57,6 +61,10 @@ private: // ------------------------------------------------------------------------ void setLanServers(const std::map >& servers); + + void setDefaultBroadcastAddresses(); + void addAllBroadcastAddresses(const TransportAddress &a, int len); + void updateBroadcastAddresses(); public: // ------------------------------------------------------------------------ // Singleton @@ -66,11 +74,13 @@ public: // ------------------------------------------------------------------------ void cleanUpServers() { m_servers.clear(); } // ------------------------------------------------------------------------ - bool refresh(); + bool refresh(bool full_refresh); // ------------------------------------------------------------------------ std::vector >& getServers() { return m_servers; } // ------------------------------------------------------------------------ bool listUpdated() const { return m_list_updated; } + // ------------------------------------------------------------------------ + const std::vector& getBroadcastAddresses(); }; // class ServersManager #endif // HEADER_SERVERS_MANAGER_HPP diff --git a/src/states_screens/server_selection.cpp b/src/states_screens/server_selection.cpp index 4fab27af9..7087cf9ec 100644 --- a/src/states_screens/server_selection.cpp +++ b/src/states_screens/server_selection.cpp @@ -42,6 +42,7 @@ using namespace Online; ServerSelection::ServerSelection() : Screen("online/server_selection.stkgui") { m_refreshing_server = false; + m_refresh_timer = 0.0f; } // ServerSelection // ---------------------------------------------------------------------------- @@ -65,15 +66,16 @@ void ServerSelection::tearDown() /** Requests the servers manager to update its list of servers, and disables * the 'refresh' button (till the refresh was finished). */ -void ServerSelection::refresh() +void ServerSelection::refresh(bool full_refresh) { // If the request was created (i.e. no error, and not re-requested within // 5 seconds), clear the list and display the waiting message: - if (ServersManager::get()->refresh()) + if (ServersManager::get()->refresh(full_refresh)) { m_server_list_widget->clear(); m_reload_widget->setActive(false); m_refreshing_server = true; + m_refresh_timer = 0.0f; } } // refresh @@ -120,7 +122,7 @@ void ServerSelection::init() Screen::init(); m_sort_desc = true; /** Triggers the loading of the server list in the servers manager. */ - refresh(); + refresh(true); } // init // ---------------------------------------------------------------------------- @@ -215,7 +217,7 @@ void ServerSelection::eventCallback(GUIEngine::Widget* widget, } else if (name == "reload") { - refresh(); + refresh(true); } else if (name == "private_server") { @@ -250,6 +252,17 @@ void ServerSelection::onUpdate(float dt) ServerInfoDialog *sid = new ServerInfoDialog(m_servers[0]); sid->requestJoin(); } + + if (ServersManager::get()->getServers().empty() && !m_refreshing_server && + !NetworkConfig::get()->isWAN()) + { + m_refresh_timer += dt; + + if (m_refresh_timer > 10.0f) + { + refresh(false); + } + } if (!m_refreshing_server) return; @@ -269,8 +282,9 @@ void ServerSelection::onUpdate(float dt) else { SFXManager::get()->quickSound("anvil"); - new MessageDialog(_("No server is available.")); m_server_list_widget->clear(); + m_server_list_widget->addItem("loading", + _("No server is available.")); } m_reload_widget->setActive(true); } diff --git a/src/states_screens/server_selection.hpp b/src/states_screens/server_selection.hpp index b2c0d2131..dd7ac6dff 100644 --- a/src/states_screens/server_selection.hpp +++ b/src/states_screens/server_selection.hpp @@ -59,13 +59,15 @@ private: bool m_sort_desc; bool m_refreshing_server; + + float m_refresh_timer; /** Load the servers into the main list.*/ void loadList(unsigned sort_case); void copyFromServersManager(); - void refresh(); + void refresh(bool full_refresh); public: /** \brief implement callback from parent class GUIEngine::Screen */ From bc9660a81ce7283cd99068aad2419dc39a37d8dc Mon Sep 17 00:00:00 2001 From: Deve Date: Thu, 26 Apr 2018 21:48:59 +0200 Subject: [PATCH 007/137] Use known working java version for building apk if possible. It fails to build with newer versions, so that on different systems you may need to set JAVA_HOME manually. --- android/make.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/android/make.sh b/android/make.sh index f79d018d5..338a7476b 100755 --- a/android/make.sh +++ b/android/make.sh @@ -479,6 +479,10 @@ convert -scale 48x48 "$APP_ICON" "$DIRNAME/res/drawable-mdpi/icon.png" convert -scale 96x96 "$APP_ICON" "$DIRNAME/res/drawable-xhdpi/icon.png" convert -scale 144x144 "$APP_ICON" "$DIRNAME/res/drawable-xxhdpi/icon.png" +if [ -f "/usr/lib/jvm/java-8-openjdk-amd64/bin/java" ]; then + export JAVA_HOME="/usr/lib/jvm/java-8-openjdk-amd64" + export PATH=$JAVA_HOME/bin:$PATH +fi if [ "$BUILD_TOOL" = "gradle" ]; then export ANDROID_HOME="$SDK_PATH" From bf1392cadcc83095019c8da684d6b8a73ff7b2c7 Mon Sep 17 00:00:00 2001 From: deve Date: Fri, 27 Apr 2018 06:44:56 +0200 Subject: [PATCH 008/137] Keep important particles enabled on lowest settings --- src/states_screens/options_screen_video.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/states_screens/options_screen_video.cpp b/src/states_screens/options_screen_video.cpp index 17b3d6865..426379c23 100644 --- a/src/states_screens/options_screen_video.cpp +++ b/src/states_screens/options_screen_video.cpp @@ -50,7 +50,7 @@ void OptionsScreenVideo::initPresets() ({ false /* light */, 0 /* shadow */, false /* bloom */, false /* motionblur */, false /* lightshaft */, false /* glow */, false /* mlaa */, false /* ssao */, - false /* animatedCharacters */, 0 /* particles */, 0 /* image_quality */, + false /* animatedCharacters */, 1 /* particles */, 0 /* image_quality */, false /* depth of field */, true /* degraded IBL */ }); @@ -58,7 +58,7 @@ void OptionsScreenVideo::initPresets() ({ false /* light */, 0 /* shadow */, false /* bloom */, false /* motionblur */, false /* lightshaft */, false /* glow */, false /* mlaa */, false /* ssao */, - true /* animatedCharacters */, 0 /* particles */, 0 /* image_quality */, + true /* animatedCharacters */, 2 /* particles */, 0 /* image_quality */, false /* depth of field */, true /* degraded IBL */ }); @@ -66,7 +66,7 @@ void OptionsScreenVideo::initPresets() ({ true /* light */, 0 /* shadow */, false /* bloom */, false /* motionblur */, false /* lightshaft */, false /* glow */, false /* mlaa */, false /* ssao */, - true /* animatedCharacters */, 1 /* particles */, 1 /* image_quality */, + true /* animatedCharacters */, 2 /* particles */, 1 /* image_quality */, false /* depth of field */, true /* degraded IBL */ }); @@ -74,7 +74,7 @@ void OptionsScreenVideo::initPresets() ({ true /* light */, 0 /* shadow */, false /* bloom */, true /* motionblur */, true /* lightshaft */, true /* glow */, true /* mlaa */, false /* ssao */, - true /* animatedCharacters */, 1 /* particles */, 1 /* image_quality */, + true /* animatedCharacters */, 2 /* particles */, 1 /* image_quality */, false /* depth of field */, false /* degraded IBL */ }); From b90a9fbf0d6157f5b91ae4a409e1071afbae4503 Mon Sep 17 00:00:00 2001 From: deve Date: Fri, 27 Apr 2018 06:50:39 +0200 Subject: [PATCH 009/137] Don't allow totally disable particles in GUI. Now we don't use tfb shader to compute particles, so it seems to not have a sense anymore. And it's still possible to set particles_effects=0 in config.xml if someone will need it. --- data/gui/custom_video_settings.stkgui | 2 +- src/states_screens/dialogs/custom_video_settings.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/data/gui/custom_video_settings.stkgui b/data/gui/custom_video_settings.stkgui index aea2de48e..c53e123e9 100644 --- a/data/gui/custom_video_settings.stkgui +++ b/data/gui/custom_video_settings.stkgui @@ -136,7 +136,7 @@
diff --git a/src/states_screens/dialogs/custom_video_settings.cpp b/src/states_screens/dialogs/custom_video_settings.cpp index d7ef9af8f..2b55b9be9 100644 --- a/src/states_screens/dialogs/custom_video_settings.cpp +++ b/src/states_screens/dialogs/custom_video_settings.cpp @@ -63,6 +63,7 @@ void CustomVideoSettingsDialog::beforeAddingWidgets() particles_effects->addLabel(_("Important only")); particles_effects->addLabel(_("Enabled")); particles_effects->setValue(UserConfigParams::m_particles_effects); + particles_effects->setMin(1); SpinnerWidget* geometry_level = getWidget("geometry_detail"); //I18N: Geometry level disabled : lowest level, no details From 6a25384ed9dab0799554c988b7c3351c19a3abdc Mon Sep 17 00:00:00 2001 From: Alayan-stk-2 Date: Mon, 30 Apr 2018 00:27:03 +0200 Subject: [PATCH 010/137] SuperTux in Story Mode (and other improvements) (#3207) * Add SuperTux difficulty & update number of karts Also make the expert challenge slightly easier to match more the difficulty of other challenges. * Add SuperTux difficulty & update number of karts & points required Also give some more time margin in easier difficulties, as it is a hard challenge compared to most. * Add SuperTux difficulty & update number of karts & points required Also change the lap count to 4 as it is a very short track (sub 30s) * Add SuperTux difficulty Also tweak the expert challenge to have a more appropriate difficulty * Add SuperTux difficulty & update number of karts * Add SuperTux difficulty & update number of karts & points required * Add SuperTux difficulty & update number of karts & points required Also correct the requirement position, since this is not a FTL race anymore. * Add SuperTux difficulty & update number of karts & points required Also slight balancing improvements for the usual difficulties. * Add SuperTux difficulty & update number of karts & points required Also adds a position requirement in expert * Add SuperTux difficulty & update number of karts & points required * Add SuperTux difficulty & update number of karts & points required * Add SuperTux difficulty & update number of karts & points required Also change the number of laps to 5, as this is a very short track. The time requirements for easier difficulties have been kept proportionally similar to before. * Add SuperTux difficulty & update number of karts & points required Also change the number of laps to 4. * Add SuperTux difficulty & update number of karts & points required Also add a position requirement to expert and intermediate. * Add SuperTux difficulty & update number of karts & points required Also change the number of laps to 4, as a lap often is 30s or less in expert/supertux * Add SuperTux difficulty & update number of karts & points required * Add SuperTux difficulty & update number of karts & points required * Rename islandtrack.challenge to gran_paradiso.challenge * Rename challenge file * Add SuperTux difficulty & update number of karts & points required Also makes the time limit in expert less easy and tweak position requirement. * Add SuperTux difficulty & update number of karts * Add SuperTux difficulty & update number of karts & points required * Add SuperTux difficulty & update number of karts & points required * Add SuperTux difficulty & update number of karts & points required * Add SuperTux difficulty & update number of karts & points required Doesn't unlock the SuperTux difficulty anymore - it's managed elsewhere. * Add SuperTux difficulty & update number of karts & points required * Add SuperTux difficulty & update number of karts & points required * Add new unlock challenges, for difficulty and karts * Add a lap to oliver's math class * Replace Northern Resort by Volcano Island * Replace Volcano Island by Candela City Candela City was in no (official) GP before this. Also sets Green Valley to 3 laps. * Add Northern Resort and remove Fort Magma In 0.9.3, this GP has only 4 races in Story Mode (5 for the other GPs) because Fort Magma is locked. Of all the tracks outside this GP before, Northern Resort is one of the hardest, the AI being rather good there. * Temporary cup for SuperTux challenges Recolored version of the gold cup * Update challenge selection UI for the SuperTux challenges * GUI used before SuperTux difficulty unlocking This is the old select_challenge.stkgui * Swap the two sara * Replace Kiki by another kart to unlock on Benau's demand * Update for improved Story Mode * Update for improved Story Mode * Add support for SuperTux challenges * Add support for SuperTux challenges * Add support for SuperTux challenges * Add support for SuperTux challenges * Update for SuperTux ; also adds the ability to unlock a challenge by points * Update for unlocking by points * Add support for SuperTux challenges * Add support for SuperTux challenges * Add support for SuperTux challenges * Minor changes to function calls * Update for SuperTux challenges * Add support for SuperTux challenges * Update for Story Mode GP changes * Allows to display the correct number of points for GP challenges * Set the unlock of the 1st bonus kart to correct non-test value * Add support for SuperTux challenges Including a bigger challenge selection diaolg * Add default value * Icon to indicate that there is an unlockable The number of points needed to unlock it are displayed next to it. * Changed format : the point requirements is now specified in the file * Changed format : the point requirements is now specified in the file * Changed format : the point requirements is now specified in the file * Function for unlock by points UI * Add default for unlock list node and use requirements node for all * Make unlockByPoints simpler and more flexible Now the code will iterate in StoryModeStatus and send the unlock_list challenges for treatment here. The question of getting the right challenge statuses beings solved, it allows for a great simplification and much more flexibility * Update unlockByPoints declaration * Adds support for next unlockable UI * Improve call of unlockByPoints Also calculations for displaying in the UI how many point the next unlockable by points requires. * Add icon for next unlockable * Displays icon/number to make the player aware of the next unlockable Also displays the number below the icon rather than on the side, for more clarity. * Changes to display karts in the unlock scene * Update unlock functions declarations * New function to clarify code and more logical recently unlocked list management In the previous version, everything was added to the recently unlocked list at some point, necessitating a clearing at the end of computeActive, which also removed from the list the non-race challenges. Checking if the feature is newly unlocked to add it to the list remove the need of that clearing. * Declaration for unlockFeatureByList * Display newly unlocked karts * Display newly unlocked karts * Clear the list of recently unlocked features at the end * Update testing code * Update unlocks finding function call * Improve UI scaling * Fixes indentation * Update the number of points before checking for unlock by points * Add const to declarations * Remove const_cast * Remove a const_cast There are other const_cast in the menu debug items (but they are unrelated to this PR) * Fix menu being bolder --- data/challenges/abyss.challenge | 13 +- ...{city.challenge => candela_city.challenge} | 17 ++- ...ungle.challenge => cocoa_temple.challenge} | 15 +- data/challenges/cornfield_crossing.challenge | 11 +- data/challenges/fortmagma.challenge | 10 +- data/challenges/gp1.challenge | 15 +- data/challenges/gp2.challenge | 15 +- data/challenges/gp3.challenge | 15 +- data/challenges/gp4.challenge | 13 +- ...hallenge => granparadiso_island.challenge} | 16 ++- ...llway.challenge => green_valley.challenge} | 15 +- data/challenges/hacienda.challenge | 19 ++- data/challenges/lighthouse.challenge | 24 ++-- data/challenges/mansion.challenge | 22 +-- data/challenges/mines.challenge | 19 ++- data/challenges/minigolf.challenge | 17 ++- data/challenges/olivermath.challenge | 22 +-- data/challenges/sandtrack.challenge | 9 +- data/challenges/scotland.challenge | 11 +- data/challenges/snowmountain.challenge | 18 ++- data/challenges/snowpeak.challenge | 19 ++- data/challenges/startrack.challenge | 19 --- data/challenges/stk_enterprise.challenge | 24 ++++ data/challenges/unlock_bonus_kart1.challenge | 8 ++ data/challenges/unlock_bonus_kart2.challenge | 8 ++ data/challenges/unlock_supertux.challenge | 8 ++ data/challenges/volcano_island.challenge | 13 +- data/challenges/xr591.challenge | 11 +- data/challenges/zengarden.challenge | 11 +- data/grandprix/1_penguinplayground.grandprix | 2 +- data/grandprix/2_offthebeatentrack.grandprix | 10 +- data/grandprix/3_tothemoonandback.grandprix | 5 +- data/grandprix/4_atworldsend.grandprix | 7 +- data/gui/cup_platinum.png | Bin 0 -> 14110 bytes data/gui/mystery_unlock.png | Bin 0 -> 7994 bytes data/gui/select_challenge.stkgui | 15 +- data/gui/select_challenge_nobest.stkgui | 46 ++++++ src/challenges/challenge_data.cpp | 104 ++++++++------ src/challenges/challenge_data.hpp | 25 +++- src/challenges/challenge_status.cpp | 32 ++++- src/challenges/challenge_status.hpp | 11 +- src/challenges/story_mode_status.cpp | 132 ++++++++++++++---- src/challenges/story_mode_status.hpp | 15 +- src/challenges/unlock_manager.cpp | 65 +++++++-- src/challenges/unlock_manager.hpp | 9 +- src/config/player_profile.hpp | 8 +- src/modes/cutscene_world.cpp | 17 ++- src/modes/overworld.cpp | 12 +- .../dialogs/select_challenge.cpp | 39 +++++- src/states_screens/feature_unlocked.cpp | 46 ++++-- src/states_screens/feature_unlocked.hpp | 11 +- src/states_screens/main_menu_screen.cpp | 11 +- src/states_screens/race_gui_overworld.cpp | 69 ++++++++- src/states_screens/race_gui_overworld.hpp | 5 +- src/states_screens/race_result_gui.cpp | 11 +- 55 files changed, 837 insertions(+), 307 deletions(-) rename data/challenges/{city.challenge => candela_city.challenge} (50%) rename data/challenges/{jungle.challenge => cocoa_temple.challenge} (63%) rename data/challenges/{islandtrack.challenge => granparadiso_island.challenge} (54%) rename data/challenges/{tuxtollway.challenge => green_valley.challenge} (54%) delete mode 100644 data/challenges/startrack.challenge create mode 100644 data/challenges/stk_enterprise.challenge create mode 100644 data/challenges/unlock_bonus_kart1.challenge create mode 100644 data/challenges/unlock_bonus_kart2.challenge create mode 100644 data/challenges/unlock_supertux.challenge create mode 100644 data/gui/cup_platinum.png create mode 100644 data/gui/mystery_unlock.png create mode 100644 data/gui/select_challenge_nobest.stkgui diff --git a/data/challenges/abyss.challenge b/data/challenges/abyss.challenge index ad94e9d03..e67359867 100644 --- a/data/challenges/abyss.challenge +++ b/data/challenges/abyss.challenge @@ -1,15 +1,20 @@ - + + + + + + - - + + - + diff --git a/data/challenges/city.challenge b/data/challenges/candela_city.challenge similarity index 50% rename from data/challenges/city.challenge rename to data/challenges/candela_city.challenge index a3eb1d71f..b8a02ff3c 100644 --- a/data/challenges/city.challenge +++ b/data/challenges/candela_city.challenge @@ -1,21 +1,26 @@ - + + - + + + + + - + - + - + - + diff --git a/data/challenges/jungle.challenge b/data/challenges/cocoa_temple.challenge similarity index 63% rename from data/challenges/jungle.challenge rename to data/challenges/cocoa_temple.challenge index b3e280633..4df77a563 100644 --- a/data/challenges/jungle.challenge +++ b/data/challenges/cocoa_temple.challenge @@ -1,19 +1,24 @@ - + + - + + + + + - + - + - + diff --git a/data/challenges/cornfield_crossing.challenge b/data/challenges/cornfield_crossing.challenge index ffd39e5da..39c48155c 100644 --- a/data/challenges/cornfield_crossing.challenge +++ b/data/challenges/cornfield_crossing.challenge @@ -1,15 +1,20 @@ - + + + + + + - + - + diff --git a/data/challenges/fortmagma.challenge b/data/challenges/fortmagma.challenge index 8868e0270..ce094635a 100644 --- a/data/challenges/fortmagma.challenge +++ b/data/challenges/fortmagma.challenge @@ -1,9 +1,14 @@ - + + - + + + + + @@ -19,6 +24,5 @@ - diff --git a/data/challenges/gp1.challenge b/data/challenges/gp1.challenge index 101ab4fc7..b54da93f6 100644 --- a/data/challenges/gp1.challenge +++ b/data/challenges/gp1.challenge @@ -1,19 +1,24 @@ - + + - + + + + + - + - + - + diff --git a/data/challenges/gp2.challenge b/data/challenges/gp2.challenge index 89a477cd3..2a1043a9c 100644 --- a/data/challenges/gp2.challenge +++ b/data/challenges/gp2.challenge @@ -1,19 +1,24 @@ - + + - + + + + + - + - + - + diff --git a/data/challenges/gp3.challenge b/data/challenges/gp3.challenge index 44fce9222..7adf3ed2d 100644 --- a/data/challenges/gp3.challenge +++ b/data/challenges/gp3.challenge @@ -1,19 +1,24 @@ - + + - + + + + + - + - + - + diff --git a/data/challenges/gp4.challenge b/data/challenges/gp4.challenge index f3a24c61e..4ed7eeb90 100644 --- a/data/challenges/gp4.challenge +++ b/data/challenges/gp4.challenge @@ -1,19 +1,24 @@ - + + + + + + - + - + - + diff --git a/data/challenges/islandtrack.challenge b/data/challenges/granparadiso_island.challenge similarity index 54% rename from data/challenges/islandtrack.challenge rename to data/challenges/granparadiso_island.challenge index bc9b888d6..a0a14f1a8 100644 --- a/data/challenges/islandtrack.challenge +++ b/data/challenges/granparadiso_island.challenge @@ -1,20 +1,24 @@ - + + - + + + + + - + - + - + - diff --git a/data/challenges/tuxtollway.challenge b/data/challenges/green_valley.challenge similarity index 54% rename from data/challenges/tuxtollway.challenge rename to data/challenges/green_valley.challenge index 0dadd8fec..a327b090f 100644 --- a/data/challenges/tuxtollway.challenge +++ b/data/challenges/green_valley.challenge @@ -1,19 +1,24 @@ - + + - + + + + + - + - + - + diff --git a/data/challenges/hacienda.challenge b/data/challenges/hacienda.challenge index e90007f95..eee7e9fcb 100644 --- a/data/challenges/hacienda.challenge +++ b/data/challenges/hacienda.challenge @@ -1,19 +1,24 @@ - + + - + + + + + - - + + - - + + - + diff --git a/data/challenges/lighthouse.challenge b/data/challenges/lighthouse.challenge index 8303cb977..d4679a061 100644 --- a/data/challenges/lighthouse.challenge +++ b/data/challenges/lighthouse.challenge @@ -1,20 +1,24 @@ - - + + + - + + + + + - - + + - - + + - - + + - diff --git a/data/challenges/mansion.challenge b/data/challenges/mansion.challenge index f3ecabb20..ebd4225b5 100644 --- a/data/challenges/mansion.challenge +++ b/data/challenges/mansion.challenge @@ -1,18 +1,24 @@ - + + - + + + + + + - - + + - - + + - - + + diff --git a/data/challenges/mines.challenge b/data/challenges/mines.challenge index 00e78ee08..1e7c66f35 100644 --- a/data/challenges/mines.challenge +++ b/data/challenges/mines.challenge @@ -1,19 +1,24 @@ - + + - + + + + + - - + + - - + + - + diff --git a/data/challenges/minigolf.challenge b/data/challenges/minigolf.challenge index 9205f1607..1e6cc7fe4 100644 --- a/data/challenges/minigolf.challenge +++ b/data/challenges/minigolf.challenge @@ -1,19 +1,24 @@ - - + + + - + + + + + - + - + - + diff --git a/data/challenges/olivermath.challenge b/data/challenges/olivermath.challenge index 66768fe73..c6176c1c1 100644 --- a/data/challenges/olivermath.challenge +++ b/data/challenges/olivermath.challenge @@ -1,20 +1,24 @@ - - + + + - + + + + + - - + + - + - - + + - diff --git a/data/challenges/sandtrack.challenge b/data/challenges/sandtrack.challenge index 189418b8e..eccda5a31 100644 --- a/data/challenges/sandtrack.challenge +++ b/data/challenges/sandtrack.challenge @@ -1,12 +1,17 @@ - + + + + + + - + diff --git a/data/challenges/scotland.challenge b/data/challenges/scotland.challenge index a5ad68208..e0edd8743 100644 --- a/data/challenges/scotland.challenge +++ b/data/challenges/scotland.challenge @@ -1,15 +1,20 @@ - + + + + + + - + - + diff --git a/data/challenges/snowmountain.challenge b/data/challenges/snowmountain.challenge index 0357581f4..1d3020764 100644 --- a/data/challenges/snowmountain.challenge +++ b/data/challenges/snowmountain.challenge @@ -1,18 +1,24 @@ - + + - + + + + + + - - + + - + - + diff --git a/data/challenges/snowpeak.challenge b/data/challenges/snowpeak.challenge index e6c05e284..6d9f829fc 100644 --- a/data/challenges/snowpeak.challenge +++ b/data/challenges/snowpeak.challenge @@ -1,19 +1,24 @@ - + + - + + + + + - - + + - + - - + + diff --git a/data/challenges/startrack.challenge b/data/challenges/startrack.challenge deleted file mode 100644 index 446eecd46..000000000 --- a/data/challenges/startrack.challenge +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/data/challenges/stk_enterprise.challenge b/data/challenges/stk_enterprise.challenge new file mode 100644 index 000000000..fdf8b9d3e --- /dev/null +++ b/data/challenges/stk_enterprise.challenge @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/challenges/unlock_bonus_kart1.challenge b/data/challenges/unlock_bonus_kart1.challenge new file mode 100644 index 000000000..1766033a1 --- /dev/null +++ b/data/challenges/unlock_bonus_kart1.challenge @@ -0,0 +1,8 @@ + + + + + + + diff --git a/data/challenges/unlock_bonus_kart2.challenge b/data/challenges/unlock_bonus_kart2.challenge new file mode 100644 index 000000000..c54f20a83 --- /dev/null +++ b/data/challenges/unlock_bonus_kart2.challenge @@ -0,0 +1,8 @@ + + + + + + + diff --git a/data/challenges/unlock_supertux.challenge b/data/challenges/unlock_supertux.challenge new file mode 100644 index 000000000..4b25f6320 --- /dev/null +++ b/data/challenges/unlock_supertux.challenge @@ -0,0 +1,8 @@ + + + + + + + diff --git a/data/challenges/volcano_island.challenge b/data/challenges/volcano_island.challenge index 10250c4f1..23eaa6716 100644 --- a/data/challenges/volcano_island.challenge +++ b/data/challenges/volcano_island.challenge @@ -1,19 +1,24 @@ - + + + + + + - + - + - + diff --git a/data/challenges/xr591.challenge b/data/challenges/xr591.challenge index 44efb0539..73b0132e6 100644 --- a/data/challenges/xr591.challenge +++ b/data/challenges/xr591.challenge @@ -1,12 +1,17 @@ - + + + + + + - + @@ -17,5 +22,3 @@ - - diff --git a/data/challenges/zengarden.challenge b/data/challenges/zengarden.challenge index 7d9b4b7fc..4e9b9aba9 100644 --- a/data/challenges/zengarden.challenge +++ b/data/challenges/zengarden.challenge @@ -1,9 +1,14 @@ - - + + + - + + + + + diff --git a/data/grandprix/1_penguinplayground.grandprix b/data/grandprix/1_penguinplayground.grandprix index 3cfd998aa..096a84799 100644 --- a/data/grandprix/1_penguinplayground.grandprix +++ b/data/grandprix/1_penguinplayground.grandprix @@ -3,7 +3,7 @@ - + diff --git a/data/grandprix/2_offthebeatentrack.grandprix b/data/grandprix/2_offthebeatentrack.grandprix index a53170f6b..52996845c 100644 --- a/data/grandprix/2_offthebeatentrack.grandprix +++ b/data/grandprix/2_offthebeatentrack.grandprix @@ -1,10 +1,10 @@ - - - - - + + + + + diff --git a/data/grandprix/3_tothemoonandback.grandprix b/data/grandprix/3_tothemoonandback.grandprix index f5535d00f..5552d911a 100644 --- a/data/grandprix/3_tothemoonandback.grandprix +++ b/data/grandprix/3_tothemoonandback.grandprix @@ -2,10 +2,9 @@ - + - + - diff --git a/data/grandprix/4_atworldsend.grandprix b/data/grandprix/4_atworldsend.grandprix index 0aa43f1e2..a12a32259 100644 --- a/data/grandprix/4_atworldsend.grandprix +++ b/data/grandprix/4_atworldsend.grandprix @@ -1,11 +1,10 @@ - - + + + - - diff --git a/data/gui/cup_platinum.png b/data/gui/cup_platinum.png new file mode 100644 index 0000000000000000000000000000000000000000..8a28e49e1cdef0e7eaf006c7ec969e4dd9d375f8 GIT binary patch literal 14110 zcmV+(H{r;MP)W(V_03ZNKL_t(|+U=cpoE=s9|KDeB-(IsxcGG)-5SkD=NK-%*=|u>F^e)Ix z1w_S;2ok`rf(S?#1q>ZUdJk!Y^tzj5`>k{Dotg9dW9H7z%$>X0geVE*>}y_o@7$^9 z`JAVp=Ljh!Uz8? z0%pt4c?W&&0U(4p4EP5S-GbBq2Am+J)IYENqkPVZzYp-l77`!e8^E805Ml=h*#3w= z4R{5pcJ3cEteG>ey@WmY*^|*jT8IY2ENNTACyN&I#GfAE?b$E+UG8^M%FA{DVB05p zH89EAt^lSwe?RZ%Kj8dx&Zat2PAm{6pae*3Dj6+DCZ8pf&$4L6axVYc8T7PwdN2Pz z&;_gj<`p(6W!DY>{O<`}15C3BJ=yVpXsL6~zVT|#Kkpo3p$O4Hm}oGJ0CI+gs^!RN zSu(jCLck-BJ;Cp`+e2Rlw%PK4Q*38q< z4THR{p$LK0Bm#jZb@GOWrt28eAgBaL_Qq)*H;R=DJ{id6hg#d=j(sU1#FM}Sz;jYc zZ3h6hjOaT#h`#HVUT4^ZF@yp^^16l)0*NFqbc6syO7gl!Ue^gI3ai$)@&3PFXY@{! z1_}VbRt}tOZ5=|0M}WTqZ)|I3fGy;*1u1sO4U0%LZb%(!Wj89QkL z4UP3kDG@?2cGz$XX^#AWVj4h(G|&wLMJO23;LhLQ#oDDSnR(R(>@s#Dsa%Gnnjx7^ z(;JVo^pmB0GG{)EKb*(X59gDLCpPC1S6V>$qm**R4gk0bzd!Ip;L8I!e$>>7jNff% z#!Q*W*vS(ZJ9ZQ!8;24O1Q9}D{$YRq&7HHDy4y5%oiYU}CC=AY6hT{O2TwfsIEz1= z$1eNrWh;wPN+ekSW#4xsx?!-kdn1dNE$5SY3t2LEA@g5$z-_>fq?GY5 zGysGUdjmfQ_V4ewKrq0x17>jWaYu2`mk*?+eh`X9Ttz4dLEmSv_Vs}vtLM4q+V4|Q zUBym&PG`u_Aq0a#y1KeqJpW_feD-C^tE!l|+s>SQ))@?~Y`~C)MRaW98x9~Ce*2_k z{>r7ye(Xt}yYErj*S7WdS{s0Cq?Gr5;Q%0nh+(>0FDd1aio~LP=_`kD;Mb01|NZx& zuDl8%EaDu4S`lEaVv3Nt3` zOj$5u6IWVdLShlxL2xhO&3y(^_KB8v7tG_?NB_Y~5B{A*cTcJAxDPm6N}2yc06++_ z2XH?y-tYHa58037&p(sB_uPZBNYqW#%>Y2(eoG`+`xFE@L!(Pgkk>UD%Brad#lT|q zCc&})o1?vtu=a00T)+u{0{1YaK~B^7&zo;@=XJj*rCh!Yd_zjv`?&^y5TePll^eak z?Rmt(ocY}gnKo&XNjSwrI4`UB0fUp^e*2tap`a%?TS2}%Wq*PAZZ2Th$9jqG;uOwf zr6i~2dE}AD`P22kBArNjyWkZm<$<4f0L%uCbna^&JBn-X_$AY)OtD9yJ%UBw$QGiQ z@FhR{=mCXe3S_tc3xTsAWZ}=+ux3w@w8rJp-o=SlFM_LkO`O@Sfv4$6fMmF1+$0BEhiT`A!h{aRTo??`SU& z+0Pm%#C3$@8C)E|>JRDo?$!RCgm-cP*JsbjcOFxCx1^fpnwgg{=ihHQAK@2L%J22l zhYB#!ay|*bur?L=SW4Nm)g3_=+x|N6P2hlj&Pxd)-UMC+{sX+`0mLBZ!TWvVD_n8S z6$BL11~+@$B8aevA;2&|2x%#DAdpDI4GM`sNQ?N=BEEEtc!|Bwey@2*aaOf=cLz;>tp{MTyzzoYu+85XxPk_tO3?am;z{9{}Qp)yE1ppz$ zIN%q+*BDsnxZJYi3%2|QA;j a72;ziTv>S0jO}o+F*h+VWN)Yzi6DKp>FPFbQfC z*Hq}Fbc}3E3?M0H`NGZmH`N9V056x=1mOYNy4v~O^}nEdLx=aax^-+FW&+Cv!0-VM zXvG?!2LQJTA^rheEu~zx*#Hni90fdN5p_#j6L2GNtq|fbz!KNfDgpDq5R`{vl!szu zv@EHdif-sOHz>Go4Qcd^WQ(|lk8)>NgezDLUU|PM5J;qtNN@naLvVbE?qu~&!e_M{ z&p-bX&pr4A!B7apD9Sad7ef32I1Pw>ioOqE<}(~2gg8k``P6^`AcVLW_^ox3B8N=N z_%V#$X%eAun0zirPkSc|-guXsnl1G(G2lBz_fb5`7*HOHQW1)g)>M+&3|gUs9HZP1 z4299}{_ZdDw}b=u=>_&@2f>~9QA8iZ%j+5+Em+74e|ws>OIMInRg{2&G>UYv>DE@V zg`*Ls?z1PAwbg_oVe;7=E9Ng^-O?54S`nbifG30y7fUJc*dzdi5Wfbl^zPqj-!Jj4 zt1sk;eGecK2o>4CYCgxD#fy07)i-(Pg;$vW+B=&Jr}`z@6h?MMC`M(d3|$&#s8-98 z)$(R1JHX&1l;P-1FEC2o(+dXY=ywrcx{q-x&wd0K!X$cEJkFZ6>sYpE3GJ=x$z`)B zib;5BNDRZ+BoIU4FjHpi&hCfo$L{;?#k7eNi3Gz%CUI8F^ZYBX@Yh@Z!2H+W@p6M% zLWmYA<@ZZk0YZp#fIoOcsXu@8PaM6^fdle@6+S1`3=iD@5cl2myOIHuLr(rWcilCs zNW=HKbqmClNHSWEbY3N$&zfSwCRG2d+)2zWFkSlvjUT{rTAq&H9@^VG>Dbs#=f(~? zH?-5ywtBhEE*pJ?u9Yt&2p!m4N+2tfHJh{_`Qm)HfF?MO~RVOIELKZ-i1l_%HQmHg!M~tAZywY{-=*FSk{*U`Oe)_?v z=}h6&T8q{{v;iQ5XarnIk3W0lk4zXe%rIe38~Hpx-^<@e_50-LnM0#;B;Q4mT242lT^C15Inx=yq##!nylKTbLM#FE## z{nk4;eb2*r?eEW&j4HM+Tgj}eZ{*O4d-BHoIrgA9_MpR2>0Z~P6v`1!AjJZgou|BSu4{rN|!sIJ=dX!7{z{j~i4 zh*io0@H=-)vj6+cK_9P-FxkJN^!-;XPzc40KUCJ$FzbcKIAF#M-*=Zte)IF2x$mam z4ai~uNW~LeaOgLAecl}V8Z*ClsrRt&2qDT8;6SIdr+MrszOv_j_RC%W?aTb^`z$cu za_soR@sXSF;tzlLlW%7UA(%LNG)H{%7&n18LrZts;sezoHs|-PF%`si0x8b_CI|w- zAW|B9>$?{+Y4qrl&+^B+?&h)I{a-1Mbrv+usn^0^;-Z+b0IFQwc`N;zIiSqp@LV*r<5^X}_^#k=$7 zmK-5d#*QOz=$rP&d6ZD;9C=$iI>x7?yw^XVD2fe)K1DEq5Q0g2OlQXKyO#vq`*Y`U z?~iZx?p_BR1carO^-{_SQp%}ToaIJmmn{2e0ssEsU7H&n@Qtr}ueFPUnP1;Wl->Hi z&i|muKK+Z7^5;^@l;@@MmaFkVAnp_g*MI9Gx)Sk{9i7Z&Hc4zdPG1z6^LNXB8k^r& zjBMfgO2I*7v;IEzABu!I?K@|d)GK;Y32vNush90{0{clR|Lu*sN-2A!l-FDCd%G>1 zGtd4!Y1f^+x0$8@uDz2cPqc{;?=DJCd3O`&Uw}FN+HM`|*7KuFuPw>0bwekoYaUVH z?~nI-;z|)&4!~cr#bDS>rBNxcf5(Z6qA+RqT^L+fU-H}^U-o@E+cvn=E8x@tWD~#d z{L<~1;WTmbgdzv8;teg1XdG;R_VFUc2EYOz2X1lhdwcdv+<)JLB?(rDmdQS;3gBcZr7?he1Thmv`m{U;4ZGt- zBE7Q+03Ds(eF|aAXu{Em^XEap*#p#uTQL(!GEQgFIR|~6XJ2@U6|HOeV8H^)LeV1P z6ti(Z5EP36IRMSP_&53uC>4QcD>9Dzem#q4UU-R>ZLK`>!b_Zez}LJ^o+R)!E16~> z?Ob4pEeoO%CJi6eM{Ngfod8(rlGp#~9h&^v$dGfv+15NHbboTu zFSpLC54eC(#06xrlFoMcrUmLazcmAcPpb86da=_^uZKt^~_X z_}b+F{#?iUBx!EGj{6fkPP%T}B#a0&B4d=-Le8*8ophw6O1g-QBlR7LM5O z(Av|%!@s%Hd-_9#kqqz{j*Q)$n&tPuo8>zC&^=~w*{wzP>CV8TLWuAHpV{#1EH4-E z13@97!vzG-=H?isr*->{1S~Cxu<)ddZ09dxKawrEb7B+cp?`F6;fe#0W4}kaza1U^p zix4wU=Ik?0cPYVhmVLtCXWiqy#E%NK51lDF4+3`RPs?jubnNNGvuRiN{P?CHd6Nsk ztmbjQHH&4y9H3u$+h6dUxjE>mc%((X*+MF4=M?kUfJ~H;9#)Nu8!XVD1-+ z@KXZg10epNFy{^QIraDb64%{yz3bRsHN{oOpY2T*|Jb56E&y1brc0fEk2Rkx#cJWY732-gXO@lI1T=j1QG)mb(FIC5p!~!{ zf7wBW3}#(7(5(?ZXmArlhBYIlfv)Kox{eUaW^n+kVC5fxGoAZ~j2g~w{xXYjAmnx4-( z3~ID=0ok7p5buZhcX&OqO(R;kbM`~jS5&)b^yj}m$SaRM?Y+z|EgItsfPw=8b8@HM zes%uU%vrJ6)fvsTjr{TXM`>&s;ywQy;MRe20Jr?#(A~+@5o4$elZw|xrHC`{YG_v@q^9r3rH#1(9_Av)^+5yJY$9rXTPbtQX8$Lv7w&Y#(LWn zR02gDASb0PHNEOKJgIw)Ekn3t_JcInyGt$1S+a;*FaM$U{8ug7(k}q4M1zxncBejY z`Eh5_lS#TX@nQ8%{PBgy{bsmcWX&+@mjgJHLZy`C^gNoTQ4xwUs(vVYjhjkcc@^EM zI3F(mgw}Yce>k{=r_f7$`q@`XAL&mlcvHePU1w!mE439>>^pH6_8dKh5jBG`q+~@` z8)F7HGk&K@=$ekMX?~8NQltMC;6mrV`XNosn*AUx4Nb0R>P@G(=A?7Ia=#ro$#3eu z(oZ1gROg6oUAB_T&c2AO<}PD1ys?Qtyzr>kQ3PZf8p59crR_8?Q|#O zWHq;2qbm_-O!F{?*SH5nXSx?51Xba3Dyyn6jDijzydogsL;PP`x*1MK&+W4xWcZ*V zE>Nra99Mn&V%D!*n~vVKkLjS=-yr{I$z@Z|Oqb`(#0}i0}}9 z;BH)h_8$l+l*h`bt*D|mnPf%lS~m7{BPAqLDH_X)r|x$p;?$R$8PJNNpliB4F{D5F zFNF9xo_U(p4Rzf1^j{e<*e!aqTAs^hUdVgT7p-;i6R+GK5C97HBH%jt&8J`B;%{F- zR?E3wV9d}KZhz(>s_Kg}U4A2k__m)2=grPC~3w}uge2D9sk34{Vc6rqq*GsGg1BG+)S zcTf(%>*vq@0n}WMstR)s&BC=S=t%c6ta=b%8b6havNGncSZZ^qte$87>ZME>Hm0w` zt&y(;6u;U4af|pr^eSu>HC5d5fK*|drfir-xR zxDetZ%=B)%l~+~r+b8d5{K%0mu9VH^x$4|Y{qhIj29C67$EN~-)fsWjYS{0&?20F5 zbJYcxlhgCA2b(-*48MKqe#$BUkyj&B@BO?f46EuP1m z<)07;1Q^@cfiM00f!H?1nq#Ug-a=Wt@U?a!coEE zKac;DOOh#m{m0vgg(G&yPaQX&Up?_>t~mNMvKiI)n!W8^=JC>0^CH0z!R~)=h(|280I~zMX7}?T7 zZCRyn$(I6)4h90OXkEwPh6WD&mm!-D4+7$t;440A1o4^Yy0TRh1LI{GvAl=

WloTq$KYW&tSIjFESr`#0x(^IPJA%C3Zb2&1( ztZTHDg(Hj}+{}bwBN;uog()M)P*+~Xm?6V341;*i%qeN_>7udRt!q@rD*CQx{dJ`h zEL^pm>hcPP4sIf-1bniN&HL+Ch<1GAFm@g_j*VSibR~OTrC< zkN6(}U$$t(e+2+mhjsaZ;N!PH;GCmQW_?$ui>=PsV-J3K?`E0&sdcy+_(2F~D%SxwB;0or;x>5BKV$JogXs~$vII7(8@IP?U!%ebwx zgVD_`$>Yp#;oX!af|p=0exsce_1ptkBkXb#VSsr?U9{xuuA|*~`~#(W+rqmg<4P zYtH?v7B1%8!%tvk+d6kH!GQ;I)g8a^7Xy|rzzYJeaww5BR@P8iQO;{0&q33)A~UhA zr<1a%sc?0sdP$^HOdm0x4asg=6CDh%X(E|UkBM$ z4I4`;lO~nT5DA4*6orNBR}#;q*pTXG&YC53c6GD1t&Ne*EeHX*T%LSBPd;aDS(Etr zY>s?3XHO8#XZ>p^efQ2^^5p~E#nV@|uH(ETPV_59{~B=MW@HY3dH`4;=&`hVSHxlC znsuCi_z5gowbC6gKKdvw_~muQ0O*Yu&-n4n1+;!2-Y}R!4W_;S)+h6LYw3LEwJzhG z=nxAd87LmC=s zjd!33gWyubMWeI~8A2!&Ld|E%@jLG;~R%DuAzn9$4#cAtD9UtPcD}ym&=mNX8V9(jecj95bw16 zqt88^Gfq3jwQu>lHC%Gksq}UfareIfU$s?F_!zUqfp zGk&+eDQEGXUjMv*{DH8(tCNYtM^PWEA{q!&7p-Eq5fcfALiD7QRvca@Vp#{NoJvK^ zowisPtsdxF^!ni4W>?m%O4i!B(GoWutm^6GB+)>as>%v78MTN5JJE1HVBbztf*}-4{)f3psYI%ZzfbGE1G>x|Qc2qUXgkd9T zsU1W>3Gn{%MJ(?!K|Z#r1;a4NWpmb+C7V%gF_2TUR;?h#j@#5+Lz|jxqJ6w_IjWwc zJRBpQcKhcwLnoC^6Ap%uNNTI9nY(fcseIovuI^kM>SI+bZeN3L=uB%FM|GsHl<~;A!Km3RBL)p&*x(_&w`3vfy4&gLjkBa< zEj`&JYvb*_vvdK2>g(+qncZYg`?#1 zd8%T?rIRN$nJ(JSOq}YJV34e; zT3nz{{s)3VyF^Srn?q3)_W8<}(RCe7GZ(Ma4TITtKj?RhQ-l!r2q7A_Ce4Cdk@%A_ zCn@;O7OEfGgdq(StNyfR=+xEK5-pF}>jIc45PdN@$451nqr%d}o2wiBol0twSj?Pl zoXDie<@4+_aax~jZHBq)meSqZOK&2<;Q9vQ=_I}J1d&LXP$)z^ouVQfqcUt-4m-7s z;hm)msH-$Vvu4A3>dX7q7*h2d8x!4h#(PO;GBnoJ5i2WmD1ItA)f6p(V4nq$P9zz# z%M>cAD$NWbT{j&_x<>ubCeoi2#c?8*vc10$;^?ilsz`7v5x)j=dRcSPQ^%qlapsAf ze!)yzK7&~rM$;@9JQ1T|+@W*xo!lFe^+yd~YJb+ll#hHgR1|HBHrZ2n6X| zmKCjQX=yS|!DJ@Gh(SYa;&*1^ENxwFyK~FSVk}#|nyRV_Dl008MI(%B97ZCSX3@Hp z)K}Lqx^5^ztDuu^7z6_Wng=y8f7LR3x|gCT=(*!X(=q-=m!+o>v=9zmQ@{6O42A&r}O#L+WzfHTXR7~kl zcxnR;88w{aE}F?v$9|RSvI>`JB@lGPd+6xuqNBY7HKTICJ~N6;FlW#uuWMv-D%E8b zHUQFDm1HJ^l#);|NL5+6t=O$-UB{QkO(h@{7HwQbM$Iy%c@*J52+h!WYwT9X1ucx-QnwrWgDx+mc zB)#b*Pd)K8kN^H|Z+bVteNxIZK64$wng?*4bKhy#Ucw`<&gPVpPoyeVQIy(cNCQpR z(KM3_tX{U#6=?CB?HdXN?Q0c4TNa5jsHT>t+B)j1stE=H_O%s7As`giC%e!zo$15J z+mXYd5@7h?AwM8FY9@o4 zQOVj{hHP4O$o`;92atwg#s)MELpRVg&7}wm1l45~oO;TMJofstoc!Gjy#z>+8s@m0s)&17rcZGHMKVJHA5$pQ^}|*nT$#%-N*VfsWh2Xs!s%DGA`C12nJCC3aMT* z0{ZITpW*B0pNU}@wjyX45>3|$1>B_RO2ql-l{dVvy7n{Y08+}8fNhdwGb-mEaUyFr ztheL(Ue>M~I@xTNKmO!qdl}V#Kl7rC{kkfH3sEu|!*2$zK#<9(yuD;TOV_Vq#fDav zw5{f&m5W)lZY72^7~I%srkLv*(QueSHFb;}(oA#XAVv-u%E%!@8Qt7MV{IL4+S&*N z%t~hItV%}Bl1^vHWHRRHPo?eu*^EjinI@A=W9WvpSVWLOppOG2x=gXMVbxlm`O70@ zRn@WvEDqpF7G1M(J?9*GqSqo=X!ZRzVFi5E8x}6Btl)?--T&_MjA)1d;O)XIe zQ2ffur*kSPHA5n45t4T%V1l6_gNC<|Q&m#E2{Or4sn@w*N_qM=b}?1;s!sehUGyXwt zS5s-i(I~-ih+x=l0%lSvU*Z>{gRRRp$<^-!F52c|0a8j`N_oD;1=_ac8h^jzUNX6? zi$J9oX7Uo;`8N>47p_@fSxq7W*ve9a4a~{ z*|vfE?!Lc-WMciLn`GYewuVLNji}-KPQr8k}2}JoH_DC zCeaHZ2n5ZAqgF0hvHH^ESda5O?V8nJYMh$;J{5z>jIYt=N&xsuIN$_>`@)LOQE(jtDZ{4Wzo z;lJFhY@r@0h^uG>(0RSWe zxT+gHbI(J3v}loU4ZbY_gY&#ngg2x?I;WbWK9eD35k8qrkxC|gh@T{#Oxki^*EB+r zu&wx+vcC@m04wJgvHu4?>;4!2>qQhw5GVl51?*`swM`BD`QJ}6xVHXNn}GdU5)w%^ zpC@a%ceA;i8Qf8`WYQU1<`)Q`N+e0e6Qtq^l09+rb8nn*G-|W{P$b;P`XlDPIj_9o zT_QGavH#>}+4rA02Pjw&n6-uMM7iz_9bA6m+4QFS%F=AEw%?zXU`PWsZ?gMjCT)#+ zQ_(Bf`H6U(RDtN$miBC^-Z-I1#2Wdg(iaMcU95losx^M=1ONK7CjK_V0UVs*GR$S` z?Y8$(hjPQMKO-Cr5efte1_E2WvX?}X)ADu&ue_$2dHnf2*=&|UsLktEp@M+qo|!eN^O6h^Ci& z+D)sboKF>;J$)c>5fVZQg=-5UqmW{y8-*yFnRKCPCgJmWjchiHmeX134xV%}iJD52?1__3m{Y(DgfEPC=Vu`VZ~pT|HmoWN*xhW+p@DeN|S=Sf`l%NwY!sj`=24On@Gif0{S!{{s3ZWxA@bJv%JWX{;N^7nH& zvTD|z$WaLIs_BgF;VU?L(upKWKp_|k*#vj~4@V~F77GNflIV(}aNr5YaOgLWCKL{#C;@wa z9|!0w%3L76u3OnRx^7O_wm?uId^V%@ae=g%)0at^vOk;7nA(2G^z#>pUI2p-f@O0T zvTV)*zw)nd07pnE6Wd%LZ_5C%KrjgSC!Y2202-PH^R@4sLCdHS<^qiY2LSZG3gRXJ zbTjiNZ{{J|Tp(-B+jC6duyguSW`xhs4b!}fgl$h>VZ;Z+A=a;4!=iUSEL95UKfqVF z1(R>P1b_vCuoW-30?$&uLI}oApUPfG986QoP=66%)#fdXe%+G)g`y3)Y}WptO{;c7 zXEvR&BYU=MH{2KG>1^A;s)dXFWPL%~{}u2JnL7kp>6r9qWS%fwj6WG zx7WA%KHZ)G;1CJF!JL2S>&dLDuVvVTF$|wDmWt{sQw(T2hM`*`AdjwTXgSRWKsJ-H z_Uo<`?m#d=Hm%ZYP4Y-s)7eWj9kZVQ)!Qn|-|hk60K$08^$SnKtU>78Dr%~z8$5{0 zx*95KstHFTHa94Y{CqBl5W=qIqidQCibPkBJ&(VirUCZ@H%Td%Y*+oby#l}igyEQr zRi6z+H+8(CC`{O67X}R*ik8>xdi}bl^Wn>{mCRQx(NxS*EkD~D8M>cE3$mRzDdlR+ z^$C7rDT(J}CZ6~X+X&22{o-lnXWfkQvlTg=m8kz_D0YF+x-3OLE|G1QL&F7`xJ~sex z0HF(WZJj$T5wRCwm(`3_lrw(nBx_Dy-U5ILgyylMykXl1fD1RL^y&@(Y=r(5+d@W|(-=E>nx7 z#pk_qz6b!=$@yjUhK`3M?uB$fn%0Va(b>%XS2*KXd+?7jm4+iyD_0Pwlx0M?W=7XYTu*aO+m zAHeugBZ-!|l)-UUcG3<2Y-jB&n3Gg(`M>kjDgDZSi6o!|*lE8nd9QWj4ghQ?qh1Ix zO$c#4mM73w22MJbifEacMJ$w}>0(YWoOt0(@1;)`Lfk8aIL?~D`T6Oy?Kv7Kga}z$ z`o6$^z&=2o-?8_+@lPg=9^+F^rubNiB`*kAqJ-maBgJ|7h@(t@C?pXJ1GpFTeU4zyID(22!@g*^-z^td9Y+ zrIhI%0N8TEPsB|BI&z>yFRQFz=l%C$|6>m4phFI%sk*M;QD01WcQd5H+)qB?m1kb$ z?PveR^0^EB(!NWzUf}1L6G62d0N6a@e;v3FDDUsMhUURcKWHDO@3$Az_t=#Q!$zVA zQG&(yKkEAv2?cPZvKi(rT*TaWKVaUQ?=kPyw+B)H>qX%BZ83RdTb&v<6>~8}Un^zG zjNLixX3bDQE6* z1ohusC9w}W@oOA^_*Y%dn}RdP+=RUq)*H6J<36^(bG_#m{@lka{oY#$bC$BA2pTGD zxcnDI3w-Uet#$Uc7z}b%5nH=t1$jdwtOPx^efmtj!svAU?pZI;`Fr02)x!7pB2M8b zQR8?1rta-%+uRhfZIJ`K1*|H3`S|S*IB({8Wc9pfBs)hsCvUd#?H0 zrn!a?g4=#@llR?T-2s42G^=3L?XF&XVm32RIg7NWmLNz!Y`q_FY$dd4Y?sv9yO2hP?4hhMG2?o5Ke z{o^Sf`t4n;UbNKjby|ThNh#Ov0Kn#fpb~Q-iv3Ic&Ynje%r5)w#l)SbFlpjA#x}Ot zOT}+Caeey24_F=PUY4w0#gau!`0%y2dH%jfH#JrKt5V8E+hiYa2NgnMnEv#O89+QB z6b>_HmnlrzV>)AZnn+DuE#(yzR8&?_QBh7sc^MVaGRh-SDkJ9VpcySoCU0&jRV9-( z*8?i74ba`$#gh3Svt-T!mV7wZZ$Nzl}5% zcm$XwrF?sv@7L`xA0P^x09*ikX&ZX9C77#u{MnkHxPA2JcAgP5(gMLW%gZ(mPi?{f zQcD3J0}HI}HQ+6|9d_n+Um(H<6pa=TrU4^>N^7fde0F{h18HkZIX36_xHZBH1YadT cCmr|y0lWTh3=VDODgXcg07*qoM6N<$f~jGa`2YX_ literal 0 HcmV?d00001 diff --git a/data/gui/mystery_unlock.png b/data/gui/mystery_unlock.png new file mode 100644 index 0000000000000000000000000000000000000000..b69b91cf2481e0207382de2240774732de057075 GIT binary patch literal 7994 zcmV-AAI0E_P)`6pHRCwC$oqLoV)qUqbx2n76sTsWxLg)y}$SUU6>zD0nQ}*aHgP- zIXN(=GtmLW{x8!oUQz?V+#Ta9B>A3Ca?r(0UGRa7w~`)UZb1*k@Yz^>P;734vh(%A; zrwAMbz6bm!usntf)!Hm@uNL4}fxopgGfFrQC|Ck|mRm$WI`TcZreMEE`+OhxE^w38 zBWi7yh<_RImv&}`3FKRX2gFVK#QiQY$2{Agj@;)R;OoHJd1PhHiIxHG0)8Gd+r0#0 zOBpbvInNLE9~KRd10St{U}oAv;A>VnlMuoD5(Jepgp~?`5&$9G_p89CEjVi+n5K3X z@E6u(D@9a41^cf=1n3yB`7!#zLEtMua}5Mj-&O+uDMr@&GYkMpxKHp~2XMbtPHP~T zB8Bfv;6J7$ep(SA6^Z~!=r0ZgT~c|4saI-7>Jo&Mglj4l0de4ng5VKg#atR_cMbzUtla$P z)~dV+u)`{cvkrpH9M<|50iU$9CK$x32Fr0AF1qLSRkL)a?V4qR0u_eA152@W4Gg%?lS+S>F34?LiE?ARgG)n0q;HM#1ltI`IP z;!M@xeZE#R2a{+2Comz_v;(aHnW^UczI^99-;r!Kn-&Cd*2r_f<{H^20-zE21|~T( z2(!*900iyXXP=c^E>|rV=mqYrk$s}JzS+(WsX+c~K+7z&M<0E(To~Ff;WT;}lbu`x zK`(IQjZ?s6mfG#N-(E^7Ed_vAfNST9$89!SUcYm${v}9{*Yva1>uB%kkVVy!7fb4wd06|-0 z2c(qOtt!XKz`y`M{_&6b@|V9XTefVGrAwDeHk*~^=4NSYYm(IcABoQi&8Tj(cFUw6g-IQdCmF#!x)~yu- z;K-39r2tSWb?iOh&KmiRJbWlLFog=7K%-{hv17;NGoSg4t^ha_J~!NOL&X-w(9n=n zU$Pa(`Ls_z9$z<~VHK1EsS}F!Wp-`waGdXzAVe1y2 zHMTofS|bzFsH5hTDy0bCdFP#yV1|)krbTlKs#Y@l`};}poN>=p7kTe_3xGPzGVQ1~ ztx_C|J02y1FfliggSKF!UQiR6xs4z_OH8h$Rt#M(rq zatVvp>Y3L7D8{hBwCq2P7$|vMDa9j?Ji_(YSMHN}=%I(CP$;DJ1je|%P z5&%$Y{a<)MD_$JBc=2NW{PWLO<^CNU9F$F)Ht7_ezop>9&6T`iif4?6JqD``ypQEEk{G00`PSz}1mwQ!$@XIz%+`Hu`C5YO45w4jnpF zsVFP;jNwo4oG$PYOey<$4S)=$7{r!IK3gJdcAUz)^L>9N-u(*u?(KR~3cRtddxiJ=SB&zdgnHN?LvycdAf*G;k{G1eQ8h z&J!31g5#K?7+1|*64D(YAXVd8A>=_nOjszgkwSU}wSzJjTY zcJ>rl9&@5Okr(+j#;^Ym2Ft6`-CK1yxXS)y8txS>Io}WbfQd`SoavsK(;@2vjdvmN zW#A8h%R{DKk+J$fY6|W*UAoG2%4HP-f`iErz7UfnQvlu#af(w?(YqW|;=PCQ;oc(g z>0Fg4QhpgFutv_|pJ@ zv^s#F61>styogvR0)GQsdMa578BCe<_Y#!6QoC>@4dkf?fJ(TRq|gnXV`BQ4nkl#-frB8>HzhAb#f<>4F+VJ^vMn6~% zd<(PxZaK$}pF#(ilC!!u8D({n8Z$xbYV&?lb z08IM*ldd^zm2Gz>K@h&dVoYh5LK&G~69H5A{?Y3WViIcWC*!8AMgwlNa_DzMY)Gfn zh|_AV)l~0Dvm$q@Te)TIQ}O;$8U$m#29r|%n<1L2=>$N~zG%(8x(M-A3?Zb7L8^4H z)FcQp6K3ON-#_lUV9YmIv-_5mCibKQJHG`SBHiwtX*YG8Z15(rk69K0)A|0noTv06qph0IUsZpH9jFDVHuSWzLPO z#~+t!ttelUFYOGMGWVehAWY}`$K7wGwa{L|EV`ZWENP>G>oN6gFSOsih`lK(S#uJ5 zDjXwf3hK6`~)y=Jx9UvOb7uIr$bLPS(wUmtyaC)LTmJ~}!( z`QYd=Ioa1ocXtoPVi99NR8*cWt)D}cFeAfb9gZOWhUEf(9CvLRB)h=B!lZ8JLN`z; zoiI_Gwc--66)LJ#^|5eaD_3516&GBvNv&JIp1Qgmg+hTsp}_F)Foi;ak&%(nW5r?- ztu;z1T-U{M9eTQYc>i!a?;kuQ2M->i0xeu6$oqg`PJ+`ZsWPfWth{|zcYCrLQ{`6R z&l$giXOcn_hsG|Zl)`ZwGMNm`&CO)9 z864Ne_k9NY2N@h1q<>(5uFg(IMn=fz^W<_l>gwt!j1)L_>=@7d{d3aa-#-;*SfJtO zVn9$zISvEoe_Q?Sy%0Ebxk8K^te^k363B}ZKYY`7F+rmJHehjE$Zz1JWQ(MD(X`xX z9N;pU4EKKN9(Bp3m!P%A^E{fHn_0PX6=$8boR-!W+)M`XeMEsmjm~)2&5&_36bc27 z9zDvw{rlLxYZvY9?W5o+7K=Q!^(S)paJy9S>=L6K4p?oz8@R~2en}7qDggvV;NRNM z=p-(s1ZFTv2VY?Pl5Lg3u~Y<}#pM2f-vZ#HjODcH7_%jf2A{?nD5r=jFiXYm`Qqo* zWtU$@KA&gNqQzW)?e$!8$)zk_vKSojJs%N4ISv`e#c>=%e8&Y8zV9OlO-+rgUcHKq zZ5vs;b}fVb19WtBkjZ4&*ml0^?;oJ6v#Sy%ISPP0@V9m{?X{fr?9g?Z@|$S8)^T?L z*b~sJ6aa1n{-re~qVhY+hTPEc{lI^*O2;;PEDKzRSqJCDD2OV>fT$E^QYpZU`|a01 z^=s-QH(bxi$Oz}0b1t`j;#bJ#G92$ZMxj`ssi_g|3mwOfvuDp<-rc>M9XodL`s=TA z;J^Xe+Yi&x(Sfgh8XD>m5%ME>nwy*1bipQCTUyw+Zy%21uzJlZ)!Era--*6T&A{OQ ze#^q2!R$P~2NQ_fV8`AmqZb7KFScUjCG6HbnFQ9q61W@KSc0wUh$-pY2mCJZcZ@H6 znz6dTAZBL}6=F|9WhG&&jV_Mk2Qz#TzzivG@-}rG(^!3uweVqFGdI}>2 ze*VIX?A^PUg9i`dc^;1AjDED%qaq`h&9Zj=I?g};JT`sEEb<*0F0$!@Hiq&;{QPGx zlFeqh^s-CT(IZEtSS+S__Nch(2Tp{(zhD)+9o8wgF~og6#;>f70^?@j*MUDPtjSb|7^DJAwjIPdZdb)d9ym%2^on3Ty_f!(k2*&(<;Kw%j76!#O z3xo!%qzBwUC+HH&a)4#lG(40EYlQayOEJZoS|YMNY?6c(xaTeL|IV_Nur44W?r_*D zF>A}{6Vs&x8rHUIB`a2(O+KGz%jH*6UtiDh&SNw;HSzX4Z?pA@t%j|+E?u2nyz|a3 z*}rc;p68>rMi3ljd;;|i^_+L!26f?wFQBQZi4Tr`z>`~_;;bmwuQd z?MKjB^Wn`Gsy%!5N(Ijz{B|P30o+gw;Asnj-Iz5P=R|ZiiSZ9?#w^j>=`u0F<9Ds| zz$v5QcLVGT%$dI8{RSD?nX&;0KtDDCkf7~c`BK*H?FY)(3eMXKQJ5HfcP@d=E`@ZshUwNLVJkL|Z`C)aW z{RnTq`4&r;E@A1?rR4L&^mg~KX5DI|OEfpqalDHYy(egDYU179dngnND#2Bp^bXJ4 z33f69j$mT;V78yz3p817;FR!^I`+JQ=xn~!h7;WoV&Xh{FJadB{UNY3^1yxqXg*+U zHE^6+7H6`>SqYLbQ00^$ab1^{tIlS4c$kcvVabvu6bnThQ4SII_zeJ0SclIaN5nMw!AY4n$|!Lz`Fx&*3m1;wwNNOqd-pC}*JXHkn77}0JLx{9X07_! z3onX@kjv#bu>U=@2u06hLCXR%*$g5=Q&UsYvnNFWk7G*bCG97mL);+n`h5fVDy9x` z__@RMUI1)~#sd0D-nrr>;1SGy8bV{Y>AMFD!OL5(lH^*_vW;5jTd77G4nspjG&e7>K^vj7tJ5fWBJ}k1rY%V~VJ7>&Pv1!+raj-o!J)CC ziKa#aHZj4bN`gJZz{|kVlo*VPz+IS?#a{&;x4J^mb^w2#i45pr;NJu9V0Q56175Xu zXHN+`HT5L1AiK@_8Uilhri_r4vl_}2mlO&k1w?$5G8|KDO`$MiMoy_o6^7DU;}vaY zmI$t!rJ=qFH)D(=rOZ7N2o_f?^IhN{Fw5+y5Cjq$^B-C6d!3EwYT!lSXK~@+19rw& zv;PhyAL^A)QAR@&m|v(J8FefLvAKbWkXf&|{m1c zQ-G4tH?dzl&&SteY$V7c^%Vk88Lh9 z=@7z`B4p(ve`zCzmbyw6{TxL<2X3=fPU`Wbj-sx|lN`eZx72;g9xwVCd~%@bscUF8 z@rT?3G&wYNc)knqdkhD&Uu;nA|4vfjp3E$nv38FQAT5pwI>nhqNghO%!U|AsCdX&* zx{32v4zhabAWK{F6p96gheycghtcBF)6>h~z|bg@FtgeG4L-hoqVRDopf$8EY^ABO z2?5SnwvtS?fxf;jdV0Gl$THq*AK|HI-j$w{6O`mY@q62P^PZUx09X$EDe&7do~@)( zGD!nc5&#PtQJ?#y!yVUaG73hD;Q8alzV^Wr@UF5jCMt?F;?= z1x}tApjh6{OCFK;p3yC_(|)u*)tQff35&PVb-#J9g`~%fWS#G$0`CqYcqWN zE2ve=z$?P43vOfG<-dcQYrt`Q6h)8%LJ_owhzH`M@XS;f53NmAI}r~NAEb!ZzBw*q zziGcPdc8;yEgqtaXk9@2KE|H&e+ZrjEF-G|9!ptBbqegcMuc^$YP6ZK;zWauF?Jy$L?P_Ki{?K3X)Q1z!|(bs5e%e-(XwgM855M^h6Ni}W1& zfUg7pYnuEpiEXaz9^QuuG+oYE9lWG9f0bm|a@|Enwzn)@!FkvGA?TB!N1*r?;vYeg zM=^xxf+enxph0`)I~wiV(mdiJTBF57YY)+8&1b+FeBW{qwCP6-moWV*Jih^zS&t(w zmtB1U?FTy<7|7GMj_eEnILh{Cw9OR&EWqrZRTpE{O-cluzZO-@!;&>0gUkZN>j&>m zr1%O-=P?1$e((#37{Ud!Uc@speiQ&!Ul8AB1e;l|#VpeGeFGqrHbAlGih$=qp$IBF zM6U5ta&0IsoCz(*L%caJjx%WC>@TtV66*<341 zWf2M}=|?$rXk9>&F_KWZqh~uxp?%*Bk214d;h^zNL`TdYbFwz7K7!*YqXc65Q3`@O z6Us5)wRaNH1(a%}uD%|vgTDA}OzQlTz{9|6^8o<2VUkNe8ZiUYc+28se{3))AcTn6 z6O2rEEHNjbMkM?tHrx>85`=#jk>F-AeTF=RS`6OCh#$1GG0C{+V(KkE#rSajX$62g zFsqF(u0q*MAEgXz^f^;IL?we`tbL5J-P4{p9bLg!S<~<0$jsL%T)GvHxAw6i+E91O zu*6=aH@CMQ+my*4uCLm&tq&uq*9eJs>unA=K$k+lCp$NB2*fL zR@!HRFT)Kz=qlh!#sX%iya-r|S(Fe*c$q?=jm|!ifb~mkcqwismzwpdbkEp6)AFow zEoUdq($h%^tYnjja@94Z1ji7O(MpkHr&vlTshk>>iizrk)`YeYvuf+K06-@uQ#DH5 za#hEw9?db)Z*QX}Dr8aRE%>e|e=fvdKk-;9!l%uKKBWK>z}ge7kTfD7CF3}O@mZg;00_V4>zH(~VDX8Y;K56?TX-t5 z;%mzV0vp^;MM;>gDuDw8=S`g1PSqSBpQ>g)X9$U38-|pgi-lXf;j`HFl#K?RIoQhe`GbxQU21EIrI!6!?B{&3N55@CGU; zhvQ}u(fD5MI@}>lMUZX4qnKrRoK64;%lku?6a372yEa=VQLD9oGmMo&AHgh^9LB6C z7_|Qv?C;8Qk~+-RDfK`@-vC$EyJWkLzK-~Ll$!y)0p)cFa$KQuAi02FSf=C(%2(i^ z#24i_h-d|lJx8IGYZ5{5t*m$OwU6T}s}w3wW(}gCLBPpm$ktzgQVzc7({;R?Oa_MX z6fCRn#;gW@#eR-WH!)@d0MW&Z1q*;}m>TyhFw4iA7%PnE*_kmA`a2jRz7BcNaux&) zKto?Yzu$NUzj}24YrOx?X~ANL%-_7GI-sx*p$yTwR78Z|&#U(QfMMU}%sh zG1n^^-;6*e@7Rce~ zB8c6xSNnn#72=s=QUGldJx$VQ5v_|RK@?xK)`s9>iH<2V6a0?|z83K{s#s9G@#=oI zJ^!}ka-bB2C9OP2nX>MuqYf}LFPPztkrD1!4i9RtKlj3856YV_{&%%v{l&B_UJ4Wu zJz~>hsFR+~ ze#z!gjzd9&`$cAl_|xiPnoI4^{uFgqfzONPK2P(ZqFu1z`(T5FDp=VgfHq0IqsM}P zQt-dl<5vu6Z9gRvR+VuV;3&vsKsme({6%vUfBgsFp2f0x^8o;7jlgf+&%=N7M?9|J z_DqHwoeY~@pAA}Y#u3+Mj`=#Y?8^co0s@gRNCH2T1;IGKu33AoQS23Ahcc_9AOFU~ wbUpBe`7sG=t=4L-)@rTRYOU64Z4R~n4?-I-3oTA+bN~PV07*qoM6N<$f-3@6JOBUy literal 0 HcmV?d00001 diff --git a/data/gui/select_challenge.stkgui b/data/gui/select_challenge.stkgui index 2d8e562b8..02ab05b65 100644 --- a/data/gui/select_challenge.stkgui +++ b/data/gui/select_challenge.stkgui @@ -21,7 +21,7 @@

- +
- + + +
+ + +
+ + diff --git a/data/gui/select_challenge_nobest.stkgui b/data/gui/select_challenge_nobest.stkgui new file mode 100644 index 000000000..a63e32ed6 --- /dev/null +++ b/data/gui/select_challenge_nobest.stkgui @@ -0,0 +1,46 @@ + + +
+ +
+ + + +
+
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+
+ diff --git a/src/challenges/challenge_data.cpp b/src/challenges/challenge_data.cpp index b0c8efd15..369a3263a 100644 --- a/src/challenges/challenge_data.cpp +++ b/src/challenges/challenge_data.cpp @@ -43,6 +43,7 @@ ChallengeData::ChallengeData(const std::string& filename) m_gp_id = ""; m_version = 0; m_num_trophies = 0; + m_is_unlock_list = false; m_is_ghost_replay = false; for (int d=0; dget("version", &m_version); // No need to get the rest of the data if this challenge @@ -79,7 +80,48 @@ ChallengeData::ChallengeData(const std::string& filename) return; } + m_is_unlock_list = false; + const XMLNode* unlock_list_node = root->getNode("unlock_list"); + if (unlock_list_node != NULL) + { + std::string list; + unlock_list_node->get("list", &list); + m_is_unlock_list = (list=="true"); + } + std::vector unlocks; + root->getNodes("unlock", unlocks); + for(unsigned int i=0; iget("kart", &s)) + setUnlocks(s, ChallengeData::UNLOCK_KART); + else if(unlocks[i]->get("track", &s)) + addUnlockTrackReward(s); + else if(unlocks[i]->get("gp", &s)) + setUnlocks(s, ChallengeData::UNLOCK_GP); + else if(unlocks[i]->get("mode", &s)) + setUnlocks(s, ChallengeData::UNLOCK_MODE); + else if(unlocks[i]->get("difficulty", &s)) + setUnlocks(s, ChallengeData::UNLOCK_DIFFICULTY); + else + { + Log::warn("ChallengeData", "Unknown unlock entry. Must be one of kart, track, gp, mode, difficulty."); + throw std::runtime_error("Unknown unlock entry"); + } + } + + const XMLNode* requirements_node = root->getNode("requirements"); + if (requirements_node == NULL) + { + throw std::runtime_error("Challenge file " + filename + + " has no node!"); + } + requirements_node->get("trophies", &m_num_trophies); + + //Don't check further if this is an unlock list + if(m_is_unlock_list) + return; const XMLNode* mode_node = root->getNode("mode"); if (mode_node == NULL) @@ -149,27 +191,19 @@ ChallengeData::ChallengeData(const std::string& filename) } } - const XMLNode* requirements_node = root->getNode("requirements"); - if (requirements_node == NULL) - { - throw std::runtime_error("Challenge file " + filename + - " has no node!"); - } - requirements_node->get("trophies", &m_num_trophies); - const XMLNode* difficulties[RaceManager::DIFFICULTY_COUNT]; difficulties[0] = root->getNode("easy"); difficulties[1] = root->getNode("medium"); difficulties[2] = root->getNode("hard"); + difficulties[3] = root->getNode("best"); - // Note that the challenges can only be done in three difficulties if (difficulties[0] == NULL || difficulties[1] == NULL || - difficulties[2] == NULL) + difficulties[2] == NULL || difficulties[3] == NULL) { - error(" or or "); + error(" or or or "); } - for (int d=0; d<=RaceManager::DIFFICULTY_HARD; d++) + for (int d=0; d<=RaceManager::DIFFICULTY_BEST; d++) { const XMLNode* karts_node = difficulties[d]->getNode("karts"); if (karts_node == NULL) error(""); @@ -229,28 +263,6 @@ ChallengeData::ChallengeData(const std::string& filename) if (requirements_node->get("energy", &energy)) m_energy[d] = energy; } - - std::vector unlocks; - root->getNodes("unlock", unlocks); - for(unsigned int i=0; iget("kart", &s)) - setUnlocks(s, ChallengeData::UNLOCK_KART); - else if(unlocks[i]->get("track", &s)) - addUnlockTrackReward(s); - else if(unlocks[i]->get("gp", &s)) - setUnlocks(s, ChallengeData::UNLOCK_GP); - else if(unlocks[i]->get("mode", &s)) - setUnlocks(s, ChallengeData::UNLOCK_MODE); - else if(unlocks[i]->get("difficulty", &s)) - setUnlocks(s, ChallengeData::UNLOCK_DIFFICULTY); - else - { - Log::warn("ChallengeData", "Unknown unlock entry. Must be one of kart, track, gp, mode, difficulty."); - throw std::runtime_error("Unknown unlock entry"); - } - } } // ChallengeData // ---------------------------------------------------------------------------- @@ -486,7 +498,7 @@ bool ChallengeData::isChallengeFulfilled() const // ---------------------------------------------------------------------------- /** Returns true if this GP challenge is fulfilled. */ -bool ChallengeData::isGPFulfilled() const +ChallengeData::GPLevel ChallengeData::isGPFulfilled() const { int d = race_manager->getDifficulty(); @@ -496,14 +508,25 @@ bool ChallengeData::isGPFulfilled() const race_manager->getMinorMode() != m_minor || race_manager->getGrandPrix().getId() != m_gp_id || race_manager->getNumberOfKarts() < (unsigned int)m_default_num_karts[d] || - race_manager->getNumPlayers() > 1) return false; + race_manager->getNumPlayers() > 1) return GP_NONE; // check if the player came first. + // rank == 0 if first, 1 if second, etc. const int rank = race_manager->getLocalPlayerGPRank(0); - if (rank != 0) return false; - - return true; + // In superior difficulty levels, losing a place means + // getting a cup of the inferior level rather than + // nothing at all + int unlock_level = d - rank; + if (unlock_level == 3) + return GP_BEST; + if (unlock_level == 2) + return GP_HARD; + if (unlock_level == 1) + return GP_MEDIUM; + if (unlock_level == 0) + return GP_EASY; + return GP_NONE; } // isGPFulfilled // ---------------------------------------------------------------------------- @@ -641,4 +664,3 @@ void ChallengeData::addUnlockKartReward(const std::string &internal_name, feature.m_user_name = user_name; m_feature.push_back(feature); } // addUnlockKartReward - diff --git a/src/challenges/challenge_data.hpp b/src/challenges/challenge_data.hpp index 6e45f23c2..1c0c5f461 100644 --- a/src/challenges/challenge_data.hpp +++ b/src/challenges/challenge_data.hpp @@ -44,6 +44,17 @@ public: UNLOCK_KART, UNLOCK_DIFFICULTY }; + + /** The level of completion of a GP challenge + */ + enum GPLevel + { + GP_NONE, + GP_EASY, + GP_MEDIUM, + GP_HARD, + GP_BEST + }; // ------------------------------------------------------------------------ class UnlockableFeature { @@ -95,6 +106,7 @@ private: std::string m_filename; /** Version number of the challenge. */ int m_version; + bool m_is_unlock_list; bool m_is_ghost_replay; void setUnlocks(const std::string &id, @@ -120,7 +132,7 @@ public: virtual void check() const; virtual bool isChallengeFulfilled() const; - virtual bool isGPFulfilled() const; + virtual GPLevel isGPFulfilled() const; void addUnlockTrackReward(const std::string &track_name); void addUnlockModeReward(const std::string &internal_mode_name, const irr::core::stringw &user_mode_name); @@ -142,11 +154,11 @@ public: // ------------------------------------------------------------------------ /** Returns the id of the challenge. */ - const std::string &getId() const { return m_id; } + const std::string &getChallengeId() const { return m_id; } // ------------------------------------------------------------------------ /** Sets the id of this challenge. */ - void setId(const std::string& s) { m_id = s; } + void setChallengeId(const std::string& s) { m_id = s; } // ------------------------------------------------------------------------ /** Returns the track associated with this challenge. */ @@ -185,6 +197,9 @@ public: /** Returns if this challenge is using ghost replay. */ bool isGhostReplay() const { return m_is_ghost_replay; } // ------------------------------------------------------------------------ + /** Returns if this challenge is an unlock list. */ + bool isUnlockList() const { return m_is_unlock_list; } + // ------------------------------------------------------------------------ /** Returns the challenge mode of this challenge. */ ChallengeModeType getMode() const { return m_mode; } // ------------------------------------------------------------------------ @@ -196,9 +211,9 @@ public: const irr::core::stringw getChallengeDescription() const; // ------------------------------------------------------------------------ - /** Returns the minimum position the player must have in order to win. + /** Returns the maximum position the player must have in order to win. */ - int getPosition(RaceManager::Difficulty difficulty) const + int getMaxPosition(RaceManager::Difficulty difficulty) const { return m_position[difficulty]; } // getPosition diff --git a/src/challenges/challenge_status.cpp b/src/challenges/challenge_status.cpp index 7b41e7b5f..1c0c36863 100644 --- a/src/challenges/challenge_status.cpp +++ b/src/challenges/challenge_status.cpp @@ -35,18 +35,19 @@ */ void ChallengeStatus::load(const XMLNode* challenges_node) { - const XMLNode* node = challenges_node->getNode( m_data->getId() ); + const XMLNode* node = challenges_node->getNode( m_data->getChallengeId() ); if(node == NULL) { Log::info("ChallengeStatus", "Couldn't find node <%s> in challenge list." "(If this is the first time you play this is normal)\n", - m_data->getId().c_str()); + m_data->getChallengeId().c_str()); return; } m_state[0] = CH_INACTIVE; m_state[1] = CH_INACTIVE; m_state[2] = CH_INACTIVE; + m_state[3] = CH_INACTIVE; std::string solved; if (node->get("solved", &solved)) @@ -64,6 +65,13 @@ void ChallengeStatus::load(const XMLNode* challenges_node) m_state[1] = CH_SOLVED; m_state[2] = CH_SOLVED; } + else if (solved == "best") + { + m_state[0] = CH_SOLVED; + m_state[1] = CH_SOLVED; + m_state[2] = CH_SOLVED; + m_state[3] = CH_SOLVED; + } } // if has 'solved' attribute } // load @@ -78,14 +86,28 @@ void ChallengeStatus::setSolved(RaceManager::Difficulty d) { m_state[curr] = CH_SOLVED; } -} +} // setSolved + +// ------------------------------------------------------------------------ +bool ChallengeStatus::isUnlockList() +{ + return m_data->isUnlockList(); +} // isUnlockList + +// ------------------------------------------------------------------------ +bool ChallengeStatus::isGrandPrix() +{ + return m_data->isGrandPrix(); +} // isUnlockList //----------------------------------------------------------------------------- void ChallengeStatus::save(UTFWriter& writer) { - writer << L" <" << m_data->getId(); - if (isSolved(RaceManager::DIFFICULTY_HARD)) + writer << L" <" << m_data->getChallengeId(); + if (isSolved(RaceManager::DIFFICULTY_BEST)) + writer << L" solved=\"best\"/>\n"; + else if (isSolved(RaceManager::DIFFICULTY_HARD)) writer << L" solved=\"hard\"/>\n"; else if (isSolved(RaceManager::DIFFICULTY_MEDIUM)) writer << L" solved=\"medium\"/>\n"; diff --git a/src/challenges/challenge_status.hpp b/src/challenges/challenge_status.hpp index 9e69e73a9..2fe5fa549 100644 --- a/src/challenges/challenge_status.hpp +++ b/src/challenges/challenge_status.hpp @@ -57,6 +57,7 @@ private: enum {CH_INACTIVE, // challenge not yet possible CH_ACTIVE, // challenge possible, but not yet solved CH_SOLVED} // challenge was solved + m_state[RaceManager::DIFFICULTY_COUNT]; /** Pointer to the original challenge data. */ @@ -69,6 +70,7 @@ public: m_state[RaceManager::DIFFICULTY_EASY] = CH_INACTIVE; m_state[RaceManager::DIFFICULTY_MEDIUM] = CH_INACTIVE; m_state[RaceManager::DIFFICULTY_HARD] = CH_INACTIVE; + m_state[RaceManager::DIFFICULTY_BEST] = CH_INACTIVE; } virtual ~ChallengeStatus() {}; void load(const XMLNode* config); @@ -88,7 +90,7 @@ public: bool isSolvedAtAnyDifficulty() const { return m_state[0]==CH_SOLVED || m_state[1]==CH_SOLVED || - m_state[2]==CH_SOLVED; + m_state[2]==CH_SOLVED || m_state[3]==CH_SOLVED; } // isSolvedAtAnyDifficulty // ------------------------------------------------------------------------ /** True if this challenge is active at the given difficulty. @@ -105,6 +107,13 @@ public: m_state[d] = CH_ACTIVE; } // setActive // ------------------------------------------------------------------------ + /** Returns if this challenge is only an unlock list */ + bool isUnlockList(); + + // ------------------------------------------------------------------------ + /** Returns if this challenge is a grand prix */ + bool isGrandPrix(); + // ------------------------------------------------------------------------ /** Returns a pointer to the actual Challenge data. */ const ChallengeData* getData() const { return m_data; } diff --git a/src/challenges/story_mode_status.cpp b/src/challenges/story_mode_status.cpp index bd1b83b4a..8fb8d820a 100644 --- a/src/challenges/story_mode_status.cpp +++ b/src/challenges/story_mode_status.cpp @@ -30,12 +30,14 @@ //----------------------------------------------------------------------------- StoryModeStatus::StoryModeStatus(const XMLNode *node) { - m_points = 0; - m_first_time = true; - m_easy_challenges = 0; - m_medium_challenges = 0; - m_hard_challenges = 0; - m_current_challenge = NULL; + m_points = 0; + m_next_unlock_points = 0; + m_first_time = true; + m_easy_challenges = 0; + m_medium_challenges = 0; + m_hard_challenges = 0; + m_best_challenges = 0; + m_current_challenge = NULL; // If there is saved data, load it if(node) @@ -62,7 +64,7 @@ StoryModeStatus::~StoryModeStatus() */ void StoryModeStatus::addStatus(ChallengeStatus *cs) { - m_challenges_state[cs->getData()->getId()] = cs; + m_challenges_state[cs->getData()->getChallengeId()] = cs; } // addStatus //----------------------------------------------------------------------------- @@ -78,9 +80,11 @@ bool StoryModeStatus::isLocked(const std::string& feature) void StoryModeStatus::computeActive() { m_points = 0; + m_next_unlock_points = 0; m_easy_challenges = 0; m_medium_challenges = 0; m_hard_challenges = 0; + m_best_challenges = 0; m_locked_features.clear(); // start afresh @@ -111,20 +115,32 @@ void StoryModeStatus::computeActive() unlockFeature(i->second, RaceManager::DIFFICULTY_HARD, /*save*/ false); } - - if (i->second->isSolved(RaceManager::DIFFICULTY_HARD)) + if (i->second->isSolved(RaceManager::DIFFICULTY_BEST)) { - m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_HARD]; + unlockFeature(i->second, RaceManager::DIFFICULTY_BEST, + /*save*/ false); + } + + int gp_factor = i->second->isGrandPrix() ? GP_FACTOR : 1; + + if (i->second->isSolved(RaceManager::DIFFICULTY_BEST) && !i->second->isUnlockList()) + { + m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_BEST]*gp_factor; + m_best_challenges++; + } + else if (i->second->isSolved(RaceManager::DIFFICULTY_HARD) && !i->second->isUnlockList()) + { + m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_HARD]*gp_factor; m_hard_challenges++; } - else if (i->second->isSolved(RaceManager::DIFFICULTY_MEDIUM)) + else if (i->second->isSolved(RaceManager::DIFFICULTY_MEDIUM) && !i->second->isUnlockList()) { - m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_MEDIUM]; + m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_MEDIUM]*gp_factor; m_medium_challenges++; } - else if (i->second->isSolved(RaceManager::DIFFICULTY_EASY)) + else if (i->second->isSolved(RaceManager::DIFFICULTY_EASY) && !i->second->isUnlockList()) { - m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_EASY]; + m_points += CHALLENGE_POINTS[RaceManager::DIFFICULTY_EASY]*gp_factor; m_easy_challenges++; } } @@ -135,29 +151,40 @@ void StoryModeStatus::computeActive() lockFeature(i->second); } - if (i->second->isSolved(RaceManager::DIFFICULTY_HARD)) + if (i->second->isSolved(RaceManager::DIFFICULTY_BEST)) { // challenge beaten at hardest, nothing more to do here continue; } + else if (i->second->isSolved(RaceManager::DIFFICULTY_HARD)) + { + i->second->setActive(RaceManager::DIFFICULTY_BEST); + } else if (i->second->isSolved(RaceManager::DIFFICULTY_MEDIUM)) { + i->second->setActive(RaceManager::DIFFICULTY_BEST); i->second->setActive(RaceManager::DIFFICULTY_HARD); } else if (i->second->isSolved(RaceManager::DIFFICULTY_EASY)) { + i->second->setActive(RaceManager::DIFFICULTY_BEST); i->second->setActive(RaceManager::DIFFICULTY_HARD); i->second->setActive(RaceManager::DIFFICULTY_MEDIUM); } else { + i->second->setActive(RaceManager::DIFFICULTY_BEST); i->second->setActive(RaceManager::DIFFICULTY_HARD); i->second->setActive(RaceManager::DIFFICULTY_MEDIUM); i->second->setActive(RaceManager::DIFFICULTY_EASY); } } // for i - // now we have the number of points. Actually lock the tracks + // now we have the number of points. + + unlockFeatureByList(); + + //Actually lock the tracks for (i = m_challenges_state.begin(); i != m_challenges_state.end(); i++) { if (m_points < i->second->getData()->getNumTrophies()) @@ -173,12 +200,37 @@ void StoryModeStatus::computeActive() } } } - - clearUnlocked(); - - } // computeActive +//----------------------------------------------------------------------------- + +void StoryModeStatus::unlockFeatureByList() +{ + // test if we have unlocked a feature requiring a certain number of points + std::map::const_iterator i; + for(i = m_challenges_state.begin(); + i != m_challenges_state.end(); i++) + { + if (i->second->isUnlockList()) + { + if (i->second->isSolvedAtAnyDifficulty()) + continue; + + bool newly_solved = unlock_manager->unlockByPoints(m_points,i->second); + + // Add to list of recently unlocked features + if(newly_solved) + m_unlocked_features.push_back(i->second->getData()); + + //Retrieve the smallest number of points for the next unlockable + if (i->second->getData()->getNumTrophies() > m_points && (m_next_unlock_points == 0 + || i->second->getData()->getNumTrophies() < m_next_unlock_points) ) + m_next_unlock_points = i->second->getData()->getNumTrophies(); + } + } +} //unlockFeatureByList + + //----------------------------------------------------------------------------- void StoryModeStatus::lockFeature(ChallengeStatus *challenge_status) @@ -216,8 +268,10 @@ void StoryModeStatus::unlockFeature(ChallengeStatus* c, RaceManager::Difficulty m_locked_features.erase(p); } - // Add to list of recently unlocked features - m_unlocked_features.push_back(c->getData()); + // Add to list of recently unlocked features if the challenge is newly completed + if (!c->isSolvedAtAnyDifficulty()) + m_unlocked_features.push_back(c->getData()); + c->setSolved(d); // reset isActive flag // Save the new unlock information @@ -250,6 +304,10 @@ void StoryModeStatus::raceFinished() unlockFeature(const_cast(m_current_challenge), race_manager->getDifficulty()); } // if isActive && challenge solved + + //This updates the number of points. + //It then calls unlockFeatureByList which checks the specially unlocked features (by points, etc) + computeActive(); } // raceFinished //----------------------------------------------------------------------------- @@ -259,11 +317,33 @@ void StoryModeStatus::raceFinished() void StoryModeStatus::grandPrixFinished() { if(m_current_challenge && - m_current_challenge->isActive(race_manager->getDifficulty()) && - m_current_challenge->getData()->isGPFulfilled() ) + m_current_challenge->isActive(race_manager->getDifficulty()) ) { - unlockFeature(const_cast(m_current_challenge), - race_manager->getDifficulty()); + ChallengeData::GPLevel unlock_level = m_current_challenge->getData()->isGPFulfilled(); + + RaceManager::Difficulty difficulty = RaceManager::DIFFICULTY_EASY; + + switch (unlock_level) + { + case ChallengeData::GP_NONE: + race_manager->setCoinTarget(0); + return; //No cup unlocked + case ChallengeData::GP_EASY: + difficulty = RaceManager::DIFFICULTY_EASY; + break; + case ChallengeData::GP_MEDIUM: + difficulty = RaceManager::DIFFICULTY_MEDIUM; + break; + case ChallengeData::GP_HARD: + difficulty = RaceManager::DIFFICULTY_HARD; + break; + case ChallengeData::GP_BEST: + difficulty = RaceManager::DIFFICULTY_BEST; + break; + } + + race_manager->setDifficulty(difficulty); + unlockFeature(const_cast(m_current_challenge), difficulty); } // if isActive && challenge solved race_manager->setCoinTarget(0); diff --git a/src/challenges/story_mode_status.hpp b/src/challenges/story_mode_status.hpp index dab906b87..94a7ee1c2 100644 --- a/src/challenges/story_mode_status.hpp +++ b/src/challenges/story_mode_status.hpp @@ -19,6 +19,7 @@ #ifndef GAME_SLOT_HPP #define GAME_SLOT_HPP +#include "challenges/challenge_data.hpp" #include "race/race_manager.hpp" #include @@ -33,7 +34,8 @@ class ChallengeStatus; class UTFWriter; class XMLNode; -const int CHALLENGE_POINTS[] = { 8, 9, 10 }; +const int CHALLENGE_POINTS[] = { 6, 7, 8, 10 }; +const int GP_FACTOR = 3; /** This class contains the progression through challenges for the story mode. * It maintains a list of all challenges in a mapping of challenge id to @@ -60,6 +62,7 @@ private: const ChallengeStatus *m_current_challenge; int m_points; + int m_next_unlock_points; /** Set to false after the initial stuff (intro, select kart, etc.) */ bool m_first_time; @@ -67,6 +70,7 @@ private: int m_easy_challenges; int m_medium_challenges; int m_hard_challenges; + int m_best_challenges; public: @@ -75,6 +79,7 @@ public: void computeActive(); bool isLocked (const std::string& feature); + void unlockFeatureByList(); void lockFeature (ChallengeStatus *challenge); void unlockFeature (ChallengeStatus* c, RaceManager::Difficulty d, bool do_save=true); @@ -96,15 +101,21 @@ public: /** Returns the number of points accumulated. */ int getPoints () const { return m_points; } // ------------------------------------------------------------------------ + /** Returns the number of points needed by the next unlockable. 0 if none. */ + int getNextUnlockPoints () const { return m_next_unlock_points; } + // ------------------------------------------------------------------------ /** Returns the number of fulfilled challenges at easy level. */ int getNumEasyTrophies () const { return m_easy_challenges; } // ------------------------------------------------------------------------ /* Returns the number of fulfilled challenges at medium level. */ int getNumMediumTrophies() const { return m_medium_challenges; } // ------------------------------------------------------------------------ - /** Returns the number of fulfilled challenges at har level. */ + /** Returns the number of fulfilled challenges at hard level. */ int getNumHardTrophies () const { return m_hard_challenges; } // ------------------------------------------------------------------------ + /** Returns the number of fulfilled challenges at best level. */ + int getNumBestTrophies () const { return m_best_challenges; } + // ------------------------------------------------------------------------ /** Sets if this is the first time the intro is shown. */ void setFirstTime(bool ft) { m_first_time = ft; } // ------------------------------------------------------------------------ diff --git a/src/challenges/unlock_manager.cpp b/src/challenges/unlock_manager.cpp index 5a6501991..88914db53 100644 --- a/src/challenges/unlock_manager.cpp +++ b/src/challenges/unlock_manager.cpp @@ -23,6 +23,7 @@ #include "audio/sfx_manager.hpp" #include "challenges/challenge_data.hpp" #include "challenges/challenge_status.hpp" +#include "challenges/story_mode_status.hpp" #include "config/player_manager.hpp" #include "config/player_profile.hpp" #include "config/user_config.hpp" @@ -142,19 +143,30 @@ void UnlockManager::addOrFreeChallenge(ChallengeData *c) { if(isSupportedVersion(*c)) { - m_all_challenges[c->getId()]=c; + m_all_challenges[c->getChallengeId()]=c; + if (c->isUnlockList()) + addListChallenge(c); } else { Log::warn("Challenge", "Challenge '%s' is not supported - ignored.", - c->getId().c_str()); + c->getChallengeId().c_str()); delete c; } } // addOrFreeChallenge +//----------------------------------------------------------------------------- +/** Add a challenge to the unlock challenges list + * \param c The challenge that is either stored or freed. + */ +void UnlockManager::addListChallenge(ChallengeData *c) +{ + m_list_challenges[c->getChallengeId()]=c; +} // addListChallenge + //----------------------------------------------------------------------------- /** Reads a challenge from the given filename. The challenge will then either - * be stored, or (if the challenge version is not supported anymore + * be stored, or (if the challenge version is not supported anymore, freed) * \param filename Name of the challenge file to read. */ void UnlockManager::addChallenge(const std::string& filename) @@ -228,20 +240,25 @@ bool UnlockManager::isSupportedVersion(const ChallengeData &challenge) { // Test if challenge version number is in between minimum // and maximum supported version. - return (challenge.getVersion()>=2 && challenge.getVersion()<=2); + return (challenge.getVersion()>=3 && challenge.getVersion()<=3); } // isSupportedVersion //----------------------------------------------------------------------------- - +/** This functions finds what new tracks, GP and karts have been unlocked + */ void UnlockManager::findWhatWasUnlocked(int points_before, int points_now, std::vector& tracks, - std::vector& gps) + std::vector& gps, + std::vector& karts, + std::vector& unlocked) { + ChallengeData* c = NULL; + for (AllChallengesType::iterator it = m_all_challenges.begin(); it != m_all_challenges.end(); it++) { - ChallengeData* c = it->second; + c = it->second; if (c->getNumTrophies() > points_before && c->getNumTrophies() <= points_now ) { @@ -257,4 +274,36 @@ void UnlockManager::findWhatWasUnlocked(int points_before, int points_now, } } } -} + + for (unsigned int n = 0; n < unlocked.size(); n++) + { + std::vector features = unlocked[n]->getFeatures(); + + for (unsigned int i = 0; i < features.size(); i++) + { + if( features[i].m_type == ChallengeData::UNLOCK_KART ) + karts.push_back(features[i].m_name); + } + } + + //std::vector + // getRecentlyCompletedChallenges() +} // findWhatWasUnlocked + +//----------------------------------------------------------------------------- +/** This functions sets as completed the "challenges" requiring a certain number + * of points, to unlock features. + * Returns true if the challenge has been completed + */ +bool UnlockManager::unlockByPoints(int points, ChallengeStatus* unlock_list) +{ + //TODO : add support for other conditions (achievements...) for alternative unlock paths + if( unlock_list!=NULL && unlock_list->getData()->getNumTrophies() <= points) + { + unlock_list->setSolved(RaceManager::DIFFICULTY_BEST); + return true; + } + return false; +} // unlockByPoints + +/* EOF */ diff --git a/src/challenges/unlock_manager.hpp b/src/challenges/unlock_manager.hpp index 781379ec7..cb382e002 100644 --- a/src/challenges/unlock_manager.hpp +++ b/src/challenges/unlock_manager.hpp @@ -44,12 +44,16 @@ private: typedef std::map AllChallengesType; AllChallengesType m_all_challenges; + /* The challenges who don't have a race, only unlockables */ + AllChallengesType m_list_challenges; + void readAllChallengesInDirs(const std::vector* all_dirs); public: UnlockManager (); ~UnlockManager (); void addOrFreeChallenge(ChallengeData *c); + void addListChallenge(ChallengeData *c); void addChallenge (const std::string& filename); const ChallengeData *getChallengeData(const std::string& id); @@ -61,7 +65,10 @@ public: void findWhatWasUnlocked(int pointsBefore, int pointsNow, std::vector& tracks, - std::vector& gps); + std::vector& gps, + std::vector& karts, + std::vector& unlocked); + bool unlockByPoints(int points, ChallengeStatus* unlock_list); StoryModeStatus *createStoryModeStatus(const XMLNode *node=NULL); diff --git a/src/config/player_profile.hpp b/src/config/player_profile.hpp index 4d7962043..519f5f5c3 100644 --- a/src/config/player_profile.hpp +++ b/src/config/player_profile.hpp @@ -230,6 +230,8 @@ public: // ------------------------------------------------------------------------ unsigned int getPoints() const { return m_story_mode_status->getPoints(); } // ------------------------------------------------------------------------ + unsigned int getNextUnlockPoints() const { return m_story_mode_status->getNextUnlockPoints(); } + // ------------------------------------------------------------------------ void setFirstTime(bool b) { m_story_mode_status->setFirstTime(b); } // ------------------------------------------------------------------------ bool isFirstTime() const { return m_story_mode_status->isFirstTime(); } @@ -263,7 +265,11 @@ public: unsigned int getNumHardTrophies() const { return m_story_mode_status->getNumHardTrophies(); - } // getNumHardTropies + } // getNumHardTrophies + unsigned int getNumBestTrophies() const + { + return m_story_mode_status->getNumBestTrophies(); + } // getNumBestTrophies // ------------------------------------------------------------------------ AchievementsStatus* getAchievementsStatus() { diff --git a/src/modes/cutscene_world.cpp b/src/modes/cutscene_world.cpp index 61b768255..13c357518 100644 --- a/src/modes/cutscene_world.cpp +++ b/src/modes/cutscene_world.cpp @@ -422,11 +422,14 @@ void CutsceneWorld::enterRaceOverState() // un-set the GP mode so that after unlocking, it doesn't try to continue the GP race_manager->setMajorMode(RaceManager::MAJOR_MODE_SINGLE); + //TODO : this code largely duplicate a similar code present in raceResultGUI. + // Try to reduce duplication std::vector unlocked = PlayerManager::getCurrentPlayer()->getRecentlyCompletedChallenges(); + if (unlocked.size() > 0) { - //PlayerManager::getCurrentPlayer()->clearUnlocked(); + PlayerManager::getCurrentPlayer()->clearUnlocked(); StateManager::get()->enterGameState(); race_manager->setMinorMode(RaceManager::MINOR_MODE_CUTSCENE); @@ -441,8 +444,8 @@ void CutsceneWorld::enterRaceOverState() ((CutsceneWorld*)World::getWorld())->setParts(parts); assert(unlocked.size() > 0); - scene->addTrophy(race_manager->getDifficulty()); - scene->findWhatWasUnlocked(race_manager->getDifficulty()); + scene->addTrophy(race_manager->getDifficulty(),true); + scene->findWhatWasUnlocked(race_manager->getDifficulty(),unlocked); StateManager::get()->replaceTopMostScreen(scene, GUIEngine::INGAME_MENU); } @@ -476,9 +479,10 @@ void CutsceneWorld::enterRaceOverState() std::vector unlocked = PlayerManager::getCurrentPlayer()->getRecentlyCompletedChallenges(); + if (unlocked.size() > 0) { - //PlayerManager::getCurrentPlayer()->clearUnlocked(); + PlayerManager::getCurrentPlayer()->clearUnlocked(); StateManager::get()->enterGameState(); race_manager->setMinorMode(RaceManager::MINOR_MODE_CUTSCENE); @@ -492,8 +496,8 @@ void CutsceneWorld::enterRaceOverState() parts.push_back("featunlocked"); ((CutsceneWorld*)World::getWorld())->setParts(parts); - scene->addTrophy(race_manager->getDifficulty()); - scene->findWhatWasUnlocked(race_manager->getDifficulty()); + scene->addTrophy(race_manager->getDifficulty(),true); + scene->findWhatWasUnlocked(race_manager->getDifficulty(),unlocked); StateManager::get()->replaceTopMostScreen(scene, GUIEngine::INGAME_MENU); } @@ -594,4 +598,3 @@ void CutsceneWorld::createRaceGUI() m_race_gui = new CutsceneGUI(); } // createRaceGUI - diff --git a/src/modes/overworld.cpp b/src/modes/overworld.cpp index 966aaf1df..d839bcfed 100644 --- a/src/modes/overworld.cpp +++ b/src/modes/overworld.cpp @@ -65,7 +65,15 @@ void OverWorld::enterOverWorld() race_manager->setMinorMode (RaceManager::MINOR_MODE_OVERWORLD); race_manager->setNumKarts( 1 ); race_manager->setTrack( "overworld" ); - race_manager->setDifficulty(RaceManager::DIFFICULTY_HARD); + + if (PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + { + race_manager->setDifficulty(RaceManager::DIFFICULTY_HARD); + } + else + { + race_manager->setDifficulty(RaceManager::DIFFICULTY_BEST); + } // Use keyboard 0 by default (FIXME: let player choose?) InputDevice* device = input_manager->getDeviceManager()->getKeyboard(0); @@ -255,7 +263,7 @@ void OverWorld::onFirePressed(Controller* who) if (unlocked) { race_manager->setKartLastPositionOnOverworld(kart_xyz); - new SelectChallengeDialog(0.8f, 0.8f, + new SelectChallengeDialog(0.9f, 0.9f, challenges[n].m_challenge_id); } } diff --git a/src/states_screens/dialogs/select_challenge.cpp b/src/states_screens/dialogs/select_challenge.cpp index 857adac4a..b2e7221f9 100644 --- a/src/states_screens/dialogs/select_challenge.cpp +++ b/src/states_screens/dialogs/select_challenge.cpp @@ -43,9 +43,9 @@ core::stringw getLabel(RaceManager::Difficulty difficulty, const ChallengeData* { core::stringw label; - if (c->getPosition(difficulty) != -1) + if (c->getMaxPosition(difficulty) != -1) { - int r = c->getPosition(difficulty); + int r = c->getMaxPosition(difficulty); if (c->getMinorMode() == RaceManager::MINOR_MODE_FOLLOW_LEADER) r--; if (label.size() > 0) label.append(L"\n"); @@ -80,7 +80,10 @@ SelectChallengeDialog::SelectChallengeDialog(const float percentWidth, std::string challenge_id) : ModalDialog(percentWidth, percentHeight) { - loadFromFile("select_challenge.stkgui"); + if (PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + loadFromFile("select_challenge_nobest.stkgui"); + else + loadFromFile("select_challenge.stkgui"); m_challenge_id = challenge_id; World::getWorld()->schedulePause(WorldStatus::IN_GAME_MENU_PHASE); @@ -95,6 +98,14 @@ SelectChallengeDialog::SelectChallengeDialog(const float percentWidth, case 2: getWidget("expert")->setFocusForPlayer(PLAYER_ID_GAME_MASTER); break; + case 3: + { + if(PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + getWidget("expert")->setFocusForPlayer(PLAYER_ID_GAME_MASTER); + else + getWidget("supertux")->setFocusForPlayer(PLAYER_ID_GAME_MASTER); + break; + } } const ChallengeStatus* c = PlayerManager::getCurrentPlayer() @@ -121,6 +132,14 @@ SelectChallengeDialog::SelectChallengeDialog(const float percentWidth, IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE); } + if (c->isSolved(RaceManager::DIFFICULTY_BEST) + && !PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + { + IconButtonWidget* btn = getWidget("supertux"); + btn->setImage(file_manager->getAsset(FileManager::GUI,"cup_platinum.png"), + IconButtonWidget::ICON_PATH_TYPE_ABSOLUTE); + } + LabelWidget* novice_label = getWidget("novice_label"); LabelWidget* medium_label = getWidget("intermediate_label"); @@ -130,6 +149,12 @@ SelectChallengeDialog::SelectChallengeDialog(const float percentWidth, medium_label->setText( getLabel(RaceManager::DIFFICULTY_MEDIUM, c->getData()), false ); expert_label->setText( getLabel(RaceManager::DIFFICULTY_HARD, c->getData()), false ); + if (!PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + { + LabelWidget* supertux_label = getWidget("supertux_label"); + supertux_label->setText( getLabel(RaceManager::DIFFICULTY_BEST, c->getData()), false ); + } + if (c->getData()->isGrandPrix()) { const GrandPrixData* gp = grand_prix_manager->getGrandPrix(c->getData()->getGPId()); @@ -167,7 +192,7 @@ GUIEngine::EventPropagation SelectChallengeDialog::processEvent(const std::strin { std::string eventSource = eventSourceParam; if (eventSource == "novice" || eventSource == "intermediate" || - eventSource == "expert") + eventSource == "expert" || eventSource == "supertux") { const ChallengeData* challenge = unlock_manager->getChallengeData(m_challenge_id); @@ -229,6 +254,11 @@ GUIEngine::EventPropagation SelectChallengeDialog::processEvent(const std::strin challenge->setRace(RaceManager::DIFFICULTY_HARD); UserConfigParams::m_difficulty = 2; } + else if (eventSource == "supertux") + { + challenge->setRace(RaceManager::DIFFICULTY_BEST); + UserConfigParams::m_difficulty = 3; + } else { Log::error("SelectChallenge", "Unknown widget <%s>\n", @@ -250,4 +280,3 @@ GUIEngine::EventPropagation SelectChallengeDialog::processEvent(const std::strin } // ---------------------------------------------------------------------------- - diff --git a/src/states_screens/feature_unlocked.cpp b/src/states_screens/feature_unlocked.cpp index c57b7ea01..06664f7da 100644 --- a/src/states_screens/feature_unlocked.cpp +++ b/src/states_screens/feature_unlocked.cpp @@ -81,7 +81,7 @@ FeatureUnlockedCutScene::UnlockedThing::UnlockedThing(std::string model, // ------------------------------------------------------------------------------------- -FeatureUnlockedCutScene::UnlockedThing::UnlockedThing(KartProperties* kart, +FeatureUnlockedCutScene::UnlockedThing::UnlockedThing(const KartProperties* kart, irr::core::stringw msg) { m_unlocked_kart = kart; @@ -202,7 +202,7 @@ void FeatureUnlockedCutScene::onCutsceneEnd() // ---------------------------------------------------------------------------- -void FeatureUnlockedCutScene::findWhatWasUnlocked(RaceManager::Difficulty difficulty) +void FeatureUnlockedCutScene::findWhatWasUnlocked(RaceManager::Difficulty difficulty,std::vector& unlocked) { PlayerProfile *player = PlayerManager::getCurrentPlayer(); int points_before = player->getPoints(); @@ -210,9 +210,10 @@ void FeatureUnlockedCutScene::findWhatWasUnlocked(RaceManager::Difficulty diffic std::vector tracks; std::vector gps; + std::vector karts; player->computeActive(); - unlock_manager->findWhatWasUnlocked(points_before, points_now, tracks, gps); + unlock_manager->findWhatWasUnlocked(points_before, points_now, tracks, gps, karts, unlocked); for (unsigned int i = 0; i < tracks.size(); i++) { @@ -222,26 +223,41 @@ void FeatureUnlockedCutScene::findWhatWasUnlocked(RaceManager::Difficulty diffic { addUnlockedGP(grand_prix_manager->getGrandPrix(gps[i])); } + for (unsigned int i = 0; i < karts.size(); i++) + { + addUnlockedKart(kart_properties_manager->getKart(karts[i])); + } } // ---------------------------------------------------------------------------- -void FeatureUnlockedCutScene::addTrophy(RaceManager::Difficulty difficulty) +void FeatureUnlockedCutScene::addTrophy(RaceManager::Difficulty difficulty, bool is_grandprix) { core::stringw msg; + + int gp_factor = is_grandprix ? GP_FACTOR : 1; + RaceManager::Difficulty max_unlocked_difficulty = RaceManager::DIFFICULTY_BEST; + + if (PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + max_unlocked_difficulty = RaceManager::DIFFICULTY_HARD; + switch (difficulty) { case RaceManager::DIFFICULTY_EASY: msg = _("You completed the easy challenge! Points earned on this level: %i/%i", - CHALLENGE_POINTS[RaceManager::DIFFICULTY_EASY], CHALLENGE_POINTS[RaceManager::DIFFICULTY_HARD]); + CHALLENGE_POINTS[RaceManager::DIFFICULTY_EASY]*gp_factor, CHALLENGE_POINTS[max_unlocked_difficulty]*gp_factor); break; case RaceManager::DIFFICULTY_MEDIUM: msg = _("You completed the intermediate challenge! Points earned on this level: %i/%i", - CHALLENGE_POINTS[RaceManager::DIFFICULTY_MEDIUM], CHALLENGE_POINTS[RaceManager::DIFFICULTY_HARD]); + CHALLENGE_POINTS[RaceManager::DIFFICULTY_MEDIUM]*gp_factor, CHALLENGE_POINTS[max_unlocked_difficulty]*gp_factor); break; case RaceManager::DIFFICULTY_HARD: msg = _("You completed the difficult challenge! Points earned on this level: %i/%i", - CHALLENGE_POINTS[RaceManager::DIFFICULTY_HARD], CHALLENGE_POINTS[RaceManager::DIFFICULTY_HARD]); + CHALLENGE_POINTS[RaceManager::DIFFICULTY_HARD]*gp_factor, CHALLENGE_POINTS[max_unlocked_difficulty]*gp_factor); + break; + case RaceManager::DIFFICULTY_BEST: + msg = _("You completed the SuperTux challenge! Points earned on this level: %i/%i", + CHALLENGE_POINTS[RaceManager::DIFFICULTY_BEST]*gp_factor, CHALLENGE_POINTS[max_unlocked_difficulty]*gp_factor); break; default: assert(false); @@ -259,6 +275,9 @@ void FeatureUnlockedCutScene::addTrophy(RaceManager::Difficulty difficulty) case RaceManager::DIFFICULTY_HARD: model = file_manager->getAsset(FileManager::MODEL,"trophy_gold.spm"); break; + case RaceManager::DIFFICULTY_BEST: + model = file_manager->getAsset(FileManager::MODEL,"trophy_platinum.spm"); + break; default: assert(false); return; @@ -269,12 +288,15 @@ void FeatureUnlockedCutScene::addTrophy(RaceManager::Difficulty difficulty) } // ---------------------------------------------------------------------------- -// unused for now, maybe will be useful later? -void FeatureUnlockedCutScene::addUnlockedKart(KartProperties* unlocked_kart, - irr::core::stringw msg) +void FeatureUnlockedCutScene::addUnlockedKart(const KartProperties* unlocked_kart) { - assert(unlocked_kart != NULL); + if (unlocked_kart == NULL) + { + Log::error("FeatureUnlockedCutScene::addUnlockedKart", "Unlocked kart does not exist"); + return; + } + irr::core::stringw msg = _("You unlocked %s!", unlocked_kart->getName()); m_unlocked_stuff.push_back( new UnlockedThing(unlocked_kart, msg) ); } // addUnlockedKart @@ -426,8 +448,6 @@ void FeatureUnlockedCutScene::init() Log::error("FeatureUnlockedCutScene::init", "Malformed unlocked goody"); } } - - PlayerManager::getCurrentPlayer()->clearUnlocked(); } // init // ---------------------------------------------------------------------------- diff --git a/src/states_screens/feature_unlocked.hpp b/src/states_screens/feature_unlocked.hpp index 6de85209c..86560c963 100644 --- a/src/states_screens/feature_unlocked.hpp +++ b/src/states_screens/feature_unlocked.hpp @@ -54,7 +54,7 @@ class FeatureUnlockedCutScene : public GUIEngine::CutsceneScreen, public GUIEngi struct UnlockedThing { /** Will be non-null if this unlocked thing is a kart */ - KartProperties* m_unlocked_kart; + const KartProperties* m_unlocked_kart; std::string m_unlock_model; @@ -80,7 +80,7 @@ class FeatureUnlockedCutScene : public GUIEngine::CutsceneScreen, public GUIEngi UnlockedThing(std::string model, irr::core::stringw msg); - UnlockedThing(KartProperties* kart, irr::core::stringw msg); + UnlockedThing(const KartProperties* kart, irr::core::stringw msg); /** * Creates a 'picture' reward. @@ -141,11 +141,11 @@ public: void eventCallback(GUIEngine::Widget* widget, const std::string& name, const int playerID) OVERRIDE; - void findWhatWasUnlocked(RaceManager::Difficulty difficulty); + void findWhatWasUnlocked(RaceManager::Difficulty difficulty,std::vector& unlocked); /** Call before showing up the screen to make a kart come out of the chest. 'addUnlockedThings' will invoke this, so you generally don't need to call this directly. */ - void addUnlockedKart(KartProperties* unlocked_kart, irr::core::stringw msg); + void addUnlockedKart(const KartProperties* unlocked_kart); /** Call before showing up the screen to make a picture come out of the chest 'addUnlockedThings' will invoke this, so you generally don't need to call this directly. */ @@ -166,7 +166,7 @@ public: void addUnlockedThings(const std::vector unlocked); */ - void addTrophy(RaceManager::Difficulty difficulty); + void addTrophy(RaceManager::Difficulty difficulty, bool is_grandprix); /** override from base class to handle escape press */ virtual bool onEscapePressed() OVERRIDE; @@ -175,4 +175,3 @@ public: }; #endif - diff --git a/src/states_screens/main_menu_screen.cpp b/src/states_screens/main_menu_screen.cpp index 102bc0bbf..0f41e82b2 100644 --- a/src/states_screens/main_menu_screen.cpp +++ b/src/states_screens/main_menu_screen.cpp @@ -318,18 +318,11 @@ void MainMenuScreen::eventCallback(Widget* widget, const std::string& name, parts.push_back("featunlocked"); ((CutsceneWorld*)World::getWorld())->setParts(parts); - scene->addTrophy(RaceManager::DIFFICULTY_EASY); + scene->addTrophy(RaceManager::DIFFICULTY_EASY, false); if (selection == "test_unlocked") { - // the passed kart will not be modified, that's why I allow myself - // to use const_cast - scene->addUnlockedKart( - const_cast( - kart_properties_manager->getKart("tux") - ), - L"You unlocked " - ); + scene->addUnlockedKart(kart_properties_manager->getKart("tux")); scene->addUnlockedTrack(track_manager->getTrack("lighthouse")); scene->push(); } diff --git a/src/states_screens/race_gui_overworld.cpp b/src/states_screens/race_gui_overworld.cpp index b45dfc909..b7c5c8358 100644 --- a/src/states_screens/race_gui_overworld.cpp +++ b/src/states_screens/race_gui_overworld.cpp @@ -65,6 +65,7 @@ const int OPEN = 1; const int COMPLETED_EASY = 2; const int COMPLETED_MEDIUM = 3; const int COMPLETED_HARD = 4; +const int COMPLETED_BEST = 5; /** The constructor is called before anything is attached to the scene node. * So rendering to a texture can be done here. But world is not yet fully @@ -83,6 +84,7 @@ RaceGUIOverworld::RaceGUIOverworld() m_trophy1 = irr_driver->getTexture(FileManager::GUI, "cup_bronze.png"); m_trophy2 = irr_driver->getTexture(FileManager::GUI, "cup_silver.png"); m_trophy3 = irr_driver->getTexture(FileManager::GUI, "cup_gold.png" ); + m_trophy4 = irr_driver->getTexture(FileManager::GUI, "cup_platinum.png" ); float scaling = irr_driver->getFrameSize().Height / 420.0f; const float map_size = 250.0f; @@ -122,6 +124,7 @@ RaceGUIOverworld::RaceGUIOverworld() // special case : when 3 players play, use available 4th space for such things + // TODO : determine if there are plans for multiplayer in story mode in the future if (race_manager->getIfEmptyScreenSpaceExists()) { m_map_left = irr_driver->getActualScreenSize().Width - m_map_width; @@ -152,12 +155,15 @@ RaceGUIOverworld::RaceGUIOverworld() m_lock = irr_driver->getTexture(FileManager::GUI,"gui_lock.png"); m_open_challenge = irr_driver->getTexture(FileManager::GUI,"challenge.png"); + m_locked_bonus = irr_driver->getTexture(FileManager::GUI,"mystery_unlock.png"); m_icons[0] = m_lock; m_icons[1] = m_open_challenge; m_icons[2] = m_trophy1; m_icons[3] = m_trophy2; m_icons[4] = m_trophy3; + m_icons[5] = m_trophy4; + m_icons[6] = m_locked_bonus; } // RaceGUIOverworld //----------------------------------------------------------------------------- @@ -179,6 +185,7 @@ void RaceGUIOverworld::renderGlobal(float dt) // Special case : when 3 players play, use 4th window to display such // stuff (but we must clear it) + //TODO : remove if no story mode multiplayer plans if (race_manager->getIfEmptyScreenSpaceExists() && !GUIEngine::ModalDialog::isADialogActive()) { @@ -257,8 +264,11 @@ void RaceGUIOverworld::drawTrophyPoints() #ifndef SERVER_ONLY PlayerProfile *player = PlayerManager::getCurrentPlayer(); const int points = player->getPoints(); + const int next_unlock_points = player->getNextUnlockPoints(); std::string s = StringUtils::toString(points); + std::string s_goal = StringUtils::toString(next_unlock_points); core::stringw sw(s.c_str()); + core::stringw swg(s_goal.c_str()); static video::SColor time_color = video::SColor(255, 255, 255, 255); @@ -274,7 +284,7 @@ void RaceGUIOverworld::drawTrophyPoints() const int size = irr_driver->getActualScreenSize().Width/20; core::rect dest(size, pos.UpperLeftCorner.Y, size*2, pos.UpperLeftCorner.Y + size); - core::rect source(core::position2di(0, 0), m_trophy3->getSize()); + core::rect source(core::position2di(0, 0), m_trophy4->getSize()); font->setShadow(video::SColor(255,0,0,0)); @@ -321,17 +331,62 @@ void RaceGUIOverworld::drawTrophyPoints() font->draw(hardTrophiesW.c_str(), dest, time_color, false, vcenter, NULL, true /* ignore RTL */); } - dest = core::rect(pos.UpperLeftCorner.X - size - 5, pos.UpperLeftCorner.Y, - pos.UpperLeftCorner.X - 5, pos.UpperLeftCorner.Y + size); + dest += core::position2di(size*2, 0); + if (!m_close_to_a_challenge && !PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + { + draw2DImage(m_trophy4, dest, source, NULL, NULL, true /* alpha */); + } + dest += core::position2di((int)(size*1.5f), 0); + std::string bestTrophies = StringUtils::toString(player->getNumBestTrophies()); + core::stringw bestTrophiesW(bestTrophies.c_str()); + if (!m_close_to_a_challenge && !PlayerManager::getCurrentPlayer()->isLocked("difficulty_best")) + { + font->draw(bestTrophiesW.c_str(), dest, time_color, false, vcenter, NULL, true /* ignore RTL */); + } + + dest = core::rect(pos.UpperLeftCorner.X - size, pos.UpperLeftCorner.Y, + pos.UpperLeftCorner.X, pos.UpperLeftCorner.Y + size); draw2DImage(m_open_challenge, dest, source, NULL, NULL, true /* alpha */); - pos.LowerRightCorner.Y = dest.LowerRightCorner.Y; - pos.UpperLeftCorner.X += 5; + core::dimension2du area = font->getDimension(L"9"); + int small_width = area.Width; + area = font->getDimension(L"99"); + int middle_width = area.Width; + area = font->getDimension(L"999"); + int large_width = area.Width; + int number_width; + + if (points < 9) number_width = small_width; + else if (points <99) number_width = middle_width; + else number_width = large_width; + + pos.LowerRightCorner.Y = dest.LowerRightCorner.Y + 1.5f*size; + pos.UpperLeftCorner.X -= (0.5f*size + number_width*0.5f); font->draw(sw.c_str(), pos, time_color, false, vcenter, NULL, true /* ignore RTL */); + + pos.UpperLeftCorner.X += (0.5f*size + number_width*0.5f); + + if (next_unlock_points > points && (points + 80) >= next_unlock_points) + { + if (next_unlock_points < 9) number_width = small_width; + else if (next_unlock_points <99) number_width = middle_width; + else number_width = large_width; + + dest = core::rect(pos.UpperLeftCorner.X - 2.5f*size, pos.UpperLeftCorner.Y, + pos.UpperLeftCorner.X - 1.5f*size, pos.UpperLeftCorner.Y + size); + + draw2DImage(m_locked_bonus, dest, source, NULL, + NULL, true /* alpha */); + + pos.UpperLeftCorner.X -= (2*size + number_width*0.5f); + + font->draw(swg.c_str(), pos, time_color, false, vcenter, NULL, true /* ignore RTL */); + } + font->disableShadow(); #endif } // drawTrophyPoints @@ -468,7 +523,8 @@ void RaceGUIOverworld::drawGlobalMiniMap() const ChallengeStatus* c = PlayerManager::getCurrentPlayer() ->getChallengeStatus(challenges[n].m_challenge_id); - if (c->isSolved(RaceManager::DIFFICULTY_HARD)) state = COMPLETED_HARD; + if (c->isSolved(RaceManager::DIFFICULTY_BEST)) state = COMPLETED_BEST; + else if (c->isSolved(RaceManager::DIFFICULTY_HARD)) state = COMPLETED_HARD; else if (c->isSolved(RaceManager::DIFFICULTY_MEDIUM)) state = COMPLETED_MEDIUM; else if (c->isSolved(RaceManager::DIFFICULTY_EASY)) state = COMPLETED_EASY; @@ -622,4 +678,3 @@ void RaceGUIOverworld::drawGlobalMiniMap() } // drawGlobalMiniMap //----------------------------------------------------------------------------- - diff --git a/src/states_screens/race_gui_overworld.hpp b/src/states_screens/race_gui_overworld.hpp index 3779d5371..5f3fc3af7 100644 --- a/src/states_screens/race_gui_overworld.hpp +++ b/src/states_screens/race_gui_overworld.hpp @@ -65,10 +65,13 @@ private: video::ITexture *m_trophy1; video::ITexture *m_trophy2; video::ITexture *m_trophy3; + video::ITexture *m_trophy4; video::ITexture *m_lock; video::ITexture *m_open_challenge; + video::ITexture *m_locked_bonus; - video::ITexture* m_icons[5]; + + video::ITexture* m_icons[7]; /** The size of a single marker on the screen for AI karts, * need not be a power of 2. */ diff --git a/src/states_screens/race_result_gui.cpp b/src/states_screens/race_result_gui.cpp index ba77050cd..6fe019225 100644 --- a/src/states_screens/race_result_gui.cpp +++ b/src/states_screens/race_result_gui.cpp @@ -298,15 +298,13 @@ void RaceResultGUI::eventCallback(GUIEngine::Widget* widget, bool gameCompleted = false; for (unsigned int n = 0; n < unlocked.size(); n++) { - if (unlocked[n]->getId() == "fortmagma") + if (unlocked[n]->getChallengeId() == "fortmagma") { gameCompleted = true; break; } } - PlayerManager::getCurrentPlayer()->clearUnlocked(); - if (gameCompleted) { // clear the race @@ -343,8 +341,8 @@ void RaceResultGUI::eventCallback(GUIEngine::Widget* widget, FeatureUnlockedCutScene* scene = FeatureUnlockedCutScene::getInstance(); - scene->addTrophy(race_manager->getDifficulty()); - scene->findWhatWasUnlocked(race_manager->getDifficulty()); + scene->addTrophy(race_manager->getDifficulty(),false); + scene->findWhatWasUnlocked(race_manager->getDifficulty(),unlocked); scene->push(); race_manager->setAIKartOverride(""); @@ -352,6 +350,9 @@ void RaceResultGUI::eventCallback(GUIEngine::Widget* widget, parts.push_back("featunlocked"); ((CutsceneWorld*)World::getWorld())->setParts(parts); } + + PlayerManager::getCurrentPlayer()->clearUnlocked(); + return; } Log::warn("RaceResultGUI", "Incorrect event '%s' when things are unlocked.", From 28eb0112e006c9620d54e4822ca98aad6217346e Mon Sep 17 00:00:00 2001 From: hiker Date: Mon, 30 Apr 2018 08:49:57 +1000 Subject: [PATCH 011/137] Moved handleMaterialSFX to updateGraphics(), which removes the need for a isLastSubstep() function in main(). Fix potential bug when a new material sfx is triggered when a previous sfx still exists. --- src/karts/kart.cpp | 46 ++++++++++++++++++++++++++++++---------------- src/karts/kart.hpp | 7 ++++++- src/main_loop.hpp | 4 ---- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/src/karts/kart.cpp b/src/karts/kart.cpp index 5e1984752..5cfa9cc1a 100644 --- a/src/karts/kart.cpp +++ b/src/karts/kart.cpp @@ -197,6 +197,7 @@ Kart::Kart (const std::string& ident, unsigned int world_kart_id, m_skid_sound = SFXManager::get()->createSoundSource( "skid" ); m_nitro_sound = SFXManager::get()->createSoundSource( "nitro" ); m_terrain_sound = NULL; + m_last_sound_material = NULL; m_previous_terrain_sound = NULL; } // Kart @@ -1202,7 +1203,8 @@ float Kart::getShieldTime() const // ------------------------------------------------------------------------ /** * Decreases the kart's shield time. - * \param t The time substracted from the shield timer. If t == 0.0f, the default amout of time is substracted. + * \param t The time substracted from the shield timer. If t == 0.0f, the + default amout of time is substracted. */ void Kart::decreaseShieldTime() { @@ -1583,7 +1585,6 @@ void Kart::update(int ticks) } body->setGravity(gravity); } // if !flying - handleMaterialSFX(material); if (material->isDriveReset() && isOnGround()) { new RescueAnimation(this); @@ -1777,7 +1778,7 @@ void Kart::setSquash(float time, float slowdown) //----------------------------------------------------------------------------- /** Plays any terrain specific sound effect. */ -void Kart::handleMaterialSFX(const Material *material) +void Kart::handleMaterialSFX() { // If a terrain specific sfx is already being played, when a new // terrain is entered, an old sfx should be finished (once, not @@ -1790,7 +1791,14 @@ void Kart::handleMaterialSFX(const Material *material) // FIXME: if there are already two sfx playing, don't add another // one. This should reduce the performance impact when driving // on the bridge in Cocoa. - if(getLastMaterial()!=material && !m_previous_terrain_sound) + const Material *material = m_terrain_info->getMaterial(); + + // We can not use getLastMaterial() since, since the last material might + // be updated several times during the physics updates, not indicating + // that we have reached a new material with regards to the sound effect. + // So we separately save the material last used for a sound effect and + // then use this for comparison. + if(m_last_sound_material!=material) { // First stop any previously playing terrain sound // and remove it, so that m_previous_terrain_sound @@ -1799,19 +1807,21 @@ void Kart::handleMaterialSFX(const Material *material) { m_previous_terrain_sound->deleteSFX(); } - m_previous_terrain_sound = m_terrain_sound; - if(m_previous_terrain_sound) - m_previous_terrain_sound->setLoop(false); - const std::string &s = material->getSFXName(); + // Disable looping for the current terrain sound, and + // make it the previous terrain sound. + if (m_terrain_sound) m_terrain_sound->setLoop(false); + m_previous_terrain_sound = m_terrain_sound; + + const std::string &sound_name = material ? material->getSFXName() : ""; // In multiplayer mode sounds are NOT positional, because we have // multiple listeners. This would make the sounds of all AIs be // audible at all times. So silence AI karts. - if (s.size()!=0 && (race_manager->getNumPlayers()==1 || - m_controller->isLocalPlayerController() ) ) + if (!sound_name.empty() && (race_manager->getNumPlayers()==1 || + m_controller->isLocalPlayerController() ) ) { - m_terrain_sound = SFXManager::get()->createSoundSource(s); + m_terrain_sound = SFXManager::get()->createSoundSource(sound_name); m_terrain_sound->play(); m_terrain_sound->setLoop(true); } @@ -1821,6 +1831,8 @@ void Kart::handleMaterialSFX(const Material *material) } } + // Check if a previous terrain sound (now not looped anymore) + // is finished and can be deleted. if(m_previous_terrain_sound && m_previous_terrain_sound->getStatus()==SFXBase::SFX_STOPPED) { @@ -1837,14 +1849,16 @@ void Kart::handleMaterialSFX(const Material *material) // terrain sound is not necessarily a looping sound so check its status before // setting its speed, to avoid 'ressuscitating' sounds that had already stopped - if(m_terrain_sound && main_loop->isLastSubstep() && - (m_terrain_sound->getStatus()==SFXBase::SFX_PLAYING || - m_terrain_sound->getStatus()==SFXBase::SFX_PAUSED)) + if(m_terrain_sound && + (m_terrain_sound->getStatus()==SFXBase::SFX_PLAYING || + m_terrain_sound->getStatus()==SFXBase::SFX_PAUSED) ) { m_terrain_sound->setPosition(getXYZ()); - material->setSFXSpeed(m_terrain_sound, m_speed, m_schedule_pause); + if(material) + material->setSFXSpeed(m_terrain_sound, m_speed, m_schedule_pause); } + m_last_sound_material = material; } // handleMaterialSFX //----------------------------------------------------------------------------- @@ -3038,7 +3052,7 @@ void Kart::updateGraphics(float dt) handleMaterialGFX(dt); updateEngineSFX(dt); - + handleMaterialSFX(); } // updateGraphics // ---------------------------------------------------------------------------- diff --git a/src/karts/kart.hpp b/src/karts/kart.hpp index 9a8592b81..7f4f7a87a 100644 --- a/src/karts/kart.hpp +++ b/src/karts/kart.hpp @@ -226,7 +226,12 @@ protected: static const int EMITTER_COUNT = 3; SFXBase *m_emitters[EMITTER_COUNT]; SFXBase *m_engine_sound; + /** Sound to be played depending on terrain. */ SFXBase *m_terrain_sound; + + /** The material for which the last sound effect was played. */ + const Material *m_last_sound_material; + SFXBase *m_nitro_sound; /** A pointer to the previous terrain sound needs to be saved so that an * 'older' sfx can be finished and an abrupt end of the sfx is avoided. */ @@ -244,7 +249,7 @@ protected: int m_min_nitro_ticks; void updatePhysics(int ticks); - void handleMaterialSFX(const Material *material); + void handleMaterialSFX(); void handleMaterialGFX(float dt); void updateFlying(); void updateSliding(); diff --git a/src/main_loop.hpp b/src/main_loop.hpp index f76dc9d91..574628a48 100644 --- a/src/main_loop.hpp +++ b/src/main_loop.hpp @@ -54,10 +54,6 @@ public: /** Returns true if STK is to be stoppe. */ bool isAborted() const { return m_abort; } // ------------------------------------------------------------------------ - /** Returns if this is the last substep. Used to reduce the amount - * of updates (e.g. to sfx position) to once per rendered frame. */ - bool isLastSubstep() const { return m_is_last_substep; } - // ------------------------------------------------------------------------ void setFrameBeforeLoadingWorld() { m_frame_before_loading_world = true; } }; // MainLoop From d17fddcac8a0fad907bd45a9d8a542cf4238698c Mon Sep 17 00:00:00 2001 From: Benau Date: Tue, 1 May 2018 01:10:12 +0800 Subject: [PATCH 012/137] Fix handicap in splitscreen and network --- src/guiengine/widgets/kart_stats_widget.cpp | 7 +-- src/guiengine/widgets/kart_stats_widget.hpp | 3 +- src/guiengine/widgets/player_kart_widget.cpp | 47 +++++++++++-------- src/guiengine/widgets/player_kart_widget.hpp | 15 +++--- src/guiengine/widgets/spinner_widget.cpp | 12 ++--- src/guiengine/widgets/spinner_widget.hpp | 3 +- src/karts/abstract_kart.cpp | 2 +- .../controller/local_player_controller.cpp | 19 +++++--- .../controller/local_player_controller.hpp | 5 +- src/karts/controller/player_controller.cpp | 9 +++- src/karts/kart_properties.cpp | 12 +++-- src/karts/kart_properties.hpp | 5 +- src/main.cpp | 2 +- src/modes/soccer_world.cpp | 4 +- src/modes/world.cpp | 3 +- src/network/network_config.hpp | 10 ++-- src/network/protocols/client_lobby.cpp | 3 ++ src/network/protocols/server_lobby.cpp | 1 + .../dialogs/splitscreen_player_dialog.cpp | 11 +++-- src/states_screens/kart_selection.cpp | 17 ++++--- src/states_screens/network_kart_selection.cpp | 7 ++- src/states_screens/online_screen.cpp | 4 +- 22 files changed, 121 insertions(+), 80 deletions(-) diff --git a/src/guiengine/widgets/kart_stats_widget.cpp b/src/guiengine/widgets/kart_stats_widget.cpp index e927b8836..a261ba487 100644 --- a/src/guiengine/widgets/kart_stats_widget.cpp +++ b/src/guiengine/widgets/kart_stats_widget.cpp @@ -80,7 +80,7 @@ KartStatsWidget::KartStatsWidget(core::recti area, const int player_id, m_children.push_back(skill_bar); } - setValues(props); + setValues(props, PLAYER_DIFFICULTY_NORMAL); move(area.UpperLeftCorner.X, area.UpperLeftCorner.Y, area.getWidth(), area.getHeight()); @@ -88,7 +88,8 @@ KartStatsWidget::KartStatsWidget(core::recti area, const int player_id, // ----------------------------------------------------------------------------- -void KartStatsWidget::setValues(const KartProperties* props) +void KartStatsWidget::setValues(const KartProperties* props, + PerPlayerDifficulty d) { // Use kart properties computed for "hard" difficulty to show the user, so // that properties don't change according to the the last used difficulty @@ -97,7 +98,7 @@ void KartStatsWidget::setValues(const KartProperties* props) RaceManager::Difficulty previous_difficulty = race_manager->getDifficulty(); race_manager->setDifficulty(RaceManager::DIFFICULTY_HARD); KartProperties kp_computed; - kp_computed.copyForPlayer(props); + kp_computed.copyForPlayer(props, d); // Scale the values so they look better // The scaling factor and offset were found by trial and error. diff --git a/src/guiengine/widgets/kart_stats_widget.hpp b/src/guiengine/widgets/kart_stats_widget.hpp index 9e9a838b5..2136f21fc 100644 --- a/src/guiengine/widgets/kart_stats_widget.hpp +++ b/src/guiengine/widgets/kart_stats_widget.hpp @@ -31,6 +31,7 @@ #include "guiengine/widgets/skill_level_widget.hpp" class KartProperties; +enum PerPlayerDifficulty : uint8_t; namespace GUIEngine { @@ -94,7 +95,7 @@ namespace GUIEngine * inside itself */ void setSize(const int x, const int y, const int w, const int h); - void setValues(const KartProperties* props); + void setValues(const KartProperties* props, PerPlayerDifficulty d); /** Change the value of the widget, it must be a percent. */ void setValue(Stats type, int value); diff --git a/src/guiengine/widgets/player_kart_widget.cpp b/src/guiengine/widgets/player_kart_widget.cpp index 0aa7323e2..069ded4f5 100644 --- a/src/guiengine/widgets/player_kart_widget.cpp +++ b/src/guiengine/widgets/player_kart_widget.cpp @@ -28,7 +28,6 @@ #include "karts/kart_model.hpp" #include "karts/kart_properties.hpp" #include "karts/kart_properties_manager.hpp" -#include "network/network_player_profile.hpp" #include "states_screens/kart_selection.hpp" #include @@ -36,7 +35,6 @@ using namespace GUIEngine; PlayerKartWidget::PlayerKartWidget(KartSelectionScreen* parent, StateManager::ActivePlayer* associated_player, - NetworkPlayerProfile* associated_user, core::recti area, const int player_id, std::string kart_group, const int irrlicht_widget_id) : Widget(WTYPE_DIV) @@ -49,14 +47,13 @@ PlayerKartWidget::PlayerKartWidget(KartSelectionScreen* parent, m_ready_text = NULL; m_parent_screen = parent; - m_associated_user = associated_user; m_associated_player = associated_player; x_speed = 1.0f; y_speed = 1.0f; w_speed = 1.0f; h_speed = 1.0f; m_ready = false; - m_handicapped = false; + m_difficulty = PLAYER_DIFFICULTY_NORMAL; m_not_updated_yet = true; m_irrlicht_widget_id = irrlicht_widget_id; @@ -116,10 +113,6 @@ PlayerKartWidget::PlayerKartWidget(KartSelectionScreen* parent, m_player_ident_spinner->setBadge(GAMEPAD_BADGE); } } - else if (m_associated_user) // online user, FIXME is that useful ? - { - m_player_ident_spinner->setBadge(OK_BADGE); - } if (irrlicht_widget_id == -1) { @@ -184,7 +177,7 @@ PlayerKartWidget::PlayerKartWidget(KartSelectionScreen* parent, "kart '%s' nor any other kart.", default_kart.c_str()); } - m_kartInternalName = props->getIdent(); + m_kart_internal_name = props->getIdent(); const KartModel &kart_model = props->getMasterKartModel(); @@ -374,8 +367,6 @@ void PlayerKartWidget::add() irr::core::stringw name; // name of the player if (m_associated_player) name = m_associated_player->getProfile()->getName(); - if (m_associated_user) - name = m_associated_user->getName(); core::stringw label = translations->fribidize(name); if (m_parent_screen->m_multiplayer) @@ -478,12 +469,12 @@ bool PlayerKartWidget::isReady() } // isReady // ------------------------------------------------------------------------ -/** \return Whether this player is handicapped or not */ -bool PlayerKartWidget::isHandicapped() +/** \return Per player difficulty */ +PerPlayerDifficulty PlayerKartWidget::getDifficulty() { assert(m_magic_number == 0x33445566); - return m_handicapped; -} // isHandicapped + return m_difficulty; +} // getDifficulty // ------------------------------------------------------------------------- /** Updates the animation (moving/shrinking/etc.) */ @@ -636,13 +627,19 @@ GUIEngine::EventPropagation PlayerKartWidget::transmitEvent(Widget* w, m_associated_player->setPlayerProfile(profile); if(UserConfigParams::m_per_player_difficulty && spinner_value % 2 != 0) { - m_handicapped = true; + m_difficulty = PLAYER_DIFFICULTY_HANDICAP; m_model_view->setBadge(ANCHOR_BADGE); + m_kart_stats->setValues( + kart_properties_manager->getKart(m_kart_internal_name), + PLAYER_DIFFICULTY_HANDICAP); } else { - m_handicapped = false; + m_difficulty = PLAYER_DIFFICULTY_NORMAL; m_model_view->unsetBadge(ANCHOR_BADGE); + m_kart_stats->setValues( + kart_properties_manager->getKart(m_kart_internal_name), + PLAYER_DIFFICULTY_NORMAL); } m_model_view->getModelViewRenderInfo()->setHue( m_associated_player->getConstProfile()->getDefaultKartColor()); @@ -728,7 +725,7 @@ void PlayerKartWidget::setSize(const int x, const int y, const int w, const int void PlayerKartWidget::setKartInternalName(const std::string& whichKart) { assert(m_magic_number == 0x33445566); - m_kartInternalName = whichKart; + m_kart_internal_name = whichKart; } // setKartInternalName // ------------------------------------------------------------------------- @@ -736,7 +733,7 @@ void PlayerKartWidget::setKartInternalName(const std::string& whichKart) const std::string& PlayerKartWidget::getKartInternalName() const { assert(m_magic_number == 0x33445566); - return m_kartInternalName; + return m_kart_internal_name; } // getKartInternalName // ------------------------------------------------------------------------- @@ -748,3 +745,15 @@ EventPropagation PlayerKartWidget::onSpinnerConfirmed() return EVENT_BLOCK; } // onSpinnerConfirmed +// ------------------------------------------------------------------------- +void PlayerKartWidget::enableHandicapForNetwork() +{ + m_difficulty = PLAYER_DIFFICULTY_HANDICAP; + m_model_view->setBadge(ANCHOR_BADGE); + m_kart_stats->setValues( + kart_properties_manager->getKart(m_kart_internal_name), + PLAYER_DIFFICULTY_HANDICAP); + core::stringw label = _("%s (handicapped)", + m_player_ident_spinner->getCustomText()); + m_player_ident_spinner->setCustomText(label); +} // enableHandicapForNetwork diff --git a/src/guiengine/widgets/player_kart_widget.hpp b/src/guiengine/widgets/player_kart_widget.hpp index 073354da4..89c17fa6c 100644 --- a/src/guiengine/widgets/player_kart_widget.hpp +++ b/src/guiengine/widgets/player_kart_widget.hpp @@ -27,7 +27,6 @@ class KartSelectionScreen; -class NetworkPlayerProfile; namespace GUIEngine { @@ -44,7 +43,7 @@ namespace GUIEngine /** Whether this player confirmed their selection */ bool m_ready; /** If the player is handicapped. */ - bool m_handicapped; + PerPlayerDifficulty m_difficulty; /** widget coordinates */ int player_name_x, player_name_y, player_name_w, player_name_h; @@ -65,9 +64,6 @@ namespace GUIEngine StateManager::ActivePlayer* m_associated_player; int m_player_id; - /** Network info about the user. */ - NetworkPlayerProfile* m_associated_user; - /** Internal name of the spinner; useful to interpret spinner events, * which contain the name of the activated object */ std::string spinnerID; @@ -91,13 +87,12 @@ namespace GUIEngine irr::gui::IGUIStaticText* m_ready_text; core::stringw deviceName; - std::string m_kartInternalName; + std::string m_kart_internal_name; bool m_not_updated_yet; PlayerKartWidget(KartSelectionScreen* parent, StateManager::ActivePlayer* associated_player, - NetworkPlayerProfile* associated_user, core::recti area, const int player_id, std::string kart_group, const int irrlicht_idget_id=-1); @@ -137,8 +132,8 @@ namespace GUIEngine bool isReady(); // ------------------------------------------------------------------------ - /** \return Whether this player is handicapped or not */ - bool isHandicapped(); + /** \return Per player difficulty */ + PerPlayerDifficulty getDifficulty(); // ------------------------------------------------------------------------- /** Updates the animation (moving/shrinking/etc.) */ @@ -169,6 +164,8 @@ namespace GUIEngine /** \brief Event callback from ISpinnerConfirmListener */ virtual GUIEngine::EventPropagation onSpinnerConfirmed(); + // ------------------------------------------------------------------------- + void enableHandicapForNetwork(); }; // PlayerKartWidget } diff --git a/src/guiengine/widgets/spinner_widget.cpp b/src/guiengine/widgets/spinner_widget.cpp index 07f124516..4a1c7eefb 100644 --- a/src/guiengine/widgets/spinner_widget.cpp +++ b/src/guiengine/widgets/spinner_widget.cpp @@ -328,7 +328,7 @@ void SpinnerWidget::addLabel(stringw label) void SpinnerWidget::setValue(const int new_value) { m_value = new_value; - m_customText = ""; + m_custom_text = ""; if (m_graphical) { @@ -402,30 +402,30 @@ void SpinnerWidget::setActive(bool active) if (active) { setText(L""); - if (m_customText.empty()) + if (m_custom_text.empty()) { setValue(getValue()); // Update the display } else { - setCustomText(m_customText); + setCustomText(m_custom_text); } } else { // Save it temporary because setValue(which is uses for update in // this case) overwrites it - core::stringw customText = m_customText; + core::stringw custom_text = m_custom_text; setText(L"-"); setValue(getValue()); // Update the display - m_customText = customText; + m_custom_text = custom_text; } } // setActive // ----------------------------------------------------------------------------- void SpinnerWidget::setCustomText(const core::stringw& text) { - m_customText = text; + m_custom_text = text; if (m_children.size() > 0) { m_children[1].m_element->setText(text.c_str()); diff --git a/src/guiengine/widgets/spinner_widget.hpp b/src/guiengine/widgets/spinner_widget.hpp index d295fde8f..139089921 100644 --- a/src/guiengine/widgets/spinner_widget.hpp +++ b/src/guiengine/widgets/spinner_widget.hpp @@ -79,7 +79,7 @@ namespace GUIEngine /** \brief Keeps track of the custom text in spinner (a text which isn't related to a value) * to remember it and set it back (example : when we deactivate the widget) */ - core::stringw m_customText; + core::stringw m_custom_text; /** \brief implementing method from base class Widget */ virtual EventPropagation transmitEvent(Widget* w, @@ -197,6 +197,7 @@ namespace GUIEngine /** Display custom text in spinner */ void setCustomText(const core::stringw& text); + const core::stringw& getCustomText() const { return m_custom_text; } }; } diff --git a/src/karts/abstract_kart.cpp b/src/karts/abstract_kart.cpp index 8290a5d1a..dfa595b15 100644 --- a/src/karts/abstract_kart.cpp +++ b/src/karts/abstract_kart.cpp @@ -48,7 +48,7 @@ AbstractKart::AbstractKart(const std::string& ident, ident.c_str()); kp = kart_properties_manager->getKart(std::string("tux")); } - m_kart_properties->copyForPlayer(kp); + m_kart_properties->copyForPlayer(kp, difficulty); m_difficulty = difficulty; m_kart_animation = NULL; assert(m_kart_properties); diff --git a/src/karts/controller/local_player_controller.cpp b/src/karts/controller/local_player_controller.cpp index 058b48cdc..3107b2857 100644 --- a/src/karts/controller/local_player_controller.cpp +++ b/src/karts/controller/local_player_controller.cpp @@ -55,19 +55,19 @@ * \param init_pos The start coordinates and heading of the kart. */ LocalPlayerController::LocalPlayerController(AbstractKart *kart, - const int local_playerID) + const int local_player_id, + PerPlayerDifficulty d) : PlayerController(kart), m_sky_particles_emitter(NULL) { - - m_player = StateManager::get()->getActivePlayer(local_playerID); + m_difficulty = d; + m_player = StateManager::get()->getActivePlayer(local_player_id); if(m_player) m_player->setKart(kart); // Keep a pointer to the camera to remove the need to search for // the right camera once per frame later. - - Camera *camera = Camera::createCamera(kart, local_playerID); - + Camera *camera = Camera::createCamera(kart, local_player_id); + m_camera_index = camera->getIndex(); m_wee_sound = SFXManager::get()->createSoundSource("wee"); m_bzzt_sound = SFXManager::get()->getBuffer("bzzt"); @@ -390,5 +390,10 @@ core::stringw LocalPlayerController::getName() const { if (NetworkConfig::get()->isNetworking()) return PlayerController::getName(); - return m_player->getProfile()->getName(); + + core::stringw name = m_player->getProfile()->getName(); + if (m_difficulty == PLAYER_DIFFICULTY_HANDICAP) + name = _("%s (handicapped)", name); + + return name; } // getName diff --git a/src/karts/controller/local_player_controller.hpp b/src/karts/controller/local_player_controller.hpp index 09d68fae3..2e64e8c1d 100644 --- a/src/karts/controller/local_player_controller.hpp +++ b/src/karts/controller/local_player_controller.hpp @@ -48,6 +48,8 @@ private: * camera object is managed in the Camera class, so no need to free it. */ int m_camera_index; + PerPlayerDifficulty m_difficulty; + SFXBase *m_wee_sound; SFXBuffer *m_bzzt_sound; SFXBuffer *m_ugh_sound; @@ -58,7 +60,8 @@ private: virtual void displayPenaltyWarning() OVERRIDE; public: LocalPlayerController(AbstractKart *kart, - const int local_playerID); + const int local_player_id, + PerPlayerDifficulty d); ~LocalPlayerController(); void update (int ticks) OVERRIDE; bool action (PlayerAction action, int value, diff --git a/src/karts/controller/player_controller.cpp b/src/karts/controller/player_controller.cpp index 9c7f9de23..3ccdacd0c 100644 --- a/src/karts/controller/player_controller.cpp +++ b/src/karts/controller/player_controller.cpp @@ -393,12 +393,17 @@ void PlayerController::rewindTo(BareNetworkString *buffer) // ---------------------------------------------------------------------------- core::stringw PlayerController::getName() const { + core::stringw name = m_kart->getName(); if (NetworkConfig::get()->isNetworking()) { auto& players = LobbyProtocol::get()->getGameSetup() ->getPlayers(); if (auto player = players.at(m_kart->getWorldKartId()).lock()) - return player->getName(); + { + name = player->getName(); + if (player->getPerPlayerDifficulty() == PLAYER_DIFFICULTY_HANDICAP) + name = _("%s (handicapped)", name); + } } - return m_kart->getName(); + return name; } // getName diff --git a/src/karts/kart_properties.cpp b/src/karts/kart_properties.cpp index 6c6b4bfc4..9281504bc 100644 --- a/src/karts/kart_properties.cpp +++ b/src/karts/kart_properties.cpp @@ -132,7 +132,8 @@ KartProperties::~KartProperties() * \param source The source kart properties from which to copy this objects' * values. */ -void KartProperties::copyForPlayer(const KartProperties *source) +void KartProperties::copyForPlayer(const KartProperties *source, + PerPlayerDifficulty d) { *this = *source; @@ -146,7 +147,7 @@ void KartProperties::copyForPlayer(const KartProperties *source) // Combine the characteristics for this object. We can't copy it because // this object has other pointers (to m_characteristic). - combineCharacteristics(); + combineCharacteristics(d); } } // copyForPlayer @@ -224,7 +225,7 @@ void KartProperties::load(const std::string &filename, const std::string &node) } getAllData(root); m_characteristic = std::make_shared(root); - combineCharacteristics(); + combineCharacteristics(PLAYER_DIFFICULTY_NORMAL); } catch(std::exception& err) { @@ -343,7 +344,7 @@ void KartProperties::setHatMeshName(const std::string &hat_name) } // setHatMeshName //----------------------------------------------------------------------------- -void KartProperties::combineCharacteristics() +void KartProperties::combineCharacteristics(PerPlayerDifficulty difficulty) { m_combined_characteristic = std::make_shared(); m_combined_characteristic->addCharacteristic(kart_properties_manager-> @@ -362,6 +363,9 @@ void KartProperties::combineCharacteristics() // Kart type found m_combined_characteristic->addCharacteristic(characteristic); + m_combined_characteristic->addCharacteristic(kart_properties_manager-> + getPlayerCharacteristic(getPerPlayerDifficultyAsString(difficulty))); + m_combined_characteristic->addCharacteristic(m_characteristic.get()); m_cached_characteristic = std::make_shared (m_combined_characteristic.get()); diff --git a/src/karts/kart_properties.hpp b/src/karts/kart_properties.hpp index 8253a28d8..74b2e78f1 100644 --- a/src/karts/kart_properties.hpp +++ b/src/karts/kart_properties.hpp @@ -207,7 +207,7 @@ private: void load (const std::string &filename, const std::string &node); - void combineCharacteristics(); + void combineCharacteristics(PerPlayerDifficulty d); public: /** Returns the string representation of a per-player difficulty. */ @@ -215,7 +215,8 @@ public: KartProperties (const std::string &filename=""); ~KartProperties (); - void copyForPlayer (const KartProperties *source); + void copyForPlayer (const KartProperties *source, + PerPlayerDifficulty d = PLAYER_DIFFICULTY_NORMAL); void copyFrom (const KartProperties *source); void getAllData (const XMLNode * root); void checkAllSet (const std::string &filename); diff --git a/src/main.cpp b/src/main.cpp index cd7fe6a45..6900e0647 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1094,7 +1094,7 @@ int handleCmdLine() !server_password.empty()); NetworkConfig::get()->addNetworkPlayer( input_manager->getDeviceManager()->getLatestUsedDevice(), - PlayerManager::getCurrentPlayer(), false/*handicap*/); + PlayerManager::getCurrentPlayer(), PLAYER_DIFFICULTY_NORMAL); NetworkConfig::get()->doneAddingNetworkPlayers(); STKHost::create(); auto cts = std::make_shared(server); diff --git a/src/modes/soccer_world.cpp b/src/modes/soccer_world.cpp index 53537153b..dac5e9914 100644 --- a/src/modes/soccer_world.cpp +++ b/src/modes/soccer_world.cpp @@ -421,8 +421,8 @@ AbstractKart *SoccerWorld::createKart(const std::string &kart_ident, int index, switch(kart_type) { case RaceManager::KT_PLAYER: - controller = new LocalPlayerController(new_kart, - local_player_id); + controller = new LocalPlayerController(new_kart, local_player_id, + difficulty); m_num_players ++; break; case RaceManager::KT_NETWORK_PLAYER: diff --git a/src/modes/world.cpp b/src/modes/world.cpp index 8f0e34ced..45bd45901 100644 --- a/src/modes/world.cpp +++ b/src/modes/world.cpp @@ -367,7 +367,8 @@ AbstractKart *World::createKart(const std::string &kart_ident, int index, { case RaceManager::KT_PLAYER: { - controller = new LocalPlayerController(new_kart, local_player_id); + controller = new LocalPlayerController(new_kart, local_player_id, + difficulty); const PlayerProfile* p = StateManager::get() ->getActivePlayer(local_player_id)->getConstProfile(); if (p && p->getDefaultKartColor() > 0.0f) diff --git a/src/network/network_config.hpp b/src/network/network_config.hpp index 6e3b7a8a7..0ad78fe5f 100644 --- a/src/network/network_config.hpp +++ b/src/network/network_config.hpp @@ -102,7 +102,7 @@ private: std::string m_server_id_file; std::vector > m_network_players; + PerPlayerDifficulty> > m_network_players; core::stringw m_motd; @@ -190,7 +190,8 @@ public: m_password = ""; } // ------------------------------------------------------------------------ - const std::vector >& + const std::vector >& getNetworkPlayers() const { return m_network_players; } // ------------------------------------------------------------------------ bool isAddingNetworkPlayers() const @@ -198,7 +199,8 @@ public: // ------------------------------------------------------------------------ void doneAddingNetworkPlayers() { m_done_adding_network_players = true; } // ------------------------------------------------------------------------ - bool addNetworkPlayer(InputDevice* device, PlayerProfile* profile, bool h) + bool addNetworkPlayer(InputDevice* device, PlayerProfile* profile, + PerPlayerDifficulty d) { for (auto& p : m_network_players) { @@ -207,7 +209,7 @@ public: if (std::get<1>(p) == profile) return false; } - m_network_players.emplace_back(device, profile, h); + m_network_players.emplace_back(device, profile, d); return true; } // ------------------------------------------------------------------------ diff --git a/src/network/protocols/client_lobby.cpp b/src/network/protocols/client_lobby.cpp index 1eaf927f6..1deb9c774 100644 --- a/src/network/protocols/client_lobby.cpp +++ b/src/network/protocols/client_lobby.cpp @@ -513,6 +513,9 @@ void ClientLobby::updatePlayerList(Event* event) // icon to be used, see NetworkingLobby::loadedFromFile std::get<3>(pl) = data.getUInt8() == 1 /*if server owner*/ ? 0 : std::get<1>(pl) != 0 /*if online account*/ ? 1 : 2; + PerPlayerDifficulty d = (PerPlayerDifficulty)data.getUInt8(); + if (d == PLAYER_DIFFICULTY_HANDICAP) + std::get<2>(pl) = _("%s (handicapped)", std::get<2>(pl)); players.push_back(pl); } NetworkingLobby::getInstance()->updatePlayers(players); diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index b48b39912..86fa932dd 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -1045,6 +1045,7 @@ void ServerLobby::updatePlayerList() if (m_server_owner.lock() == profile->getPeer()) server_owner = 1; pl->addUInt8(server_owner); + pl->addUInt8(profile->getPerPlayerDifficulty()); } sendMessageToPeersChangingToken(pl); delete pl; diff --git a/src/states_screens/dialogs/splitscreen_player_dialog.cpp b/src/states_screens/dialogs/splitscreen_player_dialog.cpp index 9bab07929..f52e95367 100644 --- a/src/states_screens/dialogs/splitscreen_player_dialog.cpp +++ b/src/states_screens/dialogs/splitscreen_player_dialog.cpp @@ -111,11 +111,14 @@ GUIEngine::EventPropagation const unsigned pid = m_profiles->getValue(); assert(pid < PlayerManager::get()->getNumPlayers()); PlayerProfile* p = m_available_players[pid]; - const bool handicap = m_handicap->getState(); - if (NetworkConfig::get()->addNetworkPlayer(m_device, p, handicap)) + const PerPlayerDifficulty d = m_handicap->getState() ? + PLAYER_DIFFICULTY_HANDICAP : PLAYER_DIFFICULTY_NORMAL; + if (NetworkConfig::get()->addNetworkPlayer(m_device, p, d)) { - NetworkingLobby::getInstance() - ->addSplitscreenPlayer(p->getName()); + core::stringw name = p->getName(); + if (d == PLAYER_DIFFICULTY_HANDICAP) + name = _("%s (handicapped)", name); + NetworkingLobby::getInstance()->addSplitscreenPlayer(name); m_self_destroy = true; return GUIEngine::EVENT_BLOCK; } diff --git a/src/states_screens/kart_selection.cpp b/src/states_screens/kart_selection.cpp index a154ac152..291db0f25 100644 --- a/src/states_screens/kart_selection.cpp +++ b/src/states_screens/kart_selection.cpp @@ -189,14 +189,14 @@ void KartHoverListener::onSelectionChanged(DynamicRibbonWidget* theWidget, { // discard events sent when putting back to the right kart if (selectionID == - m_parent->m_kart_widgets[player_id].m_kartInternalName) return; + m_parent->m_kart_widgets[player_id].m_kart_internal_name) return; DynamicRibbonWidget* w = m_parent->getWidget("karts"); assert(w != NULL); w->setSelection(m_parent->m_kart_widgets[player_id] - .m_kartInternalName, player_id, true); + .m_kart_internal_name, player_id, true); return; } @@ -516,7 +516,7 @@ bool KartSelectionScreen::joinPlayer(InputDevice* device, PlayerProfile* p) // ---- Create player/kart widget PlayerKartWidget* newPlayerWidget = - new PlayerKartWidget(this, aplayer, NULL, kartsArea, m_kart_widgets.size(), + new PlayerKartWidget(this, aplayer, kartsArea, m_kart_widgets.size(), selected_kart_group); manualAddWidget(newPlayerWidget); @@ -832,7 +832,7 @@ void KartSelectionScreen::updateKartStats(uint8_t widget_id, if (kp != NULL) { - w->setValues(kp); + w->setValues(kp, m_kart_widgets[widget_id].getDifficulty()); w->update(0); } } @@ -1206,7 +1206,7 @@ void KartSelectionScreen::allPlayersDone() const int kart_count = m_kart_widgets.size(); for (int n = 0; n < kart_count; n++) { - std::string selected_kart = m_kart_widgets[n].m_kartInternalName; + std::string selected_kart = m_kart_widgets[n].m_kart_internal_name; if (selected_kart == RANDOM_KART_ID) { @@ -1238,7 +1238,7 @@ void KartSelectionScreen::allPlayersDone() for (int i=0; isetPlayerKart(n, selected_kart); // Set per player difficulty if needed - if (m_multiplayer && UserConfigParams::m_per_player_difficulty && - m_kart_widgets[n].isHandicapped()) - race_manager->setPlayerDifficulty(n, PLAYER_DIFFICULTY_HANDICAP); + if (m_multiplayer && UserConfigParams::m_per_player_difficulty) + race_manager->setPlayerDifficulty(n, m_kart_widgets[n].getDifficulty()); } // ---- Switch to assign mode diff --git a/src/states_screens/network_kart_selection.cpp b/src/states_screens/network_kart_selection.cpp index b8ba39855..a517e5075 100644 --- a/src/states_screens/network_kart_selection.cpp +++ b/src/states_screens/network_kart_selection.cpp @@ -43,6 +43,11 @@ void NetworkKartSelectionScreen::init() for (auto& p : NetworkConfig::get()->getNetworkPlayers()) { joinPlayer(std::get<0>(p), std::get<1>(p)); + if (std::get<2>(p) == PLAYER_DIFFICULTY_HANDICAP) + { + m_kart_widgets.get(m_kart_widgets.size() -1) + ->enableHandicapForNetwork(); + } w->updateItemDisplay(); if (!w->setSelection(UserConfigParams::m_default_kart, 0, true)) { @@ -80,7 +85,7 @@ void NetworkKartSelectionScreen::allPlayersDone() { // If server recieve an invalid name, it will auto correct to a random // kart - kart.encodeString(m_kart_widgets[n].m_kartInternalName); + kart.encodeString(m_kart_widgets[n].m_kart_internal_name); } STKHost::get()->sendToServer(&kart, true); diff --git a/src/states_screens/online_screen.cpp b/src/states_screens/online_screen.cpp index ca9e49274..7f33c44b0 100644 --- a/src/states_screens/online_screen.cpp +++ b/src/states_screens/online_screen.cpp @@ -111,7 +111,7 @@ void OnlineScreen::init() { NetworkConfig::get()->addNetworkPlayer( input_manager->getDeviceManager()->getLatestUsedDevice(), - PlayerManager::getCurrentPlayer(), false/*handicap*/); + PlayerManager::getCurrentPlayer(), PLAYER_DIFFICULTY_NORMAL); NetworkConfig::get()->doneAddingNetworkPlayers(); } } // init @@ -178,7 +178,7 @@ void OnlineScreen::eventCallback(Widget* widget, const std::string& name, NetworkConfig::get()->cleanNetworkPlayers(); NetworkConfig::get()->addNetworkPlayer( input_manager->getDeviceManager()->getLatestUsedDevice(), - PlayerManager::getCurrentPlayer(), false/*handicap*/); + PlayerManager::getCurrentPlayer(), PLAYER_DIFFICULTY_NORMAL); NetworkConfig::get()->doneAddingNetworkPlayers(); } else From eb9f5b3fb39eb767487fcf5e543c30d351346913 Mon Sep 17 00:00:00 2001 From: Benau Date: Tue, 1 May 2018 01:13:09 +0800 Subject: [PATCH 013/137] Add speed and acceleration handicap to make it more visible --- data/kart_characteristics.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/kart_characteristics.xml b/data/kart_characteristics.xml index 17d8969f9..146d2c593 100644 --- a/data/kart_characteristics.xml +++ b/data/kart_characteristics.xml @@ -403,7 +403,7 @@ - + From ac54ee8888c46d9fc800b6c02c137150c747385e Mon Sep 17 00:00:00 2001 From: Benau Date: Tue, 1 May 2018 12:42:59 +0800 Subject: [PATCH 014/137] Remove max 80 words limit now we use uint16_t for wide char --- src/network/network_string.hpp | 22 ++++++++++++++++++++++ src/network/protocols/client_lobby.cpp | 9 ++++----- src/network/protocols/server_lobby.cpp | 4 ++-- src/states_screens/networking_lobby.cpp | 3 +-- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/network/network_string.hpp b/src/network/network_string.hpp index c21e58dbe..b819c8b23 100644 --- a/src/network/network_string.hpp +++ b/src/network/network_string.hpp @@ -141,6 +141,28 @@ public: /** Allows to read a buffer from the beginning again. */ void reset() { m_current_offset = 0; } // ------------------------------------------------------------------------ + BareNetworkString& encodeString16(const irr::core::stringw& value) + { + uint8_t str_len = (uint8_t)value.size(); + if (value.size() > 255) + str_len = 255; + addUInt8(str_len); + for (unsigned i = 0; i < str_len; i++) + addUInt16((uint16_t)value[i]); + return *this; + } + // ------------------------------------------------------------------------ + int decodeString16(irr::core::stringw* out) const + { + unsigned str_len = getUInt8(); + for (unsigned i = 0; i < str_len; i++) + { + uint16_t c = getUInt16(); + out->append((wchar_t)c); + } + return str_len + 1; + } + // ------------------------------------------------------------------------ BareNetworkString& encodeString(const std::string &value); BareNetworkString& encodeString(const irr::core::stringw &value); int decodeString(std::string *out) const; diff --git a/src/network/protocols/client_lobby.cpp b/src/network/protocols/client_lobby.cpp index 1deb9c774..a6f66c578 100644 --- a/src/network/protocols/client_lobby.cpp +++ b/src/network/protocols/client_lobby.cpp @@ -544,13 +544,12 @@ void ClientLobby::handleChat(Event* event) if (!UserConfigParams::m_lobby_chat) return; SFXManager::get()->quickSound("plopp"); - std::string message; - event->data().decodeString(&message); - Log::info("ClientLobby", "%s", message.c_str()); + core::stringw message; + event->data().decodeString16(&message); + Log::info("ClientLobby", "%s", StringUtils::wideToUtf8(message).c_str()); if (message.size() > 0) { - NetworkingLobby::getInstance()->addMoreServerInfo( - StringUtils::utf8ToWide(message)); + NetworkingLobby::getInstance()->addMoreServerInfo(message); } } // handleChat diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 86fa932dd..285eea670 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -169,12 +169,12 @@ void ServerLobby::handleChat(Event* event) return; } core::stringw message; - event->data().decodeStringW(&message); + event->data().decodeString16(&message); if (message.size() > 0) { NetworkString* chat = getNetworkString(); chat->setSynchronous(true); - chat->addUInt8(LE_CHAT).encodeString(message); + chat->addUInt8(LE_CHAT).encodeString16(message); sendMessageToPeersChangingToken(chat, /*reliable*/true); delete chat; } diff --git a/src/states_screens/networking_lobby.cpp b/src/states_screens/networking_lobby.cpp index fc40c4ed0..9ac5e33b9 100644 --- a/src/states_screens/networking_lobby.cpp +++ b/src/states_screens/networking_lobby.cpp @@ -258,8 +258,7 @@ void NetworkingLobby::sendChat(irr::core::stringw text) name = PlayerManager::getCurrentOnlineUserName(); else name = player->getName(); - // Max 80 words - chat.encodeString((name + L": " + text).subString(0, 80)); + chat.encodeString16(name + L": " + text); STKHost::get()->sendToServer(&chat, true); } From 022dd3e30216d509465c6d054fc3e2f3be4a00f3 Mon Sep 17 00:00:00 2001 From: Alayan-stk-2 Date: Wed, 2 May 2018 03:14:53 +0200 Subject: [PATCH 015/137] Fix handicap values (#3231) * Fix handicap values for slipstreaming * Change bugged handicap value and double-effect values * Add skidding to handicap, remove explosions --- data/kart_characteristics.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data/kart_characteristics.xml b/data/kart_characteristics.xml index 146d2c593..f8ff73651 100644 --- a/data/kart_characteristics.xml +++ b/data/kart_characteristics.xml @@ -404,16 +404,16 @@ - - - + + + + - - - + + From f5035a00ea878c4f77c411a66815dcd408cb4964 Mon Sep 17 00:00:00 2001 From: Benau Date: Wed, 2 May 2018 13:17:23 +0800 Subject: [PATCH 016/137] Allow showing paused dialog in network now it doesn't pause timer --- src/modes/world.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/modes/world.cpp b/src/modes/world.cpp index 45bd45901..267cf5749 100644 --- a/src/modes/world.cpp +++ b/src/modes/world.cpp @@ -1315,8 +1315,7 @@ void World::unpause() //----------------------------------------------------------------------------- void World::escapePressed() { - if (!(NetworkConfig::get()->isNetworking() && - getPhase() < MUSIC_PHASE)) + if (NetworkConfig::get()->isNetworking() || getPhase() >= MUSIC_PHASE) new RacePausedDialog(0.8f, 0.6f); } // escapePressed From a5b234ceefe4afa5e4d98af0c9e55c67808673b9 Mon Sep 17 00:00:00 2001 From: Benau Date: Wed, 2 May 2018 15:37:47 +0800 Subject: [PATCH 017/137] Reset player action in pause menu for network, fix #3230 --- src/states_screens/dialogs/race_paused_dialog.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/states_screens/dialogs/race_paused_dialog.cpp b/src/states_screens/dialogs/race_paused_dialog.cpp index b5fd49f58..70241696a 100644 --- a/src/states_screens/dialogs/race_paused_dialog.cpp +++ b/src/states_screens/dialogs/race_paused_dialog.cpp @@ -25,6 +25,8 @@ #include "guiengine/scalable_font.hpp" #include "guiengine/widgets/icon_button_widget.hpp" #include "guiengine/widgets/ribbon_widget.hpp" +#include "karts/controller/controller.hpp" +#include "karts/kart.hpp" #include "input/input_manager.hpp" #include "io/file_manager.hpp" #include "modes/overworld.hpp" @@ -66,6 +68,18 @@ RacePausedDialog::RacePausedDialog(const float percentWidth, { music_manager->pauseMusic(); SFXManager::get()->pauseAll(); + for (unsigned i = 0; i < World::getWorld()->getNumKarts(); i++) + { + for (unsigned j = 0; j < PA_PAUSE_RACE; j++) + { + if (World::getWorld()->getKart(i)->isEliminated() || + !World::getWorld()->getKart(i)->getController() + ->isLocalPlayerController()) + break; + World::getWorld()->getKart(i)->getController() + ->action((PlayerAction)j, 0); + } + } } else { From 3d17e68c4ec2e5a9310ee98279180a646e06f3d4 Mon Sep 17 00:00:00 2001 From: Alayan-stk-2 Date: Wed, 2 May 2018 19:13:09 +0200 Subject: [PATCH 018/137] Get a larger minimap texture to significantly reduce aliasing --- src/tracks/track.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tracks/track.cpp b/src/tracks/track.cpp index c33288a6f..3ece93d52 100644 --- a/src/tracks/track.cpp +++ b/src/tracks/track.cpp @@ -1118,10 +1118,9 @@ void Track::loadMinimap() //Create the minimap resizing it as necessary. m_mini_map_size = World::getWorld()->getRaceGUI()->getMiniMapSize(); - core::dimension2du size = m_mini_map_size - .getOptimalSize(!nonpower,!nonsquare); - m_render_target = Graph::get()->makeMiniMap(size, "minimap::" + m_ident, video::SColor(127, 255, 255, 255)); + //Use twice the size of the rendered minimap to reduce significantly aliasing + m_render_target = Graph::get()->makeMiniMap(m_mini_map_size*2, "minimap::" + m_ident, video::SColor(127, 255, 255, 255)); if (!m_render_target) return; core::dimension2du mini_map_texture_size = m_render_target->getTextureSize(); From bed03b2a43b659727ad37f17356f28fccfdfe0bd Mon Sep 17 00:00:00 2001 From: Alayan-stk-2 Date: Wed, 2 May 2018 19:40:00 +0200 Subject: [PATCH 019/137] Make minimap size 1.8 higher This factor has been chosen because : 1) It allows the toplist to be able to still display 10 karts (maximum number reached in story mode or in networking (currently 12 but will be 8-10 for release according to Benau)) 2) This size should fit nicely in the android GUI. Otherwise, there is still some margin before it hinders view in 4:3 resolutions. --- src/states_screens/race_gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/states_screens/race_gui.cpp b/src/states_screens/race_gui.cpp index dfdf57005..2de8b5e5c 100644 --- a/src/states_screens/race_gui.cpp +++ b/src/states_screens/race_gui.cpp @@ -84,7 +84,7 @@ RaceGUI::RaceGUI() // Originally m_map_height was 100, and we take 480 as minimum res float scaling = irr_driver->getFrameSize().Height / 480.0f; - const float map_size = 100.0f; + const float map_size = 180.0f; const float top_margin = 3.5f * m_font_height; if (UserConfigParams::m_multitouch_enabled && From d6c2cccccdee4255be42250a89c6046fd23df498 Mon Sep 17 00:00:00 2001 From: Alayan-stk-2 Date: Wed, 2 May 2018 20:07:10 +0200 Subject: [PATCH 020/137] Reduce minimap size in splitscreen multiplayer It won't work very well with 8 players, but still slightly better than when it was always 100. --- src/states_screens/race_gui.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/states_screens/race_gui.cpp b/src/states_screens/race_gui.cpp index 2de8b5e5c..618477189 100644 --- a/src/states_screens/race_gui.cpp +++ b/src/states_screens/race_gui.cpp @@ -82,9 +82,26 @@ RaceGUI::RaceGUI() else m_lap_width = font->getDimension(L"9/9").Width; + float map_size_splitscreen = 1.0f; + + // If there are four players or more in splitscreen + // and the map is in a player view, scale down the map + if (race_manager->getNumLocalPlayers() >= 4 && !race_manager->getIfEmptyScreenSpaceExists()) + { + if (irr_driver->getFrameSize().Width / irr_driver->getFrameSize().Height > (4.1f/3.0f)) + { + if (race_manager->getNumLocalPlayers() == 4) + map_size_splitscreen = 0.75f; + else + map_size_splitscreen = 0.5f; + } + else + map_size_splitscreen = 0.5f; + } + // Originally m_map_height was 100, and we take 480 as minimum res float scaling = irr_driver->getFrameSize().Height / 480.0f; - const float map_size = 180.0f; + const float map_size = 180.0f * map_size_splitscreen; const float top_margin = 3.5f * m_font_height; if (UserConfigParams::m_multitouch_enabled && From 74a77ed5effd93d60e58b9111b10c6ddce240a09 Mon Sep 17 00:00:00 2001 From: Alayan-stk-2 Date: Wed, 2 May 2018 20:33:08 +0200 Subject: [PATCH 021/137] Cast width and height as floats before checking the ratio --- src/states_screens/race_gui.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/states_screens/race_gui.cpp b/src/states_screens/race_gui.cpp index 618477189..bbd647029 100644 --- a/src/states_screens/race_gui.cpp +++ b/src/states_screens/race_gui.cpp @@ -88,7 +88,8 @@ RaceGUI::RaceGUI() // and the map is in a player view, scale down the map if (race_manager->getNumLocalPlayers() >= 4 && !race_manager->getIfEmptyScreenSpaceExists()) { - if (irr_driver->getFrameSize().Width / irr_driver->getFrameSize().Height > (4.1f/3.0f)) + if ( ((float) irr_driver->getFrameSize().Width / (float) irr_driver->getFrameSize().Height) > + (4.1f/3.0f)) { if (race_manager->getNumLocalPlayers() == 4) map_size_splitscreen = 0.75f; From e1a1196a9f8f7ea8b1ba7101581f7077bafeddaf Mon Sep 17 00:00:00 2001 From: "auria.mg" Date: Wed, 2 May 2018 19:35:04 -0400 Subject: [PATCH 022/137] jgh --- src/states_screens/main_menu_screen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/states_screens/main_menu_screen.cpp b/src/states_screens/main_menu_screen.cpp index 0f41e82b2..296af28de 100644 --- a/src/states_screens/main_menu_screen.cpp +++ b/src/states_screens/main_menu_screen.cpp @@ -15,7 +15,7 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -#define DEBUG_MENU_ITEM 0 +#define DEBUG_MENU_ITEM 1 #include "states_screens/main_menu_screen.hpp" From c6c005ded49a9a69af3d716912eed4a4383b3782 Mon Sep 17 00:00:00 2001 From: "auria.mg" Date: Wed, 2 May 2018 19:35:41 -0400 Subject: [PATCH 023/137] Revert "jgh" This reverts commit e1a1196a9f8f7ea8b1ba7101581f7077bafeddaf. --- src/states_screens/main_menu_screen.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/states_screens/main_menu_screen.cpp b/src/states_screens/main_menu_screen.cpp index 296af28de..0f41e82b2 100644 --- a/src/states_screens/main_menu_screen.cpp +++ b/src/states_screens/main_menu_screen.cpp @@ -15,7 +15,7 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -#define DEBUG_MENU_ITEM 1 +#define DEBUG_MENU_ITEM 0 #include "states_screens/main_menu_screen.hpp" From ded672ea0da10cd4d48aa625d699917eb4d45e94 Mon Sep 17 00:00:00 2001 From: Qwerty Chouskie Date: Wed, 2 May 2018 18:39:47 -0700 Subject: [PATCH 024/137] Remove no-longer-needed code It doesn't compile with this anymore, and the issue it fixed seems gone anyways. --- src/karts/skidding.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/karts/skidding.cpp b/src/karts/skidding.cpp index b48688f15..57655d567 100644 --- a/src/karts/skidding.cpp +++ b/src/karts/skidding.cpp @@ -368,9 +368,6 @@ void Skidding::update(int ticks, bool is_on_ground, // Some karts might use a graphical-only jump. Set it up: m_remaining_jump_time = kp->getSkidGraphicalJumpTime(); - // Don't "play-sound-than-pause-sound-when-jumping-then-play-sound-when-jump-done" - m_gfx_jump_offset = 0.001f; - #ifdef SKID_DEBUG #define SPEED 20.0f m_real_steering = updateSteering(steering, dt); From 173702cca1a30688976ced7cac21b01a2e73357f Mon Sep 17 00:00:00 2001 From: Qwerty Chouskie Date: Wed, 2 May 2018 22:23:16 -0700 Subject: [PATCH 025/137] "skid0" effect --- data/gfx/skid0.xml | 29 +++++++++++++++++++++++++++++ src/karts/kart_gfx.cpp | 28 ++++++++++++++++++++-------- src/karts/kart_gfx.hpp | 7 ++++++- src/karts/skidding.cpp | 6 +++++- 4 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 data/gfx/skid0.xml diff --git a/data/gfx/skid0.xml b/data/gfx/skid0.xml new file mode 100644 index 000000000..3f952ef98 --- /dev/null +++ b/data/gfx/skid0.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/karts/kart_gfx.cpp b/src/karts/kart_gfx.cpp index d344d49e3..0e28abeac 100644 --- a/src/karts/kart_gfx.cpp +++ b/src/karts/kart_gfx.cpp @@ -117,6 +117,8 @@ KartGFX::KartGFX(const AbstractKart *kart, bool is_day) addEffect(KGFX_SKID1R, "skid1.xml", rear_right, true ); addEffect(KGFX_SKID2L, "skid2.xml", rear_left, true ); addEffect(KGFX_SKID2R, "skid2.xml", rear_right, true ); + addEffect(KGFX_SKID0L, "skid0.xml", rear_left, true ); + addEffect(KGFX_SKID0R, "skid0.xml", rear_right, true ); if (!kart->getKartModel()->getExhaustXML().empty()) { const std::string& ex = kart->getKartModel()->getExhaustXML(); @@ -180,9 +182,9 @@ void KartGFX::addEffect(KartGFXType type, const std::string &file_name, kind = ParticleKindManager::get()->getParticles(file_name); //kind = new ParticleKind(file_manager->getGfxFile(file_name)); - // Skid2 is only used to store the emitter type, and a wheeless - // kart has no terrain effects. - if(type==KGFX_SKID2L || type==KGFX_SKID2R || + // Skid0 and Skid2 are only used to store the emitter type, and a + // wheeless kart has no terrain effects. + if(type==KGFX_SKID0L || type==KGFX_SKID0R || type==KGFX_SKID2L || type==KGFX_SKID2R || (type==KGFX_TERRAIN && m_kart->isWheeless()) ) emitter = NULL; else if(type==KGFX_TERRAIN) @@ -206,7 +208,9 @@ void KartGFX::addEffect(KartGFXType type, const std::string &file_name, } assert((int)m_all_emitters.size()==type); m_all_emitters.push_back(emitter); - if(type==KGFX_SKID1L || type==KGFX_SKID1R) + if (type==KGFX_SKID0L || type==KGFX_SKID0R) + m_skid_kind0 = kind; + else if(type==KGFX_SKID1L || type==KGFX_SKID1R) m_skid_kind1 = kind; else if (type==KGFX_SKID2L || type==KGFX_SKID2R) m_skid_kind2 = kind; @@ -234,15 +238,23 @@ void KartGFX::reset() // ---------------------------------------------------------------------------- /** Selects the correct skidding particle type depending on skid bonus level. - * \param level Must be 1 (accumulated enough for level 1 bonus) or 2 - * (accumulated enough for level 2 bonus). + * \param level Must be 0 (no bonus, showing tiny sparks), 1 (accumulated enough + * for level 1 bonus), or 2 (accumulated enough for level 2 bonus). */ void KartGFX::setSkidLevel(const unsigned int level) { - assert(level >= 1); + assert(level >= 0); assert(level <= 2); m_skid_level = level; - const ParticleKind *pk = level==1 ? m_skid_kind1 : m_skid_kind2; + const ParticleKind *pk; + if (level == 0) { + pk = m_skid_kind0; + } else if (level == 1) { + pk = m_skid_kind1; + } else { + pk = m_skid_kind2; + } +// const ParticleKind *pk = level==1 ? m_skid_kind1 : m_skid_kind2; #ifndef SERVER_ONLY if(m_all_emitters[KGFX_SKID1L]) m_all_emitters[KGFX_SKID1L]->setParticleType(pk); diff --git a/src/karts/kart_gfx.hpp b/src/karts/kart_gfx.hpp index 016b35479..95064c0b3 100644 --- a/src/karts/kart_gfx.hpp +++ b/src/karts/kart_gfx.hpp @@ -40,7 +40,7 @@ class KartGFX { public: /** All particle effects supported by this object. - * Nitro, zipper, terrain, and skidding effects. Two different + * Nitro, zipper, terrain, and skidding effects. Three different * skid types are supported, but only one emitter node will be * created. So KGFX_SKID1/2 store the two types, and KGFX_SKID * = KGFX_SKID1 stores the actual emitter node. KGFX_COUNT @@ -57,11 +57,16 @@ public: KGFX_SKID1R = KGFX_SKIDR, KGFX_SKID2L, KGFX_SKID2R, + KGFX_SKID0L, + KGFX_SKID0R, KGFX_EXHAUST1, KGFX_EXHAUST2, KGFX_COUNT}; private: + /** The particle kind for skidding bonus level 0. */ + const ParticleKind *m_skid_kind0; + /** The particle kind for skidding bonus level 1. */ const ParticleKind *m_skid_kind1; diff --git a/src/karts/skidding.cpp b/src/karts/skidding.cpp index 57655d567..221366499 100644 --- a/src/karts/skidding.cpp +++ b/src/karts/skidding.cpp @@ -429,8 +429,12 @@ void Skidding::update(int ticks, bool is_on_ground, float bonus_time, bonus_speed, bonus_force; unsigned int level = getSkidBonus(&bonus_time, &bonus_speed, &bonus_force); + + // Show tiny sparks if bonus not yet reached + if (level == 0 && m_remaining_jump_time <= 0.0f) + m_kart->getKartGFX()->setSkidLevel(level); // If at least level 1 bonus is reached, show appropriate gfx - if(level>0) + else if (level>=1) { m_skid_bonus_ready = true; m_kart->getKartGFX()->setSkidLevel(level); From b5067a935ee7e24238c8df1f92c87906bc8f19b8 Mon Sep 17 00:00:00 2001 From: Qwerty Chouskie Date: Wed, 2 May 2018 22:35:36 -0700 Subject: [PATCH 026/137] Final? --- data/gfx/skid0.xml | 4 ++-- data/gfx/skid1.xml | 2 +- data/gfx/skid2.xml | 4 ++-- src/karts/kart_gfx.cpp | 2 +- src/karts/kart_gfx.hpp | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data/gfx/skid0.xml b/data/gfx/skid0.xml index 3f952ef98..3265dffb8 100644 --- a/data/gfx/skid0.xml +++ b/data/gfx/skid0.xml @@ -23,7 +23,7 @@ - +
diff --git a/data/gfx/skid1.xml b/data/gfx/skid1.xml index 6fb0ccc5f..f0bf251b3 100644 --- a/data/gfx/skid1.xml +++ b/data/gfx/skid1.xml @@ -23,7 +23,7 @@ -
diff --git a/data/gfx/skid2.xml b/data/gfx/skid2.xml index 9c3cf52af..40de5c81c 100644 --- a/data/gfx/skid2.xml +++ b/data/gfx/skid2.xml @@ -23,7 +23,7 @@ - +
diff --git a/src/karts/kart_gfx.cpp b/src/karts/kart_gfx.cpp index 0e28abeac..5b6c59734 100644 --- a/src/karts/kart_gfx.cpp +++ b/src/karts/kart_gfx.cpp @@ -78,7 +78,7 @@ KartGFX::KartGFX(const AbstractKart *kart, bool is_day) m_skidding_light_2 = irr_driver->addLight(core::vector3df(0.0f, 0.1f, -0.5f * length - 0.05f), /* force */0.4f, /*radius*/4.0f, - 1.0f, 0.0f, 0.0f, false, node); + 1.0f, 0.2f, 0.0f, false, node); m_skidding_light_2->setVisible(false); m_skidding_light_2->setName(("skidding emitter 2 (" + m_kart->getIdent() + ")").c_str() ); diff --git a/src/karts/kart_gfx.hpp b/src/karts/kart_gfx.hpp index 95064c0b3..6515d07a5 100644 --- a/src/karts/kart_gfx.hpp +++ b/src/karts/kart_gfx.hpp @@ -42,7 +42,7 @@ public: /** All particle effects supported by this object. * Nitro, zipper, terrain, and skidding effects. Three different * skid types are supported, but only one emitter node will be - * created. So KGFX_SKID1/2 store the two types, and KGFX_SKID + * created. So KGFX_SKID1/2/3 store the two types, and KGFX_SKID * = KGFX_SKID1 stores the actual emitter node. KGFX_COUNT * is the number of entries and must therefore be last. */ enum KartGFXType { KGFX_NITRO1=0, From f44fbbfdfb6e8ac05f571dec2d53d032e9f0822c Mon Sep 17 00:00:00 2001 From: "auria.mg" Date: Fri, 4 May 2018 18:50:47 -0400 Subject: [PATCH 027/137] Fix code style --- src/karts/kart_gfx.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/karts/kart_gfx.cpp b/src/karts/kart_gfx.cpp index 5b6c59734..ac83547b9 100644 --- a/src/karts/kart_gfx.cpp +++ b/src/karts/kart_gfx.cpp @@ -247,11 +247,16 @@ void KartGFX::setSkidLevel(const unsigned int level) assert(level <= 2); m_skid_level = level; const ParticleKind *pk; - if (level == 0) { + if (level == 0) + { pk = m_skid_kind0; - } else if (level == 1) { + } + else if (level == 1) + { pk = m_skid_kind1; - } else { + } + else + { pk = m_skid_kind2; } // const ParticleKind *pk = level==1 ? m_skid_kind1 : m_skid_kind2; From bc569b9e04048c310314588ca5ec7694d6e74e4d Mon Sep 17 00:00:00 2001 From: Alayan-stk-2 Date: Sat, 5 May 2018 21:09:33 +0200 Subject: [PATCH 028/137] Display the minimap during the start countdown So the players (especially those not familiar with the tracks) can take a look at it before starting to race. --- src/states_screens/race_gui.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/states_screens/race_gui.cpp b/src/states_screens/race_gui.cpp index bbd647029..ecee4a0d5 100644 --- a/src/states_screens/race_gui.cpp +++ b/src/states_screens/race_gui.cpp @@ -227,6 +227,9 @@ void RaceGUI::renderGlobal(float dt) if(world->getPhase() == World::GOAL_PHASE) drawGlobalGoal(); + // MiniMap is drawn when the players wait for the start countdown to end + drawGlobalMiniMap(); + // Timer etc. are not displayed unless the game is actually started. if(!world->isRacePhase()) return; if (!m_enabled) return; @@ -245,8 +248,6 @@ void RaceGUI::renderGlobal(float dt) } } - drawGlobalMiniMap(); - if (!m_is_tutorial) drawGlobalPlayerIcons(m_map_height); if(Track::getCurrentTrack()->isSoccer()) drawScores(); #endif From bdf4c62abc341dfa0b05b3f4bef8c3b3711488e8 Mon Sep 17 00:00:00 2001 From: Alayan-stk-2 Date: Sat, 5 May 2018 21:25:31 +0200 Subject: [PATCH 029/137] Add config option for minimap size --- data/stk_config.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/stk_config.xml b/data/stk_config.xml index 03a53df04..ba95d2931 100644 --- a/data/stk_config.xml +++ b/data/stk_config.xml @@ -122,6 +122,10 @@ + + + + + - + + + + + +
@@ -21,13 +29,21 @@
+
+ + +
-
+
+ diff --git a/data/gui/ghost_replay_selection.stkgui b/data/gui/ghost_replay_selection.stkgui index dec25c69b..1b2f9abbf 100644 --- a/data/gui/ghost_replay_selection.stkgui +++ b/data/gui/ghost_replay_selection.stkgui @@ -8,19 +8,50 @@
+ -
-
- - -
diff --git a/data/stk_config.xml b/data/stk_config.xml index 33e60554d..3931457af 100644 --- a/data/stk_config.xml +++ b/data/stk_config.xml @@ -113,14 +113,18 @@ - + max-frames: Maximum number of transform events that can be saved + in a replay/history file. With normal play, 900 are + enough to store at least one minute, usually more. + delta-t Maximum time between saving consecutive transform events. + The recording will do more transform events when some kart data + changes significantly. + delta-speed If the speed difference exceeds this delta, a + new transform event is generated before maximum time. + delta-steering If the steering angle difference exceeds this delta, + new transform event is generated before maximum time. --> + use our own PI +#define PI 3.14159265358979323846f +const float CPU_ALIGN16 simd4fPi[4] = { PI, PI, PI, PI }; const float CPU_ALIGN16 simd4fZeroOneTwoThree[4] = { 0.0, 1.0, 2.0, 3.0 }; const uint32_t CPU_ALIGN16 simd4fAlphaMask[4] = { 0x00000000, 0x00000000, 0x00000000, 0xffffffff }; const float CPU_ALIGN16 simd4f255[4] = { 255.0f, 255.0f, 255.0f, 255.0f }; @@ -102,16 +99,16 @@ const float CPU_ALIGN16 simd4f255Inv[4] = { 1.0f/255.0f, 1.0f/255.0f, 1.0f/255.0 (this is the zlib license) */ -static const float CPU_ALIGN16 simd4f_cephes_FOPI[4] = { 1.27323954473516, 1.27323954473516, 1.27323954473516, 1.27323954473516 }; +static const float CPU_ALIGN16 simd4f_cephes_FOPI[4] = { 1.27323954473516f, 1.27323954473516f, 1.27323954473516f, 1.27323954473516f }; static const float CPU_ALIGN16 simd4f_minus_cephes_DP1[4] = { -0.78515625, -0.78515625, -0.78515625, -0.78515625 }; static const float CPU_ALIGN16 simd4f_minus_cephes_DP2[4] = { -2.4187564849853515625e-4, -2.4187564849853515625e-4, -2.4187564849853515625e-4, -2.4187564849853515625e-4 }; -static const float CPU_ALIGN16 simd4f_minus_cephes_DP3[4] = { -3.77489497744594108e-8, -3.77489497744594108e-8, -3.77489497744594108e-8, -3.77489497744594108e-8 }; -static const float CPU_ALIGN16 simd4f_sincof_p0[4] = { -1.9515295891E-4, -1.9515295891E-4, -1.9515295891E-4, -1.9515295891E-4 }; -static const float CPU_ALIGN16 simd4f_sincof_p1[4] = { 8.3321608736E-3, 8.3321608736E-3, 8.3321608736E-3, 8.3321608736E-3 }; -static const float CPU_ALIGN16 simd4f_sincof_p2[4] = { -1.6666654611E-1, -1.6666654611E-1, -1.6666654611E-1, -1.6666654611E-1 }; -static const float CPU_ALIGN16 simd4f_coscof_p0[4] = { 2.443315711809948E-005, 2.443315711809948E-005, 2.443315711809948E-005, 2.443315711809948E-005 }; -static const float CPU_ALIGN16 simd4f_coscof_p1[4] = { -1.388731625493765E-003, -1.388731625493765E-003, -1.388731625493765E-003, -1.388731625493765E-003 }; -static const float CPU_ALIGN16 simd4f_coscof_p2[4] = { 4.166664568298827E-002, 4.166664568298827E-002, 4.166664568298827E-002, 4.166664568298827E-002 }; +static const float CPU_ALIGN16 simd4f_minus_cephes_DP3[4] = { -3.77489497744594108e-8f, -3.77489497744594108e-8f, -3.77489497744594108e-8f, -3.77489497744594108e-8f }; +static const float CPU_ALIGN16 simd4f_sincof_p0[4] = { -1.9515295891E-4f, -1.9515295891E-4f, -1.9515295891E-4f, -1.9515295891E-4f }; +static const float CPU_ALIGN16 simd4f_sincof_p1[4] = { 8.3321608736E-3f, 8.3321608736E-3f, 8.3321608736E-3f, 8.3321608736E-3f }; +static const float CPU_ALIGN16 simd4f_sincof_p2[4] = { -1.6666654611E-1f, -1.6666654611E-1f, -1.6666654611E-1f, -1.6666654611E-1f }; +static const float CPU_ALIGN16 simd4f_coscof_p0[4] = { 2.443315711809948E-005f, 2.443315711809948E-005f, 2.443315711809948E-005f, 2.443315711809948E-005f }; +static const float CPU_ALIGN16 simd4f_coscof_p1[4] = { -1.388731625493765E-003f, -1.388731625493765E-003f, -1.388731625493765E-003f, -1.388731625493765E-003f }; +static const float CPU_ALIGN16 simd4f_coscof_p2[4] = { 4.166664568298827E-002f, 4.166664568298827E-002f, 4.166664568298827E-002f, 4.166664568298827E-002f }; __m128 simd4f_sin_ps( __m128 x ) { @@ -516,8 +513,8 @@ static inline CC_ALWAYSINLINE __m128 simd4f_fastpow_ps( __m128 arg, uint32_t exp __m128 ret = arg; float corrfactor, powfactor; /* Apply a constant pre-correction factor. */ - corrfactor = exp2( 127.0 * expden / expnum - 127.0 ) * pow( 1.0 * coeffnum / coeffden, 1.0 * expden / expnum ); - powfactor = 1.0 * expnum / expden; + corrfactor = (float)(exp2( 127.0 * expden / expnum - 127.0 ) * pow( 1.0 * coeffnum / coeffden, 1.0 * expden / expnum )); + powfactor = 1.0f * expnum / expden; ret = _mm_mul_ps( ret, _mm_set1_ps( corrfactor ) ); /* Reinterpret arg as integer to obtain logarithm. */ ret = _mm_cvtepi32_ps( _mm_castps_si128( ret ) ); From a9279a05d0f46cf95f470f6daf746b4817e2cc68 Mon Sep 17 00:00:00 2001 From: hiker Date: Fri, 1 Jun 2018 00:22:52 +1000 Subject: [PATCH 094/137] Made --check-debug to work with --no-graphics; print also the race time at which a check-structure is crossed. --- src/tracks/check_cannon.cpp | 3 ++- src/tracks/check_line.cpp | 3 ++- src/tracks/check_structure.cpp | 8 +++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/tracks/check_cannon.cpp b/src/tracks/check_cannon.cpp index 42ee15722..2de8cad6f 100644 --- a/src/tracks/check_cannon.cpp +++ b/src/tracks/check_cannon.cpp @@ -30,6 +30,7 @@ #include "karts/abstract_kart.hpp" #include "karts/cannon_animation.hpp" #include "karts/skidding.hpp" +#include "modes/profile_world.hpp" #include "modes/world.hpp" @@ -65,7 +66,7 @@ CheckCannon::CheckCannon(const XMLNode &node, unsigned int index) for(unsigned int i=0; iaddPoint(p[i]); } - if (UserConfigParams::m_check_debug) + if (UserConfigParams::m_check_debug && !ProfileWorld::isNoGraphics()) { m_debug_target_dy_dc = std::make_shared (scene::EPT_TRIANGLE_STRIP, diff --git a/src/tracks/check_line.cpp b/src/tracks/check_line.cpp index 4e33cea87..64c22f8e0 100644 --- a/src/tracks/check_line.cpp +++ b/src/tracks/check_line.cpp @@ -26,6 +26,7 @@ #include "io/xml_node.hpp" #include "karts/abstract_kart.hpp" #include "modes/linear_world.hpp" +#include "modes/profile_world.hpp" #include "modes/world.hpp" #include "race/race_manager.hpp" @@ -73,7 +74,7 @@ CheckLine::CheckLine(const XMLNode &node, unsigned int index) m_min_height = std::min(m_left_point.getY(), m_right_point.getY()); } m_line.setLine(p1, p2); - if(UserConfigParams::m_check_debug) + if(UserConfigParams::m_check_debug && !ProfileWorld::isNoGraphics()) { #ifndef SERVER_ONLY m_debug_dy_dc = std::make_shared diff --git a/src/tracks/check_structure.cpp b/src/tracks/check_structure.cpp index 2ca199e6a..7925c11f1 100644 --- a/src/tracks/check_structure.cpp +++ b/src/tracks/check_structure.cpp @@ -108,8 +108,10 @@ void CheckStructure::update(float dt) if(m_is_active[i] && isTriggered(m_previous_position[i], xyz, i)) { if(UserConfigParams::m_check_debug) - Log::info("CheckStructure", "Check structure %d triggered for kart %s.", - m_index, world->getKart(i)->getIdent().c_str()); + Log::info("CheckStructure", + "Check structure %d triggered for kart %s at %f.", + m_index, world->getKart(i)->getIdent().c_str(), + World::getWorld()->getTime()); trigger(i); } m_previous_position[i] = xyz; @@ -129,7 +131,7 @@ void CheckStructure::changeStatus(const std::vector &indices, ChangeState change_state) { bool update_debug_colors = - UserConfigParams::m_check_debug && + UserConfigParams::m_check_debug && race_manager->getNumPlayers()>0 && kart_index == (int)World::getWorld()->getPlayerKart(0)->getWorldKartId(); for(unsigned int i=0; i Date: Fri, 1 Jun 2018 22:11:16 +0200 Subject: [PATCH 095/137] Fixed invisible spare tire kart. Fixes #3277 --- src/karts/abstract_kart.hpp | 2 ++ src/karts/kart.cpp | 6 ++++++ src/karts/kart.hpp | 2 ++ src/modes/world.cpp | 6 ++++-- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/karts/abstract_kart.hpp b/src/karts/abstract_kart.hpp index 85a6ace85..8ac209d3f 100644 --- a/src/karts/abstract_kart.hpp +++ b/src/karts/abstract_kart.hpp @@ -517,6 +517,8 @@ public: virtual bool isJumping() const = 0; // ------------------------------------------------------------------------ virtual void playSound(SFXBuffer* buffer) = 0; + // ------------------------------------------------------------------------ + virtual bool isVisible() = 0; }; // AbstractKart diff --git a/src/karts/kart.cpp b/src/karts/kart.cpp index e8f729fe1..78321ecec 100644 --- a/src/karts/kart.cpp +++ b/src/karts/kart.cpp @@ -3157,4 +3157,10 @@ const video::SColor& Kart::getColor() const return m_kart_properties->getColor(); } // getColor +// ------------------------------------------------------------------------ +bool Kart::isVisible() +{ + return m_node && m_node->isVisible(); +} // isVisible + /* EOF */ diff --git a/src/karts/kart.hpp b/src/karts/kart.hpp index 9b611c2fa..edb037d17 100644 --- a/src/karts/kart.hpp +++ b/src/karts/kart.hpp @@ -544,6 +544,8 @@ public: SFXBase* getNextEmitter(); // ------------------------------------------------------------------------ virtual void playSound(SFXBuffer* buffer) OVERRIDE; + // ------------------------------------------------------------------------ + virtual bool isVisible() OVERRIDE; }; // Kart diff --git a/src/modes/world.cpp b/src/modes/world.cpp index 267cf5749..fe70a436f 100644 --- a/src/modes/world.cpp +++ b/src/modes/world.cpp @@ -981,9 +981,11 @@ void World::updateGraphics(float dt) const int kart_amount = (int)m_karts.size(); for (int i = 0; i < kart_amount; ++i) { - // Update all karts that are not eliminated - if (!m_karts[i]->isEliminated() ) + // Update all karts that are visible + if (m_karts[i]->isVisible()) + { m_karts[i]->updateGraphics(dt); + } } projectile_manager->updateGraphics(dt); From 7cb0ffe84457a1afad32ec73da81fdae39e24865 Mon Sep 17 00:00:00 2001 From: QwertyChouskie Date: Sat, 2 Jun 2018 01:25:56 +0000 Subject: [PATCH 096/137] Fix segfault in cutscene when pressing CTL+Shift and when using FPV cam (#3279) Fixes https://github.com/supertuxkart/stk-code/issues/2876 --- src/modes/cutscene_world.cpp | 40 ++++++++++++++++++++---------------- src/utils/debug.cpp | 8 +++++++- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/modes/cutscene_world.cpp b/src/modes/cutscene_world.cpp index 13c357518..e8a018b01 100644 --- a/src/modes/cutscene_world.cpp +++ b/src/modes/cutscene_world.cpp @@ -229,8 +229,8 @@ void CutsceneWorld::update(int ticks) } else { - // this way of calculating time and dt is more in line with what - // irrlicht does andprovides better synchronisation + // this way of calculating time and dt is more in line with what + // irrlicht does and provides better synchronisation double prev_time = m_time; double now = StkTime::getRealTime(); m_time = now - m_time_at_second_reset; @@ -297,28 +297,32 @@ void CutsceneWorld::update(int ticks) { if (curr->getType() == "cutscene_camera") { - scene::ISceneNode* anchorNode = curr->getPresentation()->getNode(); - m_camera->setPosition(anchorNode->getPosition()); - m_camera->updateAbsolutePosition(); + Camera *camera = Camera::getActiveCamera(); + if (camera && camera->getType() == Camera::CM_TYPE_NORMAL) + { + scene::ISceneNode* anchorNode = curr->getPresentation()->getNode(); + m_camera->setPosition(anchorNode->getPosition()); + m_camera->updateAbsolutePosition(); - core::vector3df rot = anchorNode->getRotation(); - Vec3 rot2(rot); - rot2.setPitch(rot2.getPitch() + 90.0f); - m_camera->setRotation(rot2.toIrrVector()); + core::vector3df rot = anchorNode->getRotation(); + Vec3 rot2(rot); + rot2.setPitch(rot2.getPitch() + 90.0f); + m_camera->setRotation(rot2.toIrrVector()); - irr::core::vector3df up(0.0f, 0.0f, 1.0f); - irr::core::matrix4 matrix = anchorNode->getAbsoluteTransformation(); - matrix.rotateVect(up); - m_camera->setUpVector(up); - - SFXManager::get()->positionListener(m_camera->getAbsolutePosition(), - m_camera->getTarget() - - m_camera->getAbsolutePosition(), - Vec3(0,1,0)); + irr::core::vector3df up(0.0f, 0.0f, 1.0f); + irr::core::matrix4 matrix = anchorNode->getAbsoluteTransformation(); + matrix.rotateVect(up); + m_camera->setUpVector(up); + SFXManager::get()->positionListener(m_camera->getAbsolutePosition(), + m_camera->getTarget() - + m_camera->getAbsolutePosition(), + Vec3(0,1,0)); + } break; } } + std::map >::iterator it; for (it = m_sounds_to_trigger.begin(); it != m_sounds_to_trigger.end(); ) { diff --git a/src/utils/debug.cpp b/src/utils/debug.cpp index d310a60c0..4962f3264 100644 --- a/src/utils/debug.cpp +++ b/src/utils/debug.cpp @@ -1025,7 +1025,13 @@ bool onEvent(const SEvent &event) bool handleStaticAction(int key) { - unsigned int kart_num = Camera::getActiveCamera()->getKart()->getWorldKartId(); + Camera* camera = Camera::getActiveCamera(); + unsigned int kart_num = 0; + if (camera != NULL && camera->getKart() != NULL) + { + kart_num = camera->getKart()->getWorldKartId(); + } + if (key == IRR_KEY_F1) { handleContextMenuAction(DEBUG_GUI_CAM_FREE); From b43a5e4d185bb10cb968b0740ebe0b12d749f825 Mon Sep 17 00:00:00 2001 From: Benau Date: Sat, 2 Jun 2018 12:28:29 +0800 Subject: [PATCH 097/137] Add AES encryption and validation for wan player --- CMakeLists.txt | 6 +- android/Android.mk | 1 + sources.cmake | 2 +- src/main.cpp | 3 +- src/network/crypto.cpp | 154 ++++++++++ src/network/crypto.hpp | 134 +++++++++ src/network/event.cpp | 17 +- src/network/event.hpp | 12 + src/network/network_config.cpp | 12 +- src/network/network_config.hpp | 2 + src/network/network_string.hpp | 1 + src/network/protocols/client_lobby.cpp | 134 ++++++--- src/network/protocols/client_lobby.hpp | 11 +- src/network/protocols/connect_to_server.cpp | 68 ++--- src/network/protocols/connect_to_server.hpp | 4 +- src/network/protocols/get_peer_address.cpp | 2 +- src/network/protocols/hide_public_address.cpp | 2 +- src/network/protocols/lobby_protocol.hpp | 12 +- src/network/protocols/request_connection.cpp | 47 +--- src/network/protocols/request_connection.hpp | 5 - src/network/protocols/server_lobby.cpp | 264 +++++++++++------- src/network/protocols/server_lobby.hpp | 23 ++ src/network/server.cpp | 10 +- src/network/server.hpp | 9 + src/network/stk_host.cpp | 39 +-- src/network/stk_peer.cpp | 43 ++- src/network/stk_peer.hpp | 15 +- src/online/http_request.cpp | 3 +- src/states_screens/online_screen.cpp | 4 +- src/utils/string_utils.cpp | 61 ++++ src/utils/string_utils.hpp | 2 + 31 files changed, 789 insertions(+), 313 deletions(-) create mode 100644 src/network/crypto.cpp create mode 100644 src/network/crypto.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bd22166fb..ef6ff5e31 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -460,11 +460,15 @@ endif() # CURL if(MSVC) target_link_libraries(supertuxkart ${PROJECT_SOURCE_DIR}/${DEPENDENCIES}/lib/libcurl.lib) + target_link_libraries(supertuxkart ${PROJECT_SOURCE_DIR}/${DEPENDENCIES}/lib/libeay32.lib) elseif(MINGW) target_link_libraries(supertuxkart ${PROJECT_SOURCE_DIR}/${DEPENDENCIES}/lib/libcurldll.a) + target_link_libraries(supertuxkart ${PROJECT_SOURCE_DIR}/${DEPENDENCIES}/lib/libeay32.dll) else() find_package(CURL REQUIRED) + find_package(OpenSSL REQUIRED) include_directories(${CURL_INCLUDE_DIRS}) + include_directories(${OpenSSL_INCLUDE_DIRS}) endif() # Common library dependencies @@ -481,7 +485,7 @@ target_link_libraries(supertuxkart ${FREETYPE_LIBRARIES} ${JPEG_LIBRARIES} ${TURBOJPEG_LIBRARY} - #${VPX_LIBRARIES} + ${OPENSSL_CRYPTO_LIBRARY} ) if(NOT SERVER_ONLY) diff --git a/android/Android.mk b/android/Android.mk index 37b5d33da..0acbbd2bd 100644 --- a/android/Android.mk +++ b/android/Android.mk @@ -158,6 +158,7 @@ LOCAL_CFLAGS := -I../lib/angelscript/include \ -Iobj/libogg/include \ -Iobj/libvorbis/include \ -Iobj/openal/include \ + -Iobj/openssl/include \ -I$(call my-dir)/../../sources/android/native_app_glue \ -DUSE_GLES2 \ -DHAVE_OGGVORBIS \ diff --git a/sources.cmake b/sources.cmake index 13db008ff..d4f28ae4d 100644 --- a/sources.cmake +++ b/sources.cmake @@ -1,5 +1,5 @@ # Modify this file to change the last-modified date when you add/remove a file. -# This will then trigger a new cmake run automatically. +# This will then trigger a new cmake run automatically. file(GLOB_RECURSE STK_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.hpp") file(GLOB_RECURSE STK_SOURCES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "src/*.cpp") file(GLOB_RECURSE STK_SHADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "data/shaders/*") diff --git a/src/main.cpp b/src/main.cpp index 6900e0647..7c20d38fd 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1110,8 +1110,7 @@ int handleCmdLine() } else { - auto cl = LobbyProtocol::create(); - cl->setAddress(server_addr); + auto cl = LobbyProtocol::create(server_addr, server); cl->requestStart(); } } diff --git a/src/network/crypto.cpp b/src/network/crypto.cpp new file mode 100644 index 000000000..e38cbcaba --- /dev/null +++ b/src/network/crypto.cpp @@ -0,0 +1,154 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2018 SuperTuxKart-Team +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#include "network/crypto.hpp" +#include "network/network_config.hpp" +#include "network/network_string.hpp" + +#include + +// ============================================================================ +std::string Crypto::m_client_key; +std::string Crypto::m_client_iv; +// ============================================================================ +bool Crypto::encryptConnectionRequest(BareNetworkString& ns) +{ + std::vector cipher(ns.m_buffer.size() + 4, 0); + + int elen; + if (EVP_EncryptInit_ex(m_encrypt, NULL, NULL, NULL, NULL) != 1) + return false; + if (EVP_EncryptUpdate(m_encrypt, cipher.data() + 4, &elen, + ns.m_buffer.data(), ns.m_buffer.size()) != 1) + return false; + if (EVP_EncryptFinal_ex(m_encrypt, cipher.data() + 4 + elen, &elen) != 1) + return false; + if (EVP_CIPHER_CTX_ctrl(m_encrypt, EVP_CTRL_GCM_GET_TAG, 4, cipher.data()) + != 1) + return false; + + std::swap(ns.m_buffer, cipher); + return true; +} // encryptConnectionRequest + +// ---------------------------------------------------------------------------- +bool Crypto::decryptConnectionRequest(BareNetworkString& ns) +{ + std::vector pt(ns.m_buffer.size() - 4, 0); + + if (EVP_DecryptInit_ex(m_decrypt, NULL, NULL, NULL, NULL) != 1) + return false; + + int dlen; + if (EVP_DecryptUpdate(m_decrypt, pt.data(), &dlen, ns.m_buffer.data() + 4, + ns.m_buffer.size() - 4) != 1) + return false; + if (!EVP_CIPHER_CTX_ctrl(m_decrypt, EVP_CTRL_GCM_SET_TAG, 4, + ns.m_buffer.data())) + return false; + + if (!(EVP_DecryptFinal_ex(m_decrypt, pt.data() + dlen, &dlen) > 0)) + { + assert(dlen == 0); + return false; + } + + std::swap(ns.m_buffer, pt); + return true; +} // decryptConnectionRequest + +// ---------------------------------------------------------------------------- +ENetPacket* Crypto::encryptSend(BareNetworkString& ns, bool reliable) +{ + // 4 bytes counter and 4 bytes tag + ENetPacket* p = enet_packet_create(NULL, ns.m_buffer.size() + 8, + (reliable ? ENET_PACKET_FLAG_RELIABLE : ENET_PACKET_FLAG_UNSEQUENCED)); + if (p == NULL) + return NULL; + + std::array iv = m_iv; + std::unique_lock ul(m_crypto_mutex); + uint32_t val = NetworkConfig::get()->isClient() ? + m_packet_counter++ : m_packet_counter--; + memcpy(iv.data(), &val, 4); + uint8_t* packet_start = p->data + 8; + + if (EVP_EncryptInit_ex(m_encrypt, NULL, NULL, NULL, iv.data()) != 1) + { + enet_packet_destroy(p); + return NULL; + } + + int elen; + if (EVP_EncryptUpdate(m_encrypt, packet_start, &elen, ns.m_buffer.data(), + ns.m_buffer.size()) != 1) + { + enet_packet_destroy(p); + return NULL; + } + if (EVP_EncryptFinal_ex(m_encrypt, packet_start, &elen) != 1) + { + enet_packet_destroy(p); + return NULL; + } + if (EVP_CIPHER_CTX_ctrl(m_encrypt, EVP_CTRL_GCM_GET_TAG, 4, p->data + 4) + != 1) + { + enet_packet_destroy(p); + return NULL; + } + ul.unlock(); + + memcpy(p->data, iv.data(), 4); + return p; +} // encryptSend + +// ---------------------------------------------------------------------------- +NetworkString* Crypto::decryptRecieve(ENetPacket* p) +{ + int clen = (int)(p->dataLength - 8); + auto ns = std::unique_ptr(new NetworkString(p->data, clen)); + + std::array iv = m_iv; + memcpy(iv.data(), p->data, 4); + uint8_t* packet_start = p->data + 8; + uint8_t* tag = p->data + 4; + if (EVP_DecryptInit_ex(m_decrypt, NULL, NULL, NULL, iv.data()) != 1) + { + throw std::runtime_error("Failed to set IV."); + } + + int dlen; + if (EVP_DecryptUpdate(m_decrypt, ns->m_buffer.data(), &dlen, + packet_start, clen) != 1) + { + throw std::runtime_error("Failed to decrypt."); + } + if (!EVP_CIPHER_CTX_ctrl(m_decrypt, EVP_CTRL_GCM_SET_TAG, 4, tag)) + { + throw std::runtime_error("Failed to set tag."); + } + if (EVP_DecryptFinal_ex(m_decrypt, ns->m_buffer.data(), &dlen) > 0) + { + assert(dlen == 0); + NetworkString* result = ns.get(); + ns.release(); + return result; + } + throw std::runtime_error("Failed to finalize decryption."); +} // decryptRecieve diff --git a/src/network/crypto.hpp b/src/network/crypto.hpp new file mode 100644 index 000000000..65b58eef7 --- /dev/null +++ b/src/network/crypto.hpp @@ -0,0 +1,134 @@ +// +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2018 SuperTuxKart-Team +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +#ifndef HEADER_CRYPTO_HPP +#define HEADER_CRYPTO_HPP + +#include "utils/string_utils.hpp" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +class BareNetworkString; +class NetworkString; + +class Crypto +{ +private: + static std::string m_client_key; + + static std::string m_client_iv; + + std::array m_iv; + + uint32_t m_packet_counter; + + EVP_CIPHER_CTX* m_encrypt; + + EVP_CIPHER_CTX* m_decrypt; + + std::mutex m_crypto_mutex; + +public: + static std::unique_ptr getClientCrypto() + { + assert(!m_client_key.empty()); + assert(!m_client_iv.empty()); + auto c = std::unique_ptr(new Crypto( + StringUtils::decode64(m_client_key), + StringUtils::decode64(m_client_iv))); + c->m_packet_counter = 1; + return c; + } + // ------------------------------------------------------------------------ + static void initClientAES() + { + std::random_device rd; + std::mt19937 g(rd()); + + // Default key and if RAND_bytes failed + std::vector key; + for (int i = 0; i < 16; i++) + key.push_back((uint8_t)(g() % 255)); + std::vector iv; + for (int i = 0; i < 12; i++) + iv.push_back((uint8_t)(g() % 255)); + if (!RAND_bytes(key.data(), 16)) + { + Log::warn("Crypto", + "Failed to generate cryptographically strong key"); + } + m_client_key = StringUtils::base64(key); + m_client_iv = StringUtils::base64(iv); + } + // ------------------------------------------------------------------------ + static void resetClientAES() + { + m_client_key = ""; + m_client_iv = ""; + } + // ------------------------------------------------------------------------ + static const std::string& getClientKey() { return m_client_key; } + // ------------------------------------------------------------------------ + static const std::string& getClientIV() { return m_client_iv; } + // ------------------------------------------------------------------------ + Crypto(const std::vector& key, + const std::vector& iv) + { + assert(key.size() == 16); + assert(iv.size() == 12); + std::copy_n(iv.begin(), 12, m_iv.begin()); + m_packet_counter = (uint32_t)-1; + m_encrypt = EVP_CIPHER_CTX_new(); + EVP_CIPHER_CTX_init(m_encrypt); + EVP_EncryptInit_ex(m_encrypt, EVP_aes_128_gcm(), NULL, key.data(), + iv.data()); + m_decrypt = EVP_CIPHER_CTX_new(); + EVP_CIPHER_CTX_init(m_decrypt); + EVP_DecryptInit_ex(m_decrypt, EVP_aes_128_gcm(), NULL, key.data(), + iv.data()); + } + // ------------------------------------------------------------------------ + ~Crypto() + { + EVP_CIPHER_CTX_free(m_encrypt); + EVP_CIPHER_CTX_free(m_decrypt); + } + // ------------------------------------------------------------------------ + bool encryptConnectionRequest(BareNetworkString& ns); + // ------------------------------------------------------------------------ + bool decryptConnectionRequest(BareNetworkString& ns); + // ------------------------------------------------------------------------ + ENetPacket* encryptSend(BareNetworkString& ns, bool reliable); + // ------------------------------------------------------------------------ + NetworkString* decryptRecieve(ENetPacket* p); + +}; + +#endif // HEADER_CRYPTO_HPP diff --git a/src/network/event.cpp b/src/network/event.cpp index 7ad8cf3bd..4683f4d9d 100644 --- a/src/network/event.cpp +++ b/src/network/event.cpp @@ -18,6 +18,7 @@ #include "network/event.hpp" +#include "network/crypto.hpp" #include "network/stk_peer.hpp" #include "utils/log.hpp" #include "utils/time.hpp" @@ -31,6 +32,7 @@ Event::Event(ENetEvent* event, std::shared_ptr peer) { m_arrival_time = (double)StkTime::getTimeSinceEpoch(); m_pdi = PDI_TIMEOUT; + m_peer = peer; switch (event->type) { @@ -50,8 +52,15 @@ Event::Event(ENetEvent* event, std::shared_ptr peer) } if (m_type == EVENT_TYPE_MESSAGE) { - m_data = new NetworkString(event->packet->data, - (int)event->packet->dataLength); + if (m_peer->getCrypto() && event->channelID == EVENT_CHANNEL_NORMAL) + { + m_data = m_peer->getCrypto()->decryptRecieve(event->packet); + } + else + { + m_data = new NetworkString(event->packet->data, + (int)event->packet->dataLength); + } } else m_data = NULL; @@ -62,7 +71,6 @@ Event::Event(ENetEvent* event, std::shared_ptr peer) enet_packet_destroy(event->packet); } - m_peer = peer; } // Event(ENetEvent) // ---------------------------------------------------------------------------- @@ -70,9 +78,6 @@ Event::Event(ENetEvent* event, std::shared_ptr peer) */ Event::~Event() { - // Do not delete m_peer, it's a pointer to the enet data structure - // which is persistent. - m_peer = NULL; delete m_data; } // ~Event diff --git a/src/network/event.hpp b/src/network/event.hpp index 0b1d5be5c..6fdf0d522 100644 --- a/src/network/event.hpp +++ b/src/network/event.hpp @@ -44,6 +44,18 @@ enum EVENT_TYPE EVENT_TYPE_DISCONNECTED,//!< A peer is disconnected EVENT_TYPE_MESSAGE //!< A message between server and client protocols }; + +/*! + * \enum EVENT_CHANNEL + * \brief Represents a list of channels stk used. + */ +enum EVENT_CHANNEL : uint8_t +{ + EVENT_CHANNEL_NORMAL = 0, //!< Normal channel (encrypted if supported) + EVENT_CHANNEL_UNENCRYPTED = 1,//!< Unencrypted channel + EVENT_CHANNEL_COUNT = 2 +}; + enum PeerDisconnectInfo : unsigned int; /*! diff --git a/src/network/network_config.cpp b/src/network/network_config.cpp index 3579074d5..f2b88b552 100644 --- a/src/network/network_config.cpp +++ b/src/network/network_config.cpp @@ -148,11 +148,21 @@ void NetworkConfig::setUserDetails(Online::XMLRequest* r, const std::string& name) { assert(!m_cur_user_token.empty()); - r->setApiURL(Online::API::SERVER_PATH, name); + r->setApiURL(Online::API::USER_PATH, name); r->addParameter("userid", m_cur_user_id); r->addParameter("token", m_cur_user_token); } // setUserDetails +// ---------------------------------------------------------------------------- +void NetworkConfig::setServerDetails(Online::XMLRequest* r, + const std::string& name) +{ + assert(!m_cur_user_token.empty()); + r->setApiURL(Online::API::SERVER_PATH, name); + r->addParameter("userid", m_cur_user_id); + r->addParameter("token", m_cur_user_token); +} // setServerDetails + // ---------------------------------------------------------------------------- core::stringw NetworkConfig::getModeName(unsigned id) { diff --git a/src/network/network_config.hpp b/src/network/network_config.hpp index 0ad78fe5f..ded9b3b1c 100644 --- a/src/network/network_config.hpp +++ b/src/network/network_config.hpp @@ -275,6 +275,8 @@ public: // ------------------------------------------------------------------------ void setUserDetails(Online::XMLRequest* r, const std::string& name); // ------------------------------------------------------------------------ + void setServerDetails(Online::XMLRequest* r, const std::string& name); + // ------------------------------------------------------------------------ void setServerIdFile(const std::string& id) { m_server_id_file = id; } // ------------------------------------------------------------------------ const std::string& getServerIdFile() const { return m_server_id_file; } diff --git a/src/network/network_string.hpp b/src/network/network_string.hpp index b819c8b23..0623a6b46 100644 --- a/src/network/network_string.hpp +++ b/src/network/network_string.hpp @@ -51,6 +51,7 @@ typedef unsigned char uchar; class BareNetworkString { +friend class Crypto; private: LEAK_CHECK(); diff --git a/src/network/protocols/client_lobby.cpp b/src/network/protocols/client_lobby.cpp index 03df03f95..4149f55bc 100644 --- a/src/network/protocols/client_lobby.cpp +++ b/src/network/protocols/client_lobby.cpp @@ -26,6 +26,7 @@ #include "input/device_manager.hpp" #include "karts/kart_properties_manager.hpp" #include "modes/linear_world.hpp" +#include "network/crypto.hpp" #include "network/event.hpp" #include "network/game_setup.hpp" #include "network/network_config.hpp" @@ -33,10 +34,9 @@ #include "network/protocols/game_protocol.hpp" #include "network/protocols/game_events_protocol.hpp" #include "network/race_event_manager.hpp" +#include "network/server.hpp" #include "network/stk_host.hpp" #include "network/stk_peer.hpp" -#include "online/online_player_profile.hpp" -#include "online/online_profile.hpp" #include "states_screens/networking_lobby.hpp" #include "states_screens/network_kart_selection.hpp" #include "states_screens/race_result_gui.hpp" @@ -66,10 +66,11 @@ engine. */ -ClientLobby::ClientLobby() : LobbyProtocol(NULL) +ClientLobby::ClientLobby(const TransportAddress& a, std::shared_ptr s) + : LobbyProtocol(NULL) { - - m_server_address.clear(); + m_server_address = a; + m_server = s; setHandleDisconnections(true); } // ClientLobby @@ -92,14 +93,6 @@ void ClientLobby::clearPlayers() } } // clearPlayers -//----------------------------------------------------------------------------- -/** Sets the address of the server. - */ -void ClientLobby::setAddress(const TransportAddress &address) -{ - m_server_address = address; -} // setAddress - //----------------------------------------------------------------------------- void ClientLobby::setup() { @@ -263,39 +256,10 @@ void ClientLobby::update(int ticks) break; case LINKED: { - NetworkString *ns = getNetworkString(); + NetworkString* ns = getNetworkString(); ns->addUInt8(LE_CONNECTION_REQUESTED) - .addUInt8(NetworkConfig::m_server_version) - .encodeString(NetworkConfig::get()->getPassword()); + .addUInt8(NetworkConfig::m_server_version); - assert(!NetworkConfig::get()->isAddingNetworkPlayers()); - ns->addUInt8( - (uint8_t)NetworkConfig::get()->getNetworkPlayers().size()); - // Only first player has online name and profile - bool first_player = true; - for (auto& p : NetworkConfig::get()->getNetworkPlayers()) - { - core::stringw name; - PlayerProfile* player = std::get<1>(p); - if (PlayerManager::getCurrentOnlineState() == - PlayerProfile::OS_SIGNED_IN && first_player) - { - name = PlayerManager::getCurrentOnlineUserName(); - } - else - { - name = player->getName(); - } - std::string name_u8 = StringUtils::wideToUtf8(name); - ns->encodeString(name_u8).addFloat(player->getDefaultKartColor()); - Online::OnlinePlayerProfile* opp = - dynamic_cast(player); - ns->addUInt32(first_player && opp && opp->getProfile() ? - opp->getProfile()->getID() : 0); - // Per-player handicap - ns->addUInt8(std::get<2>(p)); - first_player = false; - } auto all_k = kart_properties_manager->getAllAvailableKarts(); auto all_t = track_manager->getAllTrackIdentifiers(); if (all_k.size() >= 65536) @@ -311,9 +275,40 @@ void ClientLobby::update(int ticks) { ns->encodeString(track); } + assert(!NetworkConfig::get()->isAddingNetworkPlayers()); + const uint8_t player_count = + (uint8_t)NetworkConfig::get()->getNetworkPlayers().size(); + ns->addUInt8(player_count); - sendToServer(ns); - delete ns; + bool encryption = false; + uint32_t id = PlayerManager::getCurrentOnlineId(); + + BareNetworkString* rest = new BareNetworkString(); + if (m_server->supportsEncryption() && id != 0) + { + ns->addUInt32(id); + encryption = true; + } + else + { + ns->addUInt32(id).addUInt32(0); + if (id != 0) + ns->encodeString(PlayerManager::getCurrentOnlineUserName()); + } + + rest->encodeString(NetworkConfig::get()->getPassword()) + .addUInt8(player_count); + for (auto& p : NetworkConfig::get()->getNetworkPlayers()) + { + core::stringw name; + PlayerProfile* player = std::get<1>(p); + rest->encodeString(player->getName()). + addFloat(player->getDefaultKartColor()); + // Per-player handicap + rest->addUInt8(std::get<2>(p)); + } + + finalizeConnectionRequest(ns, rest, encryption); m_state.store(REQUESTING_CONNECTION); } break; @@ -336,6 +331,49 @@ void ClientLobby::update(int ticks) } } // update +//----------------------------------------------------------------------------- +void ClientLobby::finalizeConnectionRequest(NetworkString* header, + BareNetworkString* rest, + bool encrypt) +{ + if (encrypt) + { + auto crypto = Crypto::getClientCrypto(); + Crypto::resetClientAES(); + BareNetworkString* result = new BareNetworkString(); + if (!crypto->encryptConnectionRequest(*rest)) + { + // Failed + result->addUInt32(0); + *result += BareNetworkString(rest->getData(), rest->getTotalSize()); + encrypt = false; + } + else + { + Log::info("ClientLobby", "Server will validate this online player."); + result->addUInt32(rest->getTotalSize()); + *result += BareNetworkString(rest->getData(), rest->getTotalSize()); + } + delete rest; + *header += *result; + delete result; + sendToServer(header); + delete header; + if (encrypt) + { + STKHost::get()->getServerPeerForClient() + ->setCrypto(std::move(crypto)); + } + } + else + { + *header += *rest; + delete rest; + sendToServer(header); + delete header; + } +} // finalizeConnectionRequest + //----------------------------------------------------------------------------- void ClientLobby::displayPlayerVote(Event* event) { @@ -590,6 +628,10 @@ void ClientLobby::connectionRefused(Event* event) STKHost::get()->setErrorMessage( _("Connection refused: Server is full.")); break; + case RR_INVALID_PLAYER: + STKHost::get()->setErrorMessage( + _("Connection refused: Invalid player connecting.")); + break; } STKHost::get()->disconnectAllPeers(false/*timeout_waiting*/); STKHost::get()->requestShutdown(); diff --git a/src/network/protocols/client_lobby.hpp b/src/network/protocols/client_lobby.hpp index 245ae96b9..21136b47c 100644 --- a/src/network/protocols/client_lobby.hpp +++ b/src/network/protocols/client_lobby.hpp @@ -6,8 +6,12 @@ #include "utils/cpp2011.hpp" #include +#include #include +class BareNetworkString; +class Server; + class ClientLobby : public LobbyProtocol { private: @@ -29,6 +33,8 @@ private: TransportAddress m_server_address; + std::shared_ptr m_server; + enum ClientState : unsigned int { NONE, @@ -51,11 +57,12 @@ private: bool m_received_server_result = false; void addAllPlayers(Event* event); + void finalizeConnectionRequest(NetworkString* header, + BareNetworkString* rest, bool encrypt); public: - ClientLobby(); + ClientLobby(const TransportAddress& a, std::shared_ptr s); virtual ~ClientLobby(); - void setAddress(const TransportAddress &address); void doneWithResults(); bool receivedServerResult() { return m_received_server_result; } void startingRaceNow(); diff --git a/src/network/protocols/connect_to_server.cpp b/src/network/protocols/connect_to_server.cpp index ad82b50f0..4b09faf92 100644 --- a/src/network/protocols/connect_to_server.cpp +++ b/src/network/protocols/connect_to_server.cpp @@ -19,11 +19,10 @@ #include "network/protocols/connect_to_server.hpp" #include "config/user_config.hpp" +#include "network/crypto.hpp" #include "network/event.hpp" #include "network/network.hpp" #include "network/network_config.hpp" -#include "network/protocols/get_peer_address.hpp" -#include "network/protocols/hide_public_address.hpp" #include "network/protocols/request_connection.hpp" #include "network/protocols/client_lobby.hpp" #include "network/protocol_manager.hpp" @@ -118,19 +117,18 @@ void ConnectToServer::asynchronousUpdate() } servers.clear(); } - - if (handleDirectConnect()) + if (m_server->supportsEncryption()) + { + STKHost::get()->setPublicAddress(); + registerWithSTKServer(); + } + // Assume official server is firewall-less so give it more time + // to directly connect + if (handleDirectConnect(m_server->isOfficial() ? 5000 : 2000)) return; - STKHost::get()->setPublicAddress(); // Set to DONE will stop STKHost is not connected m_state = STKHost::get()->getPublicAddress().isUnset() ? - DONE : REGISTER_SELF_ADDRESS; - } - break; - case REGISTER_SELF_ADDRESS: - { - registerWithSTKServer(); // Register us with STK server - m_state = GOT_SERVER_ADDRESS; + DONE : GOT_SERVER_ADDRESS; } break; case GOT_SERVER_ADDRESS: @@ -157,13 +155,10 @@ void ConnectToServer::asynchronousUpdate() Log::info("ConnectToServer", "Connection request made"); if (m_server_address.isUnset()) { - // server data not correct, hide address and stop - m_state = HIDING_ADDRESS; + // server data not correct, stop + m_state = DONE; Log::error("ConnectToServer", "Server address is %s", m_server_address.toString().c_str()); - auto hide_address = std::make_shared(); - hide_address->requestStart(); - m_current_protocol = hide_address; return; } if (m_tried_connection++ > 7) @@ -221,8 +216,7 @@ void ConnectToServer::asynchronousUpdate() { Log::error("ConnectToServer", "Timeout connect to %s", m_server_address.toString().c_str()); - m_state = NetworkConfig::get()->isWAN() ? - HIDING_ADDRESS : DONE; + m_state = DONE; } } break; @@ -230,26 +224,9 @@ void ConnectToServer::asynchronousUpdate() case CONNECTED: { Log::info("ConnectToServer", "Connected"); - // LAN networking does not use the stk server tables. - if (NetworkConfig::get()->isWAN() && - !STKHost::get()->isClientServer() && - !STKHost::get()->getPublicAddress().isUnset()) - { - auto hide_address = std::make_shared(); - hide_address->requestStart(); - m_current_protocol = hide_address; - } - m_state = HIDING_ADDRESS; - break; - } - case HIDING_ADDRESS: - // Wait till we have hidden our address - if (!m_current_protocol.expired()) - { - return; - } m_state = DONE; break; + } case DONE: case EXITING: break; @@ -278,8 +255,8 @@ void ConnectToServer::update(int ticks) { // Let main thread create ClientLobby for better // synchronization with GUI - auto cl = LobbyProtocol::create(); - cl->setAddress(m_server_address); + auto cl = LobbyProtocol::create(m_server_address, + m_server); cl->requestStart(); } if (STKHost::get()->getPeerCount() == 0) @@ -311,7 +288,8 @@ bool ConnectToServer::handleDirectConnect(int timeout) ENetAddress ea; ea.host = STKHost::HOST_ANY; ea.port = STKHost::PORT_ANY; - Network* dc = new Network(/*peer_count*/1, /*channel_limit*/2, + Network* dc = new Network(/*peer_count*/1, + /*channel_limit*/EVENT_CHANNEL_COUNT, /*max_in_bandwidth*/0, /*max_out_bandwidth*/0, &ea, true/*change_port_if_bound*/); assert(dc); @@ -367,13 +345,17 @@ void ConnectToServer::registerWithSTKServer() // STK server. const TransportAddress& addr = STKHost::get()->getPublicAddress(); Online::XMLRequest *request = new Online::XMLRequest(); - NetworkConfig::get()->setUserDetails(request, "set"); + NetworkConfig::get()->setServerDetails(request, "join-server-key"); + request->addParameter("server-id", m_server->getServerId()); request->addParameter("address", addr.getIP()); request->addParameter("port", addr.getPort()); - request->addParameter("private_port", STKHost::get()->getPrivatePort()); + + Crypto::initClientAES(); + request->addParameter("aes-key", Crypto::getClientKey()); + request->addParameter("iv", Crypto::getClientIV()); Log::info("ConnectToServer", "Registering addr %s", - addr.toString().c_str()); + addr.toString().c_str()); // This can be done blocking: till we are registered with the // stk server, there is no need to to react to any other diff --git a/src/network/protocols/connect_to_server.hpp b/src/network/protocols/connect_to_server.hpp index f35300cc0..05b52d24d 100644 --- a/src/network/protocols/connect_to_server.hpp +++ b/src/network/protocols/connect_to_server.hpp @@ -43,12 +43,10 @@ private: enum ConnectState : unsigned int { SET_PUBLIC_ADDRESS, - REGISTER_SELF_ADDRESS, GOT_SERVER_ADDRESS, REQUESTING_CONNECTION, CONNECTING, CONNECTED, - HIDING_ADDRESS, DONE, EXITING }; @@ -64,7 +62,7 @@ public: virtual void setup() OVERRIDE; virtual void asynchronousUpdate() OVERRIDE; virtual void update(int ticks) OVERRIDE; - bool handleDirectConnect(int timeout = 2000); + bool handleDirectConnect(int timeout); }; // class ConnectToServer diff --git a/src/network/protocols/get_peer_address.cpp b/src/network/protocols/get_peer_address.cpp index c9d5cfa6e..a462b5a44 100644 --- a/src/network/protocols/get_peer_address.cpp +++ b/src/network/protocols/get_peer_address.cpp @@ -41,7 +41,7 @@ void GetPeerAddress::setup() { m_address.clear(); m_request = new Online::XMLRequest(); - NetworkConfig::get()->setUserDetails(m_request, "get"); + NetworkConfig::get()->setServerDetails(m_request, "get"); m_request->addParameter("peer_id", m_peer_id); Online::RequestManager::get()->addRequest(m_request); diff --git a/src/network/protocols/hide_public_address.cpp b/src/network/protocols/hide_public_address.cpp index a11eb5d46..a7e7e3a7f 100644 --- a/src/network/protocols/hide_public_address.cpp +++ b/src/network/protocols/hide_public_address.cpp @@ -41,7 +41,7 @@ void HidePublicAddress::asynchronousUpdate() if (m_state == NONE) { m_request = new Online::XMLRequest(); - NetworkConfig::get()->setUserDetails(m_request, "unset"); + NetworkConfig::get()->setServerDetails(m_request, "unset"); Online::RequestManager::get()->addRequest(m_request); m_state = REQUEST_PENDING; } diff --git a/src/network/protocols/lobby_protocol.hpp b/src/network/protocols/lobby_protocol.hpp index f3fe9183d..71c68073f 100644 --- a/src/network/protocols/lobby_protocol.hpp +++ b/src/network/protocols/lobby_protocol.hpp @@ -20,11 +20,11 @@ #define LOBBY_PROTOCOL_HPP #include "network/protocol.hpp" -#include "network/network_string.hpp" class GameSetup; class NetworkPlayerProfile; +#include #include #include @@ -68,7 +68,8 @@ public: RR_BANNED = 1, RR_INCORRECT_PASSWORD = 2, RR_INCOMPATIBLE_DATA = 3, - RR_TOO_MANY_PLAYERS = 4 + RR_TOO_MANY_PLAYERS = 4, + RR_INVALID_PLAYER = 5 }; protected: @@ -83,12 +84,13 @@ protected: public: /** Creates either a client or server lobby protocol as a singleton. */ - template static std::shared_ptr create() + template + static std::shared_ptr create(Types ...args) { assert(m_lobby.expired()); - auto ret = std::make_shared(); + auto ret = std::make_shared(args...); m_lobby = ret; - return std::dynamic_pointer_cast(ret); + return std::dynamic_pointer_cast(ret); } // create // ------------------------------------------------------------------------ diff --git a/src/network/protocols/request_connection.cpp b/src/network/protocols/request_connection.cpp index ee74a8353..00c88d16d 100644 --- a/src/network/protocols/request_connection.cpp +++ b/src/network/protocols/request_connection.cpp @@ -37,7 +37,6 @@ RequestConnection::RequestConnection(std::shared_ptr server) : Protocol(PROTOCOL_SILENT) { m_server = server; - m_request = NULL; } // RequestConnection // ---------------------------------------------------------------------------- @@ -132,55 +131,11 @@ void RequestConnection::asynchronousUpdate() } } - m_state = DONE; } - else - { - m_request = new Online::XMLRequest(); - NetworkConfig::get()->setUserDetails(m_request, - "request-connection"); - m_request->addParameter("server_id", m_server->getServerId()); - m_request->queue(); - m_state = REQUEST_PENDING; - } - break; - } - case REQUEST_PENDING: - { - if (!m_request->isDone()) - return; - - const XMLNode * result = m_request->getXMLData(); - std::string rec_success; - - if(result->get("success", &rec_success)) - { - if (rec_success == "yes") - { - Log::debug("RequestConnection", - "Connection Request made successfully."); - } - else - { - Log::error("RequestConnection", - "Fail to make a request to connecto to server %d", - m_server->getServerId()); - } - } - else - { - Log::error("RequestConnection", "Fail to make a request."); - } - m_state = DONE; - - break; - } - case DONE: m_state = EXITING; - delete m_request; - m_request = NULL; requestTerminate(); break; + } case EXITING: break; } diff --git a/src/network/protocols/request_connection.hpp b/src/network/protocols/request_connection.hpp index fa2bdfd30..73f3e1b7f 100644 --- a/src/network/protocols/request_connection.hpp +++ b/src/network/protocols/request_connection.hpp @@ -12,14 +12,9 @@ class RequestConnection : public Protocol protected: /** Id of the server to join. */ std::shared_ptr m_server; - - /** The request to join a server. */ - Online::XMLRequest *m_request; enum STATE { NONE, - REQUEST_PENDING, - DONE, EXITING }; diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 7a9e10666..a18486cf8 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -21,6 +21,7 @@ #include "config/user_config.hpp" #include "karts/kart_properties_manager.hpp" #include "modes/linear_world.hpp" +#include "network/crypto.hpp" #include "network/event.hpp" #include "network/game_setup.hpp" #include "network/network_config.hpp" @@ -293,6 +294,7 @@ void ServerLobby::asynchronousUpdate() // Only poll the STK server if this is a WAN server. if (NetworkConfig::get()->isWAN()) checkIncomingConnectionRequests(); + handlePendingConnection(); break; } case ERROR_LEAVE: @@ -486,7 +488,7 @@ void ServerLobby::update(int ticks) void ServerLobby::registerServer() { Online::XMLRequest *request = new Online::XMLRequest(); - NetworkConfig::get()->setUserDetails(request, "create"); + NetworkConfig::get()->setServerDetails(request, "create"); request->addParameter("address", m_server_address.getIP() ); request->addParameter("port", m_server_address.getPort() ); request->addParameter("private_port", @@ -531,7 +533,7 @@ void ServerLobby::registerServer() void ServerLobby::unregisterServer() { Online::XMLRequest* request = new Online::XMLRequest(); - NetworkConfig::get()->setUserDetails(request, "stop"); + NetworkConfig::get()->setServerDetails(request, "stop"); request->addParameter("address", m_server_address.getIP()); request->addParameter("port", m_server_address.getPort()); @@ -671,7 +673,7 @@ void ServerLobby::checkIncomingConnectionRequests() // Now poll the stk server last_poll_time = StkTime::getRealTime(); Online::XMLRequest* request = new Online::XMLRequest(); - NetworkConfig::get()->setUserDetails(request, "poll-connection-requests"); + NetworkConfig::get()->setServerDetails(request, "poll-connection-requests"); const TransportAddress &addr = STKHost::get()->getPublicAddress(); request->addParameter("address", addr.getIP() ); @@ -692,13 +694,19 @@ void ServerLobby::checkIncomingConnectionRequests() // Now start a ConnectToPeer protocol for each connection request const XMLNode * users_xml = result->getNode("users"); - uint32_t id = 0; for (unsigned int i = 0; i < users_xml->getNumNodes(); i++) { + uint32_t addr, id; + uint16_t port; + users_xml->getNode(i)->get("ip", &addr); + users_xml->getNode(i)->get("port", &port); users_xml->getNode(i)->get("id", &id); - Log::debug("ServerLobby", - "User with id %d wants to connect.", id); - std::make_shared(id)->requestStart(); + users_xml->getNode(i)->get("aes-key", &std::get<0>(m_keys[id])); + users_xml->getNode(i)->get("iv", &std::get<1>(m_keys[id])); + users_xml->getNode(i)->get("username", &std::get<2>(m_keys[id])); + std::get<3>(m_keys[id]) = false; + std::make_shared(TransportAddress(addr, port)) + ->requestStart(); } delete request; } // checkIncomingConnectionRequests @@ -817,25 +825,13 @@ void ServerLobby::clientDisconnected(Event* event) } // clientDisconnected //----------------------------------------------------------------------------- - -/*! \brief Called when a player asks for a connection. - * \param event : Event providing the information. - * - * Format of the data : - * Byte 0 1 - * --------------------- - * Size | 1 |1| | - * Data | 4 |n| player name | - * --------------------- - */ void ServerLobby::connectionRequested(Event* event) { std::lock_guard lock(m_connection_mutex); std::shared_ptr peer = event->getPeerSP(); + NetworkString& data = event->data(); peer->cleanPlayerProfiles(); - const NetworkString &data = event->data(); - // can we add the player ? if (m_state != ACCEPTING_CLIENTS || m_game_setup->isGrandPrixStarted()) @@ -843,7 +839,7 @@ void ServerLobby::connectionRequested(Event* event) NetworkString *message = getNetworkString(2); message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_BUSY); // send only to the peer that made the request and disconect it now - peer->sendPacket(message); + peer->sendPacket(message, true/*reliable*/, false/*encrypted*/); peer->reset(); delete message; Log::verbose("ServerLobby", "Player refused: selection started"); @@ -858,91 +854,13 @@ void ServerLobby::connectionRequested(Event* event) NetworkString *message = getNetworkString(2); message->addUInt8(LE_CONNECTION_REFUSED) .addUInt8(RR_INCOMPATIBLE_DATA); - peer->sendPacket(message); + peer->sendPacket(message, true/*reliable*/, false/*encrypted*/); peer->reset(); delete message; Log::verbose("ServerLobby", "Player refused: wrong server version"); return; } - // Check for password - std::string password; - data.decodeString(&password); - if (password != NetworkConfig::get()->getPassword()) - { - NetworkString *message = getNetworkString(2); - message->addUInt8(LE_CONNECTION_REFUSED) - .addUInt8(RR_INCORRECT_PASSWORD); - peer->sendPacket(message); - peer->reset(); - delete message; - Log::verbose("ServerLobby", "Player refused: incorrect password"); - return; - } - - unsigned player_count = data.getUInt8(); - if (m_game_setup->getPlayerCount() + player_count > - NetworkConfig::get()->getMaxPlayers()) - { - NetworkString *message = getNetworkString(2); - message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_TOO_MANY_PLAYERS); - peer->sendPacket(message); - peer->reset(); - delete message; - Log::verbose("ServerLobby", "Player refused: too many players"); - return; - } - - for (unsigned i = 0; i < player_count; i++) - { - std::string name_u8; - data.decodeString(&name_u8); - core::stringw name = StringUtils::utf8ToWide(name_u8); - float default_kart_color = data.getFloat(); - uint32_t online_id = data.getUInt32(); - PerPlayerDifficulty per_player_difficulty = - (PerPlayerDifficulty)data.getUInt8(); - peer->addPlayer(std::make_shared - (peer, name, peer->getHostId(), default_kart_color, online_id, - per_player_difficulty, (uint8_t)i)); - } - - bool is_banned = false; - auto ret = m_ban_list.find(peer->getAddress().getIP()); - if (ret != m_ban_list.end()) - { - // Ban all players - if (ret->second == 0) - { - is_banned = true; - } - else - { - for (auto& p : peer->getPlayerProfiles()) - { - if (ret->second == p->getOnlineId()) - { - is_banned = true; - break; - } - } - } - } - - if (is_banned) - { - NetworkString *message = getNetworkString(2); - message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_BANNED); - peer->cleanPlayerProfiles(); - peer->sendPacket(message); - peer->reset(); - delete message; - Log::verbose("ServerLobby", "Player refused: banned"); - return; - } - - // Connection accepted. - // ==================== std::set client_karts, client_tracks; const unsigned kart_num = data.getUInt16(); const unsigned track_num = data.getUInt16(); @@ -984,7 +902,7 @@ void ServerLobby::connectionRequested(Event* event) message->addUInt8(LE_CONNECTION_REFUSED) .addUInt8(RR_INCOMPATIBLE_DATA); peer->cleanPlayerProfiles(); - peer->sendPacket(message); + peer->sendPacket(message, true/*reliable*/, false/*encrypted*/); peer->reset(); delete message; Log::verbose("ServerLobby", "Player has incompatible karts / tracks"); @@ -995,6 +913,95 @@ void ServerLobby::connectionRequested(Event* event) // disconnects later in lobby it won't affect current players peer->setAvailableKartsTracks(client_karts, client_tracks); + unsigned player_count = data.getUInt8(); + uint32_t online_id = 0; + uint32_t encrypted_size = 0; + online_id = data.getUInt32(); + encrypted_size = data.getUInt32(); + + bool is_banned = false; + auto ret = m_ban_list.find(peer->getAddress().getIP()); + if (ret != m_ban_list.end()) + { + // Ban all players if ban list is zero or compare it with online id + if (ret->second == 0 || (online_id != 0 && ret->second == online_id)) + { + is_banned = true; + } + } + + if (is_banned) + { + NetworkString *message = getNetworkString(2); + message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_BANNED); + peer->cleanPlayerProfiles(); + peer->sendPacket(message, true/*reliable*/, false/*encrypted*/); + peer->reset(); + delete message; + Log::verbose("ServerLobby", "Player refused: banned"); + return; + } + + if (m_game_setup->getPlayerCount() + player_count > + NetworkConfig::get()->getMaxPlayers()) + { + NetworkString *message = getNetworkString(2); + message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_TOO_MANY_PLAYERS); + peer->sendPacket(message, true/*reliable*/, false/*encrypted*/); + peer->reset(); + delete message; + Log::verbose("ServerLobby", "Player refused: too many players"); + return; + } + + if (encrypted_size != 0) + { + m_pending_connection[peer] = std::make_pair(online_id, + BareNetworkString(data.getCurrentData(), encrypted_size)); + } + else + { + core::stringw online_name; + if (online_id > 0) + data.decodeStringW(&online_name); + handleUnencryptedConnection(peer, data, online_id, online_name); + } +} // connectionRequested + +//----------------------------------------------------------------------------- +void ServerLobby::handleUnencryptedConnection(std::shared_ptr peer, + BareNetworkString& data, uint32_t online_id, + const core::stringw& online_name) +{ + // Check for password + std::string password; + data.decodeString(&password); + if (password != NetworkConfig::get()->getPassword()) + { + NetworkString *message = getNetworkString(2); + message->addUInt8(LE_CONNECTION_REFUSED) + .addUInt8(RR_INCORRECT_PASSWORD); + peer->sendPacket(message, true/*reliable*/, false/*encrypted*/); + peer->reset(); + delete message; + Log::verbose("ServerLobby", "Player refused: incorrect password"); + return; + } + + unsigned player_count = data.getUInt8(); + for (unsigned i = 0; i < player_count; i++) + { + core::stringw name; + data.decodeStringW(&name); + float default_kart_color = data.getFloat(); + PerPlayerDifficulty per_player_difficulty = + (PerPlayerDifficulty)data.getUInt8(); + peer->addPlayer(std::make_shared + (peer, i == 0 && !online_name.empty() ? online_name : name, + peer->getHostId(), default_kart_color, i == 0 ? online_id : 0, + per_player_difficulty, (uint8_t)i)); + } + if (!peer->isClientServerTokenSet()) { // Now answer to the peer that just connected @@ -1037,7 +1044,7 @@ void ServerLobby::connectionRequested(Event* event) npp->getOnlineId(), peer->getAddress().toString().c_str()); } updatePlayerList(); -} // connectionRequested +} // handleUnencryptedConnection //----------------------------------------------------------------------------- void ServerLobby::updatePlayerList() @@ -1400,3 +1407,56 @@ bool ServerLobby::waitingForPlayers() const return m_state.load() == ACCEPTING_CLIENTS && !m_game_setup->isGrandPrixStarted(); } // waitingForPlayers + +//----------------------------------------------------------------------------- +void ServerLobby::handlePendingConnection() +{ + for (auto it = m_pending_connection.begin(); + it != m_pending_connection.end();) + { + auto peer = it->first.lock(); + if (!peer) + { + it = m_pending_connection.erase(it); + } + else + { + const uint32_t online_id = it->second.first; + auto key = m_keys.find(online_id); + if (key != m_keys.end() && std::get<3>(key->second) == false) + { + if (decryptConnectionRequest(peer, it->second.second, + std::get<0>(key->second), std::get<1>(key->second), + online_id, std::get<2>(key->second))) + { + it = m_pending_connection.erase(it); + m_keys.erase(online_id); + continue; + } + else + std::get<3>(key->second) = true; + } + it++; + } + } +} // handlePendingConnection + +//----------------------------------------------------------------------------- +bool ServerLobby::decryptConnectionRequest(std::shared_ptr peer, + BareNetworkString& data, const std::string& key, const std::string& iv, + uint32_t online_id, const core::stringw& online_name) +{ + auto crypto = std::unique_ptr(new Crypto( + StringUtils::decode64(key), StringUtils::decode64(iv))); + if (crypto->decryptConnectionRequest(data)) + { + peer->setCrypto(std::move(crypto)); + std::lock_guard lock(m_connection_mutex); + Log::info("ServerLobby", "%s validated", + StringUtils::wideToUtf8(online_name).c_str()); + handleUnencryptedConnection(peer, data, online_id, + online_name); + return true; + } + return false; +} // decryptConnectionRequest diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index 86c76f8da..a02723e5e 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -5,6 +5,9 @@ #include "network/transport_address.hpp" #include "utils/cpp2011.hpp" +#include "irrString.h" + +#include #include #include #include @@ -12,6 +15,7 @@ #include #include +class BareNetworkString; class STKPeer; class ServerLobby : public LobbyProtocol @@ -78,6 +82,14 @@ private: TransportAddress m_server_address; + std::map > m_keys; + + std::map, + std::pair, + std::owner_less > > m_pending_connection; + // connection management void clientDisconnected(Event* event); void connectionRequested(Event* event); @@ -124,6 +136,17 @@ private: } } } + void handlePendingConnection(); + void handleUnencryptedConnection(std::shared_ptr peer, + BareNetworkString& data, + uint32_t online_id, + const irr::core::stringw& online_name); + bool decryptConnectionRequest(std::shared_ptr peer, + BareNetworkString& data, + const std::string& key, + const std::string& iv, + uint32_t online_id, + const irr::core::stringw& online_name); std::tuple handleVote(); void stopCurrentRace(); diff --git a/src/network/server.cpp b/src/network/server.cpp index f35b3a4a6..2da42be9f 100644 --- a/src/network/server.cpp +++ b/src/network/server.cpp @@ -29,7 +29,7 @@ * \param xml The data for one server as received as part of the * get-all stk-server request. */ -Server::Server(const XMLNode& xml) +Server::Server(const XMLNode& xml) : m_supports_encrytion(true) { assert(xml.getName() == "server"); @@ -65,9 +65,9 @@ Server::Server(const XMLNode& xml) m_server_owner_name = L"-"; // Show owner name as Official right now if official server hoster account - bool official = false; - xml.get("official", &official); - if (official) + m_official = false; + xml.get("official", &m_official); + if (m_official) { // I18N: Official means this server is hosted by STK team m_server_owner_name = _("Official"); @@ -117,6 +117,7 @@ Server::Server(const XMLNode& xml) Server::Server(unsigned server_id, const core::stringw &name, int max_players, int current_players, unsigned difficulty, unsigned server_mode, const TransportAddress &address, bool password_protected) + : m_supports_encrytion(false) { m_name = name; m_lower_case_name = StringUtils::toLowerCase(StringUtils::wideToUtf8(name)); @@ -132,6 +133,7 @@ Server::Server(unsigned server_id, const core::stringw &name, int max_players, m_server_mode = server_mode; m_password_protected = password_protected; m_distance = 0.0f; + m_official = false; } // server(server_id, ...) // ---------------------------------------------------------------------------- diff --git a/src/network/server.hpp b/src/network/server.hpp index feb280205..41ac81619 100644 --- a/src/network/server.hpp +++ b/src/network/server.hpp @@ -80,6 +80,11 @@ protected: /* WAN server only, distance based on IP latitude and longitude. */ float m_distance; + + /* WAN server only, true if hosted officially by stk team. */ + bool m_official; + + bool m_supports_encrytion; public: /** Initialises the object from an XML node. */ @@ -123,6 +128,10 @@ public: { return m_server_owner_name; } // ------------------------------------------------------------------------ float getDistance() const { return m_distance; } + // ------------------------------------------------------------------------ + bool supportsEncryption() const { return m_supports_encrytion; } + // ------------------------------------------------------------------------ + bool isOfficial() const { return m_official; } }; // Server #endif // HEADER_SERVER_HPP diff --git a/src/network/stk_host.cpp b/src/network/stk_host.cpp index 76dd4e857..a63701722 100644 --- a/src/network/stk_host.cpp +++ b/src/network/stk_host.cpp @@ -275,14 +275,15 @@ STKHost::STKHost(bool server) addr.port = NetworkConfig::get()->getServerPort(); // Reserve 1 peer to deliver full server message m_network = new Network(NetworkConfig::get()->getMaxPlayers() + 1, - /*channel_limit*/2, /*max_in_bandwidth*/0, + /*channel_limit*/EVENT_CHANNEL_COUNT, /*max_in_bandwidth*/0, /*max_out_bandwidth*/ 0, &addr, true/*change_port_if_bound*/); } else { addr.port = NetworkConfig::get()->getClientPort(); // Client only has 1 peer - m_network = new Network(/*peer_count*/1, /*channel_limit*/2, + m_network = new Network(/*peer_count*/1, + /*channel_limit*/EVENT_CHANNEL_COUNT, /*max_in_bandwidth*/0, /*max_out_bandwidth*/0, &addr, true/*change_port_if_bound*/); } @@ -831,38 +832,16 @@ void STKHost::mainLoop() if (!stk_event && m_peers.find(event.peer) != m_peers.end()) { auto& peer = m_peers.at(event.peer); - unsigned token = 0; - // Token is after the protocol type (1 byte) in stk network - // string (network order) - token += event.packet->data[1]; - token <<= 8; - token += event.packet->data[2]; - token <<= 8; - token += event.packet->data[3]; - token <<= 8; - token += event.packet->data[4]; - - if (is_server && ((!peer->isClientServerTokenSet() && - !isConnectionRequestPacket(event.packet->data, - (int)event.packet->dataLength)) || - (token != peer->getClientServerToken()))) + try { - // For server discard all events from wrong or unset token - // peers if that is not a connection request - if (token != peer->getClientServerToken()) - { - Log::error("STKHost", "Received event with invalid token!"); - Log::error("STKHost", "HostID %d Token %d message token %d", - peer->getHostId(), peer->getClientServerToken(), token); - NetworkString wrong_event(event.packet->data, - (int)event.packet->dataLength); - Log::error("STKHost", wrong_event.getLogMessage().c_str()); - peer->unsetClientServerToken(); - } + stk_event = new Event(&event, peer); + } + catch (std::exception& e) + { + Log::warn("STKHost", "%s", e.what()); enet_packet_destroy(event.packet); continue; } - stk_event = new Event(&event, peer); } else if (!stk_event) { diff --git a/src/network/stk_peer.cpp b/src/network/stk_peer.cpp index d274fc784..06370fdea 100644 --- a/src/network/stk_peer.cpp +++ b/src/network/stk_peer.cpp @@ -18,6 +18,8 @@ #include "network/stk_peer.hpp" #include "config/user_config.hpp" +#include "network/crypto.hpp" +#include "network/event.hpp" #include "network/network_config.hpp" #include "network/network_string.hpp" #include "network/network_player_profile.hpp" @@ -46,6 +48,11 @@ STKPeer::STKPeer(ENetPeer *enet_peer, STKHost* host, uint32_t host_id) m_client_server_token.store(0); } // STKPeer +//----------------------------------------------------------------------------- +STKPeer::~STKPeer() +{ +} // ~STKPeer + //----------------------------------------------------------------------------- void STKPeer::disconnect() { @@ -84,8 +91,9 @@ void STKPeer::reset() /** Sends a packet to this host. * \param data The data to send. * \param reliable If the data is sent reliable or not. + * \param encrypted If the data is sent encrypted or not. */ -void STKPeer::sendPacket(NetworkString *data, bool reliable) +void STKPeer::sendPacket(NetworkString *data, bool reliable, bool encrypted) { TransportAddress a(m_enet_peer->address); // Enet will reuse a disconnected peer so we check here to avoid sending @@ -94,14 +102,27 @@ void STKPeer::sendPacket(NetworkString *data, bool reliable) a != m_peer_address) return; data->setToken(m_client_server_token); - Log::verbose("STKPeer", "sending packet of size %d to %s at %f", - data->size(), a.toString().c_str(),StkTime::getRealTime()); - ENetPacket* packet = enet_packet_create(data->getData(), - data->getTotalSize(), - (reliable ? ENET_PACKET_FLAG_RELIABLE - : ENET_PACKET_FLAG_UNSEQUENCED)); - m_host->addEnetCommand(m_enet_peer, packet, 0, ECT_SEND_PACKET); + ENetPacket* packet = NULL; + if (m_crypto && encrypted) + { + packet = m_crypto->encryptSend(*data, reliable); + } + else + { + packet = enet_packet_create(data->getData(), + data->getTotalSize(), (reliable ? + ENET_PACKET_FLAG_RELIABLE : ENET_PACKET_FLAG_UNSEQUENCED)); + } + + if (packet) + { + Log::verbose("STKPeer", "sending packet of size %d to %s at %f", + packet->dataLength, a.toString().c_str(), StkTime::getRealTime()); + m_host->addEnetCommand(m_enet_peer, packet, + encrypted ? EVENT_CHANNEL_NORMAL : EVENT_CHANNEL_UNENCRYPTED, + ECT_SEND_PACKET); + } } // sendPacket //----------------------------------------------------------------------------- @@ -139,3 +160,9 @@ uint32_t STKPeer::getPing() const return 0; return m_enet_peer->roundTripTime; } // getPing + +//----------------------------------------------------------------------------- +void STKPeer::setCrypto(std::unique_ptr&& c) +{ + m_crypto = std::move(c); +} // setCrypto diff --git a/src/network/stk_peer.hpp b/src/network/stk_peer.hpp index 1a96d3f8e..2a52a6569 100644 --- a/src/network/stk_peer.hpp +++ b/src/network/stk_peer.hpp @@ -34,6 +34,7 @@ #include #include +class Crypto; class NetworkPlayerProfile; class NetworkString; class STKHost; @@ -76,11 +77,15 @@ protected: /** Available karts and tracks from this peer */ std::pair, std::set > m_available_kts; + std::unique_ptr m_crypto; + public: - STKPeer(ENetPeer *enet_peer, STKHost* host, uint32_t host_id); - ~STKPeer() {} + STKPeer(ENetPeer *enet_peer, STKHost* host, uint32_t host_id); // ------------------------------------------------------------------------ - void sendPacket(NetworkString *data, bool reliable = true); + ~STKPeer(); + // ------------------------------------------------------------------------ + void sendPacket(NetworkString *data, bool reliable = true, + bool encrypted = true); // ------------------------------------------------------------------------ void disconnect(); // ------------------------------------------------------------------------ @@ -168,6 +173,10 @@ public: { enet_peer_ping_interval(m_enet_peer, interval); } // ------------------------------------------------------------------------ uint32_t getPing() const; + // ------------------------------------------------------------------------ + Crypto* getCrypto() const { return m_crypto.get(); } + // ------------------------------------------------------------------------ + void setCrypto(std::unique_ptr&& c); }; // STKPeer diff --git a/src/online/http_request.cpp b/src/online/http_request.cpp index 728500df8..2551b61a3 100644 --- a/src/online/http_request.cpp +++ b/src/online/http_request.cpp @@ -245,7 +245,8 @@ namespace Online // List of strings whose values should not be printed. "" is the // end indicator. static std::string dont_print[] = { "&password=", "&token=", "¤t=", - "&new1=", "&new2=", "&password_confirm=", ""}; + "&new1=", "&new2=", "&password_confirm=", + "&aes-key=", "&iv=", ""}; unsigned int j = 0; while (dont_print[j].size() > 0) { diff --git a/src/states_screens/online_screen.cpp b/src/states_screens/online_screen.cpp index 7f33c44b0..2d1e1d7ac 100644 --- a/src/states_screens/online_screen.cpp +++ b/src/states_screens/online_screen.cpp @@ -273,8 +273,8 @@ void OnlineScreen::eventCallback(Widget* widget, const std::string& name, m_entered_server_address = STKHost::get()->getServerPeerForClient()->getAddress(); - auto cl = LobbyProtocol::create(); - cl->setAddress(m_entered_server_address); + auto cl = LobbyProtocol::create + (m_entered_server_address, server); cl->requestStart(); return true; }); diff --git a/src/utils/string_utils.cpp b/src/utils/string_utils.cpp index 4aabee8bb..9fc0e1526 100644 --- a/src/utils/string_utils.cpp +++ b/src/utils/string_utils.cpp @@ -35,6 +35,9 @@ #include #include +#include +#include + namespace StringUtils { bool hasSuffix(const std::string& lhs, const std::string &rhs) @@ -896,6 +899,64 @@ namespace StringUtils return destination; } //findAndReplace + // ------------------------------------------------------------------------ + std::string base64(const std::vector& input) + { + BIO *bmem, *b64; + BUF_MEM* bptr; + std::string result; + + b64 = BIO_new(BIO_f_base64()); + bmem = BIO_new(BIO_s_mem()); + b64 = BIO_push(b64, bmem); + + BIO_set_flags(bmem, BIO_FLAGS_BASE64_NO_NL); + BIO_write(b64, input.data(), input.size()); + BIO_flush(b64); + BIO_get_mem_ptr(b64, &bptr); + result.resize(bptr->length - 1); + memcpy(&result[0], bptr->data, bptr->length - 1); + BIO_free_all(b64); + + return result; + } //base64 + // ------------------------------------------------------------------------ + inline size_t calcDecodeLength(const std::string& input) + { + // Calculates the length of a decoded string + size_t padding = 0; + const size_t len = input.size(); + if (input[len - 1] == '=' && input[len - 2] == '=') + { + // last two chars are = + padding = 2; + } + else if (input[len - 1] == '=') + { + // last char is = + padding = 1; + } + return (len * 3) / 4 - padding; + } + // ------------------------------------------------------------------------ + std::vector decode64(std::string input) + { + BIO *b64, *bmem; + size_t decode_len = calcDecodeLength(input); + std::vector result(decode_len, 0); + b64 = BIO_new(BIO_f_base64()); + + bmem = BIO_new_mem_buf(&input[0], input.size()); + bmem = BIO_push(b64, bmem); + + BIO_set_flags(bmem, BIO_FLAGS_BASE64_NO_NL); + size_t read_l = BIO_read(bmem, result.data(), input.size()); + assert(read_l == decode_len); + BIO_free_all(bmem); + + return result; + } //decode64 + } // namespace StringUtils diff --git a/src/utils/string_utils.hpp b/src/utils/string_utils.hpp index 9fcce18a9..146c0bdc5 100644 --- a/src/utils/string_utils.hpp +++ b/src/utils/string_utils.hpp @@ -233,6 +233,8 @@ namespace StringUtils std::string wideToUtf8(const wchar_t* input); std::string wideToUtf8(const irr::core::stringw& input); std::string findAndReplace(const std::string& source, const std::string& find, const std::string& replace); + std::string base64(const std::vector& input); + std::vector decode64(std::string input); } // namespace StringUtils From 7e5263168e7505866434851d7927bf6e33e412c3 Mon Sep 17 00:00:00 2001 From: Benau Date: Sun, 3 Jun 2018 14:44:53 +0800 Subject: [PATCH 098/137] Add minimum required OpenSSL version --- CMakeLists.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ef6ff5e31..4f6e1f4b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -457,7 +457,9 @@ else() target_link_libraries(supertuxkart ${PTHREAD_LIBRARY}) endif() -# CURL +# CURL and OpenSSL +# 1.0.1d for compatible AES GCM handling +SET(OPENSSL_MINIMUM_VERSION "1.0.1d") if(MSVC) target_link_libraries(supertuxkart ${PROJECT_SOURCE_DIR}/${DEPENDENCIES}/lib/libcurl.lib) target_link_libraries(supertuxkart ${PROJECT_SOURCE_DIR}/${DEPENDENCIES}/lib/libeay32.lib) @@ -467,6 +469,12 @@ elseif(MINGW) else() find_package(CURL REQUIRED) find_package(OpenSSL REQUIRED) + + if(${OPENSSL_VERSION} VERSION_LESS ${OPENSSL_MINIMUM_VERSION} OR + (${OPENSSL_VERSION} VERSION_EQUAL ${OPENSSL_MINIMUM_VERSION} AND ${OPENSSL_VERSION} STRLESS ${OPENSSL_MINIMUM_VERSION})) + message(FATAL_ERROR "OpenSSL version found (${OPENSSL_VERSION}) is less then the minimum required (${OPENSSL_MINIMUM_VERSION}), aborting.") + endif() + include_directories(${CURL_INCLUDE_DIRS}) include_directories(${OpenSSL_INCLUDE_DIRS}) endif() From e122d045ccbf11ea6883e8618e03304337c60991 Mon Sep 17 00:00:00 2001 From: Benau Date: Sun, 3 Jun 2018 15:08:52 +0800 Subject: [PATCH 099/137] Allow showing player ranking in network user dialog --- .../dialogs/network_user_dialog.cpp | 41 ++++++- .../dialogs/network_user_dialog.hpp | 33 +++--- .../dialogs/player_rankings_dialog.cpp | 102 +++++++----------- .../dialogs/player_rankings_dialog.hpp | 23 ++-- .../dialogs/ranking_callback.hpp | 97 +++++++++++++++++ src/states_screens/networking_lobby.cpp | 3 +- 6 files changed, 201 insertions(+), 98 deletions(-) create mode 100644 src/states_screens/dialogs/ranking_callback.hpp diff --git a/src/states_screens/dialogs/network_user_dialog.cpp b/src/states_screens/dialogs/network_user_dialog.cpp index 35af4cb13..50f1439b0 100644 --- a/src/states_screens/dialogs/network_user_dialog.cpp +++ b/src/states_screens/dialogs/network_user_dialog.cpp @@ -43,9 +43,23 @@ void NetworkUserDialog::beforeAddingWidgets() assert(m_name_widget != NULL); m_name_widget->setText(m_name, false); + m_info_widget = getWidget("info"); + assert(m_info_widget != NULL); + if (m_online_id != 0) + { + updatePlayerRanking(m_name, m_online_id, m_info_widget, + m_fetched_ranking); + } + else + { + m_info_widget->setVisible(false); + } + m_friend_widget = getWidget("friend"); assert(m_friend_widget != NULL); - m_friend_widget->setVisible(m_online_id != 0); + m_friend_widget->setVisible(m_online_id != 0 && + PlayerManager::getCurrentOnlineState() == PlayerProfile::OS_SIGNED_IN + && m_online_id != PlayerManager::getCurrentPlayer()->getOnlineId()); // Hide friend request button if already friend Online::OnlineProfile* opp = @@ -66,7 +80,8 @@ void NetworkUserDialog::beforeAddingWidgets() //I18N: In the network user dialog m_kick_widget->setText(_("Kick")); - m_kick_widget->setVisible(STKHost::get()->isAuthorisedToControl()); + m_kick_widget->setVisible(STKHost::get()->isAuthorisedToControl() + && m_host_id != STKHost::get()->getMyHostId()); m_cancel_widget = getWidget("cancel"); assert(m_cancel_widget != NULL); @@ -79,9 +94,29 @@ void NetworkUserDialog::beforeAddingWidgets() getWidget("accept")->setVisible(false); getWidget("remove")->setVisible(false); getWidget("enter")->setVisible(false); - getWidget("info")->setVisible(false); } // beforeAddingWidgets +// ----------------------------------------------------------------------------- +void NetworkUserDialog::onUpdate(float dt) +{ + if (*m_fetched_ranking == false) + { + // I18N: In the network player dialog, showing when waiting for + // the result of the ranking info of a player + core::stringw fetching = + StringUtils::loadingDots(_("Fetching ranking info for %s.", + m_name)); + m_info_widget->setText(fetching, false); + } + + // It's unsafe to delete from inside the event handler so we do it here + if (m_self_destroy) + { + ModalDialog::dismiss(); + return; + } +} // onUpdate + // ----------------------------------------------------------------------------- GUIEngine::EventPropagation NetworkUserDialog::processEvent(const std::string& source) diff --git a/src/states_screens/dialogs/network_user_dialog.hpp b/src/states_screens/dialogs/network_user_dialog.hpp index 2b1ee1345..7131ae5b6 100644 --- a/src/states_screens/dialogs/network_user_dialog.hpp +++ b/src/states_screens/dialogs/network_user_dialog.hpp @@ -20,9 +20,11 @@ #define HEADER_NETWORK_USER_DIALOG_HPP #include "guiengine/modaldialog.hpp" +#include "states_screens/dialogs/ranking_callback.hpp" #include "utils/types.hpp" #include +#include namespace GUIEngine { @@ -35,7 +37,8 @@ namespace GUIEngine * \brief Dialog that handle user in network lobby * \ingroup states_screens */ -class NetworkUserDialog : public GUIEngine::ModalDialog +class NetworkUserDialog : public GUIEngine::ModalDialog, + public RankingCallback { private: const uint32_t m_host_id; @@ -46,21 +49,26 @@ private: bool m_self_destroy; - GUIEngine::RibbonWidget * m_options_widget; + std::shared_ptr m_fetched_ranking; - GUIEngine::LabelWidget * m_name_widget; + GUIEngine::RibbonWidget* m_options_widget; - GUIEngine::IconButtonWidget * m_friend_widget; + GUIEngine::LabelWidget* m_name_widget; - GUIEngine::IconButtonWidget * m_kick_widget; + GUIEngine::LabelWidget* m_info_widget; - GUIEngine::IconButtonWidget * m_cancel_widget; + GUIEngine::IconButtonWidget* m_friend_widget; + + GUIEngine::IconButtonWidget* m_kick_widget; + + GUIEngine::IconButtonWidget* m_cancel_widget; public: NetworkUserDialog(uint32_t host_id, uint32_t online_id, const core::stringw& name) : ModalDialog(0.8f,0.8f), m_host_id(host_id), m_online_id(online_id), - m_name(name), m_self_destroy(false) + m_name(name), m_self_destroy(false), + m_fetched_ranking(std::make_shared(false)) { loadFromFile("online/user_info_dialog.stkgui"); } @@ -75,15 +83,8 @@ public: // ------------------------------------------------------------------------ virtual bool onEscapePressed(); // ------------------------------------------------------------------------ - virtual void onUpdate(float dt) - { - // It's unsafe to delete from inside the event handler so we do it here - if (m_self_destroy) - { - ModalDialog::dismiss(); - return; - } - } + virtual void onUpdate(float dt); + }; #endif diff --git a/src/states_screens/dialogs/player_rankings_dialog.cpp b/src/states_screens/dialogs/player_rankings_dialog.cpp index 31df6e258..6ecd49270 100644 --- a/src/states_screens/dialogs/player_rankings_dialog.cpp +++ b/src/states_screens/dialogs/player_rankings_dialog.cpp @@ -42,16 +42,17 @@ std::vector > PlayerRankingsDialog::PlayerRankingsDialog(uint32_t online_id, const core::stringw& name) : ModalDialog(0.8f,0.9f), m_online_id(online_id), - m_name(name), m_self_destroy(false) + m_name(name), m_self_destroy(false), + m_fetched_ranking(std::make_shared(false)) { loadFromFile("online/player_rankings_dialog.stkgui"); m_top_ten = getWidget("top-ten"); assert(m_top_ten != NULL); if (m_rankings.empty()) - updateTopTen(); + updateTopTenList(); else - addTopTen(); + fillTopTenList(); } // PlayerRankingsDialog // ----------------------------------------------------------------------------- @@ -67,66 +68,18 @@ void PlayerRankingsDialog::beforeAddingWidgets() m_ranking_info = getWidget("cur-rank"); assert(m_ranking_info != NULL); - core::stringw fetching = _("Fetching ranking info for %s.", m_name); - m_ranking_info->setText(fetching, false); - updatePlayerRanking(); - + updatePlayerRanking(m_name, m_online_id, m_ranking_info, + m_fetched_ranking); } // beforeAddingWidgets -// ----------------------------------------------------------------------------- -void PlayerRankingsDialog::updatePlayerRanking() -{ - class UpdatePlayerRankingRequest : public XMLRequest - { - // ------------------------------------------------------------------------ - /** Callback for the request to send a friend invitation. Shows a - * confirmation message and takes care of updating all the cached - * information. - */ - virtual void callback() - { - PlayerRankingsDialog* prd = dynamic_cast - (getCurrent()); - if (prd == NULL) - return; - core::stringw result = _("%s has no ranking yet.", prd->m_name); - if (isSuccess()) - { - int rank = -1; - float score = 0.0; - getXMLData()->get("rank", &rank); - getXMLData()->get("scores", &score); - if (rank > 0) - { - result = _("%s has a rank of %d with score %d.", - prd->m_name, rank, (int)score); - } - } - prd->m_ranking_info->setText(result, false); - - } // callback - public: - UpdatePlayerRankingRequest() : XMLRequest(true) {} - }; // UpdatePlayerRankingRequest - - // ------------------------------------------------------------------------ - - UpdatePlayerRankingRequest* request = new UpdatePlayerRankingRequest(); - PlayerManager::setUserDetails(request, "get-ranking"); - request->addParameter("id", m_online_id); - request->queue(); - -} // updatePlayerRanking - // ---------------------------------------------------------------------------- -void PlayerRankingsDialog::updateTopTen() +void PlayerRankingsDialog::updateTopTenList() { - // ---------------------------------------------------------------- + // ------------------------------------------------------------------------ class UpdateTopTenRequest : public XMLRequest { - /** Callback for the request to accept a friend invitation. Shows a - * confirmation message and takes care of updating all the cached - * information. + /** Callback for the request to update top 10 players and update the + * list. */ virtual void callback() { @@ -148,7 +101,7 @@ void PlayerRankingsDialog::updateTopTen() players->getNode(i)->get("scores", &score); prd->m_rankings.emplace_back(rank, user, score); } - prd->addTopTen(); + prd->fillTopTenList(); } } // callback public: @@ -160,10 +113,10 @@ void PlayerRankingsDialog::updateTopTen() PlayerManager::setUserDetails(request, "top-players"); request->addParameter("ntop", 10); request->queue(); -} // updateTopTen +} // updateTopTenList // ----------------------------------------------------------------------------- -void PlayerRankingsDialog::addTopTen() +void PlayerRankingsDialog::fillTopTenList() { m_top_ten->clear(); for (auto& r : m_rankings) @@ -177,7 +130,28 @@ void PlayerRankingsDialog::addTopTen() StringUtils::toWString(std::get<2>(r)), -1, 1, true)); m_top_ten->addItem("rank", row); } -} // addTopTen +} // fillTopTenList + +// ----------------------------------------------------------------------------- +void PlayerRankingsDialog::onUpdate(float dt) +{ + if (*m_fetched_ranking == false) + { + // I18N: In the network player dialog, showing when waiting for + // the result of the ranking info of a player + core::stringw fetching = + StringUtils::loadingDots(_("Fetching ranking info for %s.", + m_name)); + m_ranking_info->setText(fetching, false); + } + + // It's unsafe to delete from inside the event handler so we do it here + if (m_self_destroy) + { + ModalDialog::dismiss(); + return; + } +} // onUpdate // ----------------------------------------------------------------------------- GUIEngine::EventPropagation @@ -201,8 +175,10 @@ GUIEngine::EventPropagation return GUIEngine::EVENT_BLOCK; timer = StkTime::getRealTime(); - updatePlayerRanking(); - updateTopTen(); + *m_fetched_ranking = false; + updatePlayerRanking(m_name, m_online_id, m_ranking_info, + m_fetched_ranking); + updateTopTenList(); return GUIEngine::EVENT_BLOCK; } } diff --git a/src/states_screens/dialogs/player_rankings_dialog.hpp b/src/states_screens/dialogs/player_rankings_dialog.hpp index a1d65152e..119d273c4 100644 --- a/src/states_screens/dialogs/player_rankings_dialog.hpp +++ b/src/states_screens/dialogs/player_rankings_dialog.hpp @@ -20,9 +20,11 @@ #define HEADER_PLAYER_RANKINGS_DIALOG_HPP #include "guiengine/modaldialog.hpp" +#include "states_screens/dialogs/ranking_callback.hpp" #include "utils/types.hpp" #include +#include #include #include @@ -38,7 +40,8 @@ namespace GUIEngine * \brief Dialog that handle user in network lobby * \ingroup states_screens */ -class PlayerRankingsDialog : public GUIEngine::ModalDialog +class PlayerRankingsDialog : public GUIEngine::ModalDialog, + public RankingCallback { private: const uint32_t m_online_id; @@ -47,6 +50,8 @@ private: bool m_self_destroy; + std::shared_ptr m_fetched_ranking; + GUIEngine::RibbonWidget* m_options_widget; GUIEngine::LabelWidget* m_ranking_info; @@ -61,11 +66,9 @@ private: /*scores*/float> > m_rankings; // ------------------------------------------------------------------------ - void updatePlayerRanking(); + void updateTopTenList(); // ------------------------------------------------------------------------ - void updateTopTen(); - // ------------------------------------------------------------------------ - void addTopTen(); + void fillTopTenList(); public: PlayerRankingsDialog(uint32_t online_id, const core::stringw& name); @@ -84,15 +87,7 @@ public: return false; } // ------------------------------------------------------------------------ - virtual void onUpdate(float dt) - { - // It's unsafe to delete from inside the event handler so we do it here - if (m_self_destroy) - { - ModalDialog::dismiss(); - return; - } - } + virtual void onUpdate(float dt); }; #endif diff --git a/src/states_screens/dialogs/ranking_callback.hpp b/src/states_screens/dialogs/ranking_callback.hpp new file mode 100644 index 000000000..b6c5e8d24 --- /dev/null +++ b/src/states_screens/dialogs/ranking_callback.hpp @@ -0,0 +1,97 @@ +// SuperTuxKart - a fun racing game with go-kart +// Copyright (C) 2018 SuperTuxKart-Team +// +// 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 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + +#ifndef HEADER_RANKING_CALLBACK_HPP +#define HEADER_RANKING_CALLBACK_HPP + +#include "config/player_manager.hpp" +#include "guiengine/widgets/label_widget.hpp" +#include "online/xml_request.hpp" + +#include + +class RankingCallback +{ +protected: + // ------------------------------------------------------------------------ + void updatePlayerRanking(const core::stringw& name, uint32_t online_id, + GUIEngine::LabelWidget* info, + std::shared_ptr done) + { + // -------------------------------------------------------------------- + class UpdatePlayerRankingRequest : public Online::XMLRequest + { + private: + std::weak_ptr m_done; + + core::stringw m_name; + + GUIEngine::LabelWidget* m_info; + // ---------------------------------------------------------------- + /** Callback for the request to update rank of a player. Shows his + * rank and score. + */ + virtual void callback() + { + auto done = m_done.lock(); + // Dialog deleted + if (!done) + return; + // I18N: In the network player dialog, indiciating a network + // player has no ranking + core::stringw result = _("%s has no ranking yet.", m_name); + if (isSuccess()) + { + int rank = -1; + float score = 0.0; + getXMLData()->get("rank", &rank); + getXMLData()->get("scores", &score); + if (rank > 0) + { + // I18N: In the network player dialog show rank and + // score of a player + result = _("%s has a rank of %d with score %d.", + m_name, rank, (int)score); + } + } + *done = true; + m_info->setText(result, false); + + } // callback + public: + UpdatePlayerRankingRequest(const core::stringw& name, + uint32_t online_id, + GUIEngine::LabelWidget* info, + std::shared_ptr done) + : XMLRequest(true) + { + m_name = name; + m_info = info; + m_done = done; + } + }; // UpdatePlayerRankingRequest + + // -------------------------------------------------------------------- + UpdatePlayerRankingRequest* request = + new UpdatePlayerRankingRequest(name, online_id, info, done); + PlayerManager::setUserDetails(request, "get-ranking"); + request->addParameter("id", online_id); + request->queue(); + } +}; +#endif diff --git a/src/states_screens/networking_lobby.cpp b/src/states_screens/networking_lobby.cpp index dee5b91ef..bd2b89b55 100644 --- a/src/states_screens/networking_lobby.cpp +++ b/src/states_screens/networking_lobby.cpp @@ -302,8 +302,7 @@ void NetworkingLobby::eventCallback(Widget* widget, const std::string& name, { auto host_online_ids = StringUtils::splitToUInt (m_player_list->getSelectionInternalName(), '_'); - if (host_online_ids.size() != 2 || - STKHost::get()->getMyHostId() == host_online_ids[0]) + if (host_online_ids.size() != 2) { return; } From 490b865ddd232667070aed01f829dc7910a7df39 Mon Sep 17 00:00:00 2001 From: Benau Date: Sun, 3 Jun 2018 15:15:25 +0800 Subject: [PATCH 100/137] Allow using %f with translated message --- src/states_screens/dialogs/ranking_callback.hpp | 6 +++--- src/utils/string_utils.cpp | 6 ++++-- src/utils/string_utils.hpp | 17 ++++++++++++++--- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/states_screens/dialogs/ranking_callback.hpp b/src/states_screens/dialogs/ranking_callback.hpp index b6c5e8d24..6f39f8ece 100644 --- a/src/states_screens/dialogs/ranking_callback.hpp +++ b/src/states_screens/dialogs/ranking_callback.hpp @@ -58,15 +58,15 @@ protected: if (isSuccess()) { int rank = -1; - float score = 0.0; + float score = 0.0f; getXMLData()->get("rank", &rank); getXMLData()->get("scores", &score); if (rank > 0) { // I18N: In the network player dialog show rank and // score of a player - result = _("%s has a rank of %d with score %d.", - m_name, rank, (int)score); + result = _("%s has a rank of %d with score %f.", + m_name, rank, score); } } *done = true; diff --git a/src/utils/string_utils.cpp b/src/utils/string_utils.cpp index 9fc0e1526..43b88bb11 100644 --- a/src/utils/string_utils.cpp +++ b/src/utils/string_utils.cpp @@ -358,7 +358,8 @@ namespace StringUtils } else { - if(sv[i][1]=='s' || sv[i][1]=='d' || sv[i][1]=='i') + if(sv[i][1]=='s' || sv[i][1]=='d' || sv[i][1]=='i' || + sv[i][1]=='f') { if (insertValID >= all_vals.size()) { @@ -430,7 +431,8 @@ namespace StringUtils } else { - if (sv[i][1]=='s' || sv[i][1]=='d' || sv[i][1]=='i') + if (sv[i][1]=='s' || sv[i][1]=='d' || sv[i][1]=='i' || + sv[i][1]=='f') { if (insertValID >= all_vals.size()) { diff --git a/src/utils/string_utils.hpp b/src/utils/string_utils.hpp index 146c0bdc5..15e9dbe72 100644 --- a/src/utils/string_utils.hpp +++ b/src/utils/string_utils.hpp @@ -21,6 +21,7 @@ #ifndef HEADER_STRING_UTILS_HPP #define HEADER_STRING_UTILS_HPP +#include #include #include #include @@ -67,10 +68,20 @@ namespace StringUtils // ------------------------------------------------------------------------ template - std::string toString (const T& any) + std::string toString(const T& any) { std::ostringstream oss; - oss << any ; + oss << any; + return oss.str(); + } // toString template + + // ------------------------------------------------------------------------ + template <> + inline std::string toString(const double& any) + { + std::ostringstream oss; + oss.precision(std::numeric_limits::max_digits10); + oss << any; return oss.str(); } // toString template @@ -115,7 +126,7 @@ namespace StringUtils // ------------------------------------------------------------------------ /** - * Replaces the first %s or %i/%d in the string with the first value + * Replaces the first %s or %i/%d/%f in the string with the first value * converted to a string), the 2nd %s or %d with the second value etc. * So this is basically a simplified s(n)printf replacement, but doesn't * do any fancy formatting (and no type checks either - so you can print From 2fe50ba0c4b3c1b78b0ecb360445578f346a9e62 Mon Sep 17 00:00:00 2001 From: Benau Date: Sun, 3 Jun 2018 15:24:36 +0800 Subject: [PATCH 101/137] Make connect to server message stay longer if needed in lobby --- src/states_screens/networking_lobby.cpp | 31 ++++++++++++------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/states_screens/networking_lobby.cpp b/src/states_screens/networking_lobby.cpp index bd2b89b55..56138101f 100644 --- a/src/states_screens/networking_lobby.cpp +++ b/src/states_screens/networking_lobby.cpp @@ -34,9 +34,8 @@ #include "input/input_manager.hpp" #include "io/file_manager.hpp" #include "network/network_config.hpp" -#include "network/protocols/client_lobby.hpp" #include "network/protocols/connect_to_server.hpp" -#include "network/protocols/server_lobby.hpp" +#include "network/protocols/lobby_protocol.hpp" #include "network/server.hpp" #include "network/stk_host.hpp" #include "network/stk_peer.hpp" @@ -198,7 +197,9 @@ void NetworkingLobby::addMoreServerInfo(core::stringw info) // ---------------------------------------------------------------------------- void NetworkingLobby::onUpdate(float delta) { - if (m_state == LS_ADD_PLAYERS && NetworkConfig::get()->isClient()) + if (NetworkConfig::get()->isServer()) + return; + if (m_state == LS_ADD_PLAYERS) { m_text_bubble->setText(_("Everyone:\nPress the 'Select' button to " "join the game"), true); @@ -215,7 +216,7 @@ void NetworkingLobby::onUpdate(float delta) m_start_button->setVisible(false); m_exit_widget->setVisible(true); auto lp = LobbyProtocol::get(); - if (!lp) + if (!lp || !lp->waitingForPlayers()) { core::stringw connect_msg; if (m_joined_server) @@ -223,7 +224,7 @@ void NetworkingLobby::onUpdate(float delta) connect_msg = StringUtils::loadingDots( _("Connecting to server %s", m_joined_server->getName())); } - else if (NetworkConfig::get()->isClient()) + else { connect_msg = StringUtils::loadingDots(_("Finding a quick play server")); @@ -233,8 +234,7 @@ void NetworkingLobby::onUpdate(float delta) } else { - if (m_server_peer.expired() && NetworkConfig::get()->isClient() - && STKHost::existHost()) + if (m_server_peer.expired() && STKHost::existHost()) m_server_peer = STKHost::get()->getServerPeerForClient(); core::stringw total_msg; for (auto& string : m_server_info) @@ -244,17 +244,16 @@ void NetworkingLobby::onUpdate(float delta) } m_text_bubble->setText(total_msg, true); } - if (NetworkConfig::get()->isClient()) + + if (STKHost::get()->isAuthorisedToControl()) { - if (STKHost::get()->isAuthorisedToControl()) - { - m_start_button->setVisible(true); - } - //I18N: In the networking lobby, display ping when connected - const uint32_t ping = getServerPing(); - if (ping != 0) - m_header->setText(_("Lobby (ping: %dms)", ping), false); + m_start_button->setVisible(true); } + //I18N: In the networking lobby, display ping when connected + const uint32_t ping = getServerPing(); + if (ping != 0) + m_header->setText(_("Lobby (ping: %dms)", ping), false); + } // onUpdate // ---------------------------------------------------------------------------- From 4653089d9519acf355aa70deae9d0478bf86a27f Mon Sep 17 00:00:00 2001 From: Benau Date: Mon, 4 Jun 2018 01:19:27 +0800 Subject: [PATCH 102/137] Add ranked server for validated player --- src/main.cpp | 21 +- src/network/game_setup.hpp | 4 +- src/network/network_config.cpp | 1 + src/network/network_config.hpp | 10 +- src/network/protocols/lobby_protocol.cpp | 1 + src/network/protocols/server_lobby.cpp | 331 ++++++++++++++++------- src/network/protocols/server_lobby.hpp | 34 ++- src/network/remote_kart_info.hpp | 9 +- 8 files changed, 299 insertions(+), 112 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 7c20d38fd..bb7465d08 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -598,6 +598,10 @@ void cmdLineHelp() " --disable-lan Disable LAN detection (connect using WAN).\n" " --auto-connect Automatically connect to fist server and start race\n" " --max-players=n Maximum number of clients (server only).\n" + " --motd Message showing in all lobby of clients.\n" + " --no-validation Allow non validated and unencrypted connection in wan.\n" + " --ranked Server will submit ranking to stk addons server.\n" + " You require permission for that.\n" " --no-console-log Does not write messages in the console but to\n" " stdout.log.\n" " -h, --help Show this help.\n" @@ -1063,7 +1067,11 @@ int handleCmdLine() core::stringw motd = StringUtils::xmlDecode(s); NetworkConfig::get()->setMOTD(motd); } - + if (CommandLine::has("--ranked")) + { + NetworkConfig::get()->setValidatedPlayers(true); + NetworkConfig::get()->setRankedServer(true); + } if (CommandLine::has("--server-id-file", &s)) { NetworkConfig::get()->setServerIdFile( @@ -1140,6 +1148,14 @@ int handleCmdLine() NetworkConfig::get()->setIsServer(true); NetworkConfig::get()->setIsWAN(); NetworkConfig::get()->setIsPublicServer(); + if (CommandLine::has("--no-validation")) + { + NetworkConfig::get()->setValidatedPlayers(false); + } + else + { + NetworkConfig::get()->setValidatedPlayers(true); + } server_lobby = STKHost::create(); Log::info("main", "Creating a WAN server '%s'.", s.c_str()); } @@ -1149,6 +1165,7 @@ int handleCmdLine() NetworkConfig::get()->setServerName(StringUtils::xmlDecode(s)); NetworkConfig::get()->setIsServer(true); NetworkConfig::get()->setIsLAN(); + NetworkConfig::get()->setValidatedPlayers(false); server_lobby = STKHost::create(); Log::info("main", "Creating a LAN server '%s'.", s.c_str()); } @@ -1162,7 +1179,7 @@ int handleCmdLine() if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER) { LobbyProtocol::get()->getGameSetup() - ->setSoccerGoalTarget((bool)n); + ->setSoccerGoalTarget(n != 0); NetworkConfig::get()->setServerMode( race_manager->getMinorMode(), RaceManager::MAJOR_MODE_SINGLE); diff --git a/src/network/game_setup.hpp b/src/network/game_setup.hpp index 038e673fa..54ede216e 100644 --- a/src/network/game_setup.hpp +++ b/src/network/game_setup.hpp @@ -80,7 +80,7 @@ public: /** \brief Get the players that are in the game * \return A vector containing pointers on the players profiles. */ std::vector > - getConnectedPlayers(bool same_index_for_disconnected = false) const + getConnectedPlayers() const { std::lock_guard lock(m_players_mutex); std::vector > players; @@ -88,8 +88,6 @@ public: { if (auto player_connected = player_weak.lock()) players.push_back(player_connected); - else if (same_index_for_disconnected) - players.push_back(nullptr); } return players; } // getConnectedPlayers diff --git a/src/network/network_config.cpp b/src/network/network_config.cpp index fe4dc9bf8..8dde0c1f9 100644 --- a/src/network/network_config.cpp +++ b/src/network/network_config.cpp @@ -50,6 +50,7 @@ NetworkConfig::NetworkConfig() m_is_server = false; m_is_public_server = false; m_is_ranked_server = false; + m_validated_players = false; m_done_adding_network_players = false; m_max_players = 4; m_cur_user_id = 0; diff --git a/src/network/network_config.hpp b/src/network/network_config.hpp index 5ed8550be..fcb390b47 100644 --- a/src/network/network_config.hpp +++ b/src/network/network_config.hpp @@ -90,6 +90,9 @@ private: * immediately start a race. */ bool m_auto_connect; + /** True if only validated players are allowed to join. */ + bool m_validated_players; + bool m_done_adding_network_players; /** If this is a server, the server name. */ @@ -267,7 +270,8 @@ public: // ------------------------------------------------------------------------ /** Returns if the server use multi-session rankings. */ bool isRankedServer() const { return m_is_ranked_server; } - + // ------------------------------------------------------------------------ + void setRankedServer(bool val) { m_is_ranked_server = val; } // ------------------------------------------------------------------------ /** Returns the minor and majar game mode from server database id. */ std::pair @@ -303,6 +307,10 @@ public: core::stringw getModeName(unsigned id); // ------------------------------------------------------------------------ std::vector getResetScreens(bool lobby = false) const; + // ------------------------------------------------------------------------ + void setValidatedPlayers(bool val) { m_validated_players = val; } + // ------------------------------------------------------------------------ + bool onlyValidatedPlayers() const { return m_validated_players; } }; // class NetworkConfig diff --git a/src/network/protocols/lobby_protocol.cpp b/src/network/protocols/lobby_protocol.cpp index f52df677d..74c521f99 100644 --- a/src/network/protocols/lobby_protocol.cpp +++ b/src/network/protocols/lobby_protocol.cpp @@ -123,6 +123,7 @@ void LobbyProtocol::configRemoteKart( rki.setGlobalPlayerId(i); rki.setDefaultKartColor(profile->getDefaultKartColor()); rki.setPerPlayerDifficulty(profile->getPerPlayerDifficulty()); + rki.setOnlineId(profile->getOnlineId()); // Inform the race manager about the data for this kart. race_manager->setPlayerKart(i, rki); } // for i in players diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index d5c9eb8d0..96d2d4055 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -91,6 +91,12 @@ ServerLobby::ServerLobby() : LobbyProtocol(NULL) setHandleDisconnections(true); m_state = SET_PUBLIC_ADDRESS; updateBanList(); + if (NetworkConfig::get()->isRankedServer()) + { + Log::info("ServerLobby", "This server will submit ranking scores to " + "STK addons server, don't bother host one if you don't have the " + "corresponding permission, they will be rejected if so."); + } } // ServerLobby //----------------------------------------------------------------------------- @@ -291,6 +297,7 @@ void ServerLobby::asynchronousUpdate() } case ACCEPTING_CLIENTS: { + clearDisconnectedRankedPlayer(); // Only poll the STK server if this is a WAN server. if (NetworkConfig::get()->isWAN()) checkIncomingConnectionRequests(); @@ -759,9 +766,6 @@ void ServerLobby::checkRaceFinished() total->addUInt32(last_score).addUInt32(cur_score) .addFloat(overall_time); } - - if (NetworkConfig::get()->isRankedServer()) - computeNewRankings(); } else if (race_manager->modeHasLaps()) { @@ -769,6 +773,11 @@ void ServerLobby::checkRaceFinished() static_cast(World::getWorld())->getFastestLapTicks(); total->addUInt32(fastest_lap); } + if (NetworkConfig::get()->isRankedServer()) + { + computeNewRankings(); + submitRankingsToAddons(); + } stopCurrentRace(); // Set the delay before the server forces all clients to exit the race @@ -782,85 +791,79 @@ void ServerLobby::checkRaceFinished() } // checkRaceFinished //----------------------------------------------------------------------------- -/** Compute the new player's rankings in ranked servers - * //FIXME : this function assumes that m_rankings, - * m_num_ranked_races and m_max_ranking - * are correctly filled before - * It also assumes that the data stored by them - * is written back to the main list after the GP +/** Compute the new player's rankings used in ranked servers */ void ServerLobby::computeNewRankings() { - auto players = m_game_setup->getConnectedPlayers(true/*same_index_for_disconnected*/); - - assert (m_rankings.size() == players.size() && - m_num_ranked_races.size() == players.size() && - m_max_ranking.size() == players.size() ); - - // No ranking yet for battle mode - // TODO : separate rankings for time-trial and normal ?? + // No ranking for battle mode if (!race_manager->modeHasLaps()) return; // Using a vector of vector, it would be possible to fill // all j < i v[i][j] with -v[j][i] // Would this be worth it ? - std::vector ranking_change; + std::vector scores_change; + std::vector new_scores; + auto players = m_game_setup->getPlayers(); for (unsigned i = 0; i < players.size(); i++) { - m_rankings[i] += distributeBasePoints(i); + const uint32_t id = race_manager->getKartInfo(i).getOnlineId(); + new_scores.push_back(m_scores.at(id)); + new_scores[i] += distributeBasePoints(id); } for (unsigned i = 0; i < players.size(); i++) { - ranking_change.push_back(0); - - int player1_ranking = m_rankings[i]; + scores_change.push_back(0.0); + double player1_scores = new_scores[i]; // If the player has quitted before the race end, // the value will be incorrect, but it will not be used - float player1_time = race_manager->getKartRaceTime(i); - float player1_factor = computeRankingFactor(i); + double player1_time = race_manager->getKartRaceTime(i); + double player1_factor = + computeRankingFactor(race_manager->getKartInfo(i).getOnlineId()); - for (unsigned j = 0; j < players.size(); i++) + for (unsigned j = 0; j < players.size(); j++) { - // Don't compare a player with itself + // Don't compare a player with himself if (i == j) continue; - double result = 0.0f; - double expected_result = 0.0f; - double ranking_importance = 0.0f; + double result = 0.0; + double expected_result = 0.0; + double ranking_importance = 0.0; // No change between two quitting players - if (!players[i] && !players[j]) + if (players[i].expired() && players[j].expired()) continue; - int player2_ranking = m_rankings[j]; - float player2_time = race_manager->getKartRaceTime(j); + double player2_scores = new_scores[j]; + double player2_time = race_manager->getKartRaceTime(j); // Compute the expected result using an ELO-like function - double diff = (double) player2_ranking - player1_ranking; - expected_result = 1.0f/(1.0f+std::pow(10.0f, diff/(BASE_RANKING_POINTS*getModeSpread()/(2.0f)))); + double diff = player2_scores - player1_scores; + expected_result = 1.0/ (1.0 + std::pow(10.0, + diff / (BASE_RANKING_POINTS * getModeSpread() / 2.0))); // Compute the result and race ranking importance - float player_factors = std::max(player1_factor, - computeRankingFactor(j) ); - - float mode_factor = getModeFactor(); + double player_factors = std::max(player1_factor, + computeRankingFactor( + race_manager->getKartInfo(j).getOnlineId())); - if (!players[i]) + double mode_factor = getModeFactor(); + + if (players[i].expired()) { - result = 0.0f; - ranking_importance = - mode_factor*MAX_SCALING_TIME*MAX_POINTS_PER_SECOND*player_factors; + result = 0.0; + ranking_importance = mode_factor * + MAX_SCALING_TIME * MAX_POINTS_PER_SECOND * player_factors; } - else if (!players[j]) + else if (players[j].expired()) { - result = 1.0f; - ranking_importance = - mode_factor*MAX_SCALING_TIME*MAX_POINTS_PER_SECOND*player_factors; + result = 1.0; + ranking_importance = mode_factor * + MAX_SCALING_TIME * MAX_POINTS_PER_SECOND * player_factors; } else { @@ -868,88 +871,91 @@ void ServerLobby::computeNewRankings() // Otherwise, it is averaged between 0 and 1. if (player1_time <= player2_time) { - result = (player2_time - player1_time)/(player1_time/20); - result = std::min( (double) 1.0f, 0.5f + result); + result = + (player2_time - player1_time) / (player1_time / 20.0); + result = std::min(1.0, 0.5 + result); } else { - result = (player1_time - player2_time)/(player2_time/20); - result = std::max( (double) 0.0f, 0.5f - result); + result = + (player1_time - player2_time) / (player2_time / 20.0); + result = std::max(0.0, 0.5 - result); } - ranking_importance = mode_factor * std::min ( std::max (player1_time, player2_time), - MAX_SCALING_TIME ) * MAX_POINTS_PER_SECOND * player_factors; + ranking_importance = mode_factor * + std::min( + std::max(player1_time, player2_time), MAX_SCALING_TIME) * + MAX_POINTS_PER_SECOND * player_factors; } // Compute the ranking change - ranking_change[i] += ranking_importance * (result - expected_result); + scores_change[i] += + ranking_importance * (result - expected_result); } } - // Don't merge it in the main loop as m_rankings value are used there + // Don't merge it in the main loop as new_scores value are used there for (unsigned i = 0; i < players.size(); i++) { - m_rankings[i] += ranking_change[i]; - - if (m_rankings[i] > m_max_ranking[i]) - m_max_ranking[i] = m_rankings[i]; - m_num_ranked_races[i]++; + new_scores[i] += scores_change[i]; + const uint32_t id = race_manager->getKartInfo(i).getOnlineId(); + m_scores.at(id) = new_scores[i]; + if (m_scores.at(id) > m_max_scores.at(id)) + m_max_scores.at(id) = m_scores.at(id); + m_num_ranked_races.at(id)++; } -} //computeNewRankings +} // computeNewRankings //----------------------------------------------------------------------------- /** Compute the ranking factor, used to make top rankings more stable * and to allow new players to faster get to an appropriate ranking */ -float ServerLobby::computeRankingFactor(unsigned int player_id) +double ServerLobby::computeRankingFactor(uint32_t online_id) { - double max_points = m_max_ranking[player_id]; - int num_races = m_num_ranked_races[player_id]; + double max_points = m_max_scores.at(online_id); + unsigned num_races = m_num_ranked_races.at(online_id); - if ( max_points >= (BASE_RANKING_POINTS * 2.0f)) - return 0.4f; - else if (max_points >= (BASE_RANKING_POINTS * 1.75f) || num_races > 500) - return 0.5f; - else if (max_points >= (BASE_RANKING_POINTS * 1.5f) || num_races > 250) - return 0.6f; - else if (max_points >= (BASE_RANKING_POINTS * 1.25f) || num_races > 100) - return 0.7f; + if (max_points >= (BASE_RANKING_POINTS * 2.0)) + return 0.4; + else if (max_points >= (BASE_RANKING_POINTS * 1.75) || num_races > 500) + return 0.5; + else if (max_points >= (BASE_RANKING_POINTS * 1.5) || num_races > 250) + return 0.6; + else if (max_points >= (BASE_RANKING_POINTS * 1.25) || num_races > 100) + return 0.7; // The base ranking points are not distributed all at once // So it's not guaranteed a player reach them - else if (max_points >= (BASE_RANKING_POINTS ) || num_races > 50) - return 0.8f; + else if (max_points >= (BASE_RANKING_POINTS) || num_races > 50) + return 0.8; else - return 1.0f; + return 1.0; -} //computeRankingFactor +} // computeRankingFactor //----------------------------------------------------------------------------- /** Returns the mode race importance factor, * used to make ranking move slower in more random modes. */ -float ServerLobby::getModeFactor() +double ServerLobby::getModeFactor() { if (race_manager->isTimeTrialMode()) - return 1.0f; - - //else - return 0.4f; -} + return 1.0; + return 0.4; +} // getModeFactor //----------------------------------------------------------------------------- /** Returns the mode spread factor, used so that a similar difference in * skill will result in a similar ranking difference in more random modes. */ -float ServerLobby::getModeSpread() +double ServerLobby::getModeSpread() { if (race_manager->isTimeTrialMode()) - return 1.0f; + return 1.0; - //else - //TODO : the value used here for normal races is a wild guess. + //TODO: the value used here for normal races is a wild guess. // When hard data to the spread tendencies of time-trial // and normal mode becomes available, update this to make // the spreads more comparable - return 1.4f; -} + return 1.4; +} // getModeSpread //----------------------------------------------------------------------------- /** Manages the distribution of the base points. @@ -958,14 +964,18 @@ float ServerLobby::getModeSpread() * The first half is distributed when the player enters * for the first time in the ranked server. */ -float ServerLobby::distributeBasePoints(unsigned int player_id) +double ServerLobby::distributeBasePoints(uint32_t online_id) { - int num_races = m_num_ranked_races[player_id]; + unsigned num_races = m_num_ranked_races.at(online_id); if (num_races < 45) - return (BASE_RANKING_POINTS/2000.0f * std::max((45-num_races),4)*2.0f); + { + return + (BASE_RANKING_POINTS / 2000.0 * std::max((45u - num_races), 4u) * + 2.0); + } else - return 0.0f; -} + return 0.0; +} // distributeBasePoints //----------------------------------------------------------------------------- /** Stop any race currently in server, should only be called in main thread. @@ -1013,6 +1023,26 @@ void ServerLobby::clientDisconnected(Event* event) delete msg; } // clientDisconnected +//----------------------------------------------------------------------------- +void ServerLobby::clearDisconnectedRankedPlayer() +{ + for (auto it = m_ranked_players.begin(); it != m_ranked_players.end();) + { + if (it->second.expired()) + { + const uint32_t id = it->first; + m_scores.erase(id); + m_max_scores.erase(id); + m_num_ranked_races.erase(id); + it = m_ranked_players.erase(it); + } + else + { + it++; + } + } +} // clearDisconnectedRankedPlayer + //----------------------------------------------------------------------------- void ServerLobby::connectionRequested(Event* event) { @@ -1143,6 +1173,26 @@ void ServerLobby::connectionRequested(Event* event) return; } + // Reject non-valiated player joinning if WAN server and not disabled + // encforement of validation, unless it's player from localhost or lan + // And no duplicated online id or split screen players in ranked server + if ((encrypted_size == 0 && + !(peer->getAddress().isPublicAddressLocalhost() || + peer->getAddress().isLAN()) && + NetworkConfig::get()->isWAN() && + NetworkConfig::get()->onlyValidatedPlayers()) || + ((player_count != 1 || m_scores.find(online_id) != m_scores.end()) && + NetworkConfig::get()->isRankedServer())) + { + NetworkString* message = getNetworkString(2); + message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_INVALID_PLAYER); + peer->sendPacket(message, true/*reliable*/, false/*encrypted*/); + peer->reset(); + delete message; + Log::verbose("ServerLobby", "Player refused: invalid player"); + return; + } + if (encrypted_size != 0) { m_pending_connection[peer] = std::make_pair(online_id, @@ -1177,6 +1227,19 @@ void ServerLobby::handleUnencryptedConnection(std::shared_ptr peer, return; } + // Check again for duplicated online id in ranked server + if (m_scores.find(online_id) != m_scores.end() && + NetworkConfig::get()->isRankedServer()) + { + NetworkString* message = getNetworkString(2); + message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_INVALID_PLAYER); + peer->sendPacket(message, true/*reliable*/, false/*encrypted*/); + peer->reset(); + delete message; + Log::verbose("ServerLobby", "Player refused: invalid player"); + return; + } + unsigned player_count = data.getUInt8(); for (unsigned i = 0; i < player_count; i++) { @@ -1233,6 +1296,11 @@ void ServerLobby::handleUnencryptedConnection(std::shared_ptr peer, npp->getOnlineId(), peer->getAddress().toString().c_str()); } updatePlayerList(); + + if (NetworkConfig::get()->isRankedServer()) + { + getRankingForPlayer(peer->getPlayerProfiles()[0]); + } } // handleUnencryptedConnection //----------------------------------------------------------------------------- @@ -1649,3 +1717,82 @@ bool ServerLobby::decryptConnectionRequest(std::shared_ptr peer, } return false; } // decryptConnectionRequest + +//----------------------------------------------------------------------------- +void ServerLobby::getRankingForPlayer(std::shared_ptr p) +{ + Online::XMLRequest* request = new Online::XMLRequest(); + NetworkConfig::get()->setUserDetails(request, "get-ranking"); + + const uint32_t id = p->getOnlineId(); + request->addParameter("id", id); + request->executeNow(); + + const XMLNode* result = request->getXMLData(); + std::string rec_success; + + // Default result + double score = 2000.0; + double max_score = 2000.0; + unsigned num_races = 0; + if (result->get("success", &rec_success)) + { + if (rec_success == "yes") + { + result->get("scores", &score); + result->get("max-scores", &max_score); + result->get("num-races-done", &num_races); + } + else + { + Log::error("ServerLobby", "No ranking info found."); + } + } + else + { + Log::error("ServerLobby", "No ranking info found."); + } + m_ranked_players[id] = p; + m_scores[id] = score; + m_max_scores[id] = max_score; + m_num_ranked_races[id] = num_races; + delete request; +} // getRankingForPlayer + +//----------------------------------------------------------------------------- +void ServerLobby::submitRankingsToAddons() +{ + // No ranking for battle mode + if (!race_manager->modeHasLaps()) + return; + + // -------------------------------------------------------------------- + class SumbitRankingRequest : public Online::XMLRequest + { + public: + SumbitRankingRequest(uint32_t online_id, double scores, + double max_scores, unsigned num_races) + : XMLRequest(true) + { + addParameter("id", online_id); + addParameter("scores", scores); + addParameter("max-scores", max_scores); + addParameter("num-races-done", num_races); + } + }; // UpdatePlayerRankingRequest + // -------------------------------------------------------------------- + + for (unsigned i = 0; i < race_manager->getNumPlayers(); i++) + { + const uint32_t id = race_manager->getKartInfo(i).getOnlineId(); + SumbitRankingRequest* request = new SumbitRankingRequest + (id, m_scores.at(id), m_max_scores.at(id), + m_num_ranked_races.at(id)); + NetworkConfig::get()->setUserDetails(request, "submit-ranking"); + Log::info("ServerLobby", "Submiting ranking for %s (%d) : %lf, %lf %d", + StringUtils::wideToUtf8( + race_manager->getKartInfo(i).getPlayerName()).c_str(), id, + m_scores.at(id), m_max_scores.at(id), m_num_ranked_races.at(id)); + request->queue(); + } +} // submitRankingsToAddons diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index 2acfe5cc0..71a35569c 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -16,6 +16,7 @@ #include class BareNetworkString; +class NetworkPlayerProfile; class STKPeer; class ServerLobby : public LobbyProtocol @@ -91,15 +92,22 @@ private: std::owner_less > > m_pending_connection; /* Ranking related variables */ - // If updating the base points, update the base points distribution in DB - const float BASE_RANKING_POINTS = 4000.0f; - const float MAX_SCALING_TIME = 600.0f; - const float MAX_POINTS_PER_SECOND = 0.125f; + const double BASE_RANKING_POINTS = 4000.0; + const double MAX_SCALING_TIME = 600.0; + const double MAX_POINTS_PER_SECOND = 0.125; - std::vector m_rankings; // TODO : convert from and to int when communicating with the server. Think to round it correctly - std::vector m_num_ranked_races; - std::vector m_max_ranking; + /** Online id to profile map, handling disconnection in ranked server */ + std::map > m_ranked_players; + + /** Multi-session ranking scores for each current player */ + std::map m_scores; + + /** The maximum ranking scores achieved for each current player */ + std::map m_max_scores; + + /** Number of ranked races done for each current players */ + std::map m_num_ranked_races; // connection management void clientDisconnected(Event* event); @@ -160,11 +168,15 @@ private: const irr::core::stringw& online_name); std::tuple handleVote(); void stopCurrentRace(); + + void getRankingForPlayer(std::shared_ptr p); + void submitRankingsToAddons(); void computeNewRankings(); - float computeRankingFactor(unsigned int player_id); - float distributeBasePoints(unsigned int player_id); - float getModeFactor(); - float getModeSpread(); + void clearDisconnectedRankedPlayer(); + double computeRankingFactor(uint32_t online_id); + double distributeBasePoints(uint32_t online_id); + double getModeFactor(); + double getModeSpread(); public: ServerLobby(); diff --git a/src/network/remote_kart_info.hpp b/src/network/remote_kart_info.hpp index 88ec51740..f604c8f6a 100644 --- a/src/network/remote_kart_info.hpp +++ b/src/network/remote_kart_info.hpp @@ -52,6 +52,7 @@ class RemoteKartInfo bool m_network_player; PerPlayerDifficulty m_difficulty; float m_default_kart_color; + uint32_t m_online_id; public: RemoteKartInfo(int player_id, const std::string& kart_name, const irr::core::stringw& user_name, int host_id, @@ -61,21 +62,21 @@ public: m_host_id(host_id), m_soccer_team(SOCCER_TEAM_NONE), m_network_player(network), m_difficulty(PLAYER_DIFFICULTY_NORMAL), - m_default_kart_color(0.0f) + m_default_kart_color(0.0f), m_online_id(0) {} RemoteKartInfo(const std::string& kart_name) : m_kart_name(kart_name), m_user_name(""), m_local_player_id(-1), m_global_player_id(-1), m_host_id(-1), m_soccer_team(SOCCER_TEAM_NONE), m_network_player(false), m_difficulty(PLAYER_DIFFICULTY_NORMAL), - m_default_kart_color(0.0f) + m_default_kart_color(0.0f), m_online_id(0) {} RemoteKartInfo() : m_kart_name(""), m_user_name(""), m_local_player_id(-1), m_global_player_id(-1), m_host_id(-1), m_soccer_team(SOCCER_TEAM_NONE), m_network_player(false), m_difficulty(PLAYER_DIFFICULTY_NORMAL), - m_default_kart_color(0.0f) + m_default_kart_color(0.0f), m_online_id(0) {} void setKartName(const std::string& n) { m_kart_name = n; } void setPlayerName(const irr::core::stringw& u) { m_user_name = u; } @@ -87,6 +88,7 @@ public: void setDefaultKartColor(float value) { m_default_kart_color = value; } void setPerPlayerDifficulty(PerPlayerDifficulty value) { m_difficulty = value; } + void setOnlineId(uint32_t id) { m_online_id = id; } int getHostId() const { return m_host_id; } int getLocalPlayerId() const { return m_local_player_id; } int getGlobalPlayerId() const { return m_global_player_id; } @@ -96,6 +98,7 @@ public: SoccerTeam getSoccerTeam() const { return m_soccer_team; } PerPlayerDifficulty getDifficulty() const { return m_difficulty; } float getDefaultKartColor() const { return m_default_kart_color; } + uint32_t getOnlineId() const { return m_online_id; } bool operator<(const RemoteKartInfo& other) const { From 79e3d298ce679de0ae001baa223476ce9523fb4f Mon Sep 17 00:00:00 2001 From: Alayan-stk-2 Date: Mon, 4 Jun 2018 01:50:24 +0200 Subject: [PATCH 103/137] Correct finish time for GPs when skipping a race (#3276) * Make human players have a correct finish time when skipping a GP race * Don't compute the estimated finish time twice --- src/modes/standard_race.cpp | 38 ++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/modes/standard_race.cpp b/src/modes/standard_race.cpp index 0fa6f72d3..e701c6e6b 100644 --- a/src/modes/standard_race.cpp +++ b/src/modes/standard_race.cpp @@ -98,36 +98,56 @@ void StandardRace::endRaceEarly() std::vector active_players; // Required for debugging purposes beginSetKartPositions(); + float worse_finish_time = 0.0f; + for (unsigned int i = 1; i <= kart_amount; i++) { int kartid = m_position_index[i-1]; AbstractKart* kart = m_karts[kartid]; + if (kart->hasFinishedRace()) { + if (kart->getFinishTime() > worse_finish_time) + worse_finish_time = kart->getFinishTime(); + // Have to do this to keep endSetKartPosition happy setKartPosition(kartid, kart->getPosition()); - continue; } - if (kart->getController()->isPlayerController()) - { - // Keep active players apart for now - active_players.push_back(kartid); - } else { + float estimated_finish_time = estimateFinishTimeForKart(kart); + if (estimated_finish_time > worse_finish_time) + worse_finish_time = estimated_finish_time; + + // Keep active players apart for now + if (kart->getController()->isPlayerController()) + { + active_players.push_back(kartid); + } // AI karts finish - setKartPosition(kartid, i - (unsigned int) active_players.size()); - kart->finishedRace(estimateFinishTimeForKart(kart)); + else + { + setKartPosition(kartid, i - (unsigned int) active_players.size()); + kart->finishedRace(estimated_finish_time); + } } } // i <= kart_amount + // Now make the active players finish for (unsigned int i = 0; i < active_players.size(); i++) { int kartid = active_players[i]; int position = getNumKarts() - (int) active_players.size() + 1 + i; setKartPosition(kartid, position); - m_karts[kartid]->eliminate(); + float punished_time = estimateFinishTimeForKart(m_karts[kartid]) + + worse_finish_time - WorldStatus::getTime(); + m_karts[kartid]->finishedRace(punished_time); + + // In networked races, endRaceEarly will be called if a player + // takes too much time to finish, so don't mark him as eliminated + if (!isNetworkWorld()) + m_karts[kartid]->eliminate(); } // Finish the active players endSetKartPositions(); setPhase(RESULT_DISPLAY_PHASE); From 42d98f8ff12cda393bdf527fa20178fbf50400ee Mon Sep 17 00:00:00 2001 From: "auria.mg" Date: Sun, 3 Jun 2018 21:04:46 -0400 Subject: [PATCH 104/137] Start on UTF-8 config conversion --- src/achievements/achievement.cpp | 4 +-- src/achievements/achievements_status.cpp | 4 +-- src/challenges/challenge_status.cpp | 12 ++++---- src/challenges/story_mode_status.cpp | 4 +-- src/config/player_manager.cpp | 10 +++---- src/config/player_profile.cpp | 26 +++++++++--------- src/io/utf_writer.cpp | 35 +++++++++++++++++++----- src/io/utf_writer.hpp | 21 ++++++++++++-- src/race/grand_prix_data.cpp | 16 +++++------ src/race/highscore_manager.cpp | 8 +++--- src/race/highscores.cpp | 22 +++++++-------- 11 files changed, 99 insertions(+), 63 deletions(-) diff --git a/src/achievements/achievement.cpp b/src/achievements/achievement.cpp index d6dbb3589..7aa60397b 100644 --- a/src/achievements/achievement.cpp +++ b/src/achievements/achievement.cpp @@ -68,8 +68,8 @@ void Achievement::load(const XMLNode *node) */ void Achievement::save(UTFWriter &out) { - out << L" \n"; diff --git a/src/achievements/achievements_status.cpp b/src/achievements/achievements_status.cpp index bccf443d2..2b51abe7e 100644 --- a/src/achievements/achievements_status.cpp +++ b/src/achievements/achievements_status.cpp @@ -94,14 +94,14 @@ void AchievementsStatus::add(Achievement *achievement) */ void AchievementsStatus::save(UTFWriter &out) { - out << L" \n"; + out << " \n"; std::map::const_iterator i; for(i = m_achievements.begin(); i != m_achievements.end(); i++) { if (i->second != NULL) i->second->save(out); } - out << L" \n"; + out << " \n"; } // save // ---------------------------------------------------------------------------- diff --git a/src/challenges/challenge_status.cpp b/src/challenges/challenge_status.cpp index 1c0c36863..1397422ea 100644 --- a/src/challenges/challenge_status.cpp +++ b/src/challenges/challenge_status.cpp @@ -104,15 +104,15 @@ bool ChallengeStatus::isGrandPrix() void ChallengeStatus::save(UTFWriter& writer) { - writer << L" <" << m_data->getChallengeId(); + writer << " <" << m_data->getChallengeId(); if (isSolved(RaceManager::DIFFICULTY_BEST)) - writer << L" solved=\"best\"/>\n"; + writer << " solved=\"best\"/>\n"; else if (isSolved(RaceManager::DIFFICULTY_HARD)) - writer << L" solved=\"hard\"/>\n"; + writer << " solved=\"hard\"/>\n"; else if (isSolved(RaceManager::DIFFICULTY_MEDIUM)) - writer << L" solved=\"medium\"/>\n"; + writer << " solved=\"medium\"/>\n"; else if (isSolved(RaceManager::DIFFICULTY_EASY)) - writer << L" solved=\"easy\"/>\n"; + writer << " solved=\"easy\"/>\n"; else - writer << L" solved=\"none\"/>\n"; + writer << " solved=\"none\"/>\n"; } // save diff --git a/src/challenges/story_mode_status.cpp b/src/challenges/story_mode_status.cpp index 8fb8d820a..266f6548d 100644 --- a/src/challenges/story_mode_status.cpp +++ b/src/challenges/story_mode_status.cpp @@ -355,7 +355,7 @@ void StoryModeStatus::grandPrixFinished() */ void StoryModeStatus::save(UTFWriter &out) { - out << L" \n"; + out << " \n"; std::map::const_iterator i; for(i = m_challenges_state.begin(); i != m_challenges_state.end(); i++) @@ -363,5 +363,5 @@ void StoryModeStatus::save(UTFWriter &out) if (i->second != NULL) i->second->save(out); } - out << L" \n"; + out << " \n"; } // save diff --git a/src/config/player_manager.cpp b/src/config/player_manager.cpp index 416600b46..f52e470e4 100644 --- a/src/config/player_manager.cpp +++ b/src/config/player_manager.cpp @@ -261,14 +261,14 @@ void PlayerManager::save() std::string filename = file_manager->getUserConfigFile("players.xml"); try { - UTFWriter players_file(filename.c_str()); + UTFWriter players_file(filename.c_str(), false); - players_file << L"\n"; - players_file << L"\n"; + players_file << "\n"; + players_file << "\n"; if(m_current_player) { - players_file << L" getName(true/*ignoreRTL*/)) << L"\"/>\n"; } @@ -278,7 +278,7 @@ void PlayerManager::save() if(!player->isGuestAccount()) player->save(players_file); } - players_file << L"\n"; + players_file << "\n"; players_file.close(); } catch (std::runtime_error& e) diff --git a/src/config/player_profile.cpp b/src/config/player_profile.cpp index a7b10f42a..d9b465e16 100644 --- a/src/config/player_profile.cpp +++ b/src/config/player_profile.cpp @@ -200,21 +200,21 @@ const std::string PlayerProfile::getIconFilename() const */ void PlayerProfile::save(UTFWriter &out) { - out << L" \n"; + out << " saved-user=\"" << m_saved_user_id + << "\" saved-token=\"" << m_saved_token << "\"\n"; + out << " last-online-name=\"" << StringUtils::xmlEncode(m_last_online_name) + << "\" last-was-online=\"" << m_last_was_online << "\"\n"; + out << " remember-password=\"" << m_remember_password << "\"\n"; + out << " default-kart-color=\"" << m_default_kart_color << "\">\n"; { if(m_story_mode_status) m_story_mode_status->save(out); @@ -222,7 +222,7 @@ void PlayerProfile::save(UTFWriter &out) if(m_achievements_status) m_achievements_status->save(out); } - out << L" \n"; + out << " \n"; } // save //------------------------------------------------------------------------------ diff --git a/src/io/utf_writer.cpp b/src/io/utf_writer.cpp index 4232f20ab..9e12f3770 100644 --- a/src/io/utf_writer.cpp +++ b/src/io/utf_writer.cpp @@ -25,27 +25,40 @@ using namespace irr; // ---------------------------------------------------------------------------- -UTFWriter::UTFWriter(const char* dest) +UTFWriter::UTFWriter(const char* dest, bool wide) : m_base(dest, std::ios::out | std::ios::binary) { + m_wide = wide; + if (!m_base.is_open()) { throw std::runtime_error("Failed to open file for writing : " + std::string(dest)); } - // FIXME: make sure to properly handle endianness - // UTF-16 BOM is 0xFEFF; UTF-32 BOM is 0x0000FEFF. So this works in either case - wchar_t BOM = 0xFEFF; + if (wide) + { + // FIXME: make sure to properly handle endianness + // UTF-16 BOM is 0xFEFF; UTF-32 BOM is 0x0000FEFF. So this works in either case + wchar_t BOM = 0xFEFF; - m_base.write((char *) &BOM, sizeof(wchar_t)); + m_base.write((char *) &BOM, sizeof(wchar_t)); + } } // UTFWriter // ---------------------------------------------------------------------------- UTFWriter& UTFWriter::operator<< (const irr::core::stringw& txt) { - m_base.write((char *) txt.c_str(), txt.size() * sizeof(wchar_t)); + if (m_wide) + { + m_base.write((char *) txt.c_str(), txt.size() * sizeof(wchar_t)); + } + else + { + std::string utf8 = StringUtils::wideToUtf8(txt); + operator<<(utf8); + } return *this; } // operator<< (stringw) @@ -53,7 +66,15 @@ UTFWriter& UTFWriter::operator<< (const irr::core::stringw& txt) UTFWriter& UTFWriter::operator<< (const wchar_t*txt) { - m_base.write((char *) txt, wcslen(txt) * sizeof(wchar_t)); + if (m_wide) + { + m_base.write((char *) txt, wcslen(txt) * sizeof(wchar_t)); + } + else + { + std::string utf8 = StringUtils::wideToUtf8(txt); + operator<<(utf8); + } return *this; } // operator<< (wchar_t) diff --git a/src/io/utf_writer.hpp b/src/io/utf_writer.hpp index 02f8ebcd7..9ac71f7a5 100644 --- a/src/io/utf_writer.hpp +++ b/src/io/utf_writer.hpp @@ -34,9 +34,12 @@ class UTFWriter { std::ofstream m_base; + + /** If true, use utf-16/32 (obsolete) */ + bool m_wide; public: - UTFWriter(const char* dest); + UTFWriter(const char* dest, bool wide); void close(); UTFWriter& operator<< (const irr::core::stringw& txt); @@ -44,12 +47,24 @@ public: // ------------------------------------------------------------------------ UTFWriter& operator<< (const char *txt) { - return operator<<(irr::core::stringw(txt)); + if (m_wide) + { + return operator<<(irr::core::stringw(txt)); + } + else + { + m_base.write((char *)txt, strlen(txt)); + return *this; + } + } // operator<<(char*) // ------------------------------------------------------------------------ UTFWriter& operator<< (const std::string &txt) { - return operator<< (irr::core::stringw(txt.c_str())); + if (m_wide) + return operator<<(irr::core::stringw(txt.c_str())); + else + return operator<<(txt.c_str()); } // operator<<(std::string) // ------------------------------------------------------------------------ UTFWriter& operator<< (const bool &b) diff --git a/src/race/grand_prix_data.cpp b/src/race/grand_prix_data.cpp index 1a662f091..9e7c1460b 100644 --- a/src/race/grand_prix_data.cpp +++ b/src/race/grand_prix_data.cpp @@ -361,20 +361,20 @@ bool GrandPrixData::writeToFile() { try { - UTFWriter file(m_filename.c_str()); + UTFWriter file(m_filename.c_str(), false); if (file.is_open()) { - file << L"\n\n\n"; + file << "\n\n\n"; for (unsigned int i = 0; i < m_tracks.size(); i++) { file << - L"\t\n"; + "\t\n"; } - file << L"\n\n"; + file << "\n\n"; file.close(); diff --git a/src/race/highscore_manager.cpp b/src/race/highscore_manager.cpp index 92eff9094..e0fc094f4 100644 --- a/src/race/highscore_manager.cpp +++ b/src/race/highscore_manager.cpp @@ -150,15 +150,15 @@ void HighscoreManager::saveHighscores() try { - UTFWriter highscore_file(m_filename.c_str()); - highscore_file << L"\n"; - highscore_file << L"\n"; + UTFWriter highscore_file(m_filename.c_str(), false); + highscore_file << "\n"; + highscore_file << "\n"; for(unsigned int i=0; iwriteEntry(highscore_file); } - highscore_file << L"\n"; + highscore_file << "\n"; highscore_file.close(); } catch(std::exception &e) diff --git a/src/race/highscores.cpp b/src/race/highscores.cpp index bc5a4f2c5..9f7ca37f6 100644 --- a/src/race/highscores.cpp +++ b/src/race/highscores.cpp @@ -113,25 +113,25 @@ void Highscores::writeEntry(UTFWriter &writer) one_is_set |= m_time[i]>=0; if(!one_is_set) return; - writer << L" \n"; + writer << " \n"; for(int i=0; i 0.0f) { assert(m_kart_name[i].size() > 0); - writer << L" \n"; + writer << " \n"; } } // for i - writer << L" \n"; + writer << " \n"; } // writeEntry // ----------------------------------------------------------------------------- From 2fbf0ab49f14823105e72011cbc85239fb0c4751 Mon Sep 17 00:00:00 2001 From: Benau Date: Mon, 4 Jun 2018 09:28:38 +0800 Subject: [PATCH 105/137] Avoid unnecessary slow down and mutex locking in server lobby --- src/network/protocols/server_lobby.cpp | 184 ++++++++++++++---------- src/network/protocols/server_lobby.hpp | 15 +- src/states_screens/networking_lobby.cpp | 1 - src/utils/string_utils.cpp | 4 + 4 files changed, 122 insertions(+), 82 deletions(-) diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 96d2d4055..2f0448814 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -87,6 +87,7 @@ */ ServerLobby::ServerLobby() : LobbyProtocol(NULL) { + m_server_owner_id.store(-1); m_has_created_server_id_file = false; setHandleDisconnections(true); m_state = SET_PUBLIC_ADDRESS; @@ -106,7 +107,7 @@ ServerLobby::~ServerLobby() { if (m_server_registered) { - unregisterServer(); + unregisterServer(true/*now*/); } } // ~ServerLobby @@ -158,7 +159,6 @@ bool ServerLobby::notifyEvent(Event* event) message_type); switch (message_type) { - case LE_REQUEST_BEGIN: startSelection(event); break; case LE_RACE_FINISHED_ACK: playerFinishedResult(event); break; default: Log::error("ServerLobby", "Unknown message type %d - ignored.", message_type); @@ -175,6 +175,8 @@ void ServerLobby::handleChat(Event* event) Log::warn("ServerLobby", "Unauthorized peer wants to chat."); return; } + if (!checkDataSize(event, 1)) return; + core::stringw message; event->data().decodeString16(&message); if (message.size() > 0) @@ -190,10 +192,8 @@ void ServerLobby::handleChat(Event* event) //----------------------------------------------------------------------------- void ServerLobby::kickHost(Event* event) { - std::unique_lock lock(m_connection_mutex); if (m_server_owner.lock() != event->getPeerSP()) return; - lock.unlock(); if (!checkDataSize(event, 4)) return; NetworkString& data = event->data(); uint32_t host_id = data.getUInt32(); @@ -222,6 +222,7 @@ bool ServerLobby::notifyEventAsynchronous(Event* event) case LE_STARTED_RACE: startedRaceOnClient(event); break; case LE_VOTE: playerVote(event); break; case LE_KICK_HOST: kickHost(event); break; + case LE_REQUEST_BEGIN: startSelection(event); break; case LE_CHAT: handleChat(event); break; default: break; } // switch @@ -251,6 +252,9 @@ void ServerLobby::createServerIdFile() /** Find out the public IP server or poll STK server asynchronously. */ void ServerLobby::asynchronousUpdate() { + // Check if server owner has left + updateServerOwner(); + switch (m_state.load()) { case SET_PUBLIC_ADDRESS: @@ -285,7 +289,7 @@ void ServerLobby::asynchronousUpdate() break; } // Register this server with the STK server. This will block - // this thread, but there is no need for the protocol manager + // this thread, because there is no need for the protocol manager // to react to any requests before the server is registered. registerServer(); if (m_server_registered) @@ -348,7 +352,6 @@ void ServerLobby::asynchronousUpdate() { m_game_setup->setRace(std::get<0>(result), std::get<1>(result), std::get<2>(result)); - std::lock_guard lock(m_connection_mutex); // Remove disconnected player (if any) one last time m_game_setup->update(true); m_game_setup->sortPlayersForGrandPrix(); @@ -417,8 +420,6 @@ void ServerLobby::update(int ticks) setup(); } - // Check if server owner has left - updateServerOwner(); if (m_game_setup) { // Remove disconnected players if in these two states @@ -469,7 +470,7 @@ void ServerLobby::update(int ticks) std::lock_guard lock(m_connection_mutex); m_state = NetworkConfig::get()->isLAN() ? ACCEPTING_CLIENTS : REGISTER_SELF_ADDRESS; - updatePlayerList(); + updatePlayerList(true/*force_update*/); NetworkString* server_info = getNetworkString(); server_info->setSynchronous(true); server_info->addUInt8(LE_SERVER_INFO); @@ -535,38 +536,28 @@ void ServerLobby::registerServer() //----------------------------------------------------------------------------- /** Unregister this server (i.e. its public address) with the STK server, - * currently when karts enter kart selection screen it will be done. + * currently when karts enter kart selection screen it will be done or quit + * stk. */ -void ServerLobby::unregisterServer() +void ServerLobby::unregisterServer(bool now) { - Online::XMLRequest* request = new Online::XMLRequest(); + Online::XMLRequest* request = + new Online::XMLRequest(!now/*manage memory*/); NetworkConfig::get()->setServerDetails(request, "stop"); request->addParameter("address", m_server_address.getIP()); request->addParameter("port", m_server_address.getPort()); Log::info("ServerLobby", "Unregister server address %s", m_server_address.toString().c_str()); - request->executeNow(); - - const XMLNode * result = request->getXMLData(); - std::string rec_success; - - if (result->get("success", &rec_success)) + // No need to check for result as server will be auto-cleared anyway + // when no polling is done + if (now) { - if (rec_success == "yes") - { - Log::info("ServerLobby", "Server is now unregister."); - } - else - { - Log::error("ServerLobby", "Fail to unregister server."); - } + request->executeNow(); + delete request; } else - { - Log::error("ServerLobby", "Fail to stop server."); - } - delete request; + request->queue(); } // unregisterServer @@ -591,8 +582,6 @@ void ServerLobby::signalRaceStartToClients() */ void ServerLobby::startSelection(const Event *event) { - std::lock_guard lock(m_connection_mutex); - if (m_state != ACCEPTING_CLIENTS) { Log::warn("ServerLobby", @@ -608,9 +597,17 @@ void ServerLobby::startSelection(const Event *event) return; } + // Drop all pending players + for (auto& p : m_pending_connection) + { + if (auto peer = p.first.lock()) + peer->disconnect(); + } + m_pending_connection.clear(); + if (m_server_registered) { - unregisterServer(); + unregisterServer(false/*now*/); m_server_registered = false; } @@ -679,43 +676,64 @@ void ServerLobby::checkIncomingConnectionRequests() // Now poll the stk server last_poll_time = StkTime::getRealTime(); - Online::XMLRequest* request = new Online::XMLRequest(); - NetworkConfig::get()->setServerDetails(request, "poll-connection-requests"); + // ======================================================================== + class PollServerRequest : public Online::XMLRequest + { + private: + std::weak_ptr m_server_lobby; + protected: + virtual void afterOperation() + { + Online::XMLRequest::afterOperation(); + const XMLNode *result = getXMLData(); + std::string success; + + if (!result->get("success", &success) || success != "yes") + { + Log::error("ServerLobby", "Cannot retrieve the list."); + return; + } + + // Now start a ConnectToPeer protocol for each connection request + const XMLNode * users_xml = result->getNode("users"); + std::map > keys; + for (unsigned int i = 0; i < users_xml->getNumNodes(); i++) + { + uint32_t addr, id; + uint16_t port; + users_xml->getNode(i)->get("ip", &addr); + users_xml->getNode(i)->get("port", &port); + users_xml->getNode(i)->get("id", &id); + users_xml->getNode(i)->get("aes-key", &std::get<0>(keys[id])); + users_xml->getNode(i)->get("iv", &std::get<1>(keys[id])); + users_xml->getNode(i)->get("username", &std::get<2>(keys[id])); + std::get<3>(keys[id]) = false; + std::make_shared(TransportAddress(addr, port)) + ->requestStart(); + } + auto sl = m_server_lobby.lock(); + if (keys.empty() || !sl || sl->m_state.load() != ACCEPTING_CLIENTS) + return; + sl->addAndReplaceKeys(keys); + } + public: + PollServerRequest(std::shared_ptr sl) + : XMLRequest(true), m_server_lobby(sl) {} + }; // PollServerRequest + // ======================================================================== + + PollServerRequest* request = new PollServerRequest( + std::dynamic_pointer_cast(shared_from_this())); + NetworkConfig::get()->setServerDetails(request, + "poll-connection-requests"); const TransportAddress &addr = STKHost::get()->getPublicAddress(); request->addParameter("address", addr.getIP() ); request->addParameter("port", addr.getPort()); request->addParameter("current_players", m_game_setup->getPlayerCount()); + request->queue(); - request->executeNow(); - assert(request->isDone()); - - const XMLNode *result = request->getXMLData(); - std::string success; - - if (!result->get("success", &success) || success != "yes") - { - Log::error("ServerLobby", "Cannot retrieve the list."); - return; - } - - // Now start a ConnectToPeer protocol for each connection request - const XMLNode * users_xml = result->getNode("users"); - for (unsigned int i = 0; i < users_xml->getNumNodes(); i++) - { - uint32_t addr, id; - uint16_t port; - users_xml->getNode(i)->get("ip", &addr); - users_xml->getNode(i)->get("port", &port); - users_xml->getNode(i)->get("id", &id); - users_xml->getNode(i)->get("aes-key", &std::get<0>(m_keys[id])); - users_xml->getNode(i)->get("iv", &std::get<1>(m_keys[id])); - users_xml->getNode(i)->get("username", &std::get<2>(m_keys[id])); - std::get<3>(m_keys[id]) = false; - std::make_shared(TransportAddress(addr, port)) - ->requestStart(); - } - delete request; } // checkIncomingConnectionRequests //----------------------------------------------------------------------------- @@ -1004,7 +1022,6 @@ void ServerLobby::stopCurrentRace() */ void ServerLobby::clientDisconnected(Event* event) { - std::lock_guard lock(m_connection_mutex); auto players_on_peer = event->getPeer()->getPlayerProfiles(); if (players_on_peer.empty()) return; @@ -1049,6 +1066,8 @@ void ServerLobby::connectionRequested(Event* event) std::lock_guard lock(m_connection_mutex); std::shared_ptr peer = event->getPeerSP(); NetworkString& data = event->data(); + if (!checkDataSize(event, 14)) return; + peer->cleanPlayerProfiles(); // can we add the player ? @@ -1212,6 +1231,8 @@ void ServerLobby::handleUnencryptedConnection(std::shared_ptr peer, BareNetworkString& data, uint32_t online_id, const core::stringw& online_name) { + if (data.size() < 2) return; + // Check for password std::string password; data.decodeString(&password); @@ -1304,9 +1325,9 @@ void ServerLobby::handleUnencryptedConnection(std::shared_ptr peer, } // handleUnencryptedConnection //----------------------------------------------------------------------------- -void ServerLobby::updatePlayerList() +void ServerLobby::updatePlayerList(bool force_update) { - if (m_state.load() > ACCEPTING_CLIENTS) + if (m_state.load() > ACCEPTING_CLIENTS && !force_update) return; auto all_profiles = STKHost::get()->getAllPlayerProfiles(); NetworkString* pl = getNetworkString(); @@ -1317,7 +1338,7 @@ void ServerLobby::updatePlayerList() pl->addUInt32(profile->getHostId()).addUInt32(profile->getOnlineId()) .encodeString(profile->getName()); uint8_t server_owner = 0; - if (m_server_owner.lock() == profile->getPeer()) + if (m_server_owner_id.load() == profile->getPeer()->getHostId()) server_owner = 1; pl->addUInt8(server_owner); pl->addUInt8(profile->getPerPlayerDifficulty()); @@ -1344,8 +1365,6 @@ void ServerLobby::updateServerOwner() }); std::shared_ptr owner; - std::lock_guard lock(m_connection_mutex); - // Make sure no one access the weak pointer or adding player to peers for (auto peer: peers) { // Only 127.0.0.1 can be server owner in case of graphics-client-server @@ -1364,6 +1383,7 @@ void ServerLobby::updateServerOwner() owner->sendPacket(ns); delete ns; m_server_owner = owner; + m_server_owner_id.store(owner->getHostId()); updatePlayerList(); } } // updateServerOwner @@ -1374,7 +1394,6 @@ void ServerLobby::updateServerOwner() */ void ServerLobby::kartSelectionRequested(Event* event) { - std::lock_guard lock(m_connection_mutex); if (m_state != SELECTING || m_game_setup->isGrandPrixStarted()) { Log::warn("ServerLobby", "Received kart selection while in state %d.", @@ -1395,18 +1414,12 @@ void ServerLobby::kartSelectionRequested(Event* event) data.decodeString(&kart); if (m_available_kts.first.find(kart) == m_available_kts.first.end()) { - // Will be reset to a random one later - Log::debug("ServerLobby", "Player %d from peer %d chose unknown " - "kart %s, use a random one", i, peer->getHostId(), - kart.c_str()); continue; } else { peer->getPlayerProfiles()[i]->setKartName(kart); } - Log::debug("ServerLobby", "Player %d from peer %d chose %s", i, - peer->getHostId(), kart.c_str()); } } // kartSelectionRequested @@ -1668,6 +1681,8 @@ bool ServerLobby::waitingForPlayers() const //----------------------------------------------------------------------------- void ServerLobby::handlePendingConnection() { + std::lock_guard lock(m_keys_mutex); + for (auto it = m_pending_connection.begin(); it != m_pending_connection.end();) { @@ -1766,7 +1781,7 @@ void ServerLobby::submitRankingsToAddons() if (!race_manager->modeHasLaps()) return; - // -------------------------------------------------------------------- + // ======================================================================== class SumbitRankingRequest : public Online::XMLRequest { public: @@ -1779,8 +1794,19 @@ void ServerLobby::submitRankingsToAddons() addParameter("max-scores", max_scores); addParameter("num-races-done", num_races); } + virtual void afterOperation() + { + Online::XMLRequest::afterOperation(); + const XMLNode* result = getXMLData(); + std::string rec_success; + if (!(result->get("success", &rec_success) && + rec_success == "yes")) + { + Log::error("ServerLobby", "Failed to submit scores."); + } + } }; // UpdatePlayerRankingRequest - // -------------------------------------------------------------------- + // ======================================================================== for (unsigned i = 0; i < race_manager->getNumPlayers(); i++) { diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index 71a35569c..db74a140c 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -45,6 +45,8 @@ private: * (disconnected). */ std::weak_ptr m_server_owner; + std::atomic m_server_owner_id; + /** Available karts and tracks for all clients, this will be initialized * with data in server first. */ std::pair, std::set > m_available_kts; @@ -83,6 +85,8 @@ private: TransportAddress m_server_address; + std::mutex m_keys_mutex; + std::map > m_keys; @@ -122,9 +126,9 @@ private: void startedRaceOnClient(Event *event); void kickHost(Event* event); void handleChat(Event* event); - void unregisterServer(); + void unregisterServer(bool now); void createServerIdFile(); - void updatePlayerList(); + void updatePlayerList(bool force_update = false); void updateServerOwner(); bool checkPeersReady() const { @@ -155,6 +159,13 @@ private: } } } + void addAndReplaceKeys(const std::map >& new_keys) + { + std::lock_guard lock(m_keys_mutex); + for (auto& k : new_keys) + m_keys[k.first] = k.second; + } void handlePendingConnection(); void handleUnencryptedConnection(std::shared_ptr peer, BareNetworkString& data, diff --git a/src/states_screens/networking_lobby.cpp b/src/states_screens/networking_lobby.cpp index 56138101f..340ec5b9f 100644 --- a/src/states_screens/networking_lobby.cpp +++ b/src/states_screens/networking_lobby.cpp @@ -327,7 +327,6 @@ void NetworkingLobby::eventCallback(Widget* widget, const std::string& name, { // Send a message to the server to start NetworkString start(PROTOCOL_LOBBY_ROOM); - start.setSynchronous(true); start.addUInt8(LobbyProtocol::LE_REQUEST_BEGIN); STKHost::get()->sendToServer(&start, true); } diff --git a/src/utils/string_utils.cpp b/src/utils/string_utils.cpp index 43b88bb11..b3a1b2957 100644 --- a/src/utils/string_utils.cpp +++ b/src/utils/string_utils.cpp @@ -952,8 +952,12 @@ namespace StringUtils bmem = BIO_push(b64, bmem); BIO_set_flags(bmem, BIO_FLAGS_BASE64_NO_NL); +#ifdef DEBUG size_t read_l = BIO_read(bmem, result.data(), input.size()); assert(read_l == decode_len); +#else + BIO_read(bmem, result.data(), input.size()); +#endif BIO_free_all(bmem); return result; From aa1bb720e397cdc29705a492335c5f34d3284fdf Mon Sep 17 00:00:00 2001 From: Benau Date: Mon, 4 Jun 2018 09:41:50 +0800 Subject: [PATCH 106/137] No joinning of password-proected server if no password is given --- src/states_screens/dialogs/server_info_dialog.cpp | 7 +++++++ src/states_screens/dialogs/server_info_dialog.hpp | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/states_screens/dialogs/server_info_dialog.cpp b/src/states_screens/dialogs/server_info_dialog.cpp index a2e27a87a..20447f6dd 100644 --- a/src/states_screens/dialogs/server_info_dialog.cpp +++ b/src/states_screens/dialogs/server_info_dialog.cpp @@ -97,6 +97,8 @@ void ServerInfoDialog::requestJoin() if (m_server->isPasswordProtected()) { assert(m_password != NULL); + if (m_password->getText().empty()) + return; NetworkConfig::get()->setPassword( StringUtils::wideToUtf8(m_password->getText())); } @@ -157,6 +159,11 @@ bool ServerInfoDialog::onEscapePressed() // ----------------------------------------------------------------------------- void ServerInfoDialog::onUpdate(float dt) { + if (m_password && m_password->getText().empty()) + m_join_widget->setActive(false); + else if (!m_join_widget->isActivated()) + m_join_widget->setActive(true); + // It's unsafe to delete from inside the event handler so we do it here if (m_self_destroy) { diff --git a/src/states_screens/dialogs/server_info_dialog.hpp b/src/states_screens/dialogs/server_info_dialog.hpp index 38b08b985..9f5d69609 100644 --- a/src/states_screens/dialogs/server_info_dialog.hpp +++ b/src/states_screens/dialogs/server_info_dialog.hpp @@ -48,7 +48,7 @@ private: GUIEngine::RibbonWidget *m_options_widget; - /** The joinb button. */ + /** The join button. */ GUIEngine::IconButtonWidget *m_join_widget; /** The cancel button. */ From c599195eeb53acbf0e40215f4aeca5eb6145c94f Mon Sep 17 00:00:00 2001 From: "auria.mg" Date: Sun, 3 Jun 2018 21:48:34 -0400 Subject: [PATCH 107/137] XML fixes --- src/config/player_profile.cpp | 2 +- src/race/grand_prix_data.cpp | 2 +- src/race/highscores.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config/player_profile.cpp b/src/config/player_profile.cpp index d9b465e16..913c70aa5 100644 --- a/src/config/player_profile.cpp +++ b/src/config/player_profile.cpp @@ -87,7 +87,7 @@ PlayerProfile::PlayerProfile(const XMLNode* node) node->get("saved-session", &m_saved_session ); node->get("saved-user", &m_saved_user_id ); node->get("saved-token", &m_saved_token ); - node->get("last-online-name", &m_last_online_name ); + node->getAndDecode("last-online-name", &m_last_online_name ); node->get("last-was-online", &m_last_was_online ); node->get("remember-password", &m_remember_password); node->get("icon-filename", &m_icon_filename ); diff --git a/src/race/grand_prix_data.cpp b/src/race/grand_prix_data.cpp index 9e7c1460b..90ca62cb3 100644 --- a/src/race/grand_prix_data.cpp +++ b/src/race/grand_prix_data.cpp @@ -270,7 +270,7 @@ void GrandPrixData::reload() throw std::runtime_error("Wrong root node name"); } - if (!root->get("name", &m_name)) + if (!root->getAndDecode("name", &m_name)) { Log::error("GrandPrixData", "Error while trying to read grandprix file '%s': " diff --git a/src/race/highscores.cpp b/src/race/highscores.cpp index 9f7ca37f6..54daf277e 100644 --- a/src/race/highscores.cpp +++ b/src/race/highscores.cpp @@ -83,7 +83,7 @@ void Highscores::readEntry(const XMLNode &node) { const XMLNode *entry = node.getNode(i); entry->get("time", &m_time[i] ); - entry->get("name", &m_name[i] ); + entry->getAndDecode("name", &m_name[i] ); entry->get("kartname", &m_kart_name[i] ); // a non-empty entry needs a non-empty kart name. From e326d7bcfdc0ef607b32cc0e6bc4f1c31c213b66 Mon Sep 17 00:00:00 2001 From: Benau Date: Mon, 4 Jun 2018 11:12:03 +0800 Subject: [PATCH 108/137] Fix valgrind uninitialized --- src/network/protocols/client_lobby.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/network/protocols/client_lobby.cpp b/src/network/protocols/client_lobby.cpp index 4149f55bc..eeeacce1a 100644 --- a/src/network/protocols/client_lobby.cpp +++ b/src/network/protocols/client_lobby.cpp @@ -69,6 +69,7 @@ engine. ClientLobby::ClientLobby(const TransportAddress& a, std::shared_ptr s) : LobbyProtocol(NULL) { + m_state.store(NONE); m_server_address = a; m_server = s; setHandleDisconnections(true); From ec51a2dfbb9f878515c67d1d1f327f2362d1f1df Mon Sep 17 00:00:00 2001 From: Benau Date: Mon, 4 Jun 2018 11:12:11 +0800 Subject: [PATCH 109/137] Fix memory leak --- src/io/file_manager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/io/file_manager.cpp b/src/io/file_manager.cpp index eb84db104..dd73f0f49 100644 --- a/src/io/file_manager.cpp +++ b/src/io/file_manager.cpp @@ -482,6 +482,7 @@ XMLNode *FileManager::createXMLTreeFromString(const std::string & content) io::IXMLReader * reader = m_file_system->createXMLReader(ireadfile); XMLNode* node = new XMLNode(reader); reader->drop(); + ireadfile->drop(); return node; } catch (std::runtime_error& e) From d89c65b0c9ddd20ed3b22dde0122432c89263d38 Mon Sep 17 00:00:00 2001 From: Benau Date: Mon, 4 Jun 2018 13:25:10 +0800 Subject: [PATCH 110/137] Remove token in network string --- src/config/user_config.hpp | 3 ++ src/network/event.cpp | 23 ++++++++ src/network/network_string.cpp | 22 -------- src/network/network_string.hpp | 52 ++++--------------- src/network/protocol.cpp | 9 ++-- src/network/protocol.hpp | 6 +-- src/network/protocols/client_lobby.cpp | 7 --- src/network/protocols/client_lobby.hpp | 2 + .../protocols/game_events_protocol.cpp | 6 +-- src/network/protocols/game_protocol.cpp | 2 +- src/network/protocols/server_lobby.cpp | 46 ++++++---------- src/network/stk_host.cpp | 34 ++++++------ src/network/stk_host.hpp | 2 - src/network/stk_peer.cpp | 4 +- src/network/stk_peer.hpp | 32 +++--------- 15 files changed, 85 insertions(+), 165 deletions(-) diff --git a/src/config/user_config.hpp b/src/config/user_config.hpp index 8e8c09a39..72c2ff2b6 100644 --- a/src/config/user_config.hpp +++ b/src/config/user_config.hpp @@ -717,6 +717,9 @@ namespace UserConfigParams PARAM_PREFIX FloatUserConfigParam m_voting_timeout PARAM_DEFAULT(FloatUserConfigParam(20.0f, "voting-timeout", &m_network_group, "Timeout in seconds for voting tracks in server.")); + PARAM_PREFIX FloatUserConfigParam m_validation_timeout + PARAM_DEFAULT(FloatUserConfigParam(20.0f, "validation-timeout", + &m_network_group, "Timeout in seconds for validation of clients.")); PARAM_PREFIX IntUserConfigParam m_server_max_players PARAM_DEFAULT(IntUserConfigParam(12, "server_max_players", &m_network_group, "Maximum number of players on the server.")); diff --git a/src/network/event.cpp b/src/network/event.cpp index 4683f4d9d..f9bdc7f41 100644 --- a/src/network/event.cpp +++ b/src/network/event.cpp @@ -19,6 +19,7 @@ #include "network/event.hpp" #include "network/crypto.hpp" +#include "network/protocols/client_lobby.hpp" #include "network/stk_peer.hpp" #include "utils/log.hpp" #include "utils/time.hpp" @@ -28,6 +29,16 @@ /** \brief Constructor * \param event : The event that needs to be translated. */ + +// ============================================================================ +constexpr bool isConnectionRequestPacket(unsigned char* data, size_t length) +{ + // Connection request is not synchronous + return length < 2 ? false : (uint8_t)data[0] == PROTOCOL_LOBBY_ROOM && + (uint8_t)data[1] == LobbyProtocol::LE_CONNECTION_REQUESTED; +} // isConnectionRequestPacket + +// ============================================================================ Event::Event(ENetEvent* event, std::shared_ptr peer) { m_arrival_time = (double)StkTime::getTimeSinceEpoch(); @@ -52,6 +63,18 @@ Event::Event(ENetEvent* event, std::shared_ptr peer) } if (m_type == EVENT_TYPE_MESSAGE) { + if (!m_peer->isValidated() && !isConnectionRequestPacket + (event->packet->data, event->packet->dataLength)) + { + throw std::runtime_error("Invalid packet before validation."); + } + + auto cl = LobbyProtocol::get(); + if (event->channelID == EVENT_CHANNEL_UNENCRYPTED && (!cl || + (cl && !cl->waitingForServerRespond()))) + { + throw std::runtime_error("Unencrypted content at wrong state."); + } if (m_peer->getCrypto() && event->channelID == EVENT_CHANNEL_NORMAL) { m_data = m_peer->getCrypto()->decryptRecieve(event->packet); diff --git a/src/network/network_string.cpp b/src/network/network_string.cpp index 217d4abbd..0d2bfee50 100644 --- a/src/network/network_string.cpp +++ b/src/network/network_string.cpp @@ -37,28 +37,6 @@ void NetworkString::unitTesting() s.setSynchronous(false); assert(!s.isSynchronous()); - uint32_t token = 0x12345678; - // Check token setting and reading - s.setToken(token); - assert(s.getToken()==token); - assert(s.getToken()!=0x87654321); - - // Append some values from the message - s.addUInt16(12345); - s.addFloat(1.2345f); - - // Since this string was not received, we need to skip the type and token explicitly. - s.skip(5); - assert(s.getUInt16() == 12345); - float f = s.getFloat(); - assert(f==1.2345f); - - // Check modifying a token in an already assembled message - uint32_t new_token = 0x87654321; - s.setToken(new_token); - assert(s.getToken()!=token); - assert(s.getToken()==new_token); - // Check log message format BareNetworkString slog(28); for(unsigned int i=0; i<28; i++) diff --git a/src/network/network_string.hpp b/src/network/network_string.hpp index 0623a6b46..4488e5d43 100644 --- a/src/network/network_string.hpp +++ b/src/network/network_string.hpp @@ -45,8 +45,7 @@ typedef unsigned char uchar; * This class allows you to easily create and parse 8-bit strings, has * functions to add and read other data types (e.g. int, strings). It does * not enforce any structure on the sequence (NetworkString uses this as - * a base class, and enforces a protocol type in the first byte, and a - * 4-byte authentication token in bytes 2-5) + * a base class, and enforces a protocol type in the first byte) */ class BareNetworkString @@ -62,8 +61,8 @@ protected: /** To avoid copying the buffer when bytes are deleted (which only * happens at the front), use an offset index. All positions given * by the user will be relative to this index. Note that the type - * should be left as signed, otherwise certain arithmetic (e.g. - * 1-m_current_offset in checkToken() will be done unsigned). + * should be left as signed, otherwise certain arithmetic will be done + * unsigned). */ mutable int m_current_offset; @@ -359,7 +358,6 @@ public: * manager thread. * bits 6-0: The protocol ID, which identifies the receiving protocol * for this message. - * Byte 1-4: A token to authenticate the sender. * * Otherwise this class offers template functions to add arbitrary variables, * and retrieve them again. It kept the functionality of 'removing' bytes @@ -373,31 +371,30 @@ public: static void unitTesting(); /** Constructor for a message to be sent. It sets the - * protocol type of this message. It adds 5 bytes to the capacity: - * 1 byte for the protocol type, and 4 bytes for the token. */ + * protocol type of this message. It adds 1 byte to the capacity: + * 1 byte for the protocol type. */ NetworkString(ProtocolType type, int capacity=16) - : BareNetworkString(capacity+5) + : BareNetworkString(capacity+1) { m_buffer.push_back(type); - addUInt32(0); // add dummy token for now } // NetworkString // ------------------------------------------------------------------------ /** Constructor for a received message. It automatically ignored the first - * 5 bytes which contain the type and token. Those will be accessed using + * 5 bytes which contain the type. Those will be accessed using * special functions. */ NetworkString(const uint8_t *data, int len) : BareNetworkString((char*)data, len) { - m_current_offset = 5; // ignore type and token + m_current_offset = 1; // ignore type } // NetworkString // ------------------------------------------------------------------------ /** Empties the string, but does not reset the pre-allocated size. */ void clear() { - m_buffer.erase(m_buffer.begin() + 5, m_buffer.end()); - m_current_offset = 5; + m_buffer.erase(m_buffer.begin() + 1, m_buffer.end()); + m_current_offset = 1; } // clear // ------------------------------------------------------------------------ /** Returns the protocol type of this message. */ @@ -422,35 +419,6 @@ public: { return (m_buffer[0] & PROTOCOL_SYNCHRONOUS) == PROTOCOL_SYNCHRONOUS; } // isSynchronous - // ------------------------------------------------------------------------ - /** Sets a token for a message. Note that the token in an already - * assembled message might be updated (e.g. if the same message is sent - * from the server to a set of clients). */ - void setToken(uint32_t token) - { - // Make sure there is enough space for the token: - if(m_buffer.size()<5) - m_buffer.resize(5); - - m_buffer[1] = (token >> 24) & 0xff; - m_buffer[2] = (token >> 16) & 0xff; - m_buffer[3] = (token >> 8) & 0xff; - m_buffer[4] = token & 0xff; - } // setToken - - // ------------------------------------------------------------------------ - /** Returns if the security token of this message is the same as the - * specified token. */ - uint32_t getToken() const - { - // We need to reset the current position to 1 so that we can use the - // existing read 4 byte integer function to get the token. - int save_pos = m_current_offset; - m_current_offset = 1; - uint32_t token = getUInt32(); - m_current_offset = save_pos; - return token; - } // getToken }; // class NetworkString diff --git a/src/network/protocol.cpp b/src/network/protocol.cpp index 17c597725..a7572fb1a 100644 --- a/src/network/protocol.cpp +++ b/src/network/protocol.cpp @@ -111,16 +111,15 @@ void Protocol::requestTerminate() } // requestTerminate // ---------------------------------------------------------------------------- -/** Sends a message to all peers, inserting the peer's token into the message. +/** Sends a message to all peers, encrypt the message if needed. * The message is composed of a 1-byte message (usually the message type) - * followed by the token of this client and then actual message). + * followed by the actual message. * \param message The actual message content. */ -void Protocol::sendMessageToPeersChangingToken(NetworkString *message, - bool reliable) +void Protocol::sendMessageToPeers(NetworkString *message, bool reliable) { STKHost::get()->sendPacketToAllPeers(message, reliable); -} // sendMessageToPeersChangingToken +} // sendMessageToPeers // ---------------------------------------------------------------------------- /** Sends a message from a client to the server. diff --git a/src/network/protocol.hpp b/src/network/protocol.hpp index 67e5179ba..d7b10628e 100644 --- a/src/network/protocol.hpp +++ b/src/network/protocol.hpp @@ -125,10 +125,8 @@ public: /// functions to check incoming data easily NetworkString* getNetworkString(size_t capacity = 16); bool checkDataSize(Event* event, unsigned int minimum_size); - void sendMessageToPeersChangingToken(NetworkString *message, - bool reliable = true); - void sendToServer(NetworkString *message, - bool reliable = true); + void sendMessageToPeers(NetworkString *message, bool reliable = true); + void sendToServer(NetworkString *message, bool reliable = true); void requestStart(); void requestPause(); void requestUnpause(); diff --git a/src/network/protocols/client_lobby.cpp b/src/network/protocols/client_lobby.cpp index eeeacce1a..7c258e959 100644 --- a/src/network/protocols/client_lobby.cpp +++ b/src/network/protocols/client_lobby.cpp @@ -444,19 +444,12 @@ void ClientLobby::connectionAccepted(Event* event) if (!checkDataSize(event, 4)) return; NetworkString &data = event->data(); - STKPeer* peer = event->getPeer(); - // Accepted // ======== Log::info("ClientLobby", "The server accepted the connection."); - STKHost::get()->setMyHostId(data.getUInt32()); assert(!NetworkConfig::get()->isAddingNetworkPlayers()); - // connection token - uint32_t token = data.getToken(); - peer->setClientServerToken(token); m_state.store(CONNECTED); - } // connectionAccepted //----------------------------------------------------------------------------- diff --git a/src/network/protocols/client_lobby.hpp b/src/network/protocols/client_lobby.hpp index 21136b47c..f516e38d6 100644 --- a/src/network/protocols/client_lobby.hpp +++ b/src/network/protocols/client_lobby.hpp @@ -80,6 +80,8 @@ public: virtual void asynchronousUpdate() OVERRIDE {} virtual bool allPlayersReady() const OVERRIDE { return m_state.load() >= PLAYING; } + bool waitingForServerRespond() const + { return m_state.load() == REQUESTING_CONNECTION; } }; #endif // CLIENT_LOBBY_HPP diff --git a/src/network/protocols/game_events_protocol.cpp b/src/network/protocols/game_events_protocol.cpp index abd4dc2eb..dd435bc34 100644 --- a/src/network/protocols/game_events_protocol.cpp +++ b/src/network/protocols/game_events_protocol.cpp @@ -41,7 +41,7 @@ bool GameEventsProtocol::notifyEvent(Event* event) if (event->getType() != EVENT_TYPE_MESSAGE || !World::getWorld()) return true; NetworkString &data = event->data(); - if (data.size() < 1) // for token and type + if (data.size() < 1) // for type { Log::warn("GameEventsProtocol", "Too short message."); return true; @@ -134,7 +134,7 @@ void GameEventsProtocol::kartFinishedRace(AbstractKart *kart, float time) ns->setSynchronous(true); ns->addUInt8(GE_KART_FINISHED_RACE).addUInt8(kart->getWorldKartId()) .addFloat(time); - sendMessageToPeersChangingToken(ns, /*reliable*/true); + sendMessageToPeers(ns, /*reliable*/true); delete ns; } // kartFinishedRace @@ -145,7 +145,7 @@ void GameEventsProtocol::kartFinishedRace(AbstractKart *kart, float time) */ void GameEventsProtocol::kartFinishedRace(const NetworkString &ns) { - if (ns.size() < 5) // for token and type + if (ns.size() < 5) { Log::warn("GameEventsProtocol", "kartFinisheRace: Too short message."); return; diff --git a/src/network/protocols/game_protocol.cpp b/src/network/protocols/game_protocol.cpp index 16446f72b..1c2ef3de4 100644 --- a/src/network/protocols/game_protocol.cpp +++ b/src/network/protocols/game_protocol.cpp @@ -306,7 +306,7 @@ void GameProtocol::sendState() Log::info("GameProtocol", "Sending new state at %d.", World::getWorld()->getTimeTicks()); assert(NetworkConfig::get()->isServer()); - sendMessageToPeersChangingToken(m_data_to_send, /*reliable*/true); + sendMessageToPeers(m_data_to_send, /*reliable*/true); } // sendState // ---------------------------------------------------------------------------- diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 2f0448814..8ed006e74 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -170,11 +170,6 @@ bool ServerLobby::notifyEvent(Event* event) //----------------------------------------------------------------------------- void ServerLobby::handleChat(Event* event) { - if (!event->getPeer()->hasPlayerProfiles()) - { - Log::warn("ServerLobby", "Unauthorized peer wants to chat."); - return; - } if (!checkDataSize(event, 1)) return; core::stringw message; @@ -184,7 +179,7 @@ void ServerLobby::handleChat(Event* event) NetworkString* chat = getNetworkString(); chat->setSynchronous(true); chat->addUInt8(LE_CHAT).encodeString16(message); - sendMessageToPeersChangingToken(chat, /*reliable*/true); + sendMessageToPeers(chat, /*reliable*/true); delete chat; } } // handleChat @@ -384,7 +379,7 @@ void ServerLobby::asynchronousUpdate() // Reset for next state usage resetPeersReady(); m_state = LOAD_WORLD; - sendMessageToPeersChangingToken(load_world); + sendMessageToPeers(load_world); delete load_world; } break; @@ -464,7 +459,7 @@ void ServerLobby::update(int ticks) NetworkString *exit_result_screen = getNetworkString(1); exit_result_screen->setSynchronous(true); exit_result_screen->addUInt8(LE_EXIT_RESULT); - sendMessageToPeersChangingToken(exit_result_screen, + sendMessageToPeers(exit_result_screen, /*reliable*/true); delete exit_result_screen; std::lock_guard lock(m_connection_mutex); @@ -475,7 +470,7 @@ void ServerLobby::update(int ticks) server_info->setSynchronous(true); server_info->addUInt8(LE_SERVER_INFO); m_game_setup->addServerInfo(server_info); - sendMessageToPeersChangingToken(server_info); + sendMessageToPeers(server_info); delete server_info; setup(); } @@ -572,7 +567,7 @@ void ServerLobby::signalRaceStartToClients() StkTime::getRealTime()); NetworkString *ns = getNetworkString(1); ns->addUInt8(LE_START_RACE); - sendMessageToPeersChangingToken(ns, /*reliable*/true); + sendMessageToPeers(ns, /*reliable*/true); delete ns; } // startGame @@ -623,6 +618,8 @@ void ServerLobby::startSelection(const Event *event) auto peers = STKHost::get()->getPeers(); for (auto peer : peers) { + if (!peer->isValidated()) + continue; peer->eraseServerKarts(m_available_kts.first, karts_erase); peer->eraseServerTracks(m_available_kts.second, tracks_erase); } @@ -647,7 +644,7 @@ void ServerLobby::startSelection(const Event *event) ns->encodeString(track); } - sendMessageToPeersChangingToken(ns, /*reliable*/true); + sendMessageToPeers(ns, /*reliable*/true); delete ns; m_state = SELECTING; @@ -802,7 +799,7 @@ void ServerLobby::checkRaceFinished() // result screen and go back to the lobby m_timeout.store((float)StkTime::getRealTime() + 15.0f); m_state = RESULT_DISPLAY; - sendMessageToPeersChangingToken(total, /*reliable*/ true); + sendMessageToPeers(total, /*reliable*/ true); delete total; Log::info("ServerLobby", "End of game message sent"); @@ -1035,7 +1032,7 @@ void ServerLobby::clientDisconnected(Event* event) msg->encodeString(name); Log::info("ServerLobby", "%s disconnected", name.c_str()); } - sendMessageToPeersChangingToken(msg, /*reliable*/true); + sendMessageToPeers(msg, /*reliable*/true); updatePlayerList(); delete msg; } // clientDisconnected @@ -1086,7 +1083,7 @@ void ServerLobby::connectionRequested(Event* event) // Check server version int version = data.getUInt8(); - if (version < stk_config->m_max_server_version || + if (version < stk_config->m_min_server_version || version > stk_config->m_max_server_version) { NetworkString *message = getNetworkString(2); @@ -1275,20 +1272,7 @@ void ServerLobby::handleUnencryptedConnection(std::shared_ptr peer, per_player_difficulty, (uint8_t)i)); } - if (!peer->isClientServerTokenSet()) - { - // Now answer to the peer that just connected - // ------------------------------------------ - RandomGenerator token_generator; - // use 4 random numbers because rand_max is probably 2^15-1. - uint32_t token = (uint32_t)((token_generator.get(RAND_MAX) & 0xff) << 24 | - (token_generator.get(RAND_MAX) & 0xff) << 16 | - (token_generator.get(RAND_MAX) & 0xff) << 8 | - (token_generator.get(RAND_MAX) & 0xff)); - - peer->setClientServerToken(token); - } - + peer->setValidated(); // send a message to the one that asked to connect NetworkString* message_ack = getNetworkString(4); message_ack->setSynchronous(true); @@ -1343,7 +1327,7 @@ void ServerLobby::updatePlayerList(bool force_update) pl->addUInt8(server_owner); pl->addUInt8(profile->getPerPlayerDifficulty()); } - sendMessageToPeersChangingToken(pl); + sendMessageToPeers(pl); delete pl; } // updatePlayerList @@ -1368,7 +1352,7 @@ void ServerLobby::updateServerOwner() for (auto peer: peers) { // Only 127.0.0.1 can be server owner in case of graphics-client-server - if (peer->hasPlayerProfiles() && + if (peer->isValidated() && (NetworkConfig::get()->getServerIdFile().empty() || peer->getAddress().getIP() == 0x7f000001)) { @@ -1466,7 +1450,7 @@ void ServerLobby::playerVote(Event* event) uint8_t reverse = data.getUInt8(); m_peers_votes[event->getPeerSP()] = std::make_tuple(track_name, lap, reverse == 1); - sendMessageToPeersChangingToken(&other); + sendMessageToPeers(&other); } // playerVote diff --git a/src/network/stk_host.cpp b/src/network/stk_host.cpp index a63701722..08dc71646 100644 --- a/src/network/stk_host.cpp +++ b/src/network/stk_host.cpp @@ -737,17 +737,19 @@ void STKHost::mainLoop() if (is_server) { std::unique_lock peer_lock(m_peers_mutex); - // Remove any peer which has no token for 7 seconds - // The token is set when the first connection request has happened + // Remove peer which has not been validated after a specific time + // It is validated when the first connection request has finished + const float timeout = UserConfigParams::m_validation_timeout; for (auto it = m_peers.begin(); it != m_peers.end();) { - if (!it->second->isClientServerTokenSet() && + if (!it->second->isValidated() && (float)StkTime::getRealTime() > - it->second->getConnectedTime() + 7.0f) + it->second->getConnectedTime() + timeout) { - Log::info("STKHost", "%s has no token for more than 7" - " seconds, disconnect it by force.", - it->second->getAddress().toString().c_str()); + Log::info("STKHost", "%s has not been validated for more" + " than %f seconds, disconnect it by force.", + it->second->getAddress().toString().c_str(), + timeout); enet_host_flush(host); enet_peer_reset(it->first); it = m_peers.erase(it); @@ -804,6 +806,9 @@ void STKHost::mainLoop() TransportAddress addr(event.peer->address); Log::info("STKHost", "%s has just connected. There are " "now %u peers.", addr.toString().c_str(), getPeerCount()); + // Client always trust the server + if (!is_server) + stk_peer->setValidated(); } // ENET_EVENT_TYPE_CONNECT else if (event.type == ENET_EVENT_TYPE_DISCONNECT) { @@ -1020,7 +1025,7 @@ void STKHost::sendPacketToAllPeers(NetworkString *data, bool reliable) std::lock_guard lock(m_peers_mutex); for (auto p : m_peers) { - if (p.second->isClientServerTokenSet()) + if (p.second->isValidated()) p.second->sendPacket(data, reliable); } } // sendPacketExcept @@ -1038,7 +1043,7 @@ void STKHost::sendPacketExcept(STKPeer* peer, NetworkString *data, for (auto p : m_peers) { STKPeer* stk_peer = p.second.get(); - if (!stk_peer->isSamePeer(peer) && p.second->isClientServerTokenSet()) + if (!stk_peer->isSamePeer(peer) && p.second->isValidated()) { stk_peer->sendPacket(data, reliable); } @@ -1093,6 +1098,7 @@ void STKHost::replaceNetwork(ENetEvent& event, Network* network) m_network = network; auto stk_peer = std::make_shared(event.peer, this, m_next_unique_host_id++); + stk_peer->setValidated(); m_peers[event.peer] = stk_peer; setPrivatePort(); startListening(); @@ -1100,13 +1106,3 @@ void STKHost::replaceNetwork(ENetEvent& event, Network* network) if (pm && !pm->isExiting()) pm->propagateEvent(new Event(&event, stk_peer)); } // replaceNetwork - -//----------------------------------------------------------------------------- -bool STKHost::isConnectionRequestPacket(unsigned char* data, int length) -{ - if (length < 6) - return false; - // Connection request is not synchronous - return (uint8_t)data[0] == PROTOCOL_LOBBY_ROOM && - (uint8_t)data[5] == LobbyProtocol::LE_CONNECTION_REQUESTED; -} // isConnectionRequestPacket diff --git a/src/network/stk_host.hpp b/src/network/stk_host.hpp index b2fe06fc3..67da48aa3 100644 --- a/src/network/stk_host.hpp +++ b/src/network/stk_host.hpp @@ -143,8 +143,6 @@ private: std::shared_ptr sl); // ------------------------------------------------------------------------ void mainLoop(); - // ------------------------------------------------------------------------ - bool isConnectionRequestPacket(unsigned char* data, int length); public: /** If a network console should be started. */ diff --git a/src/network/stk_peer.cpp b/src/network/stk_peer.cpp index 06370fdea..f015bc011 100644 --- a/src/network/stk_peer.cpp +++ b/src/network/stk_peer.cpp @@ -38,14 +38,13 @@ STKPeer::STKPeer(ENetPeer *enet_peer, STKHost* host, uint32_t host_id) m_enet_peer = enet_peer; m_host_id = host_id; m_connected_time = (float)StkTime::getRealTime(); - m_token_set.store(false); + m_validated.store(false); if (NetworkConfig::get()->isClient()) { // This allow client to get the correct ping as fast as possible // reset to default (0) will be done in STKHost::mainloop after 3 sec setPingInterval(10); } - m_client_server_token.store(0); } // STKPeer //----------------------------------------------------------------------------- @@ -101,7 +100,6 @@ void STKPeer::sendPacket(NetworkString *data, bool reliable, bool encrypted) if (m_enet_peer->state != ENET_PEER_STATE_CONNECTED || a != m_peer_address) return; - data->setToken(m_client_server_token); ENetPacket* packet = NULL; if (m_crypto && encrypted) diff --git a/src/network/stk_peer.hpp b/src/network/stk_peer.hpp index 2a52a6569..60d60bf83 100644 --- a/src/network/stk_peer.hpp +++ b/src/network/stk_peer.hpp @@ -57,14 +57,11 @@ protected: /** Pointer to the corresponding ENet peer data structure. */ ENetPeer* m_enet_peer; - /** The token of this client. */ - std::atomic m_client_server_token; - - /** True if the token for this peer has been set. */ - std::atomic_bool m_token_set; + /** True if this peer is validated by server. */ + std::atomic_bool m_validated; /** Host id of this peer. */ - int m_host_id; + uint32_t m_host_id; TransportAddress m_peer_address; @@ -108,27 +105,10 @@ public: void addPlayer(std::shared_ptr p) { m_players.push_back(p); } // ------------------------------------------------------------------------ - /** Sets the token for this client. */ - void setClientServerToken(const uint32_t token) - { - m_client_server_token.store(token); - m_token_set.store(true); - } // setClientServerToken + void setValidated() { m_validated.store(true); } // ------------------------------------------------------------------------ - /** Unsets the token for this client. (used in server to invalidate peer) - */ - void unsetClientServerToken() - { - m_client_server_token.store(0); - m_token_set.store(false); - } - // ------------------------------------------------------------------------ - /** Returns the token of this client. */ - uint32_t getClientServerToken() const - { return m_client_server_token.load(); } - // ------------------------------------------------------------------------ - /** Returns if the token for this client is known. */ - bool isClientServerTokenSet() const { return m_token_set.load(); } + /** Returns if the client is validated by server. */ + bool isValidated() const { return m_validated.load(); } // ------------------------------------------------------------------------ /** Returns the host id of this peer. */ uint32_t getHostId() const { return m_host_id; } From 4ef0c4e03c6ad9df8b7f6a2aefa5b8f95a159ddb Mon Sep 17 00:00:00 2001 From: Benau Date: Mon, 4 Jun 2018 13:36:24 +0800 Subject: [PATCH 111/137] Add option for official server to disable stun at all --- src/config/user_config.hpp | 4 ++++ src/network/protocols/server_lobby.cpp | 23 ++++++++++++++++------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/config/user_config.hpp b/src/config/user_config.hpp index 72c2ff2b6..0fea35807 100644 --- a/src/config/user_config.hpp +++ b/src/config/user_config.hpp @@ -723,6 +723,10 @@ namespace UserConfigParams PARAM_PREFIX IntUserConfigParam m_server_max_players PARAM_DEFAULT(IntUserConfigParam(12, "server_max_players", &m_network_group, "Maximum number of players on the server.")); + PARAM_PREFIX BoolUserConfigParam m_firewalled_server + PARAM_DEFAULT(BoolUserConfigParam(true, "firewalled-server", + &m_network_group, "Disable it to turn off all stun related code " + "in server, for official server hosting use only.")); PARAM_PREFIX StringToUIntUserConfigParam m_server_ban_list PARAM_DEFAULT(StringToUIntUserConfigParam("server_ban_list", diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 8ed006e74..0b6c16d7c 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -667,9 +667,12 @@ void ServerLobby::checkIncomingConnectionRequests() // Keep the port open, it can be sent to anywhere as we will send to the // correct peer later in ConnectToPeer. - BareNetworkString data; - data.addUInt8(0); - STKHost::get()->sendRawPacket(data, STKHost::get()->getStunAddress()); + if (UserConfigParams::m_firewalled_server) + { + BareNetworkString data; + data.addUInt8(0); + STKHost::get()->sendRawPacket(data, STKHost::get()->getStunAddress()); + } // Now poll the stk server last_poll_time = StkTime::getRealTime(); @@ -696,6 +699,10 @@ void ServerLobby::checkIncomingConnectionRequests() const XMLNode * users_xml = result->getNode("users"); std::map > keys; + auto sl = m_server_lobby.lock(); + if (!sl || sl->m_state.load() != ACCEPTING_CLIENTS) + return; + for (unsigned int i = 0; i < users_xml->getNumNodes(); i++) { uint32_t addr, id; @@ -707,11 +714,13 @@ void ServerLobby::checkIncomingConnectionRequests() users_xml->getNode(i)->get("iv", &std::get<1>(keys[id])); users_xml->getNode(i)->get("username", &std::get<2>(keys[id])); std::get<3>(keys[id]) = false; - std::make_shared(TransportAddress(addr, port)) - ->requestStart(); + if (UserConfigParams::m_firewalled_server) + { + std::make_shared + (TransportAddress(addr, port))->requestStart(); + } } - auto sl = m_server_lobby.lock(); - if (keys.empty() || !sl || sl->m_state.load() != ACCEPTING_CLIENTS) + if (keys.empty()) return; sl->addAndReplaceKeys(keys); } From a08e46f279ad946107e0e1571f57b2299ec2c152 Mon Sep 17 00:00:00 2001 From: Benau Date: Mon, 4 Jun 2018 13:47:37 +0800 Subject: [PATCH 112/137] Remove the keys too after selection --- src/network/protocols/server_lobby.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 0b6c16d7c..76b0e3979 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -592,14 +592,6 @@ void ServerLobby::startSelection(const Event *event) return; } - // Drop all pending players - for (auto& p : m_pending_connection) - { - if (auto peer = p.first.lock()) - peer->disconnect(); - } - m_pending_connection.clear(); - if (m_server_registered) { unregisterServer(false/*now*/); @@ -648,6 +640,16 @@ void ServerLobby::startSelection(const Event *event) delete ns; m_state = SELECTING; + // Drop all pending players and keys + for (auto& p : m_pending_connection) + { + if (auto peer = p.first.lock()) + peer->disconnect(); + } + m_pending_connection.clear(); + std::unique_lock ul(m_keys_mutex); + m_keys.clear(); + ul.unlock(); // Will be changed after the first vote received m_timeout.store(std::numeric_limits::max()); From 4014e8f0e4b396e74d671d0d64ea4fd1d8fae4b5 Mon Sep 17 00:00:00 2001 From: Benau Date: Mon, 4 Jun 2018 13:52:31 +0800 Subject: [PATCH 113/137] Remove unused protocol --- src/network/protocols/connect_to_peer.cpp | 60 +------------- src/network/protocols/connect_to_peer.hpp | 13 +-- src/network/protocols/get_peer_address.cpp | 79 ------------------- src/network/protocols/get_peer_address.hpp | 56 ------------- src/network/protocols/hide_public_address.cpp | 77 ------------------ src/network/protocols/hide_public_address.hpp | 56 ------------- 6 files changed, 4 insertions(+), 337 deletions(-) delete mode 100644 src/network/protocols/get_peer_address.cpp delete mode 100644 src/network/protocols/get_peer_address.hpp delete mode 100644 src/network/protocols/hide_public_address.cpp delete mode 100644 src/network/protocols/hide_public_address.hpp diff --git a/src/network/protocols/connect_to_peer.cpp b/src/network/protocols/connect_to_peer.cpp index 81a2d09b1..9e0ccd206 100644 --- a/src/network/protocols/connect_to_peer.cpp +++ b/src/network/protocols/connect_to_peer.cpp @@ -18,45 +18,20 @@ #include "network/protocols/connect_to_peer.hpp" -#include "network/event.hpp" -#include "network/network_config.hpp" -#include "network/protocols/get_peer_address.hpp" -#include "network/protocols/request_connection.hpp" -#include "network/protocol_manager.hpp" #include "network/stk_host.hpp" #include "utils/time.hpp" #include "utils/log.hpp" // ---------------------------------------------------------------------------- -/** Constructor for a WAN request. In this case we need to get the peer's - * ip address first. - * \param peer_id ID of the peer in the stk client table. - */ -ConnectToPeer::ConnectToPeer(uint32_t peer_id) : Protocol(PROTOCOL_CONNECTION) -{ - m_peer_address.clear(); - m_peer_id = peer_id; - m_state = NONE; -} // ConnectToPeer(peer_id) - -// ---------------------------------------------------------------------------- -/** Constructor for a LAN connection. +/** Constructor for peer address. * \param address The address to connect to. */ ConnectToPeer::ConnectToPeer(const TransportAddress &address) : Protocol(PROTOCOL_CONNECTION) { m_peer_address = address; - // We don't need to find the peer address, so we can start - // with the state when we found the peer address. - m_state = WAIT_FOR_CONNECTION; -} // ConnectToPeers(TransportAddress) - -// ---------------------------------------------------------------------------- - -ConnectToPeer::~ConnectToPeer() -{ -} // ~ConnectToPeer + m_state = WAIT_FOR_CONNECTION; +} // ConnectToPeer // ---------------------------------------------------------------------------- /** Simple finite state machine: Start a GetPeerAddress protocol. Once the @@ -69,35 +44,6 @@ void ConnectToPeer::asynchronousUpdate() { switch(m_state) { - case NONE: - { - m_current_protocol = std::make_shared(m_peer_id); - m_current_protocol->requestStart(); - m_state = RECEIVED_PEER_ADDRESS; - break; - } - case RECEIVED_PEER_ADDRESS: - { - // Wait until we have peer address - auto get_peer_address = - std::dynamic_pointer_cast(m_current_protocol); - assert(get_peer_address); - if (get_peer_address->getAddress().isUnset()) - return; - m_peer_address = get_peer_address->getAddress(); - m_current_protocol = nullptr; - if (m_peer_address.isUnset()) - { - Log::error("ConnectToPeer", - "The peer you want to connect to has hidden his address."); - m_state = DONE; - break; - } - - m_state = WAIT_FOR_CONNECTION; - m_timer = 0.0; - break; - } case WAIT_FOR_CONNECTION: { if (STKHost::get()->peerExists(m_peer_address)) diff --git a/src/network/protocols/connect_to_peer.hpp b/src/network/protocols/connect_to_peer.hpp index 775328252..d9318f681 100644 --- a/src/network/protocols/connect_to_peer.hpp +++ b/src/network/protocols/connect_to_peer.hpp @@ -31,13 +31,6 @@ class ConnectToPeer : public Protocol protected: TransportAddress m_peer_address; - uint32_t m_peer_id; - - /** Pointer to the protocol which is monitored for state changes, this - * need to be shared_ptr because we need to get the result from - * \ref GetPeerAddress, otherwise when it terminated the result will be - * gone. */ - std::shared_ptr m_current_protocol; /** Timer use for tracking broadcast. */ double m_timer = 0.0; @@ -47,18 +40,14 @@ protected: enum STATE { - NONE, - RECEIVED_PEER_ADDRESS, WAIT_FOR_CONNECTION, DONE, EXITING } m_state; public: - ConnectToPeer(uint32_t peer_id); ConnectToPeer(const TransportAddress &address); - virtual ~ConnectToPeer(); - + virtual ~ConnectToPeer() {} virtual void setup() OVERRIDE {} virtual void update(int ticks) OVERRIDE {} virtual void asynchronousUpdate() OVERRIDE; diff --git a/src/network/protocols/get_peer_address.cpp b/src/network/protocols/get_peer_address.cpp deleted file mode 100644 index a462b5a44..000000000 --- a/src/network/protocols/get_peer_address.cpp +++ /dev/null @@ -1,79 +0,0 @@ -// -// SuperTuxKart - a fun racing game with go-kart -// Copyright (C) 2013-2015 SuperTuxKart-Team -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -#include "network/protocols/get_peer_address.hpp" - -#include "config/user_config.hpp" -#include "network/network_config.hpp" -#include "network/stk_host.hpp" -#include "online/request_manager.hpp" -#include "online/xml_request.hpp" -#include "utils/log.hpp" - -GetPeerAddress::GetPeerAddress(uint32_t peer_id) - : Protocol(PROTOCOL_SILENT, NULL) -{ - m_peer_id = peer_id; -} // GetPeerAddress - -// ---------------------------------------------------------------------------- -GetPeerAddress::~GetPeerAddress() -{ -} // ~GetPeerAddress - -// ---------------------------------------------------------------------------- -void GetPeerAddress::setup() -{ - m_address.clear(); - m_request = new Online::XMLRequest(); - NetworkConfig::get()->setServerDetails(m_request, "get"); - m_request->addParameter("peer_id", m_peer_id); - - Online::RequestManager::get()->addRequest(m_request); -} // setup - -// ---------------------------------------------------------------------------- -void GetPeerAddress::asynchronousUpdate() -{ - if (m_request->isDone()) - { - const XMLNode * result = m_request->getXMLData(); - - std::string success; - if(result->get("success", &success) && success == "yes") - { - uint32_t ip; - result->get("ip", &ip); - m_address.setIP(ip); - - uint16_t port; - result->get("port", &port); - m_address.setPort(port); - - Log::debug("GetPeerAddress", "Peer address retrieved."); - } - else - { - Log::error("GetPeerAddress", "Failed to get peer address."); - } - requestTerminate(); - - delete m_request; - m_request = NULL; - } -} // asynchronousUpdate diff --git a/src/network/protocols/get_peer_address.hpp b/src/network/protocols/get_peer_address.hpp deleted file mode 100644 index 6a251faba..000000000 --- a/src/network/protocols/get_peer_address.hpp +++ /dev/null @@ -1,56 +0,0 @@ -// -// SuperTuxKart - a fun racing game with go-kart -// Copyright (C) 2013-2015 SuperTuxKart-Team -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -#ifndef GET_PEER_ADDRESS_HPP -#define GET_PEER_ADDRESS_HPP - -#include "network/protocol.hpp" -#include "network/transport_address.hpp" -#include "utils/cpp2011.hpp" - -namespace Online { class XMLRequest; } - -class GetPeerAddress : public Protocol -{ -private: - uint32_t m_peer_id; - Online::XMLRequest* m_request; - - /** Stores the address found. Used in a callback from the parent protocol - * to get the result. */ - TransportAddress m_address; -public: - GetPeerAddress(uint32_t peer_id); - virtual ~GetPeerAddress(); - - virtual void setup() OVERRIDE; - virtual void asynchronousUpdate() OVERRIDE; - void setPeerID(uint32_t peer_id) { m_peer_id = peer_id; } - // ------------------------------------------------------------------------ - /** Returns the address found. */ - const TransportAddress &getAddress() const { return m_address; } - // ------------------------------------------------------------------------ - virtual void update(int ticks) OVERRIDE {} - // ------------------------------------------------------------------------ - virtual bool notifyEvent(Event* event) OVERRIDE { return true; } - // ------------------------------------------------------------------------ - virtual bool notifyEventAsynchronous(Event* event) OVERRIDE { return true; } - -}; // class GetPeerAddress - -#endif // GET_PEER_ADDRESS_HPP diff --git a/src/network/protocols/hide_public_address.cpp b/src/network/protocols/hide_public_address.cpp deleted file mode 100644 index a7e7e3a7f..000000000 --- a/src/network/protocols/hide_public_address.cpp +++ /dev/null @@ -1,77 +0,0 @@ -// -// SuperTuxKart - a fun racing game with go-kart -// Copyright (C) 2013-2015 SuperTuxKart-Team -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -#include "network/protocols/hide_public_address.hpp" - -#include "network/network_config.hpp" -#include "online/request_manager.hpp" -#include "online/xml_request.hpp" -#include "utils/log.hpp" - -HidePublicAddress::HidePublicAddress() : Protocol(PROTOCOL_SILENT) -{ -} - -HidePublicAddress::~HidePublicAddress() -{ -} - -void HidePublicAddress::setup() -{ - m_state = NONE; -} - -void HidePublicAddress::asynchronousUpdate() -{ - if (m_state == NONE) - { - m_request = new Online::XMLRequest(); - NetworkConfig::get()->setServerDetails(m_request, "unset"); - Online::RequestManager::get()->addRequest(m_request); - m_state = REQUEST_PENDING; - } - else if (m_state == REQUEST_PENDING && m_request->isDone()) - { - const XMLNode * result = m_request->getXMLData(); - std::string rec_success; - - if(result->get("success", &rec_success)) - { - if(rec_success == "yes") - { - Log::info("HidePublicAddress", "Address hidden successfully."); - } - else - { - Log::error("HidePublicAddress", "Fail to hide address."); - } - } - else - { - Log::error("HidePublicAddress", "Fail to hide address."); - } - m_state = DONE; - } - else if (m_state == DONE) - { - m_state = EXITING; - delete m_request; - m_request = NULL; - requestTerminate(); - } -} diff --git a/src/network/protocols/hide_public_address.hpp b/src/network/protocols/hide_public_address.hpp deleted file mode 100644 index 48adb355c..000000000 --- a/src/network/protocols/hide_public_address.hpp +++ /dev/null @@ -1,56 +0,0 @@ -// -// SuperTuxKart - a fun racing game with go-kart -// Copyright (C) 2013-2015 SuperTuxKart-Team -// -// 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 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 General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -#ifndef HIDE_PUBLIC_ADDRESS_HPP -#define HIDE_PUBLIC_ADDRESS_HPP - -#include "network/protocol.hpp" -#include "utils/cpp2011.hpp" - -#include - -namespace Online { class XMLRequest; } - -class HidePublicAddress : public Protocol -{ -private: - Online::XMLRequest* m_request; - enum STATE - { - NONE, - REQUEST_PENDING, - DONE, - EXITING - }; - STATE m_state; - -public: - HidePublicAddress(); - virtual ~HidePublicAddress(); - - virtual void asynchronousUpdate() OVERRIDE; - virtual void setup() OVERRIDE; - // ------------------------------------------------------------------------ - virtual bool notifyEvent(Event* event) OVERRIDE { return true; } - // ------------------------------------------------------------------------ - virtual bool notifyEventAsynchronous(Event* event) OVERRIDE { return true; } - // ------------------------------------------------------------------------ - virtual void update(int ticks) OVERRIDE {} -}; // class HidePublicAddress - -#endif // HIDE_PUBLIC_ADDRESS_HPP From 2f001964204c358db3688526b86afd8d08b6eb65 Mon Sep 17 00:00:00 2001 From: Benau Date: Mon, 4 Jun 2018 14:23:29 +0800 Subject: [PATCH 114/137] Clarify voting phase --- src/states_screens/tracks_screen.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/states_screens/tracks_screen.cpp b/src/states_screens/tracks_screen.cpp index 940c43276..08e6c4202 100644 --- a/src/states_screens/tracks_screen.cpp +++ b/src/states_screens/tracks_screen.cpp @@ -21,6 +21,7 @@ #include "config/player_manager.hpp" #include "config/user_config.hpp" #include "graphics/stk_tex_manager.hpp" +#include "guiengine/message_queue.hpp" #include "guiengine/scalable_font.hpp" #include "guiengine/widget.hpp" #include "guiengine/widgets/check_box_widget.hpp" @@ -174,6 +175,11 @@ void TracksScreen::beforeAddingWidget() getWidget("all-track")->m_properties[GUIEngine::PROP_WIDTH] = "60%"; getWidget("vote")->setVisible(true); calculateLayout(); + //I18N: In track screen for networking, clarify voting phase + core::stringw msg = _("Poll will end early, after votes of all entries" + " from all players have more than 50% share, when half of the" + " remaining time is passed."); + MessageQueue::add(MessageQueue::MT_GENERIC, msg); } else { From 5baaf8340112f3388750023de62c4ed1fcc83f75 Mon Sep 17 00:00:00 2001 From: Benau Date: Mon, 4 Jun 2018 16:35:45 +0800 Subject: [PATCH 115/137] Adjust for stk-addons --- src/network/protocols/connect_to_server.cpp | 2 +- src/network/protocols/server_lobby.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/network/protocols/connect_to_server.cpp b/src/network/protocols/connect_to_server.cpp index 4b09faf92..50fcfe59b 100644 --- a/src/network/protocols/connect_to_server.cpp +++ b/src/network/protocols/connect_to_server.cpp @@ -352,7 +352,7 @@ void ConnectToServer::registerWithSTKServer() Crypto::initClientAES(); request->addParameter("aes-key", Crypto::getClientKey()); - request->addParameter("iv", Crypto::getClientIV()); + request->addParameter("aes-iv", Crypto::getClientIV()); Log::info("ConnectToServer", "Registering addr %s", addr.toString().c_str()); diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 76b0e3979..60b23ebef 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -713,7 +713,7 @@ void ServerLobby::checkIncomingConnectionRequests() users_xml->getNode(i)->get("port", &port); users_xml->getNode(i)->get("id", &id); users_xml->getNode(i)->get("aes-key", &std::get<0>(keys[id])); - users_xml->getNode(i)->get("iv", &std::get<1>(keys[id])); + users_xml->getNode(i)->get("aes-iv", &std::get<1>(keys[id])); users_xml->getNode(i)->get("username", &std::get<2>(keys[id])); std::get<3>(keys[id]) = false; if (UserConfigParams::m_firewalled_server) From 4275dbdac6bce36c54733b9bd13c09d3a01967a4 Mon Sep 17 00:00:00 2001 From: Deve Date: Mon, 4 Jun 2018 22:05:49 +0200 Subject: [PATCH 116/137] Fixed android build --- android/make.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/make.sh b/android/make.sh index 338a7476b..10ba5437f 100755 --- a/android/make.sh +++ b/android/make.sh @@ -237,7 +237,7 @@ if [ -f "$DIRNAME/obj/project_version" ]; then PROJECT_VERSION_PREV=$(cat "$DIRNAME/obj/project_version") if [ -z "$PROJECT_VERSION" ]; then - PROJECT_VERSION="$PROJECT_VERSION_PREV" + export PROJECT_VERSION="$PROJECT_VERSION_PREV" elif [ "$PROJECT_VERSION" != "$PROJECT_VERSION_PREV" ]; then echo "Different project version has been set. Forcing recompilation..." touch -c "$DIRNAME/Android.mk" @@ -246,7 +246,7 @@ fi if [ -z "$PROJECT_VERSION" ]; then if [ $IS_DEBUG_BUILD -ne 0 ]; then - PROJECT_VERSION="git" + export PROJECT_VERSION="git" else echo "Error: Variable PROJECT_VERSION is not set. It must have unique" \ "value for release build." From ffee5d266d8275391476925da8b894a0ec2a87a2 Mon Sep 17 00:00:00 2001 From: Deve Date: Mon, 4 Jun 2018 22:12:02 +0200 Subject: [PATCH 117/137] Prefer /sdcard/Android/data/ for extracting assets. Fixes #3266, #3281 --- src/io/assets_android.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/io/assets_android.cpp b/src/io/assets_android.cpp index 8d7a80853..17877f10c 100644 --- a/src/io/assets_android.cpp +++ b/src/io/assets_android.cpp @@ -59,18 +59,18 @@ void AssetsAndroid::init() if (getenv("SUPERTUXKART_DATADIR")) paths.push_back(getenv("SUPERTUXKART_DATADIR")); - if (getenv("EXTERNAL_STORAGE")) - paths.push_back(getenv("EXTERNAL_STORAGE")); - - if (getenv("SECONDARY_STORAGE")) - paths.push_back(getenv("SECONDARY_STORAGE")); - if (global_android_app->activity->externalDataPath) paths.push_back(global_android_app->activity->externalDataPath); if (global_android_app->activity->internalDataPath) paths.push_back(global_android_app->activity->internalDataPath); + if (getenv("EXTERNAL_STORAGE")) + paths.push_back(getenv("EXTERNAL_STORAGE")); + + if (getenv("SECONDARY_STORAGE")) + paths.push_back(getenv("SECONDARY_STORAGE")); + paths.push_back("/sdcard/"); paths.push_back("/storage/sdcard0/"); paths.push_back("/storage/sdcard1/"); From 6e7b2a2ecdfbe9537e08682942c6c072870da266 Mon Sep 17 00:00:00 2001 From: Deve Date: Mon, 4 Jun 2018 22:22:24 +0200 Subject: [PATCH 118/137] Remove write media storage permission from android manifest. This permission is not officially available in documentation and it was a hack for some devices that have access to sdcard only on system applications. --- android/AndroidManifest.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 3a3af85ee..7308272e6 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -39,8 +39,6 @@ - - From 47455a71f327c3b1a38b097b4372b538ad13b429 Mon Sep 17 00:00:00 2001 From: Benau Date: Tue, 5 Jun 2018 10:23:14 +0800 Subject: [PATCH 119/137] Use KeyData instead of tuple --- src/network/protocols/server_lobby.cpp | 19 +++++++++---------- src/network/protocols/server_lobby.hpp | 15 ++++++++++----- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 60b23ebef..dfc72909b 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -699,8 +699,7 @@ void ServerLobby::checkIncomingConnectionRequests() // Now start a ConnectToPeer protocol for each connection request const XMLNode * users_xml = result->getNode("users"); - std::map > keys; + std::map keys; auto sl = m_server_lobby.lock(); if (!sl || sl->m_state.load() != ACCEPTING_CLIENTS) return; @@ -712,10 +711,10 @@ void ServerLobby::checkIncomingConnectionRequests() users_xml->getNode(i)->get("ip", &addr); users_xml->getNode(i)->get("port", &port); users_xml->getNode(i)->get("id", &id); - users_xml->getNode(i)->get("aes-key", &std::get<0>(keys[id])); - users_xml->getNode(i)->get("aes-iv", &std::get<1>(keys[id])); - users_xml->getNode(i)->get("username", &std::get<2>(keys[id])); - std::get<3>(keys[id]) = false; + users_xml->getNode(i)->get("aes-key", &keys[id].m_aes_key); + users_xml->getNode(i)->get("aes-iv", &keys[id].m_aes_iv); + users_xml->getNode(i)->get("username", &keys[id].m_name); + keys[id].m_tried = false; if (UserConfigParams::m_firewalled_server) { std::make_shared @@ -1690,18 +1689,18 @@ void ServerLobby::handlePendingConnection() { const uint32_t online_id = it->second.first; auto key = m_keys.find(online_id); - if (key != m_keys.end() && std::get<3>(key->second) == false) + if (key != m_keys.end() && key->second.m_tried == false) { if (decryptConnectionRequest(peer, it->second.second, - std::get<0>(key->second), std::get<1>(key->second), - online_id, std::get<2>(key->second))) + key->second.m_aes_key, key->second.m_aes_iv, + online_id, key->second.m_name)) { it = m_pending_connection.erase(it); m_keys.erase(online_id); continue; } else - std::get<3>(key->second) = true; + key->second.m_tried = true; } it++; } diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index db74a140c..4a74bdc48 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -39,6 +39,14 @@ public: EXITING }; private: + struct KeyData + { + std::string m_aes_key; + std::string m_aes_iv; + irr::core::stringw m_name; + bool m_tried = false; + }; + std::atomic m_state; /** Hold the next connected peer for server owner if current one expired @@ -87,9 +95,7 @@ private: std::mutex m_keys_mutex; - std::map > m_keys; + std::map m_keys; std::map, std::pair, @@ -159,8 +165,7 @@ private: } } } - void addAndReplaceKeys(const std::map >& new_keys) + void addAndReplaceKeys(const std::map& new_keys) { std::lock_guard lock(m_keys_mutex); for (auto& k : new_keys) From 148677598bd47d99288aecb4938729318dbb7c0f Mon Sep 17 00:00:00 2001 From: Benau Date: Tue, 5 Jun 2018 11:21:21 +0800 Subject: [PATCH 120/137] Terminate connect to peer after kart selection start --- src/network/protocols/server_lobby.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index dfc72909b..6c0a4343a 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -26,6 +26,7 @@ #include "network/game_setup.hpp" #include "network/network_config.hpp" #include "network/network_player_profile.hpp" +#include "network/protocol_manager.hpp" #include "network/protocols/connect_to_peer.hpp" #include "network/protocols/game_protocol.hpp" #include "network/protocols/game_events_protocol.hpp" @@ -592,6 +593,7 @@ void ServerLobby::startSelection(const Event *event) return; } + ProtocolManager::lock()->findAndTerminate(PROTOCOL_CONNECTION); if (m_server_registered) { unregisterServer(false/*now*/); From 7093cc2196431d86002ea6b34ab4a47128a5ceec Mon Sep 17 00:00:00 2001 From: Benau Date: Wed, 6 Jun 2018 00:54:04 +0800 Subject: [PATCH 121/137] Don't show error for findAndTerminate if no such protocol --- src/network/protocol_manager.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/network/protocol_manager.cpp b/src/network/protocol_manager.cpp index b42932ced..f0b740efc 100644 --- a/src/network/protocol_manager.cpp +++ b/src/network/protocol_manager.cpp @@ -334,9 +334,10 @@ void ProtocolManager::findAndTerminate(ProtocolType type) { OneProtocolType &opt = m_all_protocols[type]; if (opt.isEmpty()) - Log::error("ProtocolManager", - "findAndTerminate: No protocol %d registered.", type); - + { + Log::debug("ProtocolManager", "findAndTerminate:" + " No protocol %d registered, ignore.", type); + } opt.requestTerminateAll(); } // findAndTerminate From 5da5a1972fd05fbd2bcfadcb189f783c6b12f386 Mon Sep 17 00:00:00 2001 From: Benau Date: Wed, 6 Jun 2018 01:06:44 +0800 Subject: [PATCH 122/137] Add auto start with timeout and player threshold for owner less server --- data/gui/online/networking_lobby.stkgui | 10 ++-- src/config/user_config.hpp | 10 ++++ src/main.cpp | 6 +++ src/network/game_setup.cpp | 9 ++++ src/network/network_config.cpp | 1 + src/network/network_config.hpp | 6 +++ src/network/protocols/client_lobby.cpp | 8 ++++ src/network/protocols/server_lobby.cpp | 64 ++++++++++++++++++------- src/states_screens/networking_lobby.cpp | 50 +++++++++++++++++++ src/states_screens/networking_lobby.hpp | 6 +++ 10 files changed, 150 insertions(+), 20 deletions(-) diff --git a/data/gui/online/networking_lobby.stkgui b/data/gui/online/networking_lobby.stkgui index 957e273c3..fbed2df06 100644 --- a/data/gui/online/networking_lobby.stkgui +++ b/data/gui/online/networking_lobby.stkgui @@ -14,13 +14,17 @@
- +
- -
diff --git a/src/config/user_config.hpp b/src/config/user_config.hpp index 0fea35807..f5c08f0de 100644 --- a/src/config/user_config.hpp +++ b/src/config/user_config.hpp @@ -727,6 +727,16 @@ namespace UserConfigParams PARAM_DEFAULT(BoolUserConfigParam(true, "firewalled-server", &m_network_group, "Disable it to turn off all stun related code " "in server, for official server hosting use only.")); + PARAM_PREFIX FloatUserConfigParam m_start_game_counter + PARAM_DEFAULT(FloatUserConfigParam(30.0f, "start-game-counter", + &m_network_group, "Time to wait before entering kart selection screen " + "if satisfied start-game-threshold below for owner less or ranked " + "server.")); + PARAM_PREFIX FloatUserConfigParam m_start_game_threshold + PARAM_DEFAULT(FloatUserConfigParam(0.7f, "start-game-threshold", + &m_network_group, "Only auto start kart selection when number of " + "connected player is larger than max player * this value, for " + "owner less or ranked server, after start-game-counter.")); PARAM_PREFIX StringToUIntUserConfigParam m_server_ban_list PARAM_DEFAULT(StringToUIntUserConfigParam("server_ban_list", diff --git a/src/main.cpp b/src/main.cpp index bb7465d08..b28d1b95f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -602,6 +602,7 @@ void cmdLineHelp() " --no-validation Allow non validated and unencrypted connection in wan.\n" " --ranked Server will submit ranking to stk addons server.\n" " You require permission for that.\n" + " --owner-less Race will auto start and no one can kick players in server.\n" " --no-console-log Does not write messages in the console but to\n" " stdout.log.\n" " -h, --help Show this help.\n" @@ -1071,6 +1072,11 @@ int handleCmdLine() { NetworkConfig::get()->setValidatedPlayers(true); NetworkConfig::get()->setRankedServer(true); + NetworkConfig::get()->setOwnerLess(true); + } + if (CommandLine::has("--owner-less")) + { + NetworkConfig::get()->setOwnerLess(true); } if (CommandLine::has("--server-id-file", &s)) { diff --git a/src/network/game_setup.cpp b/src/network/game_setup.cpp index 800be8bd9..8350a9d67 100644 --- a/src/network/game_setup.cpp +++ b/src/network/game_setup.cpp @@ -134,6 +134,15 @@ void GameSetup::addServerInfo(NetworkString* ns) // No extra server info ns->addUInt8((uint8_t)0); } + if (NetworkConfig::get()->isOwnerLess()) + { + ns->addFloat(UserConfigParams::m_start_game_threshold) + .addFloat(UserConfigParams::m_start_game_counter); + } + else + ns->addFloat(0.0f).addFloat(0.0f); + ns->addUInt8(NetworkConfig::get()->getMaxPlayers()); + ns->encodeString(NetworkConfig::get()->getMOTD()); } // addServerInfo diff --git a/src/network/network_config.cpp b/src/network/network_config.cpp index 8dde0c1f9..95a9d725a 100644 --- a/src/network/network_config.cpp +++ b/src/network/network_config.cpp @@ -51,6 +51,7 @@ NetworkConfig::NetworkConfig() m_is_public_server = false; m_is_ranked_server = false; m_validated_players = false; + m_owner_less = false; m_done_adding_network_players = false; m_max_players = 4; m_cur_user_id = 0; diff --git a/src/network/network_config.hpp b/src/network/network_config.hpp index fcb390b47..5b61bef1f 100644 --- a/src/network/network_config.hpp +++ b/src/network/network_config.hpp @@ -93,6 +93,8 @@ private: /** True if only validated players are allowed to join. */ bool m_validated_players; + bool m_owner_less; + bool m_done_adding_network_players; /** If this is a server, the server name. */ @@ -311,6 +313,10 @@ public: void setValidatedPlayers(bool val) { m_validated_players = val; } // ------------------------------------------------------------------------ bool onlyValidatedPlayers() const { return m_validated_players; } + // ------------------------------------------------------------------------ + void setOwnerLess(bool val) { m_owner_less = val; } + // ------------------------------------------------------------------------ + bool isOwnerLess() const { return m_owner_less; } }; // class NetworkConfig diff --git a/src/network/protocols/client_lobby.cpp b/src/network/protocols/client_lobby.cpp index 7c258e959..27ba1c39d 100644 --- a/src/network/protocols/client_lobby.cpp +++ b/src/network/protocols/client_lobby.cpp @@ -494,6 +494,7 @@ void ClientLobby::handleServerInfo(Event* event) NetworkingLobby::getInstance()->addMoreServerInfo(each_line); uint8_t extra_server_info = data.getUInt8(); + bool grand_prix_started = false; switch (extra_server_info) { case 0: @@ -513,6 +514,7 @@ void ClientLobby::handleServerInfo(Event* event) case 2: { unsigned cur_gp_track = data.getUInt8(); + grand_prix_started = cur_gp_track != 0; unsigned total_gp_track = data.getUInt8(); m_game_setup->setGrandPrixTrack(total_gp_track); each_line = _("Grand prix progress: %d / %d", cur_gp_track, @@ -521,6 +523,12 @@ void ClientLobby::handleServerInfo(Event* event) break; } } + // Auto start info + float start_threshold = data.getFloat(); + float start_timeout = data.getFloat(); + unsigned max_player = data.getUInt8(); + NetworkingLobby::getInstance()->initAutoStartTimer(grand_prix_started, + start_threshold, start_timeout, max_player); // MOTD data.decodeStringW(&str); diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 6c0a4343a..f3a4e8504 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -142,6 +142,7 @@ void ServerLobby::setup() resetPeersReady(); m_peers_votes.clear(); m_server_delay = 0.0; + m_timeout.store(std::numeric_limits::max()); Log::info("ServerLobby", "Reset server to initial state."); } // setup @@ -297,6 +298,31 @@ void ServerLobby::asynchronousUpdate() } case ACCEPTING_CLIENTS: { + if (NetworkConfig::get()->isOwnerLess()) + { + auto players = m_game_setup->getPlayers(); + if (((float)players.size() > + (float)NetworkConfig::get()->getMaxPlayers() * + UserConfigParams::m_start_game_threshold || + m_game_setup->isGrandPrixStarted()) && + m_timeout.load() == std::numeric_limits::max()) + { + m_timeout.store((float)StkTime::getRealTime() + + UserConfigParams::m_start_game_counter); + } + else if ((float)players.size() < + (float)NetworkConfig::get()->getMaxPlayers() * + UserConfigParams::m_start_game_threshold && + !m_game_setup->isGrandPrixStarted()) + { + m_timeout.store(std::numeric_limits::max()); + } + if (m_timeout.load() < (float)StkTime::getRealTime()) + { + startSelection(); + return; + } + } clearDisconnectedRankedPlayer(); // Only poll the STK server if this is a WAN server. if (NetworkConfig::get()->isWAN()) @@ -573,24 +599,27 @@ void ServerLobby::signalRaceStartToClients() } // startGame //----------------------------------------------------------------------------- -/** Instructs all clients to start the kart selection. If event is not NULL, - * the command comes from a client (which needs to be authorised). +/** Instructs all clients to start the kart selection. If event is NULL, + * the command comes from the owner less server. */ void ServerLobby::startSelection(const Event *event) { - if (m_state != ACCEPTING_CLIENTS) + if (event != NULL) { - Log::warn("ServerLobby", - "Received startSelection while being in state %d", - m_state.load()); - return; - } - if (event->getPeerSP() != m_server_owner.lock()) - { - Log::warn("ServerLobby", - "Client %lx is not authorised to start selection.", - event->getPeer()); - return; + if (m_state != ACCEPTING_CLIENTS) + { + Log::warn("ServerLobby", + "Received startSelection while being in state %d", + m_state.load()); + return; + } + if (event->getPeerSP() != m_server_owner.lock()) + { + Log::warn("ServerLobby", + "Client %d is not authorised to start selection.", + event->getPeer()->getHostId()); + return; + } } ProtocolManager::lock()->findAndTerminate(PROTOCOL_CONNECTION); @@ -1347,7 +1376,8 @@ void ServerLobby::updatePlayerList(bool force_update) void ServerLobby::updateServerOwner() { if (m_state.load() < ACCEPTING_CLIENTS || - m_state.load() > RESULT_DISPLAY) + m_state.load() > RESULT_DISPLAY || + NetworkConfig::get()->isOwnerLess()) return; if (!m_server_owner.expired()) return; @@ -1694,8 +1724,8 @@ void ServerLobby::handlePendingConnection() if (key != m_keys.end() && key->second.m_tried == false) { if (decryptConnectionRequest(peer, it->second.second, - key->second.m_aes_key, key->second.m_aes_iv, - online_id, key->second.m_name)) + key->second.m_aes_key, key->second.m_aes_iv, online_id, + key->second.m_name)) { it = m_pending_connection.erase(it); m_keys.erase(online_id); diff --git a/src/states_screens/networking_lobby.cpp b/src/states_screens/networking_lobby.cpp index 340ec5b9f..266e5fff8 100644 --- a/src/states_screens/networking_lobby.cpp +++ b/src/states_screens/networking_lobby.cpp @@ -67,6 +67,7 @@ NetworkingLobby::NetworkingLobby() : Screen("online/networking_lobby.stkgui") m_back_widget = NULL; m_header = NULL; m_text_bubble = NULL; + m_timeout_message = NULL; m_exit_widget = NULL; m_start_button = NULL; m_player_list = NULL; @@ -91,6 +92,9 @@ void NetworkingLobby::loadedFromFile() m_text_bubble = getWidget("text"); assert(m_text_bubble != NULL); + m_timeout_message = getWidget("timeout-message"); + assert(m_timeout_message != NULL); + m_chat_box = getWidget("chat"); assert(m_chat_box != NULL); @@ -131,6 +135,10 @@ void NetworkingLobby::init() { Screen::init(); + m_cur_starting_timer = m_start_threshold = m_start_timeout = + m_server_max_player = std::numeric_limits::max(); + m_timeout_message->setVisible(false); + //I18N: In the networking lobby m_header->setText(_("Lobby"), false); m_server_info_height = GUIEngine::getFont()->getDimension(L"X").Height; @@ -199,6 +207,33 @@ void NetworkingLobby::onUpdate(float delta) { if (NetworkConfig::get()->isServer()) return; + if (m_timeout_message->isVisible() && m_player_list) + { + float cur_player = (float)(m_player_list->getItemCount()); + if (cur_player > m_server_max_player * m_start_threshold && + m_cur_starting_timer == std::numeric_limits::max()) + { + m_cur_starting_timer = m_start_timeout; + } + else if (cur_player < m_server_max_player * m_start_threshold) + { + m_cur_starting_timer = std::numeric_limits::max(); + m_timeout_message->setText(L"", true); + } + + if (m_cur_starting_timer != std::numeric_limits::max()) + { + m_cur_starting_timer -= delta; + if (m_cur_starting_timer < 0.0f) + m_cur_starting_timer = 0.0f; + //I18N: In the networking lobby, display the starting timeout + //for owner-less server + core::stringw msg = _("Game will start after %d second", + (int)m_cur_starting_timer); + m_timeout_message->setText(msg, true); + } + } + if (m_state == LS_ADD_PLAYERS) { m_text_bubble->setText(_("Everyone:\nPress the 'Select' button to " @@ -419,3 +454,18 @@ void NetworkingLobby::cleanAddedPlayers() return; m_player_list->clear(); } // cleanAddedPlayers + +// ---------------------------------------------------------------------------- +void NetworkingLobby::initAutoStartTimer(bool grand_prix_started, + float start_threshold, + float start_timeout, + unsigned server_max_player) +{ + if (start_threshold == 0.0f || start_timeout == 0.0f) + return; + + m_timeout_message->setVisible(true); + m_start_threshold = grand_prix_started ? 0.0f : start_threshold; + m_start_timeout = start_timeout; + m_server_max_player = (float)server_max_player; +} // initAutoStartTimer diff --git a/src/states_screens/networking_lobby.hpp b/src/states_screens/networking_lobby.hpp index db7b5088d..ef70e083f 100644 --- a/src/states_screens/networking_lobby.hpp +++ b/src/states_screens/networking_lobby.hpp @@ -67,9 +67,13 @@ private: std::vector m_server_info; int m_server_info_height; + float m_cur_starting_timer, m_start_threshold, m_start_timeout, + m_server_max_player; + GUIEngine::IconButtonWidget* m_back_widget; GUIEngine::LabelWidget* m_header; GUIEngine::LabelWidget* m_text_bubble; + GUIEngine::LabelWidget* m_timeout_message; GUIEngine::IconButtonWidget* m_exit_widget; GUIEngine::IconButtonWidget* m_start_button; GUIEngine::ListWidget* m_player_list; @@ -126,6 +130,8 @@ public: void addSplitscreenPlayer(irr::core::stringw name); void cleanAddedPlayers(); uint32_t getServerPing() const; + void initAutoStartTimer(bool grand_prix_started, float start_threshold, + float start_timeout, unsigned server_max_player); }; // class NetworkingLobby #endif From 5e798ad6d4a1ab6d2680c665f02ec88ed0725d28 Mon Sep 17 00:00:00 2001 From: Deve Date: Tue, 5 Jun 2018 23:55:31 +0200 Subject: [PATCH 123/137] Restore max texture size when reloading skin. Fixes #3111 --- src/graphics/irr_driver.cpp | 7 +++++++ src/graphics/irr_driver.hpp | 1 + src/states_screens/options_screen_ui.cpp | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/src/graphics/irr_driver.cpp b/src/graphics/irr_driver.cpp index 306214c50..675228c9e 100644 --- a/src/graphics/irr_driver.cpp +++ b/src/graphics/irr_driver.cpp @@ -740,6 +740,13 @@ void IrrDriver::setMaxTextureSize() att.setAttribute("MAX_TEXTURE_SIZE", core::dimension2du(max, max)); } // setMaxTextureSize +// ---------------------------------------------------------------------------- +void IrrDriver::unsetMaxTextureSize() +{ + io::IAttributes &att = m_video_driver->getNonConstDriverAttributes(); + att.setAttribute("MAX_TEXTURE_SIZE", core::dimension2du(2048, 2048)); +} // unsetMaxTextureSize + // ---------------------------------------------------------------------------- void IrrDriver::cleanSunInterposer() { diff --git a/src/graphics/irr_driver.hpp b/src/graphics/irr_driver.hpp index 73f2df74e..42243a531 100644 --- a/src/graphics/irr_driver.hpp +++ b/src/graphics/irr_driver.hpp @@ -197,6 +197,7 @@ public: void initDevice(); void reset(); void setMaxTextureSize(); + void unsetMaxTextureSize(); void getOpenGLData(std::string *vendor, std::string *renderer, std::string *version); diff --git a/src/states_screens/options_screen_ui.cpp b/src/states_screens/options_screen_ui.cpp index 07902fee0..3179d64c1 100644 --- a/src/states_screens/options_screen_ui.cpp +++ b/src/states_screens/options_screen_ui.cpp @@ -26,6 +26,7 @@ #include "font/bold_face.hpp" #include "font/font_manager.hpp" #include "font/regular_face.hpp" +#include "graphics/irr_driver.hpp" #include "guiengine/scalable_font.hpp" #include "guiengine/screen.hpp" #include "guiengine/widgets/button_widget.hpp" @@ -190,7 +191,9 @@ void OptionsScreenUI::init() Log::warn("OptionsScreenUI", "Couldn't find current skin in the list of skins!"); skinSelector->setValue(0); + irr_driver->unsetMaxTextureSize(); GUIEngine::reloadSkin(); + irr_driver->setMaxTextureSize(); } // --- language @@ -265,7 +268,9 @@ void OptionsScreenUI::eventCallback(Widget* widget, const std::string& name, con const core::stringw selectedSkin = skinSelector->getStringValue(); UserConfigParams::m_skin_file = core::stringc(selectedSkin.c_str()).c_str() + std::string(".stkskin"); + irr_driver->unsetMaxTextureSize(); GUIEngine::reloadSkin(); + irr_driver->setMaxTextureSize(); } else if (name == "split_screen_horizontally") { From cb8f519bd996a3a08bb81113cdb507de48ac7489 Mon Sep 17 00:00:00 2001 From: Marianne Gagnon Date: Tue, 5 Jun 2018 19:56:15 -0400 Subject: [PATCH 124/137] Change OSX build system to be homebrew-based --- CMakeLists.txt | 50 +++++++++++++++------------------------ cmake/FindFribidi.cmake | 9 +------ cmake/FindOggVorbis.cmake | 14 ----------- 3 files changed, 20 insertions(+), 53 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f6e1f4b6..72891c2bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,6 +28,14 @@ option(USE_SYSTEM_GLEW "Use system GLEW instead of the built-in version, when av CMAKE_DEPENDENT_OPTION(BUILD_RECORDER "Build opengl recorder" ON "NOT SERVER_ONLY;NOT APPLE" OFF) +if(APPLE) + list(APPEND CMAKE_PREFIX_PATH /usr/local/opt) + include_directories(/usr/local/opt/fribidi/include/) + include_directories(/usr/local/opt/openssl@1.1/include/) + include_directories(/usr/local/opt/openssl@1.1/include/openssl/) + include_directories(/usr/local/opt/freetype/include/freetype2/) +endif() + if((UNIX AND NOT APPLE) AND NOT SERVER_ONLY) option(ENABLE_WAYLAND_DEVICE "Enable Wayland device for linux build" ON) @@ -150,7 +158,13 @@ else() endif() # Find system GLEW library or build it if missing -if(NOT USE_GLES2 AND NOT SERVER_ONLY) +if (APPLE) + find_package(glew) + + if(GLEW_FOUND) + include_directories(${GLEW_INCLUDE_DIRS}) + endif() +elseif(NOT USE_GLES2 AND NOT SERVER_ONLY) add_definitions(-DGLEW_NO_GLU) if((UNIX AND NOT APPLE) AND USE_SYSTEM_GLEW) find_package(PkgConfig) @@ -284,29 +298,12 @@ else() endif() # OpenAL -if(APPLE) - # In theory it would be cleaner to let CMake detect the right dependencies. In practice, this means that if a OSX user has - # unix-style installs of Vorbis/Ogg/OpenAL/etc. they will be picked up over our frameworks. This is blocking when I make releases : - # the mac I use to make STK releases does have other installs of vorbis/ogg/etc. which aren't compatible with STK, so letting - # CMake pick the library it wants essentially means I can't build. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -framework OpenAL") - set(OPENAL_LIBRARY) -else() - find_package(OpenAL REQUIRED) - include_directories(${OPENAL_INCLUDE_DIR}) -endif() +find_package(OpenAL REQUIRED) +include_directories(${OPENAL_INCLUDE_DIR}) # OggVorbis -if(APPLE) - # In theory it would be cleaner to let CMake detect the right dependencies. In practice, this means that if a OSX user has - # unix-style installs of Vorbis/Ogg/OpenAL/etc. they will be picked up over our frameworks. This is blocking when I make releases : - # the mac I use to make STK releases does have other installs of vorbis/ogg/etc. which aren't compatible with STK, so letting - # CMake pick the library it wants essentially means I can't build. - set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -I/Library/Frameworks/Ogg.framework/Versions/A/Headers -I/Library/Frameworks/Vorbis.framework/Versions/A/Headers") -else() - find_package(OggVorbis REQUIRED) - include_directories(${OGGVORBIS_INCLUDE_DIRS}) -endif() +find_package(OggVorbis REQUIRED) +include_directories(${OGGVORBIS_INCLUDE_DIRS}) # Freetype find_package(Freetype) @@ -525,15 +522,6 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD") SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L/usr/local/lib") endif() -if(APPLE) - # In theory it would be cleaner to let CMake detect the right dependencies. In practice, this means that if a OSX user has - # unix-style installs of Vorbis/Ogg/OpenAL/etc. they will be picked up over our frameworks. This is blocking when I make releases : - # the mac I use to make STK releases does have other installs of vorbis/ogg/etc. which aren't compatible with STK, so letting - # CMake pick the library it wants essentially means I can't build. - set_target_properties(supertuxkart PROPERTIES LINK_FLAGS "-arch x86_64 -F/Library/Frameworks -framework OpenAL -framework Ogg -framework Vorbis") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I/Library/Frameworks/OpenAL.framework/Versions/A/Headers") -endif() - if(USE_FRIBIDI) target_link_libraries(supertuxkart ${FRIBIDI_LIBRARIES}) add_definitions(-DENABLE_BIDI) diff --git a/cmake/FindFribidi.cmake b/cmake/FindFribidi.cmake index c31f4bfe3..4ef0405e8 100644 --- a/cmake/FindFribidi.cmake +++ b/cmake/FindFribidi.cmake @@ -9,14 +9,7 @@ # FRIBIDI_LIBRARIES # Fribidi library list -if(APPLE) - set(FRIBIDI_INCLUDE_DIR NAMES fribidi/fribidi.h PATHS /Library/Frameworks/fribidi.framework/Headers) - find_library(FRIBIDI_LIBRARY NAMES fribidi PATHS /Library/Frameworks/fribidi.framework) - set(FRIBIDI_LIBRARIES ${FRIBIDI_LIBRARY}) - include_directories(/Library/Frameworks/fribidi.framework/Headers) - #add_definitions(-framework fribidi) - set(FRIBIDI_FOUND 1) -elseif(UNIX) +if(UNIX) include(FindPkgConfig) pkg_check_modules(FRIBIDI fribidi) else() diff --git a/cmake/FindOggVorbis.cmake b/cmake/FindOggVorbis.cmake index 7f16d7e66..93adae122 100644 --- a/cmake/FindOggVorbis.cmake +++ b/cmake/FindOggVorbis.cmake @@ -17,20 +17,6 @@ find_library(OGGVORBIS_VORBIS_LIBRARY NAMES vorbis Vorbis libvorbis PATHS "${PRO find_library(OGGVORBIS_VORBISFILE_LIBRARY NAMES vorbisfile libvorbisfile PATHS "${PROJECT_SOURCE_DIR}/${DEPENDENCIES}/lib") find_library(OGGVORBIS_VORBISENC_LIBRARY NAMES vorbisenc libvorbisenc PATHS "${PROJECT_SOURCE_DIR}/${DEPENDENCIES}/lib") -if (APPLE) - set(OGGVORBIS_OGG_INCLUDE_DIR "/Library/Frameworks/Ogg.framework/Headers/") - set(OGGVORBIS_VORBIS_INCLUDE_DIR "/Library/Frameworks/Vorbis.framework/Headers/") -endif() - -if(APPLE AND NOT OGGVORBIS_VORBISFILE_LIBRARY) - # Seems to be the same on Apple systems - set(OGGVORBIS_VORBISFILE_LIBRARY ${OGGVORBIS_VORBIS_LIBRARY}) -endif() - -if(APPLE AND NOT OGGVORBIS_VORBISENC_LIBRARY) - # Seems to be the same on Apple systems - set(OGGVORBIS_VORBISENC_LIBRARY ${OGGVORBIS_VORBIS_LIBRARY}) -endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(OggVorbis DEFAULT_MSG From 939e5d70575ef605102b8c098c203461bc62988d Mon Sep 17 00:00:00 2001 From: auriamg Date: Tue, 5 Jun 2018 20:06:55 -0400 Subject: [PATCH 125/137] Update build instructions for new OSX build system --- README.md | 49 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2996aa843..fe132907f 100644 --- a/README.md +++ b/README.md @@ -188,20 +188,55 @@ On OS X 10.9.5, you might need the following workaround: sudo ln -s `xcrun --show-sdk-path`/usr/include/ /usr/include sudo ln -s `xcrun --show-sdk-path`/System/Library/Frameworks/OpenGL.framework/Headers/ /usr/local/include/OpenGL ``` - The first link is required in order to find libcurl, the second to find opengl. -Download pre-built dependencies from [here](https://sourceforge.net/projects/supertuxkart/files/SuperTuxKart%20Dependencies/OSX/) and put the frameworks in [hard disk root]/Library/Frameworks - ### CMake CMake is used to build STK. At this time CMake will not make a binary that is ready for distribution. You'll have to run these commands inside your stk-code directory. -### Building -With clang: +### STK 0.9.4 or later (or latest git) + +Install homebrew ( https://brew.sh/) +Install all of the dependencies using homebrew : + +```bash +brew install libogg +brew install libvorbis +brew install openal-soft +brew install freetype +brew install curl +brew install openssl@1.1 +brew install fribidi +brew install glew +``` + +Build STK +```bash +mkdir cmake_build +cd cmake_build +CMAKE_PREFIX_PATH=/usr/local/opt/freetype/:/usr/local/opt/curl/:/usr/local/opt/libogg/:/usr/local/opt/libogg/:/usr/local/opt/libvorbis/:/usr/local/opt/openssl\@1.1/:/usr/local/opt/glew/:/usr/local/opt/fribidi/ /usr/local/opt/cmake/bin/cmake .. -DFREETYPE_INCLUDE_DIRS=/usr/local/opt/freetype/include/freetype2/ -DUSE_SYSTEM_GLEW=1 -DOPENAL_INCLUDE_DIR=/usr/local/opt/openal-soft/include/ -DOPENAL_LIBRARY=/usr/local/opt/openal-soft/lib/libopenal.dylib +make +``` + +#### (Optional) packaging for distribution + +By default, the executable that is produced is not ready for distribution. Install https://github.com/auriamg/macdylibbundler + +```bash +dylibbundler -od -b -x ./bin/SuperTuxKart.app/Contents/MacOS/supertuxkart -d ./bin/SuperTuxKart.app/Contents/libs/ -p @executable_path/../libs/ +``` + +then copy the datafiles into /SuperTuxKart.app/Contents/Resources/data + + +### STK 0.9.3 or earlier + +Download pre-built dependencies from [here](https://sourceforge.net/projects/supertuxkart/files/SuperTuxKart%20Dependencies/OSX/) and put the frameworks in [hard disk root]/Library/Frameworks + +Building with clang: ```bash mkdir cmake_build @@ -210,7 +245,7 @@ cmake .. make ``` -With GCC: +Building with GCC: ```bash mkdir cmake_build cd cmake_build @@ -223,7 +258,7 @@ Building on 10.10 with 10.9 compatibility: cmake .. -DCMAKE_OSX_SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.9.sdk -DCMAKE_OSX_DEPLOYMENT_TARGET=10.9 ``` -### Xcode +#### Xcode Place an additional copy of the dependencies into `Users//Library/Frameworks`. Then cd to your cloned stk-code directory and execute the following commands: From ca039b13ff3cb4c4b302aed8fd71581a54607067 Mon Sep 17 00:00:00 2001 From: Benau Date: Wed, 6 Jun 2018 13:23:48 +0800 Subject: [PATCH 126/137] Add observeExistence for request until porting request to shared_ptr --- src/online/xml_request.cpp | 1 + src/online/xml_request.hpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/src/online/xml_request.cpp b/src/online/xml_request.cpp index 89c8d9bba..6101bb318 100644 --- a/src/online/xml_request.cpp +++ b/src/online/xml_request.cpp @@ -43,6 +43,7 @@ namespace Online m_info = ""; m_success = false; m_xml_data = NULL; + m_exists = std::make_shared(true); } // XMLRequest // ------------------------------------------------------------------------ diff --git a/src/online/xml_request.hpp b/src/online/xml_request.hpp index ce7533def..88d8cc1ac 100644 --- a/src/online/xml_request.hpp +++ b/src/online/xml_request.hpp @@ -30,8 +30,10 @@ #endif #include #include +#include #include + namespace Online { /** A http request expecting a xml return value. @@ -42,6 +44,7 @@ namespace Online /** On a successful download contains the converted XML tree. */ XMLNode *m_xml_data; + std::shared_ptr m_exists; protected: /** Additional info contained the downloaded data (or an error @@ -89,6 +92,8 @@ namespace Online assert(hasBeenExecuted()); return m_success; } // isSuccess + // -------------------------------------------------------------------- + std::weak_ptr observeExistence() const { return m_exists; } }; // class XMLRequest } //namespace Online From d87cf6fba607761e6726d8b76fd3eaef78d5e0ba Mon Sep 17 00:00:00 2001 From: Benau Date: Wed, 6 Jun 2018 15:04:27 +0800 Subject: [PATCH 127/137] Don't allow starting a ranked race with only 1 player left --- src/main.cpp | 2 +- src/network/protocols/server_lobby.cpp | 62 ++++++++++++++++++-------- src/network/protocols/server_lobby.hpp | 6 +-- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index b28d1b95f..b2e8bdc5a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2006,8 +2006,8 @@ int main(int argc, char *argv[] ) if (STKHost::existHost()) STKHost::get()->shutdown(); - NetworkConfig::destroy(); cleanSuperTuxKart(); + NetworkConfig::destroy(); #ifdef DEBUG MemoryLeaks::checkForLeaks(); diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index f3a4e8504..f899a09b9 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -106,7 +106,7 @@ ServerLobby::ServerLobby() : LobbyProtocol(NULL) */ ServerLobby::~ServerLobby() { - if (m_server_registered) + if (NetworkConfig::get()->isWAN()) { unregisterServer(true/*now*/); } @@ -134,7 +134,6 @@ void ServerLobby::setup() m_available_kts.first = { all_k.begin(), all_k.end() }; m_available_kts.second = { all_t.begin(), all_t.end() }; - m_server_registered = false; m_server_has_loaded_world.store(false); // Initialise the data structures to detect if all clients and @@ -288,12 +287,15 @@ void ServerLobby::asynchronousUpdate() // Register this server with the STK server. This will block // this thread, because there is no need for the protocol manager // to react to any requests before the server is registered. - registerServer(); - if (m_server_registered) + if (registerServer()) { m_state = ACCEPTING_CLIENTS; createServerIdFile(); } + else + { + m_state = ERROR_LEAVE; + } break; } case ACCEPTING_CLIENTS: @@ -442,6 +444,30 @@ void ServerLobby::update(int ticks) setup(); } + // Reset for ranked server if in kart / track selection has only 1 player + if (NetworkConfig::get()->isRankedServer() && + m_state.load() == SELECTING && + m_game_setup->getPlayerCount() == 1) + { + NetworkString* exit_result_screen = getNetworkString(1); + exit_result_screen->setSynchronous(true); + exit_result_screen->addUInt8(LE_EXIT_RESULT); + sendMessageToPeers(exit_result_screen, /*reliable*/true); + delete exit_result_screen; + std::lock_guard lock(m_connection_mutex); + m_game_setup->stopGrandPrix(); + m_state = NetworkConfig::get()->isLAN() ? + ACCEPTING_CLIENTS : REGISTER_SELF_ADDRESS; + updatePlayerList(true/*force_update*/); + NetworkString* server_info = getNetworkString(); + server_info->setSynchronous(true); + server_info->addUInt8(LE_SERVER_INFO); + m_game_setup->addServerInfo(server_info); + sendMessageToPeers(server_info); + delete server_info; + setup(); + } + if (m_game_setup) { // Remove disconnected players if in these two states @@ -486,8 +512,7 @@ void ServerLobby::update(int ticks) NetworkString *exit_result_screen = getNetworkString(1); exit_result_screen->setSynchronous(true); exit_result_screen->addUInt8(LE_EXIT_RESULT); - sendMessageToPeers(exit_result_screen, - /*reliable*/true); + sendMessageToPeers(exit_result_screen, /*reliable*/true); delete exit_result_screen; std::lock_guard lock(m_connection_mutex); m_state = NetworkConfig::get()->isLAN() ? @@ -515,8 +540,11 @@ void ServerLobby::update(int ticks) * ProtocolManager thread). The information about this client is added * to the table 'server'. */ -void ServerLobby::registerServer() +bool ServerLobby::registerServer() { + while (!m_server_unregistered.expired()) + StkTime::sleep(1); + Online::XMLRequest *request = new Online::XMLRequest(); NetworkConfig::get()->setServerDetails(request, "create"); request->addParameter("address", m_server_address.getIP() ); @@ -538,22 +566,20 @@ void ServerLobby::registerServer() request->executeNow(); - const XMLNode * result = request->getXMLData(); + const XMLNode* result = request->getXMLData(); std::string rec_success; if (result->get("success", &rec_success) && rec_success == "yes") { Log::info("ServerLobby", "Server is now online."); - m_server_registered = true; - } - else - { - irr::core::stringc error(request->getInfo().c_str()); - Log::error("ServerLobby", "%s", error.c_str()); - m_server_registered = false; - m_state = ERROR_LEAVE; + delete request; + return true; } + + irr::core::stringc error(request->getInfo().c_str()); + Log::error("ServerLobby", "%s", error.c_str()); delete request; + return false; } // registerServer //----------------------------------------------------------------------------- @@ -565,6 +591,7 @@ void ServerLobby::unregisterServer(bool now) { Online::XMLRequest* request = new Online::XMLRequest(!now/*manage memory*/); + m_server_unregistered = request->observeExistence(); NetworkConfig::get()->setServerDetails(request, "stop"); request->addParameter("address", m_server_address.getIP()); @@ -623,10 +650,9 @@ void ServerLobby::startSelection(const Event *event) } ProtocolManager::lock()->findAndTerminate(PROTOCOL_CONNECTION); - if (m_server_registered) + if (NetworkConfig::get()->isWAN()) { unregisterServer(false/*now*/); - m_server_registered = false; } NetworkString *ns = getNetworkString(1); diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index 4a74bdc48..dbe9c612e 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -78,8 +78,8 @@ private: bool m_has_created_server_id_file; - /** It indicates if this server is registered with the stk server. */ - std::atomic_bool m_server_registered; + /** It indicates if this server is unregistered with the stk server. */ + std::weak_ptr m_server_unregistered; /** Timeout counter for various state. */ std::atomic m_timeout; @@ -127,7 +127,7 @@ private: // Track(s) votes void playerVote(Event *event); void playerFinishedResult(Event *event); - void registerServer(); + bool registerServer(); void finishedLoadingWorldClient(Event *event); void startedRaceOnClient(Event *event); void kickHost(Event* event); From 644d1b15217c80042967892acf8207d623921ec5 Mon Sep 17 00:00:00 2001 From: Benau Date: Wed, 6 Jun 2018 16:14:49 +0800 Subject: [PATCH 128/137] Allow specify motd with .txt file --- src/main.cpp | 19 +++++++++++++++++-- src/network/game_setup.cpp | 2 +- src/network/network_string.hpp | 12 ++++++------ src/network/protocols/client_lobby.cpp | 7 ++++--- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index b2e8bdc5a..df34a9785 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -598,7 +598,7 @@ void cmdLineHelp() " --disable-lan Disable LAN detection (connect using WAN).\n" " --auto-connect Automatically connect to fist server and start race\n" " --max-players=n Maximum number of clients (server only).\n" - " --motd Message showing in all lobby of clients.\n" + " --motd Message showing in all lobby of clients, can specify a .txt file.\n" " --no-validation Allow non validated and unencrypted connection in wan.\n" " --ranked Server will submit ranking to stk addons server.\n" " You require permission for that.\n" @@ -1065,7 +1065,22 @@ int handleCmdLine() if (CommandLine::has("--motd", &s)) { - core::stringw motd = StringUtils::xmlDecode(s); + core::stringw motd; + if (s.find(".txt") != std::string::npos) + { + std::ifstream message(s); + if (message.is_open()) + { + for (std::string line; std::getline(message, line); ) + { + motd += StringUtils::utf8ToWide(line).trim() + L"\n"; + } + // Remove last newline + motd.erase(motd.size() - 1); + } + } + else + motd = StringUtils::xmlDecode(s); NetworkConfig::get()->setMOTD(motd); } if (CommandLine::has("--ranked")) diff --git a/src/network/game_setup.cpp b/src/network/game_setup.cpp index 8350a9d67..59dcfdc95 100644 --- a/src/network/game_setup.cpp +++ b/src/network/game_setup.cpp @@ -143,7 +143,7 @@ void GameSetup::addServerInfo(NetworkString* ns) ns->addFloat(0.0f).addFloat(0.0f); ns->addUInt8(NetworkConfig::get()->getMaxPlayers()); - ns->encodeString(NetworkConfig::get()->getMOTD()); + ns->encodeString16(NetworkConfig::get()->getMOTD()); } // addServerInfo //----------------------------------------------------------------------------- diff --git a/src/network/network_string.hpp b/src/network/network_string.hpp index 4488e5d43..5d090539e 100644 --- a/src/network/network_string.hpp +++ b/src/network/network_string.hpp @@ -143,10 +143,10 @@ public: // ------------------------------------------------------------------------ BareNetworkString& encodeString16(const irr::core::stringw& value) { - uint8_t str_len = (uint8_t)value.size(); - if (value.size() > 255) - str_len = 255; - addUInt8(str_len); + uint16_t str_len = (uint16_t)value.size(); + if (value.size() > 65535) + str_len = 65535; + addUInt16(str_len); for (unsigned i = 0; i < str_len; i++) addUInt16((uint16_t)value[i]); return *this; @@ -154,13 +154,13 @@ public: // ------------------------------------------------------------------------ int decodeString16(irr::core::stringw* out) const { - unsigned str_len = getUInt8(); + uint16_t str_len = getUInt16(); for (unsigned i = 0; i < str_len; i++) { uint16_t c = getUInt16(); out->append((wchar_t)c); } - return str_len + 1; + return str_len + 2; } // ------------------------------------------------------------------------ BareNetworkString& encodeString(const std::string &value); diff --git a/src/network/protocols/client_lobby.cpp b/src/network/protocols/client_lobby.cpp index 27ba1c39d..c35edfb4e 100644 --- a/src/network/protocols/client_lobby.cpp +++ b/src/network/protocols/client_lobby.cpp @@ -531,9 +531,10 @@ void ClientLobby::handleServerInfo(Event* event) start_threshold, start_timeout, max_player); // MOTD - data.decodeStringW(&str); - if (!str.empty()) - NetworkingLobby::getInstance()->addMoreServerInfo(str); + core::stringw motd; + data.decodeString16(&motd); + if (!motd.empty()) + NetworkingLobby::getInstance()->addMoreServerInfo(motd); } // handleServerInfo From 5e3a482588d85e7ed59c06f35155c82543b0a433 Mon Sep 17 00:00:00 2001 From: Benau Date: Thu, 7 Jun 2018 01:34:25 +0800 Subject: [PATCH 129/137] Let player connecting in the middle of count down know --- src/network/protocols/client_lobby.cpp | 7 +++++-- src/network/protocols/server_lobby.cpp | 19 ++++++++++++------- src/states_screens/networking_lobby.cpp | 2 +- src/states_screens/networking_lobby.hpp | 2 ++ 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/network/protocols/client_lobby.cpp b/src/network/protocols/client_lobby.cpp index c35edfb4e..5d735caa1 100644 --- a/src/network/protocols/client_lobby.cpp +++ b/src/network/protocols/client_lobby.cpp @@ -440,8 +440,8 @@ void ClientLobby::disconnectedPlayer(Event* event) */ void ClientLobby::connectionAccepted(Event* event) { - // At least 4 bytes should remain now - if (!checkDataSize(event, 4)) return; + // At least 8 bytes should remain now + if (!checkDataSize(event, 8)) return; NetworkString &data = event->data(); // Accepted @@ -450,6 +450,9 @@ void ClientLobby::connectionAccepted(Event* event) STKHost::get()->setMyHostId(data.getUInt32()); assert(!NetworkConfig::get()->isAddingNetworkPlayers()); m_state.store(CONNECTED); + float auto_start_timer = data.getFloat(); + if (auto_start_timer != std::numeric_limits::max()) + NetworkingLobby::getInstance()->setStartingTimerTo(auto_start_timer); } // connectionAccepted //----------------------------------------------------------------------------- diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index f899a09b9..d0dc8906d 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -1340,19 +1340,24 @@ void ServerLobby::handleUnencryptedConnection(std::shared_ptr peer, } peer->setValidated(); - // send a message to the one that asked to connect - NetworkString* message_ack = getNetworkString(4); - message_ack->setSynchronous(true); - // connection success -- return the host id of peer - message_ack->addUInt8(LE_CONNECTION_ACCEPTED).addUInt32(peer->getHostId()); - peer->sendPacket(message_ack); - delete message_ack; + // send a message to the one that asked to connect NetworkString* server_info = getNetworkString(); server_info->setSynchronous(true); server_info->addUInt8(LE_SERVER_INFO); m_game_setup->addServerInfo(server_info); peer->sendPacket(server_info); + + NetworkString* message_ack = getNetworkString(4); + message_ack->setSynchronous(true); + // connection success -- return the host id of peer + float auto_start_timer = m_timeout.load(); + message_ack->addUInt8(LE_CONNECTION_ACCEPTED).addUInt32(peer->getHostId()) + .addFloat(auto_start_timer == std::numeric_limits::max() ? + auto_start_timer : auto_start_timer - (float)StkTime::getRealTime()); + peer->sendPacket(message_ack); + delete message_ack; + // Make sure it will always ping at least the frequency of state exchange // so enet will not ping when we exchange state but keep ping elsewhere // then in lobby the ping seen will be correct diff --git a/src/states_screens/networking_lobby.cpp b/src/states_screens/networking_lobby.cpp index 266e5fff8..359c008f4 100644 --- a/src/states_screens/networking_lobby.cpp +++ b/src/states_screens/networking_lobby.cpp @@ -228,7 +228,7 @@ void NetworkingLobby::onUpdate(float delta) m_cur_starting_timer = 0.0f; //I18N: In the networking lobby, display the starting timeout //for owner-less server - core::stringw msg = _("Game will start after %d second", + core::stringw msg = _("Game will start after %d second(s).", (int)m_cur_starting_timer); m_timeout_message->setText(msg, true); } diff --git a/src/states_screens/networking_lobby.hpp b/src/states_screens/networking_lobby.hpp index ef70e083f..46db87174 100644 --- a/src/states_screens/networking_lobby.hpp +++ b/src/states_screens/networking_lobby.hpp @@ -132,6 +132,8 @@ public: uint32_t getServerPing() const; void initAutoStartTimer(bool grand_prix_started, float start_threshold, float start_timeout, unsigned server_max_player); + void setStartingTimerTo(float t) { m_cur_starting_timer = t; } + }; // class NetworkingLobby #endif From a39c934d2b675e366084aaa94994786d93fe57ff Mon Sep 17 00:00:00 2001 From: Deve Date: Wed, 6 Jun 2018 22:11:10 +0200 Subject: [PATCH 130/137] Fade background when general text field dialog is opened. Fixes #3240 --- src/states_screens/dialogs/general_text_field_dialog.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/states_screens/dialogs/general_text_field_dialog.cpp b/src/states_screens/dialogs/general_text_field_dialog.cpp index 266f037aa..dd6d2aa52 100644 --- a/src/states_screens/dialogs/general_text_field_dialog.cpp +++ b/src/states_screens/dialogs/general_text_field_dialog.cpp @@ -37,7 +37,6 @@ GeneralTextFieldDialog::GeneralTextFieldDialog(const wchar_t* title, GUIEngine::MODAL_DIALOG_LOCATION_BOTTOM), m_dm_cb(dm_cb), m_val_cb(val_cb), m_self_destroy(false) { - m_fade_background = false; loadFromFile("general_text_field_dialog.stkgui"); m_text_field = getWidget("textfield"); From 6effdeced4e6b6a7a584ae9a9d1ec87609d1b418 Mon Sep 17 00:00:00 2001 From: Deve Date: Wed, 6 Jun 2018 22:19:14 +0200 Subject: [PATCH 131/137] Avoid a crash in debug menu --- src/utils/debug.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/debug.cpp b/src/utils/debug.cpp index 4962f3264..759daa264 100644 --- a/src/utils/debug.cpp +++ b/src/utils/debug.cpp @@ -722,6 +722,7 @@ bool handleContextMenuAction(s32 cmd_id) break; case DEBUG_ADJUST_LIGHTS: { + if (!world) return false; // Some sliders use multipliers because the spinner widget // only supports integers DebugSliderDialog *dsd = new DebugSliderDialog(); From 96b27e427c87fe4cb7403427f2bb5c482089f6df Mon Sep 17 00:00:00 2001 From: Benau Date: Thu, 7 Jun 2018 08:32:47 +0800 Subject: [PATCH 132/137] Use a simpler english --- src/states_screens/tracks_screen.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/states_screens/tracks_screen.cpp b/src/states_screens/tracks_screen.cpp index 08e6c4202..7a0b98ff7 100644 --- a/src/states_screens/tracks_screen.cpp +++ b/src/states_screens/tracks_screen.cpp @@ -176,9 +176,8 @@ void TracksScreen::beforeAddingWidget() getWidget("vote")->setVisible(true); calculateLayout(); //I18N: In track screen for networking, clarify voting phase - core::stringw msg = _("Poll will end early, after votes of all entries" - " from all players have more than 50% share, when half of the" - " remaining time is passed."); + core::stringw msg = _("If a majority of players all select the same" + " track and race settings, voting will end early."); MessageQueue::add(MessageQueue::MT_GENERIC, msg); } else From 5a5bdbe85129c236000fe51ab079002d2ded2a92 Mon Sep 17 00:00:00 2001 From: Benau Date: Thu, 7 Jun 2018 08:43:26 +0800 Subject: [PATCH 133/137] Remove duplicated id --- data/gui/tracks.stkgui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/gui/tracks.stkgui b/data/gui/tracks.stkgui index 5ad274a77..40013ce52 100644 --- a/data/gui/tracks.stkgui +++ b/data/gui/tracks.stkgui @@ -41,7 +41,7 @@
-
From 53156a3a07f001908f17c69b78f42a329784f149 Mon Sep 17 00:00:00 2001 From: "auria.mg" Date: Wed, 6 Jun 2018 20:49:16 -0400 Subject: [PATCH 134/137] Update pot --- data/po/supertuxkart.pot | 1723 +++++++++++++++++++++++--------------- 1 file changed, 1044 insertions(+), 679 deletions(-) diff --git a/data/po/supertuxkart.pot b/data/po/supertuxkart.pot index 4b298d84e..db01b01e5 100644 --- a/data/po/supertuxkart.pot +++ b/data/po/supertuxkart.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: supertuxkart\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-10-04 20:58-0400\n" +"POT-Creation-Date: 2018-06-06 20:48-0400\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -113,11 +113,11 @@ msgid "Penguin Playground" msgstr "" #. I18N: ./data/grandprix/2_offthebeatentrack.grandprix -msgid "Off the beaten track" +msgid "Off the Beaten Track" msgstr "" #. I18N: ./data/grandprix/3_tothemoonandback.grandprix -msgid "To the moon and back" +msgid "To the Moon and Back" msgstr "" #. I18N: ./data/grandprix/4_atworldsend.grandprix @@ -177,11 +177,91 @@ msgstr "" msgid "Arenas" msgstr "" +#. I18N: ./data/gui/android/init_android.stkgui +msgid "Select a type of control that you prefer" +msgstr "" + +#. I18N: ./data/gui/android/init_android.stkgui +#. I18N: Control type +#. I18N: ./data/gui/android/multitouch_settings.stkgui +#. I18N: In the multitouch settings screen +msgid "Accelerometer" +msgstr "" + +#. I18N: ./data/gui/android/init_android.stkgui +#. I18N: Control type +msgid "Steering wheel" +msgstr "" + +#. I18N: ./data/gui/android/init_android.stkgui +msgid "You can change it later in touch device settings." +msgstr "" + +#. I18N: ./data/gui/android/init_android.stkgui +#. I18N: ./data/gui/android/multitouch_settings.stkgui +#. I18N: ./data/gui/custom_video_settings.stkgui +#. I18N: ./data/gui/kart_color_slider.stkgui +msgid "Apply" +msgstr "" + +#. I18N: ./data/gui/android/multitouch_settings.stkgui +msgid "Touch Device Settings" +msgstr "" + +#. I18N: ./data/gui/android/multitouch_settings.stkgui +#. I18N: In the multitouch settings screen +#. I18N: ./data/gui/help1.stkgui +#. I18N: Tab in help menu +#. I18N: ./data/gui/help2.stkgui +#. I18N: Tab in help menu +#. I18N: ./data/gui/help3.stkgui +#. I18N: Tab in help menu +#. I18N: ./data/gui/help4.stkgui +#. I18N: Tab in help menu +#. I18N: ./data/gui/help5.stkgui +#. I18N: Tab in help menu +msgid "General" +msgstr "" + +#. I18N: ./data/gui/android/multitouch_settings.stkgui +#. I18N: In the multitouch settings screen +msgid "Device enabled" +msgstr "" + +#. I18N: ./data/gui/android/multitouch_settings.stkgui +#. I18N: In the multitouch settings screen +msgid "Inverted buttons" +msgstr "" + +#. I18N: ./data/gui/android/multitouch_settings.stkgui +#. I18N: In the multitouch settings screen +msgid "Buttons scale" +msgstr "" + +#. I18N: ./data/gui/android/multitouch_settings.stkgui +#. I18N: In the multitouch settings screen +msgid "Advanced" +msgstr "" + +#. I18N: ./data/gui/android/multitouch_settings.stkgui +#. I18N: In the multitouch settings screen +msgid "Deadzone" +msgstr "" + +#. I18N: ./data/gui/android/multitouch_settings.stkgui +#. I18N: In the multitouch settings screen +msgid "Sensitivity" +msgstr "" + +#. I18N: ./data/gui/android/multitouch_settings.stkgui +msgid "Restore defaults" +msgstr "" + #. I18N: ./data/gui/arenas.stkgui #. I18N: track group #. I18N: ./data/gui/easter_egg.stkgui #. I18N: track group -#: src/states_screens/grand_prix_editor_screen.cpp:337 +#: src/states_screens/grand_prix_editor_screen.cpp:334 msgid "Standard" msgstr "" @@ -193,11 +273,11 @@ msgstr "" #. I18N: track group name #. I18N: kart group name #. I18N: track group name -#: src/states_screens/arenas_screen.cpp:83 -#: src/states_screens/easter_egg_screen.cpp:145 -#: src/states_screens/grand_prix_editor_screen.cpp:339 -#: src/states_screens/kart_selection.cpp:287 -#: src/states_screens/tracks_and_gp_screen.cpp:148 +#: src/states_screens/arenas_screen.cpp:81 +#: src/states_screens/easter_egg_screen.cpp:143 +#: src/states_screens/grand_prix_editor_screen.cpp:336 +#: src/states_screens/kart_selection.cpp:290 +#: src/states_screens/tracks_and_gp_screen.cpp:145 msgid "Add-Ons" msgstr "" @@ -208,25 +288,24 @@ msgstr "" #. I18N: Time filters for add-ons #. I18N: name of the tab that will show arenas from all groups #. I18N: name of the tab that will show tracks from all groups -#. I18N: if all kart animations are enabled -#. I18N: name of the tab that will show tracks from all groups -#: src/states_screens/addons_screen.cpp:49 -#: src/states_screens/arenas_screen.cpp:75 -#: src/states_screens/easter_egg_screen.cpp:137 -#: src/states_screens/edit_track_screen.cpp:147 -#: src/states_screens/gp_info_screen.cpp:76 -#: src/states_screens/grand_prix_editor_screen.cpp:336 -#: src/states_screens/kart_selection.cpp:279 -#: src/states_screens/options_screen_video.cpp:456 -#: src/states_screens/tracks_and_gp_screen.cpp:138 -#: src/states_screens/tracks_screen.cpp:141 +#: src/states_screens/addons_screen.cpp:47 +#: src/states_screens/arenas_screen.cpp:73 +#: src/states_screens/easter_egg_screen.cpp:135 +#: src/states_screens/edit_track_screen.cpp:145 +#: src/states_screens/gp_info_screen.cpp:74 +#: src/states_screens/grand_prix_editor_screen.cpp:333 +#: src/states_screens/kart_selection.cpp:282 +#: src/states_screens/tracks_and_gp_screen.cpp:135 +#: src/states_screens/tracks_screen.cpp:209 msgid "All" msgstr "" #. I18N: ./data/gui/confirm_dialog.stkgui #. I18N: In a 'are you sure?' dialog -#: src/states_screens/edit_gp_screen.cpp:257 -#: src/states_screens/ghost_replay_selection.cpp:117 +#: src/network/protocols/client_lobby.cpp:398 +#: src/states_screens/dialogs/ghost_replay_info_dialog.cpp:169 +#: src/states_screens/edit_gp_screen.cpp:255 +#: src/states_screens/ghost_replay_selection.cpp:312 msgid "Yes" msgstr "" @@ -247,13 +326,15 @@ msgstr "" #. I18N: In the registration dialog #. I18N: ./data/gui/online/server_info_dialog.stkgui #. I18N: In the server info dialog +#. I18N: ./data/gui/online/splitscreen_player_dialog.stkgui +#. I18N: Splitscreen player in network #. I18N: ./data/gui/user_screen.stkgui #. I18N: In the user screen #. I18N: ./data/gui/user_screen_tab.stkgui #. I18N: In the user screen #: src/states_screens/dialogs/addons_loading.cpp:228 #: src/states_screens/dialogs/add_device_dialog.cpp:128 -#: src/states_screens/user_screen.cpp:112 +#: src/states_screens/user_screen.cpp:110 msgid "Cancel" msgstr "" @@ -281,11 +362,6 @@ msgstr "" msgid "Shadows" msgstr "" -#. I18N: ./data/gui/custom_video_settings.stkgui -#. I18N: Video settings -msgid "Image-based lighting" -msgstr "" - #. I18N: ./data/gui/custom_video_settings.stkgui #. I18N: Video settings msgid "Bloom" @@ -298,17 +374,17 @@ msgstr "" #. I18N: ./data/gui/custom_video_settings.stkgui #. I18N: Video settings -msgid "Ambient Occlusion" +msgid "Ambient occlusion" msgstr "" #. I18N: ./data/gui/custom_video_settings.stkgui #. I18N: Video settings -msgid "Global illumination" +msgid "Depth of field" msgstr "" #. I18N: ./data/gui/custom_video_settings.stkgui #. I18N: Video settings -msgid "Glow (outlines)" +msgid "Glow (Outlines)" msgstr "" #. I18N: ./data/gui/custom_video_settings.stkgui @@ -323,12 +399,12 @@ msgstr "" #. I18N: ./data/gui/custom_video_settings.stkgui #. I18N: Video settings -msgid "Depth of field" +msgid "Image-based lighting" msgstr "" #. I18N: ./data/gui/custom_video_settings.stkgui #. I18N: Video settings -msgid "Weather Effects" +msgid "Animated characters" msgstr "" #. I18N: ./data/gui/custom_video_settings.stkgui @@ -338,12 +414,7 @@ msgstr "" #. I18N: ./data/gui/custom_video_settings.stkgui #. I18N: Video settings -msgid "Animated Scenery" -msgstr "" - -#. I18N: ./data/gui/custom_video_settings.stkgui -#. I18N: Video settings -msgid "Animated Characters" +msgid "Particle effects" msgstr "" #. I18N: ./data/gui/custom_video_settings.stkgui @@ -361,11 +432,6 @@ msgstr "" msgid "* Restart STK to apply new settings" msgstr "" -#. I18N: ./data/gui/custom_video_settings.stkgui -#. I18N: ./data/gui/multitouch_settings.stkgui -msgid "Apply" -msgstr "" - #. I18N: ./data/gui/easter_egg.stkgui #. I18N: Section in easter egg tracks selection screen #. I18N: ./data/gui/tracks.stkgui @@ -431,6 +497,7 @@ msgstr "" #. I18N: ./data/gui/edit_track.stkgui #. I18N: ./data/gui/general_text_field_dialog.stkgui #. I18N: In the general textfield dialog +#. I18N: ./data/gui/online/player_rankings_dialog.stkgui #. I18N: ./data/gui/online/register.stkgui #. I18N: In the registration dialog #. I18N: ./data/gui/user_screen.stkgui @@ -449,7 +516,8 @@ msgstr "" #. I18N: In soccer setup screen #. I18N: ./data/gui/tutorial_message_dialog.stkgui #. I18N: Button in tutorial -#: src/states_screens/race_result_gui.cpp:205 +#: src/states_screens/race_result_gui.cpp:173 +#: src/states_screens/race_result_gui.cpp:198 msgid "Continue" msgstr "" @@ -465,22 +533,50 @@ msgstr "" msgid "Watch replay only" msgstr "" +#. I18N: ./data/gui/ghost_replay_info_dialog.stkgui +#. I18N: Ghost replay info action +msgid "Compare to another ghost" +msgstr "" + #. I18N: ./data/gui/ghost_replay_info_dialog.stkgui #. I18N: Ghost replay info screen action #. I18N: ./data/gui/gp_info.stkgui #. I18N: In the grand prix info screen -#. I18N: ./data/gui/online/networking_lobby.stkgui -#. I18N: In the network lobby #. I18N: ./data/gui/track_info.stkgui #. I18N: In the track info screen msgid "Start Race" msgstr "" +#. I18N: ./data/gui/ghost_replay_info_dialog.stkgui +#. I18N: Ghost replay info screen action +msgid "Compare ghost" +msgstr "" + #. I18N: ./data/gui/ghost_replay_selection.stkgui #. I18N: In the ghost replay selection screen msgid "Ghost Replay Selection" msgstr "" +#. I18N: ./data/gui/ghost_replay_selection.stkgui +#. I18N: In the ghost replay selection screen +msgid "Time trial" +msgstr "" + +#. I18N: ./data/gui/ghost_replay_selection.stkgui +#. I18N: In the ghost replay selection screen +msgid "Egg hunt" +msgstr "" + +#. I18N: ./data/gui/ghost_replay_selection.stkgui +#. I18N: In the ghost replay selection screen +msgid "Only show the best times" +msgstr "" + +#. I18N: ./data/gui/ghost_replay_selection.stkgui +#. I18N: In the ghost replay selection screen +msgid "Compare replay" +msgstr "" + #. I18N: ./data/gui/ghost_replay_selection.stkgui #. I18N: In the ghost replay selection screen msgid "Only show replays matching the current difficulty" @@ -488,7 +584,12 @@ msgstr "" #. I18N: ./data/gui/ghost_replay_selection.stkgui #. I18N: In the ghost replay selection screen -msgid "Record ghost replay" +msgid "Only show replays matching the current version" +msgstr "" + +#. I18N: ./data/gui/ghost_replay_selection.stkgui +#. I18N: In the ghost replay selection screen +msgid "Record a ghost replay" msgstr "" #. I18N: ./data/gui/gp_info.stkgui @@ -498,7 +599,8 @@ msgstr "" #. I18N: ./data/gui/gp_info.stkgui #. I18N: In the grand prix info screen -#: src/states_screens/ghost_replay_selection.cpp:79 +#: src/states_screens/dialogs/ghost_replay_info_dialog.cpp:135 +#: src/states_screens/ghost_replay_selection.cpp:112 msgid "Reverse" msgstr "" @@ -509,7 +611,7 @@ msgstr "" #. I18N: ./data/gui/gp_info.stkgui #. I18N: In the grand prix info screen -#: src/states_screens/gp_info_screen.cpp:147 +#: src/states_screens/gp_info_screen.cpp:145 msgid "Continue saved GP" msgstr "" @@ -550,21 +652,6 @@ msgstr "" msgid "SuperTuxKart Help" msgstr "" -#. I18N: ./data/gui/help1.stkgui -#. I18N: Tab in help menu -#. I18N: ./data/gui/help2.stkgui -#. I18N: Tab in help menu -#. I18N: ./data/gui/help3.stkgui -#. I18N: Tab in help menu -#. I18N: ./data/gui/help4.stkgui -#. I18N: Tab in help menu -#. I18N: ./data/gui/help5.stkgui -#. I18N: Tab in help menu -#. I18N: ./data/gui/multitouch_settings.stkgui -#. I18N: In the multitouch settings screen -msgid "General" -msgstr "" - #. I18N: ./data/gui/help1.stkgui #. I18N: Tab in help menu #. I18N: ./data/gui/help2.stkgui @@ -812,11 +899,14 @@ msgstr "" #. I18N: ./data/gui/karts.stkgui #. I18N: In the kart selection (player setup) screen -#. I18N: ./data/gui/karts_online.stkgui -#. I18N: In the kart selection (player setup) screen msgid "Choose a Kart" msgstr "" +#. I18N: ./data/gui/kart_color_slider.stkgui +#. I18N: In the kart color slider dialog +msgid "0 to use the original color, otherwise pick one from slider." +msgstr "" + #. I18N: ./data/gui/main_menu.stkgui #. I18N: Main menu button msgid "Story Mode" @@ -834,12 +924,12 @@ msgstr "" #. I18N: ./data/gui/main_menu.stkgui #. I18N: Main menu button +#. I18N: ./data/gui/online/online.stkgui #. I18N: ./data/gui/user_screen.stkgui #. I18N: In the user screen #. I18N: ./data/gui/user_screen_tab.stkgui #. I18N: In the user screen -#: src/states_screens/main_menu_screen.cpp:78 -#: src/states_screens/online_profile_friends.cpp:222 +#: src/states_screens/online_profile_friends.cpp:220 msgid "Online" msgstr "" @@ -868,7 +958,7 @@ msgstr "" #. I18N: ./data/gui/main_menu.stkgui #. I18N: In the main screen -#: src/states_screens/race_gui_overworld.cpp:511 +#: src/states_screens/race_gui_overworld.cpp:584 msgid "Tutorial" msgstr "" @@ -876,7 +966,7 @@ msgstr "" #. I18N: In the main screen #. I18N: ./data/gui/online/profile_achievements_tab.stkgui #. I18N: Section in the profile screen -#: src/states_screens/online_profile_base.cpp:112 +#: src/states_screens/online_profile_base.cpp:107 msgid "Achievements" msgstr "" @@ -895,49 +985,6 @@ msgstr "" msgid "Quit" msgstr "" -#. I18N: ./data/gui/multitouch_settings.stkgui -msgid "Touch Device Settings" -msgstr "" - -#. I18N: ./data/gui/multitouch_settings.stkgui -#. I18N: In the multitouch settings screen -msgid "Device enabled" -msgstr "" - -#. I18N: ./data/gui/multitouch_settings.stkgui -#. I18N: In the multitouch settings screen -msgid "Inverted buttons" -msgstr "" - -#. I18N: ./data/gui/multitouch_settings.stkgui -#. I18N: In the multitouch settings screen -msgid "Buttons scale" -msgstr "" - -#. I18N: ./data/gui/multitouch_settings.stkgui -#. I18N: In the multitouch settings screen -msgid "Accelerometer" -msgstr "" - -#. I18N: ./data/gui/multitouch_settings.stkgui -#. I18N: In the multitouch settings screen -msgid "Advanced" -msgstr "" - -#. I18N: ./data/gui/multitouch_settings.stkgui -#. I18N: In the multitouch settings screen -msgid "Deadzone" -msgstr "" - -#. I18N: ./data/gui/multitouch_settings.stkgui -#. I18N: In the multitouch settings screen -msgid "Sensitivity" -msgstr "" - -#. I18N: ./data/gui/multitouch_settings.stkgui -msgid "Restore defaults" -msgstr "" - #. I18N: ./data/gui/online/change_password.stkgui #. I18N: In the change password dialog msgid "Password Change" @@ -1008,8 +1055,9 @@ msgstr "" #. I18N: ./data/gui/online/create_server.stkgui #. I18N: In the server creation screen -#: src/states_screens/ghost_replay_selection.cpp:80 -#: src/states_screens/server_selection.cpp:104 +#: src/states_screens/dialogs/ghost_replay_info_dialog.cpp:137 +#: src/states_screens/ghost_replay_selection.cpp:114 +#: src/states_screens/server_selection.cpp:105 msgid "Difficulty" msgstr "" @@ -1019,8 +1067,9 @@ msgstr "" #. I18N: Difficulty #. I18N: ./data/gui/select_challenge.stkgui #. I18N: Difficulty -#: src/race/race_manager.hpp:546 -#: src/states_screens/ghost_replay_selection.cpp:121 +#. I18N: ./data/gui/select_challenge_nobest.stkgui +#. I18N: Difficulty +#: src/race/race_manager.hpp:575 msgid "Novice" msgstr "" @@ -1030,8 +1079,9 @@ msgstr "" #. I18N: Difficulty #. I18N: ./data/gui/select_challenge.stkgui #. I18N: Difficulty -#: src/race/race_manager.hpp:547 -#: src/states_screens/ghost_replay_selection.cpp:121 +#. I18N: ./data/gui/select_challenge_nobest.stkgui +#. I18N: Difficulty +#: src/race/race_manager.hpp:576 msgid "Intermediate" msgstr "" @@ -1041,8 +1091,9 @@ msgstr "" #. I18N: Difficulty #. I18N: ./data/gui/select_challenge.stkgui #. I18N: Difficulty -#: src/race/race_manager.hpp:548 -#: src/states_screens/ghost_replay_selection.cpp:120 +#. I18N: ./data/gui/select_challenge_nobest.stkgui +#. I18N: Difficulty +#: src/race/race_manager.hpp:577 msgid "Expert" msgstr "" @@ -1050,31 +1101,46 @@ msgstr "" #. I18N: Difficulty #. I18N: ./data/gui/race_setup.stkgui #. I18N: Difficulty -#: src/race/race_manager.hpp:549 -#: src/states_screens/ghost_replay_selection.cpp:119 +#. I18N: ./data/gui/select_challenge.stkgui +#. I18N: Difficulty +#: src/race/race_manager.hpp:578 msgid "SuperTux" msgstr "" #. I18N: ./data/gui/online/create_server.stkgui #. I18N: In the server creation screen -#: src/states_screens/server_selection.cpp:105 +#: src/states_screens/server_selection.cpp:106 msgid "Game mode" msgstr "" #. I18N: ./data/gui/online/create_server.stkgui #. I18N: Multiplayer game mode #. I18N: Game mode -#: src/race/race_manager.hpp:175 +#: src/network/network_config.cpp:179 src/race/race_manager.hpp:176 msgid "Normal Race" msgstr "" #. I18N: ./data/gui/online/create_server.stkgui #. I18N: Multiplayer game mode #. I18N: Game mode -#: src/race/race_manager.hpp:177 +#: src/network/network_config.cpp:181 src/race/race_manager.hpp:178 msgid "Time Trial" msgstr "" +#. I18N: ./data/gui/online/create_server.stkgui +#. I18N: Multiplayer game mode +#. I18N: Game mode +#: src/network/network_config.cpp:183 src/race/race_manager.hpp:182 +msgid "3 Strikes Battle" +msgstr "" + +#. I18N: ./data/gui/online/create_server.stkgui +#. I18N: Multiplayer game mode +#. I18N: Game mode +#: src/network/network_config.cpp:185 src/race/race_manager.hpp:186 +msgid "Soccer" +msgstr "" + #. I18N: ./data/gui/online/create_server.stkgui #. I18N: In the server creation screen msgid "Create" @@ -1082,8 +1148,8 @@ msgstr "" #. I18N: ./data/gui/online/guest_login.stkgui #. I18N: ./data/gui/user_screen.stkgui -#. I18N: Used as a verb, appears on the main menu (login button) -#: src/states_screens/main_menu_screen.cpp:80 +#. I18N: Used as a verb, appears on the main networking menu (login button) +#: src/states_screens/online_screen.cpp:60 msgid "Login" msgstr "" @@ -1120,6 +1186,25 @@ msgstr "" msgid "Log In" msgstr "" +#. I18N: ./data/gui/online/lan.stkgui +msgid "Local Networking" +msgstr "" + +#. I18N: ./data/gui/online/lan.stkgui +#. I18N: In the online multiplayer screen +#. I18N: ./data/gui/online/profile_servers.stkgui +#. I18N: In the online multiplayer screen +msgid "Find Server" +msgstr "" + +#. I18N: ./data/gui/online/lan.stkgui +#. I18N: In the online multiplayer screen +#. I18N: ./data/gui/online/profile_servers.stkgui +#. I18N: In the online multiplayer screen +#: src/states_screens/create_server_screen.cpp:88 +msgid "Create Server" +msgstr "" + #. I18N: ./data/gui/online/lobby_settings.stkgui #. I18N: In the lobby settings screen msgid "Lobby Settings" @@ -1127,32 +1212,23 @@ msgstr "" #. I18N: ./data/gui/online/networking_lobby.stkgui #. I18N: In networking lobby +#. I18N: In the networking lobby +#: src/states_screens/networking_lobby.cpp:143 msgid "Lobby" msgstr "" #. I18N: ./data/gui/online/networking_lobby.stkgui -#. I18N: In the networking lobby -#. I18N: ./data/gui/online/server_info_dialog.stkgui -#. I18N: In the networking lobby -msgid "Server name:" +#. I18N: In the network lobby +msgid "Send" msgstr "" #. I18N: ./data/gui/online/networking_lobby.stkgui -#. I18N: In the networking lobby -#. I18N: ./data/gui/online/server_info_dialog.stkgui -#. I18N: In the networking lobby -msgid "Difficulty:" +#. I18N: In the network lobby +msgid "Start race" msgstr "" #. I18N: ./data/gui/online/networking_lobby.stkgui -#. I18N: In the networking lobby -#. I18N: ./data/gui/online/server_info_dialog.stkgui -#. I18N: In the networking lobby -msgid "Game mode:" -msgstr "" - -#. I18N: ./data/gui/online/networking_lobby.stkgui -#. I18N: In the networking lobby +#. I18N: In the network lobby msgid "Exit" msgstr "" @@ -1163,18 +1239,59 @@ msgstr "" msgid "View" msgstr "" +#. I18N: ./data/gui/online/online.stkgui +#. I18N: In the networking menu +msgid "Enable splitscreen or player handicaps" +msgstr "" + +#. I18N: ./data/gui/online/online.stkgui +#. I18N: Networking menu button +msgid "Local networking" +msgstr "" + +#. I18N: ./data/gui/online/online.stkgui +#. I18N: Networking menu button +msgid "Global networking" +msgstr "" + +#. I18N: ./data/gui/online/online.stkgui +#. I18N: Networking menu button +msgid "Enter server address" +msgstr "" + +#. I18N: ./data/gui/online/online.stkgui +#. I18N: Networking menu button +#: src/states_screens/online_profile_base.cpp:112 +#: src/states_screens/online_profile_base.cpp:122 +#: src/states_screens/online_screen.cpp:58 +msgid "Your profile" +msgstr "" + +#. I18N: ./data/gui/online/player_rankings_dialog.stkgui +#. I18N: In player rankings dialog +msgid "Top 10 players" +msgstr "" + +#. I18N: ./data/gui/online/player_rankings_dialog.stkgui +msgid "Refresh" +msgstr "" + #. I18N: ./data/gui/online/profile_achievements.stkgui #. I18N: ./data/gui/online/profile_achievements_tab.stkgui #. I18N: ./data/gui/online/profile_friends.stkgui #. I18N: ./data/gui/online/profile_overview.stkgui -#. I18N: ./data/gui/online/profile_servers.stkgui #. I18N: ./data/gui/online/profile_settings.stkgui msgid "..." msgstr "" +#. I18N: ./data/gui/online/profile_achievements_tab.stkgui +#. I18N: In the achievements screen +msgid "Player rankings" +msgstr "" + #. I18N: ./data/gui/online/profile_friends.stkgui #. I18N: Section in the profile screen -#: src/states_screens/online_profile_base.cpp:111 +#: src/states_screens/online_profile_base.cpp:106 msgid "Friends" msgstr "" @@ -1193,27 +1310,6 @@ msgstr "" msgid "Overview" msgstr "" -#. I18N: ./data/gui/online/profile_servers.stkgui -#. I18N: Section in the profile screen -#: src/states_screens/online_profile_base.cpp:110 -msgid "Servers" -msgstr "" - -#. I18N: ./data/gui/online/profile_servers.stkgui -msgid "Local Networking" -msgstr "" - -#. I18N: ./data/gui/online/profile_servers.stkgui -#. I18N: In the online multiplayer screen -msgid "Find Server" -msgstr "" - -#. I18N: ./data/gui/online/profile_servers.stkgui -#. I18N: In the online multiplayer screen -#: src/states_screens/create_server_screen.cpp:83 -msgid "Create Server" -msgstr "" - #. I18N: ./data/gui/online/profile_servers.stkgui msgid "Global Networking" msgstr "" @@ -1225,7 +1321,7 @@ msgstr "" #. I18N: ./data/gui/online/profile_settings.stkgui #. I18N: Section in the profile screen -#: src/states_screens/online_profile_base.cpp:113 +#: src/states_screens/online_profile_base.cpp:108 msgid "Account Settings" msgstr "" @@ -1264,8 +1360,8 @@ msgstr "" #. I18N: In the user screen #. I18N: ./data/gui/user_screen_tab.stkgui #. I18N: In the user screen -#: src/states_screens/online_profile_friends.cpp:72 -#: src/states_screens/online_user_search.cpp:73 +#: src/states_screens/online_profile_friends.cpp:70 +#: src/states_screens/online_user_search.cpp:71 msgid "Username" msgstr "" @@ -1308,10 +1404,11 @@ msgstr "" #. I18N: ./data/gui/online/register.stkgui #. I18N: In the registration dialog +#. I18N: ./data/gui/online/server_info_dialog.stkgui #. I18N: ./data/gui/user_screen.stkgui #. I18N: In the user screen #. I18N: ./data/gui/user_screen_tab.stkgui -#. I18N: In the registration dialog +#. I18N: In the user screen msgid "Password" msgstr "" @@ -1345,6 +1442,21 @@ msgstr "" msgid "Server Info" msgstr "" +#. I18N: ./data/gui/online/server_info_dialog.stkgui +#. I18N: In the networking lobby +msgid "Server name:" +msgstr "" + +#. I18N: ./data/gui/online/server_info_dialog.stkgui +#. I18N: In the networking lobby +msgid "Difficulty:" +msgstr "" + +#. I18N: ./data/gui/online/server_info_dialog.stkgui +#. I18N: In the networking lobby +msgid "Game mode:" +msgstr "" + #. I18N: ./data/gui/online/server_info_dialog.stkgui #. I18N: In the server info dialog msgid "Join" @@ -1354,16 +1466,48 @@ msgstr "" msgid "Server Selection" msgstr "" +#. I18N: ./data/gui/online/server_selection.stkgui +#. I18N: In the server selection screen +msgid "Show only private server(s)" +msgstr "" + +#. I18N: ./data/gui/online/splitscreen_player_dialog.stkgui +#. I18N: Splitscreen player in network +msgid "Add player" +msgstr "" + +#. I18N: ./data/gui/online/splitscreen_player_dialog.stkgui +#. I18N: Splitscreen player in network #. I18N: ./data/gui/online/user_info_dialog.stkgui -#. I18N: User info dialog' dialog -msgid "User Info" +#. I18N: User info dialog +#: src/states_screens/online_profile_achievements.cpp:71 +#: src/states_screens/server_selection.cpp:103 +msgid "Name" +msgstr "" + +#. I18N: ./data/gui/online/splitscreen_player_dialog.stkgui +#. I18N: Splitscreen player in network +msgid "Handicap" +msgstr "" + +#. I18N: ./data/gui/online/splitscreen_player_dialog.stkgui +#. I18N: Splitscreen player in network +msgid "Press the 'All players ready' button after the player list is ready." +msgstr "" + +#. I18N: ./data/gui/online/splitscreen_player_dialog.stkgui +#. I18N: Splitscreen player in network +msgid "All players ready" +msgstr "" + +#. I18N: ./data/gui/online/splitscreen_player_dialog.stkgui +#. I18N: Splitscreen player in network +msgid "Clear added player" msgstr "" #. I18N: ./data/gui/online/user_info_dialog.stkgui -#. I18N: User info dialog -#: src/states_screens/online_profile_achievements.cpp:73 -#: src/states_screens/server_selection.cpp:102 -msgid "Name" +#. I18N: User info dialog' dialog +msgid "User Info" msgstr "" #. I18N: ./data/gui/online/user_info_dialog.stkgui @@ -1385,11 +1529,6 @@ msgstr "" msgid "Vote" msgstr "" -#. I18N: ./data/gui/online/waiting_for_others.stkgui -#. I18N: Networking screen -msgid "Waiting for the others..." -msgstr "" - #. I18N: ./data/gui/options_audio.stkgui #. I18N: ./data/gui/options_device.stkgui #. I18N: ./data/gui/options_input.stkgui @@ -1402,11 +1541,11 @@ msgstr "" #. I18N: ./data/gui/options_audio.stkgui #. I18N: Section in the settings menu -#: src/states_screens/options_screen_device.cpp:86 -#: src/states_screens/options_screen_input.cpp:151 -#: src/states_screens/options_screen_ui.cpp:123 -#: src/states_screens/options_screen_video.cpp:238 -#: src/states_screens/user_screen.cpp:660 +#: src/states_screens/options_screen_device.cpp:84 +#: src/states_screens/options_screen_input.cpp:160 +#: src/states_screens/options_screen_ui.cpp:122 +#: src/states_screens/options_screen_video.cpp:205 +#: src/states_screens/user_screen.cpp:670 msgid "Audio" msgstr "" @@ -1419,8 +1558,8 @@ msgstr "" #. I18N: In the audio options screen #. I18N: in the graphical options tooltip; #. indicates a graphical feature is enabled -#: src/states_screens/dialogs/custom_video_settings.cpp:68 -#: src/states_screens/options_screen_video.cpp:451 +#: src/states_screens/dialogs/custom_video_settings.cpp:64 +#: src/states_screens/options_screen_video.cpp:415 msgid "Enabled" msgstr "" @@ -1438,16 +1577,17 @@ msgstr "" #. I18N: Section in the settings menu #. I18N: ./data/gui/options_input.stkgui #. I18N: Section in the settings menu -#: src/states_screens/options_screen_audio.cpp:68 -#: src/states_screens/options_screen_ui.cpp:125 -#: src/states_screens/options_screen_video.cpp:241 -#: src/states_screens/user_screen.cpp:662 +#: src/states_screens/options_screen_audio.cpp:66 +#: src/states_screens/options_screen_ui.cpp:124 +#: src/states_screens/options_screen_video.cpp:208 +#: src/states_screens/user_screen.cpp:672 msgid "Controls" msgstr "" #. I18N: ./data/gui/options_device.stkgui #. I18N: In the input configuration screen -#: src/states_screens/options_screen_device.cpp:111 +#: src/states_screens/options_screen_device.cpp:109 +#: src/states_screens/options_screen_device.cpp:114 msgid "Delete Configuration" msgstr "" @@ -1477,13 +1617,13 @@ msgstr "" #. I18N: Section in the settings menu #. I18N: ./data/gui/user_screen_tab.stkgui #. I18N: Section in the settings menu -#: src/states_screens/ghost_replay_selection.cpp:78 -#: src/states_screens/options_screen_audio.cpp:67 -#: src/states_screens/options_screen_device.cpp:88 -#: src/states_screens/options_screen_input.cpp:153 -#: src/states_screens/options_screen_ui.cpp:124 -#: src/states_screens/options_screen_video.cpp:240 -#: src/states_screens/server_selection.cpp:103 +#: src/states_screens/ghost_replay_selection.cpp:110 +#: src/states_screens/options_screen_audio.cpp:65 +#: src/states_screens/options_screen_device.cpp:86 +#: src/states_screens/options_screen_input.cpp:162 +#: src/states_screens/options_screen_ui.cpp:123 +#: src/states_screens/options_screen_video.cpp:207 +#: src/states_screens/server_selection.cpp:104 msgid "Players" msgstr "" @@ -1503,11 +1643,11 @@ msgstr "" #. I18N: ./data/gui/options_ui.stkgui #. I18N: Section in the settings menu -#: src/states_screens/options_screen_audio.cpp:66 -#: src/states_screens/options_screen_device.cpp:87 -#: src/states_screens/options_screen_input.cpp:152 -#: src/states_screens/options_screen_video.cpp:239 -#: src/states_screens/user_screen.cpp:661 +#: src/states_screens/options_screen_audio.cpp:64 +#: src/states_screens/options_screen_device.cpp:85 +#: src/states_screens/options_screen_input.cpp:161 +#: src/states_screens/options_screen_video.cpp:206 +#: src/states_screens/user_screen.cpp:671 msgid "User Interface" msgstr "" @@ -1541,13 +1681,23 @@ msgstr "" msgid "Connect to the Internet" msgstr "" +#. I18N: ./data/gui/options_ui.stkgui +#. I18N: In the ui settings +msgid "Multiplayer splits screen horizontally" +msgstr "" + +#. I18N: ./data/gui/options_ui.stkgui +#. I18N: In the ui settings +msgid "Enable chatting in networking lobby" +msgstr "" + #. I18N: ./data/gui/options_video.stkgui #. I18N: Section in the settings menu -#: src/states_screens/options_screen_audio.cpp:65 -#: src/states_screens/options_screen_device.cpp:85 -#: src/states_screens/options_screen_input.cpp:150 -#: src/states_screens/options_screen_ui.cpp:122 -#: src/states_screens/user_screen.cpp:659 +#: src/states_screens/options_screen_audio.cpp:63 +#: src/states_screens/options_screen_device.cpp:83 +#: src/states_screens/options_screen_input.cpp:159 +#: src/states_screens/options_screen_ui.cpp:121 +#: src/states_screens/user_screen.cpp:669 msgid "Graphics" msgstr "" @@ -1607,7 +1757,8 @@ msgid "Back to menu" msgstr "" #. I18N: ./data/gui/press_a_key_dialog.stkgui -msgid "Press a key" +#. I18N: When changing input configurations +msgid "Press fully and release..." msgstr "" #. I18N: ./data/gui/press_a_key_dialog.stkgui @@ -1632,7 +1783,7 @@ msgstr "" #. I18N: ./data/gui/race_paused_dialog.stkgui #. I18N: Race paused button -#: src/states_screens/race_result_gui.cpp:228 +#: src/states_screens/race_result_gui.cpp:232 msgid "Setup New Race" msgstr "" @@ -1653,6 +1804,7 @@ msgstr "" #. I18N: ./data/gui/race_setup.stkgui #. I18N: ./data/gui/select_challenge.stkgui +#. I18N: ./data/gui/select_challenge_nobest.stkgui #. I18N: ./data/gui/soccer_setup.stkgui msgid "Race Setup" msgstr "" @@ -1667,6 +1819,8 @@ msgstr "" #. I18N: ./data/gui/select_challenge.stkgui #. I18N: Type of race, in a challenge +#. I18N: ./data/gui/select_challenge_nobest.stkgui +#. I18N: Type of race, in a challenge msgid "Type:" msgstr "" @@ -1702,7 +1856,7 @@ msgstr "" #. I18N: ./data/gui/tracks_and_gp.stkgui #. I18N: In the track and grand prix selection screen -#: src/states_screens/dialogs/select_challenge.cpp:147 +#: src/states_screens/dialogs/select_challenge.cpp:172 msgid "Grand Prix" msgstr "" @@ -1712,12 +1866,14 @@ msgstr "" #. I18N: ./data/gui/track_info.stkgui #. I18N: for empty highscores entries -#: src/states_screens/track_info_screen.cpp:322 +#: src/states_screens/track_info_screen.cpp:326 msgid "(Empty)" msgstr "" #. I18N: ./data/gui/track_info.stkgui #. I18N: In the track info screen +#. I18N: In track screen +#: src/states_screens/tracks_screen.cpp:165 msgid "Number of laps" msgstr "" @@ -1747,6 +1903,13 @@ msgstr "" msgid "Delete" msgstr "" +#. I18N: ./data/gui/user_screen.stkgui +#. I18N: In the user screen +#. I18N: ./data/gui/user_screen_tab.stkgui +#. I18N: In the user screen +msgid "Default kart color" +msgstr "" + #. I18N: ../stk-assets/tracks/abyss/track.xml msgid "Antediluvian Abyss" msgstr "" @@ -1973,37 +2136,37 @@ msgid "Error downloading news: '%s'." msgstr "" #. I18N: number of laps to race in a challenge -#: src/challenges/challenge_data.cpp:266 -#: src/states_screens/race_result_gui.cpp:1424 +#: src/challenges/challenge_data.cpp:278 +#: src/states_screens/race_result_gui.cpp:1469 #, c-format msgid "Laps: %i" msgstr "" -#: src/challenges/challenge_data.cpp:272 +#: src/challenges/challenge_data.cpp:284 msgid "Follow the leader" msgstr "" -#: src/challenges/challenge_data.cpp:518 +#: src/challenges/challenge_data.cpp:545 #, c-format msgid "New track '%s' now available" msgstr "" -#: src/challenges/challenge_data.cpp:522 +#: src/challenges/challenge_data.cpp:549 #, c-format msgid "New game mode '%s' now available" msgstr "" -#: src/challenges/challenge_data.cpp:532 +#: src/challenges/challenge_data.cpp:559 #, c-format msgid "New Grand Prix '%s' now available" msgstr "" -#: src/challenges/challenge_data.cpp:536 +#: src/challenges/challenge_data.cpp:563 #, c-format msgid "New difficulty '%s' now available" msgstr "" -#: src/challenges/challenge_data.cpp:546 +#: src/challenges/challenge_data.cpp:573 #, c-format msgid "New kart '%s' now available" msgstr "" @@ -2023,547 +2186,572 @@ msgstr "" msgid "Guest %d" msgstr "" -#: src/config/user_config.cpp:700 +#: src/config/user_config.cpp:652 msgid "" "Your config file was malformed, so it was deleted and a new one will be " "created." msgstr "" -#: src/config/user_config.cpp:711 +#: src/config/user_config.cpp:663 msgid "" "Your config file was too old, so it was deleted and a new one will be " "created." msgstr "" -#: src/graphics/irr_driver.cpp:535 +#: src/graphics/irr_driver.cpp:585 msgid "Video recording started." msgstr "" -#: src/graphics/irr_driver.cpp:541 +#: src/graphics/irr_driver.cpp:591 #, c-format msgid "Video saved in \"%s\"." msgstr "" -#: src/graphics/irr_driver.cpp:545 +#: src/graphics/irr_driver.cpp:595 msgid "Encoding progress:" msgstr "" -#: src/graphics/irr_driver.cpp:1682 +#: src/graphics/irr_driver.cpp:1771 src/graphics/irr_driver.cpp:1777 #, c-format -msgid "FPS: %d/%d/%d - %d KTris" +msgid "FPS: %d/%d/%d - %d KTris, Ping: %dms" msgstr "" -#: src/guiengine/engine.cpp:1296 +#: src/guiengine/engine.cpp:1310 msgid "Loading" msgstr "" #. I18N: 'handicapped' indicates that per-player handicaps are #. activated for this kart (i.e. it will drive slower) -#: src/guiengine/widgets/player_kart_widget.cpp:392 +#: src/guiengine/widgets/player_kart_widget.cpp:384 +#: src/guiengine/widgets/player_kart_widget.cpp:756 +#: src/karts/controller/local_player_controller.cpp:413 +#: src/karts/controller/player_controller.cpp:405 +#: src/network/protocols/client_lobby.cpp:562 +#: src/states_screens/dialogs/splitscreen_player_dialog.cpp:120 #, c-format msgid "%s (handicapped)" msgstr "" -#: src/guiengine/widgets/player_kart_widget.cpp:451 +#: src/guiengine/widgets/player_kart_widget.cpp:443 #, c-format msgid "%s is ready" msgstr "" #. I18N: Unbound key binding -#: src/input/binding.cpp:90 +#: src/input/binding.cpp:94 msgid "[none]" msgstr "" #. I18N: input configuration screen: mouse button -#: src/input/binding.cpp:114 +#: src/input/binding.cpp:118 msgctxt "input_key" msgid "Left Mouse Button" msgstr "" #. I18N: input configuration screen: mouse button -#: src/input/binding.cpp:116 +#: src/input/binding.cpp:120 msgctxt "input_key" msgid "Right Mouse Button" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:118 +#: src/input/binding.cpp:122 msgctxt "input_key" msgid "Cancel" msgstr "" #. I18N: input configuration screen: mouse button -#: src/input/binding.cpp:120 +#: src/input/binding.cpp:124 msgctxt "input_key" msgid "Middle Mouse Button" msgstr "" #. I18N: input configuration screen: mouse button -#: src/input/binding.cpp:122 +#: src/input/binding.cpp:126 msgctxt "input_key" msgid "X1 Mouse Button" msgstr "" #. I18N: input configuration screen: mouse button -#: src/input/binding.cpp:124 +#: src/input/binding.cpp:128 msgctxt "input_key" msgid "X2 Mouse Button" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:126 +#: src/input/binding.cpp:130 msgctxt "input_key" msgid "Backspace" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:128 +#: src/input/binding.cpp:132 msgctxt "input_key" msgid "Tab" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:130 +#: src/input/binding.cpp:134 msgctxt "input_key" msgid "Clear" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:132 +#: src/input/binding.cpp:136 msgctxt "input_key" msgid "Return" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:134 +#: src/input/binding.cpp:138 msgctxt "input_key" msgid "Shift" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:136 +#: src/input/binding.cpp:140 msgctxt "input_key" msgid "Control" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:138 +#: src/input/binding.cpp:142 msgctxt "input_key" msgid "Alt/Menu" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:140 +#: src/input/binding.cpp:144 msgctxt "input_key" msgid "Pause" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:142 +#: src/input/binding.cpp:146 msgctxt "input_key" msgid "Caps Lock" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:144 +#: src/input/binding.cpp:148 msgctxt "input_key" msgid "Kana" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:146 +#: src/input/binding.cpp:150 msgctxt "input_key" msgid "Junja" msgstr "" #. I18N: input configuration screen: keyboard key #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:149 +#: src/input/binding.cpp:153 msgctxt "input_key" msgid "Final" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:151 +#: src/input/binding.cpp:155 msgctxt "input_key" msgid "Escape" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:153 +#: src/input/binding.cpp:157 msgctxt "input_key" msgid "Convert" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:155 +#: src/input/binding.cpp:159 msgctxt "input_key" msgid "Nonconvert" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:157 +#: src/input/binding.cpp:161 msgctxt "input_key" msgid "Accept" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:159 +#: src/input/binding.cpp:163 msgctxt "input_key" msgid "Modechange" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:161 +#: src/input/binding.cpp:165 msgctxt "input_key" msgid "Space" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:163 +#: src/input/binding.cpp:167 msgctxt "input_key" msgid "Page Up" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:165 +#: src/input/binding.cpp:169 msgctxt "input_key" msgid "Page Down" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:167 +#: src/input/binding.cpp:171 msgctxt "input_key" msgid "End" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:169 +#: src/input/binding.cpp:173 msgctxt "input_key" msgid "Home" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:171 +#: src/input/binding.cpp:175 src/input/gamepad_android_config.cpp:57 msgctxt "input_key" msgid "Left" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:173 +#: src/input/binding.cpp:177 src/input/gamepad_android_config.cpp:63 msgctxt "input_key" msgid "Up" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:175 +#: src/input/binding.cpp:179 src/input/gamepad_android_config.cpp:60 msgctxt "input_key" msgid "Right" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:177 +#: src/input/binding.cpp:181 src/input/gamepad_android_config.cpp:66 msgctxt "input_key" msgid "Down" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:179 +#: src/input/binding.cpp:183 src/input/gamepad_android_config.cpp:108 msgctxt "input_key" msgid "Select" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:181 +#: src/input/binding.cpp:185 msgctxt "input_key" msgid "Print" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:183 +#: src/input/binding.cpp:187 msgctxt "input_key" msgid "Exec" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:185 +#: src/input/binding.cpp:189 msgctxt "input_key" msgid "Print Screen" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:187 +#: src/input/binding.cpp:191 msgctxt "input_key" msgid "Insert" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:189 +#: src/input/binding.cpp:193 msgctxt "input_key" msgid "Delete" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:191 +#: src/input/binding.cpp:195 msgctxt "input_key" msgid "Help" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:229 +#: src/input/binding.cpp:233 msgctxt "input_key" msgid "Left Logo" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:231 +#: src/input/binding.cpp:235 msgctxt "input_key" msgid "Right Logo" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:233 +#: src/input/binding.cpp:237 msgctxt "input_key" msgid "Apps" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:235 +#: src/input/binding.cpp:239 msgctxt "input_key" msgid "Sleep" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:237 +#: src/input/binding.cpp:241 msgctxt "input_key" msgid "Numpad 0" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:239 +#: src/input/binding.cpp:243 msgctxt "input_key" msgid "Numpad 1" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:241 +#: src/input/binding.cpp:245 msgctxt "input_key" msgid "Numpad 2" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:243 +#: src/input/binding.cpp:247 msgctxt "input_key" msgid "Numpad 3" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:245 +#: src/input/binding.cpp:249 msgctxt "input_key" msgid "Numpad 4" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:247 +#: src/input/binding.cpp:251 msgctxt "input_key" msgid "Numpad 5" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:249 +#: src/input/binding.cpp:253 msgctxt "input_key" msgid "Numpad 6" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:251 +#: src/input/binding.cpp:255 msgctxt "input_key" msgid "Numpad 7" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:253 +#: src/input/binding.cpp:257 msgctxt "input_key" msgid "Numpad 8" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:255 +#: src/input/binding.cpp:259 msgctxt "input_key" msgid "Numpad 9" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:259 +#: src/input/binding.cpp:263 msgctxt "input_key" msgid "Separator" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:261 +#: src/input/binding.cpp:265 msgctxt "input_key" msgid "- (Subtract)" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:263 +#: src/input/binding.cpp:267 msgctxt "input_key" msgid "Decimal" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:265 +#: src/input/binding.cpp:269 msgctxt "input_key" msgid "/ (Divide)" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:291 +#: src/input/binding.cpp:295 msgctxt "input_key" msgid "Num Lock" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:293 +#: src/input/binding.cpp:297 msgctxt "input_key" msgid "Scroll Lock" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:295 +#: src/input/binding.cpp:299 msgctxt "input_key" msgid "Left Shift" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:297 +#: src/input/binding.cpp:301 msgctxt "input_key" msgid "Right Shift" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:299 +#: src/input/binding.cpp:303 msgctxt "input_key" msgid "Left Control" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:301 +#: src/input/binding.cpp:305 msgctxt "input_key" msgid "Right Control" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:303 +#: src/input/binding.cpp:307 msgctxt "input_key" msgid "Left Menu" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:305 +#: src/input/binding.cpp:309 msgctxt "input_key" msgid "Right Menu" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:311 +#: src/input/binding.cpp:315 msgctxt "input_key" msgid "Attn" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:313 +#: src/input/binding.cpp:317 msgctxt "input_key" msgid "Crsel" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:315 +#: src/input/binding.cpp:319 msgctxt "input_key" msgid "Exsel" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:317 +#: src/input/binding.cpp:321 msgctxt "input_key" msgid "Ereof" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:319 +#: src/input/binding.cpp:323 msgctxt "input_key" msgid "Play" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:321 +#: src/input/binding.cpp:325 msgctxt "input_key" msgid "Zoom" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:323 +#: src/input/binding.cpp:327 msgctxt "input_key" msgid "Pa1" msgstr "" #. I18N: input configuration screen: keyboard key -#: src/input/binding.cpp:325 +#: src/input/binding.cpp:329 msgctxt "input_key" msgid "Oem Clear" msgstr "" #. I18N: to appear in input configuration screen, for gamepad hats -#: src/input/binding.cpp:334 src/input/binding.cpp:339 +#: src/input/binding.cpp:355 src/input/binding.cpp:360 #, c-format msgid "Gamepad hat %d" msgstr "" #. I18N: to appear in input configuration screen, for gamepad axes -#: src/input/binding.cpp:346 +#: src/input/binding.cpp:367 #, c-format msgid "Axis %d %s" msgstr "" #. I18N: to appear in input configuration screen, for gamepad axes -#: src/input/binding.cpp:353 +#: src/input/binding.cpp:374 #, c-format msgid "Axis %d inverted" msgstr "" #. I18N: to appear in input configuration screen, for gamepad axes -#: src/input/binding.cpp:358 +#: src/input/binding.cpp:379 #, c-format msgid "Axis %d" msgstr "" #. I18N: to appear in input configuration screen, for gamepad buttons -#: src/input/binding.cpp:366 +#: src/input/binding.cpp:387 #, c-format msgid "Gamepad button %d" msgstr "" #. I18N: to appear in input configuration screen, for mouse (might not be used at all) -#: src/input/binding.cpp:369 +#: src/input/binding.cpp:390 #, c-format msgid "Mouse button %d" msgstr "" #. I18N: to appear in input configuration screen, for mouse (might not be used at all) -#: src/input/binding.cpp:373 +#: src/input/binding.cpp:394 #, c-format msgid "Mouse axis %d %s" msgstr "" #. I18N: shown when config file is too old -#: src/input/device_manager.cpp:543 +#: src/input/device_manager.cpp:575 msgid "Please re-configure your key bindings." msgstr "" -#: src/input/device_manager.cpp:544 +#: src/input/device_manager.cpp:576 msgid "Your input config file is not compatible with this version of STK." msgstr "" +#: src/input/gamepad_android_config.cpp:99 +msgctxt "input_key" +msgid "Thumb Left" +msgstr "" + +#: src/input/gamepad_android_config.cpp:102 +msgctxt "input_key" +msgid "Thumb Right" +msgstr "" + +#: src/input/gamepad_android_config.cpp:105 +msgctxt "input_key" +msgid "Start" +msgstr "" + +#: src/input/gamepad_android_config.cpp:111 +msgctxt "input_key" +msgid "Mode" +msgstr "" + #. I18N: Name of the black button on xbox controller #: src/input/gamepad_config.cpp:167 msgid "Black" @@ -2675,12 +2863,12 @@ msgstr "" msgid "Left thumb up" msgstr "" -#: src/input/input_manager.cpp:807 +#: src/input/input_manager.cpp:845 #, c-format msgid "Ignoring '%s'. You needed to join earlier to play!" msgstr "" -#: src/input/input_manager.cpp:837 +#: src/input/input_manager.cpp:875 msgid "Only the Game Master may act at this point!" msgstr "" @@ -2707,11 +2895,11 @@ msgstr[1] "" msgid "Could not detect any wiimote :/" msgstr "" -#: src/karts/controller/local_player_controller.cpp:241 +#: src/karts/controller/local_player_controller.cpp:266 msgid "Penalty time!!" msgstr "" -#: src/karts/controller/local_player_controller.cpp:243 +#: src/karts/controller/local_player_controller.cpp:269 msgid "Don't accelerate before go" msgstr "" @@ -2723,15 +2911,15 @@ msgstr "" msgid "+1 life." msgstr "" -#: src/karts/kart.cpp:908 src/karts/kart.cpp:913 +#: src/karts/kart.cpp:953 src/karts/kart.cpp:958 msgid "You won the race!" msgstr "" -#: src/karts/kart.cpp:913 +#: src/karts/kart.cpp:958 msgid "You finished the race!" msgstr "" -#: src/main.cpp:1501 +#: src/main.cpp:1664 msgid "" "SuperTuxKart may connect to a server to download add-ons and notify you of " "updates. We also collect anonymous hardware statistics to help with the " @@ -2741,120 +2929,246 @@ msgid "" "edit \"Connect to the Internet\" and \"Send anonymous HW statistics\")." msgstr "" -#: src/main.cpp:1654 +#: src/main.cpp:1830 msgid "Your screen resolution is too low to run STK." msgstr "" -#: src/main.cpp:1668 +#: src/main.cpp:1859 msgid "" "Your driver version is too old. Please install the latest video drivers." msgstr "" -#: src/main.cpp:1685 +#: src/main.cpp:1877 #, c-format msgid "" "Your OpenGL version appears to be too old. Please verify if an update for " "your video driver is available. SuperTuxKart requires %s or better." msgstr "" -#: src/modes/easter_egg_hunt.cpp:202 +#: src/main_loop.cpp:338 src/network/protocols/client_lobby.cpp:185 +msgid "Server connection timed out." +msgstr "" + +#: src/modes/easter_egg_hunt.cpp:227 #, c-format msgid "Eggs: %d / %d" msgstr "" -#: src/modes/follow_the_leader.cpp:62 src/modes/follow_the_leader.cpp:285 +#: src/modes/follow_the_leader.cpp:62 src/modes/follow_the_leader.cpp:286 msgid "Leader" msgstr "" -#: src/modes/linear_world.cpp:287 +#: src/modes/linear_world.cpp:358 msgid "Final lap!" msgstr "" -#: src/modes/linear_world.cpp:314 +#: src/modes/linear_world.cpp:386 #, c-format msgid "Lap %i" msgstr "" -#: src/modes/linear_world.cpp:369 +#: src/modes/linear_world.cpp:469 #, c-format msgctxt "fastest_lap" msgid "%s by %s" msgstr "" -#: src/modes/linear_world.cpp:374 +#: src/modes/linear_world.cpp:474 msgid "New fastest lap" msgstr "" -#: src/modes/linear_world.cpp:891 +#: src/modes/linear_world.cpp:1001 msgid "WRONG WAY!" msgstr "" -#: src/modes/three_strikes_battle.cpp:664 +#: src/modes/three_strikes_battle.cpp:660 #, c-format msgid "%i spare tire kart has been spawned!" msgid_plural "%i spare tire karts have been spawned!" msgstr[0] "" msgstr[1] "" -#: src/modes/world.cpp:1202 +#: src/modes/world.cpp:1234 msgid "You have been eliminated!" msgstr "" -#: src/modes/world.cpp:1205 +#: src/modes/world.cpp:1241 #, c-format msgid "'%s' has been eliminated." msgstr "" -#: src/network/protocols/server_lobby.cpp:318 +#: src/network/network_config.cpp:175 +msgid "Normal Race (Grand Prix)" +msgstr "" + +#: src/network/network_config.cpp:177 +msgid "Time Trial (Grand Prix)" +msgstr "" + +#: src/network/protocols/client_lobby.cpp:189 +msgid "Server has been shut down." +msgstr "" + +#: src/network/protocols/client_lobby.cpp:193 +msgid "You were kicked from the server." +msgstr "" + +#: src/network/protocols/client_lobby.cpp:399 +#: src/states_screens/dialogs/ghost_replay_info_dialog.cpp:169 +#: src/states_screens/dialogs/message_dialog.cpp:129 +#: src/states_screens/edit_gp_screen.cpp:255 +#: src/states_screens/ghost_replay_selection.cpp:312 +msgid "No" +msgstr "" + +#. I18N: Vote message in network game from a player +#: src/network/protocols/client_lobby.cpp:401 #, c-format -msgid "Failed to register server: %s" +msgid "" +"Track: %s,\n" +"laps: %d, reversed: %s" msgstr "" -#: src/network/servers_manager.cpp:198 -msgid "No LAN server detected" +#: src/network/protocols/client_lobby.cpp:430 +#, c-format +msgid "%s disconnected." msgstr "" -#: src/online/online_player_profile.cpp:419 +#. I18N: In the networking lobby +#: src/network/protocols/client_lobby.cpp:471 +#, c-format +msgid "Server name: %s" +msgstr "" + +#. I18N: In the networking lobby +#: src/network/protocols/client_lobby.cpp:479 +#: src/states_screens/race_result_gui.cpp:1477 +#, c-format +msgid "Difficulty: %s" +msgstr "" + +#. I18N: In the networking lobby +#: src/network/protocols/client_lobby.cpp:484 +#, c-format +msgid "Max players: %d" +msgstr "" + +#: src/network/protocols/client_lobby.cpp:496 +#, c-format +msgid "Game mode: %s" +msgstr "" + +#. I18N: In the create server screen for soccer server +#: src/network/protocols/client_lobby.cpp:508 +#: src/states_screens/create_server_screen.cpp:173 +msgid "Time limit" +msgstr "" + +#. I18N: In the create server screen for soccer server +#: src/network/protocols/client_lobby.cpp:509 +#: src/states_screens/create_server_screen.cpp:175 +msgid "Goals limit" +msgstr "" + +#. I18N: In the networking lobby +#: src/network/protocols/client_lobby.cpp:513 +#, c-format +msgid "Soccer game type: %s" +msgstr "" + +#: src/network/protocols/client_lobby.cpp:523 +#, c-format +msgid "Grand prix progress: %d / %d" +msgstr "" + +#: src/network/protocols/client_lobby.cpp:572 +msgid "You are now the owner of server." +msgstr "" + +#: src/network/protocols/client_lobby.cpp:619 +msgid "Connection refused: Server is busy." +msgstr "" + +#: src/network/protocols/client_lobby.cpp:623 +msgid "Connection refused: You are banned from the server." +msgstr "" + +#: src/network/protocols/client_lobby.cpp:627 +msgid "Connection refused: Server password is incorrect." +msgstr "" + +#: src/network/protocols/client_lobby.cpp:631 +msgid "Connection refused: Game data is incompatible." +msgstr "" + +#: src/network/protocols/client_lobby.cpp:635 +msgid "Connection refused: Server is full." +msgstr "" + +#: src/network/protocols/client_lobby.cpp:639 +msgid "Connection refused: Invalid player connecting." +msgstr "" + +#: src/network/protocols/connect_to_server.cpp:113 +msgid "No quick play server available." +msgstr "" + +#: src/network/protocols/connect_to_server.cpp:266 +#: src/states_screens/online_screen.cpp:266 +#, c-format +msgid "Cannot connect to server %s." +msgstr "" + +#: src/network/protocols/server_lobby.cpp:339 +msgid "Failed to setup server." +msgstr "" + +#. I18N: Official means this server is hosted by STK team +#: src/network/server.cpp:73 +msgid "Official" +msgstr "" + +#: src/online/online_player_profile.cpp:428 #, c-format msgid "%s is now online." msgstr "" -#: src/online/online_player_profile.cpp:423 +#: src/online/online_player_profile.cpp:432 #, c-format msgid "%s and %s are now online." msgstr "" -#: src/online/online_player_profile.cpp:428 +#: src/online/online_player_profile.cpp:437 #, c-format msgid "%s, %s and %s are now online." msgstr "" #. I18N: Only used for count > 3 -#: src/online/online_player_profile.cpp:434 +#: src/online/online_player_profile.cpp:443 #, c-format msgid "%d friend is now online." msgid_plural "%d friends are now online." msgstr[0] "" msgstr[1] "" -#: src/online/online_player_profile.cpp:471 +#: src/online/online_player_profile.cpp:480 #, c-format msgid "You have %d new friend request!" msgid_plural "You have %d new friend requests!" msgstr[0] "" msgstr[1] "" -#: src/online/online_player_profile.cpp:477 +#: src/online/online_player_profile.cpp:486 msgid "You have a new friend request!" msgstr "" -#: src/online/xml_request.cpp:83 +#: src/online/xml_request.cpp:84 msgid "" "Unable to connect to the server. Check your internet connection or try again " "later." msgstr "" -#: src/race/grand_prix_data.hpp:171 +#: src/race/grand_prix_data.hpp:174 msgid "Random Grand Prix" msgstr "" @@ -2865,93 +3179,83 @@ msgid "" msgstr "" #. I18N: Game mode -#: src/race/race_manager.hpp:179 +#: src/race/race_manager.hpp:180 msgid "Follow the Leader" msgstr "" #. I18N: Game mode -#: src/race/race_manager.hpp:181 -msgid "3 Strikes Battle" -msgstr "" - -#. I18N: Game mode -#: src/race/race_manager.hpp:183 +#: src/race/race_manager.hpp:184 msgid "Egg Hunt" msgstr "" -#. I18N: Game mode -#: src/race/race_manager.hpp:185 -msgid "Soccer" -msgstr "" - -#: src/replay/replay_recorder.cpp:183 +#: src/replay/replay_recorder.cpp:358 msgid "Incomplete replay file will not be saved." msgstr "" -#: src/replay/replay_recorder.cpp:219 +#: src/replay/replay_recorder.cpp:394 #, c-format msgid "Replay saved in \"%s\"." msgstr "" -#: src/states_screens/addons_screen.cpp:50 +#: src/states_screens/addons_screen.cpp:48 msgid "1 week" msgstr "" -#: src/states_screens/addons_screen.cpp:51 +#: src/states_screens/addons_screen.cpp:49 msgid "2 weeks" msgstr "" -#: src/states_screens/addons_screen.cpp:52 +#: src/states_screens/addons_screen.cpp:50 msgid "1 month" msgstr "" -#: src/states_screens/addons_screen.cpp:53 +#: src/states_screens/addons_screen.cpp:51 msgid "3 months" msgstr "" -#: src/states_screens/addons_screen.cpp:54 +#: src/states_screens/addons_screen.cpp:52 msgid "6 months" msgstr "" -#: src/states_screens/addons_screen.cpp:55 +#: src/states_screens/addons_screen.cpp:53 msgid "9 months" msgstr "" -#: src/states_screens/addons_screen.cpp:56 +#: src/states_screens/addons_screen.cpp:54 msgid "1 year" msgstr "" -#: src/states_screens/addons_screen.cpp:57 +#: src/states_screens/addons_screen.cpp:55 msgid "2 years" msgstr "" -#: src/states_screens/addons_screen.cpp:115 +#: src/states_screens/addons_screen.cpp:113 msgid "Add-on name" msgstr "" -#: src/states_screens/addons_screen.cpp:116 +#: src/states_screens/addons_screen.cpp:114 msgid "Updated date" msgstr "" -#: src/states_screens/addons_screen.cpp:147 +#: src/states_screens/addons_screen.cpp:145 msgid "" "Access to the Internet is disabled. (To enable it, go to options and select " "tab 'User Interface')" msgstr "" #. I18N: as in: The Old Island by Johannes Sjolund -#: src/states_screens/addons_screen.cpp:343 +#: src/states_screens/addons_screen.cpp:341 #, c-format msgctxt "addons" msgid "%s by %s" msgstr "" -#: src/states_screens/addons_screen.cpp:474 +#: src/states_screens/addons_screen.cpp:472 msgid "Please wait while addons are updated" msgstr "" -#: src/states_screens/addons_screen.cpp:551 -#: src/states_screens/main_menu_screen.cpp:571 +#: src/states_screens/addons_screen.cpp:549 +#: src/states_screens/main_menu_screen.cpp:558 msgid "" "Sorry, an error occurred while contacting the add-ons website. Make sure you " "are connected to the Internet and that SuperTuxKart is not blocked by a " @@ -2962,56 +3266,73 @@ msgstr "" #. I18N: track group name #. I18N: kart group name #. I18N: track group name -#: src/states_screens/arenas_screen.cpp:81 -#: src/states_screens/easter_egg_screen.cpp:143 -#: src/states_screens/kart_selection.cpp:285 -#: src/states_screens/tracks_and_gp_screen.cpp:146 +#: src/states_screens/arenas_screen.cpp:79 +#: src/states_screens/easter_egg_screen.cpp:141 +#: src/states_screens/kart_selection.cpp:288 +#: src/states_screens/tracks_and_gp_screen.cpp:143 msgid "standard" msgstr "" -#: src/states_screens/arenas_screen.cpp:275 -#: src/states_screens/arenas_screen.cpp:327 -#: src/states_screens/easter_egg_screen.cpp:225 -#: src/states_screens/easter_egg_screen.cpp:256 -#: src/states_screens/kart_selection.cpp:864 -#: src/states_screens/kart_selection.cpp:1481 -#: src/states_screens/race_setup_screen.cpp:99 +#: src/states_screens/arenas_screen.cpp:273 +#: src/states_screens/arenas_screen.cpp:325 +#: src/states_screens/easter_egg_screen.cpp:223 +#: src/states_screens/easter_egg_screen.cpp:254 +#: src/states_screens/kart_selection.cpp:886 +#: src/states_screens/kart_selection.cpp:1501 +#: src/states_screens/race_setup_screen.cpp:98 msgid "Locked : solve active challenges to gain access to more!" msgstr "" -#: src/states_screens/arenas_screen.cpp:339 +#: src/states_screens/arenas_screen.cpp:337 msgid "Random Arena" msgstr "" -#: src/states_screens/arenas_screen.cpp:343 +#: src/states_screens/arenas_screen.cpp:341 #, c-format msgid "%d arena unavailable in single player." msgid_plural "%d arenas unavailable in single player." msgstr[0] "" msgstr[1] "" -#: src/states_screens/create_server_screen.cpp:82 +#: src/states_screens/create_server_screen.cpp:87 msgid "Create LAN Server" msgstr "" -#: src/states_screens/create_server_screen.cpp:87 +#: src/states_screens/create_server_screen.cpp:92 #, c-format msgid "%s's server" msgstr "" -#: src/states_screens/create_server_screen.cpp:154 -msgid "Creating server" +#. I18N: In the create server screen +#: src/states_screens/create_server_screen.cpp:151 +msgid "No. of grand prix track(s)" msgstr "" -#: src/states_screens/create_server_screen.cpp:181 +#. I18N: Geometry level disabled : lowest level, no details +#. I18N: in the graphical options tooltip; +#. indicates a graphical feature is disabled +#: src/states_screens/create_server_screen.cpp:155 +#: src/states_screens/dialogs/custom_video_settings.cpp:62 +#: src/states_screens/dialogs/custom_video_settings.cpp:69 +#: src/states_screens/dialogs/custom_video_settings.cpp:85 +#: src/states_screens/options_screen_video.cpp:418 +msgid "Disabled" +msgstr "" + +#. I18N: In the create server screen +#: src/states_screens/create_server_screen.cpp:169 +msgid "Soccer game type" +msgstr "" + +#: src/states_screens/create_server_screen.cpp:221 msgid "Name has to be between 4 and 30 characters long!" msgstr "" -#: src/states_screens/create_server_screen.cpp:188 -msgid "The maxinum number of players has to be between 2 and 12." +#: src/states_screens/create_server_screen.cpp:238 +msgid "Incorrect characters in password!" msgstr "" -#: src/states_screens/credits.cpp:180 +#: src/states_screens/credits.cpp:183 msgid "translator-credits" msgstr "" @@ -3090,12 +3411,12 @@ msgid "Current password invalid." msgstr "" #: src/states_screens/dialogs/change_password_dialog.cpp:140 -#: src/states_screens/register_screen.cpp:289 +#: src/states_screens/register_screen.cpp:290 msgid "Password has to be between 8 and 30 characters long!" msgstr "" #: src/states_screens/dialogs/change_password_dialog.cpp:147 -#: src/states_screens/register_screen.cpp:273 +#: src/states_screens/register_screen.cpp:274 msgid "Passwords don't match!" msgstr "" @@ -3105,7 +3426,7 @@ msgstr "" #: src/states_screens/dialogs/change_password_dialog.cpp:235 #: src/states_screens/dialogs/recovery_dialog.cpp:209 -#: src/states_screens/register_screen.cpp:354 +#: src/states_screens/register_screen.cpp:355 msgid "Validating info" msgstr "" @@ -3117,79 +3438,83 @@ msgid_plural "Confirm resolution within %i seconds" msgstr[0] "" msgstr[1] "" -#. I18N: Geometry level disabled : lowest level, no details -#. I18N: in the graphical options tooltip; -#. indicates a graphical feature is disabled -#: src/states_screens/dialogs/custom_video_settings.cpp:66 -#: src/states_screens/dialogs/custom_video_settings.cpp:72 -#: src/states_screens/dialogs/custom_video_settings.cpp:84 -#: src/states_screens/dialogs/custom_video_settings.cpp:101 -#: src/states_screens/dialogs/multitouch_settings_dialog.cpp:59 -#: src/states_screens/options_screen_video.cpp:454 -msgid "Disabled" -msgstr "" - -#. I18N: if only important animated scenery is enabled -#: src/states_screens/dialogs/custom_video_settings.cpp:67 -#: src/states_screens/options_screen_video.cpp:462 +#. I18N: if only important particles effects is enabled +#: src/states_screens/dialogs/custom_video_settings.cpp:63 +#: src/states_screens/options_screen_video.cpp:420 msgid "Important only" msgstr "" -#. I18N: animations setting (only karts with human players are animated) -#: src/states_screens/dialogs/custom_video_settings.cpp:74 -msgid "Human players only" -msgstr "" - -#: src/states_screens/dialogs/custom_video_settings.cpp:77 -msgid "Enabled for all" -msgstr "" - #. I18N: Geometry level low : few details are displayed #. I18N: in the graphical options tooltip; #. indicates the rendered image quality is low +#: src/states_screens/dialogs/custom_video_settings.cpp:71 +#: src/states_screens/dialogs/custom_video_settings.cpp:80 #: src/states_screens/dialogs/custom_video_settings.cpp:86 -#: src/states_screens/dialogs/custom_video_settings.cpp:95 -#: src/states_screens/dialogs/custom_video_settings.cpp:102 -#: src/states_screens/options_screen_video.cpp:469 +#: src/states_screens/options_screen_video.cpp:427 msgid "Low" msgstr "" #. I18N: Geometry level high : everything is displayed #. I18N: in the graphical options tooltip; #. indicates the rendered image quality is high -#: src/states_screens/dialogs/custom_video_settings.cpp:88 -#: src/states_screens/dialogs/custom_video_settings.cpp:96 -#: src/states_screens/dialogs/custom_video_settings.cpp:103 -#: src/states_screens/options_screen_video.cpp:472 +#: src/states_screens/dialogs/custom_video_settings.cpp:73 +#: src/states_screens/dialogs/custom_video_settings.cpp:81 +#: src/states_screens/dialogs/custom_video_settings.cpp:87 +#: src/states_screens/options_screen_video.cpp:430 msgid "High" msgstr "" #. I18N: in the graphical options tooltip; #. indicates the rendered image quality is very low -#: src/states_screens/dialogs/custom_video_settings.cpp:94 -#: src/states_screens/options_screen_video.cpp:466 +#: src/states_screens/dialogs/custom_video_settings.cpp:79 +#: src/states_screens/options_screen_video.cpp:424 msgid "Very Low" msgstr "" -#. I18N: in the graphical options tooltip; -#. indicates the rendered image quality is very high -#: src/states_screens/dialogs/custom_video_settings.cpp:97 -#: src/states_screens/options_screen_video.cpp:475 -msgid "Very High" +#: src/states_screens/dialogs/ghost_replay_info_dialog.cpp:140 +#: src/states_screens/edit_gp_screen.cpp:65 +#: src/states_screens/ghost_replay_selection.cpp:116 +msgid "Laps" msgstr "" -#: src/states_screens/dialogs/message_dialog.cpp:129 -#: src/states_screens/edit_gp_screen.cpp:257 +#: src/states_screens/dialogs/ghost_replay_info_dialog.cpp:142 #: src/states_screens/ghost_replay_selection.cpp:117 -msgid "No" +msgid "Time" msgstr "" -#: src/states_screens/dialogs/multitouch_settings_dialog.cpp:60 -msgid "Tablet" +#: src/states_screens/dialogs/ghost_replay_info_dialog.cpp:144 +#: src/states_screens/ghost_replay_selection.cpp:118 +msgid "User" msgstr "" -#: src/states_screens/dialogs/multitouch_settings_dialog.cpp:61 -msgid "Phone" +#: src/states_screens/dialogs/ghost_replay_info_dialog.cpp:146 +#: src/states_screens/ghost_replay_selection.cpp:120 +msgid "Version" +msgstr "" + +#. I18N: In the network user dialog +#: src/states_screens/dialogs/network_user_dialog.cpp:82 +msgid "Kick" +msgstr "" + +#: src/states_screens/dialogs/network_user_dialog.cpp:107 +#: src/states_screens/dialogs/player_rankings_dialog.cpp:143 +#, c-format +msgid "Fetching ranking info for %s." +msgstr "" + +#. I18N: In the network player dialog, indiciating a network +#. player has no ranking +#: src/states_screens/dialogs/ranking_callback.hpp:57 +#, c-format +msgid "%s has no ranking yet." +msgstr "" + +#. I18N: In the network player dialog show rank and +#. score of a player +#: src/states_screens/dialogs/ranking_callback.hpp:68 +#, c-format +msgid "%s has a rank of %d with score %f." msgstr "" #: src/states_screens/dialogs/recovery_dialog.cpp:121 @@ -3197,13 +3522,7 @@ msgid "Username and/or email address invalid." msgstr "" #: src/states_screens/dialogs/registration_dialog.cpp:42 -#, c-format -msgid "" -"Please read the terms and conditions for SuperTuxKart at '%s'. You must " -"agree to these terms in order to register an account for STK. By checking " -"the box below, you are confirming that you understand these terms. If you " -"have any questions or comments regarding these terms, one of the members of " -"the development team would gladly assist you." +msgid "Please read the terms and conditions " msgstr "" #: src/states_screens/dialogs/select_challenge.cpp:52 @@ -3226,17 +3545,22 @@ msgstr "" msgid "Number of AI Karts: %i" msgstr "" -#: src/states_screens/dialogs/select_challenge.cpp:149 +#: src/states_screens/dialogs/select_challenge.cpp:174 msgid "Nitro challenge" msgstr "" -#: src/states_screens/dialogs/select_challenge.cpp:151 -#: src/states_screens/race_setup_screen.cpp:136 +#: src/states_screens/dialogs/select_challenge.cpp:176 +#: src/states_screens/race_setup_screen.cpp:135 msgid "Ghost replay race" msgstr "" -#: src/states_screens/dialogs/server_info_dialog.cpp:75 -msgid "Server successfully created. You can now join it." +#: src/states_screens/dialogs/splitscreen_player_dialog.cpp:129 +msgid "Input device already exists." +msgstr "" + +#: src/states_screens/dialogs/splitscreen_player_dialog.cpp:146 +#: src/states_screens/online_screen.cpp:233 +msgid "No player available for connecting to server." msgstr "" #: src/states_screens/dialogs/user_info_dialog.cpp:56 @@ -3294,352 +3618,376 @@ msgstr "" msgid "Performing vote" msgstr "" -#: src/states_screens/easter_egg_screen.cpp:270 -#: src/states_screens/tracks_and_gp_screen.cpp:292 -#: src/states_screens/tracks_screen.cpp:241 +#: src/states_screens/easter_egg_screen.cpp:268 +#: src/states_screens/tracks_and_gp_screen.cpp:289 +#: src/states_screens/tracks_screen.cpp:340 msgid "Random Track" msgstr "" -#: src/states_screens/edit_gp_screen.cpp:66 -#: src/states_screens/ghost_replay_selection.cpp:77 +#: src/states_screens/edit_gp_screen.cpp:64 +#: src/states_screens/ghost_replay_selection.cpp:109 msgid "Track" msgstr "" -#: src/states_screens/edit_gp_screen.cpp:67 -#: src/states_screens/ghost_replay_selection.cpp:81 -msgid "Laps" -msgstr "" - -#: src/states_screens/edit_gp_screen.cpp:68 +#: src/states_screens/edit_gp_screen.cpp:66 msgid "Reversed" msgstr "" -#: src/states_screens/edit_gp_screen.cpp:124 -#: src/states_screens/ghost_replay_selection.cpp:177 -#: src/states_screens/grand_prix_editor_screen.cpp:112 +#: src/states_screens/edit_gp_screen.cpp:122 +#: src/states_screens/ghost_replay_selection.cpp:407 +#: src/states_screens/grand_prix_editor_screen.cpp:109 #, c-format msgid "Are you sure you want to remove '%s'?" msgstr "" -#: src/states_screens/edit_gp_screen.cpp:141 +#: src/states_screens/edit_gp_screen.cpp:139 msgid "Do you want to save your changes?" msgstr "" #. I18N: Indicate that the grand prix is modified and not saved -#: src/states_screens/edit_gp_screen.cpp:286 +#: src/states_screens/edit_gp_screen.cpp:284 #, c-format msgid "%s (+)" msgstr "" -#: src/states_screens/edit_gp_screen.cpp:325 +#: src/states_screens/edit_gp_screen.cpp:323 msgid "An error occurred while trying to save your grand prix." msgstr "" -#: src/states_screens/edit_track_screen.cpp:235 +#: src/states_screens/edit_track_screen.cpp:233 msgid "Select a track" msgstr "" -#: src/states_screens/feature_unlocked.cpp:201 +#: src/states_screens/feature_unlocked.cpp:247 #, c-format msgid "You completed the easy challenge! Points earned on this level: %i/%i" msgstr "" -#: src/states_screens/feature_unlocked.cpp:205 +#: src/states_screens/feature_unlocked.cpp:251 #, c-format msgid "" "You completed the intermediate challenge! Points earned on this level: %i/%i" msgstr "" -#: src/states_screens/feature_unlocked.cpp:209 +#: src/states_screens/feature_unlocked.cpp:255 #, c-format msgid "" "You completed the difficult challenge! Points earned on this level: %i/%i" msgstr "" -#: src/states_screens/feature_unlocked.cpp:494 +#: src/states_screens/feature_unlocked.cpp:259 +#, c-format +msgid "" +"You completed the SuperTux challenge! Points earned on this level: %i/%i" +msgstr "" + +#: src/states_screens/feature_unlocked.cpp:299 +#, c-format +msgid "You unlocked %s!" +msgstr "" + +#: src/states_screens/feature_unlocked.cpp:607 msgid "Challenge Completed" msgstr "" -#: src/states_screens/feature_unlocked.cpp:531 +#: src/states_screens/feature_unlocked.cpp:644 msgid "You unlocked track %0" msgstr "" -#: src/states_screens/feature_unlocked.cpp:569 +#: src/states_screens/feature_unlocked.cpp:682 msgid "You unlocked grand prix %0" msgstr "" -#: src/states_screens/ghost_replay_selection.cpp:82 -msgid "Finish Time" -msgstr "" - -#: src/states_screens/ghost_replay_selection.cpp:83 -msgid "User" -msgstr "" - -#: src/states_screens/gp_info_screen.cpp:74 +#: src/states_screens/gp_info_screen.cpp:72 msgid "Default" msgstr "" -#. I18N: if no kart animations are enabled -#: src/states_screens/gp_info_screen.cpp:75 -#: src/states_screens/options_screen_video.cpp:460 +#: src/states_screens/gp_info_screen.cpp:73 msgid "None" msgstr "" -#: src/states_screens/gp_info_screen.cpp:77 +#: src/states_screens/gp_info_screen.cpp:75 msgid "Random" msgstr "" -#: src/states_screens/gp_info_screen.cpp:154 -#: src/states_screens/gp_info_screen.cpp:179 +#: src/states_screens/gp_info_screen.cpp:152 +#: src/states_screens/gp_info_screen.cpp:177 msgid "Reload" msgstr "" #: src/states_screens/grand_prix_cutscene.cpp:75 -#: src/states_screens/grand_prix_editor_screen.cpp:101 -#: src/states_screens/grand_prix_editor_screen.cpp:118 +#: src/states_screens/grand_prix_editor_screen.cpp:98 +#: src/states_screens/grand_prix_editor_screen.cpp:115 msgid "Please enter the name of the grand prix" msgstr "" -#: src/states_screens/grand_prix_editor_screen.cpp:169 +#: src/states_screens/grand_prix_editor_screen.cpp:166 msgid "Please select a Grand Prix" msgstr "" -#: src/states_screens/grand_prix_editor_screen.cpp:338 +#: src/states_screens/grand_prix_editor_screen.cpp:335 msgid "User defined" msgstr "" -#: src/states_screens/grand_prix_editor_screen.cpp:351 +#: src/states_screens/grand_prix_editor_screen.cpp:348 msgid "Name is empty." msgstr "" -#: src/states_screens/grand_prix_editor_screen.cpp:359 +#: src/states_screens/grand_prix_editor_screen.cpp:356 msgid "Another grand prix with this name already exists." msgstr "" -#: src/states_screens/grand_prix_editor_screen.cpp:365 +#: src/states_screens/grand_prix_editor_screen.cpp:362 msgid "Name is too long." msgstr "" #. I18N: when failing a GP -#: src/states_screens/grand_prix_lose.cpp:155 +#: src/states_screens/grand_prix_lose.cpp:153 msgid "Better luck next time!" msgstr "" -#: src/states_screens/grand_prix_win.cpp:166 -#: src/states_screens/race_result_gui.cpp:194 +#: src/states_screens/grand_prix_win.cpp:164 +#: src/states_screens/race_result_gui.cpp:186 msgid "You completed a challenge!" msgstr "" -#: src/states_screens/grand_prix_win.cpp:322 +#: src/states_screens/grand_prix_win.cpp:321 msgid "You completed the Grand Prix!" msgstr "" -#: src/states_screens/kart_selection.cpp:843 -#: src/states_screens/kart_selection.cpp:1499 +#: src/states_screens/kart_selection.cpp:862 +#: src/states_screens/kart_selection.cpp:1519 msgid "Random Kart" msgstr "" -#: src/states_screens/kart_selection.cpp:859 +#: src/states_screens/kart_selection.cpp:881 msgid "Locked" msgstr "" -#: src/states_screens/kart_selection.cpp:961 +#: src/states_screens/kart_selection.cpp:982 +#: src/states_screens/networking_lobby.cpp:239 msgid "" "Everyone:\n" "Press the 'Select' button to join the game" msgstr "" -#: src/states_screens/main_menu_screen.cpp:510 +#: src/states_screens/main_menu_screen.cpp:489 +#: src/states_screens/online_screen.cpp:211 msgid "" "You can not play online without internet access. If you want to play online, " "go to options, select tab 'User Interface', and edit \"Connect to the " "Internet\"." msgstr "" -#: src/states_screens/main_menu_screen.cpp:534 +#: src/states_screens/main_menu_screen.cpp:521 msgid "" "You can not download addons without internet access. If you want to download " "addons, go to options, select tab 'User Interface', and edit \"Connect to " "the Internet\"." msgstr "" -#: src/states_screens/main_menu_screen.cpp:566 +#: src/states_screens/main_menu_screen.cpp:553 msgid "The add-ons module is currently disabled in the Options screen" msgstr "" -#: src/states_screens/main_menu_screen.cpp:578 +#: src/states_screens/main_menu_screen.cpp:565 msgid "Please wait while the add-ons are loading" msgstr "" -#: src/states_screens/online_profile_achievements.cpp:79 +#. I18N: In the networking lobby, display the starting timeout +#. for owner-less server +#: src/states_screens/networking_lobby.cpp:231 +#, c-format +msgid "Game will start after %d second(s)." +msgstr "" + +#: src/states_screens/networking_lobby.cpp:260 +#, c-format +msgid "Connecting to server %s" +msgstr "" + +#: src/states_screens/networking_lobby.cpp:265 +msgid "Finding a quick play server" +msgstr "" + +#: src/states_screens/networking_lobby.cpp:290 +#, c-format +msgid "Lobby (ping: %dms)" +msgstr "" + +#: src/states_screens/online_profile_achievements.cpp:77 msgid "Progress" msgstr "" -#: src/states_screens/online_profile_achievements.cpp:131 -#: src/states_screens/online_profile_achievements.cpp:171 +#: src/states_screens/online_profile_achievements.cpp:129 +#: src/states_screens/online_profile_achievements.cpp:174 msgid "Fetching achievements" msgstr "" -#: src/states_screens/online_profile_base.cpp:117 -#: src/states_screens/online_profile_base.cpp:127 -msgid "Your profile" -msgstr "" - -#: src/states_screens/online_profile_base.cpp:120 +#: src/states_screens/online_profile_base.cpp:115 #, c-format msgid "%s's profile" msgstr "" -#: src/states_screens/online_profile_friends.cpp:75 +#: src/states_screens/online_profile_friends.cpp:73 msgid "Since" msgstr "" -#: src/states_screens/online_profile_friends.cpp:76 +#: src/states_screens/online_profile_friends.cpp:74 msgid "Status" msgstr "" -#: src/states_screens/online_profile_friends.cpp:96 -#: src/states_screens/online_profile_friends.cpp:247 +#: src/states_screens/online_profile_friends.cpp:94 +#: src/states_screens/online_profile_friends.cpp:245 msgid "Fetching friends" msgstr "" -#: src/states_screens/online_profile_friends.cpp:218 +#: src/states_screens/online_profile_friends.cpp:216 msgid "New Request" msgstr "" -#: src/states_screens/online_profile_friends.cpp:219 +#: src/states_screens/online_profile_friends.cpp:217 msgid "Pending" msgstr "" -#: src/states_screens/online_profile_friends.cpp:223 +#: src/states_screens/online_profile_friends.cpp:221 msgid "Offline" msgstr "" -#: src/states_screens/online_user_search.cpp:207 -#: src/states_screens/online_user_search.cpp:271 +#: src/states_screens/online_screen.cpp:238 +msgid "" +"Enter the server address with IP (optional) followed by : and then port." +msgstr "" + +#: src/states_screens/online_screen.cpp:249 +#, c-format +msgid "Invalid server address: %s." +msgstr "" + +#: src/states_screens/online_user_search.cpp:205 +#: src/states_screens/online_user_search.cpp:269 msgid "Searching" msgstr "" -#: src/states_screens/options_screen_device.cpp:71 +#: src/states_screens/options_screen_device.cpp:69 msgid "Action" msgstr "" -#: src/states_screens/options_screen_device.cpp:72 +#: src/states_screens/options_screen_device.cpp:70 msgid "Key binding" msgstr "" #. I18N: button to disable a gamepad configuration -#: src/states_screens/options_screen_device.cpp:96 -#: src/states_screens/options_screen_device.cpp:579 +#: src/states_screens/options_screen_device.cpp:94 +#: src/states_screens/options_screen_device.cpp:586 msgid "Disable Device" msgstr "" #. I18N: button to enable a gamepad configuration -#: src/states_screens/options_screen_device.cpp:98 -#: src/states_screens/options_screen_device.cpp:580 +#: src/states_screens/options_screen_device.cpp:96 +#: src/states_screens/options_screen_device.cpp:587 msgid "Enable Device" msgstr "" #. I18N: Key binding section -#: src/states_screens/options_screen_device.cpp:137 +#: src/states_screens/options_screen_device.cpp:140 msgid "Game Keys" msgstr "" #. I18N: Key binding section -#: src/states_screens/options_screen_device.cpp:151 +#: src/states_screens/options_screen_device.cpp:154 msgid "Menu Keys" msgstr "" #. I18N: Key binding name -#: src/states_screens/options_screen_device.cpp:218 +#: src/states_screens/options_screen_device.cpp:225 msgid "Steer Left" msgstr "" #. I18N: Key binding name -#: src/states_screens/options_screen_device.cpp:221 +#: src/states_screens/options_screen_device.cpp:228 msgid "Steer Right" msgstr "" #. I18N: Key binding name -#: src/states_screens/options_screen_device.cpp:224 +#: src/states_screens/options_screen_device.cpp:231 msgid "Accelerate" msgstr "" #. I18N: Key binding name -#: src/states_screens/options_screen_device.cpp:227 +#: src/states_screens/options_screen_device.cpp:234 msgid "Brake" msgstr "" #. I18N: Key binding name -#: src/states_screens/options_screen_device.cpp:230 +#: src/states_screens/options_screen_device.cpp:237 msgid "Fire" msgstr "" #. I18N: Key binding name -#: src/states_screens/options_screen_device.cpp:233 +#: src/states_screens/options_screen_device.cpp:240 msgid "Nitro" msgstr "" #. I18N: Key binding name -#: src/states_screens/options_screen_device.cpp:236 +#: src/states_screens/options_screen_device.cpp:243 msgid "Skidding" msgstr "" #. I18N: Key binding name -#: src/states_screens/options_screen_device.cpp:239 +#: src/states_screens/options_screen_device.cpp:246 msgid "Look Back" msgstr "" #. I18N: Key binding name -#: src/states_screens/options_screen_device.cpp:242 +#: src/states_screens/options_screen_device.cpp:249 msgid "Rescue" msgstr "" #. I18N: Key binding name -#: src/states_screens/options_screen_device.cpp:245 +#: src/states_screens/options_screen_device.cpp:252 msgid "Pause Game" msgstr "" #. I18N: Key binding name -#: src/states_screens/options_screen_device.cpp:250 +#: src/states_screens/options_screen_device.cpp:257 msgid "Up" msgstr "" #. I18N: Key binding name -#: src/states_screens/options_screen_device.cpp:253 +#: src/states_screens/options_screen_device.cpp:260 msgid "Down" msgstr "" #. I18N: Key binding name -#: src/states_screens/options_screen_device.cpp:256 +#: src/states_screens/options_screen_device.cpp:263 msgid "Left" msgstr "" #. I18N: Key binding name -#: src/states_screens/options_screen_device.cpp:259 +#: src/states_screens/options_screen_device.cpp:266 msgid "Right" msgstr "" #. I18N: Key binding name -#: src/states_screens/options_screen_device.cpp:262 +#: src/states_screens/options_screen_device.cpp:269 msgid "Select" msgstr "" #. I18N: Key binding name -#: src/states_screens/options_screen_device.cpp:265 +#: src/states_screens/options_screen_device.cpp:272 msgid "Cancel/Back" msgstr "" -#: src/states_screens/options_screen_device.cpp:360 +#: src/states_screens/options_screen_device.cpp:367 msgid "* A blue item means a conflict with another configuration" msgstr "" -#: src/states_screens/options_screen_device.cpp:365 +#: src/states_screens/options_screen_device.cpp:372 msgid "* A red item means a conflict in the current configuration" msgstr "" -#: src/states_screens/options_screen_device.cpp:471 +#: src/states_screens/options_screen_device.cpp:478 msgid "" "Warning: The 'Shift' is not a recommended key. When 'Shift' is pressed down, " "all keys that contain a character that is different in upper-case will stop " @@ -3647,131 +3995,115 @@ msgid "" msgstr "" #. I18N: shown before deleting an input configuration -#: src/states_screens/options_screen_device.cpp:567 +#: src/states_screens/options_screen_device.cpp:574 msgid "Are you sure you want to permanently delete this configuration?" msgstr "" -#: src/states_screens/options_screen_input.cpp:100 +#: src/states_screens/options_screen_input.cpp:101 +msgid "Gamepad" +msgstr "" + +#: src/states_screens/options_screen_input.cpp:108 #, c-format msgid "Keyboard %i" msgstr "" -#: src/states_screens/options_screen_input.cpp:138 +#: src/states_screens/options_screen_input.cpp:147 msgid "Touch Device" msgstr "" -#: src/states_screens/options_screen_ui.cpp:159 +#: src/states_screens/options_screen_ui.cpp:168 msgid "" "In multiplayer mode, players can select handicapped (more difficult) " "profiles on the kart selection screen" msgstr "" #. I18N: in the language choice, to select the same language as the OS -#: src/states_screens/options_screen_ui.cpp:191 +#: src/states_screens/options_screen_ui.cpp:203 msgid "System Language" msgstr "" #. I18N: custom video settings -#: src/states_screens/options_screen_video.cpp:433 +#: src/states_screens/options_screen_video.cpp:397 msgid "Custom" msgstr "" -#. I18N: if some kart animations are enabled -#: src/states_screens/options_screen_video.cpp:458 -msgid "Me Only" -msgstr "" - #. I18N: in graphical options -#. tooltip = tooltip + L"\n" + _("Pixel shaders: %s", -#. UserConfigParams::m_pixel_shaders ? enabled : disabled); -#. I18N: in graphical options -#: src/states_screens/options_screen_video.cpp:481 +#: src/states_screens/options_screen_video.cpp:433 #, c-format -msgid "Animated Scenery: %s" +msgid "Particles Effects: %s" msgstr "" #. I18N: in graphical options -#: src/states_screens/options_screen_video.cpp:487 -#, c-format -msgid "Weather Effects: %s" -msgstr "" - -#. I18N: in graphical options -#: src/states_screens/options_screen_video.cpp:490 +#: src/states_screens/options_screen_video.cpp:439 #, c-format msgid "Animated Characters: %s" msgstr "" #. I18N: in graphical options -#: src/states_screens/options_screen_video.cpp:495 +#: src/states_screens/options_screen_video.cpp:442 #, c-format msgid "Dynamic lights: %s" msgstr "" #. I18N: in graphical options -#: src/states_screens/options_screen_video.cpp:498 +#: src/states_screens/options_screen_video.cpp:445 #, c-format msgid "Motion blur: %s" msgstr "" #. I18N: in graphical options -#: src/states_screens/options_screen_video.cpp:501 +#: src/states_screens/options_screen_video.cpp:448 #, c-format msgid "Anti-aliasing: %s" msgstr "" #. I18N: in graphical options -#: src/states_screens/options_screen_video.cpp:504 +#: src/states_screens/options_screen_video.cpp:451 #, c-format msgid "Ambient occlusion: %s" msgstr "" -#: src/states_screens/options_screen_video.cpp:508 +#: src/states_screens/options_screen_video.cpp:455 #, c-format msgid "Shadows: %s" msgstr "" -#: src/states_screens/options_screen_video.cpp:510 +#: src/states_screens/options_screen_video.cpp:457 #, c-format msgid "Shadows: %i" msgstr "" #. I18N: in graphical options -#: src/states_screens/options_screen_video.cpp:513 +#: src/states_screens/options_screen_video.cpp:460 #, c-format msgid "Bloom: %s" msgstr "" #. I18N: in graphical options -#: src/states_screens/options_screen_video.cpp:517 +#: src/states_screens/options_screen_video.cpp:464 #, c-format msgid "Glow (outlines): %s" msgstr "" #. I18N: in graphical options -#: src/states_screens/options_screen_video.cpp:521 +#: src/states_screens/options_screen_video.cpp:468 #, c-format msgid "Light shaft (God rays): %s" msgstr "" #. I18N: in graphical options -#: src/states_screens/options_screen_video.cpp:525 +#: src/states_screens/options_screen_video.cpp:472 #, c-format msgid "Depth of field: %s" msgstr "" -#. I18N: in graphical options -#: src/states_screens/options_screen_video.cpp:529 -#, c-format -msgid "Global illumination: %s" -msgstr "" - -#: src/states_screens/options_screen_video.cpp:534 +#: src/states_screens/options_screen_video.cpp:477 #, c-format msgid "Rendered image quality: %s" msgstr "" -#: src/states_screens/race_gui.cpp:358 src/states_screens/race_gui.cpp:360 +#: src/states_screens/race_gui.cpp:400 src/states_screens/race_gui.cpp:402 msgid "Challenge Failed" msgstr "" @@ -3796,255 +4128,288 @@ msgid "GOAL!" msgstr "" #. I18N: string used to show the author of the music. (e.g. "Sunny Song" by "John Doe") -#: src/states_screens/race_gui_base.cpp:517 +#: src/states_screens/race_gui_base.cpp:551 msgid "by" msgstr "" -#: src/states_screens/race_gui_base.cpp:626 +#: src/states_screens/race_gui_base.cpp:658 msgid "Collect nitro!" msgstr "" -#: src/states_screens/race_gui_base.cpp:628 +#: src/states_screens/race_gui_base.cpp:660 msgid "Follow the leader!" msgstr "" #. I18N: When some GlobalPlayerIcons are hidden, write "Top 10" to show it -#: src/states_screens/race_gui_base.cpp:806 +#: src/states_screens/race_gui_base.cpp:840 #, c-format msgid "Top %i" msgstr "" #. I18N: Shown at the end of a race -#: src/states_screens/race_gui_overworld.cpp:143 +#: src/states_screens/race_gui_overworld.cpp:146 msgid "Lap" msgstr "" -#: src/states_screens/race_gui_overworld.cpp:144 +#: src/states_screens/race_gui_overworld.cpp:147 msgid "Rank" msgstr "" -#: src/states_screens/race_gui_overworld.cpp:518 +#: src/states_screens/race_gui_overworld.cpp:591 msgid "Press fire to start the tutorial" msgstr "" -#: src/states_screens/race_gui_overworld.cpp:557 +#: src/states_screens/race_gui_overworld.cpp:630 msgid "Type: Grand Prix" msgstr "" -#: src/states_screens/race_gui_overworld.cpp:594 +#: src/states_screens/race_gui_overworld.cpp:667 msgid "Press fire to start the challenge" msgstr "" -#: src/states_screens/race_result_gui.cpp:175 -msgid "Continue." +#: src/states_screens/race_result_gui.cpp:176 +msgid "Quit the server" msgstr "" -#: src/states_screens/race_result_gui.cpp:178 -msgid "Quit the server." -msgstr "" - -#: src/states_screens/race_result_gui.cpp:195 +#: src/states_screens/race_result_gui.cpp:187 msgid "You completed challenges!" msgstr "" -#: src/states_screens/race_result_gui.cpp:208 +#: src/states_screens/race_result_gui.cpp:203 msgid "Abort Grand Prix" msgstr "" -#: src/states_screens/race_result_gui.cpp:218 +#: src/states_screens/race_result_gui.cpp:219 msgid "Restart" msgstr "" -#: src/states_screens/race_result_gui.cpp:224 +#: src/states_screens/race_result_gui.cpp:225 msgid "Back to challenge selection" msgstr "" #: src/states_screens/race_result_gui.cpp:230 +msgid "Race against the new ghost replay" +msgstr "" + +#: src/states_screens/race_result_gui.cpp:234 msgid "Back to the menu" msgstr "" -#: src/states_screens/race_result_gui.cpp:372 +#: src/states_screens/race_result_gui.cpp:268 +msgid "Waiting for others" +msgstr "" + +#: src/states_screens/race_result_gui.cpp:379 msgid "Do you really want to abort the Grand Prix?" msgstr "" -#: src/states_screens/race_result_gui.cpp:499 -#: src/states_screens/race_result_gui.cpp:865 +#: src/states_screens/race_result_gui.cpp:464 +msgid "Network grand prix has been finished." +msgstr "" + +#: src/states_screens/race_result_gui.cpp:544 +#: src/states_screens/race_result_gui.cpp:910 msgid "Eliminated" msgstr "" -#: src/states_screens/race_result_gui.cpp:1012 +#: src/states_screens/race_result_gui.cpp:1057 msgid "Red Team Wins" msgstr "" -#: src/states_screens/race_result_gui.cpp:1016 +#: src/states_screens/race_result_gui.cpp:1061 msgid "Blue Team Wins" msgstr "" -#: src/states_screens/race_result_gui.cpp:1021 +#: src/states_screens/race_result_gui.cpp:1066 msgid "It's a draw" msgstr "" #. I18N: indicates a player that scored in their own goal in result screen -#: src/states_screens/race_result_gui.cpp:1098 -#: src/states_screens/race_result_gui.cpp:1154 +#: src/states_screens/race_result_gui.cpp:1143 +#: src/states_screens/race_result_gui.cpp:1199 msgid "(Own Goal)" msgstr "" -#: src/states_screens/race_result_gui.cpp:1220 +#: src/states_screens/race_result_gui.cpp:1265 #, c-format msgid "Track %i/%i" msgstr "" -#: src/states_screens/race_result_gui.cpp:1304 +#: src/states_screens/race_result_gui.cpp:1349 msgid "Grand Prix progress:" msgstr "" -#: src/states_screens/race_result_gui.cpp:1346 +#: src/states_screens/race_result_gui.cpp:1391 msgid "Highscores" msgstr "" -#: src/states_screens/race_result_gui.cpp:1432 -#, c-format -msgid "Difficulty: %s" -msgstr "" - -#: src/states_screens/race_result_gui.cpp:1440 +#: src/states_screens/race_result_gui.cpp:1485 #, c-format msgid "Best lap time: %s" msgstr "" -#: src/states_screens/race_setup_screen.cpp:87 +#: src/states_screens/race_setup_screen.cpp:86 msgid "All blows allowed, so catch weapons and make clever use of them!" msgstr "" -#: src/states_screens/race_setup_screen.cpp:94 +#: src/states_screens/race_setup_screen.cpp:93 msgid "Contains no powerups, so only your driving skills matter!" msgstr "" #. I18N: short definition for follow-the-leader game mode -#: src/states_screens/race_setup_screen.cpp:107 +#: src/states_screens/race_setup_screen.cpp:106 msgid "Keep up with the leader kart but don't overtake it!" msgstr "" -#: src/states_screens/race_setup_screen.cpp:114 +#: src/states_screens/race_setup_screen.cpp:113 msgid "Hit others with weapons until they lose all their lives." msgstr "" -#: src/states_screens/race_setup_screen.cpp:119 +#: src/states_screens/race_setup_screen.cpp:118 msgid "Push the ball into the opposite cage to score goals." msgstr "" -#: src/states_screens/race_setup_screen.cpp:129 +#: src/states_screens/race_setup_screen.cpp:128 msgid "Explore tracks to find all hidden eggs" msgstr "" -#: src/states_screens/race_setup_screen.cpp:137 +#: src/states_screens/race_setup_screen.cpp:136 msgid "Race against ghost karts and try to beat them!" msgstr "" -#: src/states_screens/register_screen.cpp:218 -#: src/states_screens/register_screen.cpp:225 +#: src/states_screens/register_screen.cpp:219 +#: src/states_screens/register_screen.cpp:226 #, c-format msgid "Could not create player '%s'." msgstr "" -#: src/states_screens/register_screen.cpp:277 +#: src/states_screens/register_screen.cpp:278 msgid "Emails don't match!" msgstr "" -#: src/states_screens/register_screen.cpp:281 +#: src/states_screens/register_screen.cpp:282 msgid "Online username has to be between 3 and 30 characters long!" msgstr "" -#: src/states_screens/register_screen.cpp:285 +#: src/states_screens/register_screen.cpp:286 msgid "Online username must not start with a number!" msgstr "" -#: src/states_screens/register_screen.cpp:293 +#: src/states_screens/register_screen.cpp:294 msgid "Email has to be between 5 and 254 characters long!" msgstr "" -#: src/states_screens/register_screen.cpp:299 +#: src/states_screens/register_screen.cpp:300 msgid "Email is invalid!" msgstr "" -#: src/states_screens/register_screen.cpp:362 +#: src/states_screens/register_screen.cpp:363 msgid "" "You will receive an email with further instructions regarding account " "activation. Please be patient and be sure to check your spam folder." msgstr "" -#: src/states_screens/register_screen.cpp:402 -#: src/states_screens/user_screen.cpp:338 +#: src/states_screens/register_screen.cpp:403 +#: src/states_screens/user_screen.cpp:337 msgid "Internet access is disabled, please enable it in the options" msgstr "" -#: src/states_screens/server_selection.cpp:79 -#: src/states_screens/server_selection.cpp:247 +#. I18N: In server selection screen, owner of server, only displayed +#. if it's localhost or friends' +#: src/states_screens/server_selection.cpp:111 +msgid "Owner" +msgstr "" + +#. I18N: In server selection screen, distance to server +#: src/states_screens/server_selection.cpp:113 +msgid "Distance (km)" +msgstr "" + +#. I18N: In server selection screen, unknown distance to server +#: src/states_screens/server_selection.cpp:189 +msgid "Unknown" +msgstr "" + +#: src/states_screens/server_selection.cpp:287 +msgid "No server is available." +msgstr "" + +#: src/states_screens/server_selection.cpp:295 msgid "Fetching servers" msgstr "" #. I18N: track group name -#: src/states_screens/tracks_and_gp_screen.cpp:144 +#: src/states_screens/tracks_and_gp_screen.cpp:141 msgid "all" msgstr "" -#: src/states_screens/tracks_and_gp_screen.cpp:195 +#: src/states_screens/tracks_and_gp_screen.cpp:192 msgid "Locked!" msgstr "" -#: src/states_screens/tracks_and_gp_screen.cpp:278 -#: src/states_screens/tracks_screen.cpp:227 +#: src/states_screens/tracks_and_gp_screen.cpp:275 +#: src/states_screens/tracks_screen.cpp:326 msgid "Locked: solve active challenges to gain access to more!" msgstr "" -#: src/states_screens/tracks_screen.cpp:194 -msgid "Only official tracks are supported." +#. I18N: In track screen +#. I18N: In the track info screen +#: src/states_screens/tracks_screen.cpp:171 +#: src/states_screens/track_info_screen.cpp:216 +msgid "Drive in reverse" +msgstr "" + +#. I18N: In track screen for networking, clarify voting phase +#: src/states_screens/tracks_screen.cpp:179 +msgid "" +"If a majority of players all select the same track and race settings, voting " +"will end early." +msgstr "" + +#. I18N: In tracks screen, about voting of tracks in network +#: src/states_screens/tracks_screen.cpp:398 +#, c-format +msgid "Remaining time: %d\n" msgstr "" #. I18N: when showing who is the author of track '%s' #. I18N: (place %s where the name of the author should appear) -#: src/states_screens/track_info_screen.cpp:112 +#: src/states_screens/track_info_screen.cpp:110 #, c-format msgid "Track by %s" msgstr "" #. I18N: the max players supported by an arena. -#: src/states_screens/track_info_screen.cpp:120 +#: src/states_screens/track_info_screen.cpp:118 #, c-format msgid "Max players supported: %d" msgstr "" #. I18N: In the track info screen -#: src/states_screens/track_info_screen.cpp:213 -msgid "Drive in reverse" -msgstr "" - -#. I18N: In the track info screen -#: src/states_screens/track_info_screen.cpp:218 +#: src/states_screens/track_info_screen.cpp:221 msgid "Random item location" msgstr "" -#: src/states_screens/user_screen.cpp:111 +#: src/states_screens/user_screen.cpp:109 msgid "Exit game" msgstr "" -#: src/states_screens/user_screen.cpp:484 +#: src/states_screens/user_screen.cpp:494 msgid "You need to enter a password." msgstr "" -#: src/states_screens/user_screen.cpp:505 +#: src/states_screens/user_screen.cpp:515 #, c-format msgid "Logging out '%s'" msgstr "" -#: src/states_screens/user_screen.cpp:506 +#: src/states_screens/user_screen.cpp:516 #, c-format msgid "Logging in '%s'" msgstr "" #. I18N: In the player info dialog (when deleting) -#: src/states_screens/user_screen.cpp:595 +#: src/states_screens/user_screen.cpp:605 #, c-format msgid "Do you really want to delete player '%s' ?" msgstr "" @@ -4057,7 +4422,7 @@ msgstr "" #. I18N: Do NOT literally translate this string!! Please enter Y as the #. translation if your language is a RTL (right-to-left) language, #. N (or nothing) otherwise -#: src/utils/translation.cpp:376 +#: src/utils/translation.cpp:398 msgid " Is this a RTL language?" msgstr "" From 785aaf764707d27ce099cd4e4d21593f883b4038 Mon Sep 17 00:00:00 2001 From: Benau Date: Thu, 7 Jun 2018 14:28:06 +0800 Subject: [PATCH 135/137] Add auto end network game (implicitly on for ranked server) --- src/main.cpp | 7 +++++++ src/modes/linear_world.cpp | 19 +++++++++++++++++++ src/modes/linear_world.hpp | 4 +++- src/network/network_config.cpp | 1 + src/network/network_config.hpp | 7 +++++++ src/network/protocols/server_lobby.cpp | 2 ++ src/network/race_event_manager.cpp | 9 --------- 7 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index df34a9785..8e98a525d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -599,6 +599,8 @@ void cmdLineHelp() " --auto-connect Automatically connect to fist server and start race\n" " --max-players=n Maximum number of clients (server only).\n" " --motd Message showing in all lobby of clients, can specify a .txt file.\n" + " --auto-end Automatically end network game after 1st player finished\n" + " for some time (currently his finished time * 1.25 + 15.0). \n" " --no-validation Allow non validated and unencrypted connection in wan.\n" " --ranked Server will submit ranking to stk addons server.\n" " You require permission for that.\n" @@ -1088,6 +1090,11 @@ int handleCmdLine() NetworkConfig::get()->setValidatedPlayers(true); NetworkConfig::get()->setRankedServer(true); NetworkConfig::get()->setOwnerLess(true); + NetworkConfig::get()->setAutoEnd(true); + } + if (CommandLine::has("--auto-end")) + { + NetworkConfig::get()->setAutoEnd(true); } if (CommandLine::has("--owner-less")) { diff --git a/src/modes/linear_world.cpp b/src/modes/linear_world.cpp index f0ea17e38..b2e238e84 100644 --- a/src/modes/linear_world.cpp +++ b/src/modes/linear_world.cpp @@ -30,6 +30,7 @@ #include "graphics/material.hpp" #include "guiengine/modaldialog.hpp" #include "physics/physics.hpp" +#include "network/network_config.hpp" #include "race/history.hpp" #include "states_screens/race_gui_base.hpp" #include "tracks/drive_graph.hpp" @@ -92,6 +93,7 @@ LinearWorld::~LinearWorld() void LinearWorld::reset() { WorldWithRank::reset(); + m_finish_timeout = std::numeric_limits::max(); m_last_lap_sfx_played = false; m_last_lap_sfx_playing = false; m_fastest_lap_ticks = INT_MAX; @@ -157,6 +159,16 @@ void LinearWorld::reset() */ void LinearWorld::update(int ticks) { + if (getPhase() == RACE_PHASE && + m_finish_timeout != std::numeric_limits::max()) + { + m_finish_timeout -= stk_config->ticks2Time(ticks); + if (m_finish_timeout < 0.0f) + { + endRaceEarly(); + m_finish_timeout = std::numeric_limits::max(); + } + } const unsigned int kart_amount = getNumKarts(); // Do stuff specific to this subtype of race. @@ -439,6 +451,13 @@ void LinearWorld::newLap(unsigned int kart_index) float prev_time = kart->getRecentPreviousXYZTime(); float finish_time = prev_time*finish_proportion + getTime()*(1.0f-finish_proportion); + + if (NetworkConfig::get()->isServer() && + NetworkConfig::get()->isAutoEnd() && + m_finish_timeout == std::numeric_limits::max()) + { + m_finish_timeout = finish_time * 1.25f + 15.0f; + } kart->finishedRace(finish_time); } } diff --git a/src/modes/linear_world.hpp b/src/modes/linear_world.hpp index 3ac616b61..8a41a1d17 100644 --- a/src/modes/linear_world.hpp +++ b/src/modes/linear_world.hpp @@ -58,12 +58,14 @@ private: /** This stores the live time difference between a ghost kart * and a second kart racing against it (normal or ghost). */ - float m_live_time_difference; /** True if the live_time_difference is invalid */ bool m_valid_reference_time; + /* if set then the game will auto end after this time for networking */ + float m_finish_timeout; + /** This calculate the time difference between the second kart in the race * (there must be at least two) and the first kart in the race * (who must be a ghost). diff --git a/src/network/network_config.cpp b/src/network/network_config.cpp index 95a9d725a..803a06a56 100644 --- a/src/network/network_config.cpp +++ b/src/network/network_config.cpp @@ -51,6 +51,7 @@ NetworkConfig::NetworkConfig() m_is_public_server = false; m_is_ranked_server = false; m_validated_players = false; + m_auto_end = false; m_owner_less = false; m_done_adding_network_players = false; m_max_players = 4; diff --git a/src/network/network_config.hpp b/src/network/network_config.hpp index 5b61bef1f..a8913107d 100644 --- a/src/network/network_config.hpp +++ b/src/network/network_config.hpp @@ -70,6 +70,9 @@ private: /** True if this is a ranked server */ bool m_is_ranked_server; + /* True if automatically end after 1st player finished for some time. */ + bool m_auto_end; + /** The password for a server (or to authenticate to a server). */ std::string m_password; @@ -317,6 +320,10 @@ public: void setOwnerLess(bool val) { m_owner_less = val; } // ------------------------------------------------------------------------ bool isOwnerLess() const { return m_owner_less; } + // ------------------------------------------------------------------------ + void setAutoEnd(bool val) { m_auto_end = val; } + // ------------------------------------------------------------------------ + bool isAutoEnd() const { return m_auto_end; } }; // class NetworkConfig diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index d0dc8906d..65e852f2f 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -811,6 +811,8 @@ void ServerLobby::checkRaceFinished() assert(World::getWorld()); if (!RaceEventManager::getInstance()->isRaceOver()) return; + Log::info("ServerLobby", "The game is considered finish."); + // Reset for next state usage resetPeersReady(); NetworkString* total = getNetworkString(); diff --git a/src/network/race_event_manager.cpp b/src/network/race_event_manager.cpp index 53b2cc7ca..efaca7e91 100644 --- a/src/network/race_event_manager.cpp +++ b/src/network/race_event_manager.cpp @@ -33,15 +33,6 @@ void RaceEventManager::update(int ticks) &ticks); PROFILER_POP_CPU_MARKER(); World::getWorld()->updateWorld(ticks); - - // if the race is over - if (World::getWorld()->getPhase() >= WorldStatus::RESULT_DISPLAY_PHASE && - World::getWorld()->getPhase() != WorldStatus::IN_GAME_MENU_PHASE) - { - // consider the world finished. - stop(); - Log::info("RaceEventManager", "The game is considered finish."); - } } // update // ---------------------------------------------------------------------------- From 9f6c87a6364bb58a660d8eee2f98567dbaf4b05e Mon Sep 17 00:00:00 2001 From: Benau Date: Thu, 7 Jun 2018 15:31:13 +0800 Subject: [PATCH 136/137] Fix cornfield crossing network play --- src/tracks/track_object_manager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tracks/track_object_manager.cpp b/src/tracks/track_object_manager.cpp index 0fcda15c1..47bc3b430 100644 --- a/src/tracks/track_object_manager.cpp +++ b/src/tracks/track_object_manager.cpp @@ -244,7 +244,7 @@ void TrackObjectManager::removeForRewind() { for (TrackObject* curr : m_all_objects) { - if (curr->getPhysicalObject() && + if (curr->isEnabled() && curr->getPhysicalObject() && curr->getPhysicalObject()->isDynamic()) curr->getPhysicalObject()->removeBody(); } @@ -255,7 +255,7 @@ void TrackObjectManager::addForRewind() { for (TrackObject* curr : m_all_objects) { - if (curr->getPhysicalObject() && + if (curr->isEnabled() && curr->getPhysicalObject() && curr->getPhysicalObject()->isDynamic()) curr->getPhysicalObject()->addBody(); } From 304787f106ad361e0cef7ee47d80e664e86964e0 Mon Sep 17 00:00:00 2001 From: Benau Date: Thu, 7 Jun 2018 16:09:48 +0800 Subject: [PATCH 137/137] Remove unneeded translation in server lobby --- src/network/protocols/server_lobby.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 65e852f2f..73a7a0e6d 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -336,7 +336,6 @@ void ServerLobby::asynchronousUpdate() { requestTerminate(); m_state = EXITING; - STKHost::get()->setErrorMessage(_("Failed to setup server.")); STKHost::get()->requestShutdown(); break; }