Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
62b7ca3
Add translation badge to `README.md`
Robyt3 Oct 26, 2025
c8366aa
Fix client crash when community removed while on browser tab
Robyt3 Nov 1, 2025
253d5f4
Initial plan
Copilot Nov 2, 2025
fbd3dac
Add star friends feature - core data structures and persistence
Copilot Nov 2, 2025
b5e886e
Add tooltips for star/unstar functionality
Copilot Nov 2, 2025
de19f25
Merge remote-tracking branch 'upstream/master' into copilot/add-frien…
gongfuture Nov 2, 2025
35e1fb5
Add translation badge to `README.md` (#11127)
heinrich5991 Nov 2, 2025
ebdbfae
Revert "Don't list header files twice in CMakeLists.txt"
heinrich5991 Nov 2, 2025
3d920db
Fix client crash when community removed while on browser tab (#11165)
Learath2 Nov 2, 2025
06f60a9
Revert "Don't list header files twice in CMakeLists.txt" (#11169)
ChillerDragon Nov 3, 2025
280a29f
Add Android long-press support with platform-specific tooltips
Copilot Nov 3, 2025
cfbf813
fix wrong check in quad envelope animation calculation
mwinkens Nov 3, 2025
4ae82a2
Fix: Wrong check in quad envelope animation calculation (#11173)
Learath2 Nov 3, 2025
cf7c721
Remove unused `CBinds::GetConsole` function
Robyt3 Nov 1, 2025
c95633f
Remove unnecessary special case when binding to left mouse button
Robyt3 Nov 2, 2025
799ec27
Remove unnecessary special case when binding to left mouse button, re…
heinrich5991 Nov 5, 2025
0062102
Merge commit '799ec2773bf4006624653b5504fb53b9924dfdda' into copilot/…
gongfuture Nov 5, 2025
27702b3
Refactor platform-specific tooltip code to reduce duplication
Copilot Nov 5, 2025
f0f64e7
Fix code style to comply with clang-format requirements
Copilot Nov 10, 2025
e329e23
Fix clang-format v10 compatibility for line 747
Copilot Nov 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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})

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.

Expand Down
28 changes: 26 additions & 2 deletions src/engine/client/friends.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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");
}
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
}
1 change: 1 addition & 0 deletions src/engine/client/friends.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
16 changes: 16 additions & 0 deletions src/engine/client/serverbrowser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions src/engine/client/serverbrowser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<CCommunity> &Communities() const override;
Expand Down
2 changes: 2 additions & 0 deletions src/engine/friends.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
1 change: 0 additions & 1 deletion src/game/client/components/binds.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
3 changes: 1 addition & 2 deletions src/game/client/components/key_binder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions src/game/client/components/menus.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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; }
Expand All @@ -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);
}
Expand Down
53 changes: 49 additions & 4 deletions src/game/client/components/menus_browser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1827,7 +1872,7 @@ void CMenus::RenderServerbrowser(CUIRect MainView)
| | | tool |
| | | box |
+---------------------------+ | |
status box +-----------------+
status box +-----------------+
*/

CUIRect ServerList, StatusBox, ToolBox, TabBar;
Expand Down
2 changes: 1 addition & 1 deletion src/game/map/render_layer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down