This repository has been archived on 2023-11-26. You can view files and clone it, but cannot push or open issues or pull requests.
Gestus/src/main/java/com/thebrokenrail/gestus/entity/FakePlayerEntity.java

272 lines
9.5 KiB
Java

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<FakePlayerEntity> 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<FakePlayerEntity> 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<EmotePart, EulerAngle> data = layer.next();
for (Map.Entry<EmotePart, EulerAngle> entry : data.entrySet()) {
TrackedData<EulerAngle> 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);
}
}