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