Phase 4: veins in every chunk, drops for the mobs the island lacks
Ore veins: make_world.py loads the island again row by row until Regenerating Ore Veins has filled every chunk (/cobblesurvival veins unfilled), cave tiers sit at their biome's depths, small zones get denser veins, showcase veins are full size. The world report checks surface tiers against the zone grid, cave tiers within two biome cells of their cave, and fails on unfilled chunks; the template passes (37,689 chunks). Drops: tools/drop_audit.py checks every creature drop a recipe or brewing needs (docs/pokemon-drops.md). Blaze rods, ghast tears, breeze rods, shulker shells and nether stars now drop from Pokemon through species additions (self-test drop_additions_apply). The field guide counts vanilla mobs as absent, applies species additions and reads the veins as sources. Cobblebase tried (loads, self-tests pass) and left out until Adrian decides its scope. Phase 4 checklist with the open questions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5d275e4e14
commit
1a1fb93da8
33 files changed
+1279
-126
No files matched your search
@@ -4,6 +4,7 @@ import com.cobblemon.mod.common.Cobblemon
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.mojang.logging.LogUtils
|
||||
import dev.cobblesurvival.dev.BiomePreview
|
||||
import dev.cobblesurvival.dev.OreVeins
|
||||
import dev.cobblesurvival.permadeath.Permadeath
|
||||
import dev.cobblesurvival.selftest.SelfTest
|
||||
import dev.cobblesurvival.world.OnlyPokemon
|
||||
@@ -92,6 +93,7 @@ class CobbleSurvival {
|
||||
1
|
||||
})
|
||||
.then(BiomePreview.command())
|
||||
.then(OreVeins.command())
|
||||
.then(Commands.literal("zone").executes { context ->
|
||||
val source = context.source
|
||||
val zone = Zones.at(source.level, BlockPos.containing(source.position))
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package dev.cobblesurvival.dev
|
||||
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder
|
||||
import net.minecraft.commands.CommandSourceStack
|
||||
import net.minecraft.commands.Commands
|
||||
import net.minecraft.commands.arguments.coordinates.ColumnPosArgument
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.server.level.ColumnPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.world.level.ChunkPos
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Bridge to Regenerating Ore Veins' saved data, by reflection (the mod is CurseForge-only and not a compile
|
||||
* dependency of the companion mod).
|
||||
*
|
||||
* Dev tool: `/cobblesurvival veins unfilled <x1> <z1> <x2> <z2>` counts the loaded chunks in a block rectangle that
|
||||
* the mod hasn't put veins into yet. It fills a new chunk only while that chunk is loaded, 8 chunks a tick, and
|
||||
* forgets the rest when the server stops, so a pregeneration run outpaces it; tools/make_world.py loads the island
|
||||
* again row by row and waits until this count is 0 around each row (loading a row also fully loads the chunks next
|
||||
* to it). The self-tests use [markMined] to wind a vein block's clock back.
|
||||
*/
|
||||
object OreVeins {
|
||||
private val savedData by lazy { Class.forName("net.stargazer.regenerating_ore_veins.VeinSavedData") }
|
||||
private val get by lazy { savedData.getMethod("get", ServerLevel::class.java) }
|
||||
private val isChunkGenerated by lazy { savedData.getMethod("isChunkGenerated", Long::class.javaPrimitiveType) }
|
||||
private val markMined by lazy { savedData.getMethod("markMined", BlockPos::class.java, Long::class.javaPrimitiveType) }
|
||||
|
||||
fun isChunkFilled(level: ServerLevel, chunk: ChunkPos): Boolean =
|
||||
isChunkGenerated.invoke(get.invoke(null, level), chunk.toLong()) as Boolean
|
||||
|
||||
/** Sets when the vein block at [pos] was last mined, in seconds since the epoch (the mod counts real time). */
|
||||
fun markMined(level: ServerLevel, pos: BlockPos, epochSecond: Long) {
|
||||
markMined.invoke(get.invoke(null, level), pos, epochSecond)
|
||||
}
|
||||
|
||||
fun command(): LiteralArgumentBuilder<CommandSourceStack> =
|
||||
Commands.literal("veins")
|
||||
.requires { it.hasPermission(4) }
|
||||
.then(Commands.literal("unfilled")
|
||||
.then(Commands.argument("from", ColumnPosArgument.columnPos())
|
||||
.then(Commands.argument("to", ColumnPosArgument.columnPos())
|
||||
.executes { context ->
|
||||
val level = context.source.server.overworld()
|
||||
val unfilled = chunks(ColumnPosArgument.getColumnPos(context, "from"),
|
||||
ColumnPosArgument.getColumnPos(context, "to"))
|
||||
.count { level.chunkSource.hasChunk(it.x, it.z) && !isChunkFilled(level, it) }
|
||||
context.source.sendSuccess({ Component.literal("unfilled chunks: $unfilled") }, false)
|
||||
1
|
||||
})))
|
||||
|
||||
private fun chunks(from: ColumnPos, to: ColumnPos): List<ChunkPos> {
|
||||
val x0 = min(from.x(), to.x()) shr 4
|
||||
val x1 = max(from.x(), to.x()) shr 4
|
||||
val z0 = min(from.z(), to.z()) shr 4
|
||||
val z1 = max(from.z(), to.z()) shr 4
|
||||
return (x0..x1).flatMap { x -> (z0..z1).map { z -> ChunkPos(x, z) } }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package dev.cobblesurvival.selftest
|
||||
|
||||
import com.cobblemon.mod.common.api.drop.ItemDropEntry
|
||||
import com.cobblemon.mod.common.api.pokemon.PokemonSpecies
|
||||
import com.google.gson.JsonParser
|
||||
import dev.cobblesurvival.CobbleSurvival
|
||||
import net.minecraft.gametest.framework.GameTestAssertException
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
|
||||
/**
|
||||
* Roadmap Phase 4, unique drops: the core data pack gives some Pokémon the drops of mobs the island doesn't have
|
||||
* (blaze rods, ghast tears, ...) through Cobblemon's species additions (`species_additions/`). This checks that
|
||||
* Cobblemon really applied every item they add, as tools/drop_audit.py assumes.
|
||||
*/
|
||||
object DropTests {
|
||||
fun all() = listOf(SelfTestCase("drop_additions_apply", 20, ::dropAdditionsApply))
|
||||
|
||||
private fun dropAdditionsApply(s: Scenario) {
|
||||
val additions = s.level.server.resourceManager.listResources("species_additions") { it.path.endsWith(".json") }
|
||||
.filterKeys { it.namespace == CobbleSurvival.MOD_ID }
|
||||
s.check(additions.isNotEmpty()) { "the core data pack has no species additions" }
|
||||
for ((id, resource) in additions) {
|
||||
val json = resource.openAsReader().use { JsonParser.parseReader(it).asJsonObject }
|
||||
val target = ResourceLocation.parse(json.get("target").asString)
|
||||
val species = PokemonSpecies.getByIdentifier(target) ?: throw GameTestAssertException("$id: no species $target")
|
||||
val drops = species.drops.entries.filterIsInstance<ItemDropEntry>().map { it.item }.toSet()
|
||||
for (entry in json.getAsJsonObject("drops")?.getAsJsonArray("entries") ?: continue) {
|
||||
val item = ResourceLocation.parse(entry.asJsonObject.get("item").asString)
|
||||
s.check(item in drops) { "${species.name} doesn't drop $item, although $id adds it" }
|
||||
}
|
||||
}
|
||||
s.helper.succeed()
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ object SelfTest {
|
||||
|
||||
private val cases: List<SelfTestCase> get() =
|
||||
PermadeathTests.all() + SurvivalRulesTests.all() + ZoneTests.all() + HazardTests.all() + GuardianTests.all() +
|
||||
VeinTests.all()
|
||||
VeinTests.all() + DropTests.all()
|
||||
|
||||
private var running: List<Scenario> = emptyList()
|
||||
private var forced: List<ChunkPos> = emptyList()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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
|
||||
@@ -114,9 +115,5 @@ object VeinTests {
|
||||
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)
|
||||
}
|
||||
private fun rewind(level: ServerLevel, pos: BlockPos) = OreVeins.markMined(level, pos, 1L)
|
||||
}
|
||||
Reference in new issue
Block a user