Phase 4 play-test fixes: deep caves, fuller cave spawns, strays, shared loot
From Adrian's play-test (2026-10-30): - The Deep Dark no longer fills the deepslate layer: world/biomes.json splits it by temperature (Deep Dark under cold and temperate land, basalt deltas under the hottest, ordinary caves between); at y -52 it drops from 30 % to 14 % of the land, basalt 7 %. - Cave zones fill up to their targets with lines whose primary type fits the cave; top stages spawn up to the band's top (Deep Dark Lv 60, Basalt 70); no time-of-day conditions underground. - Wild Pokemon more than 10 levels above the band of the zone they wander into despawn (zone/Wanderers.kt). - Loot is shared: all players in one scoreboard team, Lootr team_loot. - Basalt ore nodes reach the deep basalt. Self-tests strays_despawn and loot_is_shared; the catching rules from the previous commit are now tested too. Template rebuilt (world check passed), island quick check passed (36 self-tests and the restart test). Shoulder Pokemon noted for Phase 5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6361636da7
commit
d49684a2eb
40 files changed
+8212
-9412
No files matched your search
@@ -3,6 +3,7 @@ package dev.cobblesurvival.selftest
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder
|
||||
import dev.cobblesurvival.CobbleSurvival
|
||||
import dev.cobblesurvival.catching.Catching
|
||||
import dev.cobblesurvival.zone.Wanderers
|
||||
import net.minecraft.commands.CommandSourceStack
|
||||
import net.minecraft.commands.Commands
|
||||
import net.minecraft.core.BlockPos
|
||||
@@ -53,7 +54,7 @@ object SelfTest {
|
||||
private val cases: List<SelfTestCase> get() =
|
||||
PermadeathTests.all() + SurvivalRulesTests.all() + ZoneTests.all() + HazardTests.all() + GuardianTests.all() +
|
||||
VeinTests.all() + OreNodeTests.all() + DropTests.all() + ProgressionTests.all() +
|
||||
CatchingTests.all()
|
||||
CatchingTests.all() + WorldRulesTests.all()
|
||||
|
||||
private var running: List<Scenario> = emptyList()
|
||||
private var forced: List<ChunkPos> = emptyList()
|
||||
@@ -102,6 +103,7 @@ object SelfTest {
|
||||
}
|
||||
running = scenarios
|
||||
Catching.hostilityPaused = true
|
||||
Wanderers.paused = true
|
||||
GameTestTicker.SINGLETON.clear()
|
||||
GameTestRunner.Builder.fromInfo(infos, level)
|
||||
.newStructureSpawner(StructureGridSpawner(origin, TESTS_PER_ROW, false))
|
||||
@@ -132,5 +134,6 @@ object SelfTest {
|
||||
forced.forEach { level.setChunkForced(it.x, it.z, false) }
|
||||
running = emptyList()
|
||||
Catching.hostilityPaused = false
|
||||
Wanderers.paused = false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package dev.cobblesurvival.selftest
|
||||
|
||||
import dev.cobblesurvival.world.SharedLoot
|
||||
import dev.cobblesurvival.zone.Wanderers
|
||||
import dev.cobblesurvival.zone.Zones
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.core.Direction
|
||||
import net.minecraft.core.registries.BuiltInRegistries
|
||||
import net.minecraft.gametest.framework.GameTestAssertException
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.InteractionHand
|
||||
import net.minecraft.world.RandomizableContainer
|
||||
import net.minecraft.world.inventory.ChestMenu
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.level.block.Blocks
|
||||
import net.minecraft.world.level.storage.loot.BuiltInLootTables
|
||||
import net.minecraft.world.phys.BlockHitResult
|
||||
import net.minecraft.world.phys.Vec3
|
||||
|
||||
/**
|
||||
* World rules from Adrian's play-test (2026-10-30): a wild Pokémon far above the band of the zone it wandered into
|
||||
* despawns (one from the Basalt Furnace turned up in the Lush Caves), and loot containers are shared by all players.
|
||||
* The stray case switches the stray rule on in a batch of its own.
|
||||
*/
|
||||
object WorldRulesTests {
|
||||
private val LOOTR_CHEST = ResourceLocation.parse("lootr:lootr_chest")
|
||||
|
||||
fun all() = listOf(
|
||||
SelfTestCase("strays_despawn", 400, ::straysDespawn, batch = "wanderers"),
|
||||
SelfTestCase("loot_is_shared", 40, ::lootIsShared),
|
||||
)
|
||||
|
||||
private fun straysDespawn(s: Scenario) {
|
||||
Wanderers.paused = false
|
||||
s.afterTest { Wanderers.paused = true }
|
||||
val level = s.level
|
||||
s.player() // the rule only looks near players
|
||||
val zone = Zones.at(level, s.helper.absolutePos(BlockPos(7, 1, 7)))
|
||||
?: throw GameTestAssertException("the test platform lies in no zone")
|
||||
val grace = Zones.wanderGrace ?: throw GameTestAssertException("no wanderers grace in the zone layout")
|
||||
val stray = s.wild(s.pokemon("geodude", zone.maxLevel + grace + 5), s.at(4.5, 1.0, 10.5))
|
||||
val local = s.wild(s.pokemon("geodude", zone.maxLevel), s.at(10.5, 1.0, 10.5))
|
||||
s.helper.startSequence()
|
||||
.thenWaitUntil { s.check(stray.isRemoved) { "a Lv ${stray.pokemon.level} wild Pokémon stays in ${zone.name} (Lv ${zone.minLevel}–${zone.maxLevel})" } }
|
||||
.thenExecute { s.check(!local.isRemoved) { "a Lv ${local.pokemon.level} wild Pokémon was removed from ${zone.name}, its own band" } }
|
||||
.thenSucceed()
|
||||
}
|
||||
|
||||
/** Player A empties a fresh Lootr chest; player B then finds it empty too. */
|
||||
private fun lootIsShared(s: Scenario) {
|
||||
val level = s.level
|
||||
val chest = BuiltInRegistries.BLOCK.get(LOOTR_CHEST)
|
||||
s.check(chest != Blocks.AIR) { "Lootr isn't installed" }
|
||||
val a = s.player()
|
||||
val b = s.player()
|
||||
val team = level.scoreboard.getPlayerTeam(SharedLoot.TEAM)
|
||||
s.check(team != null && a.team == team && b.team == team) { "the players aren't both in the team ${SharedLoot.TEAM}" }
|
||||
val pos = s.helper.absolutePos(BlockPos(7, 2, 7))
|
||||
level.setBlockAndUpdate(pos, chest.defaultBlockState())
|
||||
val container = level.getBlockEntity(pos) as? RandomizableContainer
|
||||
?: throw GameTestAssertException("the Lootr chest has no loot table to set")
|
||||
container.setLootTable(BuiltInLootTables.SIMPLE_DUNGEON, 1234L)
|
||||
|
||||
val found = open(s, a, pos)
|
||||
s.check(found > 0) { "the chest had no loot for the first player" }
|
||||
(a.containerMenu as ChestMenu).container.clearContent()
|
||||
a.closeContainer()
|
||||
val left = open(s, b, pos)
|
||||
b.closeContainer()
|
||||
s.check(left == 0) { "the second player found $left stacks in a chest the first one emptied: loot is per player" }
|
||||
s.helper.succeed()
|
||||
}
|
||||
|
||||
private fun open(s: Scenario, player: ServerPlayer, pos: BlockPos): Int {
|
||||
val hit = BlockHitResult(Vec3.atCenterOf(pos), Direction.UP, pos, false)
|
||||
player.gameMode.useItemOn(player, s.level, ItemStack.EMPTY, InteractionHand.MAIN_HAND, hit)
|
||||
val menu = player.containerMenu as? ChestMenu ?: throw GameTestAssertException("the Lootr chest didn't open for ${player.gameProfile.name}")
|
||||
return (0 until menu.container.containerSize).count { !menu.container.getItem(it).isEmpty }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user