Cobblemon Survival Pack: phases 0-2 and Phase 3 zones and hazards

Free NeoForge 1.21.1 modpack on Cobblemon 1.8.4 built with packwiz, plus
the Kotlin companion mod (cobblesurvival) and its built-in data pack.

- Phase 0: packwiz pack with Modrinth/CurseForge cross-links, build and
  export tooling, Prism instance sync, test server with smoke checks.
- Phase 1: Poke Ball shaped island (WorldGen Editor preset, seed isle-26),
  Adrian's biome list, no natural ore, structures without ore or vanilla
  mobs, no Nether portals, world template builder and world report.
- Phase 2: permadeath (CobbleHardcoreMon plus the companion mod's gap
  fixes), Revival Charm, expensive Healing Machine, Lootr refreshing
  loot, in-game self-tests on Minecraft's GameTest framework.
- Phase 3 so far: 14 zones with their own spawn pools and level bands
  (tools/zone_layout.py, tools/zone_spawns.py, "zone" spawn condition),
  Cold Sweat heat and cold as gates with boss-drop insulation recipes,
  spawn survey and hazard self-tests in tools/quick_check.py.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
claudeandClaude Opus 5.5 committed 2026-10-29 19:01:21 +01:00
commit 398301f0be
232 files changed
+72665

No files matched your search

