package net.minecraft.client.renderer.item.properties.numeric; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.renderer.item.properties.numeric.NeedleDirectionHelper.Wobbler; import net.minecraft.core.BlockPos; import net.minecraft.core.GlobalPos; import net.minecraft.util.Mth; import net.minecraft.util.RandomSource; import net.minecraft.util.StringRepresentable; import net.minecraft.world.entity.Entity; 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 CompassAngleState extends NeedleDirectionHelper { public static final MapCodec MAP_CODEC = RecordCodecBuilder.mapCodec( instance -> instance.group( Codec.BOOL.optionalFieldOf("wobble", true).forGetter(NeedleDirectionHelper::wobble), CompassAngleState.CompassTarget.CODEC.fieldOf("target").forGetter(CompassAngleState::target) ) .apply(instance, CompassAngleState::new) ); private final Wobbler wobbler; private final Wobbler noTargetWobbler; private final CompassAngleState.CompassTarget compassTarget; private final RandomSource random = RandomSource.create(); public CompassAngleState(boolean wobble, CompassAngleState.CompassTarget compassTarget) { super(wobble); this.wobbler = this.newWobbler(0.8F); this.noTargetWobbler = this.newWobbler(0.8F); this.compassTarget = compassTarget; } @Override protected float calculate(ItemStack stack, ClientLevel level, int seed, Entity entity) { GlobalPos globalPos = this.compassTarget.get(level, stack, entity); long l = level.getGameTime(); return !isValidCompassTargetPos(entity, globalPos) ? this.getRandomlySpinningRotation(seed, l) : this.getRotationTowardsCompassTarget(entity, l, globalPos.pos()); } private float getRandomlySpinningRotation(int seed, long gameTime) { if (this.noTargetWobbler.shouldUpdate(gameTime)) { this.noTargetWobbler.update(gameTime, this.random.nextFloat()); } float f = this.noTargetWobbler.rotation() + hash(seed) / 2.1474836E9F; return Mth.positiveModulo(f, 1.0F); } private float getRotationTowardsCompassTarget(Entity entity, long gameTime, BlockPos targetPos) { float f = (float)getAngleFromEntityToPos(entity, targetPos); float g = getWrappedVisualRotationY(entity); float h; if (entity instanceof Player player && player.isLocalPlayer() && player.level().tickRateManager().runsNormally()) { if (this.wobbler.shouldUpdate(gameTime)) { this.wobbler.update(gameTime, 0.5F - (g - 0.25F)); } h = f + this.wobbler.rotation(); } else { h = 0.5F - (g - 0.25F - f); } return Mth.positiveModulo(h, 1.0F); } private static boolean isValidCompassTargetPos(Entity entity, @Nullable GlobalPos pos) { return pos != null && pos.dimension() == entity.level().dimension() && !(pos.pos().distToCenterSqr(entity.position()) < 1.0E-5F); } private static 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 static float getWrappedVisualRotationY(Entity entity) { return Mth.positiveModulo(entity.getVisualRotationYInDegrees() / 360.0F, 1.0F); } private static int hash(int seed) { return seed * 1327217883; } protected CompassAngleState.CompassTarget target() { return this.compassTarget; } @Environment(EnvType.CLIENT) public static enum CompassTarget implements StringRepresentable { NONE("NONE", 0, "none"), LODESTONE("LODESTONE", 1, "lodestone"), SPAWN("SPAWN", 2, "spawn"), RECOVERY("RECOVERY", 3, "recovery"); public static final Codec CODEC = StringRepresentable.fromEnum(CompassAngleState.CompassTarget::values); private final String name; CompassTarget(final String name) { this.name = name; } @Override public String getSerializedName() { return this.name; } @Nullable abstract GlobalPos get(ClientLevel level, ItemStack stack, Entity entity); } }