Merge pull request 'chore/audit-s02-s05-state-cleanup' (#13) from chore/audit-s02-s05-state-cleanup into develop
Reviewed-on: #13
This commit was merged in pull request #13.
This commit is contained in:
@@ -11,6 +11,7 @@ import com.tiedup.remake.state.components.PlayerClothesPermission;
|
||||
import com.tiedup.remake.state.components.PlayerDataRetrieval;
|
||||
import com.tiedup.remake.state.components.PlayerEquipment;
|
||||
import com.tiedup.remake.state.components.PlayerLifecycle;
|
||||
import com.tiedup.remake.state.components.PlayerMovement;
|
||||
import com.tiedup.remake.state.components.PlayerSale;
|
||||
import com.tiedup.remake.state.components.PlayerShockCollar;
|
||||
import com.tiedup.remake.state.components.PlayerSpecialActions;
|
||||
@@ -49,8 +50,10 @@ import org.jetbrains.annotations.Nullable;
|
||||
* - Enslavement lifecycle (can be enslaved, act as master)
|
||||
* - Advanced collar features (shocks, GPS tracking)
|
||||
*
|
||||
* Thread Safety: This class is accessed from both server and client threads.
|
||||
* Use appropriate synchronization when accessing the instances map.
|
||||
* Thread Safety: All mutating methods must be called from the server thread
|
||||
* (via enqueueWork for packet handlers). The only cross-thread access is
|
||||
* the StruggleSnapshot (atomic volatile record) and the Player reference (volatile).
|
||||
* The instances map uses ConcurrentHashMap for thread-safe lookup.
|
||||
*
|
||||
* Refactoring: Component-Host pattern
|
||||
*/
|
||||
@@ -138,89 +141,27 @@ public class PlayerBindState implements IRestrainable, IPlayerBindStateHost {
|
||||
private PlayerCaptorManager captorManager;
|
||||
// Note: transport field removed - now using IPlayerLeashAccess mixin
|
||||
|
||||
// Struggle animation state
|
||||
// volatile: accessed from multiple threads (network, tick, render)
|
||||
private volatile boolean isStruggling = false;
|
||||
private volatile long struggleStartTick = 0;
|
||||
// Struggle animation state — atomic snapshot for cross-thread visibility.
|
||||
// Server thread writes via setStruggling(), render thread reads via isStruggling()/getStruggleStartTick().
|
||||
// Single volatile reference guarantees both fields are visible atomically.
|
||||
private record StruggleSnapshot(boolean struggling, long startTick) {}
|
||||
private volatile StruggleSnapshot struggleState = new StruggleSnapshot(false, 0);
|
||||
|
||||
// ========== Movement Style State (Phase: Movement Styles) ==========
|
||||
// Managed exclusively by MovementStyleManager. Stored here to piggyback
|
||||
// on existing lifecycle cleanup hooks (death, logout, dimension change).
|
||||
// ========== Movement Style State ==========
|
||||
// Encapsulated in PlayerMovement component. Managed exclusively by MovementStyleManager.
|
||||
private final PlayerMovement movement;
|
||||
|
||||
/** Currently active movement style, or null if none. */
|
||||
@Nullable
|
||||
private com.tiedup.remake.v2.bondage.movement.MovementStyle activeMovementStyle;
|
||||
|
||||
/** Resolved speed multiplier for the active style (0.0-1.0). */
|
||||
private float resolvedMovementSpeed = 1.0f;
|
||||
|
||||
/** Whether jumping is currently disabled by the active style. */
|
||||
private boolean resolvedJumpDisabled = false;
|
||||
|
||||
@Nullable
|
||||
public com.tiedup.remake.v2.bondage.movement.MovementStyle getActiveMovementStyle() {
|
||||
return activeMovementStyle;
|
||||
/** Access movement style state (hop, crawl, position tracking, etc.). */
|
||||
public PlayerMovement getMovement() {
|
||||
return movement;
|
||||
}
|
||||
|
||||
public void setActiveMovementStyle(
|
||||
@Nullable com.tiedup.remake.v2.bondage.movement.MovementStyle style
|
||||
) {
|
||||
this.activeMovementStyle = style;
|
||||
}
|
||||
|
||||
public float getResolvedMovementSpeed() {
|
||||
return resolvedMovementSpeed;
|
||||
}
|
||||
|
||||
public void setResolvedMovementSpeed(float speed) {
|
||||
this.resolvedMovementSpeed = speed;
|
||||
}
|
||||
|
||||
public boolean isResolvedJumpDisabled() {
|
||||
return resolvedJumpDisabled;
|
||||
}
|
||||
|
||||
public void setResolvedJumpDisabled(boolean disabled) {
|
||||
this.resolvedJumpDisabled = disabled;
|
||||
}
|
||||
|
||||
/** Ticks until next hop is allowed (HOP style). */
|
||||
public int hopCooldown = 0;
|
||||
|
||||
/** True during the 4-tick startup delay before the first hop. */
|
||||
public boolean hopStartupPending = false;
|
||||
|
||||
/** Countdown ticks for the hop startup delay. */
|
||||
public int hopStartupTicks = 0;
|
||||
|
||||
/** True if crawl deactivated but player can't stand yet (space blocked). */
|
||||
public boolean pendingPoseRestore = false;
|
||||
|
||||
/** Last known X position for movement detection (position delta). */
|
||||
public double lastX;
|
||||
|
||||
/** Last known Y position for movement detection (position delta). */
|
||||
public double lastY;
|
||||
|
||||
/** Last known Z position for movement detection (position delta). */
|
||||
public double lastZ;
|
||||
|
||||
/** Consecutive non-moving ticks counter for hop startup reset. */
|
||||
public int hopNotMovingTicks = 0;
|
||||
|
||||
/**
|
||||
* Resets all movement style state to defaults.
|
||||
* Called on death, logout, and dimension change to ensure clean re-activation.
|
||||
*/
|
||||
public void clearMovementState() {
|
||||
this.activeMovementStyle = null;
|
||||
this.resolvedMovementSpeed = 1.0f;
|
||||
this.resolvedJumpDisabled = false;
|
||||
this.hopCooldown = 0;
|
||||
this.hopStartupPending = false;
|
||||
this.hopStartupTicks = 0;
|
||||
this.pendingPoseRestore = false;
|
||||
this.hopNotMovingTicks = 0;
|
||||
movement.clear();
|
||||
}
|
||||
|
||||
// ========== Constructor ==========
|
||||
@@ -249,6 +190,9 @@ public class PlayerBindState implements IRestrainable, IPlayerBindStateHost {
|
||||
this.lifecycle = new PlayerLifecycle(this);
|
||||
this.captivity = new PlayerCaptivity(this);
|
||||
|
||||
// Initialize movement component
|
||||
this.movement = new PlayerMovement();
|
||||
|
||||
this.captor = null;
|
||||
this.captorManager = new PlayerCaptorManager(player);
|
||||
}
|
||||
@@ -578,47 +522,42 @@ public class PlayerBindState implements IRestrainable, IPlayerBindStateHost {
|
||||
|
||||
/**
|
||||
* Check if player is currently playing struggle animation.
|
||||
* Thread-safe (volatile field).
|
||||
* IPlayerBindStateHost implementation.
|
||||
* Thread-safe: reads from atomic StruggleSnapshot.
|
||||
*/
|
||||
@Override
|
||||
public boolean isStruggling() {
|
||||
return isStruggling;
|
||||
return struggleState.struggling();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tick when struggle animation started.
|
||||
* Thread-safe (volatile field).
|
||||
* IPlayerBindStateHost implementation.
|
||||
* Thread-safe: reads from atomic StruggleSnapshot.
|
||||
*/
|
||||
@Override
|
||||
public long getStruggleStartTick() {
|
||||
return struggleStartTick;
|
||||
return struggleState.startTick();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set struggle animation state (server-side).
|
||||
* IPlayerBindStateHost implementation.
|
||||
* @param struggling True to start struggle animation, false to stop
|
||||
* @param currentTick Current game time tick (for timer)
|
||||
* Single volatile write ensures both fields are visible atomically to render thread.
|
||||
*/
|
||||
@Override
|
||||
public void setStruggling(boolean struggling, long currentTick) {
|
||||
this.isStruggling = struggling;
|
||||
if (struggling) {
|
||||
this.struggleStartTick = currentTick;
|
||||
}
|
||||
this.struggleState = new StruggleSnapshot(
|
||||
struggling,
|
||||
struggling ? currentTick : struggleState.startTick()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set struggle animation flag (client-side only).
|
||||
* IPlayerBindStateHost implementation.
|
||||
* Used by network sync - does NOT update timer (server manages timer).
|
||||
* @param struggling True if struggling, false otherwise
|
||||
*/
|
||||
@Override
|
||||
public void setStrugglingClient(boolean struggling) {
|
||||
this.isStruggling = struggling;
|
||||
StruggleSnapshot current = this.struggleState;
|
||||
this.struggleState = new StruggleSnapshot(struggling, current.startTick());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -635,31 +574,34 @@ public class PlayerBindState implements IRestrainable, IPlayerBindStateHost {
|
||||
* Delegated to PlayerEquipment component
|
||||
*/
|
||||
@Override
|
||||
public synchronized int getCurrentBindResistance() {
|
||||
public int getCurrentBindResistance() {
|
||||
return equipment.getCurrentBindResistance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegated to PlayerEquipment component
|
||||
* Delegated to PlayerEquipment component.
|
||||
* Thread safety: must be called from the server thread.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setCurrentBindResistance(int resistance) {
|
||||
public void setCurrentBindResistance(int resistance) {
|
||||
equipment.setCurrentBindResistance(resistance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegated to PlayerEquipment component
|
||||
* Delegated to PlayerEquipment component.
|
||||
* Thread safety: must be called from the server thread.
|
||||
*/
|
||||
@Override
|
||||
public synchronized int getCurrentCollarResistance() {
|
||||
public int getCurrentCollarResistance() {
|
||||
return equipment.getCurrentCollarResistance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegated to PlayerEquipment component
|
||||
* Delegated to PlayerEquipment component.
|
||||
* Thread safety: must be called from the server thread.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setCurrentCollarResistance(int resistance) {
|
||||
public void setCurrentCollarResistance(int resistance) {
|
||||
equipment.setCurrentCollarResistance(resistance);
|
||||
}
|
||||
|
||||
@@ -1006,12 +948,12 @@ public class PlayerBindState implements IRestrainable, IPlayerBindStateHost {
|
||||
// Delegated to PlayerEquipment component (except TORSO - handled by PlayerClothesPermission)
|
||||
|
||||
@Override
|
||||
public synchronized ItemStack replaceEquipment(
|
||||
public ItemStack replaceEquipment(
|
||||
BodyRegionV2 region,
|
||||
ItemStack newStack,
|
||||
boolean force
|
||||
) {
|
||||
// MEDIUM FIX: Synchronized to prevent race condition during equipment replacement
|
||||
// Thread safety: must be called from the server thread.
|
||||
return switch (region) {
|
||||
case ARMS -> equipment.replaceBind(newStack, force);
|
||||
case MOUTH -> equipment.replaceGag(newStack, force);
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
package com.tiedup.remake.state.components;
|
||||
|
||||
import com.tiedup.remake.v2.bondage.movement.MovementStyle;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Component responsible for movement style state management.
|
||||
* Stores all per-player movement fields used exclusively by MovementStyleManager.
|
||||
*
|
||||
* <p>Thread safety: All fields are server-tick-only (PlayerTickEvent + LivingJumpEvent,
|
||||
* both on the server thread). No synchronization needed.</p>
|
||||
*/
|
||||
public class PlayerMovement {
|
||||
|
||||
// --- Resolved style state ---
|
||||
@Nullable
|
||||
private MovementStyle activeMovementStyle;
|
||||
private float resolvedMovementSpeed = 1.0f;
|
||||
private boolean resolvedJumpDisabled = false;
|
||||
|
||||
// --- Hop state ---
|
||||
private int hopCooldown = 0;
|
||||
private boolean hopStartupPending = false;
|
||||
private int hopStartupTicks = 0;
|
||||
private int hopNotMovingTicks = 0;
|
||||
|
||||
// --- Crawl state ---
|
||||
private boolean pendingPoseRestore = false;
|
||||
|
||||
// --- Position tracking for movement detection ---
|
||||
private double lastX;
|
||||
private double lastY;
|
||||
private double lastZ;
|
||||
|
||||
public PlayerMovement() {
|
||||
}
|
||||
|
||||
// --- Resolved style ---
|
||||
|
||||
@Nullable
|
||||
public MovementStyle getActiveMovementStyle() {
|
||||
return activeMovementStyle;
|
||||
}
|
||||
|
||||
public void setActiveMovementStyle(@Nullable MovementStyle style) {
|
||||
this.activeMovementStyle = style;
|
||||
}
|
||||
|
||||
public float getResolvedMovementSpeed() {
|
||||
return resolvedMovementSpeed;
|
||||
}
|
||||
|
||||
public void setResolvedMovementSpeed(float speed) {
|
||||
this.resolvedMovementSpeed = speed;
|
||||
}
|
||||
|
||||
public boolean isResolvedJumpDisabled() {
|
||||
return resolvedJumpDisabled;
|
||||
}
|
||||
|
||||
public void setResolvedJumpDisabled(boolean disabled) {
|
||||
this.resolvedJumpDisabled = disabled;
|
||||
}
|
||||
|
||||
// --- Hop ---
|
||||
|
||||
public int getHopCooldown() {
|
||||
return hopCooldown;
|
||||
}
|
||||
|
||||
public void setHopCooldown(int hopCooldown) {
|
||||
this.hopCooldown = hopCooldown;
|
||||
}
|
||||
|
||||
public boolean isHopStartupPending() {
|
||||
return hopStartupPending;
|
||||
}
|
||||
|
||||
public void setHopStartupPending(boolean hopStartupPending) {
|
||||
this.hopStartupPending = hopStartupPending;
|
||||
}
|
||||
|
||||
public int getHopStartupTicks() {
|
||||
return hopStartupTicks;
|
||||
}
|
||||
|
||||
public void setHopStartupTicks(int hopStartupTicks) {
|
||||
this.hopStartupTicks = hopStartupTicks;
|
||||
}
|
||||
|
||||
public int getHopNotMovingTicks() {
|
||||
return hopNotMovingTicks;
|
||||
}
|
||||
|
||||
public void setHopNotMovingTicks(int hopNotMovingTicks) {
|
||||
this.hopNotMovingTicks = hopNotMovingTicks;
|
||||
}
|
||||
|
||||
// --- Crawl ---
|
||||
|
||||
public boolean isPendingPoseRestore() {
|
||||
return pendingPoseRestore;
|
||||
}
|
||||
|
||||
public void setPendingPoseRestore(boolean pendingPoseRestore) {
|
||||
this.pendingPoseRestore = pendingPoseRestore;
|
||||
}
|
||||
|
||||
// --- Position tracking ---
|
||||
|
||||
public double getLastX() {
|
||||
return lastX;
|
||||
}
|
||||
|
||||
public void setLastX(double lastX) {
|
||||
this.lastX = lastX;
|
||||
}
|
||||
|
||||
public double getLastY() {
|
||||
return lastY;
|
||||
}
|
||||
|
||||
public void setLastY(double lastY) {
|
||||
this.lastY = lastY;
|
||||
}
|
||||
|
||||
public double getLastZ() {
|
||||
return lastZ;
|
||||
}
|
||||
|
||||
public void setLastZ(double lastZ) {
|
||||
this.lastZ = lastZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all movement style state to defaults.
|
||||
* Called on death, logout, and dimension change to ensure clean re-activation.
|
||||
* Note: lastX/Y/Z are NOT reset — they track position, not style state.
|
||||
*/
|
||||
public void clear() {
|
||||
this.activeMovementStyle = null;
|
||||
this.resolvedMovementSpeed = 1.0f;
|
||||
this.resolvedJumpDisabled = false;
|
||||
this.hopCooldown = 0;
|
||||
this.hopStartupPending = false;
|
||||
this.hopStartupTicks = 0;
|
||||
this.pendingPoseRestore = false;
|
||||
this.hopNotMovingTicks = 0;
|
||||
}
|
||||
}
|
||||
@@ -42,10 +42,9 @@ public class PlayerStruggle {
|
||||
* Entry point for the Struggle logic (Key R).
|
||||
* Distributes effort between Binds and Collar.
|
||||
*
|
||||
* Thread Safety: Synchronized to prevent lost updates when multiple struggle
|
||||
* packets arrive simultaneously (e.g., from macro/rapid keypresses).
|
||||
* Thread Safety: Must be called from the server thread (packet handlers use enqueueWork).
|
||||
*/
|
||||
public synchronized void struggle() {
|
||||
public void struggle() {
|
||||
if (struggleBindState != null) struggleBindState.struggle(state);
|
||||
if (struggleCollarState != null) struggleCollarState.struggle(state);
|
||||
}
|
||||
@@ -53,9 +52,9 @@ public class PlayerStruggle {
|
||||
/**
|
||||
* Restores resistance to base values when a master tightens the ties.
|
||||
*
|
||||
* Thread Safety: Synchronized to prevent race with struggle operations.
|
||||
* Thread Safety: Must be called from the server thread.
|
||||
*/
|
||||
public synchronized void tighten(Player tightener) {
|
||||
public void tighten(Player tightener) {
|
||||
if (struggleBindState != null) struggleBindState.tighten(
|
||||
tightener,
|
||||
state
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user