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
219 lines
6.9 KiB
Java
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
|
|
}
|
|
}
|