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 95f656936e..20a6ef116c 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 @@ } boolean flag4 = (float)this.func_71024_bL().func_75116_a() > 6.0F || this.field_71075_bZ.field_75101_c; +@@ -667,7 +675,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)); + } + } @@ -961,4 +969,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 0000000000..58f9444f8a --- /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 9323e64c29..44b647ada0 100644 --- a/patches/minecraft/net/minecraft/entity/LivingEntity.java.patch +++ b/patches/minecraft/net/minecraft/entity/LivingEntity.java.patch @@ -367,7 +367,16 @@ if (!itemstack.func_190926_b()) { this.func_110140_aT().func_111148_a(itemstack.func_111283_C(equipmentslottype)); } -@@ -2474,13 +2518,22 @@ +@@ -2258,6 +2302,8 @@ + boolean flag = this.func_70083_f(7); + if (flag && !this.field_70122_E && !this.func_184218_aH()) { + 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) { +@@ -2474,13 +2520,22 @@ private void func_184608_ct() { if (this.func_184587_cr()) { @@ -392,7 +401,7 @@ this.func_71036_o(); } } else { -@@ -2522,8 +2575,10 @@ +@@ -2522,8 +2577,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()) { @@ -404,7 +413,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); -@@ -2583,6 +2638,9 @@ +@@ -2583,6 +2640,9 @@ vec3d1 = vec3d1.func_178789_a(-this.field_70125_A * ((float)Math.PI / 180F)); vec3d1 = vec3d1.func_178785_b(-this.field_70177_z * ((float)Math.PI / 180F)); vec3d1 = vec3d1.func_72441_c(this.func_226277_ct_(), this.func_226280_cw_(), this.func_226281_cx_()); @@ -414,7 +423,7 @@ this.field_70170_p.func_195594_a(new ItemParticleData(ParticleTypes.field_197591_B, p_195062_1_), vec3d1.field_72450_a, vec3d1.field_72448_b, vec3d1.field_72449_c, vec3d.field_72450_a, vec3d.field_72448_b + 0.05D, vec3d.field_72449_c); } -@@ -2594,7 +2652,9 @@ +@@ -2594,7 +2654,9 @@ } else { if (!this.field_184627_bm.func_190926_b() && this.func_184587_cr()) { this.func_226293_b_(this.field_184627_bm, 16); @@ -425,7 +434,7 @@ this.func_184602_cy(); } -@@ -2615,7 +2675,11 @@ +@@ -2615,7 +2677,11 @@ public void func_184597_cx() { if (!this.field_184627_bm.func_190926_b()) { @@ -437,7 +446,7 @@ if (this.field_184627_bm.func_222122_m()) { this.func_184608_ct(); } -@@ -2772,16 +2836,16 @@ +@@ -2772,16 +2838,16 @@ private boolean func_213359_p() { return this.func_213374_dv().map((p_213347_1_) -> { @@ -458,7 +467,7 @@ BlockPos blockpos = p_213368_1_.func_177984_a(); return new Vec3d((double)blockpos.func_177958_n() + 0.5D, (double)blockpos.func_177956_o() + 0.1D, (double)blockpos.func_177952_p() + 0.5D); }); -@@ -2797,7 +2861,9 @@ +@@ -2797,7 +2863,9 @@ @OnlyIn(Dist.CLIENT) public Direction func_213376_dz() { BlockPos blockpos = this.func_213374_dv().orElse((BlockPos)null); @@ -469,7 +478,7 @@ } public boolean func_70094_T() { -@@ -2866,4 +2932,58 @@ +@@ -2866,4 +2934,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 5abefe10d7..cda0b67eaa 100644 --- a/patches/minecraft/net/minecraft/entity/player/PlayerEntity.java.patch +++ b/patches/minecraft/net/minecraft/entity/player/PlayerEntity.java.patch @@ -415,6 +415,15 @@ return false; } else { if (p_225503_1_ >= 2.0F) { +@@ -1519,7 +1636,7 @@ + public boolean func_226566_ei_() { + if (!this.field_70122_E && !this.func_184613_cA() && !this.func_70090_H()) { + 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; + } @@ -1560,6 +1677,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 0000000000..ffd553dc9e --- /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 +@@ -38,4 +38,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 560626c6e6..2f1108bd1a 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeItem.java @@ -784,4 +784,35 @@ public interface IForgeItem default int damageItem(ItemStack stack, int amount, T entity, Consumer onBroken) { return amount; } + + /** + * 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 8913a65993..b356558bb6 100644 --- a/src/main/java/net/minecraftforge/common/extensions/IForgeItemStack.java +++ b/src/main/java/net/minecraftforge/common/extensions/IForgeItemStack.java @@ -448,4 +448,32 @@ public interface IForgeItemStack extends ICapabilitySerializable { return getStack().getItem().isRepairable(getStack()); } + + /** + * 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 0000000000..a73998138b --- /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 9e1b2bdbfd..c618f0b966 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -69,4 +69,6 @@ loaderVersion="[28,)" [[mods]] modId="deferred_registry_test" [[mods]] - modId="player_name_event_test" \ No newline at end of file + modId="player_name_event_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 0000000000..242bb75ec0 --- /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 0000000000..a2039fe006 Binary files /dev/null and b/src/test/resources/assets/custom_elytra_test/textures/entity/custom_elytra.png differ diff --git a/src/test/resources/assets/custom_elytra_test/textures/item/custom_elytra.png b/src/test/resources/assets/custom_elytra_test/textures/item/custom_elytra.png new file mode 100644 index 0000000000..6c0e3614be Binary files /dev/null and b/src/test/resources/assets/custom_elytra_test/textures/item/custom_elytra.png differ