diff --git a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp index e66772cd0..20bad6535 100644 --- a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp +++ b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.cpp @@ -352,31 +352,20 @@ void BossSpiderQueenEnemyServer::RunRainOfFire(Entity* self) { ToggleForSpecial(self, true); SetRainOfFireStun(self, true); - impactList.clear(); - - auto index = 0u; + std::vector> targetGroups; for (const auto& rofGroup : ROFTargetGroupIDTable) { - const auto spawners = Game::zoneManager->GetSpawnersInGroup(rofGroup); - - std::vector spawned; - - for (auto* spawner : spawners) { + auto& spawned = targetGroups.emplace_back(); + for (auto* spawner : Game::zoneManager->GetSpawnersInGroup(rofGroup)) { for (const auto* node : spawner->m_Info.nodes) { spawned.insert(spawned.end(), node->entities.begin(), node->entities.end()); } } - - if (index == 0) { - impactList.insert(impactList.end(), spawned.begin(), spawned.end()); - } else if (!spawned.empty()) { - const auto randomIndex = GeneralUtils::GenerateRandomNumber(0, spawned.size() - 1); - - impactList.push_back(spawned[randomIndex]); - } - - index++; } + impactList = PickRainOfFireImpacts(targetGroups, [](const size_t count) { + return GeneralUtils::GenerateRandomNumber(0, count - 1); + }); + const auto animTime = PlayAnimAndReturnTime(self, spiderROFAnim); self->AddTimer("StartROF", animTime); @@ -504,6 +493,25 @@ std::vector BossSpiderQueenEnemyServer::BuildRapidFireTargets(const st return attackTargets; } +std::vector BossSpiderQueenEnemyServer::PickRainOfFireImpacts(const std::vector>& groups, + const std::function& randomIndex) { + std::vector impacts; + for (size_t i = 0; i < groups.size(); i++) { + if (i == 0) { + impacts.insert(impacts.end(), groups[i].begin(), groups[i].end()); + continue; + } + + auto remaining = groups[i]; + for (size_t picked = 0; picked < ROFImpactCnt && !remaining.empty(); picked++) { + const auto index = randomIndex(remaining.size()); + impacts.push_back(remaining[index]); + remaining.erase(remaining.begin() + static_cast(index)); + } + } + return impacts; +} + void BossSpiderQueenEnemyServer::OnZoneVolumeEntered(Entity* self, Entity* player, const std::string& volumeGroup) { if (!player || !player->IsPlayer()) return; diff --git a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.h b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.h index a5495314f..5575b4753 100644 --- a/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.h +++ b/dScripts/02_server/Enemy/AG/BossSpiderQueenEnemyServer.h @@ -65,6 +65,11 @@ public: static std::vector BuildRapidFireTargets(const std::string& primaryZone, bool clockwise, const std::function(const std::string& group)>& groupTargets); + // The rain of fire's impacts: every target of the first group and ROFImpactCnt different random targets of each + // other group. randomIndex(n) returns a number in [0, n). + static std::vector PickRainOfFireImpacts(const std::vector>& groups, + const std::function& randomIndex); + // A player touched one of the arena's zone volumes (group Zone1Vol..Zone8Vol, AggroVol or TeleVol) void OnZoneVolumeEntered(Entity* self, Entity* player, const std::string& volumeGroup); @@ -99,7 +104,7 @@ private: const std::vector spiderWaveCntTable = { 2, 3 }; //The number of Dark Spiderling enemies to spawn per indexed wave number - const int ROFImpactCnt = 2; //The number of ROF impacts in each quadrant of the arena selected at random + static constexpr size_t ROFImpactCnt = 2; //The number of ROF impacts in each quadrant of the arena selected at random const LOT SpiderlingID = 16197; //Reference obj ID for hatched Spiderlings diff --git a/tests/dGameTests/SpiderQueenTests.cpp b/tests/dGameTests/SpiderQueenTests.cpp index 2b98f9325..d049be259 100644 --- a/tests/dGameTests/SpiderQueenTests.cpp +++ b/tests/dGameTests/SpiderQueenTests.cpp @@ -149,6 +149,13 @@ TEST_F(SpiderQueenTest, RapidFireSweepsOverTheEdgeBetweenZonesEightAndOne) { EXPECT_EQ(BossSpiderQueenEnemyServer::BuildRapidFireTargets("Zone2Vol", true, ArenaGroup), Ids({ 1, 2, 3, 4, 5, 6, 7 })); } +TEST_F(SpiderQueenTest, RainOfFireTakesTheFirstGroupAndTwoOfEachOther) { + const std::vector> groups = { { 1, 2, 3 }, { 10, 11, 12, 13 }, { 20 }, {}, { 40, 41 } }; + // Always the last remaining target + const auto impacts = BossSpiderQueenEnemyServer::PickRainOfFireImpacts(groups, [](const size_t count) { return count - 1; }); + EXPECT_EQ(impacts, (std::vector{ 1, 2, 3, 13, 12, 20, 41, 40 })); +} + TEST_F(SpiderQueenTest, ZoneVolumesTellTheBossWhereThePlayerIs) { for (const auto& group : { "Zone1Vol", "Zone2Vol", "Zone3Vol", "Zone4Vol", "Zone5Vol", "Zone6Vol", "Zone7Vol", "Zone8Vol", "AggroVol", "TeleVol" }) { Add(0x2000 + static_cast(entities.size()), 14400, { group });