package net.minecraft.client.renderer.item; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.core.BlockPos; import net.minecraft.core.GlobalPos; import net.minecraft.util.Mth; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.phys.Vec3; import org.jetbrains.annotations.Nullable; @Environment(EnvType.CLIENT) public class CompassItemPropertyFunction implements ClampedItemPropertyFunction { public static final int DEFAULT_ROTATION = 0; private final CompassItemPropertyFunction.CompassWobble wobble = new CompassItemPropertyFunction.CompassWobble(); private final CompassItemPropertyFunction.CompassWobble wobbleRandom = new CompassItemPropertyFunction.CompassWobble(); public final CompassItemPropertyFunction.CompassTarget compassTarget; public CompassItemPropertyFunction(CompassItemPropertyFunction.CompassTarget compassTarget) { this.compassTarget = compassTarget; } @Override public float unclampedCall(ItemStack itemStack, @Nullable ClientLevel clientLevel, @Nullable LivingEntity livingEntity, int i) { Entity entity = (Entity)(livingEntity != null ? livingEntity : itemStack.getEntityRepresentation()); if (entity == null) { return 0.0F; } else { clientLevel = this.tryFetchLevelIfMissing(entity, clientLevel); return clientLevel == null ? 0.0F : this.getCompassRotation(itemStack, clientLevel, i, entity); } } private float getCompassRotation(ItemStack stack, ClientLevel level, int seed, Entity entity) { GlobalPos globalPos = this.compassTarget.getPos(level, stack, entity); long l = level.getGameTime(); return !this.isValidCompassTargetPos(entity, globalPos) ? this.getRandomlySpinningRotation(seed, l) : this.getRotationTowardsCompassTarget(entity, l, globalPos.pos()); } private float getRandomlySpinningRotation(int seed, long ticks) { if (this.wobbleRandom.shouldUpdate(ticks)) { this.wobbleRandom.update(ticks, Math.random()); } double d = this.wobbleRandom.rotation + this.hash(seed) / 2.1474836E9F; return Mth.positiveModulo((float)d, 1.0F); } private float getRotationTowardsCompassTarget(Entity entity, long ticks, BlockPos pos) { double d = this.getAngleFromEntityToPos(entity, pos); double e = this.getWrappedVisualRotationY(entity); double f; if (entity instanceof Player player && player.isLocalPlayer() && player.level().tickRateManager().runsNormally()) { if (this.wobble.shouldUpdate(ticks)) { this.wobble.update(ticks, 0.5 - (e - 0.25)); } f = d + this.wobble.rotation; } else { f = 0.5 - (e - 0.25 - d); } return Mth.positiveModulo((float)f, 1.0F); } @Nullable private ClientLevel tryFetchLevelIfMissing(Entity entity, @Nullable ClientLevel level) { return level == null && entity.level() instanceof ClientLevel ? (ClientLevel)entity.level() : level; } private boolean isValidCompassTargetPos(Entity entity, @Nullable GlobalPos pos) { return pos != null && pos.dimension() == entity.level().dimension() && !(pos.pos().distToCenterSqr(entity.position()) < 1.0E-5F); } private double getAngleFromEntityToPos(Entity entity, BlockPos pos) { Vec3 vec3 = Vec3.atCenterOf(pos); return Math.atan2(vec3.z() - entity.getZ(), vec3.x() - entity.getX()) / (float) (Math.PI * 2); } private double getWrappedVisualRotationY(Entity entity) { return Mth.positiveModulo((double)(entity.getVisualRotationYInDegrees() / 360.0F), 1.0); } private int hash(int value) { return value * 1327217883; } @Environment(EnvType.CLIENT) public interface CompassTarget { @Nullable GlobalPos getPos(ClientLevel clientLevel, ItemStack itemStack, Entity entity); } @Environment(EnvType.CLIENT) static class CompassWobble { double rotation; private double deltaRotation; private long lastUpdateTick; boolean shouldUpdate(long ticks) { return this.lastUpdateTick != ticks; } void update(long ticks, double rotation) { this.lastUpdateTick = ticks; double d = rotation - this.rotation; d = Mth.positiveModulo(d + 0.5, 1.0) - 0.5; this.deltaRotation += d * 0.1; this.deltaRotation *= 0.8; this.rotation = Mth.positiveModulo(this.rotation + this.deltaRotation, 1.0); } } }