E1: Initialize currentResistance in NBT at equip time from
ResistanceComponent — eliminates MAX-scan fallback bug
E2: BuiltInLockComponent for organic items (already committed)
E3: canStruggle refactor — new model:
- ARMS: always struggle-able (no lock gating)
- Non-ARMS: only if locked OR built-in lock
- Removed dead isItemLocked() from StruggleState + overrides
E4: canUnequip already handled by BuiltInLockComponent.blocksUnequip()
via ComponentHolder delegation
E5: Help/assist mechanic deferred (needs UI design)
E6: Removed lock resistance from ILockable (5 methods + NBT key deleted)
- GenericKnife: new knifeCutProgress NBT for cutting locks
- StruggleAccessory: accessoryStruggleResistance NBT replaces lock resistance
- PacketV2StruggleStart: uses config-based padlock resistance
- All lock/unlock packets cleaned of initializeLockResistance/clearLockResistance
E7: Fixed 3 pre-existing bugs:
- B2: DataDrivenItemRegistry.clear() synchronized on RELOAD_LOCK
- B3: V2TyingPlayerTask validates heldStack before equip (prevents duplication)
- B5: EntityKidnapperMerchant.remove() cleans playerToMerchant map (memory leak)
224 lines
7.0 KiB
Java
224 lines
7.0 KiB
Java
package com.tiedup.remake.state.struggle;
|
|
|
|
import com.tiedup.remake.core.SettingsAccessor;
|
|
import com.tiedup.remake.core.SystemMessageManager;
|
|
import com.tiedup.remake.core.SystemMessageManager.MessageCategory;
|
|
import com.tiedup.remake.core.TiedUpMod;
|
|
import com.tiedup.remake.state.PlayerBindState;
|
|
import com.tiedup.remake.util.time.Timer;
|
|
import net.minecraft.world.entity.player.Player;
|
|
import net.minecraft.world.level.GameRules;
|
|
|
|
/**
|
|
*
|
|
* Handles the logic for players/NPCs struggling against restraints:
|
|
* - Cooldown timer between attempts
|
|
* - Probability-based success (dice roll)
|
|
* - Resistance decrease on success
|
|
* - Automatic removal when resistance reaches 0
|
|
*
|
|
* v2.5: This is now used for NPC struggle only.
|
|
* Players use the continuous struggle mini-game (ContinuousStruggleMiniGameState).
|
|
* Knife bonuses have been removed - knives now work by active cutting.
|
|
*
|
|
* Architecture:
|
|
* - StruggleState (abstract base)
|
|
* ├─ StruggleBinds (struggle against bind restraints)
|
|
* └─ StruggleCollar (struggle against collar/ownership)
|
|
*/
|
|
public abstract class StruggleState {
|
|
|
|
/**
|
|
* Cooldown timer for struggle attempts.
|
|
* Reset after each struggle attempt.
|
|
* Volatile for thread safety across network/tick threads.
|
|
*/
|
|
protected volatile Timer struggleCooldownTimer;
|
|
|
|
/**
|
|
* Main struggle logic (dice roll based).
|
|
* Used for NPC struggle - players use QTE mini-game instead.
|
|
*
|
|
* Flow:
|
|
* 1. Check if struggle is enabled (GameRule)
|
|
* 2. Check if item can be struggled out of
|
|
* 3. Check cooldown timer
|
|
* 4. Roll dice for success (probability %)
|
|
* 5. If success: decrease resistance by random amount
|
|
* 6. If resistance <= 0: call successAction()
|
|
* 7. Set cooldown for next attempt
|
|
*
|
|
* @param state The player's bind state
|
|
*/
|
|
public synchronized void struggle(PlayerBindState state) {
|
|
Player player = state.getPlayer();
|
|
if (player == null || player.level() == null) {
|
|
return;
|
|
}
|
|
|
|
GameRules gameRules = player.level().getGameRules();
|
|
|
|
// 1. Check if struggle system is enabled
|
|
if (!SettingsAccessor.isStruggleEnabled(gameRules)) {
|
|
return;
|
|
}
|
|
|
|
// 2. Check if item can be struggled out of (not locked or forced)
|
|
if (!canStruggle(state)) {
|
|
return;
|
|
}
|
|
|
|
// 3. Check cooldown timer (prevent spamming)
|
|
if (
|
|
struggleCooldownTimer != null && !struggleCooldownTimer.isExpired()
|
|
) {
|
|
TiedUpMod.LOGGER.debug(
|
|
"[STRUGGLE] Cooldown not expired yet - ignoring struggle attempt"
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Start struggle animation (after cooldown check passes)
|
|
if (!state.isStruggling()) {
|
|
state.setStruggling(true, player.level().getGameTime());
|
|
com.tiedup.remake.network.sync.SyncManager.syncStruggleState(
|
|
player
|
|
);
|
|
}
|
|
|
|
if (!onAttempt(state)) {
|
|
return; // Interrupted by pain
|
|
}
|
|
|
|
// 4. Roll for success
|
|
int probability = SettingsAccessor.getProbabilityStruggle(gameRules);
|
|
int roll = player.getRandom().nextInt(100) + 1;
|
|
boolean success = roll <= probability;
|
|
|
|
if (success) {
|
|
// Calculate resistance decrease
|
|
int currentResistance = getResistanceState(state);
|
|
int minDecrease = SettingsAccessor.getStruggleMinDecrease(
|
|
gameRules
|
|
);
|
|
int maxDecrease = SettingsAccessor.getStruggleMaxDecrease(
|
|
gameRules
|
|
);
|
|
|
|
int decrease =
|
|
minDecrease +
|
|
player.getRandom().nextInt(maxDecrease - minDecrease + 1);
|
|
|
|
int newResistance = Math.max(0, currentResistance - decrease);
|
|
setResistanceState(state, newResistance);
|
|
|
|
// Feedback
|
|
notifySuccess(player, newResistance);
|
|
|
|
// Check for escape
|
|
if (newResistance <= 0) {
|
|
TiedUpMod.LOGGER.debug(
|
|
"[STRUGGLE] {} broke free! (resistance reached 0)",
|
|
player.getName().getString()
|
|
);
|
|
successAction(state);
|
|
}
|
|
} else {
|
|
notifyFailure(player);
|
|
}
|
|
|
|
// 5. Set cooldown
|
|
int cooldownTicks = SettingsAccessor.getStruggleTimer(gameRules);
|
|
struggleCooldownTimer = new Timer(cooldownTicks / 20, player.level());
|
|
}
|
|
|
|
/**
|
|
* Get the message category for successful struggle attempts.
|
|
*/
|
|
protected abstract MessageCategory getSuccessCategory();
|
|
|
|
/**
|
|
* Get the message category for failed struggle attempts.
|
|
*/
|
|
protected abstract MessageCategory getFailCategory();
|
|
|
|
/**
|
|
* Send success notification to the player.
|
|
*
|
|
* @param player The player to notify
|
|
* @param newResistance The new resistance value after decrease
|
|
*/
|
|
protected void notifySuccess(Player player, int newResistance) {
|
|
SystemMessageManager.sendWithResistance(
|
|
player,
|
|
getSuccessCategory(),
|
|
newResistance
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Send failure notification to the player.
|
|
*
|
|
* @param player The player to notify
|
|
*/
|
|
protected void notifyFailure(Player player) {
|
|
SystemMessageManager.sendToPlayer(player, getFailCategory());
|
|
}
|
|
|
|
/**
|
|
* Called when the player successfully struggles free (resistance reaches 0).
|
|
* Subclasses implement this to handle the escape action.
|
|
*
|
|
* @param state The player's bind state
|
|
*/
|
|
protected abstract void successAction(PlayerBindState state);
|
|
|
|
/**
|
|
* Called when a struggle attempt is performed (regardless of success/fail).
|
|
* Can be used for side effects like random shocks.
|
|
*
|
|
* @param state The player's bind state
|
|
* @return true to proceed with struggle, false to interrupt
|
|
*/
|
|
protected boolean onAttempt(PlayerBindState state) {
|
|
return true; // Default: proceed
|
|
}
|
|
|
|
/**
|
|
* Get the current resistance value from the player's state.
|
|
*
|
|
* @param state The player's bind state
|
|
* @return Current resistance value
|
|
*/
|
|
protected abstract int getResistanceState(PlayerBindState state);
|
|
|
|
/**
|
|
* Set the current resistance value in the player's state.
|
|
*
|
|
* @param state The player's bind state
|
|
* @param resistance The new resistance value
|
|
*/
|
|
protected abstract void setResistanceState(
|
|
PlayerBindState state,
|
|
int resistance
|
|
);
|
|
|
|
/**
|
|
* Check if the player can struggle.
|
|
* Returns false if the item cannot be struggled out of.
|
|
*
|
|
* @param state The player's bind state
|
|
* @return True if struggling is allowed
|
|
*/
|
|
protected abstract boolean canStruggle(PlayerBindState state);
|
|
|
|
/**
|
|
* Check if debug logging is enabled.
|
|
*
|
|
* @return True if debug logging should be printed
|
|
*/
|
|
protected boolean isDebugEnabled() {
|
|
return true;
|
|
}
|
|
}
|