From c323654113d30f8470893de3b11fd69c8c462fc0 Mon Sep 17 00:00:00 2001 From: Mgazul Date: Sat, 7 Dec 2024 23:43:09 +0800 Subject: [PATCH] Update neoforge --- docs/README.md | 2 +- .../resources/assets/c/lang/en_us.json | 1 - .../data/c/tags/item/slime_balls.json | 4 - .../data/c/tags/item/slimeballs.json | 5 - .../loot_table/blocks/pale_oak_leaves.json | 132 ++++++++++ .../minecraft/recipe/pale_oak_chest_boat.json | 17 ++ .../data/minecraft/recipe/pale_oak_fence.json | 17 ++ .../minecraft/recipe/pale_oak_fence_gate.json | 17 ++ .../data/minecraft/recipe/pale_oak_sign.json | 18 ++ .../block/incorrect_for_diamond_tool.json | 5 + .../tags/block/incorrect_for_gold_tool.json | 5 + .../tags/block/incorrect_for_iron_tool.json | 5 + .../tags/block/incorrect_for_stone_tool.json | 5 + .../tags/block/incorrect_for_wooden_tool.json | 5 + .../entity_type/parrot_imitations.json | 3 - .../neoforge/data_maps/item/compostables.json | 6 + .../tags/block/needs_netherite_tool.json | 3 + src/generated/resources/pack.mcmeta | 2 +- .../neoforge/client/ClientHooks.java | 56 +++-- .../neoforge/client/ClientNeoForgeMod.java | 100 +++++++- .../neoforge/client/ItemDecoratorHandler.java | 4 + .../neoforge/client/NeoForgeRenderTypes.java | 106 ++++---- .../client/color/item/FluidContentsTint.java | 39 +++ .../geometry => color/item}/package-info.java | 2 +- .../animation/json/AnimationLoader.java | 3 +- .../neoforge/client/event/ModelEvent.java | 83 ++++--- .../event/RegisterColorHandlersEvent.java | 79 ++---- ...sterConditionalItemModelPropertyEvent.java | 35 +++ .../client/event/RegisterItemModelsEvent.java | 35 +++ .../event/RegisterMaterialAtlasesEvent.java | 58 +++++ ...sterRangeSelectItemModelPropertyEvent.java | 35 +++ .../RegisterSelectItemModelPropertyEvent.java | 34 +++ .../RegisterSpecialModelRendererEvent.java | 35 +++ .../client/event/RenderItemInFrameEvent.java | 8 +- .../client/event/RenderLivingEvent.java | 11 +- .../client/event/RenderNameTagEvent.java | 21 +- .../client/event/RenderPlayerEvent.java | 14 +- .../client/event/SelectMusicEvent.java | 17 +- .../extensions/IBakedModelExtension.java | 13 +- .../IDimensionSpecialEffectsExtension.java | 3 +- .../extensions/IGuiGraphicsExtension.java | 2 +- .../extensions/IModelBakerExtension.java | 37 ++- .../extensions/IRenderStateExtension.java | 66 +++++ .../extensions/IUnbakedModelExtension.java | 69 ++++++ .../common/IClientFluidTypeExtensions.java | 5 +- .../common/IClientItemExtensions.java | 52 ++-- .../client/gui/ConfigurationScreen.java | 22 +- .../client/gui/LoadingErrorScreen.java | 2 +- .../neoforge/client/gui/ModListScreen.java | 60 +++-- .../gui/ScrollableExperimentsScreen.java | 147 +++++++++++ .../client/gui/widget/ModListWidget.java | 2 +- .../model/AbstractSimpleUnbakedModel.java | 60 +++++ .../client/model/BakedModelWrapper.java | 119 --------- .../client/model/DelegateUnbakedModel.java | 67 +++++ .../model/DynamicFluidContainerModel.java | 231 ------------------ .../neoforge/client/model/EmptyModel.java | 30 ++- .../model/ExtendedBlockModelDeserializer.java | 107 -------- .../client/model/ExtendedUnbakedModel.java | 30 +++ .../neoforge/client/model/IModelBuilder.java | 21 ++ .../neoforge/client/model/ItemLayerModel.java | 126 ---------- .../client/model/NeoForgeModelProperties.java | 29 +++ .../client/model/SeparateTransformsModel.java | 162 ------------ ...eModel.java => UnbakedCompositeModel.java} | 116 ++++----- ...Helper.java => UnbakedElementsHelper.java} | 105 +++----- .../client/model/UnbakedModelLoader.java | 36 +++ .../client/model/UnbakedModelParser.java | 80 ++++++ .../model/generators/CustomLoaderBuilder.java | 1 - .../client/model/generators/ModelBuilder.java | 7 +- .../model/generators/ModelProvider.java | 4 + .../geometry/BlockGeometryBakingContext.java | 161 ------------ .../model/geometry/GeometryLoaderManager.java | 51 ---- .../geometry/IGeometryBakingContext.java | 92 ------- .../model/geometry/IGeometryLoader.java | 26 -- .../model/geometry/IUnbakedGeometry.java | 44 ---- .../model/geometry/SimpleUnbakedGeometry.java | 40 --- .../StandaloneGeometryBakingContext.java | 213 ---------------- .../item/DynamicFluidContainerModel.java | 206 ++++++++++++++++ .../{renderable => item}/package-info.java | 2 +- .../neoforge/client/model/obj/ObjLoader.java | 26 +- .../neoforge/client/model/obj/ObjModel.java | 156 ++++-------- .../renderable/BakedModelRenderable.java | 86 ------- .../model/renderable/CompositeRenderable.java | 153 ------------ .../client/model/renderable/IRenderable.java | 42 ---- .../renderable/ITextureRenderTypeLookup.java | 17 -- .../client/renderstate/BaseRenderState.java | 43 ++++ .../MapDecorationRenderStateModifier.java | 27 ++ .../RegisterRenderStateModifiersEvent.java | 142 +++++++++++ .../renderstate/RenderStateExtensions.java | 83 +++++++ .../client/renderstate/package-info.java | 13 + .../neoforge/common/CommonHooks.java | 4 +- .../neoforge/common/DeferredSpawnEggItem.java | 100 -------- .../neoforge/common/ModConfigSpec.java | 3 +- .../neoforge/common/NeoForgeConfig.java | 22 ++ .../neoforge/common/NeoForgeMod.java | 68 +----- .../neoforge/common/PercentageAttribute.java | 7 +- .../net/neoforged/neoforge/common/Tags.java | 27 +- .../common/conditions/ConditionContext.java | 20 +- .../FeatureFlagsEnabledCondition.java | 38 +++ .../common/conditions/ICondition.java | 13 + .../common/conditions/IConditionBuilder.java | 18 ++ .../common/crafting/CompoundIngredient.java | 2 +- .../crafting/CustomDisplayIngredient.java | 2 +- .../common/crafting/DifferenceIngredient.java | 2 +- .../crafting/IntersectionIngredient.java | 2 +- .../neoforge/common/data/DataMapProvider.java | 3 +- .../GeneratingOverlayMetadataSection.java | 4 +- .../internal/NeoForgeBlockTagsProvider.java | 8 + .../internal/NeoForgeItemTagsProvider.java | 3 +- .../internal/NeoForgeLanguageProvider.java | 1 - .../extensions/IAttributeExtension.java | 3 +- .../common/extensions/IBlockExtension.java | 19 +- .../extensions/IBlockStateExtension.java | 17 +- .../common/extensions/IEntityExtension.java | 13 - .../common/extensions/IFallableExtension.java | 24 ++ .../common/extensions/IItemExtension.java | 25 +- .../common/extensions/ILevelExtension.java | 11 - .../common/loot/LootModifierManager.java | 3 +- .../neoforge/common/util/FakePlayer.java | 4 - .../neoforge/common/util/SelfTest.java | 79 ++++++ .../common/util/flag/FeatureFlagLoader.java | 71 ++++++ .../common/util/flag/package-info.java | 13 + .../world/BiomeSpecialEffectsBuilder.java | 3 +- .../neoforge/data/event/GatherDataEvent.java | 91 +++++-- .../data/loading/DatagenModLoader.java | 18 +- .../neoforged/neoforge/event/EventHooks.java | 12 +- .../event/entity/player/PlayerEvent.java | 8 +- .../neoforge/event/level/ExplosionEvent.java | 12 +- .../event/level/ExplosionKnockbackEvent.java | 13 +- .../network/ConfigurationInitialization.java | 4 +- .../network/NetworkInitialization.java | 11 + .../neoforge/network/PacketDistributor.java | 14 +- .../configuration/CheckExtensibleEnums.java | 34 ++- .../configuration/CheckFeatureFlags.java | 108 ++++++++ .../FeatureFlagAcknowledgePayload.java | 26 ++ .../payload/FeatureFlagDataPayload.java | 29 +++ .../network/registration/NetworkRegistry.java | 5 + .../neoforge/registries/DeferredRegister.java | 55 +++-- .../neoforge/registries/GameData.java | 6 +- .../resource/ContextAwareReloadListener.java | 12 + .../neoforge/resource/EmptyPackResources.java | 7 +- .../neoforge/resource/ResourcePackLoader.java | 2 +- .../neoforge/server/command/TagsCommand.java | 39 ++- .../resources/assets/neoforge/lang/cs_cz.json | 55 +++-- .../resources/assets/neoforge/lang/da_dk.json | 13 +- .../resources/assets/neoforge/lang/de_de.json | 21 +- .../resources/assets/neoforge/lang/en_gb.json | 13 +- .../resources/assets/neoforge/lang/en_us.json | 9 +- .../resources/assets/neoforge/lang/eo_uy.json | 13 +- .../resources/assets/neoforge/lang/es_es.json | 19 +- .../resources/assets/neoforge/lang/et_ee.json | 13 +- .../resources/assets/neoforge/lang/fr_fr.json | 13 +- .../resources/assets/neoforge/lang/hu_hu.json | 13 +- .../resources/assets/neoforge/lang/it_it.json | 13 +- .../resources/assets/neoforge/lang/ja_jp.json | 21 +- .../resources/assets/neoforge/lang/ko_kr.json | 13 +- .../resources/assets/neoforge/lang/ms_my.json | 39 +-- .../resources/assets/neoforge/lang/nl_nl.json | 13 +- .../resources/assets/neoforge/lang/pl_pl.json | 13 +- .../resources/assets/neoforge/lang/pt_br.json | 13 +- .../resources/assets/neoforge/lang/pt_pt.json | 13 +- .../resources/assets/neoforge/lang/ro_ro.json | 13 +- .../resources/assets/neoforge/lang/ru_ru.json | 87 ++++--- .../resources/assets/neoforge/lang/sk_sk.json | 13 +- .../resources/assets/neoforge/lang/tr_tr.json | 13 +- .../resources/assets/neoforge/lang/tt_ru.json | 13 +- .../resources/assets/neoforge/lang/uk_ua.json | 21 +- .../resources/assets/neoforge/lang/vi_vn.json | 13 +- .../resources/assets/neoforge/lang/zh_cn.json | 13 +- .../resources/assets/neoforge/lang/zh_tw.json | 169 +++++++------ 169 files changed, 3492 insertions(+), 2782 deletions(-) delete mode 100644 src/generated/resources/data/c/tags/item/slimeballs.json create mode 100644 src/generated/resources/data/minecraft/loot_table/blocks/pale_oak_leaves.json create mode 100644 src/generated/resources/data/minecraft/recipe/pale_oak_chest_boat.json create mode 100644 src/generated/resources/data/minecraft/recipe/pale_oak_fence.json create mode 100644 src/generated/resources/data/minecraft/recipe/pale_oak_fence_gate.json create mode 100644 src/generated/resources/data/minecraft/recipe/pale_oak_sign.json create mode 100644 src/generated/resources/data/minecraft/tags/block/incorrect_for_diamond_tool.json create mode 100644 src/generated/resources/data/minecraft/tags/block/incorrect_for_gold_tool.json create mode 100644 src/generated/resources/data/minecraft/tags/block/incorrect_for_iron_tool.json create mode 100644 src/generated/resources/data/minecraft/tags/block/incorrect_for_stone_tool.json create mode 100644 src/generated/resources/data/minecraft/tags/block/incorrect_for_wooden_tool.json create mode 100644 src/generated/resources/data/neoforge/tags/block/needs_netherite_tool.json create mode 100644 src/main/java/net/neoforged/neoforge/client/color/item/FluidContentsTint.java rename src/main/java/net/neoforged/neoforge/client/{model/geometry => color/item}/package-info.java (86%) create mode 100644 src/main/java/net/neoforged/neoforge/client/event/RegisterConditionalItemModelPropertyEvent.java create mode 100644 src/main/java/net/neoforged/neoforge/client/event/RegisterItemModelsEvent.java create mode 100644 src/main/java/net/neoforged/neoforge/client/event/RegisterMaterialAtlasesEvent.java create mode 100644 src/main/java/net/neoforged/neoforge/client/event/RegisterRangeSelectItemModelPropertyEvent.java create mode 100644 src/main/java/net/neoforged/neoforge/client/event/RegisterSelectItemModelPropertyEvent.java create mode 100644 src/main/java/net/neoforged/neoforge/client/event/RegisterSpecialModelRendererEvent.java create mode 100644 src/main/java/net/neoforged/neoforge/client/extensions/IRenderStateExtension.java create mode 100644 src/main/java/net/neoforged/neoforge/client/extensions/IUnbakedModelExtension.java create mode 100644 src/main/java/net/neoforged/neoforge/client/gui/ScrollableExperimentsScreen.java create mode 100644 src/main/java/net/neoforged/neoforge/client/model/AbstractSimpleUnbakedModel.java delete mode 100644 src/main/java/net/neoforged/neoforge/client/model/BakedModelWrapper.java create mode 100644 src/main/java/net/neoforged/neoforge/client/model/DelegateUnbakedModel.java delete mode 100644 src/main/java/net/neoforged/neoforge/client/model/DynamicFluidContainerModel.java delete mode 100644 src/main/java/net/neoforged/neoforge/client/model/ExtendedBlockModelDeserializer.java create mode 100644 src/main/java/net/neoforged/neoforge/client/model/ExtendedUnbakedModel.java delete mode 100644 src/main/java/net/neoforged/neoforge/client/model/ItemLayerModel.java create mode 100644 src/main/java/net/neoforged/neoforge/client/model/NeoForgeModelProperties.java delete mode 100644 src/main/java/net/neoforged/neoforge/client/model/SeparateTransformsModel.java rename src/main/java/net/neoforged/neoforge/client/model/{CompositeModel.java => UnbakedCompositeModel.java} (74%) rename src/main/java/net/neoforged/neoforge/client/model/{geometry/UnbakedGeometryHelper.java => UnbakedElementsHelper.java} (64%) create mode 100644 src/main/java/net/neoforged/neoforge/client/model/UnbakedModelLoader.java create mode 100644 src/main/java/net/neoforged/neoforge/client/model/UnbakedModelParser.java delete mode 100644 src/main/java/net/neoforged/neoforge/client/model/geometry/BlockGeometryBakingContext.java delete mode 100644 src/main/java/net/neoforged/neoforge/client/model/geometry/GeometryLoaderManager.java delete mode 100644 src/main/java/net/neoforged/neoforge/client/model/geometry/IGeometryBakingContext.java delete mode 100644 src/main/java/net/neoforged/neoforge/client/model/geometry/IGeometryLoader.java delete mode 100644 src/main/java/net/neoforged/neoforge/client/model/geometry/IUnbakedGeometry.java delete mode 100644 src/main/java/net/neoforged/neoforge/client/model/geometry/SimpleUnbakedGeometry.java delete mode 100644 src/main/java/net/neoforged/neoforge/client/model/geometry/StandaloneGeometryBakingContext.java create mode 100644 src/main/java/net/neoforged/neoforge/client/model/item/DynamicFluidContainerModel.java rename src/main/java/net/neoforged/neoforge/client/model/{renderable => item}/package-info.java (85%) delete mode 100644 src/main/java/net/neoforged/neoforge/client/model/renderable/BakedModelRenderable.java delete mode 100644 src/main/java/net/neoforged/neoforge/client/model/renderable/CompositeRenderable.java delete mode 100644 src/main/java/net/neoforged/neoforge/client/model/renderable/IRenderable.java delete mode 100644 src/main/java/net/neoforged/neoforge/client/model/renderable/ITextureRenderTypeLookup.java create mode 100644 src/main/java/net/neoforged/neoforge/client/renderstate/BaseRenderState.java create mode 100644 src/main/java/net/neoforged/neoforge/client/renderstate/MapDecorationRenderStateModifier.java create mode 100644 src/main/java/net/neoforged/neoforge/client/renderstate/RegisterRenderStateModifiersEvent.java create mode 100644 src/main/java/net/neoforged/neoforge/client/renderstate/RenderStateExtensions.java create mode 100644 src/main/java/net/neoforged/neoforge/client/renderstate/package-info.java delete mode 100644 src/main/java/net/neoforged/neoforge/common/DeferredSpawnEggItem.java create mode 100644 src/main/java/net/neoforged/neoforge/common/conditions/FeatureFlagsEnabledCondition.java create mode 100644 src/main/java/net/neoforged/neoforge/common/extensions/IFallableExtension.java create mode 100644 src/main/java/net/neoforged/neoforge/common/util/SelfTest.java create mode 100644 src/main/java/net/neoforged/neoforge/common/util/flag/FeatureFlagLoader.java create mode 100644 src/main/java/net/neoforged/neoforge/common/util/flag/package-info.java create mode 100644 src/main/java/net/neoforged/neoforge/network/configuration/CheckFeatureFlags.java create mode 100644 src/main/java/net/neoforged/neoforge/network/payload/FeatureFlagAcknowledgePayload.java create mode 100644 src/main/java/net/neoforged/neoforge/network/payload/FeatureFlagDataPayload.java diff --git a/docs/README.md b/docs/README.md index ddb98c54..f013ad3f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,7 +21,7 @@ Progress ------ -- [x] Rectify NeoForge([**dd989e5cff**](https://github.com/neoforged/NeoForge/commit/dd989e5cff)) +- [x] Rectify NeoForge([**00f3f80ef9**](https://github.com/neoforged/NeoForge/commit/00f3f80ef9)) - [ ] Start patch * [x] Bukkit([**8ec77750**](https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/commits/8ec77750)) * [ ] CraftBukkit([**d50f50585**](https://hub.spigotmc.org/stash/projects/SPIGOT/repos/craftbukkit/commits/d50f50585)) diff --git a/src/generated/resources/assets/c/lang/en_us.json b/src/generated/resources/assets/c/lang/en_us.json index 3c27e29b..ddba91b3 100644 --- a/src/generated/resources/assets/c/lang/en_us.json +++ b/src/generated/resources/assets/c/lang/en_us.json @@ -330,7 +330,6 @@ "tag.item.c.seeds.wheat": "Wheat Seeds", "tag.item.c.shulker_boxes": "Shulker Boxes", "tag.item.c.slime_balls": "Slimeballs", - "tag.item.c.slimeballs": "Slimeballs", "tag.item.c.stones": "Stones", "tag.item.c.storage_blocks": "Storage Blocks", "tag.item.c.storage_blocks.bone_meal": "Bone Meal Storage Blocks", diff --git a/src/generated/resources/data/c/tags/item/slime_balls.json b/src/generated/resources/data/c/tags/item/slime_balls.json index c5d2c7d8..168a8c2a 100644 --- a/src/generated/resources/data/c/tags/item/slime_balls.json +++ b/src/generated/resources/data/c/tags/item/slime_balls.json @@ -1,10 +1,6 @@ { "values": [ "minecraft:slime_ball", - { - "id": "#c:slimeballs", - "required": false - }, { "id": "#forge:slime_balls", "required": false diff --git a/src/generated/resources/data/c/tags/item/slimeballs.json b/src/generated/resources/data/c/tags/item/slimeballs.json deleted file mode 100644 index 533c25d9..00000000 --- a/src/generated/resources/data/c/tags/item/slimeballs.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "values": [ - "minecraft:slime_ball" - ] -} \ No newline at end of file diff --git a/src/generated/resources/data/minecraft/loot_table/blocks/pale_oak_leaves.json b/src/generated/resources/data/minecraft/loot_table/blocks/pale_oak_leaves.json new file mode 100644 index 00000000..ff55c722 --- /dev/null +++ b/src/generated/resources/data/minecraft/loot_table/blocks/pale_oak_leaves.json @@ -0,0 +1,132 @@ +{ + "type": "minecraft:block", + "pools": [ + { + "bonus_rolls": 0.0, + "entries": [ + { + "type": "minecraft:alternatives", + "children": [ + { + "type": "minecraft:item", + "conditions": [ + { + "condition": "minecraft:any_of", + "terms": [ + { + "ability": "shears_dig", + "condition": "neoforge:can_item_perform_ability" + }, + { + "condition": "minecraft:match_tool", + "predicate": { + "predicates": { + "minecraft:enchantments": [ + { + "enchantments": "minecraft:silk_touch", + "levels": { + "min": 1 + } + } + ] + } + } + } + ] + } + ], + "name": "minecraft:pale_oak_leaves" + }, + { + "type": "minecraft:item", + "conditions": [ + { + "condition": "minecraft:survives_explosion" + }, + { + "chances": [ + 0.05, + 0.0625, + 0.083333336, + 0.1 + ], + "condition": "minecraft:table_bonus", + "enchantment": "minecraft:fortune" + } + ], + "name": "minecraft:pale_oak_sapling" + } + ] + } + ], + "rolls": 1.0 + }, + { + "bonus_rolls": 0.0, + "conditions": [ + { + "condition": "minecraft:inverted", + "term": { + "condition": "minecraft:any_of", + "terms": [ + { + "ability": "shears_dig", + "condition": "neoforge:can_item_perform_ability" + }, + { + "condition": "minecraft:match_tool", + "predicate": { + "predicates": { + "minecraft:enchantments": [ + { + "enchantments": "minecraft:silk_touch", + "levels": { + "min": 1 + } + } + ] + } + } + } + ] + } + } + ], + "entries": [ + { + "type": "minecraft:item", + "conditions": [ + { + "chances": [ + 0.02, + 0.022222223, + 0.025, + 0.033333335, + 0.1 + ], + "condition": "minecraft:table_bonus", + "enchantment": "minecraft:fortune" + } + ], + "functions": [ + { + "add": false, + "count": { + "type": "minecraft:uniform", + "max": 2.0, + "min": 1.0 + }, + "function": "minecraft:set_count" + }, + { + "function": "minecraft:explosion_decay" + } + ], + "name": "minecraft:stick" + } + ], + "rolls": 1.0 + } + ], + "random_sequence": "minecraft:blocks/pale_oak_leaves" +} \ No newline at end of file diff --git a/src/generated/resources/data/minecraft/recipe/pale_oak_chest_boat.json b/src/generated/resources/data/minecraft/recipe/pale_oak_chest_boat.json new file mode 100644 index 00000000..8339b115 --- /dev/null +++ b/src/generated/resources/data/minecraft/recipe/pale_oak_chest_boat.json @@ -0,0 +1,17 @@ +{ + "type": "minecraft:crafting_shapeless", + "category": "misc", + "group": "chest_boat", + "ingredients": [ + { + "neoforge:ingredient_type": "neoforge:difference", + "base": "#c:chests/wooden", + "subtracted": "#c:chests/trapped" + }, + "minecraft:pale_oak_boat" + ], + "result": { + "count": 1, + "id": "minecraft:pale_oak_chest_boat" + } +} \ No newline at end of file diff --git a/src/generated/resources/data/minecraft/recipe/pale_oak_fence.json b/src/generated/resources/data/minecraft/recipe/pale_oak_fence.json new file mode 100644 index 00000000..c4a34ad5 --- /dev/null +++ b/src/generated/resources/data/minecraft/recipe/pale_oak_fence.json @@ -0,0 +1,17 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "group": "wooden_fence", + "key": { + "#": "#c:rods/wooden", + "W": "minecraft:pale_oak_planks" + }, + "pattern": [ + "W#W", + "W#W" + ], + "result": { + "count": 3, + "id": "minecraft:pale_oak_fence" + } +} \ No newline at end of file diff --git a/src/generated/resources/data/minecraft/recipe/pale_oak_fence_gate.json b/src/generated/resources/data/minecraft/recipe/pale_oak_fence_gate.json new file mode 100644 index 00000000..b09f87fa --- /dev/null +++ b/src/generated/resources/data/minecraft/recipe/pale_oak_fence_gate.json @@ -0,0 +1,17 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "redstone", + "group": "wooden_fence_gate", + "key": { + "#": "#c:rods/wooden", + "W": "minecraft:pale_oak_planks" + }, + "pattern": [ + "#W#", + "#W#" + ], + "result": { + "count": 1, + "id": "minecraft:pale_oak_fence_gate" + } +} \ No newline at end of file diff --git a/src/generated/resources/data/minecraft/recipe/pale_oak_sign.json b/src/generated/resources/data/minecraft/recipe/pale_oak_sign.json new file mode 100644 index 00000000..ac471218 --- /dev/null +++ b/src/generated/resources/data/minecraft/recipe/pale_oak_sign.json @@ -0,0 +1,18 @@ +{ + "type": "minecraft:crafting_shaped", + "category": "misc", + "group": "wooden_sign", + "key": { + "#": "minecraft:pale_oak_planks", + "X": "#c:rods/wooden" + }, + "pattern": [ + "###", + "###", + " X " + ], + "result": { + "count": 3, + "id": "minecraft:pale_oak_sign" + } +} \ No newline at end of file diff --git a/src/generated/resources/data/minecraft/tags/block/incorrect_for_diamond_tool.json b/src/generated/resources/data/minecraft/tags/block/incorrect_for_diamond_tool.json new file mode 100644 index 00000000..14864848 --- /dev/null +++ b/src/generated/resources/data/minecraft/tags/block/incorrect_for_diamond_tool.json @@ -0,0 +1,5 @@ +{ + "values": [ + "#neoforge:needs_netherite_tool" + ] +} \ No newline at end of file diff --git a/src/generated/resources/data/minecraft/tags/block/incorrect_for_gold_tool.json b/src/generated/resources/data/minecraft/tags/block/incorrect_for_gold_tool.json new file mode 100644 index 00000000..14864848 --- /dev/null +++ b/src/generated/resources/data/minecraft/tags/block/incorrect_for_gold_tool.json @@ -0,0 +1,5 @@ +{ + "values": [ + "#neoforge:needs_netherite_tool" + ] +} \ No newline at end of file diff --git a/src/generated/resources/data/minecraft/tags/block/incorrect_for_iron_tool.json b/src/generated/resources/data/minecraft/tags/block/incorrect_for_iron_tool.json new file mode 100644 index 00000000..14864848 --- /dev/null +++ b/src/generated/resources/data/minecraft/tags/block/incorrect_for_iron_tool.json @@ -0,0 +1,5 @@ +{ + "values": [ + "#neoforge:needs_netherite_tool" + ] +} \ No newline at end of file diff --git a/src/generated/resources/data/minecraft/tags/block/incorrect_for_stone_tool.json b/src/generated/resources/data/minecraft/tags/block/incorrect_for_stone_tool.json new file mode 100644 index 00000000..14864848 --- /dev/null +++ b/src/generated/resources/data/minecraft/tags/block/incorrect_for_stone_tool.json @@ -0,0 +1,5 @@ +{ + "values": [ + "#neoforge:needs_netherite_tool" + ] +} \ No newline at end of file diff --git a/src/generated/resources/data/minecraft/tags/block/incorrect_for_wooden_tool.json b/src/generated/resources/data/minecraft/tags/block/incorrect_for_wooden_tool.json new file mode 100644 index 00000000..14864848 --- /dev/null +++ b/src/generated/resources/data/minecraft/tags/block/incorrect_for_wooden_tool.json @@ -0,0 +1,5 @@ +{ + "values": [ + "#neoforge:needs_netherite_tool" + ] +} \ No newline at end of file diff --git a/src/generated/resources/data/neoforge/data_maps/entity_type/parrot_imitations.json b/src/generated/resources/data/neoforge/data_maps/entity_type/parrot_imitations.json index 513f0341..8f1d1c00 100644 --- a/src/generated/resources/data/neoforge/data_maps/entity_type/parrot_imitations.json +++ b/src/generated/resources/data/neoforge/data_maps/entity_type/parrot_imitations.json @@ -15,9 +15,6 @@ "minecraft:creaking": { "sound": "minecraft:entity.parrot.imitate.creaking" }, - "minecraft:creaking_transient": { - "sound": "minecraft:entity.parrot.imitate.creaking" - }, "minecraft:creeper": { "sound": "minecraft:entity.parrot.imitate.creeper" }, diff --git a/src/generated/resources/data/neoforge/data_maps/item/compostables.json b/src/generated/resources/data/neoforge/data_maps/item/compostables.json index 2b3bdff6..c0b37892 100644 --- a/src/generated/resources/data/neoforge/data_maps/item/compostables.json +++ b/src/generated/resources/data/neoforge/data_maps/item/compostables.json @@ -70,6 +70,9 @@ "minecraft:cherry_sapling": { "chance": 0.3 }, + "minecraft:closed_eyeblossom": { + "chance": 0.65 + }, "minecraft:cocoa_beans": { "chance": 0.65 }, @@ -184,6 +187,9 @@ "minecraft:oak_sapling": { "chance": 0.3 }, + "minecraft:open_eyeblossom": { + "chance": 0.65 + }, "minecraft:orange_tulip": { "chance": 0.65 }, diff --git a/src/generated/resources/data/neoforge/tags/block/needs_netherite_tool.json b/src/generated/resources/data/neoforge/tags/block/needs_netherite_tool.json new file mode 100644 index 00000000..f72d209d --- /dev/null +++ b/src/generated/resources/data/neoforge/tags/block/needs_netherite_tool.json @@ -0,0 +1,3 @@ +{ + "values": [] +} \ No newline at end of file diff --git a/src/generated/resources/pack.mcmeta b/src/generated/resources/pack.mcmeta index f49d678e..607b7ff4 100644 --- a/src/generated/resources/pack.mcmeta +++ b/src/generated/resources/pack.mcmeta @@ -3,7 +3,7 @@ "description": { "translate": "pack.neoforge.description" }, - "pack_format": 57, + "pack_format": 61, "supported_formats": [ 0, 2147483647 diff --git a/src/main/java/net/neoforged/neoforge/client/ClientHooks.java b/src/main/java/net/neoforged/neoforge/client/ClientHooks.java index a9472815..50750c45 100644 --- a/src/main/java/net/neoforged/neoforge/client/ClientHooks.java +++ b/src/main/java/net/neoforged/neoforge/client/ClientHooks.java @@ -17,16 +17,15 @@ import java.io.File; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.Stack; import java.util.UUID; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -38,7 +37,6 @@ import net.minecraft.client.MouseHandler; import net.minecraft.client.Options; import net.minecraft.client.color.block.BlockColors; -import net.minecraft.client.color.item.ItemColors; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.LerpingBossEvent; @@ -85,11 +83,12 @@ import net.minecraft.client.resources.language.I18n; import net.minecraft.client.resources.model.AtlasSet; import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.EquipmentClientInfo; import net.minecraft.client.resources.model.Material; import net.minecraft.client.resources.model.ModelBakery; import net.minecraft.client.resources.model.ModelManager; -import net.minecraft.client.resources.model.ModelResourceLocation; import net.minecraft.client.resources.sounds.SoundInstance; +import net.minecraft.client.sounds.MusicInfo; import net.minecraft.client.sounds.SoundEngine; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -104,7 +103,6 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.ReloadableResourceManager; -import net.minecraft.sounds.Music; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.util.profiling.Profiler; @@ -118,7 +116,6 @@ import net.minecraft.world.inventory.tooltip.TooltipComponent; import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.equipment.EquipmentModel; import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.GameType; import net.minecraft.world.level.Level; @@ -154,6 +151,7 @@ import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent; import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent; import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent; +import net.neoforged.neoforge.client.event.RegisterMaterialAtlasesEvent; import net.neoforged.neoforge.client.event.RegisterParticleProvidersEvent; import net.neoforged.neoforge.client.event.RegisterShadersEvent; import net.neoforged.neoforge.client.event.RegisterSpriteSourceTypesEvent; @@ -179,6 +177,7 @@ import net.neoforged.neoforge.client.gui.GuiLayerManager; import net.neoforged.neoforge.client.gui.map.MapDecorationRendererManager; import net.neoforged.neoforge.client.model.data.ModelData; +import net.neoforged.neoforge.client.renderstate.RegisterRenderStateModifiersEvent; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.common.NeoForgeMod; import net.neoforged.neoforge.forge.snapshots.ForgeSnapshotsModClient; @@ -253,8 +252,8 @@ public static float getGuiFarPlane() { return 11_000F + depth; } - public static ResourceLocation getArmorTexture(ItemStack armor, EquipmentModel.LayerType type, EquipmentModel.Layer layer, ResourceLocation _default) { - ResourceLocation result = armor.getItem().getArmorTexture(armor, type, layer, _default); + public static ResourceLocation getArmorTexture(ItemStack armor, EquipmentClientInfo.LayerType type, EquipmentClientInfo.Layer layer, ResourceLocation _default) { + ResourceLocation result = IClientItemExtensions.of(armor).getArmorTexture(armor, type, layer, _default); return result != null ? result : _default; } @@ -301,11 +300,7 @@ public static void onBlockColorsInit(BlockColors blockColors) { ModLoader.postEvent(new RegisterColorHandlersEvent.Block(blockColors)); } - public static void onItemColorsInit(ItemColors itemColors, BlockColors blockColors) { - ModLoader.postEvent(new RegisterColorHandlersEvent.Item(itemColors, blockColors)); - } - - public static Model getArmorModel(ItemStack itemStack, EquipmentModel.LayerType layerType, Model _default) { + public static Model getArmorModel(ItemStack itemStack, EquipmentClientInfo.LayerType layerType, Model _default) { return IClientItemExtensions.of(itemStack).getGenericArmorModel(itemStack, layerType, _default); } @@ -393,7 +388,7 @@ public static SoundInstance playSound(SoundEngine manager, SoundInstance sound) } @Nullable - public static Music selectMusic(Music situational, @Nullable SoundInstance playing) { + public static MusicInfo selectMusic(MusicInfo situational, @Nullable SoundInstance playing) { SelectMusicEvent e = new SelectMusicEvent(situational, playing); NeoForge.EVENT_BUS.post(e); return e.getMusic(); @@ -418,9 +413,9 @@ private static void drawScreenInternal(Screen screen, GuiGraphics guiGraphics, i public static Vector4f getFogColor(Camera camera, float partialTick, ClientLevel level, int renderDistance, float darkenWorldAmount, float fogRed, float fogGreen, float fogBlue) { // Modify fog color depending on the fluid - FluidState state = level.getFluidState(camera.getBlockPos()); + FluidState state = level.getFluidState(camera.getBlockPosition()); Vector4f fluidFogColor = new Vector4f(fogRed, fogGreen, fogBlue, 1F); - if (camera.getPosition().y < (double) ((float) camera.getBlockPos().getY() + state.getHeight(level, camera.getBlockPos()))) + if (camera.getPosition().y < (double) ((float) camera.getBlockPosition().getY() + state.getHeight(level, camera.getBlockPosition()))) fluidFogColor = IClientFluidTypeExtensions.of(state).modifyFogColor(camera, partialTick, level, renderDistance, darkenWorldAmount, fluidFogColor); ViewportEvent.ComputeFogColor event = new ViewportEvent.ComputeFogColor(camera, partialTick, fluidFogColor.x(), fluidFogColor.y(), fluidFogColor.z()); @@ -432,8 +427,8 @@ public static Vector4f getFogColor(Camera camera, float partialTick, ClientLevel public static FogParameters onFogRender(FogRenderer.FogMode mode, FogType type, Camera camera, float partialTick, float renderDistance, FogParameters fogParameters) { // Modify fog rendering depending on the fluid - FluidState state = camera.getEntity().level().getFluidState(camera.getBlockPos()); - if (camera.getPosition().y < (double) ((float) camera.getBlockPos().getY() + state.getHeight(camera.getEntity().level(), camera.getBlockPos()))) + FluidState state = camera.getEntity().level().getFluidState(camera.getBlockPosition()); + if (camera.getPosition().y < (double) ((float) camera.getBlockPosition().getY() + state.getHeight(camera.getEntity().level(), camera.getBlockPosition()))) fogParameters = IClientFluidTypeExtensions.of(state).modifyFogRender(camera, mode, renderDistance, partialTick, fogParameters); ViewportEvent.RenderFog event = new ViewportEvent.RenderFog(mode, type, camera, partialTick, fogParameters); @@ -450,7 +445,7 @@ public static FogParameters onFogRender(FogRenderer.FogMode mode, FogType type, return fogParameters; } - public static void onModifyBakingResult(Map models, Map stitchResults, ModelBakery modelBakery) { + public static void onModifyBakingResult(ModelBakery.BakingResult bakingResult, Map stitchResults, ModelBakery modelBakery) { Function textureGetter = material -> { AtlasSet.StitchResult stitchResult = stitchResults.get(material.atlasLocation()); TextureAtlasSprite sprite = stitchResult.getSprite(material.texture()); @@ -460,14 +455,14 @@ public static void onModifyBakingResult(Map m LOGGER.warn("Failed to retrieve texture '{}' from atlas '{}'", material.texture(), material.atlasLocation(), new Throwable()); return stitchResult.missing(); }; - ModLoader.postEvent(new ModelEvent.ModifyBakingResult(models, textureGetter, modelBakery)); + ModLoader.postEvent(new ModelEvent.ModifyBakingResult(bakingResult, textureGetter, modelBakery)); } - public static void onModelBake(ModelManager modelManager, Map models, ModelBakery modelBakery) { - ModLoader.postEvent(new ModelEvent.BakingCompleted(modelManager, Collections.unmodifiableMap(models), modelBakery)); + public static void onModelBake(ModelManager modelManager, ModelBakery.BakingResult bakingResult, ModelBakery modelBakery) { + ModLoader.postEvent(new ModelEvent.BakingCompleted(modelManager, bakingResult, modelBakery)); } - public static BakedModel handleCameraTransforms(PoseStack poseStack, BakedModel model, ItemDisplayContext cameraTransformType, boolean applyLeftHandTransform) { + public static BakedModel handleCameraTransforms(PoseStack poseStack, @Nullable BakedModel model, ItemDisplayContext cameraTransformType, boolean applyLeftHandTransform) { model = model.applyTransform(cameraTransformType, poseStack, applyLeftHandTransform); return model; } @@ -723,8 +718,8 @@ public static void onRegisterKeyMappings(Options options) { ModLoader.postEvent(new RegisterKeyMappingsEvent(options)); } - public static void onRegisterAdditionalModels(Set additionalModels) { - ModLoader.postEvent(new ModelEvent.RegisterAdditional(additionalModels)); + public static void onRegisterAdditionalModels(Consumer registrar) { + ModLoader.postEvent(new ModelEvent.RegisterAdditional(registrar)); } @Nullable @@ -989,6 +984,7 @@ public static void initClientHooks(Minecraft mc, ReloadableResourceManager resou ModLoader.postEvent(new RegisterClientReloadListenersEvent(resourceManager)); ModLoader.postEvent(new EntityRenderersEvent.RegisterLayerDefinitions()); ModLoader.postEvent(new EntityRenderersEvent.RegisterRenderers()); + ModLoader.postEvent(new RegisterRenderStateModifiersEvent()); ClientTooltipComponentManager.init(); EntitySpectatorShaderManager.init(); ClientHooks.onRegisterKeyMappings(mc.options); @@ -1094,4 +1090,14 @@ public static boolean isInTranslucentBlockOutlinePass(Level level, BlockPos pos, ChunkRenderTypeSet renderTypes = model.getRenderTypes(state, OUTLINE_PASS_RANDOM, level.getModelData(pos)); return renderTypes.contains(RenderType.TRANSLUCENT) || renderTypes.contains(RenderType.TRIPWIRE); } + + public static void reloadRenderer() { + Minecraft.getInstance().levelRenderer.allChanged(); + } + + public static Map gatherMaterialAtlases(Map vanillaAtlases) { + vanillaAtlases = new HashMap<>(vanillaAtlases); + ModLoader.postEvent(new RegisterMaterialAtlasesEvent(vanillaAtlases)); + return Map.copyOf(vanillaAtlases); + } } diff --git a/src/main/java/net/neoforged/neoforge/client/ClientNeoForgeMod.java b/src/main/java/net/neoforged/neoforge/client/ClientNeoForgeMod.java index c3781944..f783a342 100644 --- a/src/main/java/net/neoforged/neoforge/client/ClientNeoForgeMod.java +++ b/src/main/java/net/neoforged/neoforge/client/ClientNeoForgeMod.java @@ -5,11 +5,18 @@ package net.neoforged.neoforge.client; +import java.util.Optional; +import net.minecraft.DetectedVersion; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.BiomeColors; import net.minecraft.client.renderer.RenderType; import net.minecraft.core.BlockPos; +import net.minecraft.data.metadata.PackMetadataGenerator; +import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.metadata.pack.PackMetadataSection; +import net.minecraft.util.InclusiveRange; import net.minecraft.world.level.BlockAndTintGetter; import net.minecraft.world.level.material.FluidState; import net.neoforged.api.distmarker.Dist; @@ -18,33 +25,58 @@ import net.neoforged.fml.ModContainer; import net.neoforged.fml.common.Mod; import net.neoforged.fml.config.ModConfigs; +import net.neoforged.neoforge.client.color.item.FluidContentsTint; import net.neoforged.neoforge.client.entity.animation.json.AnimationLoader; import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; import net.neoforged.neoforge.client.event.ModelEvent; import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent; +import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent; +import net.neoforged.neoforge.client.event.RegisterItemModelsEvent; import net.neoforged.neoforge.client.event.RegisterNamedRenderTypesEvent; import net.neoforged.neoforge.client.event.RegisterSpriteSourceTypesEvent; import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions; import net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent; import net.neoforged.neoforge.client.gui.ConfigurationScreen; import net.neoforged.neoforge.client.gui.IConfigScreenFactory; -import net.neoforged.neoforge.client.model.CompositeModel; -import net.neoforged.neoforge.client.model.DynamicFluidContainerModel; import net.neoforged.neoforge.client.model.EmptyModel; -import net.neoforged.neoforge.client.model.ItemLayerModel; -import net.neoforged.neoforge.client.model.SeparateTransformsModel; +import net.neoforged.neoforge.client.model.UnbakedCompositeModel; +import net.neoforged.neoforge.client.model.item.DynamicFluidContainerModel; import net.neoforged.neoforge.client.model.obj.ObjLoader; import net.neoforged.neoforge.client.textures.NamespacedDirectoryLister; import net.neoforged.neoforge.common.ModConfigSpec; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.common.NeoForgeMod; +import net.neoforged.neoforge.common.data.internal.NeoForgeAdvancementProvider; +import net.neoforged.neoforge.common.data.internal.NeoForgeBiomeTagsProvider; +import net.neoforged.neoforge.common.data.internal.NeoForgeBlockTagsProvider; +import net.neoforged.neoforge.common.data.internal.NeoForgeDamageTypeTagsProvider; +import net.neoforged.neoforge.common.data.internal.NeoForgeDataMapsProvider; +import net.neoforged.neoforge.common.data.internal.NeoForgeEnchantmentTagsProvider; +import net.neoforged.neoforge.common.data.internal.NeoForgeEntityTypeTagsProvider; +import net.neoforged.neoforge.common.data.internal.NeoForgeFluidTagsProvider; +import net.neoforged.neoforge.common.data.internal.NeoForgeItemTagsProvider; +import net.neoforged.neoforge.common.data.internal.NeoForgeLanguageProvider; +import net.neoforged.neoforge.common.data.internal.NeoForgeLootTableProvider; +import net.neoforged.neoforge.common.data.internal.NeoForgeRecipeProvider; +import net.neoforged.neoforge.common.data.internal.NeoForgeRegistryOrderReportProvider; +import net.neoforged.neoforge.common.data.internal.NeoForgeSpriteSourceProvider; +import net.neoforged.neoforge.common.data.internal.NeoForgeStructureTagsProvider; +import net.neoforged.neoforge.common.data.internal.VanillaSoundDefinitionsProvider; +import net.neoforged.neoforge.common.util.SelfTest; +import net.neoforged.neoforge.data.event.GatherDataEvent; import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; import org.jetbrains.annotations.ApiStatus; @ApiStatus.Internal @Mod(value = "neoforge", dist = Dist.CLIENT) public class ClientNeoForgeMod { + private static ResourceLocation neoForgeId(String path) { + return ResourceLocation.fromNamespaceAndPath("neoforge", path); + } + public ClientNeoForgeMod(IEventBus modEventBus, ModContainer container) { + SelfTest.initClient(); + ClientCommandHandler.init(); TagConventionLogWarningClient.init(); @@ -63,13 +95,39 @@ public ClientNeoForgeMod(IEventBus modEventBus, ModContainer container) { } @SubscribeEvent - static void onRegisterGeometryLoaders(ModelEvent.RegisterGeometryLoaders event) { - event.register(ResourceLocation.fromNamespaceAndPath("neoforge", "empty"), EmptyModel.LOADER); - event.register(ResourceLocation.fromNamespaceAndPath("neoforge", "obj"), ObjLoader.INSTANCE); - event.register(ResourceLocation.fromNamespaceAndPath("neoforge", "fluid_container"), DynamicFluidContainerModel.Loader.INSTANCE); - event.register(ResourceLocation.fromNamespaceAndPath("neoforge", "composite"), CompositeModel.Loader.INSTANCE); - event.register(ResourceLocation.fromNamespaceAndPath("neoforge", "item_layers"), ItemLayerModel.Loader.INSTANCE); - event.register(ResourceLocation.fromNamespaceAndPath("neoforge", "separate_transforms"), SeparateTransformsModel.Loader.INSTANCE); + static void onGatherData(GatherDataEvent.Client event) { + // We perform client and server datagen in a single clientData run to avoid + // having to juggle two generated resources folders and two runs for no additional benefit. + + event.createProvider(output -> new PackMetadataGenerator(output) + .add(PackMetadataSection.TYPE, new PackMetadataSection( + Component.translatable("pack.neoforge.description"), + DetectedVersion.BUILT_IN.getPackVersion(PackType.SERVER_DATA), + Optional.of(new InclusiveRange<>(0, Integer.MAX_VALUE))))); + + event.createProvider(NeoForgeAdvancementProvider::new); + event.createBlockAndItemTags(NeoForgeBlockTagsProvider::new, NeoForgeItemTagsProvider::new); + event.createProvider(NeoForgeEntityTypeTagsProvider::new); + event.createProvider(NeoForgeFluidTagsProvider::new); + event.createProvider(NeoForgeEnchantmentTagsProvider::new); + event.createProvider(NeoForgeRecipeProvider.Runner::new); + event.createProvider(NeoForgeLootTableProvider::new); + event.createProvider(NeoForgeBiomeTagsProvider::new); + event.createProvider(NeoForgeStructureTagsProvider::new); + event.createProvider(NeoForgeDamageTypeTagsProvider::new); + event.createProvider(NeoForgeRegistryOrderReportProvider::new); + event.createProvider(NeoForgeDataMapsProvider::new); + + event.createProvider(NeoForgeSpriteSourceProvider::new); + event.createProvider(VanillaSoundDefinitionsProvider::new); + event.createProvider(NeoForgeLanguageProvider::new); + } + + @SubscribeEvent + static void onRegisterModelLoaders(ModelEvent.RegisterLoaders event) { + event.register(neoForgeId("empty"), EmptyModel.LOADER); + event.register(neoForgeId("obj"), ObjLoader.INSTANCE); + event.register(neoForgeId("composite"), UnbakedCompositeModel.Loader.INSTANCE); } @SubscribeEvent @@ -157,4 +215,24 @@ public ResourceLocation getFlowingTexture() { } }, milkType)); } + + @SubscribeEvent + static void registerItemTintSources(RegisterColorHandlersEvent.ItemTintSources event) { + event.register(neoForgeId("fluid_contents_tint"), FluidContentsTint.MAP_CODEC); + } + + @SubscribeEvent + static void registerItemModels(RegisterItemModelsEvent event) { + event.register(neoForgeId("fluid_container"), DynamicFluidContainerModel.Unbaked.MAP_CODEC); + } + + // TODO 1.21.4 +// @SubscribeEvent(priority = EventPriority.LOWEST) +// static void registerSpawnEggColors(RegisterColorHandlersEvent.Item event) { +// SpawnEggItem.eggs().forEach(egg -> { +// if (event.getItemColors().get(egg) == null) { +// event.register((stack, layer) -> ARGB.opaque(egg.getColor(layer)), egg); +// } +// }); +// } } diff --git a/src/main/java/net/neoforged/neoforge/client/ItemDecoratorHandler.java b/src/main/java/net/neoforged/neoforge/client/ItemDecoratorHandler.java index 5be3ae25..33fe41b1 100644 --- a/src/main/java/net/neoforged/neoforge/client/ItemDecoratorHandler.java +++ b/src/main/java/net/neoforged/neoforge/client/ItemDecoratorHandler.java @@ -50,6 +50,10 @@ public static ItemDecoratorHandler of(ItemStack stack) { } public void render(GuiGraphics guiGraphics, Font font, ItemStack stack, int xOffset, int yOffset) { + if (itemDecorators.isEmpty()) { + return; + } + RenderSystem.backupGlState(stateBackup); resetRenderState(); diff --git a/src/main/java/net/neoforged/neoforge/client/NeoForgeRenderTypes.java b/src/main/java/net/neoforged/neoforge/client/NeoForgeRenderTypes.java index 98988e55..bc467405 100644 --- a/src/main/java/net/neoforged/neoforge/client/NeoForgeRenderTypes.java +++ b/src/main/java/net/neoforged/neoforge/client/NeoForgeRenderTypes.java @@ -7,7 +7,6 @@ import com.mojang.blaze3d.vertex.DefaultVertexFormat; import com.mojang.blaze3d.vertex.VertexFormat; -import java.util.function.BooleanSupplier; import java.util.function.Function; import java.util.function.Supplier; import net.minecraft.Util; @@ -32,8 +31,6 @@ public enum NeoForgeRenderTypes { ITEM_UNSORTED_UNLIT_TRANSLUCENT(() -> getUnlitTranslucent(TextureAtlas.LOCATION_BLOCKS, false)), TRANSLUCENT_ON_PARTICLES_TARGET(() -> getTranslucentParticlesTarget(TextureAtlas.LOCATION_BLOCKS)); - public static boolean enableTextTextureLinearFiltering = false; - /** * @return A RenderType fit for multi-layer solid item rendering. */ @@ -94,45 +91,45 @@ public static RenderType getEntityCutoutMipped(ResourceLocation textureLocation) } /** - * @return Replacement of {@link RenderType#text(ResourceLocation)}, but with optional linear texture filtering. + * @return Replacement of {@link RenderType#text(ResourceLocation)}, but with linear texture filtering. */ - public static RenderType getText(ResourceLocation locationIn) { - return Internal.TEXT.apply(locationIn); + public static RenderType getTextFiltered(ResourceLocation locationIn) { + return Internal.TEXT_FILTERED.apply(locationIn); } /** - * @return Replacement of {@link RenderType#textIntensity(ResourceLocation)}, but with optional linear texture filtering. + * @return Replacement of {@link RenderType#textIntensity(ResourceLocation)}, but with linear texture filtering. */ - public static RenderType getTextIntensity(ResourceLocation locationIn) { - return Internal.TEXT_INTENSITY.apply(locationIn); + public static RenderType getTextIntensityFiltered(ResourceLocation locationIn) { + return Internal.TEXT_INTENSITY_FILTERED.apply(locationIn); } /** - * @return Replacement of {@link RenderType#textPolygonOffset(ResourceLocation)}, but with optional linear texture filtering. + * @return Replacement of {@link RenderType#textPolygonOffset(ResourceLocation)}, but with linear texture filtering. */ - public static RenderType getTextPolygonOffset(ResourceLocation locationIn) { - return Internal.TEXT_POLYGON_OFFSET.apply(locationIn); + public static RenderType getTextPolygonOffsetFiltered(ResourceLocation locationIn) { + return Internal.TEXT_POLYGON_OFFSET_FILTERED.apply(locationIn); } /** - * @return Replacement of {@link RenderType#textIntensityPolygonOffset(ResourceLocation)}, but with optional linear texture filtering. + * @return Replacement of {@link RenderType#textIntensityPolygonOffset(ResourceLocation)}, but with linear texture filtering. */ - public static RenderType getTextIntensityPolygonOffset(ResourceLocation locationIn) { - return Internal.TEXT_INTENSITY_POLYGON_OFFSET.apply(locationIn); + public static RenderType getTextIntensityPolygonOffsetFiltered(ResourceLocation locationIn) { + return Internal.TEXT_INTENSITY_POLYGON_OFFSET_FILTERED.apply(locationIn); } /** - * @return Replacement of {@link RenderType#textSeeThrough(ResourceLocation)}, but with optional linear texture filtering. + * @return Replacement of {@link RenderType#textSeeThrough(ResourceLocation)}, but with linear texture filtering. */ - public static RenderType getTextSeeThrough(ResourceLocation locationIn) { - return Internal.TEXT_SEETHROUGH.apply(locationIn); + public static RenderType getTextSeeThroughFiltered(ResourceLocation locationIn) { + return Internal.TEXT_SEETHROUGH_FILTERED.apply(locationIn); } /** - * @return Replacement of {@link RenderType#textIntensitySeeThrough(ResourceLocation)}, but with optional linear texture filtering. + * @return Replacement of {@link RenderType#textIntensitySeeThrough(ResourceLocation)}, but with linear texture filtering. */ - public static RenderType getTextIntensitySeeThrough(ResourceLocation locationIn) { - return Internal.TEXT_INTENSITY_SEETHROUGH.apply(locationIn); + public static RenderType getTextIntensitySeeThroughFiltered(ResourceLocation locationIn) { + return Internal.TEXT_INTENSITY_SEETHROUGH_FILTERED.apply(locationIn); } /** @@ -242,82 +239,82 @@ private static RenderType layeredItemTranslucent(ResourceLocation locationIn) { return RenderType.create("neoforge_item_entity_translucent_cull", DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, true, true, rendertype$state); } - public static Function TEXT = Util.memoize(Internal::getText); + public static Function TEXT_FILTERED = Util.memoize(Internal::getTextFiltered); - private static RenderType getText(ResourceLocation locationIn) { + private static RenderType getTextFiltered(ResourceLocation locationIn) { var rendertype$state = RenderType.CompositeState.builder() .setShaderState(RenderType.RENDERTYPE_TEXT_SHADER) - .setTextureState(new CustomizableTextureState(locationIn, () -> NeoForgeRenderTypes.enableTextTextureLinearFiltering, () -> false)) + .setTextureState(new RenderStateShard.TextureStateShard(locationIn, TriState.TRUE, false)) .setTransparencyState(RenderType.TRANSLUCENT_TRANSPARENCY) .setLightmapState(RenderType.LIGHTMAP) .createCompositeState(false); - return RenderType.create("neoforge_text", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, true, rendertype$state); + return RenderType.create("neoforge_text", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, false, rendertype$state); } - public static Function TEXT_INTENSITY = Util.memoize(Internal::getTextIntensity); + public static Function TEXT_INTENSITY_FILTERED = Util.memoize(Internal::getTextIntensityFiltered); - private static RenderType getTextIntensity(ResourceLocation locationIn) { + private static RenderType getTextIntensityFiltered(ResourceLocation locationIn) { var rendertype$state = RenderType.CompositeState.builder() .setShaderState(RenderType.RENDERTYPE_TEXT_INTENSITY_SHADER) - .setTextureState(new CustomizableTextureState(locationIn, () -> NeoForgeRenderTypes.enableTextTextureLinearFiltering, () -> false)) + .setTextureState(new RenderStateShard.TextureStateShard(locationIn, TriState.TRUE, false)) .setTransparencyState(RenderType.TRANSLUCENT_TRANSPARENCY) .setLightmapState(RenderType.LIGHTMAP) .createCompositeState(false); - return RenderType.create("neoforge_text_intensity", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, true, rendertype$state); + return RenderType.create("neoforge_text_intensity", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, false, rendertype$state); } - public static Function TEXT_POLYGON_OFFSET = Util.memoize(Internal::getTextPolygonOffset); + public static Function TEXT_POLYGON_OFFSET_FILTERED = Util.memoize(Internal::getTextPolygonOffsetFiltered); - private static RenderType getTextPolygonOffset(ResourceLocation locationIn) { + private static RenderType getTextPolygonOffsetFiltered(ResourceLocation locationIn) { var rendertype$state = RenderType.CompositeState.builder() .setShaderState(RenderType.RENDERTYPE_TEXT_SHADER) - .setTextureState(new CustomizableTextureState(locationIn, () -> NeoForgeRenderTypes.enableTextTextureLinearFiltering, () -> false)) + .setTextureState(new RenderStateShard.TextureStateShard(locationIn, TriState.TRUE, false)) .setTransparencyState(RenderType.TRANSLUCENT_TRANSPARENCY) .setLightmapState(RenderType.LIGHTMAP) .setLayeringState(RenderType.POLYGON_OFFSET_LAYERING) .createCompositeState(false); - return RenderType.create("neoforge_text_polygon_offset", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, true, rendertype$state); + return RenderType.create("neoforge_text_polygon_offset", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, false, rendertype$state); } - public static Function TEXT_INTENSITY_POLYGON_OFFSET = Util.memoize(Internal::getTextIntensityPolygonOffset); + public static Function TEXT_INTENSITY_POLYGON_OFFSET_FILTERED = Util.memoize(Internal::getTextIntensityPolygonOffsetFiltered); - private static RenderType getTextIntensityPolygonOffset(ResourceLocation locationIn) { + private static RenderType getTextIntensityPolygonOffsetFiltered(ResourceLocation locationIn) { var rendertype$state = RenderType.CompositeState.builder() .setShaderState(RenderType.RENDERTYPE_TEXT_INTENSITY_SHADER) - .setTextureState(new CustomizableTextureState(locationIn, () -> NeoForgeRenderTypes.enableTextTextureLinearFiltering, () -> false)) + .setTextureState(new RenderStateShard.TextureStateShard(locationIn, TriState.TRUE, false)) .setTransparencyState(RenderType.TRANSLUCENT_TRANSPARENCY) .setLightmapState(RenderType.LIGHTMAP) .setLayeringState(RenderType.POLYGON_OFFSET_LAYERING) .createCompositeState(false); - return RenderType.create("neoforge_text_intensity_polygon_offset", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, true, rendertype$state); + return RenderType.create("neoforge_text_intensity_polygon_offset", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, false, rendertype$state); } - public static Function TEXT_SEETHROUGH = Util.memoize(Internal::getTextSeeThrough); + public static Function TEXT_SEETHROUGH_FILTERED = Util.memoize(Internal::getTextSeeThroughFiltered); - private static RenderType getTextSeeThrough(ResourceLocation locationIn) { + private static RenderType getTextSeeThroughFiltered(ResourceLocation locationIn) { var rendertype$state = RenderType.CompositeState.builder() .setShaderState(RenderType.RENDERTYPE_TEXT_SEE_THROUGH_SHADER) - .setTextureState(new CustomizableTextureState(locationIn, () -> NeoForgeRenderTypes.enableTextTextureLinearFiltering, () -> false)) + .setTextureState(new RenderStateShard.TextureStateShard(locationIn, TriState.TRUE, false)) .setTransparencyState(RenderType.TRANSLUCENT_TRANSPARENCY) .setLightmapState(RenderType.LIGHTMAP) .setDepthTestState(RenderType.NO_DEPTH_TEST) .setWriteMaskState(RenderType.COLOR_WRITE) .createCompositeState(false); - return RenderType.create("neoforge_text_see_through", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, true, rendertype$state); + return RenderType.create("neoforge_text_see_through", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, false, rendertype$state); } - public static Function TEXT_INTENSITY_SEETHROUGH = Util.memoize(Internal::getTextIntensitySeeThrough); + public static Function TEXT_INTENSITY_SEETHROUGH_FILTERED = Util.memoize(Internal::getTextIntensitySeeThroughFiltered); - private static RenderType getTextIntensitySeeThrough(ResourceLocation locationIn) { + private static RenderType getTextIntensitySeeThroughFiltered(ResourceLocation locationIn) { var rendertype$state = RenderType.CompositeState.builder() .setShaderState(RenderType.RENDERTYPE_TEXT_INTENSITY_SEE_THROUGH_SHADER) - .setTextureState(new CustomizableTextureState(locationIn, () -> NeoForgeRenderTypes.enableTextTextureLinearFiltering, () -> false)) + .setTextureState(new RenderStateShard.TextureStateShard(locationIn, TriState.TRUE, false)) .setTransparencyState(RenderType.TRANSLUCENT_TRANSPARENCY) .setLightmapState(RenderType.LIGHTMAP) .setDepthTestState(RenderType.NO_DEPTH_TEST) .setWriteMaskState(RenderType.COLOR_WRITE) .createCompositeState(false); - return RenderType.create("neoforge_text_see_through", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, true, rendertype$state); + return RenderType.create("neoforge_text_see_through", DefaultVertexFormat.POSITION_COLOR_TEX_LIGHTMAP, VertexFormat.Mode.QUADS, 256, false, false, rendertype$state); } public static Function TRANSLUCENT_PARTICLES_TARGET = Util.memoize(Internal::getTranslucentParticlesTarget); @@ -333,23 +330,4 @@ private static RenderType getTranslucentParticlesTarget(ResourceLocation locatio return RenderType.create("neoforge_translucent_particles_target", DefaultVertexFormat.BLOCK, VertexFormat.Mode.QUADS, 2097152, true, true, rendertype$state); } } - - private static class CustomizableTextureState extends TextureStateShard { - private final BooleanSupplier blurSupplier; - private final BooleanSupplier mipmapSupplier; - - private CustomizableTextureState(ResourceLocation resLoc, BooleanSupplier blur, BooleanSupplier mipmap) { - super(resLoc, blur.getAsBoolean() ? TriState.TRUE : TriState.DEFAULT, mipmap.getAsBoolean()); - blurSupplier = blur; - mipmapSupplier = mipmap; - } - - @Override - public void setupRenderState() { - // must be done before super call as super uses the `blur` and `mipmap` fields within the `setupState` runnable | See super constructor - blur = blurSupplier.getAsBoolean() ? TriState.TRUE : TriState.DEFAULT; - mipmap = mipmapSupplier.getAsBoolean(); - super.setupRenderState(); - } - } } diff --git a/src/main/java/net/neoforged/neoforge/client/color/item/FluidContentsTint.java b/src/main/java/net/neoforged/neoforge/client/color/item/FluidContentsTint.java new file mode 100644 index 00000000..db4842d0 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/color/item/FluidContentsTint.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.color.item; + +import com.mojang.serialization.MapCodec; +import net.minecraft.client.color.item.ItemTintSource; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions; +import net.neoforged.neoforge.client.model.item.DynamicFluidContainerModel; +import net.neoforged.neoforge.fluids.FluidUtil; +import org.jetbrains.annotations.Nullable; + +/** + * Returns the tint color of the fluid contained in the item stack. + * Notably, this is used internally by {@link DynamicFluidContainerModel}. + */ +public final class FluidContentsTint implements ItemTintSource { + public static final FluidContentsTint INSTANCE = new FluidContentsTint(); + public static final MapCodec MAP_CODEC = MapCodec.unit(INSTANCE); + + private FluidContentsTint() {} + + @Override + public int calculate(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity) { + return FluidUtil.getFluidContained(stack) + .map(fluidStack -> IClientFluidTypeExtensions.of(fluidStack.getFluid()).getTintColor(fluidStack)) + .orElse(0xFFFFFFFF); + } + + @Override + public MapCodec type() { + return MAP_CODEC; + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/package-info.java b/src/main/java/net/neoforged/neoforge/client/color/item/package-info.java similarity index 86% rename from src/main/java/net/neoforged/neoforge/client/model/geometry/package-info.java rename to src/main/java/net/neoforged/neoforge/client/color/item/package-info.java index a553db45..bceffd3e 100644 --- a/src/main/java/net/neoforged/neoforge/client/model/geometry/package-info.java +++ b/src/main/java/net/neoforged/neoforge/client/color/item/package-info.java @@ -6,7 +6,7 @@ @FieldsAreNonnullByDefault @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault -package net.neoforged.neoforge.client.model.geometry; +package net.neoforged.neoforge.client.color.item; import javax.annotation.ParametersAreNonnullByDefault; import net.minecraft.FieldsAreNonnullByDefault; diff --git a/src/main/java/net/neoforged/neoforge/client/entity/animation/json/AnimationLoader.java b/src/main/java/net/neoforged/neoforge/client/entity/animation/json/AnimationLoader.java index 7d788419..4e959af3 100644 --- a/src/main/java/net/neoforged/neoforge/client/entity/animation/json/AnimationLoader.java +++ b/src/main/java/net/neoforged/neoforge/client/entity/animation/json/AnimationLoader.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Map; import net.minecraft.client.animation.AnimationDefinition; +import net.minecraft.resources.FileToIdConverter; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; @@ -31,7 +32,7 @@ public final class AnimationLoader extends SimpleJsonResourceReloadListener strongHolderReferences = new ArrayList<>(); private AnimationLoader() { - super(AnimationParser.CODEC, "neoforge/animations/entity"); + super(AnimationParser.CODEC, FileToIdConverter.json("neoforge/animations/entity")); } /** diff --git a/src/main/java/net/neoforged/neoforge/client/event/ModelEvent.java b/src/main/java/net/neoforged/neoforge/client/event/ModelEvent.java index 650217ec..a3a234a8 100644 --- a/src/main/java/net/neoforged/neoforge/client/event/ModelEvent.java +++ b/src/main/java/net/neoforged/neoforge/client/event/ModelEvent.java @@ -6,21 +6,20 @@ package net.neoforged.neoforge.client.event; import com.google.common.base.Preconditions; +import java.util.Collections; import java.util.Map; -import java.util.Set; +import java.util.function.Consumer; import java.util.function.Function; import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.Material; import net.minecraft.client.resources.model.ModelBakery; import net.minecraft.client.resources.model.ModelManager; -import net.minecraft.client.resources.model.ModelResourceLocation; import net.minecraft.resources.ResourceLocation; import net.neoforged.bus.api.Event; import net.neoforged.bus.api.ICancellableEvent; import net.neoforged.fml.LogicalSide; import net.neoforged.fml.event.IModBusEvent; -import net.neoforged.neoforge.client.model.geometry.IGeometryLoader; +import net.neoforged.neoforge.client.model.UnbakedModelLoader; import org.jetbrains.annotations.ApiStatus; /** @@ -41,27 +40,27 @@ protected ModelEvent() {} * must therefore not be accessed in this event. *

* - *

This event is not {@linkplain ICancellableEvent cancellable}, and does not {@linkplain HasResult have a result}.

+ *

This event is not {@linkplain ICancellableEvent cancellable}.

* *

This event is fired on the mod-specific event bus, only on the {@linkplain LogicalSide#CLIENT logical client}.

*/ public static class ModifyBakingResult extends ModelEvent implements IModBusEvent { - private final Map models; + private final ModelBakery.BakingResult bakingResult; private final Function textureGetter; private final ModelBakery modelBakery; @ApiStatus.Internal - public ModifyBakingResult(Map models, Function textureGetter, ModelBakery modelBakery) { - this.models = models; + public ModifyBakingResult(ModelBakery.BakingResult bakingResult, Function textureGetter, ModelBakery modelBakery) { + this.bakingResult = bakingResult; this.textureGetter = textureGetter; this.modelBakery = modelBakery; } /** - * @return the modifiable registry map of models and their model names + * @return The result of the model baking */ - public Map getModels() { - return models; + public ModelBakery.BakingResult getBakingResult() { + return bakingResult; } /** @@ -89,19 +88,25 @@ public ModelBakery getModelBakery() { * The model registry given by this event is unmodifiable. To modify the model registry, use * {@link ModelEvent.ModifyBakingResult} instead. * - *

This event is not {@linkplain ICancellableEvent cancellable}, and does not {@linkplain HasResult have a result}.

+ *

This event is not {@linkplain ICancellableEvent cancellable}.

* *

This event is fired on the mod-specific event bus, only on the {@linkplain LogicalSide#CLIENT logical client}.

*/ public static class BakingCompleted extends ModelEvent implements IModBusEvent { private final ModelManager modelManager; - private final Map models; + private final ModelBakery.BakingResult bakingResult; private final ModelBakery modelBakery; @ApiStatus.Internal - public BakingCompleted(ModelManager modelManager, Map models, ModelBakery modelBakery) { + public BakingCompleted(ModelManager modelManager, ModelBakery.BakingResult bakingResult, ModelBakery modelBakery) { this.modelManager = modelManager; - this.models = models; + this.bakingResult = new ModelBakery.BakingResult( + bakingResult.missingModel(), + Collections.unmodifiableMap(bakingResult.blockStateModels()), + bakingResult.missingItemModel(), + Collections.unmodifiableMap(bakingResult.itemStackModels()), + Collections.unmodifiableMap(bakingResult.itemProperties()), + Collections.unmodifiableMap(bakingResult.standaloneModels())); this.modelBakery = modelBakery; } @@ -113,10 +118,10 @@ public ModelManager getModelManager() { } /** - * @return an unmodifiable view of the registry map of models and their model names + * @return The result of the model baking */ - public Map getModels() { - return models; + public ModelBakery.BakingResult getBakingResult() { + return bakingResult; } /** @@ -128,58 +133,52 @@ public ModelBakery getModelBakery() { } /** - * Fired when the {@link net.minecraft.client.resources.model.ModelBakery} is notified of the resource manager reloading. - * Allows developers to register models to be loaded, along with their dependencies. Models registered through this - * event must use the {@link ModelResourceLocation#STANDALONE_VARIANT} variant. + * Fired when the {@link net.minecraft.client.resources.model.ModelDiscovery} is notified of dependency discovery of its top models. + * Allows developers to register models to be loaded, along with their dependencies. * - *

This event is not {@linkplain ICancellableEvent cancellable}, and does not {@linkplain HasResult have a result}.

+ *

This event is not {@linkplain ICancellableEvent cancellable}.

* *

This event is fired on the mod-specific event bus, only on the {@linkplain LogicalSide#CLIENT logical client}.

*/ public static class RegisterAdditional extends ModelEvent implements IModBusEvent { - private final Set models; + private final Consumer registrar; @ApiStatus.Internal - public RegisterAdditional(Set models) { - this.models = models; + public RegisterAdditional(Consumer registrar) { + this.registrar = registrar; } /** * Registers a model to be loaded, along with its dependencies. - *

- * The {@link ModelResourceLocation} passed to this method must later be used to recover the loaded model. */ - public void register(ModelResourceLocation model) { - Preconditions.checkArgument( - model.getVariant().equals(ModelResourceLocation.STANDALONE_VARIANT), - "Side-loaded models must use the '" + ModelResourceLocation.STANDALONE_VARIANT + "' variant"); - models.add(model); + public void register(ResourceLocation model) { + registrar.accept(model); } } /** - * Allows users to register their own {@link IGeometryLoader geometry loaders} for use in block/item models. + * Allows users to register their own {@link UnbakedModelLoader unbaked model loaders} for use in block/item models. * - *

This event is not {@linkplain ICancellableEvent cancellable}, and does not {@linkplain HasResult have a result}.

+ *

This event is not {@linkplain ICancellableEvent cancellable}.

* *

This event is fired on the mod-specific event bus, only on the {@linkplain LogicalSide#CLIENT logical client}.

*/ - public static class RegisterGeometryLoaders extends ModelEvent implements IModBusEvent { - private final Map> loaders; + public static class RegisterLoaders extends ModelEvent implements IModBusEvent { + private final Map> loaders; @ApiStatus.Internal - public RegisterGeometryLoaders(Map> loaders) { + public RegisterLoaders(Map> loaders) { this.loaders = loaders; } /** - * Registers a new geometry loader. - * + * Registers a new unbaked model loader. + * * @param key the ID of the loader - * @param loader the geometry loader to register + * @param loader the loader to register */ - public void register(ResourceLocation key, IGeometryLoader loader) { - Preconditions.checkArgument(!loaders.containsKey(key), "Geometry loader already registered: " + key); + public void register(ResourceLocation key, UnbakedModelLoader loader) { + Preconditions.checkArgument(!loaders.containsKey(key), "Unbaked model loader already registered: " + key); loaders.put(key, loader); } } diff --git a/src/main/java/net/neoforged/neoforge/client/event/RegisterColorHandlersEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RegisterColorHandlersEvent.java index b4210540..afa28734 100644 --- a/src/main/java/net/neoforged/neoforge/client/event/RegisterColorHandlersEvent.java +++ b/src/main/java/net/neoforged/neoforge/client/event/RegisterColorHandlersEvent.java @@ -6,13 +6,14 @@ package net.neoforged.neoforge.client.event; import com.google.common.collect.ImmutableList; +import com.mojang.serialization.MapCodec; import net.minecraft.client.color.block.BlockColor; import net.minecraft.client.color.block.BlockColors; -import net.minecraft.client.color.item.ItemColor; -import net.minecraft.client.color.item.ItemColors; +import net.minecraft.client.color.item.ItemTintSource; import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ExtraCodecs; import net.minecraft.world.level.ColorResolver; -import net.minecraft.world.level.ItemLike; import net.neoforged.bus.api.Event; import net.neoforged.bus.api.ICancellableEvent; import net.neoforged.fml.LogicalSide; @@ -26,7 +27,8 @@ *

These events are fired on the mod-specific event bus, only on the {@linkplain LogicalSide#CLIENT logical client}.

* * @see RegisterColorHandlersEvent.Block - * @see RegisterColorHandlersEvent.Item + * @see RegisterColorHandlersEvent.ItemTintSources + * @see RegisterColorHandlersEvent.ColorResolvers */ public abstract class RegisterColorHandlersEvent extends Event implements IModBusEvent { @ApiStatus.Internal @@ -35,7 +37,7 @@ protected RegisterColorHandlersEvent() {} /** * Fired for registering block color handlers. * - *

This event is not {@linkplain ICancellableEvent cancellable}, and does not {@linkplain HasResult have a result}.

+ *

This event is not {@linkplain ICancellableEvent cancellable}.

* *

This event is fired on the mod-specific event bus, only on the {@linkplain LogicalSide#CLIENT logical client}.

*/ @@ -68,67 +70,40 @@ public void register(BlockColor blockColor, net.minecraft.world.level.block.Bloc } /** - * Fired for registering item color handlers. - * - *

The block colors should only be used for referencing or delegating item colors to their respective block - * colors. Use {@link RegisterColorHandlersEvent.Block} for registering your block color handlers.

- * - *

This event is not {@linkplain ICancellableEvent cancellable}, and does not {@linkplain HasResult have a result}.

- * - *

This event is fired on the mod-specific event bus, only on the {@linkplain LogicalSide#CLIENT logical client}.

+ * Allows registration of custom {@link ColorResolver} implementations to be used with + * {@link net.minecraft.world.level.BlockAndTintGetter#getBlockTint(BlockPos, ColorResolver)}. */ - public static class Item extends RegisterColorHandlersEvent { - private final ItemColors itemColors; - private final BlockColors blockColors; + public static class ColorResolvers extends RegisterColorHandlersEvent { + private final ImmutableList.Builder builder; @ApiStatus.Internal - public Item(ItemColors itemColors, BlockColors blockColors) { - this.itemColors = itemColors; - this.blockColors = blockColors; - } - - /** - * {@return the item colors registry} - * - * @see ItemColors#register(ItemColor, ItemLike...) - */ - public ItemColors getItemColors() { - return itemColors; - } - - /** - * {@return the block colors registry} - * This should only be used for referencing or delegating item colors to their respective block colors. - */ - public BlockColors getBlockColors() { - return blockColors; + public ColorResolvers(ImmutableList.Builder builder) { + this.builder = builder; } - /** - * Registers a {@link ItemColor} instance for a set of blocks. - * - * @param itemColor The color provider - * @param items The items - */ - public void register(ItemColor itemColor, ItemLike... items) { - itemColors.register(itemColor, items); + public void register(ColorResolver resolver) { + this.builder.add(resolver); } } /** - * Allows registration of custom {@link ColorResolver} implementations to be used with - * {@link net.minecraft.world.level.BlockAndTintGetter#getBlockTint(BlockPos, ColorResolver)}. + * Fired for registering item color handlers. + *

+ * This event is fired on the mod-specific event bus, only on the {@linkplain LogicalSide#CLIENT logical client} + * + * @see ItemTintSource + * @see ItemTintSources */ - public static class ColorResolvers extends RegisterColorHandlersEvent { - private final ImmutableList.Builder builder; + public static class ItemTintSources extends RegisterColorHandlersEvent { + private final ExtraCodecs.LateBoundIdMapper> idMapper; @ApiStatus.Internal - public ColorResolvers(ImmutableList.Builder builder) { - this.builder = builder; + public ItemTintSources(ExtraCodecs.LateBoundIdMapper> idMapper) { + this.idMapper = idMapper; } - public void register(ColorResolver resolver) { - this.builder.add(resolver); + public void register(ResourceLocation location, MapCodec source) { + this.idMapper.put(location, source); } } } diff --git a/src/main/java/net/neoforged/neoforge/client/event/RegisterConditionalItemModelPropertyEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RegisterConditionalItemModelPropertyEvent.java new file mode 100644 index 00000000..8b76afe1 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/event/RegisterConditionalItemModelPropertyEvent.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.event; + +import com.mojang.serialization.MapCodec; +import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperty; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ExtraCodecs; +import net.neoforged.bus.api.Event; +import net.neoforged.fml.event.IModBusEvent; +import org.jetbrains.annotations.ApiStatus; + +/** + * Event fired when special model renderers are registered. + *

+ * This event is fired during the model registration process for conditional item model properties. + * It is used to register property codecs which can be used to create custom conditional item model properties. + *

+ * This event is fired on the mod event bus. + */ +public class RegisterConditionalItemModelPropertyEvent extends Event implements IModBusEvent { + private final ExtraCodecs.LateBoundIdMapper> idMapper; + + @ApiStatus.Internal + public RegisterConditionalItemModelPropertyEvent(ExtraCodecs.LateBoundIdMapper> idMapper) { + this.idMapper = idMapper; + } + + public void register(ResourceLocation location, MapCodec source) { + this.idMapper.put(location, source); + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/event/RegisterItemModelsEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RegisterItemModelsEvent.java new file mode 100644 index 00000000..0887e4b1 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/event/RegisterItemModelsEvent.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.event; + +import com.mojang.serialization.MapCodec; +import net.minecraft.client.renderer.item.ItemModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ExtraCodecs; +import net.neoforged.bus.api.Event; +import net.neoforged.fml.event.IModBusEvent; +import org.jetbrains.annotations.ApiStatus; + +/** + * Event fired when item models are registered. + *

+ * This event is fired during the model registration process for items. + * It is used to register custom item model codecs which can be used to create custom item models. + *

+ * This event is fired on the mod event bus. + */ +public class RegisterItemModelsEvent extends Event implements IModBusEvent { + private final ExtraCodecs.LateBoundIdMapper> idMapper; + + @ApiStatus.Internal + public RegisterItemModelsEvent(ExtraCodecs.LateBoundIdMapper> idMapper) { + this.idMapper = idMapper; + } + + public void register(ResourceLocation location, MapCodec source) { + this.idMapper.put(location, source); + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/event/RegisterMaterialAtlasesEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RegisterMaterialAtlasesEvent.java new file mode 100644 index 00000000..9d6f6c68 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/event/RegisterMaterialAtlasesEvent.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.event; + +import java.util.Map; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.TextureAtlas; +import net.minecraft.client.resources.TextureAtlasHolder; +import net.minecraft.client.resources.model.Material; +import net.minecraft.client.resources.model.ModelManager; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.bus.api.Event; +import net.neoforged.bus.api.ICancellableEvent; +import net.neoforged.fml.LogicalSide; +import net.neoforged.fml.event.IModBusEvent; +import org.jetbrains.annotations.ApiStatus; + +/** + * Fired for registering {@linkplain TextureAtlas texture atlases} that will be used with {@link Material} or + * other systems which retrieve the atlas via {@link Minecraft#getTextureAtlas(ResourceLocation)} or + * {@link ModelManager#getAtlas(ResourceLocation)}. + *

+ * If an atlas is registered via this event, then it must NOT be used through a {@link TextureAtlasHolder}. + *

+ * This event fires during startup when the {@link ModelManager} is constructed. + *

+ * This event is not {@linkplain ICancellableEvent cancellable}. + *

+ * This event is fired on the mod-specific event bus, only on the {@linkplain LogicalSide#CLIENT logical client}. + */ +public class RegisterMaterialAtlasesEvent extends Event implements IModBusEvent { + private final Map atlases; + + @ApiStatus.Internal + public RegisterMaterialAtlasesEvent(Map atlases) { + this.atlases = atlases; + } + + /** + * Register a texture atlas with the given name and info location + * + * @param atlasLocation The name of the texture atlas + * @param atlasInfoLocation The location of the atlas info JSON relative to the {@code atlases} directory + */ + public void register(ResourceLocation atlasLocation, ResourceLocation atlasInfoLocation) { + ResourceLocation oldAtlasInfoLoc = this.atlases.putIfAbsent(atlasLocation, atlasInfoLocation); + if (oldAtlasInfoLoc != null) { + throw new IllegalStateException(String.format( + "Duplicate registration of atlas: %s (old info: %s, new info: %s)", + atlasLocation, + oldAtlasInfoLoc, + atlasInfoLocation)); + } + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/event/RegisterRangeSelectItemModelPropertyEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RegisterRangeSelectItemModelPropertyEvent.java new file mode 100644 index 00000000..28a71926 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/event/RegisterRangeSelectItemModelPropertyEvent.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.event; + +import com.mojang.serialization.MapCodec; +import net.minecraft.client.renderer.item.properties.numeric.RangeSelectItemModelProperty; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ExtraCodecs; +import net.neoforged.bus.api.Event; +import net.neoforged.fml.event.IModBusEvent; +import org.jetbrains.annotations.ApiStatus; + +/** + * Event fired when special model renderers are registered. + *

+ * This event is fired during the model registration process for range select item model properties. + * It is used to register property codecs which can be used to create custom range select item model properties. + *

+ * This event is fired on the mod event bus. + */ +public class RegisterRangeSelectItemModelPropertyEvent extends Event implements IModBusEvent { + private final ExtraCodecs.LateBoundIdMapper> idMapper; + + @ApiStatus.Internal + public RegisterRangeSelectItemModelPropertyEvent(ExtraCodecs.LateBoundIdMapper> idMapper) { + this.idMapper = idMapper; + } + + public void register(ResourceLocation location, MapCodec source) { + this.idMapper.put(location, source); + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/event/RegisterSelectItemModelPropertyEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RegisterSelectItemModelPropertyEvent.java new file mode 100644 index 00000000..2db147ea --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/event/RegisterSelectItemModelPropertyEvent.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.event; + +import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperty; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ExtraCodecs; +import net.neoforged.bus.api.Event; +import net.neoforged.fml.event.IModBusEvent; +import org.jetbrains.annotations.ApiStatus; + +/** + * Event fired when item model property selectors are registered. + *

+ * This event is fired during the model registration process for item model property selectors. + * It is used to register custom selector types which can be used to create custom item model property selectors. + *

+ * This event is fired on the mod event bus. + */ +public class RegisterSelectItemModelPropertyEvent extends Event implements IModBusEvent { + private final ExtraCodecs.LateBoundIdMapper> idMapper; + + @ApiStatus.Internal + public RegisterSelectItemModelPropertyEvent(ExtraCodecs.LateBoundIdMapper> idMapper) { + this.idMapper = idMapper; + } + + public void register(ResourceLocation location, SelectItemModelProperty.Type source) { + this.idMapper.put(location, source); + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/event/RegisterSpecialModelRendererEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RegisterSpecialModelRendererEvent.java new file mode 100644 index 00000000..b521657b --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/event/RegisterSpecialModelRendererEvent.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.event; + +import com.mojang.serialization.MapCodec; +import net.minecraft.client.renderer.special.SpecialModelRenderer; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ExtraCodecs; +import net.neoforged.bus.api.Event; +import net.neoforged.fml.event.IModBusEvent; +import org.jetbrains.annotations.ApiStatus; + +/** + * Event fired when special model renderers are registered. + *

+ * This event is fired during the model registration process for special item model renderers. + * It is used to register custom special item model renderer codecs which can be used to create custom special item model renderers. + *

+ * This event is fired on the mod event bus. + */ +public class RegisterSpecialModelRendererEvent extends Event implements IModBusEvent { + private final ExtraCodecs.LateBoundIdMapper> idMapper; + + @ApiStatus.Internal + public RegisterSpecialModelRendererEvent(ExtraCodecs.LateBoundIdMapper> idMapper) { + this.idMapper = idMapper; + } + + public void register(ResourceLocation location, MapCodec source) { + this.idMapper.put(location, source); + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/event/RenderItemInFrameEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RenderItemInFrameEvent.java index 479758d4..5d1fdb41 100644 --- a/src/main/java/net/neoforged/neoforge/client/event/RenderItemInFrameEvent.java +++ b/src/main/java/net/neoforged/neoforge/client/event/RenderItemInFrameEvent.java @@ -10,7 +10,7 @@ import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.entity.ItemFrameRenderer; import net.minecraft.client.renderer.entity.state.ItemFrameRenderState; -import net.minecraft.world.item.ItemStack; +import net.minecraft.client.renderer.item.ItemStackRenderState; import net.neoforged.bus.api.Event; import net.neoforged.bus.api.ICancellableEvent; import net.neoforged.fml.LogicalSide; @@ -30,7 +30,7 @@ * @see ItemFrameRenderer */ public class RenderItemInFrameEvent extends Event implements ICancellableEvent { - private final ItemStack itemStack; + private final ItemStackRenderState itemStack; private final ItemFrameRenderState frameRenderState; private final ItemFrameRenderer renderer; private final PoseStack poseStack; @@ -40,7 +40,7 @@ public class RenderItemInFrameEvent extends Event implements ICancellableEvent { @ApiStatus.Internal public RenderItemInFrameEvent(ItemFrameRenderState frameRenderState, ItemFrameRenderer renderItemFrame, PoseStack poseStack, MultiBufferSource multiBufferSource, int packedLight) { - this.itemStack = frameRenderState.itemStack; + this.itemStack = frameRenderState.item; this.frameRenderState = frameRenderState; this.renderer = renderItemFrame; this.poseStack = poseStack; @@ -51,7 +51,7 @@ public RenderItemInFrameEvent(ItemFrameRenderState frameRenderState, ItemFrameRe /** * {@return the item stack being rendered} */ - public ItemStack getItemStack() { + public ItemStackRenderState getItemStackRenderState() { return itemStack; } diff --git a/src/main/java/net/neoforged/neoforge/client/event/RenderLivingEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RenderLivingEvent.java index 5ecbefe3..d705fb15 100644 --- a/src/main/java/net/neoforged/neoforge/client/event/RenderLivingEvent.java +++ b/src/main/java/net/neoforged/neoforge/client/event/RenderLivingEvent.java @@ -22,9 +22,6 @@ * Fired when a {@link LivingEntity} is rendered. * See the two subclasses to listen for before and after rendering. * - *

Despite this event's use of generic type parameters, this is not a {@link net.neoforged.bus.api.GenericEvent}, - * and should not be treated as such (such as using generic-specific listeners, which may cause a {@link ClassCastException}).

- * * @param the living entity that is being rendered * @param the model for the living entity * @see RenderLivingEvent.Pre @@ -99,11 +96,11 @@ public int getPackedLight() { * Fired before an entity is rendered. * This can be used to render additional effects or suppress rendering. * - *

This event is {@linkplain ICancellableEvent cancelable}, and does not {@linkplain HasResult have a result}. + *

This event is {@linkplain ICancellableEvent cancelable}. * If this event is cancelled, then the entity will not be rendered and the corresponding * {@link RenderLivingEvent.Post} will not be fired.

* - *

This event is fired on the {@linkplain NeoForge#EVENT_BUS main Forge event bus}, + *

This event is fired on the {@linkplain NeoForge#EVENT_BUS main game event bus}, * only on the {@linkplain LogicalSide#CLIENT logical client}.

* * @param the living entity that is being rendered @@ -119,9 +116,9 @@ public Pre(S renderState, LivingEntityRenderer renderer, float partialT /** * Fired after an entity is rendered, if the corresponding {@link RenderLivingEvent.Post} is not cancelled. * - *

This event is not {@linkplain ICancellableEvent cancelable}, and does not {@linkplain HasResult have a result}.

+ *

This event is not {@linkplain ICancellableEvent cancelable}.

* - *

This event is fired on the {@linkplain NeoForge#EVENT_BUS main Forge event bus}, + *

This event is fired on the {@linkplain NeoForge#EVENT_BUS main game event bus}, * only on the {@linkplain LogicalSide#CLIENT logical client}.

* * @param the living entity that was rendered diff --git a/src/main/java/net/neoforged/neoforge/client/event/RenderNameTagEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RenderNameTagEvent.java index 6e6c501f..a33fc96f 100644 --- a/src/main/java/net/neoforged/neoforge/client/event/RenderNameTagEvent.java +++ b/src/main/java/net/neoforged/neoforge/client/event/RenderNameTagEvent.java @@ -16,6 +16,7 @@ import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.common.util.TriState; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; /** * This event is fired before an entity renderer renders the nameplate of an entity. @@ -26,14 +27,12 @@ */ public abstract class RenderNameTagEvent extends Event { private final EntityRenderState renderState; - protected final Component originalContent; private final EntityRenderer entityRenderer; private final float partialTick; @ApiStatus.Internal - public RenderNameTagEvent(EntityRenderState renderState, Component content, EntityRenderer entityRenderer, float partialTick) { + public RenderNameTagEvent(EntityRenderState renderState, EntityRenderer entityRenderer, float partialTick) { this.renderState = renderState; - this.originalContent = content; this.entityRenderer = entityRenderer; this.partialTick = partialTick; } @@ -69,12 +68,16 @@ public float getPartialTick() { */ public static class CanRender extends RenderNameTagEvent { private final Entity entity; + @Nullable + private final Component originalContent; + @Nullable private Component content; private TriState canRender = TriState.DEFAULT; - public CanRender(Entity entity, EntityRenderState renderState, Component content, EntityRenderer entityRenderer, float partialTick) { - super(renderState, content, entityRenderer, partialTick); + public CanRender(Entity entity, EntityRenderState renderState, @Nullable Component content, EntityRenderer entityRenderer, float partialTick) { + super(renderState, entityRenderer, partialTick); this.entity = entity; + this.originalContent = content; this.content = content; } @@ -88,6 +91,7 @@ public Entity getEntity() { /** * {@return the original text on the nameplate} */ + @Nullable public Component getOriginalContent() { return this.originalContent; } @@ -121,6 +125,7 @@ public void setContent(Component contents) { /** * {@return the text on the nameplate that will be rendered} */ + @Nullable public Component getContent() { return this.content; } @@ -137,12 +142,14 @@ public Component getContent() { * @see EntityRenderer */ public static class DoRender extends RenderNameTagEvent implements ICancellableEvent { + private final Component content; private final PoseStack poseStack; private final MultiBufferSource multiBufferSource; private final int packedLight; public DoRender(EntityRenderState renderState, Component content, EntityRenderer entityRenderer, PoseStack poseStack, MultiBufferSource multiBufferSource, int packedLight, float partialTick) { - super(renderState, content, entityRenderer, partialTick); + super(renderState, entityRenderer, partialTick); + this.content = content; this.poseStack = poseStack; this.multiBufferSource = multiBufferSource; this.packedLight = packedLight; @@ -152,7 +159,7 @@ public DoRender(EntityRenderState renderState, Component content, EntityRenderer * {@return the text on the nameplate} */ public Component getContent() { - return this.originalContent; + return this.content; } /** diff --git a/src/main/java/net/neoforged/neoforge/client/event/RenderPlayerEvent.java b/src/main/java/net/neoforged/neoforge/client/event/RenderPlayerEvent.java index b2fd3652..ff811395 100644 --- a/src/main/java/net/neoforged/neoforge/client/event/RenderPlayerEvent.java +++ b/src/main/java/net/neoforged/neoforge/client/event/RenderPlayerEvent.java @@ -11,7 +11,6 @@ import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.entity.player.PlayerRenderer; import net.minecraft.client.renderer.entity.state.PlayerRenderState; -import net.neoforged.bus.api.Event; import net.neoforged.bus.api.ICancellableEvent; import net.neoforged.fml.LogicalSide; import net.neoforged.neoforge.common.NeoForge; @@ -31,15 +30,20 @@ protected RenderPlayerEvent(PlayerRenderState renderState, PlayerRenderer render super(renderState, renderer, partialTick, poseStack, multiBufferSource, packedLight); } + @Override + public PlayerRenderer getRenderer() { + return (PlayerRenderer) super.getRenderer(); + } + /** * Fired before the player is rendered. * This can be used for rendering additional effects or suppressing rendering. * - *

This event is {@linkplain ICancellableEvent cancellable}, and does not {@linkplain Event.HasResult have a result}. + *

This event is {@linkplain ICancellableEvent cancellable}. * If this event is cancelled, then the player will not be rendered and the corresponding * {@link RenderPlayerEvent.Post} will not be fired.

* - *

This event is fired on the {@linkplain NeoForge#EVENT_BUS main Forge event bus}, + *

This event is fired on the {@linkplain NeoForge#EVENT_BUS main game event bus}, * only on the {@linkplain LogicalSide#CLIENT logical client}.

*/ public static class Pre extends RenderPlayerEvent implements ICancellableEvent { @@ -52,9 +56,9 @@ public Pre(PlayerRenderState renderState, PlayerRenderer renderer, float partial /** * Fired after the player is rendered, if the corresponding {@link RenderPlayerEvent.Pre} is not cancelled. * - *

This event is not {@linkplain ICancellableEvent cancellable}, and does not {@linkplain Event.HasResult have a result}.

+ *

This event is not {@linkplain ICancellableEvent cancellable}.

* - *

This event is fired on the {@linkplain NeoForge#EVENT_BUS main Forge event bus}, + *

This event is fired on the {@linkplain NeoForge#EVENT_BUS main game event bus}, * only on the {@linkplain LogicalSide#CLIENT logical client}.

*/ public static class Post extends RenderPlayerEvent { diff --git a/src/main/java/net/neoforged/neoforge/client/event/SelectMusicEvent.java b/src/main/java/net/neoforged/neoforge/client/event/SelectMusicEvent.java index e0d20a71..5a4004a7 100644 --- a/src/main/java/net/neoforged/neoforge/client/event/SelectMusicEvent.java +++ b/src/main/java/net/neoforged/neoforge/client/event/SelectMusicEvent.java @@ -6,6 +6,7 @@ package net.neoforged.neoforge.client.event; import net.minecraft.client.resources.sounds.SoundInstance; +import net.minecraft.client.sounds.MusicInfo; import net.minecraft.sounds.Music; import net.neoforged.bus.api.Event; import net.neoforged.bus.api.ICancellableEvent; @@ -25,7 +26,7 @@ *
* Higher priorities would likely be better suited for biome-based or dimension-based musics, whereas lower priority is likely good for specific structures or situations.
*
- * This event is {@linkplain ICancellableEvent cancellable}, and does not {@linkplain HasResult have a result}.
+ * This event is {@linkplain ICancellableEvent cancellable}.
* If the event is canceled, then whatever the latest music set was will be used as the music. *
* This event is fired on the {@linkplain NeoForge#EVENT_BUS main Forge event bus},
@@ -33,11 +34,11 @@ * */ public class SelectMusicEvent extends Event implements ICancellableEvent { - private @Nullable Music music; - private final Music originalMusic; + private @Nullable MusicInfo music; + private final MusicInfo originalMusic; private final @Nullable SoundInstance playingMusic; - public SelectMusicEvent(Music music, @Nullable SoundInstance playingMusic) { + public SelectMusicEvent(MusicInfo music, @Nullable SoundInstance playingMusic) { this.music = music; this.originalMusic = music; this.playingMusic = playingMusic; @@ -46,7 +47,7 @@ public SelectMusicEvent(Music music, @Nullable SoundInstance playingMusic) { /** * {@return the original situational music that was selected} */ - public Music getOriginalMusic() { + public MusicInfo getOriginalMusic() { return originalMusic; } @@ -62,7 +63,7 @@ public SoundInstance getPlayingMusic() { * {@return the Music to be played, or {@code null} if any playing music should be cancelled} */ @Nullable - public Music getMusic() { + public MusicInfo getMusic() { return music; } @@ -71,7 +72,7 @@ public Music getMusic() { * If this was {@code null} but on the next tick isn't, the music given will be immediately played.
*
*/ - public void setMusic(@Nullable Music newMusic) { + public void setMusic(@Nullable MusicInfo newMusic) { this.music = newMusic; } @@ -79,7 +80,7 @@ public void setMusic(@Nullable Music newMusic) { * Sets the music and then cancels the event so that other listeners will not be invoked.
* Note that listeners using {@link SubscribeEvent#receiveCanceled()} will still be able to override this, but by default they will not */ - public void overrideMusic(@Nullable Music newMusic) { + public void overrideMusic(@Nullable MusicInfo newMusic) { this.music = newMusic; this.setCanceled(true); } diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/IBakedModelExtension.java b/src/main/java/net/neoforged/neoforge/client/extensions/IBakedModelExtension.java index eee572c5..25674945 100644 --- a/src/main/java/net/neoforged/neoforge/client/extensions/IBakedModelExtension.java +++ b/src/main/java/net/neoforged/neoforge/client/extensions/IBakedModelExtension.java @@ -11,6 +11,7 @@ import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.item.ItemModel; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.resources.model.BakedModel; import net.minecraft.core.BlockPos; @@ -86,7 +87,7 @@ default ChunkRenderTypeSet getRenderTypes(BlockState state, RandomSource rand, M } /** - * Gets an ordered list of {@link RenderType render types} to use when drawing this item. + * Gets the {@link RenderType render type} to use when drawing this item. * All render types using the {@link com.mojang.blaze3d.vertex.DefaultVertexFormat#NEW_ENTITY} format are supported. *

* This method will only be called on the models returned by {@link #getRenderPasses(ItemStack)}. @@ -95,18 +96,20 @@ default ChunkRenderTypeSet getRenderTypes(BlockState state, RandomSource rand, M * * @see #getRenderPasses(ItemStack) */ - default List getRenderTypes(ItemStack itemStack) { - return List.of(RenderTypeHelper.getFallbackItemRenderType(itemStack, self())); + default RenderType getRenderType(ItemStack itemStack) { + return RenderTypeHelper.getFallbackItemRenderType(itemStack, self()); } /** * Gets an ordered list of baked models used to render this model as an item. - * Each of those models' render types will be queried via {@link #getRenderTypes(ItemStack)}. + * Each of those models' render type will be queried via {@link #getRenderType(ItemStack)}. *

* By default, returns the model itself. * - * @see #getRenderTypes(ItemStack) + * @see #getRenderType(ItemStack) + * @deprecated Please migrate to {@link ItemModel}s, or if this is not possible contact us at NeoForge. */ + @Deprecated(forRemoval = true, since = "1.21.4") default List getRenderPasses(ItemStack itemStack) { return List.of(self()); } diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/IDimensionSpecialEffectsExtension.java b/src/main/java/net/neoforged/neoforge/client/extensions/IDimensionSpecialEffectsExtension.java index 9268c200..f6391f93 100644 --- a/src/main/java/net/neoforged/neoforge/client/extensions/IDimensionSpecialEffectsExtension.java +++ b/src/main/java/net/neoforged/neoforge/client/extensions/IDimensionSpecialEffectsExtension.java @@ -8,7 +8,6 @@ import net.minecraft.client.Camera; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.renderer.DimensionSpecialEffects; -import net.minecraft.client.renderer.LightTexture; import org.joml.Matrix4f; /** @@ -42,7 +41,7 @@ default boolean renderSky(ClientLevel level, int ticks, float partialTick, Matri * * @return true to prevent vanilla snow and rain rendering */ - default boolean renderSnowAndRain(ClientLevel level, int ticks, float partialTick, LightTexture lightTexture, double camX, double camY, double camZ) { + default boolean renderSnowAndRain(ClientLevel level, int ticks, float partialTick, double camX, double camY, double camZ) { return false; } diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/IGuiGraphicsExtension.java b/src/main/java/net/neoforged/neoforge/client/extensions/IGuiGraphicsExtension.java index d6b1da00..3d4d365b 100644 --- a/src/main/java/net/neoforged/neoforge/client/extensions/IGuiGraphicsExtension.java +++ b/src/main/java/net/neoforged/neoforge/client/extensions/IGuiGraphicsExtension.java @@ -143,7 +143,7 @@ default void blitInscribed(ResourceLocation texture, int x, int y, int boundsWid if (centerX) x += (w - boundsWidth) / 2; } - self().blit(RenderType::guiTextured, texture, x, y, boundsWidth, boundsHeight, 0, 0, rectWidth, rectHeight, rectWidth, rectHeight); + self().blit(RenderType::guiTextured, texture, x, y, 0, 0, boundsWidth, boundsHeight, rectWidth, rectHeight, rectWidth, rectHeight); } // TODO: 1.20.2: do we need to fix these or can we just remove them? diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/IModelBakerExtension.java b/src/main/java/net/neoforged/neoforge/client/extensions/IModelBakerExtension.java index 90d84c39..c0ed814b 100644 --- a/src/main/java/net/neoforged/neoforge/client/extensions/IModelBakerExtension.java +++ b/src/main/java/net/neoforged/neoforge/client/extensions/IModelBakerExtension.java @@ -5,23 +5,40 @@ package net.neoforged.neoforge.client.extensions; -import java.util.function.Function; +import net.minecraft.client.renderer.block.model.TextureSlots; import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.Material; -import net.minecraft.client.resources.model.ModelResourceLocation; -import net.minecraft.client.resources.model.ModelState; +import net.minecraft.client.resources.model.ModelBaker; import net.minecraft.client.resources.model.UnbakedModel; import net.minecraft.resources.ResourceLocation; import org.jetbrains.annotations.Nullable; +/** + * An extension to {@link ModelBaker} that allows for custom model baking. + */ public interface IModelBakerExtension { - @Nullable - UnbakedModel getTopLevelModel(ModelResourceLocation location); + default ModelBaker self() { + return (ModelBaker) this; + } - BakedModel bake(ResourceLocation location, ModelState state, Function sprites); - - BakedModel bakeUncached(UnbakedModel model, ModelState state, Function sprites); + /** + * Gets the unbaked model for the given location. + * + * @param location The location of the model + * @return The unbaked model, or null if not found + */ + @Nullable + UnbakedModel getModel(ResourceLocation location); - Function getModelTextureGetter(); + /** + * Finds a sprite for the given slot name. + * + * @param slots The texture slots + * @param slotName The name of the slot + * @return The sprite, or a missing reference sprite if not found + */ + default TextureAtlasSprite findSprite(TextureSlots slots, String slotName) { + Material material = slots.getMaterial(slotName); + return material != null ? self().sprites().get(material) : self().sprites().reportMissingReference(slotName); + } } diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/IRenderStateExtension.java b/src/main/java/net/neoforged/neoforge/client/extensions/IRenderStateExtension.java new file mode 100644 index 00000000..1170a443 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/extensions/IRenderStateExtension.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.extensions; + +import net.minecraft.util.context.ContextKey; +import net.neoforged.neoforge.client.renderstate.BaseRenderState; +import org.jetbrains.annotations.Nullable; + +/** + * Extension class for render state objects. Implemented by {@link BaseRenderState} for + * simple class extension. + */ +public interface IRenderStateExtension { + /** + * Gets the object associated with the given key. + * + * @param key Static key reference object + * @return The object associated with the key or null if the key is not present. + * @param Type of render data + */ + @Nullable + T getRenderData(ContextKey key); + + /** + * Sets the object associated with the given key. Key should be stored statically for later retrieval of the object. + * + * @param key Static key reference object + * @param data Object to store for custom rendering + * @param Type of render data + */ + void setRenderData(ContextKey key, @Nullable T data); + + /** + * Gets the value or throws an exception. Should be used in cases where the data must be present. + * + * @param key Static key reference object + * @return The data associate with the key + * @param Type of render data + */ + default T getRenderDataOrThrow(ContextKey key) { + T data = getRenderData(key); + if (data == null) { + throw new IllegalStateException("No value associated for key " + key); + } + return data; + } + + /** + * Gets the value or returns the default object if an object is not present + * + * @param key Static key reference object + * @param defaultVal Default value if an object is not present + * @return Value from the render data or the given default value if value is not present + * @param Type of render data + */ + default T getRenderDataOrDefault(ContextKey key, T defaultVal) { + T data = getRenderData(key); + if (data == null) { + return defaultVal; + } + return data; + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/IUnbakedModelExtension.java b/src/main/java/net/neoforged/neoforge/client/extensions/IUnbakedModelExtension.java new file mode 100644 index 00000000..0d528dd6 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/extensions/IUnbakedModelExtension.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.extensions; + +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.block.model.TextureSlots; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ModelState; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.util.context.ContextKeySet; +import net.minecraft.util.context.ContextMap; +import net.neoforged.neoforge.client.model.ExtendedUnbakedModel; +import net.neoforged.neoforge.client.model.NeoForgeModelProperties; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +/** + * Extension type for the {@link UnbakedModel} interface. + */ +public interface IUnbakedModelExtension { + private UnbakedModel self() { + return (UnbakedModel) this; + } + + /** + * {@code bake} override with additional context. + * Consider inheriting from {@link ExtendedUnbakedModel} which overrides the vanilla {@code bake} method. + * + * @param additionalProperties additional properties provided by NeoForge or mods + */ + default BakedModel bake(TextureSlots textures, ModelBaker baker, ModelState modelState, boolean useAmbientOcclusion, boolean usesBlockLight, ItemTransforms itemTransforms, ContextMap additionalProperties) { + return self().bake(textures, baker, modelState, useAmbientOcclusion, usesBlockLight, itemTransforms); + } + + /** + * Appends additional properties for this model to the builder. + * + *

This method will already have been called on the parent models. + * It can modify the properties added by a parent model and/or add its own. + * This ensures that the properties are merged across the model parent-child chain. + * + *

The context map containing all the properties will be passed as the last parameter to + * {@link #bake(TextureSlots, ModelBaker, ModelState, boolean, boolean, ItemTransforms, ContextMap)}. + * + * @see NeoForgeModelProperties + */ + @ApiStatus.OverrideOnly + default void fillAdditionalProperties(ContextMap.Builder propertiesBuilder) {} + + /** + * Resolves additional properties by walking the model child-parent chain and calling {@link #fillAdditionalProperties(ContextMap.Builder)}. + */ + static ContextMap getTopAdditionalProperties(UnbakedModel topModel) { + var builder = new ContextMap.Builder(); + fillAdditionalProperties(topModel, builder); + return builder.create(ContextKeySet.EMPTY); + } + + private static void fillAdditionalProperties(@Nullable UnbakedModel model, ContextMap.Builder propertiesBuilder) { + if (model != null) { + fillAdditionalProperties(model.getParent(), propertiesBuilder); + model.fillAdditionalProperties(propertiesBuilder); + } + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/common/IClientFluidTypeExtensions.java b/src/main/java/net/neoforged/neoforge/client/extensions/common/IClientFluidTypeExtensions.java index d9df521c..f53cfe25 100644 --- a/src/main/java/net/neoforged/neoforge/client/extensions/common/IClientFluidTypeExtensions.java +++ b/src/main/java/net/neoforged/neoforge/client/extensions/common/IClientFluidTypeExtensions.java @@ -13,6 +13,7 @@ import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.renderer.FogParameters; import net.minecraft.client.renderer.FogRenderer; +import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.ScreenEffectRenderer; import net.minecraft.core.BlockPos; import net.minecraft.resources.ResourceLocation; @@ -150,10 +151,10 @@ default ResourceLocation getRenderOverlayTexture(Minecraft mc) { * @param mc the client instance * @param poseStack the transformations representing the current rendering position */ - default void renderOverlay(Minecraft mc, PoseStack poseStack) { + default void renderOverlay(Minecraft mc, PoseStack poseStack, MultiBufferSource buffers) { ResourceLocation texture = this.getRenderOverlayTexture(mc); if (texture != null) - ScreenEffectRenderer.renderFluid(mc, poseStack, texture); + ScreenEffectRenderer.renderFluid(mc, poseStack, buffers, texture); } /** diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/common/IClientItemExtensions.java b/src/main/java/net/neoforged/neoforge/client/extensions/common/IClientItemExtensions.java index 34018888..71032b91 100644 --- a/src/main/java/net/neoforged/neoforge/client/extensions/common/IClientItemExtensions.java +++ b/src/main/java/net/neoforged/neoforge/client/extensions/common/IClientItemExtensions.java @@ -6,14 +6,14 @@ package net.neoforged.neoforge.client.extensions.common; import com.mojang.blaze3d.vertex.PoseStack; -import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.client.model.HumanoidModel; import net.minecraft.client.model.Model; import net.minecraft.client.player.LocalPlayer; -import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer; import net.minecraft.client.renderer.entity.layers.EquipmentLayerRenderer; -import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.EquipmentClientInfo; +import net.minecraft.core.component.DataComponents; +import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.ItemTags; import net.minecraft.util.ARGB; import net.minecraft.world.InteractionHand; @@ -24,8 +24,6 @@ import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.component.DyedItemColor; -import net.minecraft.world.item.equipment.EquipmentModel; -import net.minecraft.world.level.block.state.BlockState; import net.neoforged.fml.LogicalSide; import net.neoforged.neoforge.client.IArmPoseTransformer; import org.jetbrains.annotations.Nullable; @@ -96,15 +94,15 @@ default boolean applyForgeHandTransform(PoseStack poseStack, LocalPlayer player, * @param layerType The slot the item is in * @param original The original armor model. Will have attributes set. * @return A HumanoidModel to be rendered. Relevant properties are to be copied over by the caller. - * @see #getGenericArmorModel(ItemStack, EquipmentModel.LayerType, Model) + * @see #getGenericArmorModel(ItemStack, EquipmentClientInfo.LayerType, Model) */ - default Model getHumanoidArmorModel(ItemStack itemStack, EquipmentModel.LayerType layerType, Model original) { + default Model getHumanoidArmorModel(ItemStack itemStack, EquipmentClientInfo.LayerType layerType, Model original) { return original; } /** * Queries the armor model for this item when it's equipped. Useful in place of - * {@link #getHumanoidArmorModel(ItemStack, EquipmentModel.LayerType, Model)} for wrapping the original + * {@link #getHumanoidArmorModel(ItemStack, EquipmentClientInfo.LayerType, Model)} for wrapping the original * model or returning anything non-standard. *

* If you override this method you are responsible for copying any properties you care about from the original model. @@ -113,9 +111,9 @@ default Model getHumanoidArmorModel(ItemStack itemStack, EquipmentModel.LayerTyp * @param layerType The slot the item is in * @param original The original armor model. Will have attributes set. * @return A Model to be rendered. Relevant properties must be copied over manually. - * @see #getHumanoidArmorModel(ItemStack, EquipmentModel.LayerType, Model) + * @see #getHumanoidArmorModel(ItemStack, EquipmentClientInfo.LayerType, Model) */ - default Model getGenericArmorModel(ItemStack itemStack, EquipmentModel.LayerType layerType, Model original) { + default Model getGenericArmorModel(ItemStack itemStack, EquipmentClientInfo.LayerType layerType, Model original) { Model replacement = getHumanoidArmorModel(itemStack, layerType, original); if (replacement != original) { // FIXME: equipment rendering deals with a plain Model now @@ -155,18 +153,6 @@ default void setupModelAnimations(LivingEntity livingEntity, ItemStack itemStack */ default void renderHelmetOverlay(ItemStack stack, Player player, int width, int height, float partialTick) {} - /** - * Queries this item's renderer. - *

- * Only used if {@link BakedModel#isCustomRenderer()} returns {@code true} or {@link BlockState#getRenderShape()} - * returns {@link net.minecraft.world.level.block.RenderShape#ENTITYBLOCK_ANIMATED}. - *

- * By default, returns vanilla's block entity renderer. - */ - default BlockEntityWithoutLevelRenderer getCustomRenderer() { - return Minecraft.getInstance().getItemRenderer().getBlockEntityRenderer(); - } - /** * {@return Whether the item should bob when rendered in the world as an entity} * @@ -201,13 +187,13 @@ default boolean shouldSpreadAsEntity(ItemStack stack) { * performance * @return a custom color for the layer, in ARGB format, or 0 to skip rendering */ - default int getArmorLayerTintColor(ItemStack stack, EquipmentModel.Layer layer, int layerIdx, int fallbackColor) { + default int getArmorLayerTintColor(ItemStack stack, EquipmentClientInfo.Layer layer, int layerIdx, int fallbackColor) { return EquipmentLayerRenderer.getColorForLayer(layer, fallbackColor); } /** * Called once per render pass of equipped armor items, regardless of the number of layers; the return value of this - * method is passed to {@link #getArmorLayerTintColor(ItemStack, EquipmentModel.Layer, int, int)} as + * method is passed to {@link #getArmorLayerTintColor(ItemStack, EquipmentClientInfo.Layer, int, int)} as * the {@code fallbackColor} parameter. *

* You can override this method for your custom armor item to provide an alternative default color for the item when @@ -220,6 +206,24 @@ default int getDefaultDyeColor(ItemStack stack) { return stack.is(ItemTags.DYEABLE) ? ARGB.opaque(DyedItemColor.getOrDefault(stack, 0)) : 0; } + /** + * Called by RenderBiped and RenderPlayer to determine the armor texture that + * should be used for the currently equipped item. This will be called on + * stacks with the {@link DataComponents#EQUIPPABLE} component. + * + * Returning null from this function will use the default value. + * + * @param stack ItemStack for the equipped armor + * @param type The layer type of the armor + * @param layer The armor layer + * @param _default The default texture determined by the equipment renderer + * @return Path of texture to bind, or null to use default + */ + @Nullable + default ResourceLocation getArmorTexture(ItemStack stack, EquipmentClientInfo.LayerType type, EquipmentClientInfo.Layer layer, ResourceLocation _default) { + return null; + } + enum FontContext { /** * Used to display the amount of items in the {@link ItemStack}. diff --git a/src/main/java/net/neoforged/neoforge/client/gui/ConfigurationScreen.java b/src/main/java/net/neoforged/neoforge/client/gui/ConfigurationScreen.java index becd2d4c..3ad5d25f 100644 --- a/src/main/java/net/neoforged/neoforge/client/gui/ConfigurationScreen.java +++ b/src/main/java/net/neoforged/neoforge/client/gui/ConfigurationScreen.java @@ -11,6 +11,7 @@ import com.electronwill.nightconfig.core.UnmodifiableConfig; import com.electronwill.nightconfig.core.UnmodifiableConfig.Entry; import com.google.common.collect.ImmutableList; +import com.mojang.datafixers.util.Function4; import com.mojang.realmsclient.RealmsMainScreen; import com.mojang.serialization.Codec; import it.unimi.dsi.fastutil.booleans.BooleanConsumer; @@ -53,8 +54,6 @@ import net.minecraft.client.gui.screens.options.OptionsSubScreen; import net.minecraft.client.multiplayer.ServerData; import net.minecraft.client.resources.language.I18n; -import net.minecraft.data.models.blockstates.PropertyDispatch.QuadFunction; -import net.minecraft.data.models.blockstates.PropertyDispatch.TriFunction; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; @@ -87,9 +86,9 @@ *

  • As an entry point for your custom configuration screen that handles fetching your configs, matching {@link Type} to the current game, enforcing level and game restarts, etc. *
  • As a ready-made system but extensible that works out of the box with all configs that use the {@link ModConfigSpec} system and don't do anything overly weird with it. * - * For the former one, use the 3-argument constructor {@link #ConfigurationScreen(ModContainer, Screen, TriFunction)} and return your own screen from the TriFunction. For the latter, + * For the former one, use the 3-argument constructor {@link #ConfigurationScreen(ModContainer, Screen, Function4)} and return your own screen from the Function4. For the latter, * use either the 2-argument constructor {@link #ConfigurationScreen(ModContainer, Screen)} if you don't need to extend the system, or the 3-argument one and return a subclass of - * {@link ConfigurationSectionScreen} from the TriFunction.

    + * {@link ConfigurationSectionScreen} from the Function4.

    * * In any case, register your configuration screen in your client mod class like this: * @@ -260,7 +259,7 @@ public void finish() { protected static final TranslationChecker translationChecker = new TranslationChecker(); protected final ModContainer mod; - private final QuadFunction sectionScreen; + private final Function4 sectionScreen; public RestartType needsRestart = RestartType.NONE; // If there is only one config type (and it can be edited, we show that instantly on the way "down" and want to close on the way "up". @@ -275,8 +274,7 @@ public ConfigurationScreen(final ModContainer mod, final Screen parent, Configur this(mod, parent, (a, b, c, d) -> new ConfigurationSectionScreen(a, b, c, d, filter)); } - @SuppressWarnings("resource") - public ConfigurationScreen(final ModContainer mod, final Screen parent, QuadFunction sectionScreen) { + public ConfigurationScreen(final ModContainer mod, final Screen parent, Function4 sectionScreen) { super(parent, Minecraft.getInstance().options, Component.translatable(translationChecker.check(mod.getModId() + ".configuration.title", LANG_PREFIX + "title"), mod.getModInfo().getDisplayName())); this.mod = mod; this.sectionScreen = sectionScreen; @@ -1389,6 +1387,16 @@ protected void checkButtons() { protected void updateWidgetNarration(final NarrationElementOutput pNarrationElementOutput) { // TODO I have no idea. Help? } + + @Override + protected int contentHeight() { + return 0; // TODO 1.21.4 no idea + } + + @Override + protected double scrollRate() { + return 4.0; // TODO 1.21.4 no idea + } } } diff --git a/src/main/java/net/neoforged/neoforge/client/gui/LoadingErrorScreen.java b/src/main/java/net/neoforged/neoforge/client/gui/LoadingErrorScreen.java index 77cf1328..741c3025 100644 --- a/src/main/java/net/neoforged/neoforge/client/gui/LoadingErrorScreen.java +++ b/src/main/java/net/neoforged/neoforge/client/gui/LoadingErrorScreen.java @@ -115,7 +115,7 @@ public static class LoadingEntryList extends ObjectSelectionList logoData = selectedMod.getLogoFile().map(logoFile -> { - TextureManager tm = this.minecraft.getTextureManager(); - final Pack.ResourcesSupplier resourcePack = ResourcePackLoader.getPackFor(selectedMod.getModId()) - .orElse(ResourcePackLoader.getPackFor("neoforge").orElseThrow(() -> new RuntimeException("Can't find neoforge, WHAT!"))); - try (PackResources packResources = resourcePack.openPrimary(new PackLocationInfo("mod/" + selectedMod.getModId(), Component.empty(), PackSource.BUILT_IN, Optional.empty()))) { - NativeImage logo = null; - IoSupplier logoResource = packResources.getRootResource(logoFile.split("[/\\\\]")); - if (logoResource != null) - logo = NativeImage.read(logoResource.get()); - if (logo != null) { - - return Pair.of(tm.register("modlogo", new DynamicTexture(logo) { - @Override - public void upload() { - this.bind(); - NativeImage td = this.getPixels(); - // Use custom "blur" value which controls texture filtering (nearest-neighbor vs linear) - this.getPixels().upload(0, 0, 0, 0, 0, td.getWidth(), td.getHeight(), selectedMod.getLogoBlur(), false, false, false); - } - }), new Size2i(logo.getWidth(), logo.getHeight())); - } - } catch (IOException | IllegalArgumentException e) {} - return Pair.of(null, new Size2i(0, 0)); - }).orElse(Pair.of(null, new Size2i(0, 0))); + Pair logoData; + + if (selectedMod.getModId().equals(ResourceLocation.DEFAULT_NAMESPACE)) { + logoData = Pair.of(LogoRenderer.MINECRAFT_LOGO, new Size2i(LogoRenderer.LOGO_TEXTURE_WIDTH, LogoRenderer.LOGO_TEXTURE_HEIGHT)); + } else { + logoData = selectedMod.getLogoFile().map(logoFile -> { + TextureManager tm = this.minecraft.getTextureManager(); + final Pack.ResourcesSupplier resourcePack = ResourcePackLoader.getPackFor(selectedMod.getModId()) + .orElse(ResourcePackLoader.getPackFor("neoforge").orElseThrow(() -> new RuntimeException("Can't find neoforge, WHAT!"))); + try (PackResources packResources = resourcePack.openPrimary(new PackLocationInfo("mod/" + selectedMod.getModId(), Component.empty(), PackSource.BUILT_IN, Optional.empty()))) { + NativeImage logo = null; + IoSupplier logoResource = packResources.getRootResource(logoFile.split("[/\\\\]")); + if (logoResource != null) + logo = NativeImage.read(logoResource.get()); + if (logo != null) { + var textureId = ResourceLocation.fromNamespaceAndPath("neoforge", "modlogo"); + tm.register(textureId, new DynamicTexture(logo) { + @Override + public void upload() { + this.bind(); + NativeImage td = this.getPixels(); + // Use custom "blur" value which controls texture filtering (nearest-neighbor vs linear) + // TODO 1.21.4 restore selectedMod.getLogoBlur() check + this.getPixels().upload(0, 0, 0, 0, 0, td.getWidth(), td.getHeight(), false); + } + }); + + return Pair.of(textureId, new Size2i(logo.getWidth(), logo.getHeight())); + } + } catch (IOException | IllegalArgumentException e) {} + return Pair.of(null, new Size2i(0, 0)); + }).orElse(Pair.of(null, new Size2i(0, 0))); + } lines.add(selectedMod.getDisplayName()); lines.add(FMLTranslations.parseMessage("fml.menu.mods.info.version", MavenVersionTranslator.artifactVersionToString(selectedMod.getVersion()))); @@ -416,7 +426,7 @@ public void upload() { lines.add(FMLTranslations.parseMessage("fml.menu.mods.info.updateavailable", vercheck.url() == null ? "" : vercheck.url()).replace("\r\n", "\n")); lines.add(FMLTranslations.parseMessage("fml.menu.mods.info.license", selectedMod.getOwningFile().getLicense()).replace("\r\n", "\n")); lines.add(null); - lines.add(FMLTranslations.parseMessageWithFallback("fml.menu.mods.info.description." + selectedMod.getModId(), selectedMod::getDescription)); + lines.add(FMLTranslations.getPattern("fml.menu.mods.info.description." + selectedMod.getModId(), selectedMod::getDescription)); /* Removed because people bitched that this information was misleading. lines.add(null); diff --git a/src/main/java/net/neoforged/neoforge/client/gui/ScrollableExperimentsScreen.java b/src/main/java/net/neoforged/neoforge/client/gui/ScrollableExperimentsScreen.java new file mode 100644 index 00000000..fb3cbe29 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/gui/ScrollableExperimentsScreen.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.gui; + +import java.util.List; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.ContainerObjectSelectionList; +import net.minecraft.client.gui.components.CycleButton; +import net.minecraft.client.gui.components.MultiLineTextWidget; +import net.minecraft.client.gui.components.StringWidget; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.layouts.LayoutSettings; +import net.minecraft.client.gui.layouts.LinearLayout; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.worldselection.ExperimentsScreen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.server.packs.repository.PackRepository; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +@ApiStatus.Internal +public class ScrollableExperimentsScreen extends ExperimentsScreen { + private static final int DEFAULT_LIST_HEIGHT = 121; + private static final int ROW_PADDING = 4; + private static final int LIST_PADDING = ROW_PADDING + 34; + private static final int ENTRY_HEIGHT = 42; + + @Nullable + private ExperimentSelectionList selectionList; + @Nullable + private LinearLayout listLayout; + + public ScrollableExperimentsScreen(Screen parent, PackRepository packRepository, Consumer output) { + super(parent, packRepository, output); + } + + @Override + protected void init() { + this.layout.addTitleHeader(TITLE, this.font); + + LinearLayout contentLayout = this.layout.addToContents(LinearLayout.vertical(), LayoutSettings::alignVerticallyTop); + contentLayout.addChild( + new MultiLineTextWidget(INFO, this.font).setMaxWidth(MAIN_CONTENT_WIDTH), + settings -> settings.paddingBottom(15).alignHorizontallyCenter()); + + this.listLayout = contentLayout.addChild(LinearLayout.vertical()); + this.selectionList = new ExperimentSelectionList(this.minecraft); + this.packs.forEach((pack, selected) -> selectionList.addEntry(new ExperimentSelectionList.ExperimentEntry( + getHumanReadableTitle(pack), + () -> this.packs.getBoolean(pack), + flag -> this.packs.put(pack, flag.booleanValue()), + pack.getDescription()))); + this.listLayout.addChild(selectionList); + + LinearLayout footerLayout = this.layout.addToFooter(LinearLayout.horizontal().spacing(8)); + footerLayout.addChild(Button.builder(CommonComponents.GUI_DONE, btn -> this.onDone()).build()); + footerLayout.addChild(Button.builder(CommonComponents.GUI_CANCEL, btn -> this.onClose()).build()); + + this.layout.visitWidgets(this::addRenderableWidget); + this.repositionElements(); + } + + @Override + protected void repositionElements() { + if (this.selectionList != null) { + // Reset list height to empirical default because layouts can't squish elements to fit... + this.selectionList.setHeight(DEFAULT_LIST_HEIGHT); + } + super.repositionElements(); + if (this.selectionList != null && this.listLayout != null) { + this.selectionList.setHeight(this.layout.getContentHeight() - this.listLayout.getY()); + this.selectionList.setPosition(this.listLayout.getX(), this.listLayout.getY()); + this.selectionList.refreshScrollAmount(); + } + } + + private static class ExperimentSelectionList extends ContainerObjectSelectionList { + public ExperimentSelectionList(Minecraft mc) { + super(mc, ExperimentsScreen.MAIN_CONTENT_WIDTH + LIST_PADDING, DEFAULT_LIST_HEIGHT, 0, ENTRY_HEIGHT); + } + + @Override + public int getRowWidth() { + return ExperimentsScreen.MAIN_CONTENT_WIDTH + ROW_PADDING; + } + + @Override + protected int addEntry(ExperimentEntry entry) { + return super.addEntry(entry); + } + + private static class ExperimentEntry extends ContainerObjectSelectionList.Entry { + private static final int BUTTON_WIDTH = 44; + private static final int TITLE_Y = 6; + private static final int DESCRIPTION_Y = 20; + + private final StringWidget titleWidget; + private final MultiLineTextWidget descriptionWidget; + private final CycleButton button; + private final List children; + + public ExperimentEntry(Component title, BooleanSupplier selectedSupplier, Consumer selectedSetter, Component description) { + this.titleWidget = new StringWidget(title, Minecraft.getInstance().font).alignLeft(); + this.descriptionWidget = new MultiLineTextWidget(description.copy().withStyle(ChatFormatting.GRAY), Minecraft.getInstance().font) + .setMaxRows(2); + this.button = CycleButton.onOffBuilder(selectedSupplier.getAsBoolean()) + .displayOnlyValue() + .withCustomNarration(btn -> CommonComponents.joinForNarration(title, btn.createDefaultNarrationMessage(), description)) + .create(0, 0, BUTTON_WIDTH, Button.DEFAULT_HEIGHT, Component.empty(), (btn, val) -> selectedSetter.accept(val)); + this.children = List.of(titleWidget, descriptionWidget, this.button); + } + + @Override + public void render(GuiGraphics graphics, int entryIdx, int top, int left, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float partialTick) { + this.titleWidget.setPosition(left, top + TITLE_Y); + this.descriptionWidget.setPosition(left, top + DESCRIPTION_Y); + this.descriptionWidget.setMaxWidth(entryWidth - this.button.getWidth()); + this.button.setPosition(left + entryWidth - this.button.getWidth() - ROW_PADDING, top); + + this.titleWidget.render(graphics, mouseX, mouseY, partialTick); + this.descriptionWidget.render(graphics, mouseX, mouseY, partialTick); + this.button.render(graphics, mouseX, mouseY, partialTick); + } + + @Override + public List children() { + return this.children; + } + + @Override + public List narratables() { + return this.children; + } + } + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/gui/widget/ModListWidget.java b/src/main/java/net/neoforged/neoforge/client/gui/widget/ModListWidget.java index cd6626c3..831ac723 100644 --- a/src/main/java/net/neoforged/neoforge/client/gui/widget/ModListWidget.java +++ b/src/main/java/net/neoforged/neoforge/client/gui/widget/ModListWidget.java @@ -40,7 +40,7 @@ public ModListWidget(ModListScreen parent, int listWidth, int top, int bottom) { } @Override - protected int getScrollbarPosition() { + protected int scrollBarX() { return this.listWidth; } diff --git a/src/main/java/net/neoforged/neoforge/client/model/AbstractSimpleUnbakedModel.java b/src/main/java/net/neoforged/neoforge/client/model/AbstractSimpleUnbakedModel.java new file mode 100644 index 00000000..a00f3502 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/model/AbstractSimpleUnbakedModel.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.model; + +import net.minecraft.client.data.models.model.TextureSlot; +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.block.model.TextureSlots; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ModelState; +import net.minecraft.client.resources.model.SimpleBakedModel; +import net.minecraft.util.context.ContextMap; +import net.neoforged.neoforge.client.RenderTypeGroup; + +/** + * @deprecated Extend {@link ExtendedUnbakedModel} directly instead, and use {@link SimpleBakedModel.Builder} if appropriate. + */ +@Deprecated(forRemoval = true, since = "1.21.4") +public abstract class AbstractSimpleUnbakedModel implements ExtendedUnbakedModel { + @Override + public BakedModel bake(TextureSlots slots, + ModelBaker baker, + ModelState state, + boolean useAmbientOcclusion, + boolean usesBlockLight, + ItemTransforms transforms, + ContextMap additionalProperties) { + TextureAtlasSprite particle = baker.findSprite(slots, TextureSlot.PARTICLE.getId()); + var renderTypes = additionalProperties.getOrDefault(NeoForgeModelProperties.RENDER_TYPE, RenderTypeGroup.EMPTY); + + IModelBuilder builder = IModelBuilder.of(useAmbientOcclusion, usesBlockLight, isGui3d(), + transforms, particle, renderTypes); + + addQuads(builder, slots, baker, state, useAmbientOcclusion, usesBlockLight, transforms); + + return builder.build(); + } + + @Override + public void resolveDependencies(Resolver p_387087_) { + //Has no dependencies + } + + public abstract void addQuads( + IModelBuilder builder, + TextureSlots slots, + ModelBaker baker, + ModelState state, + boolean useAmbientOcclusion, + boolean usesBlockLight, + ItemTransforms transforms); + + protected boolean isGui3d() { + return true; + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/model/BakedModelWrapper.java b/src/main/java/net/neoforged/neoforge/client/model/BakedModelWrapper.java deleted file mode 100644 index 01417638..00000000 --- a/src/main/java/net/neoforged/neoforge/client/model/BakedModelWrapper.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.client.model; - -import com.mojang.blaze3d.vertex.PoseStack; -import java.util.List; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.block.model.BakedOverrides; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.renderer.block.model.ItemTransforms; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.util.RandomSource; -import net.minecraft.world.item.ItemDisplayContext; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.BlockAndTintGetter; -import net.minecraft.world.level.block.state.BlockState; -import net.neoforged.neoforge.client.ChunkRenderTypeSet; -import net.neoforged.neoforge.client.model.data.ModelData; -import net.neoforged.neoforge.common.util.TriState; -import org.jetbrains.annotations.Nullable; - -/** - * Wrapper for {@link BakedModel} which delegates all operations to its parent. - *

    - * Useful for creating wrapper baked models which only override certain properties. - */ -public abstract class BakedModelWrapper implements BakedModel { - protected final T originalModel; - - public BakedModelWrapper(T originalModel) { - this.originalModel = originalModel; - } - - @Override - public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand) { - return originalModel.getQuads(state, side, rand); - } - - @Override - public boolean useAmbientOcclusion() { - return originalModel.useAmbientOcclusion(); - } - - @Override - public TriState useAmbientOcclusion(BlockState state, ModelData data, RenderType renderType) { - return originalModel.useAmbientOcclusion(state, data, renderType); - } - - @Override - public boolean isGui3d() { - return originalModel.isGui3d(); - } - - @Override - public boolean usesBlockLight() { - return originalModel.usesBlockLight(); - } - - @Override - public boolean isCustomRenderer() { - return originalModel.isCustomRenderer(); - } - - @Override - public TextureAtlasSprite getParticleIcon() { - return originalModel.getParticleIcon(); - } - - @Override - public ItemTransforms getTransforms() { - return originalModel.getTransforms(); - } - - @Override - public BakedOverrides overrides() { - return originalModel.overrides(); - } - - @Override - public BakedModel applyTransform(ItemDisplayContext cameraTransformType, PoseStack poseStack, boolean applyLeftHandTransform) { - return originalModel.applyTransform(cameraTransformType, poseStack, applyLeftHandTransform); - } - - @Override - public TextureAtlasSprite getParticleIcon(ModelData data) { - return originalModel.getParticleIcon(data); - } - - @Override - public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand, ModelData extraData, @Nullable RenderType renderType) { - return originalModel.getQuads(state, side, rand, extraData, renderType); - } - - @Override - public ModelData getModelData(BlockAndTintGetter level, BlockPos pos, BlockState state, ModelData modelData) { - return originalModel.getModelData(level, pos, state, modelData); - } - - @Override - public ChunkRenderTypeSet getRenderTypes(BlockState state, RandomSource rand, ModelData data) { - return originalModel.getRenderTypes(state, rand, data); - } - - @Override - public List getRenderTypes(ItemStack itemStack) { - return originalModel.getRenderTypes(itemStack); - } - - @Override - public List getRenderPasses(ItemStack itemStack) { - return originalModel.getRenderPasses(itemStack); - } -} diff --git a/src/main/java/net/neoforged/neoforge/client/model/DelegateUnbakedModel.java b/src/main/java/net/neoforged/neoforge/client/model/DelegateUnbakedModel.java new file mode 100644 index 00000000..1d61e20c --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/model/DelegateUnbakedModel.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.model; + +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.block.model.TextureSlots; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ModelState; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.util.context.ContextMap; +import org.jetbrains.annotations.Nullable; + +public abstract class DelegateUnbakedModel implements ExtendedUnbakedModel { + protected final UnbakedModel wrapped; + + protected DelegateUnbakedModel(UnbakedModel wrapped) { + this.wrapped = wrapped; + } + + @Override + public BakedModel bake(TextureSlots textures, ModelBaker baker, ModelState modelState, boolean useAmbientOcclusion, boolean usesBlockLight, ItemTransforms itemTransforms, ContextMap additionalProperties) { + return this.wrapped.bake(textures, baker, modelState, useAmbientOcclusion, usesBlockLight, itemTransforms, additionalProperties); + } + + @Override + public void resolveDependencies(Resolver resolver) { + this.wrapped.resolveDependencies(resolver); + } + + @Nullable + @Override + public Boolean getAmbientOcclusion() { + return this.wrapped.getAmbientOcclusion(); + } + + @Nullable + @Override + public GuiLight getGuiLight() { + return this.wrapped.getGuiLight(); + } + + @Nullable + @Override + public ItemTransforms getTransforms() { + return this.wrapped.getTransforms(); + } + + @Override + public TextureSlots.Data getTextureSlots() { + return this.wrapped.getTextureSlots(); + } + + @Nullable + @Override + public UnbakedModel getParent() { + return this.wrapped.getParent(); + } + + @Override + public void fillAdditionalProperties(ContextMap.Builder propertiesBuilder) { + this.wrapped.fillAdditionalProperties(propertiesBuilder); + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/model/DynamicFluidContainerModel.java b/src/main/java/net/neoforged/neoforge/client/model/DynamicFluidContainerModel.java deleted file mode 100644 index b15c779a..00000000 --- a/src/main/java/net/neoforged/neoforge/client/model/DynamicFluidContainerModel.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.client.model; - -import com.google.common.collect.Maps; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonObject; -import com.mojang.math.Transformation; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import net.minecraft.client.color.item.ItemColor; -import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.block.model.BakedOverrides; -import net.minecraft.client.renderer.block.model.ItemOverride; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.BlockModelRotation; -import net.minecraft.client.resources.model.ItemModel; -import net.minecraft.client.resources.model.Material; -import net.minecraft.client.resources.model.ModelBaker; -import net.minecraft.client.resources.model.ModelState; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.GsonHelper; -import net.minecraft.world.entity.LivingEntity; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.material.Fluid; -import net.minecraft.world.level.material.Fluids; -import net.neoforged.neoforge.client.ClientHooks; -import net.neoforged.neoforge.client.NeoForgeRenderTypes; -import net.neoforged.neoforge.client.RenderTypeGroup; -import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions; -import net.neoforged.neoforge.client.model.geometry.IGeometryBakingContext; -import net.neoforged.neoforge.client.model.geometry.IGeometryLoader; -import net.neoforged.neoforge.client.model.geometry.IUnbakedGeometry; -import net.neoforged.neoforge.client.model.geometry.StandaloneGeometryBakingContext; -import net.neoforged.neoforge.client.model.geometry.UnbakedGeometryHelper; -import net.neoforged.neoforge.fluids.FluidUtil; -import org.jetbrains.annotations.Nullable; -import org.joml.Quaternionf; -import org.joml.Vector3f; - -/** - * A dynamic fluid container model, capable of re-texturing itself at runtime to match the contained fluid. - *

    - * Composed of a base layer, a fluid layer (applied with a mask) and a cover layer (optionally applied with a mask). - * The entire model may optionally be flipped if the fluid is gaseous, and the fluid layer may glow if light-emitting. - *

    - * Fluid tinting requires registering a separate {@link ItemColor}. An implementation is provided in {@link Colors}. - * - * @see Colors - */ -public class DynamicFluidContainerModel implements IUnbakedGeometry { - // Depth offsets to prevent Z-fighting - private static final Transformation FLUID_TRANSFORM = new Transformation(new Vector3f(), new Quaternionf(), new Vector3f(1, 1, 1.002f), new Quaternionf()); - private static final Transformation COVER_TRANSFORM = new Transformation(new Vector3f(), new Quaternionf(), new Vector3f(1, 1, 1.004f), new Quaternionf()); - - private final Fluid fluid; - private final boolean flipGas; - private final boolean coverIsMask; - private final boolean applyFluidLuminosity; - - private DynamicFluidContainerModel(Fluid fluid, boolean flipGas, boolean coverIsMask, boolean applyFluidLuminosity) { - this.fluid = fluid; - this.flipGas = flipGas; - this.coverIsMask = coverIsMask; - this.applyFluidLuminosity = applyFluidLuminosity; - } - - public static RenderTypeGroup getLayerRenderTypes(boolean unlit) { - return new RenderTypeGroup(RenderType.translucent(), unlit ? NeoForgeRenderTypes.ITEM_UNSORTED_UNLIT_TRANSLUCENT.get() : NeoForgeRenderTypes.ITEM_UNSORTED_TRANSLUCENT.get()); - } - - /** - * Returns a new ModelDynBucket representing the given fluid, but with the same - * other properties (flipGas, tint, coverIsMask). - */ - public DynamicFluidContainerModel withFluid(Fluid newFluid) { - return new DynamicFluidContainerModel(newFluid, flipGas, coverIsMask, applyFluidLuminosity); - } - - @Override - public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides) { - Material particleLocation = context.hasMaterial("particle") ? context.getMaterial("particle") : null; - Material baseLocation = context.hasMaterial("base") ? context.getMaterial("base") : null; - Material fluidMaskLocation = context.hasMaterial("fluid") ? context.getMaterial("fluid") : null; - Material coverLocation = context.hasMaterial("cover") ? context.getMaterial("cover") : null; - - TextureAtlasSprite baseSprite = baseLocation != null ? spriteGetter.apply(baseLocation) : null; - TextureAtlasSprite fluidSprite = fluid != Fluids.EMPTY ? spriteGetter.apply(ClientHooks.getBlockMaterial(IClientFluidTypeExtensions.of(fluid).getStillTexture())) : null; - TextureAtlasSprite coverSprite = (coverLocation != null && (!coverIsMask || baseLocation != null)) ? spriteGetter.apply(coverLocation) : null; - - TextureAtlasSprite particleSprite = particleLocation != null ? spriteGetter.apply(particleLocation) : null; - - if (particleSprite == null) particleSprite = fluidSprite; - if (particleSprite == null) particleSprite = baseSprite; - if (particleSprite == null && !coverIsMask) particleSprite = coverSprite; - - // If the fluid is lighter than air, rotate 180deg to turn it upside down - if (flipGas && fluid != Fluids.EMPTY && fluid.getFluidType().isLighterThanAir()) { - modelState = new SimpleModelState( - modelState.getRotation().compose( - new Transformation(null, new Quaternionf(0, 0, 1, 0), null, null))); - } - - // We need to disable GUI 3D and block lighting for this to render properly - var itemContext = StandaloneGeometryBakingContext.builder(context).withGui3d(false).withUseBlockLight(false).build(ResourceLocation.fromNamespaceAndPath("neoforge", "dynamic_fluid_container")); - var modelBuilder = CompositeModel.Baked.builder(itemContext, particleSprite, context.getTransforms()); - - var normalRenderTypes = getLayerRenderTypes(false); - - if (baseLocation != null && baseSprite != null) { - // Base texture - var unbaked = UnbakedGeometryHelper.createUnbakedItemElements(0, baseSprite); - var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> baseSprite, modelState); - modelBuilder.addQuads(normalRenderTypes, quads); - } - - if (fluidMaskLocation != null && fluidSprite != null) { - TextureAtlasSprite templateSprite = spriteGetter.apply(fluidMaskLocation); - if (templateSprite != null) { - // Fluid layer - var transformedState = new SimpleModelState(modelState.getRotation().compose(FLUID_TRANSFORM), modelState.isUvLocked()); - var unbaked = UnbakedGeometryHelper.createUnbakedItemMaskElements(1, templateSprite); // Use template as mask - var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> fluidSprite, transformedState); // Bake with fluid texture - - var emissive = applyFluidLuminosity && fluid.getFluidType().getLightLevel() > 0; - var renderTypes = getLayerRenderTypes(emissive); - if (emissive) QuadTransformers.settingMaxEmissivity().processInPlace(quads); - - modelBuilder.addQuads(renderTypes, quads); - } - } - - if (coverSprite != null) { - var sprite = coverIsMask ? baseSprite : coverSprite; - if (sprite != null) { - // Cover/overlay - var transformedState = new SimpleModelState(modelState.getRotation().compose(COVER_TRANSFORM), modelState.isUvLocked()); - var unbaked = UnbakedGeometryHelper.createUnbakedItemMaskElements(2, coverSprite); // Use cover as mask - var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> sprite, transformedState); // Bake with selected texture - modelBuilder.addQuads(normalRenderTypes, quads); - } - } - - modelBuilder.setParticle(particleSprite); - - BakedModel bakedModel = modelBuilder.build(); - var bakedOverrides = new ContainedFluidOverrideHandler(new BakedOverrides(baker, overrides, spriteGetter), bakedModel, baker, itemContext, this); - return new ItemModel.BakedModelWithOverrides(bakedModel, bakedOverrides); - } - - public static final class Loader implements IGeometryLoader { - public static final Loader INSTANCE = new Loader(); - - private Loader() {} - - @Override - public DynamicFluidContainerModel read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) { - if (!jsonObject.has("fluid")) - throw new RuntimeException("Bucket model requires 'fluid' value."); - - ResourceLocation fluidName = ResourceLocation.parse(jsonObject.get("fluid").getAsString()); - - Fluid fluid = BuiltInRegistries.FLUID.getValue(fluidName); - - boolean flip = GsonHelper.getAsBoolean(jsonObject, "flip_gas", false); - boolean coverIsMask = GsonHelper.getAsBoolean(jsonObject, "cover_is_mask", true); - boolean applyFluidLuminosity = GsonHelper.getAsBoolean(jsonObject, "apply_fluid_luminosity", true); - - // create new model with correct liquid - return new DynamicFluidContainerModel(fluid, flip, coverIsMask, applyFluidLuminosity); - } - } - - private static final class ContainedFluidOverrideHandler extends BakedOverrides { - private final Map cache = Maps.newHashMap(); // contains all the baked models since they'll never change - private final BakedOverrides nested; - private final BakedModel baseModel; - private final ModelBaker baker; - private final IGeometryBakingContext owner; - private final DynamicFluidContainerModel parent; - - private ContainedFluidOverrideHandler(BakedOverrides nested, BakedModel baseModel, ModelBaker baker, IGeometryBakingContext owner, DynamicFluidContainerModel parent) { - this.nested = nested; - this.baseModel = baseModel; - this.baker = baker; - this.owner = owner; - this.parent = parent; - } - - @Override - @Nullable - public BakedModel findOverride(ItemStack stack, @Nullable ClientLevel level, @Nullable LivingEntity entity, int seed) { - BakedModel overridden = nested.findOverride(stack, level, entity, seed); - if (overridden != null) return overridden; - - return FluidUtil.getFluidContained(stack) - .map(fluidStack -> { - Fluid fluid = fluidStack.getFluid(); - String name = BuiltInRegistries.FLUID.getKey(fluid).toString(); - - if (!cache.containsKey(name)) { - DynamicFluidContainerModel unbaked = this.parent.withFluid(fluid); - BakedModel bakedModel = unbaked.bake(owner, baker, Material::sprite, BlockModelRotation.X0_Y0, List.of()); - cache.put(name, bakedModel); - return bakedModel; - } - - return cache.get(name); - }) - // not a fluid item apparently - .orElse(baseModel); // empty bucket - } - } - - public static class Colors implements ItemColor { - @Override - public int getColor(ItemStack stack, int tintIndex) { - if (tintIndex != 1) return 0xFFFFFFFF; - return FluidUtil.getFluidContained(stack) - .map(fluidStack -> IClientFluidTypeExtensions.of(fluidStack.getFluid()).getTintColor(fluidStack)) - .orElse(0xFFFFFFFF); - } - } -} diff --git a/src/main/java/net/neoforged/neoforge/client/model/EmptyModel.java b/src/main/java/net/neoforged/neoforge/client/model/EmptyModel.java index f3e1084e..b5128352 100644 --- a/src/main/java/net/neoforged/neoforge/client/model/EmptyModel.java +++ b/src/main/java/net/neoforged/neoforge/client/model/EmptyModel.java @@ -5,13 +5,17 @@ package net.neoforged.neoforge.client.model; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import java.lang.reflect.Type; import java.util.EnumMap; import java.util.List; import java.util.Map; -import java.util.function.Function; import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.renderer.block.model.ItemOverride; import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.block.model.TextureSlots; import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite; import net.minecraft.client.renderer.texture.TextureAtlas; import net.minecraft.client.renderer.texture.TextureAtlasSprite; @@ -20,34 +24,34 @@ import net.minecraft.client.resources.model.ModelBaker; import net.minecraft.client.resources.model.ModelState; import net.minecraft.client.resources.model.SimpleBakedModel; +import net.minecraft.client.resources.model.UnbakedModel; import net.minecraft.core.Direction; import net.neoforged.neoforge.client.RenderTypeGroup; -import net.neoforged.neoforge.client.model.geometry.IGeometryBakingContext; -import net.neoforged.neoforge.client.model.geometry.IGeometryLoader; -import net.neoforged.neoforge.client.model.geometry.IUnbakedGeometry; -import net.neoforged.neoforge.client.model.geometry.SimpleUnbakedGeometry; import net.neoforged.neoforge.client.textures.UnitTextureAtlasSprite; /** * A completely empty model with no quads or texture dependencies. *

    - * You can access it as a {@link BakedModel}, an {@link IUnbakedGeometry} or an {@link IGeometryLoader}. + * You can access it as a {@link BakedModel}. */ -public class EmptyModel extends SimpleUnbakedGeometry { +public class EmptyModel implements UnbakedModel, JsonDeserializer { public static final BakedModel BAKED = new Baked(); public static final EmptyModel INSTANCE = new EmptyModel(); - public static final IGeometryLoader LOADER = (json, ctx) -> INSTANCE; + public static final UnbakedModelLoader LOADER = (object, context) -> INSTANCE; private EmptyModel() {} @Override - protected void addQuads(IGeometryBakingContext owner, IModelBuilder modelBuilder, ModelBaker baker, Function spriteGetter, ModelState modelTransform) { - // NO-OP + public BakedModel bake(TextureSlots p_386641_, ModelBaker p_250133_, ModelState p_119536_, boolean p_387129_, boolean p_388638_, ItemTransforms p_386911_) { + return BAKED; } @Override - public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides) { - return BAKED; + public void resolveDependencies(Resolver p_387087_) {} + + @Override + public EmptyModel deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + return INSTANCE; } private static class Baked extends SimpleBakedModel { diff --git a/src/main/java/net/neoforged/neoforge/client/model/ExtendedBlockModelDeserializer.java b/src/main/java/net/neoforged/neoforge/client/model/ExtendedBlockModelDeserializer.java deleted file mode 100644 index db0e1957..00000000 --- a/src/main/java/net/neoforged/neoforge/client/model/ExtendedBlockModelDeserializer.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.client.model; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import com.mojang.math.Transformation; -import java.lang.reflect.Type; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import net.minecraft.client.renderer.block.model.BlockElement; -import net.minecraft.client.renderer.block.model.BlockElementFace; -import net.minecraft.client.renderer.block.model.BlockFaceUV; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.renderer.block.model.ItemOverride; -import net.minecraft.client.renderer.block.model.ItemTransform; -import net.minecraft.client.renderer.block.model.ItemTransforms; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.GsonHelper; -import net.neoforged.neoforge.client.model.geometry.GeometryLoaderManager; -import net.neoforged.neoforge.client.model.geometry.IUnbakedGeometry; -import net.neoforged.neoforge.common.util.TransformationHelper; -import org.jetbrains.annotations.Nullable; - -/** - * A version of {@link BlockModel.Deserializer} capable of deserializing models with custom loaders, as well as other - * changes introduced to the spec by Forge. - */ -public class ExtendedBlockModelDeserializer extends BlockModel.Deserializer { - public static final Gson INSTANCE = (new GsonBuilder()) - .registerTypeAdapter(BlockModel.class, new ExtendedBlockModelDeserializer()) - .registerTypeAdapter(BlockElement.class, new BlockElement.Deserializer()) - .registerTypeAdapter(BlockElementFace.class, new BlockElementFace.Deserializer()) - .registerTypeAdapter(BlockFaceUV.class, new BlockFaceUV.Deserializer()) - .registerTypeAdapter(ItemTransform.class, new ItemTransform.Deserializer()) - .registerTypeAdapter(ItemTransforms.class, new ItemTransforms.Deserializer()) - .registerTypeAdapter(ItemOverride.class, new ItemOverride.Deserializer()) - .registerTypeAdapter(Transformation.class, new TransformationHelper.Deserializer()) - .create(); - - @Override - public BlockModel deserialize(JsonElement element, Type targetType, JsonDeserializationContext deserializationContext) throws JsonParseException { - BlockModel model = super.deserialize(element, targetType, deserializationContext); - JsonObject jsonobject = element.getAsJsonObject(); - IUnbakedGeometry geometry = deserializeGeometry(deserializationContext, jsonobject); - - List elements = model.getElements(); - if (geometry != null) { - elements.clear(); - model.customData.setCustomGeometry(geometry); - } - - if (jsonobject.has("transform")) { - JsonElement transform = jsonobject.get("transform"); - model.customData.setRootTransform(deserializationContext.deserialize(transform, Transformation.class)); - } - - if (jsonobject.has("render_type")) { - var renderTypeHintName = GsonHelper.getAsString(jsonobject, "render_type"); - model.customData.setRenderTypeHint(ResourceLocation.parse(renderTypeHintName)); - } - - if (jsonobject.has("visibility")) { - JsonObject visibility = GsonHelper.getAsJsonObject(jsonobject, "visibility"); - for (Map.Entry part : visibility.entrySet()) { - model.customData.visibilityData.setVisibilityState(part.getKey(), part.getValue().getAsBoolean()); - } - } - - return model; - } - - @Nullable - public static IUnbakedGeometry deserializeGeometry(JsonDeserializationContext deserializationContext, JsonObject object) throws JsonParseException { - if (!object.has("loader")) - return null; - - ResourceLocation name; - boolean optional; - if (object.get("loader").isJsonObject()) { - JsonObject loaderObj = object.getAsJsonObject("loader"); - name = ResourceLocation.parse(GsonHelper.getAsString(loaderObj, "id")); - optional = GsonHelper.getAsBoolean(loaderObj, "optional", false); - } else { - name = ResourceLocation.parse(GsonHelper.getAsString(object, "loader")); - optional = false; - } - - var loader = GeometryLoaderManager.get(name); - if (loader == null) { - if (optional) { - return null; - } - throw new JsonParseException(String.format(Locale.ENGLISH, "Model loader '%s' not found. Registered loaders: %s", name, GeometryLoaderManager.getLoaderList())); - } - - return loader.read(object, deserializationContext); - } -} diff --git a/src/main/java/net/neoforged/neoforge/client/model/ExtendedUnbakedModel.java b/src/main/java/net/neoforged/neoforge/client/model/ExtendedUnbakedModel.java new file mode 100644 index 00000000..7c981e8f --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/model/ExtendedUnbakedModel.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.model; + +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.block.model.TextureSlots; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.client.resources.model.ModelBaker; +import net.minecraft.client.resources.model.ModelState; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.util.context.ContextMap; + +/** + * Base interface for unbaked models that wish to support the NeoForge-added {@code bake} method + * that receives {@linkplain #fillAdditionalProperties(ContextMap.Builder) additional properties}. + */ +public interface ExtendedUnbakedModel extends UnbakedModel { + @Deprecated + @Override + default BakedModel bake(TextureSlots textures, ModelBaker baker, ModelState modelState, boolean useAmbientOcclusion, boolean usesBlockLight, ItemTransforms itemTransforms) { + return bake(textures, baker, modelState, useAmbientOcclusion, usesBlockLight, itemTransforms, ContextMap.EMPTY); + } + + // Re-abstract the extended version + @Override + BakedModel bake(TextureSlots textures, ModelBaker baker, ModelState modelState, boolean useAmbientOcclusion, boolean usesBlockLight, ItemTransforms itemTransforms, ContextMap additionalProperties); +} diff --git a/src/main/java/net/neoforged/neoforge/client/model/IModelBuilder.java b/src/main/java/net/neoforged/neoforge/client/model/IModelBuilder.java index 51111b4d..a75207f3 100644 --- a/src/main/java/net/neoforged/neoforge/client/model/IModelBuilder.java +++ b/src/main/java/net/neoforged/neoforge/client/model/IModelBuilder.java @@ -19,7 +19,10 @@ *

    * Provides a generic base implementation via {@link #of(boolean, boolean, boolean, ItemTransforms, TextureAtlasSprite, RenderTypeGroup)} * and a quad-collecting alternative via {@link #collecting(List)}. + * + * @deprecated Use {@link SimpleBakedModel.Builder} instead. */ +@Deprecated(forRemoval = true, since = "1.21.4") public interface IModelBuilder> { /** * Creates a new model builder that uses the provided attributes in the final baked model. @@ -38,10 +41,28 @@ static IModelBuilder collecting(List quads) { return new Collecting(quads); } + /** + * Adds a face to the model that will be culled based on the provided facing. + * + * @param facing The facing + * @param quad The quad + * @return This builder + */ T addCulledFace(Direction facing, BakedQuad quad); + /** + * Adds a face to the model that will not be culled. + * + * @param quad The quad + * @return This builder + */ T addUnculledFace(BakedQuad quad); + /** + * Builds the model from the collected faces. + * + * @return The baked model + */ BakedModel build(); class Simple implements IModelBuilder { diff --git a/src/main/java/net/neoforged/neoforge/client/model/ItemLayerModel.java b/src/main/java/net/neoforged/neoforge/client/model/ItemLayerModel.java deleted file mode 100644 index 005c98f6..00000000 --- a/src/main/java/net/neoforged/neoforge/client/model/ItemLayerModel.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.client.model; - -import com.google.common.collect.ImmutableList; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectMap; -import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.block.model.BakedOverrides; -import net.minecraft.client.renderer.block.model.ItemModelGenerator; -import net.minecraft.client.renderer.block.model.ItemOverride; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.ItemModel; -import net.minecraft.client.resources.model.Material; -import net.minecraft.client.resources.model.ModelBaker; -import net.minecraft.client.resources.model.ModelState; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.neoforge.client.NeoForgeRenderTypes; -import net.neoforged.neoforge.client.RenderTypeGroup; -import net.neoforged.neoforge.client.model.geometry.IGeometryBakingContext; -import net.neoforged.neoforge.client.model.geometry.IGeometryLoader; -import net.neoforged.neoforge.client.model.geometry.IUnbakedGeometry; -import net.neoforged.neoforge.client.model.geometry.UnbakedGeometryHelper; -import org.jetbrains.annotations.Nullable; - -/** - * Forge reimplementation of vanilla's {@link ItemModelGenerator}, i.e. builtin/generated models with some tweaks: - * - Represented as {@link IUnbakedGeometry} so it can be baked as usual instead of being special-cased - * - Not limited to an arbitrary number of layers (5) - * - Support for per-layer render types - */ -public class ItemLayerModel implements IUnbakedGeometry { - @Nullable - private ImmutableList textures; - private final Int2ObjectMap layerData; - private final Int2ObjectMap renderTypeNames; - - private ItemLayerModel(@Nullable ImmutableList textures, Int2ObjectMap layerData, Int2ObjectMap renderTypeNames) { - this.textures = textures; - this.layerData = layerData; - this.renderTypeNames = renderTypeNames; - } - - @Override - public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides) { - if (textures == null) { - ImmutableList.Builder builder = ImmutableList.builder(); - for (int i = 0; context.hasMaterial("layer" + i); i++) { - builder.add(context.getMaterial("layer" + i)); - } - textures = builder.build(); - } - - TextureAtlasSprite particle = spriteGetter.apply( - context.hasMaterial("particle") ? context.getMaterial("particle") : textures.get(0)); - var rootTransform = context.getRootTransform(); - if (!rootTransform.isIdentity()) - modelState = UnbakedGeometryHelper.composeRootTransformIntoModelState(modelState, rootTransform); - - var normalRenderTypes = new RenderTypeGroup(RenderType.translucent(), NeoForgeRenderTypes.ITEM_UNSORTED_TRANSLUCENT.get()); - CompositeModel.Baked.Builder builder = CompositeModel.Baked.builder(context, particle, context.getTransforms()); - for (int i = 0; i < textures.size(); i++) { - TextureAtlasSprite sprite = spriteGetter.apply(textures.get(i)); - var unbaked = UnbakedGeometryHelper.createUnbakedItemElements(i, sprite, this.layerData.get(i)); - var quads = UnbakedGeometryHelper.bakeElements(unbaked, $ -> sprite, modelState); - var renderTypeName = renderTypeNames.get(i); - var renderTypes = renderTypeName != null ? context.getRenderType(renderTypeName) : null; - builder.addQuads(renderTypes != null ? renderTypes : normalRenderTypes, quads); - } - - BakedModel baked = builder.build(); - if (!overrides.isEmpty()) { - baked = new ItemModel.BakedModelWithOverrides(baked, new BakedOverrides(baker, overrides, spriteGetter)); - } - return baked; - } - - public static final class Loader implements IGeometryLoader { - public static final Loader INSTANCE = new Loader(); - - @Override - public ItemLayerModel read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) { - var renderTypeNames = new Int2ObjectOpenHashMap(); - if (jsonObject.has("render_types")) { - var renderTypes = jsonObject.getAsJsonObject("render_types"); - for (Map.Entry entry : renderTypes.entrySet()) { - var renderType = ResourceLocation.parse(entry.getKey()); - for (var layer : entry.getValue().getAsJsonArray()) - if (renderTypeNames.put(layer.getAsInt(), renderType) != null) - throw new JsonParseException("Registered duplicate render type for layer " + layer); - } - } - - var emissiveLayers = new Int2ObjectArrayMap(); - if (jsonObject.has("neoforge_data")) { - JsonObject forgeData = jsonObject.get("neoforge_data").getAsJsonObject(); - readLayerData(forgeData, "layers", renderTypeNames, emissiveLayers, false); - } - return new ItemLayerModel(null, emissiveLayers, renderTypeNames); - } - - protected void readLayerData(JsonObject jsonObject, String name, Int2ObjectOpenHashMap renderTypeNames, Int2ObjectMap layerData, boolean logWarning) { - if (!jsonObject.has(name)) { - return; - } - var fullbrightLayers = jsonObject.getAsJsonObject(name); - for (var entry : fullbrightLayers.entrySet()) { - int layer = Integer.parseInt(entry.getKey()); - var data = ExtraFaceData.read(entry.getValue(), ExtraFaceData.DEFAULT); - layerData.put(layer, data); - } - } - } -} diff --git a/src/main/java/net/neoforged/neoforge/client/model/NeoForgeModelProperties.java b/src/main/java/net/neoforged/neoforge/client/model/NeoForgeModelProperties.java new file mode 100644 index 00000000..43dd5a89 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/model/NeoForgeModelProperties.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.model; + +import com.mojang.math.Transformation; +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.util.context.ContextKey; +import net.neoforged.neoforge.client.RenderTypeGroup; + +/** + * Properties that NeoForge adds for {@link BlockModel}s and {@link UnbakedModel}s. + */ +public final class NeoForgeModelProperties { + private NeoForgeModelProperties() {} + + /** + * Root transform. For block models, this can be specified under the {@code transform} JSON key. + */ + public static final ContextKey TRANSFORM = ContextKey.vanilla("transform"); + + /** + * Render type to use. For block models, this can be specified under the {@code render_type} JSON key. + */ + public static final ContextKey RENDER_TYPE = ContextKey.vanilla("render_type"); +} diff --git a/src/main/java/net/neoforged/neoforge/client/model/SeparateTransformsModel.java b/src/main/java/net/neoforged/neoforge/client/model/SeparateTransformsModel.java deleted file mode 100644 index 03e4d72b..00000000 --- a/src/main/java/net/neoforged/neoforge/client/model/SeparateTransformsModel.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.client.model; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Maps; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonObject; -import com.mojang.blaze3d.vertex.PoseStack; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.block.model.BakedOverrides; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.renderer.block.model.ItemOverride; -import net.minecraft.client.renderer.block.model.ItemTransforms; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.ItemModel; -import net.minecraft.client.resources.model.Material; -import net.minecraft.client.resources.model.ModelBaker; -import net.minecraft.client.resources.model.ModelState; -import net.minecraft.client.resources.model.UnbakedModel; -import net.minecraft.core.Direction; -import net.minecraft.util.GsonHelper; -import net.minecraft.util.RandomSource; -import net.minecraft.world.item.ItemDisplayContext; -import net.minecraft.world.level.block.state.BlockState; -import net.neoforged.neoforge.client.ChunkRenderTypeSet; -import net.neoforged.neoforge.client.model.data.ModelData; -import net.neoforged.neoforge.client.model.geometry.IGeometryBakingContext; -import net.neoforged.neoforge.client.model.geometry.IGeometryLoader; -import net.neoforged.neoforge.client.model.geometry.IUnbakedGeometry; -import org.jetbrains.annotations.Nullable; - -/** - * A model composed of multiple sub-models which are picked based on the {@link ItemDisplayContext} being used. - */ -public class SeparateTransformsModel implements IUnbakedGeometry { - private final BlockModel baseModel; - private final ImmutableMap perspectives; - - public SeparateTransformsModel(BlockModel baseModel, ImmutableMap perspectives) { - this.baseModel = baseModel; - this.perspectives = perspectives; - } - - @Override - public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides) { - BakedModel baked = new Baked( - context.useAmbientOcclusion(), context.isGui3d(), context.useBlockLight(), - spriteGetter.apply(context.getMaterial("particle")), - baseModel.bake(baker, spriteGetter, modelState), - ImmutableMap.copyOf(Maps.transformValues(perspectives, value -> value.bake(baker, spriteGetter, modelState)))); - if (!overrides.isEmpty()) { - baked = new ItemModel.BakedModelWithOverrides(baked, new BakedOverrides(baker, overrides, spriteGetter)); - } - return baked; - } - - @Override - public void resolveDependencies(UnbakedModel.Resolver modelGetter, IGeometryBakingContext context) { - baseModel.resolveDependencies(modelGetter); - perspectives.values().forEach(model -> model.resolveDependencies(modelGetter)); - } - - public static class Baked implements IDynamicBakedModel { - private final boolean isAmbientOcclusion; - private final boolean isGui3d; - private final boolean isSideLit; - private final TextureAtlasSprite particle; - private final BakedModel baseModel; - private final ImmutableMap perspectives; - - public Baked(boolean isAmbientOcclusion, boolean isGui3d, boolean isSideLit, TextureAtlasSprite particle, BakedModel baseModel, ImmutableMap perspectives) { - this.isAmbientOcclusion = isAmbientOcclusion; - this.isGui3d = isGui3d; - this.isSideLit = isSideLit; - this.particle = particle; - this.baseModel = baseModel; - this.perspectives = perspectives; - } - - @Override - public List getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource rand, ModelData data, @Nullable RenderType renderType) { - return baseModel.getQuads(state, side, rand, data, renderType); - } - - @Override - public boolean useAmbientOcclusion() { - return isAmbientOcclusion; - } - - @Override - public boolean isGui3d() { - return isGui3d; - } - - @Override - public boolean usesBlockLight() { - return isSideLit; - } - - @Override - public boolean isCustomRenderer() { - return false; - } - - @Override - public TextureAtlasSprite getParticleIcon() { - return particle; - } - - @Override - public ItemTransforms getTransforms() { - return ItemTransforms.NO_TRANSFORMS; - } - - @Override - public BakedModel applyTransform(ItemDisplayContext cameraTransformType, PoseStack poseStack, boolean applyLeftHandTransform) { - if (perspectives.containsKey(cameraTransformType)) { - BakedModel p = perspectives.get(cameraTransformType); - return p.applyTransform(cameraTransformType, poseStack, applyLeftHandTransform); - } - return baseModel.applyTransform(cameraTransformType, poseStack, applyLeftHandTransform); - } - - @Override - public ChunkRenderTypeSet getRenderTypes(BlockState state, RandomSource rand, ModelData data) { - return baseModel.getRenderTypes(state, rand, data); - } - } - - public static final class Loader implements IGeometryLoader { - public static final Loader INSTANCE = new Loader(); - - private Loader() {} - - @Override - public SeparateTransformsModel read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) { - BlockModel baseModel = deserializationContext.deserialize(GsonHelper.getAsJsonObject(jsonObject, "base"), BlockModel.class); - - JsonObject perspectiveData = GsonHelper.getAsJsonObject(jsonObject, "perspectives"); - - Map perspectives = new HashMap<>(); - for (ItemDisplayContext transform : ItemDisplayContext.values()) { - if (perspectiveData.has(transform.getSerializedName())) { - BlockModel perspectiveModel = deserializationContext.deserialize(GsonHelper.getAsJsonObject(perspectiveData, transform.getSerializedName()), BlockModel.class); - perspectives.put(transform, perspectiveModel); - } - } - - return new SeparateTransformsModel(baseModel, ImmutableMap.copyOf(perspectives)); - } - } -} diff --git a/src/main/java/net/neoforged/neoforge/client/model/CompositeModel.java b/src/main/java/net/neoforged/neoforge/client/model/UnbakedCompositeModel.java similarity index 74% rename from src/main/java/net/neoforged/neoforge/client/model/CompositeModel.java rename to src/main/java/net/neoforged/neoforge/client/model/UnbakedCompositeModel.java index 93fde9b7..b79dfa0c 100644 --- a/src/main/java/net/neoforged/neoforge/client/model/CompositeModel.java +++ b/src/main/java/net/neoforged/neoforge/client/model/UnbakedCompositeModel.java @@ -11,30 +11,29 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; +import com.mojang.math.Transformation; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; -import java.util.function.Function; +import net.minecraft.client.data.models.model.TextureSlot; import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.block.model.BakedOverrides; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.renderer.block.model.ItemOverride; import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.block.model.TextureSlots; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.ItemModel; -import net.minecraft.client.resources.model.Material; import net.minecraft.client.resources.model.ModelBaker; import net.minecraft.client.resources.model.ModelState; import net.minecraft.client.resources.model.UnbakedModel; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.resources.ResourceLocation; import net.minecraft.util.RandomSource; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockAndTintGetter; @@ -43,44 +42,44 @@ import net.neoforged.neoforge.client.RenderTypeGroup; import net.neoforged.neoforge.client.model.data.ModelData; import net.neoforged.neoforge.client.model.data.ModelProperty; -import net.neoforged.neoforge.client.model.geometry.IGeometryBakingContext; -import net.neoforged.neoforge.client.model.geometry.IGeometryLoader; -import net.neoforged.neoforge.client.model.geometry.IUnbakedGeometry; -import net.neoforged.neoforge.client.model.geometry.UnbakedGeometryHelper; import net.neoforged.neoforge.common.util.ConcatenatedListView; import org.jetbrains.annotations.Nullable; /** * A model composed of several named children. - *

    - * These respect component visibility as specified in {@link IGeometryBakingContext} and can additionally be provided - * with an item-specific render ordering, for multi-pass arrangements. */ -public class CompositeModel implements IUnbakedGeometry { - private final ImmutableMap children; +public class UnbakedCompositeModel implements UnbakedModel { + private final ImmutableMap children; private final ImmutableList itemPasses; + private final Transformation rootTransform; + private final Map partVisibility; - public CompositeModel(ImmutableMap children, ImmutableList itemPasses) { + public UnbakedCompositeModel(ImmutableMap children, ImmutableList itemPasses, Transformation rootTransform, Map partVisibility) { this.children = children; this.itemPasses = itemPasses; + this.rootTransform = rootTransform; + this.partVisibility = partVisibility; } @Override - public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides) { - Material particleLocation = context.getMaterial("particle"); - TextureAtlasSprite particle = spriteGetter.apply(particleLocation); + public BakedModel bake(TextureSlots slots, + ModelBaker baker, + ModelState state, + boolean useAmbientOcclusion, + boolean usesBlockLight, + ItemTransforms transforms) { + TextureAtlasSprite particle = baker.findSprite(slots, TextureSlot.PARTICLE.getId()); - var rootTransform = context.getRootTransform(); if (!rootTransform.isIdentity()) - modelState = UnbakedGeometryHelper.composeRootTransformIntoModelState(modelState, rootTransform); + state = UnbakedElementsHelper.composeRootTransformIntoModelState(state, rootTransform); var bakedPartsBuilder = ImmutableMap.builder(); for (var entry : children.entrySet()) { var name = entry.getKey(); - if (!context.isComponentVisible(name, true)) + if (!partVisibility.getOrDefault(name, true)) continue; var model = entry.getValue(); - bakedPartsBuilder.put(name, model.bake(baker, spriteGetter, modelState)); + bakedPartsBuilder.put(name, baker.bake(model, state)); } var bakedParts = bakedPartsBuilder.build(); @@ -92,36 +91,27 @@ public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Functio itemPassesBuilder.add(model); } - BakedModel baked = new Baked(context.isGui3d(), context.useBlockLight(), context.useAmbientOcclusion(), particle, context.getTransforms(), bakedParts, itemPassesBuilder.build()); - if (!overrides.isEmpty()) { - baked = new ItemModel.BakedModelWithOverrides(baked, new BakedOverrides(baker, overrides, spriteGetter)); - } - return baked; - } - - @Override - public void resolveDependencies(UnbakedModel.Resolver modelGetter, IGeometryBakingContext context) { - children.values().forEach(child -> child.resolveDependencies(modelGetter)); + return new Baked(usesBlockLight, useAmbientOcclusion, particle, transforms, bakedParts, itemPassesBuilder.build()); } @Override - public Set getConfigurableComponentNames() { - return children.keySet(); + public void resolveDependencies(Resolver resolver) { + for (ResourceLocation path : children.values()) { + resolver.resolve(path); + } } public static class Baked implements IDynamicBakedModel { private final boolean isAmbientOcclusion; - private final boolean isGui3d; private final boolean isSideLit; private final TextureAtlasSprite particle; private final ItemTransforms transforms; private final ImmutableMap children; private final ImmutableList itemPasses; - public Baked(boolean isGui3d, boolean isSideLit, boolean isAmbientOcclusion, TextureAtlasSprite particle, ItemTransforms transforms, ImmutableMap children, ImmutableList itemPasses) { + public Baked(boolean isSideLit, boolean isAmbientOcclusion, TextureAtlasSprite particle, ItemTransforms transforms, ImmutableMap children, ImmutableList itemPasses) { this.children = children; this.isAmbientOcclusion = isAmbientOcclusion; - this.isGui3d = isGui3d; this.isSideLit = isSideLit; this.particle = particle; this.transforms = transforms; @@ -133,7 +123,7 @@ public List getQuads(@Nullable BlockState state, @Nullable Direction List> quadLists = new ArrayList<>(); for (Map.Entry entry : children.entrySet()) { if (renderType == null || (state != null && entry.getValue().getRenderTypes(state, rand, data).contains(renderType))) { - quadLists.add(entry.getValue().getQuads(state, side, rand, CompositeModel.Data.resolve(data, entry.getKey()), renderType)); + quadLists.add(entry.getValue().getQuads(state, side, rand, Data.resolve(data, entry.getKey()), renderType)); } } return ConcatenatedListView.of(quadLists); @@ -154,7 +144,7 @@ public boolean useAmbientOcclusion() { @Override public boolean isGui3d() { - return isGui3d; + return true; } @Override @@ -162,11 +152,6 @@ public boolean usesBlockLight() { return isSideLit; } - @Override - public boolean isCustomRenderer() { - return false; - } - @Override public TextureAtlasSprite getParticleIcon() { return particle; @@ -181,7 +166,7 @@ public ItemTransforms getTransforms() { public ChunkRenderTypeSet getRenderTypes(BlockState state, RandomSource rand, ModelData data) { var sets = new ArrayList(); for (Map.Entry entry : children.entrySet()) - sets.add(entry.getValue().getRenderTypes(state, rand, CompositeModel.Data.resolve(data, entry.getKey()))); + sets.add(entry.getValue().getRenderTypes(state, rand, Data.resolve(data, entry.getKey()))); return ChunkRenderTypeSet.union(sets); } @@ -195,10 +180,6 @@ public BakedModel getPart(String name) { return children.get(name); } - public static Builder builder(IGeometryBakingContext owner, TextureAtlasSprite particle, ItemTransforms cameraTransforms) { - return builder(owner.useAmbientOcclusion(), owner.isGui3d(), owner.useBlockLight(), particle, cameraTransforms); - } - public static Builder builder(boolean isAmbientOcclusion, boolean isGui3d, boolean isSideLit, TextureAtlasSprite particle, ItemTransforms cameraTransforms) { return new Builder(isAmbientOcclusion, isGui3d, isSideLit, particle, cameraTransforms); } @@ -270,7 +251,7 @@ public BakedModel build() { childrenBuilder.put("model_" + (i++), model); itemPassesBuilder.add(model); } - return new Baked(isGui3d, isSideLit, isAmbientOcclusion, particle, transforms, childrenBuilder.build(), itemPassesBuilder.build()); + return new Baked(isSideLit, isAmbientOcclusion, particle, transforms, childrenBuilder.build(), itemPassesBuilder.build()); } } } @@ -325,16 +306,16 @@ public Data build() { } } - public static final class Loader implements IGeometryLoader { + public static final class Loader implements UnbakedModelLoader { public static final Loader INSTANCE = new Loader(); private Loader() {} @Override - public CompositeModel read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) { + public UnbakedCompositeModel read(JsonObject jsonObject, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { List itemPasses = new ArrayList<>(); - ImmutableMap.Builder childrenBuilder = ImmutableMap.builder(); - readChildren(jsonObject, "children", deserializationContext, childrenBuilder, itemPasses); + ImmutableMap.Builder childrenBuilder = ImmutableMap.builder(); + readChildren(jsonObject, "children", childrenBuilder, itemPasses); var children = childrenBuilder.build(); if (children.isEmpty()) @@ -350,15 +331,34 @@ public CompositeModel read(JsonObject jsonObject, JsonDeserializationContext des } } - return new CompositeModel(children, ImmutableList.copyOf(itemPasses)); + final Map partVisibility; + if (jsonObject.has("visibility")) { + partVisibility = new HashMap<>(); + JsonObject visibility = jsonObject.getAsJsonObject("visibility"); + for (Map.Entry part : visibility.entrySet()) { + partVisibility.put(part.getKey(), part.getValue().getAsBoolean()); + } + } else { + partVisibility = Collections.emptyMap(); + } + + final Transformation transformation; + if (jsonObject.has("transform")) { + transformation = BlockModel.GSON.fromJson(jsonObject.get("transform"), Transformation.class); + } else { + transformation = Transformation.identity(); + } + + return new UnbakedCompositeModel(children, ImmutableList.copyOf(itemPasses), transformation, partVisibility); } - private void readChildren(JsonObject jsonObject, String name, JsonDeserializationContext deserializationContext, ImmutableMap.Builder children, List itemPasses) { + private void readChildren(JsonObject jsonObject, String name, ImmutableMap.Builder children, List itemPasses) { if (!jsonObject.has(name)) return; var childrenJsonObject = jsonObject.getAsJsonObject(name); for (Map.Entry entry : childrenJsonObject.entrySet()) { - children.put(entry.getKey(), deserializationContext.deserialize(entry.getValue(), BlockModel.class)); + ResourceLocation location = ResourceLocation.parse(entry.getValue().getAsString()); + children.put(entry.getKey(), location); itemPasses.add(entry.getKey()); // We can do this because GSON preserves ordering during deserialization } } diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/UnbakedGeometryHelper.java b/src/main/java/net/neoforged/neoforge/client/model/UnbakedElementsHelper.java similarity index 64% rename from src/main/java/net/neoforged/neoforge/client/model/geometry/UnbakedGeometryHelper.java rename to src/main/java/net/neoforged/neoforge/client/model/UnbakedElementsHelper.java index e790cef4..5c5e67c5 100644 --- a/src/main/java/net/neoforged/neoforge/client/model/geometry/UnbakedGeometryHelper.java +++ b/src/main/java/net/neoforged/neoforge/client/model/UnbakedElementsHelper.java @@ -1,86 +1,38 @@ /* - * Copyright (c) Forge Development LLC and contributors + * Copyright (c) NeoForged and contributors * SPDX-License-Identifier: LGPL-2.1-only */ -package net.neoforged.neoforge.client.model.geometry; +package net.neoforged.neoforge.client.model; import com.mojang.math.Transformation; import java.util.ArrayList; import java.util.BitSet; -import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import net.minecraft.Util; import net.minecraft.client.renderer.block.model.BakedQuad; import net.minecraft.client.renderer.block.model.BlockElement; import net.minecraft.client.renderer.block.model.BlockElementFace; import net.minecraft.client.renderer.block.model.BlockFaceUV; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.renderer.block.model.FaceBakery; import net.minecraft.client.renderer.block.model.ItemModelGenerator; -import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite; import net.minecraft.client.renderer.texture.SpriteContents; import net.minecraft.client.renderer.texture.TextureAtlas; import net.minecraft.client.renderer.texture.TextureAtlasSprite; import net.minecraft.client.resources.model.Material; import net.minecraft.client.resources.model.ModelState; +import net.minecraft.client.resources.model.SimpleBakedModel; import net.minecraft.core.Direction; import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; import net.neoforged.neoforge.client.ClientHooks; -import net.neoforged.neoforge.client.model.ExtraFaceData; -import net.neoforged.neoforge.client.model.IModelBuilder; -import net.neoforged.neoforge.client.model.SimpleModelState; import org.jetbrains.annotations.Nullable; import org.joml.Vector3f; -/** - * Helper for dealing with unbaked models and geometries. - */ -public class UnbakedGeometryHelper { - private static final ItemModelGenerator ITEM_MODEL_GENERATOR = new ItemModelGenerator(); - private static final FaceBakery FACE_BAKERY = new FaceBakery(); - - /** - * Explanation: - * This takes anything that looks like a valid resourcepack texture location, and tries to extract a resourcelocation out of it. - * 1. it will ignore anything up to and including an /assets/ folder, - * 2. it will take the next path component as a namespace, - * 3. it will match but skip the /textures/ part of the path, - * 4. it will take the rest of the path up to but excluding the .png extension as the resource path - * It's a best-effort situation, to allow model files exported by modelling software to be used without post-processing. - * Example: - * C:\Something\Or Other\src\main\resources\assets\mymodid\textures\item\my_thing.png - * ........................................--------_______----------_____________---- - * - * Result after replacing '\' to '/': mymodid:item/my_thing - */ - private static final Pattern FILESYSTEM_PATH_TO_RESLOC = Pattern.compile("(?:.*[\\\\/]assets[\\\\/](?[a-z_-]+)[\\\\/]textures[\\\\/])?(?[a-z_\\\\/-]+)\\.png"); - - /** - * Resolves a material that may have been defined with a filesystem path instead of a proper {@link ResourceLocation}. - *

    - * The target atlas will always be {@link TextureAtlas#LOCATION_BLOCKS}. - */ - public static Material resolveDirtyMaterial(@Nullable String tex, IGeometryBakingContext owner) { - if (tex == null) - return new Material(TextureAtlas.LOCATION_BLOCKS, MissingTextureAtlasSprite.getLocation()); - if (tex.startsWith("#")) - return owner.getMaterial(tex); - - // Attempt to convert a common (windows/linux/mac) filesystem path to a ResourceLocation. - // This makes no promises, if it doesn't work, too bad, fix your mtl file. - Matcher match = FILESYSTEM_PATH_TO_RESLOC.matcher(tex); - if (match.matches()) { - String namespace = match.group("namespace"); - String path = match.group("path").replace("\\", "/"); - tex = namespace != null ? namespace + ":" + path : path; - } +public final class UnbakedElementsHelper { + private UnbakedElementsHelper() {} - return new Material(TextureAtlas.LOCATION_BLOCKS, ResourceLocation.parse(tex)); - } + private static final ItemModelGenerator ITEM_MODEL_GENERATOR = new ItemModelGenerator(); /** * @see #createUnbakedItemElements(int, TextureAtlasSprite, ExtraFaceData) @@ -118,12 +70,13 @@ public static List createUnbakedItemMaskElements(int layerIndex, T * The {@link Direction#NORTH} and {@link Direction#SOUTH} faces take up only the pixels the texture uses. */ public static List createUnbakedItemMaskElements(int layerIndex, TextureAtlasSprite sprite, @Nullable ExtraFaceData faceData) { - var elements = createUnbakedItemElements(layerIndex, sprite, faceData); - elements.remove(0); // Remove north and south faces + List elements = createUnbakedItemElements(layerIndex, sprite, faceData); + elements.removeFirst(); // Remove north and south faces + float expand = -sprite.uvShrinkRatio(); SpriteContents spriteContents = sprite.contents(); int width = spriteContents.width(), height = spriteContents.height(); - var bits = new BitSet(width * height); + BitSet bits = new BitSet(width * height); // For every frame in the texture, mark all the opaque pixels (this is what vanilla does too) spriteContents.getUniqueFrames().forEach(frame -> { @@ -137,9 +90,8 @@ public static List createUnbakedItemMaskElements(int layerIndex, T for (int y = 0; y < height; y++) { int xStart = -1; for (int x = 0; x < width; x++) { - var opaque = bits.get(x + y * width); - if (opaque == (xStart == -1)) // (opaque && -1) || (!opaque && !-1) - { + boolean opaque = bits.get(x + y * width); + if (opaque == (xStart == -1)) { // (opaque && -1) || (!opaque && !-1) if (xStart == -1) { // We have found the start of a new segment, continue xStart = x; @@ -160,16 +112,31 @@ public static List createUnbakedItemMaskElements(int layerIndex, T bits.clear(i + j * width); // Create element - elements.add(new BlockElement( + BlockElement element = new BlockElement( new Vector3f(16 * xStart / (float) width, 16 - 16 * yEnd / (float) height, 7.5F), new Vector3f(16 * x / (float) width, 16 - 16 * y / (float) height, 8.5F), - Util.make(new HashMap<>(), map -> { - for (Direction direction : Direction.values()) - map.put(direction, new BlockElementFace(null, layerIndex, "layer" + layerIndex, new BlockFaceUV(null, 0))); - }), + Map.of( + Direction.NORTH, new BlockElementFace(null, layerIndex, "layer" + layerIndex, new BlockFaceUV(null, 0)), + Direction.SOUTH, new BlockElementFace(null, layerIndex, "layer" + layerIndex, new BlockFaceUV(null, 0))), null, true, - 0)); + 0); + // Expand coordinates to match the shrunk UVs of the front/back face on a standard generated model (done after to not affect the auto-generated UVs) + element.from.x = Mth.clamp(Mth.lerp(expand, element.from.x, 8F), 0F, 16F); + element.from.y = Mth.clamp(Mth.lerp(expand, element.from.y, 8F), 0F, 16F); + element.to.x = Mth.clamp(Mth.lerp(expand, element.to.x, 8F), 0F, 16F); + element.to.y = Mth.clamp(Mth.lerp(expand, element.to.y, 8F), 0F, 16F); + // Counteract sprite expansion to ensure pixel alignment + element.faces.forEach((dir, face) -> { + float[] uv = face.uv().uvs; + float centerU = (uv[0] + uv[0] + uv[2] + uv[2]) / 4.0F; + uv[0] = Mth.clamp(Mth.lerp(expand, uv[0], centerU), 0F, 16F); + uv[2] = Mth.clamp(Mth.lerp(expand, uv[2], centerU), 0F, 16F); + float centerV = (uv[1] + uv[1] + uv[3] + uv[3]) / 4.0F; + uv[1] = Mth.clamp(Mth.lerp(expand, uv[1], centerV), 0F, 16F); + uv[3] = Mth.clamp(Mth.lerp(expand, uv[3], centerV), 0F, 16F); + }); + elements.add(element); // Reset xStart xStart = -1; @@ -186,7 +153,7 @@ public static void bakeElements(IModelBuilder builder, List ele for (BlockElement element : elements) { element.faces.forEach((side, face) -> { var sprite = spriteGetter.apply(new Material(TextureAtlas.LOCATION_BLOCKS, ResourceLocation.parse(face.texture()))); - BakedQuad quad = BlockModel.bakeFace(element, face, sprite, side, modelState); + BakedQuad quad = SimpleBakedModel.bakeFace(element, face, sprite, side, modelState); if (face.cullForDirection() == null) builder.addUnculledFace(quad); else diff --git a/src/main/java/net/neoforged/neoforge/client/model/UnbakedModelLoader.java b/src/main/java/net/neoforged/neoforge/client/model/UnbakedModelLoader.java new file mode 100644 index 00000000..0f518f18 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/model/UnbakedModelLoader.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.model; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.server.packs.resources.ResourceManagerReloadListener; +import net.neoforged.neoforge.client.event.ModelEvent; +import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent; + +/** + * A loader for custom {@linkplain UnbakedModel unbaked models}. + *

    + * If you do any caching, you should implement {@link ResourceManagerReloadListener} and register it with + * {@link RegisterClientReloadListenersEvent}. + * + * @see ModelEvent.RegisterLoaders + * @see RegisterClientReloadListenersEvent + */ +public interface UnbakedModelLoader { + /** + * Reads an unbaked model from the passed JSON object. + * + *

    The {@link JsonDeserializationContext} argument can be used to deserialize types that the system already understands. + * For example, {@code deserializationContext.deserialize(, Transformation.class)} to parse a transformation, + * or {@code deserializationContext.deserialize(, UnbakedModel.class)} to parse a nested model. + * The set of supported types can be found in the declaration of {@link BlockModel#GSON}. + */ + T read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) throws JsonParseException; +} diff --git a/src/main/java/net/neoforged/neoforge/client/model/UnbakedModelParser.java b/src/main/java/net/neoforged/neoforge/client/model/UnbakedModelParser.java new file mode 100644 index 00000000..37e26215 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/model/UnbakedModelParser.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.model; + +import com.google.common.collect.ImmutableMap; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import java.io.Reader; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.stream.Collectors; +import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; +import net.neoforged.fml.ModLoader; +import net.neoforged.neoforge.client.event.ModelEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +public class UnbakedModelParser { + private static ImmutableMap> LOADERS; + + @Nullable + public static UnbakedModelLoader get(ResourceLocation name) { + return LOADERS.get(name); + } + + @ApiStatus.Internal + public static void init() { + var loaders = new HashMap>(); + ModLoader.postEventWrapContainerInModOrder(new ModelEvent.RegisterLoaders(loaders)); + LOADERS = ImmutableMap.copyOf(loaders); + } + + public static UnbakedModel parse(Reader reader) { + return GsonHelper.fromJson(BlockModel.GSON, reader, UnbakedModel.class); + } + + @ApiStatus.Internal + public static final class Deserializer implements JsonDeserializer { + @Override + public UnbakedModel deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + if (!jsonElement.isJsonObject()) { + throw new JsonParseException("Expected object, got " + jsonElement); + } else { + JsonObject jsonObject = jsonElement.getAsJsonObject(); + + if (jsonObject.has("loader")) { + final ResourceLocation loader; + final boolean optional; + if (jsonObject.get("loader").isJsonObject()) { + JsonObject loaderObject = jsonObject.getAsJsonObject("loader"); + loader = ResourceLocation.parse(GsonHelper.getAsString(loaderObject, "id")); + optional = GsonHelper.getAsBoolean(loaderObject, "optional", false); + } else { + loader = ResourceLocation.parse(GsonHelper.getAsString(jsonObject, "loader")); + optional = false; + } + + var loaderInstance = UnbakedModelParser.get(loader); + if (loaderInstance != null) { + return loaderInstance.read(jsonObject, jsonDeserializationContext); + } + if (!optional) { + throw new JsonParseException("Unknown loader: " + loader + " (did you forget to register it?) Available loaders: " + LOADERS.keySet().stream().map(ResourceLocation::toString).collect(Collectors.joining(", "))); + } + } + + return jsonDeserializationContext.deserialize(jsonObject, BlockModel.class); + } + } + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/model/generators/CustomLoaderBuilder.java b/src/main/java/net/neoforged/neoforge/client/model/generators/CustomLoaderBuilder.java index a8717c55..7e088ea4 100644 --- a/src/main/java/net/neoforged/neoforge/client/model/generators/CustomLoaderBuilder.java +++ b/src/main/java/net/neoforged/neoforge/client/model/generators/CustomLoaderBuilder.java @@ -10,7 +10,6 @@ import java.util.LinkedHashMap; import java.util.Map; import net.minecraft.resources.ResourceLocation; -import net.neoforged.neoforge.client.model.geometry.IGeometryLoader; import net.neoforged.neoforge.common.data.ExistingFileHelper; public abstract class CustomLoaderBuilder> { diff --git a/src/main/java/net/neoforged/neoforge/client/model/generators/ModelBuilder.java b/src/main/java/net/neoforged/neoforge/client/model/generators/ModelBuilder.java index 9afe777b..90d619fd 100644 --- a/src/main/java/net/neoforged/neoforge/client/model/generators/ModelBuilder.java +++ b/src/main/java/net/neoforged/neoforge/client/model/generators/ModelBuilder.java @@ -25,9 +25,9 @@ import net.minecraft.client.renderer.block.model.BlockElementFace; import net.minecraft.client.renderer.block.model.BlockElementRotation; import net.minecraft.client.renderer.block.model.BlockFaceUV; -import net.minecraft.client.renderer.block.model.BlockModel.GuiLight; import net.minecraft.client.renderer.block.model.ItemTransform; import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite; +import net.minecraft.client.resources.model.UnbakedModel; import net.minecraft.core.Direction; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; @@ -61,7 +61,8 @@ public class ModelBuilder> extends ModelFile { protected String renderType = null; protected boolean ambientOcclusion = true; - protected GuiLight guiLight = null; + + protected UnbakedModel.GuiLight guiLight = null; protected final List elements = new ArrayList<>(); @@ -185,7 +186,7 @@ public T ao(boolean ao) { return self(); } - public T guiLight(GuiLight light) { + public T guiLight(UnbakedModel.GuiLight light) { this.guiLight = light; return self(); } diff --git a/src/main/java/net/neoforged/neoforge/client/model/generators/ModelProvider.java b/src/main/java/net/neoforged/neoforge/client/model/generators/ModelProvider.java index e39d3e8e..f52db6e3 100644 --- a/src/main/java/net/neoforged/neoforge/client/model/generators/ModelProvider.java +++ b/src/main/java/net/neoforged/neoforge/client/model/generators/ModelProvider.java @@ -371,6 +371,10 @@ public T leaves(String name, ResourceLocation texture) { return singleTexture(name, BLOCK_FOLDER + "/leaves", "all", texture); } + public T flowerPotCross(String name, ResourceLocation plant) { + return singleTexture(name, BLOCK_FOLDER + "/flower_pot_cross", "plant", plant); + } + /** * {@return a model builder that's not directly saved to disk. Meant for use in custom model loaders.} */ diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/BlockGeometryBakingContext.java b/src/main/java/net/neoforged/neoforge/client/model/geometry/BlockGeometryBakingContext.java deleted file mode 100644 index 02c53783..00000000 --- a/src/main/java/net/neoforged/neoforge/client/model/geometry/BlockGeometryBakingContext.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.client.model.geometry; - -import com.mojang.math.Transformation; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.renderer.block.model.ItemOverride; -import net.minecraft.client.renderer.block.model.ItemTransforms; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.Material; -import net.minecraft.client.resources.model.ModelBaker; -import net.minecraft.client.resources.model.ModelState; -import net.minecraft.resources.ResourceLocation; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; - -/** - * A {@linkplain IGeometryBakingContext geometry baking context} that is bound to a {@link BlockModel}. - *

    - * Users should not be instantiating this themselves. - */ -public class BlockGeometryBakingContext implements IGeometryBakingContext { - public final BlockModel owner; - public final VisibilityData visibilityData = new VisibilityData(); - @Nullable - private IUnbakedGeometry customGeometry; - @Nullable - private Transformation rootTransform; - @Nullable - private ResourceLocation renderTypeHint; - private boolean gui3d = true; - - @ApiStatus.Internal - public BlockGeometryBakingContext(BlockModel owner) { - this.owner = owner; - } - - @Override - public String getModelName() { - return owner.name; - } - - public boolean hasCustomGeometry() { - return getCustomGeometry() != null; - } - - @Nullable - public IUnbakedGeometry getCustomGeometry() { - return owner.parent != null && customGeometry == null ? owner.parent.customData.getCustomGeometry() : customGeometry; - } - - public void setCustomGeometry(IUnbakedGeometry geometry) { - this.customGeometry = geometry; - } - - @Override - public boolean isComponentVisible(String part, boolean fallback) { - return owner.parent != null && !visibilityData.hasCustomVisibility(part) ? owner.parent.customData.isComponentVisible(part, fallback) : visibilityData.isVisible(part, fallback); - } - - @Override - public boolean hasMaterial(String name) { - return owner.hasTexture(name); - } - - @Override - public Material getMaterial(String name) { - return owner.getMaterial(name); - } - - @Override - public boolean isGui3d() { - return gui3d; - } - - @Override - public boolean useBlockLight() { - return owner.getGuiLight().lightLikeBlock(); - } - - @Override - public boolean useAmbientOcclusion() { - return owner.hasAmbientOcclusion(); - } - - @Override - public ItemTransforms getTransforms() { - return owner.getTransforms(); - } - - @Override - public Transformation getRootTransform() { - if (rootTransform != null) - return rootTransform; - return owner.parent != null ? owner.parent.customData.getRootTransform() : Transformation.identity(); - } - - public void setRootTransform(Transformation rootTransform) { - this.rootTransform = rootTransform; - } - - @Nullable - @Override - public ResourceLocation getRenderTypeHint() { - if (renderTypeHint != null) - return renderTypeHint; - return owner.parent != null ? owner.parent.customData.getRenderTypeHint() : null; - } - - public void setRenderTypeHint(ResourceLocation renderTypeHint) { - this.renderTypeHint = renderTypeHint; - } - - public void setGui3d(boolean gui3d) { - this.gui3d = gui3d; - } - - public void copyFrom(BlockGeometryBakingContext other) { - this.customGeometry = other.customGeometry; - this.rootTransform = other.rootTransform; - this.visibilityData.copyFrom(other.visibilityData); - this.renderTypeHint = other.renderTypeHint; - this.gui3d = other.gui3d; - } - - public BakedModel bake(ModelBaker baker, Function bakedTextureGetter, ModelState modelTransform, List overrides) { - IUnbakedGeometry geometry = getCustomGeometry(); - if (geometry == null) - throw new IllegalStateException("Can not use custom baking without custom geometry"); - return geometry.bake(this, baker, bakedTextureGetter, modelTransform, overrides); - } - - public static class VisibilityData { - private final Map data = new HashMap<>(); - - public boolean hasCustomVisibility(String part) { - return data.containsKey(part); - } - - public boolean isVisible(String part, boolean fallback) { - return data.getOrDefault(part, fallback); - } - - public void setVisibilityState(String partName, boolean type) { - data.put(partName, type); - } - - public void copyFrom(VisibilityData visibilityData) { - data.clear(); - data.putAll(visibilityData.data); - } - } -} diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/GeometryLoaderManager.java b/src/main/java/net/neoforged/neoforge/client/model/geometry/GeometryLoaderManager.java deleted file mode 100644 index 1382c821..00000000 --- a/src/main/java/net/neoforged/neoforge/client/model/geometry/GeometryLoaderManager.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.client.model.geometry; - -import com.google.common.collect.ImmutableMap; -import java.util.HashMap; -import java.util.stream.Collectors; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.fml.ModLoader; -import net.neoforged.neoforge.client.event.ModelEvent; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; - -/** - * Manager for {@linkplain IGeometryLoader geometry loaders}. - *

    - * Provides a lookup. - */ -public final class GeometryLoaderManager { - private static ImmutableMap> LOADERS; - private static String LOADER_LIST; - - /** - * Finds the {@link IGeometryLoader} for a given name, or null if not found. - */ - @Nullable - public static IGeometryLoader get(ResourceLocation name) { - return LOADERS.get(name); - } - - /** - * Retrieves a comma-separated list of all active loaders, for use in error messages. - */ - public static String getLoaderList() { - return LOADER_LIST; - } - - @ApiStatus.Internal - public static void init() { - var loaders = new HashMap>(); - var event = new ModelEvent.RegisterGeometryLoaders(loaders); - ModLoader.postEventWrapContainerInModOrder(event); - LOADERS = ImmutableMap.copyOf(loaders); - LOADER_LIST = loaders.keySet().stream().map(ResourceLocation::toString).collect(Collectors.joining(", ")); - } - - private GeometryLoaderManager() {} -} diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/IGeometryBakingContext.java b/src/main/java/net/neoforged/neoforge/client/model/geometry/IGeometryBakingContext.java deleted file mode 100644 index 3b1950ce..00000000 --- a/src/main/java/net/neoforged/neoforge/client/model/geometry/IGeometryBakingContext.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.client.model.geometry; - -import com.mojang.math.Transformation; -import net.minecraft.client.renderer.block.model.ItemTransforms; -import net.minecraft.client.resources.model.Material; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.neoforge.client.NamedRenderTypeManager; -import net.neoforged.neoforge.client.RenderTypeGroup; -import org.jetbrains.annotations.Nullable; - -/** - * The context in which a geometry is being baked, providing information such as lighting and - * {@linkplain ItemTransforms transforms}, and allowing the user to create {@linkplain Material materials} and query - * {@linkplain RenderTypeGroup render types}. - * - * @see StandaloneGeometryBakingContext - * @see BlockGeometryBakingContext - */ -public interface IGeometryBakingContext { - /** - * {@return the name of the model being baked for logging and caching purposes.} - */ - String getModelName(); - - /** - * Checks if a material is present in the model. - * - * @param name The name of the material - * @return true if the material is present, false otherwise - */ - boolean hasMaterial(String name); - - /** - * Resolves the final texture name, taking into account texture aliases and replacements. - * - * @param name The name of the material - * @return The material, or the missing texture if not found - */ - Material getMaterial(String name); - - /** - * {@return true if this model should render in 3D in a GUI, false otherwise} - */ - boolean isGui3d(); - - /** - * {@return true if block lighting should be used for this model, false otherwise} - */ - boolean useBlockLight(); - - /** - * {@return true if per-vertex ambient occlusion should be used for this model, false otherwise} - */ - boolean useAmbientOcclusion(); - - /** - * {@return the transforms for display in item form.} - */ - ItemTransforms getTransforms(); - - /** - * {@return the root transformation to be applied to all variants of this model, regardless of item transforms.} - */ - Transformation getRootTransform(); - - /** - * {@return a hint of the render type this model should use. Custom loaders may ignore this.} - */ - @Nullable - ResourceLocation getRenderTypeHint(); - - /** - * Queries the visibility of a component of this model. - * - * @param component The component for which to query visibility - * @param fallback The default visibility if an override isn't found - * @return The visibility of the component - */ - boolean isComponentVisible(String component, boolean fallback); - - /** - * {@return a {@link RenderTypeGroup} with the given name, or the empty group if not found.} - */ - default RenderTypeGroup getRenderType(ResourceLocation name) { - return NamedRenderTypeManager.get(name); - } -} diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/IGeometryLoader.java b/src/main/java/net/neoforged/neoforge/client/model/geometry/IGeometryLoader.java deleted file mode 100644 index 67f2c7d0..00000000 --- a/src/main/java/net/neoforged/neoforge/client/model/geometry/IGeometryLoader.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.client.model.geometry; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; -import net.minecraft.server.packs.resources.ResourceManagerReloadListener; -import net.neoforged.neoforge.client.event.ModelEvent; -import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent; - -/** - * A loader for custom {@linkplain IUnbakedGeometry model geometries}. - *

    - * If you do any caching, you should implement {@link ResourceManagerReloadListener} and register it with - * {@link RegisterClientReloadListenersEvent}. - * - * @see ModelEvent.RegisterGeometryLoaders - * @see RegisterClientReloadListenersEvent - */ -public interface IGeometryLoader> { - T read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) throws JsonParseException; -} diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/IUnbakedGeometry.java b/src/main/java/net/neoforged/neoforge/client/model/geometry/IUnbakedGeometry.java deleted file mode 100644 index 79be568a..00000000 --- a/src/main/java/net/neoforged/neoforge/client/model/geometry/IUnbakedGeometry.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.client.model.geometry; - -import java.util.List; -import java.util.Set; -import java.util.function.Function; -import net.minecraft.client.renderer.block.model.BlockModel; -import net.minecraft.client.renderer.block.model.ItemOverride; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.Material; -import net.minecraft.client.resources.model.ModelBaker; -import net.minecraft.client.resources.model.ModelState; -import net.minecraft.client.resources.model.UnbakedModel; - -/** - * General interface for any model that can be baked, superset of vanilla {@link UnbakedModel}. - *

    - * Instances of this class ar usually created via {@link IGeometryLoader}. - * - * @see IGeometryLoader - * @see IGeometryBakingContext - */ -public interface IUnbakedGeometry> { - BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides); - - /** - * Resolve parents of nested {@link BlockModel}s which are later used in - * {@link IUnbakedGeometry#bake(IGeometryBakingContext, ModelBaker, Function, ModelState, List)} - * via {@link BlockModel#resolveDependencies(UnbakedModel.Resolver)} - */ - default void resolveDependencies(UnbakedModel.Resolver modelGetter, IGeometryBakingContext context) {} - - /** - * {@return a set of all the components whose visibility may be configured via {@link IGeometryBakingContext}} - */ - default Set getConfigurableComponentNames() { - return Set.of(); - } -} diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/SimpleUnbakedGeometry.java b/src/main/java/net/neoforged/neoforge/client/model/geometry/SimpleUnbakedGeometry.java deleted file mode 100644 index 1c8ea096..00000000 --- a/src/main/java/net/neoforged/neoforge/client/model/geometry/SimpleUnbakedGeometry.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.client.model.geometry; - -import java.util.List; -import java.util.function.Function; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.renderer.block.model.ItemOverride; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.Material; -import net.minecraft.client.resources.model.ModelBaker; -import net.minecraft.client.resources.model.ModelState; -import net.neoforged.neoforge.client.RenderTypeGroup; -import net.neoforged.neoforge.client.model.IModelBuilder; - -/** - * Base class for implementations of {@link IUnbakedGeometry} which do not wish to handle model creation themselves, - * instead supplying {@linkplain BakedQuad baked quads} through a builder. - */ -public abstract class SimpleUnbakedGeometry> implements IUnbakedGeometry { - @Override - public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function spriteGetter, ModelState modelState, List overrides) { - TextureAtlasSprite particle = spriteGetter.apply(context.getMaterial("particle")); - - var renderTypeHint = context.getRenderTypeHint(); - var renderTypes = renderTypeHint != null ? context.getRenderType(renderTypeHint) : RenderTypeGroup.EMPTY; - IModelBuilder builder = IModelBuilder.of(context.useAmbientOcclusion(), context.useBlockLight(), context.isGui3d(), - context.getTransforms(), particle, renderTypes); - - addQuads(context, builder, baker, spriteGetter, modelState); - - return builder.build(); - } - - protected abstract void addQuads(IGeometryBakingContext owner, IModelBuilder modelBuilder, ModelBaker baker, Function spriteGetter, ModelState modelTransform); -} diff --git a/src/main/java/net/neoforged/neoforge/client/model/geometry/StandaloneGeometryBakingContext.java b/src/main/java/net/neoforged/neoforge/client/model/geometry/StandaloneGeometryBakingContext.java deleted file mode 100644 index f983b777..00000000 --- a/src/main/java/net/neoforged/neoforge/client/model/geometry/StandaloneGeometryBakingContext.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.client.model.geometry; - -import com.google.common.base.Predicates; -import com.mojang.math.Transformation; -import it.unimi.dsi.fastutil.objects.Object2BooleanMap; -import java.util.Map; -import java.util.function.BiPredicate; -import java.util.function.Function; -import java.util.function.Predicate; -import net.minecraft.client.renderer.block.model.ItemTransforms; -import net.minecraft.client.renderer.texture.MissingTextureAtlasSprite; -import net.minecraft.client.renderer.texture.TextureAtlas; -import net.minecraft.client.resources.model.Material; -import net.minecraft.resources.ResourceLocation; -import org.jetbrains.annotations.Nullable; - -/** - * A {@linkplain IGeometryBakingContext geometry baking context} that is not bound to block/item model loading. - */ -public class StandaloneGeometryBakingContext implements IGeometryBakingContext { - public static final ResourceLocation LOCATION = ResourceLocation.fromNamespaceAndPath("neoforge", "standalone"); - - public static final StandaloneGeometryBakingContext INSTANCE = create(LOCATION); - - public static StandaloneGeometryBakingContext create(ResourceLocation modelName) { - return builder().build(modelName); - } - - public static StandaloneGeometryBakingContext create(Map textures) { - return create(LOCATION, textures); - } - - public static StandaloneGeometryBakingContext create(ResourceLocation modelName, Map textures) { - return builder().withTextures(textures, MissingTextureAtlasSprite.getLocation()).build(modelName); - } - - private final ResourceLocation modelName; - private final Predicate materialCheck; - private final Function materialLookup; - private final boolean isGui3d; - private final boolean useBlockLight; - private final boolean useAmbientOcclusion; - private final ItemTransforms transforms; - private final Transformation rootTransform; - @Nullable - private final ResourceLocation renderTypeHint; - private final BiPredicate visibilityTest; - - private StandaloneGeometryBakingContext(ResourceLocation modelName, Predicate materialCheck, - Function materialLookup, boolean isGui3d, - boolean useBlockLight, boolean useAmbientOcclusion, - ItemTransforms transforms, Transformation rootTransform, - @Nullable ResourceLocation renderTypeHint, - BiPredicate visibilityTest) { - this.modelName = modelName; - this.materialCheck = materialCheck; - this.materialLookup = materialLookup; - this.isGui3d = isGui3d; - this.useBlockLight = useBlockLight; - this.useAmbientOcclusion = useAmbientOcclusion; - this.transforms = transforms; - this.rootTransform = rootTransform; - this.renderTypeHint = renderTypeHint; - this.visibilityTest = visibilityTest; - } - - @Override - public String getModelName() { - return modelName.toString(); - } - - @Override - public boolean hasMaterial(String name) { - return materialCheck.test(name); - } - - @Override - public Material getMaterial(String name) { - return materialLookup.apply(name); - } - - @Override - public boolean isGui3d() { - return isGui3d; - } - - @Override - public boolean useBlockLight() { - return useBlockLight; - } - - @Override - public boolean useAmbientOcclusion() { - return useAmbientOcclusion; - } - - @Override - public ItemTransforms getTransforms() { - return transforms; - } - - @Override - public Transformation getRootTransform() { - return rootTransform; - } - - @Nullable - @Override - public ResourceLocation getRenderTypeHint() { - return renderTypeHint; - } - - @Override - public boolean isComponentVisible(String component, boolean fallback) { - return visibilityTest.test(component, fallback); - } - - public static Builder builder() { - return new Builder(); - } - - public static Builder builder(IGeometryBakingContext parent) { - return new Builder(parent); - } - - public static final class Builder { - private static final Material NO_MATERIAL = new Material(TextureAtlas.LOCATION_BLOCKS, MissingTextureAtlasSprite.getLocation()); - private Predicate materialCheck = Predicates.alwaysFalse(); - private Function materialLookup = $ -> NO_MATERIAL; - private boolean isGui3d = true; - private boolean useBlockLight = true; - private boolean useAmbientOcclusion = true; - private ItemTransforms transforms = ItemTransforms.NO_TRANSFORMS; - private Transformation rootTransform = Transformation.identity(); - @Nullable - private ResourceLocation renderTypeHint; - private BiPredicate visibilityTest = (c, def) -> def; - - private Builder() {} - - private Builder(IGeometryBakingContext parent) { - this.materialCheck = parent::hasMaterial; - this.materialLookup = parent::getMaterial; - this.isGui3d = parent.isGui3d(); - this.useBlockLight = parent.useBlockLight(); - this.useAmbientOcclusion = parent.useAmbientOcclusion(); - this.transforms = parent.getTransforms(); - this.rootTransform = parent.getRootTransform(); - this.renderTypeHint = parent.getRenderTypeHint(); - this.visibilityTest = parent::isComponentVisible; - } - - public Builder withTextures(Map textures, ResourceLocation defaultTexture) { - return withTextures(TextureAtlas.LOCATION_BLOCKS, textures, defaultTexture); - } - - public Builder withTextures(ResourceLocation atlasLocation, Map textures, ResourceLocation defaultTexture) { - this.materialCheck = textures::containsKey; - this.materialLookup = name -> new Material(atlasLocation, textures.getOrDefault(name, defaultTexture)); - return this; - } - - public Builder withMaterials(Map materials, Material defaultMaterial) { - this.materialCheck = materials::containsKey; - this.materialLookup = name -> materials.getOrDefault(name, defaultMaterial); - return this; - } - - public Builder withGui3d(boolean isGui3d) { - this.isGui3d = isGui3d; - return this; - } - - public Builder withUseBlockLight(boolean useBlockLight) { - this.useBlockLight = useBlockLight; - return this; - } - - public Builder withUseAmbientOcclusion(boolean useAmbientOcclusion) { - this.useAmbientOcclusion = useAmbientOcclusion; - return this; - } - - public Builder withTransforms(ItemTransforms transforms) { - this.transforms = transforms; - return this; - } - - public Builder withRootTransform(Transformation rootTransform) { - this.rootTransform = rootTransform; - return this; - } - - public Builder withRenderTypeHint(ResourceLocation renderTypeHint) { - this.renderTypeHint = renderTypeHint; - return this; - } - - public Builder withVisibleComponents(Object2BooleanMap parts) { - this.visibilityTest = parts::getOrDefault; - return this; - } - - public StandaloneGeometryBakingContext build(ResourceLocation modelName) { - return new StandaloneGeometryBakingContext(modelName, materialCheck, materialLookup, isGui3d, useBlockLight, useAmbientOcclusion, transforms, rootTransform, renderTypeHint, visibilityTest); - } - } -} diff --git a/src/main/java/net/neoforged/neoforge/client/model/item/DynamicFluidContainerModel.java b/src/main/java/net/neoforged/neoforge/client/model/item/DynamicFluidContainerModel.java new file mode 100644 index 00000000..d2f69a1d --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/model/item/DynamicFluidContainerModel.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.model.item; + +import com.mojang.math.Transformation; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import net.minecraft.client.color.item.Constant; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.item.BlockModelWrapper; +import net.minecraft.client.renderer.item.ItemModel; +import net.minecraft.client.renderer.item.ItemModelResolver; +import net.minecraft.client.renderer.item.ItemStackRenderState; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.model.BlockModelRotation; +import net.minecraft.client.resources.model.Material; +import net.minecraft.client.resources.model.ModelState; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.Fluids; +import net.neoforged.neoforge.client.ClientHooks; +import net.neoforged.neoforge.client.NeoForgeRenderTypes; +import net.neoforged.neoforge.client.RenderTypeGroup; +import net.neoforged.neoforge.client.color.item.FluidContentsTint; +import net.neoforged.neoforge.client.extensions.common.IClientFluidTypeExtensions; +import net.neoforged.neoforge.client.model.QuadTransformers; +import net.neoforged.neoforge.client.model.SimpleModelState; +import net.neoforged.neoforge.client.model.UnbakedCompositeModel; +import net.neoforged.neoforge.client.model.UnbakedElementsHelper; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.FluidUtil; +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +/** + * A dynamic fluid container model, capable of re-texturing itself at runtime to match the contained fluid. + *

    + * Composed of a base layer, a fluid layer (applied with a mask) and a cover layer (optionally applied with a mask). + * The entire model may optionally be flipped if the fluid is gaseous, and the fluid layer may glow if light-emitting. + */ +public class DynamicFluidContainerModel implements ItemModel { + // Depth offsets to prevent Z-fighting + private static final Transformation FLUID_TRANSFORM = new Transformation(new Vector3f(), new Quaternionf(), new Vector3f(1, 1, 1.002f), new Quaternionf()); + private static final Transformation COVER_TRANSFORM = new Transformation(new Vector3f(), new Quaternionf(), new Vector3f(1, 1, 1.004f), new Quaternionf()); + + private static RenderTypeGroup getLayerRenderTypes(boolean unlit) { + return new RenderTypeGroup(RenderType.translucent(), unlit ? NeoForgeRenderTypes.ITEM_UNSORTED_UNLIT_TRANSLUCENT.get() : NeoForgeRenderTypes.ITEM_UNSORTED_TRANSLUCENT.get()); + } + + private final Unbaked unbakedModel; + private final BakingContext bakingContext; + private final ItemTransforms itemTransforms; + private final Map cache = new IdentityHashMap<>(); // contains all the baked models since they'll never change + + private DynamicFluidContainerModel(Unbaked unbakedModel, BakingContext bakingContext) { + this.unbakedModel = unbakedModel; + this.bakingContext = bakingContext; + // Source ItemTransforms from the base item model + var baseItemModel = bakingContext.blockModelBaker().getModel(ResourceLocation.withDefaultNamespace("item/generated")); + if (baseItemModel == null) { + throw new IllegalStateException("Failed to access item/generated model"); + } + this.itemTransforms = baseItemModel.getTransforms(); + } + + private ItemModel bakeModelForFluid(Fluid fluid) { + var sprites = bakingContext.blockModelBaker().sprites(); + + Material particleLocation = unbakedModel.textures.particle.map(ClientHooks::getBlockMaterial).orElse(null); + Material baseLocation = unbakedModel.textures.base.map(ClientHooks::getBlockMaterial).orElse(null); + Material fluidMaskLocation = unbakedModel.textures.fluid.map(ClientHooks::getBlockMaterial).orElse(null); + Material coverLocation = unbakedModel.textures.cover.map(ClientHooks::getBlockMaterial).orElse(null); + + TextureAtlasSprite baseSprite = baseLocation != null ? sprites.get(baseLocation) : null; + TextureAtlasSprite fluidSprite = fluid != Fluids.EMPTY ? sprites.get(ClientHooks.getBlockMaterial(IClientFluidTypeExtensions.of(fluid).getStillTexture())) : null; + TextureAtlasSprite coverSprite = (coverLocation != null && (!unbakedModel.coverIsMask || baseLocation != null)) ? sprites.get(coverLocation) : null; + + TextureAtlasSprite particleSprite = particleLocation != null ? sprites.get(particleLocation) : null; + + if (particleSprite == null) particleSprite = fluidSprite; + if (particleSprite == null) particleSprite = baseSprite; + if (particleSprite == null && !unbakedModel.coverIsMask) particleSprite = coverSprite; + + // If the fluid is lighter than air, rotate 180deg to turn it upside down + ModelState state = BlockModelRotation.X0_Y0; + if (unbakedModel.flipGas && fluid != Fluids.EMPTY && fluid.getFluidType().isLighterThanAir()) { + state = new SimpleModelState( + state.getRotation().compose( + new Transformation(null, new Quaternionf(0, 0, 1, 0), null, null))); + } + + // We need to disable GUI 3D and block lighting for this to render properly + var modelBuilder = UnbakedCompositeModel.Baked.builder(true, false, false, particleSprite, itemTransforms); + + var normalRenderTypes = getLayerRenderTypes(false); + + if (baseLocation != null) { + // Base texture + var unbaked = UnbakedElementsHelper.createUnbakedItemElements(0, baseSprite); + var quads = UnbakedElementsHelper.bakeElements(unbaked, $ -> baseSprite, state); + modelBuilder.addQuads(normalRenderTypes, quads); + } + + if (fluidMaskLocation != null && fluidSprite != null) { + TextureAtlasSprite templateSprite = sprites.get(fluidMaskLocation); + // Fluid layer + var transformedState = new SimpleModelState(state.getRotation().compose(FLUID_TRANSFORM), state.isUvLocked()); + var unbaked = UnbakedElementsHelper.createUnbakedItemMaskElements(1, templateSprite); // Use template as mask + var quads = UnbakedElementsHelper.bakeElements(unbaked, $ -> fluidSprite, transformedState); // Bake with fluid texture + + var emissive = unbakedModel.applyFluidLuminosity && fluid.getFluidType().getLightLevel() > 0; + var renderTypes = getLayerRenderTypes(emissive); + if (emissive) QuadTransformers.settingMaxEmissivity().processInPlace(quads); + + modelBuilder.addQuads(renderTypes, quads); + } + + if (coverSprite != null) { + var sprite = unbakedModel.coverIsMask ? baseSprite : coverSprite; + // Cover/overlay + var transformedState = new SimpleModelState(state.getRotation().compose(COVER_TRANSFORM), state.isUvLocked()); + var unbaked = UnbakedElementsHelper.createUnbakedItemMaskElements(2, coverSprite); // Use cover as mask + var quads = UnbakedElementsHelper.bakeElements(unbaked, $ -> sprite, transformedState); // Bake with selected texture + modelBuilder.addQuads(normalRenderTypes, quads); + } + + modelBuilder.setParticle(particleSprite); + + return new BlockModelWrapper(modelBuilder.build(), List.of(new Constant(-1), FluidContentsTint.INSTANCE)); + } + + @Override + public void update(ItemStackRenderState renderState, ItemStack stack, ItemModelResolver modelResolver, ItemDisplayContext displayContext, @Nullable ClientLevel level, @Nullable LivingEntity entity, int p_387820_) { + var fluid = FluidUtil.getFluidContained(stack) + .map(FluidStack::getFluid) + // not a fluid item apparently + .orElse(unbakedModel.fluid); + + cache.computeIfAbsent(fluid, this::bakeModelForFluid) + .update(renderState, stack, modelResolver, displayContext, level, entity, p_387820_); + } + + public record Textures( + Optional particle, + Optional base, + Optional fluid, + Optional cover) { + public static final Codec CODEC = RecordCodecBuilder.create( + instance -> instance + .group( + ResourceLocation.CODEC.optionalFieldOf("particle").forGetter(Textures::particle), + ResourceLocation.CODEC.optionalFieldOf("base").forGetter(Textures::base), + ResourceLocation.CODEC.optionalFieldOf("fluid").forGetter(Textures::fluid), + ResourceLocation.CODEC.optionalFieldOf("cover").forGetter(Textures::cover)) + .apply(instance, Textures::new)) + .validate(textures -> { + if (textures.particle.isPresent() || textures.base.isPresent() || textures.fluid.isPresent() || textures.cover.isPresent()) { + return DataResult.success(textures); + } + return DataResult.error(() -> "Dynamic fluid container model requires at least one particle, base, fluid or cover texture."); + }); + } + + public record Unbaked(Textures textures, Fluid fluid, boolean flipGas, boolean coverIsMask, boolean applyFluidLuminosity) implements ItemModel.Unbaked { + + public static final MapCodec MAP_CODEC = RecordCodecBuilder.mapCodec( + instance -> instance + .group( + Textures.CODEC.fieldOf("textures").forGetter(Unbaked::textures), + BuiltInRegistries.FLUID.byNameCodec().fieldOf("fluid").forGetter(Unbaked::fluid), + Codec.BOOL.optionalFieldOf("flip_gas", false).forGetter(Unbaked::flipGas), + Codec.BOOL.optionalFieldOf("cover_is_mask", true).forGetter(Unbaked::coverIsMask), + Codec.BOOL.optionalFieldOf("apply_fluid_luminosity", true).forGetter(Unbaked::applyFluidLuminosity)) + .apply(instance, Unbaked::new)); + @Override + public MapCodec type() { + return MAP_CODEC; + } + + @Override + public ItemModel bake(BakingContext bakingContext) { + return new DynamicFluidContainerModel(this, bakingContext); + } + + @Override + public void resolveDependencies(Resolver resolver) { + //No dependencies + } + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/model/renderable/package-info.java b/src/main/java/net/neoforged/neoforge/client/model/item/package-info.java similarity index 85% rename from src/main/java/net/neoforged/neoforge/client/model/renderable/package-info.java rename to src/main/java/net/neoforged/neoforge/client/model/item/package-info.java index c5923aab..87c7c56d 100644 --- a/src/main/java/net/neoforged/neoforge/client/model/renderable/package-info.java +++ b/src/main/java/net/neoforged/neoforge/client/model/item/package-info.java @@ -6,7 +6,7 @@ @FieldsAreNonnullByDefault @MethodsReturnNonnullByDefault @ParametersAreNonnullByDefault -package net.neoforged.neoforge.client.model.renderable; +package net.neoforged.neoforge.client.model.item; import javax.annotation.ParametersAreNonnullByDefault; import net.minecraft.FieldsAreNonnullByDefault; diff --git a/src/main/java/net/neoforged/neoforge/client/model/obj/ObjLoader.java b/src/main/java/net/neoforged/neoforge/client/model/obj/ObjLoader.java index 65d2b939..0bf9931d 100644 --- a/src/main/java/net/neoforged/neoforge/client/model/obj/ObjLoader.java +++ b/src/main/java/net/neoforged/neoforge/client/model/obj/ObjLoader.java @@ -7,17 +7,21 @@ import com.google.common.collect.Maps; import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParseException; +import com.mojang.math.Transformation; import java.io.FileNotFoundException; +import java.util.HashMap; import java.util.Map; import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.block.model.BlockModel; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.server.packs.resources.ResourceManagerReloadListener; import net.minecraft.util.GsonHelper; -import net.neoforged.neoforge.client.model.geometry.IGeometryLoader; +import net.neoforged.neoforge.client.model.UnbakedModelLoader; /** * A loader for {@link ObjModel OBJ models}. @@ -25,7 +29,7 @@ * Allows the user to enable automatic face culling, toggle quad shading, flip UVs, render emissively and specify a * {@link ObjMaterialLibrary material library} override. */ -public class ObjLoader implements IGeometryLoader, ResourceManagerReloadListener { +public class ObjLoader implements UnbakedModelLoader, ResourceManagerReloadListener { public static ObjLoader INSTANCE = new ObjLoader(); private final Map modelCache = Maps.newConcurrentMap(); @@ -40,7 +44,7 @@ public void onResourceManagerReload(ResourceManager resourceManager) { } @Override - public ObjModel read(JsonObject jsonObject, JsonDeserializationContext deserializationContext) { + public ObjModel read(JsonObject jsonObject, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { if (!jsonObject.has("model")) throw new JsonParseException("OBJ Loader requires a 'model' key that points to a valid .OBJ model."); @@ -52,7 +56,21 @@ public ObjModel read(JsonObject jsonObject, JsonDeserializationContext deseriali boolean emissiveAmbient = GsonHelper.getAsBoolean(jsonObject, "emissive_ambient", true); String mtlOverride = GsonHelper.getAsString(jsonObject, "mtl_override", null); - return loadModel(new ObjModel.ModelSettings(ResourceLocation.parse(modelLocation), automaticCulling, shadeQuads, flipV, emissiveAmbient, mtlOverride)); + final Map partVisibility = new HashMap<>(); + if (jsonObject.has("visibility")) { + JsonObject visibility = GsonHelper.getAsJsonObject(jsonObject, "visibility"); + for (Map.Entry part : visibility.entrySet()) { + partVisibility.put(part.getKey(), part.getValue().getAsBoolean()); + } + } + + Transformation transformation = Transformation.identity(); + if (jsonObject.has("transform")) { + JsonElement transform = jsonObject.get("transform"); + transformation = BlockModel.GSON.fromJson(transform, Transformation.class); + } + + return loadModel(new ObjModel.ModelSettings(ResourceLocation.parse(modelLocation), automaticCulling, shadeQuads, flipV, emissiveAmbient, mtlOverride, partVisibility, transformation)); } public ObjModel loadModel(ObjModel.ModelSettings settings) { diff --git a/src/main/java/net/neoforged/neoforge/client/model/obj/ObjModel.java b/src/main/java/net/neoforged/neoforge/client/model/obj/ObjModel.java index 25404e54..98625b04 100644 --- a/src/main/java/net/neoforged/neoforge/client/model/obj/ObjModel.java +++ b/src/main/java/net/neoforged/neoforge/client/model/obj/ObjModel.java @@ -7,40 +7,33 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.collect.Sets; import com.mojang.math.Transformation; import java.io.IOException; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; import joptsimple.internal.Strings; +import net.minecraft.client.data.models.model.TextureSlot; import net.minecraft.client.renderer.LightTexture; import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.renderer.block.model.TextureSlots; import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.Material; +import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.ModelBaker; import net.minecraft.client.resources.model.ModelState; -import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.client.resources.model.SimpleBakedModel; import net.minecraft.core.Direction; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.Mth; +import net.minecraft.util.context.ContextMap; import net.minecraft.world.phys.Vec2; -import net.neoforged.neoforge.client.model.IModelBuilder; -import net.neoforged.neoforge.client.model.geometry.IGeometryBakingContext; -import net.neoforged.neoforge.client.model.geometry.SimpleUnbakedGeometry; -import net.neoforged.neoforge.client.model.geometry.UnbakedGeometryHelper; +import net.neoforged.neoforge.client.RenderTypeGroup; +import net.neoforged.neoforge.client.model.ExtendedUnbakedModel; +import net.neoforged.neoforge.client.model.NeoForgeModelProperties; import net.neoforged.neoforge.client.model.pipeline.QuadBakingVertexConsumer; -import net.neoforged.neoforge.client.model.renderable.CompositeRenderable; -import net.neoforged.neoforge.client.textures.UnitTextureAtlasSprite; import org.apache.commons.lang3.tuple.Pair; import org.jetbrains.annotations.Nullable; import org.joml.Vector3f; @@ -52,7 +45,7 @@ * Supports positions, texture coordinates, normals and colors. The {@link ObjMaterialLibrary material library} * has support for numerous features, including support for {@link ResourceLocation} textures (non-standard). */ -public class ObjModel extends SimpleUnbakedGeometry { +public class ObjModel implements ExtendedUnbakedModel { private static final Vector4f COLOR_WHITE = new Vector4f(1, 1, 1, 1); private static final Vec2[] DEFAULT_COORDS = { new Vec2(0, 0), @@ -62,8 +55,6 @@ public class ObjModel extends SimpleUnbakedGeometry { }; private final Map parts = Maps.newLinkedHashMap(); - private final Set rootComponentNames = Collections.unmodifiableSet(parts.keySet()); - private Set allComponentNames; private final List positions = Lists.newArrayList(); private final List texCoords = Lists.newArrayList(); @@ -78,6 +69,8 @@ public class ObjModel extends SimpleUnbakedGeometry { public final String mtlOverride; public final ResourceLocation modelLocation; + public final Map partVisibility; + public final Transformation rootTransform; private ObjModel(ModelSettings settings) { this.modelLocation = settings.modelLocation; @@ -86,6 +79,8 @@ private ObjModel(ModelSettings settings) { this.flipV = settings.flipV; this.emissiveAmbient = settings.emissiveAmbient; this.mtlOverride = settings.mtlOverride; + this.partVisibility = settings.partVisibility; + this.rootTransform = settings.rootTransform; } public static ObjModel parse(ObjTokenizer tokenizer, ModelSettings settings) throws IOException { @@ -292,26 +287,6 @@ static Vector4f parseVector4(String[] line) { }; } - @Override - protected void addQuads(IGeometryBakingContext owner, IModelBuilder modelBuilder, ModelBaker baker, Function spriteGetter, ModelState modelTransform) { - parts.values().stream().filter(part -> owner.isComponentVisible(part.name(), true)) - .forEach(part -> part.addQuads(owner, modelBuilder, baker, spriteGetter, modelTransform)); - } - - public Set getRootComponentNames() { - return rootComponentNames; - } - - @Override - public Set getConfigurableComponentNames() { - if (allComponentNames != null) - return allComponentNames; - var names = new HashSet(); - for (var group : parts.values()) - group.addNamesRecursively(names); - return allComponentNames = Collections.unmodifiableSet(names); - } - private Pair makeQuad(int[][] indices, int tintIndex, Vector4f colorTint, Vector4f ambientColor, TextureAtlasSprite texture, Transformation transform) { boolean needsNormalRecalculation = false; for (int[] ints : indices) { @@ -433,16 +408,22 @@ private Pair makeQuad(int[][] indices, int tintIndex, Vect return Pair.of(quadBaker.bakeQuad(), cull); } - public CompositeRenderable bakeRenderable(IGeometryBakingContext configuration) { - var builder = CompositeRenderable.builder(); + public boolean isComponentVisible(String part, boolean fallback) { + return partVisibility.getOrDefault(part, fallback); + } - for (var entry : parts.entrySet()) { - var name = entry.getKey(); - var part = entry.getValue(); - part.bake(builder.child(name), configuration); - } + @Override + public BakedModel bake(TextureSlots slots, ModelBaker baker, ModelState state, boolean useAmbientOcclusion, boolean usesBlockLight, ItemTransforms transforms, ContextMap additionalProperties) { + var builder = new SimpleBakedModel.Builder(useAmbientOcclusion, usesBlockLight, true, transforms); + builder.particle(baker.findSprite(slots, TextureSlot.PARTICLE.getId())); + parts.values().stream().filter(part -> isComponentVisible(part.name(), true)) + .forEach(part -> part.addQuads(builder, slots, baker, state, useAmbientOcclusion, usesBlockLight, transforms)); + return builder.build(additionalProperties.getOrDefault(NeoForgeModelProperties.RENDER_TYPE, RenderTypeGroup.EMPTY)); + } - return builder.get(); + @Override + public void resolveDependencies(Resolver resolver) { + // no dependencies } public class ModelObject { @@ -458,26 +439,12 @@ public String name() { return name; } - public void addQuads(IGeometryBakingContext owner, IModelBuilder modelBuilder, ModelBaker baker, Function spriteGetter, ModelState modelTransform) { + public void addQuads(SimpleBakedModel.Builder builder, TextureSlots slots, ModelBaker baker, ModelState state, boolean useAmbientOcclusion, boolean usesBlockLight, ItemTransforms transforms) { for (ModelMesh mesh : meshes) { - mesh.addQuads(owner, modelBuilder, spriteGetter, modelTransform); + mesh.addQuads(builder, slots, baker, state, useAmbientOcclusion, usesBlockLight, transforms); } } - public void bake(CompositeRenderable.PartBuilder builder, IGeometryBakingContext configuration) { - for (ModelMesh mesh : this.meshes) { - mesh.bake(builder, configuration); - } - } - - public Collection getTextures(IGeometryBakingContext owner, Function modelGetter, Set> missingTextureErrors) { - return meshes.stream() - .flatMap(mesh -> mesh.mat != null - ? Stream.of(UnbakedGeometryHelper.resolveDirtyMaterial(mesh.mat.diffuseColorMap, owner)) - : Stream.of()) - .collect(Collectors.toSet()); - } - protected void addNamesRecursively(Set names) { names.add(name()); } @@ -491,31 +458,11 @@ public class ModelGroup extends ModelObject { } @Override - public void addQuads(IGeometryBakingContext owner, IModelBuilder modelBuilder, ModelBaker baker, Function spriteGetter, ModelState modelTransform) { - super.addQuads(owner, modelBuilder, baker, spriteGetter, modelTransform); + public void addQuads(SimpleBakedModel.Builder builder, TextureSlots slots, ModelBaker baker, ModelState state, boolean useAmbientOcclusion, boolean usesBlockLight, ItemTransforms transforms) { + super.addQuads(builder, slots, baker, state, useAmbientOcclusion, usesBlockLight, transforms); - parts.values().stream().filter(part -> owner.isComponentVisible(part.name(), true)) - .forEach(part -> part.addQuads(owner, modelBuilder, baker, spriteGetter, modelTransform)); - } - - @Override - public void bake(CompositeRenderable.PartBuilder builder, IGeometryBakingContext configuration) { - super.bake(builder, configuration); - - for (var entry : parts.entrySet()) { - var name = entry.getKey(); - var part = entry.getValue(); - part.bake(builder.child(name), configuration); - } - } - - @Override - public Collection getTextures(IGeometryBakingContext owner, Function modelGetter, Set> missingTextureErrors) { - Set combined = Sets.newHashSet(); - combined.addAll(super.getTextures(owner, modelGetter, missingTextureErrors)); - for (ModelObject part : parts.values()) - combined.addAll(part.getTextures(owner, modelGetter, missingTextureErrors)); - return combined; + parts.values().stream().filter(part -> isComponentVisible("%s.%s".formatted(name(), part.name()), true)) + .forEach(part -> part.addQuads(builder, slots, baker, state, useAmbientOcclusion, usesBlockLight, transforms)); } @Override @@ -538,46 +485,27 @@ public ModelMesh(@Nullable ObjMaterialLibrary.Material currentMat, @Nullable Str this.smoothingGroup = currentSmoothingGroup; } - public void addQuads(IGeometryBakingContext owner, IModelBuilder modelBuilder, Function spriteGetter, ModelState modelTransform) { + public void addQuads(SimpleBakedModel.Builder builder, TextureSlots slots, ModelBaker baker, ModelState state, boolean useAmbientOcclusion, boolean usesBlockLight, ItemTransforms transforms) { if (mat == null) return; - TextureAtlasSprite texture = spriteGetter.apply(UnbakedGeometryHelper.resolveDirtyMaterial(mat.diffuseColorMap, owner)); + TextureAtlasSprite texture = baker.findSprite(slots, mat.diffuseColorMap); int tintIndex = mat.diffuseTintIndex; Vector4f colorTint = mat.diffuseColor; - var rootTransform = owner.getRootTransform(); - var transform = rootTransform.isIdentity() ? modelTransform.getRotation() : modelTransform.getRotation().compose(rootTransform); + var rootTransform = ObjModel.this.rootTransform; + var transform = rootTransform.isIdentity() ? state.getRotation() : state.getRotation().compose(rootTransform); for (int[][] face : faces) { Pair quad = makeQuad(face, tintIndex, colorTint, mat.ambientColor, texture, transform); if (quad.getRight() == null) - modelBuilder.addUnculledFace(quad.getLeft()); + builder.addUnculledFace(quad.getLeft()); else - modelBuilder.addCulledFace(quad.getRight(), quad.getLeft()); - } - } - - public void bake(CompositeRenderable.PartBuilder builder, IGeometryBakingContext configuration) { - ObjMaterialLibrary.Material mat = this.mat; - if (mat == null) - return; - int tintIndex = mat.diffuseTintIndex; - Vector4f colorTint = mat.diffuseColor; - - final List quads = new ArrayList<>(); - - for (var face : this.faces) { - var pair = makeQuad(face, tintIndex, colorTint, mat.ambientColor, UnitTextureAtlasSprite.INSTANCE, Transformation.identity()); - quads.add(pair.getLeft()); + builder.addCulledFace(quad.getRight(), quad.getLeft()); } - - ResourceLocation textureLocation = UnbakedGeometryHelper.resolveDirtyMaterial(mat.diffuseColorMap, configuration).texture(); - ResourceLocation texturePath = ResourceLocation.fromNamespaceAndPath(textureLocation.getNamespace(), "textures/" + textureLocation.getPath() + ".png"); - - builder.addMesh(texturePath, quads); } } public record ModelSettings(ResourceLocation modelLocation, boolean automaticCulling, boolean shadeQuads, boolean flipV, - boolean emissiveAmbient, @Nullable String mtlOverride) {} + boolean emissiveAmbient, @Nullable String mtlOverride, + Map partVisibility, Transformation rootTransform) {} } diff --git a/src/main/java/net/neoforged/neoforge/client/model/renderable/BakedModelRenderable.java b/src/main/java/net/neoforged/neoforge/client/model/renderable/BakedModelRenderable.java deleted file mode 100644 index ec17ba24..00000000 --- a/src/main/java/net/neoforged/neoforge/client/model/renderable/BakedModelRenderable.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.client.model.renderable; - -import com.mojang.blaze3d.vertex.PoseStack; -import java.util.Arrays; -import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.client.resources.model.BakedModel; -import net.minecraft.client.resources.model.ModelResourceLocation; -import net.minecraft.core.Direction; -import net.minecraft.util.RandomSource; -import net.minecraft.util.Unit; -import net.minecraft.world.inventory.InventoryMenu; -import net.minecraft.world.level.block.state.BlockState; -import net.neoforged.neoforge.client.event.ModelEvent; -import net.neoforged.neoforge.client.model.data.ModelData; -import org.jetbrains.annotations.Nullable; -import org.joml.Vector4f; - -/** - * {@linkplain IRenderable Renderable} wrapper for {@linkplain BakedModel baked models}. - *

    - * The context can provide the {@link BlockState}, faces to be rendered, a {@link RandomSource} and seed, - * a {@link ModelData} instance, and a {@link Vector4f tint}. - * - * @see Context - */ -public class BakedModelRenderable implements IRenderable { - /** - * Constructs a {@link BakedModelRenderable} from the given model location. - * The model is expected to have been baked ahead of time. - * - * @see ModelEvent.RegisterAdditional - */ - public static BakedModelRenderable of(ModelResourceLocation model) { - return of(Minecraft.getInstance().getModelManager().getModel(model)); - } - - /** - * Constructs a {@link BakedModelRenderable} from the given baked model. - */ - public static BakedModelRenderable of(BakedModel model) { - return new BakedModelRenderable(model); - } - - private final BakedModel model; - - private BakedModelRenderable(BakedModel model) { - this.model = model; - } - - @Override - public void render(PoseStack poseStack, MultiBufferSource bufferSource, ITextureRenderTypeLookup textureRenderTypeLookup, int lightmap, int overlay, float partialTick, Context context) { - var buffer = bufferSource.getBuffer(textureRenderTypeLookup.get(InventoryMenu.BLOCK_ATLAS)); - var tint = context.tint(); - var randomSource = context.randomSource(); - for (Direction direction : context.faces()) { - randomSource.setSeed(context.seed()); - // Given the lack of context, the requested render type has to be null to ensure the model renders all of its geometry - for (BakedQuad quad : model.getQuads(context.state(), direction, randomSource, context.data(), null)) - buffer.putBulkData(poseStack.last(), quad, tint.x(), tint.y(), tint.z(), tint.w(), lightmap, overlay, true); - } - } - - public IRenderable withContext(ModelData modelData) { - return withContext(new Context(modelData)); - } - - public IRenderable withModelDataContext() { - return (poseStack, bufferSource, textureRenderTypeLookup, lightmap, overlay, partialTick, context) -> render(poseStack, bufferSource, textureRenderTypeLookup, lightmap, overlay, partialTick, new Context(context)); - } - - public record Context(@Nullable BlockState state, Direction[] faces, RandomSource randomSource, long seed, ModelData data, Vector4f tint) { - - private static final Direction[] ALL_FACES_AND_NULL = Arrays.copyOf(Direction.values(), Direction.values().length + 1); - private static final Vector4f WHITE = new Vector4f(1, 1, 1, 1); - public Context(ModelData data) { - this(null, ALL_FACES_AND_NULL, RandomSource.create(), 42, data, WHITE); - } - } -} diff --git a/src/main/java/net/neoforged/neoforge/client/model/renderable/CompositeRenderable.java b/src/main/java/net/neoforged/neoforge/client/model/renderable/CompositeRenderable.java deleted file mode 100644 index d4fec4b8..00000000 --- a/src/main/java/net/neoforged/neoforge/client/model/renderable/CompositeRenderable.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.client.model.renderable; - -import com.google.common.collect.ImmutableMap; -import com.mojang.blaze3d.vertex.PoseStack; -import java.util.ArrayList; -import java.util.List; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.block.model.BakedQuad; -import net.minecraft.resources.ResourceLocation; -import org.jetbrains.annotations.Nullable; -import org.joml.Matrix4f; - -/** - * A renderable object composed of a hierarchy of parts, each made up of a number of meshes. - *

    - * Each mesh renders a set of quads using a different texture. - * - * @see Builder - */ -public class CompositeRenderable implements IRenderable { - private final List components = new ArrayList<>(); - - private CompositeRenderable() {} - - @Override - public void render(PoseStack poseStack, MultiBufferSource bufferSource, ITextureRenderTypeLookup textureRenderTypeLookup, int lightmap, int overlay, float partialTick, Transforms context) { - for (var component : components) - component.render(poseStack, bufferSource, textureRenderTypeLookup, lightmap, overlay, context); - } - - public static Builder builder() { - return new Builder(); - } - - private static class Component { - private final String name; - private final List children = new ArrayList<>(); - private final List meshes = new ArrayList<>(); - - public Component(String name) { - this.name = name; - } - - public void render(PoseStack poseStack, MultiBufferSource bufferSource, ITextureRenderTypeLookup textureRenderTypeLookup, int lightmap, int overlay, Transforms context) { - Matrix4f matrix = context.getTransform(name); - if (matrix != null) { - poseStack.pushPose(); - poseStack.mulPose(matrix); - } - - for (var part : children) - part.render(poseStack, bufferSource, textureRenderTypeLookup, lightmap, overlay, context); - - for (var mesh : meshes) - mesh.render(poseStack, bufferSource, textureRenderTypeLookup, lightmap, overlay); - - if (matrix != null) - poseStack.popPose(); - } - } - - private static class Mesh { - private final ResourceLocation texture; - private final List quads = new ArrayList<>(); - - public Mesh(ResourceLocation texture) { - this.texture = texture; - } - - public void render(PoseStack poseStack, MultiBufferSource bufferSource, ITextureRenderTypeLookup textureRenderTypeLookup, int lightmap, int overlay) { - var consumer = bufferSource.getBuffer(textureRenderTypeLookup.get(texture)); - for (var quad : quads) { - consumer.putBulkData(poseStack.last(), quad, 1, 1, 1, 1, lightmap, overlay, true); - } - } - } - - public static class Builder { - private final CompositeRenderable renderable = new CompositeRenderable(); - - private Builder() {} - - public PartBuilder child(String name) { - var child = new Component(name); - renderable.components.add(child); - return new PartBuilder<>(this, child); - } - - public CompositeRenderable get() { - return renderable; - } - } - - public static class PartBuilder { - private final T parent; - private final Component component; - - private PartBuilder(T parent, Component component) { - this.parent = parent; - this.component = component; - } - - public PartBuilder> child(String name) { - var child = new Component(component.name + "/" + name); - this.component.children.add(child); - return new PartBuilder<>(this, child); - } - - public PartBuilder addMesh(ResourceLocation texture, List quads) { - var mesh = new Mesh(texture); - mesh.quads.addAll(quads); - component.meshes.add(mesh); - return this; - } - - public T end() { - return parent; - } - } - - /** - * A context value that provides {@link Matrix4f} transforms for certain parts of the model. - */ - public static class Transforms { - /** - * A default instance that has no transforms specified. - */ - public static final Transforms EMPTY = new Transforms(ImmutableMap.of()); - - /** - * Builds a MultipartTransforms object with the given mapping. - */ - public static Transforms of(ImmutableMap parts) { - return new Transforms(parts); - } - - private final ImmutableMap parts; - - private Transforms(ImmutableMap parts) { - this.parts = parts; - } - - @Nullable - public Matrix4f getTransform(String part) { - return parts.get(part); - } - } -} diff --git a/src/main/java/net/neoforged/neoforge/client/model/renderable/IRenderable.java b/src/main/java/net/neoforged/neoforge/client/model/renderable/IRenderable.java deleted file mode 100644 index 3a55eb3e..00000000 --- a/src/main/java/net/neoforged/neoforge/client/model/renderable/IRenderable.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.client.model.renderable; - -import com.mojang.blaze3d.vertex.PoseStack; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.util.Unit; - -/** - * A standard interface for things that can be rendered to a {@link MultiBufferSource}. - * - * @param The type of context object used by the rendering logic - */ -@FunctionalInterface -public interface IRenderable { - /** - * Draws the renderable by adding the geometry to the provided {@link MultiBufferSource} - * - * @param poseStack The pose stack - * @param bufferSource The buffer source where the vertex data should be output - * @param textureRenderTypeLookup A function that provides a RenderType for the given texture - * @param lightmap The lightmap coordinates representing the current lighting conditions. See {@link net.minecraft.client.renderer.LightTexture} - * @param overlay The overlay coordinates representing the current overlay status. See {@link net.minecraft.client.renderer.texture.OverlayTexture} - * @param partialTick The current time expressed in the fraction of a tick elapsed since the last client tick - * @param context The context used for rendering - */ - void render(PoseStack poseStack, MultiBufferSource bufferSource, ITextureRenderTypeLookup textureRenderTypeLookup, int lightmap, int overlay, float partialTick, T context); - - /** - * Wraps the current renderable along with a context. - * Useful for keeping a list of various renderables paired with their contexts. - * - * @param context The context used for rendering - * @return A renderable that accepts {@link Unit#INSTANCE} as context, but uses the provided {@code context} instead - */ - default IRenderable withContext(T context) { - return (poseStack, bufferSource, textureRenderTypeLookup, lightmap, overlay, partialTick, unused) -> this.render(poseStack, bufferSource, textureRenderTypeLookup, lightmap, overlay, partialTick, context); - } -} diff --git a/src/main/java/net/neoforged/neoforge/client/model/renderable/ITextureRenderTypeLookup.java b/src/main/java/net/neoforged/neoforge/client/model/renderable/ITextureRenderTypeLookup.java deleted file mode 100644 index 8c05205f..00000000 --- a/src/main/java/net/neoforged/neoforge/client/model/renderable/ITextureRenderTypeLookup.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.client.model.renderable; - -import net.minecraft.client.renderer.RenderType; -import net.minecraft.resources.ResourceLocation; - -/** - * A generic lookup for {@link RenderType} implementations that use the specified texture. - */ -@FunctionalInterface -public interface ITextureRenderTypeLookup { - RenderType get(ResourceLocation name); -} diff --git a/src/main/java/net/neoforged/neoforge/client/renderstate/BaseRenderState.java b/src/main/java/net/neoforged/neoforge/client/renderstate/BaseRenderState.java new file mode 100644 index 00000000..cbdd4021 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/renderstate/BaseRenderState.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.renderstate; + +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import java.util.Map; +import net.minecraft.client.renderer.entity.state.EntityRenderState; +import net.minecraft.util.context.ContextKey; +import net.neoforged.neoforge.client.extensions.IRenderStateExtension; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +/** + * Extension class for RenderState objects (ie {@link EntityRenderState}). + * Allows modders to add arbitrary data onto render states for use in custom rendering. + */ +public abstract class BaseRenderState implements IRenderStateExtension { + protected final Map, Object> extensions = new Reference2ObjectOpenHashMap<>(); + + @SuppressWarnings("unchecked") + @Nullable + @Override + public T getRenderData(ContextKey key) { + return (T) extensions.get(key); + } + + @Override + public void setRenderData(ContextKey key, @Nullable T data) { + if (data != null) { + extensions.put(key, data); + } else { + extensions.remove(key); + } + } + + @ApiStatus.Internal + public void resetRenderData() { + extensions.clear(); + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/renderstate/MapDecorationRenderStateModifier.java b/src/main/java/net/neoforged/neoforge/client/renderstate/MapDecorationRenderStateModifier.java new file mode 100644 index 00000000..50f8c1a1 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/renderstate/MapDecorationRenderStateModifier.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.renderstate; + +import net.minecraft.client.renderer.state.MapRenderState; +import net.minecraft.world.level.saveddata.maps.MapDecorationType; +import net.minecraft.world.level.saveddata.maps.MapItemSavedData; +import net.neoforged.neoforge.client.gui.map.IMapDecorationRenderer; + +/** + * Function interface for render state modifiers that target MapDecorations. Useful for adding custom data for rendering + * in {@link IMapDecorationRenderer}s. + */ +@FunctionalInterface +public interface MapDecorationRenderStateModifier { + /** + * Called when the registered {@link MapDecorationType} is added to a {@link MapRenderState}. + * + * @param mapItemSavedData The map SavedData. + * @param mapRenderState The render state of the map after the texture has been set and custom data is added. + * @param mapDecorationRenderState The decoration render state after vanilla has set it up. + */ + void accept(MapItemSavedData mapItemSavedData, MapRenderState mapRenderState, MapRenderState.MapDecorationRenderState mapDecorationRenderState); +} diff --git a/src/main/java/net/neoforged/neoforge/client/renderstate/RegisterRenderStateModifiersEvent.java b/src/main/java/net/neoforged/neoforge/client/renderstate/RegisterRenderStateModifiersEvent.java new file mode 100644 index 00000000..27e30088 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/renderstate/RegisterRenderStateModifiersEvent.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.renderstate; + +import com.google.common.reflect.TypeParameter; +import com.google.common.reflect.TypeToken; +import java.lang.reflect.ParameterizedType; +import java.util.function.BiConsumer; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.client.renderer.entity.state.EntityRenderState; +import net.minecraft.client.renderer.state.MapRenderState; +import net.minecraft.resources.ResourceKey; +import net.minecraft.util.context.ContextKey; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.saveddata.maps.MapDecorationType; +import net.minecraft.world.level.saveddata.maps.MapItemSavedData; +import net.neoforged.bus.api.Event; +import net.neoforged.fml.LogicalSide; +import net.neoforged.fml.event.IModBusEvent; +import net.neoforged.neoforge.client.extensions.IRenderStateExtension; +import org.jetbrains.annotations.ApiStatus; + +/** + * Fired for registering modifier functions for various render state objects. Useful for gathering context for + * custom rendering with objects that are not your own. + * + *

    This event is fired on the mod-specific event bus, only on the {@linkplain LogicalSide#CLIENT logical client}.

    + */ +public class RegisterRenderStateModifiersEvent extends Event implements IModBusEvent { + @ApiStatus.Internal + public RegisterRenderStateModifiersEvent() {} + + /** + * Registers a render state modifier for {@link EntityRenderState}s which are run after all vanilla data is + * extracted. Can add custom data to the map using {@link EntityRenderState#setRenderData(ContextKey, Object)}. + * Any subclasses of the passed renderer class will also have this modifier applied. + * + *
    +     * 
    +     *     event.registerEntityModifier(new TypeToken>() {}, (entity, renderState) -> {
    +     *         . . .
    +     *     });
    +     * 
    +     * 
    + * + * @param baseRenderer Entity renderer class. Any subclasses will also apply this modifier. + * @param modifier The function for modifying the {@link EntityRenderState} and adding custom render data. + * @param The type of the entity + * @param The specific render state type + */ + public void registerEntityModifier(TypeToken> baseRenderer, BiConsumer modifier) { + ensureParametersMatchBounds(baseRenderer); + RenderStateExtensions.registerEntity(baseRenderer.getRawType(), modifier); + } + + /** + * Convenience method for cases where generics are not present. Registers a render state modifier for + * {@link EntityRenderState}s which are run after all vanilla data is extracted. Can add custom data to the map + * using {@link EntityRenderState#setRenderData(ContextKey, Object)}. Any subclasses of the passed renderer class + * will also have this modifier applied. + * + *
    +     * 
    +     *     event.registerEntityModifier(PlayerRenderer.class, (entity, renderState) -> {
    +     *         . . .
    +     *     });
    +     * 
    +     * 
    + * + * @param baseRenderer Entity renderer class. Any subclasses will also apply this modifier. + * @param modifier The function for modifying the {@link EntityRenderState} and adding custom render data. + * @param The type of the entity + * @param The specific render state type + */ + public void registerEntityModifier(Class> baseRenderer, BiConsumer modifier) { + ensureParametersMatchBounds(TypeToken.of(baseRenderer)); + RenderStateExtensions.registerEntity(baseRenderer, modifier); + } + + /** + * Registers a render state modifier for {@link MapRenderState}s which are run after the texture has been set + * and before decorations have been added. Can add custom data to the map using + * {@link IRenderStateExtension#setRenderData(ContextKey, Object)}. + * + * @param modifier The function for modifying the {@link net.minecraft.client.renderer.state.MapRenderState} and adding custom render data. + */ + public void registerMapModifier(BiConsumer modifier) { + RenderStateExtensions.registerMap(modifier); + } + + /** + * Registers a render state modifier for {@link MapRenderState.MapDecorationRenderState}s which are run after + * vanilla map decoration data has been set. Can add custom data to the map using + * {@link IRenderStateExtension#setRenderData(ContextKey, Object)}. + * + * @param mapDecorationTypeKey Key for the registered {@link MapDecorationType} + * @param modifier The function for modifying the {@link MapRenderState.MapDecorationRenderState} and adding custom render data. + */ + public void registerMapDecorationModifier(ResourceKey mapDecorationTypeKey, MapDecorationRenderStateModifier modifier) { + RenderStateExtensions.registerMapDecoration(mapDecorationTypeKey, modifier); + } + + private static void ensureParametersMatchBounds(TypeToken> baseRenderer) { + if (baseRenderer.getType() instanceof ParameterizedType parameterizedType) { + Class bound = baseRenderer.getRawType(); + ParameterizedType parameterized = parameterizedType; + do { + var userArgs = parameterized.getActualTypeArguments(); + var typeArgs = bound.getTypeParameters(); + + for (int i = 0; i < userArgs.length; i++) { + var userArg = userArgs[i]; + var userToken = Container.of(TypeToken.of(userArg)); + var typeArg = typeArgs[i]; + for (var singleBound : typeArg.getBounds()) { + var token = Container.of(TypeToken.of(singleBound)); + if (!token.isSubtypeOf(userToken)) { + throw new IllegalArgumentException("%s does not match expected type parameter %s".formatted(userArg, singleBound)); + } + } + } + + if (!(parameterized.getOwnerType() instanceof ParameterizedType parameterizedOwner)) { + break; + } + parameterized = parameterizedOwner; + bound = bound.getEnclosingClass(); + } while (bound != null); + } + } + + @SuppressWarnings("unused") + private record Container() { + private static TypeToken> of(TypeToken parameter) { + return new TypeToken>() {} + .where(new TypeParameter<>() {}, parameter); + } + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/renderstate/RenderStateExtensions.java b/src/main/java/net/neoforged/neoforge/client/renderstate/RenderStateExtensions.java new file mode 100644 index 00000000..76c0525f --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/renderstate/RenderStateExtensions.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.renderstate; + +import com.google.common.collect.ImmutableList; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.Reference2ObjectArrayMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; +import net.minecraft.client.renderer.entity.EntityRenderer; +import net.minecraft.client.renderer.entity.state.EntityRenderState; +import net.minecraft.client.renderer.state.MapRenderState; +import net.minecraft.core.Holder; +import net.minecraft.resources.ResourceKey; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.saveddata.maps.MapDecorationType; +import net.minecraft.world.level.saveddata.maps.MapItemSavedData; +import org.jetbrains.annotations.ApiStatus; + +public final class RenderStateExtensions { + private RenderStateExtensions() {} + + private static final Map, Collection>> ENTITY = new Reference2ObjectArrayMap<>(); + private static final Map, Collection>> ENTITY_CACHE = new Reference2ObjectOpenHashMap<>(); + + private static final List> MAP = new ObjectArrayList<>(); + + private static final Map, Collection> MAP_DECORATION = new Reference2ObjectArrayMap<>(); + + @SuppressWarnings("unchecked") + @ApiStatus.Internal + public static void onUpdateEntityRenderState(EntityRenderer renderer, E entity, S renderState) { + renderState.resetRenderData(); + var modifiers = (Collection>) (Object) ENTITY_CACHE.computeIfAbsent(renderer.getClass(), aClass -> { + var builder = ImmutableList.>builder(); + for (var entry : ENTITY.entrySet()) { + if (entry.getKey().isAssignableFrom(aClass)) { + builder.addAll(entry.getValue()); + } + } + return builder.build(); + }); + for (BiConsumer modifier : modifiers) { + modifier.accept(entity, renderState); + } + } + + @ApiStatus.Internal + public static void onUpdateMapRenderState(MapItemSavedData mapItemSavedData, MapRenderState renderState) { + renderState.resetRenderData(); + for (BiConsumer modifier : MAP) { + modifier.accept(mapItemSavedData, renderState); + } + } + + @ApiStatus.Internal + public static MapRenderState.MapDecorationRenderState onUpdateMapDecorationRenderState(Holder mapDecorationTypeHolder, MapItemSavedData mapItemSavedData, MapRenderState mapRenderState, MapRenderState.MapDecorationRenderState mapDecorationRenderState) { + mapDecorationRenderState.resetRenderData(); + var modifiers = MAP_DECORATION.getOrDefault(mapDecorationTypeHolder.getKey(), List.of()); + for (var modifier : modifiers) { + modifier.accept(mapItemSavedData, mapRenderState, mapDecorationRenderState); + } + return mapDecorationRenderState; + } + + static void registerEntity(Class baseRenderer, BiConsumer modifier) { + ENTITY.computeIfAbsent(baseRenderer, aClass -> new ObjectArrayList<>()).add(modifier); + } + + static void registerMap(BiConsumer modifier) { + MAP.add(modifier); + } + + static void registerMapDecoration(ResourceKey mapDecorationTypeKey, MapDecorationRenderStateModifier modifier) { + MAP_DECORATION.computeIfAbsent(mapDecorationTypeKey, aClass -> new ObjectArrayList<>()).add(modifier); + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/renderstate/package-info.java b/src/main/java/net/neoforged/neoforge/client/renderstate/package-info.java new file mode 100644 index 00000000..c6982501 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/renderstate/package-info.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +@FieldsAreNonnullByDefault +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package net.neoforged.neoforge.client.renderstate; + +import javax.annotation.ParametersAreNonnullByDefault; +import net.minecraft.FieldsAreNonnullByDefault; +import net.minecraft.MethodsReturnNonnullByDefault; diff --git a/src/main/java/net/neoforged/neoforge/common/CommonHooks.java b/src/main/java/net/neoforged/neoforge/common/CommonHooks.java index d81c7891..f99ab9d2 100644 --- a/src/main/java/net/neoforged/neoforge/common/CommonHooks.java +++ b/src/main/java/net/neoforged/neoforge/common/CommonHooks.java @@ -952,7 +952,7 @@ public static ItemStack getProjectile(LivingEntity entity, ItemStack projectileW * Used as the default implementation of {@link Item#getCreatorModId}. Call that method instead. */ @Nullable - public static String getDefaultCreatorModId(ItemStack itemStack) { + public static String getDefaultCreatorModId(HolderLookup.Provider registries, ItemStack itemStack) { Item item = itemStack.getItem(); ResourceLocation registryName = BuiltInRegistries.ITEM.getKey(item); String modId = registryName == null ? null : registryName.getNamespace(); @@ -974,7 +974,7 @@ public static String getDefaultCreatorModId(ItemStack itemStack) { return key.get().location().getNamespace(); } } else if (item instanceof SpawnEggItem spawnEggItem) { - Optional>> key = BuiltInRegistries.ENTITY_TYPE.getResourceKey(spawnEggItem.getType(itemStack)); + Optional>> key = BuiltInRegistries.ENTITY_TYPE.getResourceKey(spawnEggItem.getType(registries, itemStack)); if (key.isPresent()) { return key.get().location().getNamespace(); } diff --git a/src/main/java/net/neoforged/neoforge/common/DeferredSpawnEggItem.java b/src/main/java/net/neoforged/neoforge/common/DeferredSpawnEggItem.java deleted file mode 100644 index 67c84ef0..00000000 --- a/src/main/java/net/neoforged/neoforge/common/DeferredSpawnEggItem.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.common; - -import java.util.ArrayList; -import java.util.IdentityHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Supplier; -import net.minecraft.core.Direction; -import net.minecraft.core.dispenser.DispenseItemBehavior; -import net.minecraft.util.ARGB; -import net.minecraft.world.entity.EntitySpawnReason; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.Mob; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.SpawnEggItem; -import net.minecraft.world.level.block.DispenserBlock; -import net.minecraft.world.level.gameevent.GameEvent; -import net.neoforged.api.distmarker.Dist; -import net.neoforged.bus.api.EventPriority; -import net.neoforged.bus.api.SubscribeEvent; -import net.neoforged.fml.common.EventBusSubscriber; -import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; -import net.neoforged.neoforge.client.event.RegisterColorHandlersEvent; -import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.Nullable; - -public class DeferredSpawnEggItem extends SpawnEggItem { - private static final List MOD_EGGS = new ArrayList<>(); - private static final Map, DeferredSpawnEggItem> TYPE_MAP = new IdentityHashMap<>(); - private final Supplier> typeSupplier; - - public DeferredSpawnEggItem(Supplier> type, int backgroundColor, int highlightColor, Properties props) { - super((EntityType) null, backgroundColor, highlightColor, props); - this.typeSupplier = type; - - MOD_EGGS.add(this); - } - - @Nullable - protected DispenseItemBehavior createDispenseBehavior() { - return DEFAULT_DISPENSE_BEHAVIOR; - } - - @ApiStatus.Internal - @Nullable - public static SpawnEggItem deferredOnlyById(@Nullable EntityType type) { - return TYPE_MAP.get(type); - } - - @Override - protected EntityType getDefaultType() { - return this.typeSupplier.get(); - } - - private static final DispenseItemBehavior DEFAULT_DISPENSE_BEHAVIOR = (source, stack) -> { - Direction face = source.state().getValue(DispenserBlock.FACING); - EntityType type = ((SpawnEggItem) stack.getItem()).getType(stack); - - try { - type.spawn(source.level(), stack, null, source.pos().relative(face), EntitySpawnReason.DISPENSER, face != Direction.UP, false); - } catch (Exception exception) { - DispenseItemBehavior.LOGGER.error("Error while dispensing spawn egg from dispenser at {}", source.pos(), exception); - return ItemStack.EMPTY; - } - - stack.shrink(1); - source.level().gameEvent(GameEvent.ENTITY_PLACE, source.pos(), GameEvent.Context.of(source.state())); - return stack; - }; - - @EventBusSubscriber(modid = "neoforge", bus = EventBusSubscriber.Bus.MOD) - private static class CommonHandler { - @SubscribeEvent - public static void onCommonSetup(FMLCommonSetupEvent event) { - event.enqueueWork(() -> { - MOD_EGGS.forEach(egg -> { - DispenseItemBehavior dispenseBehavior = egg.createDispenseBehavior(); - if (dispenseBehavior != null) { - DispenserBlock.registerBehavior(egg, dispenseBehavior); - } - - TYPE_MAP.put(egg.typeSupplier.get(), egg); - }); - }); - } - } - - @EventBusSubscriber(value = Dist.CLIENT, modid = "neoforge", bus = EventBusSubscriber.Bus.MOD) - private static class ColorRegisterHandler { - @SubscribeEvent(priority = EventPriority.HIGHEST) - public static void registerSpawnEggColors(RegisterColorHandlersEvent.Item event) { - MOD_EGGS.forEach(egg -> event.register((stack, layer) -> ARGB.opaque(egg.getColor(layer)), egg)); - } - } -} diff --git a/src/main/java/net/neoforged/neoforge/common/ModConfigSpec.java b/src/main/java/net/neoforged/neoforge/common/ModConfigSpec.java index 870ce31e..0bd99144 100644 --- a/src/main/java/net/neoforged/neoforge/common/ModConfigSpec.java +++ b/src/main/java/net/neoforged/neoforge/common/ModConfigSpec.java @@ -363,7 +363,8 @@ public > ConfigValue defineInRange(String pat public > ConfigValue defineInRange(List path, Supplier defaultSupplier, V min, V max, Class clazz) { Range range = new Range<>(clazz, min, max); context.setRange(range); - comment("Range: " + range.toString()); + comment(" Default: " + defaultSupplier.get()); + comment(" Range: " + range); return define(path, defaultSupplier, range); } diff --git a/src/main/java/net/neoforged/neoforge/common/NeoForgeConfig.java b/src/main/java/net/neoforged/neoforge/common/NeoForgeConfig.java index 91a232a8..e7335e0b 100644 --- a/src/main/java/net/neoforged/neoforge/common/NeoForgeConfig.java +++ b/src/main/java/net/neoforged/neoforge/common/NeoForgeConfig.java @@ -8,6 +8,7 @@ import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.Logging; import net.neoforged.fml.event.config.ModConfigEvent; +import net.neoforged.neoforge.client.ClientHooks; import net.neoforged.neoforge.common.ModConfigSpec.BooleanValue; import net.neoforged.neoforge.common.ModConfigSpec.ConfigValue; import org.apache.commons.lang3.tuple.Pair; @@ -64,8 +65,11 @@ public static class Server { */ public static class Common { public final ModConfigSpec.EnumValue logUntranslatedItemTagWarnings; + public final ModConfigSpec.EnumValue logLegacyTagWarnings; + public final BooleanValue attributeAdvancedTooltipDebugInfo; + Common(ModConfigSpec.Builder builder) { logUntranslatedItemTagWarnings = builder .comment("A config option mainly for developers. Logs out modded item tags that do not have translations when running on integrated server. Format desired is tag.item.. for the translation key. Defaults to SILENCED.") @@ -76,6 +80,11 @@ public static class Common { .comment("A config option mainly for developers. Logs out modded tags that are using the 'forge' namespace when running on integrated server. Defaults to DEV_SHORT.") .translation("neoforge.configgui.logLegacyTagWarnings") .defineEnum("logLegacyTagWarnings", TagConventionLogWarning.LogWarningMode.DEV_SHORT); + + attributeAdvancedTooltipDebugInfo = builder + .comment("Set this to true to enable showing debug information about attributes on an item when advanced tooltips is on.") + .translation("neoforge.configgui.attributeAdvancedTooltipDebugInfo") + .define("attributeAdvancedTooltipDebugInfo", true); } } @@ -84,6 +93,7 @@ public static class Common { */ public static class Client { public final BooleanValue experimentalForgeLightPipelineEnabled; + boolean experimentalPipelineActive; public final BooleanValue showLoadWarnings; @@ -134,11 +144,23 @@ public static class Client { @SubscribeEvent public static void onLoad(final ModConfigEvent.Loading configEvent) { LogManager.getLogger().debug(Logging.FORGEMOD, "Loaded NeoForge config file {}", configEvent.getConfig().getFileName()); + + if (configEvent.getConfig().getSpec() == clientSpec) { + CLIENT.experimentalPipelineActive = CLIENT.experimentalForgeLightPipelineEnabled.getAsBoolean(); + } } @SubscribeEvent public static void onFileChange(final ModConfigEvent.Reloading configEvent) { LogManager.getLogger().debug(Logging.FORGEMOD, "NeoForge config just got changed on the file system!"); + + if (configEvent.getConfig().getSpec() == clientSpec) { + boolean experimentalPipelineActive = CLIENT.experimentalForgeLightPipelineEnabled.getAsBoolean(); + if (experimentalPipelineActive != CLIENT.experimentalPipelineActive) { + CLIENT.experimentalPipelineActive = experimentalPipelineActive; + ClientHooks.reloadRenderer(); + } + } } //General diff --git a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java index c19d9e78..16819218 100644 --- a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java +++ b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java @@ -11,10 +11,8 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; import java.util.EnumSet; import java.util.List; -import java.util.Optional; import java.util.Set; import java.util.UUID; -import java.util.concurrent.CompletableFuture; import java.util.function.Function; import net.minecraft.DetectedVersion; import net.minecraft.advancements.critereon.EntitySubPredicate; @@ -25,22 +23,14 @@ import net.minecraft.commands.synchronization.SingletonArgumentInfo; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; -import net.minecraft.core.HolderLookup; import net.minecraft.core.RegistryCodecs; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.core.registries.Registries; -import net.minecraft.data.DataGenerator; -import net.minecraft.data.PackOutput; -import net.minecraft.data.metadata.PackMetadataGenerator; -import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.packs.PackType; -import net.minecraft.server.packs.metadata.pack.PackMetadataSection; import net.minecraft.sounds.SoundEvent; import net.minecraft.sounds.SoundEvents; -import net.minecraft.util.InclusiveRange; import net.minecraft.world.damagesource.DamageSources; import net.minecraft.world.damagesource.DamageType; import net.minecraft.world.effect.MobEffects; @@ -52,6 +42,7 @@ import net.minecraft.world.entity.ai.attributes.RangedAttribute; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.item.Items; +import net.minecraft.world.item.SpawnEggItem; import net.minecraft.world.item.crafting.display.SlotDisplay; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.GameRules; @@ -90,6 +81,7 @@ import net.neoforged.neoforge.common.advancements.critereon.SnowBootsEntityPredicate; import net.neoforged.neoforge.common.conditions.AndCondition; import net.neoforged.neoforge.common.conditions.FalseCondition; +import net.neoforged.neoforge.common.conditions.FeatureFlagsEnabledCondition; import net.neoforged.neoforge.common.conditions.ICondition; import net.neoforged.neoforge.common.conditions.ItemExistsCondition; import net.neoforged.neoforge.common.conditions.ModLoadedCondition; @@ -104,28 +96,12 @@ import net.neoforged.neoforge.common.crafting.DifferenceIngredient; import net.neoforged.neoforge.common.crafting.IngredientType; import net.neoforged.neoforge.common.crafting.IntersectionIngredient; -import net.neoforged.neoforge.common.data.ExistingFileHelper; -import net.neoforged.neoforge.common.data.internal.NeoForgeAdvancementProvider; -import net.neoforged.neoforge.common.data.internal.NeoForgeBiomeTagsProvider; -import net.neoforged.neoforge.common.data.internal.NeoForgeBlockTagsProvider; -import net.neoforged.neoforge.common.data.internal.NeoForgeDamageTypeTagsProvider; -import net.neoforged.neoforge.common.data.internal.NeoForgeDataMapsProvider; -import net.neoforged.neoforge.common.data.internal.NeoForgeEnchantmentTagsProvider; -import net.neoforged.neoforge.common.data.internal.NeoForgeEntityTypeTagsProvider; -import net.neoforged.neoforge.common.data.internal.NeoForgeFluidTagsProvider; -import net.neoforged.neoforge.common.data.internal.NeoForgeItemTagsProvider; -import net.neoforged.neoforge.common.data.internal.NeoForgeLanguageProvider; -import net.neoforged.neoforge.common.data.internal.NeoForgeLootTableProvider; -import net.neoforged.neoforge.common.data.internal.NeoForgeRecipeProvider; -import net.neoforged.neoforge.common.data.internal.NeoForgeRegistryOrderReportProvider; -import net.neoforged.neoforge.common.data.internal.NeoForgeSpriteSourceProvider; -import net.neoforged.neoforge.common.data.internal.NeoForgeStructureTagsProvider; -import net.neoforged.neoforge.common.data.internal.VanillaSoundDefinitionsProvider; import net.neoforged.neoforge.common.extensions.IPlayerExtension; import net.neoforged.neoforge.common.loot.AddTableLootModifier; import net.neoforged.neoforge.common.loot.CanItemPerformAbility; import net.neoforged.neoforge.common.loot.IGlobalLootModifier; import net.neoforged.neoforge.common.loot.LootTableIdCondition; +import net.neoforged.neoforge.common.util.SelfTest; import net.neoforged.neoforge.common.world.BiomeModifier; import net.neoforged.neoforge.common.world.BiomeModifiers; import net.neoforged.neoforge.common.world.BiomeModifiers.AddFeaturesBiomeModifier; @@ -136,7 +112,6 @@ import net.neoforged.neoforge.common.world.NoneStructureModifier; import net.neoforged.neoforge.common.world.StructureModifier; import net.neoforged.neoforge.common.world.StructureModifiers; -import net.neoforged.neoforge.data.event.GatherDataEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; import net.neoforged.neoforge.fluids.BaseFlowingFluid; import net.neoforged.neoforge.fluids.CauldronFluidContent; @@ -389,6 +364,7 @@ public class NeoForgeMod { public static final DeferredHolder, MapCodec> OR_CONDITION = CONDITION_CODECS.register("or", () -> OrCondition.CODEC); public static final DeferredHolder, MapCodec> TAG_EMPTY_CONDITION = CONDITION_CODECS.register("tag_empty", () -> TagEmptyCondition.CODEC); public static final DeferredHolder, MapCodec> TRUE_CONDITION = CONDITION_CODECS.register("true", () -> TrueCondition.CODEC); + public static final DeferredHolder, MapCodec> FEATURE_FLAGS_ENABLED_CONDITION = CONDITION_CODECS.register("feature_flags_enabled", () -> FeatureFlagsEnabledCondition.CODEC); private static final DeferredRegister> ENTITY_PREDICATE_CODECS = DeferredRegister.create(Registries.ENTITY_SUB_PREDICATE_TYPE, NeoForgeVersion.MOD_ID); public static final DeferredHolder, MapCodec> PIGLIN_NEUTRAL_ARMOR_PREDICATE = ENTITY_PREDICATE_CODECS.register("piglin_neutral_armor", () -> PiglinNeutralArmorEntityPredicate.CODEC); @@ -529,6 +505,8 @@ public NeoForgeMod(IEventBus modEventBus, Dist dist, ModContainer container) { LOGGER.info(NEOFORGEMOD, "NeoForge mod loading, version {}, for MC {}", NeoForgeVersion.getVersion(), DetectedVersion.BUILT_IN.getName()); ForgeSnapshotsMod.logStartupWarning(); + SelfTest.initCommon(); + CrashReportCallables.registerCrashCallable("Crash Report UUID", () -> { final UUID uuid = UUID.randomUUID(); LOGGER.fatal("Preparing crash report with UUID {}", uuid); @@ -544,7 +522,6 @@ public NeoForgeMod(IEventBus modEventBus, Dist dist, ModContainer container) { event.dataPackRegistry(NeoForgeRegistries.Keys.STRUCTURE_MODIFIERS, StructureModifier.DIRECT_CODEC); }); modEventBus.addListener(this::preInit); - modEventBus.addListener(this::gatherData); modEventBus.addListener(this::loadComplete); modEventBus.addListener(this::registerFluids); modEventBus.addListener(this::registerLootData); @@ -588,6 +565,8 @@ public NeoForgeMod(IEventBus modEventBus, Dist dist, ModContainer container) { modEventBus.register(NeoForgeDataMaps.class); + modEventBus.register(SpawnEggItem.class); // Registers dispenser behaviour for eggs + if (isPRBuild(container.getModInfo().getVersion().toString())) { isPRBuild = true; ModLoader.addLoadingIssue(ModLoadingIssue.warning("loadwarning.neoforge.prbuild").withAffectedMod(container.getModInfo())); @@ -611,37 +590,6 @@ public void serverStopping(ServerStoppingEvent evt) { }); } - public void gatherData(GatherDataEvent event) { - DataGenerator gen = event.getGenerator(); - PackOutput packOutput = gen.getPackOutput(); - CompletableFuture lookupProvider = event.getLookupProvider(); - - ExistingFileHelper existingFileHelper = event.getExistingFileHelper(); - gen.addProvider(true, new PackMetadataGenerator(packOutput) - .add(PackMetadataSection.TYPE, new PackMetadataSection( - Component.translatable("pack.neoforge.description"), - DetectedVersion.BUILT_IN.getPackVersion(PackType.SERVER_DATA), - Optional.of(new InclusiveRange<>(0, Integer.MAX_VALUE))))); - gen.addProvider(event.includeServer(), new NeoForgeAdvancementProvider(packOutput, lookupProvider, existingFileHelper)); - NeoForgeBlockTagsProvider blockTags = new NeoForgeBlockTagsProvider(packOutput, lookupProvider, existingFileHelper); - gen.addProvider(event.includeServer(), blockTags); - gen.addProvider(event.includeServer(), new NeoForgeItemTagsProvider(packOutput, lookupProvider, blockTags.contentsGetter(), existingFileHelper)); - gen.addProvider(event.includeServer(), new NeoForgeEntityTypeTagsProvider(packOutput, lookupProvider, existingFileHelper)); - gen.addProvider(event.includeServer(), new NeoForgeFluidTagsProvider(packOutput, lookupProvider, existingFileHelper)); - gen.addProvider(event.includeServer(), new NeoForgeEnchantmentTagsProvider(packOutput, lookupProvider, existingFileHelper)); - gen.addProvider(event.includeServer(), new NeoForgeRecipeProvider.Runner(packOutput, lookupProvider)); - gen.addProvider(event.includeServer(), new NeoForgeLootTableProvider(packOutput, lookupProvider)); - gen.addProvider(event.includeServer(), new NeoForgeBiomeTagsProvider(packOutput, lookupProvider, existingFileHelper)); - gen.addProvider(event.includeServer(), new NeoForgeStructureTagsProvider(packOutput, lookupProvider, existingFileHelper)); - gen.addProvider(event.includeServer(), new NeoForgeDamageTypeTagsProvider(packOutput, lookupProvider, existingFileHelper)); - gen.addProvider(event.includeServer(), new NeoForgeRegistryOrderReportProvider(packOutput)); - gen.addProvider(event.includeServer(), new NeoForgeDataMapsProvider(packOutput, lookupProvider)); - - gen.addProvider(event.includeClient(), new NeoForgeSpriteSourceProvider(packOutput, lookupProvider, existingFileHelper)); - gen.addProvider(event.includeClient(), new VanillaSoundDefinitionsProvider(packOutput, existingFileHelper)); - gen.addProvider(event.includeClient(), new NeoForgeLanguageProvider(packOutput)); - } - // done in an event instead of deferred to only enable if a mod requests it public void registerFluids(RegisterEvent event) { if (enableMilkFluid) { diff --git a/src/main/java/net/neoforged/neoforge/common/PercentageAttribute.java b/src/main/java/net/neoforged/neoforge/common/PercentageAttribute.java index 75a845fe..cb993e31 100644 --- a/src/main/java/net/neoforged/neoforge/common/PercentageAttribute.java +++ b/src/main/java/net/neoforged/neoforge/common/PercentageAttribute.java @@ -10,6 +10,7 @@ import net.minecraft.world.entity.ai.attributes.AttributeModifier.Operation; import net.minecraft.world.entity.ai.attributes.RangedAttribute; import net.minecraft.world.item.TooltipFlag; +import net.neoforged.neoforge.common.extensions.IAttributeExtension; /** * A Percentage Attribute is one which always displays modifiers as percentages, including for {@link Operation#ADD_VALUE}. @@ -46,6 +47,10 @@ public PercentageAttribute(String pDescriptionId, double pDefaultValue, double p @Override public MutableComponent toValueComponent(Operation op, double value, TooltipFlag flag) { - return Component.translatable("neoforge.value.percent", FORMAT.format(value * this.scaleFactor)); + if (IAttributeExtension.isNullOrAddition(op)) { + return Component.translatable("neoforge.value.percent", FORMAT.format(value * this.scaleFactor)); + } + + return Component.translatable("neoforge.value.percent", FORMAT.format(value * 100)); } } diff --git a/src/main/java/net/neoforged/neoforge/common/Tags.java b/src/main/java/net/neoforged/neoforge/common/Tags.java index 8a57785d..68d31d12 100644 --- a/src/main/java/net/neoforged/neoforge/common/Tags.java +++ b/src/main/java/net/neoforged/neoforge/common/Tags.java @@ -35,8 +35,28 @@ public static class Blocks { * This is patched into the following method: {@link net.minecraft.world.entity.monster.EnderMan.EndermanLeaveBlockGoal#canPlaceBlock(Level, BlockPos, BlockState, BlockState, BlockState, BlockPos)} */ public static final TagKey ENDERMAN_PLACE_ON_BLACKLIST = neoforgeTag("enderman_place_on_blacklist"); + + /** + * For denoting blocks that need tools that are Wood or higher to mine. + * By default, this is not added to any Minecraft tag since Wood is in the lowest "tier". + */ public static final TagKey NEEDS_WOOD_TOOL = neoforgeTag("needs_wood_tool"); + + /** + * For denoting blocks that need tools that are Gold or higher to mine. + * By default, this is not added to any Minecraft tag since Gold is in the lowest "tier". + */ public static final TagKey NEEDS_GOLD_TOOL = neoforgeTag("needs_gold_tool"); + + /** + * For denoting blocks that need tools that are Netherite or higher to mine. + * Blocks in this tag gets added to the following Minecraft tags: + * {@link BlockTags#INCORRECT_FOR_WOODEN_TOOL} + * {@link BlockTags#INCORRECT_FOR_STONE_TOOL} + * {@link BlockTags#INCORRECT_FOR_IRON_TOOL} + * {@link BlockTags#INCORRECT_FOR_GOLD_TOOL} + * {@link BlockTags#INCORRECT_FOR_DIAMOND_TOOL} + */ public static final TagKey NEEDS_NETHERITE_TOOL = neoforgeTag("needs_netherite_tool"); // `c` tags for common conventions @@ -593,13 +613,6 @@ public static class Items { */ public static final TagKey SHULKER_BOXES = tag("shulker_boxes"); public static final TagKey SLIME_BALLS = tag("slime_balls"); - /** - * Please use properly named {@link Tags.Items#SLIME_BALLS} tag and field instead - *

    - * TODO: Remove in 1.21.1 - */ - @Deprecated(since = "1.21") - public static final TagKey SLIMEBALLS = tag("slimeballs"); /** * Natural stone-like blocks that can be used as a base ingredient in recipes that takes stone. */ diff --git a/src/main/java/net/neoforged/neoforge/common/conditions/ConditionContext.java b/src/main/java/net/neoforged/neoforge/common/conditions/ConditionContext.java index de5b9b5d..cffc6d01 100644 --- a/src/main/java/net/neoforged/neoforge/common/conditions/ConditionContext.java +++ b/src/main/java/net/neoforged/neoforge/common/conditions/ConditionContext.java @@ -12,17 +12,30 @@ import net.minecraft.core.Registry; import net.minecraft.resources.ResourceKey; import net.minecraft.tags.TagKey; +import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.flag.FeatureFlags; +import org.jetbrains.annotations.ApiStatus; public class ConditionContext implements ICondition.IContext { private final Map>, HolderLookup.RegistryLookup> pendingTags; + private final FeatureFlagSet enabledFeatures; - public ConditionContext(List> pendingTags) { + public ConditionContext(List> pendingTags, FeatureFlagSet enabledFeatures) { this.pendingTags = new IdentityHashMap<>(); + this.enabledFeatures = enabledFeatures; + for (var tags : pendingTags) { this.pendingTags.put(tags.key(), tags.lookup()); } } + // Use FeatureFlagSet sensitive constructor + @ApiStatus.ScheduledForRemoval(inVersion = "1.21.4") + @Deprecated(forRemoval = true, since = "1.21.3") + public ConditionContext(List> pendingTags) { + this(pendingTags, FeatureFlags.VANILLA_SET); + } + public void clear() { this.pendingTags.clear(); } @@ -33,4 +46,9 @@ public boolean isTagLoaded(TagKey key) { var lookup = pendingTags.get(key.registry()); return lookup != null && lookup.get((TagKey) key).isPresent(); } + + @Override + public FeatureFlagSet enabledFeatures() { + return enabledFeatures; + } } diff --git a/src/main/java/net/neoforged/neoforge/common/conditions/FeatureFlagsEnabledCondition.java b/src/main/java/net/neoforged/neoforge/common/conditions/FeatureFlagsEnabledCondition.java new file mode 100644 index 00000000..6eb0aef8 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/conditions/FeatureFlagsEnabledCondition.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.common.conditions; + +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.world.flag.FeatureFlag; +import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.flag.FeatureFlags; + +/** + * Condition checking that a set of {@link FeatureFlag feature flags} are enabled. + * + * @apiNote Mainly to be used when flagged content is not contained within the same feature pack which also enables said {@link FeatureFlag feature flags}. + */ +public record FeatureFlagsEnabledCondition(FeatureFlagSet flags) implements ICondition { + public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + FeatureFlags.CODEC.fieldOf("flags").forGetter(condition -> condition.flags)).apply(instance, FeatureFlagsEnabledCondition::new)); + + public FeatureFlagsEnabledCondition { + if (flags.isEmpty()) { + throw new IllegalArgumentException("FeatureFlagsEnabledCondition requires a non-empty feature flag set"); + } + } + + @Override + public boolean test(IContext context) { + return flags.isSubsetOf(context.enabledFeatures()); + } + + @Override + public MapCodec codec() { + return CODEC; + } +} diff --git a/src/main/java/net/neoforged/neoforge/common/conditions/ICondition.java b/src/main/java/net/neoforged/neoforge/common/conditions/ICondition.java index 1be7e0ce..2c8ca858 100644 --- a/src/main/java/net/neoforged/neoforge/common/conditions/ICondition.java +++ b/src/main/java/net/neoforged/neoforge/common/conditions/ICondition.java @@ -19,7 +19,10 @@ import net.minecraft.resources.RegistryOps; import net.minecraft.tags.TagKey; import net.minecraft.util.Unit; +import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.flag.FeatureFlags; import net.neoforged.neoforge.registries.NeoForgeRegistries; +import net.neoforged.neoforge.server.ServerLifecycleHooks; public interface ICondition { Codec CODEC = NeoForgeRegistries.CONDITION_SERIALIZERS.byNameCodec() @@ -91,5 +94,15 @@ public boolean isTagLoaded(TagKey key) { * Returns {@code true} if the requested tag is available. */ boolean isTagLoaded(TagKey key); + + default FeatureFlagSet enabledFeatures() { + // returning the vanilla set causes reports false positives for flags outside of vanilla + // return FeatureFlags.VANILLA_SET; + + // lookup the active enabledFeatures from the current server + // if no server exists, delegating back to 'VANILLA_SET' should be fine (should rarely ever happen) + var server = ServerLifecycleHooks.getCurrentServer(); + return server == null ? FeatureFlags.VANILLA_SET : server.getWorldData().enabledFeatures(); + } } } diff --git a/src/main/java/net/neoforged/neoforge/common/conditions/IConditionBuilder.java b/src/main/java/net/neoforged/neoforge/common/conditions/IConditionBuilder.java index 06836647..ef10000e 100644 --- a/src/main/java/net/neoforged/neoforge/common/conditions/IConditionBuilder.java +++ b/src/main/java/net/neoforged/neoforge/common/conditions/IConditionBuilder.java @@ -7,7 +7,10 @@ import java.util.List; import net.minecraft.tags.TagKey; +import net.minecraft.world.flag.FeatureFlag; +import net.minecraft.world.flag.FeatureFlagSet; import net.minecraft.world.item.Item; +import org.apache.commons.lang3.ArrayUtils; public interface IConditionBuilder { default ICondition and(ICondition... values) { @@ -41,4 +44,19 @@ default ICondition modLoaded(String modid) { default ICondition tagEmpty(TagKey tag) { return new TagEmptyCondition(tag.location()); } + + default ICondition featureFlagsEnabled(FeatureFlagSet requiredFeatures) { + return new FeatureFlagsEnabledCondition(requiredFeatures); + } + + default ICondition featureFlagsEnabled(FeatureFlag... requiredFlags) { + if (requiredFlags.length == 0) { + throw new IllegalArgumentException("FeatureFlagsEnabledCondition requires at least one feature flag."); + } + if (requiredFlags.length == 1) { + return new FeatureFlagsEnabledCondition(FeatureFlagSet.of(requiredFlags[0])); + } else { + return new FeatureFlagsEnabledCondition(FeatureFlagSet.of(requiredFlags[0], ArrayUtils.remove(requiredFlags, 0))); + } + } } diff --git a/src/main/java/net/neoforged/neoforge/common/crafting/CompoundIngredient.java b/src/main/java/net/neoforged/neoforge/common/crafting/CompoundIngredient.java index f7030270..ab4cf41c 100644 --- a/src/main/java/net/neoforged/neoforge/common/crafting/CompoundIngredient.java +++ b/src/main/java/net/neoforged/neoforge/common/crafting/CompoundIngredient.java @@ -37,7 +37,7 @@ public static Ingredient of(Ingredient... children) { @Override public Stream> items() { - return children.stream().flatMap(child -> child.items().stream()); + return children.stream().flatMap(Ingredient::items); } @Override diff --git a/src/main/java/net/neoforged/neoforge/common/crafting/CustomDisplayIngredient.java b/src/main/java/net/neoforged/neoforge/common/crafting/CustomDisplayIngredient.java index 2c88c2fa..12ce024b 100644 --- a/src/main/java/net/neoforged/neoforge/common/crafting/CustomDisplayIngredient.java +++ b/src/main/java/net/neoforged/neoforge/common/crafting/CustomDisplayIngredient.java @@ -37,7 +37,7 @@ public boolean test(ItemStack stack) { @Override public Stream> items() { - return base.items().stream(); + return base.items(); } @Override diff --git a/src/main/java/net/neoforged/neoforge/common/crafting/DifferenceIngredient.java b/src/main/java/net/neoforged/neoforge/common/crafting/DifferenceIngredient.java index 3ce8c7d3..8e7e3ec2 100644 --- a/src/main/java/net/neoforged/neoforge/common/crafting/DifferenceIngredient.java +++ b/src/main/java/net/neoforged/neoforge/common/crafting/DifferenceIngredient.java @@ -25,7 +25,7 @@ public record DifferenceIngredient(Ingredient base, Ingredient subtracted) imple @Override public Stream> items() { - return base.items().stream().filter(i -> !subtracted.test(i.value().getDefaultInstance())); + return base.items().filter(i -> !subtracted.test(i.value().getDefaultInstance())); } @Override diff --git a/src/main/java/net/neoforged/neoforge/common/crafting/IntersectionIngredient.java b/src/main/java/net/neoforged/neoforge/common/crafting/IntersectionIngredient.java index b41dc850..d726e710 100644 --- a/src/main/java/net/neoforged/neoforge/common/crafting/IntersectionIngredient.java +++ b/src/main/java/net/neoforged/neoforge/common/crafting/IntersectionIngredient.java @@ -56,7 +56,7 @@ public boolean test(ItemStack stack) { @Override public Stream> items() { return children.stream() - .flatMap(child -> child.items().stream()) + .flatMap(child -> child.items()) .filter(i -> test(i.value().getDefaultInstance())); } diff --git a/src/main/java/net/neoforged/neoforge/common/data/DataMapProvider.java b/src/main/java/net/neoforged/neoforge/common/data/DataMapProvider.java index 4c325590..a19f34bc 100644 --- a/src/main/java/net/neoforged/neoforge/common/data/DataMapProvider.java +++ b/src/main/java/net/neoforged/neoforge/common/data/DataMapProvider.java @@ -25,7 +25,6 @@ import net.minecraft.data.CachedOutput; import net.minecraft.data.DataProvider; import net.minecraft.data.PackOutput; -import net.minecraft.resources.RegistryOps; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagKey; @@ -63,7 +62,7 @@ public CompletableFuture run(CachedOutput cache) { return lookupProvider.thenCompose(provider -> { gather(provider); - final DynamicOps dynamicOps = RegistryOps.create(JsonOps.INSTANCE, provider); + final DynamicOps dynamicOps = provider.createSerializationContext(JsonOps.INSTANCE); return CompletableFuture.allOf(this.builders.entrySet().stream().map(entry -> { DataMapType type = entry.getKey(); diff --git a/src/main/java/net/neoforged/neoforge/common/data/GeneratingOverlayMetadataSection.java b/src/main/java/net/neoforged/neoforge/common/data/GeneratingOverlayMetadataSection.java index 431fdb4a..1b401583 100644 --- a/src/main/java/net/neoforged/neoforge/common/data/GeneratingOverlayMetadataSection.java +++ b/src/main/java/net/neoforged/neoforge/common/data/GeneratingOverlayMetadataSection.java @@ -22,6 +22,6 @@ public record GeneratingOverlayMetadataSection(List TYPE = MetadataSectionType.fromCodec("overlays", CODEC); - public static final MetadataSectionType NEOFORGE_TYPE = MetadataSectionType.fromCodec("neoforge:overlays", CODEC); + public static final MetadataSectionType TYPE = new MetadataSectionType<>("overlays", CODEC); + public static final MetadataSectionType NEOFORGE_TYPE = new MetadataSectionType<>("neoforge:overlays", CODEC); } diff --git a/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeBlockTagsProvider.java b/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeBlockTagsProvider.java index a1959d5a..09b564d1 100644 --- a/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeBlockTagsProvider.java +++ b/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeBlockTagsProvider.java @@ -160,6 +160,14 @@ public void addTags(HolderLookup.Provider p_256380_) { tag(Tags.Blocks.VILLAGER_FARMLANDS).add(Blocks.FARMLAND); + // Make our Needs Netherite Tool tag be functional. + tag(Tags.Blocks.NEEDS_NETHERITE_TOOL); + tag(BlockTags.INCORRECT_FOR_WOODEN_TOOL).addTag(Tags.Blocks.NEEDS_NETHERITE_TOOL); + tag(BlockTags.INCORRECT_FOR_STONE_TOOL).addTag(Tags.Blocks.NEEDS_NETHERITE_TOOL); + tag(BlockTags.INCORRECT_FOR_IRON_TOOL).addTag(Tags.Blocks.NEEDS_NETHERITE_TOOL); + tag(BlockTags.INCORRECT_FOR_GOLD_TOOL).addTag(Tags.Blocks.NEEDS_NETHERITE_TOOL); + tag(BlockTags.INCORRECT_FOR_DIAMOND_TOOL).addTag(Tags.Blocks.NEEDS_NETHERITE_TOOL); + // Backwards compat with pre-1.21 tags. Done after so optional tag is last for better readability. // TODO: Remove backwards compat tag entries in 1.22 tagWithOptionalLegacy(Tags.Blocks.BARRELS); diff --git a/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeItemTagsProvider.java b/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeItemTagsProvider.java index 36c67fba..1b0db4cf 100644 --- a/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeItemTagsProvider.java +++ b/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeItemTagsProvider.java @@ -222,8 +222,7 @@ public void addTags(HolderLookup.Provider lookupProvider) { tag(Tags.Items.SEEDS_MELON).add(Items.MELON_SEEDS); tag(Tags.Items.SEEDS_PUMPKIN).add(Items.PUMPKIN_SEEDS); tag(Tags.Items.SEEDS_WHEAT).add(Items.WHEAT_SEEDS); - tag(Tags.Items.SLIMEBALLS).add(Items.SLIME_BALL); // Deprecated - tag(Tags.Items.SLIME_BALLS).add(Items.SLIME_BALL).addOptionalTag(Tags.Items.SLIMEBALLS); + tag(Tags.Items.SLIME_BALLS).add(Items.SLIME_BALL); tag(Tags.Items.SHULKER_BOXES) .add(Items.SHULKER_BOX).add(Items.WHITE_SHULKER_BOX).add(Items.ORANGE_SHULKER_BOX) .add(Items.MAGENTA_SHULKER_BOX).add(Items.LIGHT_BLUE_SHULKER_BOX).add(Items.YELLOW_SHULKER_BOX) diff --git a/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeLanguageProvider.java b/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeLanguageProvider.java index f764016f..83b4cbcc 100644 --- a/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeLanguageProvider.java +++ b/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeLanguageProvider.java @@ -304,7 +304,6 @@ protected void addTranslations() { add(Tags.Items.SEEDS_WHEAT, "Wheat Seeds"); add(Tags.Items.SHULKER_BOXES, "Shulker Boxes"); add(Tags.Items.SLIME_BALLS, "Slimeballs"); - add(Tags.Items.SLIMEBALLS, "Slimeballs"); add(Tags.Items.STONES, "Stones"); add(Tags.Items.STORAGE_BLOCKS, "Storage Blocks"); add(Tags.Items.STORAGE_BLOCKS_BONE_MEAL, "Bone Meal Storage Blocks"); diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IAttributeExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IAttributeExtension.java index 6a5c5156..dd7074d9 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IAttributeExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IAttributeExtension.java @@ -23,6 +23,7 @@ import net.minecraft.world.entity.ai.attributes.AttributeModifier.Operation; import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.item.TooltipFlag; +import net.neoforged.neoforge.common.NeoForgeConfig; import net.neoforged.neoforge.common.NeoForgeMod; import net.neoforged.neoforge.common.util.AttributeUtil; import org.jetbrains.annotations.Nullable; @@ -83,7 +84,7 @@ default MutableComponent toComponent(AttributeModifier modif, TooltipFlag flag) default Component getDebugInfo(AttributeModifier modif, TooltipFlag flag) { Component debugInfo = CommonComponents.EMPTY; - if (flag.isAdvanced()) { + if (flag.isAdvanced() && NeoForgeConfig.COMMON.attributeAdvancedTooltipDebugInfo.get()) { // Advanced Tooltips show the underlying operation and the "true" value. We offset MULTIPLY_TOTAL by 1 due to how the operation is calculated. double advValue = (modif.operation() == Operation.ADD_MULTIPLIED_TOTAL ? 1 : 0) + modif.amount(); String valueStr = FORMAT.format(advValue); diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IBlockExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IBlockExtension.java index 813b8a39..6640d904 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IBlockExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IBlockExtension.java @@ -73,7 +73,6 @@ import net.minecraft.world.level.material.PushReaction; import net.minecraft.world.level.pathfinder.PathType; import net.minecraft.world.level.pathfinder.WalkNodeEvaluator; -import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; import net.neoforged.fml.loading.FMLEnvironment; import net.neoforged.neoforge.capabilities.BlockCapabilityCache; @@ -324,14 +323,12 @@ default float getExplosionResistance(BlockState state, BlockGetter level, BlockP } /** - * * Called when A user uses the creative pick block button on this block * - * @param target The full target the player is looking at * @return A ItemStack to add to the player's inventory, empty itemstack if nothing should be added. */ - default ItemStack getCloneItemStack(BlockState state, HitResult target, LevelReader level, BlockPos pos, Player player) { - return self().getCloneItemStack(level, pos, state); + default ItemStack getCloneItemStack(LevelReader level, BlockPos pos, BlockState state, boolean includeData, Player player) { + return state.getCloneItemStack(level, pos, includeData); } /** @@ -1019,4 +1016,16 @@ default BubbleColumnDirection getBubbleColumnDirection(BlockState state) { return BubbleColumnDirection.NONE; } } + + /** + * Determines if a fluid adjacent to the block on the given side should not be rendered. + * + * @param state the block state of the block + * @param selfFace the face of this block that the fluid is adjacent to + * @param adjacentFluid the fluid that is touching that face + * @return true if this block should cause the fluid's face to not render + */ + default boolean shouldHideAdjacentFluidFace(BlockState state, Direction selfFace, FluidState adjacentFluid) { + return state.getFluidState().getType().isSame(adjacentFluid.getType()); + } } diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IBlockStateExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IBlockStateExtension.java index 3e08a8b5..8402500f 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IBlockStateExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IBlockStateExtension.java @@ -39,7 +39,6 @@ import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.material.PushReaction; import net.minecraft.world.level.pathfinder.PathType; -import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; import net.neoforged.neoforge.capabilities.BlockCapabilityCache; import net.neoforged.neoforge.common.ItemAbilities; @@ -223,11 +222,10 @@ default float getExplosionResistance(BlockGetter level, BlockPos pos, Explosion * * Called when A user uses the creative pick block button on this block * - * @param target The full target the player is looking at * @return A ItemStack to add to the player's inventory, empty itemstack if nothing should be added. */ - default ItemStack getCloneItemStack(HitResult target, LevelReader level, BlockPos pos, Player player) { - return self().getBlock().getCloneItemStack(self(), target, level, pos, player); + default ItemStack getCloneItemStack(BlockPos pos, LevelReader level, boolean includeData, Player player) { + return self().getBlock().getCloneItemStack(level, pos, self(), includeData, player); } /** @@ -755,4 +753,15 @@ default boolean isEmpty() { default BubbleColumnDirection getBubbleColumnDirection() { return self().getBlock().getBubbleColumnDirection(self()); } + + /** + * Determines if a fluid adjacent to the block on the given side should not be rendered. + * + * @param selfFace the face of this block that the fluid is adjacent to + * @param adjacentFluid the fluid that is touching that face + * @return true if this block should cause the fluid's face to not render + */ + default boolean shouldHideAdjacentFluidFace(Direction selfFace, FluidState adjacentFluid) { + return self().getBlock().shouldHideAdjacentFluidFace(self(), selfFace, adjacentFluid); + } } diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IEntityExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IEntityExtension.java index 3b495ea5..ba454043 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IEntityExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IEntityExtension.java @@ -20,11 +20,9 @@ import net.minecraft.world.entity.boss.enderdragon.EnderDragon; import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.material.FluidState; -import net.minecraft.world.phys.HitResult; import net.neoforged.neoforge.attachment.AttachmentInternals; import net.neoforged.neoforge.attachment.AttachmentType; import net.neoforged.neoforge.common.SoundAction; @@ -89,17 +87,6 @@ default boolean shouldRiderSit() { return true; } - /** - * Called when a user uses the creative pick block button on this entity. - * - * @param target The full target the player is looking at - * @return A ItemStack to add to the player's inventory, null ItemStack if nothing should be added. - */ - @Nullable - default ItemStack getPickedResult(HitResult target) { - return self().getPickResult(); - } - /** * If a rider of this entity can interact with this entity. Should return true on the * ridden entity if so. diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IFallableExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IFallableExtension.java new file mode 100644 index 00000000..e4551d46 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IFallableExtension.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.common.extensions; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.entity.item.FallingBlockEntity; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Fallable; + +public interface IFallableExtension { + /** + * Called in {@link FallingBlockEntity#tick()} after vanilla processing on both server and client. + *

    + * This is not called in the tick where the entity lands, see {@link Fallable}. + * + * @param level The current level. + * @param currentPosition The current position of the entity as a {@link BlockPos}. + * @param entity The falling entity. + */ + default void fallingTick(Level level, BlockPos currentPosition, FallingBlockEntity entity) {} +} diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IItemExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IItemExtension.java index ab5cc0f8..662f8169 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IItemExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IItemExtension.java @@ -13,13 +13,13 @@ import java.util.function.Consumer; import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; import net.minecraft.core.HolderLookup.RegistryLookup; import net.minecraft.core.HolderSet; import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponentType; import net.minecraft.core.component.DataComponents; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.tags.ItemTags; import net.minecraft.util.Mth; @@ -48,7 +48,6 @@ import net.minecraft.world.item.enchantment.Enchantment.EnchantmentDefinition; import net.minecraft.world.item.enchantment.EnchantmentInstance; import net.minecraft.world.item.enchantment.ItemEnchantments; -import net.minecraft.world.item.equipment.EquipmentModel; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.FuelValues; import net.minecraft.world.phys.AABB; @@ -296,24 +295,6 @@ default boolean isBookEnchantable(ItemStack stack, ItemStack book) { return true; } - /** - * Called by RenderBiped and RenderPlayer to determine the armor texture that - * should be used for the currently equipped item. This will be called on - * stacks with the {@link DataComponents#EQUIPPABLE} component. - * - * Returning null from this function will use the default value. - * - * @param stack ItemStack for the equipped armor - * @param type The layer type of the armor - * @param layer The armor layer - * @param _default The default texture determined by the equipment renderer - * @return Path of texture to bind, or null to use default - */ - @Nullable - default ResourceLocation getArmorTexture(ItemStack stack, EquipmentModel.LayerType type, EquipmentModel.Layer layer, ResourceLocation _default) { - return null; - } - /** * Called when a entity tries to play the 'swing' animation. * @@ -550,8 +531,8 @@ default boolean canContinueUsing(ItemStack oldStack, ItemStack newStack) { * associated mod and {@link net.minecraft.core.Registry#getKey(Object)} would return null. */ @Nullable - default String getCreatorModId(ItemStack itemStack) { - return CommonHooks.getDefaultCreatorModId(itemStack); + default String getCreatorModId(HolderLookup.Provider registries, ItemStack itemStack) { + return CommonHooks.getDefaultCreatorModId(registries, itemStack); } /** diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/ILevelExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/ILevelExtension.java index b8534774..1b84f151 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/ILevelExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/ILevelExtension.java @@ -5,8 +5,6 @@ package net.neoforged.neoforge.common.extensions; -import java.util.Collection; -import java.util.Collections; import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; @@ -16,7 +14,6 @@ import net.minecraft.world.level.block.state.BlockState; import net.neoforged.neoforge.capabilities.BlockCapability; import net.neoforged.neoforge.client.model.data.ModelDataManager; -import net.neoforged.neoforge.entity.PartEntity; import org.jetbrains.annotations.Nullable; public interface ILevelExtension { @@ -49,14 +46,6 @@ private Level self() { */ public double increaseMaxEntityRadius(double value); - /** - * All part entities in this world. Used when collecting entities in an AABB to fix parts being - * ignored whose parent entity is in a chunk that does not intersect with the AABB. - */ - public default Collection> getPartEntities() { - return Collections.emptyList(); - } - /** * Retrieves the model data manager for the given level. May be null on a server level. * diff --git a/src/main/java/net/neoforged/neoforge/common/loot/LootModifierManager.java b/src/main/java/net/neoforged/neoforge/common/loot/LootModifierManager.java index c7a060cf..f75bb6ea 100644 --- a/src/main/java/net/neoforged/neoforge/common/loot/LootModifierManager.java +++ b/src/main/java/net/neoforged/neoforge/common/loot/LootModifierManager.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.function.Function; +import net.minecraft.resources.FileToIdConverter; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.ResourceManager; @@ -39,7 +40,7 @@ public class LootModifierManager extends SimpleJsonResourceReloadListener { + if (Minecraft.getInstance().getOverlay() instanceof LoadingOverlay) { + return; + } + if (Minecraft.getInstance().isRunning()) { + writeSelfTestReport(clientSelfTestDestination); + Minecraft.getInstance().stop(); + } + }); + } + } + + public static void initCommon() { + var serverSelfTestDestination = System.getenv("NEOFORGE_DEDICATED_SERVER_SELFTEST"); + if (serverSelfTestDestination != null) { + if (FMLLoader.getDist() != Dist.DEDICATED_SERVER) { + LOGGER.error("The server self-test ran with a dist of {} instead of dedicated server!", FMLLoader.getDist()); + System.exit(1); + } + NeoForge.EVENT_BUS.addListener((ServerTickEvent.Pre e) -> { + if (e.getServer().isRunning()) { + writeSelfTestReport(serverSelfTestDestination); + e.getServer().halt(false); + } + }); + } + } + + /** + * This is used by our GitHub Actions pipeline to run an E2E test for PRs. + * It writes a small self-test report to the file indicated by the system property and exits. + */ + private static void writeSelfTestReport(String path) { + try { + Files.createFile(Paths.get(path)); + LOGGER.info("Wrote self-test report to '{}'", path); + } catch (IOException e) { + LOGGER.error("Failed to write self-test to '{}'", path, e); + System.exit(1); + } + } +} diff --git a/src/main/java/net/neoforged/neoforge/common/util/flag/FeatureFlagLoader.java b/src/main/java/net/neoforged/neoforge/common/util/flag/FeatureFlagLoader.java new file mode 100644 index 00000000..17bd9a5a --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/util/flag/FeatureFlagLoader.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.common.util.flag; + +import com.google.common.base.Preconditions; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.io.BufferedReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.flag.FeatureFlagRegistry; +import net.neoforged.fml.ModLoader; +import net.neoforged.fml.ModLoadingIssue; +import net.neoforged.fml.loading.LoadingModList; +import net.neoforged.fml.loading.moddiscovery.ModFileInfo; +import net.neoforged.neoforgespi.language.IModInfo; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public final class FeatureFlagLoader { + private static final Gson GSON = new Gson(); + + private FeatureFlagLoader() {} + + public static void loadModdedFlags(FeatureFlagRegistry.Builder builder) { + Map pathPerMod = new HashMap<>(); + LoadingModList.get() + .getModFiles() + .stream() + .map(ModFileInfo::getMods) + .flatMap(List::stream) + .forEach(mod -> mod.getConfig().getConfigElement("featureFlags").ifPresent(file -> { + Path path = mod.getOwningFile().getFile().findResource(file); + if (!Files.isRegularFile(path)) { + ModLoader.addLoadingIssue(ModLoadingIssue.error("fml.modloadingissue.feature_flags.file_not_found", path).withAffectedMod(mod)); + return; + } + pathPerMod.put(mod, path); + })); + + pathPerMod.forEach((mod, path) -> { + try (BufferedReader reader = Files.newBufferedReader(path)) { + JsonObject obj = GSON.fromJson(reader, JsonObject.class); + JsonArray flagArray = GsonHelper.getAsJsonArray(obj, "flags"); + for (JsonElement elem : flagArray) { + String flagName = GsonHelper.convertToString(elem, "flag"); + ResourceLocation flagLocation = ResourceLocation.parse(flagName); + Preconditions.checkArgument( + flagLocation.getNamespace().equals(mod.getModId()), + "Cannot add new flags to foreign namespaces: %s", + flagLocation); + builder.create(flagLocation, true); + } + } catch (Throwable e) { + ModLoader.addLoadingIssue(ModLoadingIssue.error("fml.modloadingissue.feature_flags.loading_error", path) + .withAffectedMod(mod) + .withCause(e)); + } + }); + } +} diff --git a/src/main/java/net/neoforged/neoforge/common/util/flag/package-info.java b/src/main/java/net/neoforged/neoforge/common/util/flag/package-info.java new file mode 100644 index 00000000..e2afddc9 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/util/flag/package-info.java @@ -0,0 +1,13 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +@FieldsAreNonnullByDefault +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package net.neoforged.neoforge.common.util.flag; + +import javax.annotation.ParametersAreNonnullByDefault; +import net.minecraft.FieldsAreNonnullByDefault; +import net.minecraft.MethodsReturnNonnullByDefault; diff --git a/src/main/java/net/neoforged/neoforge/common/world/BiomeSpecialEffectsBuilder.java b/src/main/java/net/neoforged/neoforge/common/world/BiomeSpecialEffectsBuilder.java index 083d3863..a4e283c6 100644 --- a/src/main/java/net/neoforged/neoforge/common/world/BiomeSpecialEffectsBuilder.java +++ b/src/main/java/net/neoforged/neoforge/common/world/BiomeSpecialEffectsBuilder.java @@ -9,6 +9,7 @@ import net.minecraft.core.Holder; import net.minecraft.sounds.Music; import net.minecraft.sounds.SoundEvent; +import net.minecraft.util.random.SimpleWeightedRandomList; import net.minecraft.world.level.biome.AmbientAdditionsSettings; import net.minecraft.world.level.biome.AmbientMoodSettings; import net.minecraft.world.level.biome.AmbientParticleSettings; @@ -88,7 +89,7 @@ public Optional getAmbientAdditionsSound() { return this.ambientAdditionsSettings; } - public Optional getBackgroundMusic() { + public Optional> getBackgroundMusic() { return this.backgroundMusic; } } diff --git a/src/main/java/net/neoforged/neoforge/data/event/GatherDataEvent.java b/src/main/java/net/neoforged/neoforge/data/event/GatherDataEvent.java index 64bb7a0b..f35bb176 100644 --- a/src/main/java/net/neoforged/neoforge/data/event/GatherDataEvent.java +++ b/src/main/java/net/neoforged/neoforge/data/event/GatherDataEvent.java @@ -20,12 +20,17 @@ import net.minecraft.DetectedVersion; import net.minecraft.core.HolderLookup; import net.minecraft.data.DataGenerator; +import net.minecraft.data.DataProvider; +import net.minecraft.data.PackOutput; +import net.minecraft.data.tags.TagsProvider; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; import net.neoforged.bus.api.Event; import net.neoforged.fml.ModContainer; import net.neoforged.fml.event.IModBusEvent; import net.neoforged.neoforge.common.data.ExistingFileHelper; -public class GatherDataEvent extends Event implements IModBusEvent { +public abstract class GatherDataEvent extends Event implements IModBusEvent { private final DataGenerator dataGenerator; private final DataGeneratorConfig config; private final ExistingFileHelper existingFileHelper; @@ -58,14 +63,6 @@ public CompletableFuture getLookupProvider() { return this.config.lookupProvider; } - public boolean includeServer() { - return this.config.server; - } - - public boolean includeClient() { - return this.config.client; - } - public boolean includeDev() { return this.config.dev; } @@ -78,13 +75,23 @@ public boolean validate() { return this.config.validate; } + public static class Server extends GatherDataEvent { + public Server(ModContainer mc, DataGenerator dataGenerator, DataGeneratorConfig dataGeneratorConfig, ExistingFileHelper existingFileHelper) { + super(mc, dataGenerator, dataGeneratorConfig, existingFileHelper); + } + } + + public static class Client extends GatherDataEvent { + public Client(ModContainer mc, DataGenerator dataGenerator, DataGeneratorConfig dataGeneratorConfig, ExistingFileHelper existingFileHelper) { + super(mc, dataGenerator, dataGeneratorConfig, existingFileHelper); + } + } + public static class DataGeneratorConfig { private final Set mods; private final Path path; private final Collection inputs; private final CompletableFuture lookupProvider; - private final boolean server; - private final boolean client; private final boolean dev; private final boolean reports; private final boolean validate; @@ -92,17 +99,18 @@ public static class DataGeneratorConfig { private final List generators = new ArrayList<>(); public DataGeneratorConfig(final Set mods, final Path path, final Collection inputs, final CompletableFuture lookupProvider, - final boolean server, final boolean client, final boolean dev, final boolean reports, final boolean validate, final boolean flat) { + final boolean dev, final boolean reports, final boolean validate, final boolean flat, final DataGenerator vanillaGenerator) { this.mods = mods; this.path = path; this.inputs = inputs; this.lookupProvider = lookupProvider; - this.server = server; - this.client = client; this.dev = dev; this.reports = reports; this.validate = validate; this.flat = flat; + if (mods.contains("minecraft") || mods.isEmpty()) { + this.generators.add(vanillaGenerator); + } } public Collection getInputs() { @@ -139,4 +147,59 @@ public void runAll() { }); } } + + public T addProvider(T provider) { + return dataGenerator.addProvider(true, provider); + } + + public T createProvider(DataProviderFromOutput builder) { + return addProvider(builder.create(dataGenerator.getPackOutput())); + } + + public T createProvider(DataProviderFromOutputFileHelper builder) { + return addProvider(builder.create(dataGenerator.getPackOutput(), existingFileHelper)); + } + + public T createProvider(DataProviderFromOutputLookup builder) { + return addProvider(builder.create(dataGenerator.getPackOutput(), config.lookupProvider)); + } + + public T createProvider(DataProviderFromOutputLookupFileHelper builder) { + return addProvider(builder.create(dataGenerator.getPackOutput(), config.lookupProvider, existingFileHelper)); + } + + public void createBlockAndItemTags(DataProviderFromOutputLookupFileHelper> blockTagsProvider, ItemTagsProvider itemTagsProvider) { + var blockTags = createProvider(blockTagsProvider); + addProvider(itemTagsProvider.create(this.getGenerator().getPackOutput(), this.getLookupProvider(), blockTags.contentsGetter(), this.getExistingFileHelper())); + } + + @FunctionalInterface + public interface DataProviderFromOutput { + T create(PackOutput output); + } + + @FunctionalInterface + public interface DataProviderFromOutputLookup { + T create(PackOutput output, CompletableFuture lookupProvider); + } + + @FunctionalInterface + public interface DataProviderFromOutputFileHelper { + T create(PackOutput output, ExistingFileHelper existingFileHelper); + } + + @FunctionalInterface + public interface DataProviderFromOutputLookupFileHelper { + T create(PackOutput output, CompletableFuture lookupProvider, ExistingFileHelper existingFileHelper); + } + + @FunctionalInterface + public interface GatherDataEventGenerator { + GatherDataEvent create(final ModContainer mc, final DataGenerator dataGenerator, final DataGeneratorConfig dataGeneratorConfig, ExistingFileHelper existingFileHelper); + } + + @FunctionalInterface + public interface ItemTagsProvider { + TagsProvider create(PackOutput output, CompletableFuture lookupProvider, CompletableFuture> contentsGetter, ExistingFileHelper existingFileHelper); + } } diff --git a/src/main/java/net/neoforged/neoforge/data/loading/DatagenModLoader.java b/src/main/java/net/neoforged/neoforge/data/loading/DatagenModLoader.java index 687dc8dd..faa52ca0 100644 --- a/src/main/java/net/neoforged/neoforge/data/loading/DatagenModLoader.java +++ b/src/main/java/net/neoforged/neoforge/data/loading/DatagenModLoader.java @@ -12,11 +12,10 @@ import java.util.concurrent.CompletableFuture; import net.minecraft.Util; import net.minecraft.core.HolderLookup; +import net.minecraft.data.DataGenerator; import net.minecraft.data.registries.VanillaRegistries; import net.minecraft.server.Bootstrap; import net.neoforged.fml.ModLoader; -import net.neoforged.neoforge.client.ClientHooks; -import net.neoforged.neoforge.client.entity.animation.json.AnimationTypeManager; import net.neoforged.neoforge.common.data.ExistingFileHelper; import net.neoforged.neoforge.data.event.GatherDataEvent; import net.neoforged.neoforge.internal.CommonModLoader; @@ -37,8 +36,9 @@ public static boolean isRunningDataGen() { @ApiStatus.Internal public static void begin(final Set mods, final Path path, final Collection inputs, Collection existingPacks, - Set existingMods, final boolean serverGenerators, final boolean clientGenerators, final boolean devToolGenerators, final boolean reportsGenerator, - final boolean structureValidator, final boolean flat, final String assetIndex, final File assetsDir) { + Set existingMods, final boolean devToolGenerators, final boolean reportsGenerator, + final boolean structureValidator, final boolean flat, final String assetIndex, final File assetsDir, Runnable setup, GatherDataEvent.GatherDataEventGenerator eventGenerator, + DataGenerator vanillaGenerator) { if (mods.contains("minecraft") && mods.size() == 1) return; LOGGER.info("Initializing Data Gatherer for mods {}", mods); @@ -48,18 +48,14 @@ public static void begin(final Set mods, final Path path, final Collecti // Modify components as the (modified) defaults may be required in datagen, i.e. stack size RegistrationEvents.modifyComponents(); CompletableFuture lookupProvider = CompletableFuture.supplyAsync(VanillaRegistries::createLookup, Util.backgroundExecutor()); - dataGeneratorConfig = new GatherDataEvent.DataGeneratorConfig(mods, path, inputs, lookupProvider, serverGenerators, - clientGenerators, devToolGenerators, reportsGenerator, structureValidator, flat); + dataGeneratorConfig = new GatherDataEvent.DataGeneratorConfig(mods, path, inputs, lookupProvider, devToolGenerators, reportsGenerator, structureValidator, flat, vanillaGenerator); if (!mods.contains("neoforge")) { // If we aren't generating data for forge, automatically add forge as an existing so mods can access forge's data existingMods.add("neoforge"); } - if (clientGenerators) { - ClientHooks.registerSpriteSourceTypes(); - AnimationTypeManager.init(); - } + setup.run(); existingFileHelper = new ExistingFileHelper(existingPacks, existingMods, structureValidator, assetIndex, assetsDir); - ModLoader.runEventGenerator(mc -> new GatherDataEvent(mc, dataGeneratorConfig.makeGenerator(p -> dataGeneratorConfig.isFlat() ? p : p.resolve(mc.getModId()), + ModLoader.runEventGenerator(mc -> eventGenerator.create(mc, dataGeneratorConfig.makeGenerator(p -> dataGeneratorConfig.isFlat() ? p : p.resolve(mc.getModId()), dataGeneratorConfig.getMods().contains(mc.getModId())), dataGeneratorConfig, existingFileHelper)); dataGeneratorConfig.runAll(); } diff --git a/src/main/java/net/neoforged/neoforge/event/EventHooks.java b/src/main/java/net/neoforged/neoforge/event/EventHooks.java index cca9ed38..7529a767 100644 --- a/src/main/java/net/neoforged/neoforge/event/EventHooks.java +++ b/src/main/java/net/neoforged/neoforge/event/EventHooks.java @@ -588,7 +588,7 @@ public static boolean onExplosionStart(Level level, ServerExplosion explosion) { return NeoForge.EVENT_BUS.post(new ExplosionEvent.Start(level, explosion)).isCanceled(); } - public static void onExplosionDetonate(Level level, ServerExplosion explosion, List list, double diameter) { + public static void onExplosionDetonate(Level level, ServerExplosion explosion, List entities, List blocks) { //Filter entities to only those who are effected, to prevent modders from seeing more then will be hurt. /* Enable this if we get issues with modders looping to much. Iterator itr = list.iterator(); @@ -600,7 +600,7 @@ public static void onExplosionDetonate(Level level, ServerExplosion explosion, L if (e.isImmuneToExplosions() || dist > 1.0F) itr.remove(); } */ - NeoForge.EVENT_BUS.post(new ExplosionEvent.Detonate(level, explosion, list)); + NeoForge.EVENT_BUS.post(new ExplosionEvent.Detonate(level, explosion, entities, blocks)); } /** @@ -613,8 +613,8 @@ public static void onExplosionDetonate(Level level, ServerExplosion explosion, L * @param initialVelocity The explosion calculated velocity for the entity * @return The new explosion velocity to add to the entity's existing velocity */ - public static Vec3 getExplosionKnockback(Level level, ServerExplosion explosion, Entity entity, Vec3 initialVelocity) { - ExplosionKnockbackEvent event = new ExplosionKnockbackEvent(level, explosion, entity, initialVelocity); + public static Vec3 getExplosionKnockback(Level level, ServerExplosion explosion, Entity entity, Vec3 initialVelocity, List blocks) { + ExplosionKnockbackEvent event = new ExplosionKnockbackEvent(level, explosion, entity, initialVelocity, blocks); NeoForge.EVENT_BUS.post(event); return event.getKnockbackVelocity(); } @@ -908,8 +908,8 @@ public static void firePlayerCraftingEvent(Player player, ItemStack crafted, Con NeoForge.EVENT_BUS.post(new PlayerEvent.ItemCraftedEvent(player, crafted, craftMatrix)); } - public static void firePlayerSmeltedEvent(Player player, ItemStack smelted) { - NeoForge.EVENT_BUS.post(new PlayerEvent.ItemSmeltedEvent(player, smelted)); + public static void firePlayerSmeltedEvent(Player player, ItemStack smelted, int amountRemoved) { + NeoForge.EVENT_BUS.post(new PlayerEvent.ItemSmeltedEvent(player, smelted, amountRemoved)); } /** diff --git a/src/main/java/net/neoforged/neoforge/event/entity/player/PlayerEvent.java b/src/main/java/net/neoforged/neoforge/event/entity/player/PlayerEvent.java index 020c86cb..66c55569 100644 --- a/src/main/java/net/neoforged/neoforge/event/entity/player/PlayerEvent.java +++ b/src/main/java/net/neoforged/neoforge/event/entity/player/PlayerEvent.java @@ -408,15 +408,21 @@ public Container getInventory() { public static class ItemSmeltedEvent extends PlayerEvent { private final ItemStack smelting; + private final int amountRemoved; - public ItemSmeltedEvent(Player player, ItemStack crafting) { + public ItemSmeltedEvent(Player player, ItemStack crafting, int amountRemoved) { super(player); this.smelting = crafting; + this.amountRemoved = amountRemoved; } public ItemStack getSmelting() { return this.smelting; } + + public int getAmountRemoved() { + return this.amountRemoved; + } } public static class PlayerLoggedInEvent extends PlayerEvent { diff --git a/src/main/java/net/neoforged/neoforge/event/level/ExplosionEvent.java b/src/main/java/net/neoforged/neoforge/event/level/ExplosionEvent.java index b6d1d38e..34118e37 100644 --- a/src/main/java/net/neoforged/neoforge/event/level/ExplosionEvent.java +++ b/src/main/java/net/neoforged/neoforge/event/level/ExplosionEvent.java @@ -6,6 +6,7 @@ package net.neoforged.neoforge.event.level; import java.util.List; +import net.minecraft.core.BlockPos; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; import net.minecraft.world.level.ServerExplosion; @@ -63,17 +64,18 @@ public Start(Level level, ServerExplosion explosion) { */ public static class Detonate extends ExplosionEvent { private final List entityList; + private final List blockList; - public Detonate(Level level, ServerExplosion explosion, List entityList) { + public Detonate(Level level, ServerExplosion explosion, List entityList, List blockList) { super(level, explosion); this.entityList = entityList; + this.blockList = blockList; } /** return the list of blocks affected by the explosion. */ - // FIXME porting: explosions changed a bit - /*public List getAffectedBlocks() { - return getExplosion().getToBlow(); - }*/ + public List getAffectedBlocks() { + return this.blockList; + } /** return the list of entities affected by the explosion. */ public List getAffectedEntities() { diff --git a/src/main/java/net/neoforged/neoforge/event/level/ExplosionKnockbackEvent.java b/src/main/java/net/neoforged/neoforge/event/level/ExplosionKnockbackEvent.java index d07fee5f..321e30f4 100644 --- a/src/main/java/net/neoforged/neoforge/event/level/ExplosionKnockbackEvent.java +++ b/src/main/java/net/neoforged/neoforge/event/level/ExplosionKnockbackEvent.java @@ -5,6 +5,8 @@ package net.neoforged.neoforge.event.level; +import java.util.List; +import net.minecraft.core.BlockPos; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; import net.minecraft.world.level.ServerExplosion; @@ -21,19 +23,20 @@ */ public class ExplosionKnockbackEvent extends ExplosionEvent { private final Entity entity; + private final List blockList; private Vec3 knockbackVelocity; - public ExplosionKnockbackEvent(Level level, ServerExplosion explosion, Entity entity, Vec3 knockbackVelocity) { + public ExplosionKnockbackEvent(Level level, ServerExplosion explosion, Entity entity, Vec3 knockbackVelocity, List blockList) { super(level, explosion); this.entity = entity; + this.blockList = blockList; this.knockbackVelocity = knockbackVelocity; } /** return the list of blocks affected by the explosion. */ - // FIXME porting: explosions changed a bit - /*public List getAffectedBlocks() { - return getExplosion().getToBlow(); - }*/ + public List getAffectedBlocks() { + return this.blockList; + } /** return the entity affected by the explosion knockback. */ public Entity getAffectedEntity() { diff --git a/src/main/java/net/neoforged/neoforge/network/ConfigurationInitialization.java b/src/main/java/net/neoforged/neoforge/network/ConfigurationInitialization.java index 2c646643..e74fb87a 100644 --- a/src/main/java/net/neoforged/neoforge/network/ConfigurationInitialization.java +++ b/src/main/java/net/neoforged/neoforge/network/ConfigurationInitialization.java @@ -12,6 +12,7 @@ import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.network.configuration.CheckExtensibleEnums; +import net.neoforged.neoforge.network.configuration.CheckFeatureFlags; import net.neoforged.neoforge.network.configuration.CommonRegisterTask; import net.neoforged.neoforge.network.configuration.CommonVersionTask; import net.neoforged.neoforge.network.configuration.RegistryDataMapNegotiation; @@ -53,8 +54,9 @@ private static void configureModdedClient(RegisterConfigurationTasksEvent event) event.register(new SyncConfig(listener)); } - //These two can always be registered they detect the listener connection type internally and will skip themselves. + //These can always be registered, they detect the listener connection type internally and will skip themselves. event.register(new RegistryDataMapNegotiation(listener)); event.register(new CheckExtensibleEnums(listener)); + event.register(new CheckFeatureFlags(listener)); } } diff --git a/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java b/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java index a3e9c32c..699ece24 100644 --- a/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java +++ b/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java @@ -9,6 +9,7 @@ import net.neoforged.fml.common.EventBusSubscriber; import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; import net.neoforged.neoforge.network.configuration.CheckExtensibleEnums; +import net.neoforged.neoforge.network.configuration.CheckFeatureFlags; import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; import net.neoforged.neoforge.network.handlers.ClientPayloadHandler; import net.neoforged.neoforge.network.handlers.ServerPayloadHandler; @@ -21,6 +22,8 @@ import net.neoforged.neoforge.network.payload.ConfigFilePayload; import net.neoforged.neoforge.network.payload.ExtensibleEnumAcknowledgePayload; import net.neoforged.neoforge.network.payload.ExtensibleEnumDataPayload; +import net.neoforged.neoforge.network.payload.FeatureFlagAcknowledgePayload; +import net.neoforged.neoforge.network.payload.FeatureFlagDataPayload; import net.neoforged.neoforge.network.payload.FrozenRegistryPayload; import net.neoforged.neoforge.network.payload.FrozenRegistrySyncCompletedPayload; import net.neoforged.neoforge.network.payload.FrozenRegistrySyncStartPayload; @@ -64,6 +67,10 @@ private static void register(final RegisterPayloadHandlersEvent event) { ExtensibleEnumDataPayload.TYPE, ExtensibleEnumDataPayload.STREAM_CODEC, CheckExtensibleEnums::handleClientboundPayload) + .configurationToClient( + FeatureFlagDataPayload.TYPE, + FeatureFlagDataPayload.STREAM_CODEC, + CheckFeatureFlags::handleClientboundPayload) .configurationToServer( KnownRegistryDataMapsReplyPayload.TYPE, KnownRegistryDataMapsReplyPayload.STREAM_CODEC, @@ -72,6 +79,10 @@ private static void register(final RegisterPayloadHandlersEvent event) { ExtensibleEnumAcknowledgePayload.TYPE, ExtensibleEnumAcknowledgePayload.STREAM_CODEC, CheckExtensibleEnums::handleServerboundPayload) + .configurationToServer( + FeatureFlagAcknowledgePayload.TYPE, + FeatureFlagAcknowledgePayload.STREAM_CODEC, + CheckFeatureFlags::handleServerboundPayload) .playToClient( AdvancedAddEntityPayload.TYPE, AdvancedAddEntityPayload.STREAM_CODEC, diff --git a/src/main/java/net/neoforged/neoforge/network/PacketDistributor.java b/src/main/java/net/neoforged/neoforge/network/PacketDistributor.java index f09dfae0..ea23ac4d 100644 --- a/src/main/java/net/neoforged/neoforge/network/PacketDistributor.java +++ b/src/main/java/net/neoforged/neoforge/network/PacketDistributor.java @@ -87,22 +87,24 @@ public static void sendToAllPlayers(CustomPacketPayload payload, CustomPacketPay * Send the given payload(s) to all players tracking the given entity */ public static void sendToPlayersTrackingEntity(Entity entity, CustomPacketPayload payload, CustomPacketPayload... payloads) { - if (entity.level().getChunkSource() instanceof ServerChunkCache chunkCache) { - chunkCache.broadcast(entity, makeClientboundPacket(payload, payloads)); - } else { + if (entity.level().isClientSide()) { throw new IllegalStateException("Cannot send clientbound payloads on the client"); + } else if (entity.level().getChunkSource() instanceof ServerChunkCache chunkCache) { + chunkCache.broadcast(entity, makeClientboundPacket(payload, payloads)); } + // Silently ignore custom Level implementations which may not return ServerChunkCache. } /** * Send the given payload(s) to all players tracking the given entity and the entity itself if it is a player */ public static void sendToPlayersTrackingEntityAndSelf(Entity entity, CustomPacketPayload payload, CustomPacketPayload... payloads) { - if (entity.level().getChunkSource() instanceof ServerChunkCache chunkCache) { - chunkCache.broadcastAndSend(entity, makeClientboundPacket(payload, payloads)); - } else { + if (entity.level().isClientSide()) { throw new IllegalStateException("Cannot send clientbound payloads on the client"); + } else if (entity.level().getChunkSource() instanceof ServerChunkCache chunkCache) { + chunkCache.broadcastAndSend(entity, makeClientboundPacket(payload, payloads)); } + // Silently ignore custom Level implementations which may not return ServerChunkCache. } /** diff --git a/src/main/java/net/neoforged/neoforge/network/configuration/CheckExtensibleEnums.java b/src/main/java/net/neoforged/neoforge/network/configuration/CheckExtensibleEnums.java index 9d493a3e..a62200e3 100644 --- a/src/main/java/net/neoforged/neoforge/network/configuration/CheckExtensibleEnums.java +++ b/src/main/java/net/neoforged/neoforge/network/configuration/CheckExtensibleEnums.java @@ -75,33 +75,33 @@ public static void handleClientboundPayload(ExtensibleEnumDataPayload payload, I Map localEnumEntries = getEnumEntries(); Map remoteEnumEntries = payload.enumEntries(); - Set keyDiff = Sets.symmetricDifference(localEnumEntries.keySet(), remoteEnumEntries.keySet()); - if (!keyDiff.isEmpty()) { - context.disconnect(Component.translatable("neoforge.network.extensible_enums.enum_set_mismatch")); - return; - } - Map mismatched = new HashMap<>(); - for (EnumEntry localEntry : localEnumEntries.values()) { - EnumEntry remoteEntry = remoteEnumEntries.get(localEntry.className); - if (!localEntry.isExtended() && !remoteEntry.isExtended()) { + for (String className : Sets.union(localEnumEntries.keySet(), remoteEnumEntries.keySet())) { + EnumEntry localEntry = localEnumEntries.get(className); + EnumEntry remoteEntry = remoteEnumEntries.get(className); + if ((localEntry == null && remoteEntry.isExtended()) || (remoteEntry == null && localEntry.isExtended())) { + mismatched.put(className, Mismatch.EXTENSIBILITY); + continue; + } + + if ((localEntry == null || !localEntry.isExtended()) && (remoteEntry == null || !remoteEntry.isExtended())) { continue; } if (localEntry.networkCheck != remoteEntry.networkCheck) { - mismatched.put(localEntry.className, Mismatch.NETWORK_CHECK); + mismatched.put(className, Mismatch.NETWORK_CHECK); continue; } if (localEntry.isExtended() != remoteEntry.isExtended()) { - mismatched.put(localEntry.className, Mismatch.EXTENSION); + mismatched.put(className, Mismatch.EXTENSION); continue; } ExtensionData localData = localEntry.data.orElseThrow(); ExtensionData remoteData = remoteEntry.data.orElseThrow(); if (localData.vanillaCount != remoteData.vanillaCount || localData.totalCount != remoteData.totalCount) { - mismatched.put(localEntry.className, Mismatch.ENTRY_COUNT); + mismatched.put(className, Mismatch.ENTRY_COUNT); continue; } @@ -109,7 +109,7 @@ public static void handleClientboundPayload(ExtensibleEnumDataPayload payload, I List remoteValues = remoteData.entries; for (int i = 0; i < localData.totalCount - localData.vanillaCount; i++) { if (!localValues.get(i).equals(remoteValues.get(i))) { - mismatched.put(localEntry.className, Mismatch.ENTRY_MISMATCH); + mismatched.put(className, Mismatch.ENTRY_MISMATCH); break; } } @@ -122,6 +122,13 @@ public static void handleClientboundPayload(ExtensibleEnumDataPayload payload, I String enumClass = entry.getKey(); message.append("\n").append(enumClass).append(": "); switch (entry.getValue()) { + case EXTENSIBILITY -> { + if (remoteEnumEntries.containsKey(enumClass)) { + message.append("Enum is extensible on the server but not on the client"); + } else { + message.append("Enum is extensible on the client but not on the server"); + } + } case NETWORK_CHECK -> message.append("Mismatched NetworkCheck (server: ") .append(remoteEnumEntries.get(enumClass).networkCheck) .append(", client: ") @@ -258,6 +265,7 @@ public record ExtensionData(int vanillaCount, int totalCount, List entri } private enum Mismatch { + EXTENSIBILITY, NETWORK_CHECK, EXTENSION, ENTRY_COUNT, diff --git a/src/main/java/net/neoforged/neoforge/network/configuration/CheckFeatureFlags.java b/src/main/java/net/neoforged/neoforge/network/configuration/CheckFeatureFlags.java new file mode 100644 index 00000000..74a1173a --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/configuration/CheckFeatureFlags.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.configuration; + +import com.google.common.collect.Sets; +import com.mojang.logging.LogUtils; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.configuration.ClientConfigurationPacketListener; +import net.minecraft.network.protocol.configuration.ServerConfigurationPacketListener; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.network.ConfigurationTask; +import net.minecraft.world.flag.FeatureFlags; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import net.neoforged.neoforge.network.payload.FeatureFlagAcknowledgePayload; +import net.neoforged.neoforge.network.payload.FeatureFlagDataPayload; +import org.jetbrains.annotations.ApiStatus; +import org.slf4j.Logger; + +@ApiStatus.Internal +public record CheckFeatureFlags(ServerConfigurationPacketListener listener) implements ConfigurationTask { + public static final Type TYPE = new Type(ResourceLocation.fromNamespaceAndPath("neoforge", "check_feature_flags")); + private static final Logger LOGGER = LogUtils.getLogger(); + private static Set moddedFlags = null; + + @Override + public void start(Consumer> packetSender) { + if (listener.getConnection().isMemoryConnection()) { + listener.finishCurrentTask(TYPE); + return; + } + + Set moddedFlags = getModdedFeatureFlags(); + if (listener.getConnectionType().isOther() || !listener.hasChannel(FeatureFlagDataPayload.TYPE)) { + if (!moddedFlags.isEmpty()) { + // Use plain components as vanilla connections will be missing our translation keys + listener.disconnect(Component.literal("This server does not support vanilla clients as it has custom FeatureFlags")); + } else { + listener.finishCurrentTask(TYPE); + } + return; + } + packetSender.accept(new FeatureFlagDataPayload(moddedFlags).toVanillaClientbound()); + } + + public static void handleClientboundPayload(FeatureFlagDataPayload payload, IPayloadContext context) { + Set localFlags = getModdedFeatureFlags(); + Set remoteFlags = payload.moddedFlags(); + if (localFlags.equals(remoteFlags)) { + context.reply(FeatureFlagAcknowledgePayload.INSTANCE); + } else { + context.disconnect(Component.translatable("neoforge.network.feature_flags.entry_mismatch")); + + StringBuilder message = new StringBuilder("The server and client have different sets of custom FeatureFlags"); + Set missingLocal = Sets.difference(remoteFlags, localFlags); + if (!missingLocal.isEmpty()) { + message.append("\n\tFlags missing on the client, but present on the server:"); + for (ResourceLocation flag : missingLocal) { + message.append("\n\t\t- ").append(flag); + } + } + Set missingRemote = Sets.difference(localFlags, remoteFlags); + if (!missingRemote.isEmpty()) { + message.append("\n\tFlags missing on the server, but present on the client:"); + for (ResourceLocation flag : missingRemote) { + message.append("\n\t\t- ").append(flag); + } + } + LOGGER.warn(message.toString()); + } + } + + public static void handleServerboundPayload(@SuppressWarnings("unused") FeatureFlagAcknowledgePayload payload, IPayloadContext context) { + context.finishCurrentTask(TYPE); + } + + public static boolean handleVanillaServerConnection(ClientConfigurationPacketListener listener) { + if (!getModdedFeatureFlags().isEmpty()) { + listener.disconnect(Component.translatable("neoforge.network.feature_flags.no_vanilla_server")); + return false; + } + return true; + } + + private static Set getModdedFeatureFlags() { + if (moddedFlags == null) { + moddedFlags = FeatureFlags.REGISTRY.getAllFlags() + .entrySet() + .stream() + .filter(e -> e.getValue().isModded()) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + } + return moddedFlags; + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/FeatureFlagAcknowledgePayload.java b/src/main/java/net/neoforged/neoforge/network/payload/FeatureFlagAcknowledgePayload.java new file mode 100644 index 00000000..b8492d83 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/FeatureFlagAcknowledgePayload.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.payload; + +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public final class FeatureFlagAcknowledgePayload implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath("neoforge", "feature_flags_ack")); + public static final FeatureFlagAcknowledgePayload INSTANCE = new FeatureFlagAcknowledgePayload(); + public static final StreamCodec STREAM_CODEC = StreamCodec.unit(INSTANCE); + + private FeatureFlagAcknowledgePayload() {} + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/FeatureFlagDataPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/FeatureFlagDataPayload.java new file mode 100644 index 00000000..cf83d7b2 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/FeatureFlagDataPayload.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.payload; + +import io.netty.buffer.ByteBuf; +import java.util.HashSet; +import java.util.Set; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public record FeatureFlagDataPayload(Set moddedFlags) implements CustomPacketPayload { + public static final Type TYPE = new Type<>(ResourceLocation.fromNamespaceAndPath(NeoForgeVersion.MOD_ID, "feature_flags")); + public static final StreamCodec STREAM_CODEC = ResourceLocation.STREAM_CODEC + .apply(ByteBufCodecs.>collection(HashSet::new)) + .map(FeatureFlagDataPayload::new, FeatureFlagDataPayload::moddedFlags); + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/registration/NetworkRegistry.java b/src/main/java/net/neoforged/neoforge/network/registration/NetworkRegistry.java index 899598ef..0d90287a 100644 --- a/src/main/java/net/neoforged/neoforge/network/registration/NetworkRegistry.java +++ b/src/main/java/net/neoforged/neoforge/network/registration/NetworkRegistry.java @@ -48,6 +48,7 @@ import net.neoforged.neoforge.common.extensions.ICommonPacketListener; import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; import net.neoforged.neoforge.network.configuration.CheckExtensibleEnums; +import net.neoforged.neoforge.network.configuration.CheckFeatureFlags; import net.neoforged.neoforge.network.configuration.CommonRegisterTask; import net.neoforged.neoforge.network.configuration.CommonVersionTask; import net.neoforged.neoforge.network.connection.ConnectionType; @@ -555,6 +556,10 @@ public static void initializeOtherConnection(ClientConfigurationPacketListener l if (!CheckExtensibleEnums.handleVanillaServerConnection(listener)) { return; } + // We are on the client, connected to a vanilla server, make sure we don't have any modded feature flags + if (!CheckFeatureFlags.handleVanillaServerConnection(listener)) { + return; + } // We are on the client, connected to a vanilla server, We have to load the default configs. ConfigTracker.INSTANCE.loadDefaultServerConfigs(); diff --git a/src/main/java/net/neoforged/neoforge/registries/DeferredRegister.java b/src/main/java/net/neoforged/neoforge/registries/DeferredRegister.java index a4b41334..4d44f562 100644 --- a/src/main/java/net/neoforged/neoforge/registries/DeferredRegister.java +++ b/src/main/java/net/neoforged/neoforge/registries/DeferredRegister.java @@ -170,21 +170,6 @@ public static DataComponents createDataComponents(ResourceKey> registryKey; private final String namespace; private final Map, Supplier> entries = new LinkedHashMap<>(); @@ -424,24 +409,56 @@ public DeferredBlock register(String name, Supplier DeferredBlock registerBlock(String name, Function func, BlockBehaviour.Properties props) { return this.register(name, key -> func.apply(props.setId(ResourceKey.create(Registries.BLOCK, key)))); } /** - * Adds a new simple {@link Block} to the list of entries to be registered and returns a {@link DeferredHolder} that will be populated with the created block automatically. + * Adds a new block to the list of entries to be registered and returns a {@link DeferredHolder} that will be populated with the created block automatically. + * This method uses the default {@link BlockBehaviour.Properties}. + * + * @param name The new block's name. It will automatically have the {@linkplain #getNamespace() namespace} prefixed. + * @param func A factory for the new block. The factory should not cache the created block. + * @return A {@link DeferredHolder} that will track updates from the registry for this block. + * @see #registerBlock(String, Function, BlockBehaviour.Properties) + * @see #registerSimpleBlock(String, BlockBehaviour.Properties) + * @see #registerSimpleBlock(String) + */ + public DeferredBlock registerBlock(String name, Function func) { + return this.registerBlock(name, func, BlockBehaviour.Properties.of()); + } + + /** + * Adds a new simple {@link Block} with the given {@link BlockBehaviour.Properties properties} to the list of entries to be registered and returns a {@link DeferredHolder} that will be populated with the created block automatically. * * @param name The new block's name. It will automatically have the {@linkplain #getNamespace() namespace} prefixed. * @param props The properties for the created block. * @return A {@link DeferredHolder} that will track updates from the registry for this block. * @see #registerBlock(String, Function, BlockBehaviour.Properties) + * @see #registerBlock(String, Function) + * @see #registerSimpleBlock(String) */ public DeferredBlock registerSimpleBlock(String name, BlockBehaviour.Properties props) { return this.registerBlock(name, Block::new, props); } + /** + * Adds a new simple {@link Block} with the default {@link BlockBehaviour.Properties properties} to the list of entries to be registered and returns a {@link DeferredHolder} that will be populated with the created block automatically. + * + * @param name The new block's name. It will automatically have the {@linkplain #getNamespace() namespace} prefixed. + * @return A {@link DeferredHolder} that will track updates from the registry for this block. + * @see #registerBlock(String, Function, BlockBehaviour.Properties) + * @see #registerBlock(String, Function) + * @see #registerSimpleBlock(String, BlockBehaviour.Properties) + */ + public DeferredBlock registerSimpleBlock(String name) { + return this.registerSimpleBlock(name, BlockBehaviour.Properties.of()); + } + @Override protected DeferredBlock createHolder(ResourceKey> registryKey, ResourceLocation key) { return DeferredBlock.createBlock(ResourceKey.create(registryKey, key)); @@ -619,12 +636,6 @@ protected DataComponents(ResourceKey>> registryKey super(registryKey, namespace); } - /** @deprecated Scheduled for removal in 1.21.2; use {@link DataComponents#DataComponents(ResourceKey, String)} */ - @Deprecated(since = "1.21.1", forRemoval = true) - protected DataComponents(String namespace) { - super(Registries.DATA_COMPONENT_TYPE, namespace); - } - /** * Convenience method that constructs a builder for use in the operator. Use this to avoid inference issues. * diff --git a/src/main/java/net/neoforged/neoforge/registries/GameData.java b/src/main/java/net/neoforged/neoforge/registries/GameData.java index 2c21b3c4..c5aea872 100644 --- a/src/main/java/net/neoforged/neoforge/registries/GameData.java +++ b/src/main/java/net/neoforged/neoforge/registries/GameData.java @@ -65,7 +65,11 @@ public static void unfreezeData() { public static void freezeData() { LOGGER.debug(REGISTRIES, "Freezing registries"); - BuiltInRegistries.REGISTRY.stream().filter(r -> r instanceof MappedRegistry).forEach(r -> ((MappedRegistry) r).freeze()); + BuiltInRegistries.REGISTRY.stream().filter(r -> r instanceof MappedRegistry).forEach(r -> { + // HolderSet.Named may be used for registry objects, vanilla binds these tags so freeze doesn't throw for unbound tags + ((MappedRegistry) r).bindAllTagsToEmpty(); + ((MappedRegistry) r).freeze(); + }); RegistryManager.takeFrozenSnapshot(); diff --git a/src/main/java/net/neoforged/neoforge/resource/ContextAwareReloadListener.java b/src/main/java/net/neoforged/neoforge/resource/ContextAwareReloadListener.java index 34cf074f..077dee96 100644 --- a/src/main/java/net/neoforged/neoforge/resource/ContextAwareReloadListener.java +++ b/src/main/java/net/neoforged/neoforge/resource/ContextAwareReloadListener.java @@ -6,9 +6,11 @@ package net.neoforged.neoforge.resource; import com.google.gson.JsonElement; +import com.mojang.serialization.DynamicOps; import com.mojang.serialization.JsonOps; import net.minecraft.core.HolderLookup; import net.minecraft.core.RegistryAccess; +import net.minecraft.resources.RegistryOps; import net.minecraft.server.packs.resources.PreparableReloadListener; import net.minecraft.server.packs.resources.SimplePreparableReloadListener; import net.neoforged.neoforge.common.conditions.ConditionalOps; @@ -54,4 +56,14 @@ protected final HolderLookup.Provider getRegistryLookup() { protected final ConditionalOps makeConditionalOps() { return new ConditionalOps<>(getRegistryLookup().createSerializationContext(JsonOps.INSTANCE), getContext()); } + + protected final ConditionalOps makeConditionalOps(DynamicOps ops) { + if (ops instanceof ConditionalOps conditionalOps) { + return conditionalOps; + } + if (ops instanceof RegistryOps registryOps) { + return new ConditionalOps<>(registryOps, getContext()); + } + return makeConditionalOps(); + } } diff --git a/src/main/java/net/neoforged/neoforge/resource/EmptyPackResources.java b/src/main/java/net/neoforged/neoforge/resource/EmptyPackResources.java index f349038c..3ccaf5aa 100644 --- a/src/main/java/net/neoforged/neoforge/resource/EmptyPackResources.java +++ b/src/main/java/net/neoforged/neoforge/resource/EmptyPackResources.java @@ -13,7 +13,7 @@ import net.minecraft.server.packs.PackLocationInfo; import net.minecraft.server.packs.PackResources; import net.minecraft.server.packs.PackType; -import net.minecraft.server.packs.metadata.MetadataSectionSerializer; +import net.minecraft.server.packs.metadata.MetadataSectionType; import net.minecraft.server.packs.metadata.pack.PackMetadataSection; import net.minecraft.server.packs.repository.Pack; import net.minecraft.server.packs.resources.IoSupplier; @@ -27,11 +27,10 @@ public EmptyPackResources(PackLocationInfo packId, PackMetadataSection packMeta) this.packMeta = packMeta; } - @SuppressWarnings("unchecked") @Nullable @Override - public T getMetadataSection(MetadataSectionSerializer deserializer) { - return deserializer.getMetadataSectionName().equals("pack") ? (T) this.packMeta : null; + public T getMetadataSection(MetadataSectionType type) { + return PackMetadataSection.TYPE.equals(type) ? (T) this.packMeta : null; } @Override diff --git a/src/main/java/net/neoforged/neoforge/resource/ResourcePackLoader.java b/src/main/java/net/neoforged/neoforge/resource/ResourcePackLoader.java index 049aae13..3e85ad9a 100644 --- a/src/main/java/net/neoforged/neoforge/resource/ResourcePackLoader.java +++ b/src/main/java/net/neoforged/neoforge/resource/ResourcePackLoader.java @@ -141,7 +141,7 @@ private static void packFinder(Map modResource packAcceptor.accept(makePack(packType, hiddenPacks)); } - public static final MetadataSectionType OPTIONAL_FORMAT = MetadataSectionType.fromCodec("pack", RecordCodecBuilder.create( + public static final MetadataSectionType OPTIONAL_FORMAT = new MetadataSectionType<>("pack", RecordCodecBuilder.create( in -> in.group( ComponentSerialization.CODEC.optionalFieldOf("description", Component.empty()).forGetter(PackMetadataSection::description), Codec.INT.optionalFieldOf("pack_format", -1).forGetter(PackMetadataSection::packFormat), diff --git a/src/main/java/net/neoforged/neoforge/server/command/TagsCommand.java b/src/main/java/net/neoforged/neoforge/server/command/TagsCommand.java index 38091a87..8645cc87 100644 --- a/src/main/java/net/neoforged/neoforge/server/command/TagsCommand.java +++ b/src/main/java/net/neoforged/neoforge/server/command/TagsCommand.java @@ -11,9 +11,9 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.Dynamic2CommandExceptionType; import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import java.util.Iterator; import java.util.Optional; import java.util.function.Supplier; -import java.util.stream.Collectors; import java.util.stream.Stream; import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; @@ -51,6 +51,10 @@ * */ class TagsCommand { + // The limit of how long the clipboard text can be; no more elements are added to the text if they would push it over this limit + // This is roughly below 32767, the default limit for UTF-8 strings in FriendlyByteBuf. When adjusting this, make sure to leave + // ample room for the explanatory text (see #createMessage() below). + private static final long CLIPBOARD_TEXT_LIMIT = 32600; private static final long PAGE_SIZE = 8; private static final ResourceKey>> ROOT_REGISTRY_KEY = ResourceKey.createRegistryKey(ResourceLocation.withDefaultNamespace("root")); @@ -171,16 +175,45 @@ private static MutableComponent createMessage(final MutableComponent header, final long currentPage, final ChatFormatting elementColor, final Supplier> names) { - final String allElementNames = names.get().sorted().collect(Collectors.joining("\n")); final long totalPages = (count - 1) / PAGE_SIZE + 1; final long actualPage = (long) Mth.clamp(currentPage, 1, totalPages); MutableComponent containsComponent = Component.translatable(containsText, count); if (count > 0) // Highlight the count text, make it clickable, and append page counters { + final String clipboardText; + final StringBuilder clipboardTextBuilder = new StringBuilder(); + boolean reachedLimit = false; + int countedLines = 0; + + Iterator iterator = names.get().sorted().iterator(); + while (iterator.hasNext()) { + final String line = iterator.next(); + if (clipboardTextBuilder.length() + line.length() > CLIPBOARD_TEXT_LIMIT) { + // The to-be-added line puts us over the limit, so stop adding lines + reachedLimit = true; + break; + } + clipboardTextBuilder.append(line).append('\n'); + countedLines++; + } + // Remove the trailing newline if present + if (!clipboardTextBuilder.isEmpty()) { + clipboardTextBuilder.deleteCharAt(clipboardTextBuilder.length() - 1); + } + + if (reachedLimit) { + // Almost went over the limit; add additional info to clipboard text + clipboardText = "(Too many entries to fit in clipboard, showing only first " + countedLines + " entries...)" + '\n' + + clipboardTextBuilder + '\n' + + "(..." + (count - countedLines) + " more entries not shown)"; + } else { + clipboardText = clipboardTextBuilder.toString(); + } + containsComponent = ComponentUtils.wrapInSquareBrackets(containsComponent.withStyle(s -> s .withColor(ChatFormatting.GREEN) - .withClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, allElementNames)) + .withClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, clipboardText)) .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.translatable(copyHoverText))))); containsComponent = Component.translatable("commands.neoforge.tags.page_info", diff --git a/src/main/resources/assets/neoforge/lang/cs_cz.json b/src/main/resources/assets/neoforge/lang/cs_cz.json index 2c95bf1d..737439ae 100644 --- a/src/main/resources/assets/neoforge/lang/cs_cz.json +++ b/src/main/resources/assets/neoforge/lang/cs_cz.json @@ -14,7 +14,7 @@ "fml.menu.mods.info.authors": "Autoři: %1$s", "fml.menu.mods.info.displayurl": "Domovská stránka: %1$s", "fml.menu.mods.info.license": "Licence: %1$s", - "fml.menu.mods.info.securejardisabled": "Bezpečné módové funkce jsou vypnuty, aktualizujte JDK", + "fml.menu.mods.info.securejardisabled": "Bezpečné funkce módů jsou vypnuty, aktualizujte JDK", "fml.menu.mods.info.signature": "Podepsání: %1$s", "fml.menu.mods.info.signature.unsigned": "NEPODEPSANÝ", "fml.menu.mods.info.trust": "Důvěra: %1$s", @@ -30,8 +30,8 @@ "fml.menu.multiplayer.vanilla": "Vanilla server", "fml.menu.multiplayer.vanilla.incompatible": "Nekompatibilní Vanilla server", "fml.menu.multiplayer.unknown": "Neznámý server %1$s", - "fml.menu.multiplayer.serveroutdated": "Verze sítě NeoForge serveru je zastaralá", - "fml.menu.multiplayer.clientoutdated": "Verze sítě NeoForge klienta je zastaralá", + "fml.menu.multiplayer.serveroutdated": "Verze síťování NeoForge serveru je zastaralá", + "fml.menu.multiplayer.clientoutdated": "Verze síťování NeoForge klienta je zastaralá", "fml.menu.multiplayer.extraservermods": "Server obsahuje módy navíc které mohou být potřeba i na klientovi", "fml.menu.multiplayer.modsincompatible": "Seznam módů na serveru není kompatibilní", "fml.menu.multiplayer.networkincompatible": "Seznam zpráv sítě serveru není kompatibilní", @@ -60,7 +60,7 @@ "fml.modmismatchscreen.table.visit.mod_page": "Otevřete stránku módu, který je zaregistrovaný na tento kanál: %s", "fml.modmismatchscreen.simplifiedview": "Zjednodušený pohled", "fml.resources.modresources": "Zdroje pro %1$s módové soubory", - "fml.resources.moddata": "Data pro %1$s módové soubory", + "fml.resources.moddata": "Data pro %1$s soubory módu", "loadwarning.neoforge.prbuild": "Tento NeoForge build byl vytvořen komunitním členem a proto je §c§lBEZ PODPORY§r", "commands.neoforge.arguments.enum.invalid": "Enum konstanta musí být jedna z %1$s, nalezeno %2$s", "commands.neoforge.dimensions.list": "Aktuálně registrované dimenze typu:", @@ -76,11 +76,11 @@ "commands.neoforge.setdim.invalid.dim": "Vybrané ID dimenze (%1$s) je neplatné.", "commands.neoforge.setdim.invalid.nochange": "Vybraná entita (%1$s) již existuje v specifické dimenzi (%2$s).", "commands.neoforge.setdim.deprecated": "Tento příkaz je zastaralý kvůli smazání ve verzi 1.17, použijte místo něj %s.", - "commands.neoforge.tps.overall": "Overall: %s TPS (%s ms/tick)", - "commands.neoforge.tps.tooltip": "Mean TPS; higher is better. Target TPS: %s", + "commands.neoforge.tps.overall": "Celkem: %s TPS (%s ms/tick)", + "commands.neoforge.tps.tooltip": "Průměrné TPS; vyšší je lepší. Cílový TPS: %s", "commands.neoforge.tps.dimension": "%s: %s TPS (%s ms/tick)", - "commands.neoforge.tps.dimension.tooltip": "%s (Dimension Type: %s)", - "commands.neoforge.mods.list": "Módový seznam: %1$s", + "commands.neoforge.tps.dimension.tooltip": "%s (typ dimenze: %s)", + "commands.neoforge.mods.list": "Seznam módů: %1$s", "commands.neoforge.tracking.entity.enabled": "Sledování entity povoleno na %d sekund.", "commands.neoforge.tracking.entity.reset": "Data časování entity byla vymazána!", "commands.neoforge.tracking.invalid": "Neplatná sledovací data.", @@ -115,14 +115,14 @@ "commands.neoforge.timespeed.query.default": "Čas v %s běží normální rychlostí (20 minut za den).", "commands.neoforge.timespeed.set": "Nastaven běh času v %s na %sx (%s minut za den).", "commands.neoforge.timespeed.set.default": "Nastaven běh času v %s na základní nastavení (20 minut za den).", - "commands.neoforge.data_components.list.error.held_stack_empty": "You are not holding any item", - "commands.neoforge.data_components.list.title": "Data components on %s:", + "commands.neoforge.data_components.list.error.held_stack_empty": "Nedržíte žádný předmět", + "commands.neoforge.data_components.list.title": "Komponenty dané na %s:", "commands.neoforge.data_components.list.entry": "\n - %s", "commands.neoforge.data_components.list.entry.key_value": "%s: %s", - "commands.neoforge.data_components.list.tooltip.default": "Component %s holds its default value", - "commands.neoforge.data_components.list.tooltip.deleted": "Component %s with value %s was deleted", - "commands.neoforge.data_components.list.tooltip.modified": "Component %s was modified from %s to %s", - "commands.neoforge.data_components.list.tooltip.added": "Component %s was added with value %s", + "commands.neoforge.data_components.list.tooltip.default": "Komponenta %s drží svou výchozí hodnotu", + "commands.neoforge.data_components.list.tooltip.deleted": "Komponenta %s s hodnotou %s byla smazána", + "commands.neoforge.data_components.list.tooltip.modified": "Komponenta %s byla změněna z %s na %s", + "commands.neoforge.data_components.list.tooltip.added": "Komponenta %s byla přidána s hodnotou %s", "commands.config.getwithtype": "Konfigurace pro %s typu %s nalezena na %s", "commands.config.noconfig": "Konfigurace pro %s typu %s nenalezena", "neoforge.update.beta.1": "%sPOZOR: %sNeoForge Beta", @@ -156,8 +156,8 @@ "neoforge.configuration.uitext.listelementup": "⏶", "neoforge.configuration.uitext.listelementdown": "⏷", "neoforge.configuration.uitext.listelementremove": "❌", - "neoforge.configuration.uitext.rangetooltip": "Range: %s", - "neoforge.configuration.uitext.filenametooltip": "File: \"%s\"", + "neoforge.configuration.uitext.rangetooltip": "Rozsah: %s", + "neoforge.configuration.uitext.filenametooltip": "Soubor: %s", "neoforge.configuration.uitext.common": "Obecné volby", "neoforge.configuration.uitext.client": "Volby klienta", "neoforge.configuration.uitext.server": "Volby serveru", @@ -190,14 +190,14 @@ "neoforge.configgui.permissionHandler": "Manažer oprávnění", "neoforge.configgui.permissionHandler.tooltip": "Manažer oprávnění použitý na serveru. Výchozí je neoforge:default_handler pokud žádný handler s tímto názvem není registrován.", "neoforge.configgui.removeErroringBlockEntities": "Odstranit Block Entity volající chyby", - "neoforge.configgui.removeErroringBlockEntities.tooltip": "Set this to true to remove any BlockEntity that throws an error in its update method instead of closing the server and reporting a crash log.", + "neoforge.configgui.removeErroringBlockEntities.tooltip": "Nastavte na true pro odstranění BlockEntity, která hodí chybu v metodě aktualizace namísto zavření serveru a hlášení selhání logu.", "neoforge.configgui.removeErroringBlockEntities.tooltip.warning": "POZOR, TOTO MŮŽE VŠECHNO POKAZIT.\nBUĎTE OPATRNÍ.\nNEJSME ZODPOVĚDNI ZA JAKÉKOLIV NAPÁCHANÉ ŠKODY.", "neoforge.configgui.removeErroringEntities": "Odstranit Entity volající chyby", - "neoforge.configgui.removeErroringEntities.tooltip": "Set this to true to remove any Entity that throws an error in its update method instead of closing the server and reporting a crash log.", + "neoforge.configgui.removeErroringEntities.tooltip": "Nastavte na true pro odstranění Entity, která hodí chybu v metodě aktualizace namísto zavření serveru a hlášení selhání logu.", "neoforge.configgui.removeErroringEntities.tooltip.warning": "POZOR, TOTO MŮŽE VŠECHNO POKAZIT.\nBUĎTE OPATRNÍ.\nNEJSME ZODPOVĚDNI ZA JAKÉKOLIV NAPÁCHANÉ ŠKODY.", "neoforge.configgui.showLoadWarnings": "Zobrazit Upozornění při Načítání", "neoforge.configgui.showLoadWarnings.tooltip": "Pokud je zapnuto, NeoForge zobrazí jakákoliv upozornění, která se vyskytla při načítání.", - "neoforge.configgui.useCombinedDepthStencilAttachment": "Používejte kombinovaný doplňek DEPTH_STENCIL", + "neoforge.configgui.useCombinedDepthStencilAttachment": "Používejte kombinovaný doplněk DEPTH_STENCIL", "neoforge.configgui.useCombinedDepthStencilAttachment.tooltip": "Zapněte toto nastavení pro použití kombinovaného doplňku DEPTH_STENCIL místo dvou samostatných.", "neoforge.controlsgui.shift": "SHIFT + %s", "neoforge.controlsgui.control": "CTRL + %s", @@ -230,7 +230,7 @@ "neoforge.network.negotiation.failure.version.mismatch": "Klient chce mít payload verzi: %s, ale server chce verzi: %s!", "neoforge.network.invalid_flow": "Chyba při procesu paylodu, který byl zaslán s neplatným tokem: %s", "neoforge.network.negotiation.failure.vanilla.client.not_supported": "Snažíte se připojit k serveru který běží na NeoForge, ale vy NeoForge nemáte. Prosím, nainstalujte NeoForge verzi: %s pro připojení k tomuto serveru.", - "neoforge.network.negotiation.failure.vanilla.server.not_supported": "Snažíte se připojit k serveru který neběží na NeoForge, ale máte módy, který ho požaduje. Nepodařilo se připojit.", + "neoforge.network.negotiation.failure.vanilla.server.not_supported": "Snažíte se připojit k serveru který neběží na NeoForge, ale máte módy, které ho požadují. Nepodařilo se připojit.", "neoforge.network.packet_splitter.unknown": "Snaha o rozdělení packetu bez rozdělovače packetů!", "neoforge.network.advanced_add_entity.failed": "Neúspěch při zpracovávání rozšířených dat spawnu entit: %s", "neoforge.network.advanced_open_screen.failed": "Neúspěch při snaze otevřít obrazovku s rozšířenými daty: %s", @@ -242,6 +242,17 @@ "neoforge.network.data_maps.missing_our": "Je nemožné se připoijit k serveru, jelikož chybí povinné registry map dat, které jsou přítomné na klientu: %s", "neoforge.network.data_maps.missing_their": "Je nemožné se připoijit k serveru, jelikož obsahuje povinné registry map dat, které nejsou přítomné na klientu: %s", "neoforge.network.extensible_enums.no_vanilla_server": "Tento klient nepodporuje vanilla servery, jelikož má mnoho rozšířených enumů v serverovaném připojení", - "neoforge.network.extensible_enums.enum_set_mismatch": "Sada rozšířených enumů na klientu a na serveru se nerovnají. Ujistěte se, že užíváte stejnou NeoForge verzi jako server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "Sada hodnot přidaných k rozšířeným enumů na klientu a na serveru se nerovnají. Ujistěte se, že užíváte stejný mód a NeoForge verzi jako server. Pro více informací se podívejte do logu" + "neoforge.network.extensible_enums.enum_set_mismatch": "Sada rozšířených enumů na klientovi a na serveru se nerovná. Ujistěte se, že užíváte stejnou NeoForge verzi jako server", + "neoforge.network.extensible_enums.enum_entry_mismatch": "Sada hodnot přidaných k rozšířeným enumů na klientu a na serveru se nerovnají. Ujistěte se, že užíváte stejný mód a NeoForge verzi jako server. Pro více informací se podívejte do logu", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/da_dk.json b/src/main/resources/assets/neoforge/lang/da_dk.json index 667edefa..24e5b59c 100644 --- a/src/main/resources/assets/neoforge/lang/da_dk.json +++ b/src/main/resources/assets/neoforge/lang/da_dk.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Cannot connect to server as it has mandatory registry data maps not present on the client: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/de_de.json b/src/main/resources/assets/neoforge/lang/de_de.json index 7792a3f1..b1bdcb1e 100644 --- a/src/main/resources/assets/neoforge/lang/de_de.json +++ b/src/main/resources/assets/neoforge/lang/de_de.json @@ -141,7 +141,7 @@ "neoforge.configuration.uitext.notonline": "Diese Einstellungen werden vom Server übernommen und können nicht geändert werden während Du online bist.", "neoforge.configuration.uitext.notlan": "Diese Einstellungen können nicht geändert werden während Du Dein Spiel für andere geöffnet hast. Bitte kehre ins Hauptmenü zurück und lade den Spielstand erneut.", "neoforge.configuration.uitext.notloaded": "Diese Einstellungen sind nur verfügbar, wenn du dich in einer Welt befindest.", - "neoforge.configuration.uitext.unsupportedelement": "Diese Einstellungen können nicht in der Benutzeroberfläche bearbeitet werden. Kontaktiere die/den Autor:Innen des/der Mod(s) und bitte ihn/sie ein angepasstes Bedienelement zu implementieren.", + "neoforge.configuration.uitext.unsupportedelement": "Diese Einstellungen können nicht in der Benutzeroberfläche bearbeitet werden. Kontaktiere die Mod-Autoren und bitte sie ein angepasstes Bedienelement zu implementieren.", "neoforge.configuration.uitext.longstring": "Dieser Wert ist zu lang, um hier bearbeitet werden zu können. Bitte bearbeite ihn in der Konfigurationsdatei.", "neoforge.configuration.uitext.section": "%s...", "neoforge.configuration.uitext.sectiontext": "Bearbeiten", @@ -168,7 +168,7 @@ "neoforge.configuration.uitext.restart.server.text": "Eine oder mehrere der geänderten Einstellungen sind nur wirksam, wenn die Welt neu geladen wird.", "neoforge.configuration.uitext.restart.return": "Ignorieren", "neoforge.configuration.uitext.restart.return.tooltip": "Deine Änderungen haben keine Wirkung, bis das Spiel neu gestartet wird!", - "neoforge.configuration.title": "NeoForge Konfiguration", + "neoforge.configuration.title": "NeoForge-Konfiguration", "neoforge.configuration.section.neoforge.client.toml": "Client-Einstellungen", "neoforge.configuration.section.neoforge.client.toml.title": "Client-Einstellungen", "neoforge.configuration.section.neoforge.common.toml": "Gemeinsame Einstellungen", @@ -177,12 +177,12 @@ "neoforge.configuration.section.neoforge.server.toml.title": "Server-Einstellungen", "neoforge.configgui.advertiseDedicatedServerToLan": "Dedizierten Server im LAN anzeigen", "neoforge.configgui.advertiseDedicatedServerToLan.tooltip": "Aktiviere diese Option, um den dedizierten Server zu lokalen LAN-Clients zu senden, sodass er automatisch in deren Mehrspieler-Bildschirmen angezeigt wird.", - "neoforge.configgui.forgeLightPipelineEnabled": "Neoforge Licht-Pipeline aktivieren", + "neoforge.configgui.forgeLightPipelineEnabled": "NeoForge Licht-Pipeline aktivieren", "neoforge.configgui.forgeLightPipelineEnabled.tooltip": "Aktiviert die NeoForge Block-Darstellungs-Pipeline - korrigiert die Beleuchtung von benutzerdefinierten Modellen.", "neoforge.configgui.fullBoundingBoxLadders": "Leitern im gesamten Kollisionsbereich", "neoforge.configgui.fullBoundingBoxLadders.tooltip": "Ändere dies zu 'true', um den gesamten Kollisionsbereich einer Entität, anstatt nur den Block, in dem sie sich befindet, auf Leitern zu überprüfen. Verursacht merkliche Unterschiede in der Spielmechanik, daher ist die Standardeinstellung die Vanilla-Funktionsweise. Standardwert: 'false'.", "neoforge.configgui.logLegacyTagWarnings": "Veraltete Tags protokollieren", - "neoforge.configgui.logLegacyTagWarnings.tooltip": "Eine Konfigurationsoption hauptsächlich für Entwickler. Protokolliert Tags von Modifikationen, die den 'forge'-Namensraum verwenden, wenn sie auf einem integrierten Server ausgeführt werden. Standard ist DEV_SHORT.", + "neoforge.configgui.logLegacyTagWarnings.tooltip": "Eine Konfiguration, die hauptsächlich für Entwickler gedacht ist. Meldet Tags von Mods im Log, die den 'forge'-Namensraum verwenden, wenn man der integrierte Server verwendet. Standard ist DEV_SHORT.", "neoforge.configgui.logUntranslatedConfigurationWarnings": "Nicht übersetzte Konfigurationsschlüssel protokollieren", "neoforge.configgui.logUntranslatedConfigurationWarnings.tooltip": "Eine Konfiguration, die hauptsächlich für Entwickler gedacht ist. Meldet Konfigurationswerte im Log, die keine Übersetzung haben, wenn man den Client in einer Entwicklungsumgebung verwendet.", "neoforge.configgui.logUntranslatedItemTagWarnings": "Nicht übersetzte Item-Tags protokollieren", @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Kann keine Verbindung zum Server herstellen, da obligatorische Registrierungsdatenkarten auf dem Server vorhanden sind, die auf dem Client fehlen: %s", "neoforge.network.extensible_enums.no_vanilla_server": "Dieser Client unterstützt Vanilla-Server nicht, da er erweiterte Enums in dem an den Server gerichteten Netzwerk verwendet", "neoforge.network.extensible_enums.enum_set_mismatch": "Die Menge der erweiterten Enums auf dem Client und dem Server stimmen nicht überein. Stelle sicher, dass du die gleiche NeoForge-Version wie der Server verwendest", - "neoforge.network.extensible_enums.enum_entry_mismatch": "Die Werte, die zu erweiterten Enums auf dem Client und dem Server hinzugefügt wurden, stimmen nicht überein. Stelle sicher, dass du die gleichen Mod- und NeoForge-Versionen wie der Server verwendest. Weitere Details findest du im Log" + "neoforge.network.extensible_enums.enum_entry_mismatch": "Die Werte, die zu erweiterten Enums auf dem Client und dem Server hinzugefügt wurden, stimmen nicht überein. Stelle sicher, dass du die gleichen Mod- und NeoForge-Versionen wie der Server verwendest. Weitere Details findest du im Log", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Aktiviert", + "neoforge.value.boolean.disabled": "Deaktiviert", + "neoforge.value.boolean.enable": "Aktiviert", + "neoforge.value.boolean.disable": "Deaktiviert", + "neoforge.value.boolean.invalid": "Ungültig", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/en_gb.json b/src/main/resources/assets/neoforge/lang/en_gb.json index 7797dbc9..5b4cb3ca 100644 --- a/src/main/resources/assets/neoforge/lang/en_gb.json +++ b/src/main/resources/assets/neoforge/lang/en_gb.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Cannot connect to server as it has mandatory registry data maps not present on the client: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/en_us.json b/src/main/resources/assets/neoforge/lang/en_us.json index b61eef8d..7cd26fc4 100644 --- a/src/main/resources/assets/neoforge/lang/en_us.json +++ b/src/main/resources/assets/neoforge/lang/en_us.json @@ -64,6 +64,9 @@ "fml.resources.modresources": "Resources for %1$s mod files", "fml.resources.moddata": "Data for %1$s mod files", + "fml.modloadingissue.feature_flags.file_not_found": "FeatureFlag file {0}, provided by mod {100,modinfo,id}, does not exist", + "fml.modloadingissue.feature_flags.loading_error": "Failed to load FeatureFlag data at {0} in mod {100,modinfo,id}: {102,exc,msg}", + "loadwarning.neoforge.prbuild": "This build of NeoForge was created by a community member and is thus §c§lUNSUPPORTED§r", "commands.neoforge.arguments.enum.invalid": "Enum constant must be one of %1$s, found %2$s", @@ -189,6 +192,8 @@ "neoforge.configuration.section.neoforge.server.toml.title": "Server settings", "neoforge.configgui.advertiseDedicatedServerToLan": "Advertise Dedicated Server To LAN", "neoforge.configgui.advertiseDedicatedServerToLan.tooltip": "Set this to true to enable advertising the dedicated server to local LAN clients so that it shows up in the Multiplayer screen automatically.", + "neoforge.configgui.attributeAdvancedTooltipDebugInfo": "Additional Attribute Advanced Tooltips", + "neoforge.configgui.attributeAdvancedTooltipDebugInfo.tooltip": "Set this to true to enable additional information about attributes on an item when advanced tooltips is on.", "neoforge.configgui.forgeLightPipelineEnabled": "NeoForge Light Pipeline", "neoforge.configgui.forgeLightPipelineEnabled.tooltip": "Enable the NeoForge block rendering pipeline - fixes the lighting of custom models.", "neoforge.configgui.fullBoundingBoxLadders": "Full Bounding Box Ladders", @@ -264,9 +269,11 @@ "neoforge.network.data_maps.missing_their": "Cannot connect to server as it has mandatory registry data maps not present on the client: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", - "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.network.feature_flags.entry_mismatch": "The server and client have different sets of custom FeatureFlags. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.network.feature_flags.no_vanilla_server": "This client does not support vanilla servers as it has custom FeatureFlags", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", "neoforge.value.flat": "%s", diff --git a/src/main/resources/assets/neoforge/lang/eo_uy.json b/src/main/resources/assets/neoforge/lang/eo_uy.json index cb1667ba..c496f43d 100644 --- a/src/main/resources/assets/neoforge/lang/eo_uy.json +++ b/src/main/resources/assets/neoforge/lang/eo_uy.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Cannot connect to server as it has mandatory registry data maps not present on the client: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/es_es.json b/src/main/resources/assets/neoforge/lang/es_es.json index 93be1b1b..44d34780 100644 --- a/src/main/resources/assets/neoforge/lang/es_es.json +++ b/src/main/resources/assets/neoforge/lang/es_es.json @@ -36,7 +36,7 @@ "fml.menu.multiplayer.modsincompatible": "La lista de mods del servidor no es compatible", "fml.menu.multiplayer.networkincompatible": "La lista de mensajes de red del servidor no es compatible", "fml.menu.multiplayer.missingdatapackregistries": "Missing required datapack registries: %1$s", - "fml.menu.branding": "%s (%s mods)", + "fml.menu.branding": ".", "fml.menu.notification.title": "Notificación de inicio", "fml.menu.accessdenied.title": "Acceso denegado al servidor", "fml.menu.accessdenied.message": "Fancy Mod Loader could not connect to this server\nThe server %1$s has forbidden modded access", @@ -76,7 +76,7 @@ "commands.neoforge.setdim.invalid.dim": "The dimension ID specified (%1$s) is not valid.", "commands.neoforge.setdim.invalid.nochange": "The entity selected (%1$s) is already in the dimension specified (%2$s).", "commands.neoforge.setdim.deprecated": "Este comando está obsoleto para su eliminación en 1.17, utilice %s en su lugar", - "commands.neoforge.tps.overall": "Overall: %s TPS (%s ms/tick)", + "commands.neoforge.tps.overall": "En total: %s TPS (%s ms/tick)", "commands.neoforge.tps.tooltip": "Mean TPS; higher is better. Target TPS: %s", "commands.neoforge.tps.dimension": "%s: %s TPS (%s ms/tick)", "commands.neoforge.tps.dimension.tooltip": "%s (Dimension Type: %s)", @@ -138,7 +138,7 @@ "neoforge.configuration.uitext.title.server": "%s Server Configuration", "neoforge.configuration.uitext.title.common": "%s Common Configuration", "neoforge.configuration.uitext.title.startup": "%s Startup Configuration", - "neoforge.configuration.uitext.notonline": "Settings in here are determined by the server and cannot be changed while online.", + "neoforge.configuration.uitext.notonline": "Los ajustes aquí están determinados por el servidor y no se pueden cambiar mientras estén en línea.", "neoforge.configuration.uitext.notlan": "Settings in here cannot be edited while your game is open to LAN. Please return to the main menu and load the world again.", "neoforge.configuration.uitext.notloaded": "Settings in here are only available while a world is loaded.", "neoforge.configuration.uitext.unsupportedelement": "This value cannot be edited in the UI. Please contact the mod author about providing a custom UI for it.", @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "No es posible conectarse al servidor ya que el servidor tiene mapas de datos de registro obligatorios que no están presentes en el cliente: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/et_ee.json b/src/main/resources/assets/neoforge/lang/et_ee.json index 22c87587..f598372b 100644 --- a/src/main/resources/assets/neoforge/lang/et_ee.json +++ b/src/main/resources/assets/neoforge/lang/et_ee.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Cannot connect to server as it has mandatory registry data maps not present on the client: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/fr_fr.json b/src/main/resources/assets/neoforge/lang/fr_fr.json index 984e6149..0f3e17ab 100644 --- a/src/main/resources/assets/neoforge/lang/fr_fr.json +++ b/src/main/resources/assets/neoforge/lang/fr_fr.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Impossible de se connecter au serveur car il a des mappages de données de registre obligatoires qui ne sont pas présents sur le client : %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/hu_hu.json b/src/main/resources/assets/neoforge/lang/hu_hu.json index ec786627..8e2f3f06 100644 --- a/src/main/resources/assets/neoforge/lang/hu_hu.json +++ b/src/main/resources/assets/neoforge/lang/hu_hu.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Cannot connect to server as it has mandatory registry data maps not present on the client: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/it_it.json b/src/main/resources/assets/neoforge/lang/it_it.json index 1747ce6a..d2bb100e 100644 --- a/src/main/resources/assets/neoforge/lang/it_it.json +++ b/src/main/resources/assets/neoforge/lang/it_it.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Impossibile connettersi al server in quanto presenta un registro della mappa dati obbligatorio assente nel client: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/ja_jp.json b/src/main/resources/assets/neoforge/lang/ja_jp.json index 73ad5092..0d3bd7a2 100644 --- a/src/main/resources/assets/neoforge/lang/ja_jp.json +++ b/src/main/resources/assets/neoforge/lang/ja_jp.json @@ -168,12 +168,12 @@ "neoforge.configuration.uitext.restart.server.text": "One or more of the configuration option that were changed will only take effect when the world is reloaded.", "neoforge.configuration.uitext.restart.return": "Ignore", "neoforge.configuration.uitext.restart.return.tooltip": "Your changes will have no effect until you restart!", - "neoforge.configuration.title": "NeoForge Configuration", - "neoforge.configuration.section.neoforge.client.toml": "Client settings", - "neoforge.configuration.section.neoforge.client.toml.title": "Client settings", + "neoforge.configuration.title": "NeoForge の設定", + "neoforge.configuration.section.neoforge.client.toml": "クライアントの設定", + "neoforge.configuration.section.neoforge.client.toml.title": "クライアントの設定", "neoforge.configuration.section.neoforge.common.toml": "Common settings", "neoforge.configuration.section.neoforge.common.toml.title": "Common settings", - "neoforge.configuration.section.neoforge.server.toml": "Server settings", + "neoforge.configuration.section.neoforge.server.toml": "サーバーの設定", "neoforge.configuration.section.neoforge.server.toml.title": "Server settings", "neoforge.configgui.advertiseDedicatedServerToLan": "Advertise Dedicated Server To LAN", "neoforge.configgui.advertiseDedicatedServerToLan.tooltip": "Set this to true to enable advertising the dedicated server to local LAN clients so that it shows up in the Multiplayer screen automatically.", @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "クライアントに存在しない必須のレジストリデータマップがサーバーにあるため、サーバーに接続できませんでした:%s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "クライアントとサーバーの拡張可能な列挙に追加された値のセットが一致しません。 サーバーと同じバージョンの mod と NeoForge を使用していることを確認してください。詳細はログを参照してください", + "neoforge.attribute.debug.base": "[エンティティ: %s | アイテム: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "有効", + "neoforge.value.boolean.disabled": "無効", + "neoforge.value.boolean.enable": "有効にする", + "neoforge.value.boolean.disable": "無効にする", + "neoforge.value.boolean.invalid": "無効", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/ko_kr.json b/src/main/resources/assets/neoforge/lang/ko_kr.json index c04de761..dce0d9ee 100644 --- a/src/main/resources/assets/neoforge/lang/ko_kr.json +++ b/src/main/resources/assets/neoforge/lang/ko_kr.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "서버 접속에 필요한 데이터 맵들이 클라이언트에 없어 접속할 수 없습니다: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/ms_my.json b/src/main/resources/assets/neoforge/lang/ms_my.json index 3f44f98f..4cd941ba 100644 --- a/src/main/resources/assets/neoforge/lang/ms_my.json +++ b/src/main/resources/assets/neoforge/lang/ms_my.json @@ -30,8 +30,8 @@ "fml.menu.multiplayer.vanilla": "Vanilla server", "fml.menu.multiplayer.vanilla.incompatible": "Incompatible Vanilla server", "fml.menu.multiplayer.unknown": "Unknown server %1$s", - "fml.menu.multiplayer.serveroutdated": "NeoForge server network version is outdated", - "fml.menu.multiplayer.clientoutdated": "NeoForge client network version is outdated", + "fml.menu.multiplayer.serveroutdated": "Versi rangkaian pelayan NeoForge lapuk", + "fml.menu.multiplayer.clientoutdated": "Versi rangkaian klien NeoForge lapuk", "fml.menu.multiplayer.extraservermods": "Server has additional mods that may be needed on the client", "fml.menu.multiplayer.modsincompatible": "Server mod list is not compatible", "fml.menu.multiplayer.networkincompatible": "Server network message list is not compatible", @@ -76,10 +76,10 @@ "commands.neoforge.setdim.invalid.dim": "The dimension ID specified (%1$s) is not valid.", "commands.neoforge.setdim.invalid.nochange": "The entity selected (%1$s) is already in the dimension specified (%2$s).", "commands.neoforge.setdim.deprecated": "This command is deprecated for removal in 1.17, use %s instead.", - "commands.neoforge.tps.overall": "Overall: %s TPS (%s ms/tick)", - "commands.neoforge.tps.tooltip": "Mean TPS; higher is better. Target TPS: %s", - "commands.neoforge.tps.dimension": "%s: %s TPS (%s ms/tick)", - "commands.neoforge.tps.dimension.tooltip": "%s (Dimension Type: %s)", + "commands.neoforge.tps.overall": "Keseluruhan: %s TPS (%s ms/detik)", + "commands.neoforge.tps.tooltip": "Min TPS; lebih tinggi adalah lebih baik. Sasaran TPS: %s", + "commands.neoforge.tps.dimension": "%s: %s TPS (%s ms/detik)", + "commands.neoforge.tps.dimension.tooltip": "%s (Jenis Dimensi: %s)", "commands.neoforge.mods.list": "Mod List: %1$s", "commands.neoforge.tracking.entity.enabled": "Entity tracking enabled for %d seconds.", "commands.neoforge.tracking.entity.reset": "Entity timings data has been cleared!", @@ -156,8 +156,8 @@ "neoforge.configuration.uitext.listelementup": "⏶", "neoforge.configuration.uitext.listelementdown": "⏷", "neoforge.configuration.uitext.listelementremove": "❌", - "neoforge.configuration.uitext.rangetooltip": "Range: %s", - "neoforge.configuration.uitext.filenametooltip": "File: \"%s\"", + "neoforge.configuration.uitext.rangetooltip": "Julat: %s", + "neoforge.configuration.uitext.filenametooltip": "Fail: \"%s\"", "neoforge.configuration.uitext.common": "Pilihan Biasa", "neoforge.configuration.uitext.client": "Pilihan Klien", "neoforge.configuration.uitext.server": "Pilihan Pelayan", @@ -176,7 +176,7 @@ "neoforge.configuration.section.neoforge.server.toml": "Tetapan pelayan", "neoforge.configuration.section.neoforge.server.toml.title": "Tetapan pelayan", "neoforge.configgui.advertiseDedicatedServerToLan": "Iklankan Pelayan Khusus Kepada LAN", - "neoforge.configgui.advertiseDedicatedServerToLan.tooltip": "Set this to true to enable advertising the dedicated server to local LAN clients so that it shows up in the Multiplayer screen automatically.", + "neoforge.configgui.advertiseDedicatedServerToLan.tooltip": "Tetapkan ini kepada true untuk membolehkan pengiklanan pelayan khusus kepada klien LAN tempatan supaya ia muncul dalam skrin Multipemain secara automatik.", "neoforge.configgui.forgeLightPipelineEnabled": "Talian Paip Cahaya NeoForge", "neoforge.configgui.forgeLightPipelineEnabled.tooltip": "Enable the NeoForge block rendering pipeline - fixes the lighting of custom models.", "neoforge.configgui.fullBoundingBoxLadders": "Full Bounding Box Ladders", @@ -190,11 +190,11 @@ "neoforge.configgui.permissionHandler": "Pengendali Keizinan", "neoforge.configgui.permissionHandler.tooltip": "Pengendali keizinan yang digunakan oleh pelayan. Dilalaikan kepada neoforge:default_handler jika tiada pengendali dengan nama itu didaftarkan.", "neoforge.configgui.removeErroringBlockEntities": "Remove Erroring Block Entities", - "neoforge.configgui.removeErroringBlockEntities.tooltip": "Set this to true to remove any BlockEntity that throws an error in its update method instead of closing the server and reporting a crash log.", - "neoforge.configgui.removeErroringBlockEntities.tooltip.warning": "AMARAN INI BOLEH MEROSAKKAN SEGALANYA.\nGUNAKAN SECARA CERMAT.\nKAMI TIDAK BERTANGGUNGJAWAB ATAS KEROSAKAN.", + "neoforge.configgui.removeErroringBlockEntities.tooltip": "Tetapkan ini kepada true untuk mengalih keluar mana-mana Entiti Blok yang menimbulkan ralat dalam kaedah update nya dan bukannya menutup pelayan dan melaporkan log ranap permainan.", + "neoforge.configgui.removeErroringBlockEntities.tooltip.warning": "INI BOLEH MEROSAKKAN SEGALANYA.\nGUNAKAN SECARA BERHEMAT.\nKAMI TIDAK BERTANGGUNGJAWAB ATAS SEBARANG KEROSAKAN.", "neoforge.configgui.removeErroringEntities": "Remove Erroring Entities", - "neoforge.configgui.removeErroringEntities.tooltip": "Set this to true to remove any Entity that throws an error in its update method instead of closing the server and reporting a crash log.", - "neoforge.configgui.removeErroringEntities.tooltip.warning": "AMARAN INI BOLEH MEROSAKKAN SEGALANYA.\nGUNAKAN SECARA CERMAT.\nKAMI TIDAK BERTANGGUNGJAWAB ATAS KEROSAKAN.", + "neoforge.configgui.removeErroringEntities.tooltip": "Tetapkan ini kepada true untuk mengalih keluar mana-mana Entiti yang menimbulkan ralat dalam kaedah update nya dan bukannya menutup pelayan dan melaporkan log ranap permainan.", + "neoforge.configgui.removeErroringEntities.tooltip.warning": "INI BOLEH MEROSAKKAN SEGALANYA.\nGUNAKAN SECARA BERHEMAT.\nKAMI TIDAK BERTANGGUNGJAWAB ATAS SEBARANG KEROSAKAN.", "neoforge.configgui.showLoadWarnings": "Show Load Warnings", "neoforge.configgui.showLoadWarnings.tooltip": "When enabled, NeoForge will show any warnings that occurred during loading.", "neoforge.configgui.useCombinedDepthStencilAttachment": "Gunakan gabungan Lampiran DEPTH_STENCIL", @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Cannot connect to server as it has mandatory registry data maps not present on the client: %s", "neoforge.network.extensible_enums.no_vanilla_server": "Klien ini tidak menyokong pelayan vanila kerana ia mempunyai enum lanjutan yang digunakan dalam rangkaian terikat pelayan", "neoforge.network.extensible_enums.enum_set_mismatch": "Set enum yang boleh diperluaskan pada klien dan pelayan tidak sepadan. Pastikan anda menggunakan versi NeoForge yang sama seperti pelayan", - "neoforge.network.extensible_enums.enum_entry_mismatch": "Set nilai yang ditambahkan pada enum yang boleh diperluaskan pada klien dan pelayan tidak sepadan. Pastikan anda menggunakan mod dan versi NeoForge yang sama seperti pelayan. Lihat log untuk butiran lanjut" + "neoforge.network.extensible_enums.enum_entry_mismatch": "Set nilai yang ditambahkan pada enum yang boleh diperluaskan pada klien dan pelayan tidak sepadan. Pastikan anda menggunakan mod dan versi NeoForge yang sama seperti pelayan. Lihat log untuk butiran lanjut", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/nl_nl.json b/src/main/resources/assets/neoforge/lang/nl_nl.json index f6379646..fbbb6ff0 100644 --- a/src/main/resources/assets/neoforge/lang/nl_nl.json +++ b/src/main/resources/assets/neoforge/lang/nl_nl.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Kan geen verbinding maken met de server omdat het verplichte registry data maps bevat die niet aanwezig zijn op de client: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/pl_pl.json b/src/main/resources/assets/neoforge/lang/pl_pl.json index 1e3be0e5..79b0616d 100644 --- a/src/main/resources/assets/neoforge/lang/pl_pl.json +++ b/src/main/resources/assets/neoforge/lang/pl_pl.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Nie można połączyć się z serwerem, ponieważ brakuje obowiązkowych map danych rejestru znajdujących się w kliencie: %s", "neoforge.network.extensible_enums.no_vanilla_server": "Ten klient nie obsługuje serwerów vanilla ponieważ posiada rozszerzone enums używane w sieciach związanych z serwerem", "neoforge.network.extensible_enums.enum_set_mismatch": "Zestaw rozszerzalnych enumerów na kliencie i serwerze nie pasuje. Upewnij się, że używasz tej samej wersji NeoForge co serwer", - "neoforge.network.extensible_enums.enum_entry_mismatch": "Zestaw wartości dodanych do rozszerzalnych enumów w kliencie i serwerze nie pasuje. Upewnij się, że używasz tych samych wersji modyfikacji i NeoForge co serwer. Więcej informacji znajdziesz w logu" + "neoforge.network.extensible_enums.enum_entry_mismatch": "Zestaw wartości dodanych do rozszerzalnych enumów w kliencie i serwerze nie pasuje. Upewnij się, że używasz tych samych wersji modyfikacji i NeoForge co serwer. Więcej informacji znajdziesz w logu", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/pt_br.json b/src/main/resources/assets/neoforge/lang/pt_br.json index e931213f..e7f9fc47 100644 --- a/src/main/resources/assets/neoforge/lang/pt_br.json +++ b/src/main/resources/assets/neoforge/lang/pt_br.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Impossível conectar com o servidor que tem registros de data maps não presentes no client: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/pt_pt.json b/src/main/resources/assets/neoforge/lang/pt_pt.json index f43c9088..0507d99d 100644 --- a/src/main/resources/assets/neoforge/lang/pt_pt.json +++ b/src/main/resources/assets/neoforge/lang/pt_pt.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Cannot connect to server as it has mandatory registry data maps not present on the client: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/ro_ro.json b/src/main/resources/assets/neoforge/lang/ro_ro.json index 97e50d19..d6c7e822 100644 --- a/src/main/resources/assets/neoforge/lang/ro_ro.json +++ b/src/main/resources/assets/neoforge/lang/ro_ro.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Cannot connect to server as it has mandatory registry data maps not present on the client: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/ru_ru.json b/src/main/resources/assets/neoforge/lang/ru_ru.json index 0b54ff2c..30467a05 100644 --- a/src/main/resources/assets/neoforge/lang/ru_ru.json +++ b/src/main/resources/assets/neoforge/lang/ru_ru.json @@ -36,7 +36,7 @@ "fml.menu.multiplayer.modsincompatible": "Список модов сервера несовместим", "fml.menu.multiplayer.networkincompatible": "Список сетевых сообщений сервера несовместим", "fml.menu.multiplayer.missingdatapackregistries": "Missing required datapack registries: %1$s", - "fml.menu.branding": "%s (%s mods)", + "fml.menu.branding": "%s (%s модов)", "fml.menu.notification.title": "Уведомление при запуске игры", "fml.menu.accessdenied.title": "Отказано в доступе к серверу", "fml.menu.accessdenied.message": "Fancy Mod Loader could not connect to this server\nThe server %1$s has forbidden modded access", @@ -79,7 +79,7 @@ "commands.neoforge.tps.overall": "Overall: %s TPS (%s ms/tick)", "commands.neoforge.tps.tooltip": "Mean TPS; higher is better. Target TPS: %s", "commands.neoforge.tps.dimension": "%s: %s TPS (%s ms/tick)", - "commands.neoforge.tps.dimension.tooltip": "%s (Dimension Type: %s)", + "commands.neoforge.tps.dimension.tooltip": "%s (Тип измерения: %s)", "commands.neoforge.mods.list": "Список модов: %1$s", "commands.neoforge.tracking.entity.enabled": "Отслеживание сущностей включено на %d секунд.", "commands.neoforge.tracking.entity.reset": "Данные с таймингами сущностей очищены!", @@ -107,59 +107,59 @@ "commands.neoforge.chunkgen.started": "Generating %1$s chunks, in an area of %2$sx%3$s chunks (%4$sx%5$s blocks).", "commands.neoforge.chunkgen.success": "Генерация завершена!", "commands.neoforge.chunkgen.error": "Generation experienced %1$s errors! Check the log for more information.", - "commands.neoforge.chunkgen.stopped": "Generation stopped! %1$s out of %2$s chunks generated. (%3$s%%)", - "commands.neoforge.chunkgen.status": "Generation status! %1$s out of %2$s chunks generated. (%3$s%%)", + "commands.neoforge.chunkgen.stopped": "Генерация остановлена! Сгенерировано %1$s из %2$s чанков. (%3$s%%)", + "commands.neoforge.chunkgen.status": "Состояние генерации! Сгенерировано %1$s из %2$s чанков. (%3$s%%)", "commands.neoforge.chunkgen.not_running": "Предварительная генерация не запущена. Выполните команду `/neoforge generate help`, чтобы увидеть команды для запуска генерации.", "commands.neoforge.chunkgen.help_line": "§2/neoforge generate start [шкалапрогресса] §r§f- создаёт квадрат, центрированный на указанной точке, с длиной сторон, равной chunkRadius * 2.\n§2/neoforge generate stop §r§f- останавливает текущую генерацию и отображает завершённый прогресс.\n§2/neoforge generate status §r- отображает прогресс текущей генерации.\n§2/neoforge generate help §r- отображает данное сообщение.\nСовет: Если вы запускаете команды из консоли сервера, вы можете выполнять генерацию в разных измерениях с помощью /execute in neoforge generate…", - "commands.neoforge.timespeed.query": "Time in %s flows at a rate of %sx (%s minutes per day).", - "commands.neoforge.timespeed.query.default": "Time in %s flows normally (20 minutes per day).", - "commands.neoforge.timespeed.set": "Set flow of time in %s to %sx (%s minutes per day).", - "commands.neoforge.timespeed.set.default": "Set flow of time in %s to default (20 minutes per day).", - "commands.neoforge.data_components.list.error.held_stack_empty": "You are not holding any item", - "commands.neoforge.data_components.list.title": "Data components on %s:", + "commands.neoforge.timespeed.query": "Время в %s проходит со скоростью %sx (%s минут в день).", + "commands.neoforge.timespeed.query.default": "Время в %s проходит нормально (20 минут в день).", + "commands.neoforge.timespeed.set": "Установить поток времени в %s на %sx (%s минут в день).", + "commands.neoforge.timespeed.set.default": "Установить поток времени в %s по умолчанию (20 минут в день).", + "commands.neoforge.data_components.list.error.held_stack_empty": "Вы не держите никакого предмета", + "commands.neoforge.data_components.list.title": "Компоненты данных на %s:", "commands.neoforge.data_components.list.entry": "\n - %s", "commands.neoforge.data_components.list.entry.key_value": "%s: %s", - "commands.neoforge.data_components.list.tooltip.default": "Component %s holds its default value", - "commands.neoforge.data_components.list.tooltip.deleted": "Component %s with value %s was deleted", - "commands.neoforge.data_components.list.tooltip.modified": "Component %s was modified from %s to %s", - "commands.neoforge.data_components.list.tooltip.added": "Component %s was added with value %s", + "commands.neoforge.data_components.list.tooltip.default": "Компонент %s имеет значение по умолчанию", + "commands.neoforge.data_components.list.tooltip.deleted": "Компонент %s со значением %s был удалён", + "commands.neoforge.data_components.list.tooltip.modified": "Компонент %s был изменен с %s на %s", + "commands.neoforge.data_components.list.tooltip.added": "Компонент %s был добавлен со значением %s", "commands.config.getwithtype": "Конфиг для %s типа %s обнаружен в %s", "commands.config.noconfig": "Конфиг для %s типа %s не найден", "neoforge.update.beta.1": "%sВНИМАНИЕ: %sNeoForge Бета", "neoforge.update.beta.2": "Возможны серьезные проблемы, проверьте их, прежде чем сообщать.", "neoforge.update.newversion": "Доступна новая версия NeoForge: %s", "neoforge.menu.updatescreen.title": "Обновление мода", - "neoforge.configuration.uitext.title": "%s Configuration", - "neoforge.configuration.uitext.type.client": "Client Settings", - "neoforge.configuration.uitext.type.server": "Server Settings", - "neoforge.configuration.uitext.type.common": "Common settings", - "neoforge.configuration.uitext.type.startup": "Startup settings", - "neoforge.configuration.uitext.title.client": "%s Client Configuration", - "neoforge.configuration.uitext.title.server": "%s Server Configuration", - "neoforge.configuration.uitext.title.common": "%s Common Configuration", - "neoforge.configuration.uitext.title.startup": "%s Startup Configuration", - "neoforge.configuration.uitext.notonline": "Settings in here are determined by the server and cannot be changed while online.", - "neoforge.configuration.uitext.notlan": "Settings in here cannot be edited while your game is open to LAN. Please return to the main menu and load the world again.", - "neoforge.configuration.uitext.notloaded": "Settings in here are only available while a world is loaded.", - "neoforge.configuration.uitext.unsupportedelement": "This value cannot be edited in the UI. Please contact the mod author about providing a custom UI for it.", - "neoforge.configuration.uitext.longstring": "This value is too long to be edited in the UI. Please edit it in the config file.", + "neoforge.configuration.uitext.title": "%s Конфигурация", + "neoforge.configuration.uitext.type.client": "Настройки клиента", + "neoforge.configuration.uitext.type.server": "Настройки сервера", + "neoforge.configuration.uitext.type.common": "Общие настройки", + "neoforge.configuration.uitext.type.startup": "Настройки запуска", + "neoforge.configuration.uitext.title.client": "%s Конфигурация клиента", + "neoforge.configuration.uitext.title.server": "%s Конфигурация сервера", + "neoforge.configuration.uitext.title.common": "%s Общая конфигурация", + "neoforge.configuration.uitext.title.startup": "%s Конфигурация запуска", + "neoforge.configuration.uitext.notonline": "Настройки здесь определяются сервером и не могут быть изменены в режиме онлайн.", + "neoforge.configuration.uitext.notlan": "Настройки в этом разделе нельзя редактировать, пока игра открыта для LAN. Пожалуйста, вернитесь в главное меню и загрузите мир заново.", + "neoforge.configuration.uitext.notloaded": "Настройки здесь доступны только во время загрузки мира.", + "neoforge.configuration.uitext.unsupportedelement": "Это значение нельзя редактировать в пользовательском интерфейсе. Пожалуйста, свяжитесь с автором мода для создания своего пользовательского интерфейса.", + "neoforge.configuration.uitext.longstring": "Это значение слишком длинное, чтобы его можно было редактировать в пользовательском интерфейсе. Пожалуйста, отредактируйте его в файле конфигурации.", "neoforge.configuration.uitext.section": "%s...", - "neoforge.configuration.uitext.sectiontext": "Edit", + "neoforge.configuration.uitext.sectiontext": "Редактировать", "neoforge.configuration.uitext.breadcrumb.order": "%1$s %2$s %3$s", "neoforge.configuration.uitext.breadcrumb.separator": ">", "neoforge.configuration.uitext.listelement": "%s:", - "neoforge.configuration.uitext.undo": "Undo", - "neoforge.configuration.uitext.undo.tooltip": "Reverts changes on this screen only.", - "neoforge.configuration.uitext.reset": "Reset", - "neoforge.configuration.uitext.reset.tooltip": "Reverts everything on this screen to its default value.", + "neoforge.configuration.uitext.undo": "Отменить", + "neoforge.configuration.uitext.undo.tooltip": "Возвращает изменения только на этом экране.", + "neoforge.configuration.uitext.reset": "Сброс", + "neoforge.configuration.uitext.reset.tooltip": "Возвращает всё на этом экране к значениям по умолчанию.", "neoforge.configuration.uitext.newlistelement": "+", "neoforge.configuration.uitext.listelementup": "⏶", "neoforge.configuration.uitext.listelementdown": "⏷", "neoforge.configuration.uitext.listelementremove": "❌", - "neoforge.configuration.uitext.rangetooltip": "Range: %s", - "neoforge.configuration.uitext.filenametooltip": "File: \"%s\"", - "neoforge.configuration.uitext.common": "Common Options", - "neoforge.configuration.uitext.client": "Client Options", + "neoforge.configuration.uitext.rangetooltip": "Диапазон: %s", + "neoforge.configuration.uitext.filenametooltip": "Файл: \"%s\"", + "neoforge.configuration.uitext.common": "Общие настройки", + "neoforge.configuration.uitext.client": "Настройки клиента", "neoforge.configuration.uitext.server": "Server Options", "neoforge.configuration.uitext.startup": "Startup Options", "neoforge.configuration.uitext.restart.game.title": "Minecraft needs to be restarted", @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Не удается подключиться к серверу, так как присутствуют обязательные данные реестра, отсутствующие на клиенте: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/sk_sk.json b/src/main/resources/assets/neoforge/lang/sk_sk.json index 80b274aa..a6972ea2 100644 --- a/src/main/resources/assets/neoforge/lang/sk_sk.json +++ b/src/main/resources/assets/neoforge/lang/sk_sk.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Nemožno pripojiť sa na server, nakoľko obsahuje povinné registratúrne dátové mapy neprítomné na klientovi: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/tr_tr.json b/src/main/resources/assets/neoforge/lang/tr_tr.json index 50e04e52..cc0f520c 100644 --- a/src/main/resources/assets/neoforge/lang/tr_tr.json +++ b/src/main/resources/assets/neoforge/lang/tr_tr.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Cannot connect to server as it has mandatory registry data maps not present on the client: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/tt_ru.json b/src/main/resources/assets/neoforge/lang/tt_ru.json index b2a216d1..d531fc02 100644 --- a/src/main/resources/assets/neoforge/lang/tt_ru.json +++ b/src/main/resources/assets/neoforge/lang/tt_ru.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Cannot connect to server as it has mandatory registry data maps not present on the client: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/uk_ua.json b/src/main/resources/assets/neoforge/lang/uk_ua.json index da431796..8c834f72 100644 --- a/src/main/resources/assets/neoforge/lang/uk_ua.json +++ b/src/main/resources/assets/neoforge/lang/uk_ua.json @@ -156,8 +156,8 @@ "neoforge.configuration.uitext.listelementup": "⏶", "neoforge.configuration.uitext.listelementdown": "⏷", "neoforge.configuration.uitext.listelementremove": "❌", - "neoforge.configuration.uitext.rangetooltip": "Range: %s", - "neoforge.configuration.uitext.filenametooltip": "File: \"%s\"", + "neoforge.configuration.uitext.rangetooltip": "Діапазон: %s", + "neoforge.configuration.uitext.filenametooltip": "Файл: \"%s\"", "neoforge.configuration.uitext.common": "Загальні параметри", "neoforge.configuration.uitext.client": "Клієнтські параметри", "neoforge.configuration.uitext.server": "Серверні параметри", @@ -190,10 +190,10 @@ "neoforge.configgui.permissionHandler": "Менеджер дозволів", "neoforge.configgui.permissionHandler.tooltip": "Менеджер дозволів, що використовує сервер. За замовчуванням neoforge:default_handler, коли такого обробника не зареєстровано з таким іменем.", "neoforge.configgui.removeErroringBlockEntities": "Видалити блок-сутності, які викликають помилки", - "neoforge.configgui.removeErroringBlockEntities.tooltip": "Set this to true to remove any BlockEntity that throws an error in its update method instead of closing the server and reporting a crash log.", + "neoforge.configgui.removeErroringBlockEntities.tooltip": "Встановіть значення \"true\", для видалення будь-якого блок-сутності, що спричиняє помилки в методі оновлення, замість закриття сервера та створення журналу про аварію.", "neoforge.configgui.removeErroringBlockEntities.tooltip.warning": "УВАГА: ЦЕ МОЖЕ ПОШКОДИТИ ВСЕ.\nВИКОРИСТОВУЙТЕ ОБЕРЕЖНО.\nМИ НЕ ВІДПОВІДАЛЬНІ ЗА ПОШКОДЖЕННЯ.", "neoforge.configgui.removeErroringEntities": "Видалити сутності, які викликають помилки", - "neoforge.configgui.removeErroringEntities.tooltip": "Set this to true to remove any Entity that throws an error in its update method instead of closing the server and reporting a crash log.", + "neoforge.configgui.removeErroringEntities.tooltip": "Встановіть значення \"true\", для видалення будь-якого об'єкта, що спричиняє помилки в методі оновлення, замість закриття сервера та створення журналу про аварію.", "neoforge.configgui.removeErroringEntities.tooltip.warning": "УВАГА: ЦЕ МОЖЕ ПОШКОДИТИ ВСЕ.\nВИКОРИСТОВУЙТЕ ОБЕРЕЖНО.\nМИ НЕ ВІДПОВІДАЛЬНІ ЗА ПОШКОДЖЕННЯ.", "neoforge.configgui.showLoadWarnings": "Показувати попередження при завантаженні", "neoforge.configgui.showLoadWarnings.tooltip": "Коли увімкнено, NeoForge покаже всі попередження, що сталися під час завантаження.", @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Не вдається приєднатися до сервера, оскільки присутні обов'язкові мапи даних реєстру, що відсутні на клієнті: %s", "neoforge.network.extensible_enums.no_vanilla_server": "Цей клієнт не підтримує ванільні сервери, тому що він має розширені значення enum, що використовуються у роботі з серверами", "neoforge.network.extensible_enums.enum_set_mismatch": "Набір значень розширюваних enum, на клієнті й сервері не збігаються. Переконайтеся, що ви використовуєте однакові версії NeoForge", - "neoforge.network.extensible_enums.enum_entry_mismatch": "Набір значень, що додаються до розширюваних enum, на клієнті й сервері не збігаються. Переконайтеся, що ви використовуєте однакові версії моду і NeoForge. Дивіться журнал для додаткової інформації" + "neoforge.network.extensible_enums.enum_entry_mismatch": "Набір значень, що додаються до розширюваних enum, на клієнті й сервері не збігаються. Переконайтеся, що ви використовуєте однакові версії моду і NeoForge. Дивіться журнал для додаткової інформації", + "neoforge.attribute.debug.base": "[Істота: %s | Предмет: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Увімкнено", + "neoforge.value.boolean.disabled": "Вимкнено", + "neoforge.value.boolean.enable": "Вмикає", + "neoforge.value.boolean.disable": "Вимикає", + "neoforge.value.boolean.invalid": "Недійсне", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/vi_vn.json b/src/main/resources/assets/neoforge/lang/vi_vn.json index f43c9088..0507d99d 100644 --- a/src/main/resources/assets/neoforge/lang/vi_vn.json +++ b/src/main/resources/assets/neoforge/lang/vi_vn.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "Cannot connect to server as it has mandatory registry data maps not present on the client: %s", "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/zh_cn.json b/src/main/resources/assets/neoforge/lang/zh_cn.json index 0deca20f..d54ee5b8 100644 --- a/src/main/resources/assets/neoforge/lang/zh_cn.json +++ b/src/main/resources/assets/neoforge/lang/zh_cn.json @@ -243,5 +243,16 @@ "neoforge.network.data_maps.missing_their": "无法连接至服务器,因为客户端上不存在必要的注册表数据映射:%s", "neoforge.network.extensible_enums.no_vanilla_server": "该客户端不支持原版服务器,因为它在服务器端网络中使用了不同连接方式。", "neoforge.network.extensible_enums.enum_set_mismatch": "客户端和服务器上的模组不匹配。请确保您使用的 NeoForge 版本与服务器上的版本相同。", - "neoforge.network.extensible_enums.enum_entry_mismatch": "客户端和服务器上所需要的模组不匹配。请确保您使用的模组和 NeoForge 版本与服务器上的版本相同。请查看日志以获取更多详细信息。" + "neoforge.network.extensible_enums.enum_entry_mismatch": "客户端和服务器上所需要的模组不匹配。请确保您使用的模组和 NeoForge 版本与服务器上的版本相同。请查看日志以获取更多详细信息。", + "neoforge.attribute.debug.base": "[实体:%s | 物品:%s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "已启用", + "neoforge.value.boolean.disabled": "已禁用", + "neoforge.value.boolean.enable": "启用", + "neoforge.value.boolean.disable": "禁用", + "neoforge.value.boolean.invalid": "无效", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" } diff --git a/src/main/resources/assets/neoforge/lang/zh_tw.json b/src/main/resources/assets/neoforge/lang/zh_tw.json index 2f1eab53..ffd0271e 100644 --- a/src/main/resources/assets/neoforge/lang/zh_tw.json +++ b/src/main/resources/assets/neoforge/lang/zh_tw.json @@ -17,29 +17,29 @@ "fml.menu.mods.info.securejardisabled": "安全模組功能已被停用,請更新 JDK", "fml.menu.mods.info.signature": "簽章:%1$s", "fml.menu.mods.info.signature.unsigned": "未簽署", - "fml.menu.mods.info.trust": "信任:%1$s", + "fml.menu.mods.info.trust": "信任度:%1$s", "fml.menu.mods.info.trust.noauthority": "無", "fml.menu.mods.info.nochildmods": "未發現子模組", "fml.menu.mods.info.childmods": "子模組:%1$s", - "fml.menu.mods.info.updateavailable": "有更新可用:%1$s", - "fml.menu.mods.info.changelogheader": "變更紀錄:", - "fml.menu.multiplayer.compatible": "相容的 FML 模組伺服器\n已安裝 {0,choice,1#1 mod|1<%1$s mods} 個模組", - "fml.menu.multiplayer.incompatible": "不相容 FML 模組伺服器", - "fml.menu.multiplayer.incompatible.extra": "不相容 FML 模組伺服器\n%1$s", + "fml.menu.mods.info.updateavailable": "有可用的更新:%1$s", + "fml.menu.mods.info.changelogheader": "更新日誌:", + "fml.menu.multiplayer.compatible": "相容的 FML 模組伺服器\n已安裝 {0,choice,1#1 個模組|1<%1$s 個模組}", + "fml.menu.multiplayer.incompatible": "不相容 FML 模組的伺服器", + "fml.menu.multiplayer.incompatible.extra": "不相容 FML 模組的伺服器\n%1$s", "fml.menu.multiplayer.truncated": "由於協定大小限制,資料可能不準確。", "fml.menu.multiplayer.vanilla": "原版伺服器", "fml.menu.multiplayer.vanilla.incompatible": "不相容的原版伺服器", "fml.menu.multiplayer.unknown": "未知的伺服器 %1$s", - "fml.menu.multiplayer.serveroutdated": "NeoForge 伺服器端網路版本太舊", - "fml.menu.multiplayer.clientoutdated": "NeoForge 用戶端網路版本太舊", - "fml.menu.multiplayer.extraservermods": "伺服器有用戶端可能需要的額外模組", + "fml.menu.multiplayer.serveroutdated": "NeoForge 伺服器端網路版本太舊了", + "fml.menu.multiplayer.clientoutdated": "NeoForge 用戶端網路版本太舊了", + "fml.menu.multiplayer.extraservermods": "伺服器上有用戶端可能需要的其他模組", "fml.menu.multiplayer.modsincompatible": "伺服器模組清單不相容", "fml.menu.multiplayer.networkincompatible": "伺服器網路訊息清單不相容", - "fml.menu.multiplayer.missingdatapackregistries": "缺少所需資料包登錄:%1$s", + "fml.menu.multiplayer.missingdatapackregistries": "缺少必要的資料包登錄:%1$s", "fml.menu.branding": "%s(%s 個模組)", "fml.menu.notification.title": "啟動提醒", "fml.menu.accessdenied.title": "伺服器存取被拒", - "fml.menu.accessdenied.message": "Fancy Mod Loader 無法連線到此伺服器\n伺服器 %1$s 禁止模組存取", + "fml.menu.accessdenied.message": "Fancy Mod Loader 無法連線到這個伺服器\n伺服器 %1$s 禁止模組存取", "fml.menu.backupfailed.title": "備份失敗", "fml.menu.backupfailed.message": "儲存存檔 %1$s 時發生錯誤\n請修正這個問題,然後再試一次", "fml.button.open.file": "開啟 %1$s", @@ -52,21 +52,21 @@ "fml.modmismatchscreen.mismatchedmods": "下列模組版本不符合,請安裝相同版本的這些模組以加入此伺服器:", "fml.modmismatchscreen.table.channelname": "頻道名稱", "fml.modmismatchscreen.table.youneed": "你需要", - "fml.modmismatchscreen.table.youhave": "你擁有", + "fml.modmismatchscreen.table.youhave": "你安裝了", "fml.modmismatchscreen.table.serverhas": "伺服器已安裝", "fml.modmismatchscreen.additional": "[還有 %1$s 個,查看 latest.log 取得完整清單]", - "fml.modmismatchscreen.homepage": "點擊以前往這個模組的主頁", + "fml.modmismatchscreen.homepage": "按這裡前往這個模組的首頁", "fml.modmismatchscreen.table.reason": "原因", - "fml.modmismatchscreen.table.visit.mod_page": "開啟登錄此頻道的模組(%s)頁面", + "fml.modmismatchscreen.table.visit.mod_page": "開啟登錄這個頻道的模組(%s)頁面", "fml.modmismatchscreen.simplifiedview": "簡化檢視", "fml.resources.modresources": "用於 %1$s 模組檔案的資源", "fml.resources.moddata": "%1$s 模組檔案資料", "loadwarning.neoforge.prbuild": "這個 NeoForge 由社群成員建立,是 §c§l不受支援§r 的", "commands.neoforge.arguments.enum.invalid": "枚舉常數必須是 %1$s 之一,找到 %2$s", "commands.neoforge.dimensions.list": "目前依照類型註冊的維度:", - "commands.neoforge.dump.success": "新建立的 %s 註冊檔案位於 %s", - "commands.neoforge.dump.failure": "無法建立 %s 註冊檔案於 %s", - "commands.neoforge.dump.error.unknown_registry": "未知註冊表 '%s'", + "commands.neoforge.dump.success": "新建立的 %s 登錄檔位於 %s", + "commands.neoforge.dump.failure": "無法建立 %s 登錄檔於 %s", + "commands.neoforge.dump.error.unknown_registry": "未知登錄檔 '%s'", "commands.neoforge.entity.list.invalid": "無效的篩選器,與任何實體不相符。請使用 /neoforge entity list 以提供正確清單", "commands.neoforge.entity.list.invalidworld": "無法載入維度 %1$s 的世界。請選擇一個有效的維度。", "commands.neoforge.entity.list.none": "找不到實體。", @@ -81,52 +81,52 @@ "commands.neoforge.tps.dimension": "%s:%s TPS(%s 毫秒/刻)", "commands.neoforge.tps.dimension.tooltip": "%s(維度類型:%s)", "commands.neoforge.mods.list": "模組清單:%1$s", - "commands.neoforge.tracking.entity.enabled": "實體追踪已啟用,時長 %d 秒。", - "commands.neoforge.tracking.entity.reset": "實體計時資料已被清除!", + "commands.neoforge.tracking.entity.enabled": "已啟用實體追蹤 %d 秒。", + "commands.neoforge.tracking.entity.reset": "實體計時資料已清除!", "commands.neoforge.tracking.invalid": "無效的追蹤資料", "commands.neoforge.tracking.be.enabled": "方塊實體追蹤已啟用,時長 %d 秒。", "commands.neoforge.tracking.be.reset": "方塊實體計時資料已被清除!", - "commands.neoforge.tracking.timing_entry": "%1$s - %2$s [%3$s, %4$s, %5$s]: %6$s", + "commands.neoforge.tracking.timing_entry": "%1$s - %2$s [%3$s, %4$s, %5$s]:%6$s", "commands.neoforge.tracking.no_data": "尚未紀錄資料。", - "commands.neoforge.tags.error.unknown_registry": "未知註冊表 '%s'", + "commands.neoforge.tags.error.unknown_registry": "未知登錄 '%s'", "commands.neoforge.tags.error.unknown_tag": "未知標籤 '%s' 在登錄 '%s' 中", - "commands.neoforge.tags.error.unknown_element": "未知元素 '%s' 在註冊表 '%s' 中", + "commands.neoforge.tags.error.unknown_element": "未知元素 '%s' 在登錄 '%s' 中", "commands.neoforge.tags.registry_key": "%s", "commands.neoforge.tags.tag_count": "標籤:%s", - "commands.neoforge.tags.copy_tag_names": "點擊將所有標籤名稱複製到剪貼簿", + "commands.neoforge.tags.copy_tag_names": "按這裡將所有標籤名稱複製到剪貼簿", "commands.neoforge.tags.element_count": "元素:%s", - "commands.neoforge.tags.copy_element_names": "點擊以將所有元素名稱複製到剪貼簿", + "commands.neoforge.tags.copy_element_names": "按這裡將所有元素名稱複製到剪貼簿", "commands.neoforge.tags.tag_key": "%s / %s", "commands.neoforge.tags.containing_tag_count": "包含標籤:%s", "commands.neoforge.tags.element": "%s:%s", - "commands.neoforge.tags.page_info": "%s ", + "commands.neoforge.tags.page_info": "%s <第 %s / %s 頁>", "commands.neoforge.chunkgen.progress_bar_title": "正在生成區塊...", - "commands.neoforge.chunkgen.progress_bar_progress": "正在生成 %1$s 個區塊 ", + "commands.neoforge.chunkgen.progress_bar_progress": "正在生成 %1$s 個區塊 - ", "commands.neoforge.chunkgen.progress_bar_errors": "(%1$s 個錯誤!)", - "commands.neoforge.chunkgen.already_running": "生成已在進行。請先執行 '/neoforge generate stop' 再開始新生成。", - "commands.neoforge.chunkgen.started": "Generating %1$s chunks, in an area of %2$sx%3$s chunks (%4$sx%5$s blocks).", - "commands.neoforge.chunkgen.success": "生成完畢!", - "commands.neoforge.chunkgen.error": "Generation experienced %1$s errors! Check the log for more information.", - "commands.neoforge.chunkgen.stopped": "Generation stopped! %1$s out of %2$s chunks generated. (%3$s%%)", - "commands.neoforge.chunkgen.status": "Generation status! %1$s out of %2$s chunks generated. (%3$s%%)", + "commands.neoforge.chunkgen.already_running": "生成程序已在執行中。請先執行 '/neoforge generate stop' 指令,然後再開始新的生成程序。", + "commands.neoforge.chunkgen.started": "正在生成 %1$s 個區塊,區域大小為 %2$s × %3$s 個區塊(%4$s × %5$s 個方塊)。", + "commands.neoforge.chunkgen.success": "生成完成!", + "commands.neoforge.chunkgen.error": "生成過程中發生了 %1$s 個錯誤!查看記錄檔以取得詳細資訊。", + "commands.neoforge.chunkgen.stopped": "生成已停止!已生成 %1$s/%2$s 個區塊。(%3$s%%)", + "commands.neoforge.chunkgen.status": "生成狀態!已生成 %1$s/%2$s 個區塊。(%3$s%%)", "commands.neoforge.chunkgen.not_running": "沒有正在進行的預生成。執行 `/neoforge generate help` 以查看開始生成的指令。", "commands.neoforge.chunkgen.help_line": "§2/neoforge generate start [progressBar] §r§f- 生成一個以指定座標為中心、邊長為 chunkRadius * 2 的正方形。\n§2/neoforge generate stop §r§f- 停止目前生成,並顯示已完成的進度。\n§2/neoforge generate status §r- 顯示目前生成的完成進度。\n§2/neoforge generate help §r- 顯示這則訊息\n一般提示: 如果從伺服器控制台執行,可以使用 /execute in neoforge generate... 在不同維度進行生成。", - "commands.neoforge.timespeed.query": "Time in %s flows at a rate of %sx (%s minutes per day).", - "commands.neoforge.timespeed.query.default": "Time in %s flows normally (20 minutes per day).", - "commands.neoforge.timespeed.set": "Set flow of time in %s to %sx (%s minutes per day).", - "commands.neoforge.timespeed.set.default": "Set flow of time in %s to default (20 minutes per day).", - "commands.neoforge.data_components.list.error.held_stack_empty": "You are not holding any item", - "commands.neoforge.data_components.list.title": "Data components on %s:", - "commands.neoforge.data_components.list.entry": "\n - %s", + "commands.neoforge.timespeed.query": "%s 的時間流逝速度為 %s×(每天 %s 分鐘)。", + "commands.neoforge.timespeed.query.default": "%s 的時間流逝速度為預設值(每天 20 分鐘)。", + "commands.neoforge.timespeed.set": "已將 %s 的時間流逝速度設定為 %s×(每天 %s 分鐘)。", + "commands.neoforge.timespeed.set.default": "已將 %s 的時間流逝速度設為預設值(每天 20 分鐘)。", + "commands.neoforge.data_components.list.error.held_stack_empty": "你手上沒有任何物品", + "commands.neoforge.data_components.list.title": "%s 的資料元件:", + "commands.neoforge.data_components.list.entry": "\n- %s", "commands.neoforge.data_components.list.entry.key_value": "%s: %s", - "commands.neoforge.data_components.list.tooltip.default": "Component %s holds its default value", - "commands.neoforge.data_components.list.tooltip.deleted": "Component %s with value %s was deleted", - "commands.neoforge.data_components.list.tooltip.modified": "Component %s was modified from %s to %s", - "commands.neoforge.data_components.list.tooltip.added": "Component %s was added with value %s", + "commands.neoforge.data_components.list.tooltip.default": "元件 %s 的值為預設值", + "commands.neoforge.data_components.list.tooltip.deleted": "元件 %s 已移除(原值:%s)", + "commands.neoforge.data_components.list.tooltip.modified": "元件 %s 的值已從 %s 修改為 %s", + "commands.neoforge.data_components.list.tooltip.added": "元件 %s 已新增,其值為 %s", "commands.config.getwithtype": "%s 類型 %s 的配置在 %s 發現", "commands.config.noconfig": "%s 類型 %s 的配置未發現", - "neoforge.update.beta.1": "%s警告:%s NeoForge 為測試版", - "neoforge.update.beta.2": "可能會出現重大問題,請在回報問題之前進行核實。", + "neoforge.update.beta.1": "%s警告:%s NeoForge 測試版", + "neoforge.update.beta.2": "可能會出現重大問題,請在回報前先確認問題。", "neoforge.update.newversion": "最新 NeoForge 版本可用:%s", "neoforge.menu.updatescreen.title": "模組更新", "neoforge.configuration.uitext.title": "%s 設定", @@ -138,20 +138,20 @@ "neoforge.configuration.uitext.title.server": "%s 伺服器端設定", "neoforge.configuration.uitext.title.common": "%s 一般設定", "neoforge.configuration.uitext.title.startup": "%s 啟動設定", - "neoforge.configuration.uitext.notonline": "Settings in here are determined by the server and cannot be changed while online.", - "neoforge.configuration.uitext.notlan": "Settings in here cannot be edited while your game is open to LAN. Please return to the main menu and load the world again.", - "neoforge.configuration.uitext.notloaded": "Settings in here are only available while a world is loaded.", - "neoforge.configuration.uitext.unsupportedelement": "This value cannot be edited in the UI. Please contact the mod author about providing a custom UI for it.", - "neoforge.configuration.uitext.longstring": "This value is too long to be edited in the UI. Please edit it in the config file.", + "neoforge.configuration.uitext.notonline": "這裡的設定由伺服器決定,且伺服器執行中無法變更。", + "neoforge.configuration.uitext.notlan": "當您的遊戲已對區域網路開放時,無法編輯這裡的設定。請返回主選單並重新載入世界。", + "neoforge.configuration.uitext.notloaded": "這裏的設置只有在加載世界時纔可用。", + "neoforge.configuration.uitext.unsupportedelement": "該數值無法在介面中編輯。請聯絡模組作者以提供客製化的介面。", + "neoforge.configuration.uitext.longstring": "該數值過長,無法在介面中編輯。請在設定檔中編輯它。", "neoforge.configuration.uitext.section": "%s...", "neoforge.configuration.uitext.sectiontext": "編輯", "neoforge.configuration.uitext.breadcrumb.order": "%1$s %2$s %3$s", "neoforge.configuration.uitext.breadcrumb.separator": ">", "neoforge.configuration.uitext.listelement": "%s:", "neoforge.configuration.uitext.undo": "取消復原", - "neoforge.configuration.uitext.undo.tooltip": "Reverts changes on this screen only.", + "neoforge.configuration.uitext.undo.tooltip": "僅復原這個畫面上的變更。", "neoforge.configuration.uitext.reset": "重設", - "neoforge.configuration.uitext.reset.tooltip": "Reverts everything on this screen to its default value.", + "neoforge.configuration.uitext.reset.tooltip": "將這個畫面上的所有項目還原為預設值。", "neoforge.configuration.uitext.newlistelement": "+", "neoforge.configuration.uitext.listelementup": "⏶", "neoforge.configuration.uitext.listelementdown": "⏷", @@ -163,9 +163,9 @@ "neoforge.configuration.uitext.server": "伺服器端選項", "neoforge.configuration.uitext.startup": "啟動選項", "neoforge.configuration.uitext.restart.game.title": "Minecraft 需要重新啟動", - "neoforge.configuration.uitext.restart.game.text": "One or more of the configuration option that were changed will only take effect when the game is started.", + "neoforge.configuration.uitext.restart.game.text": "一或多個已變更的設定選項僅在遊戲啟動時才會生效。", "neoforge.configuration.uitext.restart.server.title": "世界需要重新載入", - "neoforge.configuration.uitext.restart.server.text": "One or more of the configuration option that were changed will only take effect when the world is reloaded.", + "neoforge.configuration.uitext.restart.server.text": "一或多個已變更的設定選項僅在世界重新載入後才會生效。", "neoforge.configuration.uitext.restart.return": "忽略", "neoforge.configuration.uitext.restart.return.tooltip": "你的變更將於重啟遊戲後生效!", "neoforge.configuration.title": "NeoForge 設定", @@ -175,40 +175,40 @@ "neoforge.configuration.section.neoforge.common.toml.title": "一般設定", "neoforge.configuration.section.neoforge.server.toml": "伺服器端設定", "neoforge.configuration.section.neoforge.server.toml.title": "伺服器端設定", - "neoforge.configgui.advertiseDedicatedServerToLan": "Advertise Dedicated Server To LAN", - "neoforge.configgui.advertiseDedicatedServerToLan.tooltip": "Set this to true to enable advertising the dedicated server to local LAN clients so that it shows up in the Multiplayer screen automatically.", - "neoforge.configgui.forgeLightPipelineEnabled": "NeoForge Light Pipeline", - "neoforge.configgui.forgeLightPipelineEnabled.tooltip": "啟用 NeoForge 方塊繪製管線 - 修復自訂模型的光照問題。", + "neoforge.configgui.advertiseDedicatedServerToLan": "將專用伺服器公開至區域網路", + "neoforge.configgui.advertiseDedicatedServerToLan.tooltip": "開啟該選項後,即可讓專用伺服器公開至區域網路,使其自動顯示在多人遊戲畫面中。", + "neoforge.configgui.forgeLightPipelineEnabled": "NeoForge 光照繪製管線", + "neoforge.configgui.forgeLightPipelineEnabled.tooltip": "啟用 NeoForge 方塊繪製管線 - 修正自訂模型的光照問題。", "neoforge.configgui.fullBoundingBoxLadders": "全邊界框梯子", - "neoforge.configgui.fullBoundingBoxLadders.tooltip": "將這項選項設為 true 可以檢查整個實體的碰撞邊界框以尋找梯子,而不僅僅是它們所在的方塊。這會導致遊戲機制有明顯的不同,因此預設值是遵循原版的遊戲行為。預設值:false。", + "neoforge.configgui.fullBoundingBoxLadders.tooltip": "開啟該選項後,可以檢查整個實體的碰撞邊界框以尋找梯子,而不僅僅是它們所在的方塊。這會導致遊戲機制有明顯的不同,因此預設值是遵循原版的遊戲行為。預設值:關閉。", "neoforge.configgui.logLegacyTagWarnings": "記錄舊版標籤", - "neoforge.configgui.logLegacyTagWarnings.tooltip": "A config option mainly for developers. Logs out modded tags that are using the 'forge' namespace when running on integrated server. Defaults to DEV_SHORT.", + "neoforge.configgui.logLegacyTagWarnings.tooltip": "主要供開發人員使用的設定選項。在整合伺服器上執行時,會記錄使用「forge」命名空間的模組標籤。預設值為 DEV_SHORT。", "neoforge.configgui.logUntranslatedConfigurationWarnings": "記錄未翻譯的設定鍵", - "neoforge.configgui.logUntranslatedConfigurationWarnings.tooltip": "A config option mainly for developers. Logs out configuration values that do not have translations when running a client in a development environment.", + "neoforge.configgui.logUntranslatedConfigurationWarnings.tooltip": "主要供開發人員使用的設定選項。在開發環境中執行用戶端時,會記錄沒有翻譯的設定值。", "neoforge.configgui.logUntranslatedItemTagWarnings": "記錄未翻譯的物品標籤", - "neoforge.configgui.logUntranslatedItemTagWarnings.tooltip": "A config option mainly for developers. Logs out modded item tags that do not have translations when running on integrated server. Format desired is tag.item.. for the translation key. Defaults to SILENCED.", - "neoforge.configgui.permissionHandler": "Permission Handler", - "neoforge.configgui.permissionHandler.tooltip": "The permission handler used by the server. Defaults to neoforge:default_handler if no such handler with that name is registered.", + "neoforge.configgui.logUntranslatedItemTagWarnings.tooltip": "主要供開發人員使用的設定選項。在整合伺服器上執行時,會記錄沒有翻譯的模組物品標籤。所需的翻譯鍵格式為 tag.item.<命名空間>.<路徑>。預設值為 SILENCED。", + "neoforge.configgui.permissionHandler": "權限處理器", + "neoforge.configgui.permissionHandler.tooltip": "伺服器使用的權限處理器。若無以此名稱登錄的處理器,則預設為 neoforge:default_handler。", "neoforge.configgui.removeErroringBlockEntities": "移除錯誤方塊實體", - "neoforge.configgui.removeErroringBlockEntities.tooltip": "Set this to true to remove any BlockEntity that throws an error in its update method instead of closing the server and reporting a crash log.", - "neoforge.configgui.removeErroringBlockEntities.tooltip.warning": "BE WARNED THIS COULD SCREW UP EVERYTHING.\nUSE SPARINGLY.\nWE ARE NOT RESPONSIBLE FOR DAMAGES.", + "neoforge.configgui.removeErroringBlockEntities.tooltip": "開啟該選項後,可在方塊實體的更新方法發生錯誤時將其移除,而不是關閉伺服器並回報崩潰報告。", + "neoforge.configgui.removeErroringBlockEntities.tooltip.warning": "警告!這項操作可能會導致嚴重錯誤。\n請謹慎使用。\n我們不對任何損失負責。", "neoforge.configgui.removeErroringEntities": "移除錯誤實體", - "neoforge.configgui.removeErroringEntities.tooltip": "Set this to true to remove any Entity that throws an error in its update method instead of closing the server and reporting a crash log.", - "neoforge.configgui.removeErroringEntities.tooltip.warning": "BE WARNED THIS COULD SCREW UP EVERYTHING.\nUSE SPARINGLY.\nWE ARE NOT RESPONSIBLE FOR DAMAGES.", - "neoforge.configgui.showLoadWarnings": "顯示加載警告", + "neoforge.configgui.removeErroringEntities.tooltip": "開啟該選項後,可在實體的更新方法發生錯誤時將其移除,而不是關閉伺服器並回報崩潰報告。", + "neoforge.configgui.removeErroringEntities.tooltip.warning": "警告!這項操作可能會導致嚴重錯誤。\n請謹慎使用。\n我們不對任何損失負責。", + "neoforge.configgui.showLoadWarnings": "顯示載入警告", "neoforge.configgui.showLoadWarnings.tooltip": "若啟用,NeoForge 將顯示在載入過程中發生的任何警告。", - "neoforge.configgui.useCombinedDepthStencilAttachment": "Use combined DEPTH_STENCIL Attachment", - "neoforge.configgui.useCombinedDepthStencilAttachment.tooltip": "Set to true to use a combined DEPTH_STENCIL attachment instead of two separate ones.", + "neoforge.configgui.useCombinedDepthStencilAttachment": "使用合併的 DEPTH_STENCIL 附件", + "neoforge.configgui.useCombinedDepthStencilAttachment.tooltip": "開啟該選項後,即可使用合併的 DEPTH_STENCIL 附件,而不是兩個獨立的附件。", "neoforge.controlsgui.shift": "SHIFT + %s", "neoforge.controlsgui.control": "CTRL + %s", "neoforge.controlsgui.control.mac": "CMD + %s", "neoforge.controlsgui.alt": "ALT + %s", - "neoforge.container.enchant.limitedEnchantability": "有限可附魔性", + "neoforge.container.enchant.limitedEnchantability": "附魔能力受限", "neoforge.swim_speed": "游泳速度", "neoforge.name_tag_distance": "命名牌顯示距離", "neoforge.creative_flight": "創造模式飛行", - "fluid_type.minecraft.milk": "奶", - "fluid_type.minecraft.flowing_milk": "奶", + "fluid_type.minecraft.milk": "鮮奶", + "fluid_type.minecraft.flowing_milk": "鮮奶", "neoforge.froge.warningScreen.title": "NeoForge 快照提醒", "neoforge.froge.warningScreen.text": "Froge 不被官方支援。錯誤及不穩定性是預期的。", "neoforge.froge.supportWarning": "警告:NeoForge 不支持 Froge", @@ -217,8 +217,8 @@ "neoforge.selectWorld.backupWarning.experimental.additional": "此訊息將不會再為此世界顯示。", "neoforge.chatType.system": "%1$s", "pack.neoforge.description": "NeoForge 資料/資源包", - "pack.neoforge.source.child": "child", - "neoforge.network.negotiation.failure.mod": "Channel of mod \"%1$s\" failed to connect: %2$s", + "pack.neoforge.source.child": "子模組", + "neoforge.network.negotiation.failure.mod": "模組「%1$s」的頻道連線失敗:%2$s", "neoforge.network.negotiation.failure.missing.client.server": "伺服器端缺少這個頻道,但用戶端需要它!", "neoforge.network.negotiation.failure.missing.server.client": "用戶端缺少這個頻道,但伺服器需要它!", "neoforge.network.negotiation.failure.flow.client.missing": "用戶端希望在串流 %s 上傳輸承載,但伺服器端不支援!", @@ -241,7 +241,18 @@ "neoforge.network.data_maps.failed": "註冊表數據映射同步處理失敗 %s: %s", "neoforge.network.data_maps.missing_our": "無法連接服務器,因為缺少用戶端上存在的強制註冊表數據映射: %s", "neoforge.network.data_maps.missing_their": "無法連接服務器,因為服務器上有用戶端不存在的強制註冊表數據映射: %s", - "neoforge.network.extensible_enums.no_vanilla_server": "This client does not support vanilla servers as it has extended enums used in serverbound networking", - "neoforge.network.extensible_enums.enum_set_mismatch": "The set of extensible enums on the client and server do not match. Make sure you are using the same NeoForge version as the server", - "neoforge.network.extensible_enums.enum_entry_mismatch": "The set of values added to extensible enums on the client and server do not match. Make sure you are using the same mod and NeoForge versions as the server. See the log for more details" + "neoforge.network.extensible_enums.no_vanilla_server": "這個客戶端不支持原版服務器,因爲它在服務器綁定網絡中使用了擴展的枚舉", + "neoforge.network.extensible_enums.enum_set_mismatch": "用戶端和伺服器上的可擴充列舉值集合不相符。請確保您使用的 NeoForge 版本與伺服器相同。", + "neoforge.network.extensible_enums.enum_entry_mismatch": "用戶端和伺服器上新增至可擴充列舉值的值集合不相符。請確保您使用的模組和 NeoForge 版本與伺服器相同。查看記錄檔以取得詳細資訊", + "neoforge.attribute.debug.base": "[Entity: %s | Item: %s]", + "neoforge.value.flat": "%s", + "neoforge.value.percent": "%s%%", + "neoforge.value.boolean.enabled": "Enabled", + "neoforge.value.boolean.disabled": "Disabled", + "neoforge.value.boolean.enable": "Enables", + "neoforge.value.boolean.disable": "Disables", + "neoforge.value.boolean.invalid": "Invalid", + "neoforge.modifier.plus": "+%s %s", + "neoforge.modifier.take": "%s %s", + "neoforge.modifier.bool": "%s %s" }