From 42e57d132355cf429e0673d3ff41d510e650a868 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 8 Sep 2026 21:04:49 +0200 Subject: [PATCH] feat: add clearBadOmen and tickOmen to Raid --- core/src/Raid.cpp | 162 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/core/src/Raid.cpp b/core/src/Raid.cpp index 0c4a819..b3de4a1 100644 --- a/core/src/Raid.cpp +++ b/core/src/Raid.cpp @@ -158,3 +158,165 @@ void grantHeroOfTheVillage(OmenState& omen, int level, int durationTicks) { } } +void clearBadOmen(OmenState& omen) { + omen.level = 0; + omen.ticksRemaining = 0; +} + +void tickOmen(OmenState& omen, int ticks) { + if (ticks <= 0) return; + if (omen.ticksRemaining > 0) { + omen.ticksRemaining -= ticks; + if (omen.ticksRemaining <= 0) { + omen.ticksRemaining = 0; + omen.level = 0; + } + } + if (omen.heroTicksRemaining > 0) { + omen.heroTicksRemaining -= ticks; + if (omen.heroTicksRemaining <= 0) { + omen.heroTicksRemaining = 0; + omen.heroLevel = 0; + } + } +} + +const char* raidStateName(RaidState state) { + switch (state) { + case RaidState::Inactive: return "inactive"; + case RaidState::Spawning: return "spawning"; + case RaidState::Fighting: return "fighting"; + case RaidState::Victory: return "victory"; + case RaidState::Defeat: return "defeat"; + case RaidState::Count: break; + } + return "unknown"; +} + +int golemCapFor(int villagerCount) { + if (villagerCount < golemMinimumVillagers) return 0; + int cap = villagerCount / 4; + return cap < 1 ? 1 : cap; +} + +bool tickGolemSpawner(GolemSpawner& spawner, int villagerCount, int ticks) { + if (ticks <= 0) return false; + if (spawner.golemsAlive >= golemCapFor(villagerCount)) { + spawner.cooldownTicks = spawner.intervalTicks; + return false; + } + spawner.cooldownTicks -= ticks; + if (spawner.cooldownTicks > 0) return false; + spawner.cooldownTicks = spawner.intervalTicks; + ++spawner.golemsAlive; + ++spawner.spawnedTotal; + return true; +} + +void updateGolemHostility(GolemSpawner& spawner, const ReputationView& reputation) { + spawner.hostileToPlayer = reputation.golemHostile(); +} + +RaidManager::RaidManager(const Vec3i& center, uint32_t seed, int difficulty) + : villageCenter(center), centerStand(center), seed(seed), difficulty(difficulty) {} + +bool RaidManager::tryStart(OmenState& omen, const Vec3& playerPos, float villageRadius) { + if (raidState != RaidState::Inactive) return false; + if (!omen.hasBadOmen()) return false; + if (horizontalDistance(playerPos, cellCentre(villageCenter)) > villageRadius) return false; + + omenLevel = omen.level; + waves = raidWaveCount(difficulty); + waveIdx = 0; + waveTicks = 0; + clearedTicks = 0; + elapsed = 0; + spawned = 0; + lostVillagers = 0; + attackers.clear(); + golemHealth.clear(); + spawner = GolemSpawner{}; + raidState = RaidState::Spawning; + clearBadOmen(omen); + return true; +} + +int RaidManager::livingRaiders() const { + int count = 0; + for (const Raider& attacker : attackers) + if (attacker.alive) ++count; + return count; +} + +bool RaidManager::damageRaider(int index, float amount) { + if (index < 0 || index >= static_cast(attackers.size())) return false; + Raider& attacker = attackers[index]; + if (!attacker.alive) return false; + attacker.health -= amount; + if (attacker.health > 0.0f) return false; + attacker.health = 0.0f; + attacker.alive = false; + return true; +} + +void RaidManager::spawnWave(const World& world, const PathConfig& config) { + currentWave = planRaidWave(waveIdx, omenLevel, difficulty); + int count = currentWave.total(); + uint32_t rng = seed ^ (static_cast(waveIdx + 1) * 0x85ebca6bu); + + for (int slot = 0; slot < count; ++slot) { + Raider attacker; + attacker.id = spawned++; + attacker.kind = kindForSlot(currentWave, slot); + attacker.wave = waveIdx; + const Raider::Stats& stats = raiderStats(attacker.kind); + attacker.health = stats.maxHealth; + attacker.attackCooldown = static_cast(nextRandom(rng) % 8u); + + float angle = 6.2831853f * static_cast(slot) / static_cast(count) + + static_cast(waveIdx) * 0.37f; + Vec3i column{villageCenter.x + static_cast(std::lround(std::cos(angle) * raidSpawnRadius)), + villageCenter.y, + villageCenter.z + static_cast(std::lround(std::sin(angle) * raidSpawnRadius))}; + Vec3i spawnCell; + standableNear(world, column, 8, config, spawnCell); + attacker.position = cellCentre(spawnCell); + attacker.path = findPath(world, spawnCell, centerStand, config); + attacker.pathCursor = 0; + attackers.push_back(std::move(attacker)); + } +} + +// Raiders follow their route to the bell, then hunt down whoever is left. +void RaidManager::moveRaiders(const std::vector& villagers) { + for (Raider& attacker : attackers) { + if (!attacker.alive) continue; + const Raider::Stats& stats = raiderStats(attacker.kind); + if (!attacker.path.empty() && + attacker.pathCursor < static_cast(attacker.path.size()) - 1) { + advanceAlong(attacker.position, attacker.path, attacker.pathCursor, stats.speed, + raidTickSeconds); + continue; + } + + Vec3 target = cellCentre(centerStand); + float bestDistance = 0.0f; + bool found = false; + for (const Villager& villager : villagers) { + if (!villager.alive) continue; + float distance = horizontalDistance(attacker.position, villager.body.position); + if (!found || distance < bestDistance) { + target = villager.body.position; + bestDistance = distance; + found = true; + } + } + + Vec3 delta = target - attacker.position; + float remaining = length(delta); + float budget = stats.speed * raidTickSeconds; + if (remaining <= budget) attacker.position = target; + else attacker.position += normalize(delta) * budget; + } +} +