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 | |
20 | struct CAntibotRoundData; |
21 | |
22 | // When recording a demo on the server, the ClientId -1 is used |
23 | enum |
24 | { |
25 | SERVER_DEMO_CLIENT = -1 |
26 | }; |
27 | |
28 | class IServer : public IInterface |
29 | { |
30 | MACRO_INTERFACE("server" ) |
31 | protected: |
32 | int m_CurrentGameTick; |
33 | |
34 | public: |
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 | |
293 | class IGameServer : public IInterface |
294 | { |
295 | MACRO_INTERFACE("gameserver" ) |
296 | protected: |
297 | public: |
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 | virtual void TeehistorianRecordPlayerName(int ClientId, const char *pName) = 0; |
360 | virtual void TeehistorianRecordPlayerFinish(int ClientId, int TimeTicks) = 0; |
361 | virtual void TeehistorianRecordTeamFinish(int TeamId, int TimeTicks) = 0; |
362 | |
363 | virtual void FillAntibot(CAntibotRoundData *pData) = 0; |
364 | |
365 | /** |
366 | * Used to report custom player info to master servers. |
367 | * |
368 | * @param aBuf Should be the json key values to add, starting with a ',' beforehand, like: ',"skin": "default", "team": 1' |
369 | * @param i The client id. |
370 | */ |
371 | virtual void OnUpdatePlayerServerInfo(char *aBuf, int BufSize, int Id) = 0; |
372 | }; |
373 | |
374 | extern IGameServer *CreateGameServer(); |
375 | #endif |
376 | |