diff --git a/core/src/Boss.cpp b/core/src/Boss.cpp new file mode 100644 index 0000000..84bc50e --- /dev/null +++ b/core/src/Boss.cpp @@ -0,0 +1,156 @@ +#include "vf/Boss.h" + +#include +#include +#include + +namespace vf { + +namespace { + +constexpr float fullTurn = 6.2831853f; + +float distanceBetween(const Vec3& a, const Vec3& b) { return length(a - b); } + +std::unique_ptr cond(const char* name, BehaviorPredicate predicate) { + return std::unique_ptr(new ConditionNode(name, std::move(predicate))); +} + +std::unique_ptr act(const char* name, BehaviorAction fn) { + return std::unique_ptr(new ActionNode(name, std::move(fn))); +} + +std::unique_ptr seq(const char* name) { + return std::unique_ptr(new SequenceNode(name)); +} + +std::unique_ptr sel(const char* name) { + return std::unique_ptr(new SelectorNode(name)); +} + +std::unique_ptr gated(const char* name, std::unique_ptr child, + int ticks) { + return std::unique_ptr(new CooldownNode(name, std::move(child), ticks)); +} + +std::unique_ptr bounded(const char* name, std::unique_ptr child, + int ticks) { + return std::unique_ptr(new TimeLimitNode(name, std::move(child), ticks)); +} + +Vec3 crystalCenter(const Vec3i& cell) { + return {static_cast(cell.x) + 0.5f, static_cast(cell.y) + 0.5f, + static_cast(cell.z) + 0.5f}; +} + +Vec3i witherOffset(const Vec3i& base, int along, int up, bool alongX) { + return alongX ? Vec3i{base.x + along, base.y + up, base.z} + : Vec3i{base.x, base.y + up, base.z + along}; +} + +} // namespace + +const char* bossKindName(BossKind kind) { + switch (kind) { + case BossKind::EnderDragon: return "enderDragon"; + case BossKind::Wither: return "wither"; + case BossKind::Count: break; + } + return "unknown"; +} + +const char* bossPhaseName(BossPhase phase) { + switch (phase) { + case BossPhase::Dormant: return "dormant"; + case BossPhase::Circling: return "circling"; + case BossPhase::Charging: return "charging"; + case BossPhase::Breathing: return "breathing"; + case BossPhase::Perching: return "perching"; + case BossPhase::Summoning: return "summoning"; + case BossPhase::Explosive: return "explosive"; + case BossPhase::Ranged: return "ranged"; + case BossPhase::Armored: return "armored"; + case BossPhase::Dying: return "dying"; + case BossPhase::Defeated: return "defeated"; + case BossPhase::Count: break; + } + return "unknown"; +} + +int BossBar::notchCount() const { + switch (style) { + case BossBarStyle::Solid: return 1; + case BossBarStyle::Notched6: return 6; + case BossBarStyle::Notched10: return 10; + case BossBarStyle::Notched12: return 12; + case BossBarStyle::Notched20: return 20; + case BossBarStyle::Count: break; + } + return 1; +} + +int BossBar::filledNotches() const { + float exact = progress * static_cast(notchCount()); + int whole = static_cast(std::floor(exact + 1e-4f)); + return std::max(0, std::min(notchCount(), whole)); +} + +int BossBar::percent() const { + return static_cast(std::floor(progress * 100.0f + 0.5f)); +} + +void BossBar::setFromHealth(float health, float maxHealth) { + if (maxHealth <= 0.0f) { + progress = 0.0f; + return; + } + float fraction = health / maxHealth; + progress = std::max(0.0f, std::min(1.0f, fraction)); +} + +void BossState::enterPhase(BossPhase next) { + if (phase == next) return; + phase = next; + phaseTicks = 0; + ++phaseChanges; +} + +float BossState::healthFraction() const { + if (maxHealth <= 0.0f) return 0.0f; + return health / maxHealth; +} + +std::vector crystalsFromPillars(const std::vector& pillars) { + std::vector nodes; + nodes.reserve(pillars.size()); + for (const ObsidianPillar& pillar : pillars) { + CrystalNode node; + node.pillarBase = pillar.base; + node.pillarHeight = pillar.height; + node.position = {pillar.base.x, pillar.base.y + pillar.height + 1, pillar.base.z}; + node.caged = pillar.crowned; + node.cageIntact = pillar.crowned; + nodes.push_back(node); + } + return nodes; +} + +DragonFight::DragonFight(const EndGenerator& end, uint32_t seed, const DragonFightConfig& config) + : cfg(config) { + boss.kind = BossKind::EnderDragon; + boss.maxHealth = cfg.maxHealth; + boss.health = cfg.maxHealth; + boss.rng = seed != 0u ? seed : 0x51ed270bu; + boss.phase = BossPhase::Circling; + boss.bar.title = "Ender Dragon"; + boss.bar.color = BossBarColor::Pink; + boss.bar.style = BossBarStyle::Notched10; + boss.bar.visible = true; + boss.bar.setFromHealth(boss.health, boss.maxHealth); + + pillarCrystals = crystalsFromPillars(end.pillars()); + + int surface = end.surfaceHeight(0, 0); + portal = {0.5f, static_cast(surface), 0.5f}; + eggCell = {0, surface + 1, 0}; +