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