feat: extend Raid
This commit is contained in:
1 parent
42e57d1323
commit
71a936867b
1 file changed
+144
@@ -320,3 +320,147 @@ void RaidManager::moveRaiders(const std::vector<Villager>& villagers) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RaidManager::resolveRaiderAttacks(std::vector<Villager>& 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<int>(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<int>(i);
|
||||||
|
bestDistance = distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (target < 0) return;
|
||||||
|
damageRaider(target, spawner.damagePerAttack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RaidManager::finishVictory(std::vector<Villager>& 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<Villager>& 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<Villager>& 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
|
||||||
Reference in new issue
Block a user