diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveRotateUser.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveRotateUser.java index c08487fb..6be0ff53 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveRotateUser.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveRotateUser.java @@ -10,6 +10,7 @@ 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; @@ -17,6 +18,7 @@ import com.eu.habbo.habbohotel.wired.core.WiredManager; 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; @@ -29,6 +31,10 @@ 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; @@ -59,10 +65,12 @@ public class WiredEffectMoveRotateUser extends InteractionWiredEffect { RoomTile targetTile = (this.movementDirection >= 0) ? this.getTargetTile(room, roomUnit, this.movementDirection) : null; boolean canMove = this.canMoveTo(room, roomUnit, targetTile); 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); - int animationDuration = noAnimation ? 0 : WiredMoveCarryHelper.getAnimationDuration(room, this, WiredUserMovementHelper.DEFAULT_ANIMATION_DURATION); + this.markActive(roomUnit, activeWindowMs); if (!WiredUserMovementHelper.moveUser(room, roomUnit, targetTile, targetZ, targetBodyRotation, targetHeadRotation, animationDuration, noAnimation)) { if (hasRotation) { @@ -73,6 +81,7 @@ public class WiredEffectMoveRotateUser extends InteractionWiredEffect { } if (hasRotation) { + this.markActive(roomUnit, activeWindowMs); WiredUserMovementHelper.updateUserDirection(room, roomUnit, targetBodyRotation, targetHeadRotation); } } @@ -275,6 +284,105 @@ public class WiredEffectMoveRotateUser extends InteractionWiredEffect { return true; } + 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()); + } + } + static class JsonData { int delay; int movementDirection; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraRandom.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraRandom.java index 3a78591a..c79df874 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraRandom.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraRandom.java @@ -1,15 +1,41 @@ package com.eu.habbo.habbohotel.items.interactions.wired.extra; +import com.eu.habbo.Emulator; +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.InteractionWiredExtra; +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.wired.api.IWiredEffect; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.function.ToIntFunction; public class WiredExtraRandom extends InteractionWiredExtra { + public static final int CODE = 63; + private static final int DEFAULT_PICK_AMOUNT = 1; + private static final int DEFAULT_SKIP_EXECUTIONS = 0; + private static final int MAX_PICK_AMOUNT = 1000; + private static final int MAX_SKIP_EXECUTIONS = 1000; + + private final Deque> recentExecutionEffectIds = new ArrayDeque<>(); + + private int pickAmount = DEFAULT_PICK_AMOUNT; + private int skipExecutions = DEFAULT_SKIP_EXECUTIONS; + public WiredExtraRandom(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); } @@ -23,28 +49,192 @@ public class WiredExtraRandom extends InteractionWiredExtra { return false; } + @Override + public boolean saveData(WiredSettings settings, GameClient gameClient) { + int resolvedPickAmount = (settings.getIntParams().length > 0) ? settings.getIntParams()[0] : DEFAULT_PICK_AMOUNT; + int resolvedSkipExecutions = (settings.getIntParams().length > 1) ? settings.getIntParams()[1] : DEFAULT_SKIP_EXECUTIONS; + + this.pickAmount = normalizePickAmount(resolvedPickAmount); + this.skipExecutions = normalizeSkipExecutions(resolvedSkipExecutions); + this.clearRecentExecutions(); + return true; + } + @Override public String getWiredData() { - return null; + return WiredManager.getGson().toJson(new JsonData(this.pickAmount, this.skipExecutions)); } @Override public void serializeWiredData(ServerMessage message, Room room) { - + message.appendBoolean(false); + message.appendInt(0); + message.appendInt(0); + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(""); + message.appendInt(2); + message.appendInt(this.pickAmount); + message.appendInt(this.skipExecutions); + message.appendInt(0); + message.appendInt(CODE); + message.appendInt(0); + message.appendInt(0); } @Override public void loadWiredData(ResultSet set, Room room) throws SQLException { + this.onPickUp(); + String wiredData = set.getString("wired_data"); + if (wiredData == null || wiredData.isEmpty()) { + return; + } + + if (wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + this.pickAmount = normalizePickAmount((data != null) ? data.pickAmount : DEFAULT_PICK_AMOUNT); + this.skipExecutions = normalizeSkipExecutions((data != null) ? data.skipExecutions : DEFAULT_SKIP_EXECUTIONS); + return; + } } @Override public void onPickUp() { - + this.pickAmount = DEFAULT_PICK_AMOUNT; + this.skipExecutions = DEFAULT_SKIP_EXECUTIONS; + this.clearRecentExecutions(); } @Override public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) throws Exception { } + + @Override + public boolean hasConfiguration() { + return true; + } + + @Override + public void onMove(Room room, com.eu.habbo.habbohotel.rooms.RoomTile oldLocation, com.eu.habbo.habbohotel.rooms.RoomTile newLocation) { + super.onMove(room, oldLocation, newLocation); + this.clearRecentExecutions(); + } + + public List selectEffects(List effects) { + return this.selectRandomEffects(effects, InteractionWiredEffect::getId); + } + + public List selectWiredEffects(List effects) { + return this.selectRandomEffects(effects, effect -> { + if (effect instanceof InteractionWiredEffect) { + return ((InteractionWiredEffect) effect).getId(); + } + + return System.identityHashCode(effect); + }); + } + + public int getPickAmount() { + return this.pickAmount; + } + + public int getSkipExecutions() { + return this.skipExecutions; + } + + private synchronized List selectRandomEffects(List effects, ToIntFunction idResolver) { + if (effects == null || effects.isEmpty()) { + return Collections.emptyList(); + } + + List shuffledEffects = new ArrayList<>(effects); + Collections.shuffle(shuffledEffects, Emulator.getRandom()); + + int desiredAmount = Math.min(this.pickAmount, shuffledEffects.size()); + Set recentEffectIds = this.getRecentEffectIds(); + LinkedHashSet selectedEffects = new LinkedHashSet<>(); + + for (T effect : shuffledEffects) { + if (recentEffectIds.contains(idResolver.applyAsInt(effect))) { + continue; + } + + selectedEffects.add(effect); + if (selectedEffects.size() >= desiredAmount) { + break; + } + } + + if (selectedEffects.size() < desiredAmount) { + for (T effect : shuffledEffects) { + selectedEffects.add(effect); + if (selectedEffects.size() >= desiredAmount) { + break; + } + } + } + + this.recordExecution(selectedEffects, idResolver); + return new ArrayList<>(selectedEffects); + } + + private synchronized void clearRecentExecutions() { + this.recentExecutionEffectIds.clear(); + } + + private Set getRecentEffectIds() { + LinkedHashSet ids = new LinkedHashSet<>(); + + if (this.skipExecutions <= 0) { + return ids; + } + + for (List executionIds : this.recentExecutionEffectIds) { + ids.addAll(executionIds); + } + + return ids; + } + + private void recordExecution(Collection selectedEffects, ToIntFunction idResolver) { + if (this.skipExecutions <= 0) { + this.recentExecutionEffectIds.clear(); + return; + } + + List executionIds = new ArrayList<>(); + if (selectedEffects != null) { + for (T effect : selectedEffects) { + if (effect != null) { + executionIds.add(idResolver.applyAsInt(effect)); + } + } + } + + this.recentExecutionEffectIds.addLast(executionIds); + + while (this.recentExecutionEffectIds.size() > this.skipExecutions) { + this.recentExecutionEffectIds.removeFirst(); + } + } + + private static int normalizePickAmount(int value) { + return Math.max(1, Math.min(MAX_PICK_AMOUNT, value)); + } + + private static int normalizeSkipExecutions(int value) { + return Math.max(0, Math.min(MAX_SKIP_EXECUTIONS, value)); + } + + static class JsonData { + int pickAmount; + int skipExecutions; + + JsonData(int pickAmount, int skipExecutions) { + this.pickAmount = pickAmount; + this.skipExecutions = skipExecutions; + } + } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraUnseen.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraUnseen.java index 127021db..c96d9415 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraUnseen.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraUnseen.java @@ -1,11 +1,15 @@ package com.eu.habbo.habbohotel.items.interactions.wired.extra; +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.InteractionWiredExtra; +import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.wired.api.IWiredEffect; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -16,6 +20,8 @@ import java.util.List; import java.util.Set; public class WiredExtraUnseen extends InteractionWiredExtra { + public static final int CODE = 62; + /** * Maximum number of effect IDs to track to prevent memory leaks. * When limit is reached, oldest entries are removed automatically. @@ -41,19 +47,34 @@ public class WiredExtraUnseen extends InteractionWiredExtra { return false; } + @Override + public boolean saveData(WiredSettings settings, GameClient gameClient) { + return true; + } + @Override public String getWiredData() { - return null; + return WiredManager.getGson().toJson(new JsonData()); } @Override public void serializeWiredData(ServerMessage message, Room room) { - + message.appendBoolean(false); + message.appendInt(0); + message.appendInt(0); + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(""); + message.appendInt(0); + message.appendInt(0); + message.appendInt(CODE); + message.appendInt(0); + message.appendInt(0); } @Override public void loadWiredData(ResultSet set, Room room) throws SQLException { - + this.onPickUp(); } @Override @@ -108,6 +129,47 @@ public class WiredExtraUnseen extends InteractionWiredExtra { return effect; } } + + public List selectWiredEffects(List effects) { + synchronized (this.seenList) { + List unseenEffects = new ArrayList<>(); + + for (IWiredEffect effect : effects) { + if ((effect instanceof InteractionWiredEffect) + && !this.seenList.contains(((InteractionWiredEffect) effect).getId())) { + unseenEffects.add(effect); + } + } + + IWiredEffect effect = null; + if (!unseenEffects.isEmpty()) { + effect = unseenEffects.get(0); + } else { + this.seenList.clear(); + + if (!effects.isEmpty()) { + effect = effects.get(0); + } + } + + if (effect instanceof InteractionWiredEffect) { + if (this.seenList.size() >= MAX_SEEN_LIST_SIZE) { + Integer oldest = this.seenList.iterator().next(); + this.seenList.remove(oldest); + } + + this.seenList.add(((InteractionWiredEffect) effect).getId()); + } + + if (effect == null) { + return new ArrayList<>(); + } + + List selectedEffects = new ArrayList<>(); + selectedEffects.add(effect); + return selectedEffects; + } + } /** * Gets the current size of the seen list. @@ -127,4 +189,12 @@ public class WiredExtraUnseen extends InteractionWiredExtra { this.seenList.clear(); } } + + @Override + public boolean hasConfiguration() { + return true; + } + + static class JsonData { + } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboClicksFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboClicksFurni.java index a801ed88..6b48577e 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboClicksFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboClicksFurni.java @@ -45,17 +45,7 @@ public class WiredTriggerHabboClicksFurni extends InteractionWiredTrigger { return false; } - switch (this.furniSource) { - case WiredSourceUtil.SOURCE_SELECTED: - return this.matchesSourceItem(this.items, sourceItem); - case WiredSourceUtil.SOURCE_SELECTOR: - return this.matchesSourceItem( - WiredTriggerSourceUtil.resolveItems(this, event, WiredSourceUtil.SOURCE_SELECTOR, this.items), - sourceItem); - case WiredSourceUtil.SOURCE_TRIGGER: - default: - return true; - } + return this.matchesSourceItem(this.resolveCandidateItems(triggerItem, event), sourceItem); } @Deprecated @@ -225,6 +215,18 @@ public class WiredTriggerHabboClicksFurni extends InteractionWiredTrigger { return WiredSourceUtil.SOURCE_TRIGGER; } + private Iterable resolveCandidateItems(HabboItem triggerItem, WiredEvent event) { + switch (this.furniSource) { + case WiredSourceUtil.SOURCE_SELECTED: + return this.items; + case WiredSourceUtil.SOURCE_SELECTOR: + return WiredTriggerSourceUtil.resolveItems(this, event, WiredSourceUtil.SOURCE_SELECTOR, this.items); + case WiredSourceUtil.SOURCE_TRIGGER: + default: + return (triggerItem != null) ? java.util.Collections.singletonList(triggerItem) : java.util.Collections.emptyList(); + } + } + private boolean matchesSourceItem(Iterable candidateItems, HabboItem sourceItem) { if (candidateItems == null || sourceItem == null) { return false; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboSaysKeyword.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboSaysKeyword.java index e2200047..2585a816 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboSaysKeyword.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboSaysKeyword.java @@ -36,7 +36,7 @@ public class WiredTriggerHabboSaysKeyword extends InteractionWiredTrigger { @Override public boolean matches(HabboItem triggerItem, WiredEvent event) { - if (this.key.length() <= 0) { + if ((this.matchMode != MATCH_ALL_WORDS) && this.key.length() <= 0) { return false; } @@ -127,7 +127,7 @@ public class WiredTriggerHabboSaysKeyword extends InteractionWiredTrigger { this.matchMode = (params.length > 0) ? this.normalizeMatchMode(params[0]) : MATCH_CONTAINS; this.hideMessage = (params.length > 1) && (params[1] == 1); this.ownerOnly = (params.length > 2) && (params[2] == 1); - this.key = settings.getStringParam(); + this.key = (this.matchMode == MATCH_ALL_WORDS) ? "" : settings.getStringParam(); return true; } @@ -149,13 +149,7 @@ public class WiredTriggerHabboSaysKeyword extends InteractionWiredTrigger { case MATCH_EXACT: return normalizedText.equals(normalizedKey); case MATCH_ALL_WORDS: - String[] requiredParts = normalizedKey.split("\\s+"); - for (String part : requiredParts) { - if (!part.isEmpty() && !normalizedText.contains(part)) { - return false; - } - } - return true; + return !normalizedText.isEmpty(); case MATCH_CONTAINS: default: return normalizedText.contains(normalizedKey); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredHandler.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredHandler.java index 9490bcf1..47ee4f1c 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredHandler.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredHandler.java @@ -189,21 +189,23 @@ public class WiredHandler { trigger.setCooldown(millis); - boolean hasExtraRandom = room.getRoomSpecialTypes().hasExtraType(trigger.getX(), trigger.getY(), WiredExtraRandom.class); boolean hasExtraUnseen = room.getRoomSpecialTypes().hasExtraType(trigger.getX(), trigger.getY(), WiredExtraUnseen.class); THashSet extras = room.getRoomSpecialTypes().getExtras(trigger.getX(), trigger.getY()); + WiredExtraRandom randomExtra = null; for (InteractionWiredExtra extra : extras) { extra.activateBox(room, roomUnit, millis); + if (randomExtra == null && extra instanceof WiredExtraRandom) { + randomExtra = (WiredExtraRandom) extra; + } } List effectList = new ArrayList<>(effects); - if (hasExtraRandom || hasExtraUnseen) { + if (randomExtra != null || hasExtraUnseen) { Collections.shuffle(effectList); } - if (hasExtraUnseen) { for (InteractionWiredExtra extra : room.getRoomSpecialTypes().getExtras(trigger.getX(), trigger.getY())) { if (extra instanceof WiredExtraUnseen) { @@ -213,12 +215,11 @@ public class WiredHandler { break; } } + } else if (randomExtra != null) { + effectsToExecute.addAll(randomExtra.selectEffects(effectList)); } else { for (final InteractionWiredEffect effect : effectList) { - boolean executed = effectsToExecute.add(effect); //triggerEffect(effect, roomUnit, room, stuff, millis); - if (hasExtraRandom && executed) { - break; - } + effectsToExecute.add(effect); //triggerEffect(effect, roomUnit, room, stuff, millis); } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java index beee0014..0ceb4035 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java @@ -6,6 +6,8 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; 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.WiredExtraFilterUser; +import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRandom; +import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraUnseen; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; import com.eu.habbo.habbohotel.items.interactions.wired.triggers.WiredTriggerHabboClicksUser; import com.eu.habbo.habbohotel.items.interactions.wired.triggers.WiredTriggerHabboSaysKeyword; @@ -392,22 +394,38 @@ public final class WiredEngine { List toExecute; if (stack.useRandom()) { - // Random mode: pick one random regular effect + WiredExtraRandom randomExtra = getRandomExtra(ctx.room(), stack); if (regulars.isEmpty()) { toExecute = new ArrayList<>(); + } else if (randomExtra != null) { + toExecute = randomExtra.selectWiredEffects(regulars); + debug(ctx.room(), "Random mode: selected {} effect(s), skip window {}", toExecute.size(), randomExtra.getSkipExecutions()); } else { int randomIndex = new Random().nextInt(regulars.size()); toExecute = Collections.singletonList(regulars.get(randomIndex)); debug(ctx.room(), "Random mode: selected effect {}/{}", randomIndex + 1, regulars.size()); } } else if (stack.useUnseen()) { - // Unseen mode: round-robin among regular effects + // Unseen mode: execute in stable order with memory if (regulars.isEmpty()) { toExecute = new ArrayList<>(); } else { - int index = getNextUnseenIndex(stack, regulars.size()); - toExecute = Collections.singletonList(regulars.get(index)); - debug(ctx.room(), "Unseen mode: selected effect {}/{}", index + 1, regulars.size()); + WiredExtraUnseen unseenExtra = getUnseenExtra(ctx.room(), stack); + + if (unseenExtra != null) { + toExecute = unseenExtra.selectWiredEffects(regulars); + + if (!toExecute.isEmpty()) { + int selectedIndex = regulars.indexOf(toExecute.get(0)); + debug(ctx.room(), "Unseen mode: selected effect {}/{}", selectedIndex + 1, regulars.size()); + } else { + debug(ctx.room(), "Unseen mode: no eligible effect found"); + } + } else { + int index = getNextUnseenIndex(stack, regulars.size()); + toExecute = Collections.singletonList(regulars.get(index)); + debug(ctx.room(), "Unseen mode fallback: selected effect {}/{}", index + 1, regulars.size()); + } } } else { // Normal mode: regular effects in random order @@ -684,6 +702,40 @@ public final class WiredEngine { } } + private WiredExtraRandom getRandomExtra(Room room, WiredStack stack) { + InteractionWiredExtra extra = getStackExtra(room, stack, WiredExtraRandom.class); + + return (extra instanceof WiredExtraRandom) ? (WiredExtraRandom) extra : null; + } + + private WiredExtraUnseen getUnseenExtra(Room room, WiredStack stack) { + InteractionWiredExtra extra = getStackExtra(room, stack, WiredExtraUnseen.class); + + return (extra instanceof WiredExtraUnseen) ? (WiredExtraUnseen) extra : null; + } + + private InteractionWiredExtra getStackExtra(Room room, WiredStack stack, Class extraClass) { + if (room == null || stack == null || stack.triggerItem() == null || room.getRoomSpecialTypes() == null) { + return null; + } + + THashSet extras = room.getRoomSpecialTypes().getExtras( + stack.triggerItem().getX(), + stack.triggerItem().getY()); + + if (extras == null || extras.isEmpty()) { + return null; + } + + for (InteractionWiredExtra extra : extras) { + if (extraClass.isInstance(extra)) { + return extra; + } + } + + return null; + } + /** * Get the services used by this engine. * @return the wired services diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredManager.java index a9cb8753..239266f0 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredManager.java @@ -36,8 +36,6 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledFuture; /** * Manager class for the new wired engine system. @@ -83,7 +81,6 @@ public final class WiredManager { private static final boolean DEFAULT_ENABLED = false; private static final boolean DEFAULT_EXCLUSIVE = false; private static final int DEFAULT_MAX_STEPS = 100; - private static final long FURNI_CLICK_TRIGGER_DELAY_MS = 400L; /** The singleton engine instance */ private static volatile WiredEngine engine; @@ -93,8 +90,6 @@ public final class WiredManager { /** Whether the engine is initialized */ private static volatile boolean initialized = false; - private static final ConcurrentHashMap> pendingFurniClickTriggers = new ConcurrentHashMap<>(); - private WiredManager() { // Static utility class } @@ -168,13 +163,6 @@ public final class WiredManager { engine.clearUnseenCache(); } - pendingFurniClickTriggers.values().forEach(future -> { - if (future != null) { - future.cancel(false); - } - }); - pendingFurniClickTriggers.clear(); - initialized = false; LOGGER.info("Wired Manager shutdown complete"); } @@ -267,34 +255,11 @@ public final class WiredManager { return; } - String clickKey = getPendingFurniClickKey(room, user, item); - - cancelPendingUserClicksFurni(room, user, item); - - ScheduledFuture future = Emulator.getThreading().run(() -> { - pendingFurniClickTriggers.remove(clickKey); - triggerUserClicksFurni(room, user, item); - }, FURNI_CLICK_TRIGGER_DELAY_MS); - - if (future != null) { - pendingFurniClickTriggers.put(clickKey, future); - } + triggerUserClicksFurni(room, user, item); } public static void cancelPendingUserClicksFurni(Room room, RoomUnit user, HabboItem item) { - if (room == null || user == null || item == null) { - return; - } - - ScheduledFuture future = pendingFurniClickTriggers.remove(getPendingFurniClickKey(room, user, item)); - - if (future != null) { - future.cancel(false); - } - } - - private static String getPendingFurniClickKey(Room room, RoomUnit user, HabboItem item) { - return room.getId() + ":" + user.getId() + ":" + item.getId(); + // Click furni triggers are now executed immediately. } /** * Trigger when a user clicks invisible click tile furniture. diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserWalkEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserWalkEvent.java index 0e174672..e54fc6e7 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserWalkEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserWalkEvent.java @@ -10,9 +10,12 @@ import com.eu.habbo.habbohotel.rooms.BedProfile; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboInfo; import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.items.interactions.wired.effects.WiredEffectMoveRotateUser; import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil; +import com.eu.habbo.habbohotel.wired.core.WiredUserMovementHelper; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.rooms.users.RoomUnitOnRollerComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer; import com.eu.habbo.plugin.events.users.UserIdleEvent; import gnu.trove.set.hash.THashSet; import org.slf4j.Logger; @@ -109,10 +112,19 @@ public class RoomUserWalkEvent extends MessageHandler { // This is where we set the end location and begin finding a path if (tile.isWalkable() || room.canSitOrLayAt(tile.x, tile.y)) { + if (WiredEffectMoveRotateUser.handleWalkWhileActive(room, roomUnit, tile)) { + return; + } + if (roomUnit.getMoveBlockingTask() != null) { roomUnit.getMoveBlockingTask().get(); } + if (WiredUserMovementHelper.shouldSuppressStatusComposer(roomUnit)) { + WiredUserMovementHelper.clearStatusComposerSuppression(roomUnit); + room.sendComposer(new RoomUserStatusComposer(roomUnit).compose()); + } + roomUnit.setGoalLocation(tile); } }