Polish V2 subsystem: lockpick kinds, package boundaries, client extractions
Architectural debt cleanup on top of the earlier V2 hardening pass.
Minigame:
- LockpickMiniGameState splits the overloaded targetSlot int into a
LockpickTargetKind enum + targetData int. Body-vs-furniture
dispatch is now a simple enum check; the NBT-tag nonce it
previously depended on is gone, along with the AIOOBE risk at
BodyRegionV2.values()[targetSlot].
- PacketLockpickAttempt.handleFurnitureLockpickSuccess takes the
entity and seat id as explicit parameters. Caller pre-validates
both before any side effect, so a corrupted ctx tag can no longer
produce a "Lock picked!" UI with a used lockpick and nothing
unlocked.
Package boundaries:
- client.gltf no longer imports v2.bondage. Render-layer attachment,
DataDrivenItemReloadListener, and GlbValidationReloadListener all
live in v2.client.V2ClientSetup.
- GlbValidationReloadListener moved to v2.bondage.client.diagnostic.
- Reload-listener ordering is preserved via EventPriority (HIGH for
the generic GLB cache clear in GltfClientSetup, LOW for bondage
consumers in V2ClientSetup).
- Removed the unused validateAgainstDefinition stub on GlbValidator.
Extractions from EntityFurniture:
- FurnitureSeatSyncCodec (pipe/semicolon serialization for the
SEAT_ASSIGNMENTS_SYNC entity data field), with 8 unit tests.
- FurnitureClientAnimator (client-only seat-pose kickoff, moved out
of the dual-side entity class).
- EntityFurniture drops ~100 lines with no behavior change.
Interface docs:
- ISeatProvider Javadoc narrowed to reflect that EntityFurniture is
the only implementation; callers that need animation state or
definition reference still downcast.
- FurnitureAuthPredicate.findOccupant uses the interface only.
- AnimationIdBuilder flagged as legacy JSON-era utility (NPC
fallback + MCA mixin).
Artist guide: corrected the "Monster Seat System (Planned)" section
to match the ISeatProvider single-impl reality.
This commit is contained in:
@@ -6,6 +6,7 @@ import com.tiedup.remake.items.base.ILockable;
|
||||
import com.tiedup.remake.minigame.LockpickMiniGameState;
|
||||
import com.tiedup.remake.minigame.LockpickMiniGameState.PickAttemptResult;
|
||||
import com.tiedup.remake.minigame.LockpickSessionManager;
|
||||
import com.tiedup.remake.minigame.LockpickTargetKind;
|
||||
import com.tiedup.remake.network.ModNetwork;
|
||||
import com.tiedup.remake.network.PacketRateLimiter;
|
||||
import com.tiedup.remake.network.sync.SyncManager;
|
||||
@@ -115,41 +116,40 @@ public class PacketLockpickAttempt {
|
||||
ServerPlayer player,
|
||||
LockpickMiniGameState session
|
||||
) {
|
||||
// Furniture seat lockpick path: presence of furniture_id AND a
|
||||
// session_id matching the current session. A ctx without the nonce
|
||||
// (or with a foreign nonce) is rejected — this is the branch a
|
||||
// stale-ctx bug could otherwise mis-route into.
|
||||
CompoundTag furnitureCtx = player
|
||||
.getPersistentData()
|
||||
.getCompound("tiedup_furniture_lockpick_ctx");
|
||||
boolean ctxValid =
|
||||
furnitureCtx != null
|
||||
&& furnitureCtx.contains("furniture_id")
|
||||
&& furnitureCtx.hasUUID("session_id")
|
||||
&& furnitureCtx.getUUID("session_id").equals(session.getSessionId());
|
||||
if (ctxValid) {
|
||||
// Distance check BEFORE endLockpickSession — consuming a
|
||||
// session without applying the reward (player walked away)
|
||||
// would burn the session with no visible effect.
|
||||
int furnitureId = furnitureCtx.getInt("furniture_id");
|
||||
Entity furnitureEntity = player.level().getEntity(furnitureId);
|
||||
if (session.getTargetKind() == LockpickTargetKind.FURNITURE_SEAT) {
|
||||
// Validate EVERY input before any side effect. Consuming the
|
||||
// session and damaging the lockpick before verifying that the
|
||||
// unlock will succeed would show "Lock picked!" to the player
|
||||
// while nothing actually unlocks.
|
||||
Entity furnitureEntity = player.level().getEntity(session.getTargetData());
|
||||
if (
|
||||
furnitureEntity == null ||
|
||||
player.distanceTo(furnitureEntity) > 10.0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
CompoundTag furnitureCtx = player
|
||||
.getPersistentData()
|
||||
.getCompound("tiedup_furniture_lockpick_ctx");
|
||||
String seatId = furnitureCtx.contains("seat_id", net.minecraft.nbt.Tag.TAG_STRING)
|
||||
? furnitureCtx.getString("seat_id")
|
||||
: "";
|
||||
if (seatId.isEmpty()) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[PacketLockpickAttempt] Furniture lockpick ctx missing seat_id for {} — aborting without consuming session",
|
||||
player.getName().getString()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Session validated — now end it
|
||||
LockpickSessionManager.getInstance().endLockpickSession(
|
||||
player.getUUID(),
|
||||
true
|
||||
);
|
||||
handleFurnitureLockpickSuccess(player, furnitureCtx);
|
||||
handleFurnitureLockpickSuccess(player, furnitureEntity, seatId);
|
||||
player.getPersistentData().remove("tiedup_furniture_lockpick_ctx");
|
||||
damageLockpick(player);
|
||||
|
||||
// Send result to client
|
||||
ModNetwork.sendToPlayer(
|
||||
new PacketLockpickMiniGameResult(
|
||||
session.getSessionId(),
|
||||
@@ -167,9 +167,8 @@ public class PacketLockpickAttempt {
|
||||
true
|
||||
);
|
||||
|
||||
// Body item lockpick path: targetSlot stores BodyRegionV2 ordinal
|
||||
BodyRegionV2 targetRegion =
|
||||
BodyRegionV2.values()[session.getTargetSlot()];
|
||||
BodyRegionV2.values()[session.getTargetData()];
|
||||
ItemStack targetStack = V2EquipmentHelper.getInRegion(
|
||||
player,
|
||||
targetRegion
|
||||
@@ -212,24 +211,21 @@ public class PacketLockpickAttempt {
|
||||
/**
|
||||
* Handle a successful furniture seat lockpick: unlock the seat, dismount
|
||||
* the passenger, play the unlock sound, and broadcast the updated state.
|
||||
* Caller is responsible for validating inputs before firing side effects.
|
||||
*/
|
||||
private void handleFurnitureLockpickSuccess(
|
||||
ServerPlayer player,
|
||||
CompoundTag ctx
|
||||
Entity furnitureEntity,
|
||||
String seatId
|
||||
) {
|
||||
int furnitureEntityId = ctx.getInt("furniture_id");
|
||||
String seatId = ctx.getString("seat_id");
|
||||
|
||||
Entity entity = player.level().getEntity(furnitureEntityId);
|
||||
if (!(entity instanceof EntityFurniture furniture)) {
|
||||
if (!(furnitureEntity instanceof EntityFurniture furniture)) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[PacketLockpickAttempt] Furniture entity {} not found or wrong type for lockpick success",
|
||||
furnitureEntityId
|
||||
"[PacketLockpickAttempt] Lockpick target {} is not an EntityFurniture",
|
||||
furnitureEntity.getId()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Unlock the seat
|
||||
furniture.setSeatLocked(seatId, false);
|
||||
|
||||
// Dismount the passenger in that seat
|
||||
@@ -244,16 +240,15 @@ public class PacketLockpickAttempt {
|
||||
passenger.stopRiding();
|
||||
}
|
||||
|
||||
// Play unlock sound from the furniture definition
|
||||
FurnitureDefinition def = furniture.getDefinition();
|
||||
if (def != null && def.feedback().unlockSound() != null) {
|
||||
player
|
||||
.level()
|
||||
.playSound(
|
||||
null,
|
||||
entity.getX(),
|
||||
entity.getY(),
|
||||
entity.getZ(),
|
||||
furniture.getX(),
|
||||
furniture.getY(),
|
||||
furniture.getZ(),
|
||||
SoundEvent.createVariableRangeEvent(
|
||||
def.feedback().unlockSound()
|
||||
),
|
||||
@@ -263,13 +258,12 @@ public class PacketLockpickAttempt {
|
||||
);
|
||||
}
|
||||
|
||||
// Broadcast updated lock/anim state to all tracking clients
|
||||
PacketSyncFurnitureState.sendToTracking(furniture);
|
||||
|
||||
TiedUpMod.LOGGER.info(
|
||||
"[PacketLockpickAttempt] Player {} picked furniture lock on entity {} seat '{}'",
|
||||
player.getName().getString(),
|
||||
furnitureEntityId,
|
||||
furniture.getId(),
|
||||
seatId
|
||||
);
|
||||
}
|
||||
@@ -330,18 +324,13 @@ public class PacketLockpickAttempt {
|
||||
// Jam mechanic (5%) only applies to body-item sessions — seat locks
|
||||
// have no ILockable stack to jam.
|
||||
boolean jammed = false;
|
||||
CompoundTag sessionCtx = player
|
||||
.getPersistentData()
|
||||
.getCompound("tiedup_furniture_lockpick_ctx");
|
||||
boolean isFurnitureSession =
|
||||
sessionCtx.contains("furniture_id")
|
||||
&& sessionCtx.hasUUID("session_id")
|
||||
&& sessionCtx.getUUID("session_id").equals(session.getSessionId());
|
||||
boolean isBodySession =
|
||||
session.getTargetKind() == LockpickTargetKind.BODY_REGION;
|
||||
|
||||
if (!isFurnitureSession && player.getRandom().nextFloat() < 0.05f) {
|
||||
int targetSlot = session.getTargetSlot();
|
||||
if (targetSlot >= 0 && targetSlot < BodyRegionV2.values().length) {
|
||||
BodyRegionV2 targetRegion = BodyRegionV2.values()[targetSlot];
|
||||
if (isBodySession && player.getRandom().nextFloat() < 0.05f) {
|
||||
int targetOrdinal = session.getTargetData();
|
||||
if (targetOrdinal >= 0 && targetOrdinal < BodyRegionV2.values().length) {
|
||||
BodyRegionV2 targetRegion = BodyRegionV2.values()[targetOrdinal];
|
||||
ItemStack targetStack = V2EquipmentHelper.getInRegion(
|
||||
player,
|
||||
targetRegion
|
||||
|
||||
@@ -122,6 +122,7 @@ public class PacketLockpickMiniGameStart {
|
||||
LockpickSessionManager manager = LockpickSessionManager.getInstance();
|
||||
LockpickMiniGameState session = manager.startLockpickSession(
|
||||
player,
|
||||
com.tiedup.remake.minigame.LockpickTargetKind.BODY_REGION,
|
||||
targetRegion.ordinal(),
|
||||
sweetSpotWidth
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user