Phase 3: the Mangrove Mire poisons anyone without Mire Waders
CI / build-and-smoke-test (push) Failing after 30m44s

- Companion mod zone hazards (zone/ZoneHazards.kt, data in
  data/cobblesurvival/zone_hazards/): in the Mire a player without an
  item from #cobblesurvival:mire_protection gets Poison every 5 seconds,
  with a one-time warning. Potions, milk and golden apples still help.
- Mire Waders: KubeJS leather boots, dropped by the Westmarch and Verdant
  Jungle guardians (the zones before the Mire), per Adrian's boss rule.
- Self-test mire_poisons_without_waders.
- Allow-list Cobblemon's misspelt jigsaw state in
  habitats/parched_peat_bog7.nbt, which test players now reach.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
claudeandClaude Opus 5.5 committed 2026-10-30 01:46:16 +01:00
1 parent 73fe71918f
commit d105f6757d
19 files changed
+197 -15

No files matched your search

@@ -1,7 +1,7 @@
{
"_comment": "Generated by tools/zone_spawns.py from world/zones.json; do not edit by hand.",
"zone": "frostpeak",
"pokemon": "tyranitar level=60 uncatchable=yes",
"pokemon": "archaludon level=60 uncatchable=yes",
"scale": 1.5,
"drops": [
{
@@ -4,9 +4,13 @@
"pokemon": "tangrowth level=35 uncatchable=yes",
"scale": 1.5,
"drops": [
{
"item": "cobblesurvival:mire_waders",
"count": 1
},
{
"item": "cobblemon:pecha_berry",
"count": 8
"count": 4
},
{
"item": "cobblemon:exp_candy_m",
@@ -1,12 +1,16 @@
{
"_comment": "Generated by tools/zone_spawns.py from world/zones.json; do not edit by hand.",
"zone": "westmarch",
"pokemon": "carbink level=30 uncatchable=yes",
"pokemon": "pinsir level=30 uncatchable=yes",
"scale": 1.5,
"drops": [
{
"item": "cobblesurvival:mire_waders",
"count": 1
},
{
"item": "cobblemon:pecha_berry",
"count": 8
"count": 4
},
{
"item": "cobblemon:exp_candy_m",
@@ -0,0 +1,6 @@
{
"replace": false,
"values": [
"cobblesurvival:mire_waders"
]
}
@@ -0,0 +1,10 @@
{
"_comment": "Phase 3 hazard: in the Mangrove Mire a player keeps getting Poison unless they wear an item from the protection tag (Mire Waders). Placeholder values, docs/balance.md.",
"zone": "mire",
"effect": "minecraft:poison",
"amplifier": 0,
"duration_ticks": 140,
"every_ticks": 100,
"protection": "cobblesurvival:mire_protection",
"warning": "message.cobblesurvival.hazard.mire"
}
+2 -1
View File
@@ -25,7 +25,8 @@ Every tunable lives in a config file or in this table. **Placeholder** means gue
| Goat Fur / Hoglin Hide recipes | 8 wool + 1 Heat Rock → 8 Goat Fur; 8 leather + 1 Icy Rock → 8 Hoglin Hide (a set takes 24) | Placeholder | `kubejs/server_scripts/insulation.js` | Heat and Icy Rocks come from the guardians before Frostpeak and Sunscorch (archaeology finds too) |
| Guardian respawn | 5 hours of game time after being beaten | Decided (Adrian, 2026-10-29) | `world/zones.json` → `guardian_respawn_hours` | Fixed spots |
| Guardian strength | strongest Pokémon spawning in the zone, band top + 5 levels, 1.5× size, uncatchable | Placeholder | `tools/zone_spawns.py` (`GUARDIAN_*`), `world/zones.json` → `guardian.species` | |
| Guardian drops | Button Isle 10 Poké Balls + 4 Potions; Southwold 6 Great Balls + 4 Exp. Candy S; Westmarch and Jungle 8 Pecha Berries + 2 Exp. Candy M; Mire and Highlands 3 Icy Rocks + 4 Super Potions; Sunscorch 3 Heat Rocks + 2 Exp. Candy L; Frostpeak and Deep Dark 3 Icy Rocks + 2 Exp. Candy L; Deep Sea Mystic Water + 3 Exp. Candy M | Placeholder | `world/zones.json` → `guardian.drops` | 3 rocks make one full fur or hide set |
| Guardian drops | Button Isle 10 Poké Balls + 4 Potions; Southwold 6 Great Balls + 4 Exp. Candy S; Westmarch and Jungle Mire Waders + 4 Pecha Berries + 2 Exp. Candy M; Mire and Highlands 3 Icy Rocks + 4 Super Potions; Sunscorch 3 Heat Rocks + 2 Exp. Candy L; Frostpeak and Deep Dark 3 Icy Rocks + 2 Exp. Candy L; Deep Sea Mystic Water + 3 Exp. Candy M | Placeholder | `world/zones.json` → `guardian.drops` | 3 rocks make one full fur or hide set |
| Mire poison | Poison I for 7 s, renewed every 5 s while in the Mire without Mire Waders | Placeholder | `datapacks/core/data/cobblesurvival/zone_hazards/mire.json` | Vanilla Poison stops at half a heart |
| Healing while sleeping | off (`healPercent` 0) | Decided (roadmap) | `config/cobblemon/main.json` | |
| Loot container refresh | 1 hour (72,000 ticks of game time) after first opened, all containers | Decided (Adrian, 2026-10-29) | `config/lootr-common.toml` | Tiering by structure/biome later |
| Gamerules in the template | `doMobSpawning false` | Decided (Adrian, 2026-10-28; may be reverted later) | `world/world.json` → `gamerules` | Pokémon spawning is Cobblemon's own `doPokemonSpawning` |
+4 -1
View File
@@ -19,7 +19,9 @@ Open `build/zone-map.png` (the island coloured by zone) and read the draft in `d
- **Alpha guardians** (step 4). One per zone at a fixed spot, back 5 hours after being beaten; drops help with the next zone (`docs/zone-species.md` lists them). Automated: `guardian_appears_and_returns`.
Still to come: the Mire's poison (and the wading gear the Westmarch and Jungle guardians should drop for it), soft level pressure, flyers.
- **The Mire's poison** (step 5). Mire Waders from the Westmarch and Jungle guardians keep it off. Automated: `mire_poisons_without_waders`.
Still to come: soft level pressure, flyers.
## In-game
@@ -30,3 +32,4 @@ Still to come: the Mire's poison (and the wading gear the Westmarch and Jungle g
- [ ] **Sunscorch heat:** the desert at noon overheats you in a couple of minutes; at night it's fine. The hide set (Hoglin Hide from 8 leather + 1 Icy Rock) or a Fire Resistance potion keeps you going.
- [ ] **Lowlands:** a night in Southwold or on Button Isle without gear never gets dangerous.
- [ ] **Guardians:** `/cobblesurvival guardians` (operator) lists each zone's guardian and spot. Walk there: a big Pokémon appears, can't be caught, and beating it puts its drops in your inventory with a gold message. It's gone for 5 hours of play.
- [ ] **Mire:** walk into the Mangrove Mire: a green warning, then Poison keeps coming back. With Mire Waders on (from the Westmarch or Jungle guardian) nothing happens.
+2
View File
@@ -109,6 +109,8 @@ Order of play the bands suggest: Button Isle → Southwold → Westmarch or Jung
- Basalt Furnace got Durant, Torkoal and Heatmor pinned to it: its own lines were all big (Charizard, Turtonator, Centiskorch, …) and Cobblemon only spawns a Pokémon where its body fits, so cramped basalt caves stayed empty.
- Tested: `spawn_pools_are_zoned` (no Cobblemon default spawn is left; every entry has a zone and spawns within its band) and `spawn_survey` (a player at a point deep inside each zone is offered only that zone's spawns, and at least one), both in `tools/quick_check.py`.
**The Mire's poison** (Phase 3, step 5): while a player is in the Mangrove Mire without Mire Waders on, the companion mod gives them Poison every 5 seconds (`zone/ZoneHazards.kt`, data in `datapacks/core/data/cobblesurvival/zone_hazards/mire.json`, protection tag `#cobblesurvival:mire_protection`); the first time they walk in unprotected they get a warning. Vanilla Poison never kills, but it pins a player at half a heart, so a fall or a Pokémon's hit becomes deadly; milk, potions and golden apples still help. Mire Waders (leather boots, a KubeJS item) drop from the guardians of Westmarch and the Verdant Jungle, the zones before the Mire. Tested: `mire_poisons_without_waders`.
**Alpha guardians** (Phase 3, step 4; Adrian, 2026-10-29: fixed spots, back 5 hours after being beaten):
- One per zone except the Shallows, Lush Caves and Dripstone; the Basalt Furnace gets its boss in Phase 7. Each stands at its zone's sample point (deep inside the zone; a cave floor for the Deep Dark) and only appears while a player is within 96 blocks; it wanders no further than 24 blocks.
- The guardian is the strongest Pokémon that spawns walking (or, at sea, swimming) in its zone, five levels above the band, half again as big, and uncatchable: Pachirisu (Button Isle), Sigilyph (Southwold), Carbink (Westmarch), Tangrowth (Jungle), Arcanine (Mire), Cinderace (Highlands), Delphox (Sunscorch), Tyranitar (Frostpeak), Kingdra (Deep Sea), Dusknoir (Deep Dark). `world/zones.json` → `guardian.species` overrides the pick.
+3 -3
View File
@@ -65,12 +65,12 @@ One per zone at its sample point, uncatchable, five levels above the band; it re
| Zone | Guardian | Level | Drops |
| --- | --- | --- | --- |
| Button Isle | Pachirisu | 15 | 10× poke_ball, 4× potion |
| Frostpeak Range | Tyranitar | 60 | 3× icy_rock, 2× exp_candy_l |
| Frostpeak Range | Archaludon | 60 | 3× icy_rock, 2× exp_candy_l |
| Sunscorch | Delphox | 50 | 3× heat_rock, 2× exp_candy_l |
| Mangrove Mire | Arcanine | 43 | 3× icy_rock, 4× super_potion |
| Verdant Jungle | Tangrowth | 35 | 8× pecha_berry, 2× exp_candy_m |
| Verdant Jungle | Tangrowth | 35 | 1× mire_waders, 4× pecha_berry, 2× exp_candy_m |
| Highland Meadows | Cinderace | 40 | 3× icy_rock, 4× super_potion |
| Westmarch | Carbink | 30 | 8× pecha_berry, 2× exp_candy_m |
| Westmarch | Pinsir | 30 | 1× mire_waders, 4× pecha_berry, 2× exp_candy_m |
| Southwold | Sigilyph | 23 | 6× great_ball, 4× exp_candy_s |
| Deep Sea | Kingdra | 55 | 1× mystic_water, 3× exp_candy_m |
| Deep Dark | Dusknoir | 65 | 3× icy_rock, 2× exp_candy_l |
@@ -9,6 +9,7 @@ import dev.cobblesurvival.selftest.SelfTest
import dev.cobblesurvival.world.OnlyPokemon
import dev.cobblesurvival.zone.Guardians
import dev.cobblesurvival.zone.ZoneCondition
import dev.cobblesurvival.zone.ZoneHazards
import dev.cobblesurvival.zone.Zones
import net.minecraft.commands.Commands
import net.minecraft.core.BlockPos
@@ -57,6 +58,7 @@ class CobbleSurvival {
ZoneCondition.register()
NeoForge.EVENT_BUS.addListener(Zones::onAddReloadListeners)
Guardians.register()
ZoneHazards.register()
SelfTest.register()
}
@@ -3,20 +3,22 @@ package dev.cobblesurvival.selftest
import dev.cobblesurvival.CobbleSurvival
import dev.cobblesurvival.zone.ZoneSample
import dev.cobblesurvival.zone.Zones
import net.minecraft.gametest.framework.GameTestAssertException
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.effect.MobEffects
import net.minecraft.world.entity.EquipmentSlot
import net.minecraft.world.entity.LivingEntity
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import net.minecraft.world.level.levelgen.Heightmap
/**
* Roadmap Phase 3, hazards as gates, on the island itself (real biomes, real altitude): in Frostpeak and Sunscorch
* a player without the right gear freezes or overheats within a minute or two, while the fur (cold) or hide (heat)
* set holds them off for many minutes; leather armour alone doesn't help. The zones without a hazard are livable
* without gear by day and by night.
* without gear by day and by night. The Mangrove Mire poisons anyone without Mire Waders.
*
* Cold Sweat's insulation doesn't change what counts as too cold or too hot; it slows how fast the body drifts
* there. So the tests reset each player's core temperature to 0, wait, and turn the drift into minutes until the
@@ -42,6 +44,7 @@ object HazardTests {
SelfTestCase("cold_needs_fur", 600, ::cold, islandOnly = true, batch = "hazard_cold"),
SelfTestCase("heat_needs_hide", 600, ::heat, islandOnly = true, batch = "hazard_heat"),
SelfTestCase("lowlands_are_mild", 4000, ::lowlands, islandOnly = true, batch = "hazard_lowlands"),
SelfTestCase("mire_poisons_without_waders", 400, ::mire),
)
/** Frostpeak at noon, its warmest hour. */
@@ -120,6 +123,35 @@ object HazardTests {
}.thenSucceed()
}
/** The Mire's fumes (companion mod zone hazard) poison a player without Mire Waders, and nobody outside it. */
private fun mire(s: Scenario) {
val level = s.level
val mire = Zones.samples["mire"] ?: throw GameTestAssertException("no sample point for the Mire")
val elsewhere = Zones.samples["button_isle"] ?: throw GameTestAssertException("no sample point for Button Isle")
val waders = BuiltInRegistries.ITEM.get(ResourceLocation.fromNamespaceAndPath("cobblesurvival", "mire_waders"))
s.check(waders != Items.AIR) { "the Mire Waders item does not exist" }
val bare = s.player()
val wading = s.player().also { it.setItemSlot(EquipmentSlot.FEET, ItemStack(waders)) }
val outside = s.player()
fun surface(sample: ZoneSample): Double {
level.getChunk(sample.x shr 4, sample.z shr 4)
return level.getHeight(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, sample.x, sample.z).toDouble()
}
s.helper.startSequence()
.thenExecute {
for (player in listOf(bare, wading)) s.moveTo(player, mire.x + 0.5, surface(mire), mire.z + 0.5)
s.moveTo(outside, elsewhere.x + 0.5, surface(elsewhere), elsewhere.z + 0.5)
}
.thenIdle(220)
.thenExecute {
val poison = MobEffects.POISON
s.check(bare.hasEffect(poison)) { "a player without waders in the Mire isn't poisoned" }
s.check(!wading.hasEffect(poison)) { "a player in Mire Waders got poisoned" }
s.check(!outside.hasEffect(poison)) { "a player on Button Isle got poisoned" }
}
.thenSucceed()
}
private fun dress(player: ServerPlayer, namespace: String, pieces: List<String>) {
val slots = listOf(EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET)
for ((slot, piece) in slots.zip(pieces)) {
@@ -0,0 +1,97 @@
package dev.cobblesurvival.zone
import com.google.gson.Gson
import com.google.gson.JsonElement
import dev.cobblesurvival.CobbleSurvival
import net.minecraft.ChatFormatting
import net.minecraft.core.Holder
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.core.registries.Registries
import net.minecraft.network.chat.Component
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.server.packs.resources.ResourceManager
import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener
import net.minecraft.tags.TagKey
import net.minecraft.util.profiling.ProfilerFiller
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.effect.MobEffectInstance
import net.minecraft.world.entity.EquipmentSlot
import net.minecraft.world.item.Item
import net.neoforged.neoforge.common.NeoForge
import net.neoforged.neoforge.event.AddReloadListenerEvent
import net.neoforged.neoforge.event.tick.ServerTickEvent
import java.util.UUID
/**
* Zone hazards that Cold Sweat doesn't cover (roadmap Phase 3: "a poison swamp ... as area effects"): while a player
* is in the zone and wears nothing from the protection tag, they keep getting the effect. Potions, milk and golden
* apples still work against it. Definitions: `data/cobblesurvival/zone_hazards/<name>.json`.
*/
object ZoneHazards : SimpleJsonResourceReloadListener(Gson(), "zone_hazards") {
class Hazard(
val zone: String,
val effect: Holder<MobEffect>,
val amplifier: Int,
val durationTicks: Int,
val everyTicks: Int,
val protection: TagKey<Item>,
val warning: String?,
)
var hazards: List<Hazard> = emptyList()
private set
private val warned = HashMap<UUID, String>()
fun register() {
NeoForge.EVENT_BUS.addListener<AddReloadListenerEvent> { it.addListener(this) }
NeoForge.EVENT_BUS.addListener(::onServerTick)
}
override fun apply(files: Map<ResourceLocation, JsonElement>, resources: ResourceManager, profiler: ProfilerFiller) {
hazards = files.mapNotNull { (id, element) ->
val json = element.asJsonObject
val effectId = ResourceLocation.parse(json.get("effect").asString)
val effect = BuiltInRegistries.MOB_EFFECT.getHolder(effectId).orElse(null)
if (effect == null) {
CobbleSurvival.LOGGER.error("Zone hazard {}: unknown effect {}", id, effectId)
return@mapNotNull null
}
Hazard(
zone = json.get("zone").asString,
effect = effect,
amplifier = json.get("amplifier")?.asInt ?: 0,
durationTicks = json.get("duration_ticks").asInt,
everyTicks = json.get("every_ticks").asInt,
protection = TagKey.create(Registries.ITEM, ResourceLocation.parse(json.get("protection").asString)),
warning = json.get("warning")?.asString,
)
}
CobbleSurvival.LOGGER.info("Loaded {} zone hazards", hazards.size)
}
private fun onServerTick(event: ServerTickEvent.Post) {
if (hazards.isEmpty()) return
val tick = event.server.tickCount
for (player in event.server.playerList.players) {
if (player.isCreative || player.isSpectator) continue
val zone = Zones.at(player.serverLevel(), player.blockPosition())?.id
if (zone == null || hazards.none { it.zone == zone }) {
warned.remove(player.uuid)
continue
}
for (hazard in hazards) {
if (hazard.zone != zone || tick % hazard.everyTicks != 0 || isProtected(player, hazard)) continue
player.addEffect(MobEffectInstance(hazard.effect, hazard.durationTicks, hazard.amplifier))
if (hazard.warning != null && warned[player.uuid] != zone) {
warned[player.uuid] = zone
player.displayClientMessage(Component.translatable(hazard.warning).withStyle(ChatFormatting.DARK_GREEN), true)
}
}
}
}
fun isProtected(player: ServerPlayer, hazard: Hazard) =
listOf(EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET)
.any { player.getItemBySlot(it).`is`(hazard.protection) }
}
@@ -2,5 +2,6 @@
"generator.cobblesurvival.island": "Cobblemon Survival Island",
"message.cobblesurvival.permadeath.lost": "%s fainted and is gone for good.",
"message.cobblesurvival.permadeath.saved": "%s's Revival Charm saved it and crumbled to dust.",
"message.cobblesurvival.guardian.beaten": "The guardian of %s is beaten! It returns in %s hours."
"message.cobblesurvival.guardian.beaten": "The guardian of %s is beaten! It returns in %s hours.",
"message.cobblesurvival.hazard.mire": "The Mire's fumes are poisoning you. Mire Waders would keep them off."
}
+8
View File
@@ -40,6 +40,10 @@ hash = "ff92c8b83a303441c81835a1ab22dfc42599fd58d43a920452257bb1c6b39d9d"
file = "defaultconfigs/cobblehardcoremon/server.toml"
hash = "5e68c4bc93fcc06f85cabc6c587c1b70cb2a22da01ad9653d678ac9131c0003b"
[[files]]
file = "kubejs/assets/cobblesurvival/textures/item/mire_waders.png"
hash = "37f7469789d281cbd2ab40f483914eec29f5c82e2c3575854cd276f3296ebb24"
[[files]]
file = "kubejs/assets/cobblesurvival/textures/item/revival_charm.png"
hash = "084c0a7b17bb429c317843a7c45096dc8cb1ab61c748293475c1f3d6a5984086"
@@ -56,6 +60,10 @@ hash = "026dc5ab98cb13c906d5b6974ea7624014e804c58141f01ec57e077e1e8bb339"
file = "kubejs/server_scripts/revival_charm.js"
hash = "68c23da4ab4daa0687f0c367c4cf837a9c26c18c4034bd3fbdedbcc6d3b716f6"
[[files]]
file = "kubejs/startup_scripts/mire_waders.js"
hash = "946be2db734646167fe55ba83b57c6f310ba40313a18a959b84c0952e2302112"
[[files]]
file = "kubejs/startup_scripts/revival_charm.js"
hash = "a93eb4401ca303c9aab15ef8cd1edcb34cc7242cbb3501e6703f32e9b3f57e2b"
Binary file not shown.

After

Width:  |  Height:  |  Size: 159 B

@@ -0,0 +1,9 @@
// Phase 3: the Mangrove Mire's fumes poison anyone not wearing these (companion mod, zone hazard
// datapacks/core/data/cobblesurvival/zone_hazards/mire.json). The guardians of Westmarch and the Verdant
// Jungle drop them: what helps with a zone drops from the boss of the zone before it (Adrian).
StartupEvents.registry('item', event => {
event.create('cobblesurvival:mire_waders', 'boots')
.material('minecraft:leather')
.displayName('Mire Waders')
.tooltip("Keeps the Mangrove Mire's poison off while worn.")
})
+1 -1
View File
@@ -6,7 +6,7 @@ pack-format = "packwiz:1.1.0"
[index]
file = "index.toml"
hash-format = "sha256"
hash = "090cc213fdd22bcaf74234d279c438a3aff08b9eb87b169802f66ae33b9b672f"
hash = "bdca52cbc4ab217c9ced202856d5be6a5af4fb4f21c07ac5ae2516d4b44ed037"
[versions]
minecraft = "1.21.1"
+3
View File
@@ -7,3 +7,6 @@ Failed to parse jigsaw replacement state 'minecraft:dirt_block'
# Cobblemon 1.8.4 logs this at ERROR on every player's first join (no save files yet, so the
# backup is missing too) and starts them with empty data. The self-tests join fresh players.
\.old File \S+ for [0-9a-f-]+ is corrupt or missing\. Data is lost
# Cobblemon 1.8.4 bug: habitats/parched_peat_bog7.nbt has a jigsaw block whose final state is the misspelt
# "minecraft:strucutre_void". Logged at worldgen; the piece still generates (as air there).
Failed to parse jigsaw replacement state 'minecraft:strucutre_void'
+2 -2
View File
@@ -22,7 +22,7 @@
"rule": {"biomes": ["minecraft:mangrove_swamp"]}
},
{
"id": "jungle", "guardian": {"drops": [["cobblemon:pecha_berry", 8], ["cobblemon:exp_candy_m", 2]]}, "lines": 30, "name": "Verdant Jungle", "levels": [18, 30], "hazard": "none (warm and humid, below Cold Sweat's damage)", "color": "#5a8f1e",
"id": "jungle", "guardian": {"drops": [["cobblesurvival:mire_waders", 1], ["cobblemon:pecha_berry", 4], ["cobblemon:exp_candy_m", 2]]}, "lines": 30, "name": "Verdant Jungle", "levels": [18, 30], "hazard": "none (warm and humid, below Cold Sweat's damage)", "color": "#5a8f1e",
"rule": {"biomes": ["minecraft:jungle"]}
},
{
@@ -30,7 +30,7 @@
"rule": {"region": {"z_max": 0, "x_min": -250}}
},
{
"id": "westmarch", "guardian": {"drops": [["cobblemon:pecha_berry", 8], ["cobblemon:exp_candy_m", 2]]}, "lines": 35, "name": "Westmarch", "levels": [15, 25], "hazard": "none", "color": "#2f8f4e",
"id": "westmarch", "guardian": {"drops": [["cobblesurvival:mire_waders", 1], ["cobblemon:pecha_berry", 4], ["cobblemon:exp_candy_m", 2]]}, "lines": 35, "name": "Westmarch", "levels": [15, 25], "hazard": "none", "color": "#2f8f4e",
"rule": {"region": {"z_max": 0, "x_max": -250}}
},
{