1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#ifndef ENGINE_SERVERBROWSER_H
4#define ENGINE_SERVERBROWSER_H
5
6#include "kernel.h"
7
8#include <base/color.h>
9#include <base/hash.h>
10#include <base/str.h>
11
12#include <engine/map.h>
13#include <engine/shared/protocol.h>
14
15#include <generated/protocol7.h>
16
17#include <optional>
18#include <unordered_set>
19#include <vector>
20
21static constexpr const char *DDNET_INFO_FILE = "ddnet-info.json";
22static constexpr const char *DDNET_INFO_URL = "https://info.ddnet.org/info";
23
24class CUIElement;
25
26class CServerInfo
27{
28public:
29 enum
30 {
31 LOC_UNKNOWN = 0,
32 LOC_AFRICA,
33 LOC_ASIA,
34 LOC_AUSTRALIA,
35 LOC_EUROPE,
36 LOC_NORTH_AMERICA,
37 LOC_SOUTH_AMERICA,
38 // Special case China because it has an exceptionally bad
39 // connection to the outside due to the Great Firewall of
40 // China:
41 // https://en.wikipedia.org/w/index.php?title=Great_Firewall&oldid=1019589632
42 LOC_CHINA,
43 NUM_LOCS,
44 };
45
46 enum EClientScoreKind
47 {
48 CLIENT_SCORE_KIND_UNSPECIFIED,
49 CLIENT_SCORE_KIND_POINTS,
50 CLIENT_SCORE_KIND_TIME,
51 CLIENT_SCORE_KIND_TIME_BACKCOMPAT,
52 };
53
54 enum ERankState
55 {
56 RANK_UNAVAILABLE,
57 RANK_RANKED,
58 RANK_UNRANKED,
59 };
60
61 enum
62 {
63 MAX_COMMUNITY_ID_LENGTH = 32,
64 MAX_COMMUNITY_COUNTRY_LENGTH = 32,
65 MAX_COMMUNITY_TYPE_LENGTH = 32,
66 };
67
68 class CClient
69 {
70 public:
71 char m_aName[MAX_NAME_LENGTH];
72 char m_aClan[MAX_CLAN_LENGTH];
73 int m_Country;
74 int m_Score;
75 bool m_Player;
76 bool m_Afk;
77 int m_FriendState;
78 // skin info 0.6
79 char m_aSkin[MAX_SKIN_LENGTH];
80 bool m_CustomSkinColors;
81 int m_CustomSkinColorBody;
82 int m_CustomSkinColorFeet;
83 // skin info 0.7
84 char m_aaSkin7[protocol7::NUM_SKINPARTS][protocol7::MAX_SKIN_LENGTH];
85 bool m_aUseCustomSkinColor7[protocol7::NUM_SKINPARTS];
86 int m_aCustomSkinColor7[protocol7::NUM_SKINPARTS];
87 };
88
89 int m_ServerIndex;
90
91 int m_Type;
92 uint64_t m_ReceivedPackets;
93 int m_NumReceivedClients;
94
95 int m_NumAddresses;
96 NETADDR m_aAddresses[MAX_SERVER_ADDRESSES];
97
98 int m_QuickSearchHit;
99 int m_FriendState;
100 int m_FriendNum;
101
102 int m_MaxClients;
103 int m_NumClients;
104 int m_MaxPlayers;
105 int m_NumPlayers;
106 int m_Flags;
107 EClientScoreKind m_ClientScoreKind;
108 TRISTATE m_Favorite;
109 TRISTATE m_FavoriteAllowPing;
110 char m_aCommunityId[MAX_COMMUNITY_ID_LENGTH];
111 char m_aCommunityCountry[MAX_COMMUNITY_COUNTRY_LENGTH];
112 char m_aCommunityType[MAX_COMMUNITY_TYPE_LENGTH];
113 int m_Location;
114 bool m_LatencyIsEstimated;
115 int m_Latency; // in ms
116 ERankState m_HasRank;
117 char m_aGameType[16];
118 ColorRGBA m_GametypeColor;
119 char m_aName[64];
120 char m_aMap[MAX_MAP_LENGTH];
121 int m_MapCrc;
122 int m_MapSize;
123 char m_aVersion[32];
124 char m_aAddress[MAX_SERVER_ADDRESSES * NETADDR_MAXSTRSIZE];
125 CClient m_aClients[SERVERINFO_MAX_CLIENTS];
126 int m_NumFilteredPlayers;
127 bool m_RequiresLogin;
128
129 static int EstimateLatency(int Loc1, int Loc2);
130 static bool ParseLocation(int *pResult, const char *pString);
131 static ColorRGBA GametypeColor(const char *pGametype);
132};
133
134class CCommunityCountryServer
135{
136 NETADDR m_Address;
137 char m_aTypeName[CServerInfo::MAX_COMMUNITY_TYPE_LENGTH];
138
139public:
140 CCommunityCountryServer(NETADDR Address, const char *pTypeName) :
141 m_Address(Address)
142 {
143 str_copy(dst&: m_aTypeName, src: pTypeName);
144 }
145
146 NETADDR Address() const { return m_Address; }
147 const char *TypeName() const { return m_aTypeName; }
148};
149
150class CCommunityCountry
151{
152 friend class CServerBrowser;
153
154 char m_aName[CServerInfo::MAX_COMMUNITY_COUNTRY_LENGTH];
155 int m_FlagId;
156 std::vector<CCommunityCountryServer> m_vServers;
157
158public:
159 CCommunityCountry(const char *pName, int FlagId) :
160 m_FlagId(FlagId)
161 {
162 str_copy(dst&: m_aName, src: pName);
163 }
164
165 const char *Name() const { return m_aName; }
166 int FlagId() const { return m_FlagId; }
167 const std::vector<CCommunityCountryServer> &Servers() const { return m_vServers; }
168};
169
170class CCommunityType
171{
172 char m_aName[CServerInfo::MAX_COMMUNITY_TYPE_LENGTH];
173
174public:
175 CCommunityType(const char *pName)
176 {
177 str_copy(dst&: m_aName, src: pName);
178 }
179
180 const char *Name() const { return m_aName; }
181};
182
183class CCommunityMap
184{
185 char m_aName[MAX_MAP_LENGTH];
186
187public:
188 CCommunityMap(const char *pName)
189 {
190 str_copy(dst&: m_aName, src: pName);
191 }
192
193 const char *Name() const { return m_aName; }
194
195 bool operator==(const CCommunityMap &Other) const
196 {
197 return str_comp(a: Name(), b: Other.Name()) == 0;
198 }
199
200 bool operator!=(const CCommunityMap &Other) const
201 {
202 return !(*this == Other);
203 }
204
205 struct SHash
206 {
207 size_t operator()(const CCommunityMap &Map) const
208 {
209 return str_quickhash(str: Map.Name());
210 }
211 };
212};
213
214class CCommunity
215{
216 friend class CServerBrowser;
217
218 char m_aId[CServerInfo::MAX_COMMUNITY_ID_LENGTH];
219 char m_aName[64];
220 std::optional<SHA256_DIGEST> m_IconSha256;
221 char m_aIconUrl[128];
222 std::vector<CCommunityCountry> m_vCountries;
223 std::vector<CCommunityType> m_vTypes;
224 int m_NumPlayers = 0;
225 bool m_HasFinishes = false;
226 std::unordered_set<CCommunityMap, CCommunityMap::SHash> m_FinishedMaps;
227
228public:
229 CCommunity(const char *pId, const char *pName, std::optional<SHA256_DIGEST> IconSha256, const char *pIconUrl) :
230 m_IconSha256(IconSha256)
231 {
232 str_copy(dst&: m_aId, src: pId);
233 str_copy(dst&: m_aName, src: pName);
234 str_copy(dst&: m_aIconUrl, src: pIconUrl);
235 }
236
237 const char *Id() const { return m_aId; }
238 const char *Name() const { return m_aName; }
239 const char *IconUrl() const { return m_aIconUrl; }
240 const std::optional<SHA256_DIGEST> &IconSha256() const { return m_IconSha256; }
241 const std::vector<CCommunityCountry> &Countries() const { return m_vCountries; }
242 const std::vector<CCommunityType> &Types() const { return m_vTypes; }
243 bool HasCountry(const char *pCountryName) const;
244 bool HasType(const char *pTypeName) const;
245 bool HasRanks() const { return m_HasFinishes; }
246 CServerInfo::ERankState HasRank(const char *pMap) const;
247 int NumPlayers() const { return m_NumPlayers; }
248};
249
250class IFilterList
251{
252public:
253 virtual ~IFilterList() = default;
254 virtual void Add(const char *pElement) = 0;
255 virtual void Remove(const char *pElement) = 0;
256 virtual void Clear() = 0;
257 virtual bool Empty() const = 0;
258 virtual bool Filtered(const char *pElement) const = 0;
259};
260
261class ICommunityCache
262{
263public:
264 virtual ~ICommunityCache() = default;
265 virtual void Update(bool Force) = 0;
266 virtual const std::vector<const CCommunity *> &SelectedCommunities() const = 0;
267 virtual const std::vector<const CCommunityCountry *> &SelectableCountries() const = 0;
268 virtual const std::vector<const CCommunityType *> &SelectableTypes() const = 0;
269 virtual bool AnyRanksAvailable() const = 0;
270 virtual bool CountriesTypesFilterAvailable() const = 0;
271 virtual const char *CountryTypeFilterKey() const = 0;
272};
273
274class IServerBrowser : public IInterface
275{
276 MACRO_INTERFACE("serverbrowser")
277public:
278 /* Constants: Server Browser Sorting
279 SORT_NAME - Sort by name.
280 SORT_PING - Sort by ping.
281 SORT_MAP - Sort by map
282 SORT_GAMETYPE - Sort by game type. DM, TDM etc.
283 SORT_NUMPLAYERS - Sort after how many players there are on the server.
284 SORT_NUMFRIENDS - Sort after how many friends there are on the server.
285 */
286 enum
287 {
288 SORT_NAME = 0,
289 SORT_PING,
290 SORT_MAP,
291 SORT_GAMETYPE,
292 SORT_NUMPLAYERS,
293 SORT_NUMFRIENDS,
294 };
295
296 enum
297 {
298 QUICK_SERVERNAME = 1,
299 QUICK_PLAYER = 2,
300 QUICK_MAPNAME = 4,
301 };
302
303 enum
304 {
305 TYPE_INTERNET = 0,
306 TYPE_LAN,
307 TYPE_FAVORITES,
308 TYPE_FAVORITE_COMMUNITY_1,
309 TYPE_FAVORITE_COMMUNITY_2,
310 TYPE_FAVORITE_COMMUNITY_3,
311 TYPE_FAVORITE_COMMUNITY_4,
312 TYPE_FAVORITE_COMMUNITY_5,
313 NUM_TYPES,
314 };
315
316 enum
317 {
318 LAN_PORT_BEGIN = 8303,
319 LAN_PORT_END = 8310,
320 };
321
322 class CServerEntry
323 {
324 public:
325 int64_t m_RequestTime;
326 bool m_RequestIgnoreInfo;
327 int m_GotInfo;
328 CServerInfo m_Info;
329
330 CServerEntry *m_pPrevReq; // request list
331 CServerEntry *m_pNextReq;
332 };
333
334 static constexpr const char *COMMUNITY_DDNET = "ddnet";
335 static constexpr const char *COMMUNITY_NONE = "none";
336
337 static constexpr const char *COMMUNITY_COUNTRY_NONE = "none";
338 static constexpr const char *COMMUNITY_TYPE_NONE = "None";
339 /**
340 * Special community value for country/type filters that
341 * affect all communities.
342 */
343 static constexpr const char *COMMUNITY_ALL = "all";
344
345 static constexpr const char *SEARCH_EXCLUDE_TOKEN = ";";
346
347 virtual void Refresh(int Type, bool Force = false) = 0;
348 virtual bool IsRefreshing() const = 0;
349 virtual bool IsGettingServerlist() const = 0;
350 virtual bool IsServerlistError() const = 0;
351 virtual int LoadingProgression() const = 0;
352
353 virtual int NumServers() const = 0;
354 virtual const CServerInfo *Get(int Index) const = 0;
355
356 virtual int Players(const CServerInfo &Item) const = 0;
357 virtual int Max(const CServerInfo &Item) const = 0;
358
359 virtual int NumSortedServers() const = 0;
360 virtual int NumSortedPlayers() const = 0;
361 virtual const CServerInfo *SortedGet(int Index) const = 0;
362
363 virtual const std::vector<CCommunity> &Communities() const = 0;
364 virtual const CCommunity *Community(const char *pCommunityId) const = 0;
365 virtual std::vector<const CCommunity *> SelectedCommunities() const = 0;
366 virtual std::vector<const CCommunity *> FavoriteCommunities() const = 0;
367 virtual std::vector<const CCommunity *> CurrentCommunities() const = 0;
368 virtual unsigned CurrentCommunitiesHash() const = 0;
369
370 virtual bool DDNetInfoAvailable() const = 0;
371 virtual std::optional<SHA256_DIGEST> DDNetInfoSha256() const = 0;
372
373 virtual ICommunityCache &CommunityCache() = 0;
374 virtual const ICommunityCache &CommunityCache() const = 0;
375 virtual IFilterList &FavoriteCommunitiesFilter() = 0;
376 virtual IFilterList &CommunitiesFilter() = 0;
377 virtual IFilterList &CountriesFilter() = 0;
378 virtual IFilterList &TypesFilter() = 0;
379 virtual const IFilterList &FavoriteCommunitiesFilter() const = 0;
380 virtual const IFilterList &CommunitiesFilter() const = 0;
381 virtual const IFilterList &CountriesFilter() const = 0;
382 virtual const IFilterList &TypesFilter() const = 0;
383 virtual void CleanFilters() = 0;
384
385 virtual CServerEntry *Find(const NETADDR &Addr) = 0;
386 virtual int GetCurrentType() = 0;
387 virtual const char *GetTutorialServer() = 0;
388};
389
390#endif
391