Files
voxelforge/core/include/vf/Raid.h
T

201 lines
6.4 KiB
C++

#pragma once
#include <cstdint>
#include <vector>
#include "Effect.h"
#include "Gossip.h"
#include "Math.h"
#include "Pathfind.h"
#include "TimeOfDay.h"
#include "Villager.h"
#include "World.h"
namespace vf {
enum class RaiderKind : uint8_t {
Raider = 0,
Vindicator,
Witch,
Ravager,
Count,
};
const char* raiderKindName(RaiderKind kind);
// One attacker in a raid wave. Movement reuses the shared A* from Pathfind.h:
// the raider walks its route to the village centre and fights whatever stands
// between it and the bell.
struct Raider {
struct Stats {
float maxHealth = 24.0f;
float damage = 3.0f;
float speed = 2.6f;
float attackRange = 1.8f;
int attackIntervalTicks = 20;
};
int id = 0;
RaiderKind kind = RaiderKind::Raider;
int wave = 0;
Vec3 position;
float health = 24.0f;
std::vector<Vec3i> path;
int pathCursor = 0;
int attackCooldown = 0;
bool alive = true;
Vec3i cell() const;
};
const Raider::Stats& raiderStats(RaiderKind kind);
// The composition of a single wave. Every count grows with the wave index, the
// difficulty and the strength of the omen that triggered the raid.
struct RaidWave {
int index = 0;
int raiders = 0;
int vindicators = 0;
int witches = 0;
int ravagers = 0;
int countOf(RaiderKind kind) const;
int total() const { return raiders + vindicators + witches + ravagers; }
};
constexpr int raidDifficultyEasy = 1;
constexpr int raidDifficultyNormal = 2;
constexpr int raidDifficultyHard = 3;
int raidWaveCount(int difficulty);
RaidWave planRaidWave(int waveIndex, int badOmenLevel, int difficulty);
constexpr int badOmenDurationTicks = 100 * ticksPerSecond;
constexpr int heroDurationTicks = 40 * 60 * ticksPerSecond;
constexpr int badOmenMaxLevel = 5;
// The two player-side effects this module owns: the bad omen that arms a raid
// and the hero of the village discount handed out for winning one.
struct OmenState {
int level = 0;
int ticksRemaining = 0;
int heroLevel = 0;
int heroTicksRemaining = 0;
bool hasBadOmen() const { return level > 0 && ticksRemaining > 0; }
bool isHero() const { return heroLevel > 0 && heroTicksRemaining > 0; }
};
void applyBadOmen(OmenState& omen, int level, int durationTicks = badOmenDurationTicks);
void grantHeroOfTheVillage(OmenState& omen, int level, int durationTicks = heroDurationTicks);
void clearBadOmen(OmenState& omen);
void tickOmen(OmenState& omen, int ticks);
enum class RaidState : uint8_t {
Inactive = 0,
Spawning,
Fighting,
Victory,
Defeat,
Count,
};
const char* raidStateName(RaidState state);
// Iron golem production during a raid. The village fields more golems the more
// villagers it still has, and turns them on the player once the village-wide
// reputation drops far enough.
struct GolemSpawner {
int intervalTicks = 120;
int cooldownTicks = 0;
int golemsAlive = 0;
int spawnedTotal = 0;
int attackIntervalTicks = 20;
int attackCooldown = 0;
float damagePerAttack = 7.0f;
float maxHealth = 100.0f;
bool hostileToPlayer = false;
};
// A hamlet too small to be worth defending fields no golems at all.
constexpr int golemMinimumVillagers = 3;
int golemCapFor(int villagerCount);
// Counts the cooldown down and reports the ticks on which a golem is due.
bool tickGolemSpawner(GolemSpawner& spawner, int villagerCount, int ticks);
void updateGolemHostility(GolemSpawner& spawner, const ReputationView& reputation);
constexpr int raidWaveDelayTicks = 100;
constexpr int raidWaveTimeoutTicks = 2400;
constexpr float raidSpawnRadius = 24.0f;
constexpr float raidGolemEngageRadius = 7.0f;
// Drives a whole raid: wave planning, deterministic raider spawning, the walk
// to the village centre, golem production, villager panic and the gossip the
// outcome leaves behind. Every decision is a function of the seed.
class RaidManager {
public:
explicit RaidManager(const Vec3i& center = {}, uint32_t seed = 1u,
int difficulty = raidDifficultyNormal);
// Arms the raid when the player carries a bad omen into the village. The
// omen is consumed.
bool tryStart(OmenState& omen, const Vec3& playerPos, float villageRadius);
// Advances `ticks` whole game ticks: raiders, golems, villagers and wave
// progression.
void tick(const World& world, std::vector<Villager>& villagers, PoiRegistry& pois,
OmenState& omen, const VillagerSchedule& schedule, const TimeOfDay& clock, int ticks,
const PathConfig& config = {});
RaidState state() const { return raidState; }
bool active() const { return raidState == RaidState::Spawning || raidState == RaidState::Fighting; }
bool villageThreatened() const { return active(); }
int waveIndex() const { return waveIdx; }
int waveCount() const { return waves; }
const RaidWave& wave() const { return currentWave; }
const std::vector<Raider>& raiders() const { return attackers; }
int livingRaiders() const;
int totalSpawned() const { return spawned; }
int villagersLost() const { return lostVillagers; }
int badOmenLevel() const { return omenLevel; }
int elapsedTicks() const { return elapsed; }
const GolemSpawner& golems() const { return spawner; }
const Vec3i& center() const { return villageCenter; }
// Returns true when the hit killed the raider.
bool damageRaider(int index, float amount);
private:
void spawnWave(const World& world, const PathConfig& config);
void moveRaiders(const std::vector<Villager>& villagers);
void resolveRaiderAttacks(std::vector<Villager>& villagers, PoiRegistry& pois);
void resolveGolems(int livingVillagers);
void finishVictory(std::vector<Villager>& villagers, OmenState& omen);
void stepOnce(const World& world, std::vector<Villager>& villagers, PoiRegistry& pois,
OmenState& omen, const VillagerSchedule& schedule, const TimeOfDay& clock,
const PathConfig& config);
Vec3i villageCenter;
Vec3i centerStand;
uint32_t seed = 1u;
int difficulty = raidDifficultyNormal;
RaidState raidState = RaidState::Inactive;
RaidWave currentWave;
std::vector<Raider> attackers;
std::vector<float> golemHealth;
GolemSpawner spawner;
int waves = 0;
int waveIdx = -1;
int waveTicks = 0;
int clearedTicks = 0;
int elapsed = 0;
int spawned = 0;
int lostVillagers = 0;
int omenLevel = 0;
};
} // namespace vf