Phase 4: regenerating ore veins, each tier in one zone

Regenerating Ore Veins 1.4.0 (CurseForge only, MIT). tools/zone_ores.py
writes its config from world/zones.json: surface zones as boxes from the
zone grid, cave tiers by cave biome, 21 tiers including Cobblemon's
evolution stones (their natural ores are off). The world report now
allows ore only in tracked veins inside their tier's zone. Self-tests:
a mined vein regrows after its chunk unloads and after a restart (new
after_restart pass in selftest.py and quick_check.py). make_world places
showcase veins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
claudeandClaude Opus 5.5 committed 2026-10-30 02:30:34 +01:00
1 parent 82f540aa51
commit 5d275e4e14
19 files changed
+10843 -37

No files matched your search

@@ -23,7 +23,8 @@ import net.neoforged.neoforge.event.tick.ServerTickEvent
* One in-game test: [body] sets it up through its [Scenario] and must end in a pass or a failure within [maxTicks].
* [islandOnly] tests need the island world and are skipped elsewhere (tools/selftest.py uses a flat world).
* Tests of one [batch] run side by side; batches run one after another (a test that changes the world around it,
* like the biome or the time of day, gets a batch of its own).
* like the biome or the time of day, gets a batch of its own). [afterRestart] tests are the second half of a case
* that spans a server restart: they only run with `/cobblesurvival selftest run after_restart`.
*/
class SelfTestCase(
val name: String,
@@ -31,24 +32,26 @@ class SelfTestCase(
val body: (Scenario) -> Unit,
val islandOnly: Boolean = false,
val batch: String = CobbleSurvival.MOD_ID,
val afterRestart: Boolean = false,
)
/**
* In-game tests on a real server with all of the pack's mods and configs: `/cobblesurvival selftest run` starts them
* on a platform high above spawn, `/cobblesurvival selftest status` reports. They use the vanilla GameTest
* framework, which NeoForge only switches on in a development environment, so this object creates and ticks the
* tests itself. Only there when the server runs with `-Dcobblesurvival.selftest=true` (tools/quick_check.py);
* tests itself. `/cobblesurvival selftest run after_restart` runs the tests that check what survived a restart. Only there when the server runs with `-Dcobblesurvival.selftest=true` (tools/quick_check.py);
* players never see the command.
*/
object SelfTest {
val enabled: Boolean = java.lang.Boolean.getBoolean("cobblesurvival.selftest")
private const val TEMPLATE = "cobblesurvival:selftest/empty"
private const val PLATFORM_Y = 200
const val PLATFORM_Y = 200
private const val TESTS_PER_ROW = 4
private const val FORCED_CHUNKS = 9
private val cases: List<SelfTestCase> get() =
PermadeathTests.all() + SurvivalRulesTests.all() + ZoneTests.all() + HazardTests.all() + GuardianTests.all()
PermadeathTests.all() + SurvivalRulesTests.all() + ZoneTests.all() + HazardTests.all() + GuardianTests.all() +
VeinTests.all()
private var running: List<Scenario> = emptyList()
private var forced: List<ChunkPos> = emptyList()
@@ -61,11 +64,15 @@ object SelfTest {
fun command(): LiteralArgumentBuilder<CommandSourceStack> =
Commands.literal("selftest")
.requires { it.hasPermission(4) }
.then(Commands.literal("run").executes { context ->
val reply = start(context.source.server)
context.source.sendSuccess({ Component.literal(reply) }, false)
1
})
.then(Commands.literal("run")
.executes { context ->
context.source.sendSuccess({ Component.literal(start(context.source.server, afterRestart = false)) }, false)
1
}
.then(Commands.literal("after_restart").executes { context ->
context.source.sendSuccess({ Component.literal(start(context.source.server, afterRestart = true)) }, false)
1
}))
.then(Commands.literal("status").executes { context ->
val done = running.count { it.result != null }
val reply = if (running.isNotEmpty()) "selftest running: $done/${running.size} done" else summary
@@ -73,7 +80,7 @@ object SelfTest {
1
})
private fun start(server: MinecraftServer): String {
private fun start(server: MinecraftServer, afterRestart: Boolean): String {
if (running.isNotEmpty()) return "selftest already running"
val level = server.overworld()
val spawn = level.sharedSpawnPos
@@ -84,7 +91,7 @@ object SelfTest {
// Only the island world type puts basalt deltas into the overworld.
val island = level.chunkSource.generator.biomeSource.possibleBiomes().any { it.`is`(Biomes.BASALT_DELTAS) }
val (runnable, skipped) = cases.partition { island || !it.islandOnly }
val (runnable, skipped) = cases.filter { it.afterRestart == afterRestart }.partition { island || !it.islandOnly }
val scenarios = runnable.mapIndexed { index, case -> Scenario(case, index) }
val infos = scenarios.map { scenario ->
val function = TestFunction(scenario.case.batch, "${CobbleSurvival.MOD_ID}.${scenario.case.name}", TEMPLATE,
@@ -0,0 +1,122 @@
package dev.cobblesurvival.selftest
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.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("vein_regrows_after_unload", 1200, ::afterUnload),
SelfTestCase("vein_mined_before_restart", 400, ::beforeRestart),
SelfTestCase("vein_regrows_after_restart", 400, ::afterRestart, afterRestart = true),
)
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) {
val data = Class.forName("net.stargazer.regenerating_ore_veins.VeinSavedData")
val saved = data.getMethod("get", ServerLevel::class.java).invoke(null, level)
data.getMethod("markMined", BlockPos::class.java, Long::class.javaPrimitiveType).invoke(saved, pos, 1L)
}
}