feat: update wired movement and show message behavior

This commit is contained in:
Lorenzune
2026-04-04 15:57:43 +02:00
parent 6ab152c47d
commit 3efcca1e34
17 changed files with 1057 additions and 437 deletions
@@ -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<RoomUnit> sourceUsers = resolveUsers(ctx);
List<Habbo> 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));
}
@@ -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) {
@@ -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 {
@@ -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);
}
@@ -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<String, Long> 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<Habbo> resolveRecipients(WiredContext ctx, List<RoomUnit> sourceUsers) {
Room room = ctx.room();
LinkedHashMap<Integer, Habbo> 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<RoomUnit> 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<Integer, Habbo> 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<RoomUnit> sourceUsers = resolveUsers(ctx);
List<Habbo> 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;
}
}
}
@@ -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) {
@@ -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]);
}
}
}
@@ -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<IWiredEffect> 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<InteractionWiredExtra> 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<WiredExtraFilterFurniByVariable> furniVariableFilters = new ArrayList<>();
List<WiredExtraFilterUsersByVariable> 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<HabboItem> 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<RoomUnit> 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 <T> List<T> limitIterable(Iterable<T> values, int limit) {
List<T> 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) {
@@ -26,6 +26,10 @@ import java.time.temporal.WeekFields;
import java.util.Locale;
public final class WiredInternalVariableSupport {
private static final ThreadLocal<Boolean> USER_MOVE_INSTANT_OVERRIDE = new ThreadLocal<>();
private static final ThreadLocal<UserMoveBatch> USER_MOVE_BATCH = new ThreadLocal<>();
private static final ThreadLocal<Integer> 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<Integer, UserMoveBatchEntry> 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;
}
}
}
@@ -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<Integer> EVENT_HANDLING_DEPTH = new ThreadLocal<>();
private static final ThreadLocal<ArrayDeque<WiredEvent>> 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<WiredEvent> 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<WiredEvent> deferredEvents = DEFERRED_EFFECT_EVENTS.get();
if (deferredEvents == null) {
deferredEvents = new ArrayDeque<>();
DEFERRED_EFFECT_EVENTS.set(deferredEvents);
}
deferredEvents.addLast(event);
return true;
}
/**
@@ -35,6 +35,7 @@ public final class WiredMoveCarryHelper {
private static final long USER_FOLLOWER_TTL_MS = 10000L;
private static final ThreadLocal<Set<Integer>> SUPPRESSED_STATUS_ROOM_UNIT_IDS = new ThreadLocal<>();
private static final ThreadLocal<List<WiredMovementsComposer.MovementData>> COLLECTED_MOVEMENTS = new ThreadLocal<>();
private static final ThreadLocal<Integer> MOVEMENT_COLLECTION_DEPTH = new ThreadLocal<>();
private static final ConcurrentHashMap<Integer, Long> SUPPRESSED_STATUS_COMPOSER_UNTIL = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<Integer, ConcurrentHashMap<Integer, UserFollowEntry>> 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<WiredMovementsComposer.MovementData> movements = COLLECTED_MOVEMENTS.get();
COLLECTED_MOVEMENTS.remove();
MOVEMENT_COLLECTION_DEPTH.remove();
if (movements == null || movements.isEmpty()) {
return null;
@@ -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<Integer> 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<HabboItem> filterItems(Room room, HabboItem triggerItem, WiredContext ctx, Iterable<HabboItem> values) {
List<HabboItem> items = toItemList(values);
if (items.isEmpty() || shouldBypass(room, triggerItem, ctx)) {
return items;
}
THashSet<InteractionWiredExtra> extras = room.getRoomSpecialTypes().getExtras(triggerItem.getX(), triggerItem.getY());
if (extras == null || extras.isEmpty()) {
return items;
}
int furniLimit = Integer.MAX_VALUE;
List<WiredExtraFilterFurniByVariable> 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<HabboItem> 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<RoomUnit> filterUsers(Room room, HabboItem triggerItem, WiredContext ctx, Iterable<RoomUnit> values) {
List<RoomUnit> users = toUserList(values);
if (users.isEmpty() || shouldBypass(room, triggerItem, ctx)) {
return users;
}
THashSet<InteractionWiredExtra> extras = room.getRoomSpecialTypes().getExtras(triggerItem.getX(), triggerItem.getY());
if (extras == null || extras.isEmpty()) {
return users;
}
int userLimit = Integer.MAX_VALUE;
List<WiredExtraFilterUsersByVariable> 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<RoomUnit> 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 <T> List<T> limitIterable(Iterable<T> values, int limit) {
List<T> 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<HabboItem> toItemList(Iterable<HabboItem> values) {
List<HabboItem> result = new ArrayList<>();
if (values == null) {
return result;
}
for (HabboItem item : values) {
if (item != null) {
result.add(item);
}
}
return result;
}
private static List<RoomUnit> toUserList(Iterable<RoomUnit> values) {
List<RoomUnit> 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);
}
}
}
}
@@ -29,24 +29,36 @@ public final class WiredSourceUtil {
}
public static List<HabboItem> resolveItems(WiredContext ctx, int sourceType, Collection<HabboItem> selectedItems) {
List<HabboItem> 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<RoomUnit> resolveUsers(WiredContext ctx, int sourceType) {
@@ -54,29 +66,43 @@ public final class WiredSourceUtil {
}
public static List<RoomUnit> resolveUsers(WiredContext ctx, int sourceType, Collection<RoomUnit> selectedUsers) {
List<RoomUnit> 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<InteractionWiredExtra> 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<WiredExtraFilterFurniByVariable> furniVariableFilters = new ArrayList<>();
List<WiredExtraFilterUsersByVariable> 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<HabboItem> 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<RoomUnit> 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 <T> List<T> limitIterable(Iterable<T> values, int limit) {
List<T> 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);
}
}
@@ -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<InteractionWiredExtra> 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<WiredExtraFilterFurniByVariable> furniVariableFilters = new ArrayList<>();
List<WiredExtraFilterUsersByVariable> 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<HabboItem> 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<RoomUnit> 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 <T> List<T> limitIterable(Iterable<T> values, int limit) {
List<T> 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);
}
}
@@ -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<Set<Integer>> 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<Habbo> 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<Habbo> 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<HabboItem> resolveAdditionalTileItems(Room room, RoomTile tile, HabboItem primaryItem) {
if (room == null || tile == null) {
return Collections.emptyList();
}
List<HabboItem> 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) {
@@ -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;