feat: add witherBaseBlock and witherHeadBlock to Boss
This commit is contained in:
1 parent
663b0cae3a
commit
78b986bdb1
1 file changed
+158
@@ -314,3 +314,161 @@ int DragonFight::resolvePlayerShot(const DragonPlayerInput& input) {
|
||||
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;
|
||||
}));
|
||||
|
||||
Reference in new issue
Block a user