Consolidate writer logic to a single package
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
from mcpacker.model.core.world import World
|
||||
|
||||
|
||||
# Class ############################################################################################
|
||||
|
||||
class Writer:
|
||||
|
||||
def __init__(self, world:World):
|
||||
self.world = world
|
||||
|
||||
def write(self):
|
||||
raise NotImplementedError()
|
||||
@@ -1,7 +0,0 @@
|
||||
import mcpacker.emit.datapack.writer
|
||||
|
||||
|
||||
# Tests ############################################################################################
|
||||
|
||||
def test_syntax():
|
||||
pass
|
||||
@@ -1,4 +0,0 @@
|
||||
class SpawnerRuleEmitter:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
@@ -1,8 +0,0 @@
|
||||
import mcpacker.emit.markdown.mobreport
|
||||
|
||||
|
||||
# Tests ############################################################################################
|
||||
|
||||
def test_syntax():
|
||||
pass
|
||||
|
||||
+41
-6
@@ -5,6 +5,11 @@ from json import loads
|
||||
from typing import TypeAlias
|
||||
|
||||
|
||||
# Constants ########################################################################################
|
||||
|
||||
INDENT = " "
|
||||
|
||||
|
||||
# Type Support #####################################################################################
|
||||
|
||||
JsonBlob:TypeAlias = dict[str, "JsonBlob"] | list["JsonBlob"] | str | int | float | bool | None
|
||||
@@ -13,12 +18,42 @@ JsonBlob:TypeAlias = dict[str, "JsonBlob"] | list["JsonBlob"] | str | int | floa
|
||||
# Functions ########################################################################################
|
||||
|
||||
def removeNoneValues(blob:JsonBlob) -> JsonBlob:
|
||||
if not isinstance(blob, dict): return blob
|
||||
if isinstance(blob, dict):
|
||||
for key in list(blob.keys()):
|
||||
if blob[key] == None:
|
||||
del blob[key]
|
||||
else:
|
||||
blob[key] = removeNoneValues(blob[key])
|
||||
|
||||
for key in list(blob.keys()):
|
||||
if blob[key] == None:
|
||||
del blob[key]
|
||||
else:
|
||||
removeNoneValues(blob[key])
|
||||
if isinstance(blob, list):
|
||||
result:list[JsonBlob] = []
|
||||
for value in blob:
|
||||
if value != None:
|
||||
result.append(removeNoneValues(value))
|
||||
|
||||
return result
|
||||
|
||||
return blob
|
||||
|
||||
def removeEmptyObjects(blob:JsonBlob) -> JsonBlob:
|
||||
if isinstance(blob, dict):
|
||||
for key in list(blob.keys()):
|
||||
value = blob[key]
|
||||
if isinstance(value, dict):
|
||||
if len(value) == 0:
|
||||
del blob[key]
|
||||
else:
|
||||
blob[key] = removeEmptyObjects(value)
|
||||
|
||||
return blob
|
||||
|
||||
if isinstance(blob, list):
|
||||
result:list[JsonBlob] = []
|
||||
for value in blob:
|
||||
if isinstance(value, dict):
|
||||
if len(value) > 0:
|
||||
result.append(removeEmptyObjects(value))
|
||||
|
||||
return result
|
||||
|
||||
return blob
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
from mcpacker.json import JsonBlob
|
||||
from pytest import fixture
|
||||
from typing import cast
|
||||
|
||||
import mcpacker.json as json
|
||||
|
||||
|
||||
# Fixtures #########################################################################################
|
||||
|
||||
@fixture(name="blob")
|
||||
def createBlob():
|
||||
yield {
|
||||
"alpha": "a",
|
||||
"bravo": 12,
|
||||
"charlie": True,
|
||||
"delta": [
|
||||
"foxtrot",
|
||||
13,
|
||||
False,
|
||||
[ "golf" ],
|
||||
{ "hotel": "india" },
|
||||
None,
|
||||
],
|
||||
"juliette": {
|
||||
"kilo": "lima",
|
||||
"mike": {},
|
||||
"november": None
|
||||
},
|
||||
"oscar": None,
|
||||
"papa": [],
|
||||
}
|
||||
|
||||
@fixture(name="noNone")
|
||||
def removeNoneValues(blob:JsonBlob):
|
||||
yield json.removeNoneValues(blob)
|
||||
|
||||
@fixture(name="noEmpty")
|
||||
def remmoveEmptyObjects(blob:JsonBlob):
|
||||
yield json.removeEmptyObjects(blob)
|
||||
|
||||
|
||||
# Tests ############################################################################################
|
||||
|
||||
def test_removeNoneValues(noNone:JsonBlob):
|
||||
assert len(cast(dict[str,list[str]], noNone)["delta"]) == 5
|
||||
assert "november" not in cast(dict[str,dict[str,JsonBlob]], noNone)["juliette"]
|
||||
assert "oscar" not in cast(dict[str,JsonBlob], noNone)
|
||||
|
||||
def test_removeEmptyObjects(noEmpty:JsonBlob):
|
||||
assert "mike" not in cast(dict[str,dict[str,JsonBlob]], noEmpty)["juliette"]
|
||||
assert "papa" in cast(dict[str,JsonBlob], noEmpty)
|
||||
@@ -7,8 +7,4 @@ from typing import Iterator
|
||||
# Classes ##########################################################################################
|
||||
|
||||
class BiomeCatalog(Catalog[Biome]):
|
||||
|
||||
def matching(self, biomeFilter:BiomeFilter) -> Iterator[Biome]:
|
||||
for biome in self:
|
||||
if biomeFilter.accepts(biome):
|
||||
yield biome
|
||||
pass
|
||||
|
||||
@@ -43,4 +43,4 @@ def createCatalog():
|
||||
# Tests ############################################################################################
|
||||
|
||||
def test_findJungles(catalog, jungles):
|
||||
assert [b.city for b in catalog.matching(jungles)] == ["singapore"]
|
||||
assert [b.city for b in catalog.filter(lambda b: jungles.accepts(b))] == ["singapore"]
|
||||
|
||||
@@ -12,15 +12,12 @@ class Mob:
|
||||
"""
|
||||
|
||||
def __init__(self, gameId:str, active:Active|Iterable[Active]=AC.DIURNAL):
|
||||
if isinstance(active, Active):
|
||||
active = [active]
|
||||
|
||||
self.gameId = gameId
|
||||
self.active = active
|
||||
|
||||
if not self.active:
|
||||
self.active = AC.DIURNAL
|
||||
|
||||
if isinstance(self.active, Active):
|
||||
self.active = (self.active,)
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
if type(self) != type(other): return False
|
||||
if self.gameId != other.gameId: return False
|
||||
|
||||
@@ -36,4 +36,4 @@ def test_str(squid):
|
||||
assert str(squid) == "minecraft:squid"
|
||||
|
||||
def test_repr(cow):
|
||||
assert repr(cow) == "Mob<minecraft:cow>{active:(Active<day>{start:0, end:12000},)}"
|
||||
assert repr(cow) == "Mob<minecraft:cow>{active:[Active<day>{start:0, end:12000}]}"
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
from mcpacker.json import JsonBlob
|
||||
from mcpacker.model.incontrol.extracondition import ExtraCondition
|
||||
from mcpacker.model.core.dimension import Dimension
|
||||
|
||||
import mcpacker.json as json
|
||||
import mcpacker.model.core.altitude as A
|
||||
import mcpacker.model.core.dimension as D
|
||||
|
||||
|
||||
# Class ############################################################################################
|
||||
|
||||
class Condition:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
dimension:Dimension=D.OVERWORLD,
|
||||
heightMin:int|None=None,
|
||||
heightMax:int|None=None,
|
||||
thisMax:int|None=None,
|
||||
inWater:bool|None=None,
|
||||
andCondition:ExtraCondition|None=None,
|
||||
):
|
||||
self.dimension = dimension
|
||||
self.heightMin = heightMin
|
||||
self.heightMax = heightMax
|
||||
self.thisMax = thisMax
|
||||
self.inWater = inWater
|
||||
self.andCondition = andCondition
|
||||
|
||||
def asJsonBlob(self) -> JsonBlob:
|
||||
return json.removeNoneValues({
|
||||
"dimension": self.dimension.name,
|
||||
"heightmin": self.heightMin,
|
||||
"heightmax": self.heightMax,
|
||||
"maxthis": self.thisMax,
|
||||
"inwater": self.inWater,
|
||||
"and": None if not self.andCondition else self.andCondition.asJsonBlob(),
|
||||
})
|
||||
@@ -1,44 +0,0 @@
|
||||
from collections.abc import Iterable
|
||||
from mcpacker.json import JsonBlob
|
||||
|
||||
import mcpacker.json as json
|
||||
|
||||
|
||||
# Class ############################################################################################
|
||||
|
||||
class ExtraCondition:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
autumn:bool|None=None,
|
||||
biome:Iterable[str]|None=None,
|
||||
cave:bool|None=None,
|
||||
seeSky:bool|None=None,
|
||||
spring:bool|None=None,
|
||||
summer:bool|None=None,
|
||||
timeMax:int|None=None,
|
||||
timeMin:int|None=None,
|
||||
winter:bool|None=None,
|
||||
):
|
||||
self.autumn = autumn
|
||||
self.biome = biome
|
||||
self.cave = cave
|
||||
self.seeSky = seeSky
|
||||
self.spring = spring
|
||||
self.summer = summer
|
||||
self.timeMax = timeMax
|
||||
self.timeMin = timeMin
|
||||
self.winter = winter
|
||||
|
||||
def asJsonBlob(self) -> JsonBlob:
|
||||
return json.removeNoneValues({
|
||||
"autumn": self.autumn,
|
||||
"biome": None if not self.biome else "[" + ", ".join(self.biome) + "]",
|
||||
"cave": self.cave,
|
||||
"seesky": self.seeSky,
|
||||
"spring": self.spring,
|
||||
"summer": self.summer,
|
||||
"maxtime": self.timeMax,
|
||||
"mintime": self.timeMin,
|
||||
"winter": self.winter,
|
||||
})
|
||||
@@ -1,41 +0,0 @@
|
||||
from mcpacker.json import JsonBlob
|
||||
from mcpacker.model.core.fauna.mobspawn import MobSpawn
|
||||
from mcpacker.model.incontrol.condition import Condition
|
||||
|
||||
|
||||
# Class ############################################################################################
|
||||
|
||||
class SpawnerRule:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mobGameId:str,
|
||||
condition:Condition,
|
||||
amountMin:int=1,
|
||||
amountMax:int=1,
|
||||
rate:float=1.0,
|
||||
attempts:int=1,
|
||||
weight:int=1,
|
||||
):
|
||||
self.mobGameId = mobGameId
|
||||
self.amountMin = amountMin
|
||||
self.amountMax = amountMax
|
||||
self.rate = rate
|
||||
self.attempts = attempts
|
||||
self.weight = weight
|
||||
self.condition = condition
|
||||
|
||||
def asJsonBlob(self) -> JsonBlob:
|
||||
return {
|
||||
"mob": [self.mobGameId],
|
||||
"amount": {
|
||||
"minimum": self.amountMin,
|
||||
"maximum": self.amountMax,
|
||||
"groupdistance": 4,
|
||||
},
|
||||
"persecond": self.rate,
|
||||
"attempts": self.attempts,
|
||||
"weights": [self.weight],
|
||||
"conditions": self.condition.asJsonBlob(),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Constants ########################################################################################
|
||||
|
||||
TICK = 1
|
||||
SEC = 20 * TICK
|
||||
MIN = 60 * SEC
|
||||
HOUR = 60 * MIN
|
||||
DAY = 24 * HOUR
|
||||
@@ -1,4 +1,4 @@
|
||||
import mcpacker.emit.modpack
|
||||
import mcpacker.model.time
|
||||
|
||||
|
||||
# Tests ############################################################################################
|
||||
+22
-12
@@ -1,8 +1,11 @@
|
||||
from mcpacker.emit.markdown.reportwriter import ReportWriter
|
||||
from mcpacker.emit.markdown.biomereport import BiomeReport
|
||||
from mcpacker.emit.markdown.mineralreport import MineralReport
|
||||
from mcpacker.emit.markdown.mobreport import MobReport
|
||||
from mcpacker.model.modpack import ModPack
|
||||
from mcpacker.model.modpack import ModPack
|
||||
from mcpacker.write.incontrol.spawnerwriter import SpawnerWriter
|
||||
from mcpacker.write.markdown.biomereport import BiomeReport
|
||||
from mcpacker.write.markdown.mineralreport import MineralReport
|
||||
from mcpacker.write.markdown.mobspawnreport import MobSpawnReport
|
||||
from mcpacker.write.markdown.reportwriter import ReportWriter
|
||||
from mcpacker.write.writer import CompositeWriter
|
||||
from pathlib import Path
|
||||
|
||||
import inspect
|
||||
import os
|
||||
@@ -11,15 +14,16 @@ import sys
|
||||
|
||||
# Constants ########################################################################################
|
||||
|
||||
OUTPUT_PATH = "output"
|
||||
OUTPUT_PATH = Path("output")
|
||||
|
||||
|
||||
# Class ############################################################################################
|
||||
|
||||
class Runner:
|
||||
|
||||
def __init__(self, pack:ModPack|None=None):
|
||||
def __init__(self, pack:ModPack|None=None, outputDir:Path=OUTPUT_PATH):
|
||||
self.pack = pack or ModPack("untitled")
|
||||
self.outputDir = outputDir
|
||||
|
||||
def abort(self, message:str, status:int=-1):
|
||||
print(message)
|
||||
@@ -40,9 +44,15 @@ class Runner:
|
||||
# Commands #################################################################
|
||||
|
||||
def _command_writeReports(self):
|
||||
writer = ReportWriter([
|
||||
BiomeReport(self.pack),
|
||||
MineralReport(self.pack),
|
||||
MobReport(self.pack),
|
||||
writer = CompositeWriter(self.pack, self.outputDir, [
|
||||
BiomeReport,
|
||||
MineralReport,
|
||||
MobSpawnReport,
|
||||
])
|
||||
writer.write(os.path.join(OUTPUT_PATH, self.pack.name, "markdown"))
|
||||
writer.write()
|
||||
|
||||
def _command_writeModPack(self):
|
||||
writer = CompositeWriter(self.pack, self.outputDir, [
|
||||
SpawnerWriter,
|
||||
])
|
||||
writer.write()
|
||||
|
||||
@@ -1,8 +1,99 @@
|
||||
import mcpacker.ui.runner
|
||||
from mcpacker.model.core.ecology.biome import Biome
|
||||
from mcpacker.model.core.ecology.biomefilter import BiomeFilter as BF
|
||||
from mcpacker.model.core.fauna.mob import Mob
|
||||
from mcpacker.model.core.fauna.mobspawn import MobSpawn
|
||||
from mcpacker.model.core.geology.mineral import Mineral
|
||||
from mcpacker.model.core.geology.replacement import Replacement
|
||||
from mcpacker.model.core.habitat import Habitat
|
||||
from mcpacker.model.modpack import ModPack
|
||||
from mcpacker.ui.runner import Runner
|
||||
from mcpacker.write.incontrol.spawnerwriter import SpawnerWriter
|
||||
from pathlib import Path
|
||||
from pytest import fixture
|
||||
|
||||
import mcpacker.json as json
|
||||
import mcpacker.model.core.altitude as AL
|
||||
import mcpacker.model.core.ecology.flora as FL
|
||||
import mcpacker.model.core.ecology.geology as GE
|
||||
import mcpacker.model.core.ecology.heat as HE
|
||||
import mcpacker.model.core.ecology.humidity as HU
|
||||
import mcpacker.model.core.ecology.soil as SO
|
||||
import mcpacker.model.core.ecology.water as WA
|
||||
import mcpacker.model.core.fauna.active as AC
|
||||
import mcpacker.model.core.fauna.group as GR
|
||||
import mcpacker.model.core.fauna.location as LO
|
||||
import mcpacker.model.core.scarcity as SC
|
||||
import mcpacker.model.core.season as SE
|
||||
import textwrap
|
||||
|
||||
|
||||
# Fixtures #########################################################################################
|
||||
|
||||
@fixture(name="addBiomes")
|
||||
def defineAddBiomes():
|
||||
def addBiomes(pack:ModPack):
|
||||
pack.world.biomes.add(Biome("singapore", "minecraft:jungle",
|
||||
FL.CANOPY, GE.SEDIMENTARY, HE.TROPICAL, HU.WET, SO.ACIDIC, WA.INLAND
|
||||
))
|
||||
|
||||
yield addBiomes
|
||||
|
||||
@fixture(name="addMobs")
|
||||
def defineAddMobs():
|
||||
def addMobs(pack:ModPack):
|
||||
pack.world.mobs.add(Mob("minecraft:chicken", AC.DIURNAL))
|
||||
|
||||
yield addMobs
|
||||
|
||||
@fixture(name="addMobSpawns")
|
||||
def defineAddMobSpawnss():
|
||||
def addMobSpawns(pack:ModPack):
|
||||
mobs = pack.world.mobs
|
||||
spawns = pack.world.mobSpawns
|
||||
|
||||
pack.world.mobSpawns.add(
|
||||
MobSpawn(mobs["minecraft:chicken"],
|
||||
Habitat(
|
||||
altitude = AL.span(AL.LOWLANDS, AL.UPLANDS),
|
||||
biomeFilter = BF([HE.TROPICAL, HU.WET, FL.within(FL.CANOPY, FL.CLEARING)]),
|
||||
seasons = SE.SUMMER,
|
||||
group = GR.TROUP,
|
||||
scarcity = SC.COMMON,
|
||||
).derive(
|
||||
seasons = SE.exclude(SE.SUMMER),
|
||||
scarcity = SC.UNCOMMON
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
yield addMobSpawns
|
||||
|
||||
@fixture(name="pack")
|
||||
def createPack(addBiomes, addMobs, addMobSpawns):
|
||||
pack = ModPack("testModPack")
|
||||
pack.augment(addBiomes)
|
||||
pack.augment(addMobs)
|
||||
pack.augment(addMobSpawns)
|
||||
yield pack
|
||||
|
||||
@fixture(name="modPackRunner")
|
||||
def createModPackRunner(tmp_path:Path):
|
||||
runner = Runner(ModPack("testModPack"), tmp_path)
|
||||
runner._command_writeModPack()
|
||||
yield runner
|
||||
|
||||
@fixture(name="reportRunner")
|
||||
def createReportRunner(tmp_path:Path):
|
||||
runner = Runner(ModPack("testModPack"), tmp_path)
|
||||
runner._command_writeReports()
|
||||
yield runner
|
||||
|
||||
# Tests ############################################################################################
|
||||
|
||||
def test_syntax():
|
||||
pass
|
||||
def test_writeModPack(tmp_path:Path, modPackRunner:Runner):
|
||||
assert (tmp_path/"testModPack"/"config"/"incontrol"/"spawner.json").exists()
|
||||
|
||||
def test_writeReports(tmp_path:Path, reportRunner:Runner):
|
||||
assert (tmp_path/"testModPack"/"reports"/"biomes.md").exists()
|
||||
assert (tmp_path/"testModPack"/"reports"/"minerals.md").exists()
|
||||
assert (tmp_path/"testModPack"/"reports"/"mobspawns.md").exists()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import mcpacker.emit.datapack.locator
|
||||
import mcpacker.write.datapack.locator
|
||||
|
||||
|
||||
# Tests ############################################################################################
|
||||
@@ -0,0 +1,7 @@
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# Constants ########################################################################################
|
||||
|
||||
INCONTROL_CONFIG_DIR = Path("config") / "incontrol"
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
from mcpacker.json import JsonBlob
|
||||
from mcpacker.model.core.ecology.biomefilter import BiomeFilter
|
||||
from mcpacker.model.core.fauna.active import Active
|
||||
from mcpacker.model.core.fauna.mobspawn import MobSpawn
|
||||
from mcpacker.model.core.habitat import Habitat
|
||||
from mcpacker.model.core.scarcity import Scarcity
|
||||
from mcpacker.model.core.season import Season
|
||||
from mcpacker.model.modpack import ModPack
|
||||
from mcpacker.write.incontrol import INCONTROL_CONFIG_DIR
|
||||
from mcpacker.write.writer import Writer
|
||||
from pathlib import Path
|
||||
|
||||
import mcpacker.json as json
|
||||
import mcpacker.model.core.dimension as DI
|
||||
import mcpacker.model.core.fauna.location as LO
|
||||
import mcpacker.model.core.scarcity as SC
|
||||
import mcpacker.model.time as TI
|
||||
|
||||
|
||||
# Class ############################################################################################
|
||||
|
||||
class SpawnerWriter(Writer):
|
||||
|
||||
def __init__(self, pack:ModPack, outputDir:Path):
|
||||
super().__init__(pack, outputDir)
|
||||
|
||||
def write(self):
|
||||
path = self.outputDir / self.pack.name / INCONTROL_CONFIG_DIR / "spawner.json"
|
||||
path.parent.mkdir(parents=True)
|
||||
path.write_text(json.dumps(self._makeAllRules(), indent=json.INDENT))
|
||||
|
||||
# Private Functions ########################################################
|
||||
|
||||
def _computeBiomeNames(self, biomeFilter:BiomeFilter) -> JsonBlob:
|
||||
return list(
|
||||
b.gameId for b in self.pack.world.biomes.filter(
|
||||
lambda b: biomeFilter.accepts(b)
|
||||
)
|
||||
)
|
||||
|
||||
def _computeSpawnRate(self, scarcity:Scarcity) -> float:
|
||||
base = {
|
||||
SC.ABSENT: 24 * TI.HOUR,
|
||||
SC.RARE: 10 * TI.MIN,
|
||||
SC.UNUSUAL: 5 * TI.MIN,
|
||||
SC.SPARSE: 1 * TI.MIN,
|
||||
SC.UNCOMMON: 15 * TI.SEC,
|
||||
SC.COMMON: 5 * TI.SEC,
|
||||
SC.CARPET: 1 * TI.SEC,
|
||||
}[scarcity]
|
||||
|
||||
return 1.0 / (base / 20)
|
||||
|
||||
def _computeWeight(self, scarcity:Scarcity) -> int:
|
||||
return {
|
||||
SC.ABSENT: 0,
|
||||
SC.RARE: 1,
|
||||
SC.UNUSUAL: 2,
|
||||
SC.SPARSE: 4,
|
||||
SC.UNCOMMON: 8,
|
||||
SC.COMMON: 16,
|
||||
SC.CARPET: 32,
|
||||
}[scarcity]
|
||||
|
||||
def _makeAndCondition(self, habitat:Habitat, season:Season, active:Active) -> JsonBlob:
|
||||
return {
|
||||
"biome": self._computeBiomeNames(habitat.biomeFilter),
|
||||
season.name: True,
|
||||
"seesky": True if LO.OUTSIDE == habitat.location else None,
|
||||
"cave": True if LO.CAVE == habitat.location else None,
|
||||
"mintime": active.start,
|
||||
"maxtime": active.end,
|
||||
}
|
||||
|
||||
def _makeAllRules(self) -> JsonBlob:
|
||||
result:list[JsonBlob] = []
|
||||
|
||||
for spawn in self.pack.world.mobSpawns:
|
||||
for habitat in spawn.habitats:
|
||||
for season in habitat.seasons:
|
||||
for active in spawn.mob.active:
|
||||
result.append(self._makeRule(spawn, habitat, season, active))
|
||||
|
||||
return json.removeEmptyObjects(json.removeNoneValues(result))
|
||||
|
||||
def _makeCondition(self, habitat:Habitat, season:Season, active:Active) -> JsonBlob:
|
||||
return {
|
||||
"dimension": DI.OVERWORLD.name,
|
||||
"minheight": habitat.altitude.bottom,
|
||||
"maxheight": habitat.altitude.top,
|
||||
"maxthis": habitat.group.largest * 2,
|
||||
"inwater": LO.WATER == habitat.location,
|
||||
"and": self._makeAndCondition(habitat, season, active),
|
||||
}
|
||||
|
||||
def _makeRule(self, spawn:MobSpawn, habitat:Habitat, season:Season, active:Active,) -> JsonBlob:
|
||||
return {
|
||||
"mob": spawn.mob.gameId,
|
||||
"amount": {
|
||||
"minimum": habitat.group.smallest,
|
||||
"maximum": habitat.group.largest,
|
||||
"groupdistance": 4,
|
||||
},
|
||||
"persecond": self._computeSpawnRate(habitat.scarcity),
|
||||
"attempts": habitat.group.largest * 4,
|
||||
"weights": [ self._computeWeight(habitat.scarcity) ],
|
||||
"conditions": self._makeCondition(habitat, season, active),
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
from mcpacker.model.core.ecology.biome import Biome
|
||||
from mcpacker.model.core.ecology.biomefilter import BiomeFilter as BF
|
||||
from mcpacker.model.core.fauna.mob import Mob
|
||||
from mcpacker.model.core.fauna.mobspawn import MobSpawn
|
||||
from mcpacker.model.core.geology.mineral import Mineral
|
||||
from mcpacker.model.core.geology.replacement import Replacement
|
||||
from mcpacker.model.core.habitat import Habitat
|
||||
from mcpacker.model.modpack import ModPack
|
||||
from mcpacker.write.incontrol.spawnerwriter import SpawnerWriter
|
||||
from pathlib import Path
|
||||
from pytest import fixture
|
||||
|
||||
import mcpacker.json as json
|
||||
import mcpacker.model.core.altitude as AL
|
||||
import mcpacker.model.core.ecology.flora as FL
|
||||
import mcpacker.model.core.ecology.geology as GE
|
||||
import mcpacker.model.core.ecology.heat as HE
|
||||
import mcpacker.model.core.ecology.humidity as HU
|
||||
import mcpacker.model.core.ecology.soil as SO
|
||||
import mcpacker.model.core.ecology.water as WA
|
||||
import mcpacker.model.core.fauna.active as AC
|
||||
import mcpacker.model.core.fauna.group as GR
|
||||
import mcpacker.model.core.fauna.location as LO
|
||||
import mcpacker.model.core.scarcity as SC
|
||||
import mcpacker.model.core.season as SE
|
||||
import textwrap
|
||||
|
||||
|
||||
# Fixtures #########################################################################################
|
||||
|
||||
@fixture(name="addBiomes")
|
||||
def defineAddBiomes():
|
||||
def addBiomes(pack:ModPack):
|
||||
pack.world.biomes.add(Biome("singapore", "minecraft:jungle",
|
||||
FL.CANOPY, GE.SEDIMENTARY, HE.TROPICAL, HU.WET, SO.ACIDIC, WA.INLAND
|
||||
))
|
||||
|
||||
yield addBiomes
|
||||
|
||||
@fixture(name="addMobs")
|
||||
def defineAddMobs():
|
||||
def addMobs(pack:ModPack):
|
||||
pack.world.mobs.add(Mob("minecraft:chicken", AC.DIURNAL))
|
||||
|
||||
yield addMobs
|
||||
|
||||
@fixture(name="addMobSpawns")
|
||||
def defineAddMobSpawnss():
|
||||
def addMobSpawns(pack:ModPack):
|
||||
mobs = pack.world.mobs
|
||||
spawns = pack.world.mobSpawns
|
||||
|
||||
pack.world.mobSpawns.add(
|
||||
MobSpawn(mobs["minecraft:chicken"],
|
||||
Habitat(
|
||||
altitude = AL.span(AL.LOWLANDS, AL.UPLANDS),
|
||||
biomeFilter = BF([HE.TROPICAL, HU.WET, FL.within(FL.CANOPY, FL.CLEARING)]),
|
||||
seasons = SE.SUMMER,
|
||||
group = GR.TROUP,
|
||||
scarcity = SC.COMMON,
|
||||
).derive(
|
||||
seasons = SE.exclude(SE.SUMMER),
|
||||
scarcity = SC.UNCOMMON
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
yield addMobSpawns
|
||||
|
||||
@fixture(name="pack")
|
||||
def createPack(addBiomes, addMobs, addMobSpawns):
|
||||
pack = ModPack("testModPack")
|
||||
pack.augment(addBiomes)
|
||||
pack.augment(addMobs)
|
||||
pack.augment(addMobSpawns)
|
||||
yield pack
|
||||
|
||||
@fixture(name="writer")
|
||||
def createWriter(pack, tmp_path):
|
||||
writer = SpawnerWriter(pack, tmp_path)
|
||||
writer.write()
|
||||
yield writer
|
||||
|
||||
|
||||
# Tests ############################################################################################
|
||||
|
||||
def test_write(writer:SpawnerWriter, tmp_path:Path):
|
||||
path = tmp_path / "testModPack" / "config" / "incontrol" / "spawner.json"
|
||||
text = path.read_text()
|
||||
data = json.loads(text)
|
||||
|
||||
assert len(data) == 4
|
||||
|
||||
rule = data[0]
|
||||
assert rule["mob"] == "minecraft:chicken"
|
||||
assert rule["amount"]["maximum"] == 6
|
||||
assert abs(rule["persecond"] - 0.2) < 0.001
|
||||
assert rule["conditions"]["and"]["biome"] == ["minecraft:jungle"]
|
||||
assert rule["conditions"]["and"]["summer"]
|
||||
|
||||
rule = data[1]
|
||||
assert abs(rule["persecond"] - 0.066) < 0.001
|
||||
assert rule["conditions"]["and"]["spring"]
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
from mcpacker.emit.markdown.report import Report
|
||||
from mcpacker.model.modpack import ModPack
|
||||
from mcpacker.write.markdown.markdownwriter import MarkdownWriter
|
||||
from mcpacker.model.modpack import ModPack
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# Class ############################################################################################
|
||||
|
||||
class BiomeReport(Report):
|
||||
class BiomeReport(MarkdownWriter):
|
||||
|
||||
def __init__(self, pack:ModPack):
|
||||
super().__init__("biomes.md", pack)
|
||||
def __init__(self, pack:ModPack, outputDir:Path):
|
||||
super().__init__("biomes.md", pack, outputDir)
|
||||
|
||||
def build(self):
|
||||
def compose(self):
|
||||
for biome in self.pack.world.biomes:
|
||||
self.line(f"# Biome: {biome.gameId} ({biome.city})")
|
||||
self.line()
|
||||
+10
-8
@@ -1,4 +1,4 @@
|
||||
from mcpacker.emit.markdown.biomereport import BiomeReport
|
||||
from mcpacker.write.markdown.biomereport import BiomeReport
|
||||
from mcpacker.model.core.ecology.biome import Biome
|
||||
from mcpacker.model.modpack import ModPack
|
||||
from pytest import fixture
|
||||
@@ -33,20 +33,21 @@ def defineAddBiomes():
|
||||
|
||||
@fixture(name="pack")
|
||||
def createPack(addBiomes):
|
||||
pack = ModPack("test")
|
||||
pack = ModPack("testModPack")
|
||||
pack.augment(addBiomes)
|
||||
yield pack
|
||||
|
||||
@fixture(name="report")
|
||||
def createReport(pack):
|
||||
report = BiomeReport(pack)
|
||||
report.build()
|
||||
def createReport(pack, tmp_path):
|
||||
report = BiomeReport(pack, tmp_path)
|
||||
report.write()
|
||||
yield report
|
||||
|
||||
# Tests ############################################################################################
|
||||
|
||||
def test_report(report):
|
||||
assert str(report) == textwrap.dedent("""
|
||||
def test_report(tmp_path, report):
|
||||
path = tmp_path / "testModPack" / "reports" / "biomes.md"
|
||||
assert path.read_text() == textwrap.dedent("""
|
||||
# Biome: minecraft:plains (kansascity)
|
||||
|
||||
* Flora: field
|
||||
@@ -64,4 +65,5 @@ def test_report(report):
|
||||
* Humidity: dry
|
||||
* Soil: sandy
|
||||
* Water: inland
|
||||
""").strip()
|
||||
|
||||
""").lstrip()
|
||||
@@ -0,0 +1,71 @@
|
||||
from mcpacker.write.writer import Writer
|
||||
from mcpacker.model.modpack import ModPack
|
||||
from pathlib import Path
|
||||
from typing import TextIO
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
|
||||
# Constants ########################################################################################
|
||||
|
||||
INDENT = " "
|
||||
REPORT_DIR_PATH = "reports"
|
||||
|
||||
|
||||
# Class ############################################################################################
|
||||
|
||||
class MarkdownWriter(Writer):
|
||||
|
||||
def __init__(self, name:str, pack:ModPack, outputDir:Path):
|
||||
super().__init__(pack, outputDir)
|
||||
|
||||
self._file:TextIO|None = None
|
||||
self._name = name
|
||||
self._indent = 0
|
||||
self._lineBuffer:list[str] = []
|
||||
|
||||
def write(self):
|
||||
path = self.outputDir / self.pack.name / REPORT_DIR_PATH / self._name
|
||||
|
||||
path.parent.mkdir(exist_ok=True, parents=True)
|
||||
with path.open("w") as file:
|
||||
try:
|
||||
self._file = file
|
||||
self.compose()
|
||||
finally:
|
||||
self._file = None
|
||||
|
||||
def compose(self) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
# Helper Methods ###########################################################
|
||||
|
||||
def indent(self) -> "MarkdownWriter":
|
||||
self._indent += 1
|
||||
self._lineBuffer.insert(0, INDENT)
|
||||
return self
|
||||
|
||||
def line(self, text:str="") -> "MarkdownWriter":
|
||||
assert self._file != None
|
||||
self.text(text)
|
||||
|
||||
self._file.write("".join(self._lineBuffer) + "\n")
|
||||
self._lineBuffer = []
|
||||
while len(self._lineBuffer) < self._indent:
|
||||
self._lineBuffer.append(INDENT)
|
||||
|
||||
return self
|
||||
|
||||
def outdent(self) -> "MarkdownWriter":
|
||||
self._indent = max(0, self._indent - 1)
|
||||
|
||||
if self._lineBuffer:
|
||||
if self._lineBuffer[0] == INDENT:
|
||||
self._lineBuffer.pop()
|
||||
|
||||
return self
|
||||
|
||||
def text(self, text:str) -> "MarkdownWriter":
|
||||
self._lineBuffer.append(str(text))
|
||||
return self
|
||||
@@ -0,0 +1,43 @@
|
||||
from mcpacker.model.modpack import ModPack
|
||||
from mcpacker.write.markdown.markdownwriter import MarkdownWriter
|
||||
from pathlib import Path
|
||||
from pytest import fixture
|
||||
|
||||
import textwrap
|
||||
|
||||
|
||||
# Helpers ##########################################################################################
|
||||
|
||||
class SampleWriter(MarkdownWriter):
|
||||
|
||||
def compose(self):
|
||||
(self.text("alpha")
|
||||
.line("bravo")
|
||||
.indent()
|
||||
.line("charlie")
|
||||
.indent()
|
||||
.line("delta")
|
||||
.outdent()
|
||||
.line("echo")
|
||||
)
|
||||
|
||||
|
||||
# Fixtures #########################################################################################
|
||||
|
||||
@fixture(name="writer")
|
||||
def createWriter(tmp_path:Path):
|
||||
writer = SampleWriter("testReport.md", ModPack("testModPack"), tmp_path)
|
||||
writer.write()
|
||||
yield writer
|
||||
|
||||
|
||||
# Tests ############################################################################################
|
||||
|
||||
def test_write(tmp_path, writer):
|
||||
path = tmp_path / "testModPack" / "reports" / "testReport.md"
|
||||
assert path.read_text() == textwrap.dedent("""
|
||||
alphabravo
|
||||
charlie
|
||||
delta
|
||||
echo
|
||||
""").lstrip()
|
||||
@@ -1,15 +1,16 @@
|
||||
from mcpacker.emit.markdown.report import Report
|
||||
from mcpacker.model.modpack import ModPack
|
||||
from mcpacker.write.markdown.markdownwriter import MarkdownWriter
|
||||
from mcpacker.model.modpack import ModPack
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# Class ############################################################################################
|
||||
|
||||
class MineralReport(Report):
|
||||
class MineralReport(MarkdownWriter):
|
||||
|
||||
def __init__(self, pack:ModPack):
|
||||
super().__init__("minerals.md", pack)
|
||||
def __init__(self, pack:ModPack, outputDir:Path):
|
||||
super().__init__("minerals.md", pack, outputDir)
|
||||
|
||||
def build(self):
|
||||
def compose(self):
|
||||
for mineral in self.pack.world.minerals:
|
||||
self.line(f"# Mineral: {mineral.name}")
|
||||
self.line()
|
||||
+11
-8
@@ -1,4 +1,4 @@
|
||||
from mcpacker.emit.markdown.mineralreport import MineralReport
|
||||
from mcpacker.write.markdown.mineralreport import MineralReport
|
||||
from mcpacker.model.core.geology.mineral import Mineral
|
||||
from mcpacker.model.core.geology.replacement import Replacement
|
||||
from mcpacker.model.modpack import ModPack
|
||||
@@ -26,21 +26,23 @@ def defineAddMinerals():
|
||||
|
||||
@fixture(name="pack")
|
||||
def createPack(addMinerals):
|
||||
pack = ModPack("test")
|
||||
pack = ModPack("testModPack")
|
||||
pack.augment(addMinerals)
|
||||
yield pack
|
||||
|
||||
@fixture(name="report")
|
||||
def createReport(pack):
|
||||
report = MineralReport(pack)
|
||||
report.build()
|
||||
def createReport(pack, tmp_path):
|
||||
report = MineralReport(pack, tmp_path)
|
||||
report.write()
|
||||
yield report
|
||||
|
||||
|
||||
# Tests ############################################################################################
|
||||
|
||||
def test_report(report):
|
||||
assert str(report) == textwrap.dedent("""
|
||||
def test_report(tmp_path, report):
|
||||
path = tmp_path / "testModPack" / "reports" / "minerals.md"
|
||||
|
||||
assert path.read_text() == textwrap.dedent("""
|
||||
# Mineral: copper
|
||||
|
||||
* #minecraft:stone_replaceables => minecraft:copper_ore
|
||||
@@ -50,4 +52,5 @@ def test_report(report):
|
||||
|
||||
* #minecraft:stone_replaceables => minecraft:iron_ore
|
||||
* #minecraft:deepslate_replaceables => minecraft:deepslate_iron_ore
|
||||
""").strip()
|
||||
|
||||
""").lstrip()
|
||||
@@ -1,22 +1,23 @@
|
||||
from mcpacker.emit.markdown.report import Report
|
||||
from mcpacker.model.core.ecology.biometrait import BiomeTrait
|
||||
from mcpacker.model.modpack import ModPack
|
||||
from mcpacker.write.markdown.markdownwriter import MarkdownWriter
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# Class ############################################################################################
|
||||
|
||||
class MobReport(Report):
|
||||
class MobSpawnReport(MarkdownWriter):
|
||||
|
||||
def __init__(self, pack:ModPack):
|
||||
super().__init__("mobs.md", pack)
|
||||
def __init__(self, pack:ModPack, outputDir:Path):
|
||||
super().__init__("mobspawns.md", pack, outputDir)
|
||||
|
||||
def build(self):
|
||||
for placement in self.pack.world.mobs:
|
||||
self.line(f"# Mob: {placement.gameId}")
|
||||
def compose(self):
|
||||
for spawn in self.pack.world.mobSpawns:
|
||||
self.line(f"# Mob: {spawn.gameId}")
|
||||
self.line()
|
||||
|
||||
self.indent()
|
||||
for index, habitat in enumerate(placement.habitats, start=1):
|
||||
for index, habitat in enumerate(spawn.habitats, start=1):
|
||||
self.text("* habitat ").line(index)
|
||||
self.indent()
|
||||
self.text("* altitude: ").line(habitat.altitude)
|
||||
@@ -0,0 +1,99 @@
|
||||
from mcpacker.model.core.ecology.biomefilter import BiomeFilter as BF
|
||||
from mcpacker.model.core.fauna.mob import Mob
|
||||
from mcpacker.model.core.fauna.mobspawn import MobSpawn
|
||||
from mcpacker.model.core.geology.mineral import Mineral
|
||||
from mcpacker.model.core.geology.replacement import Replacement
|
||||
from mcpacker.model.core.habitat import Habitat
|
||||
from mcpacker.model.modpack import ModPack
|
||||
from mcpacker.write.markdown.mobspawnreport import MobSpawnReport
|
||||
from pytest import fixture
|
||||
|
||||
import mcpacker.model.core.altitude as AL
|
||||
import mcpacker.model.core.ecology.flora as FL
|
||||
import mcpacker.model.core.ecology.geology as GE
|
||||
import mcpacker.model.core.ecology.heat as HE
|
||||
import mcpacker.model.core.ecology.humidity as HU
|
||||
import mcpacker.model.core.ecology.soil as SO
|
||||
import mcpacker.model.core.ecology.water as WA
|
||||
import mcpacker.model.core.fauna.active as AC
|
||||
import mcpacker.model.core.fauna.group as GR
|
||||
import mcpacker.model.core.fauna.location as LO
|
||||
import mcpacker.model.core.scarcity as SC
|
||||
import mcpacker.model.core.season as SE
|
||||
import textwrap
|
||||
|
||||
|
||||
# Fixtures #########################################################################################
|
||||
|
||||
@fixture(name="addMobs")
|
||||
def defineAddMobs():
|
||||
def addMobs(pack:ModPack):
|
||||
pack.world.mobs.add(Mob("minecraft:chicken", AC.DIURNAL))
|
||||
|
||||
yield addMobs
|
||||
|
||||
@fixture(name="addMobSpawns")
|
||||
def defineAddMobSpawnss():
|
||||
def addMobSpawns(pack:ModPack):
|
||||
mobs = pack.world.mobs
|
||||
spawns = pack.world.mobSpawns
|
||||
|
||||
pack.world.mobSpawns.add(
|
||||
MobSpawn(mobs["minecraft:chicken"],
|
||||
Habitat(
|
||||
altitude = AL.span(AL.LOWLANDS, AL.UPLANDS),
|
||||
biomeFilter = BF([HE.TROPICAL, HU.WET, FL.within(FL.CANOPY, FL.CLEARING)]),
|
||||
seasons = SE.SUMMER,
|
||||
group = GR.TROUP,
|
||||
scarcity = SC.COMMON,
|
||||
).derive(
|
||||
seasons = SE.exclude(SE.SUMMER),
|
||||
scarcity = SC.UNCOMMON
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
yield addMobSpawns
|
||||
|
||||
@fixture(name="pack")
|
||||
def createPack(addMobs, addMobSpawns):
|
||||
pack = ModPack("testModPack")
|
||||
pack.augment(addMobs)
|
||||
pack.augment(addMobSpawns)
|
||||
yield pack
|
||||
|
||||
@fixture(name="report")
|
||||
def createReport(pack, tmp_path):
|
||||
report = MobSpawnReport(pack, tmp_path)
|
||||
report.write()
|
||||
yield report
|
||||
|
||||
# Tests ############################################################################################
|
||||
|
||||
def test_write(report, tmp_path):
|
||||
path = tmp_path / "testModPack" / "reports" / "mobspawns.md"
|
||||
assert path.read_text() == textwrap.dedent("""
|
||||
# Mob: minecraft:chicken
|
||||
|
||||
* habitat 1
|
||||
* altitude: lowlands-uplands
|
||||
* biomeFilter:
|
||||
* Heat: tropical
|
||||
* Humidity: wet
|
||||
* Flora: any of: canopy, forest, clearing
|
||||
* seasons: summer
|
||||
* group: troup
|
||||
* location: outside
|
||||
* scarcity: common
|
||||
* habitat 2
|
||||
* altitude: lowlands-uplands
|
||||
* biomeFilter:
|
||||
* Heat: tropical
|
||||
* Humidity: wet
|
||||
* Flora: any of: canopy, forest, clearing
|
||||
* seasons: spring, autumn, winter
|
||||
* group: troup
|
||||
* location: outside
|
||||
* scarcity: uncommon
|
||||
|
||||
""").lstrip()
|
||||
@@ -1,4 +1,4 @@
|
||||
from mcpacker.emit.markdown.report import Report
|
||||
from mcpacker.write.markdown.report import Report
|
||||
from pytest import fixture
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from mcpacker.emit.markdown.report import Report
|
||||
from mcpacker.write.markdown.report import Report
|
||||
|
||||
import os
|
||||
import shutil
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import mcpacker.emit.markdown.reportwriter
|
||||
import mcpacker.write.markdown.reportwriter
|
||||
|
||||
|
||||
# Tests ############################################################################################
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import mcpacker.emit.resourcepack
|
||||
import mcpacker.write.resourcepack
|
||||
|
||||
|
||||
# Tests ############################################################################################
|
||||
@@ -0,0 +1,54 @@
|
||||
from collections.abc import Iterable
|
||||
from mcpacker.model.modpack import ModPack
|
||||
from pathlib import Path
|
||||
from typing import TypeVar
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
|
||||
# Type Support #####################################################################################
|
||||
|
||||
WriterSubclass = TypeVar("WriterSubclass", bound="Writer")
|
||||
|
||||
|
||||
# Class ############################################################################################
|
||||
|
||||
class Writer:
|
||||
|
||||
def __init__(self, pack:ModPack, outputDir:Path):
|
||||
self.pack = pack
|
||||
self.outputDir = outputDir
|
||||
|
||||
def resetOutputDir(self):
|
||||
if self.outputDir.exists():
|
||||
shutil.rmtree(self.outputDir)
|
||||
|
||||
self.outputDir.mkdir(parents=True)
|
||||
|
||||
def write(self):
|
||||
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()
|
||||
Reference in New Issue
Block a user