+580
View File
@@ -0,0 +1,580 @@
"""What can exist on the island: dimensions, biomes, structures, natural blocks, mobs.
Everything is derived from data: world/biomes.json and the world preset for
biomes and dimensions, structure and structure-set JSON for structures, biome
feature lists and NeoForge biome modifiers for natural blocks, biome spawners
and structure spawn overrides for mobs. Minecraft and Cobblemon decide a few
things in code; those are the small tables below, each saying where it is from.
Statuses: "available" (on the island), "conditional" (needs something that may
not exist, e.g. the End or a stronghold inside the border), "unavailable".
"""
import json
import re
from . import nbt
from .datasource import ROOT, norm_id
RANK = {"unavailable": 0, "conditional": 1, "available": 2}
END_BIOMES = ["minecraft:the_end", "minecraft:end_highlands", "minecraft:end_midlands",
"minecraft:small_end_islands", "minecraft:end_barrens"]
NETHER_BIOMES = ["minecraft:nether_wastes", "minecraft:soul_sand_valley", "minecraft:crimson_forest",
"minecraft:warped_forest", "minecraft:basalt_deltas"]
# Cobblemon 1.8.4 adds these placed features from code (CobblemonPlacedFeatures.register()),
# each to the biomes of one tag. None = all biomes; the feature filters itself.
COBBLEMON_CODED_FEATURES = {
"apricorn_trees": ["#cobblemon:has_feature/apricorns_dense", "#cobblemon:has_feature/apricorns_normal",
"#cobblemon:has_feature/apricorns_sparse"], # ApricornTreeFeature checks these
"saccharine_tree": ["#cobblemon:has_feature/saccharine_tree"],
"mints": None, "medicinal_leek": None,
"big_root": ["#minecraft:is_overworld"], "revival_herb": ["#cobblemon:has_feature/revival_herbs"],
"berry_groves": ["#minecraft:is_overworld"], "type_gems": ["#minecraft:is_overworld"],
"swamp_grains": ["#cobblemon:is_swamp"], "plains_grains": ["#cobblemon:is_plains"],
"galarica_nuts": ["#cobblemon:is_beach"], "habitats/treasure_hoard": ["#minecraft:is_overworld"],
"habitats/abandoned_fortress": ["#minecraft:is_nether"], "habitats/ancient_wellsprings": ["#cobblemon:is_lush"],
"habitats/chorus_briars": ["#cobblemon:is_end"], "habitats/deep_roots": ["#cobblemon:is_lush"],
"habitats/earthen_hives": ["#cobblemon:is_dripstone"], "habitats/exposed_geodes": ["#minecraft:is_overworld"],
"habitats/lost_ruins": ["#minecraft:is_overworld"], "habitats/quartz_spikes": ["#cobblemon:nether/is_wasteland"],
"habitats/rocky_tidepools": ["#cobblemon:is_stony_beach"], "habitats/root_nurseries": ["#cobblemon:is_swamp"],
"habitats/sandy_tidepools": ["#cobblemon:is_beach"], "habitats/skeletal_fall": ["#cobblemon:nether/is_soul_sand"],
"habitats/strange_crimson_fungal_dwellings": ["#cobblemon:nether/is_wasteland"],
"habitats/strange_warped_fungal_dwellings": ["#cobblemon:nether/is_wasteland"],
"habitats/volcanic_plumes": ["#minecraft:is_nether"], "habitats/rocky_outcrops": ["#cobblemon:is_mountain"],
"fossils/prehistoric_birch_tree": ["#cobblemon:has_block/birch_log"],
"fossils/prehistoric_dripstone_oasis": ["#cobblemon:is_dripstone"],
"fossils/prehistoric_enhydro_agate": ["#cobblemon:is_lush"],
"fossils/prehistoric_eroded_pillar": ["#cobblemon:has_block/red_sand"],
"fossils/prehistoric_frozen_pond": ["#cobblemon:is_glacial"],
"fossils/prehistoric_frozen_spike": ["#cobblemon:is_glacial"],
"fossils/prehistoric_hydrothermal_vents": ["#cobblemon:is_temperate_ocean"],
"fossils/prehistoric_lush_den": ["#cobblemon:is_jungle"], "fossils/prehistoric_mossy_pond": ["#cobblemon:is_lush"],
"fossils/prehistoric_mud_pit": ["#cobblemon:is_jungle"], "fossils/prehistoric_oak_tree": ["#cobblemon:has_block/oak_log"],
"fossils/prehistoric_powdered_deposit": ["#cobblemon:is_snowy"],
"fossils/prehistoric_rooted_pits": ["#cobblemon:is_swamp"], "fossils/prehistoric_sandy_den": ["#cobblemon:has_block/sand"],
"fossils/prehistoric_spruce_tree": ["#cobblemon:has_block/spruce_log"],
"fossils/prehistoric_submerged_impact": ["#cobblemon:is_temperate_ocean"],
"fossils/prehistoric_submerged_spike": ["#cobblemon:is_frozen_ocean"],
"fossils/prehistoric_preserved_skeleton": ["#cobblemon:is_frozen_ocean"],
"fossils/prehistoric_sunscorched_den": ["#cobblemon:has_block/red_sand"],
"fossils/prehistoric_sunscorched_remains": ["#cobblemon:has_block/red_sand"],
"fossils/prehistoric_suspicious_mounds": ["#cobblemon:is_plains"],
"fossils/prehistoric_underwater_fissure": ["#cobblemon:is_temperate_ocean"],
"fossils/prehistoric_vibrant_hydrothermal_vents": ["#cobblemon:is_warm_ocean"],
"ruins/crumbling_arch": ["#minecraft:is_overworld"], "ruins/deep_crypt": ["#cobblemon:is_deep_dark"],
"ruins/rooted_arch": ["#cobblemon:is_lush"],
}
# Blocks placed by feature (or tree decorator) code that do not appear in the feature's JSON,
# keyed by feature type. Lists may hold #block tags; a string is a regex over block ids.
CODE_FEATURE_BLOCKS = {
"cobblemon:apricorn_tree": ["cobblemon:apricorn_log", "cobblemon:apricorn_leaves"],
"cobblemon:saccharine_tree": ["cobblemon:saccharine_log", "cobblemon:saccharine_leaves"],
"cobblemon:type_gem": r"cobblemon:[a-z]+_gem_cluster", # TypeGemFeature picks a random gem cluster
"minecraft:glowstone_blob": ["minecraft:glowstone"],
"minecraft:basalt_columns": ["minecraft:basalt"], "minecraft:basalt_pillar": ["minecraft:basalt"],
"minecraft:ice_spike": ["minecraft:packed_ice"], "minecraft:iceberg": ["minecraft:packed_ice", "minecraft:blue_ice"],
"minecraft:blue_ice": ["minecraft:blue_ice"],
"minecraft:end_spike": ["minecraft:obsidian", "minecraft:iron_bars"], "minecraft:end_island": ["minecraft:end_stone"],
"minecraft:chorus_plant": ["minecraft:chorus_plant", "minecraft:chorus_flower"],
"minecraft:kelp": ["minecraft:kelp", "minecraft:kelp_plant"],
"minecraft:seagrass": ["minecraft:seagrass", "minecraft:tall_seagrass"], "minecraft:sea_pickle": ["minecraft:sea_pickle"],
"minecraft:coral_tree": ["#minecraft:coral_blocks", "#minecraft:corals", "#minecraft:wall_corals", "minecraft:sea_pickle"],
"minecraft:coral_claw": ["#minecraft:coral_blocks", "#minecraft:corals", "#minecraft:wall_corals", "minecraft:sea_pickle"],
"minecraft:coral_mushroom": ["#minecraft:coral_blocks", "#minecraft:corals", "#minecraft:wall_corals", "minecraft:sea_pickle"],
"minecraft:vines": ["minecraft:vine"], "minecraft:bamboo": ["minecraft:bamboo", "minecraft:podzol"],
"minecraft:monster_room": ["minecraft:cobblestone", "minecraft:mossy_cobblestone", "minecraft:spawner"],
"minecraft:desert_well": ["minecraft:sandstone", "minecraft:sandstone_slab", "minecraft:suspicious_sand"],
"minecraft:dripstone_cluster": ["minecraft:dripstone_block", "minecraft:pointed_dripstone"],
"minecraft:pointed_dripstone": ["minecraft:pointed_dripstone"], "minecraft:large_dripstone": ["minecraft:dripstone_block"],
"minecraft:sculk_patch": ["minecraft:sculk", "minecraft:sculk_vein", "minecraft:sculk_sensor",
"minecraft:sculk_shrieker", "minecraft:sculk_catalyst"],
"minecraft:underwater_magma": ["minecraft:magma_block"], "minecraft:freeze_top_layer": ["minecraft:ice", "minecraft:snow"],
"minecraft:beehive": ["minecraft:bee_nest"], "minecraft:cocoa": ["minecraft:cocoa"],
"minecraft:leave_vine": ["minecraft:vine"], "minecraft:trunk_vine": ["minecraft:vine"],
}
# Loot tables that Minecraft's structure and feature code references directly (not via templates).
CODE_LOOT = {
"minecraft:desert_pyramid": ["chests/desert_pyramid", "archaeology/desert_pyramid"],
"minecraft:jungle_temple": ["chests/jungle_temple", "chests/jungle_temple_dispenser"],
"minecraft:igloo": ["chests/igloo_chest"],
"minecraft:shipwreck": ["chests/shipwreck_map", "chests/shipwreck_supply", "chests/shipwreck_treasure"],
"minecraft:ocean_ruin": ["chests/underwater_ruin_big", "chests/underwater_ruin_small"],
"minecraft:buried_treasure": ["chests/buried_treasure"],
"minecraft:stronghold": ["chests/stronghold_corridor", "chests/stronghold_crossing", "chests/stronghold_library"],
"minecraft:mineshaft": ["chests/abandoned_mineshaft"],
"minecraft:fortress": ["chests/nether_bridge"],
"minecraft:woodland_mansion": ["chests/woodland_mansion"],
"minecraft:end_city": ["chests/end_city_treasure"],
"minecraft:ruined_portal": ["chests/ruined_portal"],
# placed features
"minecraft:monster_room": ["chests/simple_dungeon"], "minecraft:monster_room_deep": ["chests/simple_dungeon"],
"minecraft:desert_well": ["archaeology/desert_well"],
}
# Notable blocks that code-built structures place (only those that matter as a resource).
CODE_STRUCTURE_BLOCKS = {
"minecraft:ocean_monument": ["minecraft:gold_block", "minecraft:wet_sponge", "minecraft:sea_lantern",
"minecraft:prismarine", "minecraft:prismarine_bricks", "minecraft:dark_prismarine"],
"minecraft:mineshaft": ["minecraft:rail", "minecraft:cobweb", "minecraft:oak_planks", "minecraft:oak_fence"],
"minecraft:desert_pyramid": ["minecraft:tnt", "minecraft:suspicious_sand"],
"minecraft:stronghold": ["minecraft:end_portal_frame", "minecraft:bookshelf", "minecraft:iron_bars"],
"minecraft:jungle_temple": ["minecraft:dispenser", "minecraft:tripwire_hook", "minecraft:redstone_wire"],
"minecraft:ocean_ruin": ["minecraft:suspicious_sand", "minecraft:suspicious_gravel"],
"minecraft:ruined_portal": ["minecraft:obsidian", "minecraft:crying_obsidian", "minecraft:netherrack",
"minecraft:magma_block", "minecraft:gold_block"],
}
# Mobs that Minecraft spawns from code rather than biome spawners (kind, requirement, note).
SPECIAL_MOBS = {
"minecraft:villager": ("structure", "#minecraft:village", "lives in villages"),
"minecraft:iron_golem": ("structure", "#minecraft:village", "spawns in villages"),
"minecraft:cat": ("structure", "#minecraft:village", "strays in villages"),
"minecraft:wandering_trader": ("overworld", None, "visits players"),
"minecraft:trader_llama": ("overworld", None, "comes with the wandering trader"),
"minecraft:phantom": ("overworld", None, "attacks players who have not slept for three nights"),
"minecraft:pillager": ("overworld", None, "patrols"),
"minecraft:vindicator": ("structure", "#minecraft:village", "raids"),
"minecraft:evoker": ("structure", "#minecraft:village", "raids"),
"minecraft:ravager": ("structure", "#minecraft:village", "raids"),
"minecraft:witch": ("structure", "#minecraft:village", "raids"),
"minecraft:skeleton_horse": ("overworld", None, "skeleton traps in thunderstorms"),
"minecraft:zombie_villager": ("overworld", None, "zombie variant"),
"minecraft:warden": ("biome", "minecraft:deep_dark", "summoned by sculk shriekers"),
"minecraft:elder_guardian": ("structure", "minecraft:monument", "ocean monuments"),
"minecraft:ender_dragon": ("biome", "minecraft:the_end", "the End"),
"minecraft:shulker": ("structure", "minecraft:end_city", "End cities"),
"minecraft:breeze": ("structure", "minecraft:trial_chambers", "trial spawners"),
"minecraft:sniffer": ("item", "minecraft:sniffer_egg", "hatched from a sniffer egg"),
}
class Island:
def __init__(self, stack, tags, log=print):
self.stack, self.tags, self.log = stack, tags, log
self.btags = tags["worldgen/biome"]
self.notes = []
self._load_biomes()
self._load_structures()
self._load_features()
self._load_mobs()
# --- dimensions and biomes -----------------------------------------------------------
def _load_biomes(self):
cfg = json.loads((ROOT / "world/biomes.json").read_text())
world = json.loads((ROOT / "world/world.json").read_text())
self.world_type = world.get("level_type", "minecraft:normal")
preset = self.stack.get("worldgen/world_preset", self.world_type) or {}
dims = preset.get("dimensions", {})
self.dimensions = sorted(dims) or ["minecraft:overworld", "minecraft:the_nether", "minecraft:the_end"]
water = [norm_id(b) for b in cfg.get("water", [])]
allowed = [norm_id(b) for b in cfg.get("allowed", [])]
preset_biomes = set(re.findall(r'"biome":\s*"([^"]+)"', json.dumps(dims.get("minecraft:overworld", {}))))
extra = preset_biomes - set(allowed) - set(water)
if extra:
self.notes.append(f"world preset lists biomes not in world/biomes.json: {sorted(extra)}")
added_caves = {norm_id(a["biome"]) for a in cfg.get("add", [])
if a.get("parameters", {}).get("depth", [0, 0])[0] > 0}
caves = self.btags.resolve_all(["#c:is_underground", "#c:is_cave"]) | added_caves
self.biomes = {}
for b in allowed + water:
kind = "water" if b in water else "cave" if b in caves else "surface"
self.biomes[b] = {"dimension": "minecraft:overworld", "kind": kind, "status": "available"}
if "minecraft:the_end" in dims or not dims:
for b in END_BIOMES:
self.biomes[b] = {"dimension": "minecraft:the_end", "kind": "end", "status": "conditional",
"reason": "the End: needs a stronghold portal and Eyes of Ender"}
if "minecraft:the_nether" in dims or not dims:
for b in NETHER_BIOMES:
self.biomes.setdefault(b, {"dimension": "minecraft:the_nether", "kind": "nether", "status": "available"})
self.overworld = {b for b, v in self.biomes.items() if v["dimension"] == "minecraft:overworld"}
self.has_nether = "minecraft:the_nether" in dims
def biome_status(self, biomes):
best = "unavailable"
for b in biomes:
s = self.biomes.get(b, {}).get("status", "unavailable")
if RANK[s] > RANK[best]:
best = s
return best
def island_biomes(self, biomes):
return sorted(b for b in biomes if b in self.biomes)
# --- structures ------------------------------------------------------------------------
def _load_structures(self):
in_set, rings = set(), set()
for sid, rel in self.stack.ids("worldgen/structure_set").items():
d = self.stack.json(rel) or {}
members = [s["structure"] for s in d.get("structures", [])] if isinstance(d.get("structures"), list) else []
in_set.update(norm_id(m) for m in members)
if d.get("placement", {}).get("type") == "minecraft:concentric_rings":
rings.update(norm_id(m) for m in members)
stags = self.tags["worldgen/structure"]
self.structures = {}
for sid, rel in self.stack.ids("worldgen/structure").items():
d = self.stack.json(rel) or {}
biomes = self.btags.resolve_all(d.get("biomes", []))
here = self.island_biomes(biomes)
status, reason = self.biome_status(here), ""
if sid not in in_set:
status, reason = "unavailable", "not in any structure set (never generates)"
elif not here:
reason = "its biomes are not on the island"
elif sid in rings and status == "available":
status, reason = "conditional", "generates in rings 1,280+ blocks from the origin; may be outside the world border"
elif status == "conditional":
reason = "only in the End"
self.structures[sid] = {"type": d.get("type"), "biomes": here, "status": status, "reason": reason,
"tags": [], "loot": set(), "blocks": set(), "mobs": set(), "data": d}
for tag in stags.all_tags():
for sid in stags.resolve(tag):
if sid in self.structures:
self.structures[sid]["tags"].append(tag)
for sid, s in self.structures.items():
if s["status"] == "unavailable":
continue
contents = self.walk(s["data"], start=sid)
s["loot"] |= contents["loot"]
s["blocks"] |= contents["blocks"]
s["mobs"] |= contents["entities"]
s["loot"] |= {f"minecraft:{p}" for p in CODE_LOOT.get(s["type"], [])}
if s["type"] == "minecraft:ocean_ruin":
s["loot"].add(f"minecraft:archaeology/ocean_ruin_{s['data'].get('biome_temp', 'cold')}")
s["blocks"] |= set(CODE_STRUCTURE_BLOCKS.get(sid, []) + CODE_STRUCTURE_BLOCKS.get(s["type"], []))
for cat in s["data"].get("spawn_overrides", {}).values():
s["mobs"] |= {norm_id(sp["type"]) for sp in cat.get("spawns", [])}
def structure_status(self, refs):
"""Best status over structure ids or #tags (for spawn conditions and mob rules)."""
stags = self.tags["worldgen/structure"]
best, which = "unavailable", []
for ref in refs:
for sid in stags.resolve(ref):
s = self.structures.get(sid)
if s and RANK[s["status"]] > 0:
which.append(sid)
if RANK[s["status"]] > RANK[best]:
best = s["status"]
return best, sorted(set(which))
def walk(self, obj, start=""):
"""Follow template pools, processor lists, NBT templates and features from a JSON object;
collect loot tables, palette blocks and entity ids. Unknown strings are ignored."""
out = {"loot": set(), "blocks": set(), "entities": set()}
replaced = set() # template marker blocks that rule processors always swap out
rules = [] # (input block or None, output blocks) of rule processors
loot_ids = self.stack.ids("loot_table")
seen = set()
todo = list(_strings(obj))
while todo:
s = todo.pop()
if not isinstance(s, str) or ":" not in s and "/" not in s or s in seen:
continue
seen.add(s)
rid = norm_id(s)
if rid in loot_ids:
out["loot"].add(rid)
continue
for kind in ("worldgen/template_pool", "worldgen/processor_list", "worldgen/configured_feature",
"worldgen/placed_feature"):
d = self.stack.get(kind, rid)
if d is not None:
todo.extend(_strings(d))
if kind.endswith("feature"):
out["blocks"] |= _block_names(d)
elif kind.endswith("processor_list"):
found_rules, removed = _processor_blocks(d)
rules.extend(found_rules)
replaced |= removed
nbt_rel = self.stack.resource("structure", rid, ext=".nbt")
if self.stack.exists(nbt_rel):
try:
tpl = nbt.load(self.stack.read(nbt_rel))
except Exception as e: # a broken template must not stop the guide
self.notes.append(f"unreadable structure template {nbt_rel}: {e}")
continue
palettes = tpl.get("palettes") or [tpl.get("palette", [])]
for pal in palettes:
out["blocks"] |= {norm_id(p["Name"]) for p in pal if "Name" in p}
for b in tpl.get("blocks", []):
if "nbt" in b:
todo.extend(nbt.strings(b["nbt"]))
for e in tpl.get("entities", []):
eid = e.get("nbt", {}).get("id")
if eid:
out["entities"].add(norm_id(eid))
grew = True
while grew: # a rule only places its output where its input block exists
before = len(out["blocks"])
for inp, outs in rules:
if inp is None or inp in out["blocks"]:
out["blocks"] |= outs
grew = len(out["blocks"]) > before
out["blocks"] = {b for b in out["blocks"] - replaced if b not in (
"minecraft:air", "minecraft:structure_void", "minecraft:jigsaw", "minecraft:cave_air")}
return out
# --- natural blocks (features + terrain) -------------------------------------------------
def _load_features(self):
biome_features = {b: set() for b in self.biomes}
for b in self.biomes:
d = self.stack.get("worldgen/biome", b) or {}
for step in d.get("features", []):
biome_features[b].update(norm_id(f) for f in step if isinstance(f, str))
for name, refs in COBBLEMON_CODED_FEATURES.items():
# None means every biome of every dimension; the feature's own checks (dirt, water) keep it
# out of the End, so only overworld biomes are counted.
for b in (self.btags.resolve_all(refs) if refs else self.overworld) & set(self.biomes):
biome_features[b].add(f"cobblemon:{name}")
self.biome_mods = []
removed = set()
for mid, rel in self.stack.ids("neoforge/biome_modifier").items():
d = self.stack.json(rel) or {}
t = d.get("type")
if t not in ("neoforge:add_features", "neoforge:remove_features", "neoforge:add_spawns", "neoforge:remove_spawns"):
continue
biomes = self.holder_set(d.get("biomes"), self.btags)
self.biome_mods.append((mid, t, biomes, d))
if t.endswith("_features"):
feats = self.holder_set(d.get("features"), self.tags["worldgen/placed_feature"])
for b in biomes & set(self.biomes):
if t == "neoforge:add_features":
biome_features[b] |= feats
else:
removed |= biome_features[b] & feats
biome_features[b] -= feats
self.biome_features = biome_features
# Blocks that would generate if the pack had not switched their features off (ores):
# features removed by a biome modifier, and placed features overridden to zero placements.
for fid in self.stack.ids("worldgen/placed_feature"):
d = self.stack.get("worldgen/placed_feature", fid) or {}
if any(p.get("type") == "minecraft:count" and p.get("count") == 0 for p in d.get("placement", [])):
removed.add(fid)
self.removed_blocks = {}
for fid in removed:
for blk in self._feature_blocks(fid, ignore_zero=True)[0]:
self.removed_blocks.setdefault(blk, set()).add(fid)
self.natural = {} # block -> {"biomes": set, "via": set}
self.feature_loot = {} # loot table -> {"biomes", "via"}
feature_cache = {}
for b, feats in biome_features.items():
for f in feats:
if f not in feature_cache:
feature_cache[f] = self._feature_blocks(f)
blocks, filt, loot = feature_cache[f]
if filt and not filt(b):
continue
for blk in blocks:
n = self.natural.setdefault(blk, {"biomes": set(), "via": set()})
n["biomes"].add(b)
n["via"].add(f)
for lt in loot:
n = self.feature_loot.setdefault(lt, {"biomes": set(), "via": set()})
n["biomes"].add(b)
n["via"].add(f)
# terrain: whatever the noise settings place (stone, deepslate, sand, terracotta, ...)
for dim, biomes in (("minecraft:overworld", self.overworld),
("minecraft:the_end", {b for b in self.biomes if self.biomes[b]["kind"] == "end"})):
preset = self.stack.get("worldgen/world_preset", self.world_type) or {}
settings = preset.get("dimensions", {}).get(dim, {}).get("generator", {}).get("settings")
d = settings if isinstance(settings, dict) else self.stack.get("worldgen/noise_settings", settings or "")
for blk in _block_names(d or {}):
n = self.natural.setdefault(blk, {"biomes": set(), "via": set()})
n["biomes"] |= biomes
n["via"].add("terrain")
for blk in ("minecraft:bedrock", "minecraft:water", "minecraft:lava", "minecraft:grass_block", "minecraft:dirt"):
self.natural.setdefault(blk, {"biomes": set(self.overworld), "via": {"terrain"}})
for sid, s in self.structures.items():
if RANK[s["status"]] == 0:
continue
for blk in s["blocks"]:
n = self.natural.setdefault(blk, {"biomes": set(), "via": set()})
n["via"].add(f"structure:{sid}")
def _feature_blocks(self, fid, ignore_zero=False):
"""(blocks, biome filter, loot tables) for a placed feature, or nothing if it was zeroed."""
d = self.stack.get("worldgen/placed_feature", fid)
if d is None:
return set(), None, set()
placement = d.get("placement", [])
if not ignore_zero and any(p.get("type") == "minecraft:count" and p.get("count") == 0 for p in placement):
return set(), None, set()
included, excluded = self._biome_predicates(d)
blocks, loot, seen = set(), set(), set()
todo = [d.get("feature")]
while todo:
f = todo.pop()
if isinstance(f, str):
if f in seen:
continue
seen.add(f)
f = self.stack.get("worldgen/configured_feature", f) or self.stack.get("worldgen/placed_feature", f)
if not isinstance(f, dict):
continue
blocks |= _block_names(f)
inc, exc = self._biome_predicates(f)
included = included if inc is None else (inc if included is None else included & inc)
excluded |= exc
for node in _dicts(f):
extra = CODE_FEATURE_BLOCKS.get(node.get("type"))
if isinstance(extra, list):
blocks |= self.tags["block"].resolve_all(extra)
elif isinstance(extra, str):
pat = re.compile(extra)
blocks |= {k.replace(":blocks/", ":") for k in self.stack.ids("loot_table")
if ":blocks/" in k and pat.fullmatch(k.replace(":blocks/", ":"))}
if node.get("type") == "minecraft:multiface_growth" and isinstance(node.get("config", {}).get("block"), str):
blocks.add(norm_id(node["config"]["block"]))
if f.get("type") in ("cobblemon:structure", "minecraft:fossil"):
w = self.walk(f.get("config", {}))
blocks |= w["blocks"]
loot |= w["loot"]
for s in _strings(f):
if s != fid and (self.stack.get("worldgen/configured_feature", s) is not None
or self.stack.get("worldgen/placed_feature", s) is not None):
todo.append(s)
loot |= {f"minecraft:{p}" for p in CODE_LOOT.get(fid, [])}
def filt(b):
return (included is None or b in included) and b not in excluded
return blocks, (filt if included is not None or excluded else None), loot
def _biome_predicates(self, d):
"""Cobblemon's 'cobblemon:biome' predicates (includedBiomes/excludedBiomes) anywhere in a feature."""
inc, exc = None, set()
for node in _dicts(d):
if node.get("type") == "cobblemon:biome":
if node.get("includedBiomes"):
s = self.btags.resolve_all(node["includedBiomes"])
inc = s if inc is None else inc | s
exc |= self.btags.resolve_all(node.get("excludedBiomes", []))
return inc, exc
def holder_set(self, value, tags):
"""NeoForge holder sets: id, #tag, list, or {"type": "neoforge:any"|"and"|"or"|"not", ...}."""
if value is None:
return set()
if isinstance(value, str):
return tags.resolve(value) if value.startswith("#") else {norm_id(value)}
if isinstance(value, list):
out = set()
for v in value:
out |= self.holder_set(v, tags)
return out
t = value.get("type")
if t == "neoforge:any":
return set(self.biomes) if tags is self.btags else {"*"}
if t == "neoforge:or":
return set().union(*(self.holder_set(v, tags) for v in value.get("values", [])))
if t == "neoforge:and":
sets = [self.holder_set(v, tags) for v in value.get("values", [])]
return set.intersection(*sets) if sets else set()
if t == "neoforge:not":
base = set(self.biomes) if tags is self.btags else set()
return base - self.holder_set(value.get("value"), tags)
return set()
def natural_status(self, block):
n = self.natural.get(block)
if not n:
return "unavailable", []
biomes = self.island_biomes(n["biomes"])
st = self.biome_status(biomes)
for via in n["via"]:
if via.startswith("structure:"):
s = self.structures[via[10:]]["status"]
if RANK[s] > RANK[st]:
st = s
return st, biomes
# --- mobs --------------------------------------------------------------------------------
def _load_mobs(self):
self.mobs = {} # entity -> list of {"via": text, "status": s, "biomes": [...]} (item-gated ones resolved later)
for b in self.biomes:
d = self.stack.get("worldgen/biome", b) or {}
for cat, spawns in d.get("spawners", {}).items():
for sp in spawns:
self._add_mob(norm_id(sp["type"]), "biome", b, self.biomes[b]["status"])
for mid, t, biomes, d in self.biome_mods:
if t == "neoforge:add_spawns":
sps = d.get("spawners", [])
for sp in sps if isinstance(sps, list) else [sps]:
for b in biomes & set(self.biomes):
self._add_mob(norm_id(sp["type"]), "biome", b, self.biomes[b]["status"])
for sid, s in self.structures.items():
if RANK[s["status"]]:
for m in s["mobs"]:
self._add_mob(m, "structure", sid, s["status"])
self.item_mobs = {}
for mob, (kind, req, note) in SPECIAL_MOBS.items():
if kind == "overworld":
self._add_mob(mob, "special", note, "available")
elif kind == "biome" and req in self.biomes:
self._add_mob(mob, "special", note, self.biomes[req]["status"])
elif kind == "structure":
st, which = self.structure_status([req])
if RANK[st]:
self._add_mob(mob, "special", note, st)
elif kind == "item":
self.item_mobs[mob] = (req, note)
def _add_mob(self, mob, kind, where, status):
lst = self.mobs.setdefault(mob, [])
for e in lst:
if e["kind"] == kind and kind == "biome":
e["where"].add(where)
if RANK[status] > RANK[e["status"]]:
e["status"] = status
return
lst.append({"kind": kind, "where": {where}, "status": status})
def mob_status(self, mob):
best = "unavailable"
for e in self.mobs.get(mob, []):
if RANK[e["status"]] > RANK[best]:
best = e["status"]
return best
def _strings(obj):
if isinstance(obj, str):
yield obj
elif isinstance(obj, dict):
for v in obj.values():
yield from _strings(v)
elif isinstance(obj, list):
for v in obj:
yield from _strings(v)
def _dicts(obj):
if isinstance(obj, dict):
yield obj
for v in obj.values():
yield from _dicts(v)
elif isinstance(obj, list):
for v in obj:
yield from _dicts(v)
def _processor_blocks(plist):
"""([(input block or None, output blocks)], marker blocks the list always replaces)."""
rules, replaced = [], set()
procs = plist.get("processors", []) if isinstance(plist, dict) else plist
for proc in procs if isinstance(procs, list) else []:
if proc.get("processor_type") == "minecraft:block_ignore":
replaced |= _block_names(proc)
for rule in proc.get("rules", []):
pred = rule.get("input_predicate", {})
inp = norm_id(pred["block"]) if isinstance(pred.get("block"), str) else None
if pred.get("predicate_type") == "minecraft:blockstate_match":
inp = next(iter(_block_names(pred)), None)
rules.append((inp, _block_names(rule.get("output_state", {}))))
certain = pred.get("predicate_type") in ("minecraft:block_match", "minecraft:blockstate_match") or (
pred.get("predicate_type") == "minecraft:random_block_match" and pred.get("probability", 0) >= 1)
if certain and inp:
replaced.add(inp)
return rules, replaced
def _block_names(obj):
"""Block ids a feature or surface rule places: every {"Name": ...} block state in it."""
return {norm_id(d["Name"]) for d in _dicts(obj) if isinstance(d.get("Name"), str)}