diff --git a/Database Updates/008_add_show_message_chat_bubbles.sql b/Database Updates/008_add_show_message_chat_bubbles.sql new file mode 100644 index 00000000..abf71bef --- /dev/null +++ b/Database Updates/008_add_show_message_chat_bubbles.sql @@ -0,0 +1,25 @@ +INSERT INTO `chat_bubbles` (`type`, `name`, `permission`, `overridable`, `triggers_talking_furniture`) VALUES +(200, 'SHOW_MESSAGE_RED', '', 1, 0), +(201, 'SHOW_MESSAGE_GREEN', '', 1, 0), +(202, 'SHOW_MESSAGE_BLUE', '', 1, 0), +(210, 'SHOW_MESSAGE_ALERT', '', 1, 0), +(211, 'SHOW_MESSAGE_INFO', '', 1, 0), +(212, 'SHOW_MESSAGE_WARNING', '', 1, 0), +(220, 'SHOW_MESSAGE_WRONG', '', 1, 0), +(221, 'SHOW_MESSAGE_WRONG_CIRCLED', '', 1, 0), +(222, 'SHOW_MESSAGE_CORRECT', '', 1, 0), +(223, 'SHOW_MESSAGE_CORRECT_CIRCLED', '', 1, 0), +(224, 'SHOW_MESSAGE_QUESTION', '', 1, 0), +(225, 'SHOW_MESSAGE_QUESTION_CIRCLED', '', 1, 0), +(226, 'SHOW_MESSAGE_ARROW_UP', '', 1, 0), +(227, 'SHOW_MESSAGE_ARROW_UP_CIRCLED', '', 1, 0), +(228, 'SHOW_MESSAGE_ARROW_DOWN', '', 1, 0), +(229, 'SHOW_MESSAGE_ARROW_DOWN_CIRCLED', '', 1, 0), +(250, 'SHOW_MESSAGE_SKULL', '', 1, 0), +(251, 'SHOW_MESSAGE_SKULL_ALT', '', 1, 0), +(252, 'SHOW_MESSAGE_MAGNIFIER', '', 1, 0) +ON DUPLICATE KEY UPDATE + `name` = VALUES(`name`), + `permission` = VALUES(`permission`), + `overridable` = VALUES(`overridable`), + `triggers_talking_furniture` = VALUES(`triggers_talking_furniture`); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectAlert.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectAlert.java index e149b872..a6c9510e 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectAlert.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectAlert.java @@ -3,12 +3,14 @@ package com.eu.habbo.habbohotel.items.interactions.wired.effects; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.habbohotel.wired.core.WiredTextPlaceholderUtil; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.List; public class WiredEffectAlert extends WiredEffectWhisper { public WiredEffectAlert(ResultSet set, Item baseItem) throws SQLException { @@ -22,14 +24,25 @@ public class WiredEffectAlert extends WiredEffectWhisper { @Override public void execute(WiredContext ctx) { Room room = ctx.room(); + List sourceUsers = resolveUsers(ctx); + List recipients = resolveRecipients(ctx, sourceUsers); + Habbo sharedSourceHabbo = (this.visibilitySelection == VISIBILITY_ALL_ROOM_USERS) + ? resolveMessageSourceHabbo(ctx, sourceUsers) + : null; - for (com.eu.habbo.habbohotel.rooms.RoomUnit unit : resolveUsers(ctx)) { - Habbo habbo = room.getHabbo(unit); - if (habbo == null) continue; + for (Habbo habbo : recipients) { + if (!shouldDeliverToRecipient(ctx, habbo)) { + continue; + } + + Habbo referenceHabbo = (sharedSourceHabbo != null) ? sharedSourceHabbo : habbo; + String username = (referenceHabbo != null && referenceHabbo.getHabboInfo() != null) + ? referenceHabbo.getHabboInfo().getUsername() + : ""; String message = this.message .replace("%online%", Emulator.getGameEnvironment().getHabboManager().getOnlineCount() + "") - .replace("%username%", habbo.getHabboInfo().getUsername()) + .replace("%username%", username) .replace("%roomsloaded%", Emulator.getGameEnvironment().getRoomManager().loadedRoomsCount() + ""); habbo.alert(WiredTextPlaceholderUtil.applyUsernamePlaceholders(ctx, message)); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectChangeVariableValue.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectChangeVariableValue.java index 81fe314a..5e7e5c37 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectChangeVariableValue.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectChangeVariableValue.java @@ -595,7 +595,13 @@ public class WiredEffectChangeVariableValue extends InteractionWiredEffect { } private boolean writeUserInternalValue(Room room, RoomUnit roomUnit, String key, int value) { - return WiredInternalVariableSupport.writeUserValue(room, roomUnit, key, value); + return WiredInternalVariableSupport.writeUserValue( + room, + roomUnit, + key, + value, + WiredUserMovementHelper.DEFAULT_ANIMATION_DURATION, + false); } private Integer readFurniInternalValue(Room room, HabboItem item, String key) { @@ -676,7 +682,15 @@ public class WiredEffectChangeVariableValue extends InteractionWiredEffect { if (targetTile == null || targetTile.state == RoomTileState.INVALID) return false; double targetZ = targetTile.getStackHeight() + ((targetTile.state == RoomTileState.SIT) ? -0.5 : 0); - return WiredUserMovementHelper.moveUser(room, roomUnit, targetTile, targetZ, roomUnit.getBodyRotation(), roomUnit.getHeadRotation(), 0, true); + return WiredUserMovementHelper.moveUser( + room, + roomUnit, + targetTile, + targetZ, + roomUnit.getBodyRotation(), + roomUnit.getHeadRotation(), + WiredUserMovementHelper.DEFAULT_ANIMATION_DURATION, + false); } private boolean moveFurniTo(Room room, HabboItem item, int x, int y, int rotation, double z) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveRotateUser.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveRotateUser.java index 50e9b916..5df4fdc2 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveRotateUser.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveRotateUser.java @@ -10,7 +10,6 @@ import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.rooms.RoomTileState; import com.eu.habbo.habbohotel.rooms.RoomUnit; -import com.eu.habbo.habbohotel.rooms.RoomUnitStatus; import com.eu.habbo.habbohotel.rooms.RoomUserRotation; import com.eu.habbo.habbohotel.wired.WiredEffectType; import com.eu.habbo.habbohotel.wired.core.WiredContext; @@ -19,7 +18,6 @@ import com.eu.habbo.habbohotel.wired.core.WiredMovementPhysics; import com.eu.habbo.habbohotel.wired.core.WiredMoveCarryHelper; import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil; import com.eu.habbo.habbohotel.wired.core.WiredUserMovementHelper; -import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import gnu.trove.procedure.TObjectProcedure; @@ -32,10 +30,6 @@ import java.util.List; public class WiredEffectMoveRotateUser extends InteractionWiredEffect { private static final int ROTATION_CLOCKWISE = 8; private static final int ROTATION_COUNTER_CLOCKWISE = 9; - private static final String CACHE_ACTIVE_UNTIL = "wired.move_rotate_user.active_until"; - private static final String CACHE_WALK_IN_PLACE_UNTIL = "wired.move_rotate_user.walk_in_place_until"; - private static final int WALK_IN_PLACE_DURATION_MS = 550; - private static final int ROTATION_ACTIVE_WINDOW_MS = 250; public static final WiredEffectType type = WiredEffectType.MOVE_ROTATE_USER; @@ -64,15 +58,21 @@ public class WiredEffectMoveRotateUser extends InteractionWiredEffect { boolean hasRotation = this.rotationDirection >= 0; RoomUserRotation targetBodyRotation = hasRotation ? this.getTargetRotation(roomUnit) : roomUnit.getBodyRotation(); RoomUserRotation targetHeadRotation = hasRotation ? targetBodyRotation : roomUnit.getHeadRotation(); + + if (roomUnit.isWalking()) { + if (hasRotation) { + WiredUserMovementHelper.updateUserDirection(room, roomUnit, targetBodyRotation, targetHeadRotation); + } + continue; + } + RoomTile targetTile = (this.movementDirection >= 0) ? this.getTargetTile(room, roomUnit, this.movementDirection) : null; boolean canMove = this.canMoveTo(room, roomUnit, targetTile, movementPhysics); boolean noAnimation = WiredMoveCarryHelper.hasNoAnimationExtra(room, this); int animationDuration = noAnimation ? 0 : WiredMoveCarryHelper.getAnimationDuration(room, this, WiredUserMovementHelper.DEFAULT_ANIMATION_DURATION); - int activeWindowMs = this.resolveActiveWindow(canMove, hasRotation, noAnimation, animationDuration); if (canMove) { double targetZ = targetTile.getStackHeight() + ((targetTile.state == RoomTileState.SIT) ? -0.5 : 0); - this.markActive(roomUnit, activeWindowMs); if (!WiredUserMovementHelper.moveUser(room, roomUnit, targetTile, targetZ, targetBodyRotation, targetHeadRotation, animationDuration, noAnimation, movementPhysics)) { if (hasRotation) { @@ -83,7 +83,6 @@ public class WiredEffectMoveRotateUser extends InteractionWiredEffect { } if (hasRotation) { - this.markActive(roomUnit, activeWindowMs); WiredUserMovementHelper.updateUserDirection(room, roomUnit, targetBodyRotation, targetHeadRotation); } } @@ -272,103 +271,8 @@ public class WiredEffectMoveRotateUser extends InteractionWiredEffect { return WiredUserMovementHelper.canMoveTo(room, roomUnit, targetTile, movementPhysics); } - private void markActive(RoomUnit roomUnit, int durationMs) { - if (roomUnit == null || durationMs <= 0) { - return; - } - - long activeUntil = System.currentTimeMillis() + durationMs; - roomUnit.getCacheable().put(CACHE_ACTIVE_UNTIL, activeUntil); - } - - private int resolveActiveWindow(boolean canMove, boolean hasRotation, boolean noAnimation, int animationDuration) { - if (noAnimation) { - return 0; - } - - if (canMove) { - return Math.max(1, animationDuration); - } - - if (hasRotation) { - return ROTATION_ACTIVE_WINDOW_MS; - } - - return 0; - } - public static boolean handleWalkWhileActive(Room room, RoomUnit roomUnit, RoomTile targetTile) { - if (room == null || roomUnit == null || !isActive(roomUnit)) { - return false; - } - - long walkInPlaceUntil = System.currentTimeMillis() + WALK_IN_PLACE_DURATION_MS; - roomUnit.getCacheable().put(CACHE_WALK_IN_PLACE_UNTIL, walkInPlaceUntil); - roomUnit.stopWalking(); - roomUnit.removeStatus(RoomUnitStatus.MOVE); - roomUnit.setStatus(RoomUnitStatus.MOVE, roomUnit.getX() + "," + roomUnit.getY() + "," + roomUnit.getZ()); - roomUnit.statusUpdate(false); - room.sendComposer(new RoomUserStatusComposer(roomUnit).compose()); - - Emulator.getThreading().run(() -> clearWalkInPlace(room, roomUnit, walkInPlaceUntil), WALK_IN_PLACE_DURATION_MS); - return true; - } - - private static boolean isActive(RoomUnit roomUnit) { - Long activeUntil = getCachedTimestamp(roomUnit, CACHE_ACTIVE_UNTIL); - return activeUntil != null && activeUntil > System.currentTimeMillis(); - } - - private static boolean isWalkInPlaceActive(RoomUnit roomUnit) { - Long walkInPlaceUntil = getCachedTimestamp(roomUnit, CACHE_WALK_IN_PLACE_UNTIL); - - if (walkInPlaceUntil == null) { - return false; - } - - if (walkInPlaceUntil <= System.currentTimeMillis()) { - roomUnit.getCacheable().remove(CACHE_WALK_IN_PLACE_UNTIL); - return false; - } - - return true; - } - - private static Long getCachedTimestamp(RoomUnit roomUnit, String key) { - if (roomUnit == null || key == null) { - return null; - } - - Object value = roomUnit.getCacheable().get(key); - - if (value instanceof Long) { - return (Long) value; - } - - if (value instanceof Number) { - return ((Number) value).longValue(); - } - - return null; - } - - private static void clearWalkInPlace(Room room, RoomUnit roomUnit, long expectedUntil) { - if (room == null || roomUnit == null || !room.isLoaded()) { - return; - } - - Long currentUntil = getCachedTimestamp(roomUnit, CACHE_WALK_IN_PLACE_UNTIL); - if (currentUntil == null || currentUntil.longValue() != expectedUntil) { - return; - } - - roomUnit.getCacheable().remove(CACHE_WALK_IN_PLACE_UNTIL); - - if (roomUnit.hasStatus(RoomUnitStatus.MOVE) && !roomUnit.isWalking()) { - roomUnit.removeStatus(RoomUnitStatus.MOVE); - roomUnit.statusUpdate(false); - room.sendComposer(new RoomUserStatusComposer(roomUnit).compose()); - } + return false; } static class JsonData { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectSendSignal.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectSendSignal.java index fd2b5a45..f57ebd92 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectSendSignal.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectSendSignal.java @@ -118,17 +118,19 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { : Collections.singletonList(defaultFurni); int nextDepth = currentDepth + 1; + boolean isolateBranchContext = (signalPerUser && forwardedUsers.size() > 1) + || (signalPerFurni && forwardedFurni.size() > 1); for (RoomUnit user : usersToSend) { for (HabboItem sourceItem : furniToSend) { for (HabboItem antenna : resolvedAntennas) { - fireSignalAtAntenna(ctx, room, antenna, user, sourceItem, nextDepth); + fireSignalAtAntenna(ctx, room, antenna, user, sourceItem, nextDepth, isolateBranchContext); } } } } - private void fireSignalAtAntenna(WiredContext ctx, Room room, HabboItem antenna, RoomUnit actor, HabboItem sourceItem, int depth) { + private void fireSignalAtAntenna(WiredContext ctx, Room room, HabboItem antenna, RoomUnit actor, HabboItem sourceItem, int depth, boolean isolateBranchContext) { if (antenna == null) return; RoomTile tile = room.getLayout().getTile(antenna.getX(), antenna.getY()); if (tile == null) return; @@ -146,13 +148,13 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { .signalChannel(signalChannel) .signalUserCount(actor != null ? 1 : 0) .signalFurniCount(sourceItem != null ? 1 : 0) - .contextVariableScope(ctx.contextVariables()) + .contextVariableScope(isolateBranchContext ? ctx.contextVariables().copy() : ctx.contextVariables()) .triggeredByEffect(true); if (actor != null) builder.actor(actor); if (sourceItem != null) builder.sourceItem(sourceItem); - boolean result = WiredManager.handleEvent(builder.build()); + boolean result = WiredManager.dispatchEffectTriggeredEvent(builder.build()); LOGGER.debug("[SendSignal] handleEvent returned: {}", result); } 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 4f8cb4ff..35e957c4 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 @@ -22,13 +22,23 @@ import gnu.trove.procedure.TObjectProcedure; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; public class WiredEffectWhisper extends InteractionWiredEffect { public static final WiredEffectType type = WiredEffectType.SHOW_MESSAGE; + protected static final int VISIBILITY_SOURCE_USERS = 0; + protected static final int VISIBILITY_ALL_ROOM_USERS = 1; + 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<>(); protected String message = ""; protected int userSource = WiredSourceUtil.SOURCE_TRIGGER; + protected int visibilitySelection = VISIBILITY_SOURCE_USERS; + protected int bubbleStyle = RoomChatMessageBubbles.WIRED.getType(); public WiredEffectWhisper(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -46,8 +56,10 @@ public class WiredEffectWhisper extends InteractionWiredEffect { message.appendInt(this.getBaseItem().getSpriteId()); message.appendInt(this.getId()); message.appendString(this.message); - message.appendInt(1); + message.appendInt(3); message.appendInt(this.userSource); + message.appendInt(this.visibilitySelection); + message.appendInt(this.bubbleStyle); message.appendInt(0); message.appendInt(type.code); message.appendInt(this.getDelay()); @@ -77,6 +89,10 @@ public class WiredEffectWhisper extends InteractionWiredEffect { String message = settings.getStringParam(); int[] params = settings.getIntParams(); this.userSource = (params.length > 0) ? params[0] : WiredSourceUtil.SOURCE_TRIGGER; + this.visibilitySelection = (params.length > 1 && params[1] == VISIBILITY_ALL_ROOM_USERS) + ? VISIBILITY_ALL_ROOM_USERS + : VISIBILITY_SOURCE_USERS; + this.bubbleStyle = (params.length > 2) ? params[2] : RoomChatMessageBubbles.WIRED.getType(); if(gameClient.getHabbo() == null || !gameClient.getHabbo().hasPermission(Permission.ACC_SUPERWIRED)) { message = Emulator.getGameEnvironment().getWordFilter().filter(message, null); @@ -97,17 +113,106 @@ public class WiredEffectWhisper extends InteractionWiredEffect { return WiredSourceUtil.resolveUsers(ctx, this.userSource); } + protected List resolveRecipients(WiredContext ctx, List sourceUsers) { + Room room = ctx.room(); + LinkedHashMap recipients = new LinkedHashMap<>(); + + if (room == null) { + return Collections.emptyList(); + } + + if (this.visibilitySelection == VISIBILITY_ALL_ROOM_USERS) { + for (Habbo habbo : room.getCurrentHabbos().values()) { + addRecipient(recipients, habbo); + } + } else { + for (RoomUnit roomUnit : sourceUsers) { + addRecipient(recipients, room.getHabbo(roomUnit)); + } + } + + return new ArrayList<>(recipients.values()); + } + + protected Habbo resolveMessageSourceHabbo(WiredContext ctx, List sourceUsers) { + Room room = ctx.room(); + + if (room != null) { + for (RoomUnit roomUnit : sourceUsers) { + Habbo habbo = room.getHabbo(roomUnit); + if (habbo != null) { + return habbo; + } + } + } + + return (room == null) ? null : ctx.actor().map(roomUnit -> room.getHabbo(roomUnit)).orElse(null); + } + + protected String buildMessage(WiredContext ctx, Habbo referenceHabbo) { + String username = ""; + + if (referenceHabbo != null && referenceHabbo.getHabboInfo() != null) { + username = referenceHabbo.getHabboInfo().getUsername(); + } + + String msg = this.message + .replace("%user%", username) + .replace("%online_count%", Emulator.getGameEnvironment().getHabboManager().getOnlineCount() + "") + .replace("%room_count%", Emulator.getGameEnvironment().getRoomManager().getActiveRooms().size() + ""); + + return WiredTextPlaceholderUtil.applyUsernamePlaceholders(ctx, msg); + } + + private void addRecipient(LinkedHashMap recipients, Habbo habbo) { + if (habbo == null || habbo.getHabboInfo() == null || habbo.getClient() == null) { + return; + } + + recipients.putIfAbsent(habbo.getHabboInfo().getId(), habbo); + } + + protected boolean shouldDeliverToRecipient(WiredContext ctx, Habbo habbo) { + if (ctx == null || habbo == null || habbo.getHabboInfo() == null) { + return true; + } + + long now = System.currentTimeMillis(); + cleanupDeliveryDedup(now); + + String deliveryKey = buildDeliveryKey(ctx, habbo); + + return DELIVERY_DEDUP.putIfAbsent(deliveryKey, now) == null; + } + + private String buildDeliveryKey(WiredContext ctx, Habbo habbo) { + return ctx.room().getId() + ":" + this.getId() + ":" + habbo.getHabboInfo().getId() + ":" + ctx.event().getCreatedAtMs(); + } + + private static void cleanupDeliveryDedup(long now) { + if (DELIVERY_DEDUP.size() < DELIVERY_DEDUP_CLEANUP_THRESHOLD) { + return; + } + + DELIVERY_DEDUP.entrySet().removeIf(entry -> (now - entry.getValue()) > DELIVERY_DEDUP_TTL_MS); + } + @Override public void execute(WiredContext ctx) { - Room room = ctx.room(); if (this.message.length() > 0) { - for (RoomUnit roomUnit : resolveUsers(ctx)) { - Habbo habbo = room.getHabbo(roomUnit); - if (habbo == null) continue; + List sourceUsers = resolveUsers(ctx); + List recipients = resolveRecipients(ctx, sourceUsers); + Habbo sharedSourceHabbo = (this.visibilitySelection == VISIBILITY_ALL_ROOM_USERS) + ? resolveMessageSourceHabbo(ctx, sourceUsers) + : null; - String msg = this.message.replace("%user%", habbo.getHabboInfo().getUsername()).replace("%online_count%", Emulator.getGameEnvironment().getHabboManager().getOnlineCount() + "").replace("%room_count%", Emulator.getGameEnvironment().getRoomManager().getActiveRooms().size() + ""); - msg = WiredTextPlaceholderUtil.applyUsernamePlaceholders(ctx, msg); - habbo.getClient().sendResponse(new RoomUserWhisperComposer(new RoomChatMessage(msg, habbo, habbo, RoomChatMessageBubbles.WIRED))); + for (Habbo habbo : recipients) { + if (!shouldDeliverToRecipient(ctx, habbo)) { + continue; + } + + String msg = buildMessage(ctx, (sharedSourceHabbo != null) ? sharedSourceHabbo : habbo); + habbo.getClient().sendResponse(new RoomUserWhisperComposer(new RoomChatMessage(msg, habbo, habbo, RoomChatMessageBubbles.getBubble(this.bubbleStyle)))); if (habbo.getRoomUnit().isIdle()) { habbo.getRoomUnit().getRoom().unIdle(habbo); @@ -124,7 +229,7 @@ public class WiredEffectWhisper extends InteractionWiredEffect { @Override public String getWiredData() { - return WiredManager.getGson().toJson(new JsonData(this.message, this.getDelay(), this.userSource)); + return WiredManager.getGson().toJson(new JsonData(this.message, this.getDelay(), this.userSource, this.visibilitySelection, this.bubbleStyle)); } @Override @@ -135,7 +240,11 @@ public class WiredEffectWhisper extends InteractionWiredEffect { JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.message = data.message; - this.userSource = data.userSource; + this.userSource = (data.userSource != null) ? data.userSource : WiredSourceUtil.SOURCE_TRIGGER; + this.visibilitySelection = (data.visibilitySelection != null && data.visibilitySelection == VISIBILITY_ALL_ROOM_USERS) + ? VISIBILITY_ALL_ROOM_USERS + : VISIBILITY_SOURCE_USERS; + this.bubbleStyle = (data.bubbleStyle != null) ? data.bubbleStyle : RoomChatMessageBubbles.WIRED.getType(); } else { this.message = ""; @@ -146,6 +255,8 @@ public class WiredEffectWhisper extends InteractionWiredEffect { } this.userSource = WiredSourceUtil.SOURCE_TRIGGER; + this.visibilitySelection = VISIBILITY_SOURCE_USERS; + this.bubbleStyle = RoomChatMessageBubbles.WIRED.getType(); this.needsUpdate(true); } } @@ -154,6 +265,8 @@ public class WiredEffectWhisper extends InteractionWiredEffect { public void onPickUp() { this.message = ""; this.userSource = WiredSourceUtil.SOURCE_TRIGGER; + this.visibilitySelection = VISIBILITY_SOURCE_USERS; + this.bubbleStyle = RoomChatMessageBubbles.WIRED.getType(); this.setDelay(0); } @@ -170,12 +283,16 @@ public class WiredEffectWhisper extends InteractionWiredEffect { static class JsonData { String message; int delay; - int userSource; + Integer userSource; + Integer visibilitySelection; + Integer bubbleStyle; - public JsonData(String message, int delay, int userSource) { + public JsonData(String message, int delay, int userSource, int visibilitySelection, int bubbleStyle) { this.message = message; this.delay = delay; this.userSource = userSource; + this.visibilitySelection = visibilitySelection; + this.bubbleStyle = bubbleStyle; } } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraVariableEcho.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraVariableEcho.java index e063ec88..0dd43827 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraVariableEcho.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraVariableEcho.java @@ -671,8 +671,16 @@ public class WiredExtraVariableEcho extends InteractionWiredExtra { RoomTile targetTile = room.getLayout().getTile((short) x, (short) y); if (targetTile == null || targetTile.state == RoomTileState.INVALID) return false; - double targetZ = targetTile.getStackHeight() + ((targetTile.state == RoomTileState.SIT) ? -0.5 : 0); - return WiredUserMovementHelper.moveUser(room, roomUnit, targetTile, targetZ, roomUnit.getBodyRotation(), roomUnit.getHeadRotation(), 0, true); + double targetZ = WiredUserMovementHelper.resolveUserTargetZ(room, targetTile); + return WiredUserMovementHelper.moveUser( + room, + roomUnit, + targetTile, + targetZ, + roomUnit.getBodyRotation(), + roomUnit.getHeadRotation(), + WiredUserMovementHelper.DEFAULT_ANIMATION_DURATION, + false); } private boolean moveFurniTo(Room room, HabboItem item, int x, int y, int rotation, double z) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatMessageBubbles.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatMessageBubbles.java index 05093e8e..d38133e4 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatMessageBubbles.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatMessageBubbles.java @@ -52,14 +52,14 @@ public class RoomChatMessageBubbles { public static final RoomChatMessageBubbles UNKNOWN_43 = new RoomChatMessageBubbles(43, "UNKNOWN_43", "", true, false); public static final RoomChatMessageBubbles UNKNOWN_44 = new RoomChatMessageBubbles(44, "UNKNOWN_44", "", true, false); public static final RoomChatMessageBubbles UNKNOWN_45 = new RoomChatMessageBubbles(45, "UNKNOWN_45", "", true, false); - public static final RoomChatMessageBubbles UNKNOWN_46 = new RoomChatMessageBubbles(45, "UNKNOWN_46", "", true, false); - public static final RoomChatMessageBubbles UNKNOWN_47 = new RoomChatMessageBubbles(45, "UNKNOWN_47", "", true, false); - public static final RoomChatMessageBubbles UNKNOWN_48 = new RoomChatMessageBubbles(45, "UNKNOWN_48", "", true, false); - public static final RoomChatMessageBubbles UNKNOWN_49 = new RoomChatMessageBubbles(45, "UNKNOWN_49", "", true, false); - public static final RoomChatMessageBubbles UNKNOWN_50 = new RoomChatMessageBubbles(45, "UNKNOWN_50", "", true, false); - public static final RoomChatMessageBubbles UNKNOWN_51 = new RoomChatMessageBubbles(45, "UNKNOWN_51", "", true, false); - public static final RoomChatMessageBubbles UNKNOWN_52 = new RoomChatMessageBubbles(45, "UNKNOWN_52", "", true, false); - public static final RoomChatMessageBubbles UNKNOWN_53 = new RoomChatMessageBubbles(45, "UNKNOWN_53", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_46 = new RoomChatMessageBubbles(46, "UNKNOWN_46", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_47 = new RoomChatMessageBubbles(47, "UNKNOWN_47", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_48 = new RoomChatMessageBubbles(48, "UNKNOWN_48", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_49 = new RoomChatMessageBubbles(49, "UNKNOWN_49", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_50 = new RoomChatMessageBubbles(50, "UNKNOWN_50", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_51 = new RoomChatMessageBubbles(51, "UNKNOWN_51", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_52 = new RoomChatMessageBubbles(52, "UNKNOWN_52", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_53 = new RoomChatMessageBubbles(53, "UNKNOWN_53", "", true, false); static { @@ -167,11 +167,11 @@ public class RoomChatMessageBubbles { public static void removeDynamicBubbles() { synchronized (BUBBLES) { - BUBBLES.entrySet().removeIf(entry -> entry.getKey() > 45); + BUBBLES.entrySet().removeIf(entry -> entry.getKey() > 53); } } public static RoomChatMessageBubbles[] values() { return BUBBLES.values().toArray(new RoomChatMessageBubbles[0]); } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java index 10a6f172..69b29a69 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java @@ -581,9 +581,11 @@ public final class WiredEngine { WiredMoveCarryHelper.beginMovementCollection(); - try { + try (WiredInternalVariableSupport.UserMoveBatchScope ignored = WiredInternalVariableSupport.beginUserMoveBatch()) { // Execute selected effects - for (IWiredEffect effect : toExecute) { + for (int effectIndex = 0; effectIndex < toExecute.size(); effectIndex++) { + IWiredEffect effect = toExecute.get(effectIndex); + // Check if effect requires actor if (effect.requiresActor() && !ctx.hasActor()) { continue; @@ -592,8 +594,30 @@ public final class WiredEngine { // Handle delay int delay = effect.getDelay(); if (delay > 0) { - // Schedule delayed execution - scheduleDelayedEffect(effect, ctx, delay, currentTime); + List delayedBatch = new ArrayList<>(); + delayedBatch.add(effect); + + while ((effectIndex + 1) < toExecute.size()) { + IWiredEffect nextEffect = toExecute.get(effectIndex + 1); + + if (nextEffect == null || nextEffect.getDelay() != delay) { + break; + } + + if (nextEffect.requiresActor() && !ctx.hasActor()) { + effectIndex++; + continue; + } + + delayedBatch.add(nextEffect); + effectIndex++; + } + + if (delayedBatch.size() == 1) { + scheduleDelayedEffect(effect, ctx, delay, currentTime); + } else { + scheduleOrderedEffectBatch(delayedBatch, ctx, delay, currentTime); + } } else { // Execute immediately ctx.state().step(); @@ -672,82 +696,7 @@ public final class WiredEngine { return; } - THashSet extras = room.getRoomSpecialTypes().getExtras( - stack.triggerItem().getX(), - stack.triggerItem().getY()); - - if (extras == null || extras.isEmpty()) { - return; - } - - int furniLimit = Integer.MAX_VALUE; - int userLimit = Integer.MAX_VALUE; - List furniVariableFilters = new ArrayList<>(); - List userVariableFilters = new ArrayList<>(); - - for (InteractionWiredExtra extra : extras) { - if (extra instanceof WiredExtraFilterFurni) { - furniLimit = Math.min(furniLimit, ((WiredExtraFilterFurni) extra).getAmount()); - } else if (extra instanceof WiredExtraFilterUser) { - userLimit = Math.min(userLimit, ((WiredExtraFilterUser) extra).getAmount()); - } else if (extra instanceof WiredExtraFilterFurniByVariable) { - furniVariableFilters.add((WiredExtraFilterFurniByVariable) extra); - } else if (extra instanceof WiredExtraFilterUsersByVariable) { - userVariableFilters.add((WiredExtraFilterUsersByVariable) extra); - } - } - - furniVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId())); - userVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId())); - - if (ctx.targets().isItemsModifiedBySelector()) { - Iterable filteredItems = ctx.targets().items(); - - for (WiredExtraFilterFurniByVariable extra : furniVariableFilters) { - filteredItems = extra.filterItems(room, ctx, filteredItems); - } - - if (furniLimit != Integer.MAX_VALUE) { - filteredItems = limitIterable(filteredItems, furniLimit); - } - - ctx.targets().setItems(filteredItems); - } - - if (ctx.targets().isUsersModifiedBySelector()) { - Iterable filteredUsers = ctx.targets().users(); - - for (WiredExtraFilterUsersByVariable extra : userVariableFilters) { - filteredUsers = extra.filterUsers(room, ctx, filteredUsers); - } - - if (userLimit != Integer.MAX_VALUE) { - filteredUsers = limitIterable(filteredUsers, userLimit); - } - - ctx.targets().setUsers(filteredUsers); - } - } - - private List limitIterable(Iterable values, int limit) { - List result = new ArrayList<>(); - - if (values == null || limit <= 0) { - return result; - } - - for (T value : values) { - if (value != null) { - result.add(value); - } - } - - if (result.size() <= limit) { - return result; - } - - Collections.shuffle(result, Emulator.getRandom()); - return new ArrayList<>(result.subList(0, limit)); + WiredSelectionFilterSupport.applySelectorFilters(room, stack.triggerItem(), ctx); } /** @@ -916,7 +865,7 @@ public final class WiredEngine { WiredMoveCarryHelper.beginMovementCollection(); - try { + try (WiredInternalVariableSupport.UserMoveBatchScope ignored = WiredInternalVariableSupport.beginUserMoveBatch()) { for (IWiredEffect effect : batch) { try { if (!useExecutionTimeForCooldown) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredInternalVariableSupport.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredInternalVariableSupport.java index c1fe88b4..24f70935 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredInternalVariableSupport.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredInternalVariableSupport.java @@ -26,6 +26,10 @@ import java.time.temporal.WeekFields; import java.util.Locale; public final class WiredInternalVariableSupport { + private static final ThreadLocal USER_MOVE_INSTANT_OVERRIDE = new ThreadLocal<>(); + private static final ThreadLocal USER_MOVE_BATCH = new ThreadLocal<>(); + private static final ThreadLocal USER_MOVE_BATCH_DEPTH = new ThreadLocal<>(); + private WiredInternalVariableSupport() { } @@ -225,15 +229,29 @@ public final class WiredInternalVariableSupport { } public static boolean writeUserValue(Room room, RoomUnit roomUnit, String key, int value) { + Boolean instantOverride = USER_MOVE_INSTANT_OVERRIDE.get(); + + if (instantOverride != null) { + return writeUserValue(room, roomUnit, key, value, WiredUserMovementHelper.DEFAULT_ANIMATION_DURATION, instantOverride); + } + + return writeUserValue(room, roomUnit, key, value, WiredUserMovementHelper.DEFAULT_ANIMATION_DURATION, false); + } + + public static boolean writeUserValue(Room room, RoomUnit roomUnit, String key, int value, int animationDuration, boolean noAnimation) { if (room == null || roomUnit == null) { return false; } String normalized = normalizeKey(key); + if (stageUserMoveIfPossible(room, roomUnit, normalized, value, animationDuration, noAnimation)) { + return true; + } + return switch (normalized) { - case "@position_x" -> moveUserTo(room, roomUnit, value, roomUnit.getY()); - case "@position_y" -> moveUserTo(room, roomUnit, roomUnit.getX(), value); + case "@position_x" -> moveUserTo(room, roomUnit, value, roomUnit.getY(), animationDuration, noAnimation); + case "@position_y" -> moveUserTo(room, roomUnit, roomUnit.getX(), value, animationDuration, noAnimation); case "@direction" -> { RoomUserRotation rotation = RoomUserRotation.fromValue(value); yield WiredUserMovementHelper.updateUserDirection(room, roomUnit, rotation, rotation); @@ -242,6 +260,24 @@ public final class WiredInternalVariableSupport { }; } + public static UserMoveInstantScope beginUserMoveInstantOverride(boolean instant) { + Boolean previousValue = USER_MOVE_INSTANT_OVERRIDE.get(); + USER_MOVE_INSTANT_OVERRIDE.set(instant); + return new UserMoveInstantScope(previousValue); + } + + public static UserMoveBatchScope beginUserMoveBatch() { + Integer previousDepth = USER_MOVE_BATCH_DEPTH.get(); + int nextDepth = (previousDepth == null) ? 1 : (previousDepth + 1); + USER_MOVE_BATCH_DEPTH.set(nextDepth); + + if (nextDepth == 1) { + USER_MOVE_BATCH.set(new UserMoveBatch()); + } + + return new UserMoveBatchScope(previousDepth); + } + public static Integer readFurniValue(Room room, HabboItem item, String key) { if (room == null || item == null) { return null; @@ -281,7 +317,7 @@ public final class WiredInternalVariableSupport { String normalized = normalizeKey(key); if ("@state".equals(normalized)) { - item.setExtradata(String.valueOf(value)); + item.setExtradata(String.valueOf(normalizeFurniStateValue(item, value))); room.updateItemState(item); return true; } @@ -492,7 +528,7 @@ public final class WiredInternalVariableSupport { return parseInteger(roomUnit.getStatus(status)); } - private static boolean moveUserTo(Room room, RoomUnit roomUnit, int x, int y) { + private static boolean moveUserTo(Room room, RoomUnit roomUnit, int x, int y, int animationDuration, boolean noAnimation) { if (room == null || roomUnit == null || room.getLayout() == null) { return false; } @@ -502,8 +538,95 @@ public final class WiredInternalVariableSupport { return false; } - double targetZ = targetTile.getStackHeight() + ((targetTile.state == RoomTileState.SIT) ? -0.5 : 0); - return WiredUserMovementHelper.moveUser(room, roomUnit, targetTile, targetZ, roomUnit.getBodyRotation(), roomUnit.getHeadRotation(), 0, true); + double targetZ = WiredUserMovementHelper.resolveUserTargetZ(room, targetTile); + return WiredUserMovementHelper.moveUser( + room, + roomUnit, + targetTile, + targetZ, + roomUnit.getBodyRotation(), + roomUnit.getHeadRotation(), + animationDuration, + noAnimation); + } + + private static boolean stageUserMoveIfPossible(Room room, RoomUnit roomUnit, String normalizedKey, int value, int animationDuration, boolean noAnimation) { + if (room == null || roomUnit == null || normalizedKey == null) { + return false; + } + + if (!"@position_x".equals(normalizedKey) && !"@position_y".equals(normalizedKey)) { + return false; + } + + UserMoveBatch batch = USER_MOVE_BATCH.get(); + + if (batch == null) { + return false; + } + + UserMoveBatchEntry entry = batch.entries.computeIfAbsent(roomUnit.getId(), ignored -> + new UserMoveBatchEntry(room, roomUnit, roomUnit.getX(), roomUnit.getY(), animationDuration, noAnimation)); + + entry.animationDuration = animationDuration; + entry.noAnimation = noAnimation; + + if ("@position_x".equals(normalizedKey)) { + entry.targetX = value; + entry.xDirty = true; + } else { + entry.targetY = value; + entry.yDirty = true; + } + + if (entry.xDirty && entry.yDirty && !entry.noAnimation) { + executeUserMoveBatchEntry(entry); + } + + return true; + } + + private static void flushUserMoveBatch(UserMoveBatch batch) { + if (batch == null || batch.entries.isEmpty()) { + return; + } + + for (UserMoveBatchEntry entry : batch.entries.values()) { + executeUserMoveBatchEntry(entry); + } + } + + private static void executeUserMoveBatchEntry(UserMoveBatchEntry entry) { + if (entry == null || entry.room == null || entry.roomUnit == null || entry.room.getLayout() == null) { + return; + } + + if (!entry.xDirty && !entry.yDirty) { + return; + } + + RoomTile targetTile = entry.room.getLayout().getTile((short) entry.targetX, (short) entry.targetY); + + if (targetTile == null || targetTile.state == RoomTileState.INVALID) { + return; + } + + double targetZ = WiredUserMovementHelper.resolveUserTargetZ(entry.room, targetTile); + + WiredUserMovementHelper.moveUser( + entry.room, + entry.roomUnit, + targetTile, + targetZ, + entry.roomUnit.getBodyRotation(), + entry.roomUnit.getHeadRotation(), + entry.animationDuration, + entry.noAnimation); + + entry.targetX = entry.roomUnit.getX(); + entry.targetY = entry.roomUnit.getY(); + entry.xDirty = false; + entry.yDirty = false; } private static boolean moveFurniTo(Room room, HabboItem item, int x, int y, int rotation, double z) { @@ -520,6 +643,24 @@ public final class WiredInternalVariableSupport { return error == FurnitureMovementError.NONE; } + private static int normalizeFurniStateValue(HabboItem item, int value) { + if (item == null || item.getBaseItem() == null) { + return value; + } + + int stateCount = item.getBaseItem().getStateCount(); + if (stateCount <= 0) { + return value; + } + + int wrappedValue = value % stateCount; + if (wrappedValue < 0) { + wrappedValue += stateCount; + } + + return wrappedValue; + } + private static int parseInteger(String value) { try { return (value == null || value.trim().isEmpty()) ? 0 : Integer.parseInt(value.trim()); @@ -551,4 +692,96 @@ public final class WiredInternalVariableSupport { this.typeId = typeId; } } + + public static final class UserMoveInstantScope implements AutoCloseable { + private final Boolean previousValue; + private boolean closed; + + private UserMoveInstantScope(Boolean previousValue) { + this.previousValue = previousValue; + } + + @Override + public void close() { + if (this.closed) { + return; + } + + this.closed = true; + + if (this.previousValue == null) { + USER_MOVE_INSTANT_OVERRIDE.remove(); + return; + } + + USER_MOVE_INSTANT_OVERRIDE.set(this.previousValue); + } + } + + public static final class UserMoveBatchScope implements AutoCloseable { + private final Integer previousDepth; + private boolean closed; + + private UserMoveBatchScope(Integer previousDepth) { + this.previousDepth = previousDepth; + } + + @Override + public void close() { + if (this.closed) { + return; + } + + this.closed = true; + + Integer currentDepth = USER_MOVE_BATCH_DEPTH.get(); + + if (currentDepth == null || currentDepth <= 1) { + UserMoveBatch currentBatch = USER_MOVE_BATCH.get(); + + if (currentBatch != null) { + flushUserMoveBatch(currentBatch); + } + + USER_MOVE_BATCH.remove(); + + if (this.previousDepth == null) { + USER_MOVE_BATCH_DEPTH.remove(); + } else { + USER_MOVE_BATCH_DEPTH.set(this.previousDepth); + } + + return; + } + + USER_MOVE_BATCH_DEPTH.set(currentDepth - 1); + } + } + + private static final class UserMoveBatch { + private final java.util.LinkedHashMap entries = new java.util.LinkedHashMap<>(); + } + + private static final class UserMoveBatchEntry { + private final Room room; + private final RoomUnit roomUnit; + private int targetX; + private int targetY; + private int animationDuration; + private boolean noAnimation; + private boolean xDirty; + private boolean yDirty; + + private UserMoveBatchEntry(Room room, RoomUnit roomUnit, int targetX, int targetY, int animationDuration, boolean noAnimation) { + this.room = room; + this.roomUnit = roomUnit; + this.targetX = targetX; + this.targetY = targetY; + this.animationDuration = animationDuration; + this.noAnimation = noAnimation; + this.xDirty = false; + this.yDirty = false; + } + } + } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredManager.java index 9d264065..8949e0d8 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredManager.java @@ -39,6 +39,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayDeque; /** * Manager class for the wired runtime. @@ -85,6 +86,8 @@ public final class WiredManager { /** Whether the engine is initialized */ private static volatile boolean initialized = false; + private static final ThreadLocal EVENT_HANDLING_DEPTH = new ThreadLocal<>(); + private static final ThreadLocal> DEFERRED_EFFECT_EVENTS = new ThreadLocal<>(); private WiredManager() { // Static utility class } @@ -236,8 +239,65 @@ public final class WiredManager { if (event == null || RoomWiredDisableSupport.isWiredDisabled(event.getRoom())) { return false; } - - return engine.handleEvent(event); + + Integer previousDepth = EVENT_HANDLING_DEPTH.get(); + int nextDepth = (previousDepth == null) ? 1 : (previousDepth + 1); + EVENT_HANDLING_DEPTH.set(nextDepth); + + if (previousDepth == null) { + DEFERRED_EFFECT_EVENTS.set(new ArrayDeque<>()); + } + + boolean handled = false; + + try { + handled = engine.handleEvent(event); + + if (nextDepth == 1) { + ArrayDeque deferredEvents = DEFERRED_EFFECT_EVENTS.get(); + + while (deferredEvents != null && !deferredEvents.isEmpty()) { + WiredEvent deferredEvent = deferredEvents.pollFirst(); + + if (deferredEvent == null || RoomWiredDisableSupport.isWiredDisabled(deferredEvent.getRoom())) { + continue; + } + + handled = engine.handleEvent(deferredEvent) || handled; + } + } + + return handled; + } finally { + if (previousDepth == null) { + EVENT_HANDLING_DEPTH.remove(); + DEFERRED_EFFECT_EVENTS.remove(); + } else { + EVENT_HANDLING_DEPTH.set(previousDepth); + } + } + } + + public static boolean dispatchEffectTriggeredEvent(WiredEvent event) { + if (!isEnabled() || engine == null || event == null || RoomWiredDisableSupport.isWiredDisabled(event.getRoom())) { + return false; + } + + Integer currentDepth = EVENT_HANDLING_DEPTH.get(); + + if (currentDepth == null || currentDepth <= 0) { + return handleEvent(event); + } + + ArrayDeque deferredEvents = DEFERRED_EFFECT_EVENTS.get(); + + if (deferredEvents == null) { + deferredEvents = new ArrayDeque<>(); + DEFERRED_EFFECT_EVENTS.set(deferredEvents); + } + + deferredEvents.addLast(event); + return true; } /** diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredMoveCarryHelper.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredMoveCarryHelper.java index 5647a45e..813b3f15 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredMoveCarryHelper.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredMoveCarryHelper.java @@ -35,6 +35,7 @@ public final class WiredMoveCarryHelper { private static final long USER_FOLLOWER_TTL_MS = 10000L; private static final ThreadLocal> SUPPRESSED_STATUS_ROOM_UNIT_IDS = new ThreadLocal<>(); private static final ThreadLocal> COLLECTED_MOVEMENTS = new ThreadLocal<>(); + private static final ThreadLocal MOVEMENT_COLLECTION_DEPTH = new ThreadLocal<>(); private static final ConcurrentHashMap SUPPRESSED_STATUS_COMPOSER_UNTIL = new ConcurrentHashMap<>(); private static final ConcurrentHashMap> ACTIVE_USER_FOLLOWERS = new ConcurrentHashMap<>(); @@ -237,12 +238,28 @@ public final class WiredMoveCarryHelper { } public static void beginMovementCollection() { - COLLECTED_MOVEMENTS.set(new ArrayList<>()); + Integer currentDepth = MOVEMENT_COLLECTION_DEPTH.get(); + + if (currentDepth == null || currentDepth <= 0) { + COLLECTED_MOVEMENTS.set(new ArrayList<>()); + MOVEMENT_COLLECTION_DEPTH.set(1); + return; + } + + MOVEMENT_COLLECTION_DEPTH.set(currentDepth + 1); } public static ServerMessage finishMovementCollection() { + Integer currentDepth = MOVEMENT_COLLECTION_DEPTH.get(); + + if (currentDepth != null && currentDepth > 1) { + MOVEMENT_COLLECTION_DEPTH.set(currentDepth - 1); + return null; + } + List movements = COLLECTED_MOVEMENTS.get(); COLLECTED_MOVEMENTS.remove(); + MOVEMENT_COLLECTION_DEPTH.remove(); if (movements == null || movements.isEmpty()) { return null; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredSelectionFilterSupport.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredSelectionFilterSupport.java new file mode 100644 index 00000000..306e71b5 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredSelectionFilterSupport.java @@ -0,0 +1,204 @@ +package com.eu.habbo.habbohotel.wired.core; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra; +import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterFurni; +import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterFurniByVariable; +import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterUser; +import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterUsersByVariable; +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.users.HabboItem; +import gnu.trove.set.hash.THashSet; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +final class WiredSelectionFilterSupport { + private static final ThreadLocal FILTER_DEPTH = ThreadLocal.withInitial(() -> 0); + + private WiredSelectionFilterSupport() { + } + + static void applySelectorFilters(Room room, HabboItem triggerItem, WiredContext ctx) { + if (ctx == null) { + return; + } + + if (ctx.targets().isItemsModifiedBySelector()) { + ctx.targets().setItems(filterItems(room, triggerItem, ctx, ctx.targets().items())); + } + + if (ctx.targets().isUsersModifiedBySelector()) { + ctx.targets().setUsers(filterUsers(room, triggerItem, ctx, ctx.targets().users())); + } + } + + static List filterItems(Room room, HabboItem triggerItem, WiredContext ctx, Iterable values) { + List items = toItemList(values); + + if (items.isEmpty() || shouldBypass(room, triggerItem, ctx)) { + return items; + } + + THashSet extras = room.getRoomSpecialTypes().getExtras(triggerItem.getX(), triggerItem.getY()); + if (extras == null || extras.isEmpty()) { + return items; + } + + int furniLimit = Integer.MAX_VALUE; + List variableFilters = new ArrayList<>(); + + for (InteractionWiredExtra extra : extras) { + if (extra instanceof WiredExtraFilterFurni) { + furniLimit = Math.min(furniLimit, ((WiredExtraFilterFurni) extra).getAmount()); + } else if (extra instanceof WiredExtraFilterFurniByVariable) { + variableFilters.add((WiredExtraFilterFurniByVariable) extra); + } + } + + if (furniLimit == Integer.MAX_VALUE && variableFilters.isEmpty()) { + return items; + } + + variableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId())); + + try (FilterScope ignored = enterScope()) { + Iterable filteredItems = items; + + for (WiredExtraFilterFurniByVariable extra : variableFilters) { + filteredItems = extra.filterItems(room, ctx, filteredItems); + } + + if (furniLimit != Integer.MAX_VALUE) { + filteredItems = limitIterable(filteredItems, furniLimit); + } + + return toItemList(filteredItems); + } + } + + static List filterUsers(Room room, HabboItem triggerItem, WiredContext ctx, Iterable values) { + List users = toUserList(values); + + if (users.isEmpty() || shouldBypass(room, triggerItem, ctx)) { + return users; + } + + THashSet extras = room.getRoomSpecialTypes().getExtras(triggerItem.getX(), triggerItem.getY()); + if (extras == null || extras.isEmpty()) { + return users; + } + + int userLimit = Integer.MAX_VALUE; + List variableFilters = new ArrayList<>(); + + for (InteractionWiredExtra extra : extras) { + if (extra instanceof WiredExtraFilterUser) { + userLimit = Math.min(userLimit, ((WiredExtraFilterUser) extra).getAmount()); + } else if (extra instanceof WiredExtraFilterUsersByVariable) { + variableFilters.add((WiredExtraFilterUsersByVariable) extra); + } + } + + if (userLimit == Integer.MAX_VALUE && variableFilters.isEmpty()) { + return users; + } + + variableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId())); + + try (FilterScope ignored = enterScope()) { + Iterable filteredUsers = users; + + for (WiredExtraFilterUsersByVariable extra : variableFilters) { + filteredUsers = extra.filterUsers(room, ctx, filteredUsers); + } + + if (userLimit != Integer.MAX_VALUE) { + filteredUsers = limitIterable(filteredUsers, userLimit); + } + + return toUserList(filteredUsers); + } + } + + private static boolean shouldBypass(Room room, HabboItem triggerItem, WiredContext ctx) { + return room == null + || triggerItem == null + || ctx == null + || room.getRoomSpecialTypes() == null + || FILTER_DEPTH.get() > 0; + } + + private static FilterScope enterScope() { + FILTER_DEPTH.set(FILTER_DEPTH.get() + 1); + return new FilterScope(); + } + + private static List limitIterable(Iterable values, int limit) { + List result = new ArrayList<>(); + + if (values == null || limit <= 0) { + return result; + } + + for (T value : values) { + if (value != null) { + result.add(value); + } + } + + if (result.size() <= limit) { + return result; + } + + Collections.shuffle(result, Emulator.getRandom()); + return new ArrayList<>(result.subList(0, limit)); + } + + private static List toItemList(Iterable values) { + List result = new ArrayList<>(); + + if (values == null) { + return result; + } + + for (HabboItem item : values) { + if (item != null) { + result.add(item); + } + } + + return result; + } + + private static List toUserList(Iterable values) { + List result = new ArrayList<>(); + + if (values == null) { + return result; + } + + for (RoomUnit unit : values) { + if (unit != null) { + result.add(unit); + } + } + + return result; + } + + private static final class FilterScope implements AutoCloseable { + @Override + public void close() { + int depth = FILTER_DEPTH.get() - 1; + + if (depth <= 0) { + FILTER_DEPTH.remove(); + } else { + FILTER_DEPTH.set(depth); + } + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredSourceUtil.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredSourceUtil.java index a400076c..671ec052 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredSourceUtil.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredSourceUtil.java @@ -29,24 +29,36 @@ public final class WiredSourceUtil { } public static List resolveItems(WiredContext ctx, int sourceType, Collection selectedItems) { + List resolvedItems; + switch (sourceType) { case SOURCE_TRIGGER: - return ctx.sourceItem().map(Collections::singletonList).orElse(Collections.emptyList()); + resolvedItems = ctx.sourceItem().map(Collections::singletonList).orElse(Collections.emptyList()); + break; case SOURCE_SELECTED: - return (selectedItems != null) ? new ArrayList<>(selectedItems) : Collections.emptyList(); + resolvedItems = (selectedItems != null) ? new ArrayList<>(selectedItems) : Collections.emptyList(); + break; case SOURCE_SELECTOR: WiredTargets itemTargets = getSelectorTargets(ctx); - return itemTargets.isItemsModifiedBySelector() + resolvedItems = itemTargets.isItemsModifiedBySelector() ? new ArrayList<>(itemTargets.items()) : Collections.emptyList(); + break; case SOURCE_SIGNAL: if (ctx.eventType() == WiredEvent.Type.SIGNAL_RECEIVED) { - return ctx.sourceItem().map(Collections::singletonList).orElse(Collections.emptyList()); + resolvedItems = ctx.sourceItem().map(Collections::singletonList).orElse(Collections.emptyList()); + break; } - return Collections.emptyList(); + resolvedItems = Collections.emptyList(); + break; default: - return ctx.sourceItem().map(Collections::singletonList).orElse(Collections.emptyList()); + resolvedItems = ctx.sourceItem().map(Collections::singletonList).orElse(Collections.emptyList()); + break; } + + return (sourceType == SOURCE_SELECTOR) + ? resolvedItems + : WiredSelectionFilterSupport.filterItems(ctx.room(), ctx.triggerItem(), ctx, resolvedItems); } public static List resolveUsers(WiredContext ctx, int sourceType) { @@ -54,29 +66,43 @@ public final class WiredSourceUtil { } public static List resolveUsers(WiredContext ctx, int sourceType, Collection selectedUsers) { + List resolvedUsers; + switch (sourceType) { case SOURCE_TRIGGER: - return ctx.actor().map(Collections::singletonList).orElse(Collections.emptyList()); + resolvedUsers = ctx.actor().map(Collections::singletonList).orElse(Collections.emptyList()); + break; case SOURCE_CLICKED_USER: if (ctx.eventType() == WiredEvent.Type.USER_CLICKS_USER) { - return ctx.event().getTargetUnit().map(Collections::singletonList).orElse(Collections.emptyList()); + resolvedUsers = ctx.event().getTargetUnit().map(Collections::singletonList).orElse(Collections.emptyList()); + break; } - return Collections.emptyList(); + resolvedUsers = Collections.emptyList(); + break; case SOURCE_SELECTED: - return (selectedUsers != null) ? new ArrayList<>(selectedUsers) : Collections.emptyList(); + resolvedUsers = (selectedUsers != null) ? new ArrayList<>(selectedUsers) : Collections.emptyList(); + break; case SOURCE_SELECTOR: WiredTargets userTargets = getSelectorTargets(ctx); - return userTargets.isUsersModifiedBySelector() + resolvedUsers = userTargets.isUsersModifiedBySelector() ? new ArrayList<>(userTargets.users()) : Collections.emptyList(); + break; case SOURCE_SIGNAL: if (ctx.eventType() == WiredEvent.Type.SIGNAL_RECEIVED) { - return ctx.actor().map(Collections::singletonList).orElse(Collections.emptyList()); + resolvedUsers = ctx.actor().map(Collections::singletonList).orElse(Collections.emptyList()); + break; } - return Collections.emptyList(); + resolvedUsers = Collections.emptyList(); + break; default: - return ctx.actor().map(Collections::singletonList).orElse(Collections.emptyList()); + resolvedUsers = ctx.actor().map(Collections::singletonList).orElse(Collections.emptyList()); + break; } + + return (sourceType == SOURCE_SELECTOR) + ? resolvedUsers + : WiredSelectionFilterSupport.filterUsers(ctx.room(), ctx.triggerItem(), ctx, resolvedUsers); } public static boolean isDefaultUserSource(int value) { @@ -223,83 +249,6 @@ public final class WiredSourceUtil { } private static void applySelectionFilterExtras(Room room, HabboItem triggerItem, WiredContext selectorCtx) { - if (room == null || triggerItem == null || selectorCtx == null || room.getRoomSpecialTypes() == null) { - return; - } - - THashSet extras = room.getRoomSpecialTypes().getExtras(triggerItem.getX(), triggerItem.getY()); - - if (extras == null || extras.isEmpty()) { - return; - } - - int furniLimit = Integer.MAX_VALUE; - int userLimit = Integer.MAX_VALUE; - List furniVariableFilters = new ArrayList<>(); - List userVariableFilters = new ArrayList<>(); - - for (InteractionWiredExtra extra : extras) { - if (extra instanceof WiredExtraFilterFurni) { - furniLimit = Math.min(furniLimit, ((WiredExtraFilterFurni) extra).getAmount()); - } else if (extra instanceof WiredExtraFilterUser) { - userLimit = Math.min(userLimit, ((WiredExtraFilterUser) extra).getAmount()); - } else if (extra instanceof WiredExtraFilterFurniByVariable) { - furniVariableFilters.add((WiredExtraFilterFurniByVariable) extra); - } else if (extra instanceof WiredExtraFilterUsersByVariable) { - userVariableFilters.add((WiredExtraFilterUsersByVariable) extra); - } - } - - furniVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId())); - userVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId())); - - if (selectorCtx.targets().isItemsModifiedBySelector()) { - Iterable filteredItems = selectorCtx.targets().items(); - - for (WiredExtraFilterFurniByVariable extra : furniVariableFilters) { - filteredItems = extra.filterItems(room, selectorCtx, filteredItems); - } - - if (furniLimit != Integer.MAX_VALUE) { - filteredItems = limitIterable(filteredItems, furniLimit); - } - - selectorCtx.targets().setItems(filteredItems); - } - - if (selectorCtx.targets().isUsersModifiedBySelector()) { - Iterable filteredUsers = selectorCtx.targets().users(); - - for (WiredExtraFilterUsersByVariable extra : userVariableFilters) { - filteredUsers = extra.filterUsers(room, selectorCtx, filteredUsers); - } - - if (userLimit != Integer.MAX_VALUE) { - filteredUsers = limitIterable(filteredUsers, userLimit); - } - - selectorCtx.targets().setUsers(filteredUsers); - } - } - - private static List limitIterable(Iterable values, int limit) { - List result = new ArrayList<>(); - - if (values == null || limit <= 0) { - return result; - } - - for (T value : values) { - if (value != null) { - result.add(value); - } - } - - if (result.size() <= limit) { - return result; - } - - Collections.shuffle(result, Emulator.getRandom()); - return new ArrayList<>(result.subList(0, limit)); + WiredSelectionFilterSupport.applySelectorFilters(room, triggerItem, selectorCtx); } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredTriggerSourceUtil.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredTriggerSourceUtil.java index 4e7d3ecb..15c50f03 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredTriggerSourceUtil.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredTriggerSourceUtil.java @@ -162,82 +162,6 @@ public final class WiredTriggerSourceUtil { } private static void applySelectionFilterExtras(Room room, HabboItem triggerItem, WiredContext selectorCtx) { - if (room == null || triggerItem == null || selectorCtx == null || room.getRoomSpecialTypes() == null) { - return; - } - - THashSet extras = room.getRoomSpecialTypes().getExtras(triggerItem.getX(), triggerItem.getY()); - - if (extras == null || extras.isEmpty()) { - return; - } - - int furniLimit = Integer.MAX_VALUE; - int userLimit = Integer.MAX_VALUE; - List furniVariableFilters = new ArrayList<>(); - List userVariableFilters = new ArrayList<>(); - - for (InteractionWiredExtra extra : extras) { - if (extra instanceof WiredExtraFilterFurni) { - furniLimit = Math.min(furniLimit, ((WiredExtraFilterFurni) extra).getAmount()); - } else if (extra instanceof WiredExtraFilterUser) { - userLimit = Math.min(userLimit, ((WiredExtraFilterUser) extra).getAmount()); - } else if (extra instanceof WiredExtraFilterFurniByVariable) { - furniVariableFilters.add((WiredExtraFilterFurniByVariable) extra); - } else if (extra instanceof WiredExtraFilterUsersByVariable) { - userVariableFilters.add((WiredExtraFilterUsersByVariable) extra); - } - } - - furniVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId())); - userVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId())); - - if (selectorCtx.targets().isItemsModifiedBySelector()) { - Iterable filteredItems = selectorCtx.targets().items(); - - for (WiredExtraFilterFurniByVariable extra : furniVariableFilters) { - filteredItems = extra.filterItems(room, selectorCtx, filteredItems); - } - - if (furniLimit != Integer.MAX_VALUE) { - filteredItems = limitIterable(filteredItems, furniLimit); - } - - selectorCtx.targets().setItems(filteredItems); - } - - if (selectorCtx.targets().isUsersModifiedBySelector()) { - Iterable filteredUsers = selectorCtx.targets().users(); - - for (WiredExtraFilterUsersByVariable extra : userVariableFilters) { - filteredUsers = extra.filterUsers(room, selectorCtx, filteredUsers); - } - - if (userLimit != Integer.MAX_VALUE) { - filteredUsers = limitIterable(filteredUsers, userLimit); - } - - selectorCtx.targets().setUsers(filteredUsers); - } - } - - private static List limitIterable(Iterable values, int limit) { - List result = new ArrayList<>(); - - if (values == null || limit <= 0) { - return result; - } - - for (T value : values) { - if (value != null) { - result.add(value); - } - } - - if (result.size() <= limit) { - return result; - } - - return new ArrayList<>(result.subList(0, limit)); + WiredSelectionFilterSupport.applySelectorFilters(room, triggerItem, selectorCtx); } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredUserMovementHelper.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredUserMovementHelper.java index 2ead572e..a08a64ba 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredUserMovementHelper.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredUserMovementHelper.java @@ -1,8 +1,13 @@ package com.eu.habbo.habbohotel.wired.core; import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.items.interactions.InteractionStackWalkHelper; +import com.eu.habbo.habbohotel.items.interactions.InteractionTileWalkMagic; +import com.eu.habbo.habbohotel.items.interactions.interfaces.ConditionalGate; import com.eu.habbo.habbohotel.items.interactions.InteractionRoller; import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomLayout; import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.rooms.RoomUnitStatus; @@ -17,6 +22,7 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; @@ -26,7 +32,9 @@ import java.util.concurrent.ConcurrentHashMap; public final class WiredUserMovementHelper { public static final int DEFAULT_ANIMATION_DURATION = WiredMovementsComposer.DEFAULT_DURATION; + private static final int SUPPRESS_NEXT_WALK_WINDOW_MS = 250; private static final int STATUS_SUPPRESSION_GRACE_MS = 250; + private static final String SUPPRESS_NEXT_WALK_CACHE_KEY = "wired_suppress_next_walk_until"; private static final Logger LOGGER = LoggerFactory.getLogger(WiredUserMovementHelper.class); private static final ThreadLocal> SUPPRESSED_STATUS_ROOM_UNIT_IDS = new ThreadLocal<>(); @@ -62,16 +70,29 @@ public final class WiredUserMovementHelper { RoomUserRotation resolvedBodyRotation = bodyRotation == null ? roomUnit.getBodyRotation() : bodyRotation; RoomUserRotation resolvedHeadRotation = headRotation == null ? roomUnit.getHeadRotation() : headRotation; - double oldZ = roomUnit.getZ(); - HabboItem oldTopItem = room.getTopItemAt(oldLocation.x, oldLocation.y); - HabboItem newTopItem = room.getTopItemAt(targetTile.x, targetTile.y); - Habbo habbo = room.getHabbo(roomUnit); - int animationDuration = Math.max(1, duration); if (noAnimation) { - return moveUserInstant(room, roomUnit, targetTile, targetZ, resolvedBodyRotation, resolvedHeadRotation, oldLocation, oldTopItem, newTopItem, habbo); + return moveUserInstant( + room, + roomUnit, + oldLocation, + targetTile, + roomUnit.getZ(), + targetZ, + resolvedBodyRotation, + resolvedHeadRotation, + room.getTopItemAt(oldLocation.x, oldLocation.y), + resolveEnteredItem(room, targetTile), + room.getHabbo(roomUnit)); } + double oldZ = roomUnit.getZ(); + HabboItem oldTopItem = room.getTopItemAt(oldLocation.x, oldLocation.y); + HabboItem newTopItem = resolveEnteredItem(room, targetTile); + Habbo habbo = room.getHabbo(roomUnit); + + int animationDuration = (duration > 0) ? duration : DEFAULT_ANIMATION_DURATION; + runWithSuppressedStatusUpdates(Collections.singletonList(roomUnit), () -> { roomUnit.removeStatus(RoomUnitStatus.MOVE); roomUnit.setZ(targetZ); @@ -111,6 +132,61 @@ public final class WiredUserMovementHelper { return true; } + public static void suppressNextWalkCommand(RoomUnit roomUnit) { + if (roomUnit == null || roomUnit.getCacheable() == null) { + return; + } + + roomUnit.getCacheable().put(SUPPRESS_NEXT_WALK_CACHE_KEY, System.currentTimeMillis() + SUPPRESS_NEXT_WALK_WINDOW_MS); + } + + public static boolean consumeSuppressedWalkCommand(RoomUnit roomUnit) { + if (roomUnit == null || roomUnit.getCacheable() == null) { + return false; + } + + Object value = roomUnit.getCacheable().remove(SUPPRESS_NEXT_WALK_CACHE_KEY); + + if (!(value instanceof Long)) { + return false; + } + + return ((Long) value) >= System.currentTimeMillis(); + } + + private static boolean moveUserInstant(Room room, RoomUnit roomUnit, RoomTile oldLocation, RoomTile targetTile, double oldZ, double targetZ, + RoomUserRotation bodyRotation, RoomUserRotation headRotation, HabboItem oldTopItem, + HabboItem newTopItem, Habbo habbo) { + suppressNextWalkCommand(roomUnit); + roomUnit.removeStatus(RoomUnitStatus.MOVE); + roomUnit.setPath(new LinkedList<>()); + roomUnit.setBodyRotation(bodyRotation); + roomUnit.setHeadRotation(headRotation); + roomUnit.setCurrentLocation(targetTile); + roomUnit.setGoalLocation(targetTile); + roomUnit.setZ(targetZ); + roomUnit.setPreviousLocation(oldLocation); + roomUnit.setPreviousLocationZ(oldZ); + roomUnit.resetIdleTimer(); + roomUnit.statusUpdate(true); + + if (habbo != null) { + THashSet movedHabbos = new THashSet<>(); + movedHabbos.add(habbo); + room.updateHabbosAt(targetTile.x, targetTile.y, movedHabbos); + } else { + switch (roomUnit.getRoomUnitType()) { + case BOT -> room.updateBotsAt(targetTile.x, targetTile.y); + case PET -> room.updatePetsAt(targetTile.x, targetTile.y); + } + } + + processTileCallbacks(room, roomUnit, oldLocation, targetTile, oldTopItem, newTopItem); + clearStatusComposerSuppression(roomUnit); + room.sendComposer(new RoomUserStatusComposer(roomUnit).compose()); + return true; + } + public static boolean updateUserDirection(Room room, RoomUnit roomUnit, RoomUserRotation bodyRotation, RoomUserRotation headRotation) { if (room == null || roomUnit == null) { return false; @@ -244,39 +320,10 @@ public final class WiredUserMovementHelper { return hasIgnoredFurni; } - private static boolean moveUserInstant(Room room, RoomUnit roomUnit, RoomTile targetTile, double targetZ, RoomUserRotation bodyRotation, RoomUserRotation headRotation, RoomTile oldLocation, HabboItem oldTopItem, HabboItem newTopItem, Habbo habbo) { - runWithSuppressedStatusUpdates(Collections.singletonList(roomUnit), () -> { - roomUnit.removeStatus(RoomUnitStatus.MOVE); - roomUnit.setZ(targetZ); - roomUnit.setLocation(targetTile); - roomUnit.setPath(new LinkedList<>()); - roomUnit.setBodyRotation(bodyRotation); - roomUnit.setHeadRotation(headRotation); - roomUnit.resetIdleTimer(); - - if (habbo != null) { - THashSet movedHabbos = new THashSet<>(); - movedHabbos.add(habbo); - room.updateHabbosAt(targetTile.x, targetTile.y, movedHabbos); - } - roomUnit.statusUpdate(false); - }); - - processTileCallbacks(room, roomUnit, oldLocation, targetTile, oldTopItem, newTopItem); - roomUnit.setPreviousLocation(roomUnit.getCurrentLocation()); - roomUnit.setPreviousLocationZ(roomUnit.getZ()); - room.sendComposer(new RoomUserStatusComposer(roomUnit).compose()); - return true; - } - private static void scheduleTileCallbacks(Room room, RoomUnit roomUnit, RoomTile oldLocation, RoomTile targetTile, HabboItem oldTopItem, HabboItem newTopItem, int delay) { - if (oldTopItem == null && newTopItem == null) { - return; - } - Emulator.getThreading().run(() -> { processTileCallbacks(room, roomUnit, oldLocation, targetTile, oldTopItem, newTopItem); - }, Math.max(delay, InteractionRoller.DELAY)); + }, Math.max(delay, 1)); } private static void processTileCallbacks(Room room, RoomUnit roomUnit, RoomTile oldLocation, RoomTile targetTile, HabboItem oldTopItem, HabboItem newTopItem) { @@ -288,7 +335,15 @@ public final class WiredUserMovementHelper { return; } - if (oldTopItem != null && oldTopItem != newTopItem) { + HabboItem resolvedNewTopItem = resolveEnteredItem(room, targetTile); + if (resolvedNewTopItem == null) { + resolvedNewTopItem = room.getTopItemAt(targetTile.x, targetTile.y); + } + if (resolvedNewTopItem == null) { + resolvedNewTopItem = newTopItem; + } + + if (oldTopItem != null && (oldTopItem != resolvedNewTopItem || !occupiesTile(oldTopItem, targetTile))) { try { oldTopItem.onWalkOff(roomUnit, room, new Object[]{oldLocation, targetTile}); } catch (Exception exception) { @@ -296,13 +351,155 @@ public final class WiredUserMovementHelper { } } - if (newTopItem != null && newTopItem != oldTopItem) { + for (HabboItem additionalOldItem : resolveAdditionalTileItems(room, oldLocation, oldTopItem)) { + if (additionalOldItem == resolvedNewTopItem && occupiesTile(additionalOldItem, targetTile)) { + continue; + } + try { - newTopItem.onWalkOn(roomUnit, room, new Object[]{oldLocation, targetTile}); + additionalOldItem.onWalkOff(roomUnit, room, new Object[]{oldLocation, targetTile}); + } catch (Exception exception) { + LOGGER.error("Failed to process additional wired user walk off callback", exception); + } + } + + if (resolvedNewTopItem != null) { + try { + if (resolvedNewTopItem != oldTopItem || !occupiesTile(resolvedNewTopItem, oldLocation)) { + if (!resolvedNewTopItem.canWalkOn(roomUnit, room, null)) { + if (resolvedNewTopItem instanceof ConditionalGate) { + roomUnit.setLocation(oldLocation); + roomUnit.setZ(oldLocation.getStackHeight()); + roomUnit.setPreviousLocation(oldLocation); + roomUnit.setPreviousLocationZ(oldLocation.getStackHeight()); + room.sendComposer(new RoomUserStatusComposer(roomUnit).compose()); + return; + } + } else { + resolvedNewTopItem.onWalkOn(roomUnit, room, new Object[]{oldLocation, targetTile}); + } + } else { + resolvedNewTopItem.onWalk(roomUnit, room, new Object[]{oldLocation, targetTile}); + } } catch (Exception exception) { LOGGER.error("Failed to process wired user walk on callback", exception); } } + + for (HabboItem additionalNewItem : resolveAdditionalTileItems(room, targetTile, resolvedNewTopItem)) { + try { + if (occupiesTile(additionalNewItem, oldLocation)) { + additionalNewItem.onWalk(roomUnit, room, new Object[]{oldLocation, targetTile}); + } else { + additionalNewItem.onWalkOn(roomUnit, room, new Object[]{oldLocation, targetTile}); + } + } catch (Exception exception) { + LOGGER.error("Failed to process additional wired user walk on callback", exception); + } + } + } + + private static HabboItem resolveEnteredItem(Room room, RoomTile tile) { + if (room == null || tile == null || room.getItemManager() == null) { + return null; + } + + if (room.canSitAt(tile.x, tile.y)) { + HabboItem tallestChair = room.getTallestChair(tile); + if (tallestChair != null) { + return tallestChair; + } + } + + HabboItem candidate = null; + + for (HabboItem item : room.getItemsAt(tile)) { + if (item == null || !occupiesTile(item, tile)) { + continue; + } + + boolean preferred = item instanceof ConditionalGate + || item.isWalkable() + || item.getBaseItem().allowWalk() + || item.getBaseItem().allowSit() + || item.getBaseItem().allowLay(); + + if (!preferred) { + continue; + } + + if (candidate == null || item.getZ() >= candidate.getZ()) { + candidate = item; + } + } + + if (candidate != null) { + return candidate; + } + + return room.getTopItemAt(tile.x, tile.y); + } + + public static double resolveUserTargetZ(Room room, RoomTile targetTile) { + if (room == null || targetTile == null || room.getLayout() == null) { + return 0; + } + + HabboItem targetItem = resolveEnteredItem(room, targetTile); + double targetZ = room.getLayout().getHeightAtSquare(targetTile.x, targetTile.y); + + if (targetItem != null) { + targetZ = targetItem.getZ(); + + if (!targetItem.getBaseItem().allowSit() && !targetItem.getBaseItem().allowLay()) { + targetZ += Item.getCurrentHeight(targetItem); + } + } + + for (HabboItem item : room.getItemsAt(targetTile)) { + if (item instanceof InteractionTileWalkMagic || item instanceof InteractionStackWalkHelper) { + targetZ = item.getZ(); + break; + } + } + + return targetZ; + } + + private static boolean occupiesTile(HabboItem item, RoomTile tile) { + if (item == null || tile == null || item.getBaseItem() == null) { + return false; + } + + return RoomLayout.pointInSquare( + item.getX(), + item.getY(), + item.getX() + item.getBaseItem().getWidth() - 1, + item.getY() + item.getBaseItem().getLength() - 1, + tile.x, + tile.y); + } + + private static List resolveAdditionalTileItems(Room room, RoomTile tile, HabboItem primaryItem) { + if (room == null || tile == null) { + return Collections.emptyList(); + } + + List items = new ArrayList<>(); + + for (HabboItem item : room.getItemsAt(tile)) { + if (item == null || item == primaryItem || !occupiesTile(item, tile)) { + continue; + } + + items.add(item); + } + + items.sort(Comparator + .comparingDouble((HabboItem item) -> item.getZ() + Item.getCurrentHeight(item)) + .thenComparingInt(HabboItem::getId) + .reversed()); + return items; } private static void schedulePostureSync(Room room, RoomUnit roomUnit, RoomTile targetTile, int delay) { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserWalkEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserWalkEvent.java index 26f499c6..dba30ec8 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserWalkEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserWalkEvent.java @@ -51,6 +51,10 @@ public class RoomUserWalkEvent extends MessageHandler { try { if (roomUnit != null && roomUnit.isInRoom() && roomUnit.canWalk() && !WiredFreezeUtil.isFrozen(roomUnit)) { + if (WiredUserMovementHelper.consumeSuppressedWalkCommand(roomUnit)) { + return; + } + if (roomUnit.cmdTeleport) { handleTeleport(room, (short) x, (short) y, roomUnit, habboInfo); return;