package com.tiedup.remake.tasks; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.entities.EntityDamsel; import com.tiedup.remake.network.ModNetwork; import com.tiedup.remake.network.action.PacketForceFeeding; import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.state.PlayerBindState; import net.minecraft.ChatFormatting; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; /** * Server-side task for force feeding a gagged entity. * Player must keep looking at the target for 5 seconds to complete. */ public class ForceFeedingTask extends TimedInteractTask { private Player feeder; private ItemStack foodStack; private int sourceSlot; public ForceFeedingTask( IBondageState targetState, LivingEntity targetEntity, int seconds, Level level, Player feeder, ItemStack foodStack, int sourceSlot ) { super(targetState, targetEntity, seconds, level); this.feeder = feeder; this.foodStack = foodStack.copy(); this.sourceSlot = sourceSlot; } public void setFeeder(Player feeder) { this.feeder = feeder; } @Override public synchronized void update() { // Cancel if feeder is gone or dead if (feeder == null || !feeder.isAlive() || feeder.isRemoved()) { stop(); return; } if (targetEntity != null) { double distance = feeder.distanceTo(targetEntity); if (distance > 4.0) { TiedUpMod.LOGGER.debug( "[ForceFeedingTask] Feeder {} moved too far from target ({} blocks), cancelling", feeder.getName().getString(), String.format("%.1f", distance) ); stop(); return; } if (!feeder.hasLineOfSight(targetEntity)) { TiedUpMod.LOGGER.debug( "[ForceFeedingTask] Feeder {} lost line of sight to target, cancelling", feeder.getName().getString() ); stop(); return; } } super.update(); } @Override protected void onComplete() { if (!isTargetValid()) { TiedUpMod.LOGGER.warn( "[ForceFeedingTask] Target entity no longer valid, cancelling task" ); stop(); return; } if (feeder == null || !feeder.isAlive()) { TiedUpMod.LOGGER.warn( "[ForceFeedingTask] Feeder no longer valid, cancelling task" ); stop(); return; } // Validate the item in the source slot is still edible ItemStack slotStack = feeder.getInventory().getItem(sourceSlot); if (slotStack.isEmpty() || !slotStack.getItem().isEdible()) { TiedUpMod.LOGGER.warn( "[ForceFeedingTask] Food item no longer in slot {}, cancelling", sourceSlot ); stop(); return; } TiedUpMod.LOGGER.info( "[ForceFeedingTask] Force feeding complete for {}", targetEntity.getName().getString() ); if (targetEntity instanceof Player targetPlayer) { // Feed the player using vanilla eat mechanics targetPlayer.eat(targetPlayer.level(), slotStack.copy()); slotStack.shrink(1); } else if (targetEntity instanceof EntityDamsel damsel) { // Use existing NPC feed method (handles shrink internally) damsel.feedByPlayer(feeder, slotStack); } // Play eating sound at target targetEntity .level() .playSound( null, targetEntity.getX(), targetEntity.getY(), targetEntity.getZ(), SoundEvents.GENERIC_EAT, SoundSource.PLAYERS, 1.0F, 1.0F ); // Send messages String targetName = targetEntity.getName().getString(); String feederName = feeder.getName().getString(); if (feeder instanceof ServerPlayer serverFeeder) { serverFeeder.displayClientMessage( Component.translatable( "msg.tiedup.forcefeeding.you_fed", targetName ).withStyle(ChatFormatting.GRAY), true ); } if (targetEntity instanceof ServerPlayer serverTarget) { serverTarget.displayClientMessage( Component.translatable("msg.tiedup.forcefeeding.been_fed") .withStyle(ChatFormatting.GRAY), true ); } stop(); // Send completion packets (stateInfo = -1) if (targetEntity instanceof ServerPlayer serverTarget) { PacketForceFeeding completionPacket = new PacketForceFeeding( -1, this.getMaxSeconds(), false, feederName ); ModNetwork.sendToPlayer(completionPacket, serverTarget); } if (feeder instanceof ServerPlayer serverFeeder) { PacketForceFeeding completionPacket = new PacketForceFeeding( -1, this.getMaxSeconds(), true, targetName ); ModNetwork.sendToPlayer(completionPacket, serverFeeder); } } @Override public void sendProgressPackets() { if (stopped) return; String feederName = feeder != null ? feeder.getName().getString() : "Someone"; String targetName = targetEntity.getName().getString(); // Packet to target (if player): isActiveRole=false, shows feeder's name if (targetEntity instanceof ServerPlayer serverTarget) { PacketForceFeeding victimPacket = new PacketForceFeeding( this.getState(), this.getMaxSeconds(), false, feederName ); ModNetwork.sendToPlayer(victimPacket, serverTarget); } // Packet to feeder: isActiveRole=true, shows target's name if (feeder instanceof ServerPlayer serverFeeder) { PacketForceFeeding feederPacket = new PacketForceFeeding( this.getState(), this.getMaxSeconds(), true, targetName ); ModNetwork.sendToPlayer(feederPacket, serverFeeder); } } @Override public void setUpTargetState() { // Server-side: nothing to do - client handles its own PlayerStateTask via packets } }