diff --git a/Database Updates/Items_Base/update_all_interaction_types_wired.sql b/Database Updates/Items_Base/update_all_interaction_types_wired.sql index 1d57887f..f8053225 100644 --- a/Database Updates/Items_Base/update_all_interaction_types_wired.sql +++ b/Database Updates/Items_Base/update_all_interaction_types_wired.sql @@ -2,6 +2,8 @@ UPDATE `items_base` SET `interaction_type` = 'wf_cnd_time_more_than' WHERE `publ UPDATE `items_base` SET `interaction_type` = 'wf_cnd_time_less_than' WHERE `public_name` = 'wf_cnd_time_less_than'; UPDATE `items_base` SET `interaction_type` = 'wf_act_give_reward' WHERE `public_name` = 'wf_act_give_reward'; UPDATE `items_base` SET `interaction_type` = 'wf_act_call_stacks' WHERE `public_name` = 'wf_act_call_stacks'; +UPDATE `items_base` SET `interaction_type` = 'wf_act_neg_call_stack' WHERE `public_name` = 'wf_act_neg_call_stack'; +UPDATE `items_base` SET `interaction_type` = 'wf_act_neg_call_stacks' WHERE `public_name` = 'wf_act_neg_call_stacks'; UPDATE `items_base` SET `interaction_type` = 'default' WHERE `public_name` = 'wf_maze'; UPDATE `items_base` SET `interaction_type` = 'wf_act_give_score_tm' WHERE `public_name` = 'wf_act_give_score_tm'; UPDATE `items_base` SET `interaction_type` = 'wf_act_move_to_dir' WHERE `public_name` = 'wf_act_move_to_dir'; @@ -111,6 +113,7 @@ UPDATE `items_base` SET `interaction_type` = 'wf_act_furni_to_furni' WHERE `publ UPDATE `items_base` SET `interaction_type` = 'wf_act_furni_to_user' WHERE `public_name` = 'wf_act_furni_to_user'; UPDATE `items_base` SET `interaction_type` = 'wf_act_rel_mov' WHERE `public_name` = 'wf_act_rel_mov'; UPDATE `items_base` SET `interaction_type` = 'wf_act_send_signal' WHERE `public_name` = 'wf_act_send_signal'; +UPDATE `items_base` SET `interaction_type` = 'wf_act_neg_send_signal' WHERE `public_name` = 'wf_act_neg_send_signal'; UPDATE `items_base` SET `interaction_type` = 'wf_act_set_altitude' WHERE `public_name` = 'wf_act_set_altitude'; UPDATE `items_base` SET `interaction_type` = 'wf_act_unfreeze' WHERE `public_name` = 'wf_act_unfreeze'; UPDATE `items_base` SET `interaction_type` = 'antenna' WHERE `public_name` = 'wf_antenna1'; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/Item.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/Item.java index 4e0a69fe..0ca0afbe 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/Item.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/Item.java @@ -91,7 +91,23 @@ public class Item implements ISerialize { this.allowGift = set.getBoolean("allow_gift"); this.allowInventoryStack = set.getBoolean("allow_inventory_stack"); - this.interactionType = Emulator.getGameEnvironment().getItemManager().getItemInteraction(set.getString("interaction_type").toLowerCase()); + String interactionTypeName = set.getString("interaction_type"); + if (interactionTypeName == null) { + interactionTypeName = "default"; + } + + this.interactionType = Emulator.getGameEnvironment().getItemManager().getItemInteraction(interactionTypeName.toLowerCase()); + + if ((this.interactionType != null) + && "default".equalsIgnoreCase(this.interactionType.getName()) + && (this.fullName != null) + && this.fullName.toLowerCase().startsWith("wf_")) { + ItemInteraction fallbackInteraction = Emulator.getGameEnvironment().getItemManager().getItemInteraction(this.fullName.toLowerCase()); + + if ((fallbackInteraction != null) && !"default".equalsIgnoreCase(fallbackInteraction.getName())) { + this.interactionType = fallbackInteraction; + } + } this.stateCount = set.getShort("interaction_modes_count"); this.effectM = set.getShort("effect_id_male"); 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 2951ca56..0fc8a660 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 @@ -281,6 +281,8 @@ public class ItemManager { this.interactionsList.add(new ItemInteraction("wf_act_move_furni_to", WiredEffectMoveFurniTo.class)); this.interactionsList.add(new ItemInteraction("wf_act_give_reward", WiredEffectGiveReward.class)); this.interactionsList.add(new ItemInteraction("wf_act_call_stacks", WiredEffectTriggerStacks.class)); + this.interactionsList.add(new ItemInteraction("wf_act_neg_call_stack", WiredEffectNegativeTriggerStacks.class)); + this.interactionsList.add(new ItemInteraction("wf_act_neg_call_stacks", WiredEffectNegativeTriggerStacks.class)); this.interactionsList.add(new ItemInteraction("wf_act_kick_user", WiredEffectKickHabbo.class)); this.interactionsList.add(new ItemInteraction("wf_act_mute_triggerer", WiredEffectMuteHabbo.class)); this.interactionsList.add(new ItemInteraction("wf_act_bot_teleport", WiredEffectBotTeleport.class)); @@ -324,6 +326,7 @@ public class ItemManager { this.interactionsList.add(new ItemInteraction("wf_slc_furni_with_var", WiredEffectFurniWithVariable.class)); this.interactionsList.add(new ItemInteraction("wf_slc_users_with_var", WiredEffectUsersWithVariable.class)); this.interactionsList.add(new ItemInteraction("wf_act_send_signal", WiredEffectSendSignal.class)); + this.interactionsList.add(new ItemInteraction("wf_act_neg_send_signal", WiredEffectNegativeSendSignal.class)); this.interactionsList.add(new ItemInteraction("wf_act_give_var", WiredEffectGiveVariable.class)); this.interactionsList.add(new ItemInteraction("wf_act_remove_var", WiredEffectRemoveVariable.class)); this.interactionsList.add(new ItemInteraction("wf_act_change_var_val", WiredEffectChangeVariableValue.class)); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectNegativeSendSignal.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectNegativeSendSignal.java new file mode 100644 index 00000000..14c54913 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectNegativeSendSignal.java @@ -0,0 +1,31 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.effects; + +import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.wired.WiredEffectType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; +import com.eu.habbo.habbohotel.wired.core.WiredManager; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class WiredEffectNegativeSendSignal extends WiredEffectSendSignal { + public static final WiredEffectType type = WiredEffectType.NEG_SEND_SIGNAL; + + public WiredEffectNegativeSendSignal(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredEffectNegativeSendSignal(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { + super(id, userId, item, extradata, limitedStack, limitedSells); + } + + @Override + protected boolean dispatchSignalEvent(WiredEvent event) { + return WiredManager.dispatchEffectTriggeredEvent(event); + } + + @Override + public WiredEffectType getType() { + return type; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectNegativeTriggerStacks.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectNegativeTriggerStacks.java new file mode 100644 index 00000000..ae46653b --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectNegativeTriggerStacks.java @@ -0,0 +1,30 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.effects; + +import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.wired.WiredEffectType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class WiredEffectNegativeTriggerStacks extends WiredEffectTriggerStacks { + public static final WiredEffectType type = WiredEffectType.NEG_CALL_STACKS; + + public WiredEffectNegativeTriggerStacks(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredEffectNegativeTriggerStacks(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { + super(id, userId, item, extradata, limitedStack, limitedSells); + } + + @Override + public void execute(WiredContext ctx) { + super.execute(ctx); + } + + @Override + public WiredEffectType getType() { + return type; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectSendSignal.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectSendSignal.java index f57ebd92..460b6936 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectSendSignal.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectSendSignal.java @@ -103,34 +103,30 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { } LOGGER.debug("[SendSignal] Resolved {} antenna(s), firing signals", resolvedAntennas.size()); - List forwardedUsers = WiredSourceUtil.resolveUsers(ctx, this.userForward); - List forwardedFurni = WiredSourceUtil.resolveItems(ctx, this.furniForward, this.forwardItems); + List forwardedUsers = WiredSourceUtil.resolveUsersRaw(ctx, this.userForward); + List forwardedFurni = WiredSourceUtil.resolveItemsRaw(ctx, this.furniForward, this.forwardItems); RoomUnit defaultUser = forwardedUsers.isEmpty() ? null : forwardedUsers.get(0); - HabboItem defaultFurni = forwardedFurni.isEmpty() ? null : forwardedFurni.get(0); - Collection usersToSend = (signalPerUser && !forwardedUsers.isEmpty()) ? forwardedUsers : Collections.singletonList(defaultUser); - Collection furniToSend = (signalPerFurni && !forwardedFurni.isEmpty()) + Collection furniToSend = !forwardedFurni.isEmpty() ? forwardedFurni - : Collections.singletonList(defaultFurni); + : Collections.singletonList(null); int nextDepth = currentDepth + 1; - boolean isolateBranchContext = (signalPerUser && forwardedUsers.size() > 1) - || (signalPerFurni && forwardedFurni.size() > 1); for (RoomUnit user : usersToSend) { for (HabboItem sourceItem : furniToSend) { for (HabboItem antenna : resolvedAntennas) { - fireSignalAtAntenna(ctx, room, antenna, user, sourceItem, nextDepth, isolateBranchContext); + fireSignalAtAntenna(ctx, room, antenna, user, sourceItem, nextDepth); } } } } - private void fireSignalAtAntenna(WiredContext ctx, Room room, HabboItem antenna, RoomUnit actor, HabboItem sourceItem, int depth, boolean isolateBranchContext) { + private void fireSignalAtAntenna(WiredContext ctx, Room room, HabboItem antenna, RoomUnit actor, HabboItem sourceItem, int depth) { if (antenna == null) return; RoomTile tile = room.getLayout().getTile(antenna.getX(), antenna.getY()); if (tile == null) return; @@ -148,13 +144,13 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { .signalChannel(signalChannel) .signalUserCount(actor != null ? 1 : 0) .signalFurniCount(sourceItem != null ? 1 : 0) - .contextVariableScope(isolateBranchContext ? ctx.contextVariables().copy() : ctx.contextVariables()) + .contextVariableScope(ctx.contextVariables()) .triggeredByEffect(true); if (actor != null) builder.actor(actor); if (sourceItem != null) builder.sourceItem(sourceItem); - boolean result = WiredManager.dispatchEffectTriggeredEvent(builder.build()); + boolean result = dispatchSignalEvent(builder.build()); LOGGER.debug("[SendSignal] handleEvent returned: {}", result); } @@ -452,11 +448,52 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { return false; } + public boolean unlinkAntenna(int antennaItemId) { + if (antennaItemId <= 0) { + return false; + } + + boolean changed = false; + + Iterator iterator = this.items.iterator(); + while (iterator.hasNext()) { + HabboItem item = iterator.next(); + + if (item == null || item.getId() != antennaItemId) { + continue; + } + + iterator.remove(); + changed = true; + } + + if (this.antennaSource == antennaItemId) { + if (!this.items.isEmpty()) { + HabboItem firstItem = this.items.iterator().next(); + this.antennaSource = (firstItem != null) ? firstItem.getId() : ANTENNA_PICKED; + } else { + this.antennaSource = ANTENNA_PICKED; + } + + changed = true; + } + + if (changed) { + this.needsUpdate(true); + } + + return changed; + } + @Override protected long requiredCooldown() { return COOLDOWN_TRIGGER_STACKS; } + protected boolean dispatchSignalEvent(WiredEvent event) { + return WiredManager.dispatchEffectTriggeredEvent(event); + } + static class JsonData { int delay; List itemIds; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTriggerStacks.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTriggerStacks.java index 7c07f009..0adf1de5 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTriggerStacks.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTriggerStacks.java @@ -28,8 +28,8 @@ import java.util.stream.Collectors; public class WiredEffectTriggerStacks extends InteractionWiredEffect { public static final WiredEffectType type = WiredEffectType.CALL_STACKS; - private THashSet items; - private int furniSource = WiredSourceUtil.SOURCE_TRIGGER; + protected THashSet items; + protected int furniSource = WiredSourceUtil.SOURCE_TRIGGER; public WiredEffectTriggerStacks(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -132,7 +132,7 @@ public class WiredEffectTriggerStacks extends InteractionWiredEffect { /** * Maximum recursion depth to prevent infinite loops when trigger stacks call each other. */ - private static final int MAX_STACK_DEPTH = 10; + protected static final int MAX_STACK_DEPTH = 10; @Override public void execute(WiredContext ctx) { @@ -147,30 +147,8 @@ public class WiredEffectTriggerStacks extends InteractionWiredEffect { return; } - List effectiveItems = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.items); + THashSet usedTiles = collectTargetTiles(room, ctx); - THashSet usedTiles = new THashSet<>(); - - for (HabboItem item : effectiveItems) { - if (item == null) continue; - - boolean found = false; - for (RoomTile tile : usedTiles) { - if (tile.x == item.getX() && tile.y == item.getY()) { - found = true; - break; - } - } - - if (!found) { - RoomTile tile = room.getLayout().getTile(item.getX(), item.getY()); - if (tile != null) { - usedTiles.add(tile); - } - } - } - - // Execute effects at tiles with incremented call stack depth WiredManager.executeEffectsAtTiles(usedTiles, roomUnit, room, currentDepth + 1); } @@ -246,6 +224,31 @@ public class WiredEffectTriggerStacks extends InteractionWiredEffect { return COOLDOWN_TRIGGER_STACKS; } + protected List resolveEffectiveItems(WiredContext ctx) { + return WiredSourceUtil.resolveItems(ctx, this.furniSource, this.items); + } + + protected THashSet collectTargetTiles(Room room, WiredContext ctx) { + THashSet usedTiles = new THashSet<>(); + + if (room == null || room.getLayout() == null) { + return usedTiles; + } + + for (HabboItem item : resolveEffectiveItems(ctx)) { + if (item == null) { + continue; + } + + RoomTile tile = room.getLayout().getTile(item.getX(), item.getY()); + if (tile != null) { + usedTiles.add(tile); + } + } + + return usedTiles; + } + static class JsonData { int delay; List itemIds; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraVariableTextConnector.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraVariableTextConnector.java index 388f30a4..96149a5d 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraVariableTextConnector.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraVariableTextConnector.java @@ -10,6 +10,7 @@ import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport; import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.messages.incoming.wired.WiredSaveException; import java.sql.ResultSet; import java.sql.SQLException; @@ -19,7 +20,8 @@ import java.util.Map; public class WiredExtraVariableTextConnector extends InteractionWiredExtra { public static final int CODE = 79; - private static final int MAX_MAPPING_LENGTH = 4096; + public static final int MAX_MAPPING_LENGTH = 1000; + public static final int MAX_MAPPING_LINES = 30; private String mappingsText = ""; private LinkedHashMap mappings = new LinkedHashMap<>(); @@ -38,8 +40,10 @@ public class WiredExtraVariableTextConnector extends InteractionWiredExtra { } @Override - public boolean saveData(WiredSettings settings, GameClient gameClient) { - this.setMappingsText(settings.getStringParam()); + public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException { + String mappingsText = normalizeMappingsText(settings.getStringParam()); + validateMappingsText(mappingsText); + this.setMappingsText(mappingsText); Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()); if (room != null) { @@ -156,13 +160,28 @@ public class WiredExtraVariableTextConnector extends InteractionWiredExtra { return ""; } - String normalized = value.replace("\r", ""); + return value.replace("\r", ""); + } - if (normalized.length() > MAX_MAPPING_LENGTH) { - normalized = normalized.substring(0, MAX_MAPPING_LENGTH); + private static void validateMappingsText(String value) throws WiredSaveException { + if (value == null || value.isEmpty()) { + return; } - return normalized; + if (value.length() > MAX_MAPPING_LENGTH) { + throw new WiredSaveException("Variable text connector can contain at most 1000 characters."); + } + + int lineCount = 1; + for (int i = 0; i < value.length(); i++) { + if (value.charAt(i) == '\n') { + lineCount++; + } + } + + if (lineCount > MAX_MAPPING_LINES) { + throw new WiredSaveException("Variable text connector can contain at most 30 lines."); + } } private static LinkedHashMap parseMappings(String value) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredVariableNameValidator.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredVariableNameValidator.java index 0efcea57..5b3ed4ad 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredVariableNameValidator.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredVariableNameValidator.java @@ -20,21 +20,22 @@ final class WiredVariableNameValidator { return ""; } - return value.trim() + return value .replace("\t", "") .replace("\r", "") - .replace("\n", ""); + .replace("\n", "") + .replaceAll("\\s+", "_"); } static String normalizeLegacy(String value) { String normalized = normalizeForSave(value); if (normalized.contains("=")) { - normalized = normalized.substring(0, normalized.indexOf('=')).trim(); + normalized = normalized.substring(0, normalized.indexOf('=')); } while (normalized.startsWith("@") || normalized.startsWith("~")) { - normalized = normalized.substring(1).trim(); + normalized = normalized.substring(1); } if (normalized.length() > MAX_NAME_LENGTH) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredVariableReferenceSupport.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredVariableReferenceSupport.java index 0d2641e7..c83819b2 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredVariableReferenceSupport.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredVariableReferenceSupport.java @@ -169,11 +169,12 @@ public final class WiredVariableReferenceSupport { } int now = Emulator.getIntUnixTimestamp(); - SharedUserAssignment nextAssignment = (existingAssignment == null) + boolean overwritten = existingAssignment != null && overrideExisting; + SharedUserAssignment nextAssignment = (existingAssignment == null || overwritten) ? new SharedUserAssignment(normalizedValue, now, now) : new SharedUserAssignment(normalizedValue, existingAssignment.getCreatedAt(), Objects.equals(existingAssignment.getValue(), normalizedValue) ? existingAssignment.getUpdatedAt() : now); - if (existingAssignment != null && Objects.equals(existingAssignment.getValue(), normalizedValue)) { + if (!overwritten && existingAssignment != null && Objects.equals(existingAssignment.getValue(), normalizedValue)) { return false; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniByType.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniByType.java index c036239a..02c2630d 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniByType.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniByType.java @@ -11,6 +11,7 @@ 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.WiredSourceUtil; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; @@ -51,7 +52,6 @@ public class WiredEffectFurniByType extends InteractionWiredEffect { boolean includeWiredItems = this.includeWiredTargets(ctx); List sourceFurni = resolveSourceFurni(ctx, room); - if (sourceFurni.isEmpty()) return; Set matchKeys = new LinkedHashSet<>(); for (HabboItem src : sourceFurni) { @@ -85,12 +85,10 @@ public class WiredEffectFurniByType extends InteractionWiredEffect { .collect(Collectors.toList()); } case SOURCE_FURNI_SIGNAL: { - return new ArrayList<>(ctx.targets().items()); + return WiredSourceUtil.resolveItemsRaw(ctx, WiredSourceUtil.SOURCE_SIGNAL, null); } case SOURCE_FURNI_TRIGGER: { - return ctx.sourceItem() - .map(Collections::singletonList) - .orElse(Collections.emptyList()); + return WiredSourceUtil.resolveItemsRaw(ctx, WiredSourceUtil.SOURCE_TRIGGER, null); } default: return Collections.emptyList(); @@ -104,7 +102,7 @@ public class WiredEffectFurniByType extends InteractionWiredEffect { throw new WiredSaveException("wf_slc_furni_bytype: intParams must have at least 4 elements"); } - this.sourceType = SOURCE_FURNI_PICKED; + this.sourceType = normalizeSourceType(params[0]); this.matchState = params.length > 1 && params[1] == 1; this.filterExisting = params.length > 2 && params[2] == 1; this.invert = params.length > 3 && params[3] == 1; @@ -138,7 +136,7 @@ public class WiredEffectFurniByType extends InteractionWiredEffect { message.appendString(""); message.appendInt(4); - message.appendInt(SOURCE_FURNI_PICKED); + message.appendInt(this.sourceType); message.appendInt(matchState ? 1 : 0); message.appendInt(filterExisting ? 1 : 0); message.appendInt(invert ? 1 : 0); @@ -168,7 +166,7 @@ public class WiredEffectFurniByType extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if (wiredData != null && wiredData.startsWith("{")) { JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); - this.sourceType = data.sourceType; + this.sourceType = normalizeSourceType(data.sourceType); this.matchState = data.matchState; this.filterExisting = data.filterExisting; this.invert = data.invert; @@ -190,6 +188,17 @@ public class WiredEffectFurniByType extends InteractionWiredEffect { @Override public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { return false; } + private int normalizeSourceType(int value) { + switch (value) { + case SOURCE_FURNI_SIGNAL: + case SOURCE_FURNI_TRIGGER: + case SOURCE_FURNI_PICKED: + return value; + default: + return SOURCE_FURNI_PICKED; + } + } + static class JsonData { int sourceType; boolean matchState; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniNeighborhood.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniNeighborhood.java index e1974f6a..8ee8a944 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniNeighborhood.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniNeighborhood.java @@ -101,24 +101,46 @@ public class WiredEffectFurniNeighborhood extends InteractionWiredEffect { } private List resolveSourcePositions(WiredContext ctx, Room room) { - - if (isUserGroup(sourceType)) { - // Prefer the event tile for user-based sources because during walk-on/walk-off - // events the user's position (getX/getY) hasn't been updated yet (stale position). - // The event tile correctly represents where the triggering action occurred. - if (ctx.tile().isPresent()) { - return Collections.singletonList(new int[]{ ctx.tile().get().x, ctx.tile().get().y }); - } - List positions = ctx.targets().users().stream() - .map(u -> new int[]{ u.getX(), u.getY() }) - .collect(Collectors.toList()); - if (positions.isEmpty()) { - ctx.actor().ifPresent(a -> positions.add(new int[]{ a.getX(), a.getY() })); - } - return positions; - } - switch (sourceType) { + case SOURCE_USER_TRIGGER: { + if (ctx.tile().isPresent()) { + return Collections.singletonList(new int[]{ ctx.tile().get().x, ctx.tile().get().y }); + } + + return ctx.actor() + .map(actor -> Collections.singletonList(new int[]{ actor.getX(), actor.getY() })) + .orElse(Collections.emptyList()); + } + case SOURCE_USER_SIGNAL: { + List positions = ctx.targets().users().stream() + .map(user -> new int[]{ user.getX(), user.getY() }) + .collect(Collectors.toList()); + + if (!positions.isEmpty()) { + return positions; + } + + return ctx.actor() + .map(actor -> Collections.singletonList(new int[]{ actor.getX(), actor.getY() })) + .orElse(Collections.emptyList()); + } + case SOURCE_USER_CLICKED: { + if (ctx.event().getTargetUnit().isPresent()) { + RoomUnit targetUnit = ctx.event().getTargetUnit().get(); + + return Collections.singletonList(new int[]{ targetUnit.getX(), targetUnit.getY() }); + } + + List positions = ctx.targets().users().stream() + .map(user -> new int[]{ user.getX(), user.getY() }) + .collect(Collectors.toList()); + + if (!positions.isEmpty()) { + return positions; + } + + return Collections.emptyList(); + } case SOURCE_FURNI_TRIGGER: { return ctx.sourceItem() .map(i -> Collections.singletonList(new int[]{ i.getX(), i.getY() })) @@ -132,9 +154,17 @@ public class WiredEffectFurniNeighborhood extends InteractionWiredEffect { .collect(Collectors.toList()); } case SOURCE_FURNI_SIGNAL: { - return ctx.targets().items().stream() + List positions = ctx.targets().items().stream() .map(i -> new int[]{ i.getX(), i.getY() }) .collect(Collectors.toList()); + + if (!positions.isEmpty()) { + return positions; + } + + return ctx.sourceItem() + .map(item -> Collections.singletonList(new int[]{ item.getX(), item.getY() })) + .orElse(Collections.emptyList()); } default: return Collections.emptyList(); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectUsersNeighborhood.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectUsersNeighborhood.java index 9c5dd91c..db19bea6 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectUsersNeighborhood.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectUsersNeighborhood.java @@ -111,24 +111,46 @@ public class WiredEffectUsersNeighborhood extends InteractionWiredEffect { } private List resolveSourcePositions(WiredContext ctx, Room room) { - - if (isUserGroup(sourceType)) { - // Prefer the event tile for user-based sources because during walk-on/walk-off - // events the user's position (getX/getY) hasn't been updated yet (stale position). - // The event tile correctly represents where the triggering action occurred. - if (ctx.tile().isPresent()) { - return Collections.singletonList(new int[]{ ctx.tile().get().x, ctx.tile().get().y }); - } - List positions = ctx.targets().users().stream() - .map(u -> new int[]{ u.getX(), u.getY() }) - .collect(Collectors.toList()); - if (positions.isEmpty()) { - ctx.actor().ifPresent(a -> positions.add(new int[]{ a.getX(), a.getY() })); - } - return positions; - } - switch (sourceType) { + case SOURCE_USER_TRIGGER: { + if (ctx.tile().isPresent()) { + return Collections.singletonList(new int[]{ ctx.tile().get().x, ctx.tile().get().y }); + } + + return ctx.actor() + .map(actor -> Collections.singletonList(new int[]{ actor.getX(), actor.getY() })) + .orElse(Collections.emptyList()); + } + case SOURCE_USER_SIGNAL: { + List positions = ctx.targets().users().stream() + .map(user -> new int[]{ user.getX(), user.getY() }) + .collect(Collectors.toList()); + + if (!positions.isEmpty()) { + return positions; + } + + return ctx.actor() + .map(actor -> Collections.singletonList(new int[]{ actor.getX(), actor.getY() })) + .orElse(Collections.emptyList()); + } + case SOURCE_USER_CLICKED: { + if (ctx.event().getTargetUnit().isPresent()) { + RoomUnit targetUnit = ctx.event().getTargetUnit().get(); + + return Collections.singletonList(new int[]{ targetUnit.getX(), targetUnit.getY() }); + } + + List positions = ctx.targets().users().stream() + .map(user -> new int[]{ user.getX(), user.getY() }) + .collect(Collectors.toList()); + + if (!positions.isEmpty()) { + return positions; + } + + return Collections.emptyList(); + } case SOURCE_FURNI_TRIGGER: { return ctx.sourceItem() .map(i -> Collections.singletonList(new int[]{ i.getX(), i.getY() })) @@ -142,9 +164,17 @@ public class WiredEffectUsersNeighborhood extends InteractionWiredEffect { .collect(Collectors.toList()); } case SOURCE_FURNI_SIGNAL: { - return ctx.targets().items().stream() + List positions = ctx.targets().items().stream() .map(i -> new int[]{ i.getX(), i.getY() }) .collect(Collectors.toList()); + + if (!positions.isEmpty()) { + return positions; + } + + return ctx.sourceItem() + .map(item -> Collections.singletonList(new int[]{ item.getX(), item.getY() })) + .orElse(Collections.emptyList()); } default: return Collections.emptyList(); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerReceiveSignal.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerReceiveSignal.java index 77bf5f29..391293ba 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerReceiveSignal.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerReceiveSignal.java @@ -68,6 +68,52 @@ public class WiredTriggerReceiveSignal extends InteractionWiredTrigger { return channel; } + public boolean unlinkAntenna(int antennaItemId) { + if (antennaItemId <= 0) { + return false; + } + + boolean changed = false; + + if (!this.items.isEmpty()) { + THashSet itemsToRemove = new THashSet<>(); + + for (HabboItem item : this.items) { + if (item == null || item.getId() == antennaItemId) { + itemsToRemove.add(item); + } + } + + if (!itemsToRemove.isEmpty()) { + this.items.removeAll(itemsToRemove); + changed = true; + } + } + + if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) { + int nextChannel = 0; + + if (!this.items.isEmpty()) { + HabboItem firstItem = this.items.iterator().next(); + nextChannel = (firstItem != null) ? firstItem.getId() : 0; + } + + if (this.channel != nextChannel) { + this.channel = nextChannel; + changed = true; + } + } else if (this.channel == antennaItemId) { + this.channel = 0; + changed = true; + } + + if (changed) { + this.needsUpdate(true); + } + + return changed; + } + @Override public boolean isTriggeredByRoomUnit() { return false; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomFurniVariableManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomFurniVariableManager.java index 3f040ceb..0a199b43 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomFurniVariableManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomFurniVariableManager.java @@ -147,12 +147,14 @@ public class RoomFurniVariableManager { return false; } - boolean changed = existingAssignment == null || !Objects.equals(existingAssignment.getValue(), normalizedValue); + boolean overwritten = existingAssignment != null && overrideExisting; + boolean valueChanged = existingAssignment == null || !Objects.equals(existingAssignment.getValue(), normalizedValue); + boolean changed = overwritten || valueChanged; - if (existingAssignment == null) { + if (existingAssignment == null || overwritten) { int now = Emulator.getIntUnixTimestamp(); assignments.put(definitionItemId, new VariableAssignment(normalizedValue, now, now)); - } else if (changed) { + } else if (valueChanged) { existingAssignment.setValue(normalizedValue, Emulator.getIntUnixTimestamp()); } @@ -613,7 +615,13 @@ public class RoomFurniVariableManager { for (InteractionWiredExtra extra : extras) { if (extra instanceof WiredExtraFurniVariable) { - result.add((WiredExtraFurniVariable) extra); + WiredExtraFurniVariable definition = (WiredExtraFurniVariable) extra; + + if (!hasVisibleDefinitionName(definition.getVariableName())) { + continue; + } + + result.add(definition); } } @@ -659,6 +667,11 @@ public class RoomFurniVariableManager { if (extra instanceof WiredExtraFurniVariable) { WiredExtraFurniVariable definition = (WiredExtraFurniVariable) extra; + + if (!hasVisibleDefinitionName(definition.getVariableName())) { + return null; + } + return new WiredVariableDefinitionInfo( definition.getId(), definition.getVariableName(), @@ -670,7 +683,8 @@ public class RoomFurniVariableManager { } if (extra instanceof WiredExtraVariableEcho && ((WiredExtraVariableEcho) extra).isFurniEcho()) { - return ((WiredExtraVariableEcho) extra).createDefinitionInfo(this.room); + WiredVariableDefinitionInfo info = ((WiredExtraVariableEcho) extra).createDefinitionInfo(this.room); + return (info != null && hasVisibleDefinitionName(info.getName())) ? info : null; } return WiredVariableLevelSystemSupport.getDerivedDefinitionInfo(this.room, WiredVariableLevelSystemSupport.TARGET_FURNI, definitionItemId); @@ -703,7 +717,13 @@ public class RoomFurniVariableManager { for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) { if (extra instanceof WiredExtraVariableEcho && ((WiredExtraVariableEcho) extra).isFurniEcho()) { - result.add((WiredExtraVariableEcho) extra); + WiredExtraVariableEcho echo = (WiredExtraVariableEcho) extra; + + if (!hasVisibleDefinitionName(echo.getVariableName())) { + continue; + } + + result.add(echo); } } @@ -711,6 +731,10 @@ public class RoomFurniVariableManager { return result; } + private static boolean hasVisibleDefinitionName(String name) { + return name != null && !name.trim().isEmpty(); + } + private VariableAssignment getRawAssignment(int furniId, int definitionItemId) { if (furniId <= 0 || definitionItemId <= 0) { return null; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomItemManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomItemManager.java index bc330fe1..a40bdbf6 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomItemManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomItemManager.java @@ -804,6 +804,11 @@ public class RoomItemManager { return; } + boolean cleanedSignalAntennaReferences = false; + if (this.isAntennaItem(item)) { + cleanedSignalAntennaReferences = specialTypes.unlinkSignalAntennaReferences(item.getId()); + } + this.room.getFurniVariableManager().removeAssignmentsForFurni(item.getId()); boolean isWiredItem = false; @@ -905,11 +910,20 @@ public class RoomItemManager { } // Invalidate wired cache when wired items are removed - if (isWiredItem) { + if (isWiredItem || cleanedSignalAntennaReferences) { WiredManager.invalidateRoom(this.room); } } + private boolean isAntennaItem(HabboItem item) { + if (item == null || item.getBaseItem() == null || item.getBaseItem().getInteractionType() == null) { + return false; + } + + String interactionType = item.getBaseItem().getInteractionType().getName(); + return interactionType != null && interactionType.equalsIgnoreCase("antenna"); + } + // ==================== ITEM UPDATES ==================== /** diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomSpecialTypes.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomSpecialTypes.java index 9f6e1b5e..dcbdfd11 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomSpecialTypes.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomSpecialTypes.java @@ -348,7 +348,7 @@ public class RoomSpecialTypes { public static final int MAX_SENDERS_PER_RECEIVER = 5; public boolean isSignalSenderLimitReached() { - Set existing = this.wiredEffects.get(WiredEffectType.SEND_SIGNAL); + Set existing = this.getSignalSenders(); return existing != null && existing.size() >= MAX_SIGNAL_SENDERS_PER_ROOM; } @@ -358,7 +358,7 @@ public class RoomSpecialTypes { } public int countSendersTargetingReceiver(int receiverItemId, InteractionWiredEffect excludeSender) { - Set senders = this.wiredEffects.get(WiredEffectType.SEND_SIGNAL); + Set senders = this.getSignalSenders(); if (senders == null) return 0; int count = 0; @@ -383,7 +383,7 @@ public class RoomSpecialTypes { return 0; } - Set senders = this.wiredEffects.get(WiredEffectType.SEND_SIGNAL); + Set senders = this.getSignalSenders(); if (senders == null) { return 0; } @@ -411,6 +411,62 @@ public class RoomSpecialTypes { return countSendersTargetingAnyReceiver(receiverItemIds, null); } + public boolean unlinkSignalAntennaReferences(int antennaItemId) { + if (antennaItemId <= 0) { + return false; + } + + boolean changed = false; + + THashSet receivers = this.getTriggers(WiredTriggerType.RECEIVE_SIGNAL); + for (InteractionWiredTrigger trigger : receivers) { + if (!(trigger instanceof com.eu.habbo.habbohotel.items.interactions.wired.triggers.WiredTriggerReceiveSignal receiver)) { + continue; + } + + if (!receiver.unlinkAntenna(antennaItemId)) { + continue; + } + + changed = true; + Emulator.getThreading().run(receiver); + } + + Set senders = this.getSignalSenders(); + if (senders != null) { + for (InteractionWiredEffect effect : senders) { + if (!(effect instanceof WiredEffectSendSignal sender)) { + continue; + } + + if (!sender.unlinkAntenna(antennaItemId)) { + continue; + } + + changed = true; + Emulator.getThreading().run(sender); + } + } + + return changed; + } + + private Set getSignalSenders() { + Set senders = new HashSet<>(); + + Set standardSenders = this.wiredEffects.get(WiredEffectType.SEND_SIGNAL); + if (standardSenders != null) { + senders.addAll(standardSenders); + } + + Set negativeSenders = this.wiredEffects.get(WiredEffectType.NEG_SEND_SIGNAL); + if (negativeSenders != null) { + senders.addAll(negativeSenders); + } + + return senders.isEmpty() ? null : senders; + } + public void addTrigger(InteractionWiredTrigger trigger) { // Add to type-based index this.wiredTriggers.computeIfAbsent(trigger.getType(), k -> ConcurrentHashMap.newKeySet()) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUserVariableManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUserVariableManager.java index a05df8c6..a38b3a1b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUserVariableManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUserVariableManager.java @@ -158,12 +158,14 @@ public class RoomUserVariableManager { return false; } - boolean changed = existingAssignment == null || !Objects.equals(existingAssignment.getValue(), normalizedValue); + boolean overwritten = existingAssignment != null && overrideExisting; + boolean valueChanged = existingAssignment == null || !Objects.equals(existingAssignment.getValue(), normalizedValue); + boolean changed = overwritten || valueChanged; - if (existingAssignment == null) { + if (existingAssignment == null || overwritten) { int now = Emulator.getIntUnixTimestamp(); assignments.put(definitionItemId, new VariableAssignment(normalizedValue, now, now)); - } else if (changed) { + } else if (valueChanged) { existingAssignment.setValue(normalizedValue, Emulator.getIntUnixTimestamp()); } @@ -686,7 +688,13 @@ public class RoomUserVariableManager { for (InteractionWiredExtra extra : extras) { if (extra instanceof WiredExtraUserVariable) { - result.add((WiredExtraUserVariable) extra); + WiredExtraUserVariable definition = (WiredExtraUserVariable) extra; + + if (!hasVisibleDefinitionName(definition.getVariableName())) { + continue; + } + + result.add(definition); } } @@ -743,6 +751,11 @@ public class RoomUserVariableManager { if (extra instanceof WiredExtraUserVariable) { WiredExtraUserVariable definition = (WiredExtraUserVariable) extra; + + if (!hasVisibleDefinitionName(definition.getVariableName())) { + return null; + } + return new WiredVariableDefinitionInfo( definition.getId(), definition.getVariableName(), @@ -755,11 +768,17 @@ public class RoomUserVariableManager { if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isUserReference()) { WiredExtraVariableReference reference = (WiredExtraVariableReference) extra; + + if (!hasVisibleDefinitionName(reference.getVariableName())) { + return null; + } + return new WiredVariableDefinitionInfo(reference.getId(), reference.getVariableName(), reference.hasValue(), reference.getAvailability(), false, reference.isReadOnly()); } if (extra instanceof WiredExtraVariableEcho && ((WiredExtraVariableEcho) extra).isUserEcho()) { - return ((WiredExtraVariableEcho) extra).createDefinitionInfo(this.room); + WiredVariableDefinitionInfo info = ((WiredExtraVariableEcho) extra).createDefinitionInfo(this.room); + return (info != null && hasVisibleDefinitionName(info.getName())) ? info : null; } return WiredVariableLevelSystemSupport.getDerivedDefinitionInfo(this.room, WiredVariableLevelSystemSupport.TARGET_USER, definitionItemId); @@ -792,7 +811,13 @@ public class RoomUserVariableManager { for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) { if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isUserReference()) { - result.add((WiredExtraVariableReference) extra); + WiredExtraVariableReference reference = (WiredExtraVariableReference) extra; + + if (!hasVisibleDefinitionName(reference.getVariableName())) { + continue; + } + + result.add(reference); } } @@ -809,7 +834,13 @@ public class RoomUserVariableManager { for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) { if (extra instanceof WiredExtraVariableEcho && ((WiredExtraVariableEcho) extra).isUserEcho()) { - result.add((WiredExtraVariableEcho) extra); + WiredExtraVariableEcho echo = (WiredExtraVariableEcho) extra; + + if (!hasVisibleDefinitionName(echo.getVariableName())) { + continue; + } + + result.add(echo); } } @@ -817,6 +848,10 @@ public class RoomUserVariableManager { return result; } + private static boolean hasVisibleDefinitionName(String name) { + return name != null && !name.trim().isEmpty(); + } + private VariableAssignment getRawAssignment(int userId, int definitionItemId) { if (userId <= 0 || definitionItemId <= 0) { return null; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomVariableManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomVariableManager.java index 25b87649..100b2d23 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomVariableManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomVariableManager.java @@ -457,7 +457,13 @@ public class RoomVariableManager { for (InteractionWiredExtra extra : extras) { if (extra instanceof WiredExtraRoomVariable) { - result.add((WiredExtraRoomVariable) extra); + WiredExtraRoomVariable definition = (WiredExtraRoomVariable) extra; + + if (!hasVisibleDefinitionName(definition.getVariableName())) { + continue; + } + + result.add(definition); } } @@ -503,6 +509,11 @@ public class RoomVariableManager { if (extra instanceof WiredExtraRoomVariable) { WiredExtraRoomVariable definition = (WiredExtraRoomVariable) extra; + + if (!hasVisibleDefinitionName(definition.getVariableName())) { + return null; + } + return new WiredVariableDefinitionInfo( definition.getId(), definition.getVariableName(), @@ -515,11 +526,17 @@ public class RoomVariableManager { if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isRoomReference()) { WiredExtraVariableReference reference = (WiredExtraVariableReference) extra; + + if (!hasVisibleDefinitionName(reference.getVariableName())) { + return null; + } + return new WiredVariableDefinitionInfo(reference.getId(), reference.getVariableName(), reference.hasValue(), reference.getAvailability(), false, reference.isReadOnly()); } if (extra instanceof WiredExtraVariableEcho && ((WiredExtraVariableEcho) extra).isRoomEcho()) { - return ((WiredExtraVariableEcho) extra).createDefinitionInfo(this.room); + WiredVariableDefinitionInfo info = ((WiredExtraVariableEcho) extra).createDefinitionInfo(this.room); + return (info != null && hasVisibleDefinitionName(info.getName())) ? info : null; } return WiredVariableLevelSystemSupport.getDerivedDefinitionInfo(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, definitionItemId); @@ -557,7 +574,13 @@ public class RoomVariableManager { for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) { if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isRoomReference()) { - result.add((WiredExtraVariableReference) extra); + WiredExtraVariableReference reference = (WiredExtraVariableReference) extra; + + if (!hasVisibleDefinitionName(reference.getVariableName())) { + continue; + } + + result.add(reference); } } @@ -574,7 +597,13 @@ public class RoomVariableManager { for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) { if (extra instanceof WiredExtraVariableEcho && ((WiredExtraVariableEcho) extra).isRoomEcho()) { - result.add((WiredExtraVariableEcho) extra); + WiredExtraVariableEcho echo = (WiredExtraVariableEcho) extra; + + if (!hasVisibleDefinitionName(echo.getVariableName())) { + continue; + } + + result.add(echo); } } @@ -696,6 +725,10 @@ public class RoomVariableManager { return 0; } + private static boolean hasVisibleDefinitionName(String name) { + return name != null && !name.trim().isEmpty(); + } + public static class Snapshot { private final int roomId; private final List definitions; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredEffectType.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredEffectType.java index 845805a8..e80934c4 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredEffectType.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredEffectType.java @@ -59,7 +59,9 @@ public enum WiredEffectType { REMOVE_VAR(73), CHANGE_VAR_VAL(74), FURNI_WITH_VAR_SELECTOR(75), - USERS_WITH_VAR_SELECTOR(76); + USERS_WITH_VAR_SELECTOR(76), + NEG_CALL_STACKS(86), + NEG_SEND_SIGNAL(87); public final int code; 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 5be6f490..42cbddea 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 @@ -158,6 +158,34 @@ public final class RoomWiredStackIndex implements WiredStackIndex { return stacks.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(stacks); } + public List getStacksAtTile(Room room, RoomTile tile) { + if (room == null || tile == null || room.getRoomSpecialTypes() == null) { + return Collections.emptyList(); + } + + RoomSpecialTypes specialTypes = room.getRoomSpecialTypes(); + THashSet triggers = specialTypes.getTriggers(tile.x, tile.y); + + if (triggers == null || triggers.isEmpty()) { + return Collections.emptyList(); + } + + List stacks = new ArrayList<>(); + + for (InteractionWiredTrigger trigger : WiredExecutionOrderUtil.sort(triggers)) { + if (trigger == null) { + continue; + } + + WiredStack stack = buildStack(room, specialTypes, trigger, tile.x, tile.y); + if (stack != null) { + stacks.add(stack); + } + } + + return stacks.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(stacks); + } + /** * Build a single wired stack for a trigger at a specific location. */ diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredContextVariableSupport.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredContextVariableSupport.java index 1c2a2961..e1e45791 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredContextVariableSupport.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredContextVariableSupport.java @@ -71,7 +71,7 @@ public final class WiredContextVariableSupport { public static WiredVariableDefinitionInfo getDefinitionInfo(Room room, int definitionItemId) { WiredExtraContextVariable definition = getDefinition(room, definitionItemId); - if (definition == null) { + if (definition == null || definition.getVariableName() == null || definition.getVariableName().trim().isEmpty()) { return null; } @@ -85,7 +85,7 @@ public final class WiredContextVariableSupport { } public static boolean hasDefinition(Room room, int definitionItemId) { - return getDefinition(room, definitionItemId) != null; + return getDefinitionInfo(room, definitionItemId) != null; } public static boolean assignVariable(WiredContext ctx, Room room, int definitionItemId, Integer value, boolean overrideExisting) { 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 5a96d607..91f25afe 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 @@ -19,6 +19,7 @@ import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredConditionOperator; +import com.eu.habbo.habbohotel.wired.WiredEffectType; import com.eu.habbo.habbohotel.wired.api.IWiredCondition; import com.eu.habbo.habbohotel.wired.api.IWiredEffect; import com.eu.habbo.habbohotel.wired.api.WiredStack; @@ -159,6 +160,10 @@ public final class WiredEngine { * @return true if any stack was triggered (useful for SAY_SOMETHING to suppress message) */ public boolean handleEvent(WiredEvent event) { + return handleEvent(event, false); + } + + public boolean handleEvent(WiredEvent event, boolean negateConditions) { if (event == null) { return false; } @@ -192,7 +197,7 @@ public final class WiredEngine { roomRecursionDepth.put(roomId, currentDepth + 1); try { - return handleEventInternal(event, room); + return handleEventInternal(event, room, negateConditions); } finally { // Decrement recursion depth int newDepth = roomRecursionDepth.getOrDefault(roomId, 1) - 1; @@ -329,7 +334,7 @@ public final class WiredEngine { /** * Internal event handling after recursion check. */ - private boolean handleEventInternal(WiredEvent event, Room room) { + private boolean handleEventInternal(WiredEvent event, Room room, boolean negateConditions) { // Find candidate stacks for this event type List stacks = index.getStacks(room, event.getType()); @@ -345,7 +350,7 @@ public final class WiredEngine { for (WiredStack stack : stacks) { try { - boolean triggered = processStack(stack, event, triggerTime); + boolean triggered = processStack(stack, event, triggerTime, negateConditions); if (triggered) { anyTriggered = true; @@ -374,6 +379,10 @@ public final class WiredEngine { * Process a single wired stack. */ private boolean processStack(WiredStack stack, WiredEvent event, long currentTime) { + return processStack(stack, event, currentTime, false); + } + + private boolean processStack(WiredStack stack, WiredEvent event, long currentTime, boolean negateConditions) { Room room = event.getRoom(); WiredTextInputCaptureSupport.CaptureResult captureResult = resolveTextInputCapture(stack, event); @@ -417,17 +426,12 @@ public final class WiredEngine { applySelectionFilterExtras(stack, ctx, executedSelectors); } - // Evaluate conditions - if (stack.hasConditions()) { - debug(room, "Evaluating {} conditions...", stack.conditions().size()); - boolean conditionsPassed = evaluateConditions(stack, ctx); - debug(room, "Conditions result: {}", conditionsPassed ? "PASSED" : "FAILED"); - if (!conditionsPassed) { - debug(room, "Conditions failed, aborting stack"); - return false; - } - } else { - debug(room, "No conditions in stack, proceeding to effects"); + boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, negateConditions); + List executableEffects = getExecutableEffectsForCurrentExecution(stack, conditionsPassedForExecution); + boolean hasSpecialOutcome = conditionsPassedForExecution && hasSpecialTriggerOutcome(stack, event); + + if (!shouldContinueAfterConditionCheck(stack, room, conditionsPassedForExecution, executableEffects, hasSpecialOutcome)) { + return false; } WiredExtraExecutionLimit executionLimitExtra = getExecutionLimitExtra(room, stack); @@ -455,7 +459,8 @@ public final class WiredEngine { return false; } - if ((event.getType() == WiredEvent.Type.USER_CLICKS_USER) + if (conditionsPassedForExecution + && (event.getType() == WiredEvent.Type.USER_CLICKS_USER) && (stack.triggerItem() instanceof WiredTriggerHabboClicksUser) && event.getActor().isPresent()) { WiredTriggerHabboClicksUser clickUserTrigger = (WiredTriggerHabboClicksUser) stack.triggerItem(); @@ -477,8 +482,8 @@ public final class WiredEngine { finalizeSelectors(executedSelectors, ctx, currentTime); // Execute effects - if (stack.hasEffects()) { - executeEffects(stack, ctx, currentTime); + if (!executableEffects.isEmpty()) { + executeEffects(stack, executableEffects, ctx, currentTime); } // Fire executed event @@ -494,6 +499,139 @@ public final class WiredEngine { return true; } + public boolean executeDirectStack(WiredStack stack, WiredEvent event, boolean negateConditions) { + if (stack == null || event == null) { + return false; + } + + Room room = event.getRoom(); + if (room == null) { + return false; + } + + if (stack.trigger().requiresActor() && !event.getActor().isPresent()) { + return false; + } + + if (!stackHasExecutableOutcome(stack, event)) { + return false; + } + + long currentTime = System.currentTimeMillis(); + + WiredState state = new WiredState(maxStepsPerStack); + WiredContext ctx = new WiredContext(event, stack.triggerItem(), stack, services, state, null); + WiredRoomDiagnostics diagnostics = getDiagnostics(room.getId()); + + state.step(); + + int stackCost = estimateStackCost(stack, roomRecursionDepth.getOrDefault(room.getId(), 0)); + String monitorSourceLabel = getMonitorSourceLabel(stack.triggerItem(), event); + int monitorSourceId = getMonitorSourceId(stack.triggerItem()); + + debug(room, "Direct stack execution for item {} (conditions: {}, effects: {}, negated: {})", + stack.triggerItem() != null ? stack.triggerItem().getId() : "null", + stack.conditions().size(), + stack.effects().size(), + negateConditions); + + List executedSelectors = Collections.emptyList(); + if (stack.hasEffects()) { + executedSelectors = executeSelectors(stack, ctx); + applySelectionFilterExtras(stack, ctx, executedSelectors); + } + + boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, negateConditions); + List executableEffects = getExecutableEffectsForCurrentExecution(stack, conditionsPassedForExecution); + boolean hasSpecialOutcome = conditionsPassedForExecution && hasSpecialTriggerOutcome(stack, event); + + if (!shouldContinueAfterConditionCheck(stack, room, conditionsPassedForExecution, executableEffects, hasSpecialOutcome)) { + return false; + } + + WiredExtraExecutionLimit executionLimitExtra = getExecutionLimitExtra(room, stack); + if (executionLimitExtra != null && !executionLimitExtra.tryAcquireExecutionSlot(currentTime)) { + debug(room, "Execution limit blocked direct stack {} (max {} in {} ms)", + stack.triggerItem() != null ? stack.triggerItem().getId() : "null", + executionLimitExtra.getMaxExecutions(), + executionLimitExtra.getTimeWindowMs()); + return false; + } + + if (!fireTriggeredEvent(stack, event)) { + debug(room, "Direct stack cancelled by plugin"); + return false; + } + + if (!diagnostics.tryConsumeExecutionBudget( + stackCost, + currentTime, + monitorSourceLabel, + monitorSourceId, + buildStackMonitorReason(stack, event, stackCost))) { + debug(room, "Execution cap blocked direct stack {}", stack.triggerItem() != null ? stack.triggerItem().getId() : "null"); + return false; + } + + RoomUnit actor = event.getActor().orElse(null); + + if (stack.triggerItem() instanceof InteractionWiredTrigger) { + InteractionWiredTrigger trigger = (InteractionWiredTrigger) stack.triggerItem(); + trigger.activateBox(room, actor, currentTime); + } + + activateExtras(room, stack.triggerItem(), actor, currentTime); + finalizeSelectors(executedSelectors, ctx, currentTime); + + if (!executableEffects.isEmpty()) { + executeEffects(stack, executableEffects, ctx, currentTime); + } + + fireExecutedEvent(stack, event); + diagnostics.recordExecution( + state.elapsedMs(), + System.currentTimeMillis(), + monitorSourceLabel, + monitorSourceId, + buildExecutionMonitorReason(stack, state.elapsedMs()) + ); + + return true; + } + + public boolean shouldExecuteDirectStack(WiredStack stack, WiredEvent event, boolean negateConditions) { + if (stack == null || event == null) { + return false; + } + + Room room = event.getRoom(); + if (room == null) { + return false; + } + + if (stack.trigger().requiresActor() && !event.getActor().isPresent()) { + return false; + } + + if (!stack.hasEffects()) { + return false; + } + + WiredState state = new WiredState(maxStepsPerStack); + WiredContext ctx = new WiredContext(event, stack.triggerItem(), stack, services, state, null); + state.step(); + + List executedSelectors = Collections.emptyList(); + if (stack.hasEffects()) { + executedSelectors = executeSelectors(stack, ctx); + applySelectionFilterExtras(stack, ctx, executedSelectors); + } + + boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, negateConditions); + List executableEffects = getExecutableEffectsForCurrentExecution(stack, conditionsPassedForExecution); + return !executableEffects.isEmpty(); + } + private boolean wouldTriggerStack(WiredStack stack, WiredEvent event, long currentTime) { Room room = event.getRoom(); WiredTextInputCaptureSupport.CaptureResult captureResult = resolveTextInputCapture(stack, event); @@ -522,7 +660,14 @@ public final class WiredEngine { applySelectionFilterExtras(stack, ctx, executedSelectors); } - if (stack.hasConditions() && !evaluateConditions(stack, ctx)) { + boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, false); + if (!conditionsPassedForExecution) { + return false; + } + + List executableEffects = getExecutableEffectsForCurrentExecution(stack, true); + boolean hasSpecialOutcome = hasSpecialTriggerOutcome(stack, event); + if (executableEffects.isEmpty() && !hasSpecialOutcome) { return false; } @@ -553,6 +698,67 @@ public final class WiredEngine { return false; } + private boolean hasSpecialTriggerOutcome(WiredStack stack, WiredEvent event) { + if (stack == null) { + return false; + } + + if (stack.triggerItem() instanceof WiredTriggerHabboSaysKeyword) { + return ((WiredTriggerHabboSaysKeyword) stack.triggerItem()).isHideMessage(); + } + + if ((event != null) + && (event.getType() == WiredEvent.Type.USER_CLICKS_USER) + && (stack.triggerItem() instanceof WiredTriggerHabboClicksUser)) { + WiredTriggerHabboClicksUser trigger = (WiredTriggerHabboClicksUser) stack.triggerItem(); + return trigger.isBlockMenuOpen() || trigger.isDoNotRotate(); + } + + return false; + } + + private boolean getConditionOutcomeForExecution(WiredStack stack, WiredContext ctx, boolean negateConditions) { + if (!stack.hasConditions()) { + return !negateConditions; + } + + return shouldConditionsPass(stack, ctx, negateConditions); + } + + private List getExecutableEffectsForCurrentExecution(WiredStack stack, boolean conditionsPassed) { + List executableEffects = new ArrayList<>(); + + for (IWiredEffect effect : stack.effects()) { + if (effect == null || effect.isSelector()) { + continue; + } + + boolean negativeEffect = isNegativeConditionEffect(effect); + + if (conditionsPassed) { + if (!negativeEffect) { + executableEffects.add(effect); + } + continue; + } + + if (stack.hasConditions() && negativeEffect) { + executableEffects.add(effect); + } + } + + return executableEffects; + } + + private boolean isNegativeConditionEffect(IWiredEffect effect) { + if (!(effect instanceof InteractionWiredEffect)) { + return false; + } + + WiredEffectType effectType = ((InteractionWiredEffect) effect).getType(); + return effectType == WiredEffectType.NEG_CALL_STACKS || effectType == WiredEffectType.NEG_SEND_SIGNAL; + } + private WiredTextInputCaptureSupport.CaptureResult resolveTextInputCapture(WiredStack stack, WiredEvent event) { if (stack == null || event == null) { return WiredTextInputCaptureSupport.CaptureResult.noMatch(); @@ -576,6 +782,48 @@ public final class WiredEngine { return evaluateConditionsByMode(conditions, ctx, stack.conditionEvaluationMode(), stack.conditionEvaluationValue()); } + private boolean shouldContinueAfterConditionCheck(WiredStack stack, Room room, boolean conditionsPassedForExecution, List executableEffects, boolean hasSpecialOutcome) { + if (stack.hasConditions()) { + debug(room, "Evaluating {} conditions...", stack.conditions().size()); + + if (!conditionsPassedForExecution && !executableEffects.isEmpty()) { + debug(room, "Conditions failed, executing negative effects"); + return true; + } + + if (!conditionsPassedForExecution) { + debug(room, "Conditions failed, aborting stack"); + return false; + } + + if (hasSpecialOutcome || !executableEffects.isEmpty()) { + return true; + } + + debug(room, "Conditions passed, but no executable effects remain"); + return false; + } + + if (!conditionsPassedForExecution) { + debug(room, "No conditions in stack, negated execution aborted"); + return false; + } + + if (hasSpecialOutcome || !executableEffects.isEmpty()) { + debug(room, "No conditions in stack, proceeding to effects"); + return true; + } + + debug(room, "No conditions in stack, but no executable effects remain"); + return false; + } + + private boolean shouldConditionsPass(WiredStack stack, WiredContext ctx, boolean negateConditions) { + boolean conditionsPassed = evaluateConditions(stack, ctx); + debug(ctx.room(), "Conditions result: {}", conditionsPassed ? "PASSED" : "FAILED"); + return negateConditions ? !conditionsPassed : conditionsPassed; + } + /** * Evaluate conditions according to the configured stack mode. */ @@ -638,65 +886,57 @@ public final class WiredEngine { /** * Execute effects in a stack. */ - private void executeEffects(WiredStack stack, WiredContext ctx, long currentTime) { - List effects = stack.effects(); - + private void executeEffects(WiredStack stack, List effects, WiredContext ctx, long currentTime) { if (effects.isEmpty()) { return; } - // Selectors already executed before conditions; only run regular effects here - List regulars = new ArrayList<>(); - for (IWiredEffect e : effects) { - if (!e.isSelector()) regulars.add(e); - } - // Determine which (regular) effects to execute List toExecute; if (stack.useRandom()) { WiredExtraRandom randomExtra = getRandomExtra(ctx.room(), stack); - if (regulars.isEmpty()) { + if (effects.isEmpty()) { toExecute = new ArrayList<>(); } else if (randomExtra != null) { - toExecute = randomExtra.selectWiredEffects(regulars); + toExecute = randomExtra.selectWiredEffects(effects); 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()); + int randomIndex = new Random().nextInt(effects.size()); + toExecute = Collections.singletonList(effects.get(randomIndex)); + debug(ctx.room(), "Random mode: selected effect {}/{}", randomIndex + 1, effects.size()); } } else if (stack.useUnseen()) { // Unseen mode: execute in stable order with memory - if (regulars.isEmpty()) { + if (effects.isEmpty()) { toExecute = new ArrayList<>(); } else { WiredExtraUnseen unseenExtra = getUnseenExtra(ctx.room(), stack); if (unseenExtra != null) { - toExecute = unseenExtra.selectWiredEffects(regulars); + toExecute = unseenExtra.selectWiredEffects(effects); if (!toExecute.isEmpty()) { - int selectedIndex = regulars.indexOf(toExecute.get(0)); - debug(ctx.room(), "Unseen mode: selected effect {}/{}", selectedIndex + 1, regulars.size()); + int selectedIndex = effects.indexOf(toExecute.get(0)); + debug(ctx.room(), "Unseen mode: selected effect {}/{}", selectedIndex + 1, effects.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()); + int index = getNextUnseenIndex(stack, effects.size()); + toExecute = Collections.singletonList(effects.get(index)); + debug(ctx.room(), "Unseen mode fallback: selected effect {}/{}", index + 1, effects.size()); } } } else if (stack.executeInOrder()) { debug(ctx.room(), "Ordered mode: executing effect batches in stack order by delay"); - executeOrderedEffects(regulars, ctx, currentTime); + executeOrderedEffects(effects, ctx, currentTime); return; } else { // Normal mode: preserve the physical stack order. // This matches the legacy handler behavior and avoids visual/state races // for combinations such as Move/Rotate + Match To Snapshot in the same stack. - toExecute = new ArrayList<>(regulars); + toExecute = new ArrayList<>(effects); } WiredMoveCarryHelper.beginMovementCollection(); 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 5a02b34a..085170d6 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 @@ -18,6 +18,7 @@ import com.eu.habbo.habbohotel.users.HabboBadge; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredGiveRewardItem; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.api.WiredStack; import com.eu.habbo.habbohotel.wired.migrate.WiredEvents; import com.eu.habbo.habbohotel.wired.tick.WiredTickService; import com.eu.habbo.habbohotel.wired.tick.WiredTickable; @@ -40,6 +41,10 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayDeque; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.Set; /** * Manager class for the wired runtime. @@ -95,7 +100,7 @@ public final class WiredManager { /** Whether the engine is initialized */ private static volatile boolean initialized = false; private static final ThreadLocal EVENT_HANDLING_DEPTH = new ThreadLocal<>(); - private static final ThreadLocal> DEFERRED_EFFECT_EVENTS = new ThreadLocal<>(); + private static final ThreadLocal> DEFERRED_EFFECT_EVENTS = new ThreadLocal<>(); private WiredManager() { // Static utility class } @@ -241,6 +246,10 @@ public final class WiredManager { * @return true if any stack was triggered */ public static boolean handleEvent(WiredEvent event) { + return handleEvent(event, false); + } + + public static boolean handleEvent(WiredEvent event, boolean negateConditions) { if (!isEnabled() || engine == null) { return false; } @@ -260,19 +269,19 @@ public final class WiredManager { boolean handled = false; try { - handled = engine.handleEvent(event); + handled = engine.handleEvent(event, negateConditions); if (nextDepth == 1) { - ArrayDeque deferredEvents = DEFERRED_EFFECT_EVENTS.get(); + ArrayDeque deferredEvents = DEFERRED_EFFECT_EVENTS.get(); while (deferredEvents != null && !deferredEvents.isEmpty()) { - WiredEvent deferredEvent = deferredEvents.pollFirst(); + DeferredEffectEvent deferredEvent = deferredEvents.pollFirst(); - if (deferredEvent == null || RoomWiredDisableSupport.isWiredDisabled(deferredEvent.getRoom())) { + if (deferredEvent == null || deferredEvent.event == null || RoomWiredDisableSupport.isWiredDisabled(deferredEvent.event.getRoom())) { continue; } - handled = engine.handleEvent(deferredEvent) || handled; + handled = engine.handleEvent(deferredEvent.event, deferredEvent.negateConditions) || handled; } } @@ -288,6 +297,14 @@ public final class WiredManager { } public static boolean dispatchEffectTriggeredEvent(WiredEvent event) { + return dispatchEffectTriggeredEvent(event, false); + } + + public static boolean dispatchNegatedEffectTriggeredEvent(WiredEvent event) { + return dispatchEffectTriggeredEvent(event, true); + } + + private static boolean dispatchEffectTriggeredEvent(WiredEvent event, boolean negateConditions) { if (!isEnabled() || engine == null || event == null || RoomWiredDisableSupport.isWiredDisabled(event.getRoom())) { return false; } @@ -295,17 +312,17 @@ public final class WiredManager { Integer currentDepth = EVENT_HANDLING_DEPTH.get(); if (currentDepth == null || currentDepth <= 0) { - return handleEvent(event); + return handleEvent(event, negateConditions); } - ArrayDeque deferredEvents = DEFERRED_EFFECT_EVENTS.get(); + ArrayDeque deferredEvents = DEFERRED_EFFECT_EVENTS.get(); if (deferredEvents == null) { deferredEvents = new ArrayDeque<>(); DEFERRED_EFFECT_EVENTS.set(deferredEvents); } - deferredEvents.addLast(event); + deferredEvents.addLast(new DeferredEffectEvent(event, negateConditions)); return true; } @@ -971,6 +988,10 @@ public final class WiredManager { * @return true if any effects were executed */ public static boolean executeEffectsAtTiles(THashSet tiles, final RoomUnit roomUnit, final Room room, final int callStackDepth) { + if (tiles == null || tiles.isEmpty() || room == null || engine == null || stackIndex == null) { + return false; + } + for (RoomTile tile : tiles) { if (room != null) { THashSet items = room.getItemsAt(tile); @@ -994,6 +1015,82 @@ public final class WiredManager { return true; } + public static boolean executeNegatedStacksAtTiles(THashSet tiles, final RoomUnit roomUnit, final Room room, final int callStackDepth) { + if (tiles == null || tiles.isEmpty() || room == null || engine == null || stackIndex == null) { + return false; + } + + boolean handled = false; + WiredEvent event = WiredEvent.builder(WiredEvent.Type.CUSTOM, room) + .actor(roomUnit) + .callStackDepth(callStackDepth) + .build(); + + for (RoomTile tile : tiles) { + List stacks = stackIndex.getStacksAtTile(room, tile); + if (stacks.isEmpty()) { + continue; + } + + for (WiredStack stack : stacks) { + handled = engine.executeDirectStack(stack, event, true) || handled; + } + } + + return handled; + } + + public static boolean executeNegatedTargetStacks(Iterable triggerItems, final RoomUnit roomUnit, final Room room, final int callStackDepth) { + if (triggerItems == null || room == null || engine == null || stackIndex == null || room.getLayout() == null) { + return false; + } + + boolean handled = false; + Set seenTriggerIds = new HashSet<>(); + WiredEvent event = WiredEvent.builder(WiredEvent.Type.CUSTOM, room) + .actor(roomUnit) + .callStackDepth(callStackDepth) + .build(); + + for (HabboItem triggerItem : triggerItems) { + if (triggerItem == null || !seenTriggerIds.add(triggerItem.getId())) { + continue; + } + + RoomTile tile = room.getLayout().getTile(triggerItem.getX(), triggerItem.getY()); + if (tile == null) { + continue; + } + + List stacks = stackIndex.getStacksAtTile(room, tile); + if (stacks.isEmpty()) { + continue; + } + + for (WiredStack stack : stacks) { + HabboItem stackTriggerItem = stack.triggerItem(); + if (stackTriggerItem == null || stackTriggerItem.getId() != triggerItem.getId()) { + continue; + } + + handled = engine.executeDirectStack(stack, event, true) || handled; + break; + } + } + + return handled; + } + + private static final class DeferredEffectEvent { + private final WiredEvent event; + private final boolean negateConditions; + + private DeferredEffectEvent(WiredEvent event, boolean negateConditions) { + this.event = event; + this.negateConditions = negateConditions; + } + } + // ========== Reward System ========== /** 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 813b3f15..6b552b65 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 @@ -981,7 +981,7 @@ public final class WiredMoveCarryHelper { return new ArrayList<>(); } - return WiredSourceUtil.resolveItems(ctx, sourceType, null); + return WiredSourceUtil.resolveItemsRaw(ctx, sourceType, null); } private static Collection resolvePhysicsUsers(Room room, WiredContext ctx, int userSource) { @@ -997,7 +997,7 @@ public final class WiredMoveCarryHelper { return new ArrayList<>(); } - return WiredSourceUtil.resolveUsers(ctx, userSource); + return WiredSourceUtil.resolveUsersRaw(ctx, userSource); } private static WiredExtraMovePhysics getMovementPhysicsExtra(Room room, HabboItem stackItem) { 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 671ec052..00e41a15 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 @@ -29,31 +29,10 @@ public final class WiredSourceUtil { } public static List resolveItems(WiredContext ctx, int sourceType, Collection selectedItems) { - List resolvedItems; + List resolvedItems = resolveItemsInternal(ctx, sourceType, selectedItems, false); - switch (sourceType) { - case SOURCE_TRIGGER: - resolvedItems = ctx.sourceItem().map(Collections::singletonList).orElse(Collections.emptyList()); - break; - case SOURCE_SELECTED: - resolvedItems = (selectedItems != null) ? new ArrayList<>(selectedItems) : Collections.emptyList(); - break; - case SOURCE_SELECTOR: - WiredTargets itemTargets = getSelectorTargets(ctx); - resolvedItems = itemTargets.isItemsModifiedBySelector() - ? new ArrayList<>(itemTargets.items()) - : Collections.emptyList(); - break; - case SOURCE_SIGNAL: - if (ctx.eventType() == WiredEvent.Type.SIGNAL_RECEIVED) { - resolvedItems = ctx.sourceItem().map(Collections::singletonList).orElse(Collections.emptyList()); - break; - } - resolvedItems = Collections.emptyList(); - break; - default: - resolvedItems = ctx.sourceItem().map(Collections::singletonList).orElse(Collections.emptyList()); - break; + if (ctx == null) { + return resolvedItems; } return (sourceType == SOURCE_SELECTOR) @@ -61,43 +40,19 @@ public final class WiredSourceUtil { : WiredSelectionFilterSupport.filterItems(ctx.room(), ctx.triggerItem(), ctx, resolvedItems); } + public static List resolveItemsRaw(WiredContext ctx, int sourceType, Collection selectedItems) { + return resolveItemsInternal(ctx, sourceType, selectedItems, true); + } + public static List resolveUsers(WiredContext ctx, int sourceType) { return resolveUsers(ctx, sourceType, null); } public static List resolveUsers(WiredContext ctx, int sourceType, Collection selectedUsers) { - List resolvedUsers; + List resolvedUsers = resolveUsersInternal(ctx, sourceType, selectedUsers); - switch (sourceType) { - case SOURCE_TRIGGER: - resolvedUsers = ctx.actor().map(Collections::singletonList).orElse(Collections.emptyList()); - break; - case SOURCE_CLICKED_USER: - if (ctx.eventType() == WiredEvent.Type.USER_CLICKS_USER) { - resolvedUsers = ctx.event().getTargetUnit().map(Collections::singletonList).orElse(Collections.emptyList()); - break; - } - resolvedUsers = Collections.emptyList(); - break; - case SOURCE_SELECTED: - resolvedUsers = (selectedUsers != null) ? new ArrayList<>(selectedUsers) : Collections.emptyList(); - break; - case SOURCE_SELECTOR: - WiredTargets userTargets = getSelectorTargets(ctx); - resolvedUsers = userTargets.isUsersModifiedBySelector() - ? new ArrayList<>(userTargets.users()) - : Collections.emptyList(); - break; - case SOURCE_SIGNAL: - if (ctx.eventType() == WiredEvent.Type.SIGNAL_RECEIVED) { - resolvedUsers = ctx.actor().map(Collections::singletonList).orElse(Collections.emptyList()); - break; - } - resolvedUsers = Collections.emptyList(); - break; - default: - resolvedUsers = ctx.actor().map(Collections::singletonList).orElse(Collections.emptyList()); - break; + if (ctx == null) { + return resolvedUsers; } return (sourceType == SOURCE_SELECTOR) @@ -105,6 +60,14 @@ public final class WiredSourceUtil { : WiredSelectionFilterSupport.filterUsers(ctx.room(), ctx.triggerItem(), ctx, resolvedUsers); } + public static List resolveUsersRaw(WiredContext ctx, int sourceType) { + return resolveUsersRaw(ctx, sourceType, null); + } + + public static List resolveUsersRaw(WiredContext ctx, int sourceType, Collection selectedUsers) { + return resolveUsersInternal(ctx, sourceType, selectedUsers); + } + public static boolean isDefaultUserSource(int value) { switch (value) { case SOURCE_TRIGGER: @@ -251,4 +214,75 @@ public final class WiredSourceUtil { private static void applySelectionFilterExtras(Room room, HabboItem triggerItem, WiredContext selectorCtx) { WiredSelectionFilterSupport.applySelectorFilters(room, triggerItem, selectorCtx); } + + private static List resolveItemsInternal(WiredContext ctx, int sourceType, Collection selectedItems, boolean allowTriggerItemFallback) { + if (ctx == null) { + return Collections.emptyList(); + } + + switch (sourceType) { + case SOURCE_TRIGGER: + return resolveTriggerItems(ctx, allowTriggerItemFallback); + case SOURCE_SELECTED: + return (selectedItems != null) ? new ArrayList<>(selectedItems) : Collections.emptyList(); + case SOURCE_SELECTOR: + WiredTargets itemTargets = getSelectorTargets(ctx); + return itemTargets.isItemsModifiedBySelector() + ? new ArrayList<>(itemTargets.items()) + : Collections.emptyList(); + case SOURCE_SIGNAL: + if (ctx.eventType() == WiredEvent.Type.SIGNAL_RECEIVED) { + return ctx.sourceItem().map(Collections::singletonList).orElse(Collections.emptyList()); + } + return Collections.emptyList(); + default: + return resolveTriggerItems(ctx, allowTriggerItemFallback); + } + } + + private static List resolveUsersInternal(WiredContext ctx, int sourceType, Collection selectedUsers) { + if (ctx == null) { + return Collections.emptyList(); + } + + 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: + WiredTargets userTargets = getSelectorTargets(ctx); + return userTargets.isUsersModifiedBySelector() + ? new ArrayList<>(userTargets.users()) + : Collections.emptyList(); + case SOURCE_SIGNAL: + if (ctx.eventType() == WiredEvent.Type.SIGNAL_RECEIVED) { + return ctx.actor().map(Collections::singletonList).orElse(Collections.emptyList()); + } + return Collections.emptyList(); + default: + return ctx.actor().map(Collections::singletonList).orElse(Collections.emptyList()); + } + } + + private static List resolveTriggerItems(WiredContext ctx, boolean allowTriggerItemFallback) { + if (ctx == null) { + return Collections.emptyList(); + } + + if (ctx.sourceItem().isPresent()) { + return Collections.singletonList(ctx.sourceItem().get()); + } + + if (allowTriggerItemFallback && ctx.triggerItem() != null) { + return Collections.singletonList(ctx.triggerItem()); + } + + return Collections.emptyList(); + } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredVariableTextConnectorSupport.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredVariableTextConnectorSupport.java index 50aae6d9..38764d79 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredVariableTextConnectorSupport.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredVariableTextConnectorSupport.java @@ -5,6 +5,11 @@ import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariable import com.eu.habbo.habbohotel.rooms.Room; import gnu.trove.set.hash.THashSet; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + public final class WiredVariableTextConnectorSupport { private WiredVariableTextConnectorSupport() { } @@ -18,31 +23,43 @@ public final class WiredVariableTextConnectorSupport { } public static WiredExtraVariableTextConnector getConnector(Room room, int definitionItemId) { + List connectors = getConnectors(room, definitionItemId); + return connectors.isEmpty() ? null : connectors.get(0); + } + + public static List getConnectors(Room room, int definitionItemId) { if (room == null || room.getRoomSpecialTypes() == null || definitionItemId <= 0) { - return null; + return Collections.emptyList(); } InteractionWiredExtra extra = room.getRoomSpecialTypes().getExtra(definitionItemId); - return getConnector(room, extra); + return getConnectors(room, extra); } public static WiredExtraVariableTextConnector getConnector(Room room, InteractionWiredExtra definition) { + List connectors = getConnectors(room, definition); + return connectors.isEmpty() ? null : connectors.get(0); + } + + public static List getConnectors(Room room, InteractionWiredExtra definition) { if (room == null || definition == null || room.getRoomSpecialTypes() == null) { - return null; + return Collections.emptyList(); } THashSet extras = room.getRoomSpecialTypes().getExtras(definition.getX(), definition.getY()); if (extras == null || extras.isEmpty()) { - return null; + return Collections.emptyList(); } + List connectors = new ArrayList<>(); + for (InteractionWiredExtra extra : WiredExecutionOrderUtil.sort(extras)) { if (extra instanceof WiredExtraVariableTextConnector) { - return (WiredExtraVariableTextConnector) extra; + connectors.add((WiredExtraVariableTextConnector) extra); } } - return null; + return connectors; } public static String toText(Room room, int definitionItemId, Integer value) { @@ -50,12 +67,34 @@ public final class WiredVariableTextConnectorSupport { return ""; } - WiredExtraVariableTextConnector connector = getConnector(room, definitionItemId); - return connector != null ? connector.resolveText(value) : String.valueOf(value); + for (WiredExtraVariableTextConnector connector : getConnectors(room, definitionItemId)) { + Map mappings = connector.getMappings(); + if (mappings.containsKey(value)) { + String mappedValue = mappings.get(value); + return mappedValue != null ? mappedValue : String.valueOf(value); + } + } + + return String.valueOf(value); } public static Integer toValue(Room room, int definitionItemId, String text) { - WiredExtraVariableTextConnector connector = getConnector(room, definitionItemId); - return connector != null ? connector.resolveValue(text) : null; + if (text == null) { + return null; + } + + String normalizedText = text.trim(); + if (normalizedText.isEmpty()) { + return null; + } + + for (WiredExtraVariableTextConnector connector : getConnectors(room, definitionItemId)) { + Integer mappedValue = connector.resolveValue(normalizedText); + if (mappedValue != null) { + return mappedValue; + } + } + + return null; } }