HeroRecruitmentTest.cpp 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. /*
  2. * HeroRecruitmentTest.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "GameStateTest.h"
  12. #include "../../lib/gameState/TavernHeroesPool.h"
  13. #include "../../lib/networkPacks/PacksForClient.h"
  14. #include "../../lib/mapObjects/CGHeroInstance.h"
  15. #include "../../lib/mapObjects/CGTownInstance.h"
  16. #include "../../lib/CPlayerState.h"
  17. /**
  18. * Test fixture for hero recruitment netpack tests.
  19. * Inherits game setup and state management from GameStateTest.
  20. */
  21. class HeroRecruitmentTest : public GameStateTest
  22. {
  23. };
  24. // Test that hero recruitment properly assigns an ID to the recruited hero
  25. TEST_F(HeroRecruitmentTest, recruitedHeroGetsId)
  26. {
  27. startTestGame();
  28. // Get the first player and find a town
  29. PlayerColor playerColor = PlayerColor(0);
  30. auto * playerState = gameState->getPlayerState(playerColor);
  31. ASSERT_NE(playerState, nullptr);
  32. // Get initial state
  33. auto initialHeroes = playerState->getHeroes();
  34. size_t initialHeroCount = initialHeroes.size();
  35. // Find a town for recruitment
  36. auto towns = playerState->getTowns();
  37. ASSERT_FALSE(towns.empty()) << "Player must have at least one town for hero recruitment";
  38. auto * town = towns.front();
  39. ASSERT_NE(town, nullptr);
  40. // Get a hero type from the pool
  41. auto poolHeroes = gameState->heroesPool->getHeroesFor(playerColor);
  42. ASSERT_FALSE(poolHeroes.empty()) << "Hero pool must have heroes available";
  43. const auto * poolHero = poolHeroes.front();
  44. ASSERT_NE(poolHero, nullptr);
  45. HeroTypeID heroType = poolHero->getHeroTypeID();
  46. // Create and apply HeroRecruited pack
  47. HeroRecruited pack;
  48. pack.tid = town->id;
  49. pack.hid = heroType;
  50. pack.player = playerColor;
  51. pack.tile = town->visitablePos();
  52. gameEventCallback->sendAndApply(pack);
  53. // Verify player now has one more hero
  54. auto finalHeroes = playerState->getHeroes();
  55. EXPECT_EQ(finalHeroes.size(), initialHeroCount + 1)
  56. << "Player should have exactly one more hero after recruitment";
  57. // Find the newly recruited hero
  58. CGHeroInstance * recruitedHero = nullptr;
  59. for (auto * hero : finalHeroes)
  60. {
  61. if (std::find(initialHeroes.begin(), initialHeroes.end(), hero) == initialHeroes.end())
  62. {
  63. recruitedHero = hero;
  64. break;
  65. }
  66. }
  67. ASSERT_NE(recruitedHero, nullptr) << "Recruited hero must exist in player's hero list";
  68. // This is the key assertion that validates our fix:
  69. // The hero must have an ID assigned after being added to the map
  70. EXPECT_TRUE(recruitedHero->id.hasValue())
  71. << "Recruited hero must have an ID assigned (validates fix for assertion crash)";
  72. // Verify other properties
  73. EXPECT_EQ(recruitedHero->getOwner(), playerColor)
  74. << "Recruited hero must be owned by the recruiting player";
  75. EXPECT_EQ(recruitedHero->visitablePos(), town->visitablePos())
  76. << "Recruited hero must be at town's position";
  77. EXPECT_EQ(recruitedHero->getHeroTypeID(), heroType)
  78. << "Recruited hero must have the correct hero type";
  79. // Verify the hero is on the map
  80. auto * heroOnMap = dynamic_cast<CGHeroInstance *>(map->getObject(recruitedHero->id));
  81. EXPECT_EQ(heroOnMap, recruitedHero)
  82. << "Recruited hero must be retrievable from map by ID";
  83. }
  84. // Test hero recruitment without a town (should still assign ID)
  85. TEST_F(HeroRecruitmentTest, recruitedHeroWithoutTownGetsId)
  86. {
  87. startTestGame();
  88. PlayerColor playerColor = PlayerColor(0);
  89. auto * playerState = gameState->getPlayerState(playerColor);
  90. ASSERT_NE(playerState, nullptr);
  91. auto poolHeroes = gameState->heroesPool->getHeroesFor(playerColor);
  92. ASSERT_FALSE(poolHeroes.empty());
  93. const auto * poolHero = poolHeroes.front();
  94. HeroTypeID heroType = poolHero->getHeroTypeID();
  95. // Recruit hero without a town (tavern can be in random locations)
  96. HeroRecruited pack;
  97. pack.tid = ObjectInstanceID(); // No town
  98. pack.hid = heroType;
  99. pack.player = playerColor;
  100. pack.tile = int3(5, 5, 0); // Random valid position
  101. gameEventCallback->sendAndApply(pack);
  102. // Find the recruited hero
  103. auto finalHeroes = playerState->getHeroes();
  104. ASSERT_FALSE(finalHeroes.empty());
  105. auto * recruitedHero = finalHeroes.back();
  106. ASSERT_NE(recruitedHero, nullptr);
  107. // Key assertion: ID must be assigned even without a town
  108. EXPECT_TRUE(recruitedHero->id.hasValue())
  109. << "Hero recruited without town must still have ID assigned";
  110. EXPECT_EQ(recruitedHero->getOwner(), playerColor);
  111. EXPECT_EQ(recruitedHero->getHeroTypeID(), heroType);
  112. }