diff --git a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java index 990e1925..a28f0f40 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/URenderers.java +++ b/src/main/java/com/minelittlepony/unicopia/client/URenderers.java @@ -11,6 +11,9 @@ import com.minelittlepony.unicopia.client.particle.RainboomParticle; import com.minelittlepony.unicopia.client.particle.RainbowTrailParticle; import com.minelittlepony.unicopia.client.particle.RaindropsParticle; import com.minelittlepony.unicopia.client.particle.SphereParticle; +import com.minelittlepony.unicopia.client.render.AccessoryFeatureRenderer; +import com.minelittlepony.unicopia.client.render.AmuletFeatureRenderer; +import com.minelittlepony.unicopia.client.render.BraceletFeatureRenderer; import com.minelittlepony.unicopia.client.render.FloatingArtefactEntityRenderer; import com.minelittlepony.unicopia.item.ChameleonItem; import com.minelittlepony.unicopia.item.UItems; @@ -46,6 +49,9 @@ public interface URenderers { ParticleFactoryRegistry.getInstance().register(UParticles.GROUND_POUND, GroundPoundParticle::new); ParticleFactoryRegistry.getInstance().register(UParticles.CLOUDS_ESCAPING, CloudsEscapingParticle::new); + AccessoryFeatureRenderer.register(BraceletFeatureRenderer::new); + AccessoryFeatureRenderer.register(AmuletFeatureRenderer::new); + EntityRendererRegistry.INSTANCE.register(UEntities.THROWN_ITEM, (manager, context) -> new FlyingItemEntityRenderer<>(manager, context.getItemRenderer())); EntityRendererRegistry.INSTANCE.register(UEntities.FLOATING_ARTEFACT, FloatingArtefactEntityRenderer::new); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/AccessoryFeatureRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/AccessoryFeatureRenderer.java new file mode 100644 index 00000000..1d8dbf49 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/AccessoryFeatureRenderer.java @@ -0,0 +1,46 @@ +package com.minelittlepony.unicopia.client.render; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.feature.FeatureRenderer; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.LivingEntity; + +public class AccessoryFeatureRenderer< + T extends LivingEntity, + M extends BipedEntityModel> extends FeatureRenderer { + + private static final List> REGISTRY = new ArrayList<>(); + + public static void register(FeatureFactory factory) { + REGISTRY.add(factory); + } + + private final Iterable> features; + + @SuppressWarnings("unchecked") + public AccessoryFeatureRenderer(FeatureRendererContext context) { + super(context); + features = REGISTRY.stream().map(f -> ((FeatureFactory)f).create(context)).collect(Collectors.toList()); + } + + @Override + public void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch) { + features.forEach(feature -> { + feature.render(matrices, vertexConsumers, light, entity, limbAngle, limbDistance, tickDelta, animationProgress, headYaw, headPitch); + }); + } + + public interface FeatureFactory { + Feature create(FeatureRendererContext> context); + } + + public interface Feature { + void render(MatrixStack matrices, VertexConsumerProvider vertexConsumers, int light, T entity, float limbAngle, float limbDistance, float tickDelta, float animationProgress, float headYaw, float headPitch); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/AmuletFeatureRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/AmuletFeatureRenderer.java new file mode 100644 index 00000000..bdf0cd3d --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/AmuletFeatureRenderer.java @@ -0,0 +1,161 @@ +package com.minelittlepony.unicopia.client.render; + +import java.util.HashMap; +import java.util.Map; + +import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.item.AmuletItem; +import com.minelittlepony.unicopia.item.UItems; + +import net.minecraft.client.model.Model; +import net.minecraft.client.model.ModelPart; +import net.minecraft.client.render.OverlayTexture; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.render.item.ItemRenderer; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.EquipmentSlot; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.Identifier; +import net.minecraft.util.registry.Registry; + +public class AmuletFeatureRenderer implements AccessoryFeatureRenderer.Feature { + + private final BraceletModel model = new BraceletModel(0.3F); + + private final Map textures = new HashMap<>(); + + private final FeatureRendererContext> context; + + public AmuletFeatureRenderer(FeatureRendererContext> context) { + this.context = context; + } + + @Override + public void render(MatrixStack matrices, VertexConsumerProvider renderContext, int lightUv, E entity, float limbDistance, float limbAngle, float tickDelta, float age, float headYaw, float headPitch) { + + ItemStack stack = entity.getEquippedStack(EquipmentSlot.CHEST); + Item item = stack.getItem(); + + boolean amulet = item instanceof AmuletItem; + boolean wings = + (amulet && ((AmuletItem)item).isApplicable(stack)) + || (entity instanceof PlayerEntity && Pony.of((PlayerEntity)entity).getSpecies().canInteractWithClouds()); + + if (wings || amulet) { + Identifier texture = textures.computeIfAbsent(Registry.ITEM.getId(amulet ? item : UItems.PEGASUS_AMULET), id -> new Identifier(id.getNamespace(), "textures/models/armor/" + id.getPath() + ".png")); + + VertexConsumer consumer = ItemRenderer.getArmorGlintConsumer(renderContext, RenderLayer.getArmorCutoutNoCull(texture), false, false); + + model.setVisible(amulet, wings); + model.setAngles(entity, context.getModel()); + model.render(matrices, consumer, lightUv, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1); + } + } + + static class BraceletModel extends Model { + + private final ModelPart torso; + private final ModelPart amulet; + + private final Wing[] wings; + + public BraceletModel(float dilate) { + super(RenderLayer::getEntityTranslucent); + torso = new ModelPart(this, 0, 0); + amulet = new ModelPart(this, 0, 0); + amulet.addCuboid(-4, 0, -2, 8, 12, 4, dilate); + amulet.setPivot(0, 0, 0); + torso.addChild(amulet); + wings = new Wing[] { + new Wing(this, torso, -1), + new Wing(this, torso, 1) + }; + } + + public void setVisible(boolean amulet, boolean wings) { + this.amulet.visible = amulet; + for (Wing wing : this.wings) { + wing.base.visible = wings; + } + } + + public void setAngles(LivingEntity entity, BipedEntityModel biped) { + torso.copyPositionAndRotation(biped.torso); + for (Wing wing : wings) { + wing.setAngles(entity); + } + } + + @Override + public void render(MatrixStack matrices, VertexConsumer vertexConsumer, int i, int j, float f, float g, float h, float k) { + torso.render(matrices, vertexConsumer, i, j, f, g, h, k); + } + + static class Wing { + ModelPart base; + + ModelPart[] feathers; + + int k; + + Wing(Model model, ModelPart torso, int k) { + this.k = k; + base = new ModelPart(model, 0, 16); + + base.setPivot(k * 2, 2, 2 + k * 0.5F); + base.addCuboid(0, 0, 0, 2, 10, 2); + + feathers = new ModelPart[8]; + + for (int i = 0; i < feathers.length; i++) { + int texX = (i % 2) * 8; + + ModelPart feather = new ModelPart(model, 24 + texX, 0); + feather.setPivot(0, 9, 0); + + int featherLength = 21 - i * 2; + + feather.addCuboid(-k * (i % 2) / 90F, 0, 0, 2, featherLength, 2); + + base.addChild(feather); + feathers[i] = feather; + } + + torso.addChild(base); + } + + void setAngles(LivingEntity entity) { + if (entity instanceof PlayerEntity) { + Pony pony = Pony.of((PlayerEntity)entity); + + float spreadAmount = pony.getMotion().getWingAngle(); + + base.pitch = 1.5F + 0.8F - spreadAmount / 9F; + base.yaw = k * (0.8F + spreadAmount / 3F); + + spreadAmount /= 7F; + + final float ratio = 4F; + + for (int i = 0; i < feathers.length; i++) { + + float spread = i/ratio + 1.5F; + spread -= spreadAmount * ratio; + spread += spreadAmount * i / ratio; + + feathers[i].pitch = -spread; + feathers[i].yaw = k * 0.3F; + } + + } + } + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/BraceletFeatureRenderer.java b/src/main/java/com/minelittlepony/unicopia/client/render/BraceletFeatureRenderer.java index 5b90af31..e32ae037 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/render/BraceletFeatureRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/client/render/BraceletFeatureRenderer.java @@ -11,7 +11,6 @@ import net.minecraft.client.render.OverlayTexture; import net.minecraft.client.render.RenderLayer; import net.minecraft.client.render.VertexConsumer; import net.minecraft.client.render.VertexConsumerProvider; -import net.minecraft.client.render.entity.feature.FeatureRenderer; import net.minecraft.client.render.entity.feature.FeatureRendererContext; import net.minecraft.client.render.entity.model.BipedEntityModel; import net.minecraft.client.util.math.MatrixStack; @@ -23,17 +22,17 @@ import net.minecraft.item.ItemStack; import net.minecraft.util.Arm; import net.minecraft.util.Identifier; -public class BraceletFeatureRenderer< - E extends LivingEntity, - M extends BipedEntityModel> extends FeatureRenderer { +public class BraceletFeatureRenderer implements AccessoryFeatureRenderer.Feature { private static final Identifier TEXTURE = new Identifier("unicopia", "textures/models/armor/bracelet.png"); private final BraceletModel steveModel = new BraceletModel(0.3F, false); private final BraceletModel alexModel = new BraceletModel(0.3F, true); - public BraceletFeatureRenderer(FeatureRendererContext context) { - super(context); + private final FeatureRendererContext> context; + + public BraceletFeatureRenderer(FeatureRendererContext> context) { + this.context = context; } @Override @@ -51,9 +50,9 @@ public class BraceletFeatureRenderer< BraceletModel model = alex ? alexModel : steveModel; if (entity instanceof ArmorStandEntity) { - ModelPart arm = entity.getMainArm() == Arm.LEFT ? getContextModel().leftArm : getContextModel().rightArm; + ModelPart arm = entity.getMainArm() == Arm.LEFT ? context.getModel().leftArm : context.getModel().rightArm; arm.visible = true; - VertexConsumer consumer = renderContext.getBuffer(getContextModel().getLayer(getTexture(entity))); + VertexConsumer consumer = renderContext.getBuffer(context.getModel().getLayer(context.getTexture(entity))); arm.render(stack, consumer, lightUv, OverlayTexture.DEFAULT_UV, 1, 1, 1, 1); } @@ -61,7 +60,7 @@ public class BraceletFeatureRenderer< VertexConsumer consumer = CanvasCompat.getGlowingConsumer(glowing, renderContext, RenderLayer.getArmorCutoutNoCull(TEXTURE)); - model.setAngles(getContextModel()); + model.setAngles(context.getModel()); model.setVisible(entity.getMainArm()); model.render(stack, consumer, glowing ? 0x0F00F0 : lightUv, OverlayTexture.DEFAULT_UV, Color.r(j), Color.g(j), Color.b(j), 1); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/Leaner.java b/src/main/java/com/minelittlepony/unicopia/entity/Leaner.java new file mode 100644 index 00000000..e09ca7f3 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/entity/Leaner.java @@ -0,0 +1,7 @@ +package com.minelittlepony.unicopia.entity; + +public interface Leaner { + float getLeaningPitch(); + + void setLeaningPitch(float pitch); +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/RotatedView.java b/src/main/java/com/minelittlepony/unicopia/entity/RotatedView.java index dc7c0f69..774cf091 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/RotatedView.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/RotatedView.java @@ -15,8 +15,11 @@ public interface RotatedView { } default void popRotation() { - if (!getRotations().isEmpty()) { - getRotations().pop(); + Stack rotations = getRotations(); + synchronized (rotations) { + if (!rotations.isEmpty()) { + rotations.pop(); + } } } @@ -29,10 +32,13 @@ public interface RotatedView { } default int applyRotation(int y) { - if (!hasTransform() || getRotations().isEmpty()) { - return y; + Stack rotations = getRotations(); + synchronized (rotations) { + if (!hasTransform() || rotations.isEmpty()) { + return y; + } + return y - ((y - rotations.peek()) * 2); } - return y - ((y - getRotations().peek()) * 2); } } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/Motion.java b/src/main/java/com/minelittlepony/unicopia/entity/player/Motion.java index 72f70b3f..b54af7a0 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Motion.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Motion.java @@ -9,5 +9,9 @@ public interface Motion { */ boolean isFlying(); + boolean isGliding(); + + float getWingAngle(); + PlayerDimensions getDimensions(); } diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java index 139ec66c..5d4611b2 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/PlayerPhysics.java @@ -7,6 +7,7 @@ import com.minelittlepony.unicopia.ability.magic.Spell; import com.minelittlepony.unicopia.entity.Creature; import com.minelittlepony.unicopia.entity.EntityPhysics; import com.minelittlepony.unicopia.entity.Jumper; +import com.minelittlepony.unicopia.entity.Leaner; import com.minelittlepony.unicopia.entity.player.MagicReserves.Bar; import com.minelittlepony.unicopia.item.AmuletItem; import com.minelittlepony.unicopia.item.UItems; @@ -67,6 +68,30 @@ public class PlayerPhysics extends EntityPhysics implements Tickable, Moti return isFlyingSurvival && !pony.getMaster().isFallFlying() && !pony.getMaster().hasVehicle(); } + @Override + public boolean isGliding() { + return isFlying() && (pony.getMaster().isSneaking() || ((Jumper)pony.getMaster()).isJumping()) && !pony.sneakingChanged(); + } + + @Override + public float getWingAngle() { + float spreadAmount = -0.5F; + + if (isFlying()) { + //spreadAmount += Math.sin(pony.getEntity().age / 4F) * 8; + spreadAmount += isGliding() ? 3 : thrustScale * 60; + } + + if (pony.getEntity().isSneaking()) { + spreadAmount += 2; + } + + spreadAmount += Math.sin(pony.getEntity().age / 9F) / 9F; + spreadAmount = MathHelper.clamp(spreadAmount, -2, 5); + + return pony.getInterpolator().interpolate("wingSpreadAmount", spreadAmount, 10); + } + @Override public void tick() { super.tick(); @@ -260,6 +285,13 @@ public class PlayerPhysics extends EntityPhysics implements Tickable, Moti } entity.setVelocity(velocity.toImmutable()); + + if (isFlying() && !entity.isInSwimmingPose()) { + float pitch = ((Leaner)entity).getLeaningPitch(); + if (pitch < 1) { + ((Leaner)entity).setLeaningPitch(Math.max(0, pitch + 0.18F)); + } + } } protected void handleWallCollission(PlayerEntity player, MutableVector velocity) { diff --git a/src/main/java/com/minelittlepony/unicopia/item/AmuletItem.java b/src/main/java/com/minelittlepony/unicopia/item/AmuletItem.java index a58ff4be..c44e5caa 100644 --- a/src/main/java/com/minelittlepony/unicopia/item/AmuletItem.java +++ b/src/main/java/com/minelittlepony/unicopia/item/AmuletItem.java @@ -16,11 +16,11 @@ import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.LivingEntity; import net.minecraft.entity.attribute.EntityAttribute; import net.minecraft.entity.attribute.EntityAttributeModifier; -import net.minecraft.item.ArmorItem; import net.minecraft.item.ArmorMaterial; import net.minecraft.item.ArmorMaterials; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; +import net.minecraft.item.Wearable; import net.minecraft.recipe.Ingredient; import net.minecraft.sound.SoundEvent; import net.minecraft.sound.SoundEvents; @@ -33,19 +33,19 @@ import net.minecraft.text.TranslatableText; import net.minecraft.util.Formatting; import net.minecraft.world.World; -public class AmuletItem extends ArmorItem { +public class AmuletItem extends Item implements Wearable { private final int maxEnergy; private final float drain; private final ImmutableMultimap modifiers; - public AmuletItem(Item.Settings settings, int maxEnergy, int drainRate) { + public AmuletItem(FabricItemSettings settings, int maxEnergy, int drainRate) { this(settings, maxEnergy, drainRate, ImmutableMultimap.builder()); } - public AmuletItem(Item.Settings settings, int maxEnergy, int drainRate, ImmutableMultimap.Builder modifiers) { - super((Settings)settings, EquipmentSlot.CHEST, settings); + public AmuletItem(FabricItemSettings settings, int maxEnergy, int drainRate, ImmutableMultimap.Builder modifiers) { + super(settings.equipmentSlot(s -> EquipmentSlot.CHEST)); this.maxEnergy = maxEnergy; drain = ((float)drainRate / (float)maxEnergy) / 10; @@ -54,7 +54,7 @@ public class AmuletItem extends ArmorItem { @Override public void inventoryTick(ItemStack stack, World world, Entity entity, int slot, boolean selected) { - if (isChargable() && entity instanceof LivingEntity && ((LivingEntity) entity).getEquippedStack(getSlotType()) == stack) { + if (isChargable() && entity instanceof LivingEntity && ((LivingEntity) entity).getEquippedStack(EquipmentSlot.CHEST) == stack) { consumeEnergy(stack, drain); } } @@ -84,7 +84,7 @@ public class AmuletItem extends ArmorItem { @Override public Multimap getAttributeModifiers(EquipmentSlot slot) { - return slot == getSlotType() ? modifiers : ImmutableMultimap.of(); + return slot == EquipmentSlot.CHEST ? modifiers : ImmutableMultimap.of(); } public boolean isApplicable(ItemStack stack) { @@ -92,7 +92,7 @@ public class AmuletItem extends ArmorItem { } public boolean isApplicable(LivingEntity entity) { - return isApplicable(entity.getEquippedStack(getSlotType())); + return isApplicable(entity.getEquippedStack(EquipmentSlot.CHEST)); } public boolean isChargable() { diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/MixinLivingEntity.java b/src/main/java/com/minelittlepony/unicopia/mixin/MixinLivingEntity.java index 921d1b69..8d2a79fa 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/MixinLivingEntity.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/MixinLivingEntity.java @@ -20,6 +20,7 @@ import com.minelittlepony.unicopia.entity.behaviour.Disguise; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.entity.Equine; import com.minelittlepony.unicopia.entity.Jumper; +import com.minelittlepony.unicopia.entity.Leaner; import com.minelittlepony.unicopia.entity.ItemWielder; import net.minecraft.entity.Entity; @@ -32,7 +33,7 @@ import net.minecraft.util.Hand; import net.minecraft.util.math.BlockPos; @Mixin(LivingEntity.class) -abstract class MixinLivingEntity extends Entity implements PonyContainer>, ItemWielder, Jumper { +abstract class MixinLivingEntity extends Entity implements PonyContainer>, ItemWielder, Jumper, Leaner { @Shadow protected ItemStack activeItemStack; @Shadow @@ -65,6 +66,14 @@ abstract class MixinLivingEntity extends Entity implements PonyContainer info) { Creature.registerAttributes(info.getReturnValue()); diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinArmorFeatureRenderer.java b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinArmorFeatureRenderer.java index 5945638c..1511d2dc 100644 --- a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinArmorFeatureRenderer.java +++ b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinArmorFeatureRenderer.java @@ -5,7 +5,7 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import com.minelittlepony.unicopia.client.render.BraceletFeatureRenderer; +import com.minelittlepony.unicopia.client.render.AccessoryFeatureRenderer; import net.minecraft.client.render.VertexConsumerProvider; import net.minecraft.client.render.entity.feature.ArmorFeatureRenderer; @@ -18,17 +18,17 @@ import net.minecraft.entity.LivingEntity; @Mixin(ArmorFeatureRenderer.class) abstract class MixinArmorFeatureRenderer, A extends BipedEntityModel> extends FeatureRenderer { - private BraceletFeatureRenderer bracelet; + private AccessoryFeatureRenderer accessories; MixinArmorFeatureRenderer() { super(null); } @Inject(method = "", at = @At("RETURN")) private void onInit(FeatureRendererContext context, A inner, A outer, CallbackInfo info) { - bracelet = new BraceletFeatureRenderer<>(context); + accessories = new AccessoryFeatureRenderer<>(context); } @Inject(method = "render", at = @At("RETURN")) private void onRender(MatrixStack stack, VertexConsumerProvider renderContext, int lightUv, T entity, float limbDistance, float limbAngle, float tickDelta, float age, float headYaw, float headPitch, CallbackInfo info) { - bracelet.render(stack, renderContext, lightUv, entity, limbDistance, limbAngle, tickDelta, age, headYaw, headPitch); + accessories.render(stack, renderContext, lightUv, entity, limbDistance, limbAngle, tickDelta, age, headYaw, headPitch); } } diff --git a/src/main/resources/assets/unicopia/textures/models/armor/pegasus_amulet.png b/src/main/resources/assets/unicopia/textures/models/armor/pegasus_amulet.png new file mode 100644 index 00000000..39f20433 Binary files /dev/null and b/src/main/resources/assets/unicopia/textures/models/armor/pegasus_amulet.png differ