feat: add witherBaseBlock and witherHeadBlock to Boss

This commit is contained in:
dualfroz committed 2026-09-06 18:46:19 +02:00
1 parent 663b0cae3a
commit 78b986bdb1
1 file changed
+158
+158
View File
@@ -314,3 +314,161 @@ int DragonFight::resolvePlayerShot(const DragonPlayerInput& input) {
return 1; return 1;
} }
void DragonFight::applyPlayerMelee(const DragonPlayerInput& input) {
if (!input.present || input.meleeDamage <= 0.0f || !boss.alive) return;
if (distanceBetween(boss.position, input.position) > cfg.meleeReach) return;
float factor = boss.phase == BossPhase::Perching ? 1.0f : cfg.flyingDamageFactor;
float dealt = std::min(input.meleeDamage * factor, boss.health);
boss.health -= dealt;
boss.damageTaken += dealt;
}
float DragonFight::applyCrystalHealing() {
if (boss.health <= 0.0f) return 0.0f;
float healed = 0.0f;
for (const CrystalNode& crystal : pillarCrystals) {
if (!crystal.alive) continue;
if (distanceBetween(crystalCenter(crystal.position), boss.position) > cfg.crystalHealRange)
continue;
float room = boss.maxHealth - boss.health - healed;
if (room <= 0.0f) break;
healed += std::min(cfg.crystalHealPerTick, room);
}
boss.health += healed;
boss.healingReceived += healed;
return healed;
}
BossTickReport DragonFight::tick(const DragonPlayerInput& input) {
BossTickReport report;
report.previousPhase = boss.phase;
report.phase = boss.phase;
report.bossHealth = boss.health;
if (boss.phase == BossPhase::Defeated) {
report.defeated = true;
return report;
}
lastInput = input;
damageThisTick = 0.0f;
int crystalsKilled = resolvePlayerShot(input);
applyPlayerMelee(input);
float healed = applyCrystalHealing();
bool lastCrystalDown = crystalsKilled > 0 && aliveCrystalCount() == 0;
bool lethalBlow = boss.health <= 0.0f && boss.phase != BossPhase::Dying;
if (lastCrystalDown || lethalBlow) tree.reset();
ctx.playerPresent = input.present;
ctx.playerPosition = input.position;
++boss.phaseTicks;
int changesBefore = boss.phaseChanges;
tree.tick(ctx);
boss.tick = ctx.tick;
if (boss.phase != BossPhase::Defeated) boss.bar.setFromHealth(boss.health, boss.maxHealth);
report.phase = boss.phase;
report.phaseChanged = boss.phaseChanges != changesBefore;
report.bossHealth = boss.health;
report.healedThisTick = healed;
report.damageToTargets = damageThisTick;
report.crystalsDestroyed = crystalsKilled;
report.defeated = boss.phase == BossPhase::Defeated;
return report;
}
bool DragonFight::placeVictoryStructures(World& world, const EndGenerator& end) const {
if (!portalOpen) return false;
end.placeExitPortal(world);
return true;
}
Block witherBaseBlock() { return Block::SoulSand; }
Block witherHeadBlock() { return Block::Pumpkin; }
bool witherPatternAt(const World& world, const Vec3i& baseCell, bool alongX) {
if (world.blockAt(baseCell.x, baseCell.y, baseCell.z) != witherBaseBlock()) return false;
for (int along = -1; along <= 1; ++along) {
Vec3i arm = witherOffset(baseCell, along, 1, alongX);
if (world.blockAt(arm.x, arm.y, arm.z) != witherBaseBlock()) return false;
Vec3i head = witherOffset(baseCell, along, 2, alongX);
if (world.blockAt(head.x, head.y, head.z) != witherHeadBlock()) return false;
}
return true;
}
bool findWitherPattern(const World& world, const Vec3i& baseCell, bool& outAlongX) {
if (witherPatternAt(world, baseCell, true)) {
outAlongX = true;
return true;
}
if (witherPatternAt(world, baseCell, false)) {
outAlongX = false;
return true;
}
return false;
}
void consumeWitherPattern(World& world, const Vec3i& baseCell, bool alongX) {
world.setBlock(baseCell.x, baseCell.y, baseCell.z, Block::Air);
for (int along = -1; along <= 1; ++along) {
Vec3i arm = witherOffset(baseCell, along, 1, alongX);
world.setBlock(arm.x, arm.y, arm.z, Block::Air);
Vec3i head = witherOffset(baseCell, along, 2, alongX);
world.setBlock(head.x, head.y, head.z, Block::Air);
}
}
WitherFight::WitherFight(uint32_t seed, const WitherFightConfig& config) : cfg(config) {
boss.kind = BossKind::Wither;
boss.maxHealth = cfg.maxHealth;
boss.health = cfg.maxHealth;
boss.rng = seed != 0u ? seed : 0x2f6b3ca1u;
boss.phase = BossPhase::Dormant;
boss.bar.title = "Wither";
boss.bar.color = BossBarColor::Purple;
boss.bar.style = BossBarStyle::Notched6;
boss.bar.visible = false;
ctx.rng = boss.rng;
buildTree();
}
int WitherFight::aliveMinionCount() const {
int total = 0;
for (const BossMinion& minion : summoned) {
if (minion.alive) ++total;
}
return total;
}
int WitherFight::nearestAliveTarget() const {
if (activeTargets == nullptr) return -1;
int best = -1;
float bestSpan = 0.0f;
for (std::size_t i = 0; i < activeTargets->size(); ++i) {
const WitherTarget& candidate = (*activeTargets)[i];
if (!candidate.alive) continue;
float span = distanceBetween(boss.position, candidate.position);
if (best >= 0 && span >= bestSpan) continue;
best = static_cast<int>(i);
bestSpan = span;
}
return best;
}
void WitherFight::buildTree() {
auto defeat = seq("defeat");
defeat->add(cond("healthGone", [this](BehaviorContext&) { return boss.health <= 0.0f; }));
defeat->add(act("collapse", [this](BehaviorContext&) {
boss.enterPhase(BossPhase::Defeated);
boss.alive = false;
boss.bar.visible = false;
boss.bar.setFromHealth(0.0f, boss.maxHealth);
return BehaviorStatus::Success;
}));