Split recipe and page into a bunch of files

This commit is contained in:
object-Object 2023-06-20 00:54:25 -04:00
parent 5620b97060
commit 99e1e899b9
9 changed files with 251 additions and 219 deletions

View file

@ -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

View file

@ -0,0 +1,4 @@
from .abstract import *
from .concrete import *
from .ingredient import *
from .result import *

View 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

View 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

View 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

View 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

View file

@ -0,0 +1,2 @@
from .abstract import *
from .concrete import *

View 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)

View file

@ -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)