feat: extend Boss

This commit is contained in:
dualfroz committed 2026-09-06 18:10:29 +02:00
1 parent 1748324d41
commit 663b0cae3a
1 file changed
+160
+160
View File
@@ -154,3 +154,163 @@ DragonFight::DragonFight(const EndGenerator& end, uint32_t seed, const DragonFig
portal = {0.5f, static_cast<float>(surface), 0.5f};
eggCell = {0, surface + 1, 0};
boss.position = portal + Vec3{cfg.orbitRadius, cfg.orbitHeight, 0.0f};
ctx.rng = boss.rng;
buildTree();
}
int DragonFight::aliveCrystalCount() const {
int total = 0;
for (const CrystalNode& crystal : pillarCrystals) {
if (crystal.alive) ++total;
}
return total;
}
void DragonFight::buildTree() {
auto death = seq("deathThroes");
death->add(cond("healthGone", [this](BehaviorContext&) { return boss.health <= 0.0f; }));
death->add(act("collapse", [this](BehaviorContext&) {
if (boss.phase != BossPhase::Dying) {
boss.enterPhase(BossPhase::Dying);
return BehaviorStatus::Running;
}
if (boss.phaseTicks < cfg.deathTicks) return BehaviorStatus::Running;
boss.enterPhase(BossPhase::Defeated);
boss.alive = false;
boss.bar.visible = false;
boss.bar.setFromHealth(0.0f, boss.maxHealth);
portalOpen = true;
eggDropped = true;
return BehaviorStatus::Success;
}));
auto volley = std::unique_ptr<ParallelNode>(new ParallelNode("perchAssault", 1, 2));
volley->add(gated("breathCooldown",
act("breatheAcid",
[this](BehaviorContext& context) {
if (!context.playerPresent) return BehaviorStatus::Failure;
if (distanceBetween(boss.position, context.playerPosition) >
cfg.breathRange) {
return BehaviorStatus::Failure;
}
damageThisTick += cfg.breathDamage;
++boss.attacksLaunched;
return BehaviorStatus::Success;
}),
cfg.breathCooldownTicks));
volley->add(gated("swipeCooldown",
act("tailSwipe",
[this](BehaviorContext& context) {
if (!context.playerPresent) return BehaviorStatus::Failure;
if (distanceBetween(boss.position, context.playerPosition) >
cfg.swipeRange) {
return BehaviorStatus::Failure;
}
damageThisTick += cfg.swipeDamage;
++boss.attacksLaunched;
return BehaviorStatus::Success;
}),
cfg.swipeCooldownTicks));
auto perchAction = sel("perchAction");
perchAction->add(std::move(volley));
perchAction->add(act("holdPerch", [](BehaviorContext&) { return BehaviorStatus::Success; }));
auto perch = seq("perchAtPortal");
perch->add(cond("allCrystalsDown",
[this](BehaviorContext&) { return aliveCrystalCount() == 0; }));
perch->add(act("descendToPortal", [this](BehaviorContext&) {
boss.enterPhase(BossPhase::Perching);
Vec3 landing = portal + Vec3{0.0f, cfg.perchHeight, 0.0f};
Vec3 delta = landing - boss.position;
float span = length(delta);
if (span <= cfg.perchDescentStep) {
boss.position = landing;
return BehaviorStatus::Success;
}
boss.position += delta * (cfg.perchDescentStep / span);
return BehaviorStatus::Running;
}));
perch->add(std::move(perchAction));
auto charge = seq("chargePlayer");
charge->add(cond("playerWorthCharging", [this](BehaviorContext& context) {
return context.playerPresent &&
distanceBetween(boss.position, context.playerPosition) <= cfg.chargeTrigger;
}));
charge->add(gated("chargeCooldown",
bounded("chargeWindow",
act("charge",
[this](BehaviorContext& context) {
if (!context.playerPresent) return BehaviorStatus::Failure;
boss.enterPhase(BossPhase::Charging);
Vec3 delta = context.playerPosition - boss.position;
float span = length(delta);
if (span <= cfg.chargeContactRange) {
damageThisTick += cfg.chargeDamage;
++boss.attacksLaunched;
return BehaviorStatus::Success;
}
boss.position += delta * (cfg.chargeSpeed / span);
return BehaviorStatus::Running;
}),
cfg.chargeWindowTicks),
cfg.chargeCooldownTicks));
auto breath = seq("acidBreath");
breath->add(cond("playerNearPortal", [this](BehaviorContext& context) {
return context.playerPresent &&
distanceBetween(context.playerPosition, portal) <= cfg.breathRange;
}));
breath->add(gated("flightBreathCooldown",
act("breatheFromAbove",
[this](BehaviorContext&) {
boss.enterPhase(BossPhase::Breathing);
damageThisTick += cfg.breathDamage;
++boss.attacksLaunched;
return BehaviorStatus::Success;
}),
cfg.breathCooldownTicks));
auto root = sel("dragonRoot");
root->add(std::move(death));
root->add(std::move(perch));
root->add(std::move(charge));
root->add(std::move(breath));
root->add(act("orbit", [this](BehaviorContext&) {
boss.enterPhase(BossPhase::Circling);
boss.orbitAngle += cfg.orbitStep;
if (boss.orbitAngle >= fullTurn) boss.orbitAngle -= fullTurn;
boss.position = portal + Vec3{std::cos(boss.orbitAngle) * cfg.orbitRadius, cfg.orbitHeight,
std::sin(boss.orbitAngle) * cfg.orbitRadius};
boss.heading = normalize(Vec3{-std::sin(boss.orbitAngle), 0.0f, std::cos(boss.orbitAngle)});
return BehaviorStatus::Success;
}));
tree.setRoot(std::move(root));
}
int DragonFight::resolvePlayerShot(const DragonPlayerInput& input) {
if (input.shootCrystal < 0) return 0;
if (input.shootCrystal >= static_cast<int>(pillarCrystals.size())) return 0;
CrystalNode& crystal = pillarCrystals[static_cast<std::size_t>(input.shootCrystal)];
if (!crystal.alive) return 0;
if (crystal.cageIntact) {
if (input.breakCage) crystal.cageIntact = false;
return 0;
}
crystal.alive = false;
crystal.health = 0.0f;
crystal.destroyedTick = boss.tick;
if (distanceBetween(crystalCenter(crystal.position), boss.position) <= cfg.crystalBlastRange) {
float dealt = std::min(cfg.crystalBlastDamage, boss.health);
boss.health -= dealt;
boss.damageTaken += dealt;
}
return 1;
}