Add static asssets capability

This commit is contained in:
Andrew Miner
2025-10-10 19:43:28 -06:00
parent 320e9fb240
commit 5f15dac9e9
43 changed files with 458 additions and 70 deletions
+26
View File
@@ -0,0 +1,26 @@
from mcpacker.model.resourcepack.blockstate import BlockState
from mcpacker.model.resourcepack.variant import Variant
from mcpacker.model.resourcepack.model import Model
from mcpacker.model.modpack import ModPack
from mcpacker.model.core.resourceid import ResourceId
# Class ############################################################################################
class BlockFactory:
def __init__(self, pack:ModPack):
self.pack = pack
def makeBasicBlock(self, name:str, gameId:ResourceId):
basicCubeModelId = ResourceId.parse("block/cube_all")
textureId = ResourceId.parse(f"{gameId.mod}:block/{gameId.name}")
blockId = ResourceId.parse(f"{gameId.mod}:block/{gameId.name}")
resource = self.pack.resourcePack.get(gameId.mod)
resource.blockModels.append(Model(basicCubeModelId, {"all": textureId}))
resource.itemModels.append(Model(blockId))
resource.blockStates.append(BlockState(Variant(blockId)))
data = self.pack.dataPack.get(gameId.mod)
+5 -2
View File
@@ -12,11 +12,14 @@ class ResourceId:
return str(resourceId) return str(resourceId)
@staticmethod @staticmethod
def canonical(text:str): def canonical(text:str) -> str:
return str(ResourceId.parse(text)) return str(ResourceId.parse(text))
@staticmethod @staticmethod
def parse(text:str): def parse(text:"ResourceId|str") -> "ResourceId":
if isinstance(text, ResourceId):
return text
isTag = False isTag = False
if text.startswith("#"): if text.startswith("#"):
isTag = True isTag = True
+6 -2
View File
@@ -21,8 +21,12 @@ class DataPack:
self._mods[mod.name] = mod self._mods[mod.name] = mod
return self return self
def get(self, name:str) -> ModData|None: def get(self, name:str) -> ModData:
return self._mods.get(name, None) result = self._mods.get(name, None)
if not result:
result = self._mods[name] = ModData(name)
return result
@property @property
def defaultMod(self) -> ModData|None: def defaultMod(self) -> ModData|None:
+14 -5
View File
@@ -1,4 +1,5 @@
from collections.abc import Iterable from collections.abc import Iterable
from collections.abc import Iterator
from mcpacker.model.resourcepack.modresource import ModResource from mcpacker.model.resourcepack.modresource import ModResource
@@ -18,11 +19,15 @@ class ResourcePack:
if not self._defaultMod: if not self._defaultMod:
self._defaultMod = mod self._defaultMod = mod
self._mods[mod.name] = mod self._mods[mod.mod] = mod
return self return self
def get(self, name:str) -> ModResource|None: def get(self, name:str) -> ModResource:
return self._mods.get(name, None) result = self._mods.get(name, None)
if not result:
result = self._mods[name] = ModResource(name)
return result
@property @property
def defaultMod(self) -> ModResource|None: def defaultMod(self) -> ModResource|None:
@@ -31,11 +36,15 @@ class ResourcePack:
@defaultMod.setter @defaultMod.setter
def defaultMod(self, mod:ModResource): def defaultMod(self, mod:ModResource):
if mod: if mod:
existingMod = self.get(mod.name) existingMod = self.get(mod.mod)
if not existingMod: if not existingMod:
self.add(mod) self.add(mod)
elif existingMod != mod: elif existingMod != mod:
raise Exception(f"Resourcepack already has a mod named {mod.name}") raise Exception(f"Resourcepack already has a mod named {mod.mod}")
self._defaultMod = mod self._defaultMod = mod
@property
def mods(self) -> Iterator[ModResource]:
for mod in self._mods.values():
yield mod
+25
View File
@@ -0,0 +1,25 @@
from mcpacker.model.resourcepack.variant import Variant
# Class ############################################################################################
class BlockState:
"""
A mapping between a block and the model(s) used to represent it in game.
see: https://minecraft.wiki/w/Tutorial:Models#Block_states
"""
def __init__(self, defaultVariant:Variant|None=None):
self.variants:dict[str,Variant] = {}
if defaultVariant:
self.defaultVariant = defaultVariant
@property
def defaultVariant(self) -> Variant|None:
return self.variants.get("", None)
@defaultVariant.setter
def defaultVariant(self, value:Variant):
self.variants[""] = value
+13
View File
@@ -0,0 +1,13 @@
from mcpacker.model.core.resourceid import ResourceId
# Class ############################################################################################
class Model:
"""
Describes how to create a 2D or 3D representation of an object in the game
"""
def __init__(self, parent:ResourceId, textures:dict[str,ResourceId]|None=None):
self.parent = parent
self.textures = {k:v for k,v in (textures or {}).items()}
+9 -5
View File
@@ -1,9 +1,13 @@
from mcpacker.model.resourcepack.blockstate import BlockState
from mcpacker.model.resourcepack.model import Model
# Class ############################################################################################ # Class ############################################################################################
class ModResource: class ModResource:
def __init__(self, name:str): def __init__(self, mod:str):
self.name = name self.mod = mod
self.blockStates:list[str] = [] self.blockStates:list[BlockState] = []
self.models:list[str] = [] self.blockModels:list[Model] = []
self.textures:list[str] = [] self.itemModels:list[Model] = []
+13
View File
@@ -0,0 +1,13 @@
from mcpacker.model.core.resourceid import ResourceId
# Class ############################################################################################
class Variant:
def __init__(self, model:ResourceId|str, x:int=0, y:int=0, uvlock:bool=False, weight:int=1):
self.model = ResourceId.parse(model)
self.x = x
self.y = y
self.uvlock = uvlock
self.weight = weight
@@ -0,0 +1,17 @@
from mcpacker.model.resourcepack.variant import Variant
from pytest import fixture
# Fixtures #########################################################################################
@fixture(name="variant")
def createVariant():
yield Variant("grass", 90, 180, True, 10)
# Tests ############################################################################################
def test_variant(variant:Variant):
assert str(variant.model) == "minecraft:grass"
assert variant.x == 90
assert variant.uvlock == True
@@ -21,7 +21,7 @@ import mcpacker.model.core.season as SE
# Constants ######################################################################################## # Constants ########################################################################################
def addMobs(pack:ModPack): def addMobSpawns(pack:ModPack):
mobs = pack.world.mobs mobs = pack.world.mobs
spawns = pack.world.mobSpawns spawns = pack.world.mobSpawns
@@ -202,7 +202,7 @@ def addMobs(pack:ModPack):
).derive( ).derive(
altitude = AL.LOWLANDS, altitude = AL.LOWLANDS,
seasons = [SE.AUTUMN, SE.WINTER], seasons = [SE.AUTUMN, SE.WINTER],
groups = GR.TROUP, group = GR.TROUP,
scarcity = SC.SPARSE, scarcity = SC.SPARSE,
) )
) )
@@ -6,6 +6,7 @@ from mcpacker.pack.mysteriousisland.adddeposits import addDeposits
from mcpacker.pack.mysteriousisland.additems import addItems from mcpacker.pack.mysteriousisland.additems import addItems
from mcpacker.pack.mysteriousisland.addminerals import addMinerals from mcpacker.pack.mysteriousisland.addminerals import addMinerals
from mcpacker.pack.mysteriousisland.addmobs import addMobs from mcpacker.pack.mysteriousisland.addmobs import addMobs
from mcpacker.pack.mysteriousisland.addmobspawns import addMobSpawns
from mcpacker.pack.mysteriousisland.addresourcepack import addResourcePack from mcpacker.pack.mysteriousisland.addresourcepack import addResourcePack
@@ -21,6 +22,7 @@ def buildModPack():
pack.augment(addMinerals) pack.augment(addMinerals)
pack.augment(addDeposits) pack.augment(addDeposits)
pack.augment(addMobs) pack.augment(addMobs)
pack.augment(addMobSpawns)
pack.augment(addResourcePack) pack.augment(addResourcePack)
pack.augment(addDataPack) pack.augment(addDataPack)
@@ -0,0 +1,6 @@
{
"pack": {
"pack_format": 48,
"description": "modpack overrides for Mysterious Island"
}
}

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

