diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java index c21d3949..e4b8feb7 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java @@ -49,6 +49,7 @@ import com.eu.habbo.habbohotel.items.interactions.wired.conditions.*; import com.eu.habbo.habbohotel.items.interactions.wired.effects.*; import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectFurniArea; import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectUsersArea; +import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectUsersNeighborhood; import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectFurniNeighborhood; import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectFurniByType; import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredBlob; @@ -256,6 +257,7 @@ public class ItemManager { this.interactionsList.add(new ItemInteraction("wf_slc_furni_neighborhood", WiredEffectFurniNeighborhood.class)); this.interactionsList.add(new ItemInteraction("wf_slc_furni_bytype", WiredEffectFurniByType.class)); this.interactionsList.add(new ItemInteraction("wf_slc_users_area", WiredEffectUsersArea.class)); + this.interactionsList.add(new ItemInteraction("wf_slc_users_neighborhood", WiredEffectUsersNeighborhood.class)); this.interactionsList.add(new ItemInteraction("wf_cnd_has_furni_on", WiredConditionFurniHaveFurni.class)); this.interactionsList.add(new ItemInteraction("wf_cnd_furnis_hv_avtrs", WiredConditionFurniHaveHabbo.class)); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTeleport.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTeleport.java index 5a72aac0..a24b850b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTeleport.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTeleport.java @@ -86,9 +86,10 @@ public class WiredEffectBotTeleport extends InteractionWiredEffect { @Override public void serializeWiredData(ServerMessage message, Room room) { + List itemsSnapshot = new ArrayList<>(this.items); THashSet items = new THashSet<>(); - for (HabboItem item : this.items) { + for (HabboItem item : itemsSnapshot) { if (item.getRoomId() != this.getRoomId() || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) items.add(item); } @@ -97,10 +98,11 @@ public class WiredEffectBotTeleport extends InteractionWiredEffect { this.items.remove(item); } + itemsSnapshot = new ArrayList<>(this.items); message.appendBoolean(false); message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); - message.appendInt(this.items.size()); - for (HabboItem item : this.items) + message.appendInt(itemsSnapshot.size()); + for (HabboItem item : itemsSnapshot) message.appendInt(item.getId()); message.appendInt(this.getBaseItem().getSpriteId()); @@ -156,11 +158,18 @@ public class WiredEffectBotTeleport extends InteractionWiredEffect { public void execute(WiredContext ctx) { Room room = ctx.room(); - if (this.items.isEmpty()) - return; + // Use selector targets if a selector has modified them, otherwise use manually picked items + Iterable effectiveItems = ctx.targets().isItemsModifiedBySelector() + ? ctx.targets().items() + : new ArrayList<>(this.items); - if (room.getLayout() == null) - return; + List validItems = new ArrayList<>(); + for (HabboItem item : effectiveItems) { + if (item != null && item.getRoomId() != 0) validItems.add(item); + } + + if (validItems.isEmpty()) return; + if (room.getLayout() == null) return; List bots = room.getBots(this.botName); @@ -170,20 +179,12 @@ public class WiredEffectBotTeleport extends InteractionWiredEffect { Bot bot = bots.get(0); - int i = Emulator.getRandom().nextInt(this.items.size()) + 1; - int j = 1; + HabboItem targetItem = validItems.get(Emulator.getRandom().nextInt(validItems.size())); - for (HabboItem item : this.items) { - if (item.getRoomId() != 0 && item.getRoomId() == bot.getRoom().getId()) { - if (i == j) { - RoomTile tile = room.getLayout().getTile(item.getX(), item.getY()); - if (tile != null) { - teleportUnitToTile(bot.getRoomUnit(), tile); - } - return; - } else { - j++; - } + if (targetItem.getRoomId() == bot.getRoom().getId()) { + RoomTile tile = room.getLayout().getTile(targetItem.getX(), targetItem.getY()); + if (tile != null) { + teleportUnitToTile(bot.getRoomUnit(), tile); } } } @@ -199,7 +200,8 @@ public class WiredEffectBotTeleport extends InteractionWiredEffect { ArrayList itemIds = new ArrayList<>(); if (this.items != null) { - for (HabboItem item : this.items) { + List itemsSnapshot = new ArrayList<>(this.items); + for (HabboItem item : itemsSnapshot) { if (item.getRoomId() != 0) { itemIds.add(item.getId()); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotWalkToFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotWalkToFurni.java index 766744e3..9084e4ac 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotWalkToFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotWalkToFurni.java @@ -111,15 +111,25 @@ public class WiredEffectBotWalkToFurni extends InteractionWiredEffect { Room room = ctx.room(); List bots = room.getBots(this.botName); - if (this.items.isEmpty() || bots.size() != 1) { + // Use selector targets if a selector has modified them, otherwise use manually picked items + boolean useSelector = ctx.targets().isItemsModifiedBySelector(); + List effectiveItems; + + if (useSelector) { + effectiveItems = new ArrayList<>(ctx.targets().items()); + } else { + this.items.removeIf(item -> item == null || item.getRoomId() != this.getRoomId() || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null); + effectiveItems = this.items; + } + + if (effectiveItems.isEmpty() || bots.size() != 1) { return; } Bot bot = bots.get(0); - this.items.removeIf(item -> item == null || item.getRoomId() != this.getRoomId() || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null); // Bots shouldn't walk to the tile they are already standing on - List possibleItems = this.items.stream() + List possibleItems = effectiveItems.stream() .filter(item -> !room.getBotsOnItem(item).contains(bot)) .collect(Collectors.toList()); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectChangeFurniDirection.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectChangeFurniDirection.java index 7dc86a92..e9ffcef0 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectChangeFurniDirection.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectChangeFurniDirection.java @@ -49,21 +49,38 @@ public class WiredEffectChangeFurniDirection extends InteractionWiredEffect { public void execute(WiredContext ctx) { Room room = ctx.room(); if (room == null || room.getLayout() == null) return; - - THashSet items = new THashSet<>(); - for (HabboItem item : this.items.keySet()) { - if (item == null || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) - items.add(item); + // Use selector targets if a selector has modified them, otherwise use manually picked items + boolean useSelector = ctx.targets().isItemsModifiedBySelector(); + THashMap effectiveItems; + + if (useSelector) { + effectiveItems = new THashMap<>(); + for (HabboItem item : ctx.targets().items()) { + if (item != null) { + // Check if we already have settings for this item, otherwise create defaults + WiredChangeDirectionSetting setting = this.items.get(item); + if (setting == null) { + setting = new WiredChangeDirectionSetting(item.getId(), item.getRotation(), this.startRotation); + } + effectiveItems.put(item, setting); + } + } + } else { + THashSet toRemove = new THashSet<>(); + for (HabboItem item : this.items.keySet()) { + if (item == null || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) + toRemove.add(item); + } + for (HabboItem item : toRemove) { + this.items.remove(item); + } + effectiveItems = this.items; } - for (HabboItem item : items) { - this.items.remove(item); - } + if (effectiveItems.isEmpty()) return; - if (this.items.isEmpty()) return; - - for (Map.Entry entry : this.items.entrySet()) { + for (Map.Entry entry : effectiveItems.entrySet()) { HabboItem item = entry.getKey(); if (item == null || entry.getValue() == null) continue; @@ -85,7 +102,7 @@ public class WiredEffectChangeFurniDirection extends InteractionWiredEffect { } } - for (Map.Entry entry : this.items.entrySet()) { + for (Map.Entry entry : effectiveItems.entrySet()) { HabboItem item = entry.getKey(); if (item == null || entry.getValue() == null) continue; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectKickHabbo.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectKickHabbo.java index c15f336c..27e85f05 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectKickHabbo.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectKickHabbo.java @@ -21,12 +21,16 @@ import com.eu.habbo.messages.outgoing.rooms.users.RoomUserWhisperComposer; import com.eu.habbo.threading.runnables.RoomUnitKick; import gnu.trove.procedure.TObjectProcedure; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class WiredEffectKickHabbo extends InteractionWiredEffect { + private static final Logger LOGGER = LoggerFactory.getLogger(WiredEffectKickHabbo.class); public static final WiredEffectType type = WiredEffectType.KICK_USER; private String message = ""; @@ -43,8 +47,13 @@ public class WiredEffectKickHabbo extends InteractionWiredEffect { public void execute(WiredContext ctx) { Room room = ctx.room(); + LOGGER.debug("[KickHabbo] targets.users().size={} usersModifiedBySelector={}", + ctx.targets().users().size(), ctx.targets().isUsersModifiedBySelector()); + for (RoomUnit unit : ctx.targets().users()) { Habbo habbo = room.getHabbo(unit); + LOGGER.debug("[KickHabbo] RoomUnit id={} type={} -> Habbo={}", unit.getId(), unit.getRoomUnitType(), + habbo != null ? habbo.getHabboInfo().getUsername() : "null"); if (habbo == null) continue; if (habbo.hasPermission(Permission.ACC_UNKICKABLE)) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMatchFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMatchFurni.java index 5c11be6f..cfbd8459 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMatchFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMatchFurni.java @@ -55,7 +55,19 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int if (room.getLayout() == null) return; + // When a selector provides items, only apply matching to items in both the selector targets and settings + boolean useSelector = ctx.targets().isItemsModifiedBySelector(); + java.util.Set selectorItemIds = null; + if (useSelector) { + selectorItemIds = new java.util.HashSet<>(); + for (HabboItem si : ctx.targets().items()) { + selectorItemIds.add(si.getId()); + } + } + for (WiredMatchFurniSetting setting : this.settings) { + if (useSelector && !selectorItemIds.contains(setting.item_id)) continue; + HabboItem item = room.getHabboItem(setting.item_id); if (item != null) { if (this.state && (this.checkForWiredResetPermission && item.allowWiredResetState())) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniAway.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniAway.java index 1cf00a46..a36379e6 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniAway.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniAway.java @@ -23,6 +23,7 @@ import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; + public class WiredEffectMoveFurniAway extends InteractionWiredEffect { public static final WiredEffectType type = WiredEffectType.FLEE; @@ -41,16 +42,24 @@ public class WiredEffectMoveFurniAway extends InteractionWiredEffect { Room room = ctx.room(); if (room.getLayout() == null) return; - THashSet items = new THashSet<>(); + // Use selector targets if a selector has modified them, otherwise use manually picked items + boolean useSelector = ctx.targets().isItemsModifiedBySelector(); + Iterable effectiveItems; - for (HabboItem item : this.items) { - if (item.getRoomId() == 0) - items.add(item); + if (useSelector) { + effectiveItems = ctx.targets().items(); + } else { + THashSet toRemove = new THashSet<>(); + List itemsSnapshot = new ArrayList<>(this.items); + for (HabboItem item : itemsSnapshot) { + if (item.getRoomId() == 0) + toRemove.add(item); + } + this.items.removeAll(toRemove); + effectiveItems = new ArrayList<>(this.items); } - this.items.removeAll(items); - - for (HabboItem item : this.items) { + for (HabboItem item : effectiveItems) { if (item == null) continue; RoomTile t = room.getLayout().getTile(item.getX(), item.getY()); @@ -114,8 +123,8 @@ public class WiredEffectMoveFurniAway extends InteractionWiredEffect { public boolean simulate(WiredContext ctx, WiredSimulation simulation) { Room room = ctx.room(); if (room.getLayout() == null) return true; - - for (HabboItem item : this.items) { + + for (HabboItem item : new ArrayList<>(this.items)) { if (item == null) continue; WiredSimulation.SimulatedPosition currentPos = simulation.getItemPosition(item); @@ -158,9 +167,10 @@ public class WiredEffectMoveFurniAway extends InteractionWiredEffect { @Override public String getWiredData() { + List itemsSnapshot = new ArrayList<>(this.items); return WiredManager.getGson().toJson(new JsonData( this.getDelay(), - this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) + itemsSnapshot.stream().map(HabboItem::getId).collect(Collectors.toList()) )); } @@ -210,9 +220,10 @@ public class WiredEffectMoveFurniAway extends InteractionWiredEffect { @Override public void serializeWiredData(ServerMessage message, Room room) { + List itemsSnapshot = new ArrayList<>(this.items); THashSet items = new THashSet<>(); - for (HabboItem item : this.items) { + for (HabboItem item : itemsSnapshot) { if (item.getRoomId() != this.getRoomId() || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) items.add(item); } @@ -220,10 +231,11 @@ public class WiredEffectMoveFurniAway extends InteractionWiredEffect { for (HabboItem item : items) { this.items.remove(item); } + itemsSnapshot = new ArrayList<>(this.items); message.appendBoolean(false); message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); - message.appendInt(this.items.size()); - for (HabboItem item : this.items) + message.appendInt(itemsSnapshot.size()); + for (HabboItem item : itemsSnapshot) message.appendInt(item.getId()); message.appendInt(this.getBaseItem().getSpriteId()); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniTo.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniTo.java index 5ebb283d..5bdfd634 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniTo.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniTo.java @@ -74,26 +74,34 @@ public class WiredEffectMoveFurniTo extends InteractionWiredEffect { public void execute(WiredContext ctx) { Room room = ctx.room(); if (room == null || room.getLayout() == null) return; - - List items = new ArrayList<>(); - for (HabboItem item : this.items) { - if (item == null || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) - items.add(item); + // Use selector targets if a selector has modified them, otherwise use manually picked items + boolean useSelector = ctx.targets().isItemsModifiedBySelector(); + List effectiveItems; + + if (useSelector) { + effectiveItems = new ArrayList<>(ctx.targets().items()); + } else { + List toRemove = new ArrayList<>(); + List itemsSnapshot = new ArrayList<>(this.items); + for (HabboItem item : itemsSnapshot) { + if (item == null || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) + toRemove.add(item); + } + for (HabboItem item : toRemove) { + this.items.remove(item); + } + effectiveItems = new ArrayList<>(this.items); } - for (HabboItem item : items) { - this.items.remove(item); - } - - if (this.items.isEmpty()) + if (effectiveItems.isEmpty()) return; Object[] stuff = ctx.legacySettings(); if (stuff != null && stuff.length > 0) { for (Object object : stuff) { if (object instanceof HabboItem) { - HabboItem targetItem = this.items.get(Emulator.getRandom().nextInt(this.items.size())); + HabboItem targetItem = effectiveItems.get(Emulator.getRandom().nextInt(effectiveItems.size())); if (targetItem != null) { int indexOffset = 0; @@ -181,8 +189,9 @@ public class WiredEffectMoveFurniTo extends InteractionWiredEffect { @Override public String getWiredData() { THashSet itemsToRemove = new THashSet<>(); + List itemsSnapshot = new ArrayList<>(this.items); - for (HabboItem item : this.items) { + for (HabboItem item : itemsSnapshot) { if (item.getRoomId() != this.getRoomId() || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) itemsToRemove.add(item); } @@ -191,19 +200,21 @@ public class WiredEffectMoveFurniTo extends InteractionWiredEffect { this.items.remove(item); } + itemsSnapshot = new ArrayList<>(this.items); return WiredManager.getGson().toJson(new JsonData( this.direction, this.spacing, this.getDelay(), - this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) + itemsSnapshot.stream().map(HabboItem::getId).collect(Collectors.toList()) )); } @Override public void serializeWiredData(ServerMessage message, Room room) { + List itemsSnapshot = new ArrayList<>(this.items); THashSet items = new THashSet<>(); - for (HabboItem item : this.items) { + for (HabboItem item : itemsSnapshot) { if (item.getRoomId() != this.getRoomId() || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) items.add(item); } @@ -212,10 +223,11 @@ public class WiredEffectMoveFurniTo extends InteractionWiredEffect { this.items.remove(item); } + itemsSnapshot = new ArrayList<>(this.items); message.appendBoolean(false); message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); - message.appendInt(this.items.size()); - for (HabboItem item : this.items) + message.appendInt(itemsSnapshot.size()); + for (HabboItem item : itemsSnapshot) message.appendInt(item.getId()); message.appendInt(this.getBaseItem().getSpriteId()); message.appendInt(this.getId()); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniTowards.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniTowards.java index aa49c097..73ecd97c 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniTowards.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniTowards.java @@ -90,18 +90,26 @@ public class WiredEffectMoveFurniTowards extends InteractionWiredEffect { public void execute(WiredContext ctx) { Room room = ctx.room(); - THashSet items = new THashSet<>(); + // Use selector targets if a selector has modified them, otherwise use manually picked items + boolean useSelector = ctx.targets().isItemsModifiedBySelector(); + Iterable effectiveItems; - for (HabboItem item : this.items) { - if (Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) - items.add(item); + if (useSelector) { + effectiveItems = ctx.targets().items(); + } else { + THashSet toRemove = new THashSet<>(); + List itemsSnapshot = new ArrayList<>(this.items); + for (HabboItem item : itemsSnapshot) { + if (Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) + toRemove.add(item); + } + for (HabboItem item : toRemove) { + this.items.remove(item); + } + effectiveItems = new ArrayList<>(this.items); } - for (HabboItem item : items) { - this.items.remove(item); - } - - for (HabboItem item : this.items) { + for (HabboItem item : effectiveItems) { if (item == null) continue; @@ -249,9 +257,9 @@ public class WiredEffectMoveFurniTowards extends InteractionWiredEffect { RoomLayout layout = room.getLayout(); if (layout == null) return true; - for (HabboItem item : this.items) { + for (HabboItem item : new ArrayList<>(this.items)) { if (item == null) continue; - + WiredSimulation.SimulatedPosition currentPos = simulation.getItemPosition(item); RoomTile currentTile = layout.getTile(currentPos.x, currentPos.y); if (currentTile == null) continue; @@ -311,9 +319,10 @@ public class WiredEffectMoveFurniTowards extends InteractionWiredEffect { @Override public String getWiredData() { + List itemsSnapshot = new ArrayList<>(this.items); return WiredManager.getGson().toJson(new JsonData( this.getDelay(), - this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) + itemsSnapshot.stream().map(HabboItem::getId).collect(Collectors.toList()) )); } @@ -364,9 +373,10 @@ public class WiredEffectMoveFurniTowards extends InteractionWiredEffect { @Override public void serializeWiredData(ServerMessage message, Room room) { + List itemsSnapshot = new ArrayList<>(this.items); THashSet items = new THashSet<>(); - for (HabboItem item : this.items) { + for (HabboItem item : itemsSnapshot) { if (item.getRoomId() != this.getRoomId() || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) items.add(item); } @@ -374,10 +384,11 @@ public class WiredEffectMoveFurniTowards extends InteractionWiredEffect { for (HabboItem item : items) { this.items.remove(item); } + itemsSnapshot = new ArrayList<>(this.items); message.appendBoolean(false); message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); - message.appendInt(this.items.size()); - for (HabboItem item : this.items) + message.appendInt(itemsSnapshot.size()); + for (HabboItem item : itemsSnapshot) message.appendInt(item.getId()); message.appendInt(this.getBaseItem().getSpriteId()); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveRotateFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveRotateFurni.java index 37557c37..b5dfb505 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveRotateFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveRotateFurni.java @@ -46,10 +46,20 @@ public class WiredEffectMoveRotateFurni extends InteractionWiredEffect implement @Override public void execute(WiredContext ctx) { Room room = ctx.room(); - // remove items that are no longer in the room - this.items.removeIf(item -> Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null); - for (HabboItem item : this.items) { + // Use selector targets if a selector has modified them, otherwise use manually picked items + boolean useSelector = ctx.targets().isItemsModifiedBySelector(); + Iterable effectiveItems; + + if (useSelector) { + effectiveItems = ctx.targets().items(); + } else { + // remove items that are no longer in the room + this.items.removeIf(item -> Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null); + effectiveItems = this.items; + } + + for (HabboItem item : effectiveItems) { if(this.itemCooldowns.contains(item)) continue; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTeleport.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTeleport.java index d5bdd7cf..ffae7301 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTeleport.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTeleport.java @@ -113,9 +113,10 @@ public class WiredEffectTeleport extends InteractionWiredEffect { @Override public void serializeWiredData(ServerMessage message, Room room) { + List itemsSnapshot = new ArrayList<>(this.items); THashSet items = new THashSet<>(); - for (HabboItem item : this.items) { + for (HabboItem item : itemsSnapshot) { if (item.getRoomId() != this.getRoomId() || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) items.add(item); } @@ -123,10 +124,11 @@ public class WiredEffectTeleport extends InteractionWiredEffect { for (HabboItem item : items) { this.items.remove(item); } + itemsSnapshot = new ArrayList<>(this.items); message.appendBoolean(false); message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); - message.appendInt(this.items.size()); - for (HabboItem item : this.items) + message.appendInt(itemsSnapshot.size()); + for (HabboItem item : itemsSnapshot) message.appendInt(item.getId()); message.appendInt(this.getBaseItem().getSpriteId()); @@ -196,14 +198,22 @@ public class WiredEffectTeleport extends InteractionWiredEffect { return; } - this.items.removeIf(item -> item == null || item.getRoomId() != this.getRoomId() - || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null); + // Use selector targets if a selector has modified them, otherwise use manually picked items + List effectiveItems; - if (this.items.isEmpty()) return; + if (ctx.targets().isItemsModifiedBySelector()) { + effectiveItems = new ArrayList<>(ctx.targets().items()); + } else { + this.items.removeIf(item -> item == null || item.getRoomId() != this.getRoomId() + || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null); + effectiveItems = new ArrayList<>(this.items); + } + + if (effectiveItems.isEmpty()) return; for (RoomUnit roomUnit : ctx.targets().users()) { - int i = Emulator.getRandom().nextInt(this.items.size()); - HabboItem item = this.items.get(i); + int i = Emulator.getRandom().nextInt(effectiveItems.size()); + HabboItem item = effectiveItems.get(i); if (item == null) continue; @@ -222,9 +232,10 @@ public class WiredEffectTeleport extends InteractionWiredEffect { @Override public String getWiredData() { + List itemsSnapshot = new ArrayList<>(this.items); return WiredManager.getGson().toJson(new JsonData( this.getDelay(), - this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) + itemsSnapshot.stream().map(HabboItem::getId).collect(Collectors.toList()) )); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleFurni.java index 4daefe37..34c771bc 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleFurni.java @@ -95,21 +95,26 @@ public class WiredEffectToggleFurni extends InteractionWiredEffect { @Override public void serializeWiredData(ServerMessage message, Room room) { - THashSet items = new THashSet<>(); + // Snapshot items to avoid concurrent modification with execute() on room cycle thread + List snapshot = new ArrayList<>(this.items); - for (HabboItem item : this.items) { + List invalidItems = new ArrayList<>(); + for (HabboItem item : snapshot) { if (item.getRoomId() != this.getRoomId() || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) - items.add(item); + invalidItems.add(item); } - for (HabboItem item : items) { + for (HabboItem item : invalidItems) { this.items.remove(item); } + List validItems = new ArrayList<>(snapshot); + validItems.removeAll(invalidItems); + message.appendBoolean(false); message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); - message.appendInt(this.items.size()); - for (HabboItem item : this.items) { + message.appendInt(validItems.size()); + for (HabboItem item : validItems) { message.appendInt(item.getId()); } message.appendInt(this.getBaseItem().getSpriteId()); @@ -177,8 +182,15 @@ public class WiredEffectToggleFurni extends InteractionWiredEffect { Room room = ctx.room(); Habbo habbo = ctx.actor().map(unit -> room.getHabbo(unit)).orElse(null); + // Use selector targets if a selector has modified them, otherwise use manually picked items. + // Snapshot this.items into a new list to avoid undefined behavior from concurrent + // THashSet access (serializeWiredData can modify items from the network thread). + Iterable effectiveItems = ctx.targets().isItemsModifiedBySelector() + ? ctx.targets().items() + : new ArrayList<>(this.items); + THashSet itemsToRemove = new THashSet<>(); - for (HabboItem item : this.items) { + for (HabboItem item : effectiveItems) { if (item == null || item.getRoomId() == 0 || FORBIDDEN_TYPES.stream().anyMatch(a -> a.isAssignableFrom(item.getClass()))) { itemsToRemove.add(item); continue; @@ -201,7 +213,9 @@ public class WiredEffectToggleFurni extends InteractionWiredEffect { } } - this.items.removeAll(itemsToRemove); + if (!ctx.targets().isItemsModifiedBySelector()) { + this.items.removeAll(itemsToRemove); + } } @Deprecated @@ -214,7 +228,7 @@ public class WiredEffectToggleFurni extends InteractionWiredEffect { public String getWiredData() { return WiredManager.getGson().toJson(new JsonData( this.getDelay(), - this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) + new ArrayList<>(this.items).stream().map(HabboItem::getId).collect(Collectors.toList()) )); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleRandom.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleRandom.java index bf4c7a22..0f3dba28 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleRandom.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleRandom.java @@ -92,9 +92,10 @@ public class WiredEffectToggleRandom extends InteractionWiredEffect { @Override public void serializeWiredData(ServerMessage message, Room room) { + List itemsSnapshot = new ArrayList<>(this.items); THashSet items = new THashSet<>(); - for (HabboItem item : this.items) { + for (HabboItem item : itemsSnapshot) { if (item.getRoomId() != this.getRoomId() || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) items.add(item); } @@ -103,10 +104,11 @@ public class WiredEffectToggleRandom extends InteractionWiredEffect { this.items.remove(item); } + itemsSnapshot = new ArrayList<>(this.items); message.appendBoolean(false); message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); - message.appendInt(this.items.size()); - for (HabboItem item : this.items) { + message.appendInt(itemsSnapshot.size()); + for (HabboItem item : itemsSnapshot) { message.appendInt(item.getId()); } message.appendInt(this.getBaseItem().getSpriteId()); @@ -172,11 +174,17 @@ public class WiredEffectToggleRandom extends InteractionWiredEffect { @Override public void execute(WiredContext ctx) { Room room = ctx.room(); - THashSet items = this.items; - for (HabboItem item : items) { + // Use selector targets if a selector has modified them, otherwise use manually picked items + Iterable effectiveItems = ctx.targets().isItemsModifiedBySelector() + ? ctx.targets().items() + : new ArrayList<>(this.items); + + for (HabboItem item : effectiveItems) { if (item.getRoomId() == 0 || FORBIDDEN_TYPES.stream().anyMatch(a -> a.isAssignableFrom(item.getClass()))) { - this.items.remove(item); + if (!ctx.targets().isItemsModifiedBySelector()) { + this.items.remove(item); + } continue; } @@ -197,9 +205,10 @@ public class WiredEffectToggleRandom extends InteractionWiredEffect { @Override public String getWiredData() { + List itemsSnapshot = new ArrayList<>(this.items); return WiredManager.getGson().toJson(new JsonData( this.getDelay(), - this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) + itemsSnapshot.stream().map(HabboItem::getId).collect(Collectors.toList()) )); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTriggerStacks.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTriggerStacks.java index d5dec492..e3c94187 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTriggerStacks.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTriggerStacks.java @@ -41,9 +41,10 @@ public class WiredEffectTriggerStacks extends InteractionWiredEffect { @Override public void serializeWiredData(ServerMessage message, Room room) { + List itemsSnapshot = new ArrayList<>(this.items); THashSet items = new THashSet<>(); - for (HabboItem item : this.items) { + for (HabboItem item : itemsSnapshot) { if (item.getRoomId() != this.getRoomId() || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) items.add(item); } @@ -51,10 +52,11 @@ public class WiredEffectTriggerStacks extends InteractionWiredEffect { for (HabboItem item : items) { this.items.remove(item); } + itemsSnapshot = new ArrayList<>(this.items); message.appendBoolean(false); message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); - message.appendInt(this.items.size()); - for (HabboItem item : this.items) { + message.appendInt(itemsSnapshot.size()); + for (HabboItem item : itemsSnapshot) { message.appendInt(item.getId()); } message.appendInt(this.getBaseItem().getSpriteId()); @@ -135,9 +137,14 @@ public class WiredEffectTriggerStacks extends InteractionWiredEffect { return; } + // Use selector targets if a selector has modified them, otherwise use manually picked items + Iterable effectiveItems = ctx.targets().isItemsModifiedBySelector() + ? ctx.targets().items() + : new ArrayList<>(this.items); + THashSet usedTiles = new THashSet<>(); - for (HabboItem item : this.items) { + for (HabboItem item : effectiveItems) { if (item == null) continue; boolean found = false; @@ -169,9 +176,10 @@ public class WiredEffectTriggerStacks extends InteractionWiredEffect { @Override public String getWiredData() { + List itemsSnapshot = new ArrayList<>(this.items); return WiredManager.getGson().toJson(new JsonData( this.getDelay(), - this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) + itemsSnapshot.stream().map(HabboItem::getId).collect(Collectors.toList()) )); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniArea.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniArea.java index 02fdfb85..30db5f3d 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniArea.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniArea.java @@ -2,6 +2,7 @@ package com.eu.habbo.habbohotel.items.interactions.wired.selector; import com.eu.habbo.habbohotel.gameclients.GameClient; import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.items.interactions.InteractionWired; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; @@ -42,7 +43,9 @@ public class WiredEffectFurniArea extends InteractionWiredEffect { if (room == null || areaWidth <= 0 || areaHeight <= 0) return; List furniInArea = getFurniInArea(room); - ctx.targets().setItems(furniInArea); + if (!furniInArea.isEmpty()) { + ctx.targets().setItems(furniInArea); + } } private List getFurniInArea(Room room) { @@ -54,7 +57,7 @@ public class WiredEffectFurniArea extends InteractionWiredEffect { for (int x = rootX; x <= maxX; x++) { for (int y = rootY; y <= maxY; y++) { for (HabboItem item : room.getItemsAt(x, y)) { - if (item != null && !result.contains(item)) { + if (item != null && !(item instanceof InteractionWired) && !result.contains(item)) { result.add(item); } } @@ -85,6 +88,11 @@ public class WiredEffectFurniArea extends InteractionWiredEffect { return type; } + @Override + public boolean isSelector() { + return true; + } + @Override public String getWiredData() { return WiredManager.getGson().toJson(new JsonData(rootX, rootY, areaWidth, areaHeight, getDelay())); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniByType.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniByType.java index a841dd89..5edfed63 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniByType.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniByType.java @@ -2,6 +2,7 @@ package com.eu.habbo.habbohotel.items.interactions.wired.selector; import com.eu.habbo.habbohotel.gameclients.GameClient; import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.items.interactions.InteractionWired; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; @@ -60,6 +61,7 @@ public class WiredEffectFurniByType extends InteractionWiredEffect { Set result = new LinkedHashSet<>(); room.getFloorItems().forEach(item -> { + if (item instanceof InteractionWired) return; String key = matchState ? item.getBaseItem().getId() + ":" + item.getExtradata() : String.valueOf(item.getBaseItem().getId()); @@ -74,10 +76,14 @@ public class WiredEffectFurniByType extends InteractionWiredEffect { if (invert) { Set all = new LinkedHashSet<>(); - room.getFloorItems().forEach(all::add); + room.getFloorItems().forEach(item -> { + if (!(item instanceof InteractionWired)) all.add(item); + }); all.removeAll(result); - ctx.targets().setItems(all); - } else { + if (!all.isEmpty()) { + ctx.targets().setItems(all); + } + } else if (!result.isEmpty()) { ctx.targets().setItems(result); } } @@ -160,6 +166,11 @@ public class WiredEffectFurniByType extends InteractionWiredEffect { @Override public WiredEffectType getType() { return type; } + @Override + public boolean isSelector() { + return true; + } + @Override public String getWiredData() { return WiredManager.getGson().toJson( diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniNeighborhood.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniNeighborhood.java index 41118b42..f3439707 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniNeighborhood.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniNeighborhood.java @@ -2,6 +2,7 @@ package com.eu.habbo.habbohotel.items.interactions.wired.selector; import com.eu.habbo.habbohotel.gameclients.GameClient; import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.items.interactions.InteractionWired; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; @@ -12,6 +13,8 @@ import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.sql.ResultSet; import java.sql.SQLException; @@ -19,6 +22,7 @@ import java.util.*; import java.util.stream.Collectors; public class WiredEffectFurniNeighborhood extends InteractionWiredEffect { + private static final Logger LOGGER = LoggerFactory.getLogger(WiredEffectFurniNeighborhood.class); public static final WiredEffectType type = WiredEffectType.FURNI_NEIGHBORHOOD_SELECTOR; @@ -58,16 +62,30 @@ public class WiredEffectFurniNeighborhood extends InteractionWiredEffect { List sourcePositions = resolveSourcePositions(ctx, room); if (sourcePositions.isEmpty()) return; + int totalRaw = 0; + int wiredSkipped = 0; Set result = new LinkedHashSet<>(); for (int[] src : sourcePositions) { + LOGGER.info("[FurniNeighborhood] Source: ({},{}), offsets: {}", src[0], src[1], tileOffsets.size()); for (int[] offset : tileOffsets) { int tx = src[0] + offset[0]; int ty = src[1] + offset[1]; for (HabboItem item : room.getItemsAt(tx, ty)) { - if (item != null) result.add(item); + if (item == null) continue; + totalRaw++; + if (item instanceof InteractionWired) { + wiredSkipped++; + LOGGER.info("[FurniNeighborhood] SKIP wired item {} ({}) at ({},{})", + item.getId(), item.getClass().getSimpleName(), tx, ty); + } else { + result.add(item); + LOGGER.info("[FurniNeighborhood] KEEP item {} ({}) at ({},{})", + item.getId(), item.getClass().getSimpleName(), tx, ty); + } } } } + LOGGER.info("[FurniNeighborhood] Raw={}, wiredSkipped={}, kept={}", totalRaw, wiredSkipped, result.size()); if (filterExisting) { result.retainAll(ctx.targets().items()); @@ -75,17 +93,29 @@ public class WiredEffectFurniNeighborhood extends InteractionWiredEffect { if (invert) { Set all = new LinkedHashSet<>(); - room.getFloorItems().forEach(all::add); + room.getFloorItems().forEach(item -> { + if (!(item instanceof InteractionWired)) all.add(item); + }); all.removeAll(result); result = all; } + // Always set the selector result — even if empty. + // An empty result means no items matched the neighborhood, so downstream + // effects should target nothing rather than falling back to the original targets. ctx.targets().setItems(result); + LOGGER.info("[FurniNeighborhood] Set {} items as targets", result.size()); } private List resolveSourcePositions(WiredContext ctx, Room room) { if (isUserGroup(sourceType)) { + // Prefer the event tile for user-based sources because during walk-on/walk-off + // events the user's position (getX/getY) hasn't been updated yet (stale position). + // The event tile correctly represents where the triggering action occurred. + if (ctx.tile().isPresent()) { + return Collections.singletonList(new int[]{ ctx.tile().get().x, ctx.tile().get().y }); + } List positions = ctx.targets().users().stream() .map(u -> new int[]{ u.getX(), u.getY() }) .collect(Collectors.toList()); @@ -149,6 +179,14 @@ public class WiredEffectFurniNeighborhood extends InteractionWiredEffect { } this.setDelay(settings.getDelay()); + + LOGGER.info("[FurniNeighborhood] saveData: sourceType={}, filterExisting={}, invert={}, offsets={}, pickedFurniIds={}", + sourceType, filterExisting, invert, tileOffsets.size(), pickedFurniIds); + for (int[] o : tileOffsets) { + LOGGER.info("[FurniNeighborhood] offset: ({}, {})", o[0], o[1]); + } + LOGGER.info("[FurniNeighborhood] raw intParams (len={}): {}", params.length, java.util.Arrays.toString(params)); + return true; } @@ -190,6 +228,11 @@ public class WiredEffectFurniNeighborhood extends InteractionWiredEffect { @Override public WiredEffectType getType() { return type; } + @Override + public boolean isSelector() { + return true; + } + @Override public String getWiredData() { return WiredManager.getGson().toJson( diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectUsersArea.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectUsersArea.java index 0d15587d..46984906 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectUsersArea.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectUsersArea.java @@ -64,7 +64,9 @@ public class WiredEffectUsersArea extends InteractionWiredEffect { usersInArea.retainAll(ctx.targets().users()); } - ctx.targets().setUsers(usersInArea); + if (!usersInArea.isEmpty()) { + ctx.targets().setUsers(usersInArea); + } } @Override diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectUsersNeighborhood.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectUsersNeighborhood.java new file mode 100644 index 00000000..7c63b2fb --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectUsersNeighborhood.java @@ -0,0 +1,303 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.selector; + +import com.eu.habbo.habbohotel.gameclients.GameClient; +import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; +import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.rooms.RoomUnitType; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.WiredEffectType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.messages.incoming.wired.WiredSaveException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; +import java.util.stream.Collectors; + +public class WiredEffectUsersNeighborhood extends InteractionWiredEffect { + private static final Logger LOGGER = LoggerFactory.getLogger(WiredEffectUsersNeighborhood.class); + + public static final WiredEffectType type = WiredEffectType.USERS_NEIGHBORHOOD_SELECTOR; + + private static final int SOURCE_USER_TRIGGER = 0; + private static final int SOURCE_USER_SIGNAL = 1; + private static final int SOURCE_USER_CLICKED = 2; + private static final int SOURCE_FURNI_TRIGGER = 3; + private static final int SOURCE_FURNI_PICKED = 4; + private static final int SOURCE_FURNI_SIGNAL = 5; + + private static boolean isUserGroup(int src) { return src <= SOURCE_USER_CLICKED; } + private static boolean isFurniGroup(int src) { return src >= SOURCE_FURNI_TRIGGER; } + + private static final int MAX_PICKED_FURNI = 20; + private static final int MAX_TILE_OFFSETS = 64; + + private int sourceType = SOURCE_USER_TRIGGER; + private boolean filterExisting = false; + private boolean invert = false; + private boolean excludeBots = false; + private boolean excludePets = false; + private List tileOffsets = new ArrayList<>(); + private List pickedFurniIds = new ArrayList<>(); + + public WiredEffectUsersNeighborhood(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredEffectUsersNeighborhood(int id, int userId, Item item, String extradata, + int limitedStack, int limitedSells) { + super(id, userId, item, extradata, limitedStack, limitedSells); + } + + @Override + public void execute(WiredContext ctx) { + Room room = ctx.room(); + if (room == null || tileOffsets.isEmpty()) { + LOGGER.debug("[Neighborhood] Skipping: room={} tileOffsets.size={}", room != null ? room.getId() : "null", tileOffsets.size()); + return; + } + + List sourcePositions = resolveSourcePositions(ctx, room); + if (sourcePositions.isEmpty()) { + LOGGER.debug("[Neighborhood] No source positions resolved (sourceType={})", sourceType); + return; + } + + LOGGER.debug("[Neighborhood] sourceType={} sourcePositions={} tileOffsets={} filterExisting={} invert={}", + sourceType, + sourcePositions.stream().map(p -> p[0] + "," + p[1]).collect(Collectors.joining(";")), + tileOffsets.stream().map(o -> o[0] + "," + o[1]).collect(Collectors.joining(";")), + filterExisting, invert); + + // Apply tile offsets relative to each source position. + // The offsets define a neighborhood pattern around the source furni/user. + Set targetTiles = new HashSet<>(); + for (int[] src : sourcePositions) { + for (int[] offset : tileOffsets) { + int tx = src[0] + offset[0]; + int ty = src[1] + offset[1]; + targetTiles.add(tx + "," + ty); + } + } + + LOGGER.debug("[Neighborhood] Target tiles: {}", targetTiles); + + List result = new ArrayList<>(); + for (RoomUnit unit : room.getRoomUnits()) { + if (excludeBots && unit.getRoomUnitType() == RoomUnitType.BOT) continue; + if (excludePets && unit.getRoomUnitType() == RoomUnitType.PET) continue; + + String pos = unit.getX() + "," + unit.getY(); + boolean onTile = targetTiles.contains(pos); + + LOGGER.debug("[Neighborhood] Unit id={} type={} pos={} onTile={}", unit.getId(), unit.getRoomUnitType(), pos, onTile); + + if (invert ? !onTile : onTile) { + result.add(unit); + } + } + + if (filterExisting) { + result.retainAll(ctx.targets().users()); + } + + LOGGER.debug("[Neighborhood] Result: {} users selected", result.size()); + + // Always set the selector result — even if empty. + // An empty result means no users matched the neighborhood, so downstream + // effects (e.g. kick) should target nobody rather than falling back to the + // triggering user. + ctx.targets().setUsers(result); + } + + private List resolveSourcePositions(WiredContext ctx, Room room) { + + if (isUserGroup(sourceType)) { + // Prefer the event tile for user-based sources because during walk-on/walk-off + // events the user's position (getX/getY) hasn't been updated yet (stale position). + // The event tile correctly represents where the triggering action occurred. + if (ctx.tile().isPresent()) { + return Collections.singletonList(new int[]{ ctx.tile().get().x, ctx.tile().get().y }); + } + List positions = ctx.targets().users().stream() + .map(u -> new int[]{ u.getX(), u.getY() }) + .collect(Collectors.toList()); + if (positions.isEmpty()) { + ctx.actor().ifPresent(a -> positions.add(new int[]{ a.getX(), a.getY() })); + } + return positions; + } + + switch (sourceType) { + case SOURCE_FURNI_TRIGGER: { + return ctx.sourceItem() + .map(i -> Collections.singletonList(new int[]{ i.getX(), i.getY() })) + .orElse(Collections.emptyList()); + } + case SOURCE_FURNI_PICKED: { + return pickedFurniIds.stream() + .map(room::getHabboItem) + .filter(Objects::nonNull) + .map(i -> new int[]{ i.getX(), i.getY() }) + .collect(Collectors.toList()); + } + case SOURCE_FURNI_SIGNAL: { + return ctx.targets().items().stream() + .map(i -> new int[]{ i.getX(), i.getY() }) + .collect(Collectors.toList()); + } + default: + return Collections.emptyList(); + } + } + + @Override + public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException { + int[] params = settings.getIntParams(); + if (params == null || params.length < 1) { + throw new WiredSaveException("wf_slc_users_neighborhood: intParams must have at least 1 element"); + } + + this.sourceType = params[0]; + this.filterExisting = params.length > 1 && params[1] == 1; + this.invert = params.length > 2 && params[2] == 1; + this.excludeBots = params.length > 3 && params[3] == 1; + this.excludePets = params.length > 4 && params[4] == 1; + + this.tileOffsets = new ArrayList<>(); + if (params.length > 5) { + int n = params[5]; + for (int i = 0; i < n && i < MAX_TILE_OFFSETS; i++) { + int xi = 6 + i * 2; + if (xi + 1 < params.length) { + tileOffsets.add(new int[]{ params[xi], params[xi + 1] }); + } + } + } + + this.pickedFurniIds = new ArrayList<>(); + if (this.sourceType == SOURCE_FURNI_PICKED && settings.getFurniIds() != null) { + for (int id : settings.getFurniIds()) { + if (pickedFurniIds.size() >= MAX_PICKED_FURNI) break; + pickedFurniIds.add(id); + } + } + + this.setDelay(settings.getDelay()); + return true; + } + + @Override + public void serializeWiredData(ServerMessage message, Room room) { + boolean pickMode = (sourceType == SOURCE_FURNI_PICKED); + + message.appendBoolean(pickMode); + message.appendInt(pickMode ? MAX_PICKED_FURNI : 0); + + if (pickMode && !pickedFurniIds.isEmpty()) { + message.appendInt(pickedFurniIds.size()); + pickedFurniIds.forEach(message::appendInt); + } else { + message.appendInt(0); + } + + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(""); + + int paramCount = 6 + tileOffsets.size() * 2; + message.appendInt(paramCount); + message.appendInt(sourceType); + message.appendInt(filterExisting ? 1 : 0); + message.appendInt(invert ? 1 : 0); + message.appendInt(excludeBots ? 1 : 0); + message.appendInt(excludePets ? 1 : 0); + message.appendInt(tileOffsets.size()); + for (int[] offset : tileOffsets) { + message.appendInt(offset[0]); + message.appendInt(offset[1]); + } + + message.appendInt(0); + message.appendInt(this.getType().code); + message.appendInt(this.getDelay()); + message.appendInt(0); + } + + @Override + public WiredEffectType getType() { return type; } + + @Override + public boolean isSelector() { + return true; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson( + new JsonData(sourceType, filterExisting, invert, excludeBots, excludePets, tileOffsets, pickedFurniIds, getDelay())); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + String wiredData = set.getString("wired_data"); + if (wiredData != null && wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + this.sourceType = data.sourceType; + this.filterExisting = data.filterExisting; + this.invert = data.invert; + this.excludeBots = data.excludeBots; + this.excludePets = data.excludePets; + this.tileOffsets = data.tileOffsets != null ? data.tileOffsets : new ArrayList<>(); + this.pickedFurniIds = data.pickedFurniIds != null ? data.pickedFurniIds : new ArrayList<>(); + this.setDelay(data.delay); + } + } + + @Override + public void onPickUp() { + this.sourceType = SOURCE_USER_TRIGGER; + this.filterExisting = false; + this.invert = false; + this.excludeBots = false; + this.excludePets = false; + this.tileOffsets = new ArrayList<>(); + this.pickedFurniIds = new ArrayList<>(); + this.setDelay(0); + } + + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { return false; } + + static class JsonData { + int sourceType; + boolean filterExisting; + boolean invert; + boolean excludeBots; + boolean excludePets; + List tileOffsets; + List pickedFurniIds; + int delay; + + JsonData(int sourceType, boolean filterExisting, boolean invert, + boolean excludeBots, boolean excludePets, + List tileOffsets, List pickedFurniIds, int delay) { + this.sourceType = sourceType; + this.filterExisting = filterExisting; + this.invert = invert; + this.excludeBots = excludeBots; + this.excludePets = excludePets; + this.tileOffsets = tileOffsets; + this.pickedFurniIds = pickedFurniIds; + this.delay = delay; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerBotReachedFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerBotReachedFurni.java index f42bba2b..419dcb90 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerBotReachedFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerBotReachedFurni.java @@ -120,16 +120,27 @@ public class WiredTriggerBotReachedFurni extends InteractionWiredTrigger { public boolean matches(HabboItem triggerItem, WiredEvent event) { RoomUnit roomUnit = event.getActor().orElse(null); Room room = event.getRoom(); - + // Get the furniture item the bot walked onto HabboItem sourceItem = event.getSourceItem().orElse(null); - if (sourceItem == null || roomUnit == null) { + if (sourceItem == null || roomUnit == null || room == null) { return false; } - - // Check if this furniture is in our monitored list AND the actor is the correct bot - return this.items.contains(sourceItem) && - room.getBots(this.botName).stream().anyMatch(bot -> bot.getRoomUnit() == roomUnit); + + boolean isCorrectBot = room.getBots(this.botName).stream().anyMatch(bot -> bot.getRoomUnit() == roomUnit); + if (!isCorrectBot) { + return false; + } + + if (this.items.contains(sourceItem)) { + return true; + } + for (HabboItem item : room.getItemsAt(sourceItem.getX(), sourceItem.getY())) { + if (this.items.contains(item)) { + return true; + } + } + return false; } @Deprecated diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboWalkOffFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboWalkOffFurni.java index 55c2af03..3e83d923 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboWalkOffFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboWalkOffFurni.java @@ -36,9 +36,23 @@ public class WiredTriggerHabboWalkOffFurni extends InteractionWiredTrigger { @Override public boolean matches(HabboItem triggerItem, WiredEvent event) { HabboItem sourceItem = event.getSourceItem().orElse(null); - if (sourceItem != null) { - return this.items.contains(sourceItem); + if (sourceItem == null) { + return false; } + + if (this.items.contains(sourceItem)) { + return true; + } + + Room room = event.getRoom(); + if (room != null) { + for (HabboItem item : room.getItemsAt(sourceItem.getX(), sourceItem.getY())) { + if (this.items.contains(item)) { + return true; + } + } + } + return false; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboWalkOnFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboWalkOnFurni.java index 40dd0170..6f8d7ddc 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboWalkOnFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboWalkOnFurni.java @@ -36,9 +36,23 @@ public class WiredTriggerHabboWalkOnFurni extends InteractionWiredTrigger { @Override public boolean matches(HabboItem triggerItem, WiredEvent event) { HabboItem sourceItem = event.getSourceItem().orElse(null); - if (sourceItem != null) { - return this.items.contains(sourceItem); + if (sourceItem == null) { + return false; } + + if (this.items.contains(sourceItem)) { + return true; + } + + Room room = event.getRoom(); + if (room != null) { + for (HabboItem item : room.getItemsAt(sourceItem.getX(), sourceItem.getY())) { + if (this.items.contains(item)) { + return true; + } + } + } + return false; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredEffectType.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredEffectType.java index d2f9959b..ef342a58 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredEffectType.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredEffectType.java @@ -30,7 +30,8 @@ public enum WiredEffectType { FURNI_AREA_SELECTOR(28), FURNI_NEIGHBORHOOD_SELECTOR(29), FURNI_BYTYPE_SELECTOR(30), - USERS_AREA_SELECTOR(31); + USERS_AREA_SELECTOR(31), + USERS_NEIGHBORHOOD_SELECTOR(32); public final int code; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredTargets.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredTargets.java index 757c75ac..c06d9213 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredTargets.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredTargets.java @@ -29,6 +29,8 @@ public final class WiredTargets { private final Set users = new LinkedHashSet<>(); private final Set items = new LinkedHashSet<>(); + private boolean itemsModifiedBySelector = false; + private boolean usersModifiedBySelector = false; /** * Get all targeted users (read-only view). @@ -62,6 +64,24 @@ public final class WiredTargets { return !items.isEmpty(); } + /** + * Check if item targets were explicitly set by a selector. + * Effects should use this to determine whether to use selector targets + * instead of their own manually configured items. + * @return true if a selector has called setItems() + */ + public boolean isItemsModifiedBySelector() { + return itemsModifiedBySelector; + } + + /** + * Check if user targets were explicitly set by a selector. + * @return true if a selector has called setUsers() + */ + public boolean isUsersModifiedBySelector() { + return usersModifiedBySelector; + } + /** * Check if there are any targets at all. * @return true if there are users or items @@ -118,6 +138,7 @@ public final class WiredTargets { */ public void setUsers(Iterable newUsers) { users.clear(); + usersModifiedBySelector = true; if (newUsers != null) { for (RoomUnit u : newUsers) { if (u != null) users.add(u); @@ -131,6 +152,7 @@ public final class WiredTargets { */ public void setItems(Iterable newItems) { items.clear(); + itemsModifiedBySelector = true; if (newItems != null) { for (HabboItem i : newItems) { if (i != null) items.add(i); diff --git a/Latest_Compiled_Version/Habbo-4.0.5-jar-with-dependencies.jar b/Latest_Compiled_Version/Habbo-4.0.5-jar-with-dependencies.jar index 51badf35..4c3b165c 100644 Binary files a/Latest_Compiled_Version/Habbo-4.0.5-jar-with-dependencies.jar and b/Latest_Compiled_Version/Habbo-4.0.5-jar-with-dependencies.jar differ