The End gets Pokémon: a dimension zone with Cobblemon's End lines
Adrian, 2026-11-02: the End looked barren. The End is now a zone of its own (Lv 50-70, world/zones.json -> dimensions; Zones.at returns it anywhere in the End). tools/zone_spawns.py copies every line Cobblemon spawns in End biomes into spawn_pool_world/the_end.json, on top of its island home: Beheeyem, Reuniclus, Metagross; Gothitelle and Sigilyph in End cities; Malamar and Unown with a Poke Snack. Cobblemon rolls the bucket before the Pokemon and has these all uncommon or rare, so they move to the common bucket with even line weights (Metagross a fifth). Self-test end_has_spawns: the main island is in the_end and offers only End spawns, some in the common bucket. Self-tests and the island check pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4e7b16abae
commit
4f0bc03919
11 files changed
+810
-9
No files matched your search
@@ -0,0 +1,695 @@
|
||||
{
|
||||
"_comment": "Generated by tools/zone_spawns.py from Cobblemon's spawn data; do not edit by hand.",
|
||||
"enabled": true,
|
||||
"neededInstalledMods": [],
|
||||
"neededUninstalledMods": [],
|
||||
"spawns": [
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"zone": "the_end"
|
||||
},
|
||||
"drops": {
|
||||
"amount": 6,
|
||||
"entries": [
|
||||
{
|
||||
"item": "minecraft:ender_pearl",
|
||||
"quantityRange": "0-2"
|
||||
},
|
||||
{
|
||||
"item": "minecraft:chorus_fruit",
|
||||
"quantityRange": "0-2"
|
||||
},
|
||||
{
|
||||
"item": "cobblemon:light_clay",
|
||||
"percentage": 5.0
|
||||
},
|
||||
{
|
||||
"item": "cobblemon:colbur_berry",
|
||||
"percentage": 5.0
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": "the_end-beheeyem-1-1",
|
||||
"level": "50-70",
|
||||
"pokemon": "beheeyem",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0,
|
||||
"weightMultiplier": {
|
||||
"condition": {
|
||||
"isPokeSnack": true
|
||||
},
|
||||
"multiplier": 1.25
|
||||
}
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"structures": [
|
||||
"#cobblemon:spawns/is_end_structure"
|
||||
],
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-gothitelle-2-2",
|
||||
"level": "50-70",
|
||||
"pokemon": "gothitelle",
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0,
|
||||
"weightMultiplier": {
|
||||
"condition": {
|
||||
"isPokeSnack": true
|
||||
},
|
||||
"multiplier": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-malamar-3-3",
|
||||
"level": "50-70",
|
||||
"pokemon": "malamar",
|
||||
"presets": [
|
||||
"natural",
|
||||
"wild"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-metagross-2-0",
|
||||
"level": "50-70",
|
||||
"pokemon": "metagross",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 2.0,
|
||||
"weightMultiplier": {
|
||||
"condition": {
|
||||
"isPokeSnack": true
|
||||
},
|
||||
"multiplier": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-reuniclus-2-5",
|
||||
"level": "50-70",
|
||||
"pokemon": "reuniclus",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0,
|
||||
"weightMultiplier": {
|
||||
"condition": {
|
||||
"isPokeSnack": true
|
||||
},
|
||||
"multiplier": 1.25
|
||||
}
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"structures": [
|
||||
"#cobblemon:spawns/is_end_structure"
|
||||
],
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-sigilyph-7-4",
|
||||
"level": "50-70",
|
||||
"pokemon": "sigilyph",
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-!-2-32",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=!",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 2.653
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-?-2-33",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=?",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 2.653
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-a-1-6",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=a",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-b-1-7",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=b",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-c-1-8",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=c",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-d-1-9",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=d",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-e-1-10",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=e",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-f-1-11",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=f",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-g-1-12",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=g",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-h-1-13",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=h",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-i-1-14",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=i",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-j-1-15",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=j",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-k-1-16",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=k",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-l-1-17",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=l",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-m-1-18",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=m",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-n-1-19",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=n",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-o-1-20",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=o",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-p-1-21",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=p",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-q-1-22",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=q",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-r-1-23",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=r",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-s-1-24",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=s",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-t-1-25",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=t",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-u-1-26",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=u",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-v-1-27",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=v",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-w-1-28",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=w",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-x-1-29",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=x",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-y-1-30",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=y",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
},
|
||||
{
|
||||
"bucket": "common",
|
||||
"condition": {
|
||||
"biomes": [
|
||||
"#cobblemon:is_end"
|
||||
],
|
||||
"isPokeSnack": true,
|
||||
"zone": "the_end"
|
||||
},
|
||||
"id": "the_end-unown-z-1-31",
|
||||
"level": "50-70",
|
||||
"pokemon": "unown character=z",
|
||||
"presets": [
|
||||
"natural"
|
||||
],
|
||||
"spawnablePositionType": "grounded",
|
||||
"type": "pokemon",
|
||||
"weight": 10.0
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
{"_comment":"Generated by tools/zone_layout.py from world/zones.json; do not edit by hand.","seed":"isle-26","radius":1700,"step":24,"size":142,"zones":[{"id":"button_isle","name":"Button Isle","levels":[2,10]},{"id":"frostpeak","name":"Frostpeak Range","levels":[40,55]},{"id":"sunscorch","name":"Sunscorch","levels":[30,45]},{"id":"mire","name":"Mangrove Mire","levels":[25,38]},{"id":"jungle","name":"Verdant Jungle","levels":[18,30]},{"id":"highlands","name":"Highland Meadows","levels":[25,35]},{"id":"westmarch","name":"Westmarch","levels":[15,25]},{"id":"southwold","name":"Southwold","levels":[8,18]},{"id":"shallows","name":"Shallows","levels":[5,20]},{"id":"deep_sea","name":"Deep Sea","levels":[30,50]}],"caves":[{"id":"lush_caves","name":"Lush Caves","levels":[12,28],"biome":"minecraft:lush_caves"},{"id":"dripstone","name":"Dripstone Depths","levels":[22,38],"biome":"minecraft:dripstone_caves"},{"id":"deep_dark","name":"Deep Dark","levels":[45,60],"biome":"minecraft:deep_dark"},{"id":"basalt","name":"Basalt Furnace","levels":[55,70],"biome":"minecraft:basalt_deltas"}],"level_pressure":{"grace_levels":5,"per_level":0.1,"minimum":0.1},"wanderers":{"grace_levels":10},"samples":{"button_isle":[-116,null,-92],"frostpeak":[628,null,-572],"sunscorch":[1372,null,-236],"mire":[652,null,1012],"jungle":[844,null,196],"highlands":[100,null,-1076],"westmarch":[-428,null,-1100],"southwold":[-116,null,1108],"shallows":[1468,null,4],"deep_sea":[-1316,null,4],"lush_caves":[-1028,0,292],"dripstone":[940,40,-212],"deep_dark":[484,-52,-1124],"basalt":[1252,0,508]},"biome_samples":{"frostpeak":[580,null,-524],"sunscorch":[-44,null,-452],"mire":[652,null,1012],"jungle":[844,null,196]},"grid_rle":[[9,912],[8,22],[9,115],[8,32],[9,106],[8,40],[9,99],[8,18],[5,13],[8,15],[9,96],[8,12],[6,1],[5,22],[1,2],[8,12],[9,92],[8,9],[6,5],[5,27],[1,1],[8,10],[9,90],[8,8],[6,6],[5,29],[1,1],[8,10],[9,85],[8,7],[6,10],[5,21],[1,3],[5,7],[1,1],[5,1],[8,10],[9,80],[8,6],[6,1],[8,1],[6,11],[5,20],[1,8],[5,2],[1,2],[5,2],[8,11],[9,76],[8,5],[6,2],[8,1],[6,13],[5,19],[1,11],[5,5],[8,1],[5,2],[8,8],[9,73],[8,5],[6,2],[8,1],[6,15],[5,18],[1,11],[5,12],[8,7],[9,60],[8,2],[9,7],[8,5],[6,1],[8,2],[6,17],[5,18],[1,9],[5,15],[8,8],[9,57],[8,3],[9,5],[8,5],[6,2],[2,4],[6,16],[5,18],[1,6],[5,20],[8,7],[9,55],[8,12],[6,5],[2,2],[6,17],[5,46],[8,7],[9,52],[8,12],[6,6],[2,1],[6,19],[5,47],[8,6],[9,49],[8,12],[6,8],[2,1],[6,19],[5,25],[1,5],[5,18],[8,6],[9,47],[8,11],[6,1],[8,1],[6,27],[5,26],[1,6],[5,19],[8,5],[9,45],[8,10],[2,1],[8,2],[2,1],[6,27],[5,24],[1,8],[5,20],[8,6],[9,42],[8,9],[2,7],[6,26],[5,24],[1,9],[5,20],[8,6],[9,40],[8,9],[2,8],[6,26],[5,24],[1,11],[5,19],[8,6],[9,39],[8,8],[2,9],[6,26],[5,23],[1,5],[5,1],[1,5],[5,21],[8,5],[9,40],[8,6],[2,10],[6,26],[5,23],[1,12],[5,21],[8,5],[9,39],[8,5],[2,10],[6,27],[5,23],[1,13],[5,21],[8,5],[9,37],[8,5],[2,12],[6,26],[5,26],[1,10],[5,22],[8,5],[9,35],[8,5],[2,11],[6,28],[5,27],[1,7],[5,25],[8,5],[9,34],[8,5],[2,10],[6,29],[5,28],[1,5],[5,27],[8,5],[9,32],[8,5],[2,1],[6,2],[2,4],[6,33],[5,29],[1,3],[5,29],[8,4],[9,31],[8,5],[6,41],[5,62],[8,4],[9,30],[8,4],[6,42],[5,20],[1,1],[5,42],[8,4],[9,28],[8,5],[6,42],[5,16],[1,3],[5,1],[1,5],[5,38],[8,4],[9,27],[8,5],[6,43],[5,12],[1,18],[5,11],[1,1],[5,22],[8,4],[9,26],[8,5],[6,43],[5,12],[1,17],[5,3],[1,4],[5,2],[1,1],[5,2],[1,1],[5,22],[8,4],[9,25],[8,5],[6,44],[5,12],[1,18],[5,1],[1,5],[5,1],[1,3],[5,1],[1,1],[5,23],[8,4],[9,24],[8,4],[6,45],[5,12],[1,11],[5,1],[1,6],[5,1],[1,3],[5,2],[1,4],[5,25],[8,4],[9,23],[8,5],[6,44],[5,5],[1,1],[5,7],[1,13],[5,1],[1,2],[5,7],[1,2],[5,1],[1,2],[5,26],[8,4],[9,22],[8,4],[6,45],[5,4],[1,4],[5,5],[1,9],[5,2],[1,2],[5,9],[1,8],[5,24],[8,4],[9,21],[8,4],[2,3],[6,44],[5,1],[1,8],[5,3],[1,7],[5,4],[1,1],[5,9],[1,9],[5,24],[8,5],[9,20],[8,4],[2,4],[6,43],[5,1],[1,8],[5,4],[1,5],[5,3],[1,3],[5,9],[1,9],[5,25],[8,4],[9,19],[8,4],[2,5],[6,43],[1,9],[5,4],[1,10],[5,10],[1,9],[5,25],[8,5],[9,1],[8,1],[9,16],[8,4],[6,48],[1,10],[5,4],[1,3],[5,2],[1,3],[5,10],[1,10],[5,26],[8,6],[9,16],[8,4],[6,48],[1,10],[5,3],[2,1],[5,1],[2,2],[5,15],[1,9],[5,27],[8,7],[9,14],[8,4],[6,49],[1,9],[2,7],[5,16],[1,9],[5,4],[1,3],[5,21],[8,6],[9,14],[8,4],[6,48],[1,9],[2,8],[5,16],[1,9],[5,3],[1,4],[5,21],[8,6],[9,14],[8,3],[6,49],[1,7],[2,10],[5,16],[1,9],[5,3],[1,5],[5,20],[2,1],[8,6],[9,12],[8,4],[6,50],[1,5],[2,11],[5,16],[1,9],[5,3],[1,5],[5,19],[2,2],[8,6],[9,12],[8,4],[6,50],[5,2],[1,2],[2,13],[5,15],[1,10],[5,3],[1,4],[5,18],[2,3],[8,6],[9,12],[8,3],[6,51],[5,3],[2,12],[1,4],[5,14],[1,10],[5,23],[2,4],[8,7],[9,10],[8,4],[6,51],[5,3],[2,11],[1,5],[5,15],[1,9],[5,23],[2,5],[8,6],[9,10],[8,3],[6,52],[5,3],[2,11],[1,1],[5,2],[1,3],[5,15],[1,10],[5,20],[2,6],[8,6],[9,10],[8,3],[6,52],[5,3],[2,3],[8,3],[9,3],[8,2],[1,1],[5,3],[1,1],[5,16],[1,12],[5,17],[2,7],[8,6],[9,12],[8,1],[4,2],[6,50],[5,3],[2,1],[8,6],[9,2],[8,5],[5,1],[1,1],[5,16],[1,3],[5,5],[1,5],[5,15],[2,8],[5,1],[8,1],[9,1],[8,3],[9,7],[8,3],[9,2],[8,1],[4,5],[6,47],[5,1],[8,1],[9,2],[8,6]Line truncated
|
||||
{"_comment":"Generated by tools/zone_layout.py from world/zones.json; do not edit by hand.","seed":"isle-26","radius":1700,"step":24,"size":142,"zones":[{"id":"button_isle","name":"Button Isle","levels":[2,10]},{"id":"frostpeak","name":"Frostpeak Range","levels":[40,55]},{"id":"sunscorch","name":"Sunscorch","levels":[30,45]},{"id":"mire","name":"Mangrove Mire","levels":[25,38]},{"id":"jungle","name":"Verdant Jungle","levels":[18,30]},{"id":"highlands","name":"Highland Meadows","levels":[25,35]},{"id":"westmarch","name":"Westmarch","levels":[15,25]},{"id":"southwold","name":"Southwold","levels":[8,18]},{"id":"shallows","name":"Shallows","levels":[5,20]},{"id":"deep_sea","name":"Deep Sea","levels":[30,50]}],"caves":[{"id":"lush_caves","name":"Lush Caves","levels":[12,28],"biome":"minecraft:lush_caves"},{"id":"dripstone","name":"Dripstone Depths","levels":[22,38],"biome":"minecraft:dripstone_caves"},{"id":"deep_dark","name":"Deep Dark","levels":[45,60],"biome":"minecraft:deep_dark"},{"id":"basalt","name":"Basalt Furnace","levels":[55,70],"biome":"minecraft:basalt_deltas"}],"dimensions":[{"id":"the_end","name":"The End","levels":[50,70],"dimension":"minecraft:the_end"}],"level_pressure":{"grace_levels":5,"per_level":0.1,"minimum":0.1},"wanderers":{"grace_levels":10},"samples":{"button_isle":[-116,null,-92],"frostpeak":[628,null,-572],"sunscorch":[1372,null,-236],"mire":[652,null,1012],"jungle":[844,null,196],"highlands":[100,null,-1076],"westmarch":[-428,null,-1100],"southwold":[-116,null,1108],"shallows":[1468,null,4],"deep_sea":[-1316,null,4],"lush_caves":[-1028,0,292],"dripstone":[940,40,-212],"deep_dark":[484,-52,-1124],"basalt":[1252,0,508]},"biome_samples":{"frostpeak":[580,null,-524],"sunscorch":[-44,null,-452],"mire":[652,null,1012],"jungle":[844,null,196]},"grid_rle":[[9,912],[8,22],[9,115],[8,32],[9,106],[8,40],[9,99],[8,18],[5,13],[8,15],[9,96],[8,12],[6,1],[5,22],[1,2],[8,12],[9,92],[8,9],[6,5],[5,27],[1,1],[8,10],[9,90],[8,8],[6,6],[5,29],[1,1],[8,10],[9,85],[8,7],[6,10],[5,21],[1,3],[5,7],[1,1],[5,1],[8,10],[9,80],[8,6],[6,1],[8,1],[6,11],[5,20],[1,8],[5,2],[1,2],[5,2],[8,11],[9,76],[8,5],[6,2],[8,1],[6,13],[5,19],[1,11],[5,5],[8,1],[5,2],[8,8],[9,73],[8,5],[6,2],[8,1],[6,15],[5,18],[1,11],[5,12],[8,7],[9,60],[8,2],[9,7],[8,5],[6,1],[8,2],[6,17],[5,18],[1,9],[5,15],[8,8],[9,57],[8,3],[9,5],[8,5],[6,2],[2,4],[6,16],[5,18],[1,6],[5,20],[8,7],[9,55],[8,12],[6,5],[2,2],[6,17],[5,46],[8,7],[9,52],[8,12],[6,6],[2,1],[6,19],[5,47],[8,6],[9,49],[8,12],[6,8],[2,1],[6,19],[5,25],[1,5],[5,18],[8,6],[9,47],[8,11],[6,1],[8,1],[6,27],[5,26],[1,6],[5,19],[8,5],[9,45],[8,10],[2,1],[8,2],[2,1],[6,27],[5,24],[1,8],[5,20],[8,6],[9,42],[8,9],[2,7],[6,26],[5,24],[1,9],[5,20],[8,6],[9,40],[8,9],[2,8],[6,26],[5,24],[1,11],[5,19],[8,6],[9,39],[8,8],[2,9],[6,26],[5,23],[1,5],[5,1],[1,5],[5,21],[8,5],[9,40],[8,6],[2,10],[6,26],[5,23],[1,12],[5,21],[8,5],[9,39],[8,5],[2,10],[6,27],[5,23],[1,13],[5,21],[8,5],[9,37],[8,5],[2,12],[6,26],[5,26],[1,10],[5,22],[8,5],[9,35],[8,5],[2,11],[6,28],[5,27],[1,7],[5,25],[8,5],[9,34],[8,5],[2,10],[6,29],[5,28],[1,5],[5,27],[8,5],[9,32],[8,5],[2,1],[6,2],[2,4],[6,33],[5,29],[1,3],[5,29],[8,4],[9,31],[8,5],[6,41],[5,62],[8,4],[9,30],[8,4],[6,42],[5,20],[1,1],[5,42],[8,4],[9,28],[8,5],[6,42],[5,16],[1,3],[5,1],[1,5],[5,38],[8,4],[9,27],[8,5],[6,43],[5,12],[1,18],[5,11],[1,1],[5,22],[8,4],[9,26],[8,5],[6,43],[5,12],[1,17],[5,3],[1,4],[5,2],[1,1],[5,2],[1,1],[5,22],[8,4],[9,25],[8,5],[6,44],[5,12],[1,18],[5,1],[1,5],[5,1],[1,3],[5,1],[1,1],[5,23],[8,4],[9,24],[8,4],[6,45],[5,12],[1,11],[5,1],[1,6],[5,1],[1,3],[5,2],[1,4],[5,25],[8,4],[9,23],[8,5],[6,44],[5,5],[1,1],[5,7],[1,13],[5,1],[1,2],[5,7],[1,2],[5,1],[1,2],[5,26],[8,4],[9,22],[8,4],[6,45],[5,4],[1,4],[5,5],[1,9],[5,2],[1,2],[5,9],[1,8],[5,24],[8,4],[9,21],[8,4],[2,3],[6,44],[5,1],[1,8],[5,3],[1,7],[5,4],[1,1],[5,9],[1,9],[5,24],[8,5],[9,20],[8,4],[2,4],[6,43],[5,1],[1,8],[5,4],[1,5],[5,3],[1,3],[5,9],[1,9],[5,25],[8,4],[9,19],[8,4],[2,5],[6,43],[1,9],[5,4],[1,10],[5,10],[1,9],[5,25],[8,5],[9,1],[8,1],[9,16],[8,4],[6,48],[1,10],[5,4],[1,3],[5,2],[1,3],[5,10],[1,10],[5,26],[8,6],[9,16],[8,4],[6,48],[1,10],[5,3],[2,1],[5,1],[2,2],[5,15],[1,9],[5,27],[8,7],[9,14],[8,4],[6,49],[1,9],[2,7],[5,16],[1,9],[5,4],[1,3],[5,21],[8,6],[9,14],[8,4],[6,48],[1,9],[2,8],[5,16],[1,9],[5,3],[1,4],[5,21],[8,6],[9,14],[8,3],[6,49],[1,7],[2,10],[5,16],[1,9],[5,3],[1,5],[5,20],[2,1],[8,6],[9,12],[8,4],[6,50],[1,5],[2,11],[5,16],[1,9],[5,3],[1,5],[5,19],[2,2],[8,6],[9,12],[8,4],[6,50],[5,2],[1,2],[2,13],[5,15],[1,10],[5,3],[1,4],[5,18],[2,3],[8,6],[9,12],[8,3],[6,51],[5,3],[2,12],[1,4],[5,14],[1,10],[5,23],[2,4],[8,7],[9,10],[8,4],[6,51],[5,3],[2,11],[1,5],[5,15],[1,9],[5,23],[2,5],[8,6],[9,10],[8,3],[6,52],[5,3],[2,11],[1,1],[5,2],[1,3],[5,15],[1,10],[5,20],[2,6],[8,6],[9,10],[8,3],[6,52],[5,3],[2,3],[8,3],[9,3],[8,2],[1,1],[5,3],[1,1],[5,16],[1,12],[5,17],[2,7],[8,6],[9,12],[8,1],[4,2],[6,50],[5,3],[2,1],[8,6],[9,2],[8,5],[5,1],[1,1],[5,16],[1,3],[5,5],[1,5]Line truncated
|
||||
@@ -39,6 +39,7 @@ Every tunable lives in a config file or in this table. **Placeholder** means gue
|
||||
| Boss attacks (guardians) | by guardian level: up to Lv 20 a circle under the player; up to 35 + a line from the boss; up to 50 + a cone; up to 65 + a ring around the boss at half HP; above + a wide ring at a quarter HP. Telegraph 2.5 s at the lowest tier down to 1.5 s; damage level/6 health points (Morpeko 3, Moltres 12); circle every 7.4 s down to 5.6 s, line and cone less often; Fire types burn, Ice types freeze, Poison types poison | Placeholder | `tools/zone_bosses.py` → `datapacks/core/data/cobblesurvival/bosses/*.json` | Adrian, 2026-11-02: every guardian, harder in harder zones |
|
||||
| Boss arena | 24 blocks around where the boss stood; Basalt Furnace 32; the End 40; leaving it or dying loses the fight | Placeholder | same files, `arena` | |
|
||||
| End boss | Eternatus Lv 80, 0.2× size (its model is 20 m tall), back 5 hours after a win; 12,000 XP the first time, 500 after | Placeholder | `bosses/end.json` (`tools/zone_bosses.py` → `END`) | Stands in for Giratina, which Cobblemon 1.8.4 has no model for |
|
||||
| End spawns | the End is a zone, Lv 50–70; Cobblemon's End lines spawn there (Beheeyem, Reuniclus, Metagross; Gothitelle and Sigilyph in End cities; Malamar and Unown with a Poké Snack), all in the common bucket, each line's heaviest entry weight 10, Metagross 2 | Placeholder | `world/zones.json` → `dimensions` (`levels`, `bucket`, `line_weight`, `rare_factor`), then `tools/zone_layout.py` and `tools/zone_spawns.py` | Adrian, 2026-11-02: the End was barren; Lv 50–70 his pick |
|
||||
| Level pressure | full experience up to 5 levels above the zone's band, then 10 % less per level, at least 10 % (Exp. Candy and commands exempt) | Decided (Adrian, 2026-10-30) | `world/zones.json` → `level_pressure` | E.g. on Button Isle (up to Lv 10): Lv 15 full, Lv 20 half, Lv 24+ a tenth |
|
||||
| Strong flyer | ridable through the air with top speed ≥ 60 or top stamina ≥ 85 (Cobblemon ride stats) | Decided (Adrian, 2026-10-30) | `tools/zone_spawns.py` (`STRONG_FLYER_*`) | 29 species |
|
||||
| High zone for strong flyers | band starts at Lv 30 or more (Sunscorch, Frostpeak, Deep Sea, Deep Dark, Basalt Furnace) | Decided (Adrian, 2026-10-30) | `tools/zone_spawns.py` (`HIGH_ZONE_LEVEL`) | |
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
- **Boss fights:** a battle with a guardian (or the End boss) becomes a fight with area attacks: red or coloured particles on the ground show where an attack lands, about 1.5–2.5 seconds later it hits whoever stands inside. Circles, lines, cones and rings; harder zones get more of them. Fire, Ice and Poison bosses burn, freeze or poison. Automated: `boss_attack_shapes`, `boss_attacks_land_and_stop_with_the_battle`.
|
||||
- **Arena:** leaving it (24 blocks; Basalt 32; End 40) or dying ends the battle as lost; the boss heals and stays. Automated: `boss_arena_leaving_loses`.
|
||||
- **The End:** the Ender Dragon is gone; Eternatus appears on the main island, with the dragon's boss bar; beating it opens the exit portal and gateway, leaves the egg the first time and gives experience. Automated: `end_boss_replaces_the_dragon`.
|
||||
- **End spawns:** the End is a zone (Lv 50–70) with Cobblemon's End lines: Beheeyem, Reuniclus, Metagross; Gothitelle and Sigilyph in End cities; Malamar and Unown with a Poké Snack. Automated: `end_has_spawns`.
|
||||
|
||||
## In game (Prism instance)
|
||||
|
||||
@@ -21,6 +22,7 @@ On the island save (or a new one), in creative or survival:
|
||||
- [ ] Leave the End and come back without beating Eternatus: still only one (fixed 2026-11-02).
|
||||
- [ ] Go to the End: no dragon; Eternatus stands south of the portal, the boss bar shows its HP. Beat it: the exit portal opens, the egg appears.
|
||||
- [ ] Find Regigigas in a Deep Dark cave and Moltres in the Basalt Furnace.
|
||||
- [ ] Walk around the End's islands for a minute: Beheeyem, Reuniclus and now and then Metagross appear, Lv 50–70 (`/cobblesurvival zone` shows "The End").
|
||||
|
||||
## Ask Adrian
|
||||
|
||||
|
||||
@@ -290,6 +290,8 @@ Self-tests (`BossTests`): every guardian has a fight and a Cobblemon model; each
|
||||
|
||||
**Eternatus** replaces the Ender Dragon: the dragon no longer spawns; Eternatus (uncatchable, Lv 80, scaled down to fit) appears on the main End island while a player is there, and beating it does what killing the dragon did (exit portal, gateways, the egg on the first win). Only one at a time: it waits until the entities around its spot have loaded before deciding it's missing, and a second copy never joins while the recorded one is alive (Adrian met a second Eternatus on his second visit, 2026-11-02); extra copies left in older worlds are removed.
|
||||
|
||||
**The End has Pokémon** (Adrian, 2026-11-02: it looked barren). The End is a zone of its own, "The End", Lv 50–70 (`world/zones.json` → `dimensions`; `Zones.at` returns it anywhere in the End). Every line Cobblemon spawns in End biomes spawns there too, on top of its home on the island: Beheeyem, Reuniclus and Metagross anywhere; Gothitelle and Sigilyph in End cities; Malamar and Unown with a Poké Snack. Gothita had no island home (it only spawns in the End), so it's new. Cobblemon has all of these uncommon or rare, and it rolls the bucket before the Pokémon, so in the End they move to the common bucket or the End would stay nearly empty; each line weighs the same, Metagross a fifth. Being a zone, the End also has level pressure (full experience up to Lv 75, like the Basalt Furnace) and the wild-Pokémon cleanup above Lv 80; Eternatus is exempt as an uncatchable boss. Self-test `end_has_spawns`.
|
||||
|
||||
## Open decisions for Adrian (proceeding with the default, flagged)
|
||||
|
||||
| Decision | Default used for now |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Pokémon per zone
|
||||
|
||||
Generated by `tools/zone_spawns.py` from Cobblemon's spawn data and `world/zones.json`; do not edit by hand. Each evolution line spawns in one zone; the stages listed are the ones whose levels reach the zone's band. Pin a line to another zone in `world/zones.json` → `species`.
|
||||
Generated by `tools/zone_spawns.py` from Cobblemon's spawn data and `world/zones.json`; do not edit by hand. Each evolution line spawns in one zone of the island; the stages listed are the ones whose levels reach the zone's band. Pin a line to another zone in `world/zones.json` → `species`. The End's lines spawn there as well as in their island zone.
|
||||
|
||||
## Button Isle (Lv 2–10, 25 lines)
|
||||
|
||||
@@ -58,6 +58,10 @@ Bronzor (Bronzong), Carbink, Deino (Zweilous), Ditto, Duskull (Dusclops, Dusknoi
|
||||
|
||||
Absol, Charmander (Charizard), Diglett (Dugtrio), Drilbur (Excadrill), Durant, Gligar (Gliscor), Growlithe (Arcanine), Heatmor, Houndour (Houndoom), Magby (Magmortar), Mawile, Purrloin (Liepard), Sizzlipede (Centiskorch), Slugma (Magcargo), Torkoal, Turtonator
|
||||
|
||||
## The End (Lv 50–70, 7 lines)
|
||||
|
||||
Beldum (Metagross), Elgyem (Beheeyem), Gothita (Gothitelle), Inkay (Malamar), Sigilyph, Solosis (Reuniclus), Unown
|
||||
|
||||
## Alpha guardians
|
||||
|
||||
One per zone at its sample point, uncatchable, five levels above the band; it returns 5 hours after it is beaten. Its drops help with the next zone.
|
||||
|
||||
@@ -19,6 +19,8 @@ import net.minecraft.core.BlockPos
|
||||
import net.minecraft.gametest.framework.GameTestAssertException
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.util.Mth
|
||||
import net.minecraft.world.level.ChunkPos
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.level.levelgen.Heightmap
|
||||
import kotlin.math.roundToInt
|
||||
@@ -36,6 +38,7 @@ object ZoneTests {
|
||||
SelfTestCase("spawn_pools_are_zoned", 20, ::spawnPoolsAreZoned),
|
||||
SelfTestCase("spawn_survey", 2400, ::spawnSurvey, islandOnly = true),
|
||||
SelfTestCase("level_pressure_cuts_xp", 100, ::levelPressure),
|
||||
SelfTestCase("end_has_spawns", 200, ::endSpawns),
|
||||
)
|
||||
|
||||
private fun zoneOf(detail: SpawnDetail): Zone? = Zones.byId[detail.id.substringBefore('-')]
|
||||
@@ -108,6 +111,44 @@ object ZoneTests {
|
||||
}.thenSucceed()
|
||||
}
|
||||
|
||||
/**
|
||||
* The End is a zone of its own (Adrian, 2026-11-02: it was barren). On the main island Cobblemon offers End
|
||||
* spawns only, and some in the common bucket, which it rolls most of the time.
|
||||
*/
|
||||
private fun endSpawns(s: Scenario) {
|
||||
val end = s.level.server.getLevel(Level.END) ?: throw GameTestAssertException("no End")
|
||||
val chunks = (0..2).flatMap { x -> (0..2).map { z -> ChunkPos(x, z) } }
|
||||
chunks.forEach { end.setChunkForced(it.x, it.z, true) }
|
||||
s.afterTest { chunks.forEach { end.setChunkForced(it.x, it.z, false) } }
|
||||
s.helper.startSequence()
|
||||
.thenWaitUntil { s.check(chunks.all { end.areEntitiesLoaded(it.toLong()) }) { "the End's main island isn't loaded yet" } }
|
||||
.thenExecute {
|
||||
val stand = ground(end, 20, 20)
|
||||
s.check(Zones.at(end, stand)?.id == "the_end") { "the End's main island is in zone ${Zones.at(end, stand)?.id}, not the_end" }
|
||||
val (offered, common) = offeredAt(s.player(), end, stand)
|
||||
val strangers = offered.filter { zoneOf(it)?.id != "the_end" }
|
||||
s.check(strangers.isEmpty()) { "the End offers other zones' spawns: ${strangers.take(5).map { it.id }}" }
|
||||
s.check(common.isNotEmpty()) { "nothing in the common bucket can spawn on the End's main island (${offered.map { it.id }})" }
|
||||
}
|
||||
.thenSucceed()
|
||||
}
|
||||
|
||||
/** What Cobblemon could spawn around [pos] in [level] right now (the way `/checkspawn` does): all, and the common bucket's. */
|
||||
private fun offeredAt(player: ServerPlayer, level: net.minecraft.server.level.ServerLevel, pos: BlockPos): Pair<Set<SpawnDetail>, Set<SpawnDetail>> {
|
||||
val spawner = player.spawner
|
||||
val diameter = Cobblemon.config.spawningZoneDiameter
|
||||
val height = Cobblemon.config.spawningZoneHeight
|
||||
val input = SpawningZoneInput(
|
||||
SpawnCause(spawner, player), level,
|
||||
pos.x - diameter / 2, pos.y - height / 2, pos.z - diameter / 2, diameter, height, diameter,
|
||||
)
|
||||
val positions = Cobblemon.areaSpawnablePositionResolver.resolve(
|
||||
spawner, SpawnablePositionCalculator.prioritizedAreaCalculators, Cobblemon.spawningZoneGenerator.generate(spawner, input),
|
||||
)
|
||||
fun inBucket(bucket: String) = spawner.selector.getProbabilities(spawner, bucket, positions).filterValues { it > 0f }.keys
|
||||
return CobblemonSpawnPools.WORLD_SPAWN_POOL.buckets.flatMap(::inBucket).toSet() to inBucket("common")
|
||||
}
|
||||
|
||||
/**
|
||||
* The ground at [x] [z], below any tree: standing on a mangrove's roots or logs, the survey once found no natural
|
||||
* ground around it, so nothing could spawn (2026-11-02).
|
||||
|
||||
@@ -37,6 +37,8 @@ object Zones : SimpleJsonResourceReloadListener(Gson(), "zones") {
|
||||
var byId: Map<String, Zone> = emptyMap()
|
||||
private set
|
||||
private var caves: Map<ResourceLocation, Zone> = emptyMap()
|
||||
/** Zones that are a whole dimension (the End; Adrian, 2026-11-02: it was barren). */
|
||||
private var dimensions: Map<ResourceLocation, Zone> = emptyMap()
|
||||
var samples: Map<String, ZoneSample> = emptyMap()
|
||||
private set
|
||||
var levelPressure = LevelPressureRules()
|
||||
@@ -65,7 +67,9 @@ object Zones : SimpleJsonResourceReloadListener(Gson(), "zones") {
|
||||
byIndex = json.getAsJsonArray("zones").map { zone(it.asJsonObject) }
|
||||
val caveZones = json.getAsJsonArray("caves").map { it.asJsonObject }
|
||||
caves = caveZones.associate { ResourceLocation.parse(it.get("biome").asString) to zone(it) }
|
||||
byId = (byIndex + caves.values).associateBy { it.id }
|
||||
dimensions = json.getAsJsonArray("dimensions")?.map { it.asJsonObject }
|
||||
?.associate { ResourceLocation.parse(it.get("dimension").asString) to zone(it) }.orEmpty()
|
||||
byId = (byIndex + caves.values + dimensions.values).associateBy { it.id }
|
||||
levelPressure = json.getAsJsonObject("level_pressure")?.let { rules ->
|
||||
LevelPressureRules(
|
||||
graceLevels = rules.get("grace_levels")?.asInt ?: 5,
|
||||
@@ -87,7 +91,7 @@ object Zones : SimpleJsonResourceReloadListener(Gson(), "zones") {
|
||||
}
|
||||
check(cells.size == size * size) { "zone grid has ${cells.size} cells, expected ${size * size}" }
|
||||
grid = cells.toIntArray()
|
||||
CobbleSurvival.LOGGER.info("Loaded {} zones ({} surface, {} cave)", byId.size, byIndex.size, caves.size)
|
||||
CobbleSurvival.LOGGER.info("Loaded {} zones ({} surface, {} cave, {} dimension)", byId.size, byIndex.size, caves.size, dimensions.size)
|
||||
}
|
||||
|
||||
private fun samplesOf(json: JsonObject, key: String): Map<String, ZoneSample> =
|
||||
@@ -101,8 +105,9 @@ object Zones : SimpleJsonResourceReloadListener(Gson(), "zones") {
|
||||
return Zone(json.get("id").asString, json.get("name").asString, levels[0].asInt, levels[1].asInt)
|
||||
}
|
||||
|
||||
/** The zone at [pos], or null outside the island's zones or outside the overworld. */
|
||||
/** The zone at [pos]: a whole other dimension's zone, or in the overworld the island's; null outside them. */
|
||||
fun at(level: Level, pos: BlockPos, biome: Holder<Biome> = level.getBiome(pos)): Zone? {
|
||||
dimensions[level.dimension().location()]?.let { return it }
|
||||
if (level.dimension() != Level.OVERWORLD || size == 0) return null
|
||||
biome.unwrapKey().orElse(null)?.let { caves[it.location()] }?.let { return it }
|
||||
// Grid cells are centred on -radius + i * step.
|
||||
|
||||
@@ -312,6 +312,7 @@ def write_datapack(zone, plan, preview):
|
||||
"seed": preview["seed"], "radius": preview["radius"], "step": preview["step"], "size": preview["size"],
|
||||
"zones": [{k: z[k] for k in keep} for z in plan["zones"]],
|
||||
"caves": [{**{k: c[k] for k in keep}, "biome": c["biome"]} for c in plan["caves"]],
|
||||
"dimensions": [{**{k: d[k] for k in keep}, "dimension": d["dimension"]} for d in plan.get("dimensions", [])],
|
||||
"level_pressure": plan.get("level_pressure", {}),
|
||||
"wanderers": plan.get("wanderers", {}),
|
||||
"samples": samples(zone, plan, preview),
|
||||
|
||||
+48
-4
@@ -32,6 +32,11 @@ datapacks/core/data/cobblesurvival/spawn_pool_world/<zone>.json:
|
||||
6. Each zone with a "guardian" gets datapacks/core/data/cobblesurvival/guardians/<zone>.json: the
|
||||
strongest Pokémon spawning in the zone (or "species"), uncatchable, half again as big (or "scale"), five levels
|
||||
above the band, with the zone's boss drops and world/zones.json → guardian_respawn_hours.
|
||||
7. world/zones.json → "dimensions" are zones that are a whole other dimension (the End; Adrian, 2026-11-02: it was
|
||||
barren). Every line Cobblemon spawns in their "biome_tags" spawns there too, on top of its island home, with
|
||||
its stages fitted to the band as in 2. The entries go into "bucket" (Cobblemon rolls the bucket first, and its
|
||||
End entries are all uncommon or rare), the heaviest of each line weighing "line_weight", Cobblemon's rare ones
|
||||
"rare_factor" of that.
|
||||
|
||||
Also writes docs/zone-species.md, the list per zone for review.
|
||||
|
||||
@@ -306,6 +311,45 @@ def main():
|
||||
names = sorted({species[m]["name"] for _, m, _, _ in fitting})
|
||||
report[zid].append((species.get(root, {}).get("name", root), names))
|
||||
|
||||
# 4. Dimension zones (Adrian, 2026-11-02: the End was barren): every line Cobblemon spawns in the dimension's
|
||||
# biome tags spawns there too, at the dimension's band, on top of its home on the island. Read straight from
|
||||
# Cobblemon's spawn files, since the field guide only resolves the island's biomes. Cobblemon rolls the bucket
|
||||
# before the Pokémon, and its End entries are all uncommon or rare: they move to the dimension's bucket, the
|
||||
# heaviest entry of each line weighing line_weight (Cobblemon's rare ones rare_factor of that).
|
||||
dimension_roots = set()
|
||||
for dim in plan.get("dimensions", []):
|
||||
did = dim["id"]
|
||||
zones[did] = dim
|
||||
lo, hi = dim["levels"]
|
||||
tags = set(dim["biome_tags"])
|
||||
found = collections.defaultdict(list)
|
||||
for entry in entries.values():
|
||||
if "pokemon" not in entry or not tags & set(entry.get("condition", {}).get("biomes", [])):
|
||||
continue
|
||||
member = f"cobblemon:{entry['pokemon'].split()[0].lower()}"
|
||||
if EXCLUDED_LABELS & set(species.get(member, {}).get("labels", [])):
|
||||
continue
|
||||
a, _, b = str(entry["level"]).partition("-")
|
||||
found[line_root(member, species)].append((entry, member, (int(a), int(b or a))))
|
||||
for root, items in sorted(found.items()):
|
||||
fitting = [(e, m, max(lv[0], lo), hi) for e, m, lv in items if lv[0] <= hi and lv[1] >= lo]
|
||||
if not fitting:
|
||||
_, m, lv = min(items, key=lambda u: min(abs(u[2][0] - hi), abs(u[2][1] - lo)))
|
||||
fitting = [(e, um, lo, hi) for e, um, ulv in items if um == m and ulv == lv]
|
||||
heaviest = max(e.get("weight", 1) for e, _, _, _ in fitting)
|
||||
for entry, member, a, b in fitting:
|
||||
spawn = copy.deepcopy(entry)
|
||||
spawn["id"] = f"{did}-{entry.get('id') or member.split(':')[1]}-{len(pools[did])}"
|
||||
spawn["level"] = f"{a}-{b}"
|
||||
rare = entry.get("bucket") in ("rare", "ultra-rare")
|
||||
spawn["weight"] = round(dim["line_weight"] * entry.get("weight", 1) / heaviest * (dim["rare_factor"] if rare else 1), 3)
|
||||
spawn["bucket"] = dim["bucket"]
|
||||
spawn.setdefault("condition", {})["zone"] = did
|
||||
pools[did].append(spawn)
|
||||
names = sorted({species[m]["name"] for _, m, _, _ in fitting if m in species})
|
||||
report[did].append((species.get(root, {}).get("name", root), names))
|
||||
dimension_roots.add(root)
|
||||
|
||||
low_flyers = [(zid, s["pokemon"]) for zid, spawns in pools.items() for s in spawns
|
||||
if zones[zid]["levels"][0] < HIGH_ZONE_LEVEL and f"cobblemon:{s['pokemon'].split()[0].lower()}" in strong_flyers]
|
||||
if low_flyers:
|
||||
@@ -346,9 +390,9 @@ def main():
|
||||
|
||||
lines_out = ["# Pokémon per zone", "",
|
||||
"Generated by `tools/zone_spawns.py` from Cobblemon's spawn data and `world/zones.json`; "
|
||||
"do not edit by hand. Each evolution line spawns in one zone; the stages listed are the ones "
|
||||
"whose levels reach the zone's band. Pin a line to another zone in `world/zones.json` → "
|
||||
"`species`.", ""]
|
||||
"do not edit by hand. Each evolution line spawns in one zone of the island; the stages listed are "
|
||||
"the ones whose levels reach the zone's band. Pin a line to another zone in `world/zones.json` → "
|
||||
"`species`. The End's lines spawn there as well as in their island zone.", ""]
|
||||
for zid, zone in zones.items():
|
||||
rows = sorted(report.get(zid, []))
|
||||
lines_out += [f"## {zone['name']} (Lv {zone['levels'][0]}–{zone['levels'][1]}, {len(rows)} lines)", ""]
|
||||
@@ -364,7 +408,7 @@ def main():
|
||||
f"| {zone['levels'][1] + GUARDIAN_LEVELS_ABOVE_BAND} | {drop_text} |")
|
||||
REPORT.write_text("\n".join(lines_out) + "\n")
|
||||
|
||||
unplaced = [r for r in lines if r not in home]
|
||||
unplaced = [r for r in lines if r not in home and r not in dimension_roots]
|
||||
grounded = sorted(species.get(r, {}).get("name", r) for r in unplaced if r in flying_lines)
|
||||
if grounded:
|
||||
print(f"strong flyers kept out of the wild (no high zone fits their habitat): {', '.join(grounded)}")
|
||||
|
||||
@@ -78,5 +78,11 @@
|
||||
{"id": "dripstone", "types": ["rock", "ground", "steel", "fairy", "psychic", "dragon"], "lines": 20, "name": "Dripstone Depths", "biome": "minecraft:dripstone_caves", "levels": [22, 38], "hazard": "none"},
|
||||
{"id": "deep_dark", "types": ["ghost", "dark", "steel", "poison", "psychic", "rock"], "guardian": {"species": "regigigas", "scale": 1.0, "drops": [["cobblemon:icy_rock", 3], ["cobblemon:exp_candy_l", 2]]}, "lines": 21, "name": "Deep Dark", "biome": "minecraft:deep_dark", "levels": [45, 60], "hazard": "darkness (vanilla sculk; no Warden)"},
|
||||
{"id": "basalt", "types": ["fire", "rock", "ground", "steel", "dark"], "guardian": {"species": "moltres", "drops": [["cobblemon:exp_candy_xl", 3], ["minecraft:netherite_scrap", 2]]}, "lines": 15, "name": "Basalt Furnace", "biome": "minecraft:basalt_deltas", "levels": [55, 70], "hazard": "heat (Cold Sweat)"}
|
||||
],
|
||||
"dimensions": [
|
||||
{"id": "the_end", "name": "The End", "dimension": "minecraft:the_end", "levels": [50, 70], "hazard": "none",
|
||||
"biome_tags": ["#cobblemon:is_end", "#minecraft:is_end"],
|
||||
"bucket": "common", "line_weight": 10, "rare_factor": 0.2,
|
||||
"_comment": "Adrian, 2026-11-02: the End was barren. Every line Cobblemon spawns in End biomes spawns there too, at this band, on top of its island home (tools/zone_spawns.py). Cobblemon has them all uncommon or rare, and it rolls the bucket before the Pokemon, so they move to `bucket`; each line's heaviest entry weighs `line_weight`, Cobblemon's rare ones `rare_factor` of that."}
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user