diff --git a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java index 8f444cf2..f6d7ba7c 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/EarthPonyKickAbility.java @@ -10,6 +10,7 @@ import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.ability.data.Hit; import com.minelittlepony.unicopia.ability.data.Pos; import com.minelittlepony.unicopia.ability.data.tree.TreeType; +import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.util.PosHelper; import com.minelittlepony.unicopia.util.RayTraceHelper; @@ -89,6 +90,8 @@ public class EarthPonyKickAbility implements Ability { return; } + iplayer.setAnimation(Animation.KICK, 30); + BlockPos pos = data.pos(); BlockDestructionManager destr = ((BlockDestructionManager.Source)player.world).getDestructionManager(); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java index 35069cec..078b4540 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornCastingAbility.java @@ -9,6 +9,7 @@ import com.minelittlepony.unicopia.ability.data.Hit; import com.minelittlepony.unicopia.ability.magic.spell.HomingSpell; import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; +import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.item.AmuletItem; import com.minelittlepony.unicopia.particle.MagicParticleEffect; @@ -106,11 +107,14 @@ public class UnicornCastingAbility implements Ability { player.spawnParticles(ParticleTypes.LARGE_SMOKE, 6); player.playSound(SoundEvents.ENTITY_ITEM_BREAK, 1, 0.5F); } else { + player.setAnimation(Animation.ARMS_UP, 5); if (s instanceof HomingSpell) { RayTraceHelper.doTrace(player.getMaster(), 600, 1, EntityPredicates.CAN_COLLIDE).getEntity().ifPresent(((HomingSpell)s)::setTarget); } player.playSound(SoundEvents.BLOCK_BEACON_POWER_SELECT, 0.05F, 2.2F); } + } else { + player.setAnimation(Animation.WOLOLO, 20); } } } diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java index 31c933d1..38047026 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornDispellAbility.java @@ -7,6 +7,7 @@ import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.ability.data.Pos; import com.minelittlepony.unicopia.ability.magic.Caster; +import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.util.RayTraceHelper; @@ -43,6 +44,7 @@ public class UnicornDispellAbility implements Ability { public boolean onQuickAction(Pony player, ActivationType type) { if (type.getTapCount() > 1) { + player.setAnimation(Animation.WOLOLO, 10); if (player.getSpellSlot().clear()) { player.getMaster().sendMessage(new TranslatableText("gui.unicopia.action.spells_cleared"), true); } else { @@ -73,6 +75,7 @@ public class UnicornDispellAbility implements Ability { @Override public void apply(Pony player, Pos data) { + player.setAnimation(Animation.WOLOLO, 30); Caster.stream(VecHelper.findInRange(player.getEntity(), player.getWorld(), data.vec(), 2, EquinePredicates.IS_PLACED_SPELL).stream()).forEach(target -> { target.getSpellSlot().clear(); }); diff --git a/src/main/java/com/minelittlepony/unicopia/ability/UnicornProjectileAbility.java b/src/main/java/com/minelittlepony/unicopia/ability/UnicornProjectileAbility.java index feee3a0c..d2a7b696 100644 --- a/src/main/java/com/minelittlepony/unicopia/ability/UnicornProjectileAbility.java +++ b/src/main/java/com/minelittlepony/unicopia/ability/UnicornProjectileAbility.java @@ -5,6 +5,7 @@ import com.minelittlepony.unicopia.ability.data.Hit; import com.minelittlepony.unicopia.ability.magic.spell.HomingSpell; import com.minelittlepony.unicopia.ability.magic.spell.Spell; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; +import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.entity.player.Pony; import com.minelittlepony.unicopia.particle.MagicParticleEffect; import com.minelittlepony.unicopia.util.RayTraceHelper; @@ -69,6 +70,7 @@ public class UnicornProjectileAbility implements Ability { Spell spell = thrown.getValue().create(); spell.toThrowable().throwProjectile(player).ifPresent(projectile -> { + player.setAnimation(Animation.ARMS_FORWARD, 5); if (spell instanceof HomingSpell) { RayTraceHelper.doTrace(player.getMaster(), 600, 1, EntityPredicates.CAN_COLLIDE).getEntity().filter(((HomingSpell)spell)::setTarget).ifPresent(target -> { projectile.setHomingTarget(target); diff --git a/src/main/java/com/minelittlepony/unicopia/client/render/PlayerPoser.java b/src/main/java/com/minelittlepony/unicopia/client/render/PlayerPoser.java new file mode 100644 index 00000000..a1dc1aaf --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/render/PlayerPoser.java @@ -0,0 +1,91 @@ +package com.minelittlepony.unicopia.client.render; + +import com.minelittlepony.unicopia.entity.player.Pony; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.util.Arm; +import net.minecraft.util.math.MathHelper; + +public class PlayerPoser { + public static final PlayerPoser INSTANCE = new PlayerPoser(); + + public void applyPosing(PlayerEntity entity, BipedEntityModel model) { + Pony pony = Pony.of(entity); + float progress = pony.getAnimationProgress(MinecraftClient.getInstance().getTickDelta()); + + switch (pony.getAnimation()) { + case WOLOLO: { + float roll = MathHelper.sin(entity.age / 10F); + float yaw = MathHelper.cos(entity.age / 10F); + + model.leftArm.pitch += -1; + model.rightArm.pitch += -1; + + model.leftArm.roll = -roll; + model.rightArm.roll = roll; + + model.leftArm.yaw = yaw; + model.rightArm.yaw = yaw; + break; + } + case ARMS_FORWARD: { + float roll = (progress + 1) / 2F; + + float pitch = 1.5F * roll; + float yaw = 0.5F * roll; + + model.leftArm.pitch -= pitch; + model.rightArm.pitch -= pitch; + + model.leftArm.roll = 0; + model.rightArm.roll = 0; + + model.leftArm.yaw = yaw; + model.rightArm.yaw = -yaw; + break; + } + case ARMS_UP: { + float roll = (progress + 1) / 2F; + + float pitch = 3F * roll; + float yaw = 0.5F * roll; + + model.leftArm.pitch -= pitch; + model.rightArm.pitch -= pitch; + + model.leftArm.roll = 0; + model.rightArm.roll = 0; + + model.leftArm.yaw = yaw; + model.rightArm.yaw = -yaw; + break; + } + case KICK: { + float roll = (progress + 1) / 2F; + + model.rightArm.pitch += roll / 5F; + model.leftArm.roll -= roll / 5F; + model.rightArm.roll += roll / 5F; + + if (entity.getMainArm() == Arm.LEFT) { + model.rightLeg.pitch = -roll * 1.5F; + model.rightLeg.roll = roll / 10F; + } else { + model.leftLeg.pitch = -roll * 1.5F; + model.leftLeg.roll = -roll / 10F; + } + } + default: + } + } + + public enum Animation { + NONE, + WOLOLO, + ARMS_FORWARD, + ARMS_UP, + KICK + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java index 46724e97..0e974ad8 100644 --- a/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java +++ b/src/main/java/com/minelittlepony/unicopia/entity/player/Pony.java @@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable; import com.minelittlepony.unicopia.Affinity; import com.minelittlepony.unicopia.client.UnicopiaClient; +import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; import com.minelittlepony.unicopia.InteractionManager; import com.minelittlepony.unicopia.Race; import com.minelittlepony.unicopia.UTags; @@ -30,6 +31,7 @@ import com.minelittlepony.unicopia.item.UItems; import com.minelittlepony.unicopia.item.toxin.Toxin; import com.minelittlepony.unicopia.network.Channel; import com.minelittlepony.unicopia.network.MsgOtherPlayerCapabilities; +import com.minelittlepony.unicopia.network.MsgPlayerAnimationChange; import com.minelittlepony.unicopia.network.MsgRequestSpeciesChange; import com.minelittlepony.unicopia.network.datasync.Transmittable; import com.minelittlepony.unicopia.network.datasync.EffectSync.UpdateCallback; @@ -58,6 +60,7 @@ import net.minecraft.nbt.NbtCompound; import net.minecraft.network.packet.s2c.play.EntityPassengersSetS2CPacket; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.server.world.ServerWorld; +import net.minecraft.sound.SoundEvents; import net.minecraft.text.Text; import net.minecraft.text.TranslatableText; import net.minecraft.util.Hand; @@ -106,6 +109,10 @@ public class Pony extends Living implements Transmittable, Copieab private int ticksInSun; private boolean hasShades; + private Animation animation = Animation.NONE; + private int animationMaxDuration; + private int animationDuration; + public Pony(PlayerEntity player) { super(player, EFFECT); this.mana = new ManaContainer(this); @@ -121,6 +128,33 @@ public class Pony extends Living implements Transmittable, Copieab builder.add(PlayerAttributes.ENTITY_GRAVTY_MODIFIER); } + public void setAnimation(Animation animation, int duration) { + if (animation != this.animation && duration != animationDuration) { + this.animation = animation; + this.animationDuration = animation == Animation.NONE ? 0 : Math.max(0, duration); + this.animationMaxDuration = animationDuration; + + if (!isClient()) { + Channel.SERVER_PLAYER_ANIMATION_CHANGE.send(getWorld(), new MsgPlayerAnimationChange(this, animation, animationDuration)); + } + + if (animation == Animation.WOLOLO) { + playSound(SoundEvents.ENTITY_EVOKER_PREPARE_WOLOLO, 0.9F, 1); + } + } + } + + public Animation getAnimation() { + return animation; + } + + public float getAnimationProgress(float delta) { + if (animation == Animation.NONE) { + return 0; + } + return 1 - ((animationDuration + delta) / animationMaxDuration); + } + @Override public Race getSpecies() { if (getMaster() == null) { @@ -288,6 +322,11 @@ public class Pony extends Living implements Transmittable, Copieab @Override public void tick() { + if (animationDuration >= 0) { + if (--animationDuration <= 0) { + setAnimation(Animation.NONE, 0); + } + } if (isHanging()) { if (ticksHanging++ > 40) { diff --git a/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinLivingEntityRenderer.java b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinLivingEntityRenderer.java new file mode 100644 index 00000000..ae6f31d7 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/mixin/client/MixinLivingEntityRenderer.java @@ -0,0 +1,49 @@ +package com.minelittlepony.unicopia.mixin.client; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.At.Shift; +import org.spongepowered.asm.mixin.injection.Desc; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import com.minelittlepony.unicopia.client.render.PlayerPoser; + +import net.minecraft.client.render.VertexConsumerProvider; +import net.minecraft.client.render.entity.EntityRenderer; +import net.minecraft.client.render.entity.LivingEntityRenderer; +import net.minecraft.client.render.entity.feature.FeatureRendererContext; +import net.minecraft.client.render.entity.model.BipedEntityModel; +import net.minecraft.client.render.entity.model.EntityModel; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.entity.Entity; +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.player.PlayerEntity; + +@Mixin(LivingEntityRenderer.class) +abstract class MixinLivingEntityRenderer> extends EntityRenderer implements FeatureRendererContext { + MixinLivingEntityRenderer() { super(null); } + + @Inject(method = "render", + at = @At( + value = "INVOKE", + desc = @Desc( + value = "setAngles", + owner = EntityModel.class, + args = { Entity.class, float.class, float.class, float.class, float.class, float.class } + ), + shift = Shift.AFTER + ) + ) + private void onRender( + T entity, + float f, float g, + MatrixStack matrixStack, + VertexConsumerProvider vertexConsumerProvider, + int i, + CallbackInfo into) { + if (entity instanceof PlayerEntity player) { + PlayerPoser.INSTANCE.applyPosing(player, (BipedEntityModel)getModel()); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/network/Channel.java b/src/main/java/com/minelittlepony/unicopia/network/Channel.java index 82d68a35..9032ffbb 100644 --- a/src/main/java/com/minelittlepony/unicopia/network/Channel.java +++ b/src/main/java/com/minelittlepony/unicopia/network/Channel.java @@ -25,6 +25,7 @@ public interface Channel { S2CPacketType SERVER_SELECT_TRIBE = SimpleNetworking.serverToClient(SERVER_SELECT_TRIBE_ID, MsgTribeSelect::new); S2CBroadcastPacketType SERVER_OTHER_PLAYER_CAPABILITIES = SimpleNetworking.serverToClients(new Identifier("unicopia", "other_player_capabilities"), MsgOtherPlayerCapabilities::new); + S2CBroadcastPacketType SERVER_PLAYER_ANIMATION_CHANGE = SimpleNetworking.serverToClients(new Identifier("unicopia", "other_player_animation_change"), MsgPlayerAnimationChange::new); static void bootstrap() { ServerPlayConnectionEvents.JOIN.register((handler, sender, server) -> { diff --git a/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerAnimationChange.java b/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerAnimationChange.java new file mode 100644 index 00000000..8c7c3cf8 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/network/MsgPlayerAnimationChange.java @@ -0,0 +1,49 @@ +package com.minelittlepony.unicopia.network; + +import java.util.UUID; + +import com.minelittlepony.unicopia.client.render.PlayerPoser.Animation; +import com.minelittlepony.unicopia.entity.player.Pony; +import com.minelittlepony.unicopia.util.network.Packet; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.network.PacketByteBuf; + +/** + * Sent to the client when a player's animation changes. + */ +public class MsgPlayerAnimationChange implements Packet { + private final UUID playerId; + private final Animation animation; + private final int duration; + + MsgPlayerAnimationChange(PacketByteBuf buffer) { + playerId = buffer.readUuid(); + animation = Animation.values()[buffer.readInt()]; + duration = buffer.readInt(); + } + + public MsgPlayerAnimationChange(Pony player, Animation animation, int duration) { + this.playerId = player.getEntity().getUuid(); + this.animation = animation; + this.duration = duration; + } + + @Override + public void toBuffer(PacketByteBuf buffer) { + buffer.writeUuid(playerId); + buffer.writeInt(animation.ordinal()); + buffer.writeInt(duration); + } + + @Override + public void handle(PlayerEntity sender) { + Pony player = Pony.of(MinecraftClient.getInstance().world.getPlayerByUuid(playerId)); + if (player == null) { + return; + } + + player.setAnimation(animation, duration); + } +} diff --git a/src/main/resources/unicopia.mixin.json b/src/main/resources/unicopia.mixin.json index d143b93d..3c1c6751 100644 --- a/src/main/resources/unicopia.mixin.json +++ b/src/main/resources/unicopia.mixin.json @@ -42,6 +42,7 @@ "client.MixinItemModels", "client.MixinKeyboardInput", "client.MixinLightmapTextureManager", + "client.MixinLivingEntityRenderer", "client.MixinMouse", "client.MixinPlayerEntityRenderer", "client.MixinTooltipComponent",