diff --git a/fabric-renderer-registries-v1/build.gradle b/fabric-renderer-registries-v1/build.gradle index b85d29f34..9aaf5bf19 100644 --- a/fabric-renderer-registries-v1/build.gradle +++ b/fabric-renderer-registries-v1/build.gradle @@ -1,6 +1,8 @@ archivesBaseName = "fabric-renderer-registries-v1" -version = getSubprojectVersion(project, "2.0.1") +version = getSubprojectVersion(project, "2.1.0") dependencies { compile project(path: ':fabric-api-base', configuration: 'dev') + + testmodCompile project(path: ':fabric-lifecycle-events-v1', configuration: 'dev') } diff --git a/fabric-renderer-registries-v1/src/main/java/net/fabricmc/fabric/api/client/rendereregistry/v1/LivingEntityFeatureRendererRegistrationCallback.java b/fabric-renderer-registries-v1/src/main/java/net/fabricmc/fabric/api/client/rendereregistry/v1/LivingEntityFeatureRendererRegistrationCallback.java new file mode 100644 index 000000000..592971c0f --- /dev/null +++ b/fabric-renderer-registries-v1/src/main/java/net/fabricmc/fabric/api/client/rendereregistry/v1/LivingEntityFeatureRendererRegistrationCallback.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.api.client.rendereregistry.v1; + +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.feature.Deadmau5FeatureRenderer; +import net.minecraft.client.render.entity.feature.FeatureRenderer; +import net.minecraft.client.render.entity.model.EntityModel; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; + +/** + * Called when {@link FeatureRenderer feature renderers} for a {@link LivingEntityRenderer living entity renderer} are registered. + * + *

Feature renderers are typically used for rendering additional objects on an entity, such as armor, an elytra or {@link Deadmau5FeatureRenderer Deadmau5's ears}. + * This callback lets developers add additional feature renderers for use in entity rendering. + * Listeners should filter out the specific entity renderer they want to hook into, usually through {@code instanceof} checks or filtering by entity type. + * Once listeners find a suitable entity renderer, they should register their feature renderer via the registration helper. + * + *

For example, to register a feature renderer for a player model, the example below may used: + *

+ * LivingEntityFeatureRendererRegistrationCallback.EVENT.register((entityType, entityRenderer, registrationHelper) -> {
+ * 	if (entityRenderer instanceof PlayerEntityModel) {
+ * 		registrationHelper.register(new MyFeatureRenderer((PlayerEntityModel) entityRenderer));
+ * 	}
+ * });
+ * 
+ */ +@FunctionalInterface +@Environment(EnvType.CLIENT) +public interface LivingEntityFeatureRendererRegistrationCallback { + Event EVENT = EventFactory.createArrayBacked(LivingEntityFeatureRendererRegistrationCallback.class, callbacks -> (entityType, entityRenderer, registrationHelper) -> { + for (LivingEntityFeatureRendererRegistrationCallback callback : callbacks) { + callback.registerRenderers(entityType, entityRenderer, registrationHelper); + } + }); + + /** + * Called when feature renderers may be registered. + * + * @param entityType the entity type of the renderer + * @param entityRenderer the entity renderer + */ + void registerRenderers(EntityType entityType, LivingEntityRenderer entityRenderer, RegistrationHelper registrationHelper); + + /** + * A delegate object used to help register feature renderers for an entity renderer. + * + *

This is not meant for implementation by users of the API. + */ + interface RegistrationHelper { + /** + * Adds a feature renderer to the entity renderer. + * + * @param featureRenderer the feature renderer + * @param the type of entity + */ + void register(FeatureRenderer> featureRenderer); + } +} diff --git a/fabric-renderer-registries-v1/src/main/java/net/fabricmc/fabric/impl/client/renderer/registry/RegistrationHelperImpl.java b/fabric-renderer-registries-v1/src/main/java/net/fabricmc/fabric/impl/client/renderer/registry/RegistrationHelperImpl.java new file mode 100644 index 000000000..c8f0b1c26 --- /dev/null +++ b/fabric-renderer-registries-v1/src/main/java/net/fabricmc/fabric/impl/client/renderer/registry/RegistrationHelperImpl.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.client.renderer.registry; + +import java.util.Objects; +import java.util.function.Function; + +import net.minecraft.client.render.entity.feature.FeatureRenderer; +import net.minecraft.client.render.entity.model.EntityModel; +import net.minecraft.entity.LivingEntity; + +import net.fabricmc.fabric.api.client.rendereregistry.v1.LivingEntityFeatureRendererRegistrationCallback; + +public final class RegistrationHelperImpl implements LivingEntityFeatureRendererRegistrationCallback.RegistrationHelper { + private final Function, Boolean> delegate; + + public RegistrationHelperImpl(Function, Boolean> delegate) { + this.delegate = delegate; + } + + @Override + public void register(FeatureRenderer> featureRenderer) { + Objects.requireNonNull(featureRenderer, "Feature renderer cannot be null"); + this.delegate.apply(featureRenderer); + } +} + diff --git a/fabric-renderer-registries-v1/src/main/java/net/fabricmc/fabric/mixin/client/renderer/registry/LivingEntityRendererAccessor.java b/fabric-renderer-registries-v1/src/main/java/net/fabricmc/fabric/mixin/client/renderer/registry/LivingEntityRendererAccessor.java new file mode 100644 index 000000000..1b76f1df0 --- /dev/null +++ b/fabric-renderer-registries-v1/src/main/java/net/fabricmc/fabric/mixin/client/renderer/registry/LivingEntityRendererAccessor.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.client.renderer.registry; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.feature.FeatureRenderer; +import net.minecraft.client.render.entity.model.EntityModel; +import net.minecraft.entity.LivingEntity; + +@Mixin(LivingEntityRenderer.class) +public interface LivingEntityRendererAccessor> { + @Invoker("addFeature") + boolean callAddFeature(FeatureRenderer featureRenderer); +} diff --git a/fabric-renderer-registries-v1/src/main/java/net/fabricmc/fabric/mixin/client/renderer/registry/MixinEntityRenderDispatcher.java b/fabric-renderer-registries-v1/src/main/java/net/fabricmc/fabric/mixin/client/renderer/registry/MixinEntityRenderDispatcher.java index 780ac23d1..0b6b37d13 100644 --- a/fabric-renderer-registries-v1/src/main/java/net/fabricmc/fabric/mixin/client/renderer/registry/MixinEntityRenderDispatcher.java +++ b/fabric-renderer-registries-v1/src/main/java/net/fabricmc/fabric/mixin/client/renderer/registry/MixinEntityRenderDispatcher.java @@ -18,6 +18,7 @@ package net.fabricmc.fabric.mixin.client.renderer.registry; import java.util.Map; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; @@ -26,20 +27,51 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import net.minecraft.client.render.entity.EntityRenderDispatcher; import net.minecraft.client.render.entity.EntityRenderer; +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.PlayerEntityRenderer; import net.minecraft.client.render.item.ItemRenderer; import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; import net.minecraft.resource.ReloadableResourceManager; import net.fabricmc.fabric.api.client.rendereregistry.v1.EntityRendererRegistry; +import net.fabricmc.fabric.api.client.rendereregistry.v1.LivingEntityFeatureRendererRegistrationCallback; +import net.fabricmc.fabric.impl.client.renderer.registry.RegistrationHelperImpl; @Mixin(EntityRenderDispatcher.class) -public class MixinEntityRenderDispatcher { +public abstract class MixinEntityRenderDispatcher { @Shadow - Map, EntityRenderer> renderers; + @Final + private Map, EntityRenderer> renderers; - @Inject(method = "registerRenderers", at = @At(value = "RETURN"), require = 1) - public void on_method_23167(ItemRenderer itemRenderer, ReloadableResourceManager manager, CallbackInfo info) { + @Shadow + @Final + private Map modelRenderers; + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Inject(method = "registerRenderers", at = @At(value = "TAIL")) + public void onRegisterRenderers(ItemRenderer itemRenderer, ReloadableResourceManager manager, CallbackInfo info) { final EntityRenderDispatcher me = (EntityRenderDispatcher) (Object) this; EntityRendererRegistry.INSTANCE.initialize(me, me.textureManager, manager, itemRenderer, renderers); + + // Dispatch events to register feature renderers. + for (Map.Entry, EntityRenderer> entry : this.renderers.entrySet()) { + if (entry.getValue() instanceof LivingEntityRenderer) { // Must be living for features + LivingEntityRendererAccessor accessor = (LivingEntityRendererAccessor) entry.getValue(); + + LivingEntityFeatureRendererRegistrationCallback.EVENT.invoker().registerRenderers((EntityType) entry.getKey(), (LivingEntityRenderer) entry.getValue(), new RegistrationHelperImpl(accessor::callAddFeature)); + } + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + @Inject(method = "", at = @At("TAIL")) + private void afterRegisterPlayerModels(CallbackInfo ci) { + // Players are a fun case, we need to do these separately and per model type + for (Map.Entry entry : this.modelRenderers.entrySet()) { + LivingEntityRendererAccessor accessor = (LivingEntityRendererAccessor) entry.getValue(); + + LivingEntityFeatureRendererRegistrationCallback.EVENT.invoker().registerRenderers(EntityType.PLAYER, entry.getValue(), new RegistrationHelperImpl(accessor::callAddFeature)); + } } } diff --git a/fabric-renderer-registries-v1/src/main/resources/fabric-renderer-registries-v1.mixins.json b/fabric-renderer-registries-v1/src/main/resources/fabric-renderer-registries-v1.mixins.json index 17edfeec3..2a029a6b2 100644 --- a/fabric-renderer-registries-v1/src/main/resources/fabric-renderer-registries-v1.mixins.json +++ b/fabric-renderer-registries-v1/src/main/resources/fabric-renderer-registries-v1.mixins.json @@ -3,6 +3,7 @@ "package": "net.fabricmc.fabric.mixin.client.renderer.registry", "compatibilityLevel": "JAVA_8", "client": [ + "LivingEntityRendererAccessor", "MixinBlockEntityRenderDispatcher", "MixinEntityRenderDispatcher" ], diff --git a/fabric-renderer-registries-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/registry/FeatureRendererGenericTests.java b/fabric-renderer-registries-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/registry/FeatureRendererGenericTests.java new file mode 100644 index 000000000..79466c21e --- /dev/null +++ b/fabric-renderer-registries-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/registry/FeatureRendererGenericTests.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.renderer.registry; + +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.ArmorStandEntityRenderer; +import net.minecraft.client.render.entity.BipedEntityRenderer; +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.PlayerEntityRenderer; +import net.minecraft.client.render.entity.feature.ElytraFeatureRenderer; +import net.minecraft.client.render.entity.feature.FeatureRenderer; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.feature.HeldItemFeatureRenderer; +import net.minecraft.client.render.entity.model.ArmorStandArmorEntityModel; +import net.minecraft.client.render.entity.model.PlayerEntityModel; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.EntityType; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.decoration.ArmorStandEntity; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.rendereregistry.v1.LivingEntityFeatureRendererRegistrationCallback; + +/** + * This test exists solely for testing generics. + * As such it is not in the mod json + */ +public class FeatureRendererGenericTests implements ClientModInitializer { + @Override + public void onInitializeClient() { + // These aren't tests in the normal sense. These exist to test that generics are sane. + LivingEntityFeatureRendererRegistrationCallback.EVENT.register((entityType, entityRenderer, registrationHelper) -> { + if (entityRenderer instanceof PlayerEntityRenderer) { + registrationHelper.register(new TestPlayerFeature((PlayerEntityRenderer) entityRenderer)); + + // This is T extends AbstractClientPlayerEntity + registrationHelper.register(new GenericTestPlayerFeature<>((PlayerEntityRenderer) entityRenderer)); + } + + if (entityRenderer instanceof ArmorStandEntityRenderer) { + registrationHelper.register(new TestArmorStandFeature((ArmorStandEntityRenderer) entityRenderer)); + } + + // Obviously not recommended, just used for testing generics + registrationHelper.register(new ElytraFeatureRenderer<>(entityRenderer)); + + if (entityRenderer instanceof BipedEntityRenderer) { + // It works, method ref is encouraged + registrationHelper.register(new HeldItemFeatureRenderer<>((BipedEntityRenderer) entityRenderer)); + } + }); + + LivingEntityFeatureRendererRegistrationCallback.EVENT.register(this::registerFeatures); + } + + private void registerFeatures(EntityType entityType, LivingEntityRenderer entityRenderer, LivingEntityFeatureRendererRegistrationCallback.RegistrationHelper registrationHelper) { + if (entityRenderer instanceof PlayerEntityRenderer) { + registrationHelper.register(new TestPlayerFeature((PlayerEntityRenderer) entityRenderer)); + + // This is T extends AbstractClientPlayerEntity + registrationHelper.register(new GenericTestPlayerFeature<>((PlayerEntityRenderer) entityRenderer)); + } + + if (entityRenderer instanceof ArmorStandEntityRenderer) { + registrationHelper.register(new TestArmorStandFeature((ArmorStandEntityRenderer) entityRenderer)); + } + + // Obviously not recommended, just used for testing generics. + registrationHelper.register(new ElytraFeatureRenderer<>(entityRenderer)); + + if (entityRenderer instanceof BipedEntityRenderer) { + // It works, method ref is encouraged + registrationHelper.register(new HeldItemFeatureRenderer<>((BipedEntityRenderer) entityRenderer)); + } + } + + static class TestPlayerFeature extends FeatureRenderer> { + TestPlayerFeature(FeatureRendererContext> context) { + super(context); + } + + @Override + public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, AbstractClientPlayerEntity entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { + } + } + + static class GenericTestPlayerFeature> extends FeatureRenderer { + GenericTestPlayerFeature(FeatureRendererContext context) { + super(context); + } + + @Override + public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { + } + } + + static class TestArmorStandFeature extends FeatureRenderer { + TestArmorStandFeature(FeatureRendererContext context) { + super(context); + } + + @Override + public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, ArmorStandEntity entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { + } + } +} diff --git a/fabric-renderer-registries-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/registry/FeatureRendererTest.java b/fabric-renderer-registries-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/registry/FeatureRendererTest.java new file mode 100644 index 000000000..3c32b664e --- /dev/null +++ b/fabric-renderer-registries-v1/src/testmod/java/net/fabricmc/fabric/test/renderer/registry/FeatureRendererTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.renderer.registry; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import net.minecraft.block.Blocks; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.network.AbstractClientPlayerEntity; +import net.minecraft.client.render.OverlayTexture; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.PlayerEntityRenderer; +import net.minecraft.client.render.entity.feature.FeatureRenderer; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.model.PlayerEntityModel; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.EntityType; +import net.minecraft.util.registry.Registry; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; +import net.fabricmc.fabric.api.client.rendereregistry.v1.LivingEntityFeatureRendererRegistrationCallback; + +public final class FeatureRendererTest implements ClientModInitializer { + private static final Logger LOGGER = LogManager.getLogger(FeatureRendererTest.class); + private int playerRegistrations = 0; + + @Override + public void onInitializeClient() { + LOGGER.info("Registering test feature renderer"); + LivingEntityFeatureRendererRegistrationCallback.EVENT.register((entityType, entityRenderer, registrationHelper) -> { + // minecraft:player SHOULD be printed twice + LOGGER.info(String.format("Received registration for %s", Registry.ENTITY_TYPE.getId(entityType))); + + if (entityType == EntityType.PLAYER) { + this.playerRegistrations++; + } + + if (entityRenderer instanceof PlayerEntityRenderer) { + registrationHelper.register(new TestPlayerFeatureRenderer((PlayerEntityRenderer) entityRenderer)); + } + }); + + ClientLifecycleEvents.CLIENT_STARTED.register(client -> { + if (this.playerRegistrations != 2) { + throw new AssertionError(String.format("Expected 2 entity feature renderer registration events for \"minecraft:player\" but received %s registrations", this.playerRegistrations)); + } + + LOGGER.info("Successfully called feature renderer registration events"); + }); + } + + private static class TestPlayerFeatureRenderer extends FeatureRenderer> { + TestPlayerFeatureRenderer(FeatureRendererContext> context) { + super(context); + } + + @Override + public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, AbstractClientPlayerEntity entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { + matrices.push(); + + // Translate to center above the player's head + matrices.translate(-0.5F, -entity.getHeight() + 0.25F, -0.5F); + // Render a diamond block above the player's head + MinecraftClient.getInstance().getBlockRenderManager().renderBlockAsEntity(Blocks.DIAMOND_BLOCK.getDefaultState(), matrices, vertexConsumers, light, OverlayTexture.DEFAULT_UV); + + matrices.pop(); + } + } +} diff --git a/fabric-renderer-registries-v1/src/testmod/resources/fabric.mod.json b/fabric-renderer-registries-v1/src/testmod/resources/fabric.mod.json new file mode 100644 index 000000000..a9bbee589 --- /dev/null +++ b/fabric-renderer-registries-v1/src/testmod/resources/fabric.mod.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 1, + "id": "fabric-renderer-registries-v1-testmod", + "name": "Fabric Render Registries (v1) Test Mod", + "version": "1.0.0", + "environment": "*", + "license": "Apache-2.0", + "depends": { + "fabric-item-api-v1": "*" + }, + "entrypoints": { + "client": [ + "net.fabricmc.fabric.test.renderer.registry.FeatureRendererTest" + ] + } +}