From 0bb172d35bcad054584068f6e53dbb76d5b29deb Mon Sep 17 00:00:00 2001 From: DualFroz Date: Tue, 8 Sep 2026 20:26:40 +0200 Subject: [PATCH] feat: add nextRandom and cellCentre --- core/src/Raid.cpp | 160 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 core/src/Raid.cpp diff --git a/core/src/Raid.cpp b/core/src/Raid.cpp new file mode 100644 index 0000000..0c4a819 --- /dev/null +++ b/core/src/Raid.cpp @@ -0,0 +1,160 @@ +#include "vf/Raid.h" + +#include + +namespace vf { + +namespace { + +constexpr float raidTickSeconds = 1.0f / static_cast(ticksPerSecond); + +uint32_t nextRandom(uint32_t& state) { + state += 0x9e3779b9u; + uint32_t z = state; + z = (z ^ (z >> 16)) * 0x85ebca6bu; + z = (z ^ (z >> 13)) * 0xc2b2ae35u; + return z ^ (z >> 16); +} + +Vec3 cellCentre(const Vec3i& cell) { + return {static_cast(cell.x) + 0.5f, static_cast(cell.y), + static_cast(cell.z) + 0.5f}; +} + +float horizontalDistance(const Vec3& a, const Vec3& b) { + float dx = a.x - b.x; + float dz = a.z - b.z; + return std::sqrt(dx * dx + dz * dz); +} + +const Raider::Stats statsTable[static_cast(RaiderKind::Count)] = { + // maxHealth, damage, speed, attackRange, attackIntervalTicks + {24.0f, 3.0f, 2.6f, 1.8f, 20}, + {24.0f, 6.0f, 2.9f, 1.8f, 18}, + {26.0f, 4.0f, 2.4f, 6.0f, 30}, + {100.0f, 12.0f, 3.1f, 2.4f, 30}, +}; + +// Walks a body along a cell path, returning true once the final cell is under +// its feet. +bool advanceAlong(Vec3& position, const std::vector& path, int& cursor, float speed, + float dt) { + if (path.empty()) return false; + int last = static_cast(path.size()) - 1; + float budget = speed * dt; + while (budget > 0.0f) { + Vec3 target = cellCentre(path[cursor]); + Vec3 delta = target - position; + float remaining = length(delta); + if (remaining <= budget) { + position = target; + budget -= remaining; + if (cursor >= last) return true; + ++cursor; + } else { + position += normalize(delta) * budget; + return false; + } + } + return false; +} + +// Nearest cell at or around `y` that a walker can stand on, searched outward so +// the result does not depend on world height. +bool standableNear(const World& world, const Vec3i& column, int verticalSearch, + const PathConfig& config, Vec3i& outCell) { + for (int offset = 0; offset <= verticalSearch; ++offset) { + Vec3i up{column.x, column.y + offset, column.z}; + if (isStandable(world, up, config)) { + outCell = up; + return true; + } + if (offset == 0) continue; + Vec3i down{column.x, column.y - offset, column.z}; + if (isStandable(world, down, config)) { + outCell = down; + return true; + } + } + outCell = column; + return false; +} + +RaiderKind kindForSlot(const RaidWave& plan, int slot) { + if (slot < plan.raiders) return RaiderKind::Raider; + slot -= plan.raiders; + if (slot < plan.vindicators) return RaiderKind::Vindicator; + slot -= plan.vindicators; + if (slot < plan.witches) return RaiderKind::Witch; + return RaiderKind::Ravager; +} + +} // namespace + +const char* raiderKindName(RaiderKind kind) { + switch (kind) { + case RaiderKind::Raider: return "raider"; + case RaiderKind::Vindicator: return "vindicator"; + case RaiderKind::Witch: return "witch"; + case RaiderKind::Ravager: return "ravager"; + case RaiderKind::Count: break; + } + return "unknown"; +} + +const Raider::Stats& raiderStats(RaiderKind kind) { return statsTable[static_cast(kind)]; } + +Vec3i Raider::cell() const { + return {static_cast(std::floor(position.x)), static_cast(std::floor(position.y)), + static_cast(std::floor(position.z))}; +} + +int RaidWave::countOf(RaiderKind kind) const { + switch (kind) { + case RaiderKind::Raider: return raiders; + case RaiderKind::Vindicator: return vindicators; + case RaiderKind::Witch: return witches; + case RaiderKind::Ravager: return ravagers; + case RaiderKind::Count: break; + } + return 0; +} + +int raidWaveCount(int difficulty) { + if (difficulty <= raidDifficultyEasy) return 3; + if (difficulty == raidDifficultyNormal) return 5; + return 7; +} + +RaidWave planRaidWave(int waveIndex, int badOmenLevel, int difficulty) { + RaidWave plan; + plan.index = waveIndex < 0 ? 0 : waveIndex; + int omen = badOmenLevel < 1 ? 1 : badOmenLevel; + + plan.raiders = 2 + plan.index + (difficulty >= raidDifficultyHard ? 1 : 0); + if (plan.index >= 1) + plan.vindicators = plan.index / 2 + (difficulty >= raidDifficultyNormal ? 1 : 0); + if (plan.index >= 2) plan.witches = 1 + (difficulty >= raidDifficultyHard ? 1 : 0); + if (plan.index >= 3) plan.ravagers = 1; + if (omen >= 3) plan.vindicators += 1; + if (omen >= 4 && plan.index >= 2) plan.ravagers += 1; + return plan; +} + +void applyBadOmen(OmenState& omen, int level, int durationTicks) { + int capped = level > badOmenMaxLevel ? badOmenMaxLevel : level; + if (capped < 1) return; + if (capped >= omen.level) { + omen.level = capped; + omen.ticksRemaining = durationTicks; + } +} + +void grantHeroOfTheVillage(OmenState& omen, int level, int durationTicks) { + int granted = level < 1 ? 1 : level; + if (granted >= omen.heroLevel) { + omen.heroLevel = granted; + omen.heroTicksRemaining = durationTicks; + } +} +