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_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_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_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` = '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_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'; 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_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_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_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_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` = 'wf_act_unfreeze' WHERE `public_name` = 'wf_act_unfreeze';
UPDATE `items_base` SET `interaction_type` = 'antenna' WHERE `public_name` = 'wf_antenna1'; 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.allowGift = set.getBoolean("allow_gift");
this.allowInventoryStack = set.getBoolean("allow_inventory_stack"); 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.stateCount = set.getShort("interaction_modes_count");
this.effectM = set.getShort("effect_id_male"); 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_move_furni_to", WiredEffectMoveFurniTo.class));
this.interactionsList.add(new ItemInteraction("wf_act_give_reward", WiredEffectGiveReward.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_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_kick_user", WiredEffectKickHabbo.class));
this.interactionsList.add(new ItemInteraction("wf_act_mute_triggerer", WiredEffectMuteHabbo.class)); this.interactionsList.add(new ItemInteraction("wf_act_mute_triggerer", WiredEffectMuteHabbo.class));
this.interactionsList.add(new ItemInteraction("wf_act_bot_teleport", WiredEffectBotTeleport.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_furni_with_var", WiredEffectFurniWithVariable.class));
this.interactionsList.add(new ItemInteraction("wf_slc_users_with_var", WiredEffectUsersWithVariable.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_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_give_var", WiredEffectGiveVariable.class));
this.interactionsList.add(new ItemInteraction("wf_act_remove_var", WiredEffectRemoveVariable.class)); this.interactionsList.add(new ItemInteraction("wf_act_remove_var", WiredEffectRemoveVariable.class));
this.interactionsList.add(new ItemInteraction("wf_act_change_var_val", WiredEffectChangeVariableValue.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()); LOGGER.debug("[SendSignal] Resolved {} antenna(s), firing signals", resolvedAntennas.size());
List<RoomUnit> forwardedUsers = WiredSourceUtil.resolveUsers(ctx, this.userForward); List<RoomUnit> forwardedUsers = WiredSourceUtil.resolveUsersRaw(ctx, this.userForward);
List<HabboItem> forwardedFurni = WiredSourceUtil.resolveItems(ctx, this.furniForward, this.forwardItems); List<HabboItem> forwardedFurni = WiredSourceUtil.resolveItemsRaw(ctx, this.furniForward, this.forwardItems);
RoomUnit defaultUser = forwardedUsers.isEmpty() ? null : forwardedUsers.get(0); RoomUnit defaultUser = forwardedUsers.isEmpty() ? null : forwardedUsers.get(0);
HabboItem defaultFurni = forwardedFurni.isEmpty() ? null : forwardedFurni.get(0);
Collection<RoomUnit> usersToSend = (signalPerUser && !forwardedUsers.isEmpty()) Collection<RoomUnit> usersToSend = (signalPerUser && !forwardedUsers.isEmpty())
? forwardedUsers ? forwardedUsers
: Collections.singletonList(defaultUser); : Collections.singletonList(defaultUser);
Collection<HabboItem> furniToSend = (signalPerFurni && !forwardedFurni.isEmpty()) Collection<HabboItem> furniToSend = !forwardedFurni.isEmpty()
? forwardedFurni ? forwardedFurni
: Collections.singletonList(defaultFurni); : Collections.singletonList(null);
int nextDepth = currentDepth + 1; int nextDepth = currentDepth + 1;
boolean isolateBranchContext = (signalPerUser && forwardedUsers.size() > 1)
|| (signalPerFurni && forwardedFurni.size() > 1);
for (RoomUnit user : usersToSend) { for (RoomUnit user : usersToSend) {
for (HabboItem sourceItem : furniToSend) { for (HabboItem sourceItem : furniToSend) {
for (HabboItem antenna : resolvedAntennas) { 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; if (antenna == null) return;
RoomTile tile = room.getLayout().getTile(antenna.getX(), antenna.getY()); RoomTile tile = room.getLayout().getTile(antenna.getX(), antenna.getY());
if (tile == null) return; if (tile == null) return;
@@ -148,13 +144,13 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
.signalChannel(signalChannel) .signalChannel(signalChannel)
.signalUserCount(actor != null ? 1 : 0) .signalUserCount(actor != null ? 1 : 0)
.signalFurniCount(sourceItem != null ? 1 : 0) .signalFurniCount(sourceItem != null ? 1 : 0)
.contextVariableScope(isolateBranchContext ? ctx.contextVariables().copy() : ctx.contextVariables()) .contextVariableScope(ctx.contextVariables())
.triggeredByEffect(true); .triggeredByEffect(true);
if (actor != null) builder.actor(actor); if (actor != null) builder.actor(actor);
if (sourceItem != null) builder.sourceItem(sourceItem); if (sourceItem != null) builder.sourceItem(sourceItem);
boolean result = WiredManager.dispatchEffectTriggeredEvent(builder.build()); boolean result = dispatchSignalEvent(builder.build());
LOGGER.debug("[SendSignal] handleEvent returned: {}", result); LOGGER.debug("[SendSignal] handleEvent returned: {}", result);
} }
@@ -452,11 +448,52 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
return false; 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 @Override
protected long requiredCooldown() { protected long requiredCooldown() {
return COOLDOWN_TRIGGER_STACKS; return COOLDOWN_TRIGGER_STACKS;
} }
protected boolean dispatchSignalEvent(WiredEvent event) {
return WiredManager.dispatchEffectTriggeredEvent(event);
}
static class JsonData { static class JsonData {
int delay; int delay;
List<Integer> itemIds; List<Integer> itemIds;
@@ -28,8 +28,8 @@ import java.util.stream.Collectors;
public class WiredEffectTriggerStacks extends InteractionWiredEffect { public class WiredEffectTriggerStacks extends InteractionWiredEffect {
public static final WiredEffectType type = WiredEffectType.CALL_STACKS; public static final WiredEffectType type = WiredEffectType.CALL_STACKS;
private THashSet<HabboItem> items; protected THashSet<HabboItem> items;
private int furniSource = WiredSourceUtil.SOURCE_TRIGGER; protected int furniSource = WiredSourceUtil.SOURCE_TRIGGER;
public WiredEffectTriggerStacks(ResultSet set, Item baseItem) throws SQLException { public WiredEffectTriggerStacks(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem); 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. * 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 @Override
public void execute(WiredContext ctx) { public void execute(WiredContext ctx) {
@@ -147,30 +147,8 @@ public class WiredEffectTriggerStacks extends InteractionWiredEffect {
return; 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); WiredManager.executeEffectsAtTiles(usedTiles, roomUnit, room, currentDepth + 1);
} }
@@ -246,6 +224,31 @@ public class WiredEffectTriggerStacks extends InteractionWiredEffect {
return COOLDOWN_TRIGGER_STACKS; 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 { static class JsonData {
int delay; int delay;
List<Integer> itemIds; 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.WiredContextVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
@@ -19,7 +20,8 @@ import java.util.Map;
public class WiredExtraVariableTextConnector extends InteractionWiredExtra { public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
public static final int CODE = 79; 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 String mappingsText = "";
private LinkedHashMap<Integer, String> mappings = new LinkedHashMap<>(); private LinkedHashMap<Integer, String> mappings = new LinkedHashMap<>();
@@ -38,8 +40,10 @@ public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
} }
@Override @Override
public boolean saveData(WiredSettings settings, GameClient gameClient) { public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
this.setMappingsText(settings.getStringParam()); String mappingsText = normalizeMappingsText(settings.getStringParam());
validateMappingsText(mappingsText);
this.setMappingsText(mappingsText);
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()); Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room != null) { if (room != null) {
@@ -156,13 +160,28 @@ public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
return ""; return "";
} }
String normalized = value.replace("\r", ""); return value.replace("\r", "");
}
if (normalized.length() > MAX_MAPPING_LENGTH) { private static void validateMappingsText(String value) throws WiredSaveException {
normalized = normalized.substring(0, MAX_MAPPING_LENGTH); 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<Integer, String> parseMappings(String value) { private static LinkedHashMap<Integer, String> parseMappings(String value) {
@@ -20,21 +20,22 @@ final class WiredVariableNameValidator {
return ""; return "";
} }
return value.trim() return value
.replace("\t", "") .replace("\t", "")
.replace("\r", "") .replace("\r", "")
.replace("\n", ""); .replace("\n", "")
.replaceAll("\\s+", "_");
} }
static String normalizeLegacy(String value) { static String normalizeLegacy(String value) {
String normalized = normalizeForSave(value); String normalized = normalizeForSave(value);
if (normalized.contains("=")) { if (normalized.contains("=")) {
normalized = normalized.substring(0, normalized.indexOf('=')).trim(); normalized = normalized.substring(0, normalized.indexOf('='));
} }
while (normalized.startsWith("@") || normalized.startsWith("~")) { while (normalized.startsWith("@") || normalized.startsWith("~")) {
normalized = normalized.substring(1).trim(); normalized = normalized.substring(1);
} }
if (normalized.length() > MAX_NAME_LENGTH) { if (normalized.length() > MAX_NAME_LENGTH) {
@@ -169,11 +169,12 @@ public final class WiredVariableReferenceSupport {
} }
int now = Emulator.getIntUnixTimestamp(); 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, now, now)
: new SharedUserAssignment(normalizedValue, existingAssignment.getCreatedAt(), Objects.equals(existingAssignment.getValue(), normalizedValue) ? existingAssignment.getUpdatedAt() : 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; 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.WiredEffectType;
import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.habbohotel.wired.core.WiredContext;
import com.eu.habbo.habbohotel.wired.core.WiredManager; 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.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredSaveException; import com.eu.habbo.messages.incoming.wired.WiredSaveException;
@@ -51,7 +52,6 @@ public class WiredEffectFurniByType extends InteractionWiredEffect {
boolean includeWiredItems = this.includeWiredTargets(ctx); boolean includeWiredItems = this.includeWiredTargets(ctx);
List<HabboItem> sourceFurni = resolveSourceFurni(ctx, room); List<HabboItem> sourceFurni = resolveSourceFurni(ctx, room);
if (sourceFurni.isEmpty()) return;
Set<String> matchKeys = new LinkedHashSet<>(); Set<String> matchKeys = new LinkedHashSet<>();
for (HabboItem src : sourceFurni) { for (HabboItem src : sourceFurni) {
@@ -85,12 +85,10 @@ public class WiredEffectFurniByType extends InteractionWiredEffect {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
case SOURCE_FURNI_SIGNAL: { case SOURCE_FURNI_SIGNAL: {
return new ArrayList<>(ctx.targets().items()); return WiredSourceUtil.resolveItemsRaw(ctx, WiredSourceUtil.SOURCE_SIGNAL, null);
} }
case SOURCE_FURNI_TRIGGER: { case SOURCE_FURNI_TRIGGER: {
return ctx.sourceItem() return WiredSourceUtil.resolveItemsRaw(ctx, WiredSourceUtil.SOURCE_TRIGGER, null);
.map(Collections::singletonList)
.orElse(Collections.emptyList());
} }
default: default:
return Collections.emptyList(); 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"); 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.matchState = params.length > 1 && params[1] == 1;
this.filterExisting = params.length > 2 && params[2] == 1; this.filterExisting = params.length > 2 && params[2] == 1;
this.invert = params.length > 3 && params[3] == 1; this.invert = params.length > 3 && params[3] == 1;
@@ -138,7 +136,7 @@ public class WiredEffectFurniByType extends InteractionWiredEffect {
message.appendString(""); message.appendString("");
message.appendInt(4); message.appendInt(4);
message.appendInt(SOURCE_FURNI_PICKED); message.appendInt(this.sourceType);
message.appendInt(matchState ? 1 : 0); message.appendInt(matchState ? 1 : 0);
message.appendInt(filterExisting ? 1 : 0); message.appendInt(filterExisting ? 1 : 0);
message.appendInt(invert ? 1 : 0); message.appendInt(invert ? 1 : 0);
@@ -168,7 +166,7 @@ public class WiredEffectFurniByType extends InteractionWiredEffect {
String wiredData = set.getString("wired_data"); String wiredData = set.getString("wired_data");
if (wiredData != null && wiredData.startsWith("{")) { if (wiredData != null && wiredData.startsWith("{")) {
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
this.sourceType = data.sourceType; this.sourceType = normalizeSourceType(data.sourceType);
this.matchState = data.matchState; this.matchState = data.matchState;
this.filterExisting = data.filterExisting; this.filterExisting = data.filterExisting;
this.invert = data.invert; this.invert = data.invert;
@@ -190,6 +188,17 @@ public class WiredEffectFurniByType extends InteractionWiredEffect {
@Override @Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { return false; } 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 { static class JsonData {
int sourceType; int sourceType;
boolean matchState; boolean matchState;
@@ -101,24 +101,46 @@ public class WiredEffectFurniNeighborhood extends InteractionWiredEffect {
} }
private List<int[]> resolveSourcePositions(WiredContext ctx, Room room) { 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.
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 positions;
}
switch (sourceType) { 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<int[]> 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<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: { case SOURCE_FURNI_TRIGGER: {
return ctx.sourceItem() return ctx.sourceItem()
.map(i -> Collections.singletonList(new int[]{ i.getX(), i.getY() })) .map(i -> Collections.singletonList(new int[]{ i.getX(), i.getY() }))
@@ -132,9 +154,17 @@ public class WiredEffectFurniNeighborhood extends InteractionWiredEffect {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
case SOURCE_FURNI_SIGNAL: { case SOURCE_FURNI_SIGNAL: {
return ctx.targets().items().stream() List<int[]> positions = ctx.targets().items().stream()
.map(i -> new int[]{ i.getX(), i.getY() }) .map(i -> new int[]{ i.getX(), i.getY() })
.collect(Collectors.toList()); .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: default:
return Collections.emptyList(); return Collections.emptyList();
@@ -111,24 +111,46 @@ public class WiredEffectUsersNeighborhood extends InteractionWiredEffect {
} }
private List<int[]> resolveSourcePositions(WiredContext ctx, Room room) { 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.
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 positions;
}
switch (sourceType) { 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<int[]> 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<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: { case SOURCE_FURNI_TRIGGER: {
return ctx.sourceItem() return ctx.sourceItem()
.map(i -> Collections.singletonList(new int[]{ i.getX(), i.getY() })) .map(i -> Collections.singletonList(new int[]{ i.getX(), i.getY() }))
@@ -142,9 +164,17 @@ public class WiredEffectUsersNeighborhood extends InteractionWiredEffect {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
case SOURCE_FURNI_SIGNAL: { case SOURCE_FURNI_SIGNAL: {
return ctx.targets().items().stream() List<int[]> positions = ctx.targets().items().stream()
.map(i -> new int[]{ i.getX(), i.getY() }) .map(i -> new int[]{ i.getX(), i.getY() })
.collect(Collectors.toList()); .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: default:
return Collections.emptyList(); return Collections.emptyList();
@@ -68,6 +68,52 @@ public class WiredTriggerReceiveSignal extends InteractionWiredTrigger {
return channel; 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 @Override
public boolean isTriggeredByRoomUnit() { public boolean isTriggeredByRoomUnit() {
return false; return false;
@@ -147,12 +147,14 @@ public class RoomFurniVariableManager {
return false; 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(); int now = Emulator.getIntUnixTimestamp();
assignments.put(definitionItemId, new VariableAssignment(normalizedValue, now, now)); assignments.put(definitionItemId, new VariableAssignment(normalizedValue, now, now));
} else if (changed) { } else if (valueChanged) {
existingAssignment.setValue(normalizedValue, Emulator.getIntUnixTimestamp()); existingAssignment.setValue(normalizedValue, Emulator.getIntUnixTimestamp());
} }
@@ -613,7 +615,13 @@ public class RoomFurniVariableManager {
for (InteractionWiredExtra extra : extras) { for (InteractionWiredExtra extra : extras) {
if (extra instanceof WiredExtraFurniVariable) { 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) { if (extra instanceof WiredExtraFurniVariable) {
WiredExtraFurniVariable definition = (WiredExtraFurniVariable) extra; WiredExtraFurniVariable definition = (WiredExtraFurniVariable) extra;
if (!hasVisibleDefinitionName(definition.getVariableName())) {
return null;
}
return new WiredVariableDefinitionInfo( return new WiredVariableDefinitionInfo(
definition.getId(), definition.getId(),
definition.getVariableName(), definition.getVariableName(),
@@ -670,7 +683,8 @@ public class RoomFurniVariableManager {
} }
if (extra instanceof WiredExtraVariableEcho && ((WiredExtraVariableEcho) extra).isFurniEcho()) { 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); return WiredVariableLevelSystemSupport.getDerivedDefinitionInfo(this.room, WiredVariableLevelSystemSupport.TARGET_FURNI, definitionItemId);
@@ -703,7 +717,13 @@ public class RoomFurniVariableManager {
for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) { for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) {
if (extra instanceof WiredExtraVariableEcho && ((WiredExtraVariableEcho) extra).isFurniEcho()) { 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; return result;
} }
private static boolean hasVisibleDefinitionName(String name) {
return name != null && !name.trim().isEmpty();
}
private VariableAssignment getRawAssignment(int furniId, int definitionItemId) { private VariableAssignment getRawAssignment(int furniId, int definitionItemId) {
if (furniId <= 0 || definitionItemId <= 0) { if (furniId <= 0 || definitionItemId <= 0) {
return null; return null;
@@ -804,6 +804,11 @@ public class RoomItemManager {
return; return;
} }
boolean cleanedSignalAntennaReferences = false;
if (this.isAntennaItem(item)) {
cleanedSignalAntennaReferences = specialTypes.unlinkSignalAntennaReferences(item.getId());
}
this.room.getFurniVariableManager().removeAssignmentsForFurni(item.getId()); this.room.getFurniVariableManager().removeAssignmentsForFurni(item.getId());
boolean isWiredItem = false; boolean isWiredItem = false;
@@ -905,11 +910,20 @@ public class RoomItemManager {
} }
// Invalidate wired cache when wired items are removed // Invalidate wired cache when wired items are removed
if (isWiredItem) { if (isWiredItem || cleanedSignalAntennaReferences) {
WiredManager.invalidateRoom(this.room); 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 ==================== // ==================== ITEM UPDATES ====================
/** /**
@@ -348,7 +348,7 @@ public class RoomSpecialTypes {
public static final int MAX_SENDERS_PER_RECEIVER = 5; public static final int MAX_SENDERS_PER_RECEIVER = 5;
public boolean isSignalSenderLimitReached() { 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; return existing != null && existing.size() >= MAX_SIGNAL_SENDERS_PER_ROOM;
} }
@@ -358,7 +358,7 @@ public class RoomSpecialTypes {
} }
public int countSendersTargetingReceiver(int receiverItemId, InteractionWiredEffect excludeSender) { 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; if (senders == null) return 0;
int count = 0; int count = 0;
@@ -383,7 +383,7 @@ public class RoomSpecialTypes {
return 0; return 0;
} }
Set<InteractionWiredEffect> senders = this.wiredEffects.get(WiredEffectType.SEND_SIGNAL); Set<InteractionWiredEffect> senders = this.getSignalSenders();
if (senders == null) { if (senders == null) {
return 0; return 0;
} }
@@ -411,6 +411,62 @@ public class RoomSpecialTypes {
return countSendersTargetingAnyReceiver(receiverItemIds, null); 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) { public void addTrigger(InteractionWiredTrigger trigger) {
// Add to type-based index // Add to type-based index
this.wiredTriggers.computeIfAbsent(trigger.getType(), k -> ConcurrentHashMap.newKeySet()) this.wiredTriggers.computeIfAbsent(trigger.getType(), k -> ConcurrentHashMap.newKeySet())
@@ -158,12 +158,14 @@ public class RoomUserVariableManager {
return false; 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(); int now = Emulator.getIntUnixTimestamp();
assignments.put(definitionItemId, new VariableAssignment(normalizedValue, now, now)); assignments.put(definitionItemId, new VariableAssignment(normalizedValue, now, now));
} else if (changed) { } else if (valueChanged) {
existingAssignment.setValue(normalizedValue, Emulator.getIntUnixTimestamp()); existingAssignment.setValue(normalizedValue, Emulator.getIntUnixTimestamp());
} }
@@ -686,7 +688,13 @@ public class RoomUserVariableManager {
for (InteractionWiredExtra extra : extras) { for (InteractionWiredExtra extra : extras) {
if (extra instanceof WiredExtraUserVariable) { 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) { if (extra instanceof WiredExtraUserVariable) {
WiredExtraUserVariable definition = (WiredExtraUserVariable) extra; WiredExtraUserVariable definition = (WiredExtraUserVariable) extra;
if (!hasVisibleDefinitionName(definition.getVariableName())) {
return null;
}
return new WiredVariableDefinitionInfo( return new WiredVariableDefinitionInfo(
definition.getId(), definition.getId(),
definition.getVariableName(), definition.getVariableName(),
@@ -755,11 +768,17 @@ public class RoomUserVariableManager {
if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isUserReference()) { if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isUserReference()) {
WiredExtraVariableReference reference = (WiredExtraVariableReference) extra; WiredExtraVariableReference reference = (WiredExtraVariableReference) extra;
if (!hasVisibleDefinitionName(reference.getVariableName())) {
return null;
}
return new WiredVariableDefinitionInfo(reference.getId(), reference.getVariableName(), reference.hasValue(), reference.getAvailability(), false, reference.isReadOnly()); return new WiredVariableDefinitionInfo(reference.getId(), reference.getVariableName(), reference.hasValue(), reference.getAvailability(), false, reference.isReadOnly());
} }
if (extra instanceof WiredExtraVariableEcho && ((WiredExtraVariableEcho) extra).isUserEcho()) { 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); return WiredVariableLevelSystemSupport.getDerivedDefinitionInfo(this.room, WiredVariableLevelSystemSupport.TARGET_USER, definitionItemId);
@@ -792,7 +811,13 @@ public class RoomUserVariableManager {
for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) { for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) {
if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isUserReference()) { 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()) { for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) {
if (extra instanceof WiredExtraVariableEcho && ((WiredExtraVariableEcho) extra).isUserEcho()) { 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; return result;
} }
private static boolean hasVisibleDefinitionName(String name) {
return name != null && !name.trim().isEmpty();
}
private VariableAssignment getRawAssignment(int userId, int definitionItemId) { private VariableAssignment getRawAssignment(int userId, int definitionItemId) {
if (userId <= 0 || definitionItemId <= 0) { if (userId <= 0 || definitionItemId <= 0) {
return null; return null;
@@ -457,7 +457,13 @@ public class RoomVariableManager {
for (InteractionWiredExtra extra : extras) { for (InteractionWiredExtra extra : extras) {
if (extra instanceof WiredExtraRoomVariable) { 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) { if (extra instanceof WiredExtraRoomVariable) {
WiredExtraRoomVariable definition = (WiredExtraRoomVariable) extra; WiredExtraRoomVariable definition = (WiredExtraRoomVariable) extra;
if (!hasVisibleDefinitionName(definition.getVariableName())) {
return null;
}
return new WiredVariableDefinitionInfo( return new WiredVariableDefinitionInfo(
definition.getId(), definition.getId(),
definition.getVariableName(), definition.getVariableName(),
@@ -515,11 +526,17 @@ public class RoomVariableManager {
if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isRoomReference()) { if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isRoomReference()) {
WiredExtraVariableReference reference = (WiredExtraVariableReference) extra; WiredExtraVariableReference reference = (WiredExtraVariableReference) extra;
if (!hasVisibleDefinitionName(reference.getVariableName())) {
return null;
}
return new WiredVariableDefinitionInfo(reference.getId(), reference.getVariableName(), reference.hasValue(), reference.getAvailability(), false, reference.isReadOnly()); return new WiredVariableDefinitionInfo(reference.getId(), reference.getVariableName(), reference.hasValue(), reference.getAvailability(), false, reference.isReadOnly());
} }
if (extra instanceof WiredExtraVariableEcho && ((WiredExtraVariableEcho) extra).isRoomEcho()) { 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); return WiredVariableLevelSystemSupport.getDerivedDefinitionInfo(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, definitionItemId);
@@ -557,7 +574,13 @@ public class RoomVariableManager {
for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) { for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) {
if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isRoomReference()) { 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()) { for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) {
if (extra instanceof WiredExtraVariableEcho && ((WiredExtraVariableEcho) extra).isRoomEcho()) { 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; return 0;
} }
private static boolean hasVisibleDefinitionName(String name) {
return name != null && !name.trim().isEmpty();
}
public static class Snapshot { public static class Snapshot {
private final int roomId; private final int roomId;
private final List<DefinitionEntry> definitions; private final List<DefinitionEntry> definitions;
@@ -59,7 +59,9 @@ public enum WiredEffectType {
REMOVE_VAR(73), REMOVE_VAR(73),
CHANGE_VAR_VAL(74), CHANGE_VAR_VAL(74),
FURNI_WITH_VAR_SELECTOR(75), 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; public final int code;
@@ -158,6 +158,34 @@ public final class RoomWiredStackIndex implements WiredStackIndex {
return stacks.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(stacks); 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. * 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) { public static WiredVariableDefinitionInfo getDefinitionInfo(Room room, int definitionItemId) {
WiredExtraContextVariable definition = getDefinition(room, definitionItemId); WiredExtraContextVariable definition = getDefinition(room, definitionItemId);
if (definition == null) { if (definition == null || definition.getVariableName() == null || definition.getVariableName().trim().isEmpty()) {
return null; return null;
} }
@@ -85,7 +85,7 @@ public final class WiredContextVariableSupport {
} }
public static boolean hasDefinition(Room room, int definitionItemId) { 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) { 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.rooms.RoomUnit;
import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.wired.WiredConditionOperator; 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.IWiredCondition;
import com.eu.habbo.habbohotel.wired.api.IWiredEffect; import com.eu.habbo.habbohotel.wired.api.IWiredEffect;
import com.eu.habbo.habbohotel.wired.api.WiredStack; 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) * @return true if any stack was triggered (useful for SAY_SOMETHING to suppress message)
*/ */
public boolean handleEvent(WiredEvent event) { public boolean handleEvent(WiredEvent event) {
return handleEvent(event, false);
}
public boolean handleEvent(WiredEvent event, boolean negateConditions) {
if (event == null) { if (event == null) {
return false; return false;
} }
@@ -192,7 +197,7 @@ public final class WiredEngine {
roomRecursionDepth.put(roomId, currentDepth + 1); roomRecursionDepth.put(roomId, currentDepth + 1);
try { try {
return handleEventInternal(event, room); return handleEventInternal(event, room, negateConditions);
} finally { } finally {
// Decrement recursion depth // Decrement recursion depth
int newDepth = roomRecursionDepth.getOrDefault(roomId, 1) - 1; int newDepth = roomRecursionDepth.getOrDefault(roomId, 1) - 1;
@@ -329,7 +334,7 @@ public final class WiredEngine {
/** /**
* Internal event handling after recursion check. * 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 // Find candidate stacks for this event type
List<WiredStack> stacks = index.getStacks(room, event.getType()); List<WiredStack> stacks = index.getStacks(room, event.getType());
@@ -345,7 +350,7 @@ public final class WiredEngine {
for (WiredStack stack : stacks) { for (WiredStack stack : stacks) {
try { try {
boolean triggered = processStack(stack, event, triggerTime); boolean triggered = processStack(stack, event, triggerTime, negateConditions);
if (triggered) { if (triggered) {
anyTriggered = true; anyTriggered = true;
@@ -374,6 +379,10 @@ public final class WiredEngine {
* Process a single wired stack. * Process a single wired stack.
*/ */
private boolean processStack(WiredStack stack, WiredEvent event, long currentTime) { 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(); Room room = event.getRoom();
WiredTextInputCaptureSupport.CaptureResult captureResult = resolveTextInputCapture(stack, event); WiredTextInputCaptureSupport.CaptureResult captureResult = resolveTextInputCapture(stack, event);
@@ -417,17 +426,12 @@ public final class WiredEngine {
applySelectionFilterExtras(stack, ctx, executedSelectors); applySelectionFilterExtras(stack, ctx, executedSelectors);
} }
// Evaluate conditions boolean conditionsPassedForExecution = getConditionOutcomeForExecution(stack, ctx, negateConditions);
if (stack.hasConditions()) { List<IWiredEffect> executableEffects = getExecutableEffectsForCurrentExecution(stack, conditionsPassedForExecution);
debug(room, "Evaluating {} conditions...", stack.conditions().size()); boolean hasSpecialOutcome = conditionsPassedForExecution && hasSpecialTriggerOutcome(stack, event);
boolean conditionsPassed = evaluateConditions(stack, ctx);
debug(room, "Conditions result: {}", conditionsPassed ? "PASSED" : "FAILED"); if (!shouldContinueAfterConditionCheck(stack, room, conditionsPassedForExecution, executableEffects, hasSpecialOutcome)) {
if (!conditionsPassed) { return false;
debug(room, "Conditions failed, aborting stack");
return false;
}
} else {
debug(room, "No conditions in stack, proceeding to effects");
} }
WiredExtraExecutionLimit executionLimitExtra = getExecutionLimitExtra(room, stack); WiredExtraExecutionLimit executionLimitExtra = getExecutionLimitExtra(room, stack);
@@ -455,7 +459,8 @@ public final class WiredEngine {
return false; return false;
} }
if ((event.getType() == WiredEvent.Type.USER_CLICKS_USER) if (conditionsPassedForExecution
&& (event.getType() == WiredEvent.Type.USER_CLICKS_USER)
&& (stack.triggerItem() instanceof WiredTriggerHabboClicksUser) && (stack.triggerItem() instanceof WiredTriggerHabboClicksUser)
&& event.getActor().isPresent()) { && event.getActor().isPresent()) {
WiredTriggerHabboClicksUser clickUserTrigger = (WiredTriggerHabboClicksUser) stack.triggerItem(); WiredTriggerHabboClicksUser clickUserTrigger = (WiredTriggerHabboClicksUser) stack.triggerItem();
@@ -477,8 +482,8 @@ public final class WiredEngine {
finalizeSelectors(executedSelectors, ctx, currentTime); finalizeSelectors(executedSelectors, ctx, currentTime);
// Execute effects // Execute effects
if (stack.hasEffects()) { if (!executableEffects.isEmpty()) {
executeEffects(stack, ctx, currentTime); executeEffects(stack, executableEffects, ctx, currentTime);
} }
// Fire executed event // Fire executed event
@@ -494,6 +499,139 @@ public final class WiredEngine {
return true; 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) { private boolean wouldTriggerStack(WiredStack stack, WiredEvent event, long currentTime) {
Room room = event.getRoom(); Room room = event.getRoom();
WiredTextInputCaptureSupport.CaptureResult captureResult = resolveTextInputCapture(stack, event); WiredTextInputCaptureSupport.CaptureResult captureResult = resolveTextInputCapture(stack, event);
@@ -522,7 +660,14 @@ public final class WiredEngine {
applySelectionFilterExtras(stack, ctx, executedSelectors); 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; return false;
} }
@@ -553,6 +698,67 @@ public final class WiredEngine {
return false; 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) { private WiredTextInputCaptureSupport.CaptureResult resolveTextInputCapture(WiredStack stack, WiredEvent event) {
if (stack == null || event == null) { if (stack == null || event == null) {
return WiredTextInputCaptureSupport.CaptureResult.noMatch(); return WiredTextInputCaptureSupport.CaptureResult.noMatch();
@@ -576,6 +782,48 @@ public final class WiredEngine {
return evaluateConditionsByMode(conditions, ctx, stack.conditionEvaluationMode(), stack.conditionEvaluationValue()); 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. * Evaluate conditions according to the configured stack mode.
*/ */
@@ -638,65 +886,57 @@ public final class WiredEngine {
/** /**
* Execute effects in a stack. * Execute effects in a stack.
*/ */
private void executeEffects(WiredStack stack, WiredContext ctx, long currentTime) { private void executeEffects(WiredStack stack, List<IWiredEffect> effects, WiredContext ctx, long currentTime) {
List<IWiredEffect> effects = stack.effects();
if (effects.isEmpty()) { if (effects.isEmpty()) {
return; 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 // Determine which (regular) effects to execute
List<IWiredEffect> toExecute; List<IWiredEffect> toExecute;
if (stack.useRandom()) { if (stack.useRandom()) {
WiredExtraRandom randomExtra = getRandomExtra(ctx.room(), stack); WiredExtraRandom randomExtra = getRandomExtra(ctx.room(), stack);
if (regulars.isEmpty()) { if (effects.isEmpty()) {
toExecute = new ArrayList<>(); toExecute = new ArrayList<>();
} else if (randomExtra != null) { } 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()); debug(ctx.room(), "Random mode: selected {} effect(s), skip window {}", toExecute.size(), randomExtra.getSkipExecutions());
} else { } else {
int randomIndex = new Random().nextInt(regulars.size()); int randomIndex = new Random().nextInt(effects.size());
toExecute = Collections.singletonList(regulars.get(randomIndex)); toExecute = Collections.singletonList(effects.get(randomIndex));
debug(ctx.room(), "Random mode: selected effect {}/{}", randomIndex + 1, regulars.size()); debug(ctx.room(), "Random mode: selected effect {}/{}", randomIndex + 1, effects.size());
} }
} else if (stack.useUnseen()) { } else if (stack.useUnseen()) {
// Unseen mode: execute in stable order with memory // Unseen mode: execute in stable order with memory
if (regulars.isEmpty()) { if (effects.isEmpty()) {
toExecute = new ArrayList<>(); toExecute = new ArrayList<>();
} else { } else {
WiredExtraUnseen unseenExtra = getUnseenExtra(ctx.room(), stack); WiredExtraUnseen unseenExtra = getUnseenExtra(ctx.room(), stack);
if (unseenExtra != null) { if (unseenExtra != null) {
toExecute = unseenExtra.selectWiredEffects(regulars); toExecute = unseenExtra.selectWiredEffects(effects);
if (!toExecute.isEmpty()) { if (!toExecute.isEmpty()) {
int selectedIndex = regulars.indexOf(toExecute.get(0)); int selectedIndex = effects.indexOf(toExecute.get(0));
debug(ctx.room(), "Unseen mode: selected effect {}/{}", selectedIndex + 1, regulars.size()); debug(ctx.room(), "Unseen mode: selected effect {}/{}", selectedIndex + 1, effects.size());
} else { } else {
debug(ctx.room(), "Unseen mode: no eligible effect found"); debug(ctx.room(), "Unseen mode: no eligible effect found");
} }
} else { } else {
int index = getNextUnseenIndex(stack, regulars.size()); int index = getNextUnseenIndex(stack, effects.size());
toExecute = Collections.singletonList(regulars.get(index)); toExecute = Collections.singletonList(effects.get(index));
debug(ctx.room(), "Unseen mode fallback: selected effect {}/{}", index + 1, regulars.size()); debug(ctx.room(), "Unseen mode fallback: selected effect {}/{}", index + 1, effects.size());
} }
} }
} else if (stack.executeInOrder()) { } else if (stack.executeInOrder()) {
debug(ctx.room(), "Ordered mode: executing effect batches in stack order by delay"); debug(ctx.room(), "Ordered mode: executing effect batches in stack order by delay");
executeOrderedEffects(regulars, ctx, currentTime); executeOrderedEffects(effects, ctx, currentTime);
return; return;
} else { } else {
// Normal mode: preserve the physical stack order. // Normal mode: preserve the physical stack order.
// This matches the legacy handler behavior and avoids visual/state races // This matches the legacy handler behavior and avoids visual/state races
// for combinations such as Move/Rotate + Match To Snapshot in the same stack. // for combinations such as Move/Rotate + Match To Snapshot in the same stack.
toExecute = new ArrayList<>(regulars); toExecute = new ArrayList<>(effects);
} }
WiredMoveCarryHelper.beginMovementCollection(); 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.users.HabboItem;
import com.eu.habbo.habbohotel.wired.WiredGiveRewardItem; import com.eu.habbo.habbohotel.wired.WiredGiveRewardItem;
import com.eu.habbo.habbohotel.wired.WiredTriggerType; 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.migrate.WiredEvents;
import com.eu.habbo.habbohotel.wired.tick.WiredTickService; import com.eu.habbo.habbohotel.wired.tick.WiredTickService;
import com.eu.habbo.habbohotel.wired.tick.WiredTickable; import com.eu.habbo.habbohotel.wired.tick.WiredTickable;
@@ -40,6 +41,10 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayDeque; 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. * Manager class for the wired runtime.
@@ -95,7 +100,7 @@ public final class WiredManager {
/** Whether the engine is initialized */ /** Whether the engine is initialized */
private static volatile boolean initialized = false; private static volatile boolean initialized = false;
private static final ThreadLocal<Integer> EVENT_HANDLING_DEPTH = new ThreadLocal<>(); 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() { private WiredManager() {
// Static utility class // Static utility class
} }
@@ -241,6 +246,10 @@ public final class WiredManager {
* @return true if any stack was triggered * @return true if any stack was triggered
*/ */
public static boolean handleEvent(WiredEvent event) { public static boolean handleEvent(WiredEvent event) {
return handleEvent(event, false);
}
public static boolean handleEvent(WiredEvent event, boolean negateConditions) {
if (!isEnabled() || engine == null) { if (!isEnabled() || engine == null) {
return false; return false;
} }
@@ -260,19 +269,19 @@ public final class WiredManager {
boolean handled = false; boolean handled = false;
try { try {
handled = engine.handleEvent(event); handled = engine.handleEvent(event, negateConditions);
if (nextDepth == 1) { if (nextDepth == 1) {
ArrayDeque<WiredEvent> deferredEvents = DEFERRED_EFFECT_EVENTS.get(); ArrayDeque<DeferredEffectEvent> deferredEvents = DEFERRED_EFFECT_EVENTS.get();
while (deferredEvents != null && !deferredEvents.isEmpty()) { 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; 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) { 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())) { if (!isEnabled() || engine == null || event == null || RoomWiredDisableSupport.isWiredDisabled(event.getRoom())) {
return false; return false;
} }
@@ -295,17 +312,17 @@ public final class WiredManager {
Integer currentDepth = EVENT_HANDLING_DEPTH.get(); Integer currentDepth = EVENT_HANDLING_DEPTH.get();
if (currentDepth == null || currentDepth <= 0) { 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) { if (deferredEvents == null) {
deferredEvents = new ArrayDeque<>(); deferredEvents = new ArrayDeque<>();
DEFERRED_EFFECT_EVENTS.set(deferredEvents); DEFERRED_EFFECT_EVENTS.set(deferredEvents);
} }
deferredEvents.addLast(event); deferredEvents.addLast(new DeferredEffectEvent(event, negateConditions));
return true; return true;
} }
@@ -971,6 +988,10 @@ public final class WiredManager {
* @return true if any effects were executed * @return true if any effects were executed
*/ */
public static boolean executeEffectsAtTiles(THashSet<RoomTile> tiles, final RoomUnit roomUnit, final Room room, final int callStackDepth) { 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) { for (RoomTile tile : tiles) {
if (room != null) { if (room != null) {
THashSet<HabboItem> items = room.getItemsAt(tile); THashSet<HabboItem> items = room.getItemsAt(tile);
@@ -994,6 +1015,82 @@ public final class WiredManager {
return true; 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 ========== // ========== Reward System ==========
/** /**
@@ -981,7 +981,7 @@ public final class WiredMoveCarryHelper {
return new ArrayList<>(); 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) { private static Collection<RoomUnit> resolvePhysicsUsers(Room room, WiredContext ctx, int userSource) {
@@ -997,7 +997,7 @@ public final class WiredMoveCarryHelper {
return new ArrayList<>(); return new ArrayList<>();
} }
return WiredSourceUtil.resolveUsers(ctx, userSource); return WiredSourceUtil.resolveUsersRaw(ctx, userSource);
} }
private static WiredExtraMovePhysics getMovementPhysicsExtra(Room room, HabboItem stackItem) { 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) { 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) { if (ctx == null) {
case SOURCE_TRIGGER: return resolvedItems;
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;
} }
return (sourceType == SOURCE_SELECTOR) return (sourceType == SOURCE_SELECTOR)
@@ -61,43 +40,19 @@ public final class WiredSourceUtil {
: WiredSelectionFilterSupport.filterItems(ctx.room(), ctx.triggerItem(), ctx, resolvedItems); : 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) { public static List<RoomUnit> resolveUsers(WiredContext ctx, int sourceType) {
return resolveUsers(ctx, sourceType, null); return resolveUsers(ctx, sourceType, null);
} }
public static List<RoomUnit> resolveUsers(WiredContext ctx, int sourceType, Collection<RoomUnit> selectedUsers) { public static List<RoomUnit> resolveUsers(WiredContext ctx, int sourceType, Collection<RoomUnit> selectedUsers) {
List<RoomUnit> resolvedUsers; List<RoomUnit> resolvedUsers = resolveUsersInternal(ctx, sourceType, selectedUsers);
switch (sourceType) { if (ctx == null) {
case SOURCE_TRIGGER: return resolvedUsers;
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;
} }
return (sourceType == SOURCE_SELECTOR) return (sourceType == SOURCE_SELECTOR)
@@ -105,6 +60,14 @@ public final class WiredSourceUtil {
: WiredSelectionFilterSupport.filterUsers(ctx.room(), ctx.triggerItem(), ctx, resolvedUsers); : 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) { public static boolean isDefaultUserSource(int value) {
switch (value) { switch (value) {
case SOURCE_TRIGGER: case SOURCE_TRIGGER:
@@ -251,4 +214,75 @@ public final class WiredSourceUtil {
private static void applySelectionFilterExtras(Room room, HabboItem triggerItem, WiredContext selectorCtx) { private static void applySelectionFilterExtras(Room room, HabboItem triggerItem, WiredContext selectorCtx) {
WiredSelectionFilterSupport.applySelectorFilters(room, triggerItem, 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 com.eu.habbo.habbohotel.rooms.Room;
import gnu.trove.set.hash.THashSet; 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 { public final class WiredVariableTextConnectorSupport {
private WiredVariableTextConnectorSupport() { private WiredVariableTextConnectorSupport() {
} }
@@ -18,31 +23,43 @@ public final class WiredVariableTextConnectorSupport {
} }
public static WiredExtraVariableTextConnector getConnector(Room room, int definitionItemId) { 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) { if (room == null || room.getRoomSpecialTypes() == null || definitionItemId <= 0) {
return null; return Collections.emptyList();
} }
InteractionWiredExtra extra = room.getRoomSpecialTypes().getExtra(definitionItemId); InteractionWiredExtra extra = room.getRoomSpecialTypes().getExtra(definitionItemId);
return getConnector(room, extra); return getConnectors(room, extra);
} }
public static WiredExtraVariableTextConnector getConnector(Room room, InteractionWiredExtra definition) { 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) { if (room == null || definition == null || room.getRoomSpecialTypes() == null) {
return null; return Collections.emptyList();
} }
THashSet<InteractionWiredExtra> extras = room.getRoomSpecialTypes().getExtras(definition.getX(), definition.getY()); THashSet<InteractionWiredExtra> extras = room.getRoomSpecialTypes().getExtras(definition.getX(), definition.getY());
if (extras == null || extras.isEmpty()) { if (extras == null || extras.isEmpty()) {
return null; return Collections.emptyList();
} }
List<WiredExtraVariableTextConnector> connectors = new ArrayList<>();
for (InteractionWiredExtra extra : WiredExecutionOrderUtil.sort(extras)) { for (InteractionWiredExtra extra : WiredExecutionOrderUtil.sort(extras)) {
if (extra instanceof WiredExtraVariableTextConnector) { 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) { public static String toText(Room room, int definitionItemId, Integer value) {
@@ -50,12 +67,34 @@ public final class WiredVariableTextConnectorSupport {
return ""; return "";
} }
WiredExtraVariableTextConnector connector = getConnector(room, definitionItemId); for (WiredExtraVariableTextConnector connector : getConnectors(room, definitionItemId)) {
return connector != null ? connector.resolveText(value) : String.valueOf(value); 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) { public static Integer toValue(Room room, int definitionItemId, String text) {
WiredExtraVariableTextConnector connector = getConnector(room, definitionItemId); if (text == null) {
return connector != null ? connector.resolveValue(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;
} }
} }