package com.minelittlepony.client; import net.minecraft.client.MinecraftClient; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.util.hit.HitResult; import net.minecraft.util.math.*; import com.minelittlepony.api.pony.IPony; import com.minelittlepony.common.util.settings.Setting; public class HorseCam { private static float lastOriginalPitch; private static float lastComputedPitch; /** * Restores the previous camera (unadjusted) angle for the client when the server sends an update. * This is to prevent issues caused by the server updating our pitch whenever the player leaves a portal. */ public static float transformIncomingServerCameraAngle(float serverPitch) { if (MathHelper.approximatelyEquals(serverPitch, lastComputedPitch)) { return lastOriginalPitch; } return serverPitch; } /** * Transforms the client pony's pitch to the corresponding angle for a human character. */ public static float transformCameraAngle(float pitch) { if (!MineLittlePony.getInstance().getConfig().fillycam.get()) { return pitch; } if (pitch != 0) { lastOriginalPitch = pitch; lastComputedPitch = pitch; } PlayerEntity player = MinecraftClient.getInstance().player; // noop // Only run when the player has an item in their hands. Can't check for buckets specifically since mods exist. if (player.getMainHandStack().isEmpty() && player.getOffHandStack().isEmpty()) { return pitch; } IPony pony = MineLittlePony.getInstance().getManager().getPony(player); if (!pony.getRace().isHuman()) { Setting<Boolean> fillyCam = MineLittlePony.getInstance().getConfig().fillycam; fillyCam.set(false); final float vanillaHeight = player.getEyeHeight(player.getPose()); fillyCam.set(true); final float alteredHeight = player.getEyeHeight(player.getPose()); // only change the angle if required if (!MathHelper.approximatelyEquals(vanillaHeight, alteredHeight)) { pitch = rescaleCameraPitch(vanillaHeight, pitch); } //float factor = pony.getMetadata().getSize().getEyeHeightFactor(); //pitch = rescaleCameraPitch(player.getStandingEyeHeight() / factor, pitch); } if (lastOriginalPitch != 0) { lastComputedPitch = pitch; } return pitch; } /** * Calculates a corresponding camera pitch for the current player at * the specified character height. * * @param toHeight Target height. * @param originalPitch Original, unchanged pitch. * * @return The new pitch value, otherwise the original value passed in. */ public static float rescaleCameraPitch(double toHeight, float originalPitch) { /* -90 * | * ---------------0 * | * 90 */ /* A A - toHeight * |\ B - fromHeight * |?\ y - headPitch * | \ ? - result * | \ C - raytrace * B- \ * |y - \ * | - \ Tan(?) = horDist / toHeight *==|-------C=== ? = arcTan(horDist / toHeight); * horDist * * horDist * |-------C * | /. * | /. * | / . * | / . * | / . * |?/ . * A/ . * | . * | . * | . * | . * B * | * | *==o=========== */ MinecraftClient client = MinecraftClient.getInstance(); PlayerEntity player = client.player; client.gameRenderer.updateTargetedEntity(client.getTickDelta()); HitResult hit = client.crosshairTarget; if (client.targetedEntity != null) { return originalPitch; } // noop // Ignore misses, helps with bows, arrows, and projectiles if (hit == null || hit.getType() != HitResult.Type.BLOCK || player == null) { return originalPitch; } Vec3d hitPos = hit.getPos(); Vec3d pos = player.getPos(); double diffX = hitPos.x - pos.x; double diffZ = hitPos.z - pos.z; double horDist = Math.sqrt(diffX * diffX + diffZ * diffZ); double toEyePos = pos.y + toHeight; double verDist = Math.abs(hitPos.y - toEyePos); double theta = Math.atan(horDist / verDist); // convert to degress theta /= Math.PI / 180D; // convert to vertical pitch (-90 to 90). // Preserve up/down direction. double newPitch = Math.abs(90 - theta) * Math.signum(originalPitch); return (float)newPitch; } }