diff --git a/core/src/Raid.cpp b/core/src/Raid.cpp index b3de4a1..9808aaa 100644 --- a/core/src/Raid.cpp +++ b/core/src/Raid.cpp @@ -320,3 +320,147 @@ void RaidManager::moveRaiders(const std::vector& villagers) { } } +void RaidManager::resolveRaiderAttacks(std::vector& villagers, PoiRegistry& pois) { + Vec3 centrePoint = cellCentre(centerStand); + for (Raider& attacker : attackers) { + if (!attacker.alive) continue; + if (attacker.attackCooldown > 0) { + --attacker.attackCooldown; + continue; + } + const Raider::Stats& stats = raiderStats(attacker.kind); + + int victim = -1; + float bestDistance = 0.0f; + for (std::size_t i = 0; i < villagers.size(); ++i) { + if (!villagers[i].alive) continue; + float distance = horizontalDistance(attacker.position, villagers[i].body.position); + if (distance > stats.attackRange) continue; + if (victim < 0 || distance < bestDistance) { + victim = static_cast(i); + bestDistance = distance; + } + } + + if (victim >= 0) { + if (damageVillager(villagers[victim], pois, stats.damage)) ++lostVillagers; + attacker.attackCooldown = stats.attackIntervalTicks; + continue; + } + + if (spawner.golemsAlive > 0 && + horizontalDistance(attacker.position, centrePoint) <= raidGolemEngageRadius) { + for (std::size_t i = 0; i < golemHealth.size(); ++i) { + if (golemHealth[i] <= 0.0f) continue; + golemHealth[i] -= stats.damage; + if (golemHealth[i] <= 0.0f) { + golemHealth[i] = 0.0f; + --spawner.golemsAlive; + } + break; + } + attacker.attackCooldown = stats.attackIntervalTicks; + } + } +} + +void RaidManager::resolveGolems(int livingVillagers) { + if (tickGolemSpawner(spawner, livingVillagers, 1)) golemHealth.push_back(spawner.maxHealth); + + if (spawner.golemsAlive <= 0) return; + if (spawner.attackCooldown > 0) { + --spawner.attackCooldown; + return; + } + spawner.attackCooldown = spawner.attackIntervalTicks; + + Vec3 centrePoint = cellCentre(centerStand); + for (float health : golemHealth) { + if (health <= 0.0f) continue; + int target = -1; + float bestDistance = 0.0f; + for (std::size_t i = 0; i < attackers.size(); ++i) { + if (!attackers[i].alive) continue; + float distance = horizontalDistance(attackers[i].position, centrePoint); + if (target < 0 || distance < bestDistance) { + target = static_cast(i); + bestDistance = distance; + } + } + if (target < 0) return; + damageRaider(target, spawner.damagePerAttack); + } +} + +void RaidManager::finishVictory(std::vector& villagers, OmenState& omen) { + raidState = RaidState::Victory; + for (Villager& villager : villagers) { + if (!villager.alive) continue; + villager.gossip.add(GossipKind::MajorPositive, gossipMajorPositiveGain); + } + grantHeroOfTheVillage(omen, omenLevel, heroDurationTicks); +} + +void RaidManager::stepOnce(const World& world, std::vector& villagers, PoiRegistry& pois, + OmenState& omen, const VillagerSchedule& schedule, + const TimeOfDay& clock, const PathConfig& config) { + ++elapsed; + + for (Villager& villager : villagers) + tickVillager(world, villager, pois, clock, schedule, villageCenter, true, raidTickSeconds, + config); + + if (raidState == RaidState::Spawning) { + spawnWave(world, config); + raidState = RaidState::Fighting; + waveTicks = 0; + clearedTicks = 0; + } + + if (raidState != RaidState::Fighting) return; + + moveRaiders(villagers); + resolveRaiderAttacks(villagers, pois); + + int livingVillagers = 0; + for (const Villager& villager : villagers) + if (villager.alive) ++livingVillagers; + + resolveGolems(livingVillagers); + ++waveTicks; + + if (livingVillagers == 0) { + raidState = RaidState::Defeat; + return; + } + + int living = livingRaiders(); + if (living == 0) { + ++clearedTicks; + if (waveIdx + 1 >= waves) { + finishVictory(villagers, omen); + return; + } + if (clearedTicks >= raidWaveDelayTicks) { + ++waveIdx; + raidState = RaidState::Spawning; + } + return; + } + + if (waveTicks >= raidWaveTimeoutTicks && waveIdx + 1 < waves) { + ++waveIdx; + raidState = RaidState::Spawning; + } +} + +void RaidManager::tick(const World& world, std::vector& villagers, PoiRegistry& pois, + OmenState& omen, const VillagerSchedule& schedule, const TimeOfDay& clock, + int ticks, const PathConfig& config) { + if (!active()) return; + standableNear(world, villageCenter, 8, config, centerStand); + for (int i = 0; i < ticks && active(); ++i) + stepOnce(world, villagers, pois, omen, schedule, clock, config); +} + +} // namespace vf