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.state.IBondageState; import com.tiedup.remake.util.GagMaterial; import com.tiedup.remake.v2.bondage.component.ComponentType; import com.tiedup.remake.v2.bondage.component.GaggingComponent; import com.tiedup.remake.v2.bondage.datadriven.DataDrivenBondageItem; import com.tiedup.remake.util.KidnappedHelper; import com.tiedup.remake.util.TiedUpUtils; import com.tiedup.remake.v2.BodyRegionV2; import com.tiedup.remake.v2.bondage.capability.V2EquipmentHelper; 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. * * * 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 ); // V2: check gagging component GaggingComponent gaggingComp = DataDrivenBondageItem.getComponent( gagStack, ComponentType.GAGGING, GaggingComponent.class); boolean isGagItem = gaggingComp != null; if (!gagStack.isEmpty() && isGagItem) { String originalMessage = event.getRawText(); // V2: get material from GaggingComponent GagMaterial material = null; if (gaggingComp != null) { material = gaggingComp.getMaterial(); } // material stays null if no component; GagTalkManager handles null → CLOTH fallback // 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 != null ? material.getTalkRange() : (gaggingComp != null ? gaggingComp.getRange() : 10.0); 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; } } } }