feat(D-01/A): V2-aware struggle system (A9)

StruggleBinds:
- canStruggle(): instanceof ItemBind → BindModeHelper.isBindItem()
- isItemLocked(): instanceof ItemBind → instanceof ILockable (fixes R4)
- onAttempt(): instanceof ItemShockCollar → CollarHelper.canShock() (fixes R5)
- tighten(): reads ResistanceComponent directly for V2, avoids MAX scan bug

StruggleCollar:
- getResistanceState/setResistanceState: instanceof ItemCollar → IHasResistance
- canStruggle(): instanceof ItemCollar → CollarHelper.isCollar() + ILockable
- onAttempt(): shock check via CollarHelper.canShock()
- successAction(): unlock via ILockable
- tighten(): resistance via IHasResistance

All V1 items continue working through the same interfaces they already implement.
This commit is contained in:
NotEvil
2026-04-14 15:47:20 +02:00
parent 737a4fd59b
commit 19cc69985d
2 changed files with 103 additions and 113 deletions

View File

@@ -4,12 +4,18 @@ import com.tiedup.remake.core.SettingsAccessor;
import com.tiedup.remake.core.SystemMessageManager; import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.core.SystemMessageManager.MessageCategory; import com.tiedup.remake.core.SystemMessageManager.MessageCategory;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.IHasResistance;
import com.tiedup.remake.items.base.ILockable; import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemBind; import com.tiedup.remake.items.base.ItemBind;
import com.tiedup.remake.state.IPlayerLeashAccess; import com.tiedup.remake.state.IPlayerLeashAccess;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.BindModeHelper;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import com.tiedup.remake.v2.bondage.component.ComponentType;
import com.tiedup.remake.v2.bondage.component.ResistanceComponent;
import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem;
import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
@@ -75,16 +81,17 @@ public class StruggleBinds extends StruggleState {
player, player,
BodyRegionV2.ARMS BodyRegionV2.ARMS
); );
if ( if (bindStack.isEmpty() || !BindModeHelper.isBindItem(bindStack)) {
bindStack.isEmpty() ||
!(bindStack.getItem() instanceof ItemBind bind)
) {
return false; return false;
} }
// The locked check has been moved to struggle() where decrease is reduced // The locked check has been moved to struggle() where decrease is reduced
return bind.canBeStruggledOut(bindStack); // Check canBeStruggledOut — works for both V1 and V2 via IHasResistance
if (bindStack.getItem() instanceof IHasResistance resistance) {
return resistance.canBeStruggledOut(bindStack);
}
return true;
} }
/** /**
@@ -103,14 +110,13 @@ public class StruggleBinds extends StruggleState {
player, player,
BodyRegionV2.ARMS BodyRegionV2.ARMS
); );
if ( if (bindStack.isEmpty()) return false;
bindStack.isEmpty() ||
!(bindStack.getItem() instanceof ItemBind bind)
) {
return false;
}
return bind.isLocked(bindStack); // Works for both V1 (ItemBind) and V2 (DataDrivenBondageItem) via ILockable
if (bindStack.getItem() instanceof ILockable lockable) {
return lockable.isLocked(bindStack);
}
return false;
} }
/** /**
@@ -148,14 +154,18 @@ public class StruggleBinds extends StruggleState {
BodyRegionV2.NECK BodyRegionV2.NECK
); );
if ( if (!collar.isEmpty() && CollarHelper.canShock(collar)) {
!collar.isEmpty() && // V1 shock collar
collar.getItem() instanceof if (collar.getItem() instanceof com.tiedup.remake.items.ItemShockCollar shockCollar) {
com.tiedup.remake.items.ItemShockCollar shockCollar return shockCollar.notifyStruggle(player, collar);
) { }
return shockCollar.notifyStruggle(player, collar); // V2 shock collarnotify via IHasResistance if available
if (collar.getItem() instanceof IHasResistance resistance) {
resistance.notifyStruggle(player);
}
return true;
} }
return true; // No collar, proceed normally return true; // No shock collar, proceed normally
} }
/** /**
@@ -317,18 +327,23 @@ public class StruggleBinds extends StruggleState {
target, target,
BodyRegionV2.ARMS BodyRegionV2.ARMS
); );
if ( if (bindStack.isEmpty() || !BindModeHelper.isBindItem(bindStack)) {
bindStack.isEmpty() ||
!(bindStack.getItem() instanceof ItemBind bind)
) {
return; return;
} }
// Get base resistance from config (BUG-003 fix: was using ModGameRules which // Get base resistance: V2 reads from ResistanceComponent directly,
// only knew 4 types and returned hardcoded 100 for the other 10) // V1 reads from SettingsAccessor via item name (BUG-003 fix)
int baseResistance = SettingsAccessor.getBindResistance( int baseResistance;
bind.getItemName() ResistanceComponent comp = DataDrivenBondageItem.getComponent(
bindStack, ComponentType.RESISTANCE, ResistanceComponent.class
); );
if (comp != null) {
baseResistance = comp.getBaseResistance();
} else if (bindStack.getItem() instanceof ItemBind bind) {
baseResistance = SettingsAccessor.getBindResistance(bind.getItemName());
} else {
baseResistance = 100;
}
// Set current resistance to base (full restore) // Set current resistance to base (full restore)
setResistanceState(state, baseResistance); setResistanceState(state, baseResistance);

View File

@@ -2,9 +2,12 @@ package com.tiedup.remake.state.struggle;
import com.tiedup.remake.core.SystemMessageManager.MessageCategory; import com.tiedup.remake.core.SystemMessageManager.MessageCategory;
import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.items.base.IHasResistance;
import com.tiedup.remake.items.base.ILockable;
import com.tiedup.remake.items.base.ItemCollar; import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.state.PlayerBindState; import com.tiedup.remake.state.PlayerBindState;
import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.v2.bondage.CollarHelper;
import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
@@ -44,19 +47,14 @@ public class StruggleCollar extends StruggleState {
@Override @Override
protected int getResistanceState(PlayerBindState state) { protected int getResistanceState(PlayerBindState state) {
Player player = state.getPlayer(); Player player = state.getPlayer();
ItemStack collar = V2EquipmentHelper.getInRegion( ItemStack collar = V2EquipmentHelper.getInRegion(player, BodyRegionV2.NECK);
player,
BodyRegionV2.NECK
);
if ( if (collar.isEmpty() || !CollarHelper.isCollar(collar)) return 0;
collar.isEmpty() ||
!(collar.getItem() instanceof ItemCollar collarItem) if (collar.getItem() instanceof IHasResistance resistance) {
) { return resistance.getCurrentResistance(collar, player);
return 0;
} }
return 0;
return collarItem.getCurrentResistance(collar, player);
} }
/** /**
@@ -68,19 +66,13 @@ public class StruggleCollar extends StruggleState {
@Override @Override
protected void setResistanceState(PlayerBindState state, int resistance) { protected void setResistanceState(PlayerBindState state, int resistance) {
Player player = state.getPlayer(); Player player = state.getPlayer();
ItemStack collar = V2EquipmentHelper.getInRegion( ItemStack collar = V2EquipmentHelper.getInRegion(player, BodyRegionV2.NECK);
player,
BodyRegionV2.NECK
);
if ( if (collar.isEmpty() || !CollarHelper.isCollar(collar)) return;
collar.isEmpty() ||
!(collar.getItem() instanceof ItemCollar collarItem) if (collar.getItem() instanceof IHasResistance resistanceItem) {
) { resistanceItem.setCurrentResistance(collar, resistance);
return;
} }
collarItem.setCurrentResistance(collar, resistance);
} }
/** /**
@@ -104,31 +96,29 @@ public class StruggleCollar extends StruggleState {
return false; return false;
} }
ItemStack collar = V2EquipmentHelper.getInRegion( ItemStack collar = V2EquipmentHelper.getInRegion(player, BodyRegionV2.NECK);
player,
BodyRegionV2.NECK
);
if ( if (collar.isEmpty() || !CollarHelper.isCollar(collar)) {
collar.isEmpty() ||
!(collar.getItem() instanceof ItemCollar collarItem)
) {
TiedUpMod.LOGGER.debug("[StruggleCollar] No collar equipped"); TiedUpMod.LOGGER.debug("[StruggleCollar] No collar equipped");
return false; return false;
} }
// Check if locked // Check if locked (works for V1 and V2 via ILockable)
if (!collarItem.isLocked(collar)) { if (collar.getItem() instanceof ILockable lockable) {
TiedUpMod.LOGGER.debug("[StruggleCollar] Collar is not locked"); if (!lockable.isLocked(collar)) {
TiedUpMod.LOGGER.debug("[StruggleCollar] Collar is not locked");
return false;
}
} else {
return false; return false;
} }
// Check if struggle is enabled // Check if struggle is enabled (works for V1 and V2 via IHasResistance)
if (!collarItem.canBeStruggledOut(collar)) { if (collar.getItem() instanceof IHasResistance resistance) {
TiedUpMod.LOGGER.debug( if (!resistance.canBeStruggledOut(collar)) {
"[StruggleCollar] Collar struggle is disabled" TiedUpMod.LOGGER.debug("[StruggleCollar] Collar struggle is disabled");
); return false;
return false; }
} }
return true; return true;
@@ -141,17 +131,17 @@ public class StruggleCollar extends StruggleState {
@Override @Override
protected boolean onAttempt(PlayerBindState state) { protected boolean onAttempt(PlayerBindState state) {
Player player = state.getPlayer(); Player player = state.getPlayer();
ItemStack collar = V2EquipmentHelper.getInRegion( ItemStack collar = V2EquipmentHelper.getInRegion(player, BodyRegionV2.NECK);
player,
BodyRegionV2.NECK
);
if ( if (!collar.isEmpty() && CollarHelper.canShock(collar)) {
!collar.isEmpty() && // V1 shock collar
collar.getItem() instanceof if (collar.getItem() instanceof com.tiedup.remake.items.ItemShockCollar shockCollar) {
com.tiedup.remake.items.ItemShockCollar shockCollar return shockCollar.notifyStruggle(player, collar);
) { }
return shockCollar.notifyStruggle(player, collar); // V2 shock collarnotify via IHasResistance
if (collar.getItem() instanceof IHasResistance resistance) {
resistance.notifyStruggle(player);
}
} }
return true; return true;
} }
@@ -167,31 +157,19 @@ public class StruggleCollar extends StruggleState {
@Override @Override
protected void successAction(PlayerBindState state) { protected void successAction(PlayerBindState state) {
Player player = state.getPlayer(); Player player = state.getPlayer();
ItemStack collar = V2EquipmentHelper.getInRegion( ItemStack collar = V2EquipmentHelper.getInRegion(player, BodyRegionV2.NECK);
player,
BodyRegionV2.NECK
);
if ( if (collar.isEmpty() || !CollarHelper.isCollar(collar)) {
collar.isEmpty() || TiedUpMod.LOGGER.warn("[StruggleCollar] successAction called but no collar equipped");
!(collar.getItem() instanceof ItemCollar collarItem)
) {
TiedUpMod.LOGGER.warn(
"[StruggleCollar] successAction called but no collar equipped"
);
return; return;
} }
// Unlock the collar // Unlock the collar (works for V1 and V2 via ILockable)
collarItem.setLocked(collar, false); if (collar.getItem() instanceof ILockable lockable) {
lockable.setLocked(collar, false);
}
TiedUpMod.LOGGER.info( TiedUpMod.LOGGER.info("[StruggleCollar] {} unlocked their collar!", player.getName().getString());
"[StruggleCollar] {} unlocked their collar!",
player.getName().getString()
);
// Note: Collar is NOT removed, just unlocked
// Player can now manually remove it
} }
@Override @Override
@@ -230,30 +208,27 @@ public class StruggleCollar extends StruggleState {
return; return;
} }
ItemStack collar = V2EquipmentHelper.getInRegion( ItemStack collar = V2EquipmentHelper.getInRegion(target, BodyRegionV2.NECK);
target,
BodyRegionV2.NECK
);
if ( if (collar.isEmpty() || !CollarHelper.isCollar(collar)) {
collar.isEmpty() ||
!(collar.getItem() instanceof ItemCollar collarItem)
) {
TiedUpMod.LOGGER.debug("[StruggleCollar] No collar to tighten"); TiedUpMod.LOGGER.debug("[StruggleCollar] No collar to tighten");
return; return;
} }
// Check if collar is locked // Check if collar is locked (V1 and V2 via ILockable)
if (!collarItem.isLocked(collar)) { if (collar.getItem() instanceof ILockable lockable) {
TiedUpMod.LOGGER.debug( if (!lockable.isLocked(collar)) {
"[StruggleCollar] Collar must be locked to tighten" TiedUpMod.LOGGER.debug("[StruggleCollar] Collar must be locked to tighten");
); return;
}
} else {
return; return;
} }
// Get base resistance from GameRules // Get resistance (V1 and V2 via IHasResistance)
int baseResistance = collarItem.getBaseResistance(target); if (!(collar.getItem() instanceof IHasResistance resistanceItem)) return;
int currentResistance = collarItem.getCurrentResistance(collar, target); int baseResistance = resistanceItem.getBaseResistance(target);
int currentResistance = resistanceItem.getCurrentResistance(collar, target);
// Only tighten if current resistance is lower than base // Only tighten if current resistance is lower than base
if (currentResistance >= baseResistance) { if (currentResistance >= baseResistance) {
@@ -264,7 +239,7 @@ public class StruggleCollar extends StruggleState {
} }
// Restore to base resistance // Restore to base resistance
collarItem.setCurrentResistance(collar, baseResistance); resistanceItem.setCurrentResistance(collar, baseResistance);
TiedUpMod.LOGGER.info( TiedUpMod.LOGGER.info(
"[StruggleCollar] {} tightened {}'s collar (resistance {} -> {})", "[StruggleCollar] {} tightened {}'s collar (resistance {} -> {})",