Split recipe and page into a bunch of files
This commit is contained in:
parent
5620b97060
commit
99e1e899b9
9 changed files with 251 additions and 219 deletions
|
@ -1,118 +0,0 @@
|
|||
from dataclasses import dataclass, field
|
||||
from typing import Any, Literal, Self
|
||||
|
||||
from common.deserialize import TypeFn, load_json_data
|
||||
from common.tagged_union import InternallyTaggedUnion
|
||||
from common.types import Book, LocalizedItem
|
||||
from minecraft.resource import ResourceLocation
|
||||
|
||||
# ingredients/results
|
||||
|
||||
|
||||
@dataclass
|
||||
class ItemIngredientData:
|
||||
item: ResourceLocation | None = None
|
||||
tag: ResourceLocation | None = None
|
||||
|
||||
|
||||
# TODO: tagged union~!
|
||||
@dataclass
|
||||
class ModConditionalIngredient:
|
||||
type: Literal["hexcasting:mod_conditional"]
|
||||
default: ItemIngredientData
|
||||
if_loaded: ItemIngredientData
|
||||
modid: str
|
||||
|
||||
|
||||
ItemIngredient = (
|
||||
ItemIngredientData | ModConditionalIngredient | list[ItemIngredientData]
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class VillagerIngredient:
|
||||
minLevel: int
|
||||
profession: ResourceLocation | None = None
|
||||
biome: ResourceLocation | None = None
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class BlockState:
|
||||
name: LocalizedItem
|
||||
properties: dict[str, Any] | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class BlockStateIngredient:
|
||||
# TODO: StateIngredient should also be a TypeTaggedUnion, probably
|
||||
type: Literal["block"]
|
||||
block: ResourceLocation
|
||||
|
||||
|
||||
@dataclass
|
||||
class ItemResult:
|
||||
item: LocalizedItem
|
||||
count: int | None = None
|
||||
|
||||
|
||||
# recipe types
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class BaseRecipe(InternallyTaggedUnion, tag="type", value=None):
|
||||
id: ResourceLocation
|
||||
|
||||
type: ResourceLocation = field(init=False)
|
||||
group: str | None = None
|
||||
|
||||
def __init_subclass__(cls, type: str) -> None:
|
||||
super().__init_subclass__(__class__._tag_name, type)
|
||||
cls.type = ResourceLocation.from_str(type)
|
||||
|
||||
@classmethod
|
||||
def make_type_hook(cls, book: Book) -> TypeFn:
|
||||
"""Creates a type hook which, given a stringified ResourceLocation, loads and
|
||||
returns the recipe json at that location."""
|
||||
|
||||
def type_hook(raw_id: str | Any) -> Self | dict[str, Any]:
|
||||
if isinstance(raw_id, cls):
|
||||
return raw_id
|
||||
id = ResourceLocation.from_str(raw_id)
|
||||
|
||||
# FIXME: hack
|
||||
# the point of this is to ensure the recipe exists on all platforms
|
||||
# because we've had issues with that in the past, eg. in Hexal
|
||||
data: dict[str, Any] = {}
|
||||
for recipe_dir in book.props.recipe_dirs:
|
||||
# TODO: should this use id.namespace somewhere?
|
||||
data = load_json_data(cls, recipe_dir / f"{id.path}.json")
|
||||
|
||||
data["id"] = id
|
||||
return data
|
||||
|
||||
return type_hook
|
||||
|
||||
|
||||
@dataclass
|
||||
class CraftingShapedRecipe(BaseRecipe, type="minecraft:crafting_shaped"):
|
||||
pattern: list[str]
|
||||
key: dict[str, ItemIngredient]
|
||||
result: ItemResult
|
||||
|
||||
|
||||
@dataclass
|
||||
class CraftingShapelessRecipe(BaseRecipe, type="minecraft:crafting_shapeless"):
|
||||
ingredients: list[ItemIngredient]
|
||||
result: ItemResult
|
||||
|
||||
|
||||
@dataclass
|
||||
class BrainsweepRecipe(BaseRecipe, type="hexcasting:brainsweep"):
|
||||
blockIn: BlockStateIngredient
|
||||
villagerIn: VillagerIngredient
|
||||
result: BlockState
|
||||
|
||||
|
||||
CraftingRecipe = CraftingShapedRecipe | CraftingShapelessRecipe
|
||||
|
||||
Recipe = CraftingShapedRecipe | CraftingShapelessRecipe | BrainsweepRecipe
|
4
doc/src/minecraft/recipe/__init__.py
Normal file
4
doc/src/minecraft/recipe/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
|||
from .abstract import *
|
||||
from .concrete import *
|
||||
from .ingredient import *
|
||||
from .result import *
|
42
doc/src/minecraft/recipe/abstract.py
Normal file
42
doc/src/minecraft/recipe/abstract.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
from dataclasses import dataclass, field
|
||||
from typing import Any, Self
|
||||
|
||||
from common.deserialize import TypeFn, load_json_data
|
||||
from common.tagged_union import InternallyTaggedUnion
|
||||
from common.types import Book
|
||||
from minecraft.resource import ResourceLocation
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class BaseRecipe(InternallyTaggedUnion, tag="type", value=None):
|
||||
id: ResourceLocation
|
||||
|
||||
type: ResourceLocation = field(init=False)
|
||||
group: str | None = None
|
||||
|
||||
def __init_subclass__(cls, type: str) -> None:
|
||||
super().__init_subclass__(__class__._tag_name, type)
|
||||
cls.type = ResourceLocation.from_str(type)
|
||||
|
||||
@classmethod
|
||||
def make_type_hook(cls, book: Book) -> TypeFn:
|
||||
"""Creates a type hook which, given a stringified ResourceLocation, loads and
|
||||
returns the recipe json at that location."""
|
||||
|
||||
def type_hook(raw_id: str | Any) -> Self | dict[str, Any]:
|
||||
if isinstance(raw_id, cls):
|
||||
return raw_id
|
||||
id = ResourceLocation.from_str(raw_id)
|
||||
|
||||
# FIXME: hack
|
||||
# the point of this is to ensure the recipe exists on all platforms
|
||||
# because we've had issues with that in the past, eg. in Hexal
|
||||
data: dict[str, Any] = {}
|
||||
for recipe_dir in book.props.recipe_dirs:
|
||||
# TODO: should this use id.namespace somewhere?
|
||||
data = load_json_data(cls, recipe_dir / f"{id.path}.json")
|
||||
|
||||
data["id"] = id
|
||||
return data
|
||||
|
||||
return type_hook
|
30
doc/src/minecraft/recipe/concrete.py
Normal file
30
doc/src/minecraft/recipe/concrete.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from .abstract import BaseRecipe
|
||||
from .ingredient import BlockStateIngredient, ItemIngredient, VillagerIngredient
|
||||
from .result import BlockState, ItemResult
|
||||
|
||||
|
||||
@dataclass
|
||||
class CraftingShapedRecipe(BaseRecipe, type="minecraft:crafting_shaped"):
|
||||
pattern: list[str]
|
||||
key: dict[str, ItemIngredient]
|
||||
result: ItemResult
|
||||
|
||||
|
||||
@dataclass
|
||||
class CraftingShapelessRecipe(BaseRecipe, type="minecraft:crafting_shapeless"):
|
||||
ingredients: list[ItemIngredient]
|
||||
result: ItemResult
|
||||
|
||||
|
||||
@dataclass
|
||||
class BrainsweepRecipe(BaseRecipe, type="hexcasting:brainsweep"):
|
||||
blockIn: BlockStateIngredient
|
||||
villagerIn: VillagerIngredient
|
||||
result: BlockState
|
||||
|
||||
|
||||
CraftingRecipe = CraftingShapedRecipe | CraftingShapelessRecipe
|
||||
|
||||
Recipe = CraftingShapedRecipe | CraftingShapelessRecipe | BrainsweepRecipe
|
38
doc/src/minecraft/recipe/ingredient.py
Normal file
38
doc/src/minecraft/recipe/ingredient.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
|
||||
from minecraft.resource import ResourceLocation
|
||||
|
||||
|
||||
@dataclass
|
||||
class ItemIngredientData:
|
||||
item: ResourceLocation | None = None
|
||||
tag: ResourceLocation | None = None
|
||||
|
||||
|
||||
# TODO: tagged union~!
|
||||
@dataclass
|
||||
class ModConditionalIngredient:
|
||||
type: Literal["hexcasting:mod_conditional"]
|
||||
default: ItemIngredientData
|
||||
if_loaded: ItemIngredientData
|
||||
modid: str
|
||||
|
||||
|
||||
ItemIngredient = (
|
||||
ItemIngredientData | ModConditionalIngredient | list[ItemIngredientData]
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class VillagerIngredient:
|
||||
minLevel: int
|
||||
profession: ResourceLocation | None = None
|
||||
biome: ResourceLocation | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class BlockStateIngredient:
|
||||
# TODO: StateIngredient should also be a TypeTaggedUnion, probably
|
||||
type: Literal["block"]
|
||||
block: ResourceLocation
|
16
doc/src/minecraft/recipe/result.py
Normal file
16
doc/src/minecraft/recipe/result.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from common.types import LocalizedItem
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class BlockState:
|
||||
name: LocalizedItem
|
||||
properties: dict[str, Any] | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class ItemResult:
|
||||
item: LocalizedItem
|
||||
count: int | None = None
|
2
doc/src/patchouli/page/__init__.py
Normal file
2
doc/src/patchouli/page/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
from .abstract import *
|
||||
from .concrete import *
|
108
doc/src/patchouli/page/abstract.py
Normal file
108
doc/src/patchouli/page/abstract.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Callable, Self
|
||||
|
||||
from common.deserialize import TypeFn, rename
|
||||
from common.formatting import FormatTree
|
||||
from common.pattern import RawPatternInfo
|
||||
from common.tagged_union import InternallyTaggedUnion
|
||||
from common.types import Book, BookHelpers, LocalizedStr
|
||||
from minecraft.recipe import CraftingRecipe
|
||||
from minecraft.resource import ResourceLocation
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class BasePage(InternallyTaggedUnion, BookHelpers, tag="type", value=None):
|
||||
"""Fields shared by all Page types.
|
||||
|
||||
See: https://vazkiimods.github.io/Patchouli/docs/patchouli-basics/page-types
|
||||
"""
|
||||
|
||||
book: Book
|
||||
|
||||
type: ResourceLocation = field(init=False)
|
||||
advancement: ResourceLocation | None = None
|
||||
flag: str | None = None
|
||||
anchor: str | None = None
|
||||
|
||||
def __init_subclass__(cls, type: str | None) -> None:
|
||||
super().__init_subclass__(__class__._tag_name, type)
|
||||
if type is not None:
|
||||
cls.type = ResourceLocation.from_str(type)
|
||||
|
||||
@classmethod
|
||||
def make_type_hook(cls, book: Book) -> TypeFn:
|
||||
def type_hook(data: Self | dict[str, Any]) -> Self | dict[str, Any]:
|
||||
if isinstance(data, cls):
|
||||
return data
|
||||
data = cls.assert_tag(data)
|
||||
data["book"] = book
|
||||
return data
|
||||
|
||||
return type_hook
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class PageWithText(BasePage, type=None):
|
||||
text: FormatTree | None = None
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class PageWithTitle(PageWithText, type=None):
|
||||
_title: LocalizedStr | None = field(default=None, metadata=rename("title"))
|
||||
|
||||
@property
|
||||
def title(self) -> LocalizedStr | None:
|
||||
return self._title
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class PageWithCraftingRecipes(PageWithText, ABC, type=None):
|
||||
@property
|
||||
@abstractmethod
|
||||
def recipes(self) -> list[CraftingRecipe]:
|
||||
...
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class PageWithPattern(PageWithTitle, ABC, type=None):
|
||||
patterns: list[RawPatternInfo]
|
||||
op_id: ResourceLocation | None = None
|
||||
header: LocalizedStr | None = None
|
||||
input: str | None = None
|
||||
output: str | None = None
|
||||
hex_size: int | None = None
|
||||
|
||||
_title: None = None
|
||||
|
||||
@classmethod
|
||||
def make_type_hook(cls, book: Book) -> Callable[[dict[str, Any]], dict[str, Any]]:
|
||||
super_hook = super().make_type_hook(book)
|
||||
|
||||
def type_hook(data: dict[str, Any]) -> dict[str, Any]:
|
||||
# convert a single pattern to a list
|
||||
data = super_hook(data)
|
||||
patterns = data.get("patterns")
|
||||
if patterns is not None and not isinstance(patterns, list):
|
||||
data["patterns"] = [patterns]
|
||||
return data
|
||||
|
||||
return type_hook
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> LocalizedStr:
|
||||
...
|
||||
|
||||
@property
|
||||
def args(self) -> str | None:
|
||||
inp = self.input or ""
|
||||
oup = self.output or ""
|
||||
if inp or oup:
|
||||
return f"{inp} \u2192 {oup}".strip()
|
||||
return None
|
||||
|
||||
@property
|
||||
def title(self) -> LocalizedStr:
|
||||
suffix = f" ({self.args})" if self.args else ""
|
||||
return LocalizedStr(self.name + suffix)
|
|
@ -1,69 +1,23 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any, Callable, Self
|
||||
from typing import Any
|
||||
|
||||
from common.deserialize import TypeFn, TypeHooks, rename
|
||||
from common.deserialize import TypeHooks, rename
|
||||
from common.formatting import FormatTree
|
||||
from common.pattern import RawPatternInfo
|
||||
from common.tagged_union import InternallyTaggedUnion, get_union_types
|
||||
from common.types import Book, BookHelpers, LocalizedItem, LocalizedStr
|
||||
from common.tagged_union import get_union_types
|
||||
from common.types import Book, LocalizedItem, LocalizedStr
|
||||
from minecraft.recipe import BrainsweepRecipe, CraftingRecipe
|
||||
from minecraft.resource import Entity, ItemStack, ResourceLocation
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class BasePage(InternallyTaggedUnion, BookHelpers, tag="type", value=None):
|
||||
"""Fields shared by all Page types.
|
||||
|
||||
See: https://vazkiimods.github.io/Patchouli/docs/patchouli-basics/page-types
|
||||
"""
|
||||
|
||||
book: Book
|
||||
|
||||
type: ResourceLocation = field(init=False)
|
||||
advancement: ResourceLocation | None = None
|
||||
flag: str | None = None
|
||||
anchor: str | None = None
|
||||
|
||||
def __init_subclass__(cls, type: str | None) -> None:
|
||||
super().__init_subclass__(__class__._tag_name, type)
|
||||
if type is not None:
|
||||
cls.type = ResourceLocation.from_str(type)
|
||||
|
||||
@classmethod
|
||||
def make_type_hook(cls, book: Book) -> TypeFn:
|
||||
def type_hook(data: Self | dict[str, Any]) -> Self | dict[str, Any]:
|
||||
if isinstance(data, cls):
|
||||
return data
|
||||
data = cls.assert_tag(data)
|
||||
data["book"] = book
|
||||
return data
|
||||
|
||||
return type_hook
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class PageWithText(BasePage, type=None):
|
||||
text: FormatTree | None = None
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class PageWithTitle(PageWithText, type=None):
|
||||
_title: LocalizedStr | None = field(default=None, metadata=rename("title"))
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return self._title
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class PageWithCraftingRecipes(PageWithText, ABC, type=None):
|
||||
@property
|
||||
@abstractmethod
|
||||
def recipes(self) -> list[CraftingRecipe]:
|
||||
...
|
||||
from .abstract import (
|
||||
BasePage,
|
||||
PageWithCraftingRecipes,
|
||||
PageWithPattern,
|
||||
PageWithText,
|
||||
PageWithTitle,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
|
@ -154,50 +108,6 @@ class EmptyPage(BasePage, type="patchouli:empty"):
|
|||
draw_filler: bool = True
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class PageWithPattern(PageWithTitle, ABC, type=None):
|
||||
patterns: list[RawPatternInfo]
|
||||
op_id: ResourceLocation | None = None
|
||||
header: LocalizedStr | None = None
|
||||
input: str | None = None
|
||||
output: str | None = None
|
||||
hex_size: int | None = None
|
||||
|
||||
_title: None = None
|
||||
|
||||
@classmethod
|
||||
def make_type_hook(cls, book: Book) -> Callable[[dict[str, Any]], dict[str, Any]]:
|
||||
super_hook = super().make_type_hook(book)
|
||||
|
||||
def type_hook(data: dict[str, Any]) -> dict[str, Any]:
|
||||
# convert a single pattern to a list
|
||||
data = super_hook(data)
|
||||
patterns = data.get("patterns")
|
||||
if patterns is not None and not isinstance(patterns, list):
|
||||
data["patterns"] = [patterns]
|
||||
return data
|
||||
|
||||
return type_hook
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> LocalizedStr:
|
||||
...
|
||||
|
||||
@property
|
||||
def args(self) -> str | None:
|
||||
inp = self.input or ""
|
||||
oup = self.output or ""
|
||||
if inp or oup:
|
||||
return f"{inp} \u2192 {oup}".strip()
|
||||
return None
|
||||
|
||||
@property
|
||||
def title(self) -> LocalizedStr:
|
||||
suffix = f" ({self.args})" if self.args else ""
|
||||
return LocalizedStr(self.name + suffix)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PatternPage(PageWithPattern, type="hexcasting:pattern"):
|
||||
patterns: list[RawPatternInfo] = field(init=False)
|
Loading…
Reference in a new issue