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