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:
NotEvil
2026-04-12 00:51:22 +02:00
parent 2e7a1d403b
commit f6466360b6
1947 changed files with 238025 additions and 1 deletions

View File

@@ -0,0 +1,13 @@
package com.tiedup.remake.network.personality;
/**
* Discriminator for the unified NPC command packet.
* Each type maps to what was previously a separate packet class.
*/
public enum NpcCommandType {
GIVE_COMMAND,
CANCEL_COMMAND,
SELECT_JOB,
CYCLE_FOLLOW_DISTANCE,
TOGGLE_AUTO_REST
}

View File

@@ -0,0 +1,140 @@
package com.tiedup.remake.network.personality;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.core.TiedUpMod;
// Discipline now only triggers dialogue, no personality effects
import com.tiedup.remake.dialogue.EntityDialogueManager;
import com.tiedup.remake.dialogue.EntityDialogueManager.DialogueCategory;
import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.network.PacketRateLimiter;
import com.tiedup.remake.personality.DisciplineType;
import java.util.function.Supplier;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.network.NetworkEvent;
/**
* Packet sent from client to server to apply discipline to an NPC.
* Used for verbal discipline actions (Praise, Scold, Threaten) from the DialogueScreen.
*
* Training System V2: Verbal discipline support
*/
public class PacketDisciplineAction {
/** Maximum range for discipline actions */
private static final double MAX_RANGE = 32.0;
private final int entityId;
private final String disciplineTypeName;
public PacketDisciplineAction(int entityId, DisciplineType type) {
this.entityId = entityId;
this.disciplineTypeName = type.name();
}
private PacketDisciplineAction(int entityId, String disciplineTypeName) {
this.entityId = entityId;
this.disciplineTypeName = disciplineTypeName;
}
public void encode(FriendlyByteBuf buf) {
buf.writeInt(entityId);
buf.writeUtf(disciplineTypeName, 32);
}
public static PacketDisciplineAction decode(FriendlyByteBuf buf) {
return new PacketDisciplineAction(buf.readInt(), buf.readUtf(32));
}
public void handle(Supplier<NetworkEvent.Context> ctx) {
ctx
.get()
.enqueueWork(() -> {
ServerPlayer sender = ctx.get().getSender();
if (sender == null) return;
handleServer(sender);
});
ctx.get().setPacketHandled(true);
}
private void handleServer(ServerPlayer sender) {
// MEDIUM FIX: Rate limiting to prevent discipline spam exploit
if (!PacketRateLimiter.allowPacket(sender, "action")) {
return;
}
// Find the target entity
Entity entity = sender.level().getEntity(entityId);
if (!(entity instanceof EntityDamsel damsel)) {
TiedUpMod.LOGGER.warn(
"[PacketDisciplineAction] Entity {} not found or not a Damsel",
entityId
);
return;
}
// Validate distance
double distance = sender.distanceTo(damsel);
if (distance > MAX_RANGE) {
SystemMessageManager.sendToPlayer(
sender,
SystemMessageManager.MessageCategory.ERROR,
"Target is too far away!"
);
return;
}
// Parse discipline type
DisciplineType type;
try {
type = DisciplineType.valueOf(disciplineTypeName);
} catch (IllegalArgumentException e) {
TiedUpMod.LOGGER.warn(
"[PacketDisciplineAction] Unknown discipline type: {}",
disciplineTypeName
);
return;
}
// Only allow verbal discipline from this packet (Praise, Scold, Threaten)
if (!type.isVerbal()) {
TiedUpMod.LOGGER.warn(
"[PacketDisciplineAction] Non-verbal discipline {} attempted via packet",
type.name()
);
return;
}
// Show appropriate dialogue
DialogueCategory category = switch (type) {
case PRAISE -> DialogueCategory.PRAISE_RESPONSE;
case SCOLD -> DialogueCategory.SCOLD_RESPONSE;
case THREATEN -> DialogueCategory.THREATEN_RESPONSE;
default -> DialogueCategory.SCOLD_RESPONSE;
};
EntityDialogueManager.talkTo(damsel, sender, category);
// Send feedback
String npcName = damsel.getNpcName();
String actionDesc = switch (type) {
case PRAISE -> "praised";
case SCOLD -> "scolded";
case THREATEN -> "threatened";
default -> "disciplined";
};
SystemMessageManager.sendToPlayer(
sender,
SystemMessageManager.MessageCategory.INFO,
"You " + actionDesc + " " + npcName + "."
);
TiedUpMod.LOGGER.debug(
"[PacketDisciplineAction] {} {} {}",
sender.getName().getString(),
actionDesc,
npcName
);
}
}

View File

@@ -0,0 +1,312 @@
package com.tiedup.remake.network.personality;
import com.tiedup.remake.core.SystemMessageManager;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.dialogue.EntityDialogueManager;
import com.tiedup.remake.dialogue.EntityDialogueManager.DialogueCategory;
import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.items.ItemCommandWand;
import com.tiedup.remake.items.ModItems;
import com.tiedup.remake.items.base.ItemCollar;
import com.tiedup.remake.network.ModNetwork;
import com.tiedup.remake.network.PacketRateLimiter;
import com.tiedup.remake.personality.JobExperience;
import com.tiedup.remake.personality.NpcCommand;
import com.tiedup.remake.personality.NpcNeeds;
import com.tiedup.remake.personality.PersonalityState;
import com.tiedup.remake.v2.BodyRegionV2;
import java.util.UUID;
import java.util.function.Supplier;
import org.jetbrains.annotations.Nullable;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.network.NetworkEvent;
/**
* Unified C2S packet for all NPC command wand actions.
* Replaces PacketGiveCommand, PacketCancelCommand, PacketSelectJobCommand,
* PacketCycleFollowDistance, and PacketToggleAutoRest.
*/
public class PacketNpcCommand {
private static final double MAX_COMMAND_RANGE = 32.0;
private final UUID entityUUID;
private final NpcCommandType type;
// Optional fields depending on type
private final String commandName; // GIVE_COMMAND, SELECT_JOB
@Nullable
private final BlockPos targetPos; // GIVE_COMMAND only
private final boolean refreshScreen; // CYCLE_FOLLOW_DISTANCE, TOGGLE_AUTO_REST
// --- Constructors for each command type ---
public static PacketNpcCommand giveCommand(UUID entityUUID, NpcCommand command, @Nullable BlockPos targetPos) {
return new PacketNpcCommand(entityUUID, NpcCommandType.GIVE_COMMAND, command.name(), targetPos, false);
}
public static PacketNpcCommand cancelCommand(UUID entityUUID) {
return new PacketNpcCommand(entityUUID, NpcCommandType.CANCEL_COMMAND, "", null, false);
}
public static PacketNpcCommand selectJob(UUID entityUUID, NpcCommand job) {
return new PacketNpcCommand(entityUUID, NpcCommandType.SELECT_JOB, job.name(), null, false);
}
public static PacketNpcCommand cycleFollowDistance(UUID entityUUID, boolean refreshScreen) {
return new PacketNpcCommand(entityUUID, NpcCommandType.CYCLE_FOLLOW_DISTANCE, "", null, refreshScreen);
}
public static PacketNpcCommand toggleAutoRest(UUID entityUUID, boolean refreshScreen) {
return new PacketNpcCommand(entityUUID, NpcCommandType.TOGGLE_AUTO_REST, "", null, refreshScreen);
}
private PacketNpcCommand(UUID entityUUID, NpcCommandType type, String commandName,
@Nullable BlockPos targetPos, boolean refreshScreen) {
this.entityUUID = entityUUID;
this.type = type;
this.commandName = commandName;
this.targetPos = targetPos;
this.refreshScreen = refreshScreen;
}
// --- Wire format ---
public void encode(FriendlyByteBuf buf) {
buf.writeUUID(entityUUID);
buf.writeEnum(type);
switch (type) {
case GIVE_COMMAND -> {
buf.writeUtf(commandName);
buf.writeBoolean(targetPos != null);
if (targetPos != null) {
buf.writeBlockPos(targetPos);
}
}
case SELECT_JOB -> buf.writeUtf(commandName);
case CYCLE_FOLLOW_DISTANCE, TOGGLE_AUTO_REST -> buf.writeBoolean(refreshScreen);
case CANCEL_COMMAND -> {} // no extra data
}
}
public static PacketNpcCommand decode(FriendlyByteBuf buf) {
UUID uuid = buf.readUUID();
NpcCommandType type = buf.readEnum(NpcCommandType.class);
String cmd = "";
BlockPos pos = null;
boolean refresh = false;
switch (type) {
case GIVE_COMMAND -> {
cmd = buf.readUtf(32);
if (buf.readBoolean()) {
pos = buf.readBlockPos();
}
}
case SELECT_JOB -> cmd = buf.readUtf(32);
case CYCLE_FOLLOW_DISTANCE, TOGGLE_AUTO_REST -> refresh = buf.readBoolean();
case CANCEL_COMMAND -> {}
}
return new PacketNpcCommand(uuid, type, cmd, pos, refresh);
}
public void handle(Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> {
ServerPlayer sender = ctx.get().getSender();
if (sender == null) return;
handleServer(sender);
});
ctx.get().setPacketHandled(true);
}
// --- Server handling ---
private void handleServer(ServerPlayer sender) {
if (!PacketRateLimiter.allowPacket(sender, "action")) return;
Entity entity = ((net.minecraft.server.level.ServerLevel) sender.level()).getEntity(entityUUID);
if (!(entity instanceof EntityDamsel damsel)) {
TiedUpMod.LOGGER.warn("[PacketNpcCommand:{}] Entity {} not found or not a Damsel",
type, entityUUID);
return;
}
double distance = sender.distanceTo(damsel);
if (distance > MAX_COMMAND_RANGE) {
SystemMessageManager.sendToPlayer(sender,
SystemMessageManager.MessageCategory.ERROR, "Target is too far away!");
return;
}
switch (type) {
case GIVE_COMMAND -> handleGiveCommand(sender, damsel);
case CANCEL_COMMAND -> handleCancelCommand(sender, damsel);
case SELECT_JOB -> handleSelectJob(sender, damsel);
case CYCLE_FOLLOW_DISTANCE -> handleCycleFollowDistance(sender, damsel);
case TOGGLE_AUTO_REST -> handleToggleAutoRest(sender, damsel);
}
}
private void handleGiveCommand(ServerPlayer sender, EntityDamsel damsel) {
NpcCommand command = NpcCommand.fromString(commandName);
if (command == NpcCommand.NONE && !"NONE".equals(commandName)) {
TiedUpMod.LOGGER.warn("[PacketNpcCommand:GIVE] Unknown command: {}", commandName);
return;
}
boolean success = damsel.giveCommand(sender, command, targetPos);
String npcName = damsel.getNpcName();
if (success) {
EntityDialogueManager.talkTo(damsel, sender, DialogueCategory.COMMAND_ACCEPT);
SystemMessageManager.sendToPlayer(sender,
SystemMessageManager.MessageCategory.INFO,
npcName + " accepted command: " + command.name());
TiedUpMod.LOGGER.debug("[PacketNpcCommand:GIVE] {} gave command {} to {}",
sender.getName().getString(), command.name(), npcName);
}
}
private void handleCancelCommand(ServerPlayer sender, EntityDamsel damsel) {
if (!validateCollarOwnership(sender, damsel)) return;
damsel.cancelCommand();
String npcName = damsel.getNpcName();
SystemMessageManager.sendToPlayer(sender,
SystemMessageManager.MessageCategory.INFO, npcName + "'s command cancelled.");
TiedUpMod.LOGGER.debug("[PacketNpcCommand:CANCEL] {} cancelled command for {}",
sender.getName().getString(), npcName);
}
private void handleSelectJob(ServerPlayer sender, EntityDamsel damsel) {
NpcCommand job = NpcCommand.fromString(commandName);
if (!job.isWorkCommand()) {
TiedUpMod.LOGGER.warn("[PacketNpcCommand:JOB] {} is not a work command", commandName);
return;
}
PersonalityState state = damsel.getPersonalityState();
if (state == null) return;
String npcName = damsel.getNpcName();
if (state.willObeyCommand(sender, job)) {
ItemStack mainHand = sender.getItemInHand(InteractionHand.MAIN_HAND);
ItemStack offHand = sender.getItemInHand(InteractionHand.OFF_HAND);
ItemStack wand = null;
if (mainHand.getItem() == ModItems.COMMAND_WAND.get()) {
wand = mainHand;
} else if (offHand.getItem() == ModItems.COMMAND_WAND.get()) {
wand = offHand;
}
if (wand != null) {
ItemCommandWand.enterSelectionMode(wand, damsel.getUUID(), job);
EntityDialogueManager.talkTo(damsel, sender, DialogueCategory.COMMAND_ACCEPT);
SystemMessageManager.sendToPlayer(sender,
SystemMessageManager.MessageCategory.INFO,
npcName + " is ready to " + job.name() + ". Click a chest to set work zone!");
TiedUpMod.LOGGER.debug("[PacketNpcCommand:JOB] {} accepted job {} - wand in selection mode",
npcName, job.name());
}
}
}
private void handleCycleFollowDistance(ServerPlayer sender, EntityDamsel damsel) {
if (!validateCollarOwnership(sender, damsel)) return;
PersonalityState state = damsel.getPersonalityState();
if (state == null) return;
NpcCommand.FollowDistance current = state.getFollowDistance();
NpcCommand.FollowDistance next = switch (current) {
case FAR -> NpcCommand.FollowDistance.CLOSE;
case CLOSE -> NpcCommand.FollowDistance.HEEL;
case HEEL -> NpcCommand.FollowDistance.FAR;
};
state.setFollowDistance(next);
TiedUpMod.LOGGER.debug("[PacketNpcCommand:FOLLOW] {} changed {} follow distance to {}",
sender.getName().getString(), damsel.getNpcName(), next.name());
if (refreshScreen) {
sendRefreshedScreen(sender, damsel, state);
}
}
private void handleToggleAutoRest(ServerPlayer sender, EntityDamsel damsel) {
if (!validateCollarOwnership(sender, damsel)) return;
PersonalityState state = damsel.getPersonalityState();
if (state == null) return;
boolean newState = state.toggleAutoRest();
TiedUpMod.LOGGER.debug("[PacketNpcCommand:REST] {} toggled {} auto-rest to {}",
sender.getName().getString(), damsel.getNpcName(), newState ? "ON" : "OFF");
SystemMessageManager.sendToPlayer(sender,
SystemMessageManager.MessageCategory.INFO,
"Auto-Rest: " + (newState ? "ON" : "OFF"));
if (refreshScreen) {
sendRefreshedScreen(sender, damsel, state);
}
}
// --- Shared helpers ---
private boolean validateCollarOwnership(ServerPlayer sender, EntityDamsel damsel) {
if (!damsel.hasCollar()) {
SystemMessageManager.sendToPlayer(sender,
SystemMessageManager.MessageCategory.ERROR,
damsel.getNpcName() + " is not wearing a collar!");
return false;
}
ItemStack collar = damsel.getEquipment(BodyRegionV2.NECK);
if (!(collar.getItem() instanceof ItemCollar collarItem)) {
return false;
}
if (!collarItem.getOwners(collar).contains(sender.getUUID())) {
SystemMessageManager.sendToPlayer(sender,
SystemMessageManager.MessageCategory.ERROR,
"You don't own " + damsel.getNpcName() + "'s collar!");
return false;
}
return true;
}
private static void sendRefreshedScreen(ServerPlayer sender, EntityDamsel damsel,
PersonalityState state) {
NpcNeeds needs = state.getNeeds();
String homeType = state.getHomeType().name();
JobExperience jobExp = state.getJobExperience();
NpcCommand activeCmd = state.getActiveCommand();
String activeJobLevelName = "";
int activeJobXp = 0;
int activeJobXpMax = 10;
if (activeCmd.isActiveJob()) {
JobExperience.JobLevel level = jobExp.getJobLevel(activeCmd);
activeJobLevelName = level.name();
activeJobXp = jobExp.getExperience(activeCmd);
activeJobXpMax = level.maxExp;
}
ModNetwork.sendToPlayer(
new PacketOpenCommandWandScreen(
damsel.getUUID(), damsel.getNpcName(),
state.getPersonality().name(), activeCmd.name(),
needs.getHunger(), needs.getRest(), state.getMood(),
state.getFollowDistance().name(), homeType,
state.isAutoRestEnabled(), "", "",
activeJobLevelName, activeJobXp, activeJobXpMax),
sender);
}
}

View File

@@ -0,0 +1,284 @@
package com.tiedup.remake.network.personality;
import com.tiedup.remake.core.TiedUpMod;
import java.util.function.Supplier;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.network.NetworkEvent;
/**
* Packet sent from server to client to open the Command Wand screen.
* Contains all personality data needed for the GUI.
*
* Simplified: removed discovery, fear, relationship, secondaryTrait fields.
*/
public class PacketOpenCommandWandScreen {
private final java.util.UUID entityUUID;
private final String npcName;
private final String personalityTypeName;
private final String activeCommandName;
private final float hunger;
private final float rest;
private final float mood;
private final String followDistanceMode;
private final String homeType;
private final boolean autoRestEnabled;
private final String cellName;
private final String cellQualityName;
private final String activeJobLevelName;
private final int activeJobXp;
private final int activeJobXpMax;
/**
* Constructor without cell info or job experience (defaults to "").
*/
public PacketOpenCommandWandScreen(
java.util.UUID entityUUID,
String npcName,
String personalityTypeName,
String activeCommandName,
float hunger,
float rest,
float mood,
String followDistanceMode,
String homeType,
boolean autoRestEnabled
) {
this(
entityUUID,
npcName,
personalityTypeName,
activeCommandName,
hunger,
rest,
mood,
followDistanceMode,
homeType,
autoRestEnabled,
"",
"",
"",
0,
10
);
}
/**
* Constructor with cell info but no job experience.
*/
public PacketOpenCommandWandScreen(
java.util.UUID entityUUID,
String npcName,
String personalityTypeName,
String activeCommandName,
float hunger,
float rest,
float mood,
String followDistanceMode,
String homeType,
boolean autoRestEnabled,
String cellName,
String cellQualityName
) {
this(
entityUUID,
npcName,
personalityTypeName,
activeCommandName,
hunger,
rest,
mood,
followDistanceMode,
homeType,
autoRestEnabled,
cellName,
cellQualityName,
"",
0,
10
);
}
/**
* Full constructor with cell info and job experience.
*/
public PacketOpenCommandWandScreen(
java.util.UUID entityUUID,
String npcName,
String personalityTypeName,
String activeCommandName,
float hunger,
float rest,
float mood,
String followDistanceMode,
String homeType,
boolean autoRestEnabled,
String cellName,
String cellQualityName,
String activeJobLevelName,
int activeJobXp,
int activeJobXpMax
) {
this.entityUUID = entityUUID;
this.npcName = npcName;
this.personalityTypeName = personalityTypeName;
this.activeCommandName = activeCommandName;
this.hunger = hunger;
this.rest = rest;
this.mood = mood;
this.followDistanceMode = followDistanceMode;
this.homeType = homeType;
this.autoRestEnabled = autoRestEnabled;
this.cellName = cellName;
this.cellQualityName = cellQualityName;
this.activeJobLevelName = activeJobLevelName;
this.activeJobXp = activeJobXp;
this.activeJobXpMax = activeJobXpMax;
}
public void encode(FriendlyByteBuf buf) {
buf.writeUUID(entityUUID);
buf.writeUtf(npcName, 64);
buf.writeUtf(personalityTypeName, 32);
buf.writeUtf(activeCommandName, 32);
buf.writeFloat(hunger);
buf.writeFloat(rest);
buf.writeFloat(mood);
buf.writeUtf(followDistanceMode, 16);
buf.writeUtf(homeType, 16);
buf.writeBoolean(autoRestEnabled);
buf.writeUtf(cellName, 64);
buf.writeUtf(cellQualityName, 16);
buf.writeUtf(activeJobLevelName, 32);
buf.writeInt(activeJobXp);
buf.writeInt(activeJobXpMax);
}
public static PacketOpenCommandWandScreen decode(FriendlyByteBuf buf) {
return new PacketOpenCommandWandScreen(
buf.readUUID(),
buf.readUtf(64), // npcName
buf.readUtf(32), // personalityTypeName
buf.readUtf(32), // activeCommandName
buf.readFloat(), // hunger
buf.readFloat(), // rest
buf.readFloat(), // mood
buf.readUtf(16), // followDistanceMode
buf.readUtf(16), // homeType
buf.readBoolean(), // autoRestEnabled
buf.readUtf(64), // cellName
buf.readUtf(16), // cellQualityName
buf.readUtf(32), // activeJobLevelName
buf.readInt(), // activeJobXp
buf.readInt() // activeJobXpMax
);
}
public void handle(Supplier<NetworkEvent.Context> ctx) {
ctx
.get()
.enqueueWork(() -> {
if (FMLEnvironment.dist == Dist.CLIENT) {
handleClient();
}
});
ctx.get().setPacketHandled(true);
}
@OnlyIn(Dist.CLIENT)
private void handleClient() {
net.minecraft.client.Minecraft mc =
net.minecraft.client.Minecraft.getInstance();
TiedUpMod.LOGGER.debug(
"[PacketOpenCommandWandScreen] Opening screen for {} (entity {})",
npcName,
entityUUID
);
mc.setScreen(
new com.tiedup.remake.client.gui.screens.CommandWandScreen(
entityUUID,
npcName,
personalityTypeName,
activeCommandName,
hunger,
rest,
mood,
followDistanceMode,
homeType,
autoRestEnabled,
cellName,
cellQualityName,
activeJobLevelName,
activeJobXp,
activeJobXpMax
)
);
}
// --- Getters for GUI use ---
public java.util.UUID getEntityUUID() {
return entityUUID;
}
public String getNpcName() {
return npcName;
}
public String getPersonalityTypeName() {
return personalityTypeName;
}
public String getActiveCommandName() {
return activeCommandName;
}
public float getHunger() {
return hunger;
}
public float getRest() {
return rest;
}
public float getMood() {
return mood;
}
public String getFollowDistanceMode() {
return followDistanceMode;
}
public String getHomeType() {
return homeType;
}
public boolean isAutoRestEnabled() {
return autoRestEnabled;
}
public String getCellName() {
return cellName;
}
public String getCellQualityName() {
return cellQualityName;
}
public String getActiveJobLevelName() {
return activeJobLevelName;
}
public int getActiveJobXp() {
return activeJobXp;
}
public int getActiveJobXpMax() {
return activeJobXpMax;
}
}

View File

@@ -0,0 +1,106 @@
package com.tiedup.remake.network.personality;
import com.tiedup.remake.core.TiedUpMod;
import com.tiedup.remake.v2.BodyRegionV2;
import com.tiedup.remake.entities.EntityDamsel;
import com.tiedup.remake.network.PacketRateLimiter;
import java.util.function.Supplier;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraftforge.network.NetworkEvent;
/**
* Packet sent from client to server to request opening the NPC inventory screen.
* Server responds with PacketOpenNpcInventory if validation passes.
*
* Personality System Phase J: NPC Inventory
*/
public class PacketRequestNpcInventory {
private final java.util.UUID entityUUID; // HIGH FIX: use UUID for persistence
/**
* Create a request packet.
*
* @param entityUUID The NPC entity UUID
*/
public PacketRequestNpcInventory(java.util.UUID entityUUID) {
this.entityUUID = entityUUID;
}
public void encode(FriendlyByteBuf buf) {
buf.writeUUID(entityUUID); // HIGH FIX
}
public static PacketRequestNpcInventory decode(FriendlyByteBuf buf) {
return new PacketRequestNpcInventory(buf.readUUID());
}
public void handle(Supplier<NetworkEvent.Context> ctx) {
ctx
.get()
.enqueueWork(() -> {
ServerPlayer player = ctx.get().getSender();
if (player == null) return;
if (!PacketRateLimiter.allowPacket(player, "ui")) return;
// HIGH FIX: lookup by UUID
Entity entity = (
(net.minecraft.server.level.ServerLevel) player.level()
).getEntity(entityUUID);
if (!(entity instanceof EntityDamsel damsel)) {
TiedUpMod.LOGGER.warn(
"[PacketRequestNpcInventory] Entity {} is not a damsel",
entityUUID
);
return;
}
// Verify player is nearby
if (player.distanceTo(damsel) > 6.0) {
TiedUpMod.LOGGER.warn(
"[PacketRequestNpcInventory] Player {} too far from NPC",
player.getName().getString()
);
return;
}
// Verify player is owner of collar (or NPC has no collar)
if (damsel.hasCollar()) {
ItemStack collar = damsel.getEquipment(BodyRegionV2.NECK);
if (
collar.getItem() instanceof
com.tiedup.remake.items.base.ItemCollar collarItem
) {
if (!collarItem.isOwner(collar, player)) {
TiedUpMod.LOGGER.warn(
"[PacketRequestNpcInventory] Player {} is not owner of NPC collar",
player.getName().getString()
);
return;
}
}
}
// Open vanilla container via NetworkHooks
TiedUpMod.LOGGER.debug(
"[PacketRequestNpcInventory] Opening inventory for {} to {}",
damsel.getNpcName(),
player.getName().getString()
);
net.minecraftforge.network.NetworkHooks.openScreen(
player,
damsel,
buf -> {
buf.writeInt(damsel.getId());
buf.writeInt(damsel.getNpcInventorySize());
buf.writeUtf(damsel.getNpcName(), 64);
}
);
});
ctx.get().setPacketHandled(true);
}
}

View File

@@ -0,0 +1,143 @@
package com.tiedup.remake.network.personality;
import java.util.function.Supplier;
import net.minecraft.ChatFormatting;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component;
import net.minecraft.sounds.SoundEvents;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.network.NetworkEvent;
/**
* Packet sent from server to client to alert an owner that someone
* is trying to free their slave.
*
* Phase 11: Multiplayer protection system
*/
public class PacketSlaveBeingFreed {
private final String slaveName;
private final String liberatorName;
private final int x;
private final int y;
private final int z;
/**
* Create alert packet.
*
* @param slaveName Name of the slave being freed
* @param liberatorName Name of the player trying to free them
* @param x X coordinate
* @param y Y coordinate
* @param z Z coordinate
*/
public PacketSlaveBeingFreed(
String slaveName,
String liberatorName,
int x,
int y,
int z
) {
this.slaveName = slaveName;
this.liberatorName = liberatorName;
this.x = x;
this.y = y;
this.z = z;
}
public void encode(FriendlyByteBuf buf) {
buf.writeUtf(slaveName, 64);
buf.writeUtf(liberatorName, 64);
buf.writeInt(x);
buf.writeInt(y);
buf.writeInt(z);
}
public static PacketSlaveBeingFreed decode(FriendlyByteBuf buf) {
return new PacketSlaveBeingFreed(
buf.readUtf(64),
buf.readUtf(64),
buf.readInt(),
buf.readInt(),
buf.readInt()
);
}
public void handle(Supplier<NetworkEvent.Context> ctx) {
ctx
.get()
.enqueueWork(() -> {
if (FMLEnvironment.dist == Dist.CLIENT) {
handleClient();
}
});
ctx.get().setPacketHandled(true);
}
@OnlyIn(Dist.CLIENT)
private void handleClient() {
net.minecraft.client.Minecraft mc =
net.minecraft.client.Minecraft.getInstance();
if (mc.player == null) return;
// Build alert message
Component message = Component.literal("")
.append(
Component.literal("[WARNING] ").withStyle(
ChatFormatting.RED,
ChatFormatting.BOLD
)
)
.append(
Component.literal(liberatorName).withStyle(
ChatFormatting.YELLOW
)
)
.append(
Component.literal(" is trying to free ").withStyle(
ChatFormatting.RED
)
)
.append(
Component.literal(slaveName).withStyle(ChatFormatting.YELLOW)
)
.append(Component.literal(" at ").withStyle(ChatFormatting.RED))
.append(
Component.literal(
"[" + x + ", " + y + ", " + z + "]"
).withStyle(ChatFormatting.AQUA)
)
.append(Component.literal("!").withStyle(ChatFormatting.RED));
// Send to player chat
mc.player.displayClientMessage(message, false);
// Play warning sound
mc.player.playSound(SoundEvents.BELL_BLOCK, 1.0f, 1.0f);
}
// --- Getters ---
public String getSlaveName() {
return slaveName;
}
public String getLiberatorName() {
return liberatorName;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getZ() {
return z;
}
}