refactor(S-02/S-05): extract PlayerMovement component + fix thread safety

- Extract 11 movement fields from PlayerBindState into PlayerMovement component
- Replace volatile isStruggling/struggleStartTick pair with atomic StruggleSnapshot record
- Remove 5+2 misleading synchronized keywords (different monitors, all server-thread-only)
- Update all 36 MovementStyleManager field accesses to use getMovement() getters/setters
This commit is contained in:
NotEvil
2026-04-15 13:17:00 +02:00
parent 70f85b58a6
commit 22d79a452b
4 changed files with 251 additions and 155 deletions

View File

@@ -129,14 +129,14 @@ public class MovementStyleManager {
player.isDeadOrDying() ||
state.isStruggling()
) {
state.lastX = player.getX();
state.lastY = player.getY();
state.lastZ = player.getZ();
state.getMovement().setLastX(player.getX());
state.getMovement().setLastY(player.getY());
state.getMovement().setLastZ(player.getZ());
return;
}
// --- Pending pose restore (crawl deactivated but can't stand) ---
if (state.pendingPoseRestore) {
if (state.getMovement().isPendingPoseRestore()) {
tryRestoreStandingPose(player, state);
}
@@ -153,7 +153,7 @@ public class MovementStyleManager {
// --- Compare with current active style ---
MovementStyle newStyle = resolved.style();
MovementStyle oldStyle = state.getActiveMovementStyle();
MovementStyle oldStyle = state.getMovement().getActiveMovementStyle();
if (newStyle != oldStyle) {
// Style changed: deactivate old, activate new
@@ -161,14 +161,14 @@ public class MovementStyleManager {
onDeactivate(player, state, oldStyle);
}
if (newStyle != null) {
state.setResolvedMovementSpeed(resolved.speedMultiplier());
state.setResolvedJumpDisabled(resolved.jumpDisabled());
state.getMovement().setResolvedMovementSpeed(resolved.speedMultiplier());
state.getMovement().setResolvedJumpDisabled(resolved.jumpDisabled());
onActivate(player, state, newStyle);
} else {
state.setResolvedMovementSpeed(1.0f);
state.setResolvedJumpDisabled(false);
state.getMovement().setResolvedMovementSpeed(1.0f);
state.getMovement().setResolvedJumpDisabled(false);
}
state.setActiveMovementStyle(newStyle);
state.getMovement().setActiveMovementStyle(newStyle);
// Sync to all tracking clients (animation + crawl pose)
ModNetwork.sendToAllTrackingAndSelf(
@@ -178,13 +178,13 @@ public class MovementStyleManager {
}
// --- Per-style tick ---
if (state.getActiveMovementStyle() != null) {
if (state.getMovement().getActiveMovementStyle() != null) {
// Ladder suspension: skip style tick when on ladder
// (ladder movement is controlled by BondageItemRestrictionHandler)
if (player.onClimbable()) {
state.lastX = player.getX();
state.lastY = player.getY();
state.lastZ = player.getZ();
state.getMovement().setLastX(player.getX());
state.getMovement().setLastY(player.getY());
state.getMovement().setLastZ(player.getZ());
return;
}
@@ -192,9 +192,9 @@ public class MovementStyleManager {
}
// Update last position for next tick's movement detection
state.lastX = player.getX();
state.lastY = player.getY();
state.lastZ = player.getZ();
state.getMovement().setLastX(player.getX());
state.getMovement().setLastY(player.getY());
state.getMovement().setLastZ(player.getZ());
}
// ==================== Jump Suppression ====================
@@ -216,7 +216,7 @@ public class MovementStyleManager {
}
PlayerBindState state = PlayerBindState.getInstance(player);
if (state == null || !state.isResolvedJumpDisabled()) {
if (state == null || !state.getMovement().isResolvedJumpDisabled()) {
return;
}
@@ -274,7 +274,7 @@ public class MovementStyleManager {
}
private static void tickStyle(ServerPlayer player, PlayerBindState state) {
switch (state.getActiveMovementStyle()) {
switch (state.getMovement().getActiveMovementStyle()) {
case WADDLE -> tickWaddle(player, state);
case SHUFFLE -> tickShuffle(player, state);
case HOP -> tickHop(player, state);
@@ -292,7 +292,7 @@ public class MovementStyleManager {
player,
WADDLE_SPEED_UUID,
"tiedup.waddle_speed",
state.getResolvedMovementSpeed()
state.getMovement().getResolvedMovementSpeed()
);
}
@@ -318,7 +318,7 @@ public class MovementStyleManager {
player,
SHUFFLE_SPEED_UUID,
"tiedup.shuffle_speed",
state.getResolvedMovementSpeed()
state.getMovement().getResolvedMovementSpeed()
);
}
@@ -347,11 +347,11 @@ public class MovementStyleManager {
player,
HOP_SPEED_UUID,
"tiedup.hop_speed",
state.getResolvedMovementSpeed()
state.getMovement().getResolvedMovementSpeed()
);
state.hopCooldown = 0;
state.hopStartupPending = true;
state.hopStartupTicks = HOP_STARTUP_DELAY_TICKS;
state.getMovement().setHopCooldown(0);
state.getMovement().setHopStartupPending(true);
state.getMovement().setHopStartupTicks(HOP_STARTUP_DELAY_TICKS);
}
private static void deactivateHop(
@@ -359,10 +359,10 @@ public class MovementStyleManager {
PlayerBindState state
) {
removeSpeedModifier(player, HOP_SPEED_UUID);
state.hopCooldown = 0;
state.hopStartupPending = false;
state.hopStartupTicks = 0;
state.hopNotMovingTicks = 0;
state.getMovement().setHopCooldown(0);
state.getMovement().setHopStartupPending(false);
state.getMovement().setHopStartupTicks(0);
state.getMovement().setHopNotMovingTicks(0);
}
/**
@@ -375,43 +375,44 @@ public class MovementStyleManager {
* </ul>
*/
private static void tickHop(ServerPlayer player, PlayerBindState state) {
var mov = state.getMovement();
boolean isMoving =
player.distanceToSqr(state.lastX, state.lastY, state.lastZ) >
player.distanceToSqr(mov.getLastX(), mov.getLastY(), mov.getLastZ()) >
MOVEMENT_THRESHOLD_SQ;
// Decrement cooldown
if (state.hopCooldown > 0) {
state.hopCooldown--;
if (mov.getHopCooldown() > 0) {
mov.setHopCooldown(mov.getHopCooldown() - 1);
}
if (isMoving && player.onGround() && state.hopCooldown <= 0) {
if (state.hopStartupPending) {
if (isMoving && player.onGround() && mov.getHopCooldown() <= 0) {
if (mov.isHopStartupPending()) {
// Startup delay: decrement and wait (latched: completes even if
// player briefly releases input during these 4 ticks)
state.hopStartupTicks--;
if (state.hopStartupTicks <= 0) {
mov.setHopStartupTicks(mov.getHopStartupTicks() - 1);
if (mov.getHopStartupTicks() <= 0) {
// Startup complete: execute first hop
state.hopStartupPending = false;
mov.setHopStartupPending(false);
executeHop(player, state);
}
} else {
// Normal hop
executeHop(player, state);
}
state.hopNotMovingTicks = 0;
mov.setHopNotMovingTicks(0);
} else if (!isMoving) {
state.hopNotMovingTicks++;
mov.setHopNotMovingTicks(mov.getHopNotMovingTicks() + 1);
// Reset startup if not moving for >= 2 consecutive ticks
if (
state.hopNotMovingTicks >= HOP_STARTUP_RESET_TICKS &&
!state.hopStartupPending
mov.getHopNotMovingTicks() >= HOP_STARTUP_RESET_TICKS &&
!mov.isHopStartupPending()
) {
state.hopStartupPending = true;
state.hopStartupTicks = HOP_STARTUP_DELAY_TICKS;
mov.setHopStartupPending(true);
mov.setHopStartupTicks(HOP_STARTUP_DELAY_TICKS);
}
} else {
// Moving but not on ground or cooldown active — reset not-moving counter
state.hopNotMovingTicks = 0;
mov.setHopNotMovingTicks(0);
}
}
@@ -431,7 +432,7 @@ public class MovementStyleManager {
currentMotion.z + forward.z * HOP_FORWARD_IMPULSE
);
state.hopCooldown = HOP_COOLDOWN_TICKS;
state.getMovement().setHopCooldown(HOP_COOLDOWN_TICKS);
// Sync velocity to client to prevent rubber-banding
player.connection.send(new ClientboundSetEntityMotionPacket(player));
@@ -447,11 +448,11 @@ public class MovementStyleManager {
player,
CRAWL_SPEED_UUID,
"tiedup.crawl_speed",
state.getResolvedMovementSpeed()
state.getMovement().getResolvedMovementSpeed()
);
player.setForcedPose(Pose.SWIMMING);
player.refreshDimensions();
state.pendingPoseRestore = false;
state.getMovement().setPendingPoseRestore(false);
}
private static void deactivateCrawl(
@@ -470,7 +471,7 @@ public class MovementStyleManager {
player.refreshDimensions();
} else {
// Can't stand yet -- flag for periodic retry in tick flow (step 2)
state.pendingPoseRestore = true;
state.getMovement().setPendingPoseRestore(true);
}
}
@@ -501,7 +502,7 @@ public class MovementStyleManager {
if (canStand) {
player.setForcedPose(null);
player.refreshDimensions();
state.pendingPoseRestore = false;
state.getMovement().setPendingPoseRestore(false);
LOGGER.debug(
"Restored standing pose for {} (pending pose restore cleared)",
player.getName().getString()