diff options
| author | mat <27899617+mat-1@users.noreply.github.com> | 2025-08-04 20:43:10 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-08-04 20:43:10 -0500 |
| commit | 23b7f20a0d88b54d430820baeb4a6da0316a009a (patch) | |
| tree | f3e780515b3bbb9973d2b94338be6194b5ec0af3 /codegen | |
| parent | 827d943c3f27c65724ff83689b40c87d1cd1838c (diff) | |
| download | azalea-drasl-23b7f20a0d88b54d430820baeb4a6da0316a009a.tar.xz | |
Default components (#232)
* add default components
* remove debug prints
* clippy
* use default components
* fix tests
Diffstat (limited to 'codegen')
| -rw-r--r-- | codegen/genitemcomponents.py | 6 | ||||
| -rw-r--r-- | codegen/genregistries.py | 25 | ||||
| -rw-r--r-- | codegen/lib/code/data_components.py | 629 | ||||
| -rw-r--r-- | codegen/lib/code/item_components.py | 168 | ||||
| -rw-r--r-- | codegen/lib/extract.py | 4 | ||||
| -rw-r--r-- | codegen/lib/utils.py | 25 | ||||
| -rw-r--r-- | codegen/migrate.py | 45 |
7 files changed, 691 insertions, 211 deletions
diff --git a/codegen/genitemcomponents.py b/codegen/genitemcomponents.py index b0002ae2..9a4b4961 100644 --- a/codegen/genitemcomponents.py +++ b/codegen/genitemcomponents.py @@ -1,6 +1,6 @@ -import lib.code.item_components +import lib.code.data_components import lib.code.version -if __name__ == '__main__': +if __name__ == "__main__": version_id = lib.code.version.get_version_id() - lib.code.item_components.generate(version_id) + lib.code.data_components.generate(version_id) diff --git a/codegen/genregistries.py b/codegen/genregistries.py index 175d1b20..d34cce97 100644 --- a/codegen/genregistries.py +++ b/codegen/genregistries.py @@ -1,31 +1,32 @@ import lib.code.inventory import lib.code.registry import lib.code.version -import lib.code.packet import lib.code.utils import lib.code.tags -import lib.download import lib.extract -import lib.utils + def generate(version_id: str): registries = lib.extract.get_registries_report(version_id) lib.code.registry.generate_registries(registries) - lib.code.inventory.update_menus(registries['minecraft:menu']['entries']) + lib.code.inventory.update_menus(registries["minecraft:menu"]["entries"]) - block_tags = lib.extract.get_registry_tags(version_id, 'block') - item_tags = lib.extract.get_registry_tags(version_id, 'item') - fluid_tags = lib.extract.get_registry_tags(version_id, 'fluid') + block_tags = lib.extract.get_registry_tags(version_id, "block") + item_tags = lib.extract.get_registry_tags(version_id, "item") + fluid_tags = lib.extract.get_registry_tags(version_id, "fluid") + entity_tags = lib.extract.get_registry_tags(version_id, "entity_type") - lib.code.tags.generate_tags(block_tags, 'blocks', 'Block') - lib.code.tags.generate_tags(item_tags, 'items', 'Item') - lib.code.tags.generate_tags(fluid_tags, 'fluids', 'Fluid') + lib.code.tags.generate_tags(block_tags, "blocks", "Block") + lib.code.tags.generate_tags(item_tags, "items", "Item") + lib.code.tags.generate_tags(fluid_tags, "fluids", "Fluid") + lib.code.tags.generate_tags(entity_tags, "entities", "EntityKind") lib.code.utils.fmt() - print('Done!') + print("Done!") + -if __name__ == '__main__': +if __name__ == "__main__": version_id = lib.code.version.get_version_id() generate(version_id) diff --git a/codegen/lib/code/data_components.py b/codegen/lib/code/data_components.py new file mode 100644 index 00000000..c6c88f12 --- /dev/null +++ b/codegen/lib/code/data_components.py @@ -0,0 +1,629 @@ +from typing import Any, Optional +import lib.code.utils +import lib.extract +import lib.utils + + +DATA_COMPONENTS_DIR = "azalea-inventory/src/components.rs" +DEFAULT_DATA_COMPONENTS_DIR = "azalea-inventory/src/default_components/generated.rs" + + +def generate(version_id: str): + expected_variants = get_expected_variants(version_id) + actual_variants = get_actual_variants() + + new_variants = [] + removed_variants = [] + + for variant in expected_variants: + if variant not in actual_variants: + new_variants.append(variant) + for variant in actual_variants: + if variant not in expected_variants: + removed_variants.append(variant) + + print("New variants:") + for variant in new_variants: + print("-", variant) + print() + print("Removed variants:") + for variant in removed_variants: + print("-", variant) + print() + + for variant in removed_variants: + print(f"Removing {variant}...") + remove_variant(variant) + for variant in new_variants: + print(f"Adding {variant}...") + add_variant(variant) + + update_default_variants(version_id) + + lib.code.utils.fmt() + + print("Done!") + + +def get_expected_variants(version_id: str): + expected_variants = [] + registries = lib.extract.get_registries_report(version_id) + + registry = registries["minecraft:data_component_type"] + registry_entries = sorted( + registry["entries"].items(), key=lambda x: x[1]["protocol_id"] + ) + for variant_name, _variant in registry_entries: + variant_struct_name = lib.utils.to_camel_case(variant_name.split(":")[-1]) + expected_variants.append(variant_struct_name) + + return expected_variants + + +def get_actual_variants(): + actual_variants = [] + with open(DATA_COMPONENTS_DIR, "r") as f: + code = f.read().split("\n") + + in_match = False + for line in code: + if in_match: + if line == " })": + break + variant_line_prefix = " DataComponentKind::" + if line.startswith(variant_line_prefix): + variant = line[len(variant_line_prefix) :].split(" ", 1)[0] + actual_variants.append(variant) + elif line == " Ok(match kind {": + in_match = True + + return actual_variants + + +def remove_variant(variant: str): + with open(DATA_COMPONENTS_DIR, "r") as f: + code = f.read().split("\n") + + first_line_with_variant = None + line_after_variant = None + + in_match = False + for i, line in enumerate(list(code)): + if in_match: + if line == " })": + line_after_variant = i + break + variant_line_prefix = " DataComponentKind::" + if line.startswith(variant_line_prefix): + if first_line_with_variant is not None: + line_after_variant = i + break + variant_name = line[len(variant_line_prefix) :].split(" ", 1)[0] + if variant_name == variant: + first_line_with_variant = i + elif line == " Ok(match kind {": + in_match = True + + if first_line_with_variant is None: + raise ValueError(f"Variant {variant} not found") + if line_after_variant is None: + raise ValueError(f"Couldn't find end of variant {variant}") + + code = code[:first_line_with_variant] + code[line_after_variant:] + + # now remove the struct + line_before_struct = None # this is the #[derive] line + line_after_struct = None # impl DataComponent for ... {\n...\n} + for i, line in enumerate(list(code)): + if line == f"pub struct {variant} {{" or line == f"pub struct {variant};": + line_before_struct = i - 1 + elif line == f"impl DataComponent for {variant} {{": + line_after_struct = i + 3 + break + if line_before_struct is None: + raise ValueError(f"Couldn't find struct {variant}") + if line_after_struct is None: + raise ValueError(f"Couldn't find impl DataComponent for {variant}") + + code = code[:line_before_struct] + code[line_after_struct:] + + with open(DATA_COMPONENTS_DIR, "w") as f: + f.write("\n".join(code)) + + +def add_variant(variant: str): + with open(DATA_COMPONENTS_DIR, "r") as f: + code = f.read().split("\n") + + in_match = False + last_line_in_match = None + for i, line in enumerate(list(code)): + if in_match: + if line == " })": + last_line_in_match = i + break + elif line == " Ok(match kind {": + in_match = True + + if last_line_in_match is None: + raise ValueError("Couldn't find end of match") + + code = ( + code[:last_line_in_match] + + [ + f" DataComponentKind::{variant} => Box::new({variant}::azalea_read(buf)?),", + ] + + code[last_line_in_match:] + ) + + # now insert the struct + code.append("") + code.append("#[derive(Clone, PartialEq, AzBuf)]") + code.append(f"pub struct {variant} {{") + code.append(" pub todo: todo!(), // see DataComponents.java") + code.append("}") + code.append(f"impl DataComponent for {variant} {{") + code.append(f" const KIND: DataComponentKind = DataComponentKind::{variant};") + code.append("}") + + with open(DATA_COMPONENTS_DIR, "w") as f: + f.write("\n".join(code)) + + +def update_default_variants(version_id: str): + items = lib.extract.get_items_report(version_id) + + code = """// This file was @generated by codegen/lib/code/components.py, don't edit it +// manually! + +#![allow(clippy::all)] + +use std::collections::HashMap; + +use azalea_chat::translatable_component::TranslatableComponent; +use azalea_registry::{Attribute, Block, EntityKind, HolderSet, Item, MobEffect, SoundEvent}; +use simdnbt::owned::NbtCompound; + +use crate::{ + ItemStack, components::*, default_components::DefaultableComponent, + item::consume_effect::ConsumeEffect, +}; + +""".splitlines() + + # { max_stack_size: { air: 64, ... } } + components_to_item_defaults = {} + + for item_resource_id, data in items.items(): + item_resource_id = item_resource_id.split(":")[1] + components = data["components"] + for component_resource_id, component_value in components.items(): + component_resource_id = component_resource_id.split(":")[1] + if component_resource_id not in components_to_item_defaults: + components_to_item_defaults[component_resource_id] = {} + components_to_item_defaults[component_resource_id][item_resource_id] = ( + component_value + ) + + registries = lib.extract.get_registries_report(version_id) + item_resource_id_to_protocol_id = {} + item_resource_ids = [None] * len(registries["minecraft:item"]["entries"]) + for item_resource_id, item_data in registries["minecraft:item"]["entries"].items(): + item_resource_id = item_resource_id.split(":")[-1] + item_protocol_id = item_data["protocol_id"] + item_resource_id_to_protocol_id[item_resource_id] = item_protocol_id + item_resource_ids[item_protocol_id] = item_resource_id + + enum_and_struct_fields = get_enum_and_struct_fields() + # a few types that exist elsewhere + enum_and_struct_fields["ConsumeEffect::ApplyEffects"] = { + "effects": "Vec<MobEffectInstance>", + "probability": "f32", + } + enum_and_struct_fields["ConsumeEffect::RemoveEffects"] = { + "effects": "HolderSet<MobEffect, ResourceLocation>", + } + enum_and_struct_fields["ConsumeEffect::ClearAllEffects"] = {} + enum_and_struct_fields["ConsumeEffect::TeleportRandomly"] = { + "diameter": "f32", + } + enum_and_struct_fields["ConsumeEffect::PlaySound"] = { + "sound": "SoundEvent", + } + + # we can't call ::new() on enum variants, so define the defaults manually here + enum_variant_defaults = { + "ConsumeEffect::ApplyEffects": { + "effects": [], + "probability": 1.0, + }, + "ConsumeEffect::TeleportRandomly": { + "diameter": 16.0, + }, + } + + def python_to_rust_value(python_value: Any, target_rust_type: Optional[str]): + # manual implementations + if isinstance(python_value, dict) and len(python_value) > 0: + if target_rust_type == "ConsumeEffect": + variant = lib.utils.to_camel_case(python_value["type"].split(":")[-1]) + type_with_variant = f"ConsumeEffect::{variant}" + details_without_type = python_value.copy() + del details_without_type["type"] + return python_to_rust_value(details_without_type, type_with_variant) + elif target_rust_type == "MobEffectInstance": + effect_id = python_value["id"] + details_without_id = python_value.copy() + del details_without_id["id"] + return ( + "MobEffectInstance {" + + f"id: {python_to_rust_value(effect_id, 'MobEffect')}," + + f"details: {python_to_rust_value(details_without_id, 'MobEffectDetails')}" + + "}" + ) + elif target_rust_type == "AttributeModifiersEntry": + attribute = python_value["type"] + amount = python_value["amount"] + display_type = python_value.get("display", {}).get("type") or "default" + id = python_value["id"] + operation = python_value["operation"] + + del python_value["amount"] + del python_value["type"] + python_value["attribute"] = attribute + del python_value["id"] + del python_value["operation"] + if display_type is not None: + python_value["display"] = display_type + python_value["modifier"] = { + "id": id, + "amount": amount, + "operation": operation, + } + + if target_rust_type is None: + return "None" + + if target_rust_type.startswith("Option<"): + if python_value is None: + return "None" + inner_type = target_rust_type.split("<", 1)[1].rsplit(">", 1)[0] + return f"Some({python_to_rust_value(python_value, inner_type)})" + elif target_rust_type.startswith("HashMap<"): + hashmap_key, hashmap_value = ( + target_rust_type.split("<", 1)[1].rsplit(">", 1)[0].split(",", 1) + ) + hashmap_key = hashmap_key.strip() + hashmap_value = hashmap_value.strip() + + # HashMap::from_iter([("honey_level".to_string(), "0".to_string())]) + t = "HashMap::from_iter([" + for k, v in python_value.items(): + t += f"({python_to_rust_value(k, hashmap_key)}, {python_to_rust_value(v, hashmap_value)})," + t = t.rstrip(",") + "])" + return t + elif target_rust_type == "String": + return f'"{python_value}".to_string()' + elif target_rust_type == "&str": + if isinstance(python_value, dict): + return python_to_rust_value( + list(python_value.values())[0], target_rust_type + ) + return f'"{python_value}"' + elif target_rust_type in {'i64', 'u64', 'f64', 'i32', 'u32', 'f32', 'i16', 'u16', 'i8', 'u8'}: # fmt: skip + if isinstance(python_value, dict) and len(python_value) == 1: + return python_to_rust_value( + list(python_value.values())[0], target_rust_type + ) + return str(python_value) + + if isinstance(python_value, dict): + if target_rust_type == "ResourceLocation" and len(python_value) == 1: + return python_to_rust_value( + list(python_value.values())[0], target_rust_type + ) + elif target_rust_type.startswith("HolderSet<") and len(python_value) == 1: + return python_to_rust_value( + list(python_value.values())[0], target_rust_type + ) + elif target_rust_type.startswith("Vec<") and len(python_value) == 1: + return python_to_rust_value( + list(python_value.values())[0], target_rust_type + ) + elif target_rust_type == "ItemStack": + item_rust_value = python_to_rust_value(python_value["id"], "Item") + count = python_value["count"] + if count == 1: + return f"ItemStack::from({item_rust_value})" + else: + return f"ItemStack::new({item_rust_value}, {python_to_rust_value(python_value['count'], 'i32')})" + + if "::" in target_rust_type and target_rust_type in enum_variant_defaults: + # we can't call ::new() on enum variants, so extend the python type with the defaults + python_value = { + **enum_variant_defaults[target_rust_type], + **python_value, + } + + # the :: check is so we don't do this for enum variants + if len(python_value) == 0 and "::" not in target_rust_type: + if ( + target_rust_type in enum_and_struct_fields + and len(enum_and_struct_fields[target_rust_type]) == 0 + ): + # don't do ::new() for structs with no fields (like `Glider`) + return f"{target_rust_type}" + # this ::new has to be implemented manually for these types + t = f"{target_rust_type.split('<')[0]}::new()" + else: + # create a struct based on the defaults + t = f"{target_rust_type} {{" + for k, v in python_value.items(): + # get the type of the fields + inner_type = enum_and_struct_fields.get(target_rust_type, {}).get( + k, "FIXME_UNKNOWN_TYPE" + ) + t += f"{k}: {python_to_rust_value(v, inner_type)}," + + # add ..Struct::new(), unless we already know that all of the fields are there + if len(python_value) < len( + enum_and_struct_fields.get(target_rust_type, []) + ): + t += f"..{target_rust_type}::new()" + + t += "}" + return t + if isinstance(python_value, bool): + return str(python_value).lower() + if isinstance(python_value, str): + fields_for_rust_type = enum_and_struct_fields.get(target_rust_type, []) + if "Referenced(ResourceLocation)" in fields_for_rust_type: + return f"{target_rust_type}::Referenced({python_to_rust_value(python_value, 'ResourceLocation')})" + elif "Registry(registry::Instrument)" in fields_for_rust_type: + return f"{target_rust_type}::Registry({python_to_rust_value(python_value, 'azalea_registry::Instrument')})" + elif target_rust_type.startswith("HolderSet<"): + holderset_type = target_rust_type.split("<", 1)[1].split(",", 1)[0] + main_vec = python_to_rust_value( + [python_value], f"Vec<{holderset_type}>" + ) + return f"HolderSet::Direct {{ contents: {main_vec} }}" + elif target_rust_type.startswith("azalea_registry::Holder<"): + holder_type = target_rust_type.split("<", 1)[1].split(",", 1)[0] + inner_type = python_to_rust_value(python_value, holder_type) + return f"azalea_registry::Holder::Reference({inner_type})" + elif target_rust_type == "ResourceLocation": + # convert minecraft:air into ResourceLocation::from_static("minecraft:air") + return f'"{python_value}".into()' + else: + # enum variant + return f"{target_rust_type}::{lib.utils.to_camel_case(python_value.split(':')[-1])}" + if isinstance(python_value, list): + # convert Vec<Thing> into Thing + inner_type = ( + target_rust_type.split("<", 1)[1] + .rsplit(">", 1)[0] + .split(",")[0] + .strip() + if (target_rust_type and "<" in target_rust_type) + else None + ) + if inner_type is None: + # if the only field is a Vec, use that as the type + rust_type_fields = enum_and_struct_fields.get(target_rust_type, {}) + if len(rust_type_fields) == 1 and isinstance(rust_type_fields, dict): + _field_name, field_type = list(rust_type_fields.items())[0] + return python_to_rust_value(python_value, field_type) + + vectors = [] + main_vec = "vec![" + for v in python_value: + # handle tags correctly + if isinstance(v, str) and v.startswith("#minecraft:"): + tag_name = lib.utils.to_snake_case(v.split(":")[-1]).upper() + if inner_type == "EntityKind": + tag_module = "entities" + elif inner_type == "Item": + tag_module = "items" + elif inner_type == "Block": + tag_module = "blocks" + else: + tag_module = "FIXME_UNKNOWN_MODULE" + vectors.append( + f"azalea_registry::tags::{tag_module}::{tag_name}.clone().into_iter().collect()" + ) + continue + main_vec += python_to_rust_value(v, inner_type) + "," + main_vec = main_vec.rstrip(",") + "]" + if len(vectors) == 0 or main_vec != "vec![]": + vectors.append(main_vec) + + if len(vectors) == 1: + as_vec = vectors[0] + else: + # concat + as_vec = f"[{','.join(vectors)}].concat()" + + if target_rust_type.startswith("HolderSet<"): + return f"HolderSet::Direct {{ contents: {as_vec} }}" + return as_vec + + return str(python_value) + + for component_resource_id, item_defaults in components_to_item_defaults.items(): + component_struct_name = lib.utils.to_camel_case(component_resource_id) + component_struct_fields = enum_and_struct_fields[component_struct_name] + + if len(component_struct_fields) == 1 and isinstance( + component_struct_fields, dict + ): + field_name, field_type = list(component_struct_fields.items())[0] + + # if field_type not in ["i32", "u32", "f32", "bool"]: + # continue + + def transform_value_fn(rust_value: str): + return f"{component_struct_name} {{ {field_name}: {rust_value} }}" + else: + field_type = component_struct_name + # if component_resource_id != "rarity": + # continue + + def transform_value_fn(rust_value: str): + return rust_value + + rust_value = "value" + if component_resource_id == "item_name": + rust_value = f"TranslatableComponent::from({rust_value}).into()" + field_type = "&str" + elif component_resource_id == "item_model": + rust_value = f"{rust_value}.into()" + field_type = "&str" + + item_defaults_original = item_defaults + item_defaults = {} + for k, v in item_defaults_original.items(): + item_defaults[k] = python_to_rust_value(v, field_type) + + default_values_frequency = {} + for value in item_defaults.values(): + if value not in default_values_frequency: + default_values_frequency[value] = 0 + default_values_frequency[value] += 1 + most_common_default_value = max( + default_values_frequency.items(), key=lambda x: x[1] + )[0] + default_values_count_except_most_common = ( + len(item_defaults) - default_values_frequency[most_common_default_value] + ) + + # if it looks like there's a default (like, vec![]) that's used for most items, then we + # always use a match statement with a default handler + includes_every_item_but_mostly_same_values = ( + len(item_resource_ids) == len(item_defaults) + and default_values_count_except_most_common <= 128 + ) + + # use a lookup table for some components to avoid big match statements + if len(item_defaults) > 128 and not includes_every_item_but_mostly_same_values: + static_values_name = component_resource_id.upper() + "_VALUES" + + values_set = set(item_defaults.values()) + if len(values_set) == 1: + # always returns the same value + code.append(f"impl DefaultableComponent for {component_struct_name} {{") + code.append(" fn default_for_item(_item: Item) -> Option<Self> {") + value = next(iter(values_set)) + code.append(f" Some({transform_value_fn(value)})") + code.append(" }") + code.append("}") + continue + + # find a sentinel value that isn't already being used + none_value = 0 + while none_value in values_set: + none_value += 1 + none_value_is_used = False + + static_def_line = f"static {static_values_name}: [{field_type}; {len(item_resource_ids)}] = [" + for item_protocol_id, item_resource_id in enumerate(item_resource_ids): + value = item_defaults.get(item_resource_id, none_value) + static_def_line += f"{value}," + if value == none_value: + none_value_is_used = True + static_def_line = static_def_line.rstrip(",") + static_def_line += "];" + + code.append("#[rustfmt::skip]") + code.append(static_def_line) + + code.append(f"impl DefaultableComponent for {component_struct_name} {{") + code.append(" fn default_for_item(item: Item) -> Option<Self> {") + code.append(f" let value = {static_values_name}[item as usize];") + if none_value_is_used: + code.append(f" if value == {none_value} {{") + code.append(" return None;") + code.append(" }") + code.append(f" Some({transform_value_fn(rust_value)})") + code.append(" }") + code.append("}") + elif includes_every_item_but_mostly_same_values: + code.append(f"impl DefaultableComponent for {component_struct_name} {{") + if default_values_count_except_most_common > 0: + code.append(" fn default_for_item(item: Item) -> Option<Self> {") + code.append(" let value = match item {") + for item_resource_id, value in item_defaults.items(): + if value == most_common_default_value: + continue + item_variant_name = lib.utils.to_camel_case(item_resource_id) + code.append(f" Item::{item_variant_name} => {value},") + code.append(f" _ => {most_common_default_value},") + code.append(" };") + code.append(f" Some({transform_value_fn('value')})") + else: + code.append(" fn default_for_item(_item: Item) -> Option<Self> {") + code.append( + f" Some({transform_value_fn(most_common_default_value)})" + ) + code.append(" }") + code.append("}") + else: + code.append(f"impl DefaultableComponent for {component_struct_name} {{") + code.append(" fn default_for_item(item: Item) -> Option<Self> {") + code.append(" let value = match item {") + for item_resource_id, value in item_defaults.items(): + item_variant_name = lib.utils.to_camel_case(item_resource_id) + code.append(f" Item::{item_variant_name} => {value},") + code.append(" _ => return None,") + code.append(" };") + code.append(f" Some({transform_value_fn('value')})") + code.append(" }") + code.append("}") + + with open(DEFAULT_DATA_COMPONENTS_DIR, "w") as f: + f.write("\n".join(code)) + + +def get_enum_and_struct_fields(): + """ + Returns a map like map like `{ "MaxStackSize": { "count": i32 }, "Rarity": [ "common", ... ], ... }` + with an entry for each struct in components.rs. + """ + + with open(DATA_COMPONENTS_DIR, "r") as f: + code = f.read().split("\n") + + # we copy from here to `metadatas` if we find a DataComponent impl for the struct + all_enum_and_struct_fields = {} + + i = 0 + while i < len(code): + line = code[i] + if line.startswith("pub struct "): + struct_name = line.split()[2].strip(":;") + # map like { "count": i32 } + this_struct_fields = {} + if line[-1] not in "};": + while line != "}": + i += 1 + line = code[i].strip() + if line.startswith("pub "): + field_name = line.split(" ")[1].strip(":") + field_type = line.split(" ", 2)[2].strip(",") + this_struct_fields[field_name] = field_type + all_enum_and_struct_fields[struct_name] = this_struct_fields + elif line.startswith("pub enum "): + enum_name = line.split()[2].strip(":") + # list of string values + this_enum_variants = [] + if line[-1] not in "};": + while line != "}": + i += 1 + line = code[i].strip() + variant_name = line.split()[0].strip(",") + if not variant_name.startswith("#"): + this_enum_variants.append(variant_name) + all_enum_and_struct_fields[enum_name] = this_enum_variants + + i += 1 + + return all_enum_and_struct_fields diff --git a/codegen/lib/code/item_components.py b/codegen/lib/code/item_components.py deleted file mode 100644 index ce5d6d82..00000000 --- a/codegen/lib/code/item_components.py +++ /dev/null @@ -1,168 +0,0 @@ -import lib.code.utils -import lib.extract -import lib.utils - - -ITEM_COMPONENTS_DIR = "azalea-inventory/src/components.rs" - - -def generate(version_id: str): - expected_variants = get_expected_variants(version_id) - actual_variants = get_actual_variants() - - new_variants = [] - removed_variants = [] - - for variant in expected_variants: - if variant not in actual_variants: - new_variants.append(variant) - for variant in actual_variants: - if variant not in expected_variants: - removed_variants.append(variant) - - print("New variants:") - for variant in new_variants: - print("-", variant) - print() - print("Removed variants:") - for variant in removed_variants: - print("-", variant) - print() - - for variant in removed_variants: - print(f"Removing {variant}...") - remove_variant(variant) - for variant in new_variants: - print(f"Adding {variant}...") - add_variant(variant) - - lib.code.utils.fmt() - - print("Done!") - - -def get_expected_variants(version_id: str): - expected_variants = [] - registries = lib.extract.get_registries_report(version_id) - - registry = registries["minecraft:data_component_type"] - registry_entries = sorted( - registry["entries"].items(), key=lambda x: x[1]["protocol_id"] - ) - for variant_name, _variant in registry_entries: - variant_struct_name = lib.utils.to_camel_case(variant_name.split(":")[-1]) - expected_variants.append(variant_struct_name) - - return expected_variants - - -def get_actual_variants(): - actual_variants = [] - with open(ITEM_COMPONENTS_DIR, "r") as f: - code = f.read().split("\n") - - in_match = False - for line in code: - if in_match: - if line == " })": - break - variant_line_prefix = " DataComponentKind::" - if line.startswith(variant_line_prefix): - variant = line[len(variant_line_prefix) :].split(" ", 1)[0] - actual_variants.append(variant) - elif line == " Ok(match kind {": - in_match = True - - return actual_variants - - -def remove_variant(variant: str): - with open(ITEM_COMPONENTS_DIR, "r") as f: - code = f.read().split("\n") - - first_line_with_variant = None - line_after_variant = None - - in_match = False - for i, line in enumerate(list(code)): - if in_match: - if line == " })": - line_after_variant = i - break - variant_line_prefix = " DataComponentKind::" - if line.startswith(variant_line_prefix): - if first_line_with_variant is not None: - line_after_variant = i - break - variant_name = line[len(variant_line_prefix) :].split(" ", 1)[0] - if variant_name == variant: - first_line_with_variant = i - elif line == " Ok(match kind {": - in_match = True - - if first_line_with_variant is None: - raise ValueError(f"Variant {variant} not found") - if line_after_variant is None: - raise ValueError(f"Couldn't find end of variant {variant}") - - code = code[:first_line_with_variant] + code[line_after_variant:] - - # now remove the struct - line_before_struct = None # this is the #[derive] line - line_after_struct = None # impl DataComponent for ... {\n...\n} - for i, line in enumerate(list(code)): - if line == f"pub struct {variant} {{" or line == f"pub struct {variant};": - line_before_struct = i - 1 - elif line == f"impl DataComponent for {variant} {{": - line_after_struct = i + 3 - break - if line_before_struct is None: - raise ValueError(f"Couldn't find struct {variant}") - if line_after_struct is None: - raise ValueError(f"Couldn't find impl DataComponent for {variant}") - - code = code[:line_before_struct] + code[line_after_struct:] - - with open(ITEM_COMPONENTS_DIR, "w") as f: - f.write("\n".join(code)) - - -def add_variant(variant: str): - with open(ITEM_COMPONENTS_DIR, "r") as f: - code = f.read().split("\n") - - in_match = False - last_line_in_match = None - for i, line in enumerate(list(code)): - if in_match: - if line == " })": - last_line_in_match = i - break - elif line == " Ok(match kind {": - in_match = True - - if last_line_in_match is None: - raise ValueError("Couldn't find end of match") - - code = ( - code[:last_line_in_match] - + [ - f" DataComponentKind::{variant} => Box::new({variant}::azalea_read(buf)?),", - ] - + code[last_line_in_match:] - ) - - # now insert the struct - code.append("") - code.append("#[derive(Clone, PartialEq, AzBuf)]") - code.append(f"pub struct {variant} {{") - code.append(" pub todo: todo!(), // see DataComponents.java") - code.append("}") - code.append(f"impl DataComponent for {variant} {{") - code.append(f" const KIND: DataComponentKind = DataComponentKind::{variant};") - code.append("}") - - with open(ITEM_COMPONENTS_DIR, "w") as f: - f.write("\n".join(code)) - - lib.code.utils.fmt() diff --git a/codegen/lib/extract.py b/codegen/lib/extract.py index 57ece9f2..eaebab84 100644 --- a/codegen/lib/extract.py +++ b/codegen/lib/extract.py @@ -40,6 +40,10 @@ def get_packets_report(version_id: str): return get_report(version_id, "packets") +def get_items_report(version_id: str): + return get_report(version_id, "items") + + def get_report(version_id: str, name: str): generate_data_from_server_jar(version_id) with open( diff --git a/codegen/lib/utils.py b/codegen/lib/utils.py index fd1e553b..8d1e8756 100644 --- a/codegen/lib/utils.py +++ b/codegen/lib/utils.py @@ -5,18 +5,21 @@ import os def to_snake_case(name: str): - s = re.sub('([A-Z])', r'_\1', name) - return s.lower().strip('_') + s = re.sub("([A-Z])", r"_\1", name).replace(".", "_").replace("/", "_") + return s.lower().strip("_") def to_camel_case(name: str): - s = re.sub(r'[_ ](\w)', lambda m: m.group(1).upper(), - name.replace('.', '_').replace('/', '_')) + s = re.sub( + r"[_ ](\w)", + lambda m: m.group(1).upper(), + name.replace(".", "_").replace("/", "_"), + ) s = upper_first_letter(s) # if the first character is a number, we need to add an underscore # maybe we could convert it to the number name (like 2 would become "two")? if s[0].isdigit(): - s = f'_{s}' + s = f"_{s}" return s @@ -25,7 +28,7 @@ def upper_first_letter(name: str): def padded_hex(n: int): - return f'0x{n:02X}' + return f"0x{n:02X}" class PacketIdentifier: @@ -35,16 +38,20 @@ class PacketIdentifier: self.state = state def __eq__(self, other): - return self.packet_id == other.packet_id and self.direction == other.direction and self.state == other.state + return ( + self.packet_id == other.packet_id + and self.direction == other.direction + and self.state == other.state + ) def __hash__(self): return hash((self.packet_id, self.direction, self.state)) def __str__(self): - return f'{self.packet_id} {self.direction} {self.state}' + return f"{self.packet_id} {self.direction} {self.state}" def __repr__(self): - return f'PacketIdentifier({self.packet_id}, {self.direction}, {self.state})' + return f"PacketIdentifier({self.packet_id}, {self.direction}, {self.state})" def group_packets(packets: list[PacketIdentifier]): diff --git a/codegen/migrate.py b/codegen/migrate.py index 56874273..85dc927d 100644 --- a/codegen/migrate.py +++ b/codegen/migrate.py @@ -1,4 +1,4 @@ -import lib.code.item_components +import codegen.lib.code.data_components import lib.code.inventory import lib.code.language import lib.code.registry @@ -15,10 +15,10 @@ import sys lib.download.clear_version_cache() if len(sys.argv) == 1: - print('\033[91mYou must provide a version to migrate to.\033[m') + print("\033[91mYou must provide a version to migrate to.\033[m") version_manifest = lib.download.get_version_manifest() - newest_version = version_manifest['latest']['snapshot'] - print(f'Hint: newest version is \033[1m{newest_version}\033[m') + newest_version = version_manifest["latest"]["snapshot"] + print(f"Hint: newest version is \033[1m{newest_version}\033[m") exit() @@ -33,42 +33,49 @@ new_burger_data = lib.extract.get_burger_data_for_version(new_version_id) new_packets_report = lib.extract.get_packets_report(new_version_id) lib.code.packet.set_packets(new_packets_report) -lib.code.version.set_protocol_version( - new_burger_data[0]['version']['protocol']) +lib.code.version.set_protocol_version(new_burger_data[0]["version"]["protocol"]) lib.code.version.set_version_name(new_version_id) -print('Updated protocol!') +print("Updated protocol!") -print('Generating blocks and shapes...') -new_pumpkin_block_datas = lib.extract.get_pumpkin_data(new_version_id, 'blocks') +print("Generating blocks and shapes...") +new_pumpkin_block_datas = lib.extract.get_pumpkin_data(new_version_id, "blocks") new_block_states_report = lib.extract.get_block_states_report(new_version_id) new_registries = lib.extract.get_registries_report(new_version_id) new_ordered_blocks = lib.code.blocks.get_ordered_blocks(new_registries) -lib.code.blocks.generate_blocks(new_block_states_report, new_pumpkin_block_datas, new_ordered_blocks, new_burger_data) +lib.code.blocks.generate_blocks( + new_block_states_report, + new_pumpkin_block_datas, + new_ordered_blocks, + new_burger_data, +) lib.code.shapes.generate_block_shapes(new_pumpkin_block_datas, new_block_states_report) -print('Getting en_us.json...') +print("Getting en_us.json...") language = lib.extract.get_en_us_lang(new_version_id) lib.code.language.write_language(language) -print('Generating registries...') +print("Generating registries...") import genregistries + genregistries.generate(new_version_id) -print('Generating entity data...') -burger_entities_data = new_burger_data[0]['entities'] +print("Generating entity data...") +burger_entities_data = new_burger_data[0]["entities"] lib.code.entity.generate_entity_metadata(burger_entities_data, new_mappings) lib.code.entity.generate_entity_dimensions(burger_entities_data) -print('Generating item components...') -lib.code.item_components.generate(new_version_id) +print("Generating item components...") +lib.code.data_components.generate(new_version_id) -print('Finishing touches, setting version in README and formatting code...') +print("Finishing touches, setting version in README and formatting code...") lib.code.version.set_version_id(new_version_id) lib.code.utils.fmt() -print('Done!') -print('Make sure to `cargo check` and look for the generated `TODO`s to make sure everything is correct!') +print("Done!") +print( + "Make sure to `cargo check` and look for the generated `TODO`s to make sure everything is correct!" +) |
