Merge pull request #43 from duckietm/dev

Dev
This commit is contained in:
DuckieTM
2026-03-24 11:50:02 +01:00
committed by GitHub
9 changed files with 470 additions and 76 deletions
@@ -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;
@@ -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<List<Integer>> 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<InteractionWiredEffect> selectEffects(List<InteractionWiredEffect> effects) {
return this.selectRandomEffects(effects, InteractionWiredEffect::getId);
}
public List<IWiredEffect> selectWiredEffects(List<IWiredEffect> 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 <T> List<T> selectRandomEffects(List<T> effects, ToIntFunction<T> idResolver) {
if (effects == null || effects.isEmpty()) {
return Collections.emptyList();
}
List<T> shuffledEffects = new ArrayList<>(effects);
Collections.shuffle(shuffledEffects, Emulator.getRandom());
int desiredAmount = Math.min(this.pickAmount, shuffledEffects.size());
Set<Integer> recentEffectIds = this.getRecentEffectIds();
LinkedHashSet<T> 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<Integer> getRecentEffectIds() {
LinkedHashSet<Integer> ids = new LinkedHashSet<>();
if (this.skipExecutions <= 0) {
return ids;
}
for (List<Integer> executionIds : this.recentExecutionEffectIds) {
ids.addAll(executionIds);
}
return ids;
}
private <T> void recordExecution(Collection<T> selectedEffects, ToIntFunction<T> idResolver) {
if (this.skipExecutions <= 0) {
this.recentExecutionEffectIds.clear();
return;
}
List<Integer> 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;
}
}
}
@@ -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<IWiredEffect> selectWiredEffects(List<IWiredEffect> effects) {
synchronized (this.seenList) {
List<IWiredEffect> 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<IWiredEffect> 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 {
}
}
@@ -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<HabboItem> 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<HabboItem> candidateItems, HabboItem sourceItem) {
if (candidateItems == null || sourceItem == null) {
return false;
@@ -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);
@@ -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<InteractionWiredExtra> 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<InteractionWiredEffect> 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);
}
}
@@ -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<IWiredEffect> 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 <T extends InteractionWiredExtra> InteractionWiredExtra getStackExtra(Room room, WiredStack stack, Class<T> extraClass) {
if (room == null || stack == null || stack.triggerItem() == null || room.getRoomSpecialTypes() == null) {
return null;
}
THashSet<InteractionWiredExtra> 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
@@ -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<String, ScheduledFuture<?>> 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.
@@ -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);
}
}