Add biome modifier to remove managed spawns

* add formatters for biome modifiers
* streamline building writers in the runner
This commit is contained in:
Andrew Miner
2025-10-15 23:21:10 -06:00
parent 919c3d7312
commit 2da1a38c69
15 changed files with 207 additions and 26 deletions
@@ -0,0 +1,11 @@
from mcpacker.json import JsonBlob
# Classes ##########################################################################################
class BiomeModifier:
def asJsonBlob(self) -> JsonBlob:
return {
"type": "neoforge:none",
}
@@ -0,0 +1,28 @@
from collections.abc import Iterable
from mcpacker.format.datapack.biomemodifier import BiomeModifier
from mcpacker.format.datapack.biomemodifier.generationstep import GenerationStep
from mcpacker.json import JsonBlob
# Class ############################################################################################
class AddFeatureBiomeModifier(BiomeModifier):
def __init__(self, biomes:Iterable[str]|str, features:Iterable[str]|str, step:GenerationStep):
if isinstance(biomes, str):
biomes = [biomes]
if isinstance(features, str):
features = [features]
self.biomes = biomes
self.features = features
self.step = step
def asJsobBlob(self) -> JsonBlob:
return {
"type": "neoforge:add_features",
"biomes": list(self.biomes),
"features": list(self.features),
"step": str(self.step),
}
@@ -0,0 +1,52 @@
from typing import Any
# Class ############################################################################################
class GenerationStep:
def __init__(self, gameId:str):
self.gameId = gameId
def __eq__(self, other:Any) -> bool:
if type(self) != type(other): return False
if self.gameId != other.gameId: return False
return True
def __hash__(self) -> int:
return hash(self.gameId)
def __repr__(self) -> str:
return f"GenerationStep(gameId={repr(self.gameId)})"
def __str__(self) -> str:
return self.gameId
# Constants ########################################################################################
RAW_GENERATION = GenerationStep("raw_generation")
LAKES = GenerationStep("lakes")
LOCAL_MODIFICATIONS = GenerationStep("local_modifications")
UNDERGROUND_STRUCTURES = GenerationStep("underground_structures")
SURFACE_STRUCTURES = GenerationStep("surface_structures")
STRONGHOLDS = GenerationStep("strongholds")
UNDERGROUND_ORES = GenerationStep("underground_ores")
UNDERGROUND_DECORATION = GenerationStep("underground_decoration")
FLUID_SPRINGS = GenerationStep("fluid_springs")
VEGETAL_DECORATION = GenerationStep("vegetal_decoration")
TOP_LAYER_MODIFICATION = GenerationStep("top_layer_modification")
ALL = [
RAW_GENERATION,
LAKES,
LOCAL_MODIFICATIONS,
UNDERGROUND_STRUCTURES,
SURFACE_STRUCTURES,
STRONGHOLDS,
UNDERGROUND_ORES,
UNDERGROUND_DECORATION,
FLUID_SPRINGS,
VEGETAL_DECORATION,
TOP_LAYER_MODIFICATION,
]
@@ -0,0 +1,37 @@
from collections.abc import Iterable
from mcpacker.format.datapack.biomemodifier import BiomeModifier
from mcpacker.format.datapack.biomemodifier.generationstep import GenerationStep
from mcpacker.json import JsonBlob
# Classes ##########################################################################################
class RemoveFeatureBiomeModifier(BiomeModifier):
def __init__(
self,
biomes:Iterable[str]|str,
features:Iterable[str]|str,
step:GenerationStep|None=None
):
if isinstance(biomes, str):
biomes = [biomes]
if isinstance(features, str):
features = [features]
self.biomes = biomes
self.features = features
self.step = step
def asJsobBlob(self) -> JsonBlob:
result:dict[str,JsonBlob] = {
"type": "neoforge:remove_features",
"biomes": list(self.biomes),
"features": list(self.features),
}
if self.step:
result["step"] = str(self.step)
return result
@@ -0,0 +1,22 @@
from collections.abc import Iterable
from mcpacker.format.datapack.biomemodifier import BiomeModifier
from mcpacker.json import JsonBlob
# Classes ##########################################################################################
class RemoveSpawnBiomeModifier(BiomeModifier):
def __init__(self, biomes:Iterable[str]|str, entityTypes:Iterable[str]):
if isinstance(biomes, str):
biomes = [biomes]
self.biomes = biomes
self.entityTypes = entityTypes
def asJsonBlob(self) -> JsonBlob:
return {
"type": "neoforge:remove_spawns",
"biomes": list(self.biomes),
"entity_types": list(self.entityTypes),
}
+20 -12
View File
@@ -4,6 +4,7 @@ from mcpacker.format.report.mineralreport import MineralReport
from mcpacker.format.report.mobspawnreport import MobSpawnReport from mcpacker.format.report.mobspawnreport import MobSpawnReport
from mcpacker.model.modpack import ModPack from mcpacker.model.modpack import ModPack
from mcpacker.write.compositewriter import CompositeWriter from mcpacker.write.compositewriter import CompositeWriter
from mcpacker.write.datapack.disablespawnwriter import DisableSpawnWriter
from mcpacker.write.datapack.metawriter import DataPackMetaWriter from mcpacker.write.datapack.metawriter import DataPackMetaWriter
from mcpacker.write.datapack.writer import DataPackWriter from mcpacker.write.datapack.writer import DataPackWriter
from mcpacker.write.incontrol.spawnerwriter import SpawnerWriter from mcpacker.write.incontrol.spawnerwriter import SpawnerWriter
@@ -49,21 +50,28 @@ class Runner:
# Commands ################################################################# # Commands #################################################################
def _command_writeReports(self): def _command_writeReports(self):
CompositeWriter(self.pack, self.outputDir, [ p = self.pack
ReportWriter(self.pack, BiomeReport, self.outputDir, "biomes.txt"), o = self.outputDir
ReportWriter(self.pack, DepositReport, self.outputDir, "deposits.txt"),
ReportWriter(self.pack, MineralReport, self.outputDir, "minerals.txt"), CompositeWriter(p, o, [
ReportWriter(self.pack, MobSpawnReport, self.outputDir, "mobspawns.txt"), ReportWriter(p, BiomeReport, o, "biomes.txt"),
ReportWriter(p, DepositReport, o, "deposits.txt"),
ReportWriter(p, MineralReport, o, "minerals.txt"),
ReportWriter(p, MobSpawnReport, o, "mobspawns.txt"),
]).write() ]).write()
def _command_writeModPack(self): def _command_writeModPack(self):
CompositeWriter(self.pack, self.outputDir, [ p = self.pack
StaticWriter(self.pack, self.outputDir), o = self.outputDir
SpawnerWriter(self.pack, self.outputDir),
DataPackWriter(self.pack, self.outputDir, [ CompositeWriter(p, o, [
DataPackMetaWriter(self.pack, self.outputDir, 48), StaticWriter(p, o),
SpawnerWriter(p, o),
DataPackWriter(p, o, [
DataPackMetaWriter(p, o, 48),
DisableSpawnWriter(p, o),
]), ]),
ResourcePackWriter(self.pack, self.outputDir, [ ResourcePackWriter(p, o, [
ResourcePackMetaWriter(self.pack, self.outputDir, 34), ResourcePackMetaWriter(p, o, 34),
]), ]),
]).write() ]).write()
+2
View File
@@ -92,9 +92,11 @@ def createReportRunner(tmp_path:Path, pack:ModPack):
def test_writeModPack(tmp_path:Path, modPackRunner:Runner): def test_writeModPack(tmp_path:Path, modPackRunner:Runner):
assert (tmp_path/"testpack"/"config"/"incontrol"/"spawner.json").exists() assert (tmp_path/"testpack"/"config"/"incontrol"/"spawner.json").exists()
assert (tmp_path/"testpack"/"datapacks"/"testpack.jar").exists()
assert (tmp_path/"testpack"/"test.md").exists() assert (tmp_path/"testpack"/"test.md").exists()
def test_writeReports(tmp_path:Path, reportRunner:Runner): def test_writeReports(tmp_path:Path, reportRunner:Runner):
assert (tmp_path/"testpack"/"reports"/"biomes.txt").exists() assert (tmp_path/"testpack"/"reports"/"biomes.txt").exists()
assert (tmp_path/"testpack"/"reports"/"deposits.txt").exists()
assert (tmp_path/"testpack"/"reports"/"minerals.txt").exists() assert (tmp_path/"testpack"/"reports"/"minerals.txt").exists()
assert (tmp_path/"testpack"/"reports"/"mobspawns.txt").exists() assert (tmp_path/"testpack"/"reports"/"mobspawns.txt").exists()
+3 -5
View File
@@ -1,5 +1,6 @@
from collections.abc import Iterable from collections.abc import Iterable
from mcpacker.model.modpack import ModPack from mcpacker.model.modpack import ModPack
from mcpacker.write.locator import Locator
from mcpacker.write.writer import Writer from mcpacker.write.writer import Writer
from pathlib import Path from pathlib import Path
@@ -12,13 +13,10 @@ class CompositeWriter(Writer):
self, self,
pack:ModPack, pack:ModPack,
outputDir:Path, outputDir:Path,
writers:Iterable[Writer]|None=None writers:Iterable[Writer]|None=None,
): ):
super().__init__(pack, outputDir) super().__init__(pack, outputDir)
self._writers:list[Writer] = list(writers or [])
self._writers:list[Writer] = []
for writer in (writers or []):
self._writers.append(writer)
def doWrite(self): def doWrite(self):
for writer in self._writers: for writer in self._writers:
@@ -0,0 +1,19 @@
from mcpacker.format.datapack.biomemodifier.removespawn import RemoveSpawnBiomeModifier
from mcpacker.write.writer import Writer
import mcpacker.json as json
# Class ############################################################################################
class DisableSpawnWriter(Writer):
"""
Writes files to disable vanilla spawns for mobs who are to be spawned using custom rules.
"""
def doWrite(self):
entityTypes = [m.gameId for m in self.pack.world.mobs]
modifier = RemoveSpawnBiomeModifier("#c:is_overworld", entityTypes)
path = self.locator.biomeModifiers() / "remove_managed_mob_spawns.json"
self.resetOutputFile(path)
path.write_text(json.dumps(modifier.asJsonBlob(), indent=json.INDENT))
+2 -2
View File
@@ -56,7 +56,7 @@ class Locator:
return self.dataPackMod(modName, dataPackName) / "worldgen" / "configured_feature" return self.dataPackMod(modName, dataPackName) / "worldgen" / "configured_feature"
def dataPack(self, dataPackName:str|None=None): def dataPack(self, dataPackName:str|None=None):
return self.dataPacks() / (dataPackName or f"{self.pack.name}_override") return self.dataPacks() / (dataPackName or f"{self.pack.name}")
def dataPackMod(self, modName:str|None=None, dataPackName:str|None=None): def dataPackMod(self, modName:str|None=None, dataPackName:str|None=None):
return self.dataPack(dataPackName) / "data" / (modName or self.pack.name) return self.dataPack(dataPackName) / "data" / (modName or self.pack.name)
@@ -83,7 +83,7 @@ class Locator:
return self.dataPackMod(modName, dataPackName) / "recipes" return self.dataPackMod(modName, dataPackName) / "recipes"
def resourcePack(self, resourcePackName:str|None=None) -> Path: def resourcePack(self, resourcePackName:str|None=None) -> Path:
return self.resourcePacks() / (resourcePackName or f"{self.pack.name}_override") return self.resourcePacks() / (resourcePackName or f"{self.pack.name}")
def resourcePackMod(self, modName:str|None=None, resourcePackName:str|None=None) -> Path: def resourcePackMod(self, modName:str|None=None, resourcePackName:str|None=None) -> Path:
return self.resourcePack(resourcePackName) / "assets" / (modName or self.pack.name) return self.resourcePack(resourcePackName) / "assets" / (modName or self.pack.name)
+3 -3
View File
@@ -17,7 +17,7 @@ def test_blockStates(locator:Locator):
assert ( assert (
locator.blockStates() == locator.blockStates() ==
Path( Path(
"output/testPack/resourcepacks/testPack_override/" + "output/testPack/resourcepacks/testPack/" +
"assets/testPack/blockstates" "assets/testPack/blockstates"
) )
) )
@@ -26,7 +26,7 @@ def test_configuredFeatures(locator:Locator):
assert ( assert (
locator.configuredFeatures() == locator.configuredFeatures() ==
Path( Path(
"output/testPack/datapacks/testPack_override/" + "output/testPack/datapacks/testPack/" +
"data/testPack/worldgen/configured_feature" "data/testPack/worldgen/configured_feature"
) )
) )
@@ -44,7 +44,7 @@ def test_configuredFeaturesSpecificMod(locator:Locator):
assert ( assert (
locator.configuredFeatures("farmersdelight") == locator.configuredFeatures("farmersdelight") ==
Path( Path(
"output/testPack/datapacks/testPack_override/" + "output/testPack/datapacks/testPack/" +
"data/farmersdelight/worldgen/configured_feature" "data/farmersdelight/worldgen/configured_feature"
) )
) )
+1 -1
View File
@@ -14,7 +14,7 @@ class ReportWriter(Writer):
pack:ModPack, pack:ModPack,
composerClass:type[ReportComposer], composerClass:type[ReportComposer],
outputDir:Path, outputDir:Path,
name:str|Path name:str|Path,
): ):
super().__init__(pack, outputDir) super().__init__(pack, outputDir)
self.composerClass = composerClass self.composerClass = composerClass
+7
View File
@@ -0,0 +1,7 @@
import mcpacker.write.report.writer
# Tests ############################################################################################
def test_syntax():
pass
-3
View File
@@ -14,9 +14,6 @@ SOURCE_BASE = Path("mcpacker") / "pack"
class StaticWriter(Writer): class StaticWriter(Writer):
def __init__(self, pack:ModPack, outputDir:Path):
super().__init__(pack, outputDir)
def doWrite(self): def doWrite(self):
sourcePath = SOURCE_BASE / self.pack.name / "static" sourcePath = SOURCE_BASE / self.pack.name / "static"
targetPath = self.locator.root() targetPath = self.locator.root()