#pragma once #include #include #include #include "Behavior.h" #include "Block.h" #include "EndGen.h" #include "Math.h" #include "World.h" namespace vf { // Two staged boss fights driven by the behaviour trees from Behavior.h. Both // are pure tick machines: feed the same seed and the same sequence of player // actions and the fight replays move for move. enum class BossKind : uint8_t { EnderDragon = 0, Wither = 1, Count, }; const char* bossKindName(BossKind kind); enum class BossPhase : uint8_t { Dormant = 0, Circling, Charging, Breathing, Perching, Summoning, Explosive, Ranged, Armored, Dying, Defeated, Count, }; const char* bossPhaseName(BossPhase phase); enum class BossBarColor : uint8_t { Pink = 0, Blue, Red, Green, Yellow, Purple, White, Count }; enum class BossBarStyle : uint8_t { Solid = 0, Notched6, Notched10, Notched12, Notched20, Count }; struct BossBar { std::string title; float progress = 1.0f; BossBarColor color = BossBarColor::Purple; BossBarStyle style = BossBarStyle::Notched10; bool visible = false; int notchCount() const; // Whole segments currently lit; a partially drained segment reads as empty. int filledNotches() const; int percent() const; void setFromHealth(float health, float maxHealth); }; struct BossState { BossKind kind = BossKind::EnderDragon; BossPhase phase = BossPhase::Dormant; float health = 200.0f; float maxHealth = 200.0f; Vec3 position; Vec3 heading{1.0f, 0.0f, 0.0f}; float orbitAngle = 0.0f; int targetIndex = -1; long long tick = 0; int phaseTicks = 0; int phaseChanges = 0; int attacksLaunched = 0; int minionsSummoned = 0; float healingReceived = 0.0f; float damageTaken = 0.0f; bool alive = true; uint32_t rng = 0x51ed270bu; BossBar bar; void enterPhase(BossPhase next); float healthFraction() const; }; // One end crystal on top of an obsidian pillar from EndGen.h. The taller // pillars carry a cage that has to come down before the crystal can be shot. struct CrystalNode { Vec3i pillarBase; Vec3i position; int pillarHeight = 0; bool caged = false; bool cageIntact = false; bool alive = true; float health = 5.0f; long long destroyedTick = -1; }; std::vector crystalsFromPillars(const std::vector& pillars); struct BossTickReport { BossPhase phase = BossPhase::Dormant; BossPhase previousPhase = BossPhase::Dormant; bool phaseChanged = false; float bossHealth = 0.0f; float healedThisTick = 0.0f; float damageToTargets = 0.0f; int crystalsDestroyed = 0; int minionsSpawned = 0; bool defeated = false; }; struct DragonFightConfig { float maxHealth = 200.0f; float orbitRadius = 44.0f; float orbitHeight = 26.0f; float orbitStep = 0.06f; float chargeTrigger = 48.0f; float chargeSpeed = 1.4f; float chargeContactRange = 3.0f; float chargeDamage = 10.0f; int chargeCooldownTicks = 60; int chargeWindowTicks = 40; float breathRange = 14.0f; float breathDamage = 3.0f; int breathCooldownTicks = 40; int swipeCooldownTicks = 25; float swipeDamage = 6.0f; float swipeRange = 8.0f; float crystalHealPerTick = 0.4f; float crystalHealRange = 128.0f; float crystalBlastRange = 12.0f; float crystalBlastDamage = 6.0f; float perchHeight = 3.0f; float perchDescentStep = 0.9f; float meleeReach = 7.0f; float flyingDamageFactor = 0.25f; int deathTicks = 40; }; // What the player did on one tick. `shootCrystal` indexes crystals(); a caged // crystal needs `breakCage` on an earlier tick before a shot lands. struct DragonPlayerInput { Vec3 position; bool present = true; int shootCrystal = -1; bool breakCage = false; float meleeDamage = 0.0f; }; // Crystals heal the dragon while they stand, so the fight cannot be won by // damage alone. Killing the last one drops it onto the portal, where melee // lands at full strength; the kill opens the exit portal and leaves the egg. class DragonFight { public: DragonFight(const EndGenerator& end, uint32_t seed, const DragonFightConfig& config = {}); DragonFight(const DragonFight&) = delete; DragonFight& operator=(const DragonFight&) = delete; DragonFight(DragonFight&&) = delete; DragonFight& operator=(DragonFight&&) = delete;