1#include "test.h"
2
3#include <base/logger.h>
4#include <base/types.h>
5
6#include <engine/engine.h>
7#include <engine/http.h>
8#include <engine/kernel.h>
9#include <engine/server/databases/connection.h>
10#include <engine/server/databases/connection_pool.h>
11#include <engine/server/register.h>
12#include <engine/server/server.h>
13#include <engine/server/server_logger.h>
14#include <engine/shared/assertion_logger.h>
15#include <engine/shared/config.h>
16
17#include <generated/protocol.h>
18
19#include <game/server/entities/character.h>
20#include <game/server/gamecontext.h>
21#include <game/server/gamecontroller.h>
22#include <game/server/gameworld.h>
23#include <game/server/player.h>
24#include <game/version.h>
25
26#include <gtest/gtest.h>
27
28#include <memory>
29#include <thread>
30
31bool IsInterrupted()
32{
33 return false;
34}
35
36#if defined(CONF_PLATFORM_ANDROID)
37std::vector<std::string> FetchAndroidServerCommandQueue()
38{
39 return {};
40}
41#endif
42
43class GameWorld : public ::testing::Test // NOLINT(readability-identifier-naming)
44{
45public:
46 IGameServer *m_pGameServer = nullptr;
47 CServer *m_pServer = nullptr;
48 std::unique_ptr<IKernel> m_pKernel;
49 CTestInfo m_TestInfo;
50 std::unique_ptr<IStorage> m_pStorage;
51
52 CGameContext *GameServer() // NOLINT(readability-make-member-function-const)
53 {
54 return (CGameContext *)m_pGameServer;
55 }
56
57 GameWorld()
58 {
59 CServer *pServer = CreateServer();
60 m_pServer = pServer;
61
62 m_pKernel = std::unique_ptr<IKernel>(IKernel::Create());
63 m_pKernel->RegisterInterface(pInterface: m_pServer);
64
65 IEngine *pEngine = CreateTestEngine(GAME_NAME);
66 m_pKernel->RegisterInterface(pInterface: pEngine);
67
68 m_TestInfo.m_DeleteTestStorageFilesOnSuccess = true;
69 m_pStorage = m_TestInfo.CreateTestStorage();
70 EXPECT_NE(m_pStorage, nullptr);
71 m_pKernel->RegisterInterface(pInterface: m_pStorage.get(), Destroy: false);
72
73 IConsole *pConsole = CreateConsole(FlagMask: CFGFLAG_SERVER | CFGFLAG_ECON).release();
74 m_pKernel->RegisterInterface(pInterface: pConsole);
75
76 IConfigManager *pConfigManager = CreateConfigManager();
77 m_pKernel->RegisterInterface(pInterface: pConfigManager);
78
79 IEngineHttp *pEngineHttp = CreateEngineHttp();
80 m_pKernel->RegisterInterface(pInterface: pEngineHttp); // IEngineHttp
81 m_pKernel->RegisterInterface(pInterface: static_cast<IHttp *>(pEngineHttp), Destroy: false);
82
83 IEngineAntibot *pEngineAntibot = CreateEngineAntibot();
84 m_pKernel->RegisterInterface(pInterface: pEngineAntibot);
85 m_pKernel->RegisterInterface(pInterface: static_cast<IAntibot *>(pEngineAntibot), Destroy: false);
86
87 m_pGameServer = CreateGameServer();
88 m_pKernel->RegisterInterface(pInterface: m_pGameServer);
89
90 pEngine->Init();
91 pConsole->Init();
92 pConfigManager->Init();
93
94 m_pServer->RegisterCommands();
95
96 EXPECT_NE(m_pServer->LoadMap("coverage"), 0);
97
98 m_pServer->m_RunServer = CServer::RUNNING;
99
100 m_pServer->m_AuthManager.Init();
101
102 {
103 int Size = GameServer()->PersistentClientDataSize();
104 for(auto &Client : m_pServer->m_aClients)
105 {
106 Client.m_HasPersistentData = false;
107 Client.m_pPersistentData = malloc(size: Size);
108 }
109 }
110 m_pServer->m_pPersistentData = malloc(size: GameServer()->PersistentDataSize());
111 EXPECT_NE(m_pServer->LoadMap("coverage"), 0);
112
113 EXPECT_TRUE(pEngineHttp->Init(std::chrono::seconds{2})) << "Failed to initialize the HTTP client";
114
115 pServer->m_NetServer.SetCallbacks(
116 pfnNewClient: CServer::NewClientCallback,
117 pfnNewClientNoAuth: CServer::NewClientNoAuthCallback,
118 pfnClientRejoin: CServer::ClientRejoinCallback,
119 pfnDelClient: CServer::DelClientCallback, pUser: pServer);
120
121 pServer->m_Econ.Init(pConfig: pServer->Config(), pConsole: pServer->Console(), pNetBan: &pServer->m_ServerBan);
122
123 pServer->m_Fifo.Init(pConsole: pServer->Console(), pFifoFile: pServer->Config()->m_SvInputFifo, Flag: CFGFLAG_SERVER);
124 m_pServer->Antibot()->Init();
125 GameServer()->OnInit(pPersistentData: nullptr);
126 pServer->ReadAnnouncementsFile();
127 pServer->InitMaplist();
128 }
129
130 ~GameWorld() override
131 {
132 m_pServer->m_Econ.Shutdown();
133 m_pServer->m_Fifo.Shutdown();
134 m_pGameServer->OnShutdown(pPersistentData: nullptr);
135 m_pServer->DbPool()->OnShutdown();
136 }
137};
138
139TEST_F(GameWorld, ClosestCharacter)
140{
141 CNetObj_PlayerInput Input = {};
142 CCharacter *pChr1 = new(0) CCharacter(&GameServer()->m_World, Input);
143 pChr1->m_Pos = vec2(0, 0);
144 GameServer()->m_World.InsertEntity(pEntity: pChr1);
145
146 CCharacter *pChr2 = new(1) CCharacter(&GameServer()->m_World, Input);
147 pChr2->m_Pos = vec2(10, 10);
148 GameServer()->m_World.InsertEntity(pEntity: pChr2);
149
150 CCharacter *pClosest = GameServer()->m_World.ClosestCharacter(Pos: vec2(1, 1), Radius: 20, pNotThis: nullptr);
151 EXPECT_EQ(pClosest, pChr1);
152}
153
154TEST_F(GameWorld, IntersectEntity)
155{
156 CNetObj_PlayerInput Input = {};
157 CCharacter *pChrLeft = new(0) CCharacter(&GameServer()->m_World, Input);
158 pChrLeft->m_Pos = vec2(15, 10);
159 GameServer()->m_World.InsertEntity(pEntity: pChrLeft);
160
161 CCharacter *pChrRight = new(1) CCharacter(&GameServer()->m_World, Input);
162 pChrRight->m_Pos = vec2(16, 10);
163 GameServer()->m_World.InsertEntity(pEntity: pChrRight);
164
165 float Radius = 5.0f;
166 vec2 IntersectAt;
167 CCharacter *pIntersectedChar;
168
169 // both tees are exactly on the line
170 // if we go intersect left to right we find the left one
171
172 pIntersectedChar = (CCharacter *)GameServer()->m_World.IntersectEntity(
173 Pos0: vec2(10, 10), // intersect from
174 Pos1: vec2(20, 10), // intersect to
175 Radius,
176 Type: CGameWorld::ENTTYPE_CHARACTER,
177 NewPos&: IntersectAt,
178 pNotThis: nullptr, // pNotThis
179 CollideWith: -1, // CollideWith
180 pThisOnly: nullptr /* pThisOnly */);
181 EXPECT_EQ(pIntersectedChar, pChrLeft);
182
183 // if we intersect right to left we find the right one
184
185 pIntersectedChar = (CCharacter *)GameServer()->m_World.IntersectEntity(
186 Pos0: vec2(20, 10), // intersect from
187 Pos1: vec2(10, 10), // intersect to
188 Radius,
189 Type: CGameWorld::ENTTYPE_CHARACTER,
190 NewPos&: IntersectAt,
191 pNotThis: nullptr, // pNotThis
192 CollideWith: -1, // CollideWith
193 pThisOnly: nullptr /* pThisOnly */);
194 EXPECT_EQ(pIntersectedChar, pChrRight);
195
196 // but not if we ignore the right one
197
198 pIntersectedChar = (CCharacter *)GameServer()->m_World.IntersectEntity(
199 Pos0: vec2(20, 10), // intersect from
200 Pos1: vec2(10, 10), // intersect to
201 Radius,
202 Type: CGameWorld::ENTTYPE_CHARACTER,
203 NewPos&: IntersectAt,
204 pNotThis: pChrRight, // pNotThis
205 CollideWith: -1, // CollideWith
206 pThisOnly: nullptr /* pThisOnly */);
207 EXPECT_EQ(pIntersectedChar, pChrLeft);
208
209 // or we force find the left one
210
211 pIntersectedChar = (CCharacter *)GameServer()->m_World.IntersectEntity(
212 Pos0: vec2(20, 10), // intersect from
213 Pos1: vec2(10, 10), // intersect to
214 Radius,
215 Type: CGameWorld::ENTTYPE_CHARACTER,
216 NewPos&: IntersectAt,
217 pNotThis: nullptr, // pNotThis
218 CollideWith: -1, // CollideWith
219 pThisOnly: pChrLeft /* pThisOnly */);
220 EXPECT_EQ(pIntersectedChar, pChrLeft);
221
222 // pNotThis == pThisOnly => nullptr
223
224 pIntersectedChar = (CCharacter *)GameServer()->m_World.IntersectEntity(
225 Pos0: vec2(20, 10), // intersect from
226 Pos1: vec2(10, 10), // intersect to
227 Radius,
228 Type: CGameWorld::ENTTYPE_CHARACTER,
229 NewPos&: IntersectAt,
230 pNotThis: pChrLeft, // pNotThis
231 CollideWith: -1, // CollideWith
232 pThisOnly: pChrLeft /* pThisOnly */);
233 EXPECT_EQ(pIntersectedChar, nullptr);
234
235 // the tee closer to the start of the intersection line
236 // will not be matched if it is further than Radius away
237 // from the line
238
239 vec2 CloserToFromButTooFarFromLine = vec2(11, 11 + Radius + pChrLeft->GetProximityRadius());
240 pChrLeft->SetPosition(CloserToFromButTooFarFromLine);
241 pChrLeft->m_Pos = CloserToFromButTooFarFromLine;
242
243 pIntersectedChar = (CCharacter *)GameServer()->m_World.IntersectEntity(
244 Pos0: vec2(10, 10), // intersect from
245 Pos1: vec2(20, 10), // intersect to
246 Radius,
247 Type: CGameWorld::ENTTYPE_CHARACTER,
248 NewPos&: IntersectAt,
249 pNotThis: nullptr, // pNotThis
250 CollideWith: -1, // CollideWith
251 pThisOnly: nullptr /* pThisOnly */);
252 EXPECT_EQ(pIntersectedChar, pChrRight);
253}
254
255TEST_F(GameWorld, BasicTick)
256{
257 int ClientId = 0;
258 bool Afk = true;
259 int LastWhisperTo = -1;
260 const int StartTeam = GameServer()->m_pController->GetAutoTeam(NotThisId: ClientId);
261 GameServer()->CreatePlayer(ClientId, StartTeam, Afk, LastWhisperTo);
262
263 GameServer()->OnTick();
264}
265
266TEST_F(GameWorld, CharacterEmote)
267{
268 int ClientId = 0;
269 bool Afk = true;
270 int LastWhisperTo = -1;
271 GameServer()->CreatePlayer(ClientId, StartTeam: TEAM_GAME, Afk, LastWhisperTo);
272 CPlayer *pPlayer = GameServer()->m_apPlayers[ClientId];
273 pPlayer->ForceSpawn(Pos: vec2(0, 0));
274 CCharacter *pChr = pPlayer->GetCharacter();
275 ASSERT_NE(pChr, nullptr);
276
277 // afk
278 pPlayer->SetAfk(true);
279 ASSERT_EQ(pChr->DetermineEyeEmote(), EMOTE_BLINK);
280
281 // not afk
282 pPlayer->SetAfk(false);
283 ASSERT_EQ(pChr->DetermineEyeEmote(), EMOTE_NORMAL);
284
285 // frozen
286 pChr->Freeze(Seconds: 10);
287 ASSERT_EQ(pChr->DetermineEyeEmote(), EMOTE_BLINK);
288
289 // frozen and paused
290 pPlayer->Pause(State: CPlayer::PAUSE_PAUSED, Force: true);
291 ASSERT_EQ(pChr->DetermineEyeEmote(), EMOTE_NORMAL);
292
293 // ninja jetpack
294 pPlayer->Pause(State: CPlayer::PAUSE_NONE, Force: true);
295 pChr->Unfreeze();
296 pPlayer->m_NinjaJetpack = true;
297 pChr->m_NinjaJetpack = true;
298 pChr->SetJetpack(true);
299 pChr->SetActiveWeapon(WEAPON_GUN);
300 ASSERT_EQ(pChr->DetermineEyeEmote(), EMOTE_HAPPY);
301
302 // /emote angry 3 chat command
303 pChr->SetEmote(Emote: EMOTE_ANGRY, Tick: GameServer()->Server()->Tick() + GameServer()->Server()->TickSpeed() * 3);
304 ASSERT_EQ(pChr->DetermineEyeEmote(), EMOTE_ANGRY);
305
306 // /emote angry 3 chat command and frozen
307 pChr->Freeze(Seconds: 10);
308 ASSERT_EQ(pChr->DetermineEyeEmote(), EMOTE_ANGRY);
309}
310