+1
View File
@@ -0,0 +1 @@
testpack
+7 -7
View File
@@ -4,7 +4,8 @@ from mcpacker.write.markdown.biomereport import BiomeReport
from mcpacker.write.markdown.mineralreport import MineralReport from mcpacker.write.markdown.mineralreport import MineralReport
from mcpacker.write.markdown.mobspawnreport import MobSpawnReport from mcpacker.write.markdown.mobspawnreport import MobSpawnReport
from mcpacker.write.markdown.reportwriter import ReportWriter from mcpacker.write.markdown.reportwriter import ReportWriter
from mcpacker.write.writer import CompositeWriter from mcpacker.write.compositewriter import CompositeWriter
from mcpacker.write.staticwriter import StaticWriter
from pathlib import Path from pathlib import Path
import inspect import inspect
@@ -44,15 +45,14 @@ class Runner:
# Commands ################################################################# # Commands #################################################################
def _command_writeReports(self): def _command_writeReports(self):
writer = CompositeWriter(self.pack, self.outputDir, [ CompositeWriter(self.pack, self.outputDir, [
BiomeReport, BiomeReport,
MineralReport, MineralReport,
MobSpawnReport, MobSpawnReport,
]) ]).write()
writer.write()
def _command_writeModPack(self): def _command_writeModPack(self):
writer = CompositeWriter(self.pack, self.outputDir, [ CompositeWriter(self.pack, self.outputDir, [
StaticWriter,
SpawnerWriter, SpawnerWriter,
]) ]).write()
writer.write()
+10 -9
View File
@@ -70,30 +70,31 @@ def defineAddMobSpawnss():
@fixture(name="pack") @fixture(name="pack")
def createPack(addBiomes, addMobs, addMobSpawns): def createPack(addBiomes, addMobs, addMobSpawns):
pack = ModPack("testModPack") pack = ModPack("testpack")
pack.augment(addBiomes) pack.augment(addBiomes)
pack.augment(addMobs) pack.augment(addMobs)
pack.augment(addMobSpawns) pack.augment(addMobSpawns)
yield pack yield pack
@fixture(name="modPackRunner") @fixture(name="modPackRunner")
def createModPackRunner(tmp_path:Path): def createModPackRunner(tmp_path:Path, pack:ModPack):
runner = Runner(ModPack("testModPack"), tmp_path) runner = Runner(pack, tmp_path)
runner._command_writeModPack() runner._command_writeModPack()
yield runner yield runner
@fixture(name="reportRunner") @fixture(name="reportRunner")
def createReportRunner(tmp_path:Path): def createReportRunner(tmp_path:Path, pack:ModPack):
runner = Runner(ModPack("testModPack"), tmp_path) runner = Runner(pack, tmp_path)
runner._command_writeReports() runner._command_writeReports()
yield runner yield runner
# Tests ############################################################################################ # Tests ############################################################################################
def test_writeModPack(tmp_path:Path, modPackRunner:Runner): def test_writeModPack(tmp_path:Path, modPackRunner:Runner):
assert (tmp_path/"testModPack"/"config"/"incontrol"/"spawner.json").exists() assert (tmp_path/"testpack"/"config"/"incontrol"/"spawner.json").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/"testModPack"/"reports"/"biomes.md").exists() assert (tmp_path/"testpack"/"reports"/"biomes.md").exists()
assert (tmp_path/"testModPack"/"reports"/"minerals.md").exists() assert (tmp_path/"testpack"/"reports"/"minerals.md").exists()
assert (tmp_path/"testModPack"/"reports"/"mobspawns.md").exists() assert (tmp_path/"testpack"/"reports"/"mobspawns.md").exists()
+37
View File
@@ -0,0 +1,37 @@
from collections.abc import Iterable
from mcpacker.model.modpack import ModPack
from mcpacker.write.writer import Writer
from pathlib import Path
from typing import TypeVar
import os
import shutil
# Type Support #####################################################################################
WriterSubclass = TypeVar("WriterSubclass", bound=Writer)
# Class ############################################################################################
class CompositeWriter(Writer):
def __init__(
self,
pack:ModPack,
outputDir:Path,
writers:Iterable[type[WriterSubclass]]|None=None
):
super().__init__(pack, outputDir)
self._writers:list[Writer] = []
for writerClass in (writers or []):
self.add(writerClass)
def add(self, writerClass:type[WriterSubclass]):
self._writers.append(writerClass(self.pack, self.outputDir))
def write(self):
for writer in self._writers:
writer.write()
+2 -1
View File
@@ -15,6 +15,7 @@ import mcpacker.model.core.dimension as DI
import mcpacker.model.core.fauna.location as LO import mcpacker.model.core.fauna.location as LO
import mcpacker.model.core.scarcity as SC import mcpacker.model.core.scarcity as SC
import mcpacker.model.time as TI import mcpacker.model.time as TI
import os
# Class ############################################################################################ # Class ############################################################################################
@@ -26,7 +27,7 @@ class SpawnerWriter(Writer):
def write(self): def write(self):
path = self.outputDir / self.pack.name / INCONTROL_CONFIG_DIR / "spawner.json" path = self.outputDir / self.pack.name / INCONTROL_CONFIG_DIR / "spawner.json"
path.parent.mkdir(parents=True) self.resetOutputFile(path)
path.write_text(json.dumps(self._makeAllRules(), indent=json.INDENT)) path.write_text(json.dumps(self._makeAllRules(), indent=json.INDENT))
# Private Functions ######################################################## # Private Functions ########################################################
+137
View File
@@ -0,0 +1,137 @@
from mcpacker.model.modpack import ModPack
from pathlib import Path
from typing import Protocol
from typing import TypeVar
from typing import TypeAlias
# Class ############################################################################################
class Locator:
"""
Locator identifies various important paths within a modpack.
It currently has added support for the following mods:
* Cold Sweat
* InControl!
* InfoDump
* KubeJS
* Large Ore Deposits
* Thirst Was Taken
"""
def __init__(self, pack:ModPack, outputDir:Path):
self._pack = pack
self._outputDir = outputDir
def biomeModifiers(self, modName:str|None=None, dataPackName:str|None=None) -> Path:
return self.dataPackMod(modName, dataPackName) / "neoforge" / "biome_modifier"
def blockStates(self, modName:str|None=None, resourcePackName:str|None=None) -> Path:
return self.resourcePackMod(modName, resourcePackName) / "blockstates"
def config(self) -> Path:
return self.root() / "config"
def configuredFeatures(self, modName:str|None=None, dataPackName:str|None=None) -> Path:
return self.dataPackMod(modName, dataPackName) / "worldgen" / "configured_feature"
def dataPack(self, dataPackName:str|None=None):
return self.dataPacks() / (dataPackName or f"{self._pack.name}_override")
def dataPackMod(self, modName:str|None=None, dataPackName:str|None=None):
return self.dataPack(dataPackName) / "data" / (modName or self._pack.name)
def dataPacks(self) -> Path:
return self.root() / "datapacks"
def functions(self, modName:str|None=None, dataPackName:str|None=None) -> Path:
return self.dataPackMod(modName, dataPackName) / "functions"
def lootModifiers(self, modName:str|None=None, dataPackName:str|None=None) -> Path:
return self.dataPackMod(modName, dataPackName) / "loot_modifiers"
def lootTables(self, modName:str|None=None, dataPackName:str|None=None) -> Path:
return self.dataPackMod(modName, dataPackName) / "loot_tables"
def models(self, modName:str|None=None, resourcePackName:str|None=None) -> Path:
return self.resourcePackMod(modName, resourcePackName) / "models"
def placedFeatures(self, modName:str|None=None, dataPackName:str|None=None) -> Path:
return self.dataPackMod(modName, dataPackName) / "worldgen" / "placed_feature"
def recipes(self, modName:str|None=None, dataPackName:str|None=None) -> Path:
return self.dataPackMod(modName, dataPackName) / "recipes"
def resourcePack(self, resourcePackName:str|None=None) -> Path:
return self.resourcePacks() / (resourcePackName or f"{self._pack.name}_override")
def resourcePackMod(self, modName:str|None=None, resourcePackName:str|None=None) -> Path:
return self.resourcePack(resourcePackName) / "assets" / (modName or self._pack.name)
def resourcePacks(self) -> Path:
return self.root() / "resourcepacks"
def root(self) -> Path:
return self._outputDir / self._pack.name
def structures(self, modName:str|None=None, dataPackName:str|None=None) -> Path:
return self.dataPackMod(modName, dataPackName) / "structure"
def structureSets(self, modName:str|None=None, dataPackName:str|None=None) -> Path:
return self.dataPackMod(modName, dataPackName) / "structure_set"
def tags(self, modName:str|None=None, dataPackName:str|None=None) -> Path:
return self.dataPackMod(modName, dataPackName) / "tags"
def templatePools(self, modName:str|None=None, dataPackName:str|None=None) -> Path:
return self.dataPackMod(modName, dataPackName) / "template_pool"
# ColdSweat Mixins #########################################################
def cos_config(self) -> Path:
return self.config() / "coldsweat"
# InControl Mixins #########################################################
def inc_config(self) -> Path:
return self.config() / "incontrol"
# InfoDump Mixins ##########################################################
def ifd_root(self) -> Path:
return self.root() / "infodump"
# KubeJS Mixins ############################################################
def kbj_root(self) -> Path:
return self.root() / "kubejs"
def kbj_clientScripts(self) -> Path:
return self.kbj_root() / "client_scripts"
def kbj_serverScripts(self) -> Path:
return self.kbj_root() / "server_scripts"
def kbj_startupScripts(self) -> Path:
return self.kbj_root() / "startup_scripts"
# Large Ore Deposits Mixins ################################################
def lod_config(self) -> Path:
return self.config() / "adlods"
def lod_deposits(self) -> Path:
return self.lod_config() / "Deposits"
def lod_geodes(self) -> Path:
return self.lod_config() / "Geodes"
def lod_vanilla(self) -> Path:
return self.lod_config() / "Vanilla"
# ThirstWasTaken ###########################################################
def twt_config(self) -> Path:
return self.config() / "thirst"
+56
View File
@@ -0,0 +1,56 @@
from mcpacker.write.locator import Locator
from mcpacker.model.modpack import ModPack
from pathlib import Path
from pytest import fixture
# Fixtures #########################################################################################
@fixture(name="locator")
def createLocator():
return Locator(ModPack("testPack"), Path("output"))
# Tests ############################################################################################
def test_blockStates(locator:Locator):
assert (
locator.blockStates() ==
Path(
"output/testPack/resourcepacks/testPack_override/" +
"assets/testPack/blockstates"
)
)
def test_configuredFeatures(locator:Locator):
assert (
locator.configuredFeatures() ==
Path(
"output/testPack/datapacks/testPack_override/" +
"data/testPack/worldgen/configured_feature"
)
)
def test_configuredFeaturesSpecificDataPack(locator:Locator):
assert (
locator.configuredFeatures(dataPackName="alternate") ==
Path(
"output/testPack/datapacks/alternate/" +
"data/testPack/worldgen/configured_feature"
)
)
def test_configuredFeaturesSpecificMod(locator:Locator):
assert (
locator.configuredFeatures("farmersdelight") ==
Path(
"output/testPack/datapacks/testPack_override/" +
"data/farmersdelight/worldgen/configured_feature"
)
)
def test_kbj_serverScripts(locator:Locator):
assert locator.kbj_serverScripts() == Path("output/testPack/kubejs/server_scripts")
def test_root(locator:Locator):
assert locator.root() == Path("output/testPack")
+1 -1
View File
@@ -35,7 +35,7 @@ class MobSpawnReport(MarkdownWriter):
line(", ".join([t.name for t in trait])) line(", ".join([t.name for t in trait]))
for trait in habitat.biomeFilter.prohibited: for trait in habitat.biomeFilter.prohibited:
category = type(trait[0]).__name__ category = type(trait).__name__
self.text("* ").text(category).text(": not: ").line(trait.name) self.text("* ").text(category).text(": not: ").line(trait.name)
self.outdent() self.outdent()
@@ -0,0 +1,12 @@
from mcpacker.write.writer import Writer
# Class ############################################################################################
class BlockStateWiter(Writer):
def write(self):
for mod in self.pack.resourcePack.mods:
for blockState in mod.blockStates:
path = self.outputDir/self.pack.name/"resourcepacks"
+23
View File
@@ -0,0 +1,23 @@
from mcpacker.model.modpack import ModPack
from mcpacker.write.writer import Writer
from pathlib import Path
import shutil
# Constants ########################################################################################
SOURCE_BASE = Path("mcpacker") / "pack"
# Class ############################################################################################
class StaticWriter(Writer):
def __init__(self, pack:ModPack, outputDir:Path):
super().__init__(pack, outputDir)
def write(self):
sourcePath = SOURCE_BASE / self.pack.name / "static"
targetPath = self.outputDir / self.pack.name
shutil.copytree(sourcePath, targetPath, dirs_exist_ok=True)
+4 -36
View File
@@ -1,17 +1,10 @@
from collections.abc import Iterable
from mcpacker.model.modpack import ModPack from mcpacker.model.modpack import ModPack
from pathlib import Path from pathlib import Path
from typing import TypeVar
import os import os
import shutil import shutil
# Type Support #####################################################################################
WriterSubclass = TypeVar("WriterSubclass", bound="Writer")
# Class ############################################################################################ # Class ############################################################################################
class Writer: class Writer:
@@ -20,35 +13,10 @@ class Writer:
self.pack = pack self.pack = pack
self.outputDir = outputDir self.outputDir = outputDir
def resetOutputDir(self): def resetOutputFile(self, file:Path):
if self.outputDir.exists(): if file.exists():
shutil.rmtree(self.outputDir) os.remove(file)
file.parent.mkdir(parents=True, exist_ok=True)
self.outputDir.mkdir(parents=True)
def write(self): def write(self):
raise NotImplementedError() raise NotImplementedError()
# Class ############################################################################################
class CompositeWriter(Writer):
def __init__(
self,
pack:ModPack,
outputDir:Path,
writers:Iterable[type[WriterSubclass]]|None=None
):
super().__init__(pack, outputDir)
self._writers:list[Writer] = []
for writerClass in (writers or []):
self.add(writerClass)
def add(self, writerClass:type[WriterSubclass]):
self._writers.append(writerClass(self.pack, self.outputDir))
def write(self):
for writer in self._writers:
writer.write()
+30
View File
@@ -0,0 +1,30 @@
from mcpacker.write.writer import Writer
from mcpacker.model.modpack import ModPack
from pathlib import Path
from pytest import fixture
# Helpers ##########################################################################################
class SampleWriter(Writer):
def write(self):
path = self.outputDir/self.pack.name/"samplewriter.md"
self.resetOutputFile(path)
path.write_text("alpha bravo charlie")
# Fixtures #########################################################################################
@fixture(name="writer")
def createWriter(tmp_path):
writer = SampleWriter(ModPack("testpack"), tmp_path)
writer.write()
yield writer
# Tests ############################################################################################
def test_write(tmp_path:Path, writer:SampleWriter):
path = tmp_path/"testpack"/"samplewriter.md"
assert path.read_text() == "alpha bravo charlie"