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_SERVER_H
4#define ENGINE_SERVER_H
5
6#include "console.h"
7#include "kernel.h"
8#include "message.h"
9
10#include <base/dbg.h>
11#include <base/hash.h>
12#include <base/math.h>
13#include <base/mem.h>
14#include <base/str.h>
15
16#include <engine/shared/jsonwriter.h>
17#include <engine/shared/protocol.h>
18
19#include <generated/protocol.h>
20#include <generated/protocol7.h>
21#include <generated/protocolglue.h>
22
23#include <algorithm>
24#include <array>
25#include <optional>
26#include <type_traits>
27
28struct CAntibotRoundData;
29class IMap;
30
31// When recording a demo on the server, the ClientId -1 is used
32enum
33{
34 SERVER_DEMO_CLIENT = -1
35};
36
37class IServer : public IInterface
38{
39 MACRO_INTERFACE("server")
40protected:
41 int m_CurrentGameTick;
42
43public:
44 /*
45 Structure: CClientInfo
46 */
47 struct CClientInfo
48 {
49 const char *m_pName;
50 int m_Latency;
51 bool m_GotDDNetVersion;
52 int m_DDNetVersion;
53 const char *m_pDDNetVersionStr;
54 const CUuid *m_pConnectionId;
55 };
56
57 int Tick() const { return m_CurrentGameTick; }
58 int TickSpeed() const { return SERVER_TICK_SPEED; }
59
60 virtual int Port() const = 0;
61 virtual int MaxClients() const = 0;
62 virtual int ClientCount() const = 0;
63 virtual int DistinctClientCount() const = 0;
64 virtual const char *ClientName(int ClientId) const = 0;
65 virtual const char *ClientClan(int ClientId) const = 0;
66 virtual int ClientCountry(int ClientId) const = 0;
67 virtual bool ClientSlotEmpty(int ClientId) const = 0;
68 virtual bool ClientIngame(int ClientId) const = 0;
69 virtual bool GetClientInfo(int ClientId, CClientInfo *pInfo) const = 0;
70 virtual void SetClientDDNetVersion(int ClientId, int DDNetVersion) = 0;
71 virtual const NETADDR *ClientAddr(int ClientId) const = 0;
72 virtual const std::array<char, NETADDR_MAXSTRSIZE> &ClientAddrStringImpl(int ClientId, bool IncludePort) const = 0;
73 const char *ClientAddrString(int ClientId, bool IncludePort) const { return ClientAddrStringImpl(ClientId, IncludePort).data(); }
74
75 /**
76 * Returns the version of the client with the given client ID.
77 *
78 * @param ClientId the client Id, which must be between 0 and
79 * MAX_CLIENTS - 1, or equal to SERVER_DEMO_CLIENT for server demos.
80 *
81 * @return The version of the client with the given client ID.
82 * For server demos this is always the latest client version.
83 * On errors, VERSION_NONE is returned.
84 */
85 virtual int GetClientVersion(int ClientId) const = 0;
86 virtual int SendMsg(CMsgPacker *pMsg, int Flags, int ClientId) = 0;
87
88 template<class T>
89 requires(!protocol7::is_sixup<T>::value)
90 int SendPackMsg(const T *pMsg, int Flags, int ClientId)
91 {
92 int Result = 0;
93 if(ClientId == -1)
94 {
95 for(int i = 0; i < MaxClients(); i++)
96 if(ClientIngame(ClientId: i))
97 Result = SendPackMsgTranslate(pMsg, Flags, i);
98 }
99 else
100 {
101 Result = SendPackMsgTranslate(pMsg, Flags, ClientId);
102 }
103 return Result;
104 }
105
106 template<class T>
107 requires(protocol7::is_sixup<T>::value)
108 int SendPackMsg(const T *pMsg, int Flags, int ClientId)
109 {
110 int Result = 0;
111 if(ClientId == -1)
112 {
113 for(int i = 0; i < MaxClients(); i++)
114 if(ClientIngame(ClientId: i) && IsSixup(ClientId: i))
115 Result = SendPackMsgTranslate(pMsg, Flags, i);
116 }
117 else if(IsSixup(ClientId))
118 {
119 Result = SendPackMsgTranslate(pMsg, Flags, ClientId);
120 }
121
122 return Result;
123 }
124
125 template<class T>
126 int SendPackMsgTranslate(const T *pMsg, int Flags, int ClientId)
127 {
128 return SendPackMsgOne(pMsg, Flags, ClientId);
129 }
130
131 int SendPackMsgTranslate(const CNetMsg_Sv_Chat *pMsg, int Flags, int ClientId)
132 {
133 protocol7::CNetMsg_Sv_Chat Msg;
134 Msg.m_ClientId = pMsg->m_ClientId;
135 Msg.m_Mode = pMsg->m_Team ? protocol7::CHAT_TEAM : protocol7::CHAT_ALL;
136 Msg.m_pMessage = pMsg->m_pMessage;
137 Msg.m_TargetId = -1;
138 return SendPackMsgTranslateChat(pMsg: &Msg, Flags, ClientId);
139 }
140
141 // This function is directly being called from CGameContext.
142 // Whisper will always be managed in 0.7 format and only in this function properly
143 // translated for either 0.6 or 0.7 connection including 128p translation.
144 int SendPackMsgTranslateChat(const protocol7::CNetMsg_Sv_Chat *pMsg, int Flags, int ClientId)
145 {
146 protocol7::CNetMsg_Sv_Chat MsgCopy = *pMsg;
147
148 // 0.6 whisper send/recv
149 int WhisperSend = TEAM_WHISPER_SEND + (int)protocol7::NUM_CHATS;
150 int WhisperRecv = TEAM_WHISPER_RECV + (int)protocol7::NUM_CHATS;
151 int *pId = MsgCopy.m_Mode == WhisperSend ? &MsgCopy.m_TargetId : &MsgCopy.m_ClientId;
152
153 // 128 player translation
154 char aBuf[512];
155 if(GetClientVersion(ClientId) < VERSION_DDNET_128_PLAYERS)
156 {
157 // force "Name: message" for team messages from other players to not show duplicate messages when dummy is connected
158 if(*pId >= 0 && ((MsgCopy.m_Mode == protocol7::CHAT_TEAM && *pId != ClientId) || MsgCopy.m_Mode == WhisperRecv || !Translate(Target&: *pId, ClientId)))
159 {
160 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s: %s", ClientName(ClientId: *pId), MsgCopy.m_pMessage);
161 MsgCopy.m_pMessage = aBuf;
162
163 // with noname and sending a whisper to ourselves show our own client id as targetid because otherwise it would be two times id 63 with same text which gets shown twice the same msg
164 if(MsgCopy.m_Mode == WhisperSend && *pId == ClientId)
165 Translate(Target&: *pId, ClientId);
166 else
167 *pId = LEGACY_MAX_CLIENTS - 1;
168 }
169 }
170
171 if(IsSixup(ClientId))
172 {
173 if(MsgCopy.m_Mode == WhisperSend)
174 {
175 Translate(Target&: MsgCopy.m_ClientId, ClientId);
176 MsgCopy.m_Mode = protocol7::CHAT_WHISPER;
177 }
178 else if(MsgCopy.m_Mode == WhisperRecv)
179 {
180 Translate(Target&: MsgCopy.m_TargetId, ClientId);
181 MsgCopy.m_Mode = protocol7::CHAT_WHISPER;
182 }
183 return SendPackMsgOne(pMsg: &MsgCopy, Flags, ClientId);
184 }
185
186 CNetMsg_Sv_Chat Msg;
187 if(MsgCopy.m_Mode == WhisperSend || MsgCopy.m_Mode == WhisperRecv)
188 Msg.m_Team = MsgCopy.m_Mode - protocol7::NUM_CHATS;
189 else
190 Msg.m_Team = (int)(MsgCopy.m_Mode == protocol7::CHAT_TEAM);
191 Msg.m_ClientId = *pId;
192 Msg.m_pMessage = MsgCopy.m_pMessage;
193 return SendPackMsgOne(pMsg: &Msg, Flags, ClientId);
194 }
195
196 int SendPackMsgTranslate(const CNetMsg_Sv_KillMsg *pMsg, int Flags, int ClientId)
197 {
198 CNetMsg_Sv_KillMsg MsgCopy = *pMsg;
199 if(!Translate(Target&: MsgCopy.m_Victim, ClientId))
200 return 0;
201 if(!Translate(Target&: MsgCopy.m_Killer, ClientId))
202 MsgCopy.m_Killer = MsgCopy.m_Victim;
203 return SendPackMsgOne(pMsg: &MsgCopy, Flags, ClientId);
204 }
205
206 int SendPackMsgTranslate(const CNetMsg_Sv_KillMsgTeam *pMsg, int Flags, int ClientId)
207 {
208 CNetMsg_Sv_KillMsgTeam MsgCopy = *pMsg;
209 if(!Translate(Target&: MsgCopy.m_First, ClientId))
210 MsgCopy.m_First = -1;
211 return SendPackMsgOne(pMsg: &MsgCopy, Flags, ClientId);
212 }
213
214 int SendPackMsgTranslate(const CNetMsg_Sv_Emoticon *pMsg, int Flags, int ClientId)
215 {
216 CNetMsg_Sv_Emoticon MsgCopy = *pMsg;
217 return Translate(Target&: MsgCopy.m_ClientId, ClientId) && SendPackMsgOne(pMsg: &MsgCopy, Flags, ClientId);
218 }
219
220 int SendPackMsgTranslate(const protocol7::CNetMsg_Sv_VoteSet *pMsg, int Flags, int ClientId)
221 {
222 protocol7::CNetMsg_Sv_VoteSet MsgCopy = *pMsg;
223 if(!Translate(Target&: MsgCopy.m_ClientId, ClientId))
224 MsgCopy.m_ClientId = -1;
225 return SendPackMsgOne(pMsg: &MsgCopy, Flags, ClientId);
226 }
227
228 int SendPackMsgTranslate(const protocol7::CNetMsg_Sv_Team *pMsg, int Flags, int ClientId)
229 {
230 protocol7::CNetMsg_Sv_Team MsgCopy = *pMsg;
231 return Translate(Target&: MsgCopy.m_ClientId, ClientId) && SendPackMsgOne(pMsg: &MsgCopy, Flags, ClientId);
232 }
233
234 int SendPackMsgTranslate(const protocol7::CNetMsg_Sv_SkinChange *pMsg, int Flags, int ClientId)
235 {
236 protocol7::CNetMsg_Sv_SkinChange MsgCopy = *pMsg;
237 return Translate(Target&: MsgCopy.m_ClientId, ClientId) && SendPackMsgOne(pMsg: &MsgCopy, Flags, ClientId);
238 }
239
240 int SendPackMsgTranslate(const protocol7::CNetMsg_Sv_ClientInfo *pMsg, int Flags, int ClientId)
241 {
242 protocol7::CNetMsg_Sv_ClientInfo MsgCopy = *pMsg;
243 return (Flags & MSGFLAG_NOTRANSLATE || Translate(Target&: MsgCopy.m_ClientId, ClientId)) && SendPackMsgOne(pMsg: &MsgCopy, Flags, ClientId);
244 }
245
246 int SendPackMsgTranslate(const protocol7::CNetMsg_Sv_ClientDrop *pMsg, int Flags, int ClientId)
247 {
248 protocol7::CNetMsg_Sv_ClientDrop MsgCopy = *pMsg;
249 return (Flags & MSGFLAG_NOTRANSLATE || Translate(Target&: MsgCopy.m_ClientId, ClientId)) && SendPackMsgOne(pMsg: &MsgCopy, Flags, ClientId);
250 }
251
252 int SendPackMsgTranslate(const CNetMsg_Sv_RaceFinish *pMsg, int Flags, int ClientId)
253 {
254 if(IsSixup(ClientId))
255 {
256 protocol7::CNetMsg_Sv_RaceFinish Msg7;
257 Msg7.m_ClientId = pMsg->m_ClientId;
258 Msg7.m_Diff = pMsg->m_Diff;
259 Msg7.m_Time = pMsg->m_Time;
260 Msg7.m_RecordPersonal = pMsg->m_RecordPersonal;
261 Msg7.m_RecordServer = pMsg->m_RecordServer;
262 return Translate(Target&: Msg7.m_ClientId, ClientId) && SendPackMsgOne(pMsg: &Msg7, Flags, ClientId);
263 }
264
265 CNetMsg_Sv_RaceFinish MsgCopy = *pMsg;
266 return Translate(Target&: MsgCopy.m_ClientId, ClientId) && SendPackMsgOne(pMsg: &MsgCopy, Flags, ClientId);
267 }
268
269 template<class T>
270 int SendPackMsgOne(const T *pMsg, int Flags, int ClientId)
271 {
272 dbg_assert(ClientId != -1, "SendPackMsgOne called with -1");
273 CMsgPacker Packer(T::ms_MsgId, false, protocol7::is_sixup<T>::value);
274
275 if(pMsg->Pack(&Packer))
276 return -1;
277 return SendMsg(pMsg: &Packer, Flags, ClientId);
278 }
279
280 bool Translate(int &Target, int ClientId)
281 {
282 // console and server demo pseudo clients operate on untranslated ids (SERVER_DEMO_CLIENT == IConsole::CLIENT_ID_UNSPECIFIED)
283 if(ClientId == SERVER_DEMO_CLIENT || ClientId == IConsole::CLIENT_ID_GAME || ClientId == IConsole::CLIENT_ID_NO_GAME)
284 return true;
285 if(GetClientVersion(ClientId) >= VERSION_DDNET_128_PLAYERS)
286 return true;
287 if(Target < 0 || Target >= MAX_CLIENTS)
288 return false;
289 int *pMap = GetReverseIdMap(ClientId);
290 if(pMap[Target] == -1)
291 return false;
292 Target = pMap[Target];
293 return true;
294 }
295
296 bool ReverseTranslate(int &Target, int ClientId)
297 {
298 // console and server demo pseudo clients operate on untranslated ids (SERVER_DEMO_CLIENT == IConsole::CLIENT_ID_UNSPECIFIED)
299 if(ClientId == SERVER_DEMO_CLIENT || ClientId == IConsole::CLIENT_ID_GAME || ClientId == IConsole::CLIENT_ID_NO_GAME)
300 return true;
301 if(GetClientVersion(ClientId) >= VERSION_DDNET_128_PLAYERS)
302 return true;
303 if(Target < 0 || Target >= LEGACY_MAX_CLIENTS)
304 return false;
305 int *pMap = GetIdMap(ClientId);
306 if(pMap[Target] == -1)
307 return false;
308 Target = pMap[Target];
309 return true;
310 }
311
312 virtual bool WouldClientNameChange(int ClientId, const char *pNameRequest) = 0;
313 virtual bool WouldClientClanChange(int ClientId, const char *pClanRequest) = 0;
314 virtual void SetClientName(int ClientId, const char *pName) = 0;
315 virtual void SetClientClan(int ClientId, const char *pClan) = 0;
316 virtual void SetClientCountry(int ClientId, int Country) = 0;
317 virtual void SetClientScore(int ClientId, std::optional<int> Score) = 0;
318 virtual void SetClientFlags(int ClientId, int Flags) = 0;
319
320 virtual std::optional<int> SnapNewId() = 0;
321 virtual void SnapFreeId(int Id) = 0;
322 virtual bool SnapNewItem(int Type, int Id, const void *pData, int Size) = 0;
323
324 template<typename T>
325 bool SnapNewItem(int Id, const T &Data)
326 {
327 const int Type = protocol7::is_sixup<T>::value ? -T::ms_MsgId : T::ms_MsgId;
328 return SnapNewItem(Type, Id, &Data, sizeof(Data));
329 }
330
331 virtual void SnapSetStaticsize(int ItemType, int Size) = 0;
332 virtual void SnapSetStaticsize7(int ItemType, int Size) = 0;
333
334 enum
335 {
336 RCON_CID_SERV = -1,
337 RCON_CID_VOTE = -2,
338 };
339 virtual void SetRconCid(int ClientId) = 0;
340 virtual int GetAuthedState(int ClientId) const = 0;
341 virtual bool IsRconAuthed(int ClientId) const = 0;
342 virtual bool IsRconAuthedAdmin(int ClientId) const = 0;
343 virtual const char *GetAuthName(int ClientId) const = 0;
344 virtual bool HasAuthHidden(int ClientId) const = 0;
345 virtual void Kick(int ClientId, const char *pReason) = 0;
346 virtual void Ban(int ClientId, int Seconds, const char *pReason, bool VerbatimReason) = 0;
347 virtual void RedirectClient(int ClientId, int Port) = 0;
348 virtual void ChangeMap(const char *pMap) = 0;
349 virtual void ReloadMap() = 0;
350
351 virtual void DemoRecorder_HandleAutoStart() = 0;
352
353 // DDRace
354
355 virtual void SaveDemo(int ClientId, float Time) = 0;
356 virtual void StartRecord(int ClientId) = 0;
357 virtual void StopRecord(int ClientId) = 0;
358 virtual bool IsRecording(int ClientId) = 0;
359 virtual void StopDemos() = 0;
360
361 virtual int *GetIdMap(int ClientId) = 0;
362 virtual int *GetReverseIdMap(int ClientId) = 0;
363
364 virtual bool DnsblWhite(int ClientId) = 0;
365 virtual bool DnsblPending(int ClientId) = 0;
366 virtual bool DnsblBlack(int ClientId) = 0;
367 virtual const char *GetAnnouncementLine() = 0;
368 virtual bool ClientPrevIngame(int ClientId) = 0;
369 virtual const char *GetNetErrorString(int ClientId) = 0;
370 virtual void ResetNetErrorString(int ClientId) = 0;
371 virtual bool SetTimedOut(int ClientId, int OrigId) = 0;
372 virtual void SetTimeoutProtected(int ClientId) = 0;
373
374 virtual void SetErrorShutdown(const char *pReason) = 0;
375 virtual void ExpireServerInfo() = 0;
376
377 virtual void FillAntibot(CAntibotRoundData *pData) = 0;
378
379 virtual void SendMsgRaw(int ClientId, const void *pData, int Size, int Flags) = 0;
380
381 virtual bool IsSixup(int ClientId) const = 0;
382};
383
384class IGameServer : public IInterface
385{
386 MACRO_INTERFACE("gameserver")
387protected:
388public:
389 // `pPersistentData` may be null if this is the first time `IGameServer`
390 // is instantiated.
391 virtual void OnInit(const void *pPersistentData) = 0;
392 virtual void OnConsoleInit() = 0;
393 // Returns `true` if map change accepted.
394 [[nodiscard]] virtual bool OnMapChange(char *pNewMapName, int MapNameSize) = 0;
395 // `pPersistentData` may be null if this is the last time `IGameServer`
396 // is destroyed.
397 virtual void OnShutdown(void *pPersistentData) = 0;
398
399 virtual void OnTick() = 0;
400
401 // Snap for a specific client.
402 //
403 // GlobalSnap is true when sending snapshots to all clients,
404 // otherwise only forced high bandwidth clients would receive snap.
405 // RecordingDemo is true when this snapshot will be recorded to a demo.
406 virtual void OnSnap(int ClientId, bool GlobalSnap, bool RecordingDemo) = 0;
407
408 // Called after sending snapshots to all clients.
409 //
410 // Note if any client has force high bandwidth enabled,
411 // this will not be called when only sending snapshots to these clients.
412 virtual void OnPostGlobalSnap() = 0;
413
414 virtual void OnMessage(int MsgId, CUnpacker *pUnpacker, int ClientId) = 0;
415
416 // Called before map reload, for any data that the game wants to
417 // persist to the next map.
418 //
419 // Has the size of the return value of `PersistentClientDataSize()`.
420 //
421 // Returns whether the game should be supplied with the data when the
422 // client connects for the next map.
423 virtual bool OnClientDataPersist(int ClientId, void *pData) = 0;
424
425 // Called when a client connects.
426 //
427 // If it is reconnecting to the game after a map change, the
428 // `pPersistentData` point is nonnull and contains the data the game
429 // previously stored.
430 virtual void OnClientConnected(int ClientId, void *pPersistentData) = 0;
431
432 virtual void OnClientEnter(int ClientId) = 0;
433 virtual void OnClientDrop(int ClientId, const char *pReason) = 0;
434 virtual void OnClientPrepareInput(int ClientId, void *pInput) = 0;
435 virtual void OnClientDirectInput(int ClientId, const void *pInput) = 0;
436 virtual void OnClientPredictedInput(int ClientId, const void *pInput) = 0;
437 virtual void OnClientPredictedEarlyInput(int ClientId, const void *pInput) = 0;
438
439 virtual void PreInputClients(int ClientId, bool *pClients) = 0;
440
441 virtual bool IsClientReady(int ClientId) const = 0;
442 virtual bool IsClientPlayer(int ClientId) const = 0;
443 virtual bool IsClientHighBandwidth(int ClientId) const = 0;
444
445 virtual int PersistentDataSize() const = 0;
446 virtual int PersistentClientDataSize() const = 0;
447
448 virtual CUuid GameUuid() const = 0;
449 virtual const char *GameType() const = 0;
450 virtual const char *Version() const = 0;
451 virtual const char *NetVersion() const = 0;
452
453 virtual IMap *Map() = 0;
454 virtual const IMap *Map() const = 0;
455 virtual CNetObjHandler *GetNetObjHandler() = 0;
456 virtual protocol7::CNetObjHandler *GetNetObjHandler7() = 0;
457
458 // DDRace
459
460 virtual void OnPreTickTeehistorian() = 0;
461
462 virtual void OnSetTimedOut(int ClientId) = 0;
463 virtual void OnSetAuthed(int ClientId, int Level) = 0;
464 virtual bool PlayerExists(int ClientId) const = 0;
465
466 virtual void TeehistorianRecordAntibot(const void *pData, int DataSize) = 0;
467 virtual void TeehistorianRecordPlayerJoin(int ClientId, bool Sixup) = 0;
468 virtual void TeehistorianRecordPlayerDrop(int ClientId, const char *pReason) = 0;
469 virtual void TeehistorianRecordPlayerRejoin(int ClientId) = 0;
470 virtual void TeehistorianRecordPlayerName(int ClientId, const char *pName) = 0;
471 virtual void TeehistorianRecordPlayerFinish(int ClientId, int TimeTicks) = 0;
472 virtual void TeehistorianRecordTeamFinish(int TeamId, int TimeTicks) = 0;
473 virtual void TeehistorianRecordAuthLogin(int ClientId, int Level, const char *pAuthName) = 0;
474
475 virtual void FillAntibot(CAntibotRoundData *pData) = 0;
476
477 /**
478 * Used to report custom player info to the master server.
479 *
480 * @param pJsonWriter A pointer to a @link CJsonWriter @endlink to which the custom data will written.
481 * @param ClientId The client ID.
482 */
483 virtual void OnUpdatePlayerServerInfo(CJsonWriter *pJsonWriter, int ClientId) = 0;
484};
485
486extern IGameServer *CreateGameServer();
487#endif
488