package com.tiedup.remake.events.system; import com.tiedup.remake.core.SettingsAccessor; import com.tiedup.remake.core.SystemMessageManager; import com.tiedup.remake.core.TiedUpMod; import com.tiedup.remake.dialogue.GagTalkManager; import com.tiedup.remake.items.base.ItemGag; import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; import com.tiedup.remake.state.IBondageState; import com.tiedup.remake.util.GagMaterial; import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.TiedUpUtils; import java.util.List; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.item.ItemStack; import net.minecraftforge.event.CommandEvent; import net.minecraftforge.event.ServerChatEvent; import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; /** * ChatEventHandler - Intercepts chat messages to apply gag effects. * Evolution: Implements proximity-based chat for gagged players. * * Phase 14.1.5: Refactored to use IBondageState interface * * Security fix: Now blocks communication commands (/msg, /tell, etc.) when gagged * to prevent gag bypass exploit */ @Mod.EventBusSubscriber(modid = TiedUpMod.MOD_ID) public class ChatEventHandler { /** List of communication commands that should be blocked when gagged */ private static final String[] BLOCKED_COMMANDS = { "msg", "tell", "w", "whisper", "r", "reply", "me", }; @SubscribeEvent public static void onPlayerChat(ServerChatEvent event) { ServerPlayer player = event.getPlayer(); IBondageState state = KidnappedHelper.getKidnappedState(player); if (state != null && state.isGagged()) { ItemStack gagStack = V2EquipmentHelper.getInRegion( player, BodyRegionV2.MOUTH ); if ( !gagStack.isEmpty() && gagStack.getItem() instanceof ItemGag gagItem ) { String originalMessage = event.getRawText(); GagMaterial material = gagItem.getGagMaterial(); // 1. Process the message through our GagTalkManager V2 Component muffledMessage = GagTalkManager.processGagMessage( state, gagStack, originalMessage ); // 2. Proximity Chat Logic boolean useProximity = SettingsAccessor.isGagTalkProximityEnabled( player.level().getGameRules()); if (useProximity) { // Cancel global and send to nearby event.setCanceled(true); Component finalChat = Component.literal("<") .append(player.getDisplayName()) .append("> ") .append(muffledMessage); double range = material.getTalkRange(); // Phase 14.2: Use TiedUpUtils for proximity and earplugs filtering List nearbyPlayers = TiedUpUtils.getPlayersAround( player.level(), player.blockPosition(), range ); int listeners = 0; for (ServerPlayer other : nearbyPlayers) { // Check if receiver has earplugs (they can't hear) IBondageState receiverState = KidnappedHelper.getKidnappedState(other); if ( receiverState != null && receiverState.hasEarplugs() ) { // Can't hear - skip this player continue; } other.sendSystemMessage(finalChat); if (other != player) listeners++; } if (listeners == 0) { player.displayClientMessage( Component.translatable( "chat.tiedup.gag.no_one_heard" ).withStyle( net.minecraft.ChatFormatting.ITALIC, net.minecraft.ChatFormatting.GRAY ), true ); } } else { // Just replace message but keep it global event.setMessage(muffledMessage); } TiedUpMod.LOGGER.debug( "[Chat] {} muffled message processed (Proximity: {})", player.getName().getString(), useProximity ); } } } /** * Intercept commands to prevent gagged players from using communication commands. * Blocks /msg, /tell, /w, /whisper, /r, /reply, /me when player is gagged. * * Security fix: Prevents gag bypass exploit via private messages */ @SubscribeEvent(priority = EventPriority.HIGHEST) public static void onCommand(CommandEvent event) { // Only check if sender is a ServerPlayer if ( !(event .getParseResults() .getContext() .getSource() .getEntity() instanceof ServerPlayer player) ) { return; } // Check if player is gagged IBondageState state = KidnappedHelper.getKidnappedState(player); if (state == null || !state.isGagged()) { return; // Not gagged, allow all commands } // Get the command name (first part of the command string) String commandInput = event.getParseResults().getReader().getString(); if (commandInput.isEmpty()) { return; } // Remove leading slash if present String commandName = commandInput.startsWith("/") ? commandInput.substring(1) : commandInput; // Get only the first word (command name) commandName = commandName.split(" ")[0].toLowerCase(); // Check if this is a blocked communication command for (String blockedCmd : BLOCKED_COMMANDS) { if (commandName.equals(blockedCmd)) { // Block the command event.setCanceled(true); // Send muffled message to player SystemMessageManager.sendToPlayer( player, SystemMessageManager.MessageCategory.ERROR, "Mmmph! You can't use that command while gagged!" ); TiedUpMod.LOGGER.debug( "[Chat] Blocked command '{}' from gagged player {}", commandName, player.getName().getString() ); return; } } } }