Files
TiedUp-/src/main/java/com/tiedup/remake/tasks/ForceFeedingTask.java
NotEvil fd60086322 feat(i18n): complete migration — items, entities, AI goals, GUI screens
119 new translation keys across 3 domains:
- Items/Blocks/Misc (46 keys): tooltips, action messages, trap states
- Entities/AI Goals (55 keys): NPC speech, maid/master/guard messages
- Client GUI (18 keys): widget labels, screen buttons, merchant display

Remaining 119 Component.literal() are all intentional:
- Debug/Admin/Command wands (47) — dev tools, not player-facing
- Entity display names (~25) — dynamic getNpcName() calls
- Empty string roots (~15) — .append() chain bases
- User-typed text (~10) — /me, /pm, /norp chat content
- Runtime data (~12) — StringBuilder, gag muffling, MCA compat
2026-04-16 12:33:13 +02:00

219 lines
6.9 KiB
Java

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
}
}