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,672 @@
|
||||
package com.tiedup.remake.events.restriction;
|
||||
|
||||
import com.tiedup.remake.core.TiedUpMod;
|
||||
import com.tiedup.remake.v2.BodyRegionV2;
|
||||
import com.tiedup.remake.entities.EntityKidnapper;
|
||||
import com.tiedup.remake.items.base.ItemCollar;
|
||||
import com.tiedup.remake.minigame.StruggleSessionManager;
|
||||
import com.tiedup.remake.network.ModNetwork;
|
||||
import com.tiedup.remake.network.personality.PacketSlaveBeingFreed;
|
||||
import com.tiedup.remake.state.IBondageState;
|
||||
import com.tiedup.remake.state.PlayerBindState;
|
||||
import com.tiedup.remake.tasks.ForceFeedingTask;
|
||||
import com.tiedup.remake.tasks.TimedInteractTask;
|
||||
import com.tiedup.remake.tasks.UntyingPlayerTask;
|
||||
import com.tiedup.remake.tasks.UntyingTask;
|
||||
import com.tiedup.remake.util.GameConstants;
|
||||
import com.tiedup.remake.util.KidnappedHelper;
|
||||
import com.tiedup.remake.core.SettingsAccessor;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.InteractionHand;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
/**
|
||||
* Tick handler for restraint-related tasks (untying, tying, force feeding).
|
||||
*
|
||||
* Manages progress-based interaction tasks that span multiple ticks:
|
||||
* - Untying mechanic (empty hand right-click on tied entity)
|
||||
* - Tying mechanic (tick progression)
|
||||
* - Force feeding mechanic (food right-click on gagged entity)
|
||||
* - Auto-shock collar checks
|
||||
* - Struggle auto-stop (legacy QTE fallback)
|
||||
*
|
||||
* @see BondageItemRestrictionHandler for movement, interaction, and eating restrictions
|
||||
*/
|
||||
@Mod.EventBusSubscriber(
|
||||
modid = TiedUpMod.MOD_ID,
|
||||
bus = Mod.EventBusSubscriber.Bus.FORGE
|
||||
)
|
||||
public class RestraintTaskTickHandler {
|
||||
|
||||
// ========== PLAYER-SPECIFIC TICK ==========
|
||||
|
||||
/**
|
||||
* Handle player tick event for player-specific features.
|
||||
* - Auto-shock collar check (throttled to every N ticks)
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onPlayerTick(TickEvent.PlayerTickEvent event) {
|
||||
if (event.side.isClient() || event.phase != TickEvent.Phase.END) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = event.player;
|
||||
PlayerBindState playerState = PlayerBindState.getInstance(player);
|
||||
|
||||
// Check if struggle animation should stop
|
||||
// For continuous struggle: animation is managed by MiniGameSessionManager
|
||||
// Only auto-stop if NO active continuous session (legacy QTE fallback)
|
||||
if (playerState != null && playerState.isStruggling()) {
|
||||
// Don't auto-stop if there's an active continuous struggle session
|
||||
StruggleSessionManager mgr = StruggleSessionManager.getInstance();
|
||||
if (mgr.getContinuousStruggleSession(player.getUUID()) == null) {
|
||||
// Legacy behavior: stop after 80 ticks (no active continuous session)
|
||||
if (
|
||||
playerState.shouldStopStruggling(
|
||||
player.level().getGameTime()
|
||||
)
|
||||
) {
|
||||
playerState.setStruggling(false, 0);
|
||||
com.tiedup.remake.network.sync.SyncManager.syncStruggleState(
|
||||
player
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process untying task tick (progress-based system)
|
||||
// tick() increments/decrements progress based on whether update() was called this tick
|
||||
// sendProgressPackets() updates the UI for both players
|
||||
if (playerState != null) {
|
||||
com.tiedup.remake.tasks.UntyingTask currentUntyingTask =
|
||||
playerState.getCurrentUntyingTask();
|
||||
if (currentUntyingTask != null && !currentUntyingTask.isStopped()) {
|
||||
// AUTO-UPDATE: Check if player is still targeting the same entity
|
||||
// This allows "hold click" behavior without needing repeated interactLivingEntity calls
|
||||
if (
|
||||
currentUntyingTask instanceof
|
||||
com.tiedup.remake.tasks.UntyingPlayerTask untyingPlayerTask
|
||||
) {
|
||||
net.minecraft.world.entity.LivingEntity target =
|
||||
untyingPlayerTask.getTargetEntity();
|
||||
if (target != null && target.isAlive()) {
|
||||
// Check if player is looking at target and close enough
|
||||
double distance = player.distanceTo(target);
|
||||
boolean isLookingAtTarget = isPlayerLookingAtEntity(
|
||||
player,
|
||||
target,
|
||||
4.0
|
||||
);
|
||||
|
||||
if (
|
||||
distance <= 4.0 &&
|
||||
isLookingAtTarget &&
|
||||
player.hasLineOfSight(target)
|
||||
) {
|
||||
// Player is still targeting - auto-update the task
|
||||
currentUntyingTask.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process tick (increment if active, decrement if not)
|
||||
currentUntyingTask.tick();
|
||||
// Send progress packets to update UI
|
||||
currentUntyingTask.sendProgressPackets();
|
||||
|
||||
// Check if task stopped (completed or cancelled due to no progress)
|
||||
if (currentUntyingTask.isStopped()) {
|
||||
playerState.setCurrentUntyingTask(null);
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[RESTRAINT] {} untying task ended (tick update)",
|
||||
player.getName().getString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Process tying task tick (same progress-based system)
|
||||
com.tiedup.remake.tasks.TyingTask currentTyingTask =
|
||||
playerState.getCurrentTyingTask();
|
||||
if (currentTyingTask != null && !currentTyingTask.isStopped()) {
|
||||
// AUTO-UPDATE: Check if player is still targeting the same entity
|
||||
// This allows "hold click" behavior without needing repeated interactLivingEntity calls
|
||||
if (
|
||||
currentTyingTask instanceof
|
||||
com.tiedup.remake.tasks.TyingPlayerTask tyingPlayerTask
|
||||
) {
|
||||
net.minecraft.world.entity.LivingEntity target =
|
||||
tyingPlayerTask.getTargetEntity();
|
||||
boolean isSelfTying =
|
||||
target != null && target.equals(player);
|
||||
|
||||
if (isSelfTying) {
|
||||
// Self-tying: skip look-at/distance checks (player can't raycast to own hitbox)
|
||||
// Progress is driven by continuous PacketSelfBondage packets from client
|
||||
currentTyingTask.update();
|
||||
} else if (target != null && target.isAlive()) {
|
||||
// Tying another player: check distance + line of sight
|
||||
double distance = player.distanceTo(target);
|
||||
boolean isLookingAtTarget = isPlayerLookingAtEntity(
|
||||
player,
|
||||
target,
|
||||
4.0
|
||||
);
|
||||
|
||||
if (
|
||||
distance <= 4.0 &&
|
||||
isLookingAtTarget &&
|
||||
player.hasLineOfSight(target)
|
||||
) {
|
||||
currentTyingTask.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process tick (increment if active, decrement if not)
|
||||
currentTyingTask.tick();
|
||||
// Send progress packets to update UI
|
||||
currentTyingTask.sendProgressPackets();
|
||||
|
||||
// Check if task stopped (completed or cancelled due to no progress)
|
||||
if (currentTyingTask.isStopped()) {
|
||||
playerState.setCurrentTyingTask(null);
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[RESTRAINT] {} tying task ended (tick update)",
|
||||
player.getName().getString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Process force feeding task tick
|
||||
TimedInteractTask feedingTask = playerState.getCurrentFeedingTask();
|
||||
if (feedingTask != null && !feedingTask.isStopped()) {
|
||||
LivingEntity target = feedingTask.getTargetEntity();
|
||||
if (target != null && target.isAlive()) {
|
||||
double distance = player.distanceTo(target);
|
||||
boolean isLookingAtTarget = isPlayerLookingAtEntity(
|
||||
player,
|
||||
target,
|
||||
4.0
|
||||
);
|
||||
|
||||
if (
|
||||
distance <= 4.0 &&
|
||||
isLookingAtTarget &&
|
||||
player.hasLineOfSight(target)
|
||||
) {
|
||||
feedingTask.update();
|
||||
}
|
||||
}
|
||||
|
||||
feedingTask.tick();
|
||||
feedingTask.sendProgressPackets();
|
||||
|
||||
if (feedingTask.isStopped()) {
|
||||
playerState.setCurrentFeedingTask(null);
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[RESTRAINT] {} feeding task ended (tick update)",
|
||||
player.getName().getString()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Throttle: only check every N ticks (configurable via GameConstants) - per-player timing
|
||||
if (player.tickCount % GameConstants.SHOCK_COLLAR_CHECK_INTERVAL != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Phase 13: Auto-shock collar logic (Player-specific feature)
|
||||
if (playerState != null) {
|
||||
playerState.checkAutoShockCollar();
|
||||
}
|
||||
}
|
||||
|
||||
// ========== UNTYING MECHANIC ==========
|
||||
|
||||
/**
|
||||
* Handle untying a tied entity (right-click with empty hand).
|
||||
*
|
||||
* Based on original PlayerKidnapActionsHandler.onUntyingTarget() (1.12.2)
|
||||
*
|
||||
* When a player right-clicks a tied entity (player or NPC) with an empty hand,
|
||||
* starts or continues an untying task to free them.
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onUntyingTarget(
|
||||
PlayerInteractEvent.EntityInteract event
|
||||
) {
|
||||
// Only run on server side
|
||||
if (event.getLevel().isClientSide) {
|
||||
return;
|
||||
}
|
||||
|
||||
Entity target = event.getTarget();
|
||||
Player helper = event.getEntity();
|
||||
|
||||
// Must be targeting a LivingEntity, using main hand, and have empty hand
|
||||
if (
|
||||
!(target instanceof LivingEntity targetEntity) ||
|
||||
event.getHand() != InteractionHand.MAIN_HAND ||
|
||||
!helper.getMainHandItem().isEmpty()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// MCA villagers require Shift+click to untie (prevents conflict with MCA menu)
|
||||
if (
|
||||
com.tiedup.remake.compat.mca.MCACompat.isMCALoaded() &&
|
||||
com.tiedup.remake.compat.mca.MCACompat.isMCAVillager(
|
||||
targetEntity
|
||||
) &&
|
||||
!helper.isShiftKeyDown()
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if target is tied using IBondageState interface
|
||||
IBondageState targetState = KidnappedHelper.getKidnappedState(
|
||||
targetEntity
|
||||
);
|
||||
if (targetState == null || !targetState.isTiedUp()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// SECURITY: Distance and line-of-sight validation
|
||||
// ========================================
|
||||
double maxUntieDistance = 4.0; // Max distance to untie (blocks)
|
||||
double distance = helper.distanceTo(targetEntity);
|
||||
if (distance > maxUntieDistance) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[RESTRAINT] {} tried to untie {} from too far away ({} blocks)",
|
||||
helper.getName().getString(),
|
||||
targetEntity.getName().getString(),
|
||||
String.format("%.1f", distance)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check line-of-sight (helper must be able to see target)
|
||||
if (!helper.hasLineOfSight(targetEntity)) {
|
||||
TiedUpMod.LOGGER.warn(
|
||||
"[RESTRAINT] {} tried to untie {} without line of sight",
|
||||
helper.getName().getString(),
|
||||
targetEntity.getName().getString()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for Kidnapper fight back - block untying if Kidnapper is nearby
|
||||
if (targetEntity instanceof com.tiedup.remake.entities.AbstractTiedUpNpc npc) {
|
||||
if (
|
||||
npc.getCaptor() instanceof EntityKidnapper kidnapper &&
|
||||
kidnapper.isAlive()
|
||||
) {
|
||||
double distanceToKidnapper = helper.distanceTo(kidnapper);
|
||||
double fightBackRange = 16.0; // Kidnapper notices within 16 blocks
|
||||
|
||||
if (distanceToKidnapper <= fightBackRange) {
|
||||
// Trigger Kidnapper fight back by setting helper as "attacker"
|
||||
// This activates KidnapperFightBackGoal which handles pursuit and attack
|
||||
kidnapper.setLastAttacker(helper);
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[RESTRAINT] {} tried to untie {}, {} fights back!",
|
||||
helper.getName().getString(),
|
||||
npc.getName().getString(),
|
||||
kidnapper.getName().getString()
|
||||
);
|
||||
|
||||
// Block untying - send message to player
|
||||
helper.displayClientMessage(
|
||||
Component.translatable(
|
||||
"tiedup.message.kidnapper_guards_captive"
|
||||
),
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for Kidnapper fight back - block untying if player is captive or job worker
|
||||
if (targetEntity instanceof Player targetPlayer) {
|
||||
List<EntityKidnapper> nearbyKidnappers = helper
|
||||
.level()
|
||||
.getEntitiesOfClass(
|
||||
EntityKidnapper.class,
|
||||
helper.getBoundingBox().inflate(16.0)
|
||||
);
|
||||
|
||||
for (EntityKidnapper kidnapper : nearbyKidnappers) {
|
||||
if (!kidnapper.isAlive()) continue;
|
||||
|
||||
// Check if player is kidnapper's current captive (held by leash)
|
||||
IBondageState captive = kidnapper.getCaptive();
|
||||
boolean isCaptive =
|
||||
captive != null && captive.asLivingEntity() == targetPlayer;
|
||||
|
||||
// Check if player is kidnapper's job worker
|
||||
UUID workerUUID = kidnapper.getJobWorkerUUID();
|
||||
boolean isJobWorker =
|
||||
workerUUID != null &&
|
||||
workerUUID.equals(targetPlayer.getUUID());
|
||||
|
||||
if (isCaptive || isJobWorker) {
|
||||
// Trigger Kidnapper fight back
|
||||
kidnapper.setLastAttacker(helper);
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[RESTRAINT] {} tried to untie {} (captive={}, worker={}), {} fights back!",
|
||||
helper.getName().getString(),
|
||||
targetPlayer.getName().getString(),
|
||||
isCaptive,
|
||||
isJobWorker,
|
||||
kidnapper.getNpcName()
|
||||
);
|
||||
|
||||
// Block untying - send message to player
|
||||
helper.displayClientMessage(
|
||||
Component.translatable(
|
||||
"tiedup.message.kidnapper_guards_captive"
|
||||
),
|
||||
true
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if helper is tied using IBondageState interface
|
||||
IBondageState helperKidnappedState = KidnappedHelper.getKidnappedState(
|
||||
helper
|
||||
);
|
||||
if (helperKidnappedState == null || helperKidnappedState.isTiedUp()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get PlayerBindState for task management (helper only)
|
||||
PlayerBindState helperState = PlayerBindState.getInstance(helper);
|
||||
if (helperState == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Block untying while force feeding
|
||||
TimedInteractTask activeFeedTask = helperState.getCurrentFeedingTask();
|
||||
if (activeFeedTask != null && !activeFeedTask.isStopped()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get untying duration (default: 10 seconds)
|
||||
int untyingSeconds = getUntyingDuration(helper);
|
||||
|
||||
// Phase 11: Check collar ownership for TiedUp NPCs
|
||||
// Non-owners take 3x longer and trigger alert to owners
|
||||
if (targetEntity instanceof com.tiedup.remake.entities.AbstractTiedUpNpc npc) {
|
||||
if (!npc.isCollarOwner(helper)) {
|
||||
// Non-owner: triple the untying time
|
||||
untyingSeconds *= 3;
|
||||
|
||||
// Alert all collar owners
|
||||
alertCollarOwners(npc, helper);
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[RESTRAINT] Non-owner {} trying to free {} ({}s)",
|
||||
helper.getName().getString(),
|
||||
npc.getNpcName(),
|
||||
untyingSeconds
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Get current untying task (if any)
|
||||
UntyingTask currentTask = helperState.getCurrentUntyingTask();
|
||||
|
||||
// Check if we should start a new task or continue existing one
|
||||
if (
|
||||
currentTask == null ||
|
||||
!currentTask.isSameTarget(targetEntity) ||
|
||||
currentTask.isStopped()
|
||||
) {
|
||||
// Create new untying task (unified for Players and NPCs)
|
||||
UntyingPlayerTask newTask = new UntyingPlayerTask(
|
||||
targetState,
|
||||
targetEntity,
|
||||
untyingSeconds,
|
||||
helper.level(),
|
||||
helper
|
||||
);
|
||||
|
||||
// Start new task
|
||||
helperState.setCurrentUntyingTask(newTask);
|
||||
newTask.setUpTargetState(); // Initialize target's restraint state
|
||||
newTask.start();
|
||||
currentTask = newTask;
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[RESTRAINT] {} started untying {} ({} seconds)",
|
||||
helper.getName().getString(),
|
||||
targetEntity.getName().getString(),
|
||||
untyingSeconds
|
||||
);
|
||||
} else {
|
||||
// Continue existing task - ensure helper is set
|
||||
if (currentTask instanceof UntyingPlayerTask playerTask) {
|
||||
playerTask.setHelper(helper);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark this tick as active (progress will increase in onPlayerTick)
|
||||
// The tick() method in onPlayerTick handles progress increment/decrement
|
||||
currentTask.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a player is looking at a specific entity (raycast).
|
||||
*
|
||||
* @param player The player
|
||||
* @param target The target entity
|
||||
* @param maxDistance Maximum distance to check
|
||||
* @return true if player is looking at the target entity
|
||||
*/
|
||||
private static boolean isPlayerLookingAtEntity(
|
||||
Player player,
|
||||
net.minecraft.world.entity.LivingEntity target,
|
||||
double maxDistance
|
||||
) {
|
||||
// Get player's look vector
|
||||
net.minecraft.world.phys.Vec3 eyePos = player.getEyePosition(1.0F);
|
||||
net.minecraft.world.phys.Vec3 lookVec = player.getLookAngle();
|
||||
net.minecraft.world.phys.Vec3 endPos = eyePos.add(
|
||||
lookVec.x * maxDistance,
|
||||
lookVec.y * maxDistance,
|
||||
lookVec.z * maxDistance
|
||||
);
|
||||
|
||||
// Check if raycast hits the target entity
|
||||
net.minecraft.world.phys.AABB targetBounds = target
|
||||
.getBoundingBox()
|
||||
.inflate(0.3);
|
||||
java.util.Optional<net.minecraft.world.phys.Vec3> hit =
|
||||
targetBounds.clip(eyePos, endPos);
|
||||
|
||||
return hit.isPresent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the untying duration in seconds from GameRule.
|
||||
*
|
||||
* @param player The player (for accessing world/GameRules)
|
||||
* @return Duration in seconds (default: 10)
|
||||
*/
|
||||
private static int getUntyingDuration(Player player) {
|
||||
return SettingsAccessor.getUntyingPlayerTime(player.level().getGameRules());
|
||||
}
|
||||
|
||||
// ========== FORCE FEEDING MECHANIC ==========
|
||||
|
||||
/**
|
||||
* Handle force feeding a gagged entity (right-click with food).
|
||||
*
|
||||
* When a player right-clicks a gagged entity (player or NPC) while holding food,
|
||||
* starts or continues a force feeding task.
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onForceFeedingTarget(
|
||||
PlayerInteractEvent.EntityInteract event
|
||||
) {
|
||||
if (event.getLevel().isClientSide) {
|
||||
return;
|
||||
}
|
||||
|
||||
Entity target = event.getTarget();
|
||||
Player feeder = event.getEntity();
|
||||
|
||||
if (
|
||||
!(target instanceof LivingEntity targetEntity) ||
|
||||
event.getHand() != InteractionHand.MAIN_HAND
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack heldItem = feeder.getMainHandItem();
|
||||
if (heldItem.isEmpty() || !heldItem.getItem().isEdible()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Target must have IBondageState state and be gagged
|
||||
IBondageState targetState = KidnappedHelper.getKidnappedState(
|
||||
targetEntity
|
||||
);
|
||||
if (targetState == null || !targetState.isGagged()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Feeder must not be tied up
|
||||
IBondageState feederState = KidnappedHelper.getKidnappedState(feeder);
|
||||
if (feederState != null && feederState.isTiedUp()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Distance and line-of-sight validation
|
||||
double distance = feeder.distanceTo(targetEntity);
|
||||
if (distance > 4.0) {
|
||||
return;
|
||||
}
|
||||
if (!feeder.hasLineOfSight(targetEntity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get feeder's PlayerBindState for task management
|
||||
PlayerBindState feederBindState = PlayerBindState.getInstance(feeder);
|
||||
if (feederBindState == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Block feeding while untying
|
||||
UntyingTask activeUntieTask = feederBindState.getCurrentUntyingTask();
|
||||
if (activeUntieTask != null && !activeUntieTask.isStopped()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current feeding task (if any)
|
||||
TimedInteractTask currentTask = feederBindState.getCurrentFeedingTask();
|
||||
|
||||
if (
|
||||
currentTask == null ||
|
||||
!currentTask.isSameTarget(targetEntity) ||
|
||||
currentTask.isStopped()
|
||||
) {
|
||||
// Create new force feeding task (5 seconds)
|
||||
ForceFeedingTask newTask = new ForceFeedingTask(
|
||||
targetState,
|
||||
targetEntity,
|
||||
5,
|
||||
feeder.level(),
|
||||
feeder,
|
||||
heldItem,
|
||||
feeder.getInventory().selected
|
||||
);
|
||||
|
||||
feederBindState.setCurrentFeedingTask(newTask);
|
||||
newTask.setUpTargetState();
|
||||
newTask.start();
|
||||
currentTask = newTask;
|
||||
|
||||
TiedUpMod.LOGGER.debug(
|
||||
"[RESTRAINT] {} started force feeding {} (5 seconds)",
|
||||
feeder.getName().getString(),
|
||||
targetEntity.getName().getString()
|
||||
);
|
||||
} else {
|
||||
// Continue existing task - ensure feeder is set
|
||||
if (currentTask instanceof ForceFeedingTask feedTask) {
|
||||
feedTask.setFeeder(feeder);
|
||||
}
|
||||
}
|
||||
|
||||
currentTask.update();
|
||||
|
||||
// Cancel to prevent mobInteract (avoids instant NPC feed)
|
||||
event.setCancellationResult(InteractionResult.SUCCESS);
|
||||
event.setCanceled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alert all collar owners that someone is trying to free their slave.
|
||||
* Phase 11: Multiplayer protection system
|
||||
*
|
||||
* @param slave The slave being freed
|
||||
* @param liberator The player trying to free them
|
||||
*/
|
||||
private static void alertCollarOwners(
|
||||
com.tiedup.remake.entities.AbstractTiedUpNpc slave,
|
||||
Player liberator
|
||||
) {
|
||||
if (!(slave.level() instanceof ServerLevel serverLevel)) return;
|
||||
|
||||
ItemStack collar = slave.getEquipment(BodyRegionV2.NECK);
|
||||
if (
|
||||
collar.isEmpty() ||
|
||||
!(collar.getItem() instanceof ItemCollar collarItem)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<UUID> owners = collarItem.getOwners(collar);
|
||||
if (owners.isEmpty()) return;
|
||||
|
||||
// Create alert packet
|
||||
PacketSlaveBeingFreed alertPacket = new PacketSlaveBeingFreed(
|
||||
slave.getNpcName(),
|
||||
liberator.getName().getString(),
|
||||
slave.blockPosition().getX(),
|
||||
slave.blockPosition().getY(),
|
||||
slave.blockPosition().getZ()
|
||||
);
|
||||
|
||||
// Send to all online owners
|
||||
for (UUID ownerUUID : owners) {
|
||||
ServerPlayer owner = serverLevel
|
||||
.getServer()
|
||||
.getPlayerList()
|
||||
.getPlayer(ownerUUID);
|
||||
if (owner != null && owner != liberator) {
|
||||
ModNetwork.sendToPlayer(alertPacket, owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user