Move datapack objects to the format package

This commit is contained in:
Andrew Miner
2025-10-16 15:41:24 -06:00
parent 2da1a38c69
commit 87b6362d12
36 changed files with 88 additions and 88 deletions
+49
View File
@@ -0,0 +1,49 @@
from collections.abc import Iterable
from mcpacker.format.datapack.moddata import ModData
# Class ############################################################################################
class DataPack:
"""
A collection of data used to configure a Minecraft instance.
see: https://minecraft.wiki/w/Data_pack
"""
def __init__(self, name:str, mods:Iterable[ModData]|None=None):
self.name = name
self._mods:dict[str,ModData] = {}
self._defaultMod:ModData|None = None
for mod in (mods or []):
self.add(mod)
def add(self, mod:ModData) -> "DataPack":
if not self._defaultMod:
self._defaultMod = mod
self._mods[mod.name] = mod
return self
def get(self, name:str) -> ModData:
result = self._mods.get(name, None)
if not result:
result = self._mods[name] = ModData(name)
return result
@property
def defaultMod(self) -> ModData|None:
return self._defaultMod
@defaultMod.setter
def defaultMod(self, mod:ModData):
if mod:
existingModData = self.get(mod.name)
if not existingModData:
self.add(mod)
elif existingModData != mod:
raise Exception(f"Datapack already has a mod named {mod.name}")
self._defaultMod = mod
@@ -0,0 +1,8 @@
import mcpacker.format.datapack
# Tests ############################################################################################
def test_syntax():
pass
+20
View File
@@ -0,0 +1,20 @@
from mcpacker.json import JsonBlob
from typing import Any
from typing import cast
# Class ############################################################################################
class BlockState:
def __init__(self, blockId:str, properties:JsonBlob|None=None):
self.blockId = blockId
self.properties = properties
def asJsonBlob(self) -> JsonBlob:
result = cast(dict[str,JsonBlob], { "Name": self.blockId })
if self.properties:
result["Properties"] = self.properties
return result
@@ -0,0 +1,20 @@
from mcpacker.format.datapack.blockstate import BlockState
from pytest import fixture
# Fixtures #########################################################################################
@fixture(name="state")
def createBlockState():
yield BlockState("minecraft:button", {"waterlogged": "true"})
# Tests ############################################################################################
def test_asData(state:BlockState):
assert state.asJsonBlob() == {
"Name": "minecraft:button",
"Properties": {
"waterlogged": "true"
}
}
@@ -0,0 +1,12 @@
from mcpacker.json import JsonBlob
# Class ############################################################################################
class BlockStateProvider:
def __init__(self, gameId:str):
self.gameId = gameId
def asJsonBlob(self) -> JsonBlob:
raise NotImplementedError()
@@ -0,0 +1,19 @@
from mcpacker.json import JsonBlob
from mcpacker.format.datapack.blockstate import BlockState
from mcpacker.format.datapack.blockstateprovider import BlockStateProvider
from typing import Any
# Class ############################################################################################
class SimpleStateProvider(BlockStateProvider):
def __init__(self, state:BlockState):
super().__init__("minecraft:simple_state_provider")
self.state = state
def asJsonBlob(self) -> JsonBlob:
return {
"type": self.gameId,
"state": self.state.asJsonBlob(),
}
@@ -0,0 +1,28 @@
from mcpacker.format.datapack.blockstate import BlockState
from mcpacker.format.datapack.blockstateprovider.simplestateprovider import SimpleStateProvider
from pytest import fixture
# Fixtures #########################################################################################
@fixture(name="state")
def createBlockState():
yield BlockState("minecraft:button", {"waterlogged": "true"})
@fixture(name="provider")
def createProvider(state:BlockState):
return SimpleStateProvider(state)
# Tests ############################################################################################
def test_asData(provider:SimpleStateProvider):
assert provider.asJsonBlob() == {
"type": "minecraft:simple_state_provider",
"state": {
"Name": "minecraft:button",
"Properties": {
"waterlogged": "true"
}
}
}
@@ -0,0 +1,6 @@
# Class ############################################################################################
class ConfiguredFeature:
def __init__(self, gameId:str):
self.gameId = gameId
@@ -0,0 +1,35 @@
from mcpacker.format.datapack.configuredfeature import ConfiguredFeature
from typing import Any
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from mcpacker.format.datapack.placedfeature import PlacedFeature
# Class ############################################################################################
class RandomPatch(ConfiguredFeature):
def __init__(
self,
gameId:str,
feature:PlacedFeature,
tries:int=128,
xzSpread:int=7,
ySpread:int=3,
):
super().__init__("minecraft:random_patch")
self.feature = feature
self.tries = tries
self.xzSpread = xzSpread
self.ySpread = ySpread
def asData(self) -> dict[str,Any]:
return {
"type": self.gameId,
"feature": self.feature.gameId,
"tries": self.tries,
"xz_spread": self.xzSpread,
"y_spread": self.ySpread
}
@@ -0,0 +1,38 @@
from mcpacker.format.datapack.blockstate import BlockState
from mcpacker.format.datapack.blockstateprovider.simplestateprovider import SimpleStateProvider
from mcpacker.format.datapack.configuredfeature.simpleblock import SimpleBlock
from pytest import fixture
# Fixtures #########################################################################################
@fixture(name="state")
def createBlockState():
yield BlockState("minecraft:button", {"waterlogged": "true"})
@fixture(name="provider")
def createProvider(state:BlockState):
return SimpleStateProvider(state)
@fixture(name="feature")
def createSimpleBlockFeature(provider:SimpleStateProvider):
return SimpleBlock(provider)
# Tests ############################################################################################
def test_asData(feature:SimpleBlock):
assert feature.asJsonBlob() == {
"type": "minecraft:simple_block",
"config": {
"to_place": {
"type": "minecraft:simple_state_provider",
"state": {
"Name": "minecraft:button",
"Properties": {
"waterlogged": "true"
}
}
}
}
}
@@ -0,0 +1,21 @@
from mcpacker.json import JsonBlob
from mcpacker.format.datapack.blockstateprovider import BlockStateProvider
from mcpacker.format.datapack.configuredfeature import ConfiguredFeature
from typing import Any
# Class ############################################################################################
class SimpleBlock(ConfiguredFeature):
def __init__(self, toPlace:BlockStateProvider):
super().__init__("minecraft:simple_block")
self.toPlace = toPlace
def asJsonBlob(self) -> JsonBlob:
return {
"type": self.gameId,
"config": {
"to_place": self.toPlace.asJsonBlob()
}
}
@@ -0,0 +1,38 @@
from mcpacker.format.datapack.blockstate import BlockState
from mcpacker.format.datapack.blockstateprovider.simplestateprovider import SimpleStateProvider
from mcpacker.format.datapack.configuredfeature.simpleblock import SimpleBlock
from pytest import fixture
# Fixtures #########################################################################################
@fixture(name="state")
def createBlockState():
yield BlockState("minecraft:button", {"waterlogged": "true"})
@fixture(name="provider")
def createProvider(state:BlockState):
return SimpleStateProvider(state)
@fixture(name="feature")
def createSimpleBlockFeature(provider:SimpleStateProvider):
return SimpleBlock(provider)
# Tests ############################################################################################
def test_asData(feature:SimpleBlock):
assert feature.asJsonBlob() == {
"type": "minecraft:simple_block",
"config": {
"to_place": {
"type": "minecraft:simple_state_provider",
"state": {
"Name": "minecraft:button",
"Properties": {
"waterlogged": "true"
}
}
}
}
}
+24
View File
@@ -0,0 +1,24 @@
# Class ############################################################################################
class HeightMapType:
"""
A cached record of the top block at all points in the world.
see: https://minecraft.wiki/w/Heightmap
"""
def __init__(self, gameId:str):
self.gameId = gameId
def asData(self) -> str:
return self.gameId
# Constants ########################################################################################
MOTION_BLOCKING = HeightMapType("MOTION_BLOCKING")
MOTION_BLOCKING_NO_LEAVES = HeightMapType("MOTION_BLOCKING_NO_LEAVES")
OCEAN_FLOOR = HeightMapType("OCEAN_FLOOR")
OCEAN_FLOOR_WG = HeightMapType("OCEAN_FLOOR_WG")
WORLD_SURFACE = HeightMapType("WORLD_SURFACE")
WORLD_SURFACE_WG = HeightMapType("WORLD_SURFACE_WG")
@@ -0,0 +1,7 @@
from mcpacker.format.datapack.heightmaptype import HeightMapType
# Tests ############################################################################################
def test_syntax():
pass
+23
View File
@@ -0,0 +1,23 @@
from mcpacker.format.datapack.configuredfeature import ConfiguredFeature
from mcpacker.format.datapack.placedfeature import PlacedFeature
from typing import Any
# Class ############################################################################################
class ModData:
def __init__(self, name:str):
self.name = name
self.biomeModifiers:list[Any] = []
self.configuredFeatures:list[ConfiguredFeature] = []
self.functions:list[Any] = []
self.functionTags:list[Any] = []
self.lootModifiers:list[Any] = []
self.lootTables:list[Any] = []
self.placedFeatures:list[PlacedFeature] = []
self.recipes:list[Any] = []
self.structures:list[Any] = []
self.structureSets:list[Any] = []
self.tags:list[Any] = []
self.templatePools:list[Any] = []
+7
View File
@@ -0,0 +1,7 @@
import mcpacker.format.datapack.moddata
# Tests ############################################################################################
def test_syntax():
pass
+10
View File
@@ -0,0 +1,10 @@
from mcpacker.format.datapack.placement import Placement
# Class ############################################################################################
class PlacedFeature:
def __init__(self, gameId:str, placements:list[Placement]):
self.gameId = gameId
self.placements = placements
@@ -0,0 +1,7 @@
from mcpacker.format.datapack.placedfeature import PlacedFeature
# Tests ############################################################################################
def test_syntax():
pass
@@ -0,0 +1,6 @@
# Class ############################################################################################
class Placement:
def __init__(self, gameId:str):
self.gameId = gameId
@@ -0,0 +1,13 @@
from mcpacker.format.datapack.placement import Placement
from typing import Any
# Class ############################################################################################
class Biome(Placement):
def __init__(self):
super().__init__("minecraft:biome")
def asData(self) -> dict[str,Any]:
return { "type": self.gameId }
@@ -0,0 +1,18 @@
from mcpacker.format.datapack.placement.biome import Biome
from pytest import fixture
# Fixtures #########################################################################################
@fixture(name="placement")
def createPlacement():
yield Biome()
# Tests ############################################################################################
def test_asData(placement):
assert placement.asData() == {
"type": "minecraft:biome",
}
@@ -0,0 +1,17 @@
from mcpacker.format.datapack.heightmaptype import HeightMapType
from mcpacker.format.datapack.placement import Placement
from typing import Any
# Class ############################################################################################
class HeightMap(Placement):
def __init__(self, heightMap:HeightMapType):
super().__init__("minecraft:heightmap")
self.heightMap = heightMap
def asData(self) -> dict[str,Any]:
return {
"type": self.gameId,
"heightmap": self.heightMap.asData()
}
@@ -0,0 +1,20 @@
from mcpacker.format.datapack.heightmaptype import WORLD_SURFACE
from mcpacker.format.datapack.placement.heightmap import HeightMap
from pytest import fixture
# Fixtures #########################################################################################
@fixture(name="placement")
def createPlacement():
yield HeightMap(WORLD_SURFACE)
# Tests ############################################################################################
def test_asData(placement):
assert placement.asData() == {
"type": "minecraft:heightmap",
"heightmap": "WORLD_SURFACE"
}
@@ -0,0 +1,13 @@
from mcpacker.format.datapack.placement import Placement
from typing import Any
# Class ############################################################################################
class InSquare(Placement):
def __init__(self):
super().__init__("minecraft:in_square")
def asData(self) -> dict[str,Any]:
return { "type": self.gameId }
@@ -0,0 +1,18 @@
from mcpacker.format.datapack.placement.insquare import InSquare
from pytest import fixture
# Fixtures #########################################################################################
@fixture(name="placement")
def createPlacement():
yield InSquare()
# Tests ############################################################################################
def test_asData(placement):
assert placement.asData() == {
"type": "minecraft:in_square",
}
@@ -0,0 +1,17 @@
from mcpacker.format.datapack.placement import Placement
from typing import Any
# Class ############################################################################################
class RarityFilter(Placement):
def __init__(self, chance:int):
super().__init__("minecraft:rarity_filter")
self.chance = chance
def asData(self) -> dict[str,Any]:
return {
"type": self.gameId,
"chance": self.chance
}
@@ -0,0 +1,19 @@
from mcpacker.format.datapack.placement.rarityfilter import RarityFilter
from pytest import fixture
# Fixtures #########################################################################################
@fixture(name="placement")
def createPlacement():
yield RarityFilter(4)
# Tests ############################################################################################
def test_asData(placement):
assert placement.asData() == {
"type": "minecraft:rarity_filter",
"chance": 4
}
@@ -0,0 +1,20 @@
from collections.abc import Iterable
from collections.abc import Mapping
from mcpacker.model.core.resourceid import ResourceId
# Class ############################################################################################
class Recipe:
def __init__(
self,
gameId:str|ResourceId,
resultId:str|ResourceId,
resultCount:int=1,
resultComponents:Mapping[str,str]|None=None,
):
self.gameId = ResourceId.parse(gameId)
self.resultComponents = resultComponents
self.resultCount = resultCount
self.resultId = ResourceId.parse(resultId)
@@ -0,0 +1,56 @@
from collections.abc import Iterable
from collections.abc import Mapping
from mcpacker.format.datapack.recipe import Recipe
from mcpacker.model.core.resourceid import ResourceId
# Class ############################################################################################
class ShapedRecipe(Recipe):
def __init__(
self,
gameId:str|ResourceId,
key:Mapping[str,(str|ResourceId|Iterable[ResourceId|str])],
pattern:Iterable[str],
resultId:str|ResourceId,
resultCount:int=1,
resultComponents:Mapping[str,str]|None=None,
):
super().__init__(gameId, resultId, resultCount, resultComponents)
self.key = self._parseKey(key)
self.pattern = self._parsePattern(pattern)
# Private Methods ##########################################################
def _parseKey(
self,
key:Mapping[str,(str|ResourceId|Iterable[ResourceId|str])]
) -> dict[str, list[ResourceId]]:
result:dict[str, list[ResourceId]] = {}
for name, value in key.items():
finalValue:list[ResourceId] = []
if isinstance(value, str) or isinstance(value, ResourceId):
finalValue = [ ResourceId.parse(value) ]
elif isinstance(value, Iterable):
finalValue = [ ResourceId.parse(e) for e in value ]
if len(name) != 1:
raise ValueError(f"key names must be a single letter (\"{name}\" is not valid)")
result[name] = finalValue
return result
def _parsePattern(self, pattern:Iterable[str]) -> list[str]:
result:list[str] = []
for row in pattern:
if len(row) not in [2, 3]:
raise ValueError(f"pattern rows must be 2 or 3 letters: (\"{row}\" is not valid)")
result.append(row)
return result
@@ -0,0 +1,27 @@
from pytest import fixture
from mcpacker.format.datapack.recipe.shapedrecipe import ShapedRecipe
# Fixtures #########################################################################################
@fixture(name="recipe")
def createRecipe():
yield ShapedRecipe(
"iron_pickaxe",
{"s": "stick", "i": "iron_ingot"},
["iii", " s ", " s "],
"iron_pickaxe"
)
# Tests ############################################################################################
def test_recipe(recipe:ShapedRecipe):
keyText = ", ".join(f"{k}:{str(v[0])}" for k,v in recipe.key.items())
recipeText = "|".join(str(i) for i in recipe.pattern)
assert keyText == "s:minecraft:stick, i:minecraft:iron_ingot"
assert recipeText == "iii| s | s "
assert str(recipe.resultId) == "minecraft:iron_pickaxe"
assert recipe.resultCount == 1
@@ -0,0 +1,28 @@
from collections.abc import Iterable
from collections.abc import Mapping
from mcpacker.model.core.resourceid import ResourceId
from mcpacker.format.datapack.recipe import Recipe
# Class ############################################################################################
class ShapelessRecipe(Recipe):
def __init__(
self,
gameId:str|ResourceId,
ingredients:Iterable[str|ResourceId],
resultId:str|ResourceId,
resultCount:int=1,
resultComponents:Mapping[str,str]|None=None,
):
super().__init__(gameId, resultId, resultCount, resultComponents)
self.ingredients = self._parseIngredients(ingredients)
def _parseIngredients(self, ingredients:Iterable[str|ResourceId]) -> list[ResourceId]:
result = [ ResourceId.parse(e) for e in ingredients ]
if len(result) > 9:
raise ValueError("cannot have more than 9 ingredients (\"{len(result}\" is not valid")
return result
@@ -0,0 +1,17 @@
from pytest import fixture
from mcpacker.format.datapack.recipe.shapelessrecipe import ShapelessRecipe
# Fixtures #########################################################################################
@fixture(name="recipe")
def createRecipe():
yield ShapelessRecipe("flint_and_steel", ["iron_ingot", "flint"], "flint_and_steel")
# Tests ############################################################################################
def test_recipe(recipe:ShapelessRecipe):
assert ", ".join(str(i) for i in recipe.ingredients) == "minecraft:iron_ingot, minecraft:flint"
assert str(recipe.resultId) == "minecraft:flint_and_steel"
assert recipe.resultCount == 1