diff --git a/CMakeLists.txt b/CMakeLists.txt index 13f4a623cdf..0263afc247a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2203,6 +2203,9 @@ list(REMOVE_ITEM GAME_SHARED ${ENGINE_UUID_SHARED}) set(GAME_GENERATED_SHARED src/generated/data_types.h src/generated/git_revision.cpp + src/generated/protocol.h + src/generated/protocol7.h + src/generated/protocolglue.h ) set(DEPS ${DEP_JSON} ${DEP_MD5} ${ZLIB_DEP}) diff --git a/README.md b/README.md index e97d7114776..7bf3d4198fa 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ [![DDraceNetwork](https://ddnet.org/ddnet-small.png)](https://ddnet.org) -[![](https://github.com/ddnet/ddnet/workflows/Build/badge.svg)](https://github.com/ddnet/ddnet/actions?query=workflow%3ABuild+event%3Apush+branch%3Amaster) -[![](https://codecov.io/gh/ddnet/ddnet/branch/master/graph/badge.svg)](https://codecov.io/gh/ddnet/ddnet/branch/master) +[![Build status](https://github.com/ddnet/ddnet/workflows/Build/badge.svg)](https://github.com/ddnet/ddnet/actions?query=workflow%3ABuild+event%3Apush+branch%3Amaster) +[![Code coverage](https://codecov.io/gh/ddnet/ddnet/branch/master/graph/badge.svg)](https://codecov.io/gh/ddnet/ddnet/branch/master) +[![Translation status](https://hosted.weblate.org/widget/ddnet/ddnet/svg-badge.svg)](https://hosted.weblate.org/engage/ddnet/) Our own flavor of DDRace, a Teeworlds mod. See the [website](https://ddnet.org) for more information. diff --git a/src/engine/client/friends.cpp b/src/engine/client/friends.cpp index 5db1983b183..65a74e28588 100644 --- a/src/engine/client/friends.cpp +++ b/src/engine/client/friends.cpp @@ -19,7 +19,11 @@ CFriends::CFriends() void CFriends::ConAddFriend(IConsole::IResult *pResult, void *pUserData) { CFriends *pSelf = (CFriends *)pUserData; + // Support optional third parameter for starred status (backward compatibility) + bool Starred = pResult->NumArguments() >= 3 ? pResult->GetInteger(2) != 0 : false; pSelf->AddFriend(pResult->GetString(0), pResult->GetString(1)); + if(Starred) + pSelf->SetFriendStarred(pResult->GetString(0), pResult->GetString(1), true); } void CFriends::ConRemoveFriend(IConsole::IResult *pResult, void *pUserData) @@ -47,13 +51,13 @@ void CFriends::Init(bool Foes) { if(Foes) { - pConsole->Register("add_foe", "s[name] ?s[clan]", CFGFLAG_CLIENT, ConAddFriend, this, "Add a foe"); + pConsole->Register("add_foe", "s[name] ?s[clan] ?i[starred]", CFGFLAG_CLIENT, ConAddFriend, this, "Add a foe"); pConsole->Register("remove_foe", "s[name] ?s[clan]", CFGFLAG_CLIENT, ConRemoveFriend, this, "Remove a foe"); pConsole->Register("foes", "", CFGFLAG_CLIENT, ConFriends, this, "List foes"); } else { - pConsole->Register("add_friend", "s[name] ?s[clan]", CFGFLAG_CLIENT, ConAddFriend, this, "Add a friend"); + pConsole->Register("add_friend", "s[name] ?s[clan] ?i[starred]", CFGFLAG_CLIENT, ConAddFriend, this, "Add a friend"); pConsole->Register("remove_friend", "s[name] ?s[clan]", CFGFLAG_CLIENT, ConRemoveFriend, this, "Remove a friend"); pConsole->Register("friends", "", CFGFLAG_CLIENT, ConFriends, this, "List friends"); } @@ -117,6 +121,7 @@ void CFriends::AddFriend(const char *pName, const char *pClan) str_copy(m_aFriends[m_NumFriends].m_aClan, pClan); m_aFriends[m_NumFriends].m_NameHash = NameHash; m_aFriends[m_NumFriends].m_ClanHash = ClanHash; + m_aFriends[m_NumFriends].m_Starred = false; ++m_NumFriends; } @@ -159,6 +164,21 @@ void CFriends::Friends() } } +void CFriends::SetFriendStarred(const char *pName, const char *pClan, bool Starred) +{ + unsigned NameHash = str_quickhash(pName); + unsigned ClanHash = str_quickhash(pClan); + for(int i = 0; i < m_NumFriends; ++i) + { + if((m_aFriends[i].m_NameHash == NameHash && !str_comp(m_aFriends[i].m_aName, pName)) && + ((g_Config.m_ClFriendsIgnoreClan && m_aFriends[i].m_aName[0]) || (m_aFriends[i].m_ClanHash == ClanHash && !str_comp(m_aFriends[i].m_aClan, pClan)))) + { + m_aFriends[i].m_Starred = Starred; + return; + } + } +} + void CFriends::ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData) { CFriends *pSelf = (CFriends *)pUserData; @@ -176,6 +196,10 @@ void CFriends::ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserDat str_escape(&pDst, pSelf->m_aFriends[i].m_aClan, pEnd); str_append(aBuf, "\""); + // Append starred status if starred + if(pSelf->m_aFriends[i].m_Starred) + str_append(aBuf, " 1"); + pConfigManager->WriteLine(aBuf); } } diff --git a/src/engine/client/friends.h b/src/engine/client/friends.h index cd3e9a1d8f2..c5cd8e2f43a 100644 --- a/src/engine/client/friends.h +++ b/src/engine/client/friends.h @@ -34,6 +34,7 @@ class CFriends : public IFriends void RemoveFriend(const char *pName, const char *pClan) override; void RemoveFriend(int Index); void Friends(); + void SetFriendStarred(const char *pName, const char *pClan, bool Starred) override; }; #endif diff --git a/src/engine/client/serverbrowser.cpp b/src/engine/client/serverbrowser.cpp index e732979df43..38adf2009bc 100644 --- a/src/engine/client/serverbrowser.cpp +++ b/src/engine/client/serverbrowser.cpp @@ -1365,6 +1365,7 @@ const json_value *CServerBrowser::LoadDDNetInfo() UpdateServerCommunity(&m_ppServerlist[i]->m_Info); UpdateServerRank(&m_ppServerlist[i]->m_Info); } + ValidateServerlistType(); return m_pDDNetInfo; } @@ -1668,6 +1669,21 @@ void CServerBrowser::UpdateServerRank(CServerInfo *pInfo) const pInfo->m_HasRank = pCommunity == nullptr ? CServerInfo::RANK_UNAVAILABLE : pCommunity->HasRank(pInfo->m_aMap); } +void CServerBrowser::ValidateServerlistType() +{ + if(m_ServerlistType >= IServerBrowser::TYPE_FAVORITE_COMMUNITY_1 && + m_ServerlistType <= IServerBrowser::TYPE_FAVORITE_COMMUNITY_5) + { + const size_t CommunityIndex = m_ServerlistType - IServerBrowser::TYPE_FAVORITE_COMMUNITY_1; + if(CommunityIndex >= FavoriteCommunities().size()) + { + // Reset to internet type if there is no favorite community for the current browser type, + // in case communities have been removed. + m_ServerlistType = IServerBrowser::TYPE_INTERNET; + } + } +} + const char *CServerBrowser::GetTutorialServer() { const CCommunity *pCommunity = Community(COMMUNITY_DDNET); diff --git a/src/engine/client/serverbrowser.h b/src/engine/client/serverbrowser.h index d874444cd43..9bdfeed56b9 100644 --- a/src/engine/client/serverbrowser.h +++ b/src/engine/client/serverbrowser.h @@ -275,6 +275,7 @@ class CServerBrowser : public IServerBrowser void UpdateServerFriends(CServerInfo *pInfo) const; void UpdateServerCommunity(CServerInfo *pInfo) const; void UpdateServerRank(CServerInfo *pInfo) const; + void ValidateServerlistType(); const char *GetTutorialServer() override; const std::vector &Communities() const override; diff --git a/src/engine/friends.h b/src/engine/friends.h index 2d73308d3ca..dc2101c1608 100644 --- a/src/engine/friends.h +++ b/src/engine/friends.h @@ -13,6 +13,7 @@ struct CFriendInfo char m_aClan[MAX_CLAN_LENGTH]; unsigned m_NameHash; unsigned m_ClanHash; + bool m_Starred; }; class IFriends : public IInterface @@ -36,6 +37,7 @@ class IFriends : public IInterface virtual void AddFriend(const char *pName, const char *pClan) = 0; virtual void RemoveFriend(const char *pName, const char *pClan) = 0; + virtual void SetFriendStarred(const char *pName, const char *pClan, bool Starred) = 0; }; #endif diff --git a/src/game/client/components/binds.h b/src/game/client/components/binds.h index 71ba8778d62..1f6cdc8fc55 100644 --- a/src/game/client/components/binds.h +++ b/src/game/client/components/binds.h @@ -18,7 +18,6 @@ class CBinds : public CComponent static void ConBinds(IConsole::IResult *pResult, void *pUserData); static void ConUnbind(IConsole::IResult *pResult, void *pUserData); static void ConUnbindAll(IConsole::IResult *pResult, void *pUserData); - class IConsole *GetConsole() const { return Console(); } static void ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData); diff --git a/src/game/client/components/key_binder.cpp b/src/game/client/components/key_binder.cpp index 2636bf5a7fb..28afd0678ad 100644 --- a/src/game/client/components/key_binder.cpp +++ b/src/game/client/components/key_binder.cpp @@ -24,8 +24,7 @@ bool CKeyBinder::OnInput(const IInput::CEvent &Event) return false; } - int TriggeringEvent = (Event.m_Key == KEY_MOUSE_1) ? IInput::FLAG_PRESS : IInput::FLAG_RELEASE; - if(Event.m_Flags & TriggeringEvent) + if(Event.m_Flags & IInput::FLAG_RELEASE) { m_Key = Event; m_GotKey = true; diff --git a/src/game/client/components/menus.h b/src/game/client/components/menus.h index ecd71611fbd..d6e5f1cebbb 100644 --- a/src/game/client/components/menus.h +++ b/src/game/client/components/menus.h @@ -348,6 +348,7 @@ class CMenus : public CComponent int m_FriendState; bool m_IsPlayer; bool m_IsAfk; + bool m_Starred; // skin info 0.6 char m_aSkin[MAX_SKIN_LENGTH]; bool m_CustomSkinColors; @@ -363,6 +364,7 @@ class CMenus : public CComponent m_pServerInfo(nullptr), m_IsPlayer(false), m_IsAfk(false), + m_Starred(pFriendInfo->m_Starred), m_CustomSkinColors(false), m_CustomSkinColorBody(0), m_CustomSkinColorFeet(0) @@ -383,6 +385,7 @@ class CMenus : public CComponent m_FriendState(CurrentClient.m_FriendState), m_IsPlayer(CurrentClient.m_Player), m_IsAfk(CurrentClient.m_Afk), + m_Starred(false), m_CustomSkinColors(CurrentClient.m_CustomSkinColors), m_CustomSkinColorBody(CurrentClient.m_CustomSkinColorBody), m_CustomSkinColorFeet(CurrentClient.m_CustomSkinColorFeet) @@ -404,6 +407,8 @@ class CMenus : public CComponent int FriendState() const { return m_FriendState; } bool IsPlayer() const { return m_IsPlayer; } bool IsAfk() const { return m_IsAfk; } + bool IsStarred() const { return m_Starred; } + void SetStarred(bool Starred) { m_Starred = Starred; } // 0.6 skin const char *Skin() const { return m_aSkin; } bool CustomSkinColors() const { return m_CustomSkinColors; } @@ -421,6 +426,10 @@ class CMenus : public CComponent bool operator<(const CFriendItem &Other) const { + // Starred friends come first + if(m_Starred != Other.m_Starred) + return m_Starred; + // Then sort by name and clan const int Result = str_comp_nocase(m_aName, Other.m_aName); return Result < 0 || (Result == 0 && str_comp_nocase(m_aClan, Other.m_aClan) < 0); } diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp index 6e7098cad44..8ec7955f50c 100644 --- a/src/game/client/components/menus_browser.cpp +++ b/src/game/client/components/menus_browser.cpp @@ -1442,6 +1442,17 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) const int FriendIndex = CurrentClient.m_FriendState == IFriends::FRIEND_PLAYER ? FRIEND_PLAYER_ON : FRIEND_CLAN_ON; m_avFriends[FriendIndex].emplace_back(CurrentClient, pEntry); + // Update starred status from offline friend list + auto &OnlineFriend = m_avFriends[FriendIndex].back(); + for(const auto &OfflineFriend : m_avFriends[FRIEND_OFF]) + { + if((OfflineFriend.Name()[0] == '\0' || str_comp(OfflineFriend.Name(), CurrentClient.m_aName) == 0) && + ((OfflineFriend.Name()[0] != '\0' && g_Config.m_ClFriendsIgnoreClan) || str_comp(OfflineFriend.Clan(), CurrentClient.m_aClan) == 0)) + { + OnlineFriend.SetStarred(OfflineFriend.IsStarred()); + break; + } + } const auto &&RemovalPredicate = [CurrentClient](const CFriendItem &Friend) { return (Friend.Name()[0] == '\0' || str_comp(Friend.Name(), CurrentClient.m_aName) == 0) && ((Friend.Name()[0] != '\0' && g_Config.m_ClFriendsIgnoreClan) || str_comp(Friend.Clan(), CurrentClient.m_aClan) == 0); }; @@ -1518,11 +1529,33 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) continue; const bool Inside = Ui()->HotItem() == Friend.ListItemId() || Ui()->HotItem() == Friend.RemoveButtonId() || Ui()->HotItem() == Friend.CommunityTooltipId() || Ui()->HotItem() == Friend.SkinTooltipId(); - int ButtonResult = Ui()->DoButtonLogic(Friend.ListItemId(), 0, &Rect, BUTTONFLAG_LEFT); + int ButtonResult = Ui()->DoButtonLogic(Friend.ListItemId(), 0, &Rect, BUTTONFLAG_LEFT | BUTTONFLAG_RIGHT); + // Handle right-click to toggle star status + if(ButtonResult == 2) + { + GameClient()->Friends()->SetFriendStarred(Friend.Name(), Friend.Clan(), !Friend.IsStarred()); + FriendlistOnUpdate(); + ButtonResult = 0; + } + + // Show tooltip with platform-specific interaction hint +#if defined(CONF_PLATFORM_ANDROID) + const char *pStarAction = "Long press"; +#else + const char *pStarAction = "Right click"; +#endif if(Friend.ServerInfo()) { - GameClient()->m_Tooltips.DoToolTip(Friend.ListItemId(), &Rect, Localize("Click to select server. Double click to join your friend.")); + static char s_aTooltipText[128]; + str_format(s_aTooltipText, sizeof(s_aTooltipText), Localize("Click to select server. Double click to join your friend. %s to star/unstar."), pStarAction); + GameClient()->m_Tooltips.DoToolTip(Friend.ListItemId(), &Rect, s_aTooltipText); + } + else + { + static char s_aTooltipText[128]; + str_format(s_aTooltipText, sizeof(s_aTooltipText), Localize("%s to star/unstar this friend."), pStarAction); + GameClient()->m_Tooltips.DoToolTip(Friend.ListItemId(), &Rect, s_aTooltipText); } const ColorRGBA Color = PlayerBackgroundColor(FriendType == FRIEND_PLAYER_ON, FriendType == FRIEND_CLAN_ON, FriendType == FRIEND_OFF ? true : Friend.IsAfk(), Inside); Rect.Draw(Color, IGraphics::CORNER_ALL, 5.0f); @@ -1570,7 +1603,19 @@ void CMenus::RenderServerbrowserFriends(CUIRect View) } Rect.HSplitTop(11.0f, &NameLabel, &ClanLabel); - // name + // name with star indicator if starred + if(Friend.IsStarred()) + { + CUIRect StarIcon; + NameLabel.VSplitLeft(11.0f, &StarIcon, &NameLabel); + TextRender()->TextColor(ColorRGBA(1.0f, 0.84f, 0.0f, 1.0f)); // Gold color + TextRender()->SetFontPreset(EFontPreset::ICON_FONT); + TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_OVERSIZE); + Ui()->DoLabel(&StarIcon, FONT_ICON_STAR, StarIcon.h * CUi::ms_FontmodHeight, TEXTALIGN_ML); + TextRender()->SetRenderFlags(0); + TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); + TextRender()->TextColor(TextRender()->DefaultTextColor()); + } Ui()->DoLabel(&NameLabel, Friend.Name(), FontSize - 1.0f, TEXTALIGN_ML); // clan @@ -1827,7 +1872,7 @@ void CMenus::RenderServerbrowser(CUIRect MainView) | | | tool | | | | box | +---------------------------+ | | - status box +-----------------+ + status box +-----------------+ */ CUIRect ServerList, StatusBox, ToolBox, TabBar; diff --git a/src/game/map/render_layer.cpp b/src/game/map/render_layer.cpp index c661926b211..9fe40c8a9f4 100644 --- a/src/game/map/render_layer.cpp +++ b/src/game/map/render_layer.cpp @@ -876,7 +876,7 @@ void CRenderLayerQuads::RenderQuadLayer(float Alpha, const CRenderLayerParams &P CQuad *pQuad = &m_pQuads[QuadCluster.m_StartIndex + QuadClusterId]; ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f); - if(pQuad->m_ColorEnvOffset >= 0) + if(pQuad->m_ColorEnv >= 0) { m_pEnvelopeManager->EnvelopeEval()->EnvelopeEval(pQuad->m_ColorEnvOffset, pQuad->m_ColorEnv, Color, 4); }