|
1 | 1 | import re |
2 | 2 | import logging |
3 | | -from typing import Any |
4 | | -from beet import Context, TextFileBase, Recipe |
| 3 | +from typing import Any, Tuple, Callable |
| 4 | +from beet import Context, Pack, TextFileBase, Recipe, Function, NamespaceFile |
| 5 | +from beet.core.utils import SupportedFormats |
5 | 6 |
|
6 | 7 | logger = logging.getLogger("gm4.backwards") |
7 | 8 |
|
8 | 9 | # Generates overlays to support older versions |
9 | 10 | def beet_default(ctx: Context): |
10 | 11 | yield |
11 | 12 |
|
12 | | - rewrite_attributes(ctx) |
13 | | - rewrite_recipes(ctx) |
| 13 | + # backporting to 1.21.3 (57) |
| 14 | + backport(ctx.data, 57, rewrite_furnace_nbt) |
| 15 | + backport(ctx.data, 57, rewrite_custom_model_data) |
| 16 | + |
| 17 | + # backporting to 1.21.1 (48) |
| 18 | + backport(ctx.data, 48, rewrite_attributes) |
| 19 | + backport(ctx.data, 48, rewrite_recipe) |
| 20 | + |
| 21 | + |
| 22 | +FURNACE_RENAMES = { |
| 23 | + "cooking_time_spent": "CookTime", |
| 24 | + "cooking_total_time": "CookTimeTotal", |
| 25 | + "lit_time_remaining": "BurnTime", |
| 26 | + "lit_total_time": None, |
| 27 | +} |
| 28 | + |
| 29 | +def rewrite_furnace_nbt(id: str, resource: NamespaceFile): |
| 30 | + if not isinstance(resource, Function): |
| 31 | + return None |
| 32 | + text = resource.text |
| 33 | + for src_field, overlay_field in FURNACE_RENAMES.items(): |
| 34 | + if overlay_field is None: |
| 35 | + if re.match("\\b" + src_field + "\\b", text): |
| 36 | + logger.error(f"Cannot backport furnace field {src_field} in function {id}") |
| 37 | + else: |
| 38 | + text = re.sub("\\b" + src_field + "\\b", overlay_field, text) |
| 39 | + if text == resource.text: |
| 40 | + return None |
| 41 | + overlay = resource.copy() |
| 42 | + overlay.text = text |
| 43 | + return overlay |
| 44 | + |
| 45 | + |
| 46 | +def rewrite_custom_model_data(id: str, resource: NamespaceFile): |
| 47 | + if not isinstance(resource, TextFileBase): |
| 48 | + return None |
| 49 | + text = resource.text |
| 50 | + text = re.sub(r"\{\s*[\"']?floats[\"']?\s*:\s*\[\s*(\d+)[Ff]?\s*\]\s*\}", r"\1", text) |
| 51 | + if text == resource.text: |
| 52 | + return None |
| 53 | + overlay = resource.copy() |
| 54 | + overlay.text = text |
| 55 | + return overlay |
14 | 56 |
|
15 | 57 |
|
16 | 58 | ATTRIBUTES_RENAMES = { |
@@ -49,63 +91,87 @@ def beet_default(ctx: Context): |
49 | 91 | } |
50 | 92 |
|
51 | 93 | # Removes the generic. and other prefixes from attribute IDs |
52 | | -def rewrite_attributes(ctx: Context): |
53 | | - for id, resource in ctx.data.all(): |
54 | | - if isinstance(resource, TextFileBase): |
55 | | - resource.source_stop |
56 | | - overlay_text = resource.text |
57 | | - for src_attribute, overlay_attribute in ATTRIBUTES_RENAMES.items(): |
58 | | - overlay_text = re.sub("\\b" + src_attribute + "\\b", overlay_attribute, overlay_text) |
59 | | - if overlay_text != resource.text: |
60 | | - overlay_resource = resource.copy() |
61 | | - overlay_resource.text = overlay_text |
62 | | - overlay = ctx.data.overlays["overlay_48"] |
63 | | - overlay.supported_formats = { "min_inclusive": 48, "max_inclusive": 48 } |
64 | | - overlay[id] = overlay_resource |
| 94 | +def rewrite_attributes(id: str, resource: NamespaceFile): |
| 95 | + if not isinstance(resource, TextFileBase): |
| 96 | + return None |
| 97 | + text = resource.text |
| 98 | + for src_attribute, overlay_attribute in ATTRIBUTES_RENAMES.items(): |
| 99 | + text = re.sub("\\b" + src_attribute + "\\b", overlay_attribute, text) |
| 100 | + if text == resource.text: |
| 101 | + return None |
| 102 | + overlay = resource.copy() |
| 103 | + overlay.text = text |
| 104 | + return overlay |
65 | 105 |
|
66 | 106 |
|
67 | 107 | # Rewrites the recipe ingredients to the old {"item": "..."} format |
68 | | -def rewrite_recipes(ctx: Context): |
| 108 | +def rewrite_recipe(id: str, resource: NamespaceFile): |
| 109 | + if not isinstance(resource, Recipe): |
| 110 | + return None |
69 | 111 |
|
70 | 112 | def rewrite_ingredient(ingr: str | list[str]) -> Any: |
71 | 113 | if isinstance(ingr, list): |
72 | 114 | return [rewrite_ingredient(item) for item in ingr] |
73 | 115 | if ingr.startswith("#"): |
74 | 116 | return { "tag": ingr[1:] } |
75 | 117 | return { "item": ingr } |
76 | | - |
77 | | - def rewrite_recipe(id: str, resource: Recipe): |
78 | | - # If an overlay already exists for this recipe, us the contents of that |
79 | | - # TODO: generalize this for all rewrite functions and handle multiple overlays |
80 | | - for overlay in ctx.data.overlays.values(): |
81 | | - if id in overlay.recipes: |
82 | | - resource = overlay.recipes[id] |
83 | | - break |
84 | | - |
85 | | - overlay_resource = resource.copy() |
86 | | - data = overlay_resource.data |
87 | | - |
88 | | - if "crafting_transmute" in data["type"]: |
89 | | - logger.warning(f"Cannot backport crafting_transmute recipe {id}") |
90 | | - return |
91 | | - |
92 | | - if "base" in data: |
93 | | - data["base"] = rewrite_ingredient(data["base"]) |
94 | | - if "addition" in data: |
95 | | - data["addition"] = rewrite_ingredient(data["addition"]) |
96 | | - if "ingredient" in data: |
97 | | - data["ingredient"] = rewrite_ingredient(data["ingredient"]) |
98 | | - if "ingredients" in data: |
99 | | - data["ingredients"] = [rewrite_ingredient(ingr) for ingr in data["ingredients"]] |
100 | | - if "key" in data: |
101 | | - data["key"] = {k: rewrite_ingredient(ingr) for k, ingr in data["key"].items()} |
102 | | - |
103 | | - overlay = ctx.data.overlays["overlay_48"] |
104 | | - overlay.supported_formats = { "min_inclusive": 48, "max_inclusive": 48 } |
105 | | - overlay[id] = overlay_resource |
106 | | - |
107 | | - for id, resource in ctx.data.recipes.items(): |
| 118 | + |
| 119 | + overlay = resource.copy() |
| 120 | + data = overlay.data |
| 121 | + |
| 122 | + if "crafting_transmute" in data["type"]: |
| 123 | + logger.warning(f"Cannot backport crafting_transmute recipe {id}") |
| 124 | + return None |
| 125 | + |
| 126 | + if "base" in data: |
| 127 | + data["base"] = rewrite_ingredient(data["base"]) |
| 128 | + if "addition" in data: |
| 129 | + data["addition"] = rewrite_ingredient(data["addition"]) |
| 130 | + if "ingredient" in data: |
| 131 | + data["ingredient"] = rewrite_ingredient(data["ingredient"]) |
| 132 | + if "ingredients" in data: |
| 133 | + data["ingredients"] = [rewrite_ingredient(ingr) for ingr in data["ingredients"]] |
| 134 | + if "key" in data: |
| 135 | + data["key"] = {k: rewrite_ingredient(ingr) for k, ingr in data["key"].items()} |
| 136 | + |
| 137 | + return overlay |
| 138 | + |
| 139 | + |
| 140 | +def backport(pack: Pack[Any], format: int, run: Callable[[str, NamespaceFile], NamespaceFile | None]): |
| 141 | + resources: dict[Tuple[type[NamespaceFile], str], NamespaceFile] = dict() |
| 142 | + |
| 143 | + for file_type in pack.resolve_scope_map().values(): |
| 144 | + proxy = pack[file_type] |
| 145 | + for path in proxy.keys(): |
| 146 | + resources[(file_type, path)] = proxy[path] |
| 147 | + |
| 148 | + for overlay in pack.overlays.values(): |
| 149 | + overlay_formats = overlay.supported_formats or overlay.pack_format |
| 150 | + if check_formats(overlay_formats, format): |
| 151 | + for file_type in overlay.resolve_scope_map().values(): |
| 152 | + proxy = overlay[file_type] |
| 153 | + for path in proxy.keys(): |
| 154 | + resources[(file_type, path)] = proxy[path] |
| 155 | + |
| 156 | + for (file_type, path), resource in resources.items(): |
108 | 157 | try: |
109 | | - rewrite_recipe(id, resource) |
| 158 | + new_resource = run(path, resource) |
110 | 159 | except BaseException as e: |
111 | | - logger.error(f"Failed to backport recipe {id}: {e}") |
| 160 | + e.add_note(f"Failed to backport[{run.__name__}] {file_type.snake_name} {path}") |
| 161 | + raise e |
| 162 | + if new_resource: |
| 163 | + overlay = pack.overlays[f"backport_{format}"] |
| 164 | + overlay.supported_formats = { "min_inclusive": 0, "max_inclusive": format } |
| 165 | + overlay[path] = new_resource |
| 166 | + |
| 167 | + |
| 168 | +def check_formats(supported: SupportedFormats, format: int): |
| 169 | + match supported: |
| 170 | + case int(value): |
| 171 | + return value == format |
| 172 | + case [min, max]: |
| 173 | + return min <= format <= max |
| 174 | + case { "min_inclusive": min, "max_inclusive": max }: |
| 175 | + return min <= format <= max |
| 176 | + case _: |
| 177 | + raise ValueError(f"Unknown supported)formats structure {supported}") |
0 commit comments