Allow install addons live in lobby
This commit is contained in:
parent
19d008d0f8
commit
a962954c5e
@ -52,6 +52,7 @@
|
||||
#include "network/stk_peer.hpp"
|
||||
#include "online/online_profile.hpp"
|
||||
#include "online/xml_request.hpp"
|
||||
#include "states_screens/dialogs/addons_pack.hpp"
|
||||
#include "states_screens/online/networking_lobby.hpp"
|
||||
#include "states_screens/online/network_kart_selection.hpp"
|
||||
#include "states_screens/race_result_gui.hpp"
|
||||
@ -372,21 +373,7 @@ void ClientLobby::update(int ticks)
|
||||
for (const std::string& cap : stk_config->m_network_capabilities)
|
||||
ns->encodeString(cap);
|
||||
|
||||
auto all_k = kart_properties_manager->getAllAvailableKarts();
|
||||
auto all_t = track_manager->getAllTrackIdentifiers();
|
||||
if (all_k.size() >= 65536)
|
||||
all_k.resize(65535);
|
||||
if (all_t.size() >= 65536)
|
||||
all_t.resize(65535);
|
||||
ns->addUInt16((uint16_t)all_k.size()).addUInt16((uint16_t)all_t.size());
|
||||
for (const std::string& kart : all_k)
|
||||
{
|
||||
ns->encodeString(kart);
|
||||
}
|
||||
for (const std::string& track : all_t)
|
||||
{
|
||||
ns->encodeString(track);
|
||||
}
|
||||
getKartsTracksNetworkString(ns);
|
||||
assert(!NetworkConfig::get()->isAddingNetworkPlayers());
|
||||
const uint8_t player_count =
|
||||
(uint8_t)NetworkConfig::get()->getNetworkPlayers().size();
|
||||
@ -1511,3 +1498,55 @@ void ClientLobby::reportSuccess(Event* event)
|
||||
MessageQueue::add(MessageQueue::MT_GENERIC, msg);
|
||||
}
|
||||
} // reportSuccess
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
void ClientLobby::handleClientCommand(const std::string& cmd)
|
||||
{
|
||||
#ifndef SERVER_ONLY
|
||||
auto argv = StringUtils::split(cmd, ' ');
|
||||
if (argv.size() == 0)
|
||||
return;
|
||||
if (argv[0] == "installaddon" && argv.size() == 2)
|
||||
AddonsPack::install(argv[1]);
|
||||
else if (argv[0] == "uninstalladdon" && argv.size() == 2)
|
||||
AddonsPack::uninstall(argv[1]);
|
||||
else
|
||||
{
|
||||
// Send for server command
|
||||
NetworkString* cmd_ns = getNetworkString(1);
|
||||
cmd_ns->addUInt8(LE_COMMAND).encodeString(cmd);
|
||||
sendToServer(cmd_ns, /*reliable*/true);
|
||||
delete cmd_ns;
|
||||
}
|
||||
#endif
|
||||
} // handleClientCommand
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
void ClientLobby::getKartsTracksNetworkString(BareNetworkString* ns)
|
||||
{
|
||||
auto all_k = kart_properties_manager->getAllAvailableKarts();
|
||||
auto all_t = track_manager->getAllTrackIdentifiers();
|
||||
if (all_k.size() >= 65536)
|
||||
all_k.resize(65535);
|
||||
if (all_t.size() >= 65536)
|
||||
all_t.resize(65535);
|
||||
ns->addUInt16((uint16_t)all_k.size()).addUInt16((uint16_t)all_t.size());
|
||||
for (const std::string& kart : all_k)
|
||||
{
|
||||
ns->encodeString(kart);
|
||||
}
|
||||
for (const std::string& track : all_t)
|
||||
{
|
||||
ns->encodeString(track);
|
||||
}
|
||||
} // getKartsTracksNetworkString
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
void ClientLobby::updateAssetsToServer()
|
||||
{
|
||||
NetworkString* ns = getNetworkString(1);
|
||||
ns->addUInt8(LE_ASSETS_UPDATE);
|
||||
getKartsTracksNetworkString(ns);
|
||||
sendToServer(ns, /*reliable*/true);
|
||||
delete ns;
|
||||
} // updateAssetsToServer
|
||||
|
@ -139,6 +139,7 @@ private:
|
||||
decodePlayers(const BareNetworkString& data,
|
||||
std::shared_ptr<STKPeer> peer = nullptr,
|
||||
bool* is_spectator = NULL) const;
|
||||
void getKartsTracksNetworkString(BareNetworkString* ns);
|
||||
public:
|
||||
ClientLobby(const TransportAddress& a, std::shared_ptr<Server> s);
|
||||
virtual ~ClientLobby();
|
||||
@ -182,6 +183,8 @@ public:
|
||||
{ return m_server_enabled_report_player; }
|
||||
const std::vector<float>& getRankingChanges() const
|
||||
{ return m_ranking_changes; }
|
||||
void handleClientCommand(const std::string& cmd);
|
||||
void updateAssetsToServer();
|
||||
};
|
||||
|
||||
#endif // CLIENT_LOBBY_HPP
|
||||
|
@ -74,8 +74,10 @@ public:
|
||||
LE_LIVE_JOIN_ACK, // Server tell client live join or spectate succeed
|
||||
LE_KART_INFO, // Client or server exchange new kart info
|
||||
LE_CLIENT_BACK_LOBBY, // Client tell server to go back lobby
|
||||
LE_REPORT_PLAYER // Client report some player in server
|
||||
LE_REPORT_PLAYER, // Client report some player in server
|
||||
// (like abusive behaviour)
|
||||
LE_ASSETS_UPDATE, // Client tell server with updated assets
|
||||
LE_COMMAND, // Command
|
||||
};
|
||||
|
||||
enum RejectReason : uint8_t
|
||||
|
@ -755,6 +755,8 @@ bool ServerLobby::notifyEventAsynchronous(Event* event)
|
||||
case LE_CLIENT_BACK_LOBBY:
|
||||
clientSelectingAssetsWantsToBackLobby(event); break;
|
||||
case LE_REPORT_PLAYER: writePlayerReport(event); break;
|
||||
case LE_ASSETS_UPDATE:
|
||||
handleAssets(event->data(), event->getPeer()); break;
|
||||
default: break;
|
||||
} // switch
|
||||
} // if (event->getType() == EVENT_TYPE_MESSAGE)
|
||||
@ -2914,72 +2916,21 @@ void ServerLobby::saveIPBanTable(const TransportAddress& addr)
|
||||
} // saveIPBanTable
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void ServerLobby::connectionRequested(Event* event)
|
||||
bool ServerLobby::handleAssets(const NetworkString& ns, STKPeer* peer) const
|
||||
{
|
||||
std::shared_ptr<STKPeer> peer = event->getPeerSP();
|
||||
NetworkString& data = event->data();
|
||||
if (!checkDataSize(event, 14)) return;
|
||||
|
||||
peer->cleanPlayerProfiles();
|
||||
|
||||
// can we add the player ?
|
||||
if (!allowJoinedPlayersWaiting() &&
|
||||
(m_state.load() != WAITING_FOR_START_GAME ||
|
||||
m_game_setup->isGrandPrixStarted()))
|
||||
{
|
||||
NetworkString *message = getNetworkString(2);
|
||||
message->setSynchronous(true);
|
||||
message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_BUSY);
|
||||
// send only to the peer that made the request and disconnect it now
|
||||
peer->sendPacket(message, true/*reliable*/, false/*encrypted*/);
|
||||
peer->reset();
|
||||
delete message;
|
||||
Log::verbose("ServerLobby", "Player refused: selection started");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check server version
|
||||
int version = data.getUInt32();
|
||||
if (version < stk_config->m_min_server_version ||
|
||||
version > stk_config->m_max_server_version)
|
||||
{
|
||||
NetworkString *message = getNetworkString(2);
|
||||
message->setSynchronous(true);
|
||||
message->addUInt8(LE_CONNECTION_REFUSED)
|
||||
.addUInt8(RR_INCOMPATIBLE_DATA);
|
||||
peer->sendPacket(message, true/*reliable*/, false/*encrypted*/);
|
||||
peer->reset();
|
||||
delete message;
|
||||
Log::verbose("ServerLobby", "Player refused: wrong server version");
|
||||
return;
|
||||
}
|
||||
std::string user_version;
|
||||
data.decodeString(&user_version);
|
||||
event->getPeer()->setUserVersion(user_version);
|
||||
|
||||
unsigned list_caps = data.getUInt16();
|
||||
std::set<std::string> caps;
|
||||
for (unsigned i = 0; i < list_caps; i++)
|
||||
{
|
||||
std::string cap;
|
||||
data.decodeString(&cap);
|
||||
caps.insert(cap);
|
||||
}
|
||||
event->getPeer()->setClientCapabilities(caps);
|
||||
|
||||
std::set<std::string> client_karts, client_tracks;
|
||||
const unsigned kart_num = data.getUInt16();
|
||||
const unsigned track_num = data.getUInt16();
|
||||
const unsigned kart_num = ns.getUInt16();
|
||||
const unsigned track_num = ns.getUInt16();
|
||||
for (unsigned i = 0; i < kart_num; i++)
|
||||
{
|
||||
std::string kart;
|
||||
data.decodeString(&kart);
|
||||
ns.decodeString(&kart);
|
||||
client_karts.insert(kart);
|
||||
}
|
||||
for (unsigned i = 0; i < track_num; i++)
|
||||
{
|
||||
std::string track;
|
||||
data.decodeString(&track);
|
||||
ns.decodeString(&track);
|
||||
client_tracks.insert(track);
|
||||
}
|
||||
|
||||
@ -3032,12 +2983,70 @@ void ServerLobby::connectionRequested(Event* event)
|
||||
peer->reset();
|
||||
delete message;
|
||||
Log::verbose("ServerLobby", "Player has incompatible karts / tracks.");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save available karts and tracks from clients in STKPeer so if this peer
|
||||
// disconnects later in lobby it won't affect current players
|
||||
peer->setAvailableKartsTracks(client_karts, client_tracks);
|
||||
return true;
|
||||
} // handleAssets
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
void ServerLobby::connectionRequested(Event* event)
|
||||
{
|
||||
std::shared_ptr<STKPeer> peer = event->getPeerSP();
|
||||
NetworkString& data = event->data();
|
||||
if (!checkDataSize(event, 14)) return;
|
||||
|
||||
peer->cleanPlayerProfiles();
|
||||
|
||||
// can we add the player ?
|
||||
if (!allowJoinedPlayersWaiting() &&
|
||||
(m_state.load() != WAITING_FOR_START_GAME ||
|
||||
m_game_setup->isGrandPrixStarted()))
|
||||
{
|
||||
NetworkString *message = getNetworkString(2);
|
||||
message->setSynchronous(true);
|
||||
message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_BUSY);
|
||||
// send only to the peer that made the request and disconnect it now
|
||||
peer->sendPacket(message, true/*reliable*/, false/*encrypted*/);
|
||||
peer->reset();
|
||||
delete message;
|
||||
Log::verbose("ServerLobby", "Player refused: selection started");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check server version
|
||||
int version = data.getUInt32();
|
||||
if (version < stk_config->m_min_server_version ||
|
||||
version > stk_config->m_max_server_version)
|
||||
{
|
||||
NetworkString *message = getNetworkString(2);
|
||||
message->setSynchronous(true);
|
||||
message->addUInt8(LE_CONNECTION_REFUSED)
|
||||
.addUInt8(RR_INCOMPATIBLE_DATA);
|
||||
peer->sendPacket(message, true/*reliable*/, false/*encrypted*/);
|
||||
peer->reset();
|
||||
delete message;
|
||||
Log::verbose("ServerLobby", "Player refused: wrong server version");
|
||||
return;
|
||||
}
|
||||
std::string user_version;
|
||||
data.decodeString(&user_version);
|
||||
event->getPeer()->setUserVersion(user_version);
|
||||
|
||||
unsigned list_caps = data.getUInt16();
|
||||
std::set<std::string> caps;
|
||||
for (unsigned i = 0; i < list_caps; i++)
|
||||
{
|
||||
std::string cap;
|
||||
data.decodeString(&cap);
|
||||
caps.insert(cap);
|
||||
}
|
||||
event->getPeer()->setClientCapabilities(caps);
|
||||
if (!handleAssets(data, event->getPeer()))
|
||||
return;
|
||||
|
||||
unsigned player_count = data.getUInt8();
|
||||
uint32_t online_id = 0;
|
||||
|
@ -316,6 +316,7 @@ private:
|
||||
std::vector<std::shared_ptr<NetworkPlayerProfile> >& players) const;
|
||||
std::vector<std::shared_ptr<NetworkPlayerProfile> > getLivePlayers() const;
|
||||
void setPlayerKarts(const NetworkString& ns, STKPeer* peer) const;
|
||||
bool handleAssets(const NetworkString& ns, STKPeer* peer) const;
|
||||
void liveJoinRequest(Event* event);
|
||||
void rejectLiveJoin(STKPeer* peer, BackLobbyReason blr);
|
||||
bool canLiveJoinNow() const;
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include "guiengine/widgets.hpp"
|
||||
#include "input/input_manager.hpp"
|
||||
#include "io/file_manager.hpp"
|
||||
#include "network/protocols/client_lobby.hpp"
|
||||
#include "online/request_manager.hpp"
|
||||
#include "online/xml_request.hpp"
|
||||
#include "race/grand_prix_manager.hpp"
|
||||
@ -81,7 +82,12 @@ AddonsLoading::~AddonsLoading()
|
||||
{
|
||||
// Select the last selected item in the addons_screen, so that
|
||||
// users can keep on installing from the last selected item.
|
||||
AddonsScreen::getInstance()->setLastSelected();
|
||||
// This dialog can be called in network lobby screen atm for live addon
|
||||
// install
|
||||
AddonsScreen* as = dynamic_cast<AddonsScreen*>(
|
||||
GUIEngine::getCurrentScreen());
|
||||
if (as)
|
||||
as->setLastSelected();
|
||||
} // AddonsLoading
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
@ -210,7 +216,25 @@ bool AddonsLoading::onEscapePressed()
|
||||
} // onEscapePressed
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
void AddonsLoading::tryInstall()
|
||||
{
|
||||
#ifndef SERVER_ONLY
|
||||
// Only display the progress bar etc. if we are not uninstalling an addon.
|
||||
if (!m_addon.isInstalled() || m_addon.needsUpdate())
|
||||
{
|
||||
m_progress->setValue(0);
|
||||
m_progress->setVisible(true);
|
||||
// Change the 'back' button into a 'cancel' button.
|
||||
m_back_button->setLabel(_("Cancel"));
|
||||
GUIEngine::RibbonWidget* actions_ribbon =
|
||||
getWidget<GUIEngine::RibbonWidget>("actions");
|
||||
actions_ribbon->setVisible(false);
|
||||
startDownload();
|
||||
}
|
||||
#endif
|
||||
} // tryInstall
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
GUIEngine::EventPropagation AddonsLoading::processEvent(const std::string& event_source)
|
||||
{
|
||||
#ifndef SERVER_ONLY
|
||||
@ -230,19 +254,7 @@ GUIEngine::EventPropagation AddonsLoading::processEvent(const std::string& event
|
||||
}
|
||||
else if(selection == "install")
|
||||
{
|
||||
// Only display the progress bar etc. if we are
|
||||
// not uninstalling an addon.
|
||||
if(!m_addon.isInstalled() || m_addon.needsUpdate())
|
||||
{
|
||||
m_progress->setValue(0);
|
||||
m_progress->setVisible(true);
|
||||
// Change the 'back' button into a 'cancel' button.
|
||||
m_back_button->setLabel(_("Cancel"));
|
||||
|
||||
actions_ribbon->setVisible(false);
|
||||
|
||||
startDownload();
|
||||
}
|
||||
tryInstall();
|
||||
return GUIEngine::EVENT_BLOCK;
|
||||
}
|
||||
else if (selection == "uninstall")
|
||||
@ -395,7 +407,10 @@ void AddonsLoading::doInstall()
|
||||
{
|
||||
// The list of the addon screen needs to be updated to correctly
|
||||
// display the newly (un)installed addon.
|
||||
AddonsScreen::getInstance()->loadList();
|
||||
AddonsScreen* as = dynamic_cast<AddonsScreen*>(
|
||||
GUIEngine::getCurrentScreen());
|
||||
if (as)
|
||||
as->loadList();
|
||||
dismiss();
|
||||
}
|
||||
|
||||
@ -405,6 +420,9 @@ void AddonsLoading::doInstall()
|
||||
delete grand_prix_manager;
|
||||
grand_prix_manager = new GrandPrixManager();
|
||||
grand_prix_manager->checkConsistency();
|
||||
|
||||
if (auto cl = LobbyProtocol::get<ClientLobby>())
|
||||
cl->updateAssetsToServer();
|
||||
#endif
|
||||
} // doInstall
|
||||
|
||||
@ -439,7 +457,10 @@ void AddonsLoading::doUninstall()
|
||||
{
|
||||
// The list of the addon screen needs to be updated to correctly
|
||||
// display the newly (un)installed addon.
|
||||
AddonsScreen::getInstance()->loadList();
|
||||
AddonsScreen* as = dynamic_cast<AddonsScreen*>(
|
||||
GUIEngine::getCurrentScreen());
|
||||
if (as)
|
||||
as->loadList();
|
||||
dismiss();
|
||||
}
|
||||
// Update the replay file list to use latest track pointer
|
||||
@ -447,5 +468,8 @@ void AddonsLoading::doUninstall()
|
||||
delete grand_prix_manager;
|
||||
grand_prix_manager = new GrandPrixManager();
|
||||
grand_prix_manager->checkConsistency();
|
||||
|
||||
if (auto cl = LobbyProtocol::get<ClientLobby>())
|
||||
cl->updateAssetsToServer();
|
||||
#endif
|
||||
} // doUninstall
|
||||
|
@ -71,6 +71,7 @@ public:
|
||||
* */
|
||||
void onUpdate(float delta) OVERRIDE;
|
||||
void voteClicked();
|
||||
void tryInstall();
|
||||
virtual bool onEscapePressed() OVERRIDE;
|
||||
|
||||
}; // AddonsLoading
|
||||
|
@ -24,10 +24,12 @@
|
||||
#include "io/file_manager.hpp"
|
||||
#include "karts/kart_properties.hpp"
|
||||
#include "karts/kart_properties_manager.hpp"
|
||||
#include "network/protocols/client_lobby.hpp"
|
||||
#include "online/http_request.hpp"
|
||||
#include "race/grand_prix_manager.hpp"
|
||||
#include "replay/replay_play.hpp"
|
||||
#include "states_screens/addons_screen.hpp"
|
||||
#include "states_screens/dialogs/addons_loading.hpp"
|
||||
#include "states_screens/dialogs/message_dialog.hpp"
|
||||
#include "states_screens/state_manager.hpp"
|
||||
#include "tracks/track_manager.hpp"
|
||||
@ -239,8 +241,7 @@ void AddonsPack::doInstall()
|
||||
{
|
||||
if (r == ".." || r == ".")
|
||||
continue;
|
||||
std::string addon_id = "addon_";
|
||||
addon_id += r;
|
||||
std::string addon_id = Addon::createAddonId(r);
|
||||
// We assume the addons pack the user downloaded use the latest
|
||||
// revision from the stk-addons (if exists)
|
||||
if (file_manager->fileExists(tmp_extract + r + "/stkskin.xml"))
|
||||
@ -301,8 +302,44 @@ void AddonsPack::doInstall()
|
||||
GUIEngine::getCurrentScreen());
|
||||
if (as)
|
||||
as->loadList();
|
||||
if (auto cl = LobbyProtocol::get<ClientLobby>())
|
||||
cl->updateAssetsToServer();
|
||||
delete request;
|
||||
}
|
||||
} // doInstall
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
void AddonsPack::install(const std::string& name)
|
||||
{
|
||||
// Only install addon live in menu
|
||||
if (StateManager::get()->getGameState() != GUIEngine::MENU &&
|
||||
!ModalDialog::isADialogActive())
|
||||
return;
|
||||
Addon* addon = addons_manager->getAddon(Addon::createAddonId(name));
|
||||
if (addon)
|
||||
{
|
||||
AddonsLoading* al = new AddonsLoading(addon->getId());
|
||||
al->tryInstall();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Assume it's addon pack url
|
||||
new AddonsPack(name);
|
||||
}
|
||||
} // install
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
void AddonsPack::uninstall(const std::string& name)
|
||||
{
|
||||
// Only uninstall addon live in menu
|
||||
if (StateManager::get()->getGameState() != GUIEngine::MENU)
|
||||
return;
|
||||
Addon* addon = addons_manager->getAddon(Addon::createAddonId(name));
|
||||
if (addon && addons_manager->uninstall(*addon))
|
||||
{
|
||||
if (auto cl = LobbyProtocol::get<ClientLobby>())
|
||||
cl->updateAssetsToServer();
|
||||
}
|
||||
} // uninstall
|
||||
|
||||
#endif
|
||||
|
@ -38,15 +38,15 @@ private:
|
||||
/** A pointer to the download request, which gives access
|
||||
* to the progress of a download. */
|
||||
AddonsPackRequest* m_download_request;
|
||||
|
||||
public:
|
||||
AddonsPack(const std::string& url);
|
||||
|
||||
public:
|
||||
virtual GUIEngine::EventPropagation processEvent(const std::string& event_source) OVERRIDE;
|
||||
virtual void beforeAddingWidgets() OVERRIDE;
|
||||
virtual void init() OVERRIDE;
|
||||
void onUpdate(float delta) OVERRIDE;
|
||||
virtual bool onEscapePressed() OVERRIDE;
|
||||
static void install(const std::string& name);
|
||||
static void uninstall(const std::string& name);
|
||||
}; // DownloadAssets
|
||||
|
||||
#endif
|
||||
|
@ -337,7 +337,18 @@ void RacePausedDialog::beforeAddingWidgets()
|
||||
bool RacePausedDialog::onEnterPressed(const irr::core::stringw& text)
|
||||
{
|
||||
if (auto cl = LobbyProtocol::get<ClientLobby>())
|
||||
cl->sendChat(text);
|
||||
{
|
||||
if (!text.empty())
|
||||
{
|
||||
if (text[0] == L'/' && text.size() > 1)
|
||||
{
|
||||
std::string cmd = StringUtils::wideToUtf8(text);
|
||||
cl->handleClientCommand(cmd.erase(0, 1));
|
||||
}
|
||||
else
|
||||
cl->sendChat(text);
|
||||
}
|
||||
}
|
||||
m_self_destroy = true;
|
||||
return true;
|
||||
} // onEnterPressed
|
||||
|
@ -568,7 +568,18 @@ void NetworkingLobby::updatePlayerPings()
|
||||
bool NetworkingLobby::onEnterPressed(const irr::core::stringw& text)
|
||||
{
|
||||
if (auto cl = LobbyProtocol::get<ClientLobby>())
|
||||
cl->sendChat(text);
|
||||
{
|
||||
if (!text.empty())
|
||||
{
|
||||
if (text[0] == L'/' && text.size() > 1)
|
||||
{
|
||||
std::string cmd = StringUtils::wideToUtf8(text);
|
||||
cl->handleClientCommand(cmd.erase(0, 1));
|
||||
}
|
||||
else
|
||||
cl->sendChat(text);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} // onEnterPressed
|
||||
|
||||
@ -601,8 +612,7 @@ void NetworkingLobby::eventCallback(Widget* widget, const std::string& name,
|
||||
} // click on a user
|
||||
else if (name == m_send_button->m_properties[PROP_ID])
|
||||
{
|
||||
if (auto cl = LobbyProtocol::get<ClientLobby>())
|
||||
cl->sendChat(m_chat_box->getText());
|
||||
onEnterPressed(m_chat_box->getText());
|
||||
m_chat_box->setText("");
|
||||
} // send chat message
|
||||
else if (name == m_emoji_button->m_properties[PROP_ID] &&
|
||||
|
Loading…
Reference in New Issue
Block a user