Phase 8 approved (2026-11-09). Adrian's Phase 9 answers: left-out Pokemon rare in fitting zones; legendaries from crafted summons; no public release. - Ore veins more sporadic but bigger: nodes about 40 % rarer, veins about 30 % further apart, each one block larger in radius. 1.5-3x the ore overall; a new island is needed to see it. World check on the regenerated island passes. - The respawn block (Regenerating Ore Veins' regenerator): the aim passes through it unless sneaking (mixin, the pack's first), so ore behind it can be mined; much fainter glass through KubeJS assets. Self-test regenerator_lets_the_aim_through casts a real ray. - 31 lines (67 species) with no matching island biome, or no spawn data (Rotom), get rare homes in fitting zones (world/zones.json -> homeless). Existing spawns and guardians don't change. JEI data and quests regenerated. - surface_nodes_lie_in_the_ground checks the node's centre column: a wider node's edge can lie under a tree or a slope. - Roadmap: every implemented Pokemon obtainable; Iris and Complementary Reimagined as the default shader. Self-tests and the island check pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
145 lines
7.4 KiB
Kotlin
145 lines
7.4 KiB
Kotlin
package dev.cobblesurvival.selftest
|
|
|
|
import dev.cobblesurvival.dev.OreVeins
|
|
import net.minecraft.core.BlockPos
|
|
import net.minecraft.core.registries.BuiltInRegistries
|
|
import net.minecraft.resources.ResourceLocation
|
|
import net.minecraft.server.level.ServerLevel
|
|
import net.minecraft.server.level.ServerPlayer
|
|
import net.minecraft.world.level.ChunkPos
|
|
import net.minecraft.world.level.ClipContext
|
|
import net.minecraft.world.level.GameType
|
|
import net.minecraft.world.level.block.Blocks
|
|
import net.minecraft.world.phys.Vec3
|
|
|
|
/**
|
|
* Roadmap Phase 4: ore veins regrow after their chunk was unloaded and after a server restart (Regenerating Ore
|
|
* Veins). The tests place the pack's "selftest" vein (one coal block that takes an hour to regrow and never generates
|
|
* by itself) in the air away from the test platform, mine it, and wind its clock back in the mod's saved data
|
|
* instead of waiting the hour. The restart case has two halves: the first runs with the other tests, then
|
|
* tools/quick_check.py restarts the server and runs the second with `/cobblesurvival selftest run after_restart`.
|
|
*/
|
|
object VeinTests {
|
|
private const val VEIN = "selftest"
|
|
/** Blocks east (unload) and west (restart) of spawn: far beyond view distance from the platform. */
|
|
private const val UNLOAD_OFFSET = 320
|
|
private const val RESTART_OFFSET = -320
|
|
private val REGENERATOR = ResourceLocation.fromNamespaceAndPath("regenerating_ore_veins", "regenerator_block")
|
|
|
|
fun all() = listOf(
|
|
SelfTestCase("regenerator_lets_the_aim_through", 20, ::aimThrough),
|
|
SelfTestCase("vein_regrows_after_unload", 1200, ::afterUnload),
|
|
SelfTestCase("vein_mined_before_restart", 400, ::beforeRestart),
|
|
SelfTestCase("vein_regrows_after_restart", 400, ::afterRestart, afterRestart = true),
|
|
)
|
|
|
|
/**
|
|
* A mined vein block waiting to grow back doesn't stop the player's aim, so the ore behind it can be mined (Adrian,
|
|
* 2026-11-09; mixin/RegeneratorBlockMixin.java); while sneaking the player can target it, which opens its info.
|
|
*/
|
|
private fun aimThrough(s: Scenario) {
|
|
val regenerator = BuiltInRegistries.BLOCK.get(ResourceLocation.parse("regenerating_ore_veins:regenerator_block"))
|
|
s.check(regenerator != Blocks.AIR) { "Regenerating Ore Veins' regenerator isn't installed" }
|
|
val player = s.player()
|
|
val pos = s.helper.absolutePos(BlockPos(7, 2, 7))
|
|
val behind = pos.east()
|
|
s.level.setBlockAndUpdate(pos, regenerator.defaultBlockState())
|
|
s.level.setBlockAndUpdate(behind, Blocks.STONE.defaultBlockState())
|
|
fun aimedAt(): BlockPos {
|
|
val from = Vec3(pos.x - 2.5, pos.y + 0.5, pos.z + 0.5)
|
|
return s.level.clip(ClipContext(from, from.add(6.0, 0.0, 0.0), ClipContext.Block.OUTLINE, ClipContext.Fluid.NONE, player)).blockPos
|
|
}
|
|
s.check(aimedAt() == behind) { "the aim stops at ${aimedAt().toShortString()}, not at the block behind the regenerator" }
|
|
player.isShiftKeyDown = true
|
|
s.check(aimedAt() == pos) { "sneaking, the aim doesn't find the regenerator (it stops at ${aimedAt().toShortString()})" }
|
|
player.isShiftKeyDown = false
|
|
s.helper.succeed()
|
|
}
|
|
|
|
private fun afterUnload(s: Scenario) {
|
|
val level = s.level
|
|
val pos = spot(level, UNLOAD_OFFSET)
|
|
val chunk = ChunkPos(pos)
|
|
val player = s.player()
|
|
s.afterTest { level.setChunkForced(chunk.x, chunk.z, false) }
|
|
s.helper.startSequence()
|
|
.thenExecute {
|
|
level.setChunkForced(chunk.x, chunk.z, true)
|
|
placeAndMine(s, player, pos)
|
|
}
|
|
.thenWaitUntil { s.check(isRegenerator(level, pos)) { "the mined vein block is ${level.getBlockState(pos)}, not a regenerator" } }
|
|
.thenExecute { level.setChunkForced(chunk.x, chunk.z, false) }
|
|
.thenWaitUntil { s.check(!level.hasChunk(chunk.x, chunk.z)) { "the vein's chunk is still loaded" } }
|
|
.thenExecute {
|
|
rewind(level, pos)
|
|
level.setChunkForced(chunk.x, chunk.z, true)
|
|
}
|
|
.thenWaitUntil {
|
|
s.check(level.getBlockState(pos).`is`(Blocks.COAL_ORE)) { "the vein didn't regrow once its chunk was back: ${level.getBlockState(pos)}" }
|
|
}
|
|
.thenExecute { remove(player, pos) }
|
|
.thenWaitUntil { s.check(level.getBlockState(pos).isAir) { "the vein block wasn't removed: ${level.getBlockState(pos)}" } }
|
|
.thenSucceed()
|
|
}
|
|
|
|
private fun beforeRestart(s: Scenario) {
|
|
val level = s.level
|
|
val pos = spot(level, RESTART_OFFSET)
|
|
val chunk = ChunkPos(pos)
|
|
val player = s.player()
|
|
s.afterTest { level.setChunkForced(chunk.x, chunk.z, false) }
|
|
s.helper.startSequence()
|
|
.thenExecute {
|
|
level.setChunkForced(chunk.x, chunk.z, true)
|
|
placeAndMine(s, player, pos)
|
|
}
|
|
.thenWaitUntil { s.check(isRegenerator(level, pos)) { "the mined vein block is ${level.getBlockState(pos)}, not a regenerator" } }
|
|
.thenSucceed()
|
|
}
|
|
|
|
private fun afterRestart(s: Scenario) {
|
|
val level = s.level
|
|
val pos = spot(level, RESTART_OFFSET)
|
|
val chunk = ChunkPos(pos)
|
|
val player = s.player()
|
|
s.afterTest { level.setChunkForced(chunk.x, chunk.z, false) }
|
|
s.helper.startSequence()
|
|
.thenExecute { level.setChunkForced(chunk.x, chunk.z, true) }
|
|
.thenWaitUntil {
|
|
s.check(isRegenerator(level, pos)) { "no regenerator where the vein was mined before the restart: ${level.getBlockState(pos)}" }
|
|
}
|
|
.thenExecute { rewind(level, pos) }
|
|
.thenWaitUntil {
|
|
s.check(level.getBlockState(pos).`is`(Blocks.COAL_ORE)) { "the vein didn't regrow after the restart: ${level.getBlockState(pos)}" }
|
|
}
|
|
.thenExecute { remove(player, pos) }
|
|
.thenWaitUntil { s.check(level.getBlockState(pos).isAir) { "the vein block wasn't removed: ${level.getBlockState(pos)}" } }
|
|
.thenSucceed()
|
|
}
|
|
|
|
private fun spot(level: ServerLevel, offset: Int): BlockPos =
|
|
level.sharedSpawnPos.let { BlockPos(it.x + offset, SelfTest.PLATFORM_Y, it.z) }
|
|
|
|
/** Places the vein with the mod's own command and mines its block as a survival player. */
|
|
private fun placeAndMine(s: Scenario, player: ServerPlayer, pos: BlockPos) {
|
|
val level = s.level
|
|
val server = level.server
|
|
val source = server.createCommandSourceStack().withLevel(level).withPosition(Vec3.atCenterOf(pos)).withSuppressedOutput()
|
|
server.commands.performPrefixedCommand(source, "regenerating_ore_veins place $VEIN")
|
|
s.check(level.getBlockState(pos).`is`(Blocks.COAL_ORE)) { "the vein was not placed: ${level.getBlockState(pos)}" }
|
|
s.check(player.gameMode.destroyBlock(pos)) { "the vein block could not be mined" }
|
|
}
|
|
|
|
/** Breaking a regenerator in creative mode removes the vein for good, so the test world keeps no stray ore. */
|
|
private fun remove(player: ServerPlayer, pos: BlockPos) {
|
|
player.setGameMode(GameType.CREATIVE)
|
|
player.gameMode.destroyBlock(pos)
|
|
}
|
|
|
|
private fun isRegenerator(level: ServerLevel, pos: BlockPos) =
|
|
BuiltInRegistries.BLOCK.getKey(level.getBlockState(pos).block) == REGENERATOR
|
|
|
|
/** Moves the vein block's "last mined" time far into the past; the mod keeps it in its saved data. */
|
|
private fun rewind(level: ServerLevel, pos: BlockPos) = OreVeins.markMined(level, pos, 1L)
|
|
}
|