Clean repo for open source release
Remove build artifacts, dev tool configs, unused dependencies, and third-party source dumps. Add proper README, update .gitignore, clean up Makefile.
This commit is contained in:
@@ -0,0 +1,548 @@
|
||||
package com.tiedup.remake.minigame;
|
||||
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Server-side state for the continuous Struggle mini-game session.
|
||||
*
|
||||
* New system: Player holds a direction key to continuously reduce resistance.
|
||||
* - Progression: 1 resistance per second when holding correct direction
|
||||
* - Direction changes randomly every 3-5 seconds
|
||||
* - Shock collar check: 10% chance every 5 seconds (if locked collar equipped)
|
||||
* - No cooldown, no penalty for wrong key (just no progress)
|
||||
*/
|
||||
public class ContinuousStruggleMiniGameState {
|
||||
|
||||
/**
|
||||
* Possible directions for struggling.
|
||||
*/
|
||||
public enum Direction {
|
||||
UP(0), // W / Forward
|
||||
LEFT(1), // A / Strafe Left
|
||||
DOWN(2), // S / Back
|
||||
RIGHT(3); // D / Strafe Right
|
||||
|
||||
private final int index;
|
||||
|
||||
Direction(int index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public static Direction fromIndex(int index) {
|
||||
return switch (index) {
|
||||
case 0 -> UP;
|
||||
case 1 -> LEFT;
|
||||
case 2 -> DOWN;
|
||||
case 3 -> RIGHT;
|
||||
default -> UP;
|
||||
};
|
||||
}
|
||||
|
||||
public static Direction random(Random random) {
|
||||
return fromIndex(random.nextInt(4));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* State of the continuous struggle session.
|
||||
*/
|
||||
public enum State {
|
||||
/** Actively struggling */
|
||||
ACTIVE,
|
||||
/** Temporarily interrupted by shock collar */
|
||||
SHOCKED,
|
||||
/** Successfully escaped (resistance reached 0) */
|
||||
ESCAPED,
|
||||
/** Cancelled by player or damage */
|
||||
CANCELLED,
|
||||
}
|
||||
|
||||
/**
|
||||
* Type of state update sent to client.
|
||||
*/
|
||||
public enum UpdateType {
|
||||
START,
|
||||
DIRECTION_CHANGE,
|
||||
RESISTANCE_UPDATE,
|
||||
SHOCK,
|
||||
ESCAPE,
|
||||
END,
|
||||
}
|
||||
|
||||
// ==================== CONSTANTS ====================
|
||||
|
||||
/** Minimum interval for direction change (3 seconds = 60 ticks) */
|
||||
private static final int DIRECTION_CHANGE_MIN_TICKS = 60;
|
||||
|
||||
/** Maximum interval for direction change (5 seconds = 100 ticks) */
|
||||
private static final int DIRECTION_CHANGE_MAX_TICKS = 100;
|
||||
|
||||
/** Interval for shock collar check (5 seconds = 100 ticks) */
|
||||
private static final int SHOCK_CHECK_INTERVAL_TICKS = 100;
|
||||
|
||||
/** Shock probability (10% = 10 out of 100) */
|
||||
private static final int SHOCK_PROBABILITY = 10;
|
||||
|
||||
/** Default ticks per 1 resistance point (20 ticks = 1 second per resistance) */
|
||||
private static final int DEFAULT_TICKS_PER_RESISTANCE = 20;
|
||||
|
||||
/** Shock stun duration in ticks (1 second) */
|
||||
private static final int SHOCK_STUN_TICKS = 20;
|
||||
|
||||
/** Kidnapper notification interval (2 seconds = 40 ticks) */
|
||||
private static final int KIDNAPPER_NOTIFY_INTERVAL_TICKS = 40;
|
||||
|
||||
/** Struggle sound interval (1.5 seconds = 30 ticks) */
|
||||
private static final int STRUGGLE_SOUND_INTERVAL_TICKS = 30;
|
||||
|
||||
/** Session timeout in milliseconds (10 minutes) */
|
||||
private static final long SESSION_TIMEOUT_MS = 10L * 60L * 1000L;
|
||||
|
||||
// ==================== FIELDS ====================
|
||||
|
||||
private final UUID sessionId;
|
||||
private final UUID playerId;
|
||||
private final long createdAt;
|
||||
|
||||
/** Current required direction (0-3 for W/A/S/D) */
|
||||
private Direction currentDirection;
|
||||
|
||||
/** Current resistance remaining */
|
||||
private int currentResistance;
|
||||
|
||||
/** Maximum resistance (initial value) for display percentage */
|
||||
private final int maxResistance;
|
||||
|
||||
/** Accumulated progress toward next resistance point (0.0 to 1.0) */
|
||||
private float accumulatedProgress;
|
||||
|
||||
/** Tick when direction last changed */
|
||||
private long lastDirectionChangeTick;
|
||||
|
||||
/** Tick when shock was last checked */
|
||||
private long lastShockCheckTick;
|
||||
|
||||
/** Tick when kidnappers were last notified */
|
||||
private long lastKidnapperNotifyTick;
|
||||
|
||||
/** Tick when struggle sound was last played */
|
||||
private long lastStruggleSoundTick;
|
||||
|
||||
/** Ticks until direction change */
|
||||
private int ticksUntilDirectionChange;
|
||||
|
||||
/** Current session state */
|
||||
private State state;
|
||||
|
||||
/** Ticks remaining in shock stun */
|
||||
private int shockStunTicksRemaining;
|
||||
|
||||
/** Whether player is currently holding the correct key */
|
||||
private boolean isHoldingCorrectKey;
|
||||
|
||||
/** Direction player is currently holding (-1 if none) */
|
||||
private int heldDirection;
|
||||
|
||||
/** Whether the bind is locked (affects display only, locks add +250 resistance) */
|
||||
private boolean isLocked;
|
||||
|
||||
/** Target accessory slot (null = bind, otherwise = accessory) */
|
||||
private Integer targetSlot;
|
||||
|
||||
/** Target V2 body region (null = V1 session, non-null = V2 session) */
|
||||
@Nullable
|
||||
private BodyRegionV2 targetRegion;
|
||||
|
||||
/** Target furniture entity ID (non-zero = furniture struggle session) */
|
||||
private int furnitureEntityId;
|
||||
|
||||
/** Target seat ID for furniture struggles (null = not a furniture session) */
|
||||
@Nullable
|
||||
private String furnitureSeatId;
|
||||
|
||||
/** Resistance reduction per tick (computed from ticksPerResistance) */
|
||||
private final float resistancePerTick;
|
||||
|
||||
private final Random random = new Random();
|
||||
|
||||
// ==================== CONSTRUCTOR ====================
|
||||
|
||||
public ContinuousStruggleMiniGameState(
|
||||
UUID playerId,
|
||||
int targetResistance,
|
||||
boolean isLocked
|
||||
) {
|
||||
this(
|
||||
playerId,
|
||||
targetResistance,
|
||||
isLocked,
|
||||
null,
|
||||
DEFAULT_TICKS_PER_RESISTANCE
|
||||
);
|
||||
}
|
||||
|
||||
public ContinuousStruggleMiniGameState(
|
||||
UUID playerId,
|
||||
int targetResistance,
|
||||
boolean isLocked,
|
||||
Integer targetSlot
|
||||
) {
|
||||
this(
|
||||
playerId,
|
||||
targetResistance,
|
||||
isLocked,
|
||||
targetSlot,
|
||||
DEFAULT_TICKS_PER_RESISTANCE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with target slot and configurable rate.
|
||||
*
|
||||
* @param playerId Player UUID
|
||||
* @param targetResistance Current resistance
|
||||
* @param isLocked Whether the target is locked
|
||||
* @param targetSlot Target accessory slot (null = bind)
|
||||
* @param ticksPerResistance Ticks per 1 resistance point (from game rule)
|
||||
*/
|
||||
public ContinuousStruggleMiniGameState(
|
||||
UUID playerId,
|
||||
int targetResistance,
|
||||
boolean isLocked,
|
||||
Integer targetSlot,
|
||||
int ticksPerResistance
|
||||
) {
|
||||
this.sessionId = UUID.randomUUID();
|
||||
this.playerId = playerId;
|
||||
this.createdAt = System.currentTimeMillis();
|
||||
this.resistancePerTick = 1.0f / Math.max(1, ticksPerResistance);
|
||||
|
||||
this.currentResistance = targetResistance;
|
||||
this.maxResistance = targetResistance;
|
||||
this.isLocked = isLocked;
|
||||
this.targetSlot = targetSlot;
|
||||
|
||||
// Initialize direction
|
||||
this.currentDirection = Direction.random(random);
|
||||
this.accumulatedProgress = 0.0f;
|
||||
|
||||
// Initialize timers (will be set properly on first tick)
|
||||
this.lastDirectionChangeTick = 0;
|
||||
this.lastShockCheckTick = 0;
|
||||
this.lastKidnapperNotifyTick = 0;
|
||||
this.ticksUntilDirectionChange = randomDirectionChangeInterval();
|
||||
|
||||
// Initial state
|
||||
this.state = State.ACTIVE;
|
||||
this.shockStunTicksRemaining = 0;
|
||||
this.isHoldingCorrectKey = false;
|
||||
this.heldDirection = -1;
|
||||
}
|
||||
|
||||
// ==================== GETTERS ====================
|
||||
|
||||
public UUID getSessionId() {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
public UUID getPlayerId() {
|
||||
return playerId;
|
||||
}
|
||||
|
||||
public Direction getCurrentDirection() {
|
||||
return currentDirection;
|
||||
}
|
||||
|
||||
public int getCurrentDirectionIndex() {
|
||||
return currentDirection.getIndex();
|
||||
}
|
||||
|
||||
public int getCurrentResistance() {
|
||||
return currentResistance;
|
||||
}
|
||||
|
||||
public int getMaxResistance() {
|
||||
return maxResistance;
|
||||
}
|
||||
|
||||
public float getProgressPercentage() {
|
||||
if (maxResistance == 0) return 0.0f;
|
||||
return 1.0f - ((float) currentResistance / (float) maxResistance);
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public boolean isLocked() {
|
||||
return isLocked;
|
||||
}
|
||||
|
||||
public Integer getTargetSlot() {
|
||||
return targetSlot;
|
||||
}
|
||||
|
||||
public boolean isAccessoryStruggle() {
|
||||
return targetSlot != null;
|
||||
}
|
||||
|
||||
/** Get the V2 target region (null for V1 sessions). */
|
||||
@Nullable
|
||||
public BodyRegionV2 getTargetRegion() {
|
||||
return targetRegion;
|
||||
}
|
||||
|
||||
/** Set the V2 target region. */
|
||||
public void setTargetRegion(@Nullable BodyRegionV2 region) {
|
||||
this.targetRegion = region;
|
||||
}
|
||||
|
||||
/** Whether this is a V2 region-based struggle session. */
|
||||
public boolean isV2Struggle() {
|
||||
return targetRegion != null;
|
||||
}
|
||||
|
||||
/** Get the furniture entity ID (0 = not a furniture session). */
|
||||
public int getFurnitureEntityId() {
|
||||
return furnitureEntityId;
|
||||
}
|
||||
|
||||
/** Get the furniture seat ID (null = not a furniture session). */
|
||||
@Nullable
|
||||
public String getFurnitureSeatId() {
|
||||
return furnitureSeatId;
|
||||
}
|
||||
|
||||
/** Set furniture struggle context. */
|
||||
public void setFurnitureContext(int entityId, String seatId) {
|
||||
this.furnitureEntityId = entityId;
|
||||
this.furnitureSeatId = seatId;
|
||||
}
|
||||
|
||||
/** Whether this is a furniture escape struggle session. */
|
||||
public boolean isFurnitureStruggle() {
|
||||
return furnitureEntityId != 0 && furnitureSeatId != null;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return state == State.ACTIVE;
|
||||
}
|
||||
|
||||
public boolean isShocked() {
|
||||
return state == State.SHOCKED;
|
||||
}
|
||||
|
||||
public boolean isComplete() {
|
||||
return state == State.ESCAPED || state == State.CANCELLED;
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return System.currentTimeMillis() - createdAt > SESSION_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
public boolean isHoldingCorrectKey() {
|
||||
return isHoldingCorrectKey;
|
||||
}
|
||||
|
||||
public int getHeldDirection() {
|
||||
return heldDirection;
|
||||
}
|
||||
|
||||
// ==================== STATE UPDATES ====================
|
||||
|
||||
/**
|
||||
* Update the held direction from client input.
|
||||
*
|
||||
* @param direction Direction index (-1 if not holding any key)
|
||||
* @param isHolding True if player is actively holding the key
|
||||
*/
|
||||
public void updateHeldDirection(int direction, boolean isHolding) {
|
||||
this.heldDirection = direction;
|
||||
this.isHoldingCorrectKey =
|
||||
isHolding && (direction == currentDirection.getIndex());
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a server tick.
|
||||
*
|
||||
* @param currentTick Current game tick
|
||||
* @return True if state changed significantly (needs client update)
|
||||
*/
|
||||
public TickResult tick(long currentTick) {
|
||||
if (isComplete()) {
|
||||
return TickResult.NO_CHANGE;
|
||||
}
|
||||
|
||||
// Handle shock stun
|
||||
if (state == State.SHOCKED) {
|
||||
shockStunTicksRemaining--;
|
||||
if (shockStunTicksRemaining <= 0) {
|
||||
state = State.ACTIVE;
|
||||
return TickResult.SHOCK_END;
|
||||
}
|
||||
return TickResult.NO_CHANGE;
|
||||
}
|
||||
|
||||
boolean needsUpdate = false;
|
||||
TickResult result = TickResult.NO_CHANGE;
|
||||
|
||||
// Direction change check
|
||||
ticksUntilDirectionChange--;
|
||||
if (ticksUntilDirectionChange <= 0) {
|
||||
changeDirection(currentTick);
|
||||
result = TickResult.DIRECTION_CHANGE;
|
||||
}
|
||||
|
||||
// Progress resistance if holding correct key
|
||||
if (isHoldingCorrectKey) {
|
||||
accumulatedProgress += resistancePerTick;
|
||||
|
||||
if (accumulatedProgress >= 1.0f) {
|
||||
int resistanceReduced = (int) accumulatedProgress;
|
||||
accumulatedProgress -= resistanceReduced;
|
||||
currentResistance = Math.max(
|
||||
0,
|
||||
currentResistance - resistanceReduced
|
||||
);
|
||||
|
||||
if (result == TickResult.NO_CHANGE) {
|
||||
result = TickResult.RESISTANCE_UPDATE;
|
||||
}
|
||||
|
||||
// Check for escape
|
||||
if (currentResistance <= 0) {
|
||||
state = State.ESCAPED;
|
||||
return TickResult.ESCAPED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of a tick update.
|
||||
*/
|
||||
public enum TickResult {
|
||||
NO_CHANGE,
|
||||
DIRECTION_CHANGE,
|
||||
RESISTANCE_UPDATE,
|
||||
SHOCK_START,
|
||||
SHOCK_END,
|
||||
ESCAPED,
|
||||
KIDNAPPER_NOTIFY,
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if shock collar should trigger.
|
||||
*
|
||||
* @param currentTick Current game tick
|
||||
* @return True if shock should be triggered
|
||||
*/
|
||||
public boolean shouldTriggerShock(long currentTick) {
|
||||
if (state != State.ACTIVE) return false;
|
||||
if (
|
||||
currentTick - lastShockCheckTick < SHOCK_CHECK_INTERVAL_TICKS
|
||||
) return false;
|
||||
|
||||
lastShockCheckTick = currentTick;
|
||||
return random.nextInt(100) < SHOCK_PROBABILITY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a shock event.
|
||||
*/
|
||||
public void triggerShock() {
|
||||
if (state != State.ACTIVE) return;
|
||||
state = State.SHOCKED;
|
||||
shockStunTicksRemaining = SHOCK_STUN_TICKS;
|
||||
isHoldingCorrectKey = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if kidnappers should be notified.
|
||||
*
|
||||
* @param currentTick Current game tick
|
||||
* @return True if kidnappers should be notified
|
||||
*/
|
||||
public boolean shouldNotifyKidnappers(long currentTick) {
|
||||
if (state != State.ACTIVE || !isHoldingCorrectKey) return false;
|
||||
if (
|
||||
currentTick - lastKidnapperNotifyTick <
|
||||
KIDNAPPER_NOTIFY_INTERVAL_TICKS
|
||||
) return false;
|
||||
|
||||
lastKidnapperNotifyTick = currentTick;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if struggle sound should play.
|
||||
*
|
||||
* @param currentTick Current game tick
|
||||
* @return True if struggle sound should be played
|
||||
*/
|
||||
public boolean shouldPlayStruggleSound(long currentTick) {
|
||||
if (state != State.ACTIVE || !isHoldingCorrectKey) return false;
|
||||
if (
|
||||
currentTick - lastStruggleSoundTick < STRUGGLE_SOUND_INTERVAL_TICKS
|
||||
) return false;
|
||||
|
||||
lastStruggleSoundTick = currentTick;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the session.
|
||||
*/
|
||||
public void cancel() {
|
||||
state = State.CANCELLED;
|
||||
}
|
||||
|
||||
// ==================== PRIVATE HELPERS ====================
|
||||
|
||||
private void changeDirection(long currentTick) {
|
||||
Direction newDirection;
|
||||
do {
|
||||
newDirection = Direction.random(random);
|
||||
} while (newDirection == currentDirection);
|
||||
|
||||
currentDirection = newDirection;
|
||||
lastDirectionChangeTick = currentTick;
|
||||
ticksUntilDirectionChange = randomDirectionChangeInterval();
|
||||
|
||||
// LOW FIX: Always reset held key status to prevent lucky guess exploit
|
||||
// Player must release and re-press the key to react to direction change
|
||||
// even if they were already holding the new correct key
|
||||
isHoldingCorrectKey = false;
|
||||
}
|
||||
|
||||
private int randomDirectionChangeInterval() {
|
||||
return (
|
||||
DIRECTION_CHANGE_MIN_TICKS +
|
||||
random.nextInt(
|
||||
DIRECTION_CHANGE_MAX_TICKS - DIRECTION_CHANGE_MIN_TICKS + 1
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"ContinuousStruggleMiniGameState{session=%s, player=%s, dir=%s, resistance=%d/%d, state=%s}",
|
||||
sessionId.toString().substring(0, 8),
|
||||
playerId.toString().substring(0, 8),
|
||||
currentDirection,
|
||||
currentResistance,
|
||||
maxResistance,
|
||||
state
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user