// Scenario 1: Player is idle and gets suspended -- no other players in game static void doScenario1(GamePair &gamePair) { ClientGame *clientGame = gamePair.getClient(0); ServerGame *serverGame = gamePair.server; // Idle for a while -- ship should become spawn delayed. GameConnection::SPAWN_DELAY_TIME is measured in ms. gamePair.idle(10, GameConnection::SPAWN_DELAY_TIME / 10); ASSERT_TRUE(serverGame->getClientInfo(0)->isPlayerInactive()); // No input from player, should be flagged as inactive // Note that spawn delay does not get set until the delayed ship tries to spawn, even if player is marked as inactive // Kill the ship again -- should be delayed when it tries to respawn because client has been inactive fillVector.clear(); serverGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); EXPECT_EQ(1, fillVector.size()); // Should only be one ship Ship *ship = static_cast<Ship *>(fillVector[0]); // Server's copy of the ship killShip(ship); gamePair.idle(10, GameType::RespawnDelay / 10 + 5); // Since server has received no input from client for GameConnection::SPAWN_DELAY_TIME ms, and ship has attempted to respawn, should be spawn-delayed ASSERT_TRUE(serverGame->getClientInfo(0)->isSpawnDelayed()); ASSERT_TRUE(clientGame->isSpawnDelayed()); // Status should have propagated to client by now // At this point, client and server are both aware that the spawn is delayed due to player inactivity gamePair.idle(10, 10); // Idle 10x // If spawn were not delayed, ship would have respawned. Check for it on the server. // (Dead ship may linger on client while exploding, so we won't check there.) fillVector.clear(); serverGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(0, fillVector.size()); // Ship is spawn delayed and won't spawn... hence no ships ASSERT_EQ(0, serverGame->getClientInfo(0)->getReturnToGameTime()); // No returnToGamePenalty in this scenario ASSERT_EQ(0, clientGame->getReturnToGameDelay()); ASSERT_FALSE(static_cast<FullClientInfo *>(serverGame->getClientInfo(0))->hasReturnToGamePenalty()); // No penalty in the works // Undelay spawn clientGame->undelaySpawn(); // This is what gets run when player presses a key gamePair.idle(10, 5); // Idle 5x; give things time to propagate ASSERT_FALSE(serverGame->getClientInfo(0)->isSpawnDelayed()); // Player should no longer be spawn delayed ASSERT_FALSE(clientGame->inReturnToGameCountdown()); fillVector.clear(); serverGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(1, fillVector.size()); // Ship should have spawned and be available on client and server fillVector.clear(); clientGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(1, fillVector.size()); // Ship should have spawned and be available on client and server }
TEST(ObjectScopeTest, TestItemPropagation) { for(S32 i = 0; i < testInfos.size(); i++) { // Create a GamePair using our text item code, with 2 clients; one will be red, the other blue. // The test will confirm which players get the red text item, the blue line, and the zone object. string levelCode = getLevelCodeForItemPropagationTests(testInfos[i].itemToTest); GamePair gamePair(levelCode, 2); // First, ensure we have two players, one red, one blue ServerGame *serverGame = gamePair.server; ASSERT_EQ(2, serverGame->getPlayerCount()); ASSERT_EQ(Colors::blue.toHexString(), serverGame->getTeamColor(0).toHexString()); ASSERT_EQ(Colors::red.toHexString(), serverGame->getTeamColor(1).toHexString()); // Do the following rigamarole to break dependency on assumption client0 is blue and client1 is red ClientGame *client0 = gamePair.getClient(0); ClientGame *client1 = gamePair.getClient(1); ClientGame *blue = (serverGame->getTeamColor(client0->getLocalRemoteClientInfo()->getTeamIndex()).toHexString() == Colors::blue.toHexString()) ? client0 : client1; ClientGame *red = (serverGame->getTeamColor(client0->getLocalRemoteClientInfo()->getTeamIndex()).toHexString() == Colors::red.toHexString()) ? client0 : client1; ASSERT_EQ(Colors::blue.toHexString(), serverGame->getTeamColor(blue->getLocalRemoteClientInfo()->getTeamIndex()).toHexString()); ASSERT_EQ(Colors::red.toHexString(), serverGame->getTeamColor(red->getLocalRemoteClientInfo()->getTeamIndex()).toHexString()); ASSERT_FALSE(serverGame->getClientInfos()->get(0)->getConnection()->mInCommanderMap); ASSERT_FALSE(serverGame->getClientInfos()->get(1)->getConnection()->mInCommanderMap); // Now that we know which client is which, we can check to see which objects are available where { SCOPED_TRACE("Not in CommandersMap // " + testInfos[i].itemToTest); testScope(serverGame, blue, red, testInfos[i], testInfos[i].startingCondition); } // Turn on cmdrs map... should not affect results red->setUsingCommandersMap(true); blue->setUsingCommandersMap(true); gamePair.idle(10, 5); // Let things settle ASSERT_TRUE(serverGame->getClientInfos()->get(0)->getConnection()->mInCommanderMap); ASSERT_TRUE(serverGame->getClientInfos()->get(1)->getConnection()->mInCommanderMap); { SCOPED_TRACE("In CommandersMap // " + testInfos[i].itemToTest); testScope(serverGame, blue, red, testInfos[i], testInfos[i].startingCondition); } Vector<DatabaseObject *> fillVector; // Change the textitem from red to blue serverGame->getLevel()->findObjects(testInfos[i].objectTypeNumber, fillVector); ASSERT_EQ(1, fillVector.size()); BfObject *obj = static_cast<BfObject *>(fillVector[0]); obj->setTeam(0); // Blue team gamePair.idle(10, 5); // Let things settle { SCOPED_TRACE("Item Moved To Blue // " + testInfos[i].itemToTest); testScope(serverGame, blue, red, testInfos[i], testInfos[i].itemMovedToBlue); } obj->setTeam(1); // Back to red gamePair.idle(10, 5); // Let things settle { SCOPED_TRACE("Item Moved To Red // " + testInfos[i].itemToTest); testScope(serverGame, blue, red, testInfos[i], testInfos[i].itemMovedToRed); } // Now change the player's team from blue to red blue->changeOwnTeam(1); EXPECT_EQ(0, blue->getLocalRemoteClientInfo()->getTeamIndex()) << "Expect this client to be on team 0 (change hasn't propagated yet)"; gamePair.idle(10, 5); // Let things settle EXPECT_EQ(1, blue->getLocalRemoteClientInfo()->getTeamIndex()) << "Expect this client to be on team 1 (change should have propagated)"; EXPECT_EQ(1, serverGame->getClientInfos()->get(0)->getTeamIndex()); EXPECT_EQ(1, serverGame->getClientInfos()->get(1)->getTeamIndex()); { SCOPED_TRACE("Blue Player Moved To Red, Items On Red // " + testInfos[i].itemToTest); testScope(serverGame, blue, red, testInfos[i], testInfos[i].playersMovedToRed); } // And put both players on blue blue->changeOwnTeam(0); red->changeOwnTeam(0); EXPECT_EQ(1, blue->getLocalRemoteClientInfo()->getTeamIndex()) << "Expect this client to be on team 1 (change hasn't propagated yet)"; EXPECT_EQ(1, red->getLocalRemoteClientInfo()->getTeamIndex()) << "Expect this client to be on team 1 (change hasn't propagated yet)"; gamePair.idle(10, 5); // Let things settle EXPECT_EQ(0, blue->getLocalRemoteClientInfo()->getTeamIndex()) << "Expect this client to be on team 0 (change should have propagated)"; EXPECT_EQ(0, blue->getLocalRemoteClientInfo()->getTeamIndex()) << "Expect this client to be on team 0 (change should have propagated)"; EXPECT_EQ(0, serverGame->getClientInfos()->get(0)->getTeamIndex()); EXPECT_EQ(0, serverGame->getClientInfos()->get(1)->getTeamIndex()); { SCOPED_TRACE("Both Players Moved To Blue, Items On Red // " + testInfos[i].itemToTest); testScope(serverGame, blue, red, testInfos[i], testInfos[i].playersMovedToBlue); } // Make items neutral and see if they propagate properly fillVector.clear(); serverGame->getLevel()->findObjects(testInfos[i].objectTypeNumber, fillVector); ASSERT_EQ(1, fillVector.size()); for(S32 j = 0; j < fillVector.size(); j++) static_cast<BfObject *>(fillVector[j])->setTeam(TEAM_NEUTRAL); gamePair.idle(10, 5); // Let things settle { SCOPED_TRACE("Items On Neutral // " + testInfos[i].itemToTest); testScope(serverGame, blue, red, testInfos[i], testInfos[i].itemMovedToNeutral); } for(S32 j = 0; j < fillVector.size(); j++) static_cast<BfObject *>(fillVector[j])->setTeam(TEAM_HOSTILE); gamePair.idle(10, 5); // Let things settle { SCOPED_TRACE("Items On Hostile // " + testInfos[i].itemToTest); testScope(serverGame, blue, red, testInfos[i], testInfos[i].itemsMovedToHostile); } } }
// Scenario 2: Player enters idle command, other players, so server does not suspend itself. Since // player used idle command, a 5 second penalty will be levied against them. static void doScenario2(GamePair &gamePair) { ServerGame *serverGame = gamePair.server; string player1Name = "TestPlayer0"; ASSERT_EQ(1, gamePair.getClientCount()); // Should have had a client here when we arrived ASSERT_EQ(player1Name, gamePair.getClient(0)->getPlayerName()); // With this name string player2Name = "TestUser2"; gamePair.addClientAndSetTeam(player2Name.c_str(), 0); ClientGame *client1 = gamePair.getClient(0); ClientGame *client2 = gamePair.getClient(1); // Make sure the names are what we think they are... can't move on unless they are ASSERT_EQ(player1Name, client1->getPlayerName()); ASSERT_EQ(player2Name, client2->getPlayerName()); // Should now be 2 ships in the game -- one belonging to client1 and another belonging to client2 gamePair.idle(10, 5); // Idle 5x; give things time to propagate fillVector.clear(); serverGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(2, fillVector.size()); fillVector.clear(); client1->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(2, fillVector.size()); fillVector.clear(); client2->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(2, fillVector.size()); Vector<string> words; ChatCommands::idleHandler(client1, words); // Make client 1 go /idle gamePair.idle(Ship::KillDeleteDelay / 15, 30); // Give things time to propagate, timers to time out, etc. ASSERT_TRUE(serverGame->getClientInfo(0)->isSpawnDelayed()); ASSERT_TRUE(client1->isSpawnDelayed()); // Status should have propagated to client by now fillVector.clear(); serverGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(1, fillVector.size()); // Ship should have been killed off -- only 2nd player ship should be left fillVector.clear(); client1->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(0, fillVector.size()); // Suspended players don't see remote objects fillVector.clear(); client2->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); //ASSERT_EQ(1, fillVector.size()); // Player 2 should see self ASSERT_TRUE(client2->findClientInfo(client1->getPlayerName())->isSpawnDelayed()); // Check that other player knows our status // ReturnToGame penalty has been set, but won't start to count down until ship attempts to spawn ASSERT_FALSE(client1->inReturnToGameCountdown()); ASSERT_TRUE(static_cast<FullClientInfo *>(serverGame->getClientInfo(0))->hasReturnToGamePenalty()); // Penalty has been primed ASSERT_EQ(0, serverGame->getClientInfo(0)->getReturnToGameTime()); // Player presses a key to rejoin the game; there should be a SPAWN_UNDELAY_TIMER_DELAY ms penalty incurred for using /idle ASSERT_FALSE(serverGame->isSuspended()) << "Game is suspended -- subsequent tests will fail"; client1->undelaySpawn(); // Simulate effects of key press gamePair.idle(10, 10); // Idle; give things time to propagate ASSERT_TRUE(serverGame->getClientInfo(0)->getReturnToGameTime() > 0); // Timers should be set and counting down ASSERT_TRUE(client1->inReturnToGameCountdown()); // Check to ensure ship didn't spawn -- spawn should be delayed until penalty period has expired fillVector.clear(); serverGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(1, fillVector.size()); // (one ship for client2) // Client 1 won't see spawning ship while he is suspended fillVector.clear(); client1->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(0, fillVector.size()); // Client 2 should see that client 1 has been delayed ASSERT_TRUE(client2->findClientInfo(player1Name.c_str())->isSpawnDelayed()); // After some time has passed -- no longer in returnToGameCountdown period, ship should have appeared on server and client gamePair.idle(ClientInfo::SPAWN_UNDELAY_TIMER_DELAY / 100, 105); // More time than SPAWN_UNDELAY_TIMER_DELAY ASSERT_FALSE(client1->inReturnToGameCountdown()); fillVector.clear(); serverGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(2, fillVector.size()); fillVector.clear(); client1->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(2, fillVector.size()); gamePair.removeClient(1); }
// The spawnDelay mechansim is complex and interacts with other weird things like levelUp messages and server suspension TEST(SpawnDelayTest, SpawnDelayTests) { GamePair gamePair; // An empty level should work fine here ClientGame *clientGame = gamePair.getClient(0); ServerGame *serverGame = gamePair.server; ASSERT_TRUE(serverGame->getGameType()) << "Expect a GameType by now!"; // Idle for a while, let things settle gamePair.idle(10, 5); ASSERT_TRUE(clientGame->getGameType()) << "Expect a GameType by now!"; Vector<DatabaseObject *> fillVector; // Ship should have spawned by now... check for it on the client and server fillVector.clear(); serverGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(1, fillVector.size()); // Ship should have spawned by now Ship *ship = static_cast<Ship *>(fillVector[0]); // Server's copy of the ship fillVector.clear(); clientGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(1, fillVector.size()); // And it should be on the client, too ASSERT_FALSE(clientGame->isSpawnDelayed()); // Should not be spawn-delayed at this point killShip(ship); gamePair.idle(10, 5); // 5 cycles // Ship should have spawned by now... check for it on the client and server fillVector.clear(); serverGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(1, fillVector.size()); // Ship should have respawned and be available on the server... fillVector.clear(); clientGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(1, fillVector.size()); // ...and also on the client // Scenario 1: Player is idle and gets spawn delayed -- no other players in game // TODO: Also need a scenario where palyer goes idle and gets spawn delayed, but game does not get suspended due to other players doScenario1(gamePair); // Scenario 2: Player enters idle command, other players, so server does not suspend itself. Since // player used idle command, a 5 second penalty will be levied against them. doScenario2(gamePair); // Scenarios 3 & 4 -- Player enters /idle command, no other players, so server suspends itself partially (3) or fully (4) // See https://code.google.com/p/googletest/wiki/AdvancedGuide#Using_Assertions_in_Sub-routines for details on this technique { SCOPED_TRACE("Scenario 3, letGameSlipIntoFullSuspendMode is false"); doScenario34(gamePair, false); } { SCOPED_TRACE("Scenario 4, letGameSlipIntoFullSuspendMode is true"); doScenario34(gamePair, true); } // Scenario 5 -- Player enters idle when in punishment delay period for pervious /idle command doScenario5(gamePair); // Not complete // Scenario 6 -- Player is shown a new levelup screen doScenario6(gamePair); // Not complete // Scenario 7 -- Player is shown a levelup screen they have already seen doScenario7(gamePair); // Not complete // Scenario 8 -- Player is shown (new) levelup screen while they are idle due to inaction // Scenario 9 -- Player is shown (new) levelup screen while they are idle due to idle command // Scenario 10 -- Player is shown (new) levelup screen while they are in penalty phase of idle command // Scenario 11 -- Player is changing their loadout when the level changes. This triggers a delayed spawn. doScenario11(gamePair); }
// Scenario 3, 4 -- Player enters /idle command, no other players, so server suspends itself // In this case, no returnToGame penalty should be levied // In this scenario 3, player un-idles during the suspend game timer countdown (there is a 2 second delay after all players are idle) // In scenario 4, player enters full suspend mode before unidling static void doScenario34(GamePair &gamePair, bool letGameSlipIntoFullSuspendMode) { ClientGame *clientGame = gamePair.getClient(0); ServerGame *serverGame = gamePair.server; // Make sure we start off in a "normal" state ASSERT_FALSE(serverGame->isOrIsAboutToBeSuspended()); ASSERT_FALSE(clientGame->isSpawnDelayed()); gamePair.idle(Ship::KillDeleteDelay / 15, 20); // Idle; give things time to propagate fillVector.clear(); serverGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(1, fillVector.size()); // Now that player 2 has left, should only be one ship fillVector.clear(); clientGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(1, fillVector.size()); Vector<string> words; ChatCommands::idleHandler(clientGame, words); gamePair.idle(10, 10); // Idle; give things time to propagate, timers to time out, etc. ASSERT_TRUE(serverGame->getClientInfo(0)->isSpawnDelayed()); ASSERT_TRUE(serverGame->isOrIsAboutToBeSuspended()); EXPECT_FALSE(serverGame->isSuspended()); ASSERT_TRUE(clientGame->isSpawnDelayed()); // Status should have propagated to client by now EXPECT_FALSE(clientGame->isSuspended()); fillVector.clear(); serverGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(0, fillVector.size()); // No ships remaining in game -- don't check client as it may have exploding ship there fillVector.clear(); // ReturnToGame penalty has been set, but won't start to count down until ship attempts to spawn ASSERT_FALSE(clientGame->inReturnToGameCountdown()); ASSERT_TRUE(static_cast<FullClientInfo *>(serverGame->getClientInfo(0))->hasReturnToGamePenalty()); // Penalty has been primed ASSERT_EQ(0, serverGame->getClientInfo(0)->getReturnToGameTime()); if(letGameSlipIntoFullSuspendMode) gamePair.idle(ServerGame::PreSuspendSettlingPeriod / 20, 25); // Player presses a key to rejoin the game; since game was suspended, player can resume without penalty ASSERT_TRUE(serverGame->isOrIsAboutToBeSuspended()) << "Game should be suspended"; if(letGameSlipIntoFullSuspendMode) { // In here, game is suspended EXPECT_TRUE(serverGame->isSuspended()); EXPECT_TRUE(clientGame->isSuspended()); // Check if server clocks are still counting down... should be stopped S32 snow = serverGame->getGameType()->getRemainingGameTimeInMs(); S32 cnow = clientGame->getGameType()->getRemainingGameTimeInMs(); gamePair.idle(10, 10); S32 slater = serverGame->getGameType()->getRemainingGameTimeInMs(); S32 clater = clientGame->getGameType()->getRemainingGameTimeInMs(); EXPECT_EQ(snow, slater) << "Looks like server clock is still running (should be stopped)!"; EXPECT_EQ(cnow, clater) << "Looks like client clock is still running (should be stopped)!"; } else { // In here, game is not (yet) suspended, but is in the 2 second cooldown period that comes after // all players have left or are idle, but before full suspension EXPECT_FALSE(serverGame->isSuspended()); EXPECT_FALSE(clientGame->isSuspended()); // Check if clocks are still counting down... should be still running S32 snow = serverGame->getGameType()->getRemainingGameTimeInMs(); S32 cnow = clientGame->getGameType()->getRemainingGameTimeInMs(); gamePair.idle(10, 10); S32 slater = serverGame->getGameType()->getRemainingGameTimeInMs(); S32 clater = clientGame->getGameType()->getRemainingGameTimeInMs(); EXPECT_NE(snow, slater) << "Looks like server clock is stopped (should be running)!"; EXPECT_NE(cnow, clater) << "Looks like client clock is stopped (should be running)!"; } clientGame->undelaySpawn(); // Simulate effects of key press gamePair.idle(10, 5); // Idle; give things time to propagate ASSERT_FALSE(serverGame->isOrIsAboutToBeSuspended()); ASSERT_EQ(0, serverGame->getClientInfo(0)->getReturnToGameTime()); // No returnToGame penalty ASSERT_FALSE(clientGame->inReturnToGameCountdown()); gamePair.idle(Ship::KillDeleteDelay / 15, 20); // Idle; give dead ships time to be cleaned up // Check to ensure ship spawned fillVector.clear(); serverGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(1, fillVector.size()); fillVector.clear(); clientGame->getLevel()->findObjects(PlayerShipTypeNumber, fillVector); ASSERT_EQ(1, fillVector.size()); ASSERT_FALSE(serverGame->isOrIsAboutToBeSuspended()); ASSERT_FALSE(clientGame->isSpawnDelayed()); }