Phase 7: one End boss at a time; more visible attack telegraphs
Adrian (2026-11-02): a second Eternatus appeared on his second visit to the End; the attacks should be more visible. - EndBoss waits until the entities around its spot are loaded before deciding it's missing, refuses a second copy while the recorded one is alive (join check, like the guardians), and removes extra copies near the spot (cleans up worlds that already have two). - Telegraphs: 2.2-size dust redrawn every 2 ticks, the inside filled (Area.fill), rising sparks on the outline, a low bell on cast. - Tests: no second End boss (after the End's middle is loaded), fill points inside their areas. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c451c58483
commit
4e7b16abae
6 files changed
+89
-25
No files matched your search
@@ -71,7 +71,8 @@ object BossTests {
|
||||
s.check(line.contains(Vec3(8.0, 64.0, 0.5)) && !line.contains(Vec3(8.0, 64.0, 2.0)) && !line.contains(Vec3(-2.0, 64.0, 0.0))) { "line" }
|
||||
val cone = Area.Cone(o, Math.PI / 2, 10.0, Math.toRadians(30.0)) // along +z
|
||||
s.check(cone.contains(Vec3(2.0, 64.0, 8.0)) && !cone.contains(Vec3(8.0, 64.0, 2.0)) && !cone.contains(Vec3(0.0, 64.0, 11.0))) { "cone" }
|
||||
s.check(listOf(circle, ring, line, cone).all { it.outline().isNotEmpty() }) { "a shape without telegraph points" }
|
||||
s.check(listOf(circle, ring, line, cone).all { it.outline().isNotEmpty() && it.fill().isNotEmpty() }) { "a shape without telegraph points" }
|
||||
s.check(listOf(circle, ring, line, cone).all { area -> area.fill().all { area.contains(it) } }) { "telegraph points outside their area" }
|
||||
s.helper.succeed()
|
||||
}
|
||||
|
||||
@@ -160,26 +161,35 @@ object BossTests {
|
||||
|
||||
private fun endBoss(s: Scenario) {
|
||||
val end = s.level.server.getLevel(Level.END) ?: throw GameTestAssertException("no End")
|
||||
val chunks = (-2..2).flatMap { x -> (-2..2).map { z -> ChunkPos(x, z) } }
|
||||
val chunks = (-3..3).flatMap { x -> (-3..3).map { z -> ChunkPos(x, z) } }
|
||||
chunks.forEach { end.setChunkForced(it.x, it.z, true) }
|
||||
val player = s.player()
|
||||
s.afterTest {
|
||||
EndBoss.current(end)?.discard()
|
||||
chunks.forEach { end.setChunkForced(it.x, it.z, false) }
|
||||
}
|
||||
val dragon = EnderDragon(EntityType.ENDER_DRAGON, end)
|
||||
dragon.moveTo(0.0, 80.0, 0.0)
|
||||
s.check(!end.addFreshEntity(dragon) || dragon.isRemoved) { "an Ender Dragon could join the End" }
|
||||
val boss = EndBoss.spawn(end) ?: throw GameTestAssertException("the End boss didn't appear")
|
||||
s.check(boss.pokemon.species.name.equals(BossDefs.end!!.end!!.species, ignoreCase = true) && boss.pokemon.isUncatchable()) {
|
||||
"the End boss is a catchable ${boss.pokemon.species.name}"
|
||||
}
|
||||
val xp = player.totalExperience
|
||||
EndBoss.defeated(end, listOf(player))
|
||||
s.check(player.totalExperience > xp) { "no experience for beating the End boss" }
|
||||
val area = BlockPos.betweenClosed(BlockPos(-4, 0, -4), BlockPos(4, 255, 4))
|
||||
s.check(area.any { end.getBlockState(it).`is`(Blocks.END_PORTAL) }) { "no exit portal after the End boss was beaten" }
|
||||
s.check(area.any { end.getBlockState(it).`is`(Blocks.DRAGON_EGG) }) { "no dragon egg after the first win" }
|
||||
s.helper.succeed()
|
||||
s.helper.startSequence()
|
||||
.thenWaitUntil { s.check(chunks.all { end.areEntitiesLoaded(it.toLong()) }) { "the End's middle isn't loaded yet" } }
|
||||
.thenExecute {
|
||||
val dragon = EnderDragon(EntityType.ENDER_DRAGON, end)
|
||||
dragon.moveTo(0.0, 80.0, 0.0)
|
||||
s.check(!end.addFreshEntity(dragon) || dragon.isRemoved) { "an Ender Dragon could join the End" }
|
||||
val boss = EndBoss.spawn(end) ?: throw GameTestAssertException("the End boss didn't appear")
|
||||
s.check(boss.pokemon.species.name.equals(BossDefs.end!!.end!!.species, ignoreCase = true) && boss.pokemon.isUncatchable()) {
|
||||
"the End boss is a catchable ${boss.pokemon.species.name}"
|
||||
}
|
||||
// A second one may not join while the first is alive (Adrian saw one appear on each visit).
|
||||
val second = EndBoss.spawn(end)
|
||||
s.check(second == null || second.isRemoved) { "a second End boss joined beside the first" }
|
||||
s.check(EndBoss.current(end) === boss) { "the End boss on record isn't the first one" }
|
||||
val xp = player.totalExperience
|
||||
EndBoss.defeated(end, listOf(player))
|
||||
s.check(player.totalExperience > xp) { "no experience for beating the End boss" }
|
||||
val area = BlockPos.betweenClosed(BlockPos(-4, 0, -4), BlockPos(4, 255, 4))
|
||||
s.check(area.any { end.getBlockState(it).`is`(Blocks.END_PORTAL) }) { "no exit portal after the End boss was beaten" }
|
||||
s.check(area.any { end.getBlockState(it).`is`(Blocks.DRAGON_EGG) }) { "no dragon egg after the first win" }
|
||||
boss.discard()
|
||||
}
|
||||
.thenSucceed()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user