From 399bc6c9c34e930aca03122812714a3bd04c2f1c Mon Sep 17 00:00:00 2001 From: brandon3055 Date: Tue, 4 Aug 2020 08:55:10 +1000 Subject: [PATCH] Add support for custom elytra (#7202) --- .../player/ClientPlayerEntity.java.patch | 9 ++ .../entity/layers/ElytraLayer.java.patch | 54 +++++++ .../minecraft/entity/LivingEntity.java.patch | 27 ++-- .../entity/player/PlayerEntity.java.patch | 9 ++ .../net/minecraft/item/ElytraItem.java.patch | 20 +++ .../common/extensions/IForgeItem.java | 29 ++++ .../common/extensions/IForgeItemStack.java | 28 ++++ .../debug/item/CustomElytraTest.java | 132 ++++++++++++++++++ src/test/resources/META-INF/mods.toml | 2 + .../models/item/test_elytra.json | 6 + .../textures/entity/custom_elytra.png | Bin 0 -> 2573 bytes .../textures/item/custom_elytra.png | Bin 0 -> 2079 bytes 12 files changed, 307 insertions(+), 9 deletions(-) create mode 100644 patches/minecraft/net/minecraft/client/renderer/entity/layers/ElytraLayer.java.patch create mode 100644 patches/minecraft/net/minecraft/item/ElytraItem.java.patch create mode 100644 src/test/java/net/minecraftforge/debug/item/CustomElytraTest.java create mode 100644 src/test/resources/assets/custom_elytra_test/models/item/test_elytra.json create mode 100644 src/test/resources/assets/custom_elytra_test/textures/entity/custom_elytra.png create mode 100644 src/test/resources/assets/custom_elytra_test/textures/item/custom_elytra.png diff --git a/patches/minecraft/net/minecraft/client/entity/player/ClientPlayerEntity.java.patch b/patches/minecraft/net/minecraft/client/entity/player/ClientPlayerEntity.java.patch index 3dbf3cc31..c153878e9 100644 --- a/patches/minecraft/net/minecraft/client/entity/player/ClientPlayerEntity.java.patch +++ b/patches/minecraft/net/minecraft/client/entity/player/ClientPlayerEntity.java.patch @@ -46,6 +46,15 @@ } if (flag1) { +@@ -689,7 +697,7 @@ + + if (this.field_71158_b.field_78901_c && !flag7 && !flag && !this.field_71075_bZ.field_75100_b && !this.func_184218_aH() && !this.func_70617_f_()) { + ItemStack itemstack = this.func_184582_a(EquipmentSlotType.CHEST); +- if (itemstack.func_77973_b() == Items.field_185160_cR && ElytraItem.func_185069_d(itemstack) && this.func_226566_ei_()) { ++ if (itemstack.canElytraFly(this) && this.func_226566_ei_()) { + this.field_71174_a.func_147297_a(new CEntityActionPacket(this, CEntityActionPacket.Action.START_FALL_FLYING)); + } + } @@ -985,4 +993,16 @@ return this.field_204230_bP; } diff --git a/patches/minecraft/net/minecraft/client/renderer/entity/layers/ElytraLayer.java.patch b/patches/minecraft/net/minecraft/client/renderer/entity/layers/ElytraLayer.java.patch new file mode 100644 index 000000000..58f9444f8 --- /dev/null +++ b/patches/minecraft/net/minecraft/client/renderer/entity/layers/ElytraLayer.java.patch @@ -0,0 +1,54 @@ +--- a/net/minecraft/client/renderer/entity/layers/ElytraLayer.java ++++ b/net/minecraft/client/renderer/entity/layers/ElytraLayer.java +@@ -29,7 +29,7 @@ + + public void func_225628_a_(MatrixStack p_225628_1_, IRenderTypeBuffer p_225628_2_, int p_225628_3_, T p_225628_4_, float p_225628_5_, float p_225628_6_, float p_225628_7_, float p_225628_8_, float p_225628_9_, float p_225628_10_) { + ItemStack itemstack = p_225628_4_.func_184582_a(EquipmentSlotType.CHEST); +- if (itemstack.func_77973_b() == Items.field_185160_cR) { ++ if (shouldRender(itemstack, p_225628_4_)) { + ResourceLocation resourcelocation; + if (p_225628_4_ instanceof AbstractClientPlayerEntity) { + AbstractClientPlayerEntity abstractclientplayerentity = (AbstractClientPlayerEntity)p_225628_4_; +@@ -38,10 +38,10 @@ + } else if (abstractclientplayerentity.func_152122_n() && abstractclientplayerentity.func_110303_q() != null && abstractclientplayerentity.func_175148_a(PlayerModelPart.CAPE)) { + resourcelocation = abstractclientplayerentity.func_110303_q(); + } else { +- resourcelocation = field_188355_a; ++ resourcelocation = getElytraTexture(itemstack, p_225628_4_); + } + } else { +- resourcelocation = field_188355_a; ++ resourcelocation = getElytraTexture(itemstack, p_225628_4_); + } + + p_225628_1_.func_227860_a_(); +@@ -53,4 +53,29 @@ + p_225628_1_.func_227865_b_(); + } + } ++ ++ /** ++ * Determines if the ElytraLayer should render. ++ * ItemStack and Entity are provided for modder convenience, ++ * For example, using the same ElytraLayer for multiple custom Elytra. ++ * ++ * @param stack The Elytra ItemStack ++ * @param entity The entity being rendered. ++ * @return If the ElytraLayer should render. ++ */ ++ public boolean shouldRender(ItemStack stack, T entity) { ++ return stack.func_77973_b() == Items.field_185160_cR; ++ } ++ ++ /** ++ * Gets the texture to use with this ElytraLayer. ++ * This assumes the vanilla Elytra model. ++ * ++ * @param stack The Elytra ItemStack. ++ * @param entity The entity being rendered. ++ * @return The texture. ++ */ ++ public ResourceLocation getElytraTexture(ItemStack stack, T entity) { ++ return field_188355_a; ++ } + } diff --git a/patches/minecraft/net/minecraft/entity/LivingEntity.java.patch b/patches/minecraft/net/minecraft/entity/LivingEntity.java.patch index de0e9253c..525d358ed 100644 --- a/patches/minecraft/net/minecraft/entity/LivingEntity.java.patch +++ b/patches/minecraft/net/minecraft/entity/LivingEntity.java.patch @@ -360,7 +360,16 @@ if (map == null) { map = Maps.newEnumMap(EquipmentSlotType.class); } -@@ -2658,8 +2693,16 @@ +@@ -2434,6 +2469,8 @@ + boolean flag = this.func_70083_f(7); + if (flag && !this.field_70122_E && !this.func_184218_aH() && !this.func_70644_a(Effects.field_188424_y)) { + ItemStack itemstack = this.func_184582_a(EquipmentSlotType.CHEST); ++ flag = itemstack.canElytraFly(this) && itemstack.elytraFlightTick(this, this.field_184629_bo); ++ if (false) //Forge: Moved to ElytraItem + if (itemstack.func_77973_b() == Items.field_185160_cR && ElytraItem.func_185069_d(itemstack)) { + flag = true; + if (!this.field_70170_p.field_72995_K && (this.field_184629_bo + 1) % 20 == 0) { +@@ -2658,8 +2695,16 @@ private void func_184608_ct() { if (this.func_184587_cr()) { @@ -379,7 +388,7 @@ this.field_184627_bm.func_222121_b(this.field_70170_p, this, this.func_184605_cv()); if (this.func_226299_p_()) { this.func_226293_b_(this.field_184627_bm, 5); -@@ -2707,8 +2750,10 @@ +@@ -2707,8 +2752,10 @@ public void func_184598_c(Hand p_184598_1_) { ItemStack itemstack = this.func_184586_b(p_184598_1_); if (!itemstack.func_190926_b() && !this.func_184587_cr()) { @@ -391,7 +400,7 @@ if (!this.field_70170_p.field_72995_K) { this.func_204802_c(1, true); this.func_204802_c(2, p_184598_1_ == Hand.OFF_HAND); -@@ -2768,6 +2813,9 @@ +@@ -2768,6 +2815,9 @@ vector3d1 = vector3d1.func_178789_a(-this.field_70125_A * ((float)Math.PI / 180F)); vector3d1 = vector3d1.func_178785_b(-this.field_70177_z * ((float)Math.PI / 180F)); vector3d1 = vector3d1.func_72441_c(this.func_226277_ct_(), this.func_226280_cw_(), this.func_226281_cx_()); @@ -401,7 +410,7 @@ this.field_70170_p.func_195594_a(new ItemParticleData(ParticleTypes.field_197591_B, p_195062_1_), vector3d1.field_72450_a, vector3d1.field_72448_b, vector3d1.field_72449_c, vector3d.field_72450_a, vector3d.field_72448_b + 0.05D, vector3d.field_72449_c); } -@@ -2779,7 +2827,9 @@ +@@ -2779,7 +2829,9 @@ } else { if (!this.field_184627_bm.func_190926_b() && this.func_184587_cr()) { this.func_226293_b_(this.field_184627_bm, 16); @@ -412,7 +421,7 @@ this.func_184602_cy(); } -@@ -2800,7 +2850,11 @@ +@@ -2800,7 +2852,11 @@ public void func_184597_cx() { if (!this.field_184627_bm.func_190926_b()) { @@ -424,7 +433,7 @@ if (this.field_184627_bm.func_222122_m()) { this.func_184608_ct(); } -@@ -2949,8 +3003,8 @@ +@@ -2949,8 +3005,8 @@ } BlockState blockstate = this.field_70170_p.func_180495_p(p_213342_1_); @@ -435,7 +444,7 @@ } this.func_213301_b(Pose.SLEEPING); -@@ -2966,15 +3020,15 @@ +@@ -2966,15 +3022,15 @@ private boolean func_213359_p() { return this.func_213374_dv().map((p_241350_1_) -> { @@ -454,7 +463,7 @@ Vector3d vector3d1 = BedBlock.func_220172_a(this.func_200600_R(), this.field_70170_p, p_241348_1_, 0).orElseGet(() -> { BlockPos blockpos = p_241348_1_.func_177984_a(); return new Vector3d((double)blockpos.func_177958_n() + 0.5D, (double)blockpos.func_177956_o() + 0.1D, (double)blockpos.func_177952_p() + 0.5D); -@@ -2993,7 +3047,9 @@ +@@ -2993,7 +3049,9 @@ @OnlyIn(Dist.CLIENT) public Direction func_213376_dz() { BlockPos blockpos = this.func_213374_dv().orElse((BlockPos)null); @@ -465,7 +474,7 @@ } public boolean func_70094_T() { -@@ -3062,4 +3118,58 @@ +@@ -3062,4 +3120,58 @@ public void func_213334_d(Hand p_213334_1_) { this.func_213361_c(p_213334_1_ == Hand.MAIN_HAND ? EquipmentSlotType.MAINHAND : EquipmentSlotType.OFFHAND); } diff --git a/patches/minecraft/net/minecraft/entity/player/PlayerEntity.java.patch b/patches/minecraft/net/minecraft/entity/player/PlayerEntity.java.patch index 125cdb490..4188b3939 100644 --- a/patches/minecraft/net/minecraft/entity/player/PlayerEntity.java.patch +++ b/patches/minecraft/net/minecraft/entity/player/PlayerEntity.java.patch @@ -257,6 +257,15 @@ return false; } else { if (p_225503_1_ >= 2.0F) { +@@ -1434,7 +1467,7 @@ + public boolean func_226566_ei_() { + if (!this.field_70122_E && !this.func_184613_cA() && !this.func_70090_H() && !this.func_70644_a(Effects.field_188424_y)) { + ItemStack itemstack = this.func_184582_a(EquipmentSlotType.CHEST); +- if (itemstack.func_77973_b() == Items.field_185160_cR && ElytraItem.func_185069_d(itemstack)) { ++ if (itemstack.canElytraFly(this)) { + this.func_226567_ej_(); + return true; + } @@ -1475,6 +1508,10 @@ } diff --git a/patches/minecraft/net/minecraft/item/ElytraItem.java.patch b/patches/minecraft/net/minecraft/item/ElytraItem.java.patch new file mode 100644 index 000000000..244a4075f --- /dev/null +++ b/patches/minecraft/net/minecraft/item/ElytraItem.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/item/ElytraItem.java ++++ b/net/minecraft/item/ElytraItem.java +@@ -35,4 +35,17 @@ + return ActionResult.func_226251_d_(itemstack); + } + } ++ ++ @Override ++ public boolean canElytraFly(ItemStack stack, net.minecraft.entity.LivingEntity entity) { ++ return ElytraItem.func_185069_d(stack); ++ } ++ ++ @Override ++ public boolean elytraFlightTick(ItemStack stack, net.minecraft.entity.LivingEntity entity, int flightTicks) { ++ if (!entity.field_70170_p.field_72995_K && (flightTicks + 1) % 20 == 0) { ++ stack.func_222118_a(1, entity, e -> e.func_213361_c(net.minecraft.inventory.EquipmentSlotType.CHEST)); ++ } ++ return true; ++ } + } diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java b/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java index 13e974cb2..659b66134 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java @@ -819,4 +819,33 @@ public interface IForgeItem return stack.getItem() == Blocks.CARVED_PUMPKIN.asItem(); } + /** + * Used to determine if the player can use Elytra flight. + * This is called Client and Server side. + * + * @param stack The ItemStack in the Chest slot of the entity. + * @param entity The entity trying to fly. + * @return True if the entity can use Elytra flight. + */ + default boolean canElytraFly(ItemStack stack, LivingEntity entity) + { + return false; + } + + /** + * Used to determine if the player can continue Elytra flight, + * this is called each tick, and can be used to apply ItemStack damage, + * consume Energy, or what have you. + * For example the Vanilla implementation of this, applies damage to the + * ItemStack every 20 ticks. + * + * @param stack ItemStack in the Chest slot of the entity. + * @param entity The entity currently in Elytra flight. + * @param flightTicks The number of ticks the entity has been Elytra flying for. + * @return True if the entity should continue Elytra flight or False to stop. + */ + default boolean elytraFlightTick(ItemStack stack, LivingEntity entity, int flightTicks) + { + return false; + } } diff --git a/src/main/java/net/minecraftforge/common/extensions/IForgeItemStack.java b/src/main/java/net/minecraftforge/common/extensions/IForgeItemStack.java index 1f6091952..f90951152 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeItemStack.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeItemStack.java @@ -476,4 +476,32 @@ public interface IForgeItemStack extends ICapabilitySerializable { return getStack().getItem().isEnderMask(getStack(), player, endermanEntity); } + + /** + * Used to determine if the player can use Elytra flight. + * This is called Client and Server side. + * + * @param entity The entity trying to fly. + * @return True if the entity can use Elytra flight. + */ + default boolean canElytraFly(LivingEntity entity) + { + return getStack().getItem().canElytraFly(getStack(), entity); + } + + /** + * Used to determine if the player can continue Elytra flight, + * this is called each tick, and can be used to apply ItemStack damage, + * consume Energy, or what have you. + * For example the Vanilla implementation of this, applies damage to the + * ItemStack every 20 ticks. + * + * @param entity The entity currently in Elytra flight. + * @param flightTicks The number of ticks the entity has been Elytra flying for. + * @return True if the entity should continue Elytra flight or False to stop. + */ + default boolean elytraFlightTick(LivingEntity entity, int flightTicks) + { + return getStack().getItem().elytraFlightTick(getStack(), entity, flightTicks); + } } diff --git a/src/test/java/net/minecraftforge/debug/item/CustomElytraTest.java b/src/test/java/net/minecraftforge/debug/item/CustomElytraTest.java new file mode 100644 index 000000000..a73998138 --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/item/CustomElytraTest.java @@ -0,0 +1,132 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2019. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.debug.item; + +import net.minecraft.block.DispenserBlock; +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.player.AbstractClientPlayerEntity; +import net.minecraft.client.renderer.entity.IEntityRenderer; +import net.minecraft.client.renderer.entity.PlayerRenderer; +import net.minecraft.client.renderer.entity.layers.ElytraLayer; +import net.minecraft.client.renderer.entity.model.ElytraModel; +import net.minecraft.client.renderer.entity.model.PlayerModel; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.inventory.EquipmentSlotType; +import net.minecraft.item.*; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundEvent; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fml.DistExecutor; +import net.minecraftforge.fml.RegistryObject; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.thread.EffectiveSide; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; + +import javax.annotation.Nullable; + +@Mod(CustomElytraTest.MOD_ID) +@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, modid = CustomElytraTest.MOD_ID) +public class CustomElytraTest +{ + public static final String MOD_ID = "custom_elytra_test"; + private static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MOD_ID); + private static final RegistryObject TEST_ELYTRA = ITEMS.register("test_elytra",() -> new CustomElytra(new Item.Properties().maxDamage(100).group(ItemGroup.MISC))); + + public CustomElytraTest() + { + final IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus(); + ITEMS.register(modBus); + modBus.addListener(this::onClientSetup); + } + + private void onClientSetup(FMLClientSetupEvent event) + { + registerElytraLayer(); + } + + @OnlyIn(Dist.CLIENT) + private void registerElytraLayer() + { + Minecraft.getInstance().getRenderManager().getSkinMap().values().forEach(player -> player.addLayer(new CustomElytraLayer(player))); + } + + public static class CustomElytra extends Item + { + + public CustomElytra(Properties properties) + { + super(properties); + DispenserBlock.registerDispenseBehavior(this, ArmorItem.DISPENSER_BEHAVIOR); + } + + @Nullable + @Override + public EquipmentSlotType getEquipmentSlot(ItemStack stack) + { + return EquipmentSlotType.CHEST; //Or you could just extend ItemArmor + } + + @Override + public boolean canElytraFly(ItemStack stack, LivingEntity entity) + { + return true; + } + + @Override + public boolean elytraFlightTick(ItemStack stack, LivingEntity entity, int flightTicks) + { + //Adding 1 to ticksElytraFlying prevents damage on the very first tick. + if (!entity.world.isRemote && (flightTicks + 1) % 20 == 0) + { + stack.damageItem(1, entity, e -> e.sendBreakAnimation(EquipmentSlotType.CHEST)); + } + return true; + } + } + + @OnlyIn(Dist.CLIENT) + public static class CustomElytraLayer extends ElytraLayer> + { + private static final ResourceLocation TEXTURE_ELYTRA = new ResourceLocation(MOD_ID, "textures/entity/custom_elytra.png"); + + public CustomElytraLayer(IEntityRenderer> rendererIn) + { + super(rendererIn); + } + + @Override + public boolean shouldRender(ItemStack stack, AbstractClientPlayerEntity entity) + { + return stack.getItem() == TEST_ELYTRA.get(); + } + + @Override + public ResourceLocation getElytraTexture(ItemStack stack, AbstractClientPlayerEntity entity) + { + return TEXTURE_ELYTRA; + } + } +} diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index 219ca60d0..65b4b88fd 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -76,3 +76,5 @@ license="LGPL v2.1" modId="player_name_event_test" [[mods]] modId="modded_overworld_biomes_test" +[[mods]] + modId="custom_elytra_test" diff --git a/src/test/resources/assets/custom_elytra_test/models/item/test_elytra.json b/src/test/resources/assets/custom_elytra_test/models/item/test_elytra.json new file mode 100644 index 000000000..242bb75ec --- /dev/null +++ b/src/test/resources/assets/custom_elytra_test/models/item/test_elytra.json @@ -0,0 +1,6 @@ +{ + "parent": "item/generated", + "textures": { + "layer0": "custom_elytra_test:item/custom_elytra" + } +} diff --git a/src/test/resources/assets/custom_elytra_test/textures/entity/custom_elytra.png b/src/test/resources/assets/custom_elytra_test/textures/entity/custom_elytra.png new file mode 100644 index 0000000000000000000000000000000000000000..a2039fe006204f1226783c34f9555a94fded2b40 GIT binary patch literal 2573 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