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