feat: add clearBadOmen and tickOmen to Raid
This commit is contained in:
1 parent
0bb172d35b
commit
42e57d1323
1 file changed
+162
@@ -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<int>(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<uint32_t>(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<int>(nextRandom(rng) % 8u);
|
||||||
|
|
||||||
|
float angle = 6.2831853f * static_cast<float>(slot) / static_cast<float>(count) +
|
||||||
|
static_cast<float>(waveIdx) * 0.37f;
|
||||||
|
Vec3i column{villageCenter.x + static_cast<int>(std::lround(std::cos(angle) * raidSpawnRadius)),
|
||||||
|
villageCenter.y,
|
||||||
|
villageCenter.z + static_cast<int>(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<Villager>& villagers) {
|
||||||
|
for (Raider& attacker : attackers) {
|
||||||
|
if (!attacker.alive) continue;
|
||||||
|
const Raider::Stats& stats = raiderStats(attacker.kind);
|
||||||
|
if (!attacker.path.empty() &&
|
||||||
|
attacker.pathCursor < static_cast<int>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user