Add wired signal and variable fixes

This commit is contained in:
Lorenzune
2026-04-08 16:18:16 +02:00
parent b10c80ab71
commit 17e316e521
27 changed files with 1098 additions and 237 deletions
@@ -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';
@@ -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");
@@ -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));
@@ -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;
}
}
@@ -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;
}
}
@@ -103,34 +103,30 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
}
LOGGER.debug("[SendSignal] Resolved {} antenna(s), firing signals", resolvedAntennas.size());
List<RoomUnit> forwardedUsers = WiredSourceUtil.resolveUsers(ctx, this.userForward);
List<HabboItem> forwardedFurni = WiredSourceUtil.resolveItems(ctx, this.furniForward, this.forwardItems);
List<RoomUnit> forwardedUsers = WiredSourceUtil.resolveUsersRaw(ctx, this.userForward);
List<HabboItem> 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<RoomUnit> usersToSend = (signalPerUser && !forwardedUsers.isEmpty())
? forwardedUsers
: Collections.singletonList(defaultUser);
Collection<HabboItem> furniToSend = (signalPerFurni && !forwardedFurni.isEmpty())
Collection<HabboItem> 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<HabboItem> 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<Integer> itemIds;
@@ -28,8 +28,8 @@ import java.util.stream.Collectors;
public class WiredEffectTriggerStacks extends InteractionWiredEffect {
public static final WiredEffectType type = WiredEffectType.CALL_STACKS;
private THashSet<HabboItem> items;
private int furniSource = WiredSourceUtil.SOURCE_TRIGGER;
protected THashSet<HabboItem> 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<HabboItem> effectiveItems = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.items);
THashSet<RoomTile> usedTiles = collectTargetTiles(room, ctx);
THashSet<RoomTile> 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<HabboItem> resolveEffectiveItems(WiredContext ctx) {
return WiredSourceUtil.resolveItems(ctx, this.furniSource, this.items);
}
protected THashSet<RoomTile> collectTargetTiles(Room room, WiredContext ctx) {
THashSet<RoomTile> 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<Integer> itemIds;
@@ -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<Integer, String> 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", "");
if (normalized.length() > MAX_MAPPING_LENGTH) {
normalized = normalized.substring(0, MAX_MAPPING_LENGTH);
return value.replace("\r", "");
}
return normalized;
private static void validateMappingsText(String value) throws WiredSaveException {
if (value == null || value.isEmpty()) {
return;
}
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<Integer, String> parseMappings(String value) {
@@ -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) {
@@ -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;
}
@@ -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<HabboItem> sourceFurni = resolveSourceFurni(ctx, room);
if (sourceFurni.isEmpty()) return;
Set<String> 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;
@@ -101,24 +101,46 @@ public class WiredEffectFurniNeighborhood extends InteractionWiredEffect {
}
private List<int[]> 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.
switch (sourceType) {
case SOURCE_USER_TRIGGER: {
if (ctx.tile().isPresent()) {
return Collections.singletonList(new int[]{ ctx.tile().get().x, ctx.tile().get().y });
}
List<int[]> 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 ctx.actor()
.map(actor -> Collections.singletonList(new int[]{ actor.getX(), actor.getY() }))
.orElse(Collections.emptyList());
}
case SOURCE_USER_SIGNAL: {
List<int[]> positions = ctx.targets().users().stream()
.map(user -> new int[]{ user.getX(), user.getY() })
.collect(Collectors.toList());
if (!positions.isEmpty()) {
return positions;
}
switch (sourceType) {
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<int[]> 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<int[]> 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();
@@ -111,24 +111,46 @@ public class WiredEffectUsersNeighborhood extends InteractionWiredEffect {
}
private List<int[]> 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.
switch (sourceType) {
case SOURCE_USER_TRIGGER: {
if (ctx.tile().isPresent()) {
return Collections.singletonList(new int[]{ ctx.tile().get().x, ctx.tile().get().y });
}
List<int[]> 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 ctx.actor()
.map(actor -> Collections.singletonList(new int[]{ actor.getX(), actor.getY() }))
.orElse(Collections.emptyList());
}
case SOURCE_USER_SIGNAL: {
List<int[]> positions = ctx.targets().users().stream()
.map(user -> new int[]{ user.getX(), user.getY() })
.collect(Collectors.toList());
if (!positions.isEmpty()) {
return positions;
}
switch (sourceType) {
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<int[]> 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<int[]> 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();
@@ -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<HabboItem> 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;
@@ -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;
@@ -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 ====================
/**
@@ -348,7 +348,7 @@ public class RoomSpecialTypes {
public static final int MAX_SENDERS_PER_RECEIVER = 5;
public boolean isSignalSenderLimitReached() {
Set<InteractionWiredEffect> existing = this.wiredEffects.get(WiredEffectType.SEND_SIGNAL);
Set<InteractionWiredEffect> 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<InteractionWiredEffect> senders = this.wiredEffects.get(WiredEffectType.SEND_SIGNAL);
Set<InteractionWiredEffect> senders = this.getSignalSenders();
if (senders == null) return 0;
int count = 0;
@@ -383,7 +383,7 @@ public class RoomSpecialTypes {
return 0;
}
Set<InteractionWiredEffect> senders = this.wiredEffects.get(WiredEffectType.SEND_SIGNAL);
Set<InteractionWiredEffect> 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<InteractionWiredTrigger> 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<InteractionWiredEffect> 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<InteractionWiredEffect> getSignalSenders() {
Set<InteractionWiredEffect> senders = new HashSet<>();
Set<InteractionWiredEffect> standardSenders = this.wiredEffects.get(WiredEffectType.SEND_SIGNAL);
if (standardSenders != null) {
senders.addAll(standardSenders);
}
Set<InteractionWiredEffect> 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())
@@ -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;
@@ -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<DefinitionEntry> definitions;
@@ -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;
@@ -158,6 +158,34 @@ public final class RoomWiredStackIndex implements WiredStackIndex {
return stacks.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(stacks);
}
public List<WiredStack> getStacksAtTile(Room room, RoomTile tile) {
if (room == null || tile == null || room.getRoomSpecialTypes() == null) {
return Collections.emptyList();
}
RoomSpecialTypes specialTypes = room.getRoomSpecialTypes();
THashSet<InteractionWiredTrigger> triggers = specialTypes.getTriggers(tile.x, tile.y);
if (triggers == null || triggers.isEmpty()) {
return Collections.emptyList();
}
List<WiredStack> 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.
*/
@@ -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) {
@@ -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<WiredStack> 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,18 +426,13 @@ 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");
boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, negateConditions);
List<IWiredEffect> executableEffects = getExecutableEffectsForCurrentExecution(stack, conditionsPassedForExecution);
boolean hasSpecialOutcome = conditionsPassedForExecution && hasSpecialTriggerOutcome(stack, event);
if (!shouldContinueAfterConditionCheck(stack, room, conditionsPassedForExecution, executableEffects, hasSpecialOutcome)) {
return false;
}
} else {
debug(room, "No conditions in stack, proceeding to effects");
}
WiredExtraExecutionLimit executionLimitExtra = getExecutionLimitExtra(room, stack);
if (executionLimitExtra != null && !executionLimitExtra.tryAcquireExecutionSlot(currentTime)) {
@@ -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<InteractionWiredEffect> executedSelectors = Collections.emptyList();
if (stack.hasEffects()) {
executedSelectors = executeSelectors(stack, ctx);
applySelectionFilterExtras(stack, ctx, executedSelectors);
}
boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, negateConditions);
List<IWiredEffect> 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<InteractionWiredEffect> executedSelectors = Collections.emptyList();
if (stack.hasEffects()) {
executedSelectors = executeSelectors(stack, ctx);
applySelectionFilterExtras(stack, ctx, executedSelectors);
}
boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, negateConditions);
List<IWiredEffect> 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<IWiredEffect> 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<IWiredEffect> getExecutableEffectsForCurrentExecution(WiredStack stack, boolean conditionsPassed) {
List<IWiredEffect> 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<IWiredEffect> 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<IWiredEffect> effects = stack.effects();
private void executeEffects(WiredStack stack, List<IWiredEffect> effects, WiredContext ctx, long currentTime) {
if (effects.isEmpty()) {
return;
}
// Selectors already executed before conditions; only run regular effects here
List<IWiredEffect> regulars = new ArrayList<>();
for (IWiredEffect e : effects) {
if (!e.isSelector()) regulars.add(e);
}
// Determine which (regular) effects to execute
List<IWiredEffect> 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();
@@ -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<Integer> EVENT_HANDLING_DEPTH = new ThreadLocal<>();
private static final ThreadLocal<ArrayDeque<WiredEvent>> DEFERRED_EFFECT_EVENTS = new ThreadLocal<>();
private static final ThreadLocal<ArrayDeque<DeferredEffectEvent>> 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<WiredEvent> deferredEvents = DEFERRED_EFFECT_EVENTS.get();
ArrayDeque<DeferredEffectEvent> 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<WiredEvent> deferredEvents = DEFERRED_EFFECT_EVENTS.get();
ArrayDeque<DeferredEffectEvent> 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<RoomTile> 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<HabboItem> items = room.getItemsAt(tile);
@@ -994,6 +1015,82 @@ public final class WiredManager {
return true;
}
public static boolean executeNegatedStacksAtTiles(THashSet<RoomTile> 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<WiredStack> 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<HabboItem> 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<Integer> 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<WiredStack> 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 ==========
/**
@@ -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<RoomUnit> 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) {
@@ -29,31 +29,10 @@ public final class WiredSourceUtil {
}
public static List<HabboItem> resolveItems(WiredContext ctx, int sourceType, Collection<HabboItem> selectedItems) {
List<HabboItem> resolvedItems;
List<HabboItem> 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<HabboItem> resolveItemsRaw(WiredContext ctx, int sourceType, Collection<HabboItem> selectedItems) {
return resolveItemsInternal(ctx, sourceType, selectedItems, true);
}
public static List<RoomUnit> resolveUsers(WiredContext ctx, int sourceType) {
return resolveUsers(ctx, sourceType, null);
}
public static List<RoomUnit> resolveUsers(WiredContext ctx, int sourceType, Collection<RoomUnit> selectedUsers) {
List<RoomUnit> resolvedUsers;
List<RoomUnit> 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<RoomUnit> resolveUsersRaw(WiredContext ctx, int sourceType) {
return resolveUsersRaw(ctx, sourceType, null);
}
public static List<RoomUnit> resolveUsersRaw(WiredContext ctx, int sourceType, Collection<RoomUnit> 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<HabboItem> resolveItemsInternal(WiredContext ctx, int sourceType, Collection<HabboItem> 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<RoomUnit> resolveUsersInternal(WiredContext ctx, int sourceType, Collection<RoomUnit> 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<HabboItem> 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();
}
}
@@ -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<WiredExtraVariableTextConnector> connectors = getConnectors(room, definitionItemId);
return connectors.isEmpty() ? null : connectors.get(0);
}
public static List<WiredExtraVariableTextConnector> 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<WiredExtraVariableTextConnector> connectors = getConnectors(room, definition);
return connectors.isEmpty() ? null : connectors.get(0);
}
public static List<WiredExtraVariableTextConnector> getConnectors(Room room, InteractionWiredExtra definition) {
if (room == null || definition == null || room.getRoomSpecialTypes() == null) {
return null;
return Collections.emptyList();
}
THashSet<InteractionWiredExtra> extras = room.getRoomSpecialTypes().getExtras(definition.getX(), definition.getY());
if (extras == null || extras.isEmpty()) {
return null;
return Collections.emptyList();
}
List<WiredExtraVariableTextConnector> 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<Integer, String> 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;
}
}