refactor(aim): split RotationManager to 199 lines
State machine, mutable state and server-rotation packet tracking move to rotation/internal/; the singleton, event wiring and public API stay put.
This commit is contained in:
parent
7e8b6b6467
commit
3e57542bad
4 changed files with 329 additions and 228 deletions
|
|
@ -1,57 +1,39 @@
|
||||||
package dev.loki.lovisual.util.aiming.rotation;
|
package dev.loki.lovisual.util.aiming.rotation;
|
||||||
import dev.loki.lovisual.util.aiming.RequestHandler;
|
|
||||||
|
|
||||||
|
|
||||||
import dev.loki.lovisual.util.screen.ClientScreen;
|
|
||||||
import net.minecraft.client.Minecraft;
|
|
||||||
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
|
||||||
import net.minecraft.client.player.LocalPlayer;
|
|
||||||
import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
|
|
||||||
import net.minecraft.network.protocol.game.ServerboundUseItemPacket;
|
|
||||||
import net.minecraft.util.Mth;
|
|
||||||
import net.minecraft.world.entity.Entity;
|
|
||||||
import net.minecraft.world.phys.Vec3;
|
|
||||||
import dev.loki.lovisual.events.meta.EventHandler;
|
|
||||||
import dev.loki.lovisual.events.Events;
|
import dev.loki.lovisual.events.Events;
|
||||||
|
import dev.loki.lovisual.events.impl.input.RotationUpdateEvent;
|
||||||
import dev.loki.lovisual.events.impl.network.EventSync;
|
import dev.loki.lovisual.events.impl.network.EventSync;
|
||||||
import dev.loki.lovisual.events.impl.network.PacketEvent;
|
import dev.loki.lovisual.events.impl.network.PacketEvent;
|
||||||
import dev.loki.lovisual.events.impl.world.PlayerVelocityStrafe;
|
import dev.loki.lovisual.events.impl.world.PlayerVelocityStrafe;
|
||||||
import dev.loki.lovisual.events.impl.input.RotationUpdateEvent;
|
import dev.loki.lovisual.events.meta.EventHandler;
|
||||||
import dev.loki.lovisual.mixininterface.entity.ILocalPlayer;
|
import dev.loki.lovisual.mixininterface.entity.ILocalPlayer;
|
||||||
import dev.loki.lovisual.mixins.accessors.entity.EntityInvoker;
|
import dev.loki.lovisual.mixins.accessors.entity.EntityInvoker;
|
||||||
import dev.loki.lovisual.util.aiming.data.Rotation;
|
import dev.loki.lovisual.util.aiming.data.Rotation;
|
||||||
import dev.loki.lovisual.util.aiming.features.MovementCorrection;
|
import dev.loki.lovisual.util.aiming.features.MovementCorrection;
|
||||||
|
import dev.loki.lovisual.util.aiming.rotation.internal.RotationPackets;
|
||||||
|
import dev.loki.lovisual.util.aiming.rotation.internal.RotationState;
|
||||||
|
import dev.loki.lovisual.util.aiming.rotation.internal.RotationStepper;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.player.LocalPlayer;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public rotation facade: singleton entry point, event wiring and accessors.
|
||||||
|
* The state machine lives in {@link RotationStepper}, state in
|
||||||
|
* {@link RotationState} and server-rotation tracking in {@link RotationPackets}.
|
||||||
|
*/
|
||||||
public final class RotationManager {
|
public final class RotationManager {
|
||||||
|
|
||||||
public static final RotationManager INSTANCE = new RotationManager();
|
public static final RotationManager INSTANCE = new RotationManager();
|
||||||
|
|
||||||
private final RequestHandler<RotationTarget> rotationTargetHandler = new RequestHandler<>();
|
private final RotationState state = new RotationState();
|
||||||
private RotationTarget previousRotationTarget;
|
private final RotationPackets packets = new RotationPackets();
|
||||||
private Object previousRotationProvider;
|
|
||||||
|
|
||||||
private Rotation currentRotation;
|
|
||||||
private Rotation previousRotation;
|
|
||||||
private Object currentRotationProvider;
|
|
||||||
private boolean smoothReturnActive;
|
|
||||||
private int smoothReturnTicks;
|
|
||||||
|
|
||||||
private Rotation actualServerRotation = Rotation.ZERO;
|
|
||||||
private Rotation theoreticalServerRotation = Rotation.ZERO;
|
|
||||||
private int lastLifecycleAge = Integer.MIN_VALUE;
|
private int lastLifecycleAge = Integer.MIN_VALUE;
|
||||||
|
|
||||||
private RotationManager() {
|
private RotationManager() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static float computeRotationDifference(Rotation a, Rotation b) {
|
|
||||||
if (a == null || b == null) {
|
|
||||||
return Float.MAX_VALUE;
|
|
||||||
}
|
|
||||||
float yawDiff = Math.abs(RotationUtil.angleDifference(a.yaw(), b.yaw()));
|
|
||||||
float pitchDiff = Math.abs(a.pitch() - b.pitch());
|
|
||||||
return (float) Math.hypot(yawDiff, pitchDiff);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static LocalPlayer player() {
|
public static LocalPlayer player() {
|
||||||
return Minecraft.getInstance().player;
|
return Minecraft.getInstance().player;
|
||||||
}
|
}
|
||||||
|
|
@ -85,78 +67,48 @@ public final class RotationManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public RotationTarget getActiveRotationTarget() {
|
public RotationTarget getActiveRotationTarget() {
|
||||||
RotationTarget active = rotationTargetHandler.getActiveRequestValue();
|
return state.activeTarget();
|
||||||
return active != null ? active : previousRotationTarget;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public RotationTarget getPreviousRotationTarget() {
|
public RotationTarget getPreviousRotationTarget() {
|
||||||
return previousRotationTarget;
|
return state.previousTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rotation getCurrentRotation() {
|
public Rotation getCurrentRotation() {
|
||||||
return currentRotation;
|
return state.current();
|
||||||
}
|
|
||||||
|
|
||||||
private void setCurrentRotation(Rotation rotation) {
|
|
||||||
setCurrentRotation(rotation, currentRotationProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setCurrentRotation(Rotation rotation, Object provider) {
|
|
||||||
if (rotation == null) {
|
|
||||||
if (currentRotation != null) {
|
|
||||||
previousRotation = currentRotation;
|
|
||||||
}
|
|
||||||
currentRotation = null;
|
|
||||||
currentRotationProvider = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
previousRotation = currentRotation;
|
|
||||||
currentRotation = rotation;
|
|
||||||
currentRotationProvider = provider;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rotation getPreviousRotation() {
|
public Rotation getPreviousRotation() {
|
||||||
return previousRotation;
|
return state.previous();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rotation getServerRotation() {
|
public Rotation getServerRotation() {
|
||||||
return actualServerRotation;
|
return packets.actual();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rotation getMovementRotation() {
|
public Rotation getMovementRotation() {
|
||||||
LocalPlayer player = player();
|
LocalPlayer player = player();
|
||||||
|
Rotation current = state.current();
|
||||||
Rotation fallback = player != null
|
Rotation fallback = player != null
|
||||||
? new Rotation(player.getYRot(), player.getXRot(), true)
|
? new Rotation(player.getYRot(), player.getXRot(), true)
|
||||||
: (currentRotation != null ? currentRotation : Rotation.ZERO);
|
: (current != null ? current : Rotation.ZERO);
|
||||||
|
|
||||||
RotationTarget active = getActiveRotationTarget();
|
RotationTarget active = getActiveRotationTarget();
|
||||||
if (currentRotation == null || active == null || active.movementCorrection == MovementCorrection.OFF) {
|
if (current == null || active == null || active.movementCorrection == MovementCorrection.OFF) {
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentRotation;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRotationTarget(RotationTarget plan, int priority, Object provider) {
|
public void setRotationTarget(RotationTarget plan, int priority, Object provider) {
|
||||||
if (plan == null) return;
|
state.request(plan, priority, provider);
|
||||||
|
|
||||||
rotationTargetHandler.request(new RequestHandler.Request<>(plan.ticksUntilReset, priority, provider, plan));
|
|
||||||
smoothReturnActive = false;
|
|
||||||
smoothReturnTicks = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRotationTarget(RotationTarget plan, int priority) {
|
public void setRotationTarget(RotationTarget plan, int priority) {
|
||||||
setRotationTarget(plan, priority, null);
|
setRotationTarget(plan, priority, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isRotatingAllowed(RotationTarget target) {
|
|
||||||
if (!target.considerInventory) return true;
|
|
||||||
Minecraft mc = Minecraft.getInstance();
|
|
||||||
if (mc == null) return true;
|
|
||||||
return !(ClientScreen.current() instanceof AbstractContainerScreen<?>);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update current rotation to a new rotation step.
|
* Update current rotation to a new rotation step.
|
||||||
*/
|
*/
|
||||||
|
|
@ -167,124 +119,15 @@ public final class RotationManager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Rotation playerRotation = new Rotation(player.getYRot(), player.getXRot(), true);
|
RotationStepper.step(state, player, packets.actual());
|
||||||
RequestHandler.Request<RotationTarget> activeRequest = rotationTargetHandler.getActiveRequest();
|
|
||||||
RotationTarget activeRotationTarget = activeRequest != null ? activeRequest.value() : previousRotationTarget;
|
|
||||||
Object activeProvider = activeRequest != null ? activeRequest.provider() : previousRotationProvider;
|
|
||||||
|
|
||||||
if (activeRotationTarget == null) {
|
|
||||||
if (currentRotation == null || !smoothReturnActive) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float diffToPlayer = computeRotationDifference(currentRotation, playerRotation);
|
|
||||||
if (diffToPlayer <= 0.5f) {
|
|
||||||
finishSmoothReturn();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float speed = 0.25f + 0.4f * Math.min(1.0f, diffToPlayer / 30.0f);
|
|
||||||
float yawDiff = RotationUtil.angleDifference(playerRotation.yaw(), currentRotation.yaw());
|
|
||||||
float newYaw = currentRotation.yaw() + yawDiff * speed;
|
|
||||||
float newPitch = Mth.lerp(speed, currentRotation.pitch(), playerRotation.pitch());
|
|
||||||
setCurrentRotation(new Rotation(newYaw, newPitch, false).normalize(), currentRotationProvider);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRotatingAllowed(activeRotationTarget)) {
|
|
||||||
Rotation fromRotation = resolveBaseRotation(playerRotation);
|
|
||||||
if (activeRequest == null) {
|
|
||||||
if (currentRotation == null) {
|
|
||||||
finishSmoothReturn();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!smoothReturnActive) {
|
|
||||||
smoothReturnActive = true;
|
|
||||||
smoothReturnTicks = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rotation resetRotation = activeRotationTarget.towards(fromRotation, true).normalize();
|
|
||||||
setCurrentRotation(resetRotation, activeProvider);
|
|
||||||
previousRotationTarget = activeRotationTarget;
|
|
||||||
previousRotationProvider = activeProvider;
|
|
||||||
|
|
||||||
smoothReturnTicks++;
|
|
||||||
float diffToPlayer = computeRotationDifference(resetRotation, playerRotation);
|
|
||||||
int maxTicks = Math.max(1, activeRotationTarget.ticksUntilReset);
|
|
||||||
float threshold = Math.max(0.05f, activeRotationTarget.resetThreshold);
|
|
||||||
if (diffToPlayer <= threshold || smoothReturnTicks >= maxTicks) {
|
|
||||||
finishSmoothReturn();
|
|
||||||
rotationTargetHandler.tick();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Rotation rotation = activeRotationTarget.towards(fromRotation, false).normalize();
|
|
||||||
setCurrentRotation(rotation, activeProvider);
|
|
||||||
previousRotationTarget = activeRotationTarget;
|
|
||||||
previousRotationProvider = activeProvider;
|
|
||||||
smoothReturnActive = false;
|
|
||||||
smoothReturnTicks = 0;
|
|
||||||
|
|
||||||
if (activeRotationTarget.whenReached != null) {
|
|
||||||
activeRotationTarget.whenReached.invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rotationTargetHandler.tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void finishSmoothReturn() {
|
|
||||||
setCurrentRotation(null);
|
|
||||||
previousRotation = null;
|
|
||||||
previousRotationTarget = null;
|
|
||||||
previousRotationProvider = null;
|
|
||||||
smoothReturnActive = false;
|
|
||||||
smoothReturnTicks = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Rotation resolveBaseRotation(Rotation playerRotation) {
|
|
||||||
if (currentRotation != null) {
|
|
||||||
return currentRotation;
|
|
||||||
}
|
|
||||||
if (actualServerRotation != null && actualServerRotation != Rotation.ZERO) {
|
|
||||||
return actualServerRotation;
|
|
||||||
}
|
|
||||||
return playerRotation;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
rotationTargetHandler.clear();
|
state.clearAll();
|
||||||
previousRotationTarget = null;
|
|
||||||
previousRotationProvider = null;
|
|
||||||
currentRotation = null;
|
|
||||||
currentRotationProvider = null;
|
|
||||||
previousRotation = null;
|
|
||||||
smoothReturnActive = false;
|
|
||||||
smoothReturnTicks = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear(Object provider) {
|
public void clear(Object provider) {
|
||||||
if (provider == null) return;
|
state.clearProvider(provider);
|
||||||
boolean removed = rotationTargetHandler.clear(provider);
|
|
||||||
boolean ownsVisualState = provider == currentRotationProvider || provider == previousRotationProvider;
|
|
||||||
if (!removed && !ownsVisualState) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentRotation != null && ownsVisualState) {
|
|
||||||
smoothReturnActive = true;
|
|
||||||
smoothReturnTicks = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ownsVisualState) {
|
|
||||||
previousRotationTarget = null;
|
|
||||||
previousRotationProvider = null;
|
|
||||||
previousRotation = null;
|
|
||||||
}
|
|
||||||
smoothReturnActive = false;
|
|
||||||
smoothReturnTicks = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -296,33 +139,16 @@ public final class RotationManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void release(Object provider, boolean smoothReturn) {
|
public void release(Object provider, boolean smoothReturn) {
|
||||||
if (provider == null) return;
|
state.releaseProvider(provider, smoothReturn);
|
||||||
boolean removed = rotationTargetHandler.clear(provider);
|
|
||||||
boolean ownsVisualState = provider == currentRotationProvider || provider == previousRotationProvider;
|
|
||||||
if (!removed && !ownsVisualState) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!smoothReturn) {
|
|
||||||
if (ownsVisualState) {
|
|
||||||
setCurrentRotation(null);
|
|
||||||
previousRotationTarget = null;
|
|
||||||
previousRotationProvider = null;
|
|
||||||
previousRotation = null;
|
|
||||||
}
|
|
||||||
smoothReturnActive = false;
|
|
||||||
smoothReturnTicks = 0;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
smoothReturnActive = ownsVisualState && currentRotation != null;
|
|
||||||
smoothReturnTicks = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onSync(EventSync e) {
|
public void onSync(EventSync e) {
|
||||||
RotationTarget activeRotationTarget = getActiveRotationTarget();
|
RotationTarget active = state.activeTarget();
|
||||||
if (activeRotationTarget == null || currentRotation == null) return;
|
Rotation current = state.current();
|
||||||
|
if (active == null || current == null) return;
|
||||||
|
|
||||||
e.setRotation(currentRotation.yaw(), currentRotation.pitch(), true);
|
e.setRotation(current.yaw(), current.pitch(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = 100)
|
@EventHandler(priority = 100)
|
||||||
|
|
@ -346,30 +172,12 @@ public final class RotationManager {
|
||||||
|
|
||||||
@EventHandler(priority = -100)
|
@EventHandler(priority = -100)
|
||||||
public void onPacketSend(PacketEvent.Send e) {
|
public void onPacketSend(PacketEvent.Send e) {
|
||||||
if (e.isCancelled()) return;
|
packets.onSend(e);
|
||||||
var packet = e.getPacket();
|
|
||||||
|
|
||||||
Rotation rot = null;
|
|
||||||
if (packet instanceof ServerboundMovePlayerPacket move) {
|
|
||||||
if (!move.hasRotation()) return;
|
|
||||||
rot = new Rotation(move.getYRot(0.0f), move.getXRot(0.0f), true);
|
|
||||||
} else if (packet instanceof ServerboundUseItemPacket use) {
|
|
||||||
rot = new Rotation(use.getYRot(), use.getXRot(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (rot != null) {
|
|
||||||
actualServerRotation = rot;
|
|
||||||
theoreticalServerRotation = rot;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = -100)
|
@EventHandler(priority = -100)
|
||||||
public void onPacketReceive(PacketEvent.Receive e) {
|
public void onPacketReceive(PacketEvent.Receive e) {
|
||||||
if (!(e.getPacket() instanceof net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket packet))
|
packets.onReceive(e);
|
||||||
return;
|
|
||||||
Rotation rot = new Rotation(packet.change().yRot(), packet.change().xRot(), true);
|
|
||||||
theoreticalServerRotation = rot;
|
|
||||||
actualServerRotation = rot;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void runTickLifecycle() {
|
public void runTickLifecycle() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package dev.loki.lovisual.util.aiming.rotation.internal;
|
||||||
|
|
||||||
|
import dev.loki.lovisual.events.impl.network.PacketEvent;
|
||||||
|
import dev.loki.lovisual.util.aiming.data.Rotation;
|
||||||
|
import net.minecraft.network.protocol.game.ClientboundPlayerPositionPacket;
|
||||||
|
import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket;
|
||||||
|
import net.minecraft.network.protocol.game.ServerboundUseItemPacket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks the rotation the server sees, updated from outbound and inbound
|
||||||
|
* movement packets. Kept apart from the facade so packet wiring stays small.
|
||||||
|
*/
|
||||||
|
public final class RotationPackets {
|
||||||
|
|
||||||
|
private Rotation actualServerRotation = Rotation.ZERO;
|
||||||
|
private Rotation theoreticalServerRotation = Rotation.ZERO;
|
||||||
|
|
||||||
|
public Rotation actual() {
|
||||||
|
return actualServerRotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSend(PacketEvent.Send e) {
|
||||||
|
if (e.isCancelled()) return;
|
||||||
|
var packet = e.getPacket();
|
||||||
|
|
||||||
|
Rotation rot = null;
|
||||||
|
if (packet instanceof ServerboundMovePlayerPacket move) {
|
||||||
|
if (!move.hasRotation()) return;
|
||||||
|
rot = new Rotation(move.getYRot(0.0f), move.getXRot(0.0f), true);
|
||||||
|
} else if (packet instanceof ServerboundUseItemPacket use) {
|
||||||
|
rot = new Rotation(use.getYRot(), use.getXRot(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rot != null) {
|
||||||
|
actualServerRotation = rot;
|
||||||
|
theoreticalServerRotation = rot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onReceive(PacketEvent.Receive e) {
|
||||||
|
if (!(e.getPacket() instanceof ClientboundPlayerPositionPacket packet)) return;
|
||||||
|
Rotation rot = new Rotation(packet.change().yRot(), packet.change().xRot(), true);
|
||||||
|
theoreticalServerRotation = rot;
|
||||||
|
actualServerRotation = rot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rotation theoretical() {
|
||||||
|
return theoreticalServerRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
package dev.loki.lovisual.util.aiming.rotation.internal;
|
||||||
|
|
||||||
|
import dev.loki.lovisual.util.aiming.RequestHandler;
|
||||||
|
import dev.loki.lovisual.util.aiming.data.Rotation;
|
||||||
|
import dev.loki.lovisual.util.aiming.rotation.RotationTarget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutable rotation state shared by {@link RotationStepper} and the public
|
||||||
|
* {@code RotationManager} facade. Fields are package-private on purpose so the
|
||||||
|
* stepper can drive them in place, mirroring the original single-class logic.
|
||||||
|
*/
|
||||||
|
public final class RotationState {
|
||||||
|
|
||||||
|
final RequestHandler<RotationTarget> handler = new RequestHandler<>();
|
||||||
|
|
||||||
|
RotationTarget previousTarget;
|
||||||
|
Object previousProvider;
|
||||||
|
Rotation current;
|
||||||
|
Rotation previous;
|
||||||
|
Object currentProvider;
|
||||||
|
boolean smoothReturnActive;
|
||||||
|
int smoothReturnTicks;
|
||||||
|
|
||||||
|
public RotationTarget activeTarget() {
|
||||||
|
RotationTarget active = handler.getActiveRequestValue();
|
||||||
|
return active != null ? active : previousTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RotationTarget previousTarget() {
|
||||||
|
return previousTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rotation current() {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rotation previous() {
|
||||||
|
return previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void request(RotationTarget plan, int priority, Object provider) {
|
||||||
|
if (plan == null) return;
|
||||||
|
|
||||||
|
handler.request(new RequestHandler.Request<>(plan.ticksUntilReset, priority, provider, plan));
|
||||||
|
smoothReturnActive = false;
|
||||||
|
smoothReturnTicks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCurrent(Rotation rotation, Object provider) {
|
||||||
|
if (rotation == null) {
|
||||||
|
if (current != null) {
|
||||||
|
previous = current;
|
||||||
|
}
|
||||||
|
current = null;
|
||||||
|
currentProvider = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
previous = current;
|
||||||
|
current = rotation;
|
||||||
|
currentProvider = provider;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finishSmoothReturn() {
|
||||||
|
setCurrent(null, currentProvider);
|
||||||
|
previous = null;
|
||||||
|
previousTarget = null;
|
||||||
|
previousProvider = null;
|
||||||
|
smoothReturnActive = false;
|
||||||
|
smoothReturnTicks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearAll() {
|
||||||
|
handler.clear();
|
||||||
|
previousTarget = null;
|
||||||
|
previousProvider = null;
|
||||||
|
current = null;
|
||||||
|
currentProvider = null;
|
||||||
|
previous = null;
|
||||||
|
smoothReturnActive = false;
|
||||||
|
smoothReturnTicks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clearProvider(Object provider) {
|
||||||
|
if (provider == null) return;
|
||||||
|
boolean removed = handler.clear(provider);
|
||||||
|
boolean ownsVisualState = provider == currentProvider || provider == previousProvider;
|
||||||
|
if (!removed && !ownsVisualState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current != null && ownsVisualState) {
|
||||||
|
smoothReturnActive = true;
|
||||||
|
smoothReturnTicks = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ownsVisualState) {
|
||||||
|
previousTarget = null;
|
||||||
|
previousProvider = null;
|
||||||
|
previous = null;
|
||||||
|
}
|
||||||
|
smoothReturnActive = false;
|
||||||
|
smoothReturnTicks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void releaseProvider(Object provider, boolean smoothReturn) {
|
||||||
|
if (provider == null) return;
|
||||||
|
boolean removed = handler.clear(provider);
|
||||||
|
boolean ownsVisualState = provider == currentProvider || provider == previousProvider;
|
||||||
|
if (!removed && !ownsVisualState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!smoothReturn) {
|
||||||
|
if (ownsVisualState) {
|
||||||
|
setCurrent(null, currentProvider);
|
||||||
|
previousTarget = null;
|
||||||
|
previousProvider = null;
|
||||||
|
previous = null;
|
||||||
|
}
|
||||||
|
smoothReturnActive = false;
|
||||||
|
smoothReturnTicks = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
smoothReturnActive = ownsVisualState && current != null;
|
||||||
|
smoothReturnTicks = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
package dev.loki.lovisual.util.aiming.rotation.internal;
|
||||||
|
|
||||||
|
import dev.loki.lovisual.util.aiming.RequestHandler;
|
||||||
|
import dev.loki.lovisual.util.aiming.data.Rotation;
|
||||||
|
import dev.loki.lovisual.util.aiming.rotation.RotationTarget;
|
||||||
|
import dev.loki.lovisual.util.aiming.rotation.RotationUtil;
|
||||||
|
import dev.loki.lovisual.util.screen.ClientScreen;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
|
||||||
|
import net.minecraft.client.player.LocalPlayer;
|
||||||
|
import net.minecraft.util.Mth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per-tick rotation state machine: resolves the active target, advances the
|
||||||
|
* current rotation and drives the smooth return back to the player view.
|
||||||
|
*/
|
||||||
|
public final class RotationStepper {
|
||||||
|
|
||||||
|
private RotationStepper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void step(RotationState state, LocalPlayer player, Rotation serverRotation) {
|
||||||
|
Rotation playerRotation = new Rotation(player.getYRot(), player.getXRot(), true);
|
||||||
|
RequestHandler.Request<RotationTarget> activeRequest = state.handler.getActiveRequest();
|
||||||
|
RotationTarget activeRotationTarget = activeRequest != null ? activeRequest.value() : state.previousTarget;
|
||||||
|
Object activeProvider = activeRequest != null ? activeRequest.provider() : state.previousProvider;
|
||||||
|
|
||||||
|
if (activeRotationTarget == null) {
|
||||||
|
if (state.current == null || !state.smoothReturnActive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float diffToPlayer = computeRotationDifference(state.current, playerRotation);
|
||||||
|
if (diffToPlayer <= 0.5f) {
|
||||||
|
state.finishSmoothReturn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float speed = 0.25f + 0.4f * Math.min(1.0f, diffToPlayer / 30.0f);
|
||||||
|
float yawDiff = RotationUtil.angleDifference(playerRotation.yaw(), state.current.yaw());
|
||||||
|
float newYaw = state.current.yaw() + yawDiff * speed;
|
||||||
|
float newPitch = Mth.lerp(speed, state.current.pitch(), playerRotation.pitch());
|
||||||
|
state.setCurrent(new Rotation(newYaw, newPitch, false).normalize(), state.currentProvider);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRotatingAllowed(activeRotationTarget)) {
|
||||||
|
Rotation fromRotation = resolveBaseRotation(state, playerRotation, serverRotation);
|
||||||
|
if (activeRequest == null) {
|
||||||
|
if (state.current == null) {
|
||||||
|
state.finishSmoothReturn();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!state.smoothReturnActive) {
|
||||||
|
state.smoothReturnActive = true;
|
||||||
|
state.smoothReturnTicks = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rotation resetRotation = activeRotationTarget.towards(fromRotation, true).normalize();
|
||||||
|
state.setCurrent(resetRotation, activeProvider);
|
||||||
|
state.previousTarget = activeRotationTarget;
|
||||||
|
state.previousProvider = activeProvider;
|
||||||
|
|
||||||
|
state.smoothReturnTicks++;
|
||||||
|
float diffToPlayer = computeRotationDifference(resetRotation, playerRotation);
|
||||||
|
int maxTicks = Math.max(1, activeRotationTarget.ticksUntilReset);
|
||||||
|
float threshold = Math.max(0.05f, activeRotationTarget.resetThreshold);
|
||||||
|
if (diffToPlayer <= threshold || state.smoothReturnTicks >= maxTicks) {
|
||||||
|
state.finishSmoothReturn();
|
||||||
|
state.handler.tick();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Rotation rotation = activeRotationTarget.towards(fromRotation, false).normalize();
|
||||||
|
state.setCurrent(rotation, activeProvider);
|
||||||
|
state.previousTarget = activeRotationTarget;
|
||||||
|
state.previousProvider = activeProvider;
|
||||||
|
state.smoothReturnActive = false;
|
||||||
|
state.smoothReturnTicks = 0;
|
||||||
|
|
||||||
|
if (activeRotationTarget.whenReached != null) {
|
||||||
|
activeRotationTarget.whenReached.invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
state.handler.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float computeRotationDifference(Rotation a, Rotation b) {
|
||||||
|
if (a == null || b == null) {
|
||||||
|
return Float.MAX_VALUE;
|
||||||
|
}
|
||||||
|
float yawDiff = Math.abs(RotationUtil.angleDifference(a.yaw(), b.yaw()));
|
||||||
|
float pitchDiff = Math.abs(a.pitch() - b.pitch());
|
||||||
|
return (float) Math.hypot(yawDiff, pitchDiff);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isRotatingAllowed(RotationTarget target) {
|
||||||
|
if (!target.considerInventory) return true;
|
||||||
|
Minecraft mc = Minecraft.getInstance();
|
||||||
|
if (mc == null) return true;
|
||||||
|
return !(ClientScreen.current() instanceof AbstractContainerScreen<?>);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Rotation resolveBaseRotation(RotationState state, Rotation playerRotation, Rotation serverRotation) {
|
||||||
|
if (state.current != null) {
|
||||||
|
return state.current;
|
||||||
|
}
|
||||||
|
if (serverRotation != null && serverRotation != Rotation.ZERO) {
|
||||||
|
return serverRotation;
|
||||||
|
}
|
||||||
|
return playerRotation;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue