"""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)}