package com.thebrokenrail.gestus.entity; import com.thebrokenrail.gestus.Gestus; import com.thebrokenrail.gestus.emote.EmoteLayer; import com.thebrokenrail.gestus.emote.EmotePart; import com.thebrokenrail.gestus.mixin.ArmorStandEntityAccessor; import com.thebrokenrail.gestus.util.ServerPlayerEntityExtension; import net.minecraft.entity.EntityType; import net.minecraft.entity.EquipmentSlot; import net.minecraft.entity.damage.DamageSource; import net.minecraft.entity.data.TrackedData; import net.minecraft.entity.decoration.ArmorStandEntity; import net.minecraft.item.CrossbowItem; import net.minecraft.item.ItemStack; import net.minecraft.item.Items; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.LiteralText; import net.minecraft.text.Text; import net.minecraft.util.Arm; import net.minecraft.util.Hand; import net.minecraft.util.Identifier; import net.minecraft.util.UseAction; import net.minecraft.util.math.EulerAngle; import net.minecraft.util.math.Vec3d; import net.minecraft.world.World; import java.util.Map; @SuppressWarnings("EntityConstructor") public class FakePlayerEntity extends ArmorStandEntity { private static final int STILL_LAYER = 0; private static final int WALK_LAYER = 1; private static final int HIT_LAYER = 2; private static final int CUSTOM_LAYER = 3; private static final int AIM_LAYER = 4; private static final Identifier STILL_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/still"); // Still Layer private static final Identifier WALK_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/walk"); // Walk Layer private static final Identifier SPRINT_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/sprint"); // Walk Layer private static final Identifier RIDE_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/ride"); // Walk Layer private static final Identifier HIT_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/hit"); // Hit Layer private static final Identifier AIM_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/aim"); // Aim Layer private static final Identifier TRIDENT_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/trident"); // Aim Layer private static final Identifier SHIELD_EMOTE = new Identifier(Gestus.NAMESPACE, "builtin/shield"); // Aim Layer public final ServerPlayerEntity player; private final EmoteLayer[] layers; public FakePlayerEntity(EntityType entityType, World world, Text customName, ServerPlayerEntity player) { super(entityType, world); setSilent(true); ((ArmorStandEntityAccessor) this).callSetShowArms(true); ((ArmorStandEntityAccessor) this).callSetHideBasePlate(true); setInvulnerable(true); setNoGravity(true); setCustomName(customName); setCustomNameVisible(true); noClip = true; this.player = player; layers = new EmoteLayer[5]; layers[STILL_LAYER] = new EmoteLayer(STILL_EMOTE, true); // Still Layer layers[WALK_LAYER] = new EmoteLayer(null, false); // Walk Layer layers[HIT_LAYER] = new EmoteLayer(null, false); // Hit Layer layers[CUSTOM_LAYER] = new EmoteLayer(null, false); // Custom Layer layers[AIM_LAYER] = new EmoteLayer(null, false); // Aim Layer } public FakePlayerEntity(EntityType entityType, World world) { this(entityType, world, new LiteralText("Invalid"), null); if (world.isClient()) { throw new UnsupportedOperationException(); } } @Override public void tick() { super.tick(); if (getEntityWorld().isClient()) { throw new UnsupportedOperationException(); } else if (player == null || player.removed || ((ServerPlayerEntityExtension) player).getShadow() != this) { remove(); } } private void applyPose() { for (EmoteLayer layer : layers) { applyPose(layer); } } private void applyPose(EmoteLayer layer) { Map data = layer.next(); for (Map.Entry entry : data.entrySet()) { TrackedData tracker; switch (entry.getKey()) { case RIGHT_ARM: { tracker = TRACKER_RIGHT_ARM_ROTATION; break; } case LEFT_ARM: { tracker = TRACKER_LEFT_ARM_ROTATION; break; } case RIGHT_LEG: { tracker = TRACKER_RIGHT_LEG_ROTATION; break; } case LEFT_LEG: { tracker = TRACKER_LEFT_LEG_ROTATION; break; } case BODY: { tracker = TRACKER_BODY_ROTATION; break; } default: { throw new UnsupportedOperationException(); } } getDataTracker().set(tracker, entry.getValue()); } } private static final double MINIMUM_MOVEMENT_FOR_ANIMATION = 0.02d; private void updateWalk() { if (player.hasVehicle()) { layers[WALK_LAYER].play(RIDE_EMOTE, false); } else if (!layers[WALK_LAYER].isPlaying()) { Vec3d lastPos = ((ServerPlayerEntityExtension) player).getLastPos(); Vec3d pos = player.getPos(); if (lastPos != null && pos != null && (Math.abs(pos.getX() - lastPos.getX()) >= MINIMUM_MOVEMENT_FOR_ANIMATION || Math.abs(pos.getZ() - lastPos.getZ()) >= MINIMUM_MOVEMENT_FOR_ANIMATION)) { if (player.isSprinting()) { layers[WALK_LAYER].play(SPRINT_EMOTE, false); } else { layers[WALK_LAYER].play(WALK_EMOTE, false); } } } } private boolean updateAim(Hand hand, boolean active) { ItemStack stack = player.getStackInHand(hand); Identifier emote = null; UseAction action = null; if (player.getItemUseTimeLeft() > 0 && active) { action = stack.getUseAction(); } else if (!player.handSwinging && stack.getItem() == Items.CROSSBOW && CrossbowItem.isCharged(stack)) { action = UseAction.CROSSBOW; } if (action != null) { switch (action) { case BLOCK: { emote = SHIELD_EMOTE; break; } case CROSSBOW: case BOW: { emote = AIM_EMOTE; break; } case SPEAR: { emote = TRIDENT_EMOTE; break; } } } if (emote != null) { layers[AIM_LAYER].play(emote, (hand == Hand.OFF_HAND) != isLeftHanded()); return true; } else { return false; } } private void updateAim() { Hand hand = player.getActiveHand(); if (!updateAim(hand, true)) { updateAim(hand == Hand.MAIN_HAND ? Hand.OFF_HAND : Hand.MAIN_HAND, false); } } private ItemStack head = null; private void updateHead() { if (head == null) { head = new ItemStack(Items.PLAYER_HEAD); CompoundTag tag = new CompoundTag(); tag.putString("SkullOwner", player.getGameProfile().getName()); Items.PLAYER_HEAD.postProcessTag(tag); head.setTag(tag); } } private ItemStack getFallbackItem(EquipmentSlot slot, ItemStack stack, boolean invisible) { if (stack.isEmpty() && !invisible) { switch (slot) { case HEAD: { updateHead(); return head; } case CHEST: { return new ItemStack(Items.LEATHER_CHESTPLATE); } case LEGS: { return new ItemStack(Items.LEATHER_LEGGINGS); } case FEET: { return new ItemStack(Items.LEATHER_BOOTS); } default: { return ItemStack.EMPTY; } } } else { return stack; } } private boolean isLeftHanded() { return player.getMainArm() == Arm.LEFT; } private void updateEquipment() { for (EquipmentSlot slot : EquipmentSlot.values()) { ItemStack target = getFallbackItem(slot, player.getEquippedStack(slot).copy(), isInvisible()); if (!getEquippedStack(slot).isItemEqual(target)) { equipStack(slot, target); } } if (isLeftHanded()) { ItemStack main = getEquippedStack(EquipmentSlot.MAINHAND); ItemStack off = getEquippedStack(EquipmentSlot.OFFHAND); equipStack(EquipmentSlot.OFFHAND, main); equipStack(EquipmentSlot.MAINHAND, off); } } public void update() { updateEquipment(); updateWalk(); updateAim(); applyPose(); } public void playCustomEmote(Identifier id) { layers[CUSTOM_LAYER].play(id, isLeftHanded()); } public void hit(Hand hand) { layers[HIT_LAYER].play(HIT_EMOTE, (hand == Hand.OFF_HAND) != isLeftHanded()); } @Override public boolean damage(DamageSource source, float amount) { return player.damage(source, amount); } }