diff --git a/Emulator/.idea/misc.xml b/Emulator/.idea/misc.xml
index 5ddb3b31..6568344f 100644
--- a/Emulator/.idea/misc.xml
+++ b/Emulator/.idea/misc.xml
@@ -8,5 +8,5 @@
-
+
\ No newline at end of file
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 d4ac19c7..5ee05fe3 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
@@ -54,6 +54,8 @@ import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraAnimatio
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.WiredExtraMoveCarryUsers;
+import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraExecuteInOrder;
+import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraExecutionLimit;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraMovePhysics;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraMoveNoAnimation;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraOrEval;
@@ -349,6 +351,8 @@ public class ItemManager {
this.interactionsList.add(new ItemInteraction("wf_xtra_mov_no_animation", WiredExtraMoveNoAnimation.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_anim_time", WiredExtraAnimationTime.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_mov_physics", WiredExtraMovePhysics.class));
+ this.interactionsList.add(new ItemInteraction("wf_xtra_exec_in_order", WiredExtraExecuteInOrder.class));
+ this.interactionsList.add(new ItemInteraction("wf_xtra_execution_limit", WiredExtraExecutionLimit.class));
this.interactionsList.add(new ItemInteraction("wf_highscore", InteractionWiredHighscore.class));
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionActorDir.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionActorDir.java
index 8c0e79af..a9670b2e 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionActorDir.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionActorDir.java
@@ -162,14 +162,7 @@ public class WiredConditionActorDir extends InteractionWiredCondition {
}
private int normalizeUserSource(int value) {
- switch (value) {
- case WiredSourceUtil.SOURCE_SELECTOR:
- case WiredSourceUtil.SOURCE_SIGNAL:
- case WiredSourceUtil.SOURCE_TRIGGER:
- return value;
- default:
- return WiredSourceUtil.SOURCE_TRIGGER;
- }
+ return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
}
private int normalizeQuantifier(int value) {
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionGroupMember.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionGroupMember.java
index d442add5..441a5989 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionGroupMember.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionGroupMember.java
@@ -170,14 +170,7 @@ public class WiredConditionGroupMember extends InteractionWiredCondition {
}
private int normalizeUserSource(int value) {
- switch (value) {
- case WiredSourceUtil.SOURCE_TRIGGER:
- case WiredSourceUtil.SOURCE_SELECTOR:
- case WiredSourceUtil.SOURCE_SIGNAL:
- return value;
- default:
- return WiredSourceUtil.SOURCE_TRIGGER;
- }
+ return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
}
private int normalizeGroupType(int value) {
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotInGroup.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotInGroup.java
index 04bbe22c..06055f4d 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotInGroup.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotInGroup.java
@@ -170,14 +170,7 @@ public class WiredConditionNotInGroup extends InteractionWiredCondition {
}
private int normalizeUserSource(int value) {
- switch (value) {
- case WiredSourceUtil.SOURCE_TRIGGER:
- case WiredSourceUtil.SOURCE_SELECTOR:
- case WiredSourceUtil.SOURCE_SIGNAL:
- return value;
- default:
- return WiredSourceUtil.SOURCE_TRIGGER;
- }
+ return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
}
private int normalizeGroupType(int value) {
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionSelectionQuantity.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionSelectionQuantity.java
index 87e09812..590ea9d8 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionSelectionQuantity.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionSelectionQuantity.java
@@ -187,6 +187,10 @@ public class WiredConditionSelectionQuantity extends InteractionWiredCondition {
}
private int normalizeSourceType(int group, int value) {
+ if (group == SOURCE_GROUP_USERS) {
+ return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
+ }
+
switch (value) {
case WiredSourceUtil.SOURCE_SELECTOR:
case WiredSourceUtil.SOURCE_SIGNAL:
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamGameBase.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamGameBase.java
index 8e05c562..1b5dd1d9 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamGameBase.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamGameBase.java
@@ -84,14 +84,7 @@ abstract class WiredConditionTeamGameBase extends InteractionWiredCondition {
}
protected int normalizeUserSource(int value) {
- switch (value) {
- case WiredSourceUtil.SOURCE_SELECTOR:
- case WiredSourceUtil.SOURCE_SIGNAL:
- case WiredSourceUtil.SOURCE_TRIGGER:
- return value;
- default:
- return WiredSourceUtil.SOURCE_TRIGGER;
- }
+ return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
}
protected int normalizePlacement(int value) {
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTriggererMatch.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTriggererMatch.java
index f9997436..e7f01dcb 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTriggererMatch.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTriggererMatch.java
@@ -304,25 +304,16 @@ public class WiredConditionTriggererMatch extends InteractionWiredCondition {
}
private int normalizePrimaryUserSource(int value) {
- switch (value) {
- case WiredSourceUtil.SOURCE_SELECTOR:
- case WiredSourceUtil.SOURCE_SIGNAL:
- case WiredSourceUtil.SOURCE_TRIGGER:
- return value;
- default:
- return WiredSourceUtil.SOURCE_TRIGGER;
- }
+ return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
}
private int normalizeCompareUserSource(int value) {
switch (value) {
- case WiredSourceUtil.SOURCE_SELECTOR:
- case WiredSourceUtil.SOURCE_SIGNAL:
- case WiredSourceUtil.SOURCE_TRIGGER:
+ case WiredSourceUtil.SOURCE_CLICKED_USER:
case SOURCE_SPECIFIED_USERNAME:
return value;
default:
- return WiredSourceUtil.SOURCE_TRIGGER;
+ return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
}
}
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionUserPerformsAction.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionUserPerformsAction.java
index a24ce86e..5e49a6a1 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionUserPerformsAction.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionUserPerformsAction.java
@@ -185,14 +185,7 @@ public class WiredConditionUserPerformsAction extends InteractionWiredCondition
}
protected int normalizeUserSource(int value) {
- switch (value) {
- case WiredSourceUtil.SOURCE_SELECTOR:
- case WiredSourceUtil.SOURCE_SIGNAL:
- case WiredSourceUtil.SOURCE_TRIGGER:
- return value;
- default:
- return WiredSourceUtil.SOURCE_TRIGGER;
- }
+ return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
}
protected int normalizeSignId(int value) {
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotGiveHandItem.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotGiveHandItem.java
index 62fef4e0..53a8be66 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotGiveHandItem.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotGiveHandItem.java
@@ -213,14 +213,7 @@ public class WiredEffectBotGiveHandItem extends InteractionWiredEffect {
}
private int normalizeUserSource(int value) {
- switch (value) {
- case WiredSourceUtil.SOURCE_SELECTOR:
- case WiredSourceUtil.SOURCE_SIGNAL:
- case WiredSourceUtil.SOURCE_TRIGGER:
- return value;
- default:
- return WiredSourceUtil.SOURCE_TRIGGER;
- }
+ return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
}
private int normalizeBotSource(int value) {
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectFurniToUser.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectFurniToUser.java
index 9d4fad7d..63274e00 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectFurniToUser.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectFurniToUser.java
@@ -4,14 +4,22 @@ import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.rooms.FurnitureMovementError;
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.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
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.WiredMoveCarryHelper;
+import com.eu.habbo.messages.outgoing.rooms.WiredMovementsComposer;
import java.sql.ResultSet;
import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
public class WiredEffectFurniToUser extends WiredEffectUserFurniBase {
public static final WiredEffectType type = WiredEffectType.FURNI_TO_USER;
@@ -27,27 +35,102 @@ public class WiredEffectFurniToUser extends WiredEffectUserFurniBase {
@Override
public void execute(WiredContext ctx) {
Room room = ctx.room();
- HabboItem item = this.resolveLastItem(ctx);
+ List items = new ArrayList<>(this.resolveItems(ctx));
Habbo habbo = this.resolveLastHabbo(room, ctx);
- if (room == null || item == null || habbo == null || habbo.getRoomUnit() == null) {
+ if (room == null || habbo == null || habbo.getRoomUnit() == null) {
return;
}
- RoomTile targetTile = habbo.getRoomUnit().getCurrentLocation();
+ items.removeIf(item -> item == null);
+
+ if (items.isEmpty()) {
+ return;
+ }
+
+ items.sort(Comparator
+ .comparingDouble(HabboItem::getZ)
+ .thenComparingInt(HabboItem::getId));
+
+ Map followerZOverrides = new HashMap<>();
+
+ for (HabboItem item : items) {
+ followerZOverrides.put(item.getId(), item.getZ());
+ }
+
+ RoomUnit roomUnit = habbo.getRoomUnit();
+ boolean hasActiveMoveStatus = roomUnit.hasStatus(RoomUnitStatus.MOVE);
+ long moveStatusTimestamp = hasActiveMoveStatus ? roomUnit.getMoveStatusTimestamp() : 0L;
+
+ if (roomUnit.isWalking()) {
+ for (HabboItem item : items) {
+ if (item == null) {
+ continue;
+ }
+
+ WiredMoveCarryHelper.registerUserFollower(room, this, item, roomUnit, followerZOverrides.get(item.getId()), ctx);
+ }
+
+ if (!hasActiveMoveStatus) {
+ return;
+ }
+ }
+
+ RoomTile targetTile = this.resolveTargetTile(habbo);
if (targetTile == null) {
return;
}
- FurnitureMovementError error = WiredMoveCarryHelper.moveFurni(room, this, item, targetTile, item.getRotation(), null, false, ctx);
- if (error == FurnitureMovementError.NONE) {
- return;
+ Integer animationDurationOverride = WiredMoveCarryHelper.hasNoAnimationExtra(room, this)
+ ? null
+ : this.resolveFollowAnimationDuration(room, habbo, this);
+ int anchorType = hasActiveMoveStatus ? WiredMovementsComposer.FURNI_ANCHOR_USER : WiredMovementsComposer.FURNI_ANCHOR_NONE;
+ int anchorId = hasActiveMoveStatus ? roomUnit.getId() : 0;
+
+ if (hasActiveMoveStatus) {
+ int animationDuration = WiredMoveCarryHelper.resolveMoveStepDuration(roomUnit);
+ int animationElapsed = WiredMoveCarryHelper.resolveMoveStepElapsed(roomUnit);
+
+ for (HabboItem item : items) {
+ if (item == null || WiredMoveCarryHelper.isUserFollowerProcessed(roomUnit, item, moveStatusTimestamp)) {
+ continue;
+ }
+
+ Double targetZ = WiredMoveCarryHelper.resolveFollowerStackZ(room, item, targetTile, item.getRotation());
+ FurnitureMovementError error = WiredMoveCarryHelper.moveFurni(room, this, item, targetTile, item.getRotation(), targetZ, null, false, ctx, animationDuration, animationElapsed, WiredMovementsComposer.FURNI_ANCHOR_USER, roomUnit.getId());
+ if (error != FurnitureMovementError.NONE) {
+ Double fallbackZ = followerZOverrides.get(item.getId());
+
+ if (fallbackZ != null) {
+ error = WiredMoveCarryHelper.moveFurni(room, this, item, targetTile, item.getRotation(), fallbackZ, null, false, ctx, animationDuration, animationElapsed, WiredMovementsComposer.FURNI_ANCHOR_USER, roomUnit.getId());
+ }
+ }
+
+ if (error == FurnitureMovementError.NONE) {
+ WiredMoveCarryHelper.markUserFollowerProcessed(roomUnit, item, moveStatusTimestamp);
+ }
+ }
}
- if (item.getBaseItem().getStateCount() > 0) {
- error = WiredMoveCarryHelper.moveFurni(room, this, item, targetTile, item.getRotation(), item.getZ(), null, false, ctx);
+ for (HabboItem item : items) {
+ if (item == null) {
+ continue;
+ }
+
+ if (hasActiveMoveStatus && WiredMoveCarryHelper.isUserFollowerProcessed(roomUnit, item, moveStatusTimestamp)) {
+ continue;
+ }
+
+ Double targetZ = WiredMoveCarryHelper.resolveFollowerStackZ(room, item, targetTile, item.getRotation());
+ FurnitureMovementError error = WiredMoveCarryHelper.moveFurni(room, this, item, targetTile, item.getRotation(), targetZ, null, false, ctx, animationDurationOverride, null, anchorType, anchorId);
if (error == FurnitureMovementError.NONE) {
- return;
+ continue;
+ }
+
+ Double fallbackZ = followerZOverrides.get(item.getId());
+
+ if (fallbackZ != null) {
+ WiredMoveCarryHelper.moveFurni(room, this, item, targetTile, item.getRotation(), fallbackZ, null, false, ctx, animationDurationOverride, null, anchorType, anchorId);
}
}
}
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUserFurniBase.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUserFurniBase.java
index bc86f16d..9f8df66b 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUserFurniBase.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUserFurniBase.java
@@ -7,15 +7,19 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger;
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.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.Habbo;
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.habbohotel.wired.core.WiredMoveCarryHelper;
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
+import com.eu.habbo.messages.outgoing.rooms.WiredMovementsComposer;
import gnu.trove.procedure.TObjectProcedure;
import java.sql.ResultSet;
@@ -37,7 +41,7 @@ public abstract class WiredEffectUserFurniBase extends InteractionWiredEffect {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
- protected HabboItem resolveLastItem(WiredContext ctx) {
+ protected List resolveItems(WiredContext ctx) {
Room room = ctx.room();
List effectiveItems = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.items);
@@ -47,6 +51,12 @@ public abstract class WiredEffectUserFurniBase extends InteractionWiredEffect {
|| room.getHabboItem(item.getId()) == null);
}
+ return effectiveItems;
+ }
+
+ protected HabboItem resolveLastItem(WiredContext ctx) {
+ List effectiveItems = this.resolveItems(ctx);
+
if (effectiveItems.isEmpty()) {
return null;
}
@@ -90,6 +100,62 @@ public abstract class WiredEffectUserFurniBase extends InteractionWiredEffect {
return habbos;
}
+ protected RoomTile resolveTargetTile(Habbo habbo) {
+ if (habbo == null || habbo.getRoomUnit() == null) {
+ return null;
+ }
+
+ RoomUnit roomUnit = habbo.getRoomUnit();
+ RoomTile movingTile = this.resolveActiveMoveTile(roomUnit);
+
+ if (movingTile != null) {
+ return movingTile;
+ }
+
+ return roomUnit.getCurrentLocation();
+ }
+
+ private RoomTile resolveActiveMoveTile(RoomUnit roomUnit) {
+ if (roomUnit == null || roomUnit.getRoom() == null || roomUnit.getRoom().getLayout() == null) {
+ return null;
+ }
+
+ String moveStatus = roomUnit.getStatus(RoomUnitStatus.MOVE);
+ if (moveStatus != null && !moveStatus.isEmpty()) {
+ String[] parts = moveStatus.split(",");
+ if (parts.length >= 2) {
+ try {
+ return roomUnit.getRoom().getLayout().getTile(
+ Short.parseShort(parts[0]),
+ Short.parseShort(parts[1]));
+ } catch (NumberFormatException ignored) {
+ }
+ }
+ }
+
+ return null;
+ }
+
+ protected Integer resolveFollowAnimationDuration(Room room, Habbo habbo, HabboItem stackItem) {
+ if (room == null || habbo == null || habbo.getRoomUnit() == null || stackItem == null) {
+ return null;
+ }
+
+ RoomUnit roomUnit = habbo.getRoomUnit();
+ if (this.resolveActiveMoveTile(roomUnit) == null) {
+ return null;
+ }
+
+ long moveStatusTimestamp = roomUnit.getMoveStatusTimestamp();
+ if (moveStatusTimestamp <= 0L) {
+ return null;
+ }
+
+ int configuredDuration = WiredMoveCarryHelper.getAnimationDuration(room, stackItem, WiredMovementsComposer.DEFAULT_DURATION);
+ int remainingStepDuration = (int) Math.max(50L, WiredMovementsComposer.DEFAULT_DURATION - Math.max(0L, System.currentTimeMillis() - moveStatusTimestamp));
+ return Math.min(configuredDuration, remainingStepDuration);
+ }
+
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraExecuteInOrder.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraExecuteInOrder.java
new file mode 100644
index 00000000..d6023d4e
--- /dev/null
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraExecuteInOrder.java
@@ -0,0 +1,78 @@
+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.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.core.WiredManager;
+import com.eu.habbo.messages.ServerMessage;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+public class WiredExtraExecuteInOrder extends InteractionWiredExtra {
+ public static final int CODE = 64;
+
+ public WiredExtraExecuteInOrder(ResultSet set, Item baseItem) throws SQLException {
+ super(set, baseItem);
+ }
+
+ public WiredExtraExecuteInOrder(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
+ super(id, userId, item, extradata, limitedStack, limitedSells);
+ }
+
+ @Override
+ public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
+ return false;
+ }
+
+ @Override
+ public boolean saveData(WiredSettings settings, GameClient gameClient) {
+ return true;
+ }
+
+ @Override
+ public String getWiredData() {
+ 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
+ public void onPickUp() {
+
+ }
+
+ @Override
+ public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) throws Exception {
+
+ }
+
+ @Override
+ public boolean hasConfiguration() {
+ return true;
+ }
+
+ static class JsonData {
+ }
+}
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraExecutionLimit.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraExecutionLimit.java
new file mode 100644
index 00000000..f616b855
--- /dev/null
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraExecutionLimit.java
@@ -0,0 +1,204 @@
+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.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.core.WiredManager;
+import com.eu.habbo.messages.ServerMessage;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+public class WiredExtraExecutionLimit extends InteractionWiredExtra {
+ public static final int CODE = 65;
+ public static final int MIN_EXECUTIONS = 1;
+ public static final int MAX_EXECUTIONS = 100;
+ public static final int DEFAULT_EXECUTIONS = 1;
+ public static final int MIN_TIME_WINDOW_MS = 1000;
+ public static final int MAX_TIME_WINDOW_MS = 10000;
+ public static final int DEFAULT_TIME_WINDOW_MS = 1000;
+ public static final int TIME_WINDOW_STEP_MS = 500;
+
+ private final Deque recentExecutionTimestamps = new ArrayDeque<>();
+ private int maxExecutions = DEFAULT_EXECUTIONS;
+ private int timeWindowMs = DEFAULT_TIME_WINDOW_MS;
+
+ public WiredExtraExecutionLimit(ResultSet set, Item baseItem) throws SQLException {
+ super(set, baseItem);
+ }
+
+ public WiredExtraExecutionLimit(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
+ super(id, userId, item, extradata, limitedStack, limitedSells);
+ }
+
+ @Override
+ public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
+ return false;
+ }
+
+ @Override
+ public boolean saveData(WiredSettings settings, GameClient gameClient) {
+ int[] intParams = settings.getIntParams();
+ int nextExecutions = (intParams.length > 0) ? intParams[0] : this.maxExecutions;
+ int nextTimeWindowMs = (intParams.length > 1) ? intParams[1] : this.timeWindowMs;
+
+ this.maxExecutions = normalizeExecutions(nextExecutions);
+ this.timeWindowMs = normalizeTimeWindowMs(nextTimeWindowMs);
+ clearRuntimeState();
+ return true;
+ }
+
+ @Override
+ public String getWiredData() {
+ return WiredManager.getGson().toJson(new JsonData(this.maxExecutions, this.timeWindowMs));
+ }
+
+ @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.maxExecutions);
+ message.appendInt(this.timeWindowMs);
+ 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);
+
+ if (data != null) {
+ this.maxExecutions = normalizeExecutions(data.maxExecutions);
+ this.timeWindowMs = normalizeTimeWindowMs(data.timeWindowMs);
+ }
+
+ return;
+ }
+
+ String[] legacyData = wiredData.split(";");
+
+ try {
+ if (legacyData.length > 0) {
+ this.maxExecutions = normalizeExecutions(Integer.parseInt(legacyData[0]));
+ }
+
+ if (legacyData.length > 1) {
+ this.timeWindowMs = normalizeTimeWindowMs(Integer.parseInt(legacyData[1]));
+ }
+ } catch (NumberFormatException ignored) {
+ this.maxExecutions = DEFAULT_EXECUTIONS;
+ this.timeWindowMs = DEFAULT_TIME_WINDOW_MS;
+ }
+ }
+
+ @Override
+ public void onPickUp() {
+ this.maxExecutions = DEFAULT_EXECUTIONS;
+ this.timeWindowMs = DEFAULT_TIME_WINDOW_MS;
+ clearRuntimeState();
+ }
+
+ @Override
+ public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) throws Exception {
+
+ }
+
+ @Override
+ public void onMove(Room room, RoomTile oldLocation, RoomTile newLocation) {
+ super.onMove(room, oldLocation, newLocation);
+ clearRuntimeState();
+ }
+
+ @Override
+ public boolean hasConfiguration() {
+ return true;
+ }
+
+ public boolean tryAcquireExecutionSlot(long timestamp) {
+ synchronized (this.recentExecutionTimestamps) {
+ pruneExpiredTimestamps(timestamp);
+
+ if (this.recentExecutionTimestamps.size() >= this.maxExecutions) {
+ return false;
+ }
+
+ this.recentExecutionTimestamps.addLast(timestamp);
+ return true;
+ }
+ }
+
+ public boolean canExecuteAt(long timestamp) {
+ synchronized (this.recentExecutionTimestamps) {
+ pruneExpiredTimestamps(timestamp);
+ return this.recentExecutionTimestamps.size() < this.maxExecutions;
+ }
+ }
+
+ public int getMaxExecutions() {
+ return this.maxExecutions;
+ }
+
+ public int getTimeWindowMs() {
+ return this.timeWindowMs;
+ }
+
+ public void clearRuntimeState() {
+ synchronized (this.recentExecutionTimestamps) {
+ this.recentExecutionTimestamps.clear();
+ }
+ }
+
+ private void pruneExpiredTimestamps(long timestamp) {
+ while (!this.recentExecutionTimestamps.isEmpty()
+ && (timestamp - this.recentExecutionTimestamps.peekFirst()) >= this.timeWindowMs) {
+ this.recentExecutionTimestamps.removeFirst();
+ }
+ }
+
+ private static int normalizeExecutions(int value) {
+ return Math.max(MIN_EXECUTIONS, Math.min(MAX_EXECUTIONS, value));
+ }
+
+ private static int normalizeTimeWindowMs(int value) {
+ if (value < MIN_TIME_WINDOW_MS) {
+ return MIN_TIME_WINDOW_MS;
+ }
+
+ if (value > MAX_TIME_WINDOW_MS) {
+ return MAX_TIME_WINDOW_MS;
+ }
+
+ return Math.round(value / (float) TIME_WINDOW_STEP_MS) * TIME_WINDOW_STEP_MS;
+ }
+
+ static class JsonData {
+ int maxExecutions;
+ int timeWindowMs;
+
+ JsonData(int maxExecutions, int timeWindowMs) {
+ this.maxExecutions = maxExecutions;
+ this.timeWindowMs = timeWindowMs;
+ }
+ }
+}
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtSetTime.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtSetTime.java
index c1bfecef..9f460464 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtSetTime.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtSetTime.java
@@ -155,12 +155,20 @@ public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements Wi
// Check if enough time has passed
if (this.accumulatedTime >= this.executeTime) {
+ if (this.getRoomId() != 0 && room.isLoaded()) {
+ long currentTime = System.currentTimeMillis();
+ if (!WiredManager.isTriggerExecutionAllowed(room, this, currentTime)) {
+ return;
+ }
+
+ this.hasFired = true;
+ this.accumulatedTime = 0;
+ WiredManager.triggerTimerTick(room, this);
+ return;
+ }
+
this.hasFired = true;
this.accumulatedTime = 0;
-
- if (this.getRoomId() != 0 && room.isLoaded()) {
- WiredManager.triggerTimerTick(room, this);
- }
}
}
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtTimeLong.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtTimeLong.java
index 24475dcc..8864f4c3 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtTimeLong.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtTimeLong.java
@@ -155,12 +155,20 @@ public class WiredTriggerAtTimeLong extends InteractionWiredTrigger implements W
// Check if enough time has passed
if (this.accumulatedTime >= this.executeTime) {
+ if (this.getRoomId() != 0 && room.isLoaded()) {
+ long currentTime = System.currentTimeMillis();
+ if (!WiredManager.isTriggerExecutionAllowed(room, this, currentTime)) {
+ return;
+ }
+
+ this.hasFired = true;
+ this.accumulatedTime = 0;
+ WiredManager.triggerTimerTick(room, this);
+ return;
+ }
+
this.hasFired = true;
this.accumulatedTime = 0;
-
- if (this.getRoomId() != 0 && room.isLoaded()) {
- WiredManager.triggerTimerTick(room, this);
- }
}
}
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeater.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeater.java
index e91a43f3..2ac70e88 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeater.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeater.java
@@ -144,7 +144,9 @@ public class WiredTriggerRepeater extends InteractionWiredTrigger implements Wir
// Fire when elapsed time is a multiple of repeatTime
if (elapsedMs % this.repeatTime == 0) {
- if (this.getRoomId() != 0 && room.isLoaded()) {
+ long currentTime = System.currentTimeMillis();
+ if (this.getRoomId() != 0 && room.isLoaded()
+ && WiredManager.isTriggerExecutionAllowed(room, this, currentTime)) {
WiredManager.triggerTimerRepeat(room, this);
}
}
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java
index dadac6be..3986d5b8 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java
@@ -138,7 +138,9 @@ public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements
// Fire when elapsed time is a multiple of repeat time
if (elapsedMs % this.repeatTime == 0) {
- if (this.getRoomId() != 0 && room.isLoaded()) {
+ long currentTime = System.currentTimeMillis();
+ if (this.getRoomId() != 0 && room.isLoaded()
+ && WiredManager.isTriggerExecutionAllowed(room, this, currentTime)) {
WiredManager.triggerTimerRepeatLong(room, this);
}
}
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterShort.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterShort.java
index 00800e67..e20f5bae 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterShort.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterShort.java
@@ -105,7 +105,9 @@ public class WiredTriggerRepeaterShort extends WiredTriggerRepeater {
long elapsedMs = tickCount * tickIntervalMs;
if (elapsedMs % this.repeatTime == 0) {
- if (this.getRoomId() != 0 && room.isLoaded()) {
+ long currentTime = System.currentTimeMillis();
+ if (this.getRoomId() != 0 && room.isLoaded()
+ && WiredManager.isTriggerExecutionAllowed(room, this, currentTime)) {
WiredManager.triggerTimerRepeatShort(room, this);
}
}
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java
index 989f7f57..f8f61b86 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java
@@ -2032,6 +2032,10 @@ public class Room implements Comparable, ISerialize, Runnable {
this.messagingManager.sendComposer(message);
}
+ public void sendComposers(Collection messages) {
+ this.messagingManager.sendComposers(messages);
+ }
+
public void sendComposerToHabbosWithRights(ServerMessage message) {
this.messagingManager.sendComposerToHabbosWithRights(message);
}
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCycleManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCycleManager.java
index 33e1cde8..8f5bbc51 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCycleManager.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCycleManager.java
@@ -10,6 +10,7 @@ import com.eu.habbo.habbohotel.users.DanceType;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
+import com.eu.habbo.habbohotel.wired.core.WiredMoveCarryHelper;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.rooms.RoomAccessDeniedComposer;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUnitIdleComposer;
@@ -123,7 +124,19 @@ public class RoomCycleManager {
// Send status updates
if (!updatedUnit.isEmpty()) {
- this.room.sendComposer(new RoomUserStatusComposer(updatedUnit, true).compose());
+ ServerMessage statusComposer = new RoomUserStatusComposer(updatedUnit, true).compose();
+ WiredMoveCarryHelper.beginMovementCollection();
+ WiredMoveCarryHelper.processUserFollowers(this.room, updatedUnit);
+ ServerMessage wiredMovementsComposer = WiredMoveCarryHelper.finishMovementCollection();
+
+ if (wiredMovementsComposer != null) {
+ ArrayList batchedMessages = new ArrayList<>(2);
+ batchedMessages.add(statusComposer);
+ batchedMessages.add(wiredMovementsComposer);
+ this.room.sendComposers(batchedMessages);
+ } else {
+ this.room.sendComposer(statusComposer);
+ }
}
// Cycle trax manager
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomMessagingManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomMessagingManager.java
index 10c8173c..5807cdec 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomMessagingManager.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomMessagingManager.java
@@ -4,6 +4,9 @@ import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer;
+import java.util.ArrayList;
+import java.util.Collection;
+
/**
* Manages all messaging and communication within a room.
* Handles sending messages to Habbos, pet/bot chat, and alerts.
@@ -30,6 +33,34 @@ public class RoomMessagingManager {
}
}
+ public void sendComposers(Collection messages) {
+ if (messages == null || messages.isEmpty()) {
+ return;
+ }
+
+ ArrayList responses = new ArrayList<>();
+
+ for (ServerMessage message : messages) {
+ if (message == null) {
+ continue;
+ }
+
+ responses.add(message);
+ }
+
+ if (responses.isEmpty()) {
+ return;
+ }
+
+ for (Habbo habbo : this.room.getHabbos()) {
+ if (habbo.getClient() == null) {
+ continue;
+ }
+
+ habbo.getClient().sendResponses(new ArrayList<>(responses));
+ }
+ }
+
/**
* Sends a message to all Habbos with rights in the room.
*/
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnit.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnit.java
index 66d2e5e5..bda55501 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnit.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnit.java
@@ -74,6 +74,7 @@ public class RoomUnit {
private int handItem;
private long handItemTimestamp;
private long lastRollerTime;
+ private long moveStatusTimestamp;
private int walkTimeOut;
private int effectId;
private int effectEndTimestamp;
@@ -104,6 +105,7 @@ public class RoomUnit {
this.goalLocation = null;
this.startLocation = this.currentLocation;
this.inRoom = false;
+ this.moveStatusTimestamp = 0L;
this.status.clear();
@@ -611,12 +613,16 @@ public class RoomUnit {
}
public void removeStatus(RoomUnitStatus key) {
+ if (key == RoomUnitStatus.MOVE) {
+ this.moveStatusTimestamp = 0L;
+ }
this.status.remove(key);
}
public void setStatus(RoomUnitStatus key, String value) {
if (key != null && value != null) {
if (key == RoomUnitStatus.MOVE) {
+ this.moveStatusTimestamp = System.currentTimeMillis();
WiredMoveCarryHelper.clearStatusComposerSuppression(this);
WiredUserMovementHelper.clearStatusComposerSuppression(this);
}
@@ -630,6 +636,7 @@ public class RoomUnit {
}
public void clearStatus() {
+ this.moveStatusTimestamp = 0L;
this.status.clear();
}
@@ -657,6 +664,10 @@ public class RoomUnit {
this.lastRollerTime = lastRollerTime;
}
+ public long getMoveStatusTimestamp() {
+ return this.moveStatusTimestamp;
+ }
+
/**
* Checks if enough time has passed since the last roller movement to allow rolling again.
* This prevents desync issues where the client hasn't finished the roller animation.
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 47ee4f1c..759f5b86 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
@@ -10,6 +10,8 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredTriggerReset;
import com.eu.habbo.habbohotel.items.interactions.wired.effects.WiredEffectGiveReward;
import com.eu.habbo.habbohotel.items.interactions.wired.effects.WiredEffectTriggerStacks;
+import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraExecuteInOrder;
+import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraExecutionLimit;
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.rooms.Room;
@@ -27,6 +29,7 @@ import com.eu.habbo.plugin.events.furniture.wired.WiredConditionFailedEvent;
import com.eu.habbo.plugin.events.furniture.wired.WiredStackExecutedEvent;
import com.eu.habbo.plugin.events.furniture.wired.WiredStackTriggeredEvent;
import com.eu.habbo.plugin.events.users.UserWiredRewardReceived;
+import com.eu.habbo.habbohotel.wired.core.WiredExecutionOrderUtil;
import com.google.gson.GsonBuilder;
import gnu.trove.set.hash.THashSet;
import org.slf4j.Logger;
@@ -37,7 +40,7 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
-import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
public class WiredHandler {
@@ -49,6 +52,11 @@ public class WiredHandler {
private static GsonBuilder gsonBuilder = null;
+ private static final class LegacyExecutionPlan {
+ private final LinkedHashSet effects = new LinkedHashSet<>();
+ private boolean executeInOrder = false;
+ }
+
public static boolean handle(WiredTriggerType triggerType, RoomUnit roomUnit, Room room, Object[] stuff) {
if (triggerType == WiredTriggerType.CUSTOM) return false;
@@ -72,7 +80,7 @@ public class WiredHandler {
return false;
long millis = System.currentTimeMillis();
- THashSet effectsToExecute = new THashSet();
+ List executionPlans = new ArrayList<>();
List triggeredTiles = new ArrayList<>();
for (InteractionWiredTrigger trigger : triggers) {
@@ -81,10 +89,10 @@ public class WiredHandler {
if (triggeredTiles.contains(tile))
continue;
- THashSet tEffectsToExecute = new THashSet();
+ LegacyExecutionPlan executionPlan = new LegacyExecutionPlan();
- if (handle(trigger, roomUnit, room, stuff, tEffectsToExecute)) {
- effectsToExecute.addAll(tEffectsToExecute);
+ if (handle(trigger, roomUnit, room, stuff, executionPlan)) {
+ executionPlans.add(executionPlan);
if (triggerType.equals(WiredTriggerType.SAY_SOMETHING))
talked = true;
@@ -93,8 +101,8 @@ public class WiredHandler {
}
}
- for (InteractionWiredEffect effect : effectsToExecute) {
- triggerEffect(effect, roomUnit, room, stuff, millis);
+ for (LegacyExecutionPlan executionPlan : executionPlans) {
+ triggerEffects(executionPlan.effects, roomUnit, room, stuff, millis, executionPlan.executeInOrder);
}
return talked;
@@ -119,7 +127,7 @@ public class WiredHandler {
return false;
long millis = System.currentTimeMillis();
- THashSet effectsToExecute = new THashSet();
+ List executionPlans = new ArrayList<>();
List triggeredTiles = new ArrayList<>();
for (InteractionWiredTrigger trigger : triggers) {
@@ -130,44 +138,51 @@ public class WiredHandler {
if (triggeredTiles.contains(tile))
continue;
- THashSet tEffectsToExecute = new THashSet();
+ LegacyExecutionPlan executionPlan = new LegacyExecutionPlan();
- if (handle(trigger, roomUnit, room, stuff, tEffectsToExecute)) {
- effectsToExecute.addAll(tEffectsToExecute);
+ if (handle(trigger, roomUnit, room, stuff, executionPlan)) {
+ executionPlans.add(executionPlan);
triggeredTiles.add(tile);
}
}
- for (InteractionWiredEffect effect : effectsToExecute) {
- triggerEffect(effect, roomUnit, room, stuff, millis);
+ for (LegacyExecutionPlan executionPlan : executionPlans) {
+ triggerEffects(executionPlan.effects, roomUnit, room, stuff, millis, executionPlan.executeInOrder);
}
- return effectsToExecute.size() > 0;
+ return !executionPlans.isEmpty();
}
public static boolean handle(InteractionWiredTrigger trigger, final RoomUnit roomUnit, final Room room, final Object[] stuff) {
long millis = System.currentTimeMillis();
- THashSet effectsToExecute = new THashSet();
+ LegacyExecutionPlan executionPlan = new LegacyExecutionPlan();
- if(handle(trigger, roomUnit, room, stuff, effectsToExecute)) {
- for (InteractionWiredEffect effect : effectsToExecute) {
- triggerEffect(effect, roomUnit, room, stuff, millis);
- }
+ if(handle(trigger, roomUnit, room, stuff, executionPlan)) {
+ triggerEffects(executionPlan.effects, roomUnit, room, stuff, millis, executionPlan.executeInOrder);
return true;
}
return false;
}
- public static boolean handle(InteractionWiredTrigger trigger, final RoomUnit roomUnit, final Room room, final Object[] stuff, final THashSet effectsToExecute) {
+ private static boolean handle(InteractionWiredTrigger trigger, final RoomUnit roomUnit, final Room room, final Object[] stuff, final LegacyExecutionPlan executionPlan) {
long millis = System.currentTimeMillis();
int roomUnitId = roomUnit != null ? roomUnit.getId() : -1;
if (Emulator.isReady && ((Emulator.getConfig().getBoolean("wired.custom.enabled", false) && (trigger.canExecute(millis) || roomUnitId > -1) && trigger.userCanExecute(roomUnitId, millis)) || (!Emulator.getConfig().getBoolean("wired.custom.enabled", false) && trigger.canExecute(millis))) && trigger.execute(roomUnit, room, stuff)) {
- trigger.activateBox(room, roomUnit, millis);
-
THashSet conditions = room.getRoomSpecialTypes().getConditions(trigger.getX(), trigger.getY());
THashSet effects = room.getRoomSpecialTypes().getEffects(trigger.getX(), trigger.getY());
- if (Emulator.getPluginManager().fireEvent(new WiredStackTriggeredEvent(room, roomUnit, trigger, effects, conditions)).isCancelled())
- return false;
+ THashSet extras = room.getRoomSpecialTypes().getExtras(trigger.getX(), trigger.getY());
+ WiredExtraExecutionLimit executionLimitExtra = null;
+ WiredExtraRandom randomExtra = null;
+
+ for (InteractionWiredExtra extra : extras) {
+ if (executionLimitExtra == null && extra instanceof WiredExtraExecutionLimit) {
+ executionLimitExtra = (WiredExtraExecutionLimit) extra;
+ }
+
+ if (randomExtra == null && extra instanceof WiredExtraRandom) {
+ randomExtra = (WiredExtraRandom) extra;
+ }
+ }
if (!conditions.isEmpty()) {
ArrayList matchedConditions = new ArrayList<>(conditions.size());
@@ -187,39 +202,48 @@ public class WiredHandler {
}
}
+ if (executionLimitExtra != null && !executionLimitExtra.tryAcquireExecutionSlot(millis)) {
+ return false;
+ }
+
+ if (Emulator.getPluginManager().fireEvent(new WiredStackTriggeredEvent(room, roomUnit, trigger, effects, conditions)).isCancelled())
+ return false;
+
+ trigger.activateBox(room, roomUnit, millis);
+
trigger.setCooldown(millis);
boolean hasExtraUnseen = room.getRoomSpecialTypes().hasExtraType(trigger.getX(), trigger.getY(), WiredExtraUnseen.class);
- THashSet extras = room.getRoomSpecialTypes().getExtras(trigger.getX(), trigger.getY());
- WiredExtraRandom randomExtra = null;
+ boolean hasExtraExecuteInOrder = room.getRoomSpecialTypes().hasExtraType(trigger.getX(), trigger.getY(), WiredExtraExecuteInOrder.class);
for (InteractionWiredExtra extra : extras) {
extra.activateBox(room, roomUnit, millis);
- if (randomExtra == null && extra instanceof WiredExtraRandom) {
- randomExtra = (WiredExtraRandom) extra;
- }
}
- List effectList = new ArrayList<>(effects);
+ List effectList = (hasExtraUnseen || hasExtraExecuteInOrder)
+ ? WiredExecutionOrderUtil.sort(effects)
+ : new ArrayList<>(effects);
- if (randomExtra != null || hasExtraUnseen) {
- Collections.shuffle(effectList);
- }
+ executionPlan.executeInOrder = hasExtraExecuteInOrder;
if (hasExtraUnseen) {
for (InteractionWiredExtra extra : room.getRoomSpecialTypes().getExtras(trigger.getX(), trigger.getY())) {
if (extra instanceof WiredExtraUnseen) {
extra.setExtradata(extra.getExtradata().equals("1") ? "0" : "1");
InteractionWiredEffect effect = ((WiredExtraUnseen) extra).getUnseenEffect(effectList);
- effectsToExecute.add(effect); // triggerEffect(effect, roomUnit, room, stuff, millis);
+ if (effect != null) {
+ executionPlan.effects.add(effect);
+ }
break;
}
}
} else if (randomExtra != null) {
- effectsToExecute.addAll(randomExtra.selectEffects(effectList));
+ executionPlan.effects.addAll(randomExtra.selectEffects(effectList));
+ } else if (hasExtraExecuteInOrder) {
+ executionPlan.effects.addAll(effectList);
} else {
for (final InteractionWiredEffect effect : effectList) {
- effectsToExecute.add(effect); //triggerEffect(effect, roomUnit, room, stuff, millis);
+ executionPlan.effects.add(effect);
}
}
@@ -234,7 +258,7 @@ public class WiredHandler {
if (effect != null && (effect.canExecute(millis) || (roomUnit != null && effect.requiresTriggeringUser() && Emulator.getConfig().getBoolean("wired.custom.enabled", false) && effect.userCanExecute(roomUnit.getId(), millis)))) {
executed = true;
if (!effect.requiresTriggeringUser() || (roomUnit != null && effect.requiresTriggeringUser())) {
- Emulator.getThreading().run(() -> {
+ Runnable execution = () -> {
if (room.isLoaded() && room.getHabbos().size() > 0) {
try {
if (!effect.execute(roomUnit, room, stuff)) return;
@@ -245,13 +269,108 @@ public class WiredHandler {
effect.activateBox(room, roomUnit, millis);
}
- }, effect.getDelay() * 500L);
+ };
+
+ long delayMs = effect.getDelay() * 500L;
+ long elapsedSinceTrigger = Math.max(0L, System.currentTimeMillis() - millis);
+ long remainingDelayMs = Math.max(0L, delayMs - elapsedSinceTrigger);
+
+ if (delayMs <= 0) {
+ execution.run();
+ } else {
+ Emulator.getThreading().run(execution, remainingDelayMs);
+ }
}
}
return executed;
}
+ private static void triggerEffects(LinkedHashSet effects, RoomUnit roomUnit, Room room, Object[] stuff, long millis, boolean executeInOrder) {
+ if (effects == null || effects.isEmpty()) {
+ return;
+ }
+
+ if (!executeInOrder) {
+ for (InteractionWiredEffect effect : effects) {
+ triggerEffect(effect, roomUnit, room, stuff, millis);
+ }
+ return;
+ }
+
+ LinkedHashSet queueableEffects = new LinkedHashSet<>();
+
+ for (InteractionWiredEffect effect : effects) {
+ if (canQueueEffect(effect, roomUnit, millis)) {
+ queueableEffects.add(effect);
+ }
+ }
+
+ LinkedHashSet delays = new LinkedHashSet<>();
+ for (InteractionWiredEffect effect : queueableEffects) {
+ delays.add(effect.getDelay());
+ }
+
+ for (Integer delay : delays) {
+ List delayBatch = new ArrayList<>();
+
+ for (InteractionWiredEffect effect : queueableEffects) {
+ if (effect.getDelay() == delay) {
+ delayBatch.add(effect);
+ }
+ }
+
+ if (delayBatch.isEmpty()) {
+ continue;
+ }
+
+ if (delay > 0) {
+ long delayMs = delay * 500L;
+ long elapsedSinceTrigger = Math.max(0L, System.currentTimeMillis() - millis);
+ long remainingDelayMs = Math.max(0L, delayMs - elapsedSinceTrigger);
+ Emulator.getThreading().run(() -> executeOrderedEffectBatch(delayBatch, roomUnit, room, stuff, millis), remainingDelayMs);
+ } else {
+ executeOrderedEffectBatch(delayBatch, roomUnit, room, stuff, millis);
+ }
+ }
+ }
+
+ private static boolean canQueueEffect(InteractionWiredEffect effect, RoomUnit roomUnit, long millis) {
+ if (effect == null) {
+ return false;
+ }
+
+ boolean canExecute = effect.canExecute(millis)
+ || (roomUnit != null && effect.requiresTriggeringUser()
+ && Emulator.getConfig().getBoolean("wired.custom.enabled", false)
+ && effect.userCanExecute(roomUnit.getId(), millis));
+
+ if (!canExecute) {
+ return false;
+ }
+
+ return !effect.requiresTriggeringUser() || roomUnit != null;
+ }
+
+ private static void executeOrderedEffectBatch(List effects, RoomUnit roomUnit, Room room, Object[] stuff, long millis) {
+ if (!room.isLoaded() || room.getHabbos().size() <= 0) {
+ return;
+ }
+
+ for (InteractionWiredEffect effect : effects) {
+ try {
+ if (!effect.execute(roomUnit, room, stuff)) {
+ continue;
+ }
+
+ effect.setCooldown(millis);
+ effect.activateBox(room, roomUnit, millis);
+ } catch (Exception e) {
+ LOGGER.error("Caught exception", e);
+ }
+ }
+ }
+
public static GsonBuilder getGsonBuilder() {
if(gsonBuilder == null) {
gsonBuilder = new GsonBuilder();
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/api/WiredStack.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/api/WiredStack.java
index 0713715a..9833f53f 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/api/WiredStack.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/api/WiredStack.java
@@ -39,6 +39,7 @@ public final class WiredStack {
private final boolean useOrMode; // WiredExtraOrEval present
private final boolean useRandom; // WiredExtraRandom present
private final boolean useUnseen; // WiredExtraUnseen present
+ private final boolean executeInOrder; // WiredExtraExecuteInOrder present
/**
* Create a new wired stack.
@@ -52,7 +53,7 @@ public final class WiredStack {
IWiredTrigger trigger,
List conditions,
List effects) {
- this(triggerItem, trigger, conditions, effects, false, false, false);
+ this(triggerItem, trigger, conditions, effects, false, false, false, false);
}
/**
@@ -65,6 +66,7 @@ public final class WiredStack {
* @param useOrMode if true, conditions use OR logic (any pass = success)
* @param useRandom if true, select one random effect instead of all
* @param useUnseen if true, execute effects in "unseen" order (round-robin)
+ * @param executeInOrder if true, execute all regular effects in stable stack order
*/
public WiredStack(HabboItem triggerItem,
IWiredTrigger trigger,
@@ -72,7 +74,8 @@ public final class WiredStack {
List effects,
boolean useOrMode,
boolean useRandom,
- boolean useUnseen) {
+ boolean useUnseen,
+ boolean executeInOrder) {
this.triggerItem = triggerItem;
this.trigger = trigger;
this.conditions = conditions != null ? Collections.unmodifiableList(conditions) : Collections.emptyList();
@@ -80,6 +83,7 @@ public final class WiredStack {
this.useOrMode = useOrMode;
this.useRandom = useRandom;
this.useUnseen = useUnseen;
+ this.executeInOrder = executeInOrder;
}
/**
@@ -157,6 +161,15 @@ public final class WiredStack {
return useUnseen;
}
+ /**
+ * Check if ordered execution mode is enabled (WiredExtraExecuteInOrder).
+ * When true, all regular effects execute in stable stack order.
+ * @return true if ordered execution is enabled
+ */
+ public boolean executeInOrder() {
+ return executeInOrder;
+ }
+
/**
* Get the number of conditions.
* @return condition count
@@ -183,6 +196,7 @@ public final class WiredStack {
", orMode=" + useOrMode +
", random=" + useRandom +
", unseen=" + useUnseen +
+ ", executeInOrder=" + executeInOrder +
'}';
}
}
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/RoomWiredStackIndex.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/RoomWiredStackIndex.java
index 8aa7b883..d67c7b82 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/RoomWiredStackIndex.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/RoomWiredStackIndex.java
@@ -3,6 +3,7 @@ package com.eu.habbo.habbohotel.wired.core;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredCondition;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger;
+import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraExecuteInOrder;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraOrEval;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRandom;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraUnseen;
@@ -176,6 +177,7 @@ public final class RoomWiredStackIndex implements WiredStackIndex {
boolean useOrMode = specialTypes.hasExtraType(x, y, WiredExtraOrEval.class);
boolean useRandom = specialTypes.hasExtraType(x, y, WiredExtraRandom.class);
boolean useUnseen = specialTypes.hasExtraType(x, y, WiredExtraUnseen.class);
+ boolean executeInOrder = specialTypes.hasExtraType(x, y, WiredExtraExecuteInOrder.class);
return new WiredStack(
trigger,
@@ -184,7 +186,8 @@ public final class RoomWiredStackIndex implements WiredStackIndex {
effects,
useOrMode,
useRandom,
- useUnseen
+ useUnseen,
+ executeInOrder
);
}
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 0ceb4035..5d039ba6 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,7 @@ 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.WiredExtraExecutionLimit;
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;
@@ -179,11 +180,11 @@ public final class WiredEngine {
boolean anyTriggered = false;
boolean suppressSaysOutput = false;
- long currentTime = System.currentTimeMillis();
+ long triggerTime = event.getCreatedAtMs();
for (WiredStack stack : stacks) {
try {
- boolean triggered = processStack(stack, event, currentTime);
+ boolean triggered = processStack(stack, event, triggerTime);
if (triggered) {
anyTriggered = true;
@@ -257,6 +258,15 @@ public final class WiredEngine {
debug(room, "No conditions in stack, proceeding to effects");
}
+ WiredExtraExecutionLimit executionLimitExtra = getExecutionLimitExtra(room, stack);
+ if (executionLimitExtra != null && !executionLimitExtra.tryAcquireExecutionSlot(currentTime)) {
+ debug(room, "Execution limit blocked stack {} (max {} in {} ms)",
+ stack.triggerItem() != null ? stack.triggerItem().getId() : "null",
+ executionLimitExtra.getMaxExecutions(),
+ executionLimitExtra.getTimeWindowMs());
+ return false;
+ }
+
// Fire plugin event (WiredStackTriggeredEvent)
if (!fireTriggeredEvent(stack, event)) {
debug(room, "Stack cancelled by plugin");
@@ -427,6 +437,10 @@ public final class WiredEngine {
debug(ctx.room(), "Unseen mode fallback: selected effect {}/{}", index + 1, regulars.size());
}
}
+ } else if (stack.executeInOrder()) {
+ debug(ctx.room(), "Ordered mode: executing effect batches in stack order by delay");
+ executeOrderedEffects(regulars, ctx, currentTime);
+ return;
} else {
// Normal mode: regular effects in random order
toExecute = new ArrayList<>(regulars);
@@ -569,9 +583,11 @@ public final class WiredEngine {
/**
* Schedule a delayed effect execution.
*/
- private void scheduleDelayedEffect(IWiredEffect effect, WiredContext ctx, int delay, long currentTime) {
+ private void scheduleDelayedEffect(IWiredEffect effect, WiredContext ctx, int delay, long triggerTime) {
// Delay is in 500ms ticks
long delayMs = delay * 500L;
+ long elapsedSinceTrigger = Math.max(0L, System.currentTimeMillis() - triggerTime);
+ long remainingDelayMs = Math.max(0L, delayMs - elapsedSinceTrigger);
Room room = ctx.room();
RoomUnit actor = ctx.actor().orElse(null);
@@ -592,7 +608,80 @@ public final class WiredEngine {
} catch (Exception e) {
LOGGER.warn("Error executing delayed effect: {}", e.getMessage());
}
- }, delayMs);
+ }, remainingDelayMs);
+ }
+
+ private void executeOrderedEffects(List effects, WiredContext ctx, long currentTime) {
+ if (effects == null || effects.isEmpty()) {
+ return;
+ }
+
+ Map> effectsByDelay = new LinkedHashMap<>();
+
+ for (IWiredEffect effect : effects) {
+ if (effect == null) {
+ continue;
+ }
+
+ if (effect.requiresActor() && !ctx.hasActor()) {
+ continue;
+ }
+
+ effectsByDelay.computeIfAbsent(effect.getDelay(), key -> new ArrayList<>()).add(effect);
+ }
+
+ for (Map.Entry> entry : effectsByDelay.entrySet()) {
+ int delay = entry.getKey();
+ List batch = entry.getValue();
+
+ if (batch.isEmpty()) {
+ continue;
+ }
+
+ if (delay > 0) {
+ scheduleOrderedEffectBatch(batch, ctx, delay, currentTime);
+ } else {
+ executeOrderedEffectBatch(batch, ctx, currentTime, false);
+ }
+ }
+ }
+
+ private void scheduleOrderedEffectBatch(List batch, WiredContext ctx, int delay, long triggerTime) {
+ long delayMs = delay * 500L;
+ long elapsedSinceTrigger = Math.max(0L, System.currentTimeMillis() - triggerTime);
+ long remainingDelayMs = Math.max(0L, delayMs - elapsedSinceTrigger);
+ Room room = ctx.room();
+
+ Emulator.getThreading().run(() -> {
+ if (!room.isLoaded() || room.getHabbos().isEmpty()) {
+ return;
+ }
+
+ executeOrderedEffectBatch(batch, ctx, System.currentTimeMillis(), true);
+ }, remainingDelayMs);
+ }
+
+ private void executeOrderedEffectBatch(List batch, WiredContext ctx, long executionTime, boolean useExecutionTimeForCooldown) {
+ Room room = ctx.room();
+ RoomUnit actor = ctx.actor().orElse(null);
+
+ for (IWiredEffect effect : batch) {
+ try {
+ if (!useExecutionTimeForCooldown) {
+ ctx.state().step();
+ }
+
+ effect.execute(ctx);
+
+ if (effect instanceof InteractionWiredEffect) {
+ InteractionWiredEffect wiredEffect = (InteractionWiredEffect) effect;
+ wiredEffect.setCooldown(executionTime);
+ wiredEffect.activateBox(room, actor, executionTime);
+ }
+ } catch (Exception e) {
+ LOGGER.warn("Error executing ordered effect batch item: {}", e.getMessage());
+ }
+ }
}
/**
@@ -714,6 +803,12 @@ public final class WiredEngine {
return (extra instanceof WiredExtraUnseen) ? (WiredExtraUnseen) extra : null;
}
+ private WiredExtraExecutionLimit getExecutionLimitExtra(Room room, WiredStack stack) {
+ InteractionWiredExtra extra = getStackExtra(room, stack, WiredExtraExecutionLimit.class);
+
+ return (extra instanceof WiredExtraExecutionLimit) ? (WiredExtraExecutionLimit) extra : null;
+ }
+
private InteractionWiredExtra getStackExtra(Room room, WiredStack stack, Class extraClass) {
if (room == null || stack == null || stack.triggerItem() == null || room.getRoomSpecialTypes() == null) {
return null;
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 239266f0..0e15a5e0 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
@@ -4,8 +4,10 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.catalog.CatalogItem;
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.effects.WiredEffectGiveReward;
import com.eu.habbo.habbohotel.items.interactions.wired.effects.WiredEffectTriggerStacks;
+import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraExecutionLimit;
import com.eu.habbo.habbohotel.items.interactions.wired.triggers.WiredTriggerHabboClicksUser;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomTile;
@@ -725,6 +727,34 @@ public final class WiredManager {
return WiredTickService.getInstance();
}
+ public static boolean isTriggerExecutionAllowed(Room room, HabboItem triggerItem, long timestamp) {
+ WiredExtraExecutionLimit executionLimit = getExecutionLimitExtra(room, triggerItem);
+
+ return executionLimit == null || executionLimit.canExecuteAt(timestamp);
+ }
+
+ public static WiredExtraExecutionLimit getExecutionLimitExtra(Room room, HabboItem triggerItem) {
+ if (room == null || triggerItem == null || room.getRoomSpecialTypes() == null) {
+ return null;
+ }
+
+ THashSet extras = room.getRoomSpecialTypes().getExtras(
+ triggerItem.getX(),
+ triggerItem.getY());
+
+ if (extras == null || extras.isEmpty()) {
+ return null;
+ }
+
+ for (InteractionWiredExtra extra : extras) {
+ if (extra instanceof WiredExtraExecutionLimit) {
+ return (WiredExtraExecutionLimit) extra;
+ }
+ }
+
+ return null;
+ }
+
// ========== Timer Management ==========
/**
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredMoveCarryHelper.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredMoveCarryHelper.java
index 40b86acd..74ab5e11 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredMoveCarryHelper.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredMoveCarryHelper.java
@@ -14,6 +14,7 @@ import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.rooms.RoomUnitType;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
+import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.rooms.WiredMovementsComposer;
import com.eu.habbo.messages.outgoing.rooms.items.FloorItemOnRollerComposer;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer;
@@ -21,16 +22,21 @@ import gnu.trove.set.hash.THashSet;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public final class WiredMoveCarryHelper {
private static final double DIRECT_HEIGHT_TOLERANCE = 0.1D;
private static final int STATUS_SUPPRESSION_GRACE_MS = 250;
+ private static final long USER_FOLLOWER_TTL_MS = 10000L;
private static final ThreadLocal> SUPPRESSED_STATUS_ROOM_UNIT_IDS = new ThreadLocal<>();
+ private static final ThreadLocal> COLLECTED_MOVEMENTS = new ThreadLocal<>();
private static final ConcurrentHashMap SUPPRESSED_STATUS_COMPOSER_UNTIL = new ConcurrentHashMap<>();
+ private static final ConcurrentHashMap> ACTIVE_USER_FOLLOWERS = new ConcurrentHashMap<>();
private WiredMoveCarryHelper() {
}
@@ -60,10 +66,22 @@ public final class WiredMoveCarryHelper {
}
public static FurnitureMovementError moveFurni(Room room, HabboItem stackItem, HabboItem movingItem, RoomTile targetTile, int rotation, Habbo actor, boolean sendUpdates, WiredContext ctx) {
- return moveFurni(room, stackItem, movingItem, targetTile, rotation, null, actor, sendUpdates, ctx);
+ return moveFurni(room, stackItem, movingItem, targetTile, rotation, null, actor, sendUpdates, ctx, null, null, WiredMovementsComposer.FURNI_ANCHOR_NONE, 0);
}
public static FurnitureMovementError moveFurni(Room room, HabboItem stackItem, HabboItem movingItem, RoomTile targetTile, int rotation, Double z, Habbo actor, boolean sendUpdates, WiredContext ctx) {
+ return moveFurni(room, stackItem, movingItem, targetTile, rotation, z, actor, sendUpdates, ctx, null, null, WiredMovementsComposer.FURNI_ANCHOR_NONE, 0);
+ }
+
+ public static FurnitureMovementError moveFurni(Room room, HabboItem stackItem, HabboItem movingItem, RoomTile targetTile, int rotation, Double z, Habbo actor, boolean sendUpdates, WiredContext ctx, Integer animationDurationOverride) {
+ return moveFurni(room, stackItem, movingItem, targetTile, rotation, z, actor, sendUpdates, ctx, animationDurationOverride, null, WiredMovementsComposer.FURNI_ANCHOR_NONE, 0);
+ }
+
+ public static FurnitureMovementError moveFurni(Room room, HabboItem stackItem, HabboItem movingItem, RoomTile targetTile, int rotation, Double z, Habbo actor, boolean sendUpdates, WiredContext ctx, Integer animationDurationOverride, Integer animationElapsedOverride) {
+ return moveFurni(room, stackItem, movingItem, targetTile, rotation, z, actor, sendUpdates, ctx, animationDurationOverride, animationElapsedOverride, WiredMovementsComposer.FURNI_ANCHOR_NONE, 0);
+ }
+
+ public static FurnitureMovementError moveFurni(Room room, HabboItem stackItem, HabboItem movingItem, RoomTile targetTile, int rotation, Double z, Habbo actor, boolean sendUpdates, WiredContext ctx, Integer animationDurationOverride, Integer animationElapsedOverride, int anchorType, int anchorId) {
if (room == null || movingItem == null || targetTile == null) {
return FurnitureMovementError.INVALID_MOVE;
}
@@ -97,7 +115,9 @@ public final class WiredMoveCarryHelper {
}
boolean useWiredMovements = !hasNoAnimationExtra(room, stackItem);
- int animationDuration = getAnimationDuration(room, stackItem, WiredMovementsComposer.DEFAULT_DURATION);
+ int animationDuration = animationDurationOverride != null
+ ? Math.max(50, animationDurationOverride)
+ : getAnimationDuration(room, stackItem, WiredMovementsComposer.DEFAULT_DURATION);
Set previousSuppressedRoomUnitIds = SUPPRESSED_STATUS_ROOM_UNIT_IDS.get();
if (carryContext.active) {
@@ -133,7 +153,7 @@ public final class WiredMoveCarryHelper {
if (!useWiredMovements) {
applyInstantCarryState(room, movingItem, targetTile, rotation, carryContext);
} else if (oldLocation != null) {
- sendAnimatedMove(room, movingItem, oldLocation, oldZ, targetTile, rotation, carryContext, animationDuration);
+ sendAnimatedMove(room, movingItem, oldLocation, oldZ, targetTile, rotation, carryContext, animationDuration, (animationElapsedOverride != null) ? Math.max(0, animationElapsedOverride) : 0, anchorType, anchorId);
}
}
@@ -198,6 +218,165 @@ public final class WiredMoveCarryHelper {
SUPPRESSED_STATUS_COMPOSER_UNTIL.remove(roomUnit.getId());
}
+ public static void beginMovementCollection() {
+ COLLECTED_MOVEMENTS.set(new ArrayList<>());
+ }
+
+ public static ServerMessage finishMovementCollection() {
+ List movements = COLLECTED_MOVEMENTS.get();
+ COLLECTED_MOVEMENTS.remove();
+
+ if (movements == null || movements.isEmpty()) {
+ return null;
+ }
+
+ return new WiredMovementsComposer(movements).compose();
+ }
+
+ public static void registerUserFollower(Room room, HabboItem stackItem, HabboItem movingItem, RoomUnit targetUnit, Double zOverride, WiredContext ctx) {
+ if (room == null || stackItem == null || movingItem == null || targetUnit == null) {
+ return;
+ }
+
+ ACTIVE_USER_FOLLOWERS
+ .computeIfAbsent(targetUnit.getId(), key -> new ConcurrentHashMap<>())
+ .compute(movingItem.getId(), (key, existing) -> {
+ if (existing != null
+ && existing.roomId == room.getId()
+ && existing.stackItemId == stackItem.getId()) {
+ if (existing.zOverride == null && zOverride != null) {
+ existing.zOverride = zOverride;
+ }
+ existing.ctx = ctx;
+ existing.touch();
+ return existing;
+ }
+
+ return new UserFollowEntry(
+ room.getId(),
+ stackItem.getId(),
+ movingItem.getId(),
+ zOverride,
+ ctx);
+ });
+ }
+
+ public static void markUserFollowerProcessed(RoomUnit targetUnit, HabboItem movingItem, long moveStatusTimestamp) {
+ if (targetUnit == null || movingItem == null || moveStatusTimestamp <= 0L) {
+ return;
+ }
+
+ ConcurrentHashMap followers = ACTIVE_USER_FOLLOWERS.get(targetUnit.getId());
+ if (followers == null) {
+ return;
+ }
+
+ UserFollowEntry entry = followers.get(movingItem.getId());
+ if (entry == null) {
+ return;
+ }
+
+ entry.markProcessed(moveStatusTimestamp);
+ }
+
+ public static boolean isUserFollowerProcessed(RoomUnit targetUnit, HabboItem movingItem, long moveStatusTimestamp) {
+ if (targetUnit == null || movingItem == null || moveStatusTimestamp <= 0L) {
+ return false;
+ }
+
+ ConcurrentHashMap followers = ACTIVE_USER_FOLLOWERS.get(targetUnit.getId());
+ if (followers == null) {
+ return false;
+ }
+
+ UserFollowEntry entry = followers.get(movingItem.getId());
+ if (entry == null) {
+ return false;
+ }
+
+ return entry.lastProcessedMoveTimestamp == moveStatusTimestamp;
+ }
+
+ public static void processUserFollowers(Room room, Collection roomUnits) {
+ if (room == null || roomUnits == null || roomUnits.isEmpty()) {
+ return;
+ }
+
+ for (RoomUnit roomUnit : roomUnits) {
+ if (roomUnit == null) {
+ continue;
+ }
+
+ ConcurrentHashMap followers = ACTIVE_USER_FOLLOWERS.get(roomUnit.getId());
+ if (followers == null || followers.isEmpty()) {
+ continue;
+ }
+
+ if (!roomUnit.hasStatus(RoomUnitStatus.MOVE) || roomUnit.getCurrentLocation() == null) {
+ ACTIVE_USER_FOLLOWERS.remove(roomUnit.getId(), followers);
+ continue;
+ }
+
+ long moveStatusTimestamp = roomUnit.getMoveStatusTimestamp();
+ List toRemove = new ArrayList<>();
+
+ if (shouldSettleFollowersForNewStep(followers, moveStatusTimestamp)) {
+ settleUserFollowers(room, followers);
+ }
+
+ List> orderedFollowers = new ArrayList<>(followers.entrySet());
+ orderedFollowers.sort(Comparator
+ .comparingDouble((Map.Entry followerEntry) -> {
+ UserFollowEntry entry = followerEntry.getValue();
+ return (entry != null && entry.zOverride != null) ? entry.zOverride : Double.MAX_VALUE;
+ })
+ .thenComparingInt(Map.Entry::getKey));
+
+ for (Map.Entry followerEntry : orderedFollowers) {
+ UserFollowEntry entry = followerEntry.getValue();
+
+ if (entry == null || entry.roomId != room.getId() || entry.expiresAt < System.currentTimeMillis()) {
+ toRemove.add(followerEntry.getKey());
+ continue;
+ }
+
+ HabboItem stackItem = room.getHabboItem(entry.stackItemId);
+ HabboItem movingItem = room.getHabboItem(entry.movingItemId);
+
+ if (stackItem == null || movingItem == null) {
+ toRemove.add(followerEntry.getKey());
+ continue;
+ }
+
+ if (moveStatusTimestamp <= 0L || moveStatusTimestamp == entry.lastProcessedMoveTimestamp) {
+ continue;
+ }
+
+ int animationElapsed = resolveMoveStepElapsed(roomUnit);
+ int animationDuration = resolveMoveStepDuration(roomUnit);
+ Double targetZ = resolveFollowerStackZ(room, movingItem, roomUnit.getCurrentLocation(), movingItem.getRotation());
+ FurnitureMovementError error = moveFurni(room, stackItem, movingItem, roomUnit.getCurrentLocation(), movingItem.getRotation(), targetZ, null, false, entry.ctx, animationDuration, animationElapsed, WiredMovementsComposer.FURNI_ANCHOR_USER, roomUnit.getId());
+
+ if (error != FurnitureMovementError.NONE && entry.zOverride != null) {
+ error = moveFurni(room, stackItem, movingItem, roomUnit.getCurrentLocation(), movingItem.getRotation(), entry.zOverride, null, false, entry.ctx, animationDuration, animationElapsed, WiredMovementsComposer.FURNI_ANCHOR_USER, roomUnit.getId());
+ }
+
+ if (error == FurnitureMovementError.INVALID_MOVE) {
+ toRemove.add(followerEntry.getKey());
+ continue;
+ }
+
+ entry.markProcessed(moveStatusTimestamp);
+ }
+
+ for (Integer movingItemId : toRemove) {
+ followers.remove(movingItemId);
+ }
+
+ purgeExpiredFollowers(roomUnit.getId(), followers, true);
+ }
+ }
+
public static boolean hasNoAnimationExtra(Room room, HabboItem stackItem) {
return getNoAnimationExtra(room, stackItem) != null;
}
@@ -207,6 +386,141 @@ public final class WiredMoveCarryHelper {
return (extra != null) ? extra.getDurationMs() : fallbackDuration;
}
+ public static int resolveMoveStepElapsed(RoomUnit roomUnit) {
+ if (roomUnit == null) {
+ return 0;
+ }
+
+ long moveStatusTimestamp = roomUnit.getMoveStatusTimestamp();
+ if (moveStatusTimestamp <= 0L) {
+ return 0;
+ }
+
+ return (int) Math.max(0L, Math.min(WiredMovementsComposer.DEFAULT_DURATION, System.currentTimeMillis() - moveStatusTimestamp));
+ }
+
+ public static int resolveMoveStepDuration(RoomUnit roomUnit) {
+ return WiredMovementsComposer.DEFAULT_DURATION;
+ }
+
+ public static Double resolveFollowerStackZ(Room room, HabboItem movingItem, RoomTile targetTile, int rotation) {
+ if (room == null || movingItem == null || targetTile == null || room.getLayout() == null) {
+ return null;
+ }
+
+ double targetZ = room.getStackHeight(targetTile.x, targetTile.y, false, movingItem);
+ THashSet occupiedTiles = room.getLayout().getTilesAt(
+ targetTile,
+ movingItem.getBaseItem().getWidth(),
+ movingItem.getBaseItem().getLength(),
+ rotation);
+
+ if (occupiedTiles == null || occupiedTiles.isEmpty()) {
+ return targetZ;
+ }
+
+ for (RoomTile occupiedTile : occupiedTiles) {
+ if (occupiedTile == null) {
+ continue;
+ }
+
+ targetZ = Math.max(targetZ, room.getStackHeight(occupiedTile.x, occupiedTile.y, false, movingItem));
+ }
+
+ return targetZ;
+ }
+
+ private static Integer resolveRemainingMoveDuration(RoomUnit roomUnit, HabboItem stackItem, Room room) {
+ if (roomUnit == null || stackItem == null || room == null) {
+ return null;
+ }
+
+ long moveStatusTimestamp = roomUnit.getMoveStatusTimestamp();
+ if (moveStatusTimestamp <= 0L) {
+ return null;
+ }
+
+ int configuredDuration = getAnimationDuration(room, stackItem, WiredMovementsComposer.DEFAULT_DURATION);
+ int remainingStepDuration = (int) Math.max(50L, WiredMovementsComposer.DEFAULT_DURATION - Math.max(0L, System.currentTimeMillis() - moveStatusTimestamp));
+ return Math.min(configuredDuration, remainingStepDuration);
+ }
+
+ private static boolean shouldSettleFollowersForNewStep(ConcurrentHashMap followers, long moveStatusTimestamp) {
+ if (followers == null || followers.isEmpty() || moveStatusTimestamp <= 0L) {
+ return false;
+ }
+
+ for (UserFollowEntry entry : followers.values()) {
+ if (entry != null && entry.lastProcessedMoveTimestamp > 0L && entry.lastProcessedMoveTimestamp != moveStatusTimestamp) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static void settleUserFollowers(Room room, ConcurrentHashMap followers) {
+ if (room == null || followers == null || followers.isEmpty()) {
+ return;
+ }
+
+ List> entriesToSettle = new ArrayList<>(followers.entrySet());
+ entriesToSettle.sort(Comparator
+ .comparingDouble((Map.Entry followerEntry) -> {
+ UserFollowEntry entry = followerEntry.getValue();
+ return (entry != null && entry.zOverride != null) ? -entry.zOverride : Double.POSITIVE_INFINITY;
+ })
+ .thenComparingInt(Map.Entry::getKey));
+
+ for (Map.Entry followerEntry : entriesToSettle) {
+ UserFollowEntry entry = followerEntry.getValue();
+
+ if (entry == null || entry.roomId != room.getId()) {
+ continue;
+ }
+
+ HabboItem movingItem = room.getHabboItem(entry.movingItemId);
+ HabboItem stackItem = room.getHabboItem(entry.stackItemId);
+ if (movingItem == null || room.getLayout() == null) {
+ continue;
+ }
+
+ RoomTile currentTile = room.getLayout().getTile(movingItem.getX(), movingItem.getY());
+ if (currentTile == null) {
+ continue;
+ }
+
+ Double targetZ = (double) room.getLayout().getHeightAtSquare(currentTile.x, currentTile.y);
+
+ if (stackItem != null) {
+ FurnitureMovementError error = moveFurni(room, stackItem, movingItem, currentTile, movingItem.getRotation(), targetZ, null, false, entry.ctx, WiredMovementsComposer.DEFAULT_DURATION, 0, WiredMovementsComposer.FURNI_ANCHOR_NONE, 0);
+
+ if (error == FurnitureMovementError.NONE) {
+ continue;
+ }
+ }
+
+ FurnitureMovementError error = room.moveFurniTo(movingItem, currentTile, movingItem.getRotation(), targetZ, null, true, false);
+
+ if (error != FurnitureMovementError.NONE) {
+ room.moveFurniTo(movingItem, currentTile, movingItem.getRotation(), null, true, false);
+ }
+ }
+ }
+
+ private static void purgeExpiredFollowers(int roomUnitId, ConcurrentHashMap followers, boolean removeEmpty) {
+ if (followers == null) {
+ return;
+ }
+
+ long now = System.currentTimeMillis();
+ followers.entrySet().removeIf(entry -> entry.getValue() == null || entry.getValue().expiresAt < now);
+
+ if (removeEmpty && followers.isEmpty()) {
+ ACTIVE_USER_FOLLOWERS.remove(roomUnitId, followers);
+ }
+ }
+
private static boolean hasMovementBehaviorExtra(Room room, HabboItem stackItem) {
THashSet extras = getMovementExtras(room, stackItem);
if (extras == null || extras.isEmpty()) {
@@ -370,7 +684,7 @@ public final class WiredMoveCarryHelper {
return FurnitureMovementError.NONE;
}
- private static void sendAnimatedMove(Room room, HabboItem movingItem, RoomTile oldLocation, double oldZ, RoomTile targetTile, int rotation, CarryContext carryContext, int animationDuration) {
+ private static void sendAnimatedMove(Room room, HabboItem movingItem, RoomTile oldLocation, double oldZ, RoomTile targetTile, int rotation, CarryContext carryContext, int animationDuration, int animationElapsed, int anchorType, int anchorId) {
List carriedMoves = getCarriedUnitMoves(room, movingItem, targetTile, rotation, carryContext);
List movements = new ArrayList<>();
movements.add(WiredMovementsComposer.furniMovement(
@@ -382,7 +696,10 @@ public final class WiredMoveCarryHelper {
oldZ,
movingItem.getZ(),
movingItem.getRotation(),
- animationDuration));
+ animationDuration,
+ animationElapsed,
+ anchorType,
+ anchorId));
for (CarriedUnitMove carriedMove : carriedMoves) {
suppressStatusComposer(carriedMove.roomUnit, animationDuration);
@@ -399,7 +716,13 @@ public final class WiredMoveCarryHelper {
animationDuration));
}
- room.sendComposer(new WiredMovementsComposer(movements).compose());
+ List collectedMovements = COLLECTED_MOVEMENTS.get();
+
+ if (collectedMovements != null) {
+ collectedMovements.addAll(movements);
+ } else {
+ room.sendComposer(new WiredMovementsComposer(movements).compose());
+ }
for (CarriedUnitMove carriedMove : carriedMoves) {
updateCarriedUnitState(carriedMove);
@@ -711,4 +1034,32 @@ public final class WiredMoveCarryHelper {
this.newZ = newZ;
}
}
+
+ private static final class UserFollowEntry {
+ private final int roomId;
+ private final int stackItemId;
+ private final int movingItemId;
+ private Double zOverride;
+ private WiredContext ctx;
+ private long expiresAt;
+ private long lastProcessedMoveTimestamp;
+
+ private UserFollowEntry(int roomId, int stackItemId, int movingItemId, Double zOverride, WiredContext ctx) {
+ this.roomId = roomId;
+ this.stackItemId = stackItemId;
+ this.movingItemId = movingItemId;
+ this.zOverride = zOverride;
+ this.ctx = ctx;
+ this.touch();
+ }
+
+ private void markProcessed(long moveStatusTimestamp) {
+ this.lastProcessedMoveTimestamp = moveStatusTimestamp;
+ this.touch();
+ }
+
+ private void touch() {
+ this.expiresAt = System.currentTimeMillis() + USER_FOLLOWER_TTL_MS;
+ }
+ }
}
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredSourceUtil.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredSourceUtil.java
index b8ec7046..c2094c6b 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredSourceUtil.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredSourceUtil.java
@@ -18,6 +18,7 @@ import java.util.List;
public final class WiredSourceUtil {
public static final int SOURCE_TRIGGER = 0;
+ public static final int SOURCE_CLICKED_USER = 11;
public static final int SOURCE_SELECTED = 100;
public static final int SOURCE_SELECTOR = 200;
public static final int SOURCE_SIGNAL = 201;
@@ -54,6 +55,11 @@ public final class WiredSourceUtil {
switch (sourceType) {
case SOURCE_TRIGGER:
return ctx.actor().map(Collections::singletonList).orElse(Collections.emptyList());
+ case SOURCE_CLICKED_USER:
+ if (ctx.eventType() == WiredEvent.Type.USER_CLICKS_USER) {
+ return ctx.event().getTargetUnit().map(Collections::singletonList).orElse(Collections.emptyList());
+ }
+ return Collections.emptyList();
case SOURCE_SELECTED:
return (selectedUsers != null) ? new ArrayList<>(selectedUsers) : Collections.emptyList();
case SOURCE_SELECTOR:
@@ -71,6 +77,22 @@ public final class WiredSourceUtil {
}
}
+ public static boolean isDefaultUserSource(int value) {
+ switch (value) {
+ case SOURCE_TRIGGER:
+ case SOURCE_CLICKED_USER:
+ case SOURCE_SELECTOR:
+ case SOURCE_SIGNAL:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public static boolean isSelectableUserSource(int value) {
+ return value == SOURCE_SELECTED || isDefaultUserSource(value);
+ }
+
private static WiredTargets getSelectorTargets(WiredContext ctx) {
if (ctx == null) {
return new WiredTargets();
diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/WiredMovementsComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/WiredMovementsComposer.java
index 07c61622..29414eb0 100644
--- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/WiredMovementsComposer.java
+++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/WiredMovementsComposer.java
@@ -13,6 +13,10 @@ public class WiredMovementsComposer extends MessageComposer {
public static final int TYPE_WALL_ITEM_MOVE = 2;
public static final int TYPE_USER_DIRECTION = 3;
+ public static final int FURNI_ANCHOR_NONE = 0;
+ public static final int FURNI_ANCHOR_USER = 1;
+ public static final int FURNI_ANCHOR_FURNI = 2;
+
public static final int USER_MOVEMENT_WALK = 0;
public static final int USER_MOVEMENT_SLIDE = 1;
public static final int DEFAULT_DURATION = 500;
@@ -37,11 +41,19 @@ public class WiredMovementsComposer extends MessageComposer {
}
public static MovementData furniMovement(int id, int fromX, int fromY, int toX, int toY, double fromZ, double toZ) {
- return furniMovement(id, fromX, fromY, toX, toY, fromZ, toZ, 0, DEFAULT_DURATION);
+ return furniMovement(id, fromX, fromY, toX, toY, fromZ, toZ, 0, DEFAULT_DURATION, 0, FURNI_ANCHOR_NONE, 0);
}
public static MovementData furniMovement(int id, int fromX, int fromY, int toX, int toY, double fromZ, double toZ, int rotation, int duration) {
- return new FurniMovementData(id, fromX, fromY, toX, toY, fromZ, toZ, rotation, duration);
+ return furniMovement(id, fromX, fromY, toX, toY, fromZ, toZ, rotation, duration, 0, FURNI_ANCHOR_NONE, 0);
+ }
+
+ public static MovementData furniMovement(int id, int fromX, int fromY, int toX, int toY, double fromZ, double toZ, int rotation, int duration, int elapsed) {
+ return furniMovement(id, fromX, fromY, toX, toY, fromZ, toZ, rotation, duration, elapsed, FURNI_ANCHOR_NONE, 0);
+ }
+
+ public static MovementData furniMovement(int id, int fromX, int fromY, int toX, int toY, double fromZ, double toZ, int rotation, int duration, int elapsed, int anchorType, int anchorId) {
+ return new FurniMovementData(id, fromX, fromY, toX, toY, fromZ, toZ, rotation, duration, elapsed, anchorType, anchorId);
}
public static MovementData userWalkMovement(int id, int fromX, int fromY, int toX, int toY, double fromZ, double toZ, int bodyDirection, int headDirection, int duration) {
@@ -133,8 +145,11 @@ public class WiredMovementsComposer extends MessageComposer {
private final int id;
private final int rotation;
private final int duration;
+ private final int elapsed;
+ private final int anchorType;
+ private final int anchorId;
- private FurniMovementData(int id, int fromX, int fromY, int toX, int toY, double fromZ, double toZ, int rotation, int duration) {
+ private FurniMovementData(int id, int fromX, int fromY, int toX, int toY, double fromZ, double toZ, int rotation, int duration, int elapsed, int anchorType, int anchorId) {
super(TYPE_FURNI_MOVE);
this.id = id;
this.fromX = fromX;
@@ -145,6 +160,9 @@ public class WiredMovementsComposer extends MessageComposer {
this.toZ = toZ;
this.rotation = rotation;
this.duration = duration;
+ this.elapsed = elapsed;
+ this.anchorType = anchorType;
+ this.anchorId = anchorId;
}
@Override
@@ -158,6 +176,9 @@ public class WiredMovementsComposer extends MessageComposer {
response.appendInt(this.id);
response.appendInt(this.rotation);
response.appendInt(this.duration);
+ response.appendInt(this.elapsed);
+ response.appendInt(this.anchorType);
+ response.appendInt(this.anchorId);
}
}