1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

UI improvements for lobby:

- Added notifications sounds for invites and chat messages
- Added notifications for unread chat messages in inactive channels
- Added click sound when switching between channels
- Added workaround to prevent clicks due to list recreation
- Partial support for receiving invites
This commit is contained in:
Ivan Savenko 2024-03-25 18:04:44 +02:00
parent 536156dd92
commit 69f7b3169e
10 changed files with 77 additions and 15 deletions

View File

@ -95,7 +95,8 @@
"vcmi.lobby.room.description.new" : "To start the game, select a scenario or set up a random map.",
"vcmi.lobby.room.description.load" : "To start the game, use one of your saved games.",
"vcmi.lobby.room.description.limit" : "Up to %d players can enter your room, including you.",
"vcmi.lobby.room.invite" : "Invite Players",
"vcmi.lobby.invite.header" : "Invite Players",
"vcmi.lobby.invite.notification" : "Player has invited you to their game room. You can now join their private room.",
"vcmi.lobby.room.new" : "New Game",
"vcmi.lobby.room.load" : "Load Game",
"vcmi.lobby.room.type" : "Room Type",
@ -103,6 +104,7 @@
"vcmi.lobby.room.state.public" : "Public",
"vcmi.lobby.room.state.private" : "Private",
"vcmi.lobby.room.state.busy" : "In Game",
"vcmi.lobby.room.state.invited" : "Invited",
"vcmi.client.errors.invalidMap" : "{Invalid map or campaign}\n\nFailed to start game! Selected map or campaign might be invalid or corrupted. Reason:\n%s",
"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",

View File

@ -15,12 +15,13 @@
#include "GlobalLobbyLoginWindow.h"
#include "GlobalLobbyWindow.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CServerHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
#include "../windows/InfoWindows.h"
#include "../CServerHandler.h"
#include "../mainmenu/CMainMenu.h"
#include "../CGameInfo.h"
#include "../windows/InfoWindows.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/MetaString.h"
@ -189,6 +190,8 @@ void GlobalLobbyClient::receiveChatMessage(const JsonNode & json)
auto lobbyWindowPtr = lobbyWindow.lock();
if(lobbyWindowPtr)
lobbyWindowPtr->onGameChatMessage(message.displayName, message.messageText, message.timeFormatted, channelType, channelName);
CCS->soundh->playSound(AudioPath::builtin("CHAT"));
}
void GlobalLobbyClient::receiveActiveAccounts(const JsonNode & json)
@ -280,10 +283,18 @@ void GlobalLobbyClient::receiveMatchesHistory(const JsonNode & json)
void GlobalLobbyClient::receiveInviteReceived(const JsonNode & json)
{
auto lobbyWindowPtr = lobbyWindow.lock();
std::string gameRoomID = json["gameRoomID"].String();
std::string accountID = json["accountID"].String();
if(lobbyWindowPtr)
lobbyWindowPtr->onMatchesHistory(activeRooms);
{
std::string message = MetaString::createFromTextID("vcmi.lobby.invite.notification").toString();
std::string time = getCurrentTimeFormatted();
assert(0); //TODO
lobbyWindowPtr->onGameChatMessage("System", message, time, "player", accountID);
lobbyWindowPtr->onInviteReceived(gameRoomID, accountID);
}
CCS->soundh->playSound(AudioPath::builtin("CHAT"));
}
void GlobalLobbyClient::receiveJoinRoomSuccess(const JsonNode & json)

View File

@ -57,7 +57,7 @@ GlobalLobbyInviteWindow::GlobalLobbyInviteWindow()
filledBackground = std::make_shared<FilledTexturePlayerColored>(ImagePath::builtin("DiBoxBck"), Rect(0, 0, pos.w, pos.h));
filledBackground->playerColored(PlayerColor(1));
labelTitle = std::make_shared<CLabel>(
pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.room.invite").toString()
pos.w / 2, 20, FONT_BIG, ETextAlignment::CENTER, Colors::YELLOW, MetaString::createFromTextID("vcmi.lobby.invite.header").toString()
);
const auto & createAccountCardCallback = [this](size_t index) -> std::shared_ptr<CIntObject>

View File

@ -14,6 +14,8 @@
#include "GlobalLobbyClient.h"
#include "GlobalLobbyWindow.h"
#include "../CGameInfo.h"
#include "../CMusicHandler.h"
#include "../CServerHandler.h"
#include "../gui/CGuiHandler.h"
#include "../gui/WindowHandler.h"
@ -186,18 +188,22 @@ GlobalLobbyChannelCardBase::GlobalLobbyChannelCardBase(GlobalLobbyWindow * windo
if (window->isChannelOpen(channelType, channelName))
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), Colors::YELLOW, 2);
else if (window->isChannelUnread(channelType, channelName))
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), Colors::WHITE, 1);
else
backgroundOverlay = std::make_shared<TransparentFilledRectangle>(Rect(0, 0, pos.w, pos.h), ColorRGBA(0, 0, 0, 128), ColorRGBA(64, 64, 64, 64), 1);
}
void GlobalLobbyChannelCardBase::clickPressed(const Point & cursorPosition)
{
CCS->soundh->playSound(soundBase::button);
window->doOpenChannel(channelType, channelName, channelDescription);
}
GlobalLobbyAccountCard::GlobalLobbyAccountCard(GlobalLobbyWindow * window, const GlobalLobbyAccount & accountDescription)
: GlobalLobbyChannelCardBase(window, Point(130, 40), "player", accountDescription.accountID, accountDescription.displayName)
{
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
labelName = std::make_shared<CLabel>(5, 10, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::WHITE, accountDescription.displayName);
labelStatus = std::make_shared<CLabel>(5, 30, FONT_SMALL, ETextAlignment::CENTERLEFT, Colors::YELLOW, accountDescription.status);
}

View File

@ -49,6 +49,7 @@ void GlobalLobbyWindow::doOpenChannel(const std::string & channelType, const std
currentChannelType = channelType;
currentChannelName = channelName;
chatHistory.clear();
unreadChannels.erase(channelType + "_" + channelName);
widget->getGameChat()->setText("");
auto history = CSH->getGlobalLobby().getChannelHistory(channelType, channelName);
@ -110,7 +111,14 @@ void GlobalLobbyWindow::doJoinRoom(const std::string & roomID)
void GlobalLobbyWindow::onGameChatMessage(const std::string & sender, const std::string & message, const std::string & when, const std::string & channelType, const std::string & channelName)
{
if (channelType != currentChannelType || channelName != currentChannelName)
return; // TODO: send ping to player that another channel got a new message
{
// mark channel as unread
unreadChannels.insert(channelType + "_" + channelName);
widget->getAccountList()->reset();
widget->getChannelList()->reset();
widget->getMatchList()->reset();
return;
}
MetaString chatMessageFormatted;
chatMessageFormatted.appendRawString("[%s] {%s}: %s\n");
@ -123,6 +131,11 @@ void GlobalLobbyWindow::onGameChatMessage(const std::string & sender, const std:
widget->getGameChat()->setText(chatHistory);
}
bool GlobalLobbyWindow::isChannelUnread(const std::string & channelType, const std::string & channelName)
{
return unreadChannels.count(channelType + "_" + channelName) > 0;
}
void GlobalLobbyWindow::onActiveAccounts(const std::vector<GlobalLobbyAccount> & accounts)
{
if (accounts.size() == widget->getAccountList()->size())
@ -159,6 +172,11 @@ void GlobalLobbyWindow::onMatchesHistory(const std::vector<GlobalLobbyRoom> & hi
widget->getMatchListHeader()->setText(text.toString());
}
void GlobalLobbyWindow::onInviteReceived(const std::string & invitedRoomID, const std::string & invitedByAccountID)
{
widget->getRoomList()->reset();
}
void GlobalLobbyWindow::onJoinedRoom()
{
widget->getAccountList()->reset();

View File

@ -22,6 +22,7 @@ class GlobalLobbyWindow : public CWindowObject
std::string currentChannelName;
std::shared_ptr<GlobalLobbyWidget> widget;
std::set<std::string> unreadChannels;
public:
GlobalLobbyWindow();
@ -36,6 +37,7 @@ public:
/// Returns true if provided chat channel is the one that is currently open in UI
bool isChannelOpen(const std::string & channelType, const std::string & channelName);
bool isChannelUnread(const std::string & channelType, const std::string & channelName);
// Callbacks for network packs
@ -43,6 +45,7 @@ public:
void onActiveAccounts(const std::vector<GlobalLobbyAccount> & accounts);
void onActiveRooms(const std::vector<GlobalLobbyRoom> & rooms);
void onMatchesHistory(const std::vector<GlobalLobbyRoom> & history);
void onInviteReceived(const std::string & invitedRoomID, const std::string & invitedByAccountID);
void onJoinedRoom();
void onLeftRoom();
};

View File

@ -66,6 +66,7 @@ size_t CTabbedInt::getActive() const
void CTabbedInt::reset()
{
deleteItem(activeTab);
activeTab = createItem(activeID);
activeTab->moveTo(pos.topLeft());
@ -127,6 +128,11 @@ void CListBox::updatePositions()
void CListBox::reset()
{
// hack to ensure that all items will be recreated with new address
// save current item list so all shared_ptr's will be destroyed only on scope exit and not inside loop below
// see comment in EventDispatcher::handleLeftButtonClick for details on why this hack is needed
auto itemsCopy = items;
size_t current = first;
for (auto & elem : items)
{
@ -279,4 +285,4 @@ void CListBoxWithCallback::moveToPrev()
CListBox::moveToPrev();
if(movedPosCallback)
movedPosCallback(getPos());
}
}

View File

@ -198,6 +198,12 @@ void LobbyDatabase::prepareStatements()
WHERE roomID = ?
)");
getAccountInviteStatusStatement = database->prepare(R"(
SELECT COUNT(accountID)
FROM gameRoomInvites
WHERE accountID = ? AND roomID = ?
)");
getAccountGameHistoryStatement = database->prepare(R"(
SELECT gr.roomID, hostAccountID, displayName, description, status, playerLimit, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',gr.creationTime) AS secondsElapsed
FROM gameRoomPlayers grp
@ -438,8 +444,17 @@ LobbyCookieStatus LobbyDatabase::getAccountCookieStatus(const std::string & acco
LobbyInviteStatus LobbyDatabase::getAccountInviteStatus(const std::string & accountID, const std::string & roomID)
{
assert(0);
return {};
int result = 0;
getAccountInviteStatusStatement->setBinds(accountID, roomID);
if(getAccountInviteStatusStatement->execute())
getAccountInviteStatusStatement->getColumns(result);
getAccountInviteStatusStatement->reset();
if (result > 0)
return LobbyInviteStatus::INVITED;
else
return LobbyInviteStatus::NOT_INVITED;
}
LobbyRoomState LobbyDatabase::getGameRoomStatus(const std::string & roomID)

View File

@ -44,6 +44,7 @@ class LobbyDatabase
SQLiteStatementPtr getAccountGameHistoryStatement;
SQLiteStatementPtr getActiveGameRoomsStatement;
SQLiteStatementPtr getActiveAccountsStatement;
SQLiteStatementPtr getAccountInviteStatusStatement;
SQLiteStatementPtr getAccountGameRoomStatement;
SQLiteStatementPtr getAccountDisplayNameStatement;
SQLiteStatementPtr countRoomUsedSlotsStatement;

View File

@ -761,10 +761,10 @@ void LobbyServer::receiveSendInvite(const NetworkConnectionPtr & connection, con
std::string accountID = json["accountID"].String();
std::string gameRoomID = database->getAccountGameRoom(senderName);
auto targetAccount = findAccount(accountID);
auto targetAccountConnection = findAccount(accountID);
if(!targetAccount)
return sendOperationFailed(connection, "Invalid account to invite!");
if(!targetAccountConnection)
return sendOperationFailed(connection, "Player is offline or does not exists!");
if(!database->isPlayerInGameRoom(senderName))
return sendOperationFailed(connection, "You are not in the room!");
@ -776,7 +776,7 @@ void LobbyServer::receiveSendInvite(const NetworkConnectionPtr & connection, con
return sendOperationFailed(connection, "This player is already invited!");
database->insertGameRoomInvite(accountID, gameRoomID);
sendInviteReceived(targetAccount, senderName, gameRoomID);
sendInviteReceived(targetAccountConnection, senderName, gameRoomID);
}
LobbyServer::~LobbyServer() = default;