feat(wired): add antenna signals and selector-aware snapshots

This commit is contained in:
Lorenzune
2026-03-16 15:12:42 +01:00
parent 777c1d2aa1
commit e035a5dca3
9 changed files with 503 additions and 221 deletions
@@ -201,6 +201,7 @@ public class ItemManager {
this.interactionsList.add(new ItemInteraction("random_state", InteractionRandomState.class));
this.interactionsList.add(new ItemInteraction("vendingmachine_no_sides", InteractionNoSidesVendingMachine.class));
this.interactionsList.add(new ItemInteraction("tile_walkmagic", InteractionTileWalkMagic.class));
this.interactionsList.add(new ItemInteraction("antenna", InteractionDefault.class));
this.interactionsList.add(new ItemInteraction("game_timer", InteractionGameTimer.class));
@@ -19,9 +19,7 @@ import gnu.trove.set.hash.THashSet;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class WiredConditionMatchStatePosition extends InteractionWiredCondition implements InteractionWiredMatchFurniSettings {
public static final WiredConditionType type = WiredConditionType.MATCH_SSHOT;
@@ -92,14 +90,12 @@ public class WiredConditionMatchStatePosition extends InteractionWiredCondition
this.settings.clear();
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
for (int i = 0; i < count; i++) {
int itemId = settings.getFurniIds()[i];
HabboItem item = room.getHabboItem(itemId);
for (int i = 0; i < count; i++) {
int itemId = settings.getFurniIds()[i];
HabboItem item = room.getHabboItem(itemId);
if (item != null)
this.settings.add(new WiredMatchFurniSetting(item.getId(), item.getExtradata(), item.getRotation(), item.getX(), item.getY()));
}
if (item != null)
this.settings.add(new WiredMatchFurniSetting(item.getId(), item.getExtradata(), item.getRotation(), item.getX(), item.getY()));
}
return true;
@@ -108,65 +104,71 @@ public class WiredConditionMatchStatePosition extends InteractionWiredCondition
@Override
public boolean evaluate(WiredContext ctx) {
Room room = ctx.room();
this.refresh();
if (this.settings.isEmpty())
return true;
List<HabboItem> targets = null;
Set<Integer> targetIds = null;
if (this.furniSource != WiredSourceUtil.SOURCE_SELECTED) {
targets = WiredSourceUtil.resolveItems(ctx, this.furniSource, null);
List<HabboItem> targets = WiredSourceUtil.resolveItems(ctx, this.furniSource, null);
if (targets.isEmpty()) return false;
targetIds = new HashSet<>();
for (HabboItem item : targets) {
if (item != null) targetIds.add(item.getId());
}
if (targetIds.isEmpty()) return false;
}
THashSet<WiredMatchFurniSetting> toRemove = new THashSet<>();
Set<Integer> settingsIds = new HashSet<>();
for (HabboItem item : targets) {
if (item == null) return false;
WiredMatchFurniSetting setting = this.resolveSettingForTarget(room, item);
if (setting == null) {
return false;
}
if (!this.matchesSetting(item, setting)) {
return false;
}
}
return true;
}
for (WiredMatchFurniSetting setting : this.settings) {
if (targetIds != null && !targetIds.contains(setting.item_id)) {
continue;
}
HabboItem item = room.getHabboItem(setting.item_id);
if (item != null) {
settingsIds.add(setting.item_id);
if (this.state) {
if (!item.getExtradata().equals(setting.state))
return false;
}
if (this.position) {
if (!(setting.x == item.getX() && setting.y == item.getY()))
return false;
}
if (this.direction) {
if (setting.rotation != item.getRotation())
return false;
}
} else {
toRemove.add(setting);
}
}
if (targetIds != null && !settingsIds.containsAll(targetIds)) {
return false;
}
if (!toRemove.isEmpty()) {
for (WiredMatchFurniSetting setting : toRemove) {
this.settings.remove(setting);
}
if (item == null) continue;
if (!this.matchesSetting(item, setting))
return false;
}
return true;
}
private WiredMatchFurniSetting resolveSettingForTarget(Room room, HabboItem target) {
WiredMatchFurniSetting fallback = null;
for (WiredMatchFurniSetting setting : this.settings) {
HabboItem sourceItem = room.getHabboItem(setting.item_id);
if (sourceItem == null) continue;
if (sourceItem.getBaseItem().getId() != target.getBaseItem().getId()) continue;
if (setting.state.equals(target.getExtradata())) {
return setting;
}
if (fallback == null) {
fallback = setting;
}
}
return fallback;
}
private boolean matchesSetting(HabboItem item, WiredMatchFurniSetting setting) {
if (this.state && !item.getExtradata().equals(setting.state))
return false;
if (this.position && !(setting.x == item.getX() && setting.y == item.getY()))
return false;
return !this.direction || setting.rotation == item.getRotation();
}
@Deprecated
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
@@ -214,9 +216,6 @@ public class WiredConditionMatchStatePosition extends InteractionWiredCondition
this.position = data[4].equals("1");
this.furniSource = this.settings.isEmpty() ? WiredSourceUtil.SOURCE_TRIGGER : WiredSourceUtil.SOURCE_SELECTED;
}
if (this.furniSource == WiredSourceUtil.SOURCE_TRIGGER && !this.settings.isEmpty()) {
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
}
}
@Override
@@ -50,6 +50,7 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int
@Override
public void execute(WiredContext ctx) {
Room room = ctx.room();
this.refresh();
if(this.settings.isEmpty())
return;
@@ -57,54 +58,80 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int
if (room.getLayout() == null)
return;
java.util.Set<Integer> allowedItemIds = null;
if (this.furniSource != WiredSourceUtil.SOURCE_SELECTED) {
allowedItemIds = new java.util.HashSet<>();
for (HabboItem si : WiredSourceUtil.resolveItems(ctx, this.furniSource, null)) {
if (si != null) {
allowedItemIds.add(si.getId());
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
for (WiredMatchFurniSetting setting : this.settings) {
HabboItem item = room.getHabboItem(setting.item_id);
if (item != null) {
this.applySetting(room, item, setting);
}
}
if (allowedItemIds.isEmpty()) {
return;
return;
}
List<HabboItem> targets = WiredSourceUtil.resolveItems(ctx, this.furniSource, null);
if (targets.isEmpty()) {
return;
}
for (HabboItem item : targets) {
if (item == null) continue;
WiredMatchFurniSetting setting = this.resolveSettingForTarget(room, item);
if (setting == null) continue;
this.applySetting(room, item, setting);
}
}
private WiredMatchFurniSetting resolveSettingForTarget(Room room, HabboItem target) {
WiredMatchFurniSetting fallback = null;
for (WiredMatchFurniSetting setting : this.settings) {
HabboItem sourceItem = room.getHabboItem(setting.item_id);
if (sourceItem == null) continue;
if (sourceItem.getBaseItem().getId() != target.getBaseItem().getId()) continue;
if (setting.state.equals(target.getExtradata())) {
return setting;
}
if (fallback == null) {
fallback = setting;
}
}
for (WiredMatchFurniSetting setting : this.settings) {
if (allowedItemIds != null && !allowedItemIds.contains(setting.item_id)) continue;
return fallback;
}
HabboItem item = room.getHabboItem(setting.item_id);
if (item != null) {
if (this.state && (this.checkForWiredResetPermission && item.allowWiredResetState())) {
if (!setting.state.equals(" ") && !item.getExtradata().equals(setting.state)) {
item.setExtradata(setting.state);
room.updateItemState(item);
private void applySetting(Room room, HabboItem item, WiredMatchFurniSetting setting) {
if (this.state && (this.checkForWiredResetPermission && item.allowWiredResetState())) {
if (!setting.state.equals(" ") && !item.getExtradata().equals(setting.state)) {
item.setExtradata(setting.state);
room.updateItemState(item);
}
}
RoomTile oldLocation = room.getLayout().getTile(item.getX(), item.getY());
if (oldLocation == null) return;
double oldZ = item.getZ();
if(this.direction && !this.position) {
if(item.getRotation() != setting.rotation && room.furnitureFitsAt(oldLocation, item, setting.rotation, false) == FurnitureMovementError.NONE) {
room.moveFurniTo(item, oldLocation, setting.rotation, null, true);
}
}
else if(this.position) {
boolean slideAnimation = !this.direction || item.getRotation() == setting.rotation;
RoomTile newLocation = room.getLayout().getTile((short) setting.x, (short) setting.y);
int newRotation = this.direction ? setting.rotation : item.getRotation();
if(newLocation != null && newLocation.state != RoomTileState.INVALID && (newLocation != oldLocation || newRotation != item.getRotation()) && room.furnitureFitsAt(newLocation, item, newRotation, true) == FurnitureMovementError.NONE) {
if(room.moveFurniTo(item, newLocation, newRotation, null, !slideAnimation) == FurnitureMovementError.NONE) {
if(slideAnimation) {
room.sendComposer(new FloorItemOnRollerComposer(item, null, oldLocation, oldZ, newLocation, item.getZ(), 0, room).compose());
}
}
RoomTile oldLocation = room.getLayout().getTile(item.getX(), item.getY());
if (oldLocation == null) continue;
double oldZ = item.getZ();
if(this.direction && !this.position) {
if(item.getRotation() != setting.rotation && room.furnitureFitsAt(oldLocation, item, setting.rotation, false) == FurnitureMovementError.NONE) {
room.moveFurniTo(item, oldLocation, setting.rotation, null, true);
}
}
else if(this.position) {
boolean slideAnimation = !this.direction || item.getRotation() == setting.rotation;
RoomTile newLocation = room.getLayout().getTile((short) setting.x, (short) setting.y);
int newRotation = this.direction ? setting.rotation : item.getRotation();
if(newLocation != null && newLocation.state != RoomTileState.INVALID && (newLocation != oldLocation || newRotation != item.getRotation()) && room.furnitureFitsAt(newLocation, item, newRotation, true) == FurnitureMovementError.NONE) {
if(room.moveFurniTo(item, newLocation, newRotation, null, !slideAnimation) == FurnitureMovementError.NONE) {
if(slideAnimation) {
room.sendComposer(new FloorItemOnRollerComposer(item, null, oldLocation, oldZ, newLocation, item.getZ(), 0, room).compose());
}
}
}
}
}
}
}
@@ -134,9 +161,6 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int
this.settings.clear();
this.settings.addAll(data.items);
this.furniSource = data.furniSource;
if (this.furniSource == WiredSourceUtil.SOURCE_TRIGGER && !this.settings.isEmpty()) {
this.furniSource = WiredSourceUtil.SOURCE_SELECTED;
}
}
else {
String[] data = set.getString("wired_data").split(":");
@@ -221,23 +245,22 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int
if (room == null)
throw new WiredSaveException("Trying to save wired in unloaded room");
int itemsCount = settings.getFurniIds().length;
if(itemsCount > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) {
throw new WiredSaveException("Too many furni selected");
}
List<WiredMatchFurniSetting> newSettings = new ArrayList<>();
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
int itemsCount = settings.getFurniIds().length;
if(itemsCount > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) {
throw new WiredSaveException("Too many furni selected");
}
for (int i = 0; i < itemsCount; i++) {
int itemId = settings.getFurniIds()[i];
HabboItem it = room.getHabboItem(itemId);
for (int i = 0; i < itemsCount; i++) {
int itemId = settings.getFurniIds()[i];
HabboItem it = room.getHabboItem(itemId);
if(it == null)
throw new WiredSaveException(String.format("Item %s not found", itemId));
if(it == null)
throw new WiredSaveException(String.format("Item %s not found", itemId));
newSettings.add(new WiredMatchFurniSetting(it.getId(), this.checkForWiredResetPermission && it.allowWiredResetState() ? it.getExtradata() : " ", it.getRotation(), it.getX(), it.getY()));
}
newSettings.add(new WiredMatchFurniSetting(it.getId(), this.checkForWiredResetPermission && it.allowWiredResetState() ? it.getExtradata() : " ", it.getRotation(), it.getX(), it.getY()));
}
int delay = settings.getDelay();
@@ -249,9 +272,7 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int
this.direction = setDirection;
this.position = setPosition;
this.settings.clear();
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
this.settings.addAll(newSettings);
}
this.settings.addAll(newSettings);
this.setDelay(delay);
return true;
@@ -24,6 +24,7 @@ import org.slf4j.LoggerFactory;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@@ -36,14 +37,16 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
private static final int ANTENNA_PICKED = 0;
private static final int ANTENNA_TRIGGER = 1;
private static final int FORWARD_NONE = 0;
private static final int FORWARD_TRIGGER = 1;
private static final String ANTENNA_INTERACTION = "antenna";
private static final String FORWARD_ITEM_SPLIT_REGEX = "[;,\\t]";
private static final long ANTENNA_PULSE_MS = 300L;
private static final ConcurrentHashMap<Integer, Long> ANTENNA_PULSE_TOKENS = new ConcurrentHashMap<>();
private THashSet<HabboItem> items;
private THashSet<HabboItem> forwardItems;
private int antennaSource = ANTENNA_PICKED;
private int furniForward = FORWARD_NONE;
private int userForward = FORWARD_NONE;
private int furniForward = WiredSourceUtil.SOURCE_TRIGGER;
private int userForward = WiredSourceUtil.SOURCE_TRIGGER;
private boolean signalPerFurni = false;
private boolean signalPerUser = false;
private int channel = 0;
@@ -51,11 +54,13 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
public WiredEffectSendSignal(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
this.items = new THashSet<>();
this.forwardItems = new THashSet<>();
}
public WiredEffectSendSignal(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
this.items = new THashSet<>();
this.forwardItems = new THashSet<>();
}
@Override
@@ -77,73 +82,68 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
.map(Collections::<HabboItem>singleton)
.orElse(Collections.emptySet());
} else {
antennas = ctx.targets().isItemsModifiedBySelector()
? new ArrayList<>(ctx.targets().items())
: new ArrayList<>(this.items);
Collection<HabboItem> baseAntennas = new ArrayList<>(this.items);
if (baseAntennas.isEmpty() && antennaSource > ANTENNA_TRIGGER) {
HabboItem antenna = room.getHabboItem(antennaSource);
antennas = (antenna != null) ? Collections.singleton(antenna) : Collections.emptySet();
} else {
antennas = baseAntennas;
}
}
if (antennas.isEmpty()) {
List<HabboItem> resolvedAntennas = antennas.stream()
.filter(Objects::nonNull)
.filter(this::isAntennaItem)
.collect(Collectors.toList());
if (resolvedAntennas.isEmpty()) {
LOGGER.debug("[SendSignal] No antennas resolved, aborting. antennaSource={}, selectorModified={}", antennaSource, ctx.targets().isItemsModifiedBySelector());
return;
}
LOGGER.debug("[SendSignal] Resolved {} antenna(s), firing signals", antennas.size());
LOGGER.debug("[SendSignal] Resolved {} antenna(s), firing signals", resolvedAntennas.size());
RoomUnit forwardedUser = null;
if (userForward == FORWARD_TRIGGER) {
forwardedUser = ctx.actor().orElse(null);
}
List<RoomUnit> forwardedUsers = WiredSourceUtil.resolveUsers(ctx, this.userForward);
List<HabboItem> forwardedFurni = WiredSourceUtil.resolveItems(ctx, this.furniForward, this.forwardItems);
HabboItem forwardedFurni = null;
if (furniForward == FORWARD_TRIGGER) {
forwardedFurni = ctx.sourceItem().orElse(null);
}
RoomUnit defaultUser = forwardedUsers.isEmpty() ? null : forwardedUsers.get(0);
HabboItem defaultFurni = forwardedFurni.isEmpty() ? null : forwardedFurni.get(0);
Set<String> visitedTiles = new HashSet<>();
List<RoomTile> antennaTiles = new ArrayList<>();
for (HabboItem antenna : antennas) {
if (antenna == null) continue;
String key = antenna.getX() + "," + antenna.getY();
if (visitedTiles.add(key)) {
RoomTile tile = room.getLayout().getTile(antenna.getX(), antenna.getY());
if (tile != null) {
antennaTiles.add(tile);
}
}
}
Collection<RoomUnit> usersToSend = (signalPerUser && !forwardedUsers.isEmpty())
? forwardedUsers
: Collections.singletonList(defaultUser);
Collection<HabboItem> furniToSend = (signalPerFurni && !forwardedFurni.isEmpty())
? forwardedFurni
: Collections.singletonList(defaultFurni);
int nextDepth = currentDepth + 1;
if (signalPerFurni || signalPerUser) {
if (signalPerFurni) {
for (RoomTile tile : antennaTiles) {
fireSignalAtTile(room, tile, forwardedUser, forwardedFurni, nextDepth);
for (RoomUnit user : usersToSend) {
for (HabboItem sourceItem : furniToSend) {
for (HabboItem antenna : resolvedAntennas) {
fireSignalAtAntenna(room, antenna, user, sourceItem, nextDepth);
}
}
if (signalPerUser && ctx.targets().hasUsers()) {
for (RoomUnit user : ctx.targets().users()) {
for (RoomTile tile : antennaTiles) {
fireSignalAtTile(room, tile, user, forwardedFurni, nextDepth);
}
}
} else if (!signalPerFurni) {
for (RoomTile tile : antennaTiles) {
fireSignalAtTile(room, tile, forwardedUser, forwardedFurni, nextDepth);
}
}
} else {
for (RoomTile tile : antennaTiles) {
fireSignalAtTile(room, tile, forwardedUser, forwardedFurni, nextDepth);
}
}
}
private void fireSignalAtTile(Room room, RoomTile tile, RoomUnit actor, HabboItem sourceItem, int depth) {
LOGGER.debug("[SendSignal] fireSignalAtTile: tile={},{} depth={} channel={} actor={} sourceItem={}", tile.x, tile.y, depth, channel, actor != null ? actor.getId() : "null", sourceItem != null ? sourceItem.getId() : "null");
private void fireSignalAtAntenna(Room room, HabboItem antenna, RoomUnit actor, HabboItem sourceItem, int depth) {
if (antenna == null) return;
RoomTile tile = room.getLayout().getTile(antenna.getX(), antenna.getY());
if (tile == null) return;
pulseAntenna(room, antenna);
int signalChannel = antenna.getId();
LOGGER.debug("[SendSignal] fireSignalAtAntenna: antennaId={} tile={},{} depth={} channel={} actor={} sourceItem={}",
signalChannel, tile.x, tile.y, depth, signalChannel, actor != null ? actor.getId() : "null", sourceItem != null ? sourceItem.getId() : "null");
WiredEvent.Builder builder = WiredEvent.builder(WiredEvent.Type.SIGNAL_RECEIVED, room)
.tile(tile)
.callStackDepth(depth)
.signalChannel(this.channel)
.signalChannel(signalChannel)
.triggeredByEffect(true);
if (actor != null) builder.actor(actor);
@@ -153,6 +153,33 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
LOGGER.debug("[SendSignal] handleEvent returned: {}", result);
}
private void pulseAntenna(Room room, HabboItem antenna) {
if (room == null || antenna == null || antenna.getBaseItem() == null) return;
if (antenna.getBaseItem().getStateCount() <= 1) return;
final long token = System.currentTimeMillis();
ANTENNA_PULSE_TOKENS.put(antenna.getId(), token);
if ("1".equals(antenna.getExtradata())) {
antenna.setExtradata("0");
room.updateItemState(antenna);
}
antenna.setExtradata("1");
room.updateItemState(antenna);
Emulator.getThreading().run(() -> {
if (!room.isLoaded()) return;
Long currentToken = ANTENNA_PULSE_TOKENS.get(antenna.getId());
if (currentToken == null || currentToken.longValue() != token) return;
antenna.setExtradata("0");
room.updateItemState(antenna);
ANTENNA_PULSE_TOKENS.remove(antenna.getId(), token);
}, ANTENNA_PULSE_MS);
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
List<HabboItem> itemsSnapshot = new ArrayList<>(this.items);
@@ -161,6 +188,16 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
item.getRoomId() != this.getRoomId() || room.getHabboItem(item.getId()) == null);
this.items.retainAll(itemsSnapshot);
List<HabboItem> forwardSnapshot = new ArrayList<>(this.forwardItems);
forwardSnapshot.removeIf(item ->
item.getRoomId() != this.getRoomId() || room.getHabboItem(item.getId()) == null);
this.forwardItems.retainAll(forwardSnapshot);
String forwardString = forwardSnapshot.stream()
.filter(Objects::nonNull)
.map(item -> Integer.toString(item.getId()))
.collect(Collectors.joining(";"));
message.appendBoolean(false);
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
message.appendInt(itemsSnapshot.size());
@@ -169,7 +206,7 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
}
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString("");
message.appendString(forwardString);
message.appendInt(6);
message.appendInt(antennaSource);
@@ -219,6 +256,12 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
newItems.add(it);
}
for (HabboItem receiver : newItems) {
if (!isAntennaItem(receiver)) {
throw new WiredSaveException("Only antenna furni can be selected");
}
}
if (room != null && room.getRoomSpecialTypes() != null) {
for (HabboItem receiver : newItems) {
int count = room.getRoomSpecialTypes().countSendersTargetingReceiver(receiver.getId(), this);
@@ -234,18 +277,36 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
}
int[] params = settings.getIntParams();
this.antennaSource = params.length > 0 ? params[0] : ANTENNA_PICKED;
this.furniForward = params.length > 1 ? params[1] : FORWARD_NONE;
this.userForward = params.length > 2 ? params[2] : FORWARD_NONE;
int requestedAntennaSource = params.length > 0 ? params[0] : ANTENNA_PICKED;
this.furniForward = normalizeSource(params.length > 1 ? params[1] : WiredSourceUtil.SOURCE_TRIGGER);
this.userForward = normalizeSource(params.length > 2 ? params[2] : WiredSourceUtil.SOURCE_TRIGGER);
this.signalPerFurni = params.length > 3 && params[3] == 1;
this.signalPerUser = params.length > 4 && params[4] == 1;
this.channel = params.length > 5 ? params[5] : 0;
this.antennaSource = requestedAntennaSource;
if (!newItems.isEmpty()) {
this.antennaSource = newItems.get(0).getId();
}
List<HabboItem> newForwardItems = new ArrayList<>();
if (this.furniForward == WiredSourceUtil.SOURCE_SELECTED && room != null) {
newForwardItems = parseForwardItems(settings.getStringParam(), room);
}
if (newForwardItems.size() > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) {
throw new WiredSaveException("Too many furni selected");
}
this.items.clear();
this.items.addAll(newItems);
this.forwardItems.clear();
if (this.furniForward == WiredSourceUtil.SOURCE_SELECTED) {
this.forwardItems.addAll(newForwardItems);
}
this.setDelay(delay);
LOGGER.debug("[SendSignal] saveData: antennaSource={}, furniForward={}, userForward={}, signalPerFurni={}, signalPerUser={}, channel={}, items={}",
antennaSource, furniForward, userForward, signalPerFurni, signalPerUser, channel, items.size());
LOGGER.debug("[SendSignal] saveData: antennaSource={}, furniForward={}, userForward={}, signalPerFurni={}, signalPerUser={}, channel={}, items={}, forwardItems={}",
antennaSource, furniForward, userForward, signalPerFurni, signalPerUser, channel, items.size(), forwardItems.size());
return true;
}
@@ -259,9 +320,11 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
@Override
public String getWiredData() {
List<HabboItem> itemsSnapshot = new ArrayList<>(this.items);
List<HabboItem> forwardSnapshot = new ArrayList<>(this.forwardItems);
return WiredManager.getGson().toJson(new JsonData(
this.getDelay(),
itemsSnapshot.stream().map(HabboItem::getId).collect(Collectors.toList()),
forwardSnapshot.stream().map(HabboItem::getId).collect(Collectors.toList()),
antennaSource, furniForward, userForward, signalPerFurni, signalPerUser, channel
));
}
@@ -269,14 +332,15 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.items = new THashSet<>();
this.forwardItems = new THashSet<>();
String wiredData = set.getString("wired_data");
if (wiredData != null && wiredData.startsWith("{")) {
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
this.setDelay(data.delay);
this.antennaSource = data.antennaSource;
this.furniForward = data.furniForward;
this.userForward = data.userForward;
this.furniForward = normalizeSource(data.furniForward);
this.userForward = normalizeSource(data.userForward);
this.signalPerFurni = data.signalPerFurni;
this.signalPerUser = data.signalPerUser;
this.channel = data.channel;
@@ -286,21 +350,84 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
if (item != null) this.items.add(item);
}
}
if (data.forwardItemIds != null) {
for (Integer id : data.forwardItemIds) {
HabboItem item = room.getHabboItem(id);
if (item != null) this.forwardItems.add(item);
}
}
if (this.antennaSource <= ANTENNA_TRIGGER && !this.items.isEmpty()) {
HabboItem first = this.items.iterator().next();
if (first != null) this.antennaSource = first.getId();
}
}
}
@Override
public void onPickUp() {
this.items.clear();
this.forwardItems.clear();
this.antennaSource = ANTENNA_PICKED;
this.furniForward = FORWARD_NONE;
this.userForward = FORWARD_NONE;
this.furniForward = WiredSourceUtil.SOURCE_TRIGGER;
this.userForward = WiredSourceUtil.SOURCE_TRIGGER;
this.signalPerFurni = false;
this.signalPerUser = false;
this.channel = 0;
this.setDelay(0);
}
private int normalizeSource(int source) {
if (source == 1) return WiredSourceUtil.SOURCE_TRIGGER;
if (source == WiredSourceUtil.SOURCE_TRIGGER
|| source == WiredSourceUtil.SOURCE_SELECTED
|| source == WiredSourceUtil.SOURCE_SELECTOR
|| source == WiredSourceUtil.SOURCE_SIGNAL) {
return source;
}
return WiredSourceUtil.SOURCE_TRIGGER;
}
private List<HabboItem> parseForwardItems(String data, Room room) throws WiredSaveException {
List<HabboItem> results = new ArrayList<>();
if (data == null || data.trim().isEmpty() || room == null) return results;
Set<Integer> seen = new HashSet<>();
String[] parts = data.split(FORWARD_ITEM_SPLIT_REGEX);
for (String part : parts) {
if (part == null) continue;
String trimmed = part.trim();
if (trimmed.isEmpty()) continue;
int itemId;
try {
itemId = Integer.parseInt(trimmed);
} catch (NumberFormatException e) {
continue;
}
if (itemId <= 0 || !seen.add(itemId)) continue;
HabboItem item = room.getHabboItem(itemId);
if (item == null) throw new WiredSaveException(String.format("Item %s not found", itemId));
results.add(item);
}
return results;
}
private boolean isAntennaItem(HabboItem item) {
if (item == null || item.getBaseItem() == null || item.getBaseItem().getInteractionType() == null) return false;
String interaction = item.getBaseItem().getInteractionType().getName();
if (interaction == null) return false;
String normalized = interaction.toLowerCase();
return normalized.equals(ANTENNA_INTERACTION);
}
@Override
public WiredEffectType getType() {
return type;
@@ -328,6 +455,7 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
static class JsonData {
int delay;
List<Integer> itemIds;
List<Integer> forwardItemIds;
int antennaSource;
int furniForward;
int userForward;
@@ -335,10 +463,11 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
boolean signalPerUser;
int channel;
public JsonData(int delay, List<Integer> itemIds, int antennaSource, int furniForward,
public JsonData(int delay, List<Integer> itemIds, List<Integer> forwardItemIds, int antennaSource, int furniForward,
int userForward, boolean signalPerFurni, boolean signalPerUser, int channel) {
this.delay = delay;
this.itemIds = itemIds;
this.forwardItemIds = forwardItemIds;
this.antennaSource = antennaSource;
this.furniForward = furniForward;
this.userForward = userForward;
@@ -112,17 +112,17 @@ public class WiredEffectFurniByType extends InteractionWiredEffect {
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
int[] params = settings.getIntParams();
if (params == null || params.length < 1) {
throw new WiredSaveException("wf_slc_furni_bytype: intParams must have at least 1 element");
if (params == null || params.length < 4) {
throw new WiredSaveException("wf_slc_furni_bytype: intParams must have at least 4 elements");
}
this.sourceType = params[0];
this.sourceType = SOURCE_FURNI_PICKED;
this.matchState = params.length > 1 && params[1] == 1;
this.filterExisting = params.length > 2 && params[2] == 1;
this.invert = params.length > 3 && params[3] == 1;
this.pickedFurniIds = new ArrayList<>();
if (this.sourceType == SOURCE_FURNI_PICKED && settings.getFurniIds() != null) {
if (settings.getFurniIds() != null) {
for (int id : settings.getFurniIds()) {
if (pickedFurniIds.size() >= MAX_PICKED_FURNI) break;
pickedFurniIds.add(id);
@@ -135,12 +135,10 @@ public class WiredEffectFurniByType extends InteractionWiredEffect {
@Override
public void serializeWiredData(ServerMessage message, Room room) {
boolean pickMode = (sourceType == SOURCE_FURNI_PICKED);
message.appendBoolean(true);
message.appendInt(MAX_PICKED_FURNI);
message.appendBoolean(pickMode);
message.appendInt(pickMode ? MAX_PICKED_FURNI : 0);
if (pickMode && !pickedFurniIds.isEmpty()) {
if (!pickedFurniIds.isEmpty()) {
message.appendInt(pickedFurniIds.size());
pickedFurniIds.forEach(message::appendInt);
} else {
@@ -152,7 +150,7 @@ public class WiredEffectFurniByType extends InteractionWiredEffect {
message.appendString("");
message.appendInt(4);
message.appendInt(sourceType);
message.appendInt(SOURCE_FURNI_PICKED);
message.appendInt(matchState ? 1 : 0);
message.appendInt(filterExisting ? 1 : 0);
message.appendInt(invert ? 1 : 0);
@@ -1,5 +1,6 @@
package com.eu.habbo.habbohotel.items.interactions.wired.triggers;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
@@ -11,27 +12,49 @@ import com.eu.habbo.habbohotel.wired.WiredTriggerType;
import com.eu.habbo.habbohotel.wired.core.WiredEvent;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredTriggerSaveException;
import com.eu.habbo.messages.outgoing.rooms.items.ItemStateComposer;
import gnu.trove.set.hash.THashSet;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
public class WiredTriggerReceiveSignal extends InteractionWiredTrigger {
public static final WiredTriggerType type = WiredTriggerType.RECEIVE_SIGNAL;
private static final String ANTENNA_INTERACTION = "antenna";
private static final long ACTIVATION_PULSE_MS = 300L;
private int channel = 0; // signal channel (0-based)
private THashSet<HabboItem> items;
private final AtomicLong activationToken = new AtomicLong();
public WiredTriggerReceiveSignal(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
this.items = new THashSet<>();
}
public WiredTriggerReceiveSignal(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
this.items = new THashSet<>();
}
@Override
public boolean matches(HabboItem triggerItem, WiredEvent event) {
return event.getType() == WiredEvent.Type.SIGNAL_RECEIVED
&& event.getSignalChannel() == this.channel;
if (event.getType() != WiredEvent.Type.SIGNAL_RECEIVED) return false;
if (!this.items.isEmpty()) {
int signalChannel = event.getSignalChannel();
for (HabboItem antenna : this.items) {
if (antenna != null && antenna.getId() == signalChannel) return true;
}
return false;
}
return event.getSignalChannel() == this.channel;
}
public int getChannel() {
@@ -59,14 +82,33 @@ public class WiredTriggerReceiveSignal extends InteractionWiredTrigger {
int senderCount = 0;
try {
if (room != null && room.getRoomSpecialTypes() != null) {
senderCount = room.getRoomSpecialTypes().countSendersTargetingReceiver(this.getId());
if (!this.items.isEmpty()) {
for (HabboItem item : this.items) {
senderCount += room.getRoomSpecialTypes().countSendersTargetingReceiver(item.getId());
}
} else {
senderCount = room.getRoomSpecialTypes().countSendersTargetingReceiver(this.getId());
}
}
} catch (Exception e) {
}
THashSet<HabboItem> itemsToRemove = new THashSet<>();
for (HabboItem item : this.items) {
if (item.getRoomId() != this.getRoomId() || room.getHabboItem(item.getId()) == null) {
itemsToRemove.add(item);
}
}
for (HabboItem item : itemsToRemove) {
this.items.remove(item);
}
message.appendBoolean(false);
message.appendInt(0);
message.appendInt(0);
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
message.appendInt(this.items.size());
for (HabboItem item : this.items) {
message.appendInt(item.getId());
}
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString("");
@@ -82,37 +124,100 @@ public class WiredTriggerReceiveSignal extends InteractionWiredTrigger {
@Override
public boolean saveData(WiredSettings settings) {
this.items.clear();
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
int count = settings.getFurniIds().length;
for (int i = 0; i < count; i++) {
HabboItem item = room.getHabboItem(settings.getFurniIds()[i]);
if (item == null) continue;
if (!isAntennaItem(item)) throw new WiredTriggerSaveException("wiredfurni.error.require_antenna_furni");
this.items.add(item);
}
int[] params = settings.getIntParams();
this.channel = params.length > 0 ? params[0] : 0;
return true;
}
@Override
public void activateBox(Room room, RoomUnit roomUnit, long millis) {
if (roomUnit != null) {
this.addUserExecutionCache(roomUnit.getId(), millis);
}
if (room == null || room.isHideWired() || this.getBaseItem().getStateCount() <= 1) {
return;
}
final long token = this.activationToken.incrementAndGet();
if ("1".equals(this.getExtradata())) {
this.setExtradata("0");
room.sendComposer(new ItemStateComposer(this).compose());
}
this.setExtradata("1");
room.sendComposer(new ItemStateComposer(this).compose());
Emulator.getThreading().run(() -> {
if (!room.isLoaded()) return;
if (this.activationToken.get() != token) return;
this.setExtradata("0");
room.sendComposer(new ItemStateComposer(this).compose());
}, ACTIVATION_PULSE_MS);
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(channel));
return WiredManager.getGson().toJson(new JsonData(
channel,
this.items.stream().map(HabboItem::getId).collect(Collectors.toList())
));
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.items = new THashSet<>();
String wiredData = set.getString("wired_data");
if (wiredData != null && wiredData.startsWith("{")) {
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
this.channel = data.channel;
if (data.itemIds != null) {
for (Integer id : data.itemIds) {
HabboItem item = room.getHabboItem(id);
if (item != null) this.items.add(item);
}
}
}
}
@Override
public void onPickUp() {
this.channel = 0;
this.items.clear();
}
private boolean isAntennaItem(HabboItem item) {
if (item == null || item.getBaseItem() == null || item.getBaseItem().getInteractionType() == null) return false;
String interaction = item.getBaseItem().getInteractionType().getName();
if (interaction == null) return false;
String normalized = interaction.toLowerCase();
return normalized.equals(ANTENNA_INTERACTION);
}
static class JsonData {
int channel;
List<Integer> itemIds;
public JsonData() {}
public JsonData(int channel) {
public JsonData(int channel, List<Integer> itemIds) {
this.channel = channel;
this.itemIds = itemIds;
}
}
}
@@ -214,24 +214,16 @@ public final class WiredEngine {
// Initial step for trigger
state.step();
// Activate the trigger box animation
if (stack.triggerItem() instanceof InteractionWiredTrigger) {
InteractionWiredTrigger trigger = (InteractionWiredTrigger) stack.triggerItem();
trigger.activateBox(room, event.getActor().orElse(null), currentTime);
}
debug(room, "Trigger matched: {} at item {} (conditions: {}, effects: {})",
event.getType(),
stack.triggerItem() != null ? stack.triggerItem().getId() : "null",
stack.conditions().size(),
stack.effects().size());
// Activate extras (for their animation)
activateExtras(room, stack.triggerItem(), event.getActor().orElse(null), currentTime);
// Run selectors before conditions so targets are available
List<InteractionWiredEffect> executedSelectors = Collections.emptyList();
if (stack.hasEffects()) {
executeSelectors(stack, ctx, currentTime);
executedSelectors = executeSelectors(stack, ctx);
}
// Evaluate conditions
@@ -253,6 +245,17 @@ public final class WiredEngine {
return false;
}
RoomUnit actor = event.getActor().orElse(null);
// Only show the trigger/selector activation when the stack is actually allowed to continue.
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);
// Execute effects
if (stack.hasEffects()) {
executeEffects(stack, ctx, currentTime);
@@ -420,9 +423,11 @@ public final class WiredEngine {
/**
* Execute selector effects before conditions so ctx.targets() is populated.
*/
private void executeSelectors(WiredStack stack, WiredContext ctx, long currentTime) {
private List<InteractionWiredEffect> executeSelectors(WiredStack stack, WiredContext ctx) {
List<IWiredEffect> effects = stack.effects();
if (effects.isEmpty()) return;
if (effects.isEmpty()) return Collections.emptyList();
List<InteractionWiredEffect> executedSelectors = new ArrayList<>();
for (IWiredEffect effect : effects) {
if (!effect.isSelector()) continue;
@@ -433,16 +438,29 @@ public final class WiredEngine {
ctx.state().step();
try {
effect.execute(ctx);
if (effect instanceof InteractionWiredEffect) {
InteractionWiredEffect wiredEffect = (InteractionWiredEffect) effect;
wiredEffect.setCooldown(currentTime);
wiredEffect.activateBox(ctx.room(), ctx.actor().orElse(null), currentTime);
executedSelectors.add((InteractionWiredEffect) effect);
}
} catch (Exception e) {
LOGGER.warn("Error executing selector: {}", e.getMessage());
}
}
return executedSelectors;
}
private void finalizeSelectors(List<InteractionWiredEffect> executedSelectors, WiredContext ctx, long currentTime) {
if (executedSelectors == null || executedSelectors.isEmpty()) {
return;
}
Room room = ctx.room();
RoomUnit actor = ctx.actor().orElse(null);
for (InteractionWiredEffect wiredEffect : executedSelectors) {
wiredEffect.setCooldown(currentTime);
wiredEffect.activateBox(room, actor, currentTime);
}
}
/**
@@ -34,17 +34,21 @@ public class WiredTriggerSaveDataEvent extends MessageHandler {
if (saveMethod.get().getParameterTypes()[0] == WiredSettings.class) {
WiredSettings settings = InteractionWired.readSettings(this.packet, false);
if (trigger.saveData(settings)) {
this.client.sendResponse(new WiredSavedComposer());
try {
if (trigger.saveData(settings)) {
this.client.sendResponse(new WiredSavedComposer());
trigger.needsUpdate(true);
trigger.needsUpdate(true);
Emulator.getThreading().run(trigger);
// Invalidate wired cache when trigger is saved
WiredManager.invalidateRoom(room);
} else {
this.client.sendResponse(new UpdateFailedComposer("There was an error while saving that trigger"));
Emulator.getThreading().run(trigger);
// Invalidate wired cache when trigger is saved
WiredManager.invalidateRoom(room);
} else {
this.client.sendResponse(new UpdateFailedComposer("There was an error while saving that trigger"));
}
} catch (WiredTriggerSaveException e) {
this.client.sendResponse(new UpdateFailedComposer(e.getMessage()));
}
} else {
if ((boolean) saveMethod.get().invoke(trigger, this.packet)) {
@@ -0,0 +1,7 @@
package com.eu.habbo.messages.incoming.wired;
public class WiredTriggerSaveException extends RuntimeException {
public WiredTriggerSaveException(String message) {
super(message);
}
}