From 078fb3db60a9bce5e44b24452830251ae2867c79 Mon Sep 17 00:00:00 2001 From: Lorenzune Date: Tue, 21 Apr 2026 08:54:02 +0200 Subject: [PATCH] Fix wired text capture and showmessage behavior --- .../wired/effects/WiredEffectBotTalk.java | 2 +- .../effects/WiredEffectBotTalkToHabbo.java | 2 +- .../wired/effects/WiredEffectWhisper.java | 40 +++- .../core/WiredTextInputCaptureSupport.java | 180 +++++++++++++++++- 4 files changed, 213 insertions(+), 11 deletions(-) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTalk.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTalk.java index d027210b..b4f20a8f 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTalk.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTalk.java @@ -82,7 +82,7 @@ public class WiredEffectBotTalk extends InteractionWiredEffect { this.setDelay(delay); this.botName = data[0].substring(0, Math.min(data[0].length(), Emulator.getConfig().getInt("hotel.wired.message.max_length", 100))); - this.message = data[1].substring(0, Math.min(data[1].length(), Emulator.getConfig().getInt("hotel.wired.message.max_length", 100))); + this.message = data[1].substring(0, Math.min(data[1].length(), Emulator.getConfig().getInt("hotel.wired.bot.message.max_length", 100))); this.mode = mode; return true; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTalkToHabbo.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTalkToHabbo.java index 2673993c..c3bc7dfe 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTalkToHabbo.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTalkToHabbo.java @@ -105,7 +105,7 @@ public class WiredEffectBotTalkToHabbo extends InteractionWiredEffect { throw new WiredSaveException("Delay too long"); this.botName = data[0].substring(0, Math.min(data[0].length(), Emulator.getConfig().getInt("hotel.wired.message.max_length", 100))); - this.message = data[1].substring(0, Math.min(data[1].length(), Emulator.getConfig().getInt("hotel.wired.message.max_length", 100))); + this.message = data[1].substring(0, Math.min(data[1].length(), Emulator.getConfig().getInt("hotel.wired.bot.message.max_length", 100))); this.mode = mode; this.setDelay(delay); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectWhisper.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectWhisper.java index 35e957c4..a2b858f4 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectWhisper.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectWhisper.java @@ -34,6 +34,8 @@ public class WiredEffectWhisper extends InteractionWiredEffect { private static final long DELIVERY_DEDUP_TTL_MS = 60_000L; private static final int DELIVERY_DEDUP_CLEANUP_THRESHOLD = 512; private static final ConcurrentHashMap DELIVERY_DEDUP = new ConcurrentHashMap<>(); + private static final int DEFAULT_SHOW_MESSAGE_MAX_LENGTH = 200; + private static final int DEFAULT_SHOW_MESSAGE_MAX_LINES = 8; protected String message = ""; protected int userSource = WiredSourceUtil.SOURCE_TRIGGER; @@ -96,9 +98,12 @@ public class WiredEffectWhisper extends InteractionWiredEffect { if(gameClient.getHabbo() == null || !gameClient.getHabbo().hasPermission(Permission.ACC_SUPERWIRED)) { message = Emulator.getGameEnvironment().getWordFilter().filter(message, null); - message = message.substring(0, Math.min(message.length(), Emulator.getConfig().getInt("hotel.wired.message.max_length", 100))); } + int maxLength = Emulator.getConfig().getInt("hotel.wired.show_message.max_length", DEFAULT_SHOW_MESSAGE_MAX_LENGTH); + int maxLines = Emulator.getConfig().getInt("hotel.wired.show_message.max_lines", DEFAULT_SHOW_MESSAGE_MAX_LINES); + message = clampMessage(message, maxLength, maxLines); + int delay = settings.getDelay(); if(delay > Emulator.getConfig().getInt("hotel.wired.max_delay", 20)) @@ -109,6 +114,35 @@ public class WiredEffectWhisper extends InteractionWiredEffect { return true; } + private static String clampMessage(String value, int maxLength, int maxLines) { + if (value == null || value.isEmpty()) { + return ""; + } + + int safeMaxLength = Math.max(1, maxLength); + int safeMaxLines = Math.max(1, maxLines); + + String normalized = value.replace("\r\n", "\n").replace('\r', '\n'); + String[] lines = normalized.split("\n", -1); + + StringBuilder builder = new StringBuilder(); + int linesToWrite = Math.min(lines.length, safeMaxLines); + + for (int index = 0; index < linesToWrite; index++) { + if (builder.length() > 0) { + builder.append('\n'); + } + + builder.append(lines[index]); + } + + if (builder.length() > safeMaxLength) { + builder.setLength(safeMaxLength); + } + + return builder.toString(); + } + protected List resolveUsers(WiredContext ctx) { return WiredSourceUtil.resolveUsers(ctx, this.userSource); } @@ -212,7 +246,9 @@ public class WiredEffectWhisper extends InteractionWiredEffect { } String msg = buildMessage(ctx, (sharedSourceHabbo != null) ? sharedSourceHabbo : habbo); - habbo.getClient().sendResponse(new RoomUserWhisperComposer(new RoomChatMessage(msg, habbo, habbo, RoomChatMessageBubbles.getBubble(this.bubbleStyle)))); + habbo.getClient().sendResponse(new RoomUserWhisperComposer( + new RoomChatMessage(msg, habbo.getRoomUnit(), RoomChatMessageBubbles.getBubble(this.bubbleStyle)) + )); if (habbo.getRoomUnit().isIdle()) { habbo.getRoomUnit().getRoom().unIdle(habbo); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredTextInputCaptureSupport.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredTextInputCaptureSupport.java index cb73d427..1a6bc9d6 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredTextInputCaptureSupport.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredTextInputCaptureSupport.java @@ -64,8 +64,17 @@ public final class WiredTextInputCaptureSupport { return trigger.matches(stack.triggerItem(), event) ? CaptureResult.matched(new LinkedHashMap<>()) : CaptureResult.noMatch(); } - MatchResult matchResult = matchTemplate(trigger, text, capturersByName); + MatchResult matchResult = matchTemplate(trigger, text, capturersByName, room); if (!matchResult.matches) { + if (WiredManager.isDebugEnabled()) { + WiredManager.debug("[TextCapture] NO_MATCH room={} triggerId={} mode={} key='{}' text='{}' len={}", + room.getId(), + stack.triggerItem().getId(), + trigger.getMatchMode(), + safeForLog(trigger.getKey()), + safeForLog(text), + (text != null ? text.length() : 0)); + } return CaptureResult.noMatch(); } @@ -78,12 +87,28 @@ public final class WiredTextInputCaptureSupport { Integer resolvedValue = capturer.resolveCapturedValue(room, capture.getValue()); if (resolvedValue == null) { + if (WiredManager.isDebugEnabled()) { + WiredManager.debug("[TextCapture] RESOLVE_FAIL room={} triggerId={} capturer='{}' raw='{}' rawLen={}", + room.getId(), + stack.triggerItem().getId(), + capture.getKey(), + safeForLog(capture.getValue()), + (capture.getValue() != null ? capture.getValue().length() : 0)); + } return CaptureResult.noMatch(); } capturedValues.put(capturer.getVariableItemId(), resolvedValue); } + if (WiredManager.isDebugEnabled()) { + WiredManager.debug("[TextCapture] MATCH_OK room={} triggerId={} captures={} textLen={}", + room.getId(), + stack.triggerItem().getId(), + capturedValues.size(), + (text != null ? text.length() : 0)); + } + return CaptureResult.matched(capturedValues); } @@ -108,12 +133,13 @@ public final class WiredTextInputCaptureSupport { return capturers; } - private static MatchResult matchTemplate(WiredTriggerHabboSaysKeyword trigger, String rawText, Map capturersByName) { - String text = rawText != null ? rawText.trim() : ""; + private static MatchResult matchTemplate(WiredTriggerHabboSaysKeyword trigger, String rawText, Map capturersByName, Room room) { + String text = rawText != null ? rawText : ""; + String normalizedText = text.trim(); String template = trigger.getKey() != null ? trigger.getKey().trim() : ""; if (trigger.getMatchMode() == MATCH_ALL_WORDS && template.isEmpty()) { - if (capturersByName.size() != 1 || text.isEmpty()) { + if (capturersByName.size() != 1 || normalizedText.isEmpty()) { return MatchResult.noMatch(); } @@ -123,12 +149,24 @@ public final class WiredTextInputCaptureSupport { return MatchResult.matched(captures); } + MatchResult adjacentCaptureResult = matchAdjacentCapturers(template, rawText, capturersByName, room, trigger.getMatchMode()); + if (adjacentCaptureResult != null) { + if (WiredManager.isDebugEnabled()) { + WiredManager.debug("[TextCapture] ADJACENT mode used key='{}' textLen={} matched={}", + safeForLog(template), + (rawText != null ? rawText.length() : 0), + adjacentCaptureResult.matches); + } + return adjacentCaptureResult; + } + TemplatePattern pattern = buildPattern(template); if (pattern == null) { return MatchResult.noMatch(); } - Matcher matcher = pattern.pattern.matcher(text); + String matchText = pattern.placeholderNames.isEmpty() ? normalizedText : text; + Matcher matcher = pattern.pattern.matcher(matchText); boolean matches = (trigger.getMatchMode() == MATCH_CONTAINS) ? matcher.find() : matcher.matches(); if (!matches) { return MatchResult.noMatch(); @@ -142,12 +180,136 @@ public final class WiredTextInputCaptureSupport { } String capturedValue = matcher.group(index + 1); - captures.put(placeholderName, capturedValue != null ? capturedValue.trim() : ""); + captures.put(placeholderName, normalizeCapturedValue(capturedValue)); } return MatchResult.matched(captures); } + private static MatchResult matchAdjacentCapturers(String template, String rawText, Map capturersByName, Room room, int matchMode) { + if (template == null || template.isEmpty() || rawText == null || capturersByName == null || capturersByName.isEmpty() || room == null) { + return null; + } + + Matcher matcher = PLACEHOLDER_PATTERN.matcher(template); + List placeholderNames = new ArrayList<>(); + int cursor = 0; + + while (matcher.find()) { + if (matcher.start() != cursor) { + return null; + } + + String placeholderName = matcher.group(1) != null ? matcher.group(1).trim().toLowerCase() : ""; + if (placeholderName.isEmpty() || !capturersByName.containsKey(placeholderName)) { + return null; + } + + placeholderNames.add(placeholderName); + cursor = matcher.end(); + } + + if (placeholderNames.isEmpty() || cursor != template.length()) { + return null; + } + + int placeholderCount = placeholderNames.size(); + int textLength = rawText.length(); + + boolean[][] reachable = new boolean[placeholderCount + 1][textLength + 1]; + int[][] previousIndex = new int[placeholderCount + 1][textLength + 1]; + String[][] capturedValues = new String[placeholderCount + 1][textLength + 1]; + + for (int placeholderIndex = 0; placeholderIndex <= placeholderCount; placeholderIndex++) { + for (int textIndex = 0; textIndex <= textLength; textIndex++) { + previousIndex[placeholderIndex][textIndex] = -1; + } + } + + reachable[0][0] = true; + + for (int placeholderIndex = 0; placeholderIndex < placeholderCount; placeholderIndex++) { + String placeholderName = placeholderNames.get(placeholderIndex); + WiredExtraTextInputVariable capturer = capturersByName.get(placeholderName); + if (capturer == null) { + return MatchResult.noMatch(); + } + + for (int textIndex = 0; textIndex <= textLength; textIndex++) { + if (!reachable[placeholderIndex][textIndex]) { + continue; + } + + int minEndIndex = (textIndex < textLength) ? (textIndex + 1) : textIndex; + for (int endIndex = minEndIndex; endIndex <= textLength; endIndex++) { + if (reachable[placeholderIndex + 1][endIndex]) { + continue; + } + + String candidate = rawText.substring(textIndex, endIndex); + if (capturer.resolveCapturedValue(room, candidate) == null) { + continue; + } + + reachable[placeholderIndex + 1][endIndex] = true; + previousIndex[placeholderIndex + 1][endIndex] = textIndex; + capturedValues[placeholderIndex + 1][endIndex] = candidate; + } + } + } + + int resultEndIndex = -1; + if (matchMode == MATCH_CONTAINS) { + for (int endIndex = textLength; endIndex >= 0; endIndex--) { + if (reachable[placeholderCount][endIndex]) { + resultEndIndex = endIndex; + break; + } + } + } else if (reachable[placeholderCount][textLength]) { + resultEndIndex = textLength; + } + + if (resultEndIndex < 0) { + return MatchResult.noMatch(); + } + + LinkedHashMap captures = new LinkedHashMap<>(); + int backtrackTextIndex = resultEndIndex; + for (int placeholderIndex = placeholderCount; placeholderIndex > 0; placeholderIndex--) { + String placeholderName = placeholderNames.get(placeholderIndex - 1); + String capturedValue = capturedValues[placeholderIndex][backtrackTextIndex]; + captures.put(placeholderName, capturedValue != null ? capturedValue : ""); + backtrackTextIndex = previousIndex[placeholderIndex][backtrackTextIndex]; + if (backtrackTextIndex < 0) { + return MatchResult.noMatch(); + } + } + + return MatchResult.matched(captures); + } + + private static String normalizeCapturedValue(String value) { + return value != null ? value : ""; + } + + private static String safeForLog(String value) { + if (value == null) { + return ""; + } + + String normalized = value + .replace("\r", "\\r") + .replace("\n", "\\n") + .replace("\u00A0", "⍽"); + + if (normalized.length() > 180) { + return normalized.substring(0, 180) + "...(" + normalized.length() + ")"; + } + + return normalized; + } + private static TemplatePattern buildPattern(String template) { if (template == null || template.isEmpty()) { return null; @@ -160,7 +322,7 @@ public final class WiredTextInputCaptureSupport { while (matcher.find()) { regex.append(Pattern.quote(template.substring(cursor, matcher.start()))); - regex.append("(.+?)"); + regex.append(hasPlaceholderAfter(template, matcher.end()) ? "(.+?)" : "(.+)"); String placeholderName = matcher.group(1) != null ? matcher.group(1).trim().toLowerCase() : ""; placeholderNames.add(placeholderName); @@ -176,6 +338,10 @@ public final class WiredTextInputCaptureSupport { return new TemplatePattern(Pattern.compile(regex.toString(), Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE), placeholderNames); } + private static boolean hasPlaceholderAfter(String template, int cursor) { + return PLACEHOLDER_PATTERN.matcher(template.substring(cursor)).find(); + } + public static void applyToContext(WiredContext ctx, Room room, CaptureResult captureResult) { if (ctx == null || room == null || captureResult == null || !captureResult.matches || captureResult.capturedValues.isEmpty()) { return;