1 /**
2  * @file
3  * @brief Test cases for code about server game logic
4  */
5 
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23 
24 */
25 
26 #include "test_shared.h"
27 #include "test_game.h"
28 #include "../shared/ufotypes.h"
29 #include "../game/g_local.h"
30 #include "../game/g_actor.h"
31 #include "../game/g_client.h"
32 #include "../game/g_edicts.h"
33 #include "../game/g_inventory.h"
34 #include "../game/g_move.h"
35 #include "../server/server.h"
36 #include "../client/renderer/r_state.h"
37 
38 /**
39  * The suite initialization function.
40  * Returns zero on success, non-zero otherwise.
41  */
UFO_InitSuiteGame(void)42 static int UFO_InitSuiteGame (void)
43 {
44 	TEST_Init();
45 	/* we need the teamdefs for spawning ai actors */
46 	Com_ParseScripts(true);
47 	Cvar_Set("sv_threads", "0");
48 
49 	sv_genericPool = Mem_CreatePool("server-gametest");
50 	r_state.active_texunit = &r_state.texunits[0];
51 	return 0;
52 }
53 
54 /**
55  * The suite cleanup function.
56  * Returns zero on success, non-zero otherwise.
57  */
UFO_CleanSuiteGame(void)58 static int UFO_CleanSuiteGame (void)
59 {
60 	TEST_Shutdown();
61 	return 0;
62 }
63 
testSpawnAndConnect(void)64 static void testSpawnAndConnect (void)
65 {
66 	char userinfo[MAX_INFO_STRING];
67 	player_t* player;
68 	const char* name = "name";
69 	bool day = true;
70 	byte* buf;
71 	/* this entity string may not contain any inline models, we don't have the bsp tree loaded here */
72 	const int size = FS_LoadFile("game/entity.txt", &buf);
73 	edict_t* e = nullptr;
74 	int cnt = 0;
75 
76 	CU_ASSERT_NOT_EQUAL_FATAL(size, -1);
77 	CU_ASSERT_FATAL(size > 0);
78 
79 	SV_InitGameProgs();
80 	/* otherwise we can't link the entities */
81 	SV_ClearWorld();
82 
83 	player = G_PlayerGetNextHuman(0);
84 	svs.ge->SpawnEntities(name, day, (const char*)buf);
85 	CU_ASSERT_TRUE(svs.ge->ClientConnect(player, userinfo, sizeof(userinfo)));
86 	CU_ASSERT_FALSE(svs.ge->RunFrame());
87 
88 	while ((e = G_EdictsGetNextInUse(e))) {
89 		Com_Printf("entity %i: %s\n", cnt, e->classname);
90 		cnt++;
91 	}
92 
93 	CU_ASSERT_EQUAL(cnt, 45);
94 
95 	SV_ShutdownGameProgs();
96 	FS_FreeFile(buf);
97 }
98 
testDoorTrigger(void)99 static void testDoorTrigger (void)
100 {
101 	const char* mapName = "test_game";
102 	if (FS_CheckFile("maps/%s.bsp", mapName) != -1) {
103 		edict_t* e = nullptr;
104 		int cnt = 0;
105 		int doors = 0;
106 
107 		/* the other tests didn't call the server shutdown function to clean up */
108 		OBJZERO(*sv);
109 		SV_Map(true, mapName, nullptr);
110 		while ((e = G_EdictsGetNextInUse(e))) {
111 			cnt++;
112 			if (e->type == ET_DOOR) {
113 				if (Q_streq(e->targetname, "left-0")) {
114 					/* this one is triggered by an actor standing inside of a trigger_touch */
115 					CU_ASSERT_TRUE(e->doorState);
116 				} else if (Q_streq(e->targetname, "right-0")) {
117 					/* this one has a trigger_touch, too - but nobody is touching that trigger yet */
118 					CU_ASSERT_FALSE(e->doorState);
119 				} else {
120 					/* both of the used doors have a targetname set */
121 					CU_ASSERT(false);
122 				}
123 				doors++;
124 			}
125 		}
126 
127 		SV_ShutdownGameProgs();
128 
129 		CU_ASSERT_TRUE(cnt > 0);
130 		CU_ASSERT_TRUE(doors == 2);
131 	} else {
132 		UFO_CU_FAIL_MSG(va("Map resource '%s.bsp' for test is missing.", mapName));
133 	}
134 }
135 
testShooting(void)136 static void testShooting (void)
137 {
138 	const char* mapName = "test_game";
139 	if (FS_CheckFile("maps/%s.bsp", mapName) != -1) {
140 		/* the other tests didn't call the server shutdown function to clean up */
141 		OBJZERO(*sv);
142 		SV_Map(true, mapName, nullptr);
143 		/** @todo equip the soldier */
144 		/** @todo set the input variables -- gi.ReadFormat(format, &pos, &i, &firemode, &from); */
145 		/** @todo do the shot -- G_ClientShoot(player, ent, pos, i, firemode, &mock, true, from); */
146 		/** @todo implement the test here - e.g. extend shot_mock_t */
147 		SV_ShutdownGameProgs();
148 	} else {
149 		UFO_CU_FAIL_MSG(va("Map resource '%s.bsp' for test is missing.", mapName));
150 	}
151 }
152 
GAMETEST_GetItemCount(const edict_t * ent,containerIndex_t container)153 static int GAMETEST_GetItemCount (const edict_t* ent, containerIndex_t container)
154 {
155 	const Item *invlist = ent->getContainer(container);
156 	int count = 0;
157 	while (invlist != nullptr) {
158 		count += invlist->getAmount();
159 		invlist = invlist->getNext();
160 	}
161 
162 	return count;
163 }
164 
testVisFlags(void)165 static void testVisFlags (void)
166 {
167 	const char* mapName = "test_game";
168 	if (FS_CheckFile("maps/%s.bsp", mapName) != -1) {
169 		edict_t* ent;
170 		int num;
171 
172 		/* the other tests didn't call the server shutdown function to clean up */
173 		OBJZERO(*sv);
174 		SV_Map(true, mapName, nullptr);
175 
176 		num = 0;
177 		ent = nullptr;
178 		while ((ent = G_EdictsGetNextLivingActorOfTeam(ent, TEAM_ALIEN))) {
179 			const teammask_t teamMask = G_TeamToVisMask(ent->team);
180 			const bool visible = ent->visflags & teamMask;
181 			char* visFlagsBuf = Mem_StrDup(Com_UnsignedIntToBinary(ent->visflags));
182 			char* teamMaskBuf = Mem_StrDup(Com_UnsignedIntToBinary(teamMask));
183 			CU_ASSERT_EQUAL(ent->team, TEAM_ALIEN);
184 			UFO_CU_ASSERT_TRUE_MSG(visible, va("visflags: %s, teamMask: %s", visFlagsBuf, teamMaskBuf));
185 			Mem_Free(visFlagsBuf);
186 			Mem_Free(teamMaskBuf);
187 			num++;
188 		}
189 
190 		SV_ShutdownGameProgs();
191 		CU_ASSERT_TRUE(num > 0);
192 	} else {
193 		UFO_CU_FAIL_MSG(va("Map resource '%s.bsp' for test is missing.", mapName));
194 	}
195 }
testInventoryForDiedAlien(void)196 static void testInventoryForDiedAlien (void)
197 {
198 	const char* mapName = "test_game";
199 	if (FS_CheckFile("maps/%s.bsp", mapName) != -1) {
200 		edict_t* diedEnt;
201 		edict_t* ent;
202 		edict_t* floorItems;
203 		Item *invlist;
204 		int count;
205 		/* the other tests didn't call the server shutdown function to clean up */
206 		OBJZERO(*sv);
207 		SV_Map(true, mapName, nullptr);
208 		level.activeTeam = TEAM_ALIEN;
209 
210 		/* first alien that should die and drop its inventory */
211 		diedEnt = G_EdictsGetNextLivingActorOfTeam(nullptr, TEAM_ALIEN);
212 		CU_ASSERT_PTR_NOT_NULL_FATAL(diedEnt);
213 		diedEnt->HP = 0;
214 		CU_ASSERT_TRUE(G_ActorDieOrStun(diedEnt, nullptr));
215 		CU_ASSERT_TRUE_FATAL(G_IsDead(diedEnt));
216 
217 		/* now try to collect the inventory with a second alien */
218 		ent = G_EdictsGetNextLivingActorOfTeam(nullptr, TEAM_ALIEN);
219 		CU_ASSERT_PTR_NOT_NULL_FATAL(ent);
220 
221 		/* move to the location of the first died alien to drop the inventory into the same floor container */
222 		Player& player = ent->getPlayer();
223 		CU_ASSERT_TRUE_FATAL(G_IsAIPlayer(&player));
224 		G_ClientMove(player, 0, ent, diedEnt->pos);
225 		CU_ASSERT_TRUE_FATAL(VectorCompare(ent->pos, diedEnt->pos));
226 
227 		floorItems = G_GetFloorItems(ent);
228 		CU_ASSERT_PTR_NOT_NULL_FATAL(floorItems);
229 		CU_ASSERT_PTR_EQUAL(floorItems->getFloor(), ent->getFloor());
230 
231 		/* drop everything to floor to make sure we have space in the backpack */
232 		G_InventoryToFloor(ent);
233 		CU_ASSERT_EQUAL(GAMETEST_GetItemCount(ent, CID_BACKPACK), 0);
234 
235 		invlist = ent->getContainer(CID_BACKPACK);
236 		CU_ASSERT_PTR_NULL_FATAL(invlist);
237 		count = GAMETEST_GetItemCount(ent, CID_FLOOR);
238 		if (count > 0) {
239 			Item *entryToMove = ent->getFloor();
240 			int tx, ty;
241 			ent->chr.inv.findSpace(INVDEF(CID_BACKPACK), entryToMove, &tx, &ty, entryToMove);
242 			if (tx != NONE) {
243 				Com_Printf("trying to move item %s from floor into backpack to pos %i:%i\n", entryToMove->def()->name, tx, ty);
244 				CU_ASSERT_TRUE(G_ActorInvMove(ent, INVDEF(CID_FLOOR), entryToMove, INVDEF(CID_BACKPACK), tx, ty, false));
245 				UFO_CU_ASSERT_EQUAL_INT_MSG_FATAL(GAMETEST_GetItemCount(ent, CID_FLOOR), count - 1, va("item %s could not get moved successfully from floor into backpack", entryToMove->def()->name));
246 				Com_Printf("item %s was removed from floor\n", entryToMove->def()->name);
247 				UFO_CU_ASSERT_EQUAL_INT_MSG_FATAL(GAMETEST_GetItemCount(ent, CID_BACKPACK), 1, va("item %s could not get moved successfully from floor into backpack", entryToMove->def()->name));
248 				Com_Printf("item %s was moved successfully into the backpack\n", entryToMove->def()->name);
249 				invlist = ent->getContainer(CID_BACKPACK);
250 				CU_ASSERT_PTR_NOT_NULL_FATAL(invlist);
251 			}
252 		}
253 
254 		SV_ShutdownGameProgs();
255 	} else {
256 		UFO_CU_FAIL_MSG(va("Map resource '%s.bsp' for test is missing.", mapName));
257 	}
258 }
259 
testInventoryWithTwoDiedAliensOnTheSameGridTile(void)260 static void testInventoryWithTwoDiedAliensOnTheSameGridTile (void)
261 {
262 	const char* mapName = "test_game";
263 	if (FS_CheckFile("maps/%s.bsp", mapName) != -1) {
264 		edict_t* diedEnt;
265 		edict_t* diedEnt2;
266 		edict_t* ent;
267 		edict_t* floorItems;
268 		Item *invlist;
269 		int count;
270 		/* the other tests didn't call the server shutdown function to clean up */
271 		OBJZERO(*sv);
272 		SV_Map(true, mapName, nullptr);
273 		level.activeTeam = TEAM_ALIEN;
274 
275 		/* first alien that should die and drop its inventory */
276 		diedEnt = G_EdictsGetNextLivingActorOfTeam(nullptr, TEAM_ALIEN);
277 		CU_ASSERT_PTR_NOT_NULL_FATAL(diedEnt);
278 		diedEnt->HP = 0;
279 		G_ActorDieOrStun(diedEnt, nullptr);
280 		CU_ASSERT_TRUE_FATAL(G_IsDead(diedEnt));
281 
282 		/* second alien that should die and drop its inventory */
283 		diedEnt2 = G_EdictsGetNextLivingActorOfTeam(nullptr, TEAM_ALIEN);
284 		CU_ASSERT_PTR_NOT_NULL_FATAL(diedEnt2);
285 
286 		/* move to the location of the first died alien to drop the inventory into the same floor container */
287 		Player &player = diedEnt2->getPlayer();
288 		CU_ASSERT_TRUE_FATAL(G_IsAIPlayer(&player));
289 		G_ClientMove(player, 0, diedEnt2, diedEnt->pos);
290 		CU_ASSERT_TRUE_FATAL(VectorCompare(diedEnt2->pos, diedEnt->pos));
291 
292 		diedEnt2->HP = 0;
293 		G_ActorDieOrStun(diedEnt2, nullptr);
294 		CU_ASSERT_TRUE_FATAL(G_IsDead(diedEnt2));
295 
296 		/* now try to collect the inventory with a third alien */
297 		ent = G_EdictsGetNextLivingActorOfTeam(nullptr, TEAM_ALIEN);
298 		CU_ASSERT_PTR_NOT_NULL_FATAL(ent);
299 
300 		player = ent->getPlayer();
301 		CU_ASSERT_TRUE_FATAL(G_IsAIPlayer(&player));
302 
303 		G_ClientMove(player, 0, ent, diedEnt->pos);
304 		CU_ASSERT_TRUE_FATAL(VectorCompare(ent->pos, diedEnt->pos));
305 
306 		floorItems = G_GetFloorItems(ent);
307 		CU_ASSERT_PTR_NOT_NULL_FATAL(floorItems);
308 		CU_ASSERT_PTR_EQUAL(floorItems->getFloor(), ent->getFloor());
309 
310 		/* drop everything to floor to make sure we have space in the backpack */
311 		G_InventoryToFloor(ent);
312 		CU_ASSERT_EQUAL(GAMETEST_GetItemCount(ent, CID_BACKPACK), 0);
313 
314 		invlist = ent->getContainer(CID_BACKPACK);
315 		CU_ASSERT_PTR_NULL_FATAL(invlist);
316 
317 		count = GAMETEST_GetItemCount(ent, CID_FLOOR);
318 		if (count > 0) {
319 			Item *entryToMove = ent->getFloor();
320 			int tx, ty;
321 			ent->chr.inv.findSpace(INVDEF(CID_BACKPACK), entryToMove, &tx, &ty, entryToMove);
322 			if (tx != NONE) {
323 				Com_Printf("trying to move item %s from floor into backpack to pos %i:%i\n", entryToMove->def()->name, tx, ty);
324 				CU_ASSERT_TRUE(G_ActorInvMove(ent, INVDEF(CID_FLOOR), entryToMove, INVDEF(CID_BACKPACK), tx, ty, false));
325 				UFO_CU_ASSERT_EQUAL_INT_MSG_FATAL(GAMETEST_GetItemCount(ent, CID_FLOOR), count - 1, va("item %s could not get moved successfully from floor into backpack", entryToMove->def()->name));
326 				Com_Printf("item %s was removed from floor\n", entryToMove->def()->name);
327 				UFO_CU_ASSERT_EQUAL_INT_MSG_FATAL(GAMETEST_GetItemCount(ent, CID_BACKPACK), 1, va("item %s could not get moved successfully from floor into backpack", entryToMove->def()->name));
328 				Com_Printf("item %s was moved successfully into the backpack\n", entryToMove->def()->name);
329 				invlist = ent->getContainer(CID_BACKPACK);
330 				CU_ASSERT_PTR_NOT_NULL_FATAL(invlist);
331 			}
332 		}
333 
334 		SV_ShutdownGameProgs();
335 	} else {
336 		UFO_CU_FAIL_MSG(va("Map resource '%s.bsp' for test is missing.", mapName));
337 	}
338 }
339 
testInventoryTempContainerLinks(void)340 static void testInventoryTempContainerLinks (void)
341 {
342 	const char* mapName = "test_game";
343 	if (FS_CheckFile("maps/%s.bsp", mapName) != -1) {
344 		edict_t* ent;
345 		int nr;
346 
347 		/* the other tests didn't call the server shutdown function to clean up */
348 		OBJZERO(*sv);
349 		SV_Map(true, mapName, nullptr);
350 		level.activeTeam = TEAM_ALIEN;
351 
352 		/* first alien that should die and drop its inventory */
353 		ent = G_EdictsGetNextLivingActorOfTeam(nullptr, TEAM_ALIEN);
354 		nr = 0;
355 		const Container* cont = nullptr;
356 		while ((cont = ent->chr.inv.getNextCont(cont, true))) {
357 			if (cont->id == CID_ARMOUR || cont->id == CID_FLOOR)
358 				continue;
359 			nr += cont->countItems();
360 		}
361 		CU_ASSERT_TRUE(nr > 0);
362 
363 		CU_ASSERT_PTR_NULL(ent->getFloor());
364 		G_InventoryToFloor(ent);
365 		CU_ASSERT_PTR_NOT_NULL(ent->getFloor());
366 		CU_ASSERT_PTR_EQUAL(G_GetFloorItemFromPos(ent->pos)->getFloor(), ent->getFloor());
367 
368 		nr = 0;
369 		cont = nullptr;
370 		while ((cont = ent->chr.inv.getNextCont(cont, true))) {
371 			if (cont->id == CID_ARMOUR || cont->id == CID_FLOOR)
372 				continue;
373 			nr += cont->countItems();
374 		}
375 		CU_ASSERT_EQUAL(nr, 0);
376 
377 		SV_ShutdownGameProgs();
378 	} else {
379 		UFO_CU_FAIL_MSG(va("Map resource '%s.bsp' for test is missing.", mapName));
380 	}
381 }
382 
UFO_AddGameTests(void)383 int UFO_AddGameTests (void)
384 {
385 	/* add a suite to the registry */
386 	CU_pSuite GameSuite = CU_add_suite("GameTests", UFO_InitSuiteGame, UFO_CleanSuiteGame);
387 
388 	if (GameSuite == nullptr)
389 		return CU_get_error();
390 
391 	/* add the tests to the suite */
392 	if (CU_ADD_TEST(GameSuite, testSpawnAndConnect) == nullptr)
393 		return CU_get_error();
394 
395 	if (CU_ADD_TEST(GameSuite, testDoorTrigger) == nullptr)
396 		return CU_get_error();
397 
398 	if (CU_ADD_TEST(GameSuite, testShooting) == nullptr)
399 		return CU_get_error();
400 
401 	if (CU_ADD_TEST(GameSuite, testVisFlags) == nullptr)
402 		return CU_get_error();
403 
404 	if (CU_ADD_TEST(GameSuite, testInventoryForDiedAlien) == nullptr)
405 		return CU_get_error();
406 
407 	if (CU_ADD_TEST(GameSuite, testInventoryWithTwoDiedAliensOnTheSameGridTile) == nullptr)
408 		return CU_get_error();
409 
410 	if (CU_ADD_TEST(GameSuite, testInventoryTempContainerLinks) == nullptr)
411 		return CU_get_error();
412 
413 	return CUE_SUCCESS;
414 }
415