feat: add advanced wired variable system and tooling

This commit is contained in:
Lorenzune
2026-04-02 04:44:04 +02:00
parent a43fa87f4c
commit 9dc77aebf7
99 changed files with 22169 additions and 204 deletions
@@ -17,14 +17,18 @@ import java.util.Properties;
public class ConfigurationManager {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigurationManager.class);
private static final String EMULATOR_SETTINGS_TABLE = "emulator_settings";
private static final String WIRED_SETTINGS_TABLE = "wired_emulator_settings";
private final Properties properties;
private final Properties wiredProperties;
private final String configurationPath;
public boolean loaded = false;
public boolean isLoading = false;
public ConfigurationManager(String configurationPath) {
this.properties = new Properties();
this.wiredProperties = new Properties();
this.configurationPath = configurationPath;
this.reload();
}
@@ -32,6 +36,7 @@ public class ConfigurationManager {
public void reload() {
this.isLoading = true;
this.properties.clear();
this.wiredProperties.clear();
InputStream input = null;
@@ -116,31 +121,15 @@ public class ConfigurationManager {
LOGGER.info("Loading configuration from database...");
long millis = System.currentTimeMillis();
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); Statement statement = connection.createStatement()) {
if (statement.execute("SELECT * FROM emulator_settings")) {
try (ResultSet set = statement.getResultSet()) {
while (set.next()) {
this.properties.put(set.getString("key"), set.getString("value"));
}
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
this.loadSettingsTable(EMULATOR_SETTINGS_TABLE, this.properties, false);
this.loadSettingsTable(WIRED_SETTINGS_TABLE, this.wiredProperties, true);
LOGGER.info("Configuration -> loaded! ({} MS)", System.currentTimeMillis() - millis);
}
public void saveToDatabase() {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE emulator_settings SET `value` = ? WHERE `key` = ? LIMIT 1")) {
for (Map.Entry<Object, Object> entry : this.properties.entrySet()) {
statement.setString(1, entry.getValue().toString());
statement.setString(2, entry.getKey().toString());
statement.executeUpdate();
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
this.saveSettingsTable(EMULATOR_SETTINGS_TABLE, this.properties);
this.saveSettingsTable(WIRED_SETTINGS_TABLE, this.wiredProperties);
}
@@ -153,10 +142,21 @@ public class ConfigurationManager {
if (this.isLoading)
return defaultValue;
if (!this.properties.containsKey(key)) {
Properties targetProperties = this.resolveProperties(key);
if (targetProperties.containsKey(key)) {
return targetProperties.getProperty(key, defaultValue);
}
if (this.isWiredSettingKey(key) && this.properties.containsKey(key)) {
return this.properties.getProperty(key, defaultValue);
}
if (!targetProperties.containsKey(key)) {
LOGGER.error("Config key not found {}", key);
}
return this.properties.getProperty(key, defaultValue);
return defaultValue;
}
public boolean getBoolean(String key) {
@@ -209,21 +209,91 @@ public class ConfigurationManager {
}
public void update(String key, String value) {
this.properties.setProperty(key, value);
this.resolveProperties(key).setProperty(key, value);
}
public void register(String key, String value) {
if (this.properties.getProperty(key, null) != null)
this.register(key, value, "");
}
public void register(String key, String value, String comment) {
Properties targetProperties = this.resolveProperties(key);
if (targetProperties.getProperty(key, null) != null)
return;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO emulator_settings VALUES (?, ?)")) {
statement.setString(1, key);
statement.setString(2, value);
statement.execute();
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
this.insertSetting(key, value, comment);
this.update(key, value);
}
private void loadSettingsTable(String tableName, Properties targetProperties, boolean optional) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
Statement statement = connection.createStatement()) {
if (statement.execute("SELECT * FROM " + tableName)) {
try (ResultSet set = statement.getResultSet()) {
while (set.next()) {
targetProperties.put(set.getString("key"), set.getString("value"));
}
}
}
} catch (SQLException e) {
if (optional) {
LOGGER.warn("Skipping optional config table {}: {}", tableName, e.getMessage());
} else {
LOGGER.error("Caught SQL exception", e);
}
}
}
private void saveSettingsTable(String tableName, Properties sourceProperties) {
String sql = "UPDATE " + tableName + " SET `value` = ? WHERE `key` = ? LIMIT 1";
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(sql)) {
for (Map.Entry<Object, Object> entry : sourceProperties.entrySet()) {
statement.setString(1, entry.getValue().toString());
statement.setString(2, entry.getKey().toString());
statement.executeUpdate();
}
} catch (SQLException e) {
if (WIRED_SETTINGS_TABLE.equals(tableName)) {
LOGGER.warn("Skipping wired config save for table {}: {}", tableName, e.getMessage());
} else {
LOGGER.error("Caught SQL exception", e);
}
}
}
private void insertSetting(String key, String value, String comment) {
String tableName = this.isWiredSettingKey(key) ? WIRED_SETTINGS_TABLE : EMULATOR_SETTINGS_TABLE;
String sql = this.isWiredSettingKey(key)
? "INSERT INTO " + tableName + " (`key`, `value`, `comment`) VALUES (?, ?, ?)"
: "INSERT INTO " + tableName + " (`key`, `value`) VALUES (?, ?)";
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setString(1, key);
statement.setString(2, value);
if (this.isWiredSettingKey(key)) {
statement.setString(3, comment == null ? "" : comment);
}
statement.execute();
} catch (SQLException e) {
if (this.isWiredSettingKey(key)) {
LOGGER.warn("Unable to insert wired setting {} into {}: {}", key, tableName, e.getMessage());
} else {
LOGGER.error("Caught SQL exception", e);
}
}
}
private Properties resolveProperties(String key) {
return this.isWiredSettingKey(key) ? this.wiredProperties : this.properties;
}
private boolean isWiredSettingKey(String key) {
return key != null && (key.startsWith("wired.") || key.startsWith("hotel.wired."));
}
}
@@ -1,14 +1,13 @@
package com.eu.habbo.habbohotel.commands;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles;
import com.eu.habbo.messages.outgoing.users.InClientLinkComposer;
public class WiredCommand extends Command {
public WiredCommand() {
super(Permission.ACC_PLACEFURNI, new String[]{"wired"});
super(null, new String[]{"wired"});
}
@Override
@@ -20,12 +19,8 @@ public class WiredCommand extends Command {
return true;
}
boolean hasRights = room.hasRights(gameClient.getHabbo())
|| room.isOwner(gameClient.getHabbo())
|| gameClient.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER);
if (!hasRights) {
gameClient.getHabbo().whisper("You need room rights to open the Wired Creator Tools.", RoomChatMessageBubbles.ALERT);
if (!room.canInspectWired(gameClient.getHabbo())) {
gameClient.sendResponse(new InClientLinkComposer("wired-tools/invalid"));
return true;
}
@@ -52,7 +52,10 @@ import com.eu.habbo.habbohotel.items.interactions.wired.effects.*;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredBlob;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraAnimationTime;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterFurni;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterFurniByVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterUser;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterUsersByVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFurniVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraMoveCarryUsers;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraExecuteInOrder;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraExecutionLimit;
@@ -60,9 +63,18 @@ import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraMovePhys
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraMoveNoAnimation;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraOrEval;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRandom;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRoomVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraTextOutputFurniName;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraTextInputVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraTextOutputUsername;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraTextOutputVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraContextVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraUserVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableEcho;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraUnseen;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableReference;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableLevelUpSystem;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableTextConnector;
import com.eu.habbo.habbohotel.items.interactions.wired.selector.*;
import com.eu.habbo.habbohotel.items.interactions.wired.triggers.*;
import com.eu.habbo.habbohotel.users.Habbo;
@@ -225,6 +237,7 @@ public class ItemManager {
this.interactionsList.add(new ItemInteraction("wf_trg_leave_room", WiredTriggerHabboLeavesRoom.class));
this.interactionsList.add(new ItemInteraction("wf_trg_says_something", WiredTriggerHabboSaysKeyword.class));
this.interactionsList.add(new ItemInteraction("wf_trg_clock_counter", WiredTriggerClockCounter.class));
this.interactionsList.add(new ItemInteraction("wf_trg_var_changed", WiredTriggerVariableChanged.class));
this.interactionsList.add(new ItemInteraction("wf_trg_periodically", WiredTriggerRepeater.class));
this.interactionsList.add(new ItemInteraction("wf_trg_period_short", WiredTriggerRepeaterShort.class));
this.interactionsList.add(new ItemInteraction("wf_trg_period_long", WiredTriggerRepeaterLong.class));
@@ -300,7 +313,12 @@ public class ItemManager {
this.interactionsList.add(new ItemInteraction("wf_slc_users_handitem", WiredEffectUsersHandItem.class));
this.interactionsList.add(new ItemInteraction("wf_slc_users_onfurni", WiredEffectUsersOnFurni.class));
this.interactionsList.add(new ItemInteraction("wf_slc_users_group", WiredEffectUsersGroup.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_act_send_signal", WiredEffectSendSignal.class));
this.interactionsList.add(new ItemInteraction("wf_act_give_var", WiredEffectGiveVariable.class));
this.interactionsList.add(new ItemInteraction("wf_act_remove_var", WiredEffectRemoveVariable.class));
this.interactionsList.add(new ItemInteraction("wf_act_change_var_val", WiredEffectChangeVariableValue.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_has_furni_on", WiredConditionFurniHaveFurni.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_furnis_hv_avtrs", WiredConditionFurniHaveHabbo.class));
@@ -340,6 +358,10 @@ public class ItemManager {
this.interactionsList.add(new ItemInteraction("wf_cnd_not_triggerer_match", WiredConditionNotTriggererMatch.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_team_has_score", WiredConditionTeamHasScore.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_team_has_rank", WiredConditionTeamHasRank.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_has_var", WiredConditionHasVariable.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_neg_has_var", WiredConditionNotHasVariable.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_var_val_match", WiredConditionVariableValueMatch.class));
this.interactionsList.add(new ItemInteraction("wf_cnd_var_age_match", WiredConditionVariableAgeMatch.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_random", WiredExtraRandom.class));
@@ -349,6 +371,8 @@ public class ItemManager {
this.interactionsList.add(new ItemInteraction("wf_xtra_filter_furni", WiredExtraFilterFurni.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_filter_user", WiredExtraFilterUser.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_filter_users", WiredExtraFilterUser.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_filter_furni_by_var", WiredExtraFilterFurniByVariable.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_filter_users_by_var", WiredExtraFilterUsersByVariable.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_mov_carry_users", WiredExtraMoveCarryUsers.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_mov_no_animation", WiredExtraMoveNoAnimation.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_anim_time", WiredExtraAnimationTime.class));
@@ -357,6 +381,16 @@ public class ItemManager {
this.interactionsList.add(new ItemInteraction("wf_xtra_execution_limit", WiredExtraExecutionLimit.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_text_output_username", WiredExtraTextOutputUsername.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_text_output_furni_name", WiredExtraTextOutputFurniName.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_text_output_variable", WiredExtraTextOutputVariable.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_text_input_variable", WiredExtraTextInputVariable.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_var_text_connector", WiredExtraVariableTextConnector.class));
this.interactionsList.add(new ItemInteraction("wf_xtra_var_lvlup_system", WiredExtraVariableLevelUpSystem.class));
this.interactionsList.add(new ItemInteraction("wf_var_user", WiredExtraUserVariable.class));
this.interactionsList.add(new ItemInteraction("wf_var_furni", WiredExtraFurniVariable.class));
this.interactionsList.add(new ItemInteraction("wf_var_room", WiredExtraRoomVariable.class));
this.interactionsList.add(new ItemInteraction("wf_var_context", WiredExtraContextVariable.class));
this.interactionsList.add(new ItemInteraction("wf_var_reference", WiredExtraVariableReference.class));
this.interactionsList.add(new ItemInteraction("wf_var_echo", WiredExtraVariableEcho.class));
this.interactionsList.add(new ItemInteraction("wf_highscore", InteractionWiredHighscore.class));
@@ -43,7 +43,7 @@ public abstract class InteractionWiredCondition extends InteractionWired impleme
@Override
public void onClick(GameClient client, Room room, Object[] objects) throws Exception {
if (client != null) {
if (room.hasRights(client.getHabbo())) {
if (room.canInspectWired(client.getHabbo())) {
client.sendResponse(new WiredConditionDataComposer(this, room));
this.activateBox(room);
}
@@ -80,7 +80,7 @@ public abstract class InteractionWiredEffect extends InteractionWired implements
@Override
public void onClick(GameClient client, Room room, Object[] objects) throws Exception {
if (client != null) {
if (room.hasRights(client.getHabbo())) {
if (room.canInspectWired(client.getHabbo())) {
client.sendResponse(new WiredEffectDataComposer(this, room));
this.activateBox(room);
}
@@ -23,7 +23,7 @@ public abstract class InteractionWiredExtra extends InteractionWired {
@Override
public void onClick(GameClient client, Room room, Object[] objects) throws Exception {
if (client != null) {
if (room.hasRights(client.getHabbo())) {
if (room.canInspectWired(client.getHabbo())) {
if (this.hasConfiguration()) {
client.sendResponse(new WiredExtraDataComposer(this, room));
}
@@ -45,7 +45,7 @@ public abstract class InteractionWiredTrigger extends InteractionWired implement
@Override
public void onClick(GameClient client, Room room, Object[] objects) throws Exception {
if (client != null) {
if (room.hasRights(client.getHabbo())) {
if (room.canInspectWired(client.getHabbo())) {
client.sendResponse(new WiredTriggerDataComposer(this, room));
this.activateBox(room);
}
@@ -0,0 +1,506 @@
package com.eu.habbo.habbohotel.items.interactions.wired.conditions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.bots.Bot;
import com.eu.habbo.habbohotel.items.FurnitureType;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredCondition;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomRightLevels;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.users.DanceType;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.wired.WiredConditionType;
import com.eu.habbo.habbohotel.wired.core.WiredContext;
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil;
import com.eu.habbo.habbohotel.wired.core.WiredInternalVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
import com.eu.habbo.messages.ServerMessage;
import gnu.trove.set.hash.THashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class WiredConditionHasVariable extends InteractionWiredCondition {
private static final Logger LOGGER = LoggerFactory.getLogger(WiredConditionHasVariable.class);
protected static final int TARGET_USER = 0;
protected static final int TARGET_FURNI = 1;
protected static final int TARGET_CONTEXT = 2;
protected static final int TARGET_ROOM = 3;
protected static final int QUANTIFIER_ALL = 0;
protected static final int QUANTIFIER_ANY = 1;
private static final String CUSTOM_TOKEN_PREFIX = "custom:";
private static final String INTERNAL_TOKEN_PREFIX = "internal:";
public static final WiredConditionType type = WiredConditionType.HAS_VAR;
protected final THashSet<HabboItem> selectedItems = new THashSet<>();
protected int targetType = TARGET_USER;
protected int userSource = WiredSourceUtil.SOURCE_TRIGGER;
protected int furniSource = WiredSourceUtil.SOURCE_TRIGGER;
protected int quantifier = QUANTIFIER_ALL;
protected String variableToken = "";
protected int variableItemId = 0;
public WiredConditionHasVariable(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredConditionHasVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public WiredConditionType getType() {
return type;
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
this.refresh();
List<HabboItem> serializedItems = new ArrayList<>();
if (this.targetType == TARGET_FURNI && this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
serializedItems.addAll(this.selectedItems);
}
message.appendBoolean(false);
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
message.appendInt(serializedItems.size());
for (HabboItem item : serializedItems) {
message.appendInt(item.getId());
}
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(this.variableToken == null ? "" : this.variableToken);
message.appendInt(4);
message.appendInt(this.targetType);
message.appendInt(this.userSource);
message.appendInt(this.furniSource);
message.appendInt(this.quantifier);
message.appendInt(0);
message.appendInt(this.getType().code);
message.appendInt(0);
message.appendInt(0);
}
@Override
public boolean saveData(WiredSettings settings) {
int[] params = settings.getIntParams();
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
this.targetType = (params.length > 0) ? normalizeTargetType(params[0]) : TARGET_USER;
this.userSource = (params.length > 1) ? normalizeUserSource(params[1]) : WiredSourceUtil.SOURCE_TRIGGER;
this.furniSource = (params.length > 2) ? normalizeFurniSource(params[2]) : WiredSourceUtil.SOURCE_TRIGGER;
this.quantifier = (params.length > 3) ? normalizeQuantifier(params[3]) : QUANTIFIER_ALL;
this.setVariableToken(normalizeVariableToken(settings.getStringParam()));
if (this.variableToken.isEmpty()) {
return false;
}
this.selectedItems.clear();
if (this.targetType == TARGET_FURNI && this.furniSource == WiredSourceUtil.SOURCE_SELECTED && room != null) {
int[] furniIds = settings.getFurniIds();
if (furniIds.length > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) {
return false;
}
for (int furniId : furniIds) {
HabboItem item = room.getHabboItem(furniId);
if (item != null) {
this.selectedItems.add(item);
}
}
}
return true;
}
@Override
public boolean evaluate(WiredContext ctx) {
return this.evaluateWithNegation(ctx, false);
}
protected boolean evaluateWithNegation(WiredContext ctx, boolean negative) {
Room room = ctx.room();
if (room == null || this.variableToken == null || this.variableToken.isEmpty()) {
return false;
}
return switch (this.targetType) {
case TARGET_FURNI -> this.evaluateFurniTargets(ctx, room, negative);
case TARGET_CONTEXT -> {
boolean contextMatch = this.matchesContext(ctx, room);
yield negative ? !contextMatch : contextMatch;
}
case TARGET_ROOM -> {
boolean roomMatch = this.matchesRoom(room);
yield negative ? !roomMatch : roomMatch;
}
default -> this.evaluateUserTargets(ctx, room, negative);
};
}
private boolean evaluateUserTargets(WiredContext ctx, Room room, boolean negative) {
List<RoomUnit> targets = WiredSourceUtil.resolveUsers(ctx, this.userSource);
if (targets.isEmpty()) return false;
boolean match = (this.quantifier == QUANTIFIER_ANY)
? this.matchesAnyUser(room, targets)
: this.matchesAllUsers(room, targets);
return negative ? !match : match;
}
private boolean evaluateFurniTargets(WiredContext ctx, Room room, boolean negative) {
this.refresh();
List<HabboItem> targets = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.selectedItems);
if (targets.isEmpty()) return false;
boolean match = (this.quantifier == QUANTIFIER_ANY)
? this.matchesAnyFurni(room, targets)
: this.matchesAllFurni(room, targets);
return negative ? !match : match;
}
@Deprecated
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public String getWiredData() {
this.refresh();
List<Integer> itemIds = new ArrayList<>();
for (HabboItem item : this.selectedItems) {
if (item != null) itemIds.add(item.getId());
}
return WiredManager.getGson().toJson(new JsonData(
itemIds,
this.targetType,
this.variableToken,
this.variableItemId,
this.userSource,
this.furniSource,
this.quantifier
));
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty()) return;
try {
if (wiredData.startsWith("{")) {
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data == null) return;
this.targetType = normalizeTargetType(data.targetType);
this.userSource = normalizeUserSource(data.userSource);
this.furniSource = normalizeFurniSource(data.furniSource);
this.quantifier = normalizeQuantifier(data.quantifier);
this.setVariableToken(normalizeVariableToken((data.variableToken != null) ? data.variableToken : ((data.variableItemId > 0) ? String.valueOf(data.variableItemId) : "")));
if (room != null && data.itemIds != null) {
for (Integer itemId : data.itemIds) {
if (itemId == null || itemId <= 0) continue;
HabboItem item = room.getHabboItem(itemId);
if (item != null) this.selectedItems.add(item);
}
}
return;
}
this.setVariableToken(normalizeVariableToken(wiredData));
} catch (Exception e) {
LOGGER.error("Failed to load wired variable condition data for item {}", this.getId(), e);
this.onPickUp();
}
}
@Override
public void onPickUp() {
this.targetType = TARGET_USER;
this.userSource = WiredSourceUtil.SOURCE_TRIGGER;
this.furniSource = WiredSourceUtil.SOURCE_TRIGGER;
this.quantifier = QUANTIFIER_ALL;
this.selectedItems.clear();
this.setVariableToken("");
}
protected boolean matchesAnyUser(Room room, List<RoomUnit> targets) {
for (RoomUnit roomUnit : targets) {
if (this.matchesUser(room, roomUnit)) {
return true;
}
}
return false;
}
protected boolean matchesAllUsers(Room room, List<RoomUnit> targets) {
for (RoomUnit roomUnit : targets) {
if (!this.matchesUser(room, roomUnit)) {
return false;
}
}
return true;
}
protected boolean matchesAnyFurni(Room room, List<HabboItem> targets) {
for (HabboItem item : targets) {
if (this.matchesFurni(room, item)) {
return true;
}
}
return false;
}
protected boolean matchesAllFurni(Room room, List<HabboItem> targets) {
for (HabboItem item : targets) {
if (!this.matchesFurni(room, item)) {
return false;
}
}
return true;
}
protected boolean matchesUser(Room room, RoomUnit roomUnit) {
if (room == null || roomUnit == null) {
return false;
}
if (isCustomVariableToken(this.variableToken)) {
Habbo habbo = room.getHabbo(roomUnit);
return habbo != null && room.getUserVariableManager().hasVariable(habbo.getHabboInfo().getId(), this.variableItemId);
}
if (isInternalVariableToken(this.variableToken)) {
return this.hasUserInternalVariable(room, roomUnit, getInternalVariableKey(this.variableToken));
}
return false;
}
protected boolean matchesFurni(Room room, HabboItem item) {
if (room == null || item == null) {
return false;
}
if (isCustomVariableToken(this.variableToken)) {
return room.getFurniVariableManager().hasVariable(item.getId(), this.variableItemId);
}
if (isInternalVariableToken(this.variableToken)) {
return this.hasFurniInternalVariable(item, getInternalVariableKey(this.variableToken));
}
return false;
}
protected boolean matchesContext(WiredContext ctx, Room room) {
if (ctx == null || room == null) {
return false;
}
if (isCustomVariableToken(this.variableToken)) {
return WiredContextVariableSupport.hasVariable(ctx, this.variableItemId);
}
if (isInternalVariableToken(this.variableToken)) {
return WiredInternalVariableSupport.readContextValue(ctx, getInternalVariableKey(this.variableToken)) != null;
}
return false;
}
protected boolean matchesRoom(Room room) {
if (room == null) {
return false;
}
if (isCustomVariableToken(this.variableToken)) {
return room.getRoomVariableManager().hasVariable(this.variableItemId);
}
if (isInternalVariableToken(this.variableToken)) {
return this.hasRoomInternalVariable(getInternalVariableKey(this.variableToken));
}
return false;
}
protected boolean hasUserInternalVariable(Room room, RoomUnit roomUnit, String key) {
return WiredInternalVariableSupport.hasUserValue(room, roomUnit, key);
}
protected boolean hasFurniInternalVariable(HabboItem item, String key) {
return WiredInternalVariableSupport.hasFurniValue(item, key);
}
protected boolean hasRoomInternalVariable(String key) {
return WiredInternalVariableSupport.hasRoomValue(Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()), key);
}
protected void refresh() {
THashSet<HabboItem> staleItems = new THashSet<>();
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room == null) {
staleItems.addAll(this.selectedItems);
} else {
for (HabboItem item : this.selectedItems) {
if (item == null || item.getRoomId() != room.getId()) {
staleItems.add(item);
}
}
}
this.selectedItems.removeAll(staleItems);
}
protected void setVariableToken(String token) {
this.variableToken = normalizeVariableToken(token);
this.variableItemId = getCustomItemId(this.variableToken);
}
protected boolean hasRoomEntryMethod(Habbo habbo) {
if (habbo == null) return false;
String roomEntryMethod = habbo.getHabboInfo().getRoomEntryMethod();
return roomEntryMethod != null && !roomEntryMethod.trim().isEmpty() && !"unknown".equalsIgnoreCase(roomEntryMethod);
}
protected TeamEffectData getTeamEffectData(int effectValue) {
if (effectValue <= 0) return null;
if (effectValue >= 223 && effectValue <= 226) return new TeamEffectData(effectValue - 222, 0);
if (effectValue >= 33 && effectValue <= 36) return new TeamEffectData(effectValue - 32, 1);
if (effectValue >= 40 && effectValue <= 43) return new TeamEffectData(effectValue - 39, 2);
return null;
}
protected static int normalizeTargetType(int value) {
return switch (value) {
case TARGET_FURNI, TARGET_CONTEXT, TARGET_ROOM -> value;
default -> TARGET_USER;
};
}
protected static int normalizeQuantifier(int value) {
return (value == QUANTIFIER_ANY) ? QUANTIFIER_ANY : QUANTIFIER_ALL;
}
protected static int normalizeUserSource(int value) {
return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
}
protected static int normalizeFurniSource(int value) {
return switch (value) {
case WiredSourceUtil.SOURCE_SELECTED, WiredSourceUtil.SOURCE_SELECTOR, WiredSourceUtil.SOURCE_SIGNAL -> value;
default -> WiredSourceUtil.SOURCE_TRIGGER;
};
}
protected static boolean isCustomVariableToken(String token) {
return token != null && token.startsWith(CUSTOM_TOKEN_PREFIX);
}
protected static boolean isInternalVariableToken(String token) {
return token != null && token.startsWith(INTERNAL_TOKEN_PREFIX);
}
protected static int getCustomItemId(String token) {
if (!isCustomVariableToken(token)) return 0;
try {
return Integer.parseInt(token.substring(CUSTOM_TOKEN_PREFIX.length()));
} catch (NumberFormatException e) {
return 0;
}
}
protected static String getInternalVariableKey(String token) {
return isInternalVariableToken(token) ? WiredInternalVariableSupport.normalizeKey(token.substring(INTERNAL_TOKEN_PREFIX.length())) : "";
}
protected static String normalizeVariableToken(String token) {
if (token == null) return "";
String normalized = token.trim();
if (normalized.isEmpty()) return "";
if (isCustomVariableToken(normalized)) return normalized;
if (isInternalVariableToken(normalized)) return INTERNAL_TOKEN_PREFIX + WiredInternalVariableSupport.normalizeKey(normalized.substring(INTERNAL_TOKEN_PREFIX.length()));
try {
int parsed = Integer.parseInt(normalized);
return (parsed > 0) ? (CUSTOM_TOKEN_PREFIX + parsed) : "";
} catch (NumberFormatException e) {
return "";
}
}
protected static class JsonData {
List<Integer> itemIds;
int targetType;
String variableToken;
int variableItemId;
int userSource;
int furniSource;
int quantifier;
public JsonData(List<Integer> itemIds, int targetType, String variableToken, int variableItemId, int userSource, int furniSource, int quantifier) {
this.itemIds = itemIds;
this.targetType = targetType;
this.variableToken = variableToken;
this.variableItemId = variableItemId;
this.userSource = userSource;
this.furniSource = furniSource;
this.quantifier = quantifier;
}
}
protected static class TeamEffectData {
final int colorId;
final int typeId;
protected TeamEffectData(int colorId, int typeId) {
this.colorId = colorId;
this.typeId = typeId;
}
}
}
@@ -0,0 +1,30 @@
package com.eu.habbo.habbohotel.items.interactions.wired.conditions;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.wired.WiredConditionType;
import com.eu.habbo.habbohotel.wired.core.WiredContext;
import java.sql.ResultSet;
import java.sql.SQLException;
public class WiredConditionNotHasVariable extends WiredConditionHasVariable {
public static final WiredConditionType type = WiredConditionType.NOT_HAS_VAR;
public WiredConditionNotHasVariable(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredConditionNotHasVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public WiredConditionType getType() {
return type;
}
@Override
public boolean evaluate(WiredContext ctx) {
return this.evaluateWithNegation(ctx, true);
}
}
@@ -0,0 +1,420 @@
package com.eu.habbo.habbohotel.items.interactions.wired.conditions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.wired.WiredConditionType;
import com.eu.habbo.habbohotel.wired.core.WiredContext;
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
import com.eu.habbo.messages.ServerMessage;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class WiredConditionVariableAgeMatch extends WiredConditionHasVariable {
public static final WiredConditionType type = WiredConditionType.VAR_AGE_MATCH;
private static final int TARGET_CONTEXT = 2;
private static final int COMPARE_VALUE_CREATED = 0;
private static final int COMPARE_VALUE_UPDATED = 1;
private static final int COMPARISON_LOWER_THAN = 0;
private static final int COMPARISON_HIGHER_THAN = 2;
private static final int DURATION_UNIT_MILLISECONDS = 0;
private static final int DURATION_UNIT_SECONDS = 1;
private static final int DURATION_UNIT_MINUTES = 2;
private static final int DURATION_UNIT_HOURS = 3;
private static final int DURATION_UNIT_DAYS = 4;
private static final int DURATION_UNIT_WEEKS = 5;
private static final int DURATION_UNIT_MONTHS = 6;
private static final int DURATION_UNIT_YEARS = 7;
protected int compareValue = COMPARE_VALUE_CREATED;
protected int comparison = COMPARISON_LOWER_THAN;
protected int durationAmount = 0;
protected int durationUnit = DURATION_UNIT_SECONDS;
public WiredConditionVariableAgeMatch(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredConditionVariableAgeMatch(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public WiredConditionType getType() {
return type;
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
this.refresh();
List<HabboItem> serializedItems = new ArrayList<>();
if (this.targetType == TARGET_FURNI && this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
serializedItems.addAll(this.selectedItems);
}
message.appendBoolean(false);
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
message.appendInt(serializedItems.size());
for (HabboItem item : serializedItems) {
message.appendInt(item.getId());
}
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(this.variableToken == null ? "" : this.variableToken);
message.appendInt(8);
message.appendInt(this.targetType);
message.appendInt(this.compareValue);
message.appendInt(this.comparison);
message.appendInt(this.durationAmount);
message.appendInt(this.durationUnit);
message.appendInt(this.userSource);
message.appendInt(this.furniSource);
message.appendInt(this.quantifier);
message.appendInt(0);
message.appendInt(this.getType().code);
message.appendInt(0);
message.appendInt(0);
}
@Override
public boolean saveData(WiredSettings settings) {
int[] params = settings.getIntParams();
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
this.targetType = (params.length > 0) ? normalizeTargetTypeExtended(params[0]) : TARGET_USER;
this.compareValue = (params.length > 1) ? normalizeCompareValue(params[1]) : COMPARE_VALUE_CREATED;
this.comparison = (params.length > 2) ? normalizeComparison(params[2]) : COMPARISON_LOWER_THAN;
this.durationAmount = Math.max(0, (params.length > 3) ? params[3] : 0);
this.durationUnit = (params.length > 4) ? normalizeDurationUnit(params[4]) : DURATION_UNIT_SECONDS;
this.userSource = (params.length > 5) ? normalizeUserSource(params[5]) : WiredSourceUtil.SOURCE_TRIGGER;
this.furniSource = (params.length > 6) ? normalizeFurniSource(params[6]) : WiredSourceUtil.SOURCE_TRIGGER;
this.quantifier = (params.length > 7) ? normalizeQuantifier(params[7]) : QUANTIFIER_ALL;
this.setVariableToken(normalizeVariableToken(settings.getStringParam()));
if (!this.isValidSource(room)) {
return false;
}
this.selectedItems.clear();
if (this.targetType == TARGET_FURNI && this.furniSource == WiredSourceUtil.SOURCE_SELECTED && room != null) {
int[] furniIds = settings.getFurniIds();
if (furniIds.length > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) {
return false;
}
for (int furniId : furniIds) {
HabboItem item = room.getHabboItem(furniId);
if (item != null) {
this.selectedItems.add(item);
}
}
}
return true;
}
@Override
public boolean evaluate(WiredContext ctx) {
Room room = ctx.room();
if (room == null || this.variableToken == null || this.variableToken.isEmpty() || !isCustomVariableToken(this.variableToken)) {
return false;
}
long thresholdMs = durationToMillis(this.durationAmount, this.durationUnit);
return switch (this.targetType) {
case TARGET_FURNI -> this.evaluateFurniTargets(ctx, room, thresholdMs);
case TARGET_ROOM -> this.evaluateRoomTarget(room, thresholdMs);
case TARGET_CONTEXT -> this.evaluateContextTarget(ctx, room, thresholdMs);
default -> this.evaluateUserTargets(ctx, room, thresholdMs);
};
}
@Deprecated
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public String getWiredData() {
this.refresh();
List<Integer> itemIds = new ArrayList<>();
for (HabboItem item : this.selectedItems) {
if (item != null) itemIds.add(item.getId());
}
return WiredManager.getGson().toJson(new JsonData(
itemIds,
this.targetType,
this.variableToken,
this.variableItemId,
this.compareValue,
this.comparison,
this.durationAmount,
this.durationUnit,
this.userSource,
this.furniSource,
this.quantifier
));
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty()) return;
try {
if (wiredData.startsWith("{")) {
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data == null) return;
this.targetType = normalizeTargetTypeExtended(data.targetType);
this.compareValue = normalizeCompareValue(data.compareValue);
this.comparison = normalizeComparison(data.comparison);
this.durationAmount = Math.max(0, data.durationAmount);
this.durationUnit = normalizeDurationUnit(data.durationUnit);
this.userSource = normalizeUserSource(data.userSource);
this.furniSource = normalizeFurniSource(data.furniSource);
this.quantifier = normalizeQuantifier(data.quantifier);
this.setVariableToken(normalizeVariableToken((data.variableToken != null) ? data.variableToken : ((data.variableItemId > 0) ? String.valueOf(data.variableItemId) : "")));
if (room != null && data.itemIds != null) {
for (Integer itemId : data.itemIds) {
if (itemId == null || itemId <= 0) continue;
HabboItem item = room.getHabboItem(itemId);
if (item != null) this.selectedItems.add(item);
}
}
return;
}
this.setVariableToken(normalizeVariableToken(wiredData));
} catch (Exception e) {
this.onPickUp();
}
}
@Override
public void onPickUp() {
super.onPickUp();
this.compareValue = COMPARE_VALUE_CREATED;
this.comparison = COMPARISON_LOWER_THAN;
this.durationAmount = 0;
this.durationUnit = DURATION_UNIT_SECONDS;
}
private boolean evaluateUserTargets(WiredContext ctx, Room room, long thresholdMs) {
List<RoomUnit> targets = WiredSourceUtil.resolveUsers(ctx, this.userSource);
if (targets.isEmpty()) return false;
if (this.quantifier == QUANTIFIER_ANY) {
for (RoomUnit roomUnit : targets) {
if (this.matchesAge(this.readUserAgeMs(room, roomUnit), thresholdMs)) return true;
}
return false;
}
for (RoomUnit roomUnit : targets) {
if (!this.matchesAge(this.readUserAgeMs(room, roomUnit), thresholdMs)) return false;
}
return true;
}
private boolean evaluateFurniTargets(WiredContext ctx, Room room, long thresholdMs) {
this.refresh();
List<HabboItem> targets = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.selectedItems);
if (targets.isEmpty()) return false;
if (this.quantifier == QUANTIFIER_ANY) {
for (HabboItem item : targets) {
if (this.matchesAge(this.readFurniAgeMs(room, item), thresholdMs)) return true;
}
return false;
}
for (HabboItem item : targets) {
if (!this.matchesAge(this.readFurniAgeMs(room, item), thresholdMs)) return false;
}
return true;
}
private boolean evaluateRoomTarget(Room room, long thresholdMs) {
return this.matchesAge(this.readRoomAgeMs(room), thresholdMs);
}
private boolean evaluateContextTarget(WiredContext ctx, Room room, long thresholdMs) {
return this.matchesAge(this.readContextAgeMs(ctx, room), thresholdMs);
}
private Long readUserAgeMs(Room room, RoomUnit roomUnit) {
if (room == null || roomUnit == null) return null;
Habbo habbo = room.getHabbo(roomUnit);
if (habbo == null || !room.getUserVariableManager().hasVariable(habbo.getHabboInfo().getId(), this.variableItemId)) return null;
int timestamp = (this.compareValue == COMPARE_VALUE_UPDATED)
? room.getUserVariableManager().getUpdatedAt(habbo.getHabboInfo().getId(), this.variableItemId)
: room.getUserVariableManager().getCreatedAt(habbo.getHabboInfo().getId(), this.variableItemId);
return timestampToAgeMs(timestamp);
}
private Long readFurniAgeMs(Room room, HabboItem item) {
if (room == null || item == null || !room.getFurniVariableManager().hasVariable(item.getId(), this.variableItemId)) return null;
int timestamp = (this.compareValue == COMPARE_VALUE_UPDATED)
? room.getFurniVariableManager().getUpdatedAt(item.getId(), this.variableItemId)
: room.getFurniVariableManager().getCreatedAt(item.getId(), this.variableItemId);
return timestampToAgeMs(timestamp);
}
private Long readRoomAgeMs(Room room) {
if (room == null) return null;
if (this.compareValue == COMPARE_VALUE_CREATED) return null;
int timestamp = room.getRoomVariableManager().getUpdatedAt(this.variableItemId);
return timestampToAgeMs(timestamp);
}
private Long readContextAgeMs(WiredContext ctx, Room room) {
if (ctx == null || room == null || !WiredContextVariableSupport.hasVariable(ctx, this.variableItemId)) return null;
int timestamp = (this.compareValue == COMPARE_VALUE_UPDATED)
? WiredContextVariableSupport.getUpdatedAt(ctx, this.variableItemId)
: WiredContextVariableSupport.getCreatedAt(ctx, this.variableItemId);
return timestampToAgeMs(timestamp);
}
private boolean matchesAge(Long ageMs, long thresholdMs) {
if (ageMs == null) return false;
return switch (this.comparison) {
case COMPARISON_HIGHER_THAN -> ageMs > thresholdMs;
default -> ageMs < thresholdMs;
};
}
private boolean isValidSource(Room room) {
if (room == null || !isCustomVariableToken(this.variableToken)) return false;
return switch (this.targetType) {
case TARGET_FURNI -> room.getFurniVariableManager().getDefinitionInfo(this.variableItemId) != null;
case TARGET_CONTEXT -> WiredContextVariableSupport.getDefinitionInfo(room, this.variableItemId) != null;
case TARGET_ROOM -> {
WiredVariableDefinitionInfo definition = room.getRoomVariableManager().getDefinitionInfo(this.variableItemId);
yield this.compareValue == COMPARE_VALUE_UPDATED && definition != null;
}
default -> room.getUserVariableManager().getDefinitionInfo(this.variableItemId) != null;
};
}
private static Long timestampToAgeMs(int timestampSeconds) {
if (timestampSeconds <= 0) return null;
long timestampMs = (timestampSeconds * 1000L);
return Math.max(0L, System.currentTimeMillis() - timestampMs);
}
private static long durationToMillis(int amount, int unit) {
long normalizedAmount = Math.max(0L, amount);
return switch (unit) {
case DURATION_UNIT_MILLISECONDS -> normalizedAmount;
case DURATION_UNIT_MINUTES -> safeMultiply(normalizedAmount, 60_000L);
case DURATION_UNIT_HOURS -> safeMultiply(normalizedAmount, 3_600_000L);
case DURATION_UNIT_DAYS -> safeMultiply(normalizedAmount, 86_400_000L);
case DURATION_UNIT_WEEKS -> safeMultiply(normalizedAmount, 604_800_000L);
case DURATION_UNIT_MONTHS -> safeMultiply(normalizedAmount, 2_592_000_000L);
case DURATION_UNIT_YEARS -> safeMultiply(normalizedAmount, 31_536_000_000L);
default -> safeMultiply(normalizedAmount, 1_000L);
};
}
private static long safeMultiply(long left, long right) {
if (left <= 0 || right <= 0) return 0L;
if (left > (Long.MAX_VALUE / right)) return Long.MAX_VALUE;
return left * right;
}
private static int normalizeTargetTypeExtended(int value) {
return switch (value) {
case TARGET_FURNI, TARGET_CONTEXT, TARGET_ROOM -> value;
default -> TARGET_USER;
};
}
private static int normalizeCompareValue(int value) {
return (value == COMPARE_VALUE_UPDATED) ? COMPARE_VALUE_UPDATED : COMPARE_VALUE_CREATED;
}
private static int normalizeComparison(int value) {
return (value == COMPARISON_HIGHER_THAN) ? COMPARISON_HIGHER_THAN : COMPARISON_LOWER_THAN;
}
private static int normalizeDurationUnit(int value) {
return switch (value) {
case DURATION_UNIT_MILLISECONDS, DURATION_UNIT_SECONDS, DURATION_UNIT_MINUTES, DURATION_UNIT_HOURS,
DURATION_UNIT_DAYS, DURATION_UNIT_WEEKS, DURATION_UNIT_MONTHS, DURATION_UNIT_YEARS -> value;
default -> DURATION_UNIT_SECONDS;
};
}
protected static class JsonData {
List<Integer> itemIds;
int targetType;
String variableToken;
int variableItemId;
int compareValue;
int comparison;
int durationAmount;
int durationUnit;
int userSource;
int furniSource;
int quantifier;
JsonData(List<Integer> itemIds, int targetType, String variableToken, int variableItemId, int compareValue, int comparison, int durationAmount, int durationUnit, int userSource, int furniSource, int quantifier) {
this.itemIds = itemIds;
this.targetType = targetType;
this.variableToken = variableToken;
this.variableItemId = variableItemId;
this.compareValue = compareValue;
this.comparison = comparison;
this.durationAmount = durationAmount;
this.durationUnit = durationUnit;
this.userSource = userSource;
this.furniSource = furniSource;
this.quantifier = quantifier;
}
}
}
@@ -0,0 +1,814 @@
package com.eu.habbo.habbohotel.items.interactions.wired.conditions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.bots.Bot;
import com.eu.habbo.habbohotel.games.Game;
import com.eu.habbo.habbohotel.games.GamePlayer;
import com.eu.habbo.habbohotel.games.GameTeam;
import com.eu.habbo.habbohotel.games.GameTeamColors;
import com.eu.habbo.habbohotel.games.battlebanzai.BattleBanzaiGame;
import com.eu.habbo.habbohotel.games.freeze.FreezeGame;
import com.eu.habbo.habbohotel.games.wired.WiredGame;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.wired.WiredConditionType;
import com.eu.habbo.habbohotel.wired.core.WiredContext;
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredInternalVariableSupport;
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.util.HotelDateTimeUtil;
import gnu.trove.set.hash.THashSet;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.ZonedDateTime;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
public class WiredConditionVariableValueMatch extends WiredConditionHasVariable {
public static final WiredConditionType type = WiredConditionType.VAR_VAL_MATCH;
private static final int TARGET_CONTEXT = 2;
private static final int SOURCE_SECONDARY_SELECTED = 101;
private static final int REFERENCE_CONSTANT = 0;
private static final int REFERENCE_VARIABLE = 1;
private static final int COMPARISON_GREATER_THAN = 0;
private static final int COMPARISON_GREATER_THAN_OR_EQUAL = 1;
private static final int COMPARISON_EQUAL = 2;
private static final int COMPARISON_LESS_THAN_OR_EQUAL = 3;
private static final int COMPARISON_LESS_THAN = 4;
private static final int COMPARISON_NOT_EQUAL = 5;
private static final String DELIM = "\t";
private static final String FURNI_DELIM = ";";
protected int comparison = COMPARISON_EQUAL;
protected int referenceMode = REFERENCE_CONSTANT;
protected int referenceConstantValue = 0;
protected int referenceTargetType = TARGET_USER;
protected int referenceUserSource = WiredSourceUtil.SOURCE_TRIGGER;
protected int referenceFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
protected String referenceVariableToken = "";
protected int referenceVariableItemId = 0;
protected final THashSet<HabboItem> referenceSelectedItems = new THashSet<>();
public WiredConditionVariableValueMatch(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredConditionVariableValueMatch(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public WiredConditionType getType() {
return type;
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
this.refresh();
this.refreshReferenceItems();
List<HabboItem> serializedItems = new ArrayList<>();
if (this.targetType == TARGET_FURNI && this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
serializedItems.addAll(this.selectedItems);
}
message.appendBoolean(false);
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
message.appendInt(serializedItems.size());
for (HabboItem item : serializedItems) {
message.appendInt(item.getId());
}
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(this.serializeStringData());
message.appendInt(10);
message.appendInt(this.targetType);
message.appendInt(this.comparison);
message.appendInt(this.referenceMode);
message.appendInt(this.referenceConstantValue);
message.appendInt(this.referenceTargetType);
message.appendInt(this.userSource);
message.appendInt(this.furniSource);
message.appendInt(this.referenceUserSource);
message.appendInt(this.referenceFurniSource);
message.appendInt(this.quantifier);
message.appendInt(0);
message.appendInt(this.getType().code);
message.appendInt(0);
message.appendInt(0);
}
@Override
public boolean saveData(WiredSettings settings) {
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room == null) return false;
int[] params = settings.getIntParams();
String[] stringParts = this.parseStringData(settings.getStringParam());
int nextTargetType = normalizeTargetTypeExtended(param(params, 0, TARGET_USER));
int nextComparison = normalizeComparison(param(params, 1, COMPARISON_EQUAL));
int nextReferenceMode = normalizeReferenceMode(param(params, 2, REFERENCE_CONSTANT));
int nextReferenceConstantValue = param(params, 3, 0);
int nextReferenceTargetType = normalizeTargetTypeExtended(param(params, 4, TARGET_USER));
int nextUserSource = normalizeUserSource(param(params, 5, WiredSourceUtil.SOURCE_TRIGGER));
int nextFurniSource = normalizeFurniSource(param(params, 6, WiredSourceUtil.SOURCE_TRIGGER));
int nextReferenceUserSource = normalizeUserSource(param(params, 7, WiredSourceUtil.SOURCE_TRIGGER));
int nextReferenceFurniSource = normalizeReferenceFurniSource(param(params, 8, WiredSourceUtil.SOURCE_TRIGGER));
int nextQuantifier = normalizeQuantifier(param(params, 9, QUANTIFIER_ALL));
String nextVariableToken = normalizeVariableToken((stringParts.length > 0) ? stringParts[0] : settings.getStringParam());
String nextReferenceVariableToken = normalizeVariableToken((stringParts.length > 1) ? stringParts[1] : "");
if (!this.isValidSource(room, nextTargetType, nextVariableToken)) return false;
if (nextReferenceMode == REFERENCE_VARIABLE && !this.isValidReference(room, nextReferenceTargetType, nextReferenceVariableToken)) return false;
int selectionLimit = Emulator.getConfig().getInt("hotel.wired.furni.selection.count");
List<HabboItem> nextSelectedItems = (nextTargetType == TARGET_FURNI && nextFurniSource == WiredSourceUtil.SOURCE_SELECTED)
? this.parseItems(settings.getFurniIds(), room)
: new ArrayList<>();
List<HabboItem> nextReferenceItems = (nextReferenceMode == REFERENCE_VARIABLE && nextReferenceTargetType == TARGET_FURNI && nextReferenceFurniSource == SOURCE_SECONDARY_SELECTED)
? this.parseItems((stringParts.length > 2) ? stringParts[2] : "", room)
: new ArrayList<>();
if (nextSelectedItems.size() > selectionLimit || nextReferenceItems.size() > selectionLimit) return false;
this.selectedItems.clear();
this.selectedItems.addAll(nextSelectedItems);
this.referenceSelectedItems.clear();
this.referenceSelectedItems.addAll(nextReferenceItems);
this.targetType = nextTargetType;
this.comparison = nextComparison;
this.referenceMode = nextReferenceMode;
this.referenceConstantValue = nextReferenceConstantValue;
this.referenceTargetType = nextReferenceTargetType;
this.userSource = nextUserSource;
this.furniSource = nextFurniSource;
this.referenceUserSource = nextReferenceUserSource;
this.referenceFurniSource = nextReferenceFurniSource;
this.quantifier = nextQuantifier;
this.setVariableToken(nextVariableToken);
this.setReferenceVariableToken(nextReferenceVariableToken);
return true;
}
@Override
public boolean evaluate(WiredContext ctx) {
Room room = ctx.room();
if (room == null || this.variableToken == null || this.variableToken.isEmpty()) {
return false;
}
return switch (this.targetType) {
case TARGET_FURNI -> this.evaluateFurniTargets(ctx, room);
case TARGET_ROOM -> this.evaluateRoomTarget(ctx, room);
case TARGET_CONTEXT -> this.evaluateContextTarget(ctx, room);
default -> this.evaluateUserTargets(ctx, room);
};
}
@Deprecated
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public String getWiredData() {
this.refresh();
this.refreshReferenceItems();
return WiredManager.getGson().toJson(new JsonData(
this.targetType,
this.variableToken,
this.variableItemId,
this.comparison,
this.referenceMode,
this.referenceConstantValue,
this.referenceTargetType,
this.referenceVariableToken,
this.referenceVariableItemId,
this.userSource,
this.furniSource,
this.referenceUserSource,
this.referenceFurniSource,
this.quantifier,
this.toIds(this.selectedItems),
this.toIds(this.referenceSelectedItems)
));
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty() || !wiredData.startsWith("{")) return;
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data == null) return;
this.targetType = normalizeTargetTypeExtended(data.targetType);
this.setVariableToken(normalizeVariableToken((data.variableToken != null) ? data.variableToken : ((data.variableItemId > 0) ? String.valueOf(data.variableItemId) : "")));
this.comparison = normalizeComparison(data.comparison);
this.referenceMode = normalizeReferenceMode(data.referenceMode);
this.referenceConstantValue = data.referenceConstantValue;
this.referenceTargetType = normalizeTargetTypeExtended(data.referenceTargetType);
this.setReferenceVariableToken(normalizeVariableToken((data.referenceVariableToken != null) ? data.referenceVariableToken : ((data.referenceVariableItemId > 0) ? String.valueOf(data.referenceVariableItemId) : "")));
this.userSource = normalizeUserSource(data.userSource);
this.furniSource = normalizeFurniSource(data.furniSource);
this.referenceUserSource = normalizeUserSource(data.referenceUserSource);
this.referenceFurniSource = normalizeReferenceFurniSource(data.referenceFurniSource);
this.quantifier = normalizeQuantifier(data.quantifier);
if (room == null) return;
this.selectedItems.addAll(this.parseItems(data.selectedItemIds, room));
this.referenceSelectedItems.addAll(this.parseItems(data.referenceSelectedItemIds, room));
}
@Override
public void onPickUp() {
super.onPickUp();
this.comparison = COMPARISON_EQUAL;
this.referenceMode = REFERENCE_CONSTANT;
this.referenceConstantValue = 0;
this.referenceTargetType = TARGET_USER;
this.referenceUserSource = WiredSourceUtil.SOURCE_TRIGGER;
this.referenceFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
this.referenceSelectedItems.clear();
this.setReferenceVariableToken("");
}
public boolean requiresTriggeringUser() {
return (this.targetType == TARGET_USER && this.userSource == WiredSourceUtil.SOURCE_TRIGGER)
|| (this.referenceMode == REFERENCE_VARIABLE && this.referenceTargetType == TARGET_USER && this.referenceUserSource == WiredSourceUtil.SOURCE_TRIGGER);
}
private boolean evaluateUserTargets(WiredContext ctx, Room room) {
List<RoomUnit> targets = WiredSourceUtil.resolveUsers(ctx, this.userSource);
if (targets.isEmpty()) return false;
ReferenceSnapshot references = this.resolveReferences(ctx, room);
if (this.quantifier == QUANTIFIER_ANY) {
int index = 0;
for (RoomUnit roomUnit : targets) {
Integer currentValue = this.readUserValue(room, roomUnit);
Integer referenceValue = this.referenceFor(references, roomUnit != null ? roomUnit.getId() : 0, TARGET_USER, index++);
if (this.matchesComparison(currentValue, referenceValue)) return true;
}
return false;
}
int index = 0;
for (RoomUnit roomUnit : targets) {
Integer currentValue = this.readUserValue(room, roomUnit);
Integer referenceValue = this.referenceFor(references, roomUnit != null ? roomUnit.getId() : 0, TARGET_USER, index++);
if (!this.matchesComparison(currentValue, referenceValue)) return false;
}
return true;
}
private boolean evaluateFurniTargets(WiredContext ctx, Room room) {
this.refresh();
List<HabboItem> targets = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.selectedItems);
if (targets.isEmpty()) return false;
ReferenceSnapshot references = this.resolveReferences(ctx, room);
if (this.quantifier == QUANTIFIER_ANY) {
int index = 0;
for (HabboItem item : targets) {
Integer currentValue = this.readFurniValue(room, item);
Integer referenceValue = this.referenceFor(references, item != null ? item.getId() : 0, TARGET_FURNI, index++);
if (this.matchesComparison(currentValue, referenceValue)) return true;
}
return false;
}
int index = 0;
for (HabboItem item : targets) {
Integer currentValue = this.readFurniValue(room, item);
Integer referenceValue = this.referenceFor(references, item != null ? item.getId() : 0, TARGET_FURNI, index++);
if (!this.matchesComparison(currentValue, referenceValue)) return false;
}
return true;
}
private boolean evaluateRoomTarget(WiredContext ctx, Room room) {
Integer currentValue = this.readRoomValue(room);
Integer referenceValue = this.referenceFor(this.resolveReferences(ctx, room), room.getId(), TARGET_ROOM, 0);
return this.matchesComparison(currentValue, referenceValue);
}
private boolean evaluateContextTarget(WiredContext ctx, Room room) {
Integer currentValue = this.readContextTargetValue(ctx, room);
Integer referenceValue = this.referenceFor(this.resolveReferences(ctx, room), this.variableItemId, TARGET_CONTEXT, 0);
return this.matchesComparison(currentValue, referenceValue);
}
private ReferenceSnapshot resolveReferences(WiredContext ctx, Room room) {
if (this.referenceMode != REFERENCE_VARIABLE) return null;
return switch (this.referenceTargetType) {
case TARGET_USER -> this.userReferences(ctx, room);
case TARGET_FURNI -> this.furniReferences(ctx, room);
case TARGET_CONTEXT -> this.contextReferences(ctx, room);
case TARGET_ROOM -> this.roomReferences(room);
default -> null;
};
}
private ReferenceSnapshot userReferences(WiredContext ctx, Room room) {
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_USER);
if (isInternalVariableToken(this.referenceVariableToken)) {
String key = getInternalVariableKey(this.referenceVariableToken);
if (!canUseUserInternalReference(key)) return null;
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.referenceUserSource)) {
Integer value = this.readUserInternalValue(room, roomUnit, key);
if (value != null && roomUnit != null) snapshot.add(roomUnit.getId(), value);
}
return snapshot.isEmpty() ? null : snapshot;
}
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.referenceVariableItemId);
if (definition == null || !definition.hasValue()) return null;
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.referenceUserSource)) {
if (roomUnit == null) continue;
Habbo habbo = room.getHabbo(roomUnit);
if (habbo != null) snapshot.add(roomUnit.getId(), room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), this.referenceVariableItemId));
}
return snapshot.isEmpty() ? null : snapshot;
}
private ReferenceSnapshot furniReferences(WiredContext ctx, Room room) {
int source = (this.referenceFurniSource == SOURCE_SECONDARY_SELECTED) ? WiredSourceUtil.SOURCE_SELECTED : this.referenceFurniSource;
if (source == WiredSourceUtil.SOURCE_SELECTED) this.refreshReferenceItems();
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_FURNI);
if (isInternalVariableToken(this.referenceVariableToken)) {
String key = getInternalVariableKey(this.referenceVariableToken);
if (!canUseFurniInternalReference(key)) return null;
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, source, this.referenceSelectedItems)) {
Integer value = this.readFurniInternalValue(room, item, key);
if (value != null && item != null) snapshot.add(item.getId(), value);
}
return snapshot.isEmpty() ? null : snapshot;
}
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.referenceVariableItemId);
if (definition == null || !definition.hasValue()) return null;
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, source, this.referenceSelectedItems)) {
if (item != null) snapshot.add(item.getId(), room.getFurniVariableManager().getCurrentValue(item.getId(), this.referenceVariableItemId));
}
return snapshot.isEmpty() ? null : snapshot;
}
private ReferenceSnapshot roomReferences(Room room) {
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_ROOM);
if (isInternalVariableToken(this.referenceVariableToken)) {
String key = getInternalVariableKey(this.referenceVariableToken);
if (!canUseRoomInternalReference(key)) return null;
Integer value = this.readRoomInternalValue(room, key);
if (value == null) return null;
snapshot.add(room.getId(), value);
return snapshot;
}
WiredVariableDefinitionInfo definition = room.getRoomVariableManager().getDefinitionInfo(this.referenceVariableItemId);
if (definition == null || !definition.hasValue()) return null;
snapshot.add(room.getId(), room.getRoomVariableManager().getCurrentValue(this.referenceVariableItemId));
return snapshot;
}
private ReferenceSnapshot contextReferences(WiredContext ctx, Room room) {
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_CONTEXT);
if (isInternalVariableToken(this.referenceVariableToken)) {
String key = getInternalVariableKey(this.referenceVariableToken);
if (!canUseContextInternalReference(key)) return null;
Integer value = WiredInternalVariableSupport.readContextValue(ctx, key);
if (value == null) return null;
snapshot.add(this.referenceVariableItemId > 0 ? this.referenceVariableItemId : (room != null ? room.getId() : 0), value);
return snapshot;
}
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, this.referenceVariableItemId);
if (definition == null || !definition.hasValue() || !WiredContextVariableSupport.hasVariable(ctx, this.referenceVariableItemId)) return null;
Integer value = WiredContextVariableSupport.getCurrentValue(ctx, this.referenceVariableItemId);
if (value == null) return null;
snapshot.add(this.referenceVariableItemId, value);
return snapshot;
}
private Integer readUserValue(Room room, RoomUnit roomUnit) {
if (room == null || roomUnit == null) return null;
if (isInternalVariableToken(this.variableToken)) {
String key = getInternalVariableKey(this.variableToken);
return canUseUserInternalReference(key) ? this.readUserInternalValue(room, roomUnit, key) : null;
}
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.variableItemId);
if (definition == null || !definition.hasValue()) return null;
Habbo habbo = room.getHabbo(roomUnit);
return (habbo != null) ? room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), this.variableItemId) : null;
}
private Integer readFurniValue(Room room, HabboItem item) {
if (room == null || item == null) return null;
if (isInternalVariableToken(this.variableToken)) {
String key = getInternalVariableKey(this.variableToken);
return canUseFurniInternalReference(key) ? this.readFurniInternalValue(room, item, key) : null;
}
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.variableItemId);
return (definition != null && definition.hasValue()) ? room.getFurniVariableManager().getCurrentValue(item.getId(), this.variableItemId) : null;
}
private Integer readRoomValue(Room room) {
if (room == null) return null;
if (isInternalVariableToken(this.variableToken)) {
String key = getInternalVariableKey(this.variableToken);
return canUseRoomInternalReference(key) ? this.readRoomInternalValue(room, key) : null;
}
WiredVariableDefinitionInfo definition = room.getRoomVariableManager().getDefinitionInfo(this.variableItemId);
return (definition != null && definition.hasValue()) ? room.getRoomVariableManager().getCurrentValue(this.variableItemId) : null;
}
private Integer readContextTargetValue(WiredContext ctx, Room room) {
if (ctx == null || room == null) return null;
if (isInternalVariableToken(this.variableToken)) {
String key = getInternalVariableKey(this.variableToken);
return canUseContextInternalReference(key) ? WiredInternalVariableSupport.readContextValue(ctx, key) : null;
}
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, this.variableItemId);
if (definition == null || !definition.hasValue() || !WiredContextVariableSupport.hasVariable(ctx, this.variableItemId)) return null;
return WiredContextVariableSupport.getCurrentValue(ctx, this.variableItemId);
}
private Integer referenceFor(ReferenceSnapshot snapshot, int destinationEntityId, int destinationTarget, int destinationIndex) {
if (this.referenceMode != REFERENCE_VARIABLE) return this.referenceConstantValue;
if (snapshot == null || snapshot.isEmpty()) return null;
if (snapshot.targetType == destinationTarget && snapshot.values.containsKey(destinationEntityId)) return snapshot.values.get(destinationEntityId);
if (destinationIndex >= 0 && destinationIndex < snapshot.values.size()) return new ArrayList<>(snapshot.values.values()).get(destinationIndex);
return new ArrayList<>(snapshot.values.values()).get(0);
}
private boolean matchesComparison(Integer currentValue, Integer referenceValue) {
if (currentValue == null || referenceValue == null) return false;
return switch (this.comparison) {
case COMPARISON_GREATER_THAN -> currentValue > referenceValue;
case COMPARISON_GREATER_THAN_OR_EQUAL -> currentValue >= referenceValue;
case COMPARISON_LESS_THAN_OR_EQUAL -> currentValue <= referenceValue;
case COMPARISON_LESS_THAN -> currentValue < referenceValue;
case COMPARISON_NOT_EQUAL -> !currentValue.equals(referenceValue);
default -> currentValue.equals(referenceValue);
};
}
private boolean isValidSource(Room room, int targetType, String variableToken) {
if (variableToken == null || variableToken.isEmpty()) return false;
return switch (targetType) {
case TARGET_USER -> isInternalVariableToken(variableToken)
? canUseUserInternalReference(getInternalVariableKey(variableToken))
: this.isValidUserCustomValue(room, getCustomItemId(variableToken));
case TARGET_FURNI -> isInternalVariableToken(variableToken)
? canUseFurniInternalReference(getInternalVariableKey(variableToken))
: this.isValidFurniCustomValue(room, getCustomItemId(variableToken));
case TARGET_CONTEXT -> isInternalVariableToken(variableToken)
? canUseContextInternalReference(getInternalVariableKey(variableToken))
: this.isValidContextCustomValue(room, getCustomItemId(variableToken));
case TARGET_ROOM -> isInternalVariableToken(variableToken)
? canUseRoomInternalReference(getInternalVariableKey(variableToken))
: this.isValidRoomCustomValue(room, getCustomItemId(variableToken));
default -> false;
};
}
private boolean isValidReference(Room room, int targetType, String variableToken) {
return this.isValidSource(room, targetType, variableToken);
}
private boolean isValidUserCustomValue(Room room, int variableItemId) {
WiredVariableDefinitionInfo definition = (room != null) ? room.getUserVariableManager().getDefinitionInfo(variableItemId) : null;
return definition != null && definition.hasValue();
}
private boolean isValidFurniCustomValue(Room room, int variableItemId) {
WiredVariableDefinitionInfo definition = (room != null) ? room.getFurniVariableManager().getDefinitionInfo(variableItemId) : null;
return definition != null && definition.hasValue();
}
private boolean isValidRoomCustomValue(Room room, int variableItemId) {
WiredVariableDefinitionInfo definition = (room != null) ? room.getRoomVariableManager().getDefinitionInfo(variableItemId) : null;
return definition != null && definition.hasValue();
}
private boolean isValidContextCustomValue(Room room, int variableItemId) {
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, variableItemId);
return definition != null && definition.hasValue();
}
private Integer readUserInternalValue(Room room, RoomUnit roomUnit, String key) {
return WiredInternalVariableSupport.readUserValue(room, roomUnit, key);
}
private Integer readFurniInternalValue(Room room, HabboItem item, String key) {
return WiredInternalVariableSupport.readFurniValue(room, item, key);
}
private Integer readRoomInternalValue(Room room, String key) {
return WiredInternalVariableSupport.readRoomValue(room, key);
}
private Integer getUserTeamScore(Room room, Habbo habbo) {
if (room == null || habbo == null || habbo.getHabboInfo().getGamePlayer() == null) return null;
Game game = this.resolveTeamGame(room, habbo);
GamePlayer gamePlayer = habbo.getHabboInfo().getGamePlayer();
if (game == null || gamePlayer.getTeamColor() == null) return gamePlayer.getScore();
GameTeam team = game.getTeam(gamePlayer.getTeamColor());
return (team != null) ? team.getTotalScore() : gamePlayer.getScore();
}
private Integer getTeamColorId(int effectId) {
TeamEffectData data = this.getTeamEffectData(effectId);
return data == null ? null : data.colorId;
}
private Integer getTeamTypeId(int effectId) {
TeamEffectData data = this.getTeamEffectData(effectId);
return data == null ? null : data.typeId;
}
private int getTeamMetric(Room room, GameTeamColors color, boolean score) {
Game game = this.resolveTeamGame(room, null);
if (game == null || color == null) return 0;
GameTeam team = game.getTeam(color);
if (team == null) return 0;
return score ? team.getTotalScore() : team.getMembers().size();
}
private Game resolveTeamGame(Room room, Habbo habbo) {
if (room == null) return null;
if (habbo != null && habbo.getHabboInfo() != null && habbo.getHabboInfo().getCurrentGame() != null) {
Game game = room.getGame(habbo.getHabboInfo().getCurrentGame());
if (game != null) return game;
}
Game wiredGame = room.getGame(WiredGame.class);
if (wiredGame != null) return wiredGame;
Game freezeGame = room.getGame(FreezeGame.class);
if (freezeGame != null) return freezeGame;
return room.getGame(BattleBanzaiGame.class);
}
private List<HabboItem> parseItems(int[] ids, Room room) {
List<HabboItem> items = new ArrayList<>();
if (ids == null || room == null) return items;
for (int id : ids) {
HabboItem item = room.getHabboItem(id);
if (item != null) items.add(item);
}
return items;
}
private List<HabboItem> parseItems(List<Integer> ids, Room room) {
List<HabboItem> items = new ArrayList<>();
if (ids == null || room == null) return items;
for (Integer id : ids) {
if (id == null || id <= 0) continue;
HabboItem item = room.getHabboItem(id);
if (item != null) items.add(item);
}
return items;
}
private List<HabboItem> parseItems(String ids, Room room) {
List<HabboItem> items = new ArrayList<>();
if (ids == null || ids.trim().isEmpty() || room == null) return items;
for (String part : ids.split("[;,\\t]")) {
int id = parseInteger(part);
if (id <= 0) continue;
HabboItem item = room.getHabboItem(id);
if (item != null) items.add(item);
}
return items;
}
private void refreshReferenceItems() {
THashSet<HabboItem> staleItems = new THashSet<>();
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room == null) {
staleItems.addAll(this.referenceSelectedItems);
} else {
for (HabboItem item : this.referenceSelectedItems) {
if (item == null || item.getRoomId() != room.getId() || room.getHabboItem(item.getId()) == null) {
staleItems.add(item);
}
}
}
this.referenceSelectedItems.removeAll(staleItems);
}
private String serializeStringData() {
return (this.variableToken == null ? "" : this.variableToken) + DELIM + (this.referenceVariableToken == null ? "" : this.referenceVariableToken) + DELIM + this.serializeIds(this.referenceSelectedItems);
}
private String[] parseStringData(String value) {
return (value == null || value.isEmpty()) ? new String[0] : value.split("\\t", -1);
}
private List<Integer> toIds(THashSet<HabboItem> items) {
List<Integer> ids = new ArrayList<>();
for (HabboItem item : items) {
if (item != null) ids.add(item.getId());
}
return ids;
}
private String serializeIds(THashSet<HabboItem> items) {
StringBuilder builder = new StringBuilder();
for (HabboItem item : items) {
if (item == null) continue;
if (builder.length() > 0) builder.append(FURNI_DELIM);
builder.append(item.getId());
}
return builder.toString();
}
private void setReferenceVariableToken(String token) {
this.referenceVariableToken = normalizeVariableToken(token);
this.referenceVariableItemId = getCustomItemId(this.referenceVariableToken);
}
private static boolean canUseUserInternalReference(String key) {
return WiredInternalVariableSupport.canUseUserReference(key);
}
private static boolean canUseFurniInternalReference(String key) {
return WiredInternalVariableSupport.canUseFurniReference(key);
}
private static boolean canUseRoomInternalReference(String key) {
return WiredInternalVariableSupport.canUseRoomReference(key);
}
private static boolean canUseContextInternalReference(String key) {
return WiredInternalVariableSupport.canUseContextReference(key);
}
private static int param(int[] params, int index, int fallback) {
return (params.length > index) ? params[index] : fallback;
}
private static int normalizeTargetTypeExtended(int value) {
return switch (value) {
case TARGET_FURNI, TARGET_CONTEXT, TARGET_ROOM -> value;
default -> TARGET_USER;
};
}
private static int normalizeReferenceMode(int value) {
return (value == REFERENCE_VARIABLE) ? REFERENCE_VARIABLE : REFERENCE_CONSTANT;
}
private static int normalizeReferenceFurniSource(int value) {
return switch (value) {
case SOURCE_SECONDARY_SELECTED, WiredSourceUtil.SOURCE_SELECTOR, WiredSourceUtil.SOURCE_SIGNAL -> value;
default -> WiredSourceUtil.SOURCE_TRIGGER;
};
}
private static int normalizeComparison(int value) {
return switch (value) {
case COMPARISON_GREATER_THAN, COMPARISON_GREATER_THAN_OR_EQUAL, COMPARISON_LESS_THAN_OR_EQUAL, COMPARISON_LESS_THAN, COMPARISON_NOT_EQUAL -> value;
default -> COMPARISON_EQUAL;
};
}
private static int parseInteger(String value) {
try {
return (value == null || value.trim().isEmpty()) ? 0 : Integer.parseInt(value.trim());
} catch (NumberFormatException e) {
return 0;
}
}
static class JsonData {
int targetType, variableItemId, comparison, referenceMode, referenceConstantValue, referenceTargetType, referenceVariableItemId, userSource, furniSource, referenceUserSource, referenceFurniSource, quantifier;
String variableToken, referenceVariableToken;
List<Integer> selectedItemIds, referenceSelectedItemIds;
JsonData(int targetType, String variableToken, int variableItemId, int comparison, int referenceMode, int referenceConstantValue, int referenceTargetType, String referenceVariableToken, int referenceVariableItemId, int userSource, int furniSource, int referenceUserSource, int referenceFurniSource, int quantifier, List<Integer> selectedItemIds, List<Integer> referenceSelectedItemIds) {
this.targetType = targetType;
this.variableToken = variableToken;
this.variableItemId = variableItemId;
this.comparison = comparison;
this.referenceMode = referenceMode;
this.referenceConstantValue = referenceConstantValue;
this.referenceTargetType = referenceTargetType;
this.referenceVariableToken = referenceVariableToken;
this.referenceVariableItemId = referenceVariableItemId;
this.userSource = userSource;
this.furniSource = furniSource;
this.referenceUserSource = referenceUserSource;
this.referenceFurniSource = referenceFurniSource;
this.quantifier = quantifier;
this.selectedItemIds = selectedItemIds;
this.referenceSelectedItemIds = referenceSelectedItemIds;
}
}
private static class ReferenceSnapshot {
final int targetType;
final LinkedHashMap<Integer, Integer> values = new LinkedHashMap<>();
ReferenceSnapshot(int targetType) {
this.targetType = targetType;
}
void add(int entityId, int value) {
this.values.put(entityId, value);
}
boolean isEmpty() {
return this.values.isEmpty();
}
}
}
@@ -0,0 +1,932 @@
package com.eu.habbo.habbohotel.items.interactions.wired.effects;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.bots.Bot;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.games.Game;
import com.eu.habbo.habbohotel.games.GamePlayer;
import com.eu.habbo.habbohotel.games.GameTeam;
import com.eu.habbo.habbohotel.games.GameTeamColors;
import com.eu.habbo.habbohotel.games.battlebanzai.BattleBanzaiGame;
import com.eu.habbo.habbohotel.games.freeze.FreezeGame;
import com.eu.habbo.habbohotel.games.wired.WiredGame;
import com.eu.habbo.habbohotel.items.FurnitureType;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.rooms.FurnitureMovementError;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomTile;
import com.eu.habbo.habbohotel.rooms.RoomTileState;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.rooms.RoomUserRotation;
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.wired.WiredEffectType;
import com.eu.habbo.habbohotel.wired.core.WiredContext;
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredInternalVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
import com.eu.habbo.habbohotel.wired.core.WiredUserMovementHelper;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer;
import com.eu.habbo.util.HotelDateTimeUtil;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.ZonedDateTime;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
public class WiredEffectChangeVariableValue extends InteractionWiredEffect {
public static final WiredEffectType type = WiredEffectType.CHANGE_VAR_VAL;
public static final int TARGET_USER = 0, TARGET_FURNI = 1, TARGET_CONTEXT = 2, TARGET_ROOM = 3;
public static final int REF_CONSTANT = 0, REF_VARIABLE = 1;
public static final int OP_ASSIGN = 0, OP_ADD = 1, OP_SUB = 2, OP_MUL = 3, OP_DIV = 4, OP_POW = 5, OP_MOD = 6, OP_MIN = 40, OP_MAX = 41, OP_RANDOM = 50, OP_ABS = 60, OP_AND = 100, OP_OR = 101, OP_XOR = 102, OP_NOT = 103, OP_LSHIFT = 104, OP_RSHIFT = 105;
private static final int SOURCE_SECONDARY_SELECTED = 101;
private static final String DELIM = "\t", FURNI_DELIM = ";";
private static final String CUSTOM_TOKEN_PREFIX = "custom:";
private static final String INTERNAL_TOKEN_PREFIX = "internal:";
private int destinationTargetType = TARGET_USER, destinationVariableItemId = 0, operation = OP_ASSIGN, referenceMode = REF_CONSTANT, referenceConstantValue = 0, referenceTargetType = TARGET_USER, referenceVariableItemId = 0, destinationUserSource = WiredSourceUtil.SOURCE_TRIGGER, destinationFurniSource = WiredSourceUtil.SOURCE_TRIGGER, referenceUserSource = WiredSourceUtil.SOURCE_TRIGGER, referenceFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
private String destinationVariableToken = "", referenceVariableToken = "";
private final List<HabboItem> destinationSelectedFurni = new ArrayList<>();
private final List<HabboItem> referenceSelectedFurni = new ArrayList<>();
public WiredEffectChangeVariableValue(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredEffectChangeVariableValue(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) {
Room room = ctx.room();
if (room == null) return;
switch (this.destinationTargetType) {
case TARGET_USER -> this.executeUsers(ctx, room);
case TARGET_FURNI -> this.executeFurni(ctx, room);
case TARGET_CONTEXT -> this.executeContext(ctx, room);
case TARGET_ROOM -> this.executeRoom(ctx, room);
}
}
private void executeUsers(WiredContext ctx, Room room) {
if (isInternalVariableToken(this.destinationVariableToken)) {
this.executeUsersInternal(ctx, room, getInternalVariableKey(this.destinationVariableToken));
return;
}
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.destinationVariableItemId);
if (definition == null || !definition.hasValue() || definition.isReadOnly()) return;
ReferenceSnapshot references = this.resolveReferences(ctx, room);
int index = 0;
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.destinationUserSource)) {
if (roomUnit == null) continue;
Habbo habbo = room.getHabbo(roomUnit);
if (habbo == null) continue;
Integer referenceValue = this.referenceFor(references, roomUnit.getId(), TARGET_USER, index++);
if (!this.isUnaryOperation() && referenceValue == null) continue;
int currentValue = room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), this.destinationVariableItemId);
room.getUserVariableManager().updateVariableValue(habbo.getHabboInfo().getId(), this.destinationVariableItemId, this.applyOperation(currentValue, referenceValue));
}
}
private void executeUsersInternal(WiredContext ctx, Room room, String key) {
if (!canUseUserInternalDestination(key)) return;
ReferenceSnapshot references = this.resolveReferences(ctx, room);
int index = 0;
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.destinationUserSource)) {
if (roomUnit == null) continue;
Integer currentValue = this.readUserInternalValue(room, roomUnit, key);
if (currentValue == null) continue;
Integer referenceValue = this.referenceFor(references, roomUnit.getId(), TARGET_USER, index++);
if (!this.isUnaryOperation() && referenceValue == null) continue;
this.writeUserInternalValue(room, roomUnit, key, this.applyOperation(currentValue, referenceValue));
}
}
private void executeFurni(WiredContext ctx, Room room) {
if (isInternalVariableToken(this.destinationVariableToken)) {
this.executeFurniInternal(ctx, room, getInternalVariableKey(this.destinationVariableToken));
return;
}
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.destinationVariableItemId);
if (definition == null || !definition.hasValue() || definition.isReadOnly()) return;
if (this.destinationFurniSource == WiredSourceUtil.SOURCE_SELECTED) this.validateItems(this.destinationSelectedFurni);
ReferenceSnapshot references = this.resolveReferences(ctx, room);
int index = 0;
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, this.destinationFurniSource, this.destinationSelectedFurni)) {
if (item == null) continue;
Integer referenceValue = this.referenceFor(references, item.getId(), TARGET_FURNI, index++);
if (!this.isUnaryOperation() && referenceValue == null) continue;
int currentValue = room.getFurniVariableManager().getCurrentValue(item.getId(), this.destinationVariableItemId);
room.getFurniVariableManager().updateVariableValue(item.getId(), this.destinationVariableItemId, this.applyOperation(currentValue, referenceValue));
}
}
private void executeFurniInternal(WiredContext ctx, Room room, String key) {
if (!canUseFurniInternalDestination(key)) return;
if (this.destinationFurniSource == WiredSourceUtil.SOURCE_SELECTED) this.validateItems(this.destinationSelectedFurni);
ReferenceSnapshot references = this.resolveReferences(ctx, room);
int index = 0;
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, this.destinationFurniSource, this.destinationSelectedFurni)) {
if (item == null) continue;
Integer currentValue = this.readFurniInternalValue(room, item, key);
if (currentValue == null) continue;
Integer referenceValue = this.referenceFor(references, item.getId(), TARGET_FURNI, index++);
if (!this.isUnaryOperation() && referenceValue == null) continue;
this.writeFurniInternalValue(room, item, key, this.applyOperation(currentValue, referenceValue));
}
}
private void executeRoom(WiredContext ctx, Room room) {
if (isInternalVariableToken(this.destinationVariableToken)) return;
WiredVariableDefinitionInfo definition = room.getRoomVariableManager().getDefinitionInfo(this.destinationVariableItemId);
if (definition == null || !definition.hasValue() || definition.isReadOnly()) return;
ReferenceSnapshot references = this.resolveReferences(ctx, room);
Integer referenceValue = this.referenceFor(references, room.getId(), TARGET_ROOM, 0);
if (!this.isUnaryOperation() && referenceValue == null) return;
int currentValue = room.getRoomVariableManager().getCurrentValue(this.destinationVariableItemId);
room.getRoomVariableManager().updateVariableValue(this.destinationVariableItemId, this.applyOperation(currentValue, referenceValue));
}
private void executeContext(WiredContext ctx, Room room) {
if (isInternalVariableToken(this.destinationVariableToken)) return;
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, this.destinationVariableItemId);
if (definition == null || !definition.hasValue() || definition.isReadOnly()) return;
ReferenceSnapshot references = this.resolveReferences(ctx, room);
Integer referenceValue = this.referenceFor(references, this.destinationVariableItemId, TARGET_CONTEXT, 0);
if (!this.isUnaryOperation() && referenceValue == null) return;
if (!WiredContextVariableSupport.hasVariable(ctx, this.destinationVariableItemId)) return;
Integer currentValue = WiredContextVariableSupport.getCurrentValue(ctx, this.destinationVariableItemId);
int nextValue = this.applyOperation(currentValue != null ? currentValue : 0, referenceValue);
WiredContextVariableSupport.updateVariableValue(ctx, room, this.destinationVariableItemId, nextValue);
}
@Deprecated
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
this.validateItems(this.destinationSelectedFurni);
this.validateItems(this.referenceSelectedFurni);
List<HabboItem> selectedItems = new ArrayList<>();
if (this.destinationTargetType == TARGET_FURNI && this.destinationFurniSource == WiredSourceUtil.SOURCE_SELECTED) selectedItems.addAll(this.destinationSelectedFurni);
message.appendBoolean(false);
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
message.appendInt(selectedItems.size());
for (HabboItem item : selectedItems) message.appendInt(item.getId());
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(this.serializeStringData());
message.appendInt(9);
message.appendInt(this.destinationTargetType);
message.appendInt(this.operation);
message.appendInt(this.referenceMode);
message.appendInt(this.referenceConstantValue);
message.appendInt(this.referenceTargetType);
message.appendInt(this.destinationUserSource);
message.appendInt(this.destinationFurniSource);
message.appendInt(this.referenceUserSource);
message.appendInt(this.referenceFurniSource);
message.appendInt(0);
message.appendInt(this.getType().code);
message.appendInt(this.getDelay());
message.appendInt(0);
}
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
Room room = this.getRoom();
if (room == null) throw new WiredSaveException("Room not found");
int[] params = settings.getIntParams();
String[] stringParts = this.parseStringData(settings.getStringParam());
int nextDestinationTargetType = normalizeTargetType(param(params, 0, TARGET_USER));
int nextOperation = normalizeOperation(param(params, 1, OP_ASSIGN));
int nextReferenceMode = normalizeReferenceMode(param(params, 2, REF_CONSTANT));
int nextReferenceConstantValue = param(params, 3, 0);
int nextReferenceTargetType = normalizeTargetType(param(params, 4, TARGET_USER));
int nextDestinationUserSource = normalizeUserSource(param(params, 5, WiredSourceUtil.SOURCE_TRIGGER));
int nextDestinationFurniSource = normalizeDestinationFurniSource(param(params, 6, WiredSourceUtil.SOURCE_TRIGGER));
int nextReferenceUserSource = normalizeUserSource(param(params, 7, WiredSourceUtil.SOURCE_TRIGGER));
int nextReferenceFurniSource = normalizeReferenceFurniSource(param(params, 8, WiredSourceUtil.SOURCE_TRIGGER));
String nextDestinationVariableToken = normalizeVariableToken((stringParts.length > 0) ? stringParts[0] : "");
String nextReferenceVariableToken = normalizeVariableToken((stringParts.length > 1) ? stringParts[1] : "");
this.validateDestination(room, nextDestinationTargetType, nextDestinationVariableToken);
if (nextReferenceMode == REF_VARIABLE) this.validateReference(room, nextReferenceTargetType, nextReferenceVariableToken);
int maxDelay = Emulator.getConfig().getInt("hotel.wired.max_delay", 20);
if (settings.getDelay() > maxDelay) throw new WiredSaveException("Delay too long");
List<HabboItem> nextDestinationItems = (nextDestinationTargetType == TARGET_FURNI && nextDestinationFurniSource == WiredSourceUtil.SOURCE_SELECTED) ? this.parseItems(settings.getFurniIds(), room) : new ArrayList<>();
List<HabboItem> nextReferenceItems = (nextReferenceMode == REF_VARIABLE && nextReferenceTargetType == TARGET_FURNI && nextReferenceFurniSource == SOURCE_SECONDARY_SELECTED) ? this.parseItems((stringParts.length > 2) ? stringParts[2] : "", room) : new ArrayList<>();
int selectionLimit = Emulator.getConfig().getInt("hotel.wired.furni.selection.count");
if (nextDestinationItems.size() > selectionLimit || nextReferenceItems.size() > selectionLimit) throw new WiredSaveException("Too many furni selected");
this.destinationSelectedFurni.clear();
this.destinationSelectedFurni.addAll(nextDestinationItems);
this.referenceSelectedFurni.clear();
this.referenceSelectedFurni.addAll(nextReferenceItems);
this.destinationTargetType = nextDestinationTargetType;
this.setDestinationVariableToken(nextDestinationVariableToken);
this.operation = nextOperation;
this.referenceMode = nextReferenceMode;
this.referenceConstantValue = nextReferenceConstantValue;
this.referenceTargetType = nextReferenceTargetType;
this.setReferenceVariableToken(nextReferenceVariableToken);
this.destinationUserSource = nextDestinationUserSource;
this.destinationFurniSource = nextDestinationFurniSource;
this.referenceUserSource = nextReferenceUserSource;
this.referenceFurniSource = nextReferenceFurniSource;
this.setDelay(settings.getDelay());
return true;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.destinationTargetType, this.destinationVariableToken, this.destinationVariableItemId, this.operation, this.referenceMode, this.referenceConstantValue, this.referenceTargetType, this.referenceVariableToken, this.referenceVariableItemId, this.destinationUserSource, this.destinationFurniSource, this.referenceUserSource, this.referenceFurniSource, this.getDelay(), this.toIds(this.destinationSelectedFurni), this.toIds(this.referenceSelectedFurni)));
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty() || !wiredData.startsWith("{")) return;
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data == null) return;
this.destinationTargetType = normalizeTargetType(data.destinationTargetType);
this.setDestinationVariableToken(normalizeVariableToken((data.destinationVariableToken != null) ? data.destinationVariableToken : ((data.destinationVariableItemId > 0) ? String.valueOf(data.destinationVariableItemId) : "")));
this.operation = normalizeOperation(data.operation);
this.referenceMode = normalizeReferenceMode(data.referenceMode);
this.referenceConstantValue = data.referenceConstantValue;
this.referenceTargetType = normalizeTargetType(data.referenceTargetType);
this.setReferenceVariableToken(normalizeVariableToken((data.referenceVariableToken != null) ? data.referenceVariableToken : ((data.referenceVariableItemId > 0) ? String.valueOf(data.referenceVariableItemId) : "")));
this.destinationUserSource = normalizeUserSource(data.destinationUserSource);
this.destinationFurniSource = normalizeDestinationFurniSource(data.destinationFurniSource);
this.referenceUserSource = normalizeUserSource(data.referenceUserSource);
this.referenceFurniSource = normalizeReferenceFurniSource(data.referenceFurniSource);
this.setDelay(Math.max(0, data.delay));
if (room != null) {
try {
this.destinationSelectedFurni.addAll(this.parseItems(data.destinationSelectedFurniIds, room));
this.referenceSelectedFurni.addAll(this.parseItems(data.referenceSelectedFurniIds, room));
} catch (WiredSaveException ignored) {
}
}
}
@Override
public void onPickUp() {
this.destinationTargetType = TARGET_USER;
this.setDestinationVariableToken("");
this.operation = OP_ASSIGN;
this.referenceMode = REF_CONSTANT;
this.referenceConstantValue = 0;
this.referenceTargetType = TARGET_USER;
this.setReferenceVariableToken("");
this.destinationUserSource = WiredSourceUtil.SOURCE_TRIGGER;
this.destinationFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
this.referenceUserSource = WiredSourceUtil.SOURCE_TRIGGER;
this.referenceFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
this.destinationSelectedFurni.clear();
this.referenceSelectedFurni.clear();
this.setDelay(0);
}
@Override
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
}
@Override
public WiredEffectType getType() {
return type;
}
@Override
public boolean requiresTriggeringUser() {
return (this.destinationTargetType == TARGET_USER && this.destinationUserSource == WiredSourceUtil.SOURCE_TRIGGER)
|| (this.referenceMode == REF_VARIABLE && this.referenceTargetType == TARGET_USER && this.referenceUserSource == WiredSourceUtil.SOURCE_TRIGGER);
}
private ReferenceSnapshot resolveReferences(WiredContext ctx, Room room) {
if (this.referenceMode != REF_VARIABLE) return null;
return switch (this.referenceTargetType) {
case TARGET_USER -> this.userReferences(ctx, room);
case TARGET_FURNI -> this.furniReferences(ctx, room);
case TARGET_CONTEXT -> this.contextReferences(ctx, room);
case TARGET_ROOM -> this.roomReferences(room);
default -> null;
};
}
private ReferenceSnapshot userReferences(WiredContext ctx, Room room) {
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_USER);
if (isInternalVariableToken(this.referenceVariableToken)) {
String key = getInternalVariableKey(this.referenceVariableToken);
if (!canUseUserInternalReference(key)) return null;
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.referenceUserSource)) {
if (roomUnit == null) continue;
Integer value = this.readUserInternalValue(room, roomUnit, key);
if (value != null) snapshot.add(roomUnit.getId(), value);
}
return snapshot.isEmpty() ? null : snapshot;
}
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.referenceVariableItemId);
if (definition == null || !definition.hasValue()) return null;
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.referenceUserSource)) {
if (roomUnit == null) continue;
Habbo habbo = room.getHabbo(roomUnit);
if (habbo != null) snapshot.add(roomUnit.getId(), room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), this.referenceVariableItemId));
}
return snapshot.isEmpty() ? null : snapshot;
}
private ReferenceSnapshot furniReferences(WiredContext ctx, Room room) {
int source = (this.referenceFurniSource == SOURCE_SECONDARY_SELECTED) ? WiredSourceUtil.SOURCE_SELECTED : this.referenceFurniSource;
if (source == WiredSourceUtil.SOURCE_SELECTED) this.validateItems(this.referenceSelectedFurni);
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_FURNI);
if (isInternalVariableToken(this.referenceVariableToken)) {
String key = getInternalVariableKey(this.referenceVariableToken);
if (!canUseFurniInternalReference(key)) return null;
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, source, this.referenceSelectedFurni)) {
if (item == null) continue;
Integer value = this.readFurniInternalValue(room, item, key);
if (value != null) snapshot.add(item.getId(), value);
}
return snapshot.isEmpty() ? null : snapshot;
}
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.referenceVariableItemId);
if (definition == null || !definition.hasValue()) return null;
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, source, this.referenceSelectedFurni)) {
if (item != null) snapshot.add(item.getId(), room.getFurniVariableManager().getCurrentValue(item.getId(), this.referenceVariableItemId));
}
return snapshot.isEmpty() ? null : snapshot;
}
private ReferenceSnapshot roomReferences(Room room) {
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_ROOM);
if (isInternalVariableToken(this.referenceVariableToken)) {
String key = getInternalVariableKey(this.referenceVariableToken);
if (!canUseRoomInternalReference(key)) return null;
Integer value = this.readRoomInternalValue(room, key);
if (value == null) return null;
snapshot.add(room.getId(), value);
return snapshot;
}
WiredVariableDefinitionInfo definition = room.getRoomVariableManager().getDefinitionInfo(this.referenceVariableItemId);
if (definition == null || !definition.hasValue()) return null;
snapshot.add(room.getId(), room.getRoomVariableManager().getCurrentValue(this.referenceVariableItemId));
return snapshot;
}
private ReferenceSnapshot contextReferences(WiredContext ctx, Room room) {
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_CONTEXT);
if (isInternalVariableToken(this.referenceVariableToken)) {
String key = getInternalVariableKey(this.referenceVariableToken);
if (!canUseContextInternalReference(key)) return null;
Integer value = WiredInternalVariableSupport.readContextValue(ctx, key);
if (value == null) return null;
snapshot.add(this.referenceVariableItemId > 0 ? this.referenceVariableItemId : room.getId(), value);
return snapshot;
}
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, this.referenceVariableItemId);
if (definition == null || !definition.hasValue() || !WiredContextVariableSupport.hasVariable(ctx, this.referenceVariableItemId)) return null;
Integer value = WiredContextVariableSupport.getCurrentValue(ctx, this.referenceVariableItemId);
if (value == null) return null;
snapshot.add(this.referenceVariableItemId, value);
return snapshot;
}
private Integer referenceFor(ReferenceSnapshot snapshot, int destinationEntityId, int destinationTarget, int destinationIndex) {
if (this.referenceMode != REF_VARIABLE) return this.referenceConstantValue;
if (this.isUnaryOperation()) return 0;
if (snapshot == null || snapshot.isEmpty()) return null;
if (snapshot.targetType == destinationTarget && snapshot.values.containsKey(destinationEntityId)) return snapshot.values.get(destinationEntityId);
if (destinationIndex >= 0 && destinationIndex < snapshot.values.size()) return new ArrayList<>(snapshot.values.values()).get(destinationIndex);
return new ArrayList<>(snapshot.values.values()).get(0);
}
private int applyOperation(int currentValue, Integer referenceValue) {
return switch (this.operation) {
case OP_ASSIGN -> (referenceValue != null) ? referenceValue : currentValue;
case OP_ADD -> clamp((long) currentValue + referenceValue);
case OP_SUB -> clamp((long) currentValue - referenceValue);
case OP_MUL -> clamp((long) currentValue * referenceValue);
case OP_DIV -> (referenceValue == null || referenceValue == 0) ? currentValue : (currentValue / referenceValue);
case OP_POW -> (referenceValue == null || referenceValue < 0) ? 0 : clamp(Math.round(Math.pow(currentValue, referenceValue)));
case OP_MOD -> (referenceValue == null || referenceValue == 0) ? currentValue : (currentValue % referenceValue);
case OP_MIN -> (referenceValue != null) ? Math.min(currentValue, referenceValue) : currentValue;
case OP_MAX -> (referenceValue != null) ? Math.max(currentValue, referenceValue) : currentValue;
case OP_RANDOM -> (referenceValue == null || referenceValue <= 0) ? 0 : Emulator.getRandom().nextInt(referenceValue + 1);
case OP_ABS -> (currentValue == Integer.MIN_VALUE) ? Integer.MAX_VALUE : Math.abs(currentValue);
case OP_AND -> (referenceValue != null) ? (currentValue & referenceValue) : currentValue;
case OP_OR -> (referenceValue != null) ? (currentValue | referenceValue) : currentValue;
case OP_XOR -> (referenceValue != null) ? (currentValue ^ referenceValue) : currentValue;
case OP_NOT -> ~currentValue;
case OP_LSHIFT -> currentValue << shift(referenceValue);
case OP_RSHIFT -> currentValue >> shift(referenceValue);
default -> currentValue;
};
}
private boolean isUnaryOperation() {
return this.operation == OP_ABS || this.operation == OP_NOT;
}
private void validateDestination(Room room, int targetType, String variableToken) throws WiredSaveException {
if (variableToken == null || variableToken.isEmpty()) throw new WiredSaveException("wiredfurni.params.variables.validation.missing_variable");
boolean valid = switch (targetType) {
case TARGET_USER -> isInternalVariableToken(variableToken)
? canUseUserInternalDestination(getInternalVariableKey(variableToken))
: this.isValidUserCustomDestination(room, getCustomItemId(variableToken));
case TARGET_FURNI -> isInternalVariableToken(variableToken)
? canUseFurniInternalDestination(getInternalVariableKey(variableToken))
: this.isValidFurniCustomDestination(room, getCustomItemId(variableToken));
case TARGET_CONTEXT -> !isInternalVariableToken(variableToken)
&& this.isValidContextCustomDestination(room, getCustomItemId(variableToken));
case TARGET_ROOM -> !isInternalVariableToken(variableToken) && this.isValidRoomCustomDestination(room, getCustomItemId(variableToken));
default -> false;
};
if (!valid) throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
private void validateReference(Room room, int targetType, String variableToken) throws WiredSaveException {
if (variableToken == null || variableToken.isEmpty()) throw new WiredSaveException("wiredfurni.params.variables.validation.missing_variable");
boolean valid = switch (targetType) {
case TARGET_USER -> isInternalVariableToken(variableToken)
? canUseUserInternalReference(getInternalVariableKey(variableToken))
: this.isValidUserCustomReference(room, getCustomItemId(variableToken));
case TARGET_FURNI -> isInternalVariableToken(variableToken)
? canUseFurniInternalReference(getInternalVariableKey(variableToken))
: this.isValidFurniCustomDestination(room, getCustomItemId(variableToken));
case TARGET_CONTEXT -> isInternalVariableToken(variableToken)
? canUseContextInternalReference(getInternalVariableKey(variableToken))
: this.isValidContextCustomReference(room, getCustomItemId(variableToken));
case TARGET_ROOM -> isInternalVariableToken(variableToken)
? canUseRoomInternalReference(getInternalVariableKey(variableToken))
: this.isValidRoomCustomReference(room, getCustomItemId(variableToken));
default -> false;
};
if (!valid) throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
private boolean isValidUserCustomDestination(Room room, int variableItemId) {
WiredVariableDefinitionInfo definition = (room != null) ? room.getUserVariableManager().getDefinitionInfo(variableItemId) : null;
return definition != null && definition.hasValue() && !definition.isReadOnly();
}
private boolean isValidFurniCustomDestination(Room room, int variableItemId) {
WiredVariableDefinitionInfo definition = (room != null) ? room.getFurniVariableManager().getDefinitionInfo(variableItemId) : null;
return definition != null && definition.hasValue() && !definition.isReadOnly();
}
private boolean isValidRoomCustomDestination(Room room, int variableItemId) {
WiredVariableDefinitionInfo definition = (room != null) ? room.getRoomVariableManager().getDefinitionInfo(variableItemId) : null;
return definition != null && definition.hasValue() && !definition.isReadOnly();
}
private boolean isValidContextCustomDestination(Room room, int variableItemId) {
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, variableItemId);
return definition != null && definition.hasValue() && !definition.isReadOnly();
}
private boolean isValidUserCustomReference(Room room, int variableItemId) {
WiredVariableDefinitionInfo definition = (room != null) ? room.getUserVariableManager().getDefinitionInfo(variableItemId) : null;
return definition != null && definition.hasValue();
}
private boolean isValidRoomCustomReference(Room room, int variableItemId) {
WiredVariableDefinitionInfo definition = (room != null) ? room.getRoomVariableManager().getDefinitionInfo(variableItemId) : null;
return definition != null && definition.hasValue();
}
private boolean isValidContextCustomReference(Room room, int variableItemId) {
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, variableItemId);
return definition != null && definition.hasValue();
}
private boolean isValidFurniCustomReference(Room room, int variableItemId) {
WiredVariableDefinitionInfo definition = (room != null) ? room.getFurniVariableManager().getDefinitionInfo(variableItemId) : null;
return definition != null && definition.hasValue();
}
private Integer readUserInternalValue(Room room, RoomUnit roomUnit, String key) {
return WiredInternalVariableSupport.readUserValue(room, roomUnit, key);
}
private boolean writeUserInternalValue(Room room, RoomUnit roomUnit, String key, int value) {
return WiredInternalVariableSupport.writeUserValue(room, roomUnit, key, value);
}
private Integer readFurniInternalValue(Room room, HabboItem item, String key) {
return WiredInternalVariableSupport.readFurniValue(room, item, key);
}
private boolean writeFurniInternalValue(Room room, HabboItem item, String key, int value) {
return WiredInternalVariableSupport.writeFurniValue(room, item, key, value);
}
private Integer readRoomInternalValue(Room room, String key) {
return WiredInternalVariableSupport.readRoomValue(room, key);
}
private Integer getUserTeamScore(Room room, Habbo habbo) {
if (room == null || habbo == null || habbo.getHabboInfo().getGamePlayer() == null) return null;
Game game = this.resolveTeamGame(room, habbo);
GamePlayer gamePlayer = habbo.getHabboInfo().getGamePlayer();
if (game == null || gamePlayer.getTeamColor() == null) return gamePlayer.getScore();
GameTeam team = game.getTeam(gamePlayer.getTeamColor());
return (team != null) ? team.getTotalScore() : gamePlayer.getScore();
}
private Integer getTeamColorId(int effectId) {
TeamEffectData data = this.getTeamEffectData(effectId);
return data == null ? null : data.colorId;
}
private Integer getTeamTypeId(int effectId) {
TeamEffectData data = this.getTeamEffectData(effectId);
return data == null ? null : data.typeId;
}
private int getTeamMetric(Room room, GameTeamColors color, boolean score) {
Game game = this.resolveTeamGame(room, null);
if (game == null || color == null) return 0;
GameTeam team = game.getTeam(color);
if (team == null) return 0;
return score ? team.getTotalScore() : team.getMembers().size();
}
private Game resolveTeamGame(Room room, Habbo habbo) {
if (room == null) return null;
if (habbo != null && habbo.getHabboInfo() != null && habbo.getHabboInfo().getCurrentGame() != null) {
Game game = room.getGame(habbo.getHabboInfo().getCurrentGame());
if (game != null) return game;
}
Game wiredGame = room.getGame(WiredGame.class);
if (wiredGame != null) return wiredGame;
Game freezeGame = room.getGame(FreezeGame.class);
if (freezeGame != null) return freezeGame;
return room.getGame(BattleBanzaiGame.class);
}
private TeamEffectData getTeamEffectData(int effectValue) {
if (effectValue <= 0) return null;
if (effectValue >= 223 && effectValue <= 226) return new TeamEffectData(effectValue - 222, 0);
if (effectValue >= 33 && effectValue <= 36) return new TeamEffectData(effectValue - 32, 1);
if (effectValue >= 40 && effectValue <= 43) return new TeamEffectData(effectValue - 39, 2);
return null;
}
private boolean moveUserTo(Room room, RoomUnit roomUnit, int x, int y) {
if (room == null || roomUnit == null || room.getLayout() == null) return false;
RoomTile targetTile = room.getLayout().getTile((short) x, (short) y);
if (targetTile == null || targetTile.state == RoomTileState.INVALID) return false;
double targetZ = targetTile.getStackHeight() + ((targetTile.state == RoomTileState.SIT) ? -0.5 : 0);
return WiredUserMovementHelper.moveUser(room, roomUnit, targetTile, targetZ, roomUnit.getBodyRotation(), roomUnit.getHeadRotation(), 0, true);
}
private boolean moveFurniTo(Room room, HabboItem item, int x, int y, int rotation, double z) {
if (room == null || item == null || room.getLayout() == null) return false;
RoomTile targetTile = room.getLayout().getTile((short) x, (short) y);
if (targetTile == null || targetTile.state == RoomTileState.INVALID) return false;
FurnitureMovementError error = room.moveFurniTo(item, targetTile, rotation, z, null, true, true);
return error == FurnitureMovementError.NONE;
}
private List<HabboItem> parseItems(int[] ids, Room room) throws WiredSaveException {
List<HabboItem> items = new ArrayList<>();
if (ids == null || room == null) return items;
for (int id : ids) {
HabboItem item = room.getHabboItem(id);
if (item == null) throw new WiredSaveException(String.format("Item %s not found", id));
items.add(item);
}
return items;
}
private List<HabboItem> parseItems(List<Integer> ids, Room room) throws WiredSaveException {
List<HabboItem> items = new ArrayList<>();
if (ids == null || room == null) return items;
for (Integer id : ids) {
if (id == null || id <= 0) continue;
HabboItem item = room.getHabboItem(id);
if (item == null) throw new WiredSaveException(String.format("Item %s not found", id));
items.add(item);
}
return items;
}
private List<HabboItem> parseItems(String ids, Room room) throws WiredSaveException {
List<HabboItem> items = new ArrayList<>();
if (ids == null || ids.trim().isEmpty() || room == null) return items;
for (String part : ids.split("[;,\\t]")) {
int id = parseInteger(part);
if (id <= 0) continue;
HabboItem item = room.getHabboItem(id);
if (item == null) throw new WiredSaveException(String.format("Item %s not found", id));
items.add(item);
}
return items;
}
private String serializeStringData() {
return (this.destinationVariableToken == null ? "" : this.destinationVariableToken) + DELIM + (this.referenceVariableToken == null ? "" : this.referenceVariableToken) + DELIM + this.serializeIds(this.referenceSelectedFurni);
}
private String[] parseStringData(String value) {
return (value == null || value.isEmpty()) ? new String[0] : value.split("\\t", -1);
}
private List<Integer> toIds(List<HabboItem> items) {
List<Integer> ids = new ArrayList<>();
for (HabboItem item : items) if (item != null) ids.add(item.getId());
return ids;
}
private String serializeIds(List<HabboItem> items) {
StringBuilder builder = new StringBuilder();
for (HabboItem item : items) {
if (item == null) continue;
if (builder.length() > 0) builder.append(FURNI_DELIM);
builder.append(item.getId());
}
return builder.toString();
}
private void setDestinationVariableToken(String token) {
this.destinationVariableToken = normalizeVariableToken(token);
this.destinationVariableItemId = getCustomItemId(this.destinationVariableToken);
}
private void setReferenceVariableToken(String token) {
this.referenceVariableToken = normalizeVariableToken(token);
this.referenceVariableItemId = getCustomItemId(this.referenceVariableToken);
}
private static boolean isCustomVariableToken(String token) {
return token != null && token.startsWith(CUSTOM_TOKEN_PREFIX);
}
private static boolean isInternalVariableToken(String token) {
return token != null && token.startsWith(INTERNAL_TOKEN_PREFIX);
}
private static int getCustomItemId(String token) {
if (!isCustomVariableToken(token)) return 0;
return parseInteger(token.substring(CUSTOM_TOKEN_PREFIX.length()));
}
private static String getInternalVariableKey(String token) {
return isInternalVariableToken(token) ? WiredInternalVariableSupport.normalizeKey(token.substring(INTERNAL_TOKEN_PREFIX.length())) : "";
}
private static String normalizeVariableToken(String token) {
if (token == null) return "";
String normalized = token.trim();
if (normalized.isEmpty()) return "";
if (isCustomVariableToken(normalized)) return normalized;
if (isInternalVariableToken(normalized)) return INTERNAL_TOKEN_PREFIX + WiredInternalVariableSupport.normalizeKey(normalized.substring(INTERNAL_TOKEN_PREFIX.length()));
int parsedValue = parseInteger(normalized);
return parsedValue > 0 ? CUSTOM_TOKEN_PREFIX + parsedValue : "";
}
private static boolean canUseUserInternalDestination(String key) {
return WiredInternalVariableSupport.canUseUserDestination(key);
}
private static boolean canUseFurniInternalDestination(String key) {
return WiredInternalVariableSupport.canUseFurniDestination(key);
}
private static boolean canUseUserInternalReference(String key) {
return WiredInternalVariableSupport.canUseUserReference(key);
}
private static boolean canUseFurniInternalReference(String key) {
return WiredInternalVariableSupport.canUseFurniReference(key);
}
private static boolean canUseRoomInternalReference(String key) {
return WiredInternalVariableSupport.canUseRoomReference(key);
}
private static boolean canUseContextInternalReference(String key) {
return WiredInternalVariableSupport.canUseContextReference(key);
}
private static int param(int[] params, int index, int fallback) {
return (params.length > index) ? params[index] : fallback;
}
private static int normalizeTargetType(int value) {
return switch (value) {
case TARGET_FURNI, TARGET_CONTEXT, TARGET_ROOM -> value;
default -> TARGET_USER;
};
}
private static int normalizeReferenceMode(int value) {
return (value == REF_VARIABLE) ? REF_VARIABLE : REF_CONSTANT;
}
private static int normalizeUserSource(int value) {
return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
}
private static int normalizeDestinationFurniSource(int value) {
return switch (value) {
case WiredSourceUtil.SOURCE_SELECTED, WiredSourceUtil.SOURCE_SELECTOR, WiredSourceUtil.SOURCE_SIGNAL -> value;
default -> WiredSourceUtil.SOURCE_TRIGGER;
};
}
private static int normalizeReferenceFurniSource(int value) {
return switch (value) {
case SOURCE_SECONDARY_SELECTED, WiredSourceUtil.SOURCE_SELECTOR, WiredSourceUtil.SOURCE_SIGNAL -> value;
default -> WiredSourceUtil.SOURCE_TRIGGER;
};
}
private static int normalizeOperation(int value) {
return switch (value) {
case OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_POW, OP_MOD, OP_MIN, OP_MAX, OP_RANDOM, OP_ABS, OP_AND, OP_OR, OP_XOR, OP_NOT, OP_LSHIFT, OP_RSHIFT -> value;
default -> OP_ASSIGN;
};
}
private static int parseInteger(String value) {
try {
return (value == null || value.trim().isEmpty()) ? 0 : Integer.parseInt(value.trim());
} catch (NumberFormatException e) {
return 0;
}
}
private static int shift(Integer value) {
return (value == null) ? 0 : Math.max(0, Math.min(31, value));
}
private static int clamp(long value) {
return (value > Integer.MAX_VALUE) ? Integer.MAX_VALUE : ((value < Integer.MIN_VALUE) ? Integer.MIN_VALUE : (int) value);
}
static class JsonData {
int destinationTargetType, destinationVariableItemId, operation, referenceMode, referenceConstantValue, referenceTargetType, referenceVariableItemId, destinationUserSource, destinationFurniSource, referenceUserSource, referenceFurniSource, delay;
String destinationVariableToken, referenceVariableToken;
List<Integer> destinationSelectedFurniIds, referenceSelectedFurniIds;
JsonData(int destinationTargetType, String destinationVariableToken, int destinationVariableItemId, int operation, int referenceMode, int referenceConstantValue, int referenceTargetType, String referenceVariableToken, int referenceVariableItemId, int destinationUserSource, int destinationFurniSource, int referenceUserSource, int referenceFurniSource, int delay, List<Integer> destinationSelectedFurniIds, List<Integer> referenceSelectedFurniIds) {
this.destinationTargetType = destinationTargetType;
this.destinationVariableToken = destinationVariableToken;
this.destinationVariableItemId = destinationVariableItemId;
this.operation = operation;
this.referenceMode = referenceMode;
this.referenceConstantValue = referenceConstantValue;
this.referenceTargetType = referenceTargetType;
this.referenceVariableToken = referenceVariableToken;
this.referenceVariableItemId = referenceVariableItemId;
this.destinationUserSource = destinationUserSource;
this.destinationFurniSource = destinationFurniSource;
this.referenceUserSource = referenceUserSource;
this.referenceFurniSource = referenceFurniSource;
this.delay = delay;
this.destinationSelectedFurniIds = destinationSelectedFurniIds;
this.referenceSelectedFurniIds = referenceSelectedFurniIds;
}
}
private static class ReferenceSnapshot {
final int targetType;
final LinkedHashMap<Integer, Integer> values = new LinkedHashMap<>();
ReferenceSnapshot(int targetType) {
this.targetType = targetType;
}
void add(int entityId, int value) {
this.values.put(entityId, value);
}
boolean isEmpty() {
return this.values.isEmpty();
}
}
private static class TeamEffectData {
final int colorId;
final int typeId;
TeamEffectData(int colorId, int typeId) {
this.colorId = colorId;
this.typeId = typeId;
}
}
}
@@ -0,0 +1,434 @@
package com.eu.habbo.habbohotel.items.interactions.wired.effects;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.wired.WiredEffectType;
import com.eu.habbo.habbohotel.wired.core.WiredContext;
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
import gnu.trove.set.hash.THashSet;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class WiredEffectGiveVariable extends InteractionWiredEffect {
public static final WiredEffectType type = WiredEffectType.GIVE_VAR;
public static final int TARGET_USER = 0;
public static final int TARGET_FURNI = 1;
public static final int TARGET_CONTEXT = 2;
private int variableItemId = 0;
private int targetType = TARGET_USER;
private boolean overrideExisting = false;
private int initialValue = 0;
private int userSource = WiredSourceUtil.SOURCE_TRIGGER;
private int furniSource = WiredSourceUtil.SOURCE_TRIGGER;
private final THashSet<HabboItem> selectedFurni;
public WiredEffectGiveVariable(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
this.selectedFurni = new THashSet<>();
}
public WiredEffectGiveVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
this.selectedFurni = new THashSet<>();
}
@Override
public void execute(WiredContext ctx) {
Room room = ctx.room();
if (room == null) {
return;
}
switch (this.targetType) {
case TARGET_USER:
this.executeUserVariables(ctx, room);
return;
case TARGET_FURNI:
this.executeFurniVariables(ctx, room);
return;
case TARGET_CONTEXT:
this.executeContextVariables(ctx, room);
return;
default:
return;
}
}
private void executeContextVariables(WiredContext ctx, Room room) {
WiredVariableDefinitionInfo definitionInfo = WiredContextVariableSupport.getDefinitionInfo(room, this.variableItemId);
if (definitionInfo == null || definitionInfo.isReadOnly()) {
return;
}
Integer value = definitionInfo.hasValue() ? this.initialValue : null;
WiredContextVariableSupport.assignVariable(ctx, room, this.variableItemId, value, this.overrideExisting);
}
private void executeUserVariables(WiredContext ctx, Room room) {
WiredVariableDefinitionInfo definitionInfo = room.getUserVariableManager().getDefinitionInfo(this.variableItemId);
if (definitionInfo == null || definitionInfo.isReadOnly()) {
return;
}
List<RoomUnit> users = WiredSourceUtil.resolveUsers(ctx, this.userSource);
if (users.isEmpty()) {
return;
}
Integer value = definitionInfo.hasValue() ? this.initialValue : null;
for (RoomUnit roomUnit : users) {
if (roomUnit == null) {
continue;
}
Habbo habbo = room.getHabbo(roomUnit);
if (habbo == null) {
continue;
}
room.getUserVariableManager().assignVariable(habbo, this.variableItemId, value, this.overrideExisting);
}
}
private void executeFurniVariables(WiredContext ctx, Room room) {
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.variableItemId);
if (definition == null || definition.isReadOnly()) {
return;
}
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
this.validateItems(this.selectedFurni);
}
List<HabboItem> furni = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.selectedFurni);
if (furni.isEmpty()) {
return;
}
Integer value = definition.hasValue() ? this.initialValue : null;
for (HabboItem item : furni) {
if (item == null) {
continue;
}
room.getFurniVariableManager().assignVariable(item, this.variableItemId, value, this.overrideExisting);
}
}
@Deprecated
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
List<HabboItem> selectedItems = new ArrayList<>();
if (this.targetType == TARGET_FURNI && this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
for (HabboItem item : this.selectedFurni) {
if (item != null && room != null && room.getHabboItem(item.getId()) != null) {
selectedItems.add(item);
}
}
}
message.appendBoolean(false);
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
message.appendInt(selectedItems.size());
for (HabboItem item : selectedItems) {
message.appendInt(item.getId());
}
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(String.valueOf(this.variableItemId));
message.appendInt(5);
message.appendInt(this.targetType);
message.appendInt(this.overrideExisting ? 1 : 0);
message.appendInt(this.initialValue);
message.appendInt(this.userSource);
message.appendInt(this.furniSource);
message.appendInt(0);
message.appendInt(this.getType().code);
message.appendInt(this.getDelay());
message.appendInt(0);
}
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room == null) {
throw new WiredSaveException("Room not found");
}
int[] intParams = settings.getIntParams();
int nextTargetType = normalizeTargetType((intParams.length > 0) ? intParams[0] : TARGET_USER);
boolean nextOverrideExisting = (intParams.length > 1) && (intParams[1] == 1);
int nextInitialValue = (intParams.length > 2) ? intParams[2] : 0;
int nextUserSource = normalizeUserSource((intParams.length > 3) ? intParams[3] : WiredSourceUtil.SOURCE_TRIGGER);
int nextFurniSource = normalizeFurniSource((intParams.length > 4) ? intParams[4] : WiredSourceUtil.SOURCE_TRIGGER);
int nextVariableItemId = parseVariableItemId(settings.getStringParam());
if (nextVariableItemId <= 0 && settings.getFurniIds() != null && settings.getFurniIds().length > 0) {
int legacyItemId = settings.getFurniIds()[0];
if (room.getUserVariableManager().hasDefinition(legacyItemId)) {
nextVariableItemId = legacyItemId;
nextTargetType = TARGET_USER;
}
}
if (nextVariableItemId <= 0) {
throw new WiredSaveException("wiredfurni.params.variables.validation.missing_variable");
}
WiredVariableDefinitionInfo userDefinitionInfo = (nextTargetType == TARGET_USER) ? room.getUserVariableManager().getDefinitionInfo(nextVariableItemId) : null;
if (nextTargetType == TARGET_USER && (userDefinitionInfo == null || userDefinitionInfo.isReadOnly())) {
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
if (nextTargetType == TARGET_FURNI) {
WiredVariableDefinitionInfo furniDefinitionInfo = room.getFurniVariableManager().getDefinitionInfo(nextVariableItemId);
if (furniDefinitionInfo == null || furniDefinitionInfo.isReadOnly()) {
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
}
if (nextTargetType == TARGET_CONTEXT) {
WiredVariableDefinitionInfo contextDefinitionInfo = WiredContextVariableSupport.getDefinitionInfo(room, nextVariableItemId);
if (contextDefinitionInfo == null || contextDefinitionInfo.isReadOnly()) {
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
}
this.selectedFurni.clear();
if (nextTargetType == TARGET_FURNI && nextFurniSource == WiredSourceUtil.SOURCE_SELECTED) {
int[] furniIds = settings.getFurniIds();
int itemsCount = (furniIds != null) ? furniIds.length : 0;
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 = furniIds[i];
HabboItem item = room.getHabboItem(itemId);
if (item == null) {
throw new WiredSaveException(String.format("Item %s not found", itemId));
}
this.selectedFurni.add(item);
}
}
this.variableItemId = nextVariableItemId;
this.targetType = nextTargetType;
this.overrideExisting = nextOverrideExisting;
this.initialValue = nextInitialValue;
this.userSource = nextUserSource;
this.furniSource = nextFurniSource;
this.setDelay(settings.getDelay());
return true;
}
@Override
public String getWiredData() {
List<Integer> selectedItemIds = new ArrayList<>();
for (HabboItem item : this.selectedFurni) {
if (item != null) {
selectedItemIds.add(item.getId());
}
}
return WiredManager.getGson().toJson(new JsonData(this.variableItemId, this.targetType, this.overrideExisting, this.initialValue, this.userSource, this.furniSource, this.getDelay(), selectedItemIds));
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty()) {
return;
}
if (wiredData.startsWith("{")) {
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data != null) {
this.variableItemId = Math.max(0, data.variableItemId);
this.targetType = normalizeTargetType(data.targetType);
this.overrideExisting = data.overrideExisting;
this.initialValue = data.initialValue;
this.userSource = normalizeUserSource(data.userSource);
this.furniSource = normalizeFurniSource(data.furniSource);
this.setDelay(Math.max(0, data.delay));
if (room != null && data.selectedFurniIds != null) {
for (Integer itemId : data.selectedFurniIds) {
if (itemId == null || itemId <= 0) {
continue;
}
HabboItem item = room.getHabboItem(itemId);
if (item != null) {
this.selectedFurni.add(item);
}
}
}
}
return;
}
try {
this.variableItemId = Math.max(0, Integer.parseInt(wiredData.trim()));
this.targetType = TARGET_USER;
} catch (NumberFormatException ignored) {
}
}
@Override
public void onPickUp() {
this.variableItemId = 0;
this.targetType = TARGET_USER;
this.overrideExisting = false;
this.initialValue = 0;
this.userSource = WiredSourceUtil.SOURCE_TRIGGER;
this.furniSource = WiredSourceUtil.SOURCE_TRIGGER;
this.selectedFurni.clear();
this.setDelay(0);
}
@Override
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
}
@Override
public WiredEffectType getType() {
return type;
}
public int getVariableItemId() {
return this.variableItemId;
}
public int getTargetType() {
return this.targetType;
}
public boolean isOverrideExisting() {
return this.overrideExisting;
}
public int getInitialValue() {
return this.initialValue;
}
public int getUserSource() {
return this.userSource;
}
public int getFurniSource() {
return this.furniSource;
}
public THashSet<HabboItem> getSelectedFurni() {
return this.selectedFurni;
}
private static int normalizeTargetType(int value) {
switch (value) {
case TARGET_FURNI:
case TARGET_CONTEXT:
return value;
default:
return TARGET_USER;
}
}
private static int normalizeUserSource(int value) {
return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
}
private static int normalizeFurniSource(int value) {
switch (value) {
case WiredSourceUtil.SOURCE_SELECTED:
case WiredSourceUtil.SOURCE_SELECTOR:
case WiredSourceUtil.SOURCE_SIGNAL:
return value;
default:
return WiredSourceUtil.SOURCE_TRIGGER;
}
}
private static int parseVariableItemId(String value) {
if (value == null || value.trim().isEmpty()) {
return 0;
}
try {
return Math.max(0, Integer.parseInt(value.trim()));
} catch (NumberFormatException ignored) {
return 0;
}
}
static class JsonData {
int variableItemId;
int targetType;
boolean overrideExisting;
int initialValue;
int userSource;
int furniSource;
int delay;
List<Integer> selectedFurniIds;
JsonData(int variableItemId, int targetType, boolean overrideExisting, int initialValue, int userSource, int furniSource, int delay, List<Integer> selectedFurniIds) {
this.variableItemId = variableItemId;
this.targetType = targetType;
this.overrideExisting = overrideExisting;
this.initialValue = initialValue;
this.userSource = userSource;
this.furniSource = furniSource;
this.delay = delay;
this.selectedFurniIds = selectedFurniIds;
}
}
}
@@ -0,0 +1,370 @@
package com.eu.habbo.habbohotel.items.interactions.wired.effects;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.wired.WiredEffectType;
import com.eu.habbo.habbohotel.wired.core.WiredContext;
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
import gnu.trove.set.hash.THashSet;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class WiredEffectRemoveVariable extends InteractionWiredEffect {
public static final WiredEffectType type = WiredEffectType.REMOVE_VAR;
public static final int TARGET_USER = 0;
public static final int TARGET_FURNI = 1;
public static final int TARGET_CONTEXT = 2;
private int variableItemId = 0;
private int targetType = TARGET_USER;
private int userSource = WiredSourceUtil.SOURCE_TRIGGER;
private int furniSource = WiredSourceUtil.SOURCE_TRIGGER;
private final THashSet<HabboItem> selectedFurni;
public WiredEffectRemoveVariable(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
this.selectedFurni = new THashSet<>();
}
public WiredEffectRemoveVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
this.selectedFurni = new THashSet<>();
}
@Override
public void execute(WiredContext ctx) {
Room room = ctx.room();
if (room == null) {
return;
}
switch (this.targetType) {
case TARGET_USER:
this.executeUserVariables(ctx, room);
return;
case TARGET_FURNI:
this.executeFurniVariables(ctx, room);
return;
case TARGET_CONTEXT:
this.executeContextVariables(ctx, room);
return;
default:
return;
}
}
private void executeUserVariables(WiredContext ctx, Room room) {
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.variableItemId);
if (definition == null || definition.isReadOnly()) {
return;
}
List<RoomUnit> users = WiredSourceUtil.resolveUsers(ctx, this.userSource);
for (RoomUnit roomUnit : users) {
if (roomUnit == null) {
continue;
}
Habbo habbo = room.getHabbo(roomUnit);
if (habbo == null) {
continue;
}
room.getUserVariableManager().removeVariable(habbo.getHabboInfo().getId(), this.variableItemId);
}
}
private void executeFurniVariables(WiredContext ctx, Room room) {
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.variableItemId);
if (definition == null || definition.isReadOnly()) {
return;
}
if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
this.validateItems(this.selectedFurni);
}
List<HabboItem> furni = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.selectedFurni);
for (HabboItem item : furni) {
if (item == null) {
continue;
}
room.getFurniVariableManager().removeVariable(item.getId(), this.variableItemId);
}
}
private void executeContextVariables(WiredContext ctx, Room room) {
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, this.variableItemId);
if (definition == null || definition.isReadOnly()) {
return;
}
WiredContextVariableSupport.removeVariable(ctx, room, this.variableItemId);
}
@Deprecated
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
List<HabboItem> selectedItems = new ArrayList<>();
if (this.targetType == TARGET_FURNI && this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
for (HabboItem item : this.selectedFurni) {
if (item != null && room != null && room.getHabboItem(item.getId()) != null) {
selectedItems.add(item);
}
}
}
message.appendBoolean(false);
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
message.appendInt(selectedItems.size());
for (HabboItem item : selectedItems) {
message.appendInt(item.getId());
}
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(String.valueOf(this.variableItemId));
message.appendInt(3);
message.appendInt(this.targetType);
message.appendInt(this.userSource);
message.appendInt(this.furniSource);
message.appendInt(0);
message.appendInt(this.getType().code);
message.appendInt(this.getDelay());
message.appendInt(0);
}
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room == null) {
throw new WiredSaveException("Room not found");
}
int[] intParams = settings.getIntParams();
int nextTargetType = normalizeTargetType((intParams.length > 0) ? intParams[0] : TARGET_USER);
int nextUserSource = normalizeUserSource((intParams.length > 1) ? intParams[1] : WiredSourceUtil.SOURCE_TRIGGER);
int nextFurniSource = normalizeFurniSource((intParams.length > 2) ? intParams[2] : WiredSourceUtil.SOURCE_TRIGGER);
int nextVariableItemId = parseVariableItemId(settings.getStringParam());
if (nextVariableItemId <= 0) {
throw new WiredSaveException("wiredfurni.params.variables.validation.missing_variable");
}
switch (nextTargetType) {
case TARGET_USER:
WiredVariableDefinitionInfo userDefinition = room.getUserVariableManager().getDefinitionInfo(nextVariableItemId);
if (userDefinition == null || userDefinition.isReadOnly()) {
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
break;
case TARGET_FURNI:
WiredVariableDefinitionInfo furniDefinition = room.getFurniVariableManager().getDefinitionInfo(nextVariableItemId);
if (furniDefinition == null || furniDefinition.isReadOnly()) {
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
break;
case TARGET_CONTEXT:
WiredVariableDefinitionInfo contextDefinition = WiredContextVariableSupport.getDefinitionInfo(room, nextVariableItemId);
if (contextDefinition == null || contextDefinition.isReadOnly()) {
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
break;
default:
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
this.selectedFurni.clear();
if (nextTargetType == TARGET_FURNI && nextFurniSource == WiredSourceUtil.SOURCE_SELECTED) {
int[] furniIds = settings.getFurniIds();
int itemsCount = (furniIds != null) ? furniIds.length : 0;
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 = furniIds[i];
HabboItem item = room.getHabboItem(itemId);
if (item == null) {
throw new WiredSaveException(String.format("Item %s not found", itemId));
}
this.selectedFurni.add(item);
}
}
this.variableItemId = nextVariableItemId;
this.targetType = nextTargetType;
this.userSource = nextUserSource;
this.furniSource = nextFurniSource;
this.setDelay(settings.getDelay());
return true;
}
@Override
public String getWiredData() {
List<Integer> selectedItemIds = new ArrayList<>();
for (HabboItem item : this.selectedFurni) {
if (item != null) {
selectedItemIds.add(item.getId());
}
}
return WiredManager.getGson().toJson(new JsonData(this.variableItemId, this.targetType, this.userSource, this.furniSource, this.getDelay(), selectedItemIds));
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty()) {
return;
}
if (wiredData.startsWith("{")) {
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data != null) {
this.variableItemId = Math.max(0, data.variableItemId);
this.targetType = normalizeTargetType(data.targetType);
this.userSource = normalizeUserSource(data.userSource);
this.furniSource = normalizeFurniSource(data.furniSource);
this.setDelay(Math.max(0, data.delay));
if (room != null && data.selectedFurniIds != null) {
for (Integer itemId : data.selectedFurniIds) {
if (itemId == null || itemId <= 0) {
continue;
}
HabboItem item = room.getHabboItem(itemId);
if (item != null) {
this.selectedFurni.add(item);
}
}
}
}
return;
}
try {
this.variableItemId = Math.max(0, Integer.parseInt(wiredData.trim()));
this.targetType = TARGET_USER;
} catch (NumberFormatException ignored) {
}
}
@Override
public void onPickUp() {
this.variableItemId = 0;
this.targetType = TARGET_USER;
this.userSource = WiredSourceUtil.SOURCE_TRIGGER;
this.furniSource = WiredSourceUtil.SOURCE_TRIGGER;
this.selectedFurni.clear();
this.setDelay(0);
}
@Override
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
}
@Override
public WiredEffectType getType() {
return type;
}
private static int normalizeTargetType(int value) {
switch (value) {
case TARGET_FURNI:
case TARGET_CONTEXT:
return value;
default:
return TARGET_USER;
}
}
private static int normalizeUserSource(int value) {
return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
}
private static int normalizeFurniSource(int value) {
switch (value) {
case WiredSourceUtil.SOURCE_SELECTED:
case WiredSourceUtil.SOURCE_SELECTOR:
case WiredSourceUtil.SOURCE_SIGNAL:
return value;
default:
return WiredSourceUtil.SOURCE_TRIGGER;
}
}
private static int parseVariableItemId(String value) {
if (value == null || value.trim().isEmpty()) {
return 0;
}
try {
return Math.max(0, Integer.parseInt(value.trim()));
} catch (NumberFormatException ignored) {
return 0;
}
}
static class JsonData {
int variableItemId;
int targetType;
int userSource;
int furniSource;
int delay;
List<Integer> selectedFurniIds;
JsonData(int variableItemId, int targetType, int userSource, int furniSource, int delay, List<Integer> selectedFurniIds) {
this.variableItemId = variableItemId;
this.targetType = targetType;
this.userSource = userSource;
this.furniSource = furniSource;
this.delay = delay;
this.selectedFurniIds = selectedFurniIds;
}
}
}
@@ -122,13 +122,13 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
for (RoomUnit user : usersToSend) {
for (HabboItem sourceItem : furniToSend) {
for (HabboItem antenna : resolvedAntennas) {
fireSignalAtAntenna(room, antenna, user, sourceItem, nextDepth);
fireSignalAtAntenna(ctx, room, antenna, user, sourceItem, nextDepth);
}
}
}
}
private void fireSignalAtAntenna(Room room, HabboItem antenna, RoomUnit actor, HabboItem sourceItem, int depth) {
private void fireSignalAtAntenna(WiredContext ctx, Room room, HabboItem antenna, RoomUnit actor, HabboItem sourceItem, int depth) {
if (antenna == null) return;
RoomTile tile = room.getLayout().getTile(antenna.getX(), antenna.getY());
if (tile == null) return;
@@ -144,6 +144,9 @@ public class WiredEffectSendSignal extends InteractionWiredEffect {
.tile(tile)
.callStackDepth(depth)
.signalChannel(signalChannel)
.signalUserCount(actor != null ? 1 : 0)
.signalFurniCount(sourceItem != null ? 1 : 0)
.contextVariableScope(ctx.contextVariables())
.triggeredByEffect(true);
if (actor != null) builder.actor(actor);
@@ -0,0 +1,132 @@
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
import java.sql.ResultSet;
import java.sql.SQLException;
public class WiredExtraContextVariable extends InteractionWiredExtra {
public static final int CODE = 84;
private String variableName = "";
private boolean hasValue = false;
public WiredExtraContextVariable(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredExtraContextVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room == null) {
throw new WiredSaveException("Room not found");
}
int[] intParams = settings.getIntParams();
String normalizedName = WiredVariableNameValidator.normalizeForSave(settings.getStringParam());
WiredVariableNameValidator.validateDefinitionName(room, this.getId(), normalizedName);
this.variableName = normalizedName;
this.hasValue = (intParams.length > 0) && (intParams[0] == 1);
WiredContextVariableSupport.broadcastDefinitions(room);
return true;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.variableName, this.hasValue));
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
message.appendBoolean(false);
message.appendInt(0);
message.appendInt(0);
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(this.variableName);
message.appendInt(1);
message.appendInt(this.hasValue ? 1 : 0);
message.appendInt(0);
message.appendInt(CODE);
message.appendInt(0);
message.appendInt(0);
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty()) {
return;
}
if (wiredData.startsWith("{")) {
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data != null) {
this.variableName = WiredVariableNameValidator.normalizeLegacy(data.variableName);
this.hasValue = data.hasValue;
}
return;
}
this.variableName = WiredVariableNameValidator.normalizeLegacy(wiredData);
}
@Override
public void onPickUp() {
this.variableName = "";
this.hasValue = false;
}
@Override
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
}
@Override
public boolean hasConfiguration() {
return true;
}
public String getVariableName() {
return this.variableName;
}
public boolean hasValue() {
return this.hasValue;
}
static class JsonData {
String variableName;
boolean hasValue;
JsonData(String variableName, boolean hasValue) {
this.variableName = variableName;
this.hasValue = hasValue;
}
}
}
@@ -0,0 +1,28 @@
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
import com.eu.habbo.habbohotel.items.Item;
import java.sql.ResultSet;
import java.sql.SQLException;
public class WiredExtraFilterFurniByVariable extends WiredExtraVariableFilterBase {
public static final int CODE = 78;
public WiredExtraFilterFurniByVariable(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredExtraFilterFurniByVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
protected int getVariableTargetType() {
return TARGET_FURNI;
}
@Override
protected int getCode() {
return CODE;
}
}
@@ -0,0 +1,28 @@
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
import com.eu.habbo.habbohotel.items.Item;
import java.sql.ResultSet;
import java.sql.SQLException;
public class WiredExtraFilterUsersByVariable extends WiredExtraVariableFilterBase {
public static final int CODE = 77;
public WiredExtraFilterUsersByVariable(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredExtraFilterUsersByVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
protected int getVariableTargetType() {
return TARGET_USER;
}
@Override
protected int getCode() {
return CODE;
}
}
@@ -0,0 +1,157 @@
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
import java.sql.ResultSet;
import java.sql.SQLException;
public class WiredExtraFurniVariable extends InteractionWiredExtra {
public static final int CODE = 71;
public static final int AVAILABILITY_ROOM_ACTIVE = 1;
public static final int AVAILABILITY_PERMANENT = 10;
private String variableName = "";
private boolean hasValue = false;
private int availability = AVAILABILITY_ROOM_ACTIVE;
public WiredExtraFurniVariable(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredExtraFurniVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
int[] intParams = settings.getIntParams();
String normalizedName = WiredVariableNameValidator.normalizeForSave(settings.getStringParam());
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room == null) {
throw new WiredSaveException("Room not found");
}
WiredVariableNameValidator.validateDefinitionName(room, this.getId(), normalizedName);
this.variableName = normalizedName;
this.hasValue = (intParams.length > 0) && (intParams[0] == 1);
this.availability = normalizeAvailability((intParams.length > 1) ? intParams[1] : AVAILABILITY_ROOM_ACTIVE);
room.getFurniVariableManager().handleDefinitionUpdated(this);
return true;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.variableName, this.hasValue, this.availability));
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
message.appendBoolean(false);
message.appendInt(0);
message.appendInt(0);
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(this.variableName);
message.appendInt(2);
message.appendInt(this.hasValue ? 1 : 0);
message.appendInt(this.availability);
message.appendInt(0);
message.appendInt(CODE);
message.appendInt(0);
message.appendInt(0);
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty()) {
return;
}
if (wiredData.startsWith("{")) {
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data != null) {
this.variableName = WiredVariableNameValidator.normalizeLegacy(data.variableName);
this.hasValue = data.hasValue;
this.availability = normalizeAvailability(data.availability);
}
return;
}
this.variableName = WiredVariableNameValidator.normalizeLegacy(wiredData);
}
@Override
public void onPickUp() {
this.variableName = "";
this.hasValue = false;
this.availability = AVAILABILITY_ROOM_ACTIVE;
}
@Override
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
}
@Override
public boolean hasConfiguration() {
return true;
}
public String getVariableName() {
return this.variableName;
}
public boolean hasValue() {
return this.hasValue;
}
public int getAvailability() {
return this.availability;
}
public boolean isPermanentAvailability() {
return this.availability == AVAILABILITY_PERMANENT;
}
private static int normalizeAvailability(int value) {
if (value == AVAILABILITY_PERMANENT) {
return AVAILABILITY_PERMANENT;
}
return AVAILABILITY_ROOM_ACTIVE;
}
static class JsonData {
String variableName;
boolean hasValue;
int availability;
JsonData(String variableName, boolean hasValue, int availability) {
this.variableName = variableName;
this.hasValue = hasValue;
this.availability = availability;
}
}
}
@@ -0,0 +1,159 @@
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
import java.sql.ResultSet;
import java.sql.SQLException;
public class WiredExtraRoomVariable extends InteractionWiredExtra {
public static final int CODE = 72;
public static final int AVAILABILITY_ROOM_ACTIVE = 1;
public static final int AVAILABILITY_PERMANENT = 10;
public static final int AVAILABILITY_SHARED = 11;
private String variableName = "";
private int availability = AVAILABILITY_ROOM_ACTIVE;
public WiredExtraRoomVariable(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredExtraRoomVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
String normalizedName = WiredVariableNameValidator.normalizeForSave(settings.getStringParam());
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room == null) {
throw new WiredSaveException("Room not found");
}
WiredVariableNameValidator.validateDefinitionName(room, this.getId(), normalizedName);
int[] intParams = settings.getIntParams();
this.variableName = normalizedName;
this.availability = normalizeAvailability((intParams.length > 0) ? intParams[0] : AVAILABILITY_ROOM_ACTIVE);
room.getRoomVariableManager().handleDefinitionUpdated(this);
return true;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.variableName, this.availability));
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
int currentValue = (room != null) ? room.getRoomVariableManager().getCurrentValue(this.getId()) : 0;
message.appendBoolean(false);
message.appendInt(0);
message.appendInt(0);
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(this.variableName);
message.appendInt(2);
message.appendInt(this.availability);
message.appendInt(currentValue);
message.appendInt(0);
message.appendInt(CODE);
message.appendInt(0);
message.appendInt(0);
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty()) {
return;
}
if (wiredData.startsWith("{")) {
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data != null) {
this.variableName = WiredVariableNameValidator.normalizeLegacy(data.variableName);
this.availability = normalizeAvailability(data.availability);
}
return;
}
this.variableName = WiredVariableNameValidator.normalizeLegacy(wiredData);
}
@Override
public void onPickUp() {
this.variableName = "";
this.availability = AVAILABILITY_ROOM_ACTIVE;
}
@Override
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
}
@Override
public boolean hasConfiguration() {
return true;
}
public String getVariableName() {
return this.variableName;
}
public boolean hasValue() {
return true;
}
public int getAvailability() {
return this.availability;
}
public boolean isPermanentAvailability() {
return this.availability == AVAILABILITY_PERMANENT || this.availability == AVAILABILITY_SHARED;
}
public boolean isSharedAvailability() {
return this.availability == AVAILABILITY_SHARED;
}
private static int normalizeAvailability(int value) {
if (value == AVAILABILITY_PERMANENT || value == AVAILABILITY_SHARED) {
return value;
}
return AVAILABILITY_ROOM_ACTIVE;
}
static class JsonData {
String variableName;
int availability;
JsonData(String variableName, int availability) {
this.variableName = variableName;
this.availability = availability;
}
}
}
@@ -0,0 +1,271 @@
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.habbohotel.wired.core.WiredVariableTextConnectorSupport;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.regex.Pattern;
public class WiredExtraTextInputVariable extends InteractionWiredExtra {
public static final int CODE = 85;
public static final int DISPLAY_NUMERIC = 1;
public static final int DISPLAY_TEXTUAL = 2;
public static final String DEFAULT_CAPTURER_NAME = "";
public static final int MAX_CAPTURER_NAME_LENGTH = 32;
private static final String CUSTOM_TOKEN_PREFIX = "custom:";
private static final Pattern WRAPPED_PLACEHOLDER_PATTERN = Pattern.compile("^#(.*)#$");
private int variableItemId = 0;
private String variableToken = "";
private String capturerName = DEFAULT_CAPTURER_NAME;
private int displayType = DISPLAY_NUMERIC;
public WiredExtraTextInputVariable(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredExtraTextInputVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room == null) {
throw new WiredSaveException("Room not found");
}
int[] intParams = settings.getIntParams();
String[] stringData = splitStringData(settings.getStringParam());
String nextVariableToken = normalizeVariableToken(stringData[0]);
int nextVariableItemId = getCustomItemId(nextVariableToken);
if (nextVariableItemId <= 0) {
throw new WiredSaveException("wiredfurni.params.variables.validation.missing_variable");
}
WiredVariableDefinitionInfo definitionInfo = WiredContextVariableSupport.getDefinitionInfo(room, nextVariableItemId);
if (definitionInfo == null || !definitionInfo.hasValue()) {
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
this.variableItemId = nextVariableItemId;
this.variableToken = nextVariableToken;
this.capturerName = normalizeCapturerName(stringData[1]);
this.displayType = normalizeDisplayType((intParams.length > 0) ? intParams[0] : DISPLAY_NUMERIC);
if (!canUseTextualDisplay(room, this.variableItemId)) {
this.displayType = DISPLAY_NUMERIC;
}
return true;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.variableToken, this.variableItemId, this.capturerName, this.displayType));
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
message.appendBoolean(false);
message.appendInt(0);
message.appendInt(0);
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(this.variableToken + "\t" + this.capturerName);
message.appendInt(1);
message.appendInt(this.getDisplayType(room));
message.appendInt(0);
message.appendInt(CODE);
message.appendInt(0);
message.appendInt(0);
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty()) {
return;
}
if (wiredData.startsWith("{")) {
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data != null) {
this.variableToken = normalizeVariableToken((data.variableToken != null)
? data.variableToken
: ((data.variableItemId > 0) ? String.valueOf(data.variableItemId) : ""));
this.variableItemId = getCustomItemId(this.variableToken);
this.capturerName = normalizeCapturerName(data.capturerName);
this.displayType = normalizeDisplayType(data.displayType);
}
return;
}
String[] legacyData = splitStringData(wiredData);
this.variableToken = normalizeVariableToken(legacyData[0]);
this.variableItemId = getCustomItemId(this.variableToken);
this.capturerName = normalizeCapturerName(legacyData[1]);
}
@Override
public void onPickUp() {
this.variableItemId = 0;
this.variableToken = "";
this.capturerName = DEFAULT_CAPTURER_NAME;
this.displayType = DISPLAY_NUMERIC;
}
@Override
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
}
@Override
public boolean hasConfiguration() {
return true;
}
public int getVariableItemId() {
return this.variableItemId;
}
public String getVariableToken() {
return this.variableToken;
}
public String getCapturerName() {
return this.capturerName;
}
public String getPlaceholderToken() {
return this.capturerName.isEmpty() ? "" : "#" + this.capturerName + "#";
}
public int getDisplayType(Room room) {
return (this.displayType == DISPLAY_TEXTUAL && canUseTextualDisplay(room, this.variableItemId))
? DISPLAY_TEXTUAL
: DISPLAY_NUMERIC;
}
public Integer resolveCapturedValue(Room room, String rawValue) {
String normalizedValue = rawValue != null ? rawValue.trim() : "";
if (normalizedValue.isEmpty()) {
return null;
}
if (this.getDisplayType(room) == DISPLAY_TEXTUAL) {
return WiredVariableTextConnectorSupport.toValue(room, this.variableItemId, normalizedValue);
}
try {
return Integer.parseInt(normalizedValue);
} catch (NumberFormatException ignored) {
return null;
}
}
private static boolean canUseTextualDisplay(Room room, int definitionItemId) {
WiredVariableDefinitionInfo definitionInfo = WiredContextVariableSupport.getDefinitionInfo(room, definitionItemId);
return definitionInfo != null && definitionInfo.hasValue() && definitionInfo.isTextConnected();
}
private static String[] splitStringData(String value) {
if (value == null) {
return new String[]{ "", DEFAULT_CAPTURER_NAME };
}
String[] parts = value.split("\t", -1);
if (parts.length == 1) {
return new String[]{ parts[0], DEFAULT_CAPTURER_NAME };
}
return new String[]{ parts[0], parts[1] };
}
private static int normalizeDisplayType(int value) {
return (value == DISPLAY_TEXTUAL) ? DISPLAY_TEXTUAL : DISPLAY_NUMERIC;
}
private static String normalizeCapturerName(String value) {
if (value == null) {
return DEFAULT_CAPTURER_NAME;
}
String normalized = value.trim().replace("\t", "").replace("\r", "").replace("\n", "");
if (WRAPPED_PLACEHOLDER_PATTERN.matcher(normalized).matches()) {
normalized = normalized.substring(1, normalized.length() - 1).trim();
}
if (normalized.length() > MAX_CAPTURER_NAME_LENGTH) {
normalized = normalized.substring(0, MAX_CAPTURER_NAME_LENGTH);
}
return normalized;
}
private static String normalizeVariableToken(String value) {
String normalized = value == null ? "" : value.trim();
if (normalized.isEmpty()) {
return "";
}
if (normalized.startsWith(CUSTOM_TOKEN_PREFIX)) {
return normalized;
}
try {
int parsedValue = Integer.parseInt(normalized);
return parsedValue > 0 ? (CUSTOM_TOKEN_PREFIX + parsedValue) : "";
} catch (NumberFormatException ignored) {
return "";
}
}
private static int getCustomItemId(String token) {
if (token == null || !token.startsWith(CUSTOM_TOKEN_PREFIX)) {
return 0;
}
try {
return Integer.parseInt(token.substring(CUSTOM_TOKEN_PREFIX.length()));
} catch (NumberFormatException ignored) {
return 0;
}
}
static class JsonData {
String variableToken;
int variableItemId;
String capturerName;
int displayType;
JsonData(String variableToken, int variableItemId, String capturerName, int displayType) {
this.variableToken = variableToken;
this.variableItemId = variableItemId;
this.capturerName = capturerName;
this.displayType = displayType;
}
}
}
@@ -0,0 +1,544 @@
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredInternalVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
import gnu.trove.set.hash.THashSet;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public class WiredExtraTextOutputVariable extends InteractionWiredExtra {
public static final int CODE = 80;
public static final int TARGET_USER = 0;
public static final int TARGET_FURNI = 1;
public static final int TARGET_CONTEXT = 2;
public static final int TARGET_ROOM = 3;
public static final int DISPLAY_NUMERIC = 1;
public static final int DISPLAY_TEXTUAL = 2;
public static final int TYPE_SINGLE = 1;
public static final int TYPE_MULTIPLE = 2;
public static final String DEFAULT_VARIABLE_TOKEN = "";
public static final String DEFAULT_PLACEHOLDER_NAME = "";
public static final String DEFAULT_DELIMITER = ", ";
public static final int MAX_PLACEHOLDER_NAME_LENGTH = 32;
public static final int MAX_DELIMITER_LENGTH = 16;
private static final String CUSTOM_TOKEN_PREFIX = "custom:";
private static final String INTERNAL_TOKEN_PREFIX = "internal:";
private static final Pattern WRAPPED_PLACEHOLDER_PATTERN = Pattern.compile("^\\$\\((.*)\\)$");
private final THashSet<HabboItem> items;
private int targetType = TARGET_USER;
private int displayType = DISPLAY_NUMERIC;
private int placeholderType = TYPE_SINGLE;
private int userSource = WiredSourceUtil.SOURCE_TRIGGER;
private int furniSource = WiredSourceUtil.SOURCE_TRIGGER;
private int variableItemId = 0;
private String variableToken = DEFAULT_VARIABLE_TOKEN;
private String placeholderName = DEFAULT_PLACEHOLDER_NAME;
private String delimiter = DEFAULT_DELIMITER;
public WiredExtraTextOutputVariable(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
this.items = new THashSet<>();
}
public WiredExtraTextOutputVariable(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 execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room == null) {
throw new WiredSaveException("Room not found");
}
int[] intParams = settings.getIntParams();
String[] stringData = splitStringData(settings.getStringParam());
int nextTargetType = normalizeTargetType((intParams.length > 0) ? intParams[0] : TARGET_USER);
String nextVariableToken = normalizeVariableToken(stringData[0]);
if (nextVariableToken.isEmpty()) {
throw new WiredSaveException("wiredfurni.params.variables.validation.missing_variable");
}
if (!isValidVariable(room, nextTargetType, nextVariableToken)) {
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
int nextFurniSource = normalizeFurniSource((intParams.length > 4) ? intParams[4] : WiredSourceUtil.SOURCE_TRIGGER);
this.items.clear();
if (nextTargetType == TARGET_FURNI && nextFurniSource == WiredSourceUtil.SOURCE_SELECTED) {
if (settings.getFurniIds().length > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) {
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
for (int itemId : settings.getFurniIds()) {
HabboItem item = room.getHabboItem(itemId);
if (item != null) {
this.items.add(item);
}
}
}
this.targetType = nextTargetType;
this.setVariableToken(nextVariableToken);
this.displayType = normalizeDisplayType((intParams.length > 1) ? intParams[1] : DISPLAY_NUMERIC);
this.placeholderType = normalizePlaceholderType((intParams.length > 2) ? intParams[2] : TYPE_SINGLE);
this.userSource = normalizeUserSource((intParams.length > 3) ? intParams[3] : WiredSourceUtil.SOURCE_TRIGGER);
this.furniSource = nextFurniSource;
this.placeholderName = normalizePlaceholderName(stringData[1]);
this.delimiter = normalizeDelimiter(stringData[2]);
if (!canUseTextualDisplay(room, this.targetType, this.variableToken)) {
this.displayType = DISPLAY_NUMERIC;
}
return true;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(
this.targetType,
this.variableToken,
this.variableItemId,
this.displayType,
this.placeholderType,
this.userSource,
this.furniSource,
this.placeholderName,
this.delimiter,
this.items.stream().map(HabboItem::getId).collect(Collectors.toList())
));
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
this.refresh(room);
List<HabboItem> selectedItems = new ArrayList<>();
if (this.targetType == TARGET_FURNI && this.furniSource == WiredSourceUtil.SOURCE_SELECTED) {
selectedItems.addAll(this.items);
}
message.appendBoolean(false);
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
message.appendInt(selectedItems.size());
for (HabboItem item : selectedItems) {
message.appendInt(item.getId());
}
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(this.variableToken + "\t" + this.placeholderName + "\t" + this.delimiter);
message.appendInt(5);
message.appendInt(this.targetType);
message.appendInt(this.displayType);
message.appendInt(this.placeholderType);
message.appendInt(this.userSource);
message.appendInt(this.furniSource);
message.appendInt(0);
message.appendInt(CODE);
message.appendInt(0);
message.appendInt(0);
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty()) {
return;
}
if (wiredData.startsWith("{")) {
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data != null) {
this.targetType = normalizeTargetType(data.targetType);
this.setVariableToken(normalizeVariableToken((data.variableToken != null) ? data.variableToken : ((data.variableItemId > 0) ? String.valueOf(data.variableItemId) : "")));
this.displayType = normalizeDisplayType(data.displayType);
this.placeholderType = normalizePlaceholderType(data.placeholderType);
this.userSource = normalizeUserSource(data.userSource);
this.furniSource = normalizeFurniSource(data.furniSource);
this.placeholderName = normalizePlaceholderName(data.placeholderName);
this.delimiter = normalizeDelimiter(data.delimiter);
if (room != null && data.itemIds != null) {
for (Integer itemId : data.itemIds) {
if (itemId == null || itemId <= 0) {
continue;
}
HabboItem item = room.getHabboItem(itemId);
if (item != null) {
this.items.add(item);
}
}
}
if (room == null || !canUseTextualDisplay(room, this.targetType, this.variableToken)) {
this.displayType = DISPLAY_NUMERIC;
}
}
return;
}
String[] legacyData = splitStringData(wiredData);
this.setVariableToken(normalizeVariableToken(legacyData[0]));
this.placeholderName = normalizePlaceholderName(legacyData[1]);
this.delimiter = normalizeDelimiter(legacyData[2]);
}
@Override
public void onPickUp() {
this.items.clear();
this.targetType = TARGET_USER;
this.setVariableToken(DEFAULT_VARIABLE_TOKEN);
this.displayType = DISPLAY_NUMERIC;
this.placeholderType = TYPE_SINGLE;
this.userSource = WiredSourceUtil.SOURCE_TRIGGER;
this.furniSource = WiredSourceUtil.SOURCE_TRIGGER;
this.placeholderName = DEFAULT_PLACEHOLDER_NAME;
this.delimiter = DEFAULT_DELIMITER;
}
@Override
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
}
@Override
public boolean hasConfiguration() {
return true;
}
public int getTargetType() {
return this.targetType;
}
public String getVariableToken() {
return this.variableToken;
}
public int getVariableItemId() {
return this.variableItemId;
}
public int getDisplayType(Room room) {
return (this.displayType == DISPLAY_TEXTUAL && canUseTextualDisplay(room, this.targetType, this.variableToken))
? DISPLAY_TEXTUAL
: DISPLAY_NUMERIC;
}
public int getPlaceholderType() {
return this.placeholderType;
}
public String getPlaceholderName() {
return this.placeholderName;
}
public String getPlaceholderToken() {
return this.placeholderName.isEmpty() ? "" : "$(" + this.placeholderName + ")";
}
public String getDelimiter() {
return this.delimiter;
}
public int getUserSource() {
return this.userSource;
}
public int getFurniSource() {
return this.furniSource;
}
public THashSet<HabboItem> getItems() {
return this.items;
}
public boolean requiresActor() {
return this.targetType == TARGET_USER
&& (this.userSource == WiredSourceUtil.SOURCE_TRIGGER || this.userSource == WiredSourceUtil.SOURCE_CLICKED_USER);
}
public void refresh(Room room) {
THashSet<HabboItem> remove = new THashSet<>();
for (HabboItem item : this.items) {
if (room == null || room.getHabboItem(item.getId()) == null) {
remove.add(item);
}
}
for (HabboItem item : remove) {
this.items.remove(item);
}
}
public static boolean isCustomVariableToken(String token) {
return token != null && token.startsWith(CUSTOM_TOKEN_PREFIX);
}
public static boolean isInternalVariableToken(String token) {
return token != null && token.startsWith(INTERNAL_TOKEN_PREFIX);
}
public static String getInternalVariableKey(String token) {
return isInternalVariableToken(token) ? WiredInternalVariableSupport.normalizeKey(token.substring(INTERNAL_TOKEN_PREFIX.length())) : "";
}
public static int getCustomItemId(String token) {
if (!isCustomVariableToken(token)) {
return 0;
}
try {
return Integer.parseInt(token.substring(CUSTOM_TOKEN_PREFIX.length()));
} catch (NumberFormatException ignored) {
return 0;
}
}
private static String[] splitStringData(String value) {
if (value == null) {
return new String[]{ DEFAULT_VARIABLE_TOKEN, DEFAULT_PLACEHOLDER_NAME, DEFAULT_DELIMITER };
}
String[] parts = value.split("\t", -1);
if (parts.length == 1) {
return new String[]{ value, DEFAULT_PLACEHOLDER_NAME, DEFAULT_DELIMITER };
}
if (parts.length == 2) {
return new String[]{ parts[0], parts[1], DEFAULT_DELIMITER };
}
return new String[]{ parts[0], parts[1], parts[2] };
}
private static int normalizeTargetType(int value) {
return switch (value) {
case TARGET_FURNI, TARGET_CONTEXT, TARGET_ROOM -> value;
default -> TARGET_USER;
};
}
private static int normalizeDisplayType(int value) {
return (value == DISPLAY_TEXTUAL) ? DISPLAY_TEXTUAL : DISPLAY_NUMERIC;
}
private static int normalizePlaceholderType(int value) {
return (value == TYPE_MULTIPLE) ? TYPE_MULTIPLE : TYPE_SINGLE;
}
private static int normalizeUserSource(int value) {
return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
}
private static int normalizeFurniSource(int value) {
return switch (value) {
case WiredSourceUtil.SOURCE_SELECTED, WiredSourceUtil.SOURCE_SELECTOR, WiredSourceUtil.SOURCE_SIGNAL -> value;
default -> WiredSourceUtil.SOURCE_TRIGGER;
};
}
private static String normalizePlaceholderName(String value) {
if (value == null) {
return DEFAULT_PLACEHOLDER_NAME;
}
String normalized = value.trim().replace("\t", "").replace("\r", "").replace("\n", "");
if (WRAPPED_PLACEHOLDER_PATTERN.matcher(normalized).matches()) {
normalized = normalized.substring(2, normalized.length() - 1).trim();
}
if (normalized.length() > MAX_PLACEHOLDER_NAME_LENGTH) {
normalized = normalized.substring(0, MAX_PLACEHOLDER_NAME_LENGTH);
}
return normalized;
}
private static String normalizeDelimiter(String value) {
if (value == null) {
return DEFAULT_DELIMITER;
}
String normalized = value.replace("\t", "").replace("\r", "").replace("\n", "");
if (normalized.length() > MAX_DELIMITER_LENGTH) {
normalized = normalized.substring(0, MAX_DELIMITER_LENGTH);
}
return normalized;
}
private static String normalizeVariableToken(String value) {
String normalized = (value == null) ? "" : value.trim();
if (normalized.isEmpty()) {
return "";
}
if (isCustomVariableToken(normalized)) {
return normalized;
}
if (isInternalVariableToken(normalized)) {
return INTERNAL_TOKEN_PREFIX + WiredInternalVariableSupport.normalizeKey(normalized.substring(INTERNAL_TOKEN_PREFIX.length()));
}
try {
int parsedValue = Integer.parseInt(normalized);
return parsedValue > 0 ? (CUSTOM_TOKEN_PREFIX + parsedValue) : "";
} catch (NumberFormatException ignored) {
return "";
}
}
private void setVariableToken(String token) {
this.variableToken = normalizeVariableToken(token);
this.variableItemId = getCustomItemId(this.variableToken);
}
private static boolean canUseTextualDisplay(Room room, int targetType, String variableToken) {
if (room == null || !isCustomVariableToken(variableToken)) {
return false;
}
int itemId = getCustomItemId(variableToken);
if (itemId <= 0) {
return false;
}
return switch (targetType) {
case TARGET_USER -> {
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(itemId);
yield definition != null && definition.hasValue() && definition.isTextConnected();
}
case TARGET_FURNI -> {
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(itemId);
yield definition != null && definition.hasValue() && definition.isTextConnected();
}
case TARGET_CONTEXT -> {
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, itemId);
yield definition != null && definition.hasValue() && definition.isTextConnected();
}
case TARGET_ROOM -> {
WiredVariableDefinitionInfo definition = room.getRoomVariableManager().getDefinitionInfo(itemId);
yield definition != null && definition.hasValue() && definition.isTextConnected();
}
default -> false;
};
}
private static boolean isValidVariable(Room room, int targetType, String variableToken) {
if (room == null) {
return false;
}
return switch (targetType) {
case TARGET_USER -> isInternalVariableToken(variableToken)
? canUseUserInternalReference(getInternalVariableKey(variableToken))
: isUserCustomValue(room, getCustomItemId(variableToken));
case TARGET_FURNI -> isInternalVariableToken(variableToken)
? canUseFurniInternalReference(getInternalVariableKey(variableToken))
: isFurniCustomValue(room, getCustomItemId(variableToken));
case TARGET_CONTEXT -> isInternalVariableToken(variableToken)
? WiredInternalVariableSupport.canUseContextReference(getInternalVariableKey(variableToken))
: isContextCustomValue(room, getCustomItemId(variableToken));
case TARGET_ROOM -> isInternalVariableToken(variableToken)
? canUseRoomInternalReference(getInternalVariableKey(variableToken))
: isRoomCustomValue(room, getCustomItemId(variableToken));
default -> false;
};
}
private static boolean isUserCustomValue(Room room, int itemId) {
WiredVariableDefinitionInfo definition = (room != null) ? room.getUserVariableManager().getDefinitionInfo(itemId) : null;
return definition != null && definition.hasValue();
}
private static boolean isFurniCustomValue(Room room, int itemId) {
WiredVariableDefinitionInfo definition = (room != null) ? room.getFurniVariableManager().getDefinitionInfo(itemId) : null;
return definition != null && definition.hasValue();
}
private static boolean isRoomCustomValue(Room room, int itemId) {
WiredVariableDefinitionInfo definition = (room != null) ? room.getRoomVariableManager().getDefinitionInfo(itemId) : null;
return definition != null && definition.hasValue();
}
private static boolean isContextCustomValue(Room room, int itemId) {
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, itemId);
return definition != null && definition.hasValue();
}
private static boolean canUseUserInternalReference(String key) {
return WiredInternalVariableSupport.canUseUserReference(key);
}
private static boolean canUseFurniInternalReference(String key) {
return WiredInternalVariableSupport.canUseFurniReference(key);
}
private static boolean canUseRoomInternalReference(String key) {
return WiredInternalVariableSupport.canUseRoomReference(key);
}
static class JsonData {
int targetType;
String variableToken;
int variableItemId;
int displayType;
int placeholderType;
int userSource;
int furniSource;
String placeholderName;
String delimiter;
List<Integer> itemIds;
JsonData(int targetType, String variableToken, int variableItemId, int displayType, int placeholderType, int userSource, int furniSource, String placeholderName, String delimiter, List<Integer> itemIds) {
this.targetType = targetType;
this.variableToken = variableToken;
this.variableItemId = variableItemId;
this.displayType = displayType;
this.placeholderType = placeholderType;
this.userSource = userSource;
this.furniSource = furniSource;
this.placeholderName = placeholderName;
this.delimiter = delimiter;
this.itemIds = itemIds;
}
}
}
@@ -0,0 +1,162 @@
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
import java.sql.ResultSet;
import java.sql.SQLException;
public class WiredExtraUserVariable extends InteractionWiredExtra {
public static final int CODE = 70;
public static final int AVAILABILITY_ROOM = 0;
public static final int AVAILABILITY_PERMANENT = 10;
public static final int AVAILABILITY_SHARED = 11;
private String variableName = "";
private boolean hasValue = false;
private int availability = AVAILABILITY_ROOM;
public WiredExtraUserVariable(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredExtraUserVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
int[] intParams = settings.getIntParams();
String normalizedName = WiredVariableNameValidator.normalizeForSave(settings.getStringParam());
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room == null) {
throw new WiredSaveException("Room not found");
}
WiredVariableNameValidator.validateDefinitionName(room, this.getId(), normalizedName);
this.variableName = normalizedName;
this.hasValue = (intParams.length > 0) && (intParams[0] == 1);
this.availability = normalizeAvailability((intParams.length > 1) ? intParams[1] : AVAILABILITY_ROOM);
room.getUserVariableManager().handleDefinitionUpdated(this);
return true;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.variableName, this.hasValue, this.availability));
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
message.appendBoolean(false);
message.appendInt(0);
message.appendInt(0);
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(this.variableName);
message.appendInt(2);
message.appendInt(this.hasValue ? 1 : 0);
message.appendInt(this.availability);
message.appendInt(0);
message.appendInt(CODE);
message.appendInt(0);
message.appendInt(0);
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty()) {
return;
}
if (wiredData.startsWith("{")) {
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data != null) {
this.variableName = WiredVariableNameValidator.normalizeLegacy(data.variableName);
this.hasValue = data.hasValue;
this.availability = normalizeAvailability(data.availability);
}
return;
}
this.variableName = WiredVariableNameValidator.normalizeLegacy(wiredData);
}
@Override
public void onPickUp() {
this.variableName = "";
this.hasValue = false;
this.availability = AVAILABILITY_ROOM;
}
@Override
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
}
@Override
public boolean hasConfiguration() {
return true;
}
public String getVariableName() {
return variableName;
}
public boolean hasValue() {
return hasValue;
}
public int getAvailability() {
return availability;
}
public boolean isPermanentAvailability() {
return this.availability == AVAILABILITY_PERMANENT || this.availability == AVAILABILITY_SHARED;
}
public boolean isSharedAvailability() {
return this.availability == AVAILABILITY_SHARED;
}
private static int normalizeAvailability(int value) {
if (value == AVAILABILITY_PERMANENT || value == AVAILABILITY_SHARED) {
return value;
}
return AVAILABILITY_ROOM;
}
static class JsonData {
String variableName;
boolean hasValue;
int availability;
JsonData(String variableName, boolean hasValue, int availability) {
this.variableName = variableName;
this.hasValue = hasValue;
this.availability = availability;
}
}
}
@@ -0,0 +1,800 @@
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.bots.Bot;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.games.Game;
import com.eu.habbo.habbohotel.games.GamePlayer;
import com.eu.habbo.habbohotel.games.GameTeam;
import com.eu.habbo.habbohotel.games.GameTeamColors;
import com.eu.habbo.habbohotel.items.FurnitureType;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.rooms.FurnitureMovementError;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomTile;
import com.eu.habbo.habbohotel.rooms.RoomTileState;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.rooms.RoomRightLevels;
import com.eu.habbo.habbohotel.rooms.RoomUserRotation;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
import com.eu.habbo.habbohotel.users.DanceType;
import com.eu.habbo.habbohotel.users.HabboGender;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil;
import com.eu.habbo.habbohotel.wired.core.WiredInternalVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredUserMovementHelper;
import com.eu.habbo.habbohotel.wired.core.WiredVariableLevelSystemSupport;
import com.eu.habbo.habbohotel.wired.core.WiredVariableTextConnectorSupport;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
import com.eu.habbo.util.HotelDateTimeUtil;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.ZonedDateTime;
import java.time.temporal.WeekFields;
import java.util.Locale;
public class WiredExtraVariableEcho extends InteractionWiredExtra {
public static final int CODE = 83;
public static final int TARGET_USER = 0;
public static final int TARGET_FURNI = 1;
public static final int TARGET_ROOM = 3;
private static final String CUSTOM_TOKEN_PREFIX = "custom:";
private static final String INTERNAL_TOKEN_PREFIX = "internal:";
private static final int DEFAULT_USER_AVAILABILITY = WiredExtraUserVariable.AVAILABILITY_ROOM;
private static final int DEFAULT_FURNI_AVAILABILITY = WiredExtraFurniVariable.AVAILABILITY_ROOM_ACTIVE;
private static final int DEFAULT_ROOM_AVAILABILITY = WiredExtraRoomVariable.AVAILABILITY_ROOM_ACTIVE;
private String variableName = "";
private int sourceTargetType = TARGET_USER;
private String sourceVariableToken = "";
private int sourceVariableItemId = 0;
private String sourceVariableName = "";
public WiredExtraVariableEcho(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredExtraVariableEcho(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room == null) {
throw new WiredSaveException("Room not found");
}
ConfigData config = parseConfigData(settings.getStringParam());
int normalizedTargetType = normalizeTargetType(config.sourceTargetType);
String normalizedToken = normalizeVariableToken(config.sourceVariableToken, config.sourceVariableItemId);
int normalizedItemId = getCustomVariableItemId(normalizedToken);
SourceState sourceState = this.resolveSourceState(room, normalizedTargetType, normalizedToken, normalizedItemId);
if (normalizedToken.isEmpty()) {
throw new WiredSaveException("wiredfurni.params.variables.validation.missing_variable");
}
if (sourceState == null || !sourceState.hasValue()) {
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
if (!isAllowedEchoSource(sourceState, normalizedToken)) {
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
if (!WiredVariableTextConnectorSupport.isTextConnected(room, this)) {
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
if (createsCycle(room, this.getId(), normalizedTargetType, normalizedToken, normalizedItemId)) {
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
String normalizedName = deriveVariableName(config.variableName, sourceState.getName());
WiredVariableNameValidator.validateDefinitionName(room, this.getId(), normalizedName);
this.variableName = normalizedName;
this.sourceTargetType = normalizedTargetType;
this.sourceVariableToken = normalizedToken;
this.sourceVariableItemId = normalizedItemId;
this.sourceVariableName = sourceState.getName();
room.getUserVariableManager().broadcastSnapshot();
return true;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.variableName, this.sourceTargetType, this.sourceVariableToken, this.sourceVariableItemId, this.sourceVariableName));
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
message.appendBoolean(false);
message.appendInt(0);
message.appendInt(0);
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(WiredManager.getGson().toJson(new EditorPayload(this.variableName, this.sourceTargetType, this.sourceVariableToken, this.sourceVariableItemId, this.getResolvedSourceName(room))));
message.appendInt(0);
message.appendInt(0);
message.appendInt(CODE);
message.appendInt(0);
message.appendInt(0);
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty() || !wiredData.startsWith("{")) {
return;
}
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data == null) {
return;
}
this.variableName = WiredVariableNameValidator.normalizeLegacy(data.variableName);
this.sourceTargetType = normalizeTargetType(data.sourceTargetType);
this.sourceVariableToken = normalizeVariableToken(data.sourceVariableToken, data.sourceVariableItemId);
this.sourceVariableItemId = getCustomVariableItemId(this.sourceVariableToken);
this.sourceVariableName = normalizeSourceName(data.sourceVariableName);
}
@Override
public void onPickUp() {
this.variableName = "";
this.sourceTargetType = TARGET_USER;
this.sourceVariableToken = "";
this.sourceVariableItemId = 0;
this.sourceVariableName = "";
}
@Override
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
}
@Override
public boolean hasConfiguration() {
return true;
}
public String getVariableName() {
return this.variableName;
}
public int getSourceTargetType() {
return this.sourceTargetType;
}
public String getSourceVariableToken() {
return this.sourceVariableToken;
}
public int getSourceVariableItemId() {
return this.sourceVariableItemId;
}
public String getSourceVariableName() {
return this.sourceVariableName;
}
public boolean isUserEcho() {
return this.sourceTargetType == TARGET_USER;
}
public boolean isFurniEcho() {
return this.sourceTargetType == TARGET_FURNI;
}
public boolean isRoomEcho() {
return this.sourceTargetType == TARGET_ROOM;
}
public WiredVariableDefinitionInfo createDefinitionInfo(Room room) {
SourceState sourceState = this.resolveSourceState(room, this.sourceTargetType, this.sourceVariableToken, this.sourceVariableItemId);
int availability = (sourceState != null) ? sourceState.getAvailability() : defaultAvailability(this.sourceTargetType);
boolean hasValue = (sourceState == null) || sourceState.hasValue();
boolean readOnly = sourceState == null || sourceState.isReadOnly();
return new WiredVariableDefinitionInfo(
this.getId(),
this.variableName,
hasValue,
availability,
WiredVariableTextConnectorSupport.isTextConnected(room, this),
readOnly
);
}
public boolean hasVariable(Room room, int entityId) {
if (room == null) {
return false;
}
if (isCustomVariableToken(this.sourceVariableToken)) {
return switch (this.sourceTargetType) {
case TARGET_FURNI -> room.getFurniVariableManager().hasVariable(entityId, this.sourceVariableItemId);
case TARGET_ROOM -> room.getRoomVariableManager().hasVariable(this.sourceVariableItemId);
default -> room.getUserVariableManager().hasVariable(entityId, this.sourceVariableItemId);
};
}
return this.readCurrentValue(room, entityId) != null;
}
public int getCurrentValue(Room room, int entityId) {
Integer value = this.readCurrentValue(room, entityId);
return (value != null) ? value : 0;
}
public int getCreatedAt(Room room, int entityId) {
if (room == null || !isCustomVariableToken(this.sourceVariableToken)) {
return 0;
}
return switch (this.sourceTargetType) {
case TARGET_FURNI -> room.getFurniVariableManager().getCreatedAt(entityId, this.sourceVariableItemId);
case TARGET_ROOM -> room.getRoomVariableManager().getCreatedAt(this.sourceVariableItemId);
default -> room.getUserVariableManager().getCreatedAt(entityId, this.sourceVariableItemId);
};
}
public int getUpdatedAt(Room room, int entityId) {
if (room == null || !isCustomVariableToken(this.sourceVariableToken)) {
return 0;
}
return switch (this.sourceTargetType) {
case TARGET_FURNI -> room.getFurniVariableManager().getUpdatedAt(entityId, this.sourceVariableItemId);
case TARGET_ROOM -> room.getRoomVariableManager().getUpdatedAt(this.sourceVariableItemId);
default -> room.getUserVariableManager().getUpdatedAt(entityId, this.sourceVariableItemId);
};
}
public boolean assignValue(Room room, int entityId, Integer value, boolean overrideExisting) {
if (room == null) {
return false;
}
SourceState sourceState = this.resolveSourceState(room, this.sourceTargetType, this.sourceVariableToken, this.sourceVariableItemId);
if (sourceState == null || sourceState.isReadOnly()) {
return false;
}
if (isCustomVariableToken(this.sourceVariableToken)) {
return switch (this.sourceTargetType) {
case TARGET_FURNI -> room.getFurniVariableManager().assignVariable(room.getHabboItem(entityId), this.sourceVariableItemId, value, overrideExisting);
case TARGET_ROOM -> room.getRoomVariableManager().updateVariableValue(this.sourceVariableItemId, (value != null) ? value : 0);
default -> room.getUserVariableManager().assignVariable(room.getHabbo(entityId), this.sourceVariableItemId, value, overrideExisting);
};
}
return value != null && this.writeCurrentValue(room, entityId, value);
}
public boolean updateValue(Room room, int entityId, Integer value) {
if (room == null) {
return false;
}
SourceState sourceState = this.resolveSourceState(room, this.sourceTargetType, this.sourceVariableToken, this.sourceVariableItemId);
if (sourceState == null || sourceState.isReadOnly() || !sourceState.hasValue()) {
return false;
}
if (isCustomVariableToken(this.sourceVariableToken)) {
return switch (this.sourceTargetType) {
case TARGET_FURNI -> room.getFurniVariableManager().updateVariableValue(entityId, this.sourceVariableItemId, value);
case TARGET_ROOM -> room.getRoomVariableManager().updateVariableValue(this.sourceVariableItemId, (value != null) ? value : 0);
default -> room.getUserVariableManager().updateVariableValue(entityId, this.sourceVariableItemId, value);
};
}
return value != null && this.writeCurrentValue(room, entityId, value);
}
public boolean removeValue(Room room, int entityId) {
if (room == null || !isCustomVariableToken(this.sourceVariableToken)) {
return false;
}
SourceState sourceState = this.resolveSourceState(room, this.sourceTargetType, this.sourceVariableToken, this.sourceVariableItemId);
if (sourceState == null || sourceState.isReadOnly()) {
return false;
}
return switch (this.sourceTargetType) {
case TARGET_FURNI -> room.getFurniVariableManager().removeVariable(entityId, this.sourceVariableItemId);
case TARGET_ROOM -> room.getRoomVariableManager().removeVariable(this.sourceVariableItemId);
default -> room.getUserVariableManager().removeVariable(entityId, this.sourceVariableItemId);
};
}
private Integer readCurrentValue(Room room, int entityId) {
if (room == null || this.sourceVariableToken == null || this.sourceVariableToken.isEmpty()) {
return null;
}
if (isCustomVariableToken(this.sourceVariableToken)) {
return switch (this.sourceTargetType) {
case TARGET_FURNI -> room.getFurniVariableManager().hasVariable(entityId, this.sourceVariableItemId)
? room.getFurniVariableManager().getCurrentValue(entityId, this.sourceVariableItemId)
: null;
case TARGET_ROOM -> room.getRoomVariableManager().getCurrentValue(this.sourceVariableItemId);
default -> room.getUserVariableManager().hasVariable(entityId, this.sourceVariableItemId)
? room.getUserVariableManager().getCurrentValue(entityId, this.sourceVariableItemId)
: null;
};
}
String key = getInternalVariableKey(this.sourceVariableToken);
if (key.isEmpty()) {
return null;
}
return switch (this.sourceTargetType) {
case TARGET_FURNI -> this.readFurniInternalValue(room, room.getHabboItem(entityId), key);
case TARGET_ROOM -> this.readRoomInternalValue(room, key);
default -> {
Habbo habbo = room.getHabbo(entityId);
yield this.readUserInternalValue(room, (habbo != null) ? habbo.getRoomUnit() : null, key);
}
};
}
private boolean writeCurrentValue(Room room, int entityId, int value) {
if (room == null || !isInternalVariableToken(this.sourceVariableToken)) {
return false;
}
String key = getInternalVariableKey(this.sourceVariableToken);
if (key.isEmpty()) {
return false;
}
return switch (this.sourceTargetType) {
case TARGET_FURNI -> this.writeFurniInternalValue(room, room.getHabboItem(entityId), key, value);
case TARGET_ROOM -> false;
default -> {
Habbo habbo = room.getHabbo(entityId);
yield this.writeUserInternalValue(room, (habbo != null) ? habbo.getRoomUnit() : null, key, value);
}
};
}
private SourceState resolveSourceState(Room room, int targetType, String token, int variableItemId) {
if (room == null || token == null || token.isEmpty()) {
return null;
}
if (isCustomVariableToken(token)) {
WiredVariableDefinitionInfo definitionInfo = switch (targetType) {
case TARGET_FURNI -> room.getFurniVariableManager().getDefinitionInfo(variableItemId);
case TARGET_ROOM -> room.getRoomVariableManager().getDefinitionInfo(variableItemId);
default -> room.getUserVariableManager().getDefinitionInfo(variableItemId);
};
if (definitionInfo == null) {
return null;
}
return new SourceState(definitionInfo.getName(), definitionInfo.hasValue(), definitionInfo.getAvailability(), definitionInfo.isReadOnly());
}
String key = getInternalVariableKey(token);
if (key.isEmpty()) {
return null;
}
return switch (targetType) {
case TARGET_FURNI -> canUseFurniInternalReference(key)
? new SourceState(key, true, DEFAULT_FURNI_AVAILABILITY, !canUseFurniInternalDestination(key))
: null;
case TARGET_ROOM -> canUseRoomInternalReference(key)
? new SourceState(key, true, DEFAULT_ROOM_AVAILABILITY, true)
: null;
default -> canUseUserInternalReference(key)
? new SourceState(key, true, DEFAULT_USER_AVAILABILITY, !canUseUserInternalDestination(key))
: null;
};
}
private String getResolvedSourceName(Room room) {
SourceState sourceState = this.resolveSourceState(room, this.sourceTargetType, this.sourceVariableToken, this.sourceVariableItemId);
return (sourceState != null) ? sourceState.getName() : this.sourceVariableName;
}
private static boolean createsCycle(Room room, int currentItemId, int targetType, String token, int variableItemId) {
if (room == null || currentItemId <= 0 || !isCustomVariableToken(token) || variableItemId <= 0) {
return false;
}
if (variableItemId == currentItemId) {
return true;
}
WiredVariableLevelSystemSupport.DerivedDefinition derivedDefinition = WiredVariableLevelSystemSupport.resolveDerivedDefinition(room, targetType, variableItemId);
if (derivedDefinition != null) {
return createsCycle(room, currentItemId, targetType, createCustomVariableToken(derivedDefinition.getBaseDefinitionItemId()), derivedDefinition.getBaseDefinitionItemId());
}
if (room.getRoomSpecialTypes() == null) {
return false;
}
InteractionWiredExtra extra = room.getRoomSpecialTypes().getExtra(variableItemId);
if (!(extra instanceof WiredExtraVariableEcho)) {
return false;
}
WiredExtraVariableEcho echo = (WiredExtraVariableEcho) extra;
return createsCycle(room, currentItemId, echo.getSourceTargetType(), echo.getSourceVariableToken(), echo.getSourceVariableItemId());
}
private static String deriveVariableName(String requestedName, String sourceName) {
String normalizedRequestedName = WiredVariableNameValidator.normalizeForSave(requestedName);
if (!normalizedRequestedName.isEmpty()) {
return normalizedRequestedName;
}
String fallbackValue = normalizeSourceName(sourceName)
.replaceAll("^[~@]+", "")
.replaceAll("[^A-Za-z0-9_]+", "_")
.replaceAll("_+", "_")
.replaceAll("^_+", "")
.replaceAll("_+$", "");
if (fallbackValue.length() > WiredVariableNameValidator.MAX_NAME_LENGTH) {
fallbackValue = fallbackValue.substring(0, WiredVariableNameValidator.MAX_NAME_LENGTH);
}
return fallbackValue;
}
private static boolean isAllowedEchoSource(SourceState sourceState, String token) {
if (sourceState == null || token == null || token.isEmpty()) {
return false;
}
if (isInternalVariableToken(token)) {
return true;
}
return isCustomVariableToken(token) && sourceState.getName() != null && sourceState.getName().contains(".");
}
private static ConfigData parseConfigData(String value) {
if (value == null || value.isEmpty() || !value.startsWith("{")) {
return new ConfigData();
}
ConfigData config = WiredManager.getGson().fromJson(value, ConfigData.class);
return (config != null) ? config : new ConfigData();
}
private static String normalizeSourceName(String value) {
if (value == null) {
return "";
}
return value.trim().replace("\t", "").replace("\r", "").replace("\n", "");
}
private static int normalizeTargetType(int value) {
if (value == TARGET_FURNI || value == TARGET_ROOM) {
return value;
}
return TARGET_USER;
}
private static int defaultAvailability(int targetType) {
return switch (targetType) {
case TARGET_FURNI -> DEFAULT_FURNI_AVAILABILITY;
case TARGET_ROOM -> DEFAULT_ROOM_AVAILABILITY;
default -> DEFAULT_USER_AVAILABILITY;
};
}
private static boolean isCustomVariableToken(String token) {
return token != null && token.startsWith(CUSTOM_TOKEN_PREFIX);
}
private static boolean isInternalVariableToken(String token) {
return token != null && token.startsWith(INTERNAL_TOKEN_PREFIX);
}
private static String getInternalVariableKey(String token) {
return isInternalVariableToken(token) ? WiredInternalVariableSupport.normalizeKey(token.substring(INTERNAL_TOKEN_PREFIX.length())) : "";
}
private static int getCustomVariableItemId(String token) {
if (!isCustomVariableToken(token)) {
return 0;
}
try {
return Integer.parseInt(token.substring(CUSTOM_TOKEN_PREFIX.length()));
} catch (NumberFormatException ignored) {
return 0;
}
}
private static String createCustomVariableToken(int itemId) {
return itemId > 0 ? CUSTOM_TOKEN_PREFIX + itemId : "";
}
private static String normalizeVariableToken(String token, int fallbackItemId) {
String normalizedToken = (token != null) ? token.trim() : "";
if (isCustomVariableToken(normalizedToken)) {
return normalizedToken;
}
if (isInternalVariableToken(normalizedToken)) {
return INTERNAL_TOKEN_PREFIX + WiredInternalVariableSupport.normalizeKey(normalizedToken.substring(INTERNAL_TOKEN_PREFIX.length()));
}
if (fallbackItemId > 0) {
return createCustomVariableToken(fallbackItemId);
}
if (normalizedToken.isEmpty()) {
return "";
}
try {
return createCustomVariableToken(Integer.parseInt(normalizedToken));
} catch (NumberFormatException ignored) {
return "";
}
}
private Integer readUserInternalValue(Room room, RoomUnit roomUnit, String key) {
return WiredInternalVariableSupport.readUserValue(room, roomUnit, key);
}
private Integer getUserTypeValue(Habbo habbo, Bot bot, Pet pet) {
if (habbo != null) return 1;
if (bot != null) return 2;
if (pet != null) return 3;
return null;
}
private Integer getGenderValue(Habbo habbo, Bot bot) {
HabboGender gender = null;
if (habbo != null && habbo.getHabboInfo() != null) {
gender = habbo.getHabboInfo().getGender();
} else if (bot != null) {
gender = bot.getGender();
}
if (gender == null) {
return null;
}
return gender == HabboGender.F ? 1 : 2;
}
private boolean writeUserInternalValue(Room room, RoomUnit roomUnit, String key, int value) {
return WiredInternalVariableSupport.writeUserValue(room, roomUnit, key, value);
}
private Integer readFurniInternalValue(Room room, HabboItem item, String key) {
return WiredInternalVariableSupport.readFurniValue(room, item, key);
}
private boolean writeFurniInternalValue(Room room, HabboItem item, String key, int value) {
return WiredInternalVariableSupport.writeFurniValue(room, item, key, value);
}
private Integer readRoomInternalValue(Room room, String key) {
return WiredInternalVariableSupport.readRoomValue(room, key);
}
private Integer getUserTeamScore(Room room, Habbo habbo) {
if (room == null || habbo == null || habbo.getHabboInfo() == null || habbo.getHabboInfo().getGamePlayer() == null) return null;
Game game = this.resolveTeamGame(room, habbo);
GamePlayer gamePlayer = habbo.getHabboInfo().getGamePlayer();
if (game == null || gamePlayer.getTeamColor() == null) return gamePlayer.getScore();
GameTeam team = game.getTeam(gamePlayer.getTeamColor());
return (team != null) ? team.getTotalScore() : gamePlayer.getScore();
}
private int getTeamMetric(Room room, GameTeamColors color, boolean score) {
Game game = this.resolveTeamGame(room, null);
if (game == null || color == null) return 0;
GameTeam team = game.getTeam(color);
if (team == null) return 0;
return score ? team.getTotalScore() : team.getMembers().size();
}
private int getTeamColorId(int effectId) {
if (effectId >= 33 && effectId <= 36) return effectId - 32;
if (effectId >= 40 && effectId <= 43) return effectId - 39;
return 0;
}
private int getTeamTypeId(int effectId) {
if (effectId >= 33 && effectId <= 36) return 1;
if (effectId >= 40 && effectId <= 43) return 2;
return 0;
}
private Game resolveTeamGame(Room room, Habbo habbo) {
if (room == null) return null;
if (habbo != null && habbo.getHabboInfo() != null && habbo.getHabboInfo().getCurrentGame() != null) {
Game game = room.getGame(habbo.getHabboInfo().getCurrentGame());
if (game != null) return game;
}
Game game = room.getGame(com.eu.habbo.habbohotel.games.battlebanzai.BattleBanzaiGame.class);
if (game != null) return game;
game = room.getGame(com.eu.habbo.habbohotel.games.freeze.FreezeGame.class);
if (game != null) return game;
return room.getGame(com.eu.habbo.habbohotel.games.wired.WiredGame.class);
}
private boolean moveUserTo(Room room, RoomUnit roomUnit, int x, int y) {
if (room == null || roomUnit == null || room.getLayout() == null) return false;
RoomTile targetTile = room.getLayout().getTile((short) x, (short) y);
if (targetTile == null || targetTile.state == RoomTileState.INVALID) return false;
double targetZ = targetTile.getStackHeight() + ((targetTile.state == RoomTileState.SIT) ? -0.5 : 0);
return WiredUserMovementHelper.moveUser(room, roomUnit, targetTile, targetZ, roomUnit.getBodyRotation(), roomUnit.getHeadRotation(), 0, true);
}
private boolean moveFurniTo(Room room, HabboItem item, int x, int y, int rotation, double z) {
if (room == null || item == null || room.getLayout() == null) return false;
RoomTile targetTile = room.getLayout().getTile((short) x, (short) y);
if (targetTile == null || targetTile.state == RoomTileState.INVALID) return false;
FurnitureMovementError error = room.moveFurniTo(item, targetTile, rotation, z, null, true, true);
return error == FurnitureMovementError.NONE;
}
private static Integer parseInteger(String value) {
if (value == null || value.isEmpty()) return 0;
try {
return Integer.parseInt(value.trim());
} catch (NumberFormatException ignored) {
return 0;
}
}
private static boolean canUseUserInternalDestination(String key) {
return WiredInternalVariableSupport.canUseUserDestination(key);
}
private static boolean canUseFurniInternalDestination(String key) {
return WiredInternalVariableSupport.canUseFurniDestination(key);
}
private static boolean canUseUserInternalReference(String key) {
return WiredInternalVariableSupport.canUseUserReference(key);
}
private static boolean canUseFurniInternalReference(String key) {
return WiredInternalVariableSupport.canUseFurniReference(key);
}
private static boolean canUseRoomInternalReference(String key) {
return WiredInternalVariableSupport.canUseRoomReference(key);
}
private Integer getRoomEntryMethodValue(Habbo habbo) {
if (habbo == null || habbo.getHabboInfo() == null) {
return null;
}
String roomEntryMethod = habbo.getHabboInfo().getRoomEntryMethod();
if (roomEntryMethod == null || roomEntryMethod.trim().isEmpty()) {
return 0;
}
return switch (roomEntryMethod.trim().toLowerCase(Locale.ROOT)) {
case "door" -> 1;
case "teleport" -> 2;
default -> 3;
};
}
static class JsonData {
String variableName;
int sourceTargetType;
String sourceVariableToken;
int sourceVariableItemId;
String sourceVariableName;
JsonData(String variableName, int sourceTargetType, String sourceVariableToken, int sourceVariableItemId, String sourceVariableName) {
this.variableName = variableName;
this.sourceTargetType = sourceTargetType;
this.sourceVariableToken = sourceVariableToken;
this.sourceVariableItemId = sourceVariableItemId;
this.sourceVariableName = sourceVariableName;
}
}
static class ConfigData {
String variableName = "";
int sourceTargetType = TARGET_USER;
String sourceVariableToken = "";
int sourceVariableItemId = 0;
}
static class EditorPayload extends ConfigData {
String sourceVariableName;
EditorPayload(String variableName, int sourceTargetType, String sourceVariableToken, int sourceVariableItemId, String sourceVariableName) {
this.variableName = variableName;
this.sourceTargetType = sourceTargetType;
this.sourceVariableToken = sourceVariableToken;
this.sourceVariableItemId = sourceVariableItemId;
this.sourceVariableName = sourceVariableName;
}
}
private static class SourceState {
private final String name;
private final boolean hasValue;
private final int availability;
private final boolean readOnly;
private SourceState(String name, boolean hasValue, int availability, boolean readOnly) {
this.name = name;
this.hasValue = hasValue;
this.availability = availability;
this.readOnly = readOnly;
}
public String getName() {
return this.name;
}
public boolean hasValue() {
return this.hasValue;
}
public int getAvailability() {
return this.availability;
}
public boolean isReadOnly() {
return this.readOnly;
}
}
}
@@ -0,0 +1,757 @@
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.bots.Bot;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.games.Game;
import com.eu.habbo.habbohotel.games.GamePlayer;
import com.eu.habbo.habbohotel.games.GameTeam;
import com.eu.habbo.habbohotel.games.GameTeamColors;
import com.eu.habbo.habbohotel.games.battlebanzai.BattleBanzaiGame;
import com.eu.habbo.habbohotel.games.freeze.FreezeGame;
import com.eu.habbo.habbohotel.games.wired.WiredGame;
import com.eu.habbo.habbohotel.items.FurnitureType;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.users.DanceType;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.wired.core.WiredContext;
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil;
import com.eu.habbo.habbohotel.wired.core.WiredInternalVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
import com.eu.habbo.util.HotelDateTimeUtil;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.ZonedDateTime;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
public abstract class WiredExtraVariableFilterBase extends InteractionWiredExtra {
protected static final int TARGET_USER = 0;
protected static final int TARGET_FURNI = 1;
protected static final int TARGET_CONTEXT = 2;
protected static final int TARGET_ROOM = 3;
protected static final int AMOUNT_CONSTANT = 0;
protected static final int AMOUNT_VARIABLE = 1;
protected static final int SOURCE_SECONDARY_SELECTED = 101;
protected static final int SORT_VALUE_HIGHEST = 0;
protected static final int SORT_VALUE_LOWEST = 1;
protected static final int SORT_CREATION_OLDEST = 2;
protected static final int SORT_CREATION_LATEST = 3;
protected static final int SORT_UPDATE_OLDEST = 4;
protected static final int SORT_UPDATE_LATEST = 5;
private static final int MAX_FILTER_AMOUNT = 10000;
private static final String CUSTOM_TOKEN_PREFIX = "custom:";
private static final String INTERNAL_TOKEN_PREFIX = "internal:";
private static final String DELIM = "\t";
protected int sortBy = SORT_VALUE_HIGHEST;
protected int amountMode = AMOUNT_CONSTANT;
protected int amountConstantValue = 1;
protected int referenceTargetType = TARGET_USER;
protected int referenceUserSource = WiredSourceUtil.SOURCE_TRIGGER;
protected int referenceFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
protected String variableToken = "";
protected int variableItemId = 0;
protected String referenceVariableToken = "";
protected int referenceVariableItemId = 0;
protected final List<HabboItem> referenceSelectedItems = new ArrayList<>();
protected WiredExtraVariableFilterBase(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
protected WiredExtraVariableFilterBase(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
protected abstract int getVariableTargetType();
protected abstract int getCode();
public List<RoomUnit> filterUsers(Room room, WiredContext ctx, Iterable<RoomUnit> values) {
if (room == null || ctx == null || this.getVariableTargetType() != TARGET_USER || this.variableToken.isEmpty()) {
return toUserList(values);
}
int amount = this.resolveAmount(ctx, room);
if (amount <= 0) return new ArrayList<>();
List<SortableEntry<RoomUnit>> matches = new ArrayList<>();
for (RoomUnit roomUnit : values) {
if (roomUnit == null) continue;
MetricSnapshot metric = this.resolveUserMetric(room, roomUnit);
if (metric == null) continue;
matches.add(new SortableEntry<>(roomUnit, metric));
}
matches.sort(this.metricComparator());
return trimUsers(matches, amount);
}
public List<HabboItem> filterItems(Room room, WiredContext ctx, Iterable<HabboItem> values) {
if (room == null || ctx == null || this.getVariableTargetType() != TARGET_FURNI || this.variableToken.isEmpty()) {
return toItemList(values);
}
int amount = this.resolveAmount(ctx, room);
if (amount <= 0) return new ArrayList<>();
List<SortableEntry<HabboItem>> matches = new ArrayList<>();
for (HabboItem item : values) {
if (item == null) continue;
MetricSnapshot metric = this.resolveFurniMetric(room, item);
if (metric == null) continue;
matches.add(new SortableEntry<>(item, metric));
}
matches.sort(this.metricComparator());
return trimItems(matches, amount);
}
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room == null) throw new WiredSaveException("Room not found");
int[] params = settings.getIntParams();
String[] stringParts = parseStringData(settings.getStringParam());
String nextVariableToken = normalizeVariableToken((stringParts.length > 0) ? stringParts[0] : "");
String nextReferenceVariableToken = normalizeVariableToken((stringParts.length > 1) ? stringParts[1] : "");
int nextSortBy = normalizeSortBy(param(params, 0, SORT_VALUE_HIGHEST));
int nextAmountMode = normalizeAmountMode(param(params, 1, AMOUNT_CONSTANT));
int nextAmountConstantValue = normalizeAmount(param(params, 2, 1));
int nextReferenceTargetType = normalizeReferenceTargetType(param(params, 3, TARGET_USER));
int nextReferenceUserSource = normalizeUserSource(param(params, 4, WiredSourceUtil.SOURCE_TRIGGER));
int nextReferenceFurniSource = normalizeReferenceFurniSource(param(params, 5, WiredSourceUtil.SOURCE_TRIGGER));
if (!this.isValidMainVariable(room, nextVariableToken)) throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
if (nextAmountMode == AMOUNT_VARIABLE && !this.isValidReference(room, nextReferenceTargetType, nextReferenceVariableToken)) throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
List<HabboItem> nextReferenceItems = new ArrayList<>();
if (nextAmountMode == AMOUNT_VARIABLE && nextReferenceTargetType == TARGET_FURNI && nextReferenceFurniSource == SOURCE_SECONDARY_SELECTED) {
int selectionLimit = Emulator.getConfig().getInt("hotel.wired.furni.selection.count");
if (settings.getFurniIds().length > selectionLimit) throw new WiredSaveException("Too many furni selected");
for (int furniId : settings.getFurniIds()) {
HabboItem item = room.getHabboItem(furniId);
if (item != null) nextReferenceItems.add(item);
}
}
this.sortBy = nextSortBy;
this.amountMode = nextAmountMode;
this.amountConstantValue = nextAmountConstantValue;
this.referenceTargetType = nextReferenceTargetType;
this.referenceUserSource = nextReferenceUserSource;
this.referenceFurniSource = nextReferenceFurniSource;
this.setVariableToken(nextVariableToken);
this.setReferenceVariableToken(nextReferenceVariableToken);
this.referenceSelectedItems.clear();
this.referenceSelectedItems.addAll(nextReferenceItems);
return true;
}
@Override
public String getWiredData() {
this.refreshReferenceItems();
return WiredManager.getGson().toJson(new JsonData(this.sortBy, this.amountMode, this.amountConstantValue, this.referenceTargetType, this.referenceUserSource, this.referenceFurniSource, this.variableToken, this.variableItemId, this.referenceVariableToken, this.referenceVariableItemId, this.toIds(this.referenceSelectedItems)));
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
this.refreshReferenceItems();
List<HabboItem> selectedItems = new ArrayList<>();
if (this.amountMode == AMOUNT_VARIABLE && this.referenceTargetType == TARGET_FURNI && this.referenceFurniSource == SOURCE_SECONDARY_SELECTED) {
selectedItems.addAll(this.referenceSelectedItems);
}
message.appendBoolean(false);
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
message.appendInt(selectedItems.size());
for (HabboItem item : selectedItems) message.appendInt(item.getId());
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(this.serializeStringData());
message.appendInt(6);
message.appendInt(this.sortBy);
message.appendInt(this.amountMode);
message.appendInt(this.amountConstantValue);
message.appendInt(this.referenceTargetType);
message.appendInt(this.referenceUserSource);
message.appendInt(this.referenceFurniSource);
message.appendInt(0);
message.appendInt(this.getCode());
message.appendInt(0);
message.appendInt(0);
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty() || !wiredData.startsWith("{")) return;
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data == null) return;
this.sortBy = normalizeSortBy(data.sortBy);
this.amountMode = normalizeAmountMode(data.amountMode);
this.amountConstantValue = normalizeAmount(data.amountConstantValue);
this.referenceTargetType = normalizeReferenceTargetType(data.referenceTargetType);
this.referenceUserSource = normalizeUserSource(data.referenceUserSource);
this.referenceFurniSource = normalizeReferenceFurniSource(data.referenceFurniSource);
this.setVariableToken(normalizeVariableToken((data.variableToken != null) ? data.variableToken : ((data.variableItemId > 0) ? String.valueOf(data.variableItemId) : "")));
this.setReferenceVariableToken(normalizeVariableToken((data.referenceVariableToken != null) ? data.referenceVariableToken : ((data.referenceVariableItemId > 0) ? String.valueOf(data.referenceVariableItemId) : "")));
if (room == null || data.selectedItemIds == null) return;
for (Integer itemId : data.selectedItemIds) {
if (itemId == null || itemId <= 0) continue;
HabboItem item = room.getHabboItem(itemId);
if (item != null) this.referenceSelectedItems.add(item);
}
}
@Override
public void onPickUp() {
this.sortBy = SORT_VALUE_HIGHEST;
this.amountMode = AMOUNT_CONSTANT;
this.amountConstantValue = 1;
this.referenceTargetType = TARGET_USER;
this.referenceUserSource = WiredSourceUtil.SOURCE_TRIGGER;
this.referenceFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
this.referenceSelectedItems.clear();
this.setVariableToken("");
this.setReferenceVariableToken("");
}
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
}
@Override
public boolean hasConfiguration() {
return true;
}
private int resolveAmount(WiredContext ctx, Room room) {
if (this.amountMode != AMOUNT_VARIABLE) return normalizeAmount(this.amountConstantValue);
Integer value = this.resolveReferenceValue(ctx, room);
return value == null ? 0 : normalizeAmount(value);
}
private Integer resolveReferenceValue(WiredContext ctx, Room room) {
if (room == null) return null;
if (this.referenceTargetType == TARGET_FURNI) {
int source = (this.referenceFurniSource == SOURCE_SECONDARY_SELECTED) ? WiredSourceUtil.SOURCE_SELECTED : this.referenceFurniSource;
if (source == WiredSourceUtil.SOURCE_SELECTED) this.refreshReferenceItems();
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, source, this.referenceSelectedItems)) {
Integer value = this.readFurniReferenceValue(room, item);
if (value != null) return value;
}
return null;
}
if (this.referenceTargetType == TARGET_CONTEXT) {
return this.readContextReferenceValue(ctx, room);
}
if (this.referenceTargetType == TARGET_ROOM) {
return this.readRoomReferenceValue(room);
}
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.referenceUserSource)) {
Integer value = this.readUserReferenceValue(room, roomUnit);
if (value != null) return value;
}
return null;
}
private Integer readUserReferenceValue(Room room, RoomUnit roomUnit) {
if (room == null || roomUnit == null) return null;
if (isInternalVariableToken(this.referenceVariableToken)) {
String key = getInternalVariableKey(this.referenceVariableToken);
return canUseUserInternalReference(key) ? this.readUserInternalValue(room, roomUnit, key) : null;
}
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.referenceVariableItemId);
if (definition == null || !definition.hasValue()) return null;
Habbo habbo = room.getHabbo(roomUnit);
return (habbo != null) ? room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), this.referenceVariableItemId) : null;
}
private Integer readFurniReferenceValue(Room room, HabboItem item) {
if (room == null || item == null) return null;
if (isInternalVariableToken(this.referenceVariableToken)) {
String key = getInternalVariableKey(this.referenceVariableToken);
return canUseFurniInternalReference(key) ? this.readFurniInternalValue(room, item, key) : null;
}
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.referenceVariableItemId);
return (definition != null && definition.hasValue()) ? room.getFurniVariableManager().getCurrentValue(item.getId(), this.referenceVariableItemId) : null;
}
private Integer readRoomReferenceValue(Room room) {
if (room == null) return null;
if (isInternalVariableToken(this.referenceVariableToken)) {
String key = getInternalVariableKey(this.referenceVariableToken);
return canUseRoomInternalReference(key) ? this.readRoomInternalValue(room, key) : null;
}
WiredVariableDefinitionInfo definition = room.getRoomVariableManager().getDefinitionInfo(this.referenceVariableItemId);
return (definition != null && definition.hasValue()) ? room.getRoomVariableManager().getCurrentValue(this.referenceVariableItemId) : null;
}
private Integer readContextReferenceValue(WiredContext ctx, Room room) {
if (ctx == null) return null;
if (isInternalVariableToken(this.referenceVariableToken)) {
String key = getInternalVariableKey(this.referenceVariableToken);
return canUseContextInternalReference(key) ? WiredInternalVariableSupport.readContextValue(ctx, key) : null;
}
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, this.referenceVariableItemId);
if (definition == null || !definition.hasValue() || !WiredContextVariableSupport.hasVariable(ctx, this.referenceVariableItemId)) return null;
return WiredContextVariableSupport.getCurrentValue(ctx, this.referenceVariableItemId);
}
private MetricSnapshot resolveUserMetric(Room room, RoomUnit roomUnit) {
if (room == null || roomUnit == null) return null;
if (isInternalVariableToken(this.variableToken)) {
String key = getInternalVariableKey(this.variableToken);
Integer value = canUseUserInternalReference(key) ? this.readUserInternalValue(room, roomUnit, key) : null;
return (value != null) ? new MetricSnapshot(roomUnit.getId(), value, 0, 0) : null;
}
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.variableItemId);
Habbo habbo = room.getHabbo(roomUnit);
if (definition == null || habbo == null) return null;
if (!room.getUserVariableManager().hasVariable(habbo.getHabboInfo().getId(), this.variableItemId)) return null;
return new MetricSnapshot(
roomUnit.getId(),
definition.hasValue() ? room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), this.variableItemId) : 0,
room.getUserVariableManager().getCreatedAt(habbo.getHabboInfo().getId(), this.variableItemId),
room.getUserVariableManager().getUpdatedAt(habbo.getHabboInfo().getId(), this.variableItemId));
}
private MetricSnapshot resolveFurniMetric(Room room, HabboItem item) {
if (room == null || item == null) return null;
if (isInternalVariableToken(this.variableToken)) {
String key = getInternalVariableKey(this.variableToken);
Integer value = canUseFurniInternalReference(key) ? this.readFurniInternalValue(room, item, key) : null;
return (value != null) ? new MetricSnapshot(item.getId(), value, 0, 0) : null;
}
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.variableItemId);
if (definition == null) return null;
if (!room.getFurniVariableManager().hasVariable(item.getId(), this.variableItemId)) return null;
return new MetricSnapshot(
item.getId(),
definition.hasValue() ? room.getFurniVariableManager().getCurrentValue(item.getId(), this.variableItemId) : 0,
room.getFurniVariableManager().getCreatedAt(item.getId(), this.variableItemId),
room.getFurniVariableManager().getUpdatedAt(item.getId(), this.variableItemId));
}
private Comparator<SortableEntry<?>> metricComparator() {
return switch (this.sortBy) {
case SORT_VALUE_LOWEST -> Comparator.comparingInt((SortableEntry<?> entry) -> entry.metric.value).thenComparingInt(entry -> entry.metric.entityId);
case SORT_CREATION_OLDEST -> Comparator.comparingInt((SortableEntry<?> entry) -> entry.metric.createdAt).thenComparingInt(entry -> entry.metric.entityId);
case SORT_CREATION_LATEST -> Comparator.<SortableEntry<?>, Integer>comparing(entry -> entry.metric.createdAt).reversed().thenComparingInt(entry -> entry.metric.entityId);
case SORT_UPDATE_OLDEST -> Comparator.comparingInt((SortableEntry<?> entry) -> entry.metric.updatedAt).thenComparingInt(entry -> entry.metric.entityId);
case SORT_UPDATE_LATEST -> Comparator.<SortableEntry<?>, Integer>comparing(entry -> entry.metric.updatedAt).reversed().thenComparingInt(entry -> entry.metric.entityId);
default -> Comparator.<SortableEntry<?>, Integer>comparing(entry -> entry.metric.value).reversed().thenComparingInt(entry -> entry.metric.entityId);
};
}
private boolean isValidMainVariable(Room room, String token) {
if (token == null || token.isEmpty()) return false;
if (isInternalVariableToken(token)) {
String key = getInternalVariableKey(token);
return this.getVariableTargetType() == TARGET_FURNI ? canUseFurniInternalReference(key) : canUseUserInternalReference(key);
}
if (this.getVariableTargetType() == TARGET_FURNI) {
return room != null && room.getFurniVariableManager().getDefinitionInfo(getCustomItemId(token)) != null;
}
return room != null && room.getUserVariableManager().getDefinitionInfo(getCustomItemId(token)) != null;
}
private boolean isValidReference(Room room, int targetType, String token) {
if (token == null || token.isEmpty()) return false;
if (isInternalVariableToken(token)) {
String key = getInternalVariableKey(token);
return switch (targetType) {
case TARGET_FURNI -> canUseFurniInternalReference(key);
case TARGET_CONTEXT -> canUseContextInternalReference(key);
case TARGET_ROOM -> canUseRoomInternalReference(key);
default -> canUseUserInternalReference(key);
};
}
return switch (targetType) {
case TARGET_FURNI -> {
WiredVariableDefinitionInfo definition = (room != null) ? room.getFurniVariableManager().getDefinitionInfo(getCustomItemId(token)) : null;
yield definition != null && definition.hasValue();
}
case TARGET_CONTEXT -> this.isValidContextCustomReference(room, getCustomItemId(token));
case TARGET_ROOM -> {
WiredVariableDefinitionInfo definition = (room != null) ? room.getRoomVariableManager().getDefinitionInfo(getCustomItemId(token)) : null;
yield definition != null && definition.hasValue();
}
default -> {
WiredVariableDefinitionInfo definition = (room != null) ? room.getUserVariableManager().getDefinitionInfo(getCustomItemId(token)) : null;
yield definition != null && definition.hasValue();
}
};
}
private boolean isValidContextCustomReference(Room room, int variableItemId) {
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, variableItemId);
return definition != null && definition.hasValue();
}
private static List<RoomUnit> trimUsers(List<SortableEntry<RoomUnit>> matches, int amount) {
List<RoomUnit> result = new ArrayList<>();
for (SortableEntry<RoomUnit> match : matches) {
if (result.size() >= amount) break;
result.add(match.entity);
}
return result;
}
private static List<HabboItem> trimItems(List<SortableEntry<HabboItem>> matches, int amount) {
List<HabboItem> result = new ArrayList<>();
for (SortableEntry<HabboItem> match : matches) {
if (result.size() >= amount) break;
result.add(match.entity);
}
return result;
}
private static List<RoomUnit> toUserList(Iterable<RoomUnit> values) {
List<RoomUnit> result = new ArrayList<>();
if (values == null) return result;
for (RoomUnit value : values) if (value != null) result.add(value);
return result;
}
private static List<HabboItem> toItemList(Iterable<HabboItem> values) {
List<HabboItem> result = new ArrayList<>();
if (values == null) return result;
for (HabboItem value : values) if (value != null) result.add(value);
return result;
}
private String serializeStringData() {
return (this.variableToken == null ? "" : this.variableToken) + DELIM + (this.referenceVariableToken == null ? "" : this.referenceVariableToken);
}
private void refreshReferenceItems() {
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room == null) {
this.referenceSelectedItems.clear();
return;
}
this.referenceSelectedItems.removeIf(item -> item == null || item.getRoomId() != room.getId() || room.getHabboItem(item.getId()) == null);
}
private void setVariableToken(String token) {
this.variableToken = normalizeVariableToken(token);
this.variableItemId = getCustomItemId(this.variableToken);
}
private void setReferenceVariableToken(String token) {
this.referenceVariableToken = normalizeVariableToken(token);
this.referenceVariableItemId = getCustomItemId(this.referenceVariableToken);
}
private List<Integer> toIds(List<HabboItem> items) {
List<Integer> ids = new ArrayList<>();
for (HabboItem item : items) if (item != null) ids.add(item.getId());
return ids;
}
private Integer readUserInternalValue(Room room, RoomUnit roomUnit, String key) {
return WiredInternalVariableSupport.readUserValue(room, roomUnit, key);
}
private Integer readFurniInternalValue(Room room, HabboItem item, String key) {
return WiredInternalVariableSupport.readFurniValue(room, item, key);
}
private Integer readRoomInternalValue(Room room, String key) {
return WiredInternalVariableSupport.readRoomValue(room, key);
}
private int getUserTeamScore(Room room, Habbo habbo) {
if (room == null || habbo == null || habbo.getHabboInfo() == null || habbo.getHabboInfo().getGamePlayer() == null) return 0;
Game game = this.resolveTeamGame(room, habbo);
if (game == null) return 0;
GamePlayer player = habbo.getHabboInfo().getGamePlayer();
return player.getScore();
}
private int getTeamColorId(int effectValue) {
TeamEffectData effectData = this.getTeamEffectData(effectValue);
return (effectData != null) ? effectData.colorId : 0;
}
private int getTeamTypeId(int effectValue) {
TeamEffectData effectData = this.getTeamEffectData(effectValue);
return (effectData != null) ? effectData.typeId : 0;
}
private int getTeamMetric(Room room, GameTeamColors color, boolean score) {
Game game = room.getGame(WiredGame.class);
if (game == null) game = room.getGame(FreezeGame.class);
if (game == null) game = room.getGame(BattleBanzaiGame.class);
if (game == null) return 0;
GameTeam team = game.getTeam(color);
if (team == null) return 0;
return score ? team.getTotalScore() : team.getMembers().size();
}
private Game resolveTeamGame(Room room, Habbo habbo) {
if (room == null) return null;
if (habbo != null && habbo.getHabboInfo() != null && habbo.getHabboInfo().getCurrentGame() != null) {
Game game = room.getGame(habbo.getHabboInfo().getCurrentGame());
if (game != null) return game;
}
Game wiredGame = room.getGame(WiredGame.class);
if (wiredGame != null) return wiredGame;
Game freezeGame = room.getGame(FreezeGame.class);
if (freezeGame != null) return freezeGame;
return room.getGame(BattleBanzaiGame.class);
}
private TeamEffectData getTeamEffectData(int effectValue) {
if (effectValue <= 0) return null;
if (effectValue >= 223 && effectValue <= 226) return new TeamEffectData(effectValue - 222, 0);
if (effectValue >= 33 && effectValue <= 36) return new TeamEffectData(effectValue - 32, 1);
if (effectValue >= 40 && effectValue <= 43) return new TeamEffectData(effectValue - 39, 2);
return null;
}
private static int normalizeSortBy(int value) {
return switch (value) {
case SORT_VALUE_LOWEST, SORT_CREATION_OLDEST, SORT_CREATION_LATEST, SORT_UPDATE_OLDEST, SORT_UPDATE_LATEST -> value;
default -> SORT_VALUE_HIGHEST;
};
}
private static int normalizeAmountMode(int value) {
return (value == AMOUNT_VARIABLE) ? AMOUNT_VARIABLE : AMOUNT_CONSTANT;
}
private static int normalizeReferenceTargetType(int value) {
return switch (value) {
case TARGET_FURNI, TARGET_CONTEXT, TARGET_ROOM -> value;
default -> TARGET_USER;
};
}
private static int normalizeUserSource(int value) {
return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
}
private static int normalizeReferenceFurniSource(int value) {
return switch (value) {
case SOURCE_SECONDARY_SELECTED, WiredSourceUtil.SOURCE_SELECTOR, WiredSourceUtil.SOURCE_SIGNAL -> value;
default -> WiredSourceUtil.SOURCE_TRIGGER;
};
}
protected static String normalizeVariableToken(String token) {
if (token == null) return "";
String normalized = token.trim();
if (normalized.isEmpty()) return "";
if (normalized.startsWith(INTERNAL_TOKEN_PREFIX)) {
return INTERNAL_TOKEN_PREFIX + WiredInternalVariableSupport.normalizeKey(normalized.substring(INTERNAL_TOKEN_PREFIX.length()));
}
if (isCustomVariableToken(normalized) || isInternalVariableToken(normalized)) return normalized;
try {
int parsed = Integer.parseInt(normalized);
return (parsed > 0) ? (CUSTOM_TOKEN_PREFIX + parsed) : "";
} catch (NumberFormatException e) {
return "";
}
}
protected static boolean isCustomVariableToken(String token) {
return token != null && token.startsWith(CUSTOM_TOKEN_PREFIX);
}
protected static boolean isInternalVariableToken(String token) {
return token != null && token.startsWith(INTERNAL_TOKEN_PREFIX);
}
protected static int getCustomItemId(String token) {
if (!isCustomVariableToken(token)) return 0;
try {
return Integer.parseInt(token.substring(CUSTOM_TOKEN_PREFIX.length()));
} catch (NumberFormatException e) {
return 0;
}
}
protected static String getInternalVariableKey(String token) {
return isInternalVariableToken(token) ? WiredInternalVariableSupport.normalizeKey(token.substring(INTERNAL_TOKEN_PREFIX.length())) : "";
}
private static boolean canUseUserInternalReference(String key) {
return WiredInternalVariableSupport.canUseUserReference(key);
}
private static boolean canUseFurniInternalReference(String key) {
return WiredInternalVariableSupport.canUseFurniReference(key);
}
private static boolean canUseRoomInternalReference(String key) {
return WiredInternalVariableSupport.canUseRoomReference(key);
}
private static boolean canUseContextInternalReference(String key) {
return WiredInternalVariableSupport.canUseContextReference(key);
}
private static int param(int[] params, int index, int fallback) {
return (params != null && params.length > index) ? params[index] : fallback;
}
private static String[] parseStringData(String value) {
return (value == null || value.isEmpty()) ? new String[0] : value.split("\\t", -1);
}
private static int parseInteger(String value) {
try {
return (value == null || value.trim().isEmpty()) ? 0 : Integer.parseInt(value.trim());
} catch (NumberFormatException e) {
return 0;
}
}
private static int normalizeAmount(int value) {
return Math.max(0, Math.min(MAX_FILTER_AMOUNT, value));
}
protected static class JsonData {
int sortBy;
int amountMode;
int amountConstantValue;
int referenceTargetType;
int referenceUserSource;
int referenceFurniSource;
String variableToken;
int variableItemId;
String referenceVariableToken;
int referenceVariableItemId;
List<Integer> selectedItemIds;
JsonData(int sortBy, int amountMode, int amountConstantValue, int referenceTargetType, int referenceUserSource, int referenceFurniSource, String variableToken, int variableItemId, String referenceVariableToken, int referenceVariableItemId, List<Integer> selectedItemIds) {
this.sortBy = sortBy;
this.amountMode = amountMode;
this.amountConstantValue = amountConstantValue;
this.referenceTargetType = referenceTargetType;
this.referenceUserSource = referenceUserSource;
this.referenceFurniSource = referenceFurniSource;
this.variableToken = variableToken;
this.variableItemId = variableItemId;
this.referenceVariableToken = referenceVariableToken;
this.referenceVariableItemId = referenceVariableItemId;
this.selectedItemIds = selectedItemIds;
}
}
private static class SortableEntry<T> {
final T entity;
final MetricSnapshot metric;
SortableEntry(T entity, MetricSnapshot metric) {
this.entity = entity;
this.metric = metric;
}
}
private static class MetricSnapshot {
final int entityId;
final int value;
final int createdAt;
final int updatedAt;
MetricSnapshot(int entityId, int value, int createdAt, int updatedAt) {
this.entityId = entityId;
this.value = value;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
}
private static class TeamEffectData {
final int colorId;
final int typeId;
TeamEffectData(int colorId, int typeId) {
this.colorId = colorId;
this.typeId = typeId;
}
}
}
@@ -0,0 +1,270 @@
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.messages.ServerMessage;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class WiredExtraVariableLevelUpSystem extends InteractionWiredExtra {
public static final int CODE = 82;
public static final int MODE_LINEAR = 1;
public static final int MODE_EXPONENTIAL = 2;
public static final int MODE_MANUAL = 3;
public static final int SUB_CURRENT_LEVEL = 0;
public static final int SUB_CURRENT_XP = 1;
public static final int SUB_LEVEL_PROGRESS = 2;
public static final int SUB_LEVEL_PROGRESS_PERCENT = 3;
public static final int SUB_TOTAL_XP_REQUIRED = 4;
public static final int SUB_XP_REMAINING = 5;
public static final int SUB_IS_AT_MAX = 6;
public static final int SUB_MAX_LEVEL = 7;
public static final int SUBVARIABLE_COUNT = 8;
private static final int DEFAULT_STEP_SIZE = 100;
private static final int DEFAULT_MAX_LEVEL = 10;
private static final int DEFAULT_FIRST_LEVEL_XP = 100;
private static final int DEFAULT_INCREASE_FACTOR = 100;
private static final int MAX_MANUAL_TEXT_LENGTH = 4096;
private int mode = MODE_LINEAR;
private int stepSize = DEFAULT_STEP_SIZE;
private int maxLevel = DEFAULT_MAX_LEVEL;
private int firstLevelXp = DEFAULT_FIRST_LEVEL_XP;
private int increaseFactor = DEFAULT_INCREASE_FACTOR;
private String interpolationText = "";
private int subvariableMask = (1 << SUB_CURRENT_LEVEL) | (1 << SUB_CURRENT_XP);
public WiredExtraVariableLevelUpSystem(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredExtraVariableLevelUpSystem(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) {
this.applyConfig(parseJsonData(settings.getStringParam()));
return true;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(
this.mode,
this.stepSize,
this.maxLevel,
this.firstLevelXp,
this.increaseFactor,
this.interpolationText,
this.getSelectedSubvariables()
));
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
message.appendBoolean(false);
message.appendInt(0);
message.appendInt(0);
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(this.getWiredData());
message.appendInt(0);
message.appendInt(0);
message.appendInt(CODE);
message.appendInt(0);
message.appendInt(0);
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty()) {
return;
}
this.applyConfig(parseJsonData(wiredData));
}
@Override
public void onPickUp() {
this.mode = MODE_LINEAR;
this.stepSize = DEFAULT_STEP_SIZE;
this.maxLevel = DEFAULT_MAX_LEVEL;
this.firstLevelXp = DEFAULT_FIRST_LEVEL_XP;
this.increaseFactor = DEFAULT_INCREASE_FACTOR;
this.interpolationText = "";
this.subvariableMask = (1 << SUB_CURRENT_LEVEL) | (1 << SUB_CURRENT_XP);
}
@Override
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
}
@Override
public boolean hasConfiguration() {
return true;
}
public int getMode() {
return this.mode;
}
public int getStepSize() {
return this.stepSize;
}
public int getMaxLevel() {
return this.maxLevel;
}
public int getFirstLevelXp() {
return this.firstLevelXp;
}
public int getIncreaseFactor() {
return this.increaseFactor;
}
public String getInterpolationText() {
return this.interpolationText;
}
public boolean hasSubvariable(int subvariableType) {
return subvariableType >= 0
&& subvariableType < SUBVARIABLE_COUNT
&& ((this.subvariableMask & (1 << subvariableType)) != 0);
}
public List<Integer> getSelectedSubvariables() {
List<Integer> result = new ArrayList<>();
for (int index = 0; index < SUBVARIABLE_COUNT; index++) {
if (this.hasSubvariable(index)) {
result.add(index);
}
}
return result;
}
private void applyConfig(JsonData data) {
if (data == null) {
this.onPickUp();
return;
}
this.mode = normalizeMode(data.mode);
this.stepSize = normalizeNonNegative(data.stepSize, DEFAULT_STEP_SIZE);
this.maxLevel = normalizeMaxLevel(data.maxLevel);
this.firstLevelXp = normalizeNonNegative(data.firstLevelXp, DEFAULT_FIRST_LEVEL_XP);
this.increaseFactor = normalizeNonNegative(data.increaseFactor, DEFAULT_INCREASE_FACTOR);
this.interpolationText = normalizeInterpolationText(data.interpolationText);
this.subvariableMask = normalizeSubvariableMask(data.subvariables);
}
private static JsonData parseJsonData(String value) {
if (value == null || value.trim().isEmpty()) {
return new JsonData();
}
try {
if (value.trim().startsWith("{")) {
JsonData data = WiredManager.getGson().fromJson(value, JsonData.class);
return (data != null) ? data : new JsonData();
}
} catch (Exception ignored) {
}
JsonData fallback = new JsonData();
fallback.interpolationText = normalizeInterpolationText(value);
fallback.mode = MODE_MANUAL;
return fallback;
}
private static int normalizeMode(int value) {
return switch (value) {
case MODE_EXPONENTIAL, MODE_MANUAL -> value;
default -> MODE_LINEAR;
};
}
private static int normalizeNonNegative(int value, int fallback) {
return Math.max(0, (value > 0) ? value : fallback);
}
private static int normalizeMaxLevel(int value) {
return Math.max(1, (value > 0) ? value : DEFAULT_MAX_LEVEL);
}
private static String normalizeInterpolationText(String value) {
if (value == null) {
return "";
}
String normalized = value.replace("\r", "");
if (normalized.length() > MAX_MANUAL_TEXT_LENGTH) {
normalized = normalized.substring(0, MAX_MANUAL_TEXT_LENGTH);
}
return normalized;
}
private static int normalizeSubvariableMask(List<Integer> subvariables) {
if (subvariables == null) {
return (1 << SUB_CURRENT_LEVEL) | (1 << SUB_CURRENT_XP);
}
int mask = 0;
for (Integer subvariable : subvariables) {
if (subvariable == null || subvariable < 0 || subvariable >= SUBVARIABLE_COUNT) {
continue;
}
mask |= (1 << subvariable);
}
return mask;
}
static class JsonData {
int mode = MODE_LINEAR;
int stepSize = DEFAULT_STEP_SIZE;
int maxLevel = DEFAULT_MAX_LEVEL;
int firstLevelXp = DEFAULT_FIRST_LEVEL_XP;
int increaseFactor = DEFAULT_INCREASE_FACTOR;
String interpolationText = "";
List<Integer> subvariables = null;
JsonData() {
}
JsonData(int mode, int stepSize, int maxLevel, int firstLevelXp, int increaseFactor, String interpolationText, List<Integer> subvariables) {
this.mode = mode;
this.stepSize = stepSize;
this.maxLevel = maxLevel;
this.firstLevelXp = firstLevelXp;
this.increaseFactor = increaseFactor;
this.interpolationText = interpolationText;
this.subvariables = subvariables;
}
}
}
@@ -0,0 +1,321 @@
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class WiredExtraVariableReference extends InteractionWiredExtra {
public static final int CODE = 81;
private String variableName = "";
private int sourceRoomId = 0;
private String sourceRoomName = "";
private int sourceVariableItemId = 0;
private String sourceVariableName = "";
private int sourceTargetType = WiredVariableReferenceSupport.TARGET_USER;
private boolean hasValue = false;
private boolean readOnly = true;
public WiredExtraVariableReference(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredExtraVariableReference(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
if (room == null) {
throw new WiredSaveException("Room not found");
}
ConfigData config = parseConfigData(settings.getStringParam());
String normalizedName = WiredVariableNameValidator.normalizeForSave(config.variableName);
WiredVariableNameValidator.validateDefinitionName(room, this.getId(), normalizedName);
if (config.sourceRoomId <= 0 || config.sourceVariableItemId <= 0) {
throw new WiredSaveException("wiredfurni.params.variables.validation.missing_variable");
}
WiredVariableReferenceSupport.SharedDefinitionOption definition = WiredVariableReferenceSupport.findSharedDefinition(
room,
config.sourceRoomId,
config.sourceVariableItemId,
config.sourceTargetType
);
if (definition == null) {
throw new WiredSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
this.variableName = normalizedName;
this.sourceRoomId = definition.getRoomId();
this.sourceRoomName = sanitizeLabel(definition.getRoomName());
this.sourceVariableItemId = definition.getItemId();
this.sourceVariableName = definition.getName();
this.sourceTargetType = definition.getTargetType();
this.hasValue = definition.hasValue();
this.readOnly = config.readOnly;
room.getUserVariableManager().broadcastSnapshot();
return true;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(
this.variableName,
this.sourceRoomId,
this.sourceRoomName,
this.sourceVariableItemId,
this.sourceVariableName,
this.sourceTargetType,
this.hasValue,
this.readOnly
));
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
message.appendBoolean(false);
message.appendInt(0);
message.appendInt(0);
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(buildEditorPayload(room));
message.appendInt(0);
message.appendInt(0);
message.appendInt(CODE);
message.appendInt(0);
message.appendInt(0);
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty() || !wiredData.startsWith("{")) {
return;
}
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data == null) {
return;
}
this.variableName = WiredVariableNameValidator.normalizeLegacy(data.variableName);
this.sourceRoomId = Math.max(0, data.sourceRoomId);
this.sourceRoomName = sanitizeLabel(data.sourceRoomName);
this.sourceVariableItemId = Math.max(0, data.sourceVariableItemId);
this.sourceVariableName = WiredVariableNameValidator.normalizeLegacy(data.sourceVariableName);
this.sourceTargetType = normalizeTargetType(data.sourceTargetType);
this.hasValue = data.hasValue;
this.readOnly = data.readOnly;
}
@Override
public void onPickUp() {
this.variableName = "";
this.sourceRoomId = 0;
this.sourceRoomName = "";
this.sourceVariableItemId = 0;
this.sourceVariableName = "";
this.sourceTargetType = WiredVariableReferenceSupport.TARGET_USER;
this.hasValue = false;
this.readOnly = true;
}
@Override
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
}
@Override
public boolean hasConfiguration() {
return true;
}
public String getVariableName() {
return this.variableName;
}
public int getSourceRoomId() {
return this.sourceRoomId;
}
public String getSourceRoomName() {
return this.sourceRoomName;
}
public int getSourceVariableItemId() {
return this.sourceVariableItemId;
}
public String getSourceVariableName() {
return this.sourceVariableName;
}
public int getSourceTargetType() {
return this.sourceTargetType;
}
public boolean hasValue() {
return this.hasValue;
}
public boolean isReadOnly() {
return this.readOnly;
}
public int getAvailability() {
return WiredVariableReferenceSupport.SHARED_AVAILABILITY;
}
public boolean isUserReference() {
return this.sourceTargetType == WiredVariableReferenceSupport.TARGET_USER;
}
public boolean isRoomReference() {
return this.sourceTargetType == WiredVariableReferenceSupport.TARGET_ROOM;
}
private String buildEditorPayload(Room room) {
List<RoomEditorData> roomOptions = new ArrayList<>();
for (WiredVariableReferenceSupport.RoomOption option : WiredVariableReferenceSupport.loadRoomOptions(room)) {
List<VariableEditorData> variables = new ArrayList<>();
for (WiredVariableReferenceSupport.SharedDefinitionOption definition : option.getVariables()) {
variables.add(new VariableEditorData(definition.getItemId(), definition.getName(), definition.getTargetType(), definition.hasValue()));
}
roomOptions.add(new RoomEditorData(option.getRoomId(), option.getRoomName(), variables));
}
return WiredManager.getGson().toJson(new EditorPayload(
this.variableName,
this.sourceRoomId,
this.sourceRoomName,
this.sourceVariableItemId,
this.sourceVariableName,
this.sourceTargetType,
this.readOnly,
roomOptions
));
}
private static ConfigData parseConfigData(String value) {
if (value == null || value.isEmpty() || !value.startsWith("{")) {
return new ConfigData();
}
ConfigData config = WiredManager.getGson().fromJson(value, ConfigData.class);
return (config != null) ? config : new ConfigData();
}
private static int normalizeTargetType(int value) {
return (value == WiredVariableReferenceSupport.TARGET_ROOM) ? WiredVariableReferenceSupport.TARGET_ROOM : WiredVariableReferenceSupport.TARGET_USER;
}
private static String sanitizeLabel(String value) {
if (value == null) {
return "";
}
return value.trim().replace("\t", "").replace("\r", "").replace("\n", "");
}
static class JsonData {
String variableName;
int sourceRoomId;
String sourceRoomName;
int sourceVariableItemId;
String sourceVariableName;
int sourceTargetType;
boolean hasValue;
boolean readOnly;
JsonData(String variableName, int sourceRoomId, String sourceRoomName, int sourceVariableItemId, String sourceVariableName, int sourceTargetType, boolean hasValue, boolean readOnly) {
this.variableName = variableName;
this.sourceRoomId = sourceRoomId;
this.sourceRoomName = sourceRoomName;
this.sourceVariableItemId = sourceVariableItemId;
this.sourceVariableName = sourceVariableName;
this.sourceTargetType = sourceTargetType;
this.hasValue = hasValue;
this.readOnly = readOnly;
}
}
static class ConfigData {
String variableName = "";
int sourceRoomId = 0;
int sourceVariableItemId = 0;
int sourceTargetType = WiredVariableReferenceSupport.TARGET_USER;
boolean readOnly = true;
}
static class EditorPayload extends ConfigData {
String sourceRoomName;
String sourceVariableName;
List<RoomEditorData> rooms;
EditorPayload(String variableName, int sourceRoomId, String sourceRoomName, int sourceVariableItemId, String sourceVariableName, int sourceTargetType, boolean readOnly, List<RoomEditorData> rooms) {
this.variableName = variableName;
this.sourceRoomId = sourceRoomId;
this.sourceRoomName = sourceRoomName;
this.sourceVariableItemId = sourceVariableItemId;
this.sourceVariableName = sourceVariableName;
this.sourceTargetType = sourceTargetType;
this.readOnly = readOnly;
this.rooms = rooms;
}
}
static class RoomEditorData {
int roomId;
String roomName;
List<VariableEditorData> variables;
RoomEditorData(int roomId, String roomName, List<VariableEditorData> variables) {
this.roomId = roomId;
this.roomName = roomName;
this.variables = variables;
}
}
static class VariableEditorData {
int itemId;
String name;
int targetType;
boolean hasValue;
VariableEditorData(int itemId, String name, int targetType, boolean hasValue) {
this.itemId = itemId;
this.name = name;
this.targetType = targetType;
this.hasValue = hasValue;
}
}
}
@@ -0,0 +1,204 @@
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.messages.ServerMessage;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
public class WiredExtraVariableTextConnector extends InteractionWiredExtra {
public static final int CODE = 79;
private static final int MAX_MAPPING_LENGTH = 4096;
private String mappingsText = "";
private LinkedHashMap<Integer, String> mappings = new LinkedHashMap<>();
public WiredExtraVariableTextConnector(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredExtraVariableTextConnector(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) {
this.setMappingsText(settings.getStringParam());
return true;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(this.mappingsText));
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
message.appendBoolean(false);
message.appendInt(0);
message.appendInt(0);
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(this.mappingsText);
message.appendInt(0);
message.appendInt(0);
message.appendInt(CODE);
message.appendInt(0);
message.appendInt(0);
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty()) {
return;
}
if (wiredData.startsWith("{")) {
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data != null) {
this.setMappingsText(data.mappingsText);
}
return;
}
this.setMappingsText(wiredData);
}
@Override
public void onPickUp() {
this.mappingsText = "";
this.mappings = new LinkedHashMap<>();
}
@Override
public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) {
}
@Override
public boolean hasConfiguration() {
return true;
}
public String getMappingsText() {
return this.mappingsText;
}
public Map<Integer, String> getMappings() {
return Collections.unmodifiableMap(this.mappings);
}
public String resolveText(Integer value) {
if (value == null) {
return "";
}
String mappedValue = this.mappings.get(value);
return mappedValue != null ? mappedValue : String.valueOf(value);
}
public Integer resolveValue(String text) {
if (text == null) {
return null;
}
String normalizedText = text.trim();
if (normalizedText.isEmpty()) {
return null;
}
for (Map.Entry<Integer, String> entry : this.mappings.entrySet()) {
if (entry == null || entry.getKey() == null || entry.getValue() == null) {
continue;
}
if (entry.getValue().trim().equalsIgnoreCase(normalizedText)) {
return entry.getKey();
}
}
return null;
}
private void setMappingsText(String value) {
this.mappingsText = normalizeMappingsText(value);
this.mappings = parseMappings(this.mappingsText);
}
private static String normalizeMappingsText(String value) {
if (value == null) {
return "";
}
String normalized = value.replace("\r", "");
if (normalized.length() > MAX_MAPPING_LENGTH) {
normalized = normalized.substring(0, MAX_MAPPING_LENGTH);
}
return normalized;
}
private static LinkedHashMap<Integer, String> parseMappings(String value) {
LinkedHashMap<Integer, String> result = new LinkedHashMap<>();
if (value == null || value.isEmpty()) {
return result;
}
for (String rawLine : value.split("\n")) {
if (rawLine == null) {
continue;
}
String line = rawLine.trim();
if (line.isEmpty()) {
continue;
}
int separatorIndex = line.indexOf('=');
if (separatorIndex < 0) {
separatorIndex = line.indexOf(',');
}
if (separatorIndex <= 0) {
continue;
}
String keyPart = line.substring(0, separatorIndex).trim();
String valuePart = line.substring(separatorIndex + 1).trim();
try {
result.put(Integer.parseInt(keyPart), valuePart);
} catch (NumberFormatException ignored) {
}
}
return result;
}
static class JsonData {
String mappingsText;
JsonData(String mappingsText) {
this.mappingsText = mappingsText;
}
}
}
@@ -0,0 +1,110 @@
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
import java.util.regex.Pattern;
final class WiredVariableNameValidator {
static final int MIN_NAME_LENGTH = 1;
static final int MAX_NAME_LENGTH = 40;
private static final Pattern VALID_NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$");
private WiredVariableNameValidator() {
}
static String normalizeForSave(String value) {
if (value == null) {
return "";
}
return value.trim()
.replace("\t", "")
.replace("\r", "")
.replace("\n", "");
}
static String normalizeLegacy(String value) {
String normalized = normalizeForSave(value);
if (normalized.contains("=")) {
normalized = normalized.substring(0, normalized.indexOf('=')).trim();
}
while (normalized.startsWith("@") || normalized.startsWith("~")) {
normalized = normalized.substring(1).trim();
}
if (normalized.length() > MAX_NAME_LENGTH) {
normalized = normalized.substring(0, MAX_NAME_LENGTH);
}
return normalized;
}
static void validateDefinitionName(Room room, int currentItemId, String variableName) throws WiredSaveException {
String normalized = normalizeForSave(variableName);
if (normalized.length() < MIN_NAME_LENGTH || normalized.length() > MAX_NAME_LENGTH) {
throw new WiredSaveException("wiredfurni.error.variables.name_length");
}
if (!VALID_NAME_PATTERN.matcher(normalized).matches()) {
throw new WiredSaveException("wiredfurni.error.variables.name_syntax");
}
if (isNameInUse(room, currentItemId, normalized)) {
throw new WiredSaveException("wiredfurni.error.variables.name_uniq");
}
}
private static boolean isNameInUse(Room room, int currentItemId, String variableName) {
if (room == null || room.getRoomSpecialTypes() == null || variableName == null || variableName.isEmpty()) {
return false;
}
for (InteractionWiredExtra extra : room.getRoomSpecialTypes().getExtras()) {
if (extra == null || extra.getId() == currentItemId) {
continue;
}
String existingName = getDefinitionName(extra);
if (existingName != null && existingName.equalsIgnoreCase(variableName)) {
return true;
}
}
return false;
}
private static String getDefinitionName(InteractionWiredExtra extra) {
if (extra instanceof WiredExtraUserVariable) {
return ((WiredExtraUserVariable) extra).getVariableName();
}
if (extra instanceof WiredExtraFurniVariable) {
return ((WiredExtraFurniVariable) extra).getVariableName();
}
if (extra instanceof WiredExtraRoomVariable) {
return ((WiredExtraRoomVariable) extra).getVariableName();
}
if (extra instanceof WiredExtraContextVariable) {
return ((WiredExtraContextVariable) extra).getVariableName();
}
if (extra instanceof WiredExtraVariableReference) {
return ((WiredExtraVariableReference) extra).getVariableName();
}
if (extra instanceof WiredExtraVariableEcho) {
return ((WiredExtraVariableEcho) extra).getVariableName();
}
return null;
}
}
@@ -0,0 +1,629 @@
package com.eu.habbo.habbohotel.items.interactions.wired.extra;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
public final class WiredVariableReferenceSupport {
public static final int TARGET_USER = 0;
public static final int TARGET_ROOM = 3;
public static final int SHARED_AVAILABILITY = 11;
private static final Logger LOGGER = LoggerFactory.getLogger(WiredVariableReferenceSupport.class);
private static final ConcurrentHashMap<String, CachedUserAssignment> USER_ASSIGNMENT_CACHE = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, CachedRoomAssignment> ROOM_ASSIGNMENT_CACHE = new ConcurrentHashMap<>();
private WiredVariableReferenceSupport() {
}
public static boolean isSharedAvailability(int availability) {
return availability == SHARED_AVAILABILITY;
}
public static SharedDefinitionOption findSharedDefinition(Room room, int sourceRoomId, int sourceVariableItemId, int sourceTargetType) {
if (room == null || sourceRoomId <= 0 || sourceVariableItemId <= 0) {
return null;
}
for (RoomOption roomOption : loadRoomOptions(room)) {
if (roomOption.getRoomId() != sourceRoomId) {
continue;
}
for (SharedDefinitionOption definition : roomOption.getVariables()) {
if (definition.getItemId() == sourceVariableItemId && definition.getTargetType() == sourceTargetType) {
return definition;
}
}
}
return null;
}
public static List<RoomOption> loadRoomOptions(Room room) {
if (room == null || room.getOwnerId() <= 0) {
return Collections.emptyList();
}
Map<Integer, RoomOption> optionsByRoomId = new LinkedHashMap<>();
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT rooms.id AS room_id, rooms.name AS room_name, items.id AS item_id, items.wired_data, items_base.interaction_type " +
"FROM rooms " +
"INNER JOIN items ON rooms.id = items.room_id " +
"INNER JOIN items_base ON items.item_id = items_base.id " +
"WHERE rooms.owner_id = ? AND rooms.id <> ? AND items_base.interaction_type IN ('wf_var_user', 'wf_var_room') " +
"ORDER BY rooms.name ASC, items.id ASC")) {
statement.setInt(1, room.getOwnerId());
statement.setInt(2, room.getId());
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
SharedDefinitionOption definition = parseSharedDefinition(
set.getString("interaction_type"),
set.getInt("item_id"),
set.getString("wired_data"),
set.getInt("room_id"),
set.getString("room_name")
);
if (definition == null) {
continue;
}
RoomOption roomOption = optionsByRoomId.computeIfAbsent(
definition.getRoomId(),
key -> new RoomOption(definition.getRoomId(), definition.getRoomName(), new ArrayList<>())
);
roomOption.getVariables().add(definition);
}
}
} catch (SQLException e) {
LOGGER.error("Failed to load shared variable reference options for room {}", room.getId(), e);
}
List<RoomOption> result = new ArrayList<>(optionsByRoomId.values());
for (RoomOption option : result) {
option.getVariables().sort(Comparator.comparing(SharedDefinitionOption::getName, String.CASE_INSENSITIVE_ORDER).thenComparingInt(SharedDefinitionOption::getItemId));
}
result.sort(Comparator.comparing(RoomOption::getRoomName, String.CASE_INSENSITIVE_ORDER).thenComparingInt(RoomOption::getRoomId));
return result;
}
public static SharedUserAssignment getSharedUserAssignment(WiredExtraVariableReference reference, int userId) {
if (reference == null || !reference.isUserReference() || userId <= 0) {
return null;
}
String cacheKey = createUserCacheKey(reference.getSourceRoomId(), reference.getSourceVariableItemId(), userId);
CachedUserAssignment cachedValue = USER_ASSIGNMENT_CACHE.get(cacheKey);
if (cachedValue != null) {
return cachedValue.present ? cachedValue.toAssignment() : null;
}
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT value, created_at, updated_at FROM room_user_wired_variables WHERE room_id = ? AND user_id = ? AND variable_item_id = ? LIMIT 1")) {
statement.setInt(1, reference.getSourceRoomId());
statement.setInt(2, userId);
statement.setInt(3, reference.getSourceVariableItemId());
try (ResultSet set = statement.executeQuery()) {
if (!set.next()) {
USER_ASSIGNMENT_CACHE.put(cacheKey, CachedUserAssignment.missing());
return null;
}
Integer value = null;
int rawValue = set.getInt("value");
if (!set.wasNull()) {
value = rawValue;
}
int createdAt = normalizeTimestamp(set.getInt("created_at"), 0);
SharedUserAssignment assignment = new SharedUserAssignment(
value,
createdAt,
normalizeTimestamp(set.getInt("updated_at"), createdAt)
);
USER_ASSIGNMENT_CACHE.put(cacheKey, CachedUserAssignment.present(assignment));
return assignment;
}
} catch (SQLException e) {
LOGGER.error("Failed to load shared wired user variable {} for room {} user {}", reference.getSourceVariableItemId(), reference.getSourceRoomId(), userId, e);
return null;
}
}
public static boolean assignSharedUserVariable(WiredExtraVariableReference reference, int userId, Integer value, boolean overrideExisting) {
if (reference == null || !reference.isUserReference() || reference.isReadOnly() || userId <= 0 || !isSharedSourceStillAvailable(reference)) {
return false;
}
Integer normalizedValue = reference.hasValue() ? value : null;
SharedUserAssignment existingAssignment = getSharedUserAssignment(reference, userId);
if (existingAssignment != null && !overrideExisting) {
return false;
}
int now = Emulator.getIntUnixTimestamp();
SharedUserAssignment nextAssignment = (existingAssignment == null)
? new SharedUserAssignment(normalizedValue, now, now)
: new SharedUserAssignment(normalizedValue, existingAssignment.getCreatedAt(), Objects.equals(existingAssignment.getValue(), normalizedValue) ? existingAssignment.getUpdatedAt() : now);
if (existingAssignment != null && Objects.equals(existingAssignment.getValue(), normalizedValue)) {
return false;
}
upsertSharedUserAssignment(reference.getSourceRoomId(), reference.getSourceVariableItemId(), userId, nextAssignment);
USER_ASSIGNMENT_CACHE.put(createUserCacheKey(reference.getSourceRoomId(), reference.getSourceVariableItemId(), userId), CachedUserAssignment.present(nextAssignment));
return true;
}
public static boolean updateSharedUserVariable(WiredExtraVariableReference reference, int userId, Integer value) {
if (reference == null || !reference.isUserReference() || reference.isReadOnly() || userId <= 0 || !reference.hasValue() || !isSharedSourceStillAvailable(reference)) {
return false;
}
SharedUserAssignment existingAssignment = getSharedUserAssignment(reference, userId);
if (existingAssignment == null || Objects.equals(existingAssignment.getValue(), value)) {
return false;
}
SharedUserAssignment nextAssignment = new SharedUserAssignment(value, existingAssignment.getCreatedAt(), Emulator.getIntUnixTimestamp());
upsertSharedUserAssignment(reference.getSourceRoomId(), reference.getSourceVariableItemId(), userId, nextAssignment);
USER_ASSIGNMENT_CACHE.put(createUserCacheKey(reference.getSourceRoomId(), reference.getSourceVariableItemId(), userId), CachedUserAssignment.present(nextAssignment));
return true;
}
public static boolean removeSharedUserVariable(WiredExtraVariableReference reference, int userId) {
if (reference == null || !reference.isUserReference() || reference.isReadOnly() || userId <= 0 || !isSharedSourceStillAvailable(reference)) {
return false;
}
SharedUserAssignment existingAssignment = getSharedUserAssignment(reference, userId);
if (existingAssignment == null) {
return false;
}
deleteSharedUserAssignment(reference.getSourceRoomId(), reference.getSourceVariableItemId(), userId);
USER_ASSIGNMENT_CACHE.put(createUserCacheKey(reference.getSourceRoomId(), reference.getSourceVariableItemId(), userId), CachedUserAssignment.missing());
return true;
}
public static void cacheSharedUserAssignment(int sourceRoomId, int sourceVariableItemId, int userId, Integer value, int createdAt, int updatedAt) {
USER_ASSIGNMENT_CACHE.put(createUserCacheKey(sourceRoomId, sourceVariableItemId, userId), CachedUserAssignment.present(new SharedUserAssignment(value, createdAt, updatedAt)));
}
public static void clearSharedUserAssignment(int sourceRoomId, int sourceVariableItemId, int userId) {
USER_ASSIGNMENT_CACHE.put(createUserCacheKey(sourceRoomId, sourceVariableItemId, userId), CachedUserAssignment.missing());
}
public static void clearSharedUserDefinition(int sourceRoomId, int sourceVariableItemId) {
String prefix = createDefinitionPrefix(sourceRoomId, sourceVariableItemId) + ":";
USER_ASSIGNMENT_CACHE.entrySet().removeIf(entry -> entry.getKey().startsWith(prefix));
}
public static SharedRoomAssignment getSharedRoomAssignment(WiredExtraVariableReference reference) {
if (reference == null || !reference.isRoomReference()) {
return null;
}
String cacheKey = createRoomCacheKey(reference.getSourceRoomId(), reference.getSourceVariableItemId());
CachedRoomAssignment cachedValue = ROOM_ASSIGNMENT_CACHE.get(cacheKey);
if (cachedValue != null) {
return cachedValue.present ? cachedValue.toAssignment() : null;
}
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT value, updated_at FROM room_wired_variables WHERE room_id = ? AND variable_item_id = ? LIMIT 1")) {
statement.setInt(1, reference.getSourceRoomId());
statement.setInt(2, reference.getSourceVariableItemId());
try (ResultSet set = statement.executeQuery()) {
if (!set.next()) {
ROOM_ASSIGNMENT_CACHE.put(cacheKey, CachedRoomAssignment.missing());
return null;
}
SharedRoomAssignment assignment = new SharedRoomAssignment(set.getInt("value"), normalizeTimestamp(set.getInt("updated_at"), 0));
ROOM_ASSIGNMENT_CACHE.put(cacheKey, CachedRoomAssignment.present(assignment));
return assignment;
}
} catch (SQLException e) {
LOGGER.error("Failed to load shared wired room variable {} for room {}", reference.getSourceVariableItemId(), reference.getSourceRoomId(), e);
return null;
}
}
public static boolean updateSharedRoomVariable(WiredExtraVariableReference reference, int value) {
if (reference == null || !reference.isRoomReference() || reference.isReadOnly() || !isSharedSourceStillAvailable(reference)) {
return false;
}
SharedRoomAssignment existingAssignment = getSharedRoomAssignment(reference);
if (existingAssignment != null && existingAssignment.getValue() == value) {
return false;
}
SharedRoomAssignment nextAssignment = new SharedRoomAssignment(value, Emulator.getIntUnixTimestamp());
upsertSharedRoomAssignment(reference.getSourceRoomId(), reference.getSourceVariableItemId(), nextAssignment);
ROOM_ASSIGNMENT_CACHE.put(createRoomCacheKey(reference.getSourceRoomId(), reference.getSourceVariableItemId()), CachedRoomAssignment.present(nextAssignment));
return true;
}
public static boolean removeSharedRoomVariable(WiredExtraVariableReference reference) {
if (reference == null || !reference.isRoomReference() || reference.isReadOnly() || !isSharedSourceStillAvailable(reference)) {
return false;
}
SharedRoomAssignment existingAssignment = getSharedRoomAssignment(reference);
if (existingAssignment == null) {
return false;
}
deleteSharedRoomAssignment(reference.getSourceRoomId(), reference.getSourceVariableItemId());
ROOM_ASSIGNMENT_CACHE.put(createRoomCacheKey(reference.getSourceRoomId(), reference.getSourceVariableItemId()), CachedRoomAssignment.missing());
return true;
}
public static void cacheSharedRoomAssignment(int sourceRoomId, int sourceVariableItemId, int value, int updatedAt) {
ROOM_ASSIGNMENT_CACHE.put(createRoomCacheKey(sourceRoomId, sourceVariableItemId), CachedRoomAssignment.present(new SharedRoomAssignment(value, updatedAt)));
}
public static void clearSharedRoomDefinition(int sourceRoomId, int sourceVariableItemId) {
ROOM_ASSIGNMENT_CACHE.put(createRoomCacheKey(sourceRoomId, sourceVariableItemId), CachedRoomAssignment.missing());
}
private static SharedDefinitionOption parseSharedDefinition(String interactionType, int itemId, String wiredData, int roomId, String roomName) {
if ("wf_var_user".equals(interactionType)) {
UserDefinitionData data = parseUserDefinitionData(wiredData);
if (data == null || !isSharedAvailability(data.availability) || data.variableName.isEmpty()) {
return null;
}
return new SharedDefinitionOption(roomId, roomName, itemId, data.variableName, TARGET_USER, data.hasValue);
}
if ("wf_var_room".equals(interactionType)) {
RoomDefinitionData data = parseRoomDefinitionData(wiredData);
if (data == null || !isSharedAvailability(data.availability) || data.variableName.isEmpty()) {
return null;
}
return new SharedDefinitionOption(roomId, roomName, itemId, data.variableName, TARGET_ROOM, true);
}
return null;
}
private static UserDefinitionData parseUserDefinitionData(String wiredData) {
if (wiredData == null || wiredData.isEmpty() || !wiredData.startsWith("{")) {
return null;
}
UserDefinitionData data = WiredManager.getGson().fromJson(wiredData, UserDefinitionData.class);
if (data == null) {
return null;
}
data.variableName = WiredVariableNameValidator.normalizeLegacy(data.variableName);
return data;
}
private static RoomDefinitionData parseRoomDefinitionData(String wiredData) {
if (wiredData == null || wiredData.isEmpty() || !wiredData.startsWith("{")) {
return null;
}
RoomDefinitionData data = WiredManager.getGson().fromJson(wiredData, RoomDefinitionData.class);
if (data == null) {
return null;
}
data.variableName = WiredVariableNameValidator.normalizeLegacy(data.variableName);
return data;
}
private static boolean isSharedSourceStillAvailable(WiredExtraVariableReference reference) {
if (reference == null || reference.getSourceRoomId() <= 0 || reference.getSourceVariableItemId() <= 0) {
return false;
}
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT items.wired_data, items_base.interaction_type " +
"FROM items INNER JOIN items_base ON items.item_id = items_base.id " +
"WHERE items.id = ? AND items.room_id = ? LIMIT 1")) {
statement.setInt(1, reference.getSourceVariableItemId());
statement.setInt(2, reference.getSourceRoomId());
try (ResultSet set = statement.executeQuery()) {
if (!set.next()) {
return false;
}
SharedDefinitionOption definition = parseSharedDefinition(
set.getString("interaction_type"),
reference.getSourceVariableItemId(),
set.getString("wired_data"),
reference.getSourceRoomId(),
""
);
return definition != null && definition.getTargetType() == reference.getSourceTargetType();
}
} catch (SQLException e) {
LOGGER.error("Failed to validate shared wired variable source {} in room {}", reference.getSourceVariableItemId(), reference.getSourceRoomId(), e);
return false;
}
}
private static void upsertSharedUserAssignment(int sourceRoomId, int sourceVariableItemId, int userId, SharedUserAssignment assignment) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("INSERT INTO room_user_wired_variables (room_id, user_id, variable_item_id, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at)")) {
statement.setInt(1, sourceRoomId);
statement.setInt(2, userId);
statement.setInt(3, sourceVariableItemId);
if (assignment.getValue() == null) {
statement.setNull(4, java.sql.Types.INTEGER);
} else {
statement.setInt(4, assignment.getValue());
}
statement.setInt(5, assignment.getCreatedAt());
statement.setInt(6, assignment.getUpdatedAt());
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.error("Failed to store shared wired user variable {} for room {} user {}", sourceVariableItemId, sourceRoomId, userId, e);
}
}
private static void deleteSharedUserAssignment(int sourceRoomId, int sourceVariableItemId, int userId) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("DELETE FROM room_user_wired_variables WHERE room_id = ? AND user_id = ? AND variable_item_id = ?")) {
statement.setInt(1, sourceRoomId);
statement.setInt(2, userId);
statement.setInt(3, sourceVariableItemId);
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.error("Failed to delete shared wired user variable {} for room {} user {}", sourceVariableItemId, sourceRoomId, userId, e);
}
}
private static void upsertSharedRoomAssignment(int sourceRoomId, int sourceVariableItemId, SharedRoomAssignment assignment) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("INSERT INTO room_wired_variables (room_id, variable_item_id, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at)")) {
statement.setInt(1, sourceRoomId);
statement.setInt(2, sourceVariableItemId);
statement.setInt(3, assignment.getValue());
statement.setInt(4, 0);
statement.setInt(5, assignment.getUpdatedAt());
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.error("Failed to store shared wired room variable {} for room {}", sourceVariableItemId, sourceRoomId, e);
}
}
private static void deleteSharedRoomAssignment(int sourceRoomId, int sourceVariableItemId) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("DELETE FROM room_wired_variables WHERE room_id = ? AND variable_item_id = ?")) {
statement.setInt(1, sourceRoomId);
statement.setInt(2, sourceVariableItemId);
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.error("Failed to delete shared wired room variable {} for room {}", sourceVariableItemId, sourceRoomId, e);
}
}
private static String createDefinitionPrefix(int sourceRoomId, int sourceVariableItemId) {
return sourceRoomId + ":" + sourceVariableItemId;
}
private static String createUserCacheKey(int sourceRoomId, int sourceVariableItemId, int userId) {
return createDefinitionPrefix(sourceRoomId, sourceVariableItemId) + ":" + userId;
}
private static String createRoomCacheKey(int sourceRoomId, int sourceVariableItemId) {
return createDefinitionPrefix(sourceRoomId, sourceVariableItemId);
}
private static int normalizeTimestamp(int value, int fallback) {
if (value > 0) {
return value;
}
if (fallback > 0) {
return fallback;
}
return Emulator.getIntUnixTimestamp();
}
public static class RoomOption {
private final int roomId;
private final String roomName;
private final List<SharedDefinitionOption> variables;
public RoomOption(int roomId, String roomName, List<SharedDefinitionOption> variables) {
this.roomId = roomId;
this.roomName = roomName;
this.variables = variables;
}
public int getRoomId() {
return this.roomId;
}
public String getRoomName() {
return this.roomName;
}
public List<SharedDefinitionOption> getVariables() {
return this.variables;
}
}
public static class SharedDefinitionOption {
private final int roomId;
private final String roomName;
private final int itemId;
private final String name;
private final int targetType;
private final boolean hasValue;
public SharedDefinitionOption(int roomId, String roomName, int itemId, String name, int targetType, boolean hasValue) {
this.roomId = roomId;
this.roomName = roomName;
this.itemId = itemId;
this.name = name;
this.targetType = targetType;
this.hasValue = hasValue;
}
public int getRoomId() {
return this.roomId;
}
public String getRoomName() {
return this.roomName;
}
public int getItemId() {
return this.itemId;
}
public String getName() {
return this.name;
}
public int getTargetType() {
return this.targetType;
}
public boolean hasValue() {
return this.hasValue;
}
}
public static class SharedUserAssignment {
private final Integer value;
private final int createdAt;
private final int updatedAt;
public SharedUserAssignment(Integer value, int createdAt, int updatedAt) {
this.value = value;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Integer getValue() {
return this.value;
}
public int getCreatedAt() {
return this.createdAt;
}
public int getUpdatedAt() {
return this.updatedAt;
}
}
public static class SharedRoomAssignment {
private final int value;
private final int updatedAt;
public SharedRoomAssignment(int value, int updatedAt) {
this.value = value;
this.updatedAt = updatedAt;
}
public int getValue() {
return this.value;
}
public int getUpdatedAt() {
return this.updatedAt;
}
}
private static class CachedUserAssignment {
private final boolean present;
private final SharedUserAssignment assignment;
private CachedUserAssignment(boolean present, SharedUserAssignment assignment) {
this.present = present;
this.assignment = assignment;
}
private static CachedUserAssignment present(SharedUserAssignment assignment) {
return new CachedUserAssignment(true, assignment);
}
private static CachedUserAssignment missing() {
return new CachedUserAssignment(false, null);
}
private SharedUserAssignment toAssignment() {
return this.assignment;
}
}
private static class CachedRoomAssignment {
private final boolean present;
private final SharedRoomAssignment assignment;
private CachedRoomAssignment(boolean present, SharedRoomAssignment assignment) {
this.present = present;
this.assignment = assignment;
}
private static CachedRoomAssignment present(SharedRoomAssignment assignment) {
return new CachedRoomAssignment(true, assignment);
}
private static CachedRoomAssignment missing() {
return new CachedRoomAssignment(false, null);
}
private SharedRoomAssignment toAssignment() {
return this.assignment;
}
}
private static class UserDefinitionData {
String variableName;
boolean hasValue;
int availability;
}
private static class RoomDefinitionData {
String variableName;
int availability;
}
}
@@ -0,0 +1,29 @@
package com.eu.habbo.habbohotel.items.interactions.wired.selector;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.wired.WiredEffectType;
import java.sql.ResultSet;
import java.sql.SQLException;
public class WiredEffectFurniWithVariable extends WiredEffectVariableSelectorBase {
public static final WiredEffectType type = WiredEffectType.FURNI_WITH_VAR_SELECTOR;
public WiredEffectFurniWithVariable(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredEffectFurniWithVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
protected int getVariableTargetType() {
return TARGET_FURNI;
}
@Override
public WiredEffectType getType() {
return type;
}
}
@@ -0,0 +1,29 @@
package com.eu.habbo.habbohotel.items.interactions.wired.selector;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.wired.WiredEffectType;
import java.sql.ResultSet;
import java.sql.SQLException;
public class WiredEffectUsersWithVariable extends WiredEffectVariableSelectorBase {
public static final WiredEffectType type = WiredEffectType.USERS_WITH_VAR_SELECTOR;
public WiredEffectUsersWithVariable(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredEffectUsersWithVariable(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
protected int getVariableTargetType() {
return TARGET_USER;
}
@Override
public WiredEffectType getType() {
return type;
}
}
@@ -0,0 +1,862 @@
package com.eu.habbo.habbohotel.items.interactions.wired.selector;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.bots.Bot;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.games.Game;
import com.eu.habbo.habbohotel.games.GamePlayer;
import com.eu.habbo.habbohotel.games.GameTeam;
import com.eu.habbo.habbohotel.games.GameTeamColors;
import com.eu.habbo.habbohotel.games.battlebanzai.BattleBanzaiGame;
import com.eu.habbo.habbohotel.games.freeze.FreezeGame;
import com.eu.habbo.habbohotel.games.wired.WiredGame;
import com.eu.habbo.habbohotel.items.FurnitureType;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWired;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomRightLevels;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
import com.eu.habbo.habbohotel.users.DanceType;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.habbohotel.wired.WiredEffectType;
import com.eu.habbo.habbohotel.wired.core.WiredContext;
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil;
import com.eu.habbo.habbohotel.wired.core.WiredInternalVariableSupport;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.incoming.wired.WiredSaveException;
import com.eu.habbo.util.HotelDateTimeUtil;
import gnu.trove.set.hash.THashSet;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.time.ZonedDateTime;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
public abstract class WiredEffectVariableSelectorBase extends InteractionWiredEffect {
protected static final int TARGET_USER = 0;
protected static final int TARGET_FURNI = 1;
protected static final int TARGET_CONTEXT = 2;
protected static final int TARGET_ROOM = 3;
protected static final int REFERENCE_CONSTANT = 0;
protected static final int REFERENCE_VARIABLE = 1;
protected static final int SOURCE_SECONDARY_SELECTED = 101;
protected static final int COMPARISON_GREATER_THAN = 0;
protected static final int COMPARISON_GREATER_THAN_OR_EQUAL = 1;
protected static final int COMPARISON_EQUAL = 2;
protected static final int COMPARISON_LESS_THAN_OR_EQUAL = 3;
protected static final int COMPARISON_LESS_THAN = 4;
protected static final int COMPARISON_NOT_EQUAL = 5;
private static final String CUSTOM_TOKEN_PREFIX = "custom:";
private static final String INTERNAL_TOKEN_PREFIX = "internal:";
private static final String DELIM = "\t";
protected boolean selectByValue = false;
protected int comparison = COMPARISON_EQUAL;
protected int referenceMode = REFERENCE_CONSTANT;
protected int referenceConstantValue = 0;
protected int referenceTargetType = TARGET_USER;
protected int referenceUserSource = WiredSourceUtil.SOURCE_TRIGGER;
protected int referenceFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
protected boolean filterExisting = false;
protected boolean invert = false;
protected String variableToken = "";
protected int variableItemId = 0;
protected String referenceVariableToken = "";
protected int referenceVariableItemId = 0;
protected final THashSet<HabboItem> referenceSelectedItems = new THashSet<>();
protected WiredEffectVariableSelectorBase(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
protected WiredEffectVariableSelectorBase(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
protected abstract int getVariableTargetType();
@Override
public void execute(WiredContext ctx) {
Room room = ctx.room();
if (room == null || this.variableToken == null || this.variableToken.isEmpty()) {
return;
}
if (this.getVariableTargetType() == TARGET_FURNI) {
LinkedHashSet<HabboItem> matchedItems = new LinkedHashSet<>();
for (HabboItem item : this.getSelectableFloorItems(room, ctx)) {
if (item == null) continue;
if (!this.matchesFurni(room, item, ctx)) continue;
matchedItems.add(item);
}
LinkedHashSet<HabboItem> result = this.applySelectorModifiers(matchedItems, this.getSelectableFloorItems(room, ctx), ctx.targets().items(), this.filterExisting, this.invert);
ctx.targets().setItems(result);
return;
}
LinkedHashSet<RoomUnit> matchedUsers = new LinkedHashSet<>();
for (RoomUnit roomUnit : room.getRoomUnits()) {
if (roomUnit == null) continue;
if (!this.matchesUser(room, roomUnit, ctx)) continue;
matchedUsers.add(roomUnit);
}
LinkedHashSet<RoomUnit> result = this.applySelectorModifiers(matchedUsers, room.getRoomUnits(), ctx.targets().users(), this.filterExisting, this.invert);
ctx.targets().setUsers(result);
}
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException {
Room room = this.getRoom();
if (room == null) return false;
int[] params = settings.getIntParams();
String[] stringParts = parseStringData(settings.getStringParam());
boolean nextSelectByValue = param(params, 0, 0) == 1;
int nextComparison = normalizeComparison(param(params, 1, COMPARISON_EQUAL));
int nextReferenceMode = normalizeReferenceMode(param(params, 2, REFERENCE_CONSTANT));
int nextReferenceConstantValue = param(params, 3, 0);
int nextReferenceTargetType = normalizeReferenceTargetType(param(params, 4, TARGET_USER));
int nextReferenceUserSource = normalizeUserSource(param(params, 5, WiredSourceUtil.SOURCE_TRIGGER));
int nextReferenceFurniSource = normalizeReferenceFurniSource(param(params, 6, WiredSourceUtil.SOURCE_TRIGGER));
boolean nextFilterExisting = param(params, 7, 0) == 1;
boolean nextInvert = param(params, 8, 0) == 1;
String nextVariableToken = normalizeVariableToken((stringParts.length > 0) ? stringParts[0] : settings.getStringParam());
String nextReferenceVariableToken = normalizeVariableToken((stringParts.length > 1) ? stringParts[1] : "");
if (!this.isValidMainVariable(room, nextVariableToken, nextSelectByValue)) return false;
if (nextSelectByValue && nextReferenceMode == REFERENCE_VARIABLE && !this.isValidReference(room, nextReferenceTargetType, nextReferenceVariableToken)) return false;
List<HabboItem> nextReferenceItems = new ArrayList<>();
if (nextSelectByValue && nextReferenceMode == REFERENCE_VARIABLE && nextReferenceTargetType == TARGET_FURNI && nextReferenceFurniSource == SOURCE_SECONDARY_SELECTED) {
int[] furniIds = settings.getFurniIds();
if (furniIds.length > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) return false;
for (int furniId : furniIds) {
HabboItem item = room.getHabboItem(furniId);
if (item != null) nextReferenceItems.add(item);
}
}
this.referenceSelectedItems.clear();
this.referenceSelectedItems.addAll(nextReferenceItems);
this.selectByValue = nextSelectByValue;
this.comparison = nextComparison;
this.referenceMode = nextReferenceMode;
this.referenceConstantValue = nextReferenceConstantValue;
this.referenceTargetType = nextReferenceTargetType;
this.referenceUserSource = nextReferenceUserSource;
this.referenceFurniSource = nextReferenceFurniSource;
this.filterExisting = nextFilterExisting;
this.invert = nextInvert;
this.setVariableToken(nextVariableToken);
this.setReferenceVariableToken(nextReferenceVariableToken);
this.setDelay(settings.getDelay());
return true;
}
@Override
public boolean isSelector() {
return true;
}
@Override
public String getWiredData() {
this.refreshReferenceItems();
return WiredManager.getGson().toJson(new JsonData(
this.selectByValue,
this.comparison,
this.referenceMode,
this.referenceConstantValue,
this.referenceTargetType,
this.referenceUserSource,
this.referenceFurniSource,
this.filterExisting,
this.invert,
this.variableToken,
this.variableItemId,
this.referenceVariableToken,
this.referenceVariableItemId,
this.toIds(this.referenceSelectedItems),
this.getDelay()
));
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty() || !wiredData.startsWith("{")) return;
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data == null) return;
this.selectByValue = data.selectByValue;
this.comparison = normalizeComparison(data.comparison);
this.referenceMode = normalizeReferenceMode(data.referenceMode);
this.referenceConstantValue = data.referenceConstantValue;
this.referenceTargetType = normalizeReferenceTargetType(data.referenceTargetType);
this.referenceUserSource = normalizeUserSource(data.referenceUserSource);
this.referenceFurniSource = normalizeReferenceFurniSource(data.referenceFurniSource);
this.filterExisting = data.filterExisting;
this.invert = data.invert;
this.setVariableToken(normalizeVariableToken((data.variableToken != null) ? data.variableToken : ((data.variableItemId > 0) ? String.valueOf(data.variableItemId) : "")));
this.setReferenceVariableToken(normalizeVariableToken((data.referenceVariableToken != null) ? data.referenceVariableToken : ((data.referenceVariableItemId > 0) ? String.valueOf(data.referenceVariableItemId) : "")));
this.setDelay(data.delay);
if (room == null || data.selectedItemIds == null) return;
for (Integer itemId : data.selectedItemIds) {
if (itemId == null || itemId <= 0) continue;
HabboItem item = room.getHabboItem(itemId);
if (item != null) this.referenceSelectedItems.add(item);
}
}
@Override
public void onPickUp() {
this.selectByValue = false;
this.comparison = COMPARISON_EQUAL;
this.referenceMode = REFERENCE_CONSTANT;
this.referenceConstantValue = 0;
this.referenceTargetType = TARGET_USER;
this.referenceUserSource = WiredSourceUtil.SOURCE_TRIGGER;
this.referenceFurniSource = WiredSourceUtil.SOURCE_TRIGGER;
this.filterExisting = false;
this.invert = false;
this.referenceSelectedItems.clear();
this.setVariableToken("");
this.setReferenceVariableToken("");
this.setDelay(0);
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
this.refreshReferenceItems();
List<HabboItem> serializedItems = new ArrayList<>();
if (this.selectByValue && this.referenceMode == REFERENCE_VARIABLE && this.referenceTargetType == TARGET_FURNI && this.referenceFurniSource == SOURCE_SECONDARY_SELECTED) {
serializedItems.addAll(this.referenceSelectedItems);
}
message.appendBoolean(false);
message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION);
message.appendInt(serializedItems.size());
for (HabboItem item : serializedItems) {
message.appendInt(item.getId());
}
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(this.serializeStringData());
message.appendInt(9);
message.appendInt(this.selectByValue ? 1 : 0);
message.appendInt(this.comparison);
message.appendInt(this.referenceMode);
message.appendInt(this.referenceConstantValue);
message.appendInt(this.referenceTargetType);
message.appendInt(this.referenceUserSource);
message.appendInt(this.referenceFurniSource);
message.appendInt(this.filterExisting ? 1 : 0);
message.appendInt(this.invert ? 1 : 0);
message.appendInt(0);
message.appendInt(this.getType().code);
message.appendInt(this.getDelay());
message.appendInt(0);
}
@Deprecated
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public boolean requiresTriggeringUser() {
return this.selectByValue && this.referenceMode == REFERENCE_VARIABLE && this.referenceTargetType == TARGET_USER && this.referenceUserSource == WiredSourceUtil.SOURCE_TRIGGER;
}
private boolean matchesUser(Room room, RoomUnit roomUnit, WiredContext ctx) {
if (!this.selectByValue) return this.hasUserVariable(room, roomUnit);
Integer currentValue = this.readUserValue(room, roomUnit);
Integer referenceValue = this.resolveReferenceValue(ctx, room, roomUnit != null ? roomUnit.getId() : 0, TARGET_USER, -1);
return this.matchesComparison(currentValue, referenceValue);
}
private boolean matchesFurni(Room room, HabboItem item, WiredContext ctx) {
if (!this.selectByValue) return this.hasFurniVariable(room, item);
Integer currentValue = this.readFurniValue(room, item);
Integer referenceValue = this.resolveReferenceValue(ctx, room, item != null ? item.getId() : 0, TARGET_FURNI, -1);
return this.matchesComparison(currentValue, referenceValue);
}
private boolean hasUserVariable(Room room, RoomUnit roomUnit) {
if (room == null || roomUnit == null) return false;
if (isCustomVariableToken(this.variableToken)) {
Habbo habbo = room.getHabbo(roomUnit);
return habbo != null && room.getUserVariableManager().hasVariable(habbo.getHabboInfo().getId(), this.variableItemId);
}
return isInternalVariableToken(this.variableToken) && this.hasUserInternalVariable(room, roomUnit, getInternalVariableKey(this.variableToken));
}
private boolean hasFurniVariable(Room room, HabboItem item) {
if (room == null || item == null) return false;
if (isCustomVariableToken(this.variableToken)) {
return room.getFurniVariableManager().hasVariable(item.getId(), this.variableItemId);
}
return isInternalVariableToken(this.variableToken) && this.hasFurniInternalVariable(item, getInternalVariableKey(this.variableToken));
}
private Integer resolveReferenceValue(WiredContext ctx, Room room, int destinationEntityId, int destinationTargetType, int destinationIndex) {
if (!this.selectByValue || this.referenceMode != REFERENCE_VARIABLE) return this.referenceConstantValue;
ReferenceSnapshot snapshot = this.resolveReferences(ctx, room);
if (snapshot == null || snapshot.isEmpty()) return null;
if (snapshot.targetType == destinationTargetType && snapshot.values.containsKey(destinationEntityId)) return snapshot.values.get(destinationEntityId);
if (destinationIndex >= 0 && destinationIndex < snapshot.values.size()) return new ArrayList<>(snapshot.values.values()).get(destinationIndex);
return new ArrayList<>(snapshot.values.values()).get(0);
}
private ReferenceSnapshot resolveReferences(WiredContext ctx, Room room) {
return switch (this.referenceTargetType) {
case TARGET_FURNI -> this.furniReferences(ctx, room);
case TARGET_CONTEXT -> this.contextReferences(ctx, room);
case TARGET_ROOM -> this.roomReferences(room);
default -> this.userReferences(ctx, room);
};
}
private ReferenceSnapshot userReferences(WiredContext ctx, Room room) {
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_USER);
if (isInternalVariableToken(this.referenceVariableToken)) {
String key = getInternalVariableKey(this.referenceVariableToken);
if (!canUseUserInternalReference(key)) return null;
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.referenceUserSource)) {
Integer value = this.readUserInternalValue(room, roomUnit, key);
if (value != null && roomUnit != null) snapshot.add(roomUnit.getId(), value);
}
return snapshot.isEmpty() ? null : snapshot;
}
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.referenceVariableItemId);
if (definition == null || !definition.hasValue()) return null;
for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.referenceUserSource)) {
if (roomUnit == null) continue;
Habbo habbo = room.getHabbo(roomUnit);
if (habbo != null) snapshot.add(roomUnit.getId(), room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), this.referenceVariableItemId));
}
return snapshot.isEmpty() ? null : snapshot;
}
private ReferenceSnapshot furniReferences(WiredContext ctx, Room room) {
int source = (this.referenceFurniSource == SOURCE_SECONDARY_SELECTED) ? WiredSourceUtil.SOURCE_SELECTED : this.referenceFurniSource;
if (source == WiredSourceUtil.SOURCE_SELECTED) this.refreshReferenceItems();
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_FURNI);
if (isInternalVariableToken(this.referenceVariableToken)) {
String key = getInternalVariableKey(this.referenceVariableToken);
if (!canUseFurniInternalReference(key)) return null;
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, source, this.referenceSelectedItems)) {
Integer value = this.readFurniInternalValue(room, item, key);
if (value != null && item != null) snapshot.add(item.getId(), value);
}
return snapshot.isEmpty() ? null : snapshot;
}
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.referenceVariableItemId);
if (definition == null || !definition.hasValue()) return null;
for (HabboItem item : WiredSourceUtil.resolveItems(ctx, source, this.referenceSelectedItems)) {
if (item != null) snapshot.add(item.getId(), room.getFurniVariableManager().getCurrentValue(item.getId(), this.referenceVariableItemId));
}
return snapshot.isEmpty() ? null : snapshot;
}
private ReferenceSnapshot roomReferences(Room room) {
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_ROOM);
if (isInternalVariableToken(this.referenceVariableToken)) {
String key = getInternalVariableKey(this.referenceVariableToken);
if (!canUseRoomInternalReference(key)) return null;
Integer value = this.readRoomInternalValue(room, key);
if (value == null) return null;
snapshot.add(room.getId(), value);
return snapshot;
}
WiredVariableDefinitionInfo definition = room.getRoomVariableManager().getDefinitionInfo(this.referenceVariableItemId);
if (definition == null || !definition.hasValue()) return null;
snapshot.add(room.getId(), room.getRoomVariableManager().getCurrentValue(this.referenceVariableItemId));
return snapshot;
}
private ReferenceSnapshot contextReferences(WiredContext ctx, Room room) {
ReferenceSnapshot snapshot = new ReferenceSnapshot(TARGET_CONTEXT);
if (isInternalVariableToken(this.referenceVariableToken)) {
String key = getInternalVariableKey(this.referenceVariableToken);
if (!canUseContextInternalReference(key)) return null;
Integer value = WiredInternalVariableSupport.readContextValue(ctx, key);
if (value == null) return null;
snapshot.add(this.referenceVariableItemId > 0 ? this.referenceVariableItemId : (room != null ? room.getId() : 0), value);
return snapshot;
}
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, this.referenceVariableItemId);
if (definition == null || !definition.hasValue() || !WiredContextVariableSupport.hasVariable(ctx, this.referenceVariableItemId)) return null;
Integer value = WiredContextVariableSupport.getCurrentValue(ctx, this.referenceVariableItemId);
if (value == null) return null;
snapshot.add(this.referenceVariableItemId, value);
return snapshot;
}
private boolean isValidMainVariable(Room room, String token, boolean requireValue) {
if (token == null || token.isEmpty()) return false;
int targetType = this.getVariableTargetType();
if (isInternalVariableToken(token)) {
String key = getInternalVariableKey(token);
return targetType == TARGET_FURNI
? (requireValue ? canUseFurniInternalReference(key) : this.hasFurniInternalKey(key))
: (requireValue ? canUseUserInternalReference(key) : this.hasUserInternalKey(key));
}
if (targetType == TARGET_FURNI) {
WiredVariableDefinitionInfo definition = (room != null) ? room.getFurniVariableManager().getDefinitionInfo(getCustomItemId(token)) : null;
return definition != null && (!requireValue || definition.hasValue());
}
WiredVariableDefinitionInfo definition = (room != null) ? room.getUserVariableManager().getDefinitionInfo(getCustomItemId(token)) : null;
return definition != null && (!requireValue || definition.hasValue());
}
private boolean isValidReference(Room room, int targetType, String token) {
if (token == null || token.isEmpty()) return false;
if (isInternalVariableToken(token)) {
String key = getInternalVariableKey(token);
return switch (targetType) {
case TARGET_FURNI -> canUseFurniInternalReference(key);
case TARGET_CONTEXT -> canUseContextInternalReference(key);
case TARGET_ROOM -> canUseRoomInternalReference(key);
default -> canUseUserInternalReference(key);
};
}
return switch (targetType) {
case TARGET_FURNI -> {
WiredVariableDefinitionInfo definition = (room != null) ? room.getFurniVariableManager().getDefinitionInfo(getCustomItemId(token)) : null;
yield definition != null && definition.hasValue();
}
case TARGET_CONTEXT -> this.isValidContextCustomReference(room, getCustomItemId(token));
case TARGET_ROOM -> {
WiredVariableDefinitionInfo definition = (room != null) ? room.getRoomVariableManager().getDefinitionInfo(getCustomItemId(token)) : null;
yield definition != null && definition.hasValue();
}
default -> {
WiredVariableDefinitionInfo definition = (room != null) ? room.getUserVariableManager().getDefinitionInfo(getCustomItemId(token)) : null;
yield definition != null && definition.hasValue();
}
};
}
private boolean isValidContextCustomReference(Room room, int variableItemId) {
WiredVariableDefinitionInfo definition = WiredContextVariableSupport.getDefinitionInfo(room, variableItemId);
return definition != null && definition.hasValue();
}
private boolean matchesComparison(Integer currentValue, Integer referenceValue) {
if (currentValue == null || referenceValue == null) return false;
return switch (this.comparison) {
case COMPARISON_GREATER_THAN -> currentValue > referenceValue;
case COMPARISON_GREATER_THAN_OR_EQUAL -> currentValue >= referenceValue;
case COMPARISON_LESS_THAN_OR_EQUAL -> currentValue <= referenceValue;
case COMPARISON_LESS_THAN -> currentValue < referenceValue;
case COMPARISON_NOT_EQUAL -> !currentValue.equals(referenceValue);
default -> currentValue.equals(referenceValue);
};
}
private Integer readUserValue(Room room, RoomUnit roomUnit) {
if (room == null || roomUnit == null) return null;
if (isInternalVariableToken(this.variableToken)) {
String key = getInternalVariableKey(this.variableToken);
return canUseUserInternalReference(key) ? this.readUserInternalValue(room, roomUnit, key) : null;
}
WiredVariableDefinitionInfo definition = room.getUserVariableManager().getDefinitionInfo(this.variableItemId);
if (definition == null || !definition.hasValue()) return null;
Habbo habbo = room.getHabbo(roomUnit);
return (habbo != null) ? room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), this.variableItemId) : null;
}
private Integer readFurniValue(Room room, HabboItem item) {
if (room == null || item == null) return null;
if (isInternalVariableToken(this.variableToken)) {
String key = getInternalVariableKey(this.variableToken);
return canUseFurniInternalReference(key) ? this.readFurniInternalValue(room, item, key) : null;
}
WiredVariableDefinitionInfo definition = room.getFurniVariableManager().getDefinitionInfo(this.variableItemId);
return (definition != null && definition.hasValue()) ? room.getFurniVariableManager().getCurrentValue(item.getId(), this.variableItemId) : null;
}
private boolean hasUserInternalVariable(Room room, RoomUnit roomUnit, String key) {
return WiredInternalVariableSupport.hasUserValue(room, roomUnit, key);
}
private boolean hasFurniInternalVariable(HabboItem item, String key) {
return WiredInternalVariableSupport.hasFurniValue(item, key);
}
private boolean hasUserInternalKey(String key) {
return WiredInternalVariableSupport.canUseUserReference(key);
}
private boolean hasFurniInternalKey(String key) {
return WiredInternalVariableSupport.canUseFurniReference(key) || "@wallitem_offset".equals(WiredInternalVariableSupport.normalizeKey(key));
}
private boolean hasRoomEntryMethod(Habbo habbo) {
if (habbo == null) return false;
String roomEntryMethod = habbo.getHabboInfo().getRoomEntryMethod();
return roomEntryMethod != null && !roomEntryMethod.trim().isEmpty() && !"unknown".equalsIgnoreCase(roomEntryMethod);
}
private Integer readUserInternalValue(Room room, RoomUnit roomUnit, String key) {
return WiredInternalVariableSupport.readUserValue(room, roomUnit, key);
}
private Integer readFurniInternalValue(Room room, HabboItem item, String key) {
return WiredInternalVariableSupport.readFurniValue(room, item, key);
}
private Integer readRoomInternalValue(Room room, String key) {
return WiredInternalVariableSupport.readRoomValue(room, key);
}
private Integer getUserTeamScore(Room room, Habbo habbo) {
if (room == null || habbo == null || habbo.getHabboInfo().getGamePlayer() == null) return null;
Game game = this.resolveTeamGame(room, habbo);
GamePlayer gamePlayer = habbo.getHabboInfo().getGamePlayer();
if (game == null || gamePlayer.getTeamColor() == null) return gamePlayer.getScore();
GameTeam team = game.getTeam(gamePlayer.getTeamColor());
return (team != null) ? team.getTotalScore() : gamePlayer.getScore();
}
private Integer getTeamColorId(int effectId) {
TeamEffectData data = this.getTeamEffectData(effectId);
return data == null ? null : data.colorId;
}
private Integer getTeamTypeId(int effectId) {
TeamEffectData data = this.getTeamEffectData(effectId);
return data == null ? null : data.typeId;
}
private int getTeamMetric(Room room, GameTeamColors color, boolean score) {
Game game = this.resolveTeamGame(room, null);
if (game == null || color == null) return 0;
GameTeam team = game.getTeam(color);
if (team == null) return 0;
return score ? team.getTotalScore() : team.getMembers().size();
}
private Game resolveTeamGame(Room room, Habbo habbo) {
if (room == null) return null;
if (habbo != null && habbo.getHabboInfo() != null && habbo.getHabboInfo().getCurrentGame() != null) {
Game game = room.getGame(habbo.getHabboInfo().getCurrentGame());
if (game != null) return game;
}
Game wiredGame = room.getGame(WiredGame.class);
if (wiredGame != null) return wiredGame;
Game freezeGame = room.getGame(FreezeGame.class);
if (freezeGame != null) return freezeGame;
return room.getGame(BattleBanzaiGame.class);
}
private TeamEffectData getTeamEffectData(int effectValue) {
if (effectValue <= 0) return null;
if (effectValue >= 223 && effectValue <= 226) return new TeamEffectData(effectValue - 222, 0);
if (effectValue >= 33 && effectValue <= 36) return new TeamEffectData(effectValue - 32, 1);
if (effectValue >= 40 && effectValue <= 43) return new TeamEffectData(effectValue - 39, 2);
return null;
}
private void refreshReferenceItems() {
THashSet<HabboItem> staleItems = new THashSet<>();
Room room = this.getRoom();
if (room == null) {
staleItems.addAll(this.referenceSelectedItems);
} else {
for (HabboItem item : this.referenceSelectedItems) {
if (item == null || item.getRoomId() != room.getId() || room.getHabboItem(item.getId()) == null) {
staleItems.add(item);
}
}
}
this.referenceSelectedItems.removeAll(staleItems);
}
private String serializeStringData() {
return (this.variableToken == null ? "" : this.variableToken) + DELIM + (this.referenceVariableToken == null ? "" : this.referenceVariableToken);
}
private void setVariableToken(String token) {
this.variableToken = normalizeVariableToken(token);
this.variableItemId = getCustomItemId(this.variableToken);
}
private void setReferenceVariableToken(String token) {
this.referenceVariableToken = normalizeVariableToken(token);
this.referenceVariableItemId = getCustomItemId(this.referenceVariableToken);
}
private List<Integer> toIds(THashSet<HabboItem> items) {
List<Integer> ids = new ArrayList<>();
for (HabboItem item : items) {
if (item != null) ids.add(item.getId());
}
return ids;
}
private static int normalizeReferenceMode(int value) {
return (value == REFERENCE_VARIABLE) ? REFERENCE_VARIABLE : REFERENCE_CONSTANT;
}
private static int normalizeReferenceTargetType(int value) {
return switch (value) {
case TARGET_FURNI, TARGET_CONTEXT, TARGET_ROOM -> value;
default -> TARGET_USER;
};
}
private static int normalizeReferenceFurniSource(int value) {
return switch (value) {
case SOURCE_SECONDARY_SELECTED, WiredSourceUtil.SOURCE_SELECTOR, WiredSourceUtil.SOURCE_SIGNAL -> value;
default -> WiredSourceUtil.SOURCE_TRIGGER;
};
}
private static int normalizeComparison(int value) {
return switch (value) {
case COMPARISON_GREATER_THAN, COMPARISON_GREATER_THAN_OR_EQUAL, COMPARISON_LESS_THAN_OR_EQUAL, COMPARISON_LESS_THAN, COMPARISON_NOT_EQUAL -> value;
default -> COMPARISON_EQUAL;
};
}
private static int normalizeUserSource(int value) {
return WiredSourceUtil.isDefaultUserSource(value) ? value : WiredSourceUtil.SOURCE_TRIGGER;
}
protected static boolean isCustomVariableToken(String token) {
return token != null && token.startsWith(CUSTOM_TOKEN_PREFIX);
}
protected static boolean isInternalVariableToken(String token) {
return token != null && token.startsWith(INTERNAL_TOKEN_PREFIX);
}
private static int getCustomItemId(String token) {
if (!isCustomVariableToken(token)) return 0;
try {
return Integer.parseInt(token.substring(CUSTOM_TOKEN_PREFIX.length()));
} catch (NumberFormatException e) {
return 0;
}
}
private static String getInternalVariableKey(String token) {
return isInternalVariableToken(token) ? WiredInternalVariableSupport.normalizeKey(token.substring(INTERNAL_TOKEN_PREFIX.length())) : "";
}
protected static String normalizeVariableToken(String token) {
if (token == null) return "";
String normalized = token.trim();
if (normalized.isEmpty()) return "";
if (isCustomVariableToken(normalized)) return normalized;
if (isInternalVariableToken(normalized)) return INTERNAL_TOKEN_PREFIX + WiredInternalVariableSupport.normalizeKey(normalized.substring(INTERNAL_TOKEN_PREFIX.length()));
try {
int parsed = Integer.parseInt(normalized);
return (parsed > 0) ? (CUSTOM_TOKEN_PREFIX + parsed) : "";
} catch (NumberFormatException e) {
return "";
}
}
private static boolean canUseUserInternalReference(String key) {
return WiredInternalVariableSupport.canUseUserReference(key);
}
private static boolean canUseFurniInternalReference(String key) {
return WiredInternalVariableSupport.canUseFurniReference(key);
}
private static boolean canUseRoomInternalReference(String key) {
return WiredInternalVariableSupport.canUseRoomReference(key);
}
private static boolean canUseContextInternalReference(String key) {
return WiredInternalVariableSupport.canUseContextReference(key);
}
private static int param(int[] params, int index, int fallback) {
return (params != null && params.length > index) ? params[index] : fallback;
}
private static String[] parseStringData(String value) {
return (value == null || value.isEmpty()) ? new String[0] : value.split("\\t", -1);
}
private static int parseInteger(String value) {
try {
return (value == null || value.trim().isEmpty()) ? 0 : Integer.parseInt(value.trim());
} catch (NumberFormatException e) {
return 0;
}
}
protected static class JsonData {
boolean selectByValue;
int comparison;
int referenceMode;
int referenceConstantValue;
int referenceTargetType;
int referenceUserSource;
int referenceFurniSource;
boolean filterExisting;
boolean invert;
String variableToken;
int variableItemId;
String referenceVariableToken;
int referenceVariableItemId;
List<Integer> selectedItemIds;
int delay;
JsonData(boolean selectByValue, int comparison, int referenceMode, int referenceConstantValue, int referenceTargetType, int referenceUserSource, int referenceFurniSource, boolean filterExisting, boolean invert, String variableToken, int variableItemId, String referenceVariableToken, int referenceVariableItemId, List<Integer> selectedItemIds, int delay) {
this.selectByValue = selectByValue;
this.comparison = comparison;
this.referenceMode = referenceMode;
this.referenceConstantValue = referenceConstantValue;
this.referenceTargetType = referenceTargetType;
this.referenceUserSource = referenceUserSource;
this.referenceFurniSource = referenceFurniSource;
this.filterExisting = filterExisting;
this.invert = invert;
this.variableToken = variableToken;
this.variableItemId = variableItemId;
this.referenceVariableToken = referenceVariableToken;
this.referenceVariableItemId = referenceVariableItemId;
this.selectedItemIds = selectedItemIds;
this.delay = delay;
}
}
private static class ReferenceSnapshot {
final int targetType;
final LinkedHashMap<Integer, Integer> values = new LinkedHashMap<>();
ReferenceSnapshot(int targetType) {
this.targetType = targetType;
}
void add(int entityId, int value) {
this.values.put(entityId, value);
}
boolean isEmpty() {
return this.values.isEmpty();
}
}
private static class TeamEffectData {
final int colorId;
final int typeId;
TeamEffectData(int colorId, int typeId) {
this.colorId = colorId;
this.typeId = typeId;
}
}
}
@@ -141,6 +141,18 @@ public class WiredTriggerHabboSaysKeyword extends InteractionWiredTrigger {
return this.hideMessage;
}
public boolean isOwnerOnly() {
return this.ownerOnly;
}
public String getKey() {
return this.key;
}
public int getMatchMode() {
return this.matchMode;
}
private boolean matchesText(String text) {
String normalizedText = text.toLowerCase().trim();
String normalizedKey = this.key.toLowerCase().trim();
@@ -0,0 +1,302 @@
package com.eu.habbo.habbohotel.items.interactions.wired.triggers;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
import com.eu.habbo.habbohotel.users.HabboItem;
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 java.sql.ResultSet;
import java.sql.SQLException;
public class WiredTriggerVariableChanged extends InteractionWiredTrigger {
public static final WiredTriggerType type = WiredTriggerType.VARIABLE_CHANGED;
public static final int TARGET_USER = 0;
public static final int TARGET_FURNI = 1;
public static final int TARGET_ROOM = 3;
private static final String CUSTOM_TOKEN_PREFIX = "custom:";
private String variableToken = "";
private int variableItemId = 0;
private int targetType = TARGET_USER;
private boolean createdEnabled = true;
private boolean valueChangedEnabled = true;
private boolean increasedEnabled = true;
private boolean decreasedEnabled = true;
private boolean unchangedEnabled = true;
private boolean deletedEnabled = true;
public WiredTriggerVariableChanged(ResultSet set, Item baseItem) throws SQLException {
super(set, baseItem);
}
public WiredTriggerVariableChanged(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) {
super(id, userId, item, extradata, limitedStack, limitedSells);
}
@Override
public boolean matches(HabboItem triggerItem, WiredEvent event) {
if (event == null || event.getType() != WiredEvent.Type.VARIABLE_CHANGED) {
return false;
}
if (event.getVariableTargetType() != this.targetType || event.getVariableDefinitionItemId() != this.variableItemId) {
return false;
}
if (this.createdEnabled && event.isVariableCreated()) {
return true;
}
if (this.deletedEnabled && event.isVariableDeleted()) {
return true;
}
if (!this.valueChangedEnabled) {
return false;
}
return switch (event.getVariableChangeKind()) {
case INCREASED -> this.increasedEnabled;
case DECREASED -> this.decreasedEnabled;
case UNCHANGED -> this.unchangedEnabled;
default -> false;
};
}
@Deprecated
@Override
public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) {
return false;
}
@Override
public WiredTriggerType getType() {
return type;
}
@Override
public void serializeWiredData(ServerMessage message, Room room) {
message.appendBoolean(false);
message.appendInt(0);
message.appendInt(0);
message.appendInt(this.getBaseItem().getSpriteId());
message.appendInt(this.getId());
message.appendString(this.variableToken == null ? "" : this.variableToken);
message.appendInt(7);
message.appendInt(this.targetType);
message.appendInt(this.createdEnabled ? 1 : 0);
message.appendInt(this.valueChangedEnabled ? 1 : 0);
message.appendInt(this.increasedEnabled ? 1 : 0);
message.appendInt(this.decreasedEnabled ? 1 : 0);
message.appendInt(this.unchangedEnabled ? 1 : 0);
message.appendInt(this.deletedEnabled ? 1 : 0);
message.appendInt(0);
message.appendInt(this.getType().code);
message.appendInt(0);
message.appendInt(0);
}
@Override
public boolean saveData(WiredSettings settings) {
return this.saveData(settings, null);
}
@Override
public boolean saveData(WiredSettings settings, GameClient gameClient) {
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId());
int[] params = settings.getIntParams();
this.targetType = normalizeTargetType((params.length > 0) ? params[0] : TARGET_USER);
this.createdEnabled = (params.length <= 1) || (params[1] == 1);
this.valueChangedEnabled = (params.length <= 2) || (params[2] == 1);
this.increasedEnabled = (params.length <= 3) || (params[3] == 1);
this.decreasedEnabled = (params.length <= 4) || (params[4] == 1);
this.unchangedEnabled = (params.length <= 5) || (params[5] == 1);
this.deletedEnabled = (params.length <= 6) || (params[6] == 1);
this.setVariableToken(normalizeVariableToken(settings.getStringParam()));
this.normalizeOptions();
if (this.variableItemId <= 0) {
throw new WiredTriggerSaveException("wiredfurni.params.variables.validation.missing_variable");
}
if (!this.hasAnyEnabledOption()) {
return false;
}
if (room == null || !this.isValidDefinition(room)) {
throw new WiredTriggerSaveException("wiredfurni.params.variables.validation.invalid_variable");
}
return true;
}
@Override
public String getWiredData() {
return WiredManager.getGson().toJson(new JsonData(
this.variableToken,
this.variableItemId,
this.targetType,
this.createdEnabled,
this.valueChangedEnabled,
this.increasedEnabled,
this.decreasedEnabled,
this.unchangedEnabled,
this.deletedEnabled
));
}
@Override
public void loadWiredData(ResultSet set, Room room) throws SQLException {
this.onPickUp();
String wiredData = set.getString("wired_data");
if (wiredData == null || wiredData.isEmpty()) {
return;
}
if (!wiredData.startsWith("{")) {
this.setVariableToken(normalizeVariableToken(wiredData));
return;
}
JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class);
if (data == null) {
return;
}
this.targetType = normalizeTargetType(data.targetType);
this.createdEnabled = data.createdEnabled;
this.valueChangedEnabled = data.valueChangedEnabled;
this.increasedEnabled = data.increasedEnabled;
this.decreasedEnabled = data.decreasedEnabled;
this.unchangedEnabled = data.unchangedEnabled;
this.deletedEnabled = data.deletedEnabled;
this.setVariableToken(normalizeVariableToken((data.variableToken != null) ? data.variableToken : ((data.variableItemId > 0) ? String.valueOf(data.variableItemId) : "")));
this.normalizeOptions();
}
@Override
public void onPickUp() {
this.variableToken = "";
this.variableItemId = 0;
this.targetType = TARGET_USER;
this.createdEnabled = true;
this.valueChangedEnabled = true;
this.increasedEnabled = true;
this.decreasedEnabled = true;
this.unchangedEnabled = true;
this.deletedEnabled = true;
}
private void setVariableToken(String token) {
this.variableToken = normalizeVariableToken(token);
this.variableItemId = getCustomItemId(this.variableToken);
}
private void normalizeOptions() {
if (!this.valueChangedEnabled) {
this.increasedEnabled = false;
this.decreasedEnabled = false;
this.unchangedEnabled = false;
}
if (this.targetType == TARGET_ROOM) {
this.createdEnabled = false;
this.deletedEnabled = false;
}
}
private boolean hasAnyEnabledOption() {
return this.createdEnabled
|| this.deletedEnabled
|| (this.valueChangedEnabled && (this.increasedEnabled || this.decreasedEnabled || this.unchangedEnabled));
}
private boolean isValidDefinition(Room room) {
WiredVariableDefinitionInfo definitionInfo = switch (this.targetType) {
case TARGET_FURNI -> room.getFurniVariableManager().getDefinitionInfo(this.variableItemId);
case TARGET_ROOM -> room.getRoomVariableManager().getDefinitionInfo(this.variableItemId);
default -> room.getUserVariableManager().getDefinitionInfo(this.variableItemId);
};
return definitionInfo != null;
}
private static int normalizeTargetType(int value) {
return switch (value) {
case TARGET_FURNI, TARGET_ROOM -> value;
default -> TARGET_USER;
};
}
private static String normalizeVariableToken(String token) {
if (token == null) {
return "";
}
String normalized = token.trim();
if (normalized.isEmpty()) {
return "";
}
if (normalized.startsWith(CUSTOM_TOKEN_PREFIX)) {
return normalized;
}
try {
int itemId = Integer.parseInt(normalized);
return (itemId > 0) ? (CUSTOM_TOKEN_PREFIX + itemId) : "";
} catch (NumberFormatException ignored) {
return "";
}
}
private static int getCustomItemId(String token) {
if (token == null || !token.startsWith(CUSTOM_TOKEN_PREFIX)) {
return 0;
}
try {
return Integer.parseInt(token.substring(CUSTOM_TOKEN_PREFIX.length()));
} catch (NumberFormatException ignored) {
return 0;
}
}
static class JsonData {
String variableToken;
int variableItemId;
int targetType;
boolean createdEnabled;
boolean valueChangedEnabled;
boolean increasedEnabled;
boolean decreasedEnabled;
boolean unchangedEnabled;
boolean deletedEnabled;
JsonData(String variableToken, int variableItemId, int targetType, boolean createdEnabled, boolean valueChangedEnabled, boolean increasedEnabled, boolean decreasedEnabled, boolean unchangedEnabled, boolean deletedEnabled) {
this.variableToken = variableToken;
this.variableItemId = variableItemId;
this.targetType = targetType;
this.createdEnabled = createdEnabled;
this.valueChangedEnabled = valueChangedEnabled;
this.increasedEnabled = increasedEnabled;
this.decreasedEnabled = decreasedEnabled;
this.unchangedEnabled = unchangedEnabled;
this.deletedEnabled = deletedEnabled;
}
}
}
@@ -54,11 +54,23 @@ public class ModToolManager {
if (userId <= 0)
return;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT users.*, users_settings.*, permissions.rank_name, permissions.acc_hide_mail AS hide_mail, permissions.id AS rank_id FROM users INNER JOIN users_settings ON users.id = users_settings.user_id INNER JOIN permissions ON permissions.id = users.rank WHERE users.id = ? LIMIT 1")) {
String query = Emulator.getGameEnvironment().getPermissionsManager().isNormalizedSchemaEnabled()
? "SELECT users.*, users_settings.*, permission_ranks.rank_name, permission_ranks.id AS rank_id " +
"FROM users " +
"INNER JOIN users_settings ON users.id = users_settings.user_id " +
"INNER JOIN permission_ranks ON permission_ranks.id = users.rank " +
"WHERE users.id = ? LIMIT 1"
: "SELECT users.*, users_settings.*, permissions.rank_name, permissions.acc_hide_mail AS hide_mail, permissions.id AS rank_id FROM users INNER JOIN users_settings ON users.id = users_settings.user_id INNER JOIN permissions ON permissions.id = users.rank WHERE users.id = ? LIMIT 1";
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement(query)) {
statement.setInt(1, userId);
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
client.sendResponse(new ModToolUserInfoComposer(set));
boolean hideMail = Emulator.getGameEnvironment().getPermissionsManager().isNormalizedSchemaEnabled()
? Emulator.getGameEnvironment().getPermissionsManager().getRank(set.getInt("rank_id")).hasPermission("acc_hide_mail", false)
: set.getBoolean("hide_mail");
client.sendResponse(new ModToolUserInfoComposer(set, hideMail));
}
}
} catch (SQLException e) {
@@ -11,6 +11,7 @@ import org.slf4j.LoggerFactory;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -20,6 +21,7 @@ public class PermissionsManager {
private final TIntObjectHashMap<Rank> ranks;
private final TIntIntHashMap enables;
private final THashMap<String, List<Rank>> badges;
private volatile boolean normalizedSchemaEnabled;
public PermissionsManager() {
long millis = System.currentTimeMillis();
@@ -40,7 +42,30 @@ public class PermissionsManager {
private void loadPermissions() {
this.badges.clear();
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); Statement statement = connection.createStatement(); ResultSet set = statement.executeQuery("SELECT * FROM permissions ORDER BY id ASC")) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) {
if (this.hasNormalizedPermissionsSchema(connection)) {
try {
if (this.loadPermissionsNormalized(connection)) {
this.normalizedSchemaEnabled = true;
LOGGER.info("Permissions Manager -> Using normalized permissions schema.");
return;
}
} catch (SQLException e) {
LOGGER.warn("Permissions Manager -> Failed to load normalized permissions schema, falling back to legacy permissions table.", e);
}
}
this.normalizedSchemaEnabled = false;
this.badges.clear();
LOGGER.info("Permissions Manager -> Using legacy permissions schema.");
this.loadPermissionsLegacy(connection);
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
}
}
private void loadPermissionsLegacy(Connection connection) throws SQLException {
try (Statement statement = connection.createStatement(); ResultSet set = statement.executeQuery("SELECT * FROM permissions ORDER BY id ASC")) {
while (set.next()) {
Rank rank = null;
if (!this.ranks.containsKey(set.getInt("id"))) {
@@ -51,16 +76,135 @@ public class PermissionsManager {
rank.load(set);
}
if (rank != null && !rank.getBadge().isEmpty()) {
if (!this.badges.containsKey(rank.getBadge())) {
this.badges.put(rank.getBadge(), new ArrayList<Rank>());
}
this.addBadgeMapping(rank);
}
}
}
this.badges.get(rank.getBadge()).add(rank);
private boolean loadPermissionsNormalized(Connection connection) throws SQLException {
boolean hasRanks = false;
List<Rank> loadedRanks = new ArrayList<>();
try (Statement statement = connection.createStatement(); ResultSet set = statement.executeQuery("SELECT * FROM permission_ranks ORDER BY id ASC")) {
while (set.next()) {
hasRanks = true;
Rank rank = this.ranks.get(set.getInt("id"));
if (rank == null) {
rank = new Rank(set.getInt("id"));
this.ranks.put(set.getInt("id"), rank);
}
rank.loadNormalizedMetadata(set);
this.addBadgeMapping(rank);
loadedRanks.add(rank);
}
}
if (!hasRanks) {
return false;
}
this.ensureNormalizedRankColumns(connection, loadedRanks);
boolean hasDefinitions = false;
try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM permission_definitions ORDER BY permission_key ASC");
ResultSet set = statement.executeQuery()) {
ResultSetMetaData meta = set.getMetaData();
Set<String> availableColumns = new HashSet<>();
for (int i = 1; i <= meta.getColumnCount(); i++) {
availableColumns.add(meta.getColumnName(i).toLowerCase());
}
for (Rank rank : loadedRanks) {
if (!availableColumns.contains(("rank_" + rank.getId()).toLowerCase())) {
return false;
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
while (set.next()) {
hasDefinitions = true;
String permissionKey = set.getString("permission_key");
for (Rank rank : loadedRanks) {
String rankColumn = "rank_" + rank.getId();
if (!availableColumns.contains(rankColumn.toLowerCase())) {
continue;
}
rank.setPermission(permissionKey, PermissionSetting.fromString(Integer.toString(set.getInt(rankColumn))));
}
}
}
return hasDefinitions;
}
private void ensureNormalizedRankColumns(Connection connection, List<Rank> loadedRanks) throws SQLException {
Set<String> availableColumns = new HashSet<>();
try (PreparedStatement statement = connection.prepareStatement("SELECT column_name FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'permission_definitions'");
ResultSet set = statement.executeQuery()) {
while (set.next()) {
availableColumns.add(set.getString("column_name").toLowerCase());
}
}
for (Rank rank : loadedRanks) {
String rankColumn = "rank_" + rank.getId();
if (availableColumns.contains(rankColumn.toLowerCase())) {
continue;
}
try (Statement statement = connection.createStatement()) {
statement.execute("ALTER TABLE permission_definitions ADD COLUMN `" + rankColumn + "` tinyint(3) unsigned NOT NULL DEFAULT 0");
}
availableColumns.add(rankColumn.toLowerCase());
LOGGER.info("Permissions Manager -> Added missing normalized permission column {}.", rankColumn);
}
}
private boolean hasNormalizedPermissionsSchema(Connection connection) throws SQLException {
if (!this.tableExists(connection, "permission_ranks") || !this.tableExists(connection, "permission_definitions")) {
return false;
}
if (!this.tableHasRows(connection, "permission_ranks")) {
return false;
}
return this.tableHasRows(connection, "permission_definitions");
}
private boolean tableExists(Connection connection, String tableName) throws SQLException {
try (PreparedStatement statement = connection.prepareStatement("SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ?")) {
statement.setString(1, tableName);
try (ResultSet set = statement.executeQuery()) {
return set.next() && set.getInt(1) > 0;
}
}
}
private boolean tableHasRows(Connection connection, String tableName) throws SQLException {
try (Statement statement = connection.createStatement(); ResultSet set = statement.executeQuery("SELECT COUNT(*) FROM " + tableName)) {
return set.next() && set.getInt(1) > 0;
}
}
private void addBadgeMapping(Rank rank) {
if (rank != null && !rank.getBadge().isEmpty()) {
if (!this.badges.containsKey(rank.getBadge())) {
this.badges.put(rank.getBadge(), new ArrayList<Rank>());
}
this.badges.get(rank.getBadge()).add(rank);
}
}
@@ -139,4 +283,8 @@ public class PermissionsManager {
public List<Rank> getAllRanks() {
return new ArrayList<>(this.ranks.valueCollection());
}
public boolean isNormalizedSchemaEnabled() {
return this.normalizedSchemaEnabled;
}
}
@@ -35,32 +35,29 @@ public class Rank {
private int gotwTimerAmount;
public Rank(ResultSet set) throws SQLException {
this(set.getInt("id"));
this.load(set);
}
public Rank(int id) {
this.permissions = new THashMap<>();
this.variables = new THashMap<>();
this.id = set.getInt("id");
this.level = set.getInt("level");
this.id = id;
this.level = 1;
this.diamondsTimerAmount = 1;
this.creditsTimerAmount = 1;
this.pixelsTimerAmount = 1;
this.gotwTimerAmount = 1;
this.load(set);
}
public void load(ResultSet set) throws SQLException {
this.permissions.clear();
this.variables.clear();
this.loadMetadata(set);
ResultSetMetaData meta = set.getMetaData();
this.name = set.getString("rank_name");
this.badge = set.getString("badge");
this.roomEffect = set.getInt("room_effect");
this.logCommands = set.getString("log_commands").equals("1");
this.prefix = set.getString("prefix");
this.prefixColor = set.getString("prefix_color");
this.level = set.getInt("level");
this.diamondsTimerAmount = set.getInt("auto_points_amount");
this.creditsTimerAmount = set.getInt("auto_credits_amount");
this.pixelsTimerAmount = set.getInt("auto_pixels_amount");
this.gotwTimerAmount = set.getInt("auto_gotw_amount");
this.hasPrefix = !this.prefix.isEmpty();
for (int i = 1; i < meta.getColumnCount() + 1; i++) {
String columnName = meta.getColumnName(i);
if (columnName.startsWith("cmd_") || columnName.startsWith("acc_")) {
@@ -71,6 +68,51 @@ public class Rank {
}
}
public void loadNormalizedMetadata(ResultSet set) throws SQLException {
this.permissions.clear();
this.variables.clear();
this.loadMetadata(set);
this.storeMetadataVariables();
}
public void setPermission(String key, PermissionSetting setting) {
this.permissions.put(key, new Permission(key, setting));
}
private void loadMetadata(ResultSet set) throws SQLException {
this.name = this.safeString(set.getString("rank_name"));
this.badge = this.safeString(set.getString("badge"));
this.roomEffect = set.getInt("room_effect");
this.logCommands = "1".equals(this.safeString(set.getString("log_commands")));
this.prefix = this.safeString(set.getString("prefix"));
this.prefixColor = this.safeString(set.getString("prefix_color"));
this.level = set.getInt("level");
this.diamondsTimerAmount = set.getInt("auto_points_amount");
this.creditsTimerAmount = set.getInt("auto_credits_amount");
this.pixelsTimerAmount = set.getInt("auto_pixels_amount");
this.gotwTimerAmount = set.getInt("auto_gotw_amount");
this.hasPrefix = !this.prefix.isEmpty();
}
private void storeMetadataVariables() {
this.variables.put("id", Integer.toString(this.id));
this.variables.put("rank_name", this.name);
this.variables.put("badge", this.badge);
this.variables.put("room_effect", Integer.toString(this.roomEffect));
this.variables.put("log_commands", this.logCommands ? "1" : "0");
this.variables.put("prefix", this.prefix);
this.variables.put("prefix_color", this.prefixColor);
this.variables.put("level", Integer.toString(this.level));
this.variables.put("auto_points_amount", Integer.toString(this.diamondsTimerAmount));
this.variables.put("auto_credits_amount", Integer.toString(this.creditsTimerAmount));
this.variables.put("auto_pixels_amount", Integer.toString(this.pixelsTimerAmount));
this.variables.put("auto_gotw_amount", Integer.toString(this.gotwTimerAmount));
}
private String safeString(String value) {
return value == null ? "" : value;
}
public boolean hasPermission(String key, boolean isRoomOwner) {
if (this.permissions.containsKey(key)) {
Permission permission = this.permissions.get(key);
@@ -5,6 +5,7 @@ import com.eu.habbo.habbohotel.bots.Bot;
import com.eu.habbo.habbohotel.games.Game;
import com.eu.habbo.habbohotel.guilds.Guild;
import com.eu.habbo.habbohotel.guilds.GuildMember;
import com.eu.habbo.habbohotel.guilds.GuildRank;
import com.eu.habbo.habbohotel.items.FurnitureType;
import com.eu.habbo.habbohotel.items.Item;
import com.eu.habbo.habbohotel.items.interactions.*;
@@ -30,6 +31,7 @@ import com.eu.habbo.messages.outgoing.rooms.UpdateStackHeightComposer;
import com.eu.habbo.messages.outgoing.rooms.items.*;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserIgnoredComposer;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer;
import com.eu.habbo.messages.outgoing.wired.WiredRoomSettingsDataComposer;
import com.eu.habbo.plugin.Event;
import com.eu.habbo.plugin.events.furniture.FurniturePickedUpEvent;
import com.eu.habbo.plugin.events.rooms.RoomLoadedEvent;
@@ -75,6 +77,9 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
private RoomRollerManager rollerManager;
private RoomMessagingManager messagingManager;
private RoomCycleManager cycleManager;
private RoomUserVariableManager userVariableManager;
private RoomFurniVariableManager furniVariableManager;
private RoomVariableManager roomVariableManager;
public static final Comparator<Room> SORT_SCORE = (o1, o2) -> o2.getScore() - o1.getScore();
public static final Comparator<Room> SORT_ID = (o1, o2) -> o2.getId() - o1.getId();
@@ -92,6 +97,14 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
public static int ROLLERS_MAXIMUM_ROLL_AVATARS = 1;
public static boolean MUTEAREA_CAN_WHISPER = false;
public static double MAXIMUM_FURNI_HEIGHT = 40d;
public static final int WIRED_ACCESS_EVERYONE = 1;
public static final int WIRED_ACCESS_USERS_WITH_RIGHTS = 2;
public static final int WIRED_ACCESS_GROUP_MEMBERS = 4;
public static final int WIRED_ACCESS_GROUP_ADMINS = 8;
public static final int WIRED_ACCESS_ALLOWED_INSPECT_MASK = WIRED_ACCESS_EVERYONE | WIRED_ACCESS_USERS_WITH_RIGHTS | WIRED_ACCESS_GROUP_MEMBERS | WIRED_ACCESS_GROUP_ADMINS;
public static final int WIRED_ACCESS_ALLOWED_MODIFY_MASK = WIRED_ACCESS_USERS_WITH_RIGHTS | WIRED_ACCESS_GROUP_MEMBERS | WIRED_ACCESS_GROUP_ADMINS;
public static final int WIRED_ACCESS_DEFAULT_INSPECT_MASK = 0;
public static final int WIRED_ACCESS_DEFAULT_MODIFY_MASK = 0;
static {
for (int i = 1; i <= 3; i++) {
@@ -175,6 +188,10 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
private volatile boolean muted;
private RoomSpecialTypes roomSpecialTypes;
private TraxManager traxManager;
private final Object wiredSettingsLock = new Object();
private volatile boolean wiredSettingsLoaded;
private int wiredInspectMask = WIRED_ACCESS_DEFAULT_INSPECT_MASK;
private int wiredModifyMask = WIRED_ACCESS_DEFAULT_MODIFY_MASK;
public final THashMap<String, Object> cache;
@@ -269,6 +286,9 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
this.rollerManager = new RoomRollerManager(this);
this.messagingManager = new RoomMessagingManager(this);
this.cycleManager = new RoomCycleManager(this);
this.userVariableManager = new RoomUserVariableManager(this);
this.furniVariableManager = new RoomFurniVariableManager(this);
this.roomVariableManager = new RoomVariableManager(this);
}
// ==================== MANAGER GETTERS ====================
@@ -350,6 +370,18 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
return this.cycleManager;
}
public RoomUserVariableManager getUserVariableManager() {
return this.userVariableManager;
}
public RoomFurniVariableManager getFurniVariableManager() {
return this.furniVariableManager;
}
public RoomVariableManager getRoomVariableManager() {
return this.roomVariableManager;
}
/**
* Gets the roller manager for this room.
*/
@@ -924,13 +956,13 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
this.itemManager.saveAllPendingItems();
// Unregister all wired tickables for this room from the tick service
com.eu.habbo.habbohotel.wired.core.WiredManager.unregisterRoomTickables(this);
if (this.roomSpecialTypes != null) {
this.roomSpecialTypes.dispose();
}
// Unregister all wired tickables for this room from the tick service
com.eu.habbo.habbohotel.wired.core.WiredManager.unregisterRoomTickables(this);
// Clear wired engine caches for this room
if (com.eu.habbo.habbohotel.wired.core.WiredManager.getStackIndex() != null) {
com.eu.habbo.habbohotel.wired.core.WiredManager.getStackIndex().invalidateAll(this);
@@ -938,6 +970,8 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
if (com.eu.habbo.habbohotel.wired.core.WiredManager.getEngine() != null) {
com.eu.habbo.habbohotel.wired.core.WiredManager.getEngine().clearRoomRecursionDepth(this.id);
com.eu.habbo.habbohotel.wired.core.WiredManager.getEngine().clearRoomRateLimiters(this.id);
com.eu.habbo.habbohotel.wired.core.WiredManager.getEngine().clearRoomBan(this.id);
com.eu.habbo.habbohotel.wired.core.WiredManager.getEngine().clearRoomDiagnostics(this.id);
}
this.itemManager.clear();
@@ -2127,20 +2161,108 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
return this.rightsManager.hasRights(habbo);
}
public boolean hasExplicitRights(Habbo habbo) {
return habbo != null && this.rights.contains(habbo.getHabboInfo().getId());
}
public int getWiredInspectMask() {
this.ensureWiredSettingsLoaded();
return this.wiredInspectMask;
}
public int getWiredModifyMask() {
this.ensureWiredSettingsLoaded();
return this.wiredModifyMask;
}
public boolean canInspectWired(Habbo habbo) {
if (habbo == null) {
return false;
}
if (this.canManageWiredSettings(habbo)) {
return true;
}
this.ensureWiredSettingsLoaded();
return this.matchesWiredAccessMask(habbo, this.wiredInspectMask, true);
}
public boolean canModifyWired(Habbo habbo) {
if (habbo == null) {
return false;
}
if (this.canManageWiredSettings(habbo)) {
return true;
}
this.ensureWiredSettingsLoaded();
return this.matchesWiredAccessMask(habbo, this.wiredModifyMask, false);
}
public boolean canManageWiredSettings(Habbo habbo) {
return habbo != null && this.isOwner(habbo);
}
public boolean saveWiredSettings(int inspectMask, int modifyMask) {
int sanitizedInspectMask = sanitizeWiredInspectMask(inspectMask);
int sanitizedModifyMask = sanitizeWiredModifyMask(modifyMask);
sanitizedInspectMask |= sanitizedModifyMask;
synchronized (this.wiredSettingsLock) {
int previousInspectMask = this.wiredInspectMask;
int previousModifyMask = this.wiredModifyMask;
this.wiredInspectMask = sanitizedInspectMask;
this.wiredModifyMask = sanitizedModifyMask;
this.wiredSettingsLoaded = true;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"INSERT INTO room_wired_settings (room_id, inspect_mask, modify_mask) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE inspect_mask = VALUES(inspect_mask), modify_mask = VALUES(modify_mask)")) {
statement.setInt(1, this.id);
statement.setInt(2, sanitizedInspectMask);
statement.setInt(3, sanitizedModifyMask);
statement.executeUpdate();
this.pushWiredSettingsToCurrentHabbos();
return true;
} catch (SQLException e) {
this.wiredInspectMask = previousInspectMask;
this.wiredModifyMask = previousModifyMask;
LOGGER.error("Caught SQL exception while saving wired room settings", e);
return false;
}
}
}
public void giveRights(Habbo habbo) {
this.rightsManager.giveRights(habbo);
if (habbo == null) {
return;
}
this.giveRights(habbo.getHabboInfo().getId());
}
public void giveRights(int userId) {
this.rightsManager.giveRights(userId);
if (!this.rights.contains(userId)) {
this.rights.add(userId);
}
this.pushWiredSettingsToCurrentHabbos();
}
public void removeRights(int userId) {
this.rightsManager.removeRights(userId);
this.rights.remove(userId);
this.pushWiredSettingsToCurrentHabbos();
}
public void removeAllRights() {
this.rightsManager.removeAllRights();
this.rights.clear();
this.pushWiredSettingsToCurrentHabbos();
}
void refreshRightsInRoom() {
@@ -2167,6 +2289,111 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
return this.bannedHabbos;
}
private void ensureWiredSettingsLoaded() {
if (this.wiredSettingsLoaded) {
return;
}
synchronized (this.wiredSettingsLock) {
if (this.wiredSettingsLoaded) {
return;
}
this.wiredInspectMask = WIRED_ACCESS_DEFAULT_INSPECT_MASK;
this.wiredModifyMask = WIRED_ACCESS_DEFAULT_MODIFY_MASK;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT inspect_mask, modify_mask FROM room_wired_settings WHERE room_id = ? LIMIT 1")) {
statement.setInt(1, this.id);
try (ResultSet set = statement.executeQuery()) {
if (set.next()) {
this.wiredInspectMask = sanitizeWiredInspectMask(set.getInt("inspect_mask"));
this.wiredModifyMask = sanitizeWiredModifyMask(set.getInt("modify_mask"));
}
}
} catch (SQLException e) {
LOGGER.error("Caught SQL exception while loading wired room settings", e);
}
this.wiredSettingsLoaded = true;
}
}
private boolean matchesWiredAccessMask(Habbo habbo, int mask, boolean allowEveryone) {
if (habbo == null) {
return false;
}
if (allowEveryone && hasWiredAccess(mask, WIRED_ACCESS_EVERYONE)) {
return true;
}
if (hasWiredAccess(mask, WIRED_ACCESS_USERS_WITH_RIGHTS) && this.hasExplicitRights(habbo)) {
return true;
}
if (hasWiredAccess(mask, WIRED_ACCESS_GROUP_ADMINS) && this.isRoomGroupAdmin(habbo)) {
return true;
}
return hasWiredAccess(mask, WIRED_ACCESS_GROUP_MEMBERS) && this.isRoomGroupMember(habbo);
}
private boolean isRoomGroupMember(Habbo habbo) {
return habbo != null && this.guild > 0 && habbo.getHabboStats().hasGuild(this.guild);
}
private boolean isRoomGroupAdmin(Habbo habbo) {
if (!this.isRoomGroupMember(habbo)) {
return false;
}
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(this.guild, habbo.getHabboInfo().getId());
if (member == null) {
return false;
}
GuildRank rank = member.getRank();
return rank == GuildRank.OWNER || rank == GuildRank.ADMIN;
}
private static boolean hasWiredAccess(int mask, int permissionMask) {
return (mask & permissionMask) != 0;
}
private static int sanitizeWiredInspectMask(int mask) {
int sanitizedMask = mask & WIRED_ACCESS_ALLOWED_INSPECT_MASK;
if (hasWiredAccess(sanitizedMask, WIRED_ACCESS_GROUP_MEMBERS)) {
sanitizedMask |= WIRED_ACCESS_GROUP_ADMINS;
}
return sanitizedMask;
}
private static int sanitizeWiredModifyMask(int mask) {
int sanitizedMask = mask & WIRED_ACCESS_ALLOWED_MODIFY_MASK;
if (hasWiredAccess(sanitizedMask, WIRED_ACCESS_GROUP_MEMBERS)) {
sanitizedMask |= WIRED_ACCESS_GROUP_ADMINS;
}
return sanitizedMask;
}
private void pushWiredSettingsToCurrentHabbos() {
for (Habbo currentHabbo : this.getCurrentHabbos().values()) {
if (currentHabbo == null || currentHabbo.getClient() == null) {
continue;
}
currentHabbo.getClient().sendResponse(new WiredRoomSettingsDataComposer(this, currentHabbo));
}
}
public void addRoomBan(RoomBan roomBan) {
this.rightsManager.addRoomBan(roomBan);
}
@@ -328,7 +328,9 @@ public class RoomChatManager {
suppressSaysOutput = WiredManager.shouldSuppressUserSaysOutput(
habbo.getHabboInfo().getCurrentRoom(),
habbo.getRoomUnit(),
wiredSayMessage);
wiredSayMessage,
chatType.ordinal(),
roomChatMessage.getBubble() != null ? roomChatMessage.getBubble().getType() : -1);
}
}
@@ -407,7 +409,12 @@ public class RoomChatManager {
}
if (chatType != RoomChatType.WHISPER && !ignoreWired && !roomChatMessage.isCommand) {
WiredManager.triggerUserSays(habbo.getHabboInfo().getCurrentRoom(), habbo.getRoomUnit(), wiredSayMessage);
WiredManager.triggerUserSays(
habbo.getHabboInfo().getCurrentRoom(),
habbo.getRoomUnit(),
wiredSayMessage,
chatType.ordinal(),
roomChatMessage.getBubble() != null ? roomChatMessage.getBubble().getType() : -1);
}
// Notify bots and talking furniture
@@ -16,6 +16,13 @@ import com.eu.habbo.habbohotel.items.interactions.games.tag.InteractionTagPole;
import com.eu.habbo.habbohotel.items.interactions.pets.*;
import com.eu.habbo.habbohotel.items.interactions.wired.effects.WiredEffectSendSignal;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredBlob;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFurniVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRoomVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraUserVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableEcho;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableReference;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraContextVariable;
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
import com.eu.habbo.habbohotel.items.interactions.wired.triggers.WiredTriggerReceiveSignal;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.habbohotel.users.Habbo;
@@ -773,6 +780,8 @@ public class RoomItemManager {
if (specialTypes == null) {
return;
}
this.room.getFurniVariableManager().removeAssignmentsForFurni(item.getId());
boolean isWiredItem = false;
@@ -785,21 +794,50 @@ public class RoomItemManager {
specialTypes.removeCycleTask((ICycleable) item);
}
if (item instanceof InteractionBattleBanzaiTeleporter) {
specialTypes.removeBanzaiTeleporter((InteractionBattleBanzaiTeleporter) item);
} else if (item instanceof InteractionWiredTrigger) {
specialTypes.removeTrigger((InteractionWiredTrigger) item);
isWiredItem = true;
} else if (item instanceof InteractionWiredEffect) {
specialTypes.removeEffect((InteractionWiredEffect) item);
isWiredItem = true;
} else if (item instanceof InteractionWiredCondition) {
specialTypes.removeCondition((InteractionWiredCondition) item);
isWiredItem = true;
} else if (item instanceof InteractionWiredExtra) {
specialTypes.removeExtra((InteractionWiredExtra) item);
isWiredItem = true;
} else if (item instanceof InteractionRoller) {
if (item instanceof InteractionBattleBanzaiTeleporter) {
specialTypes.removeBanzaiTeleporter((InteractionBattleBanzaiTeleporter) item);
} else if (item instanceof InteractionWiredTrigger) {
specialTypes.removeTrigger((InteractionWiredTrigger) item);
isWiredItem = true;
} else if (item instanceof InteractionWiredEffect) {
specialTypes.removeEffect((InteractionWiredEffect) item);
isWiredItem = true;
} else if (item instanceof InteractionWiredCondition) {
specialTypes.removeCondition((InteractionWiredCondition) item);
isWiredItem = true;
} else if (item instanceof InteractionWiredExtra) {
boolean removedContextDefinition = false;
if (item instanceof WiredExtraUserVariable) {
this.room.getUserVariableManager().removeDefinition(item.getId());
} else if (item instanceof WiredExtraFurniVariable) {
this.room.getFurniVariableManager().removeDefinition(item.getId());
} else if (item instanceof WiredExtraRoomVariable) {
this.room.getRoomVariableManager().removeDefinition(item.getId());
} else if (item instanceof WiredExtraContextVariable) {
removedContextDefinition = true;
} else if (item instanceof WiredExtraVariableReference) {
if (((WiredExtraVariableReference) item).isRoomReference()) {
this.room.getRoomVariableManager().removeDefinition(item.getId());
} else {
this.room.getUserVariableManager().removeDefinition(item.getId());
}
} else if (item instanceof WiredExtraVariableEcho) {
WiredExtraVariableEcho echo = (WiredExtraVariableEcho) item;
if (echo.isRoomEcho()) {
this.room.getRoomVariableManager().removeDefinition(item.getId());
} else if (echo.isFurniEcho()) {
this.room.getFurniVariableManager().removeDefinition(item.getId());
} else {
this.room.getUserVariableManager().removeDefinition(item.getId());
}
}
specialTypes.removeExtra((InteractionWiredExtra) item);
if (removedContextDefinition) {
WiredContextVariableSupport.broadcastDefinitions(this.room);
}
isWiredItem = true;
} else if (item instanceof InteractionRoller) {
specialTypes.removeRoller((InteractionRoller) item);
} else if (item instanceof InteractionGameScoreboard) {
specialTypes.removeScoreboard((InteractionGameScoreboard) item);
@@ -780,6 +780,7 @@ public class RoomManager {
habbo.getRoomUnit().setInvisible(false);
room.addHabbo(habbo);
room.getUserVariableManager().restorePermanentAssignments(habbo);
// Pre-send own wearing badges so the client cache is populated before the user clicks themselves
habbo.getClient().sendResponse(new UserBadgesComposer(habbo.getInventory().getBadgesComponent().getWearingBadges(), habbo.getHabboInfo().getId()));
@@ -232,12 +232,14 @@ public class RoomUnitManager {
habbo.getRoomUnit().getCurrentLocation().removeUnit(habbo.getRoomUnit());
}
synchronized (this.room.roomUnitLock) {
this.currentHabbos.remove(habbo.getHabboInfo().getId());
}
synchronized (this.room.roomUnitLock) {
this.currentHabbos.remove(habbo.getHabboInfo().getId());
}
if (sendRemovePacket && habbo.getRoomUnit() != null && !habbo.getRoomUnit().isTeleporting) {
this.room.sendComposer(new RoomUserRemoveComposer(habbo.getRoomUnit()).compose());
this.room.getUserVariableManager().clearAssignmentsForUser(habbo.getHabboInfo().getId());
if (sendRemovePacket && habbo.getRoomUnit() != null && !habbo.getRoomUnit().isTeleporting) {
this.room.sendComposer(new RoomUserRemoveComposer(habbo.getRoomUnit()).compose());
}
if (habbo.getRoomUnit().getCurrentLocation() != null) {
@@ -0,0 +1,827 @@
package com.eu.habbo.habbohotel.rooms;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRoomVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableEcho;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableReference;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredVariableReferenceSupport;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.wired.core.WiredEvent;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.habbohotel.wired.core.WiredVariableLevelSystemSupport;
import com.eu.habbo.habbohotel.wired.core.WiredVariableTextConnectorSupport;
import com.eu.habbo.messages.outgoing.wired.WiredUserVariablesDataComposer;
import gnu.trove.set.hash.THashSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
public class RoomVariableManager {
private static final Logger LOGGER = LoggerFactory.getLogger(RoomVariableManager.class);
private final Room room;
private final ConcurrentHashMap<Integer, VariableAssignment> activeAssignmentsByDefinitionId;
private volatile boolean persistentValuesLoaded;
public RoomVariableManager(Room room) {
this.room = room;
this.activeAssignmentsByDefinitionId = new ConcurrentHashMap<>();
this.persistentValuesLoaded = false;
}
public void ensurePersistentValuesLoaded() {
if (this.persistentValuesLoaded) {
return;
}
synchronized (this) {
if (this.persistentValuesLoaded) {
return;
}
List<Integer> staleDefinitionIds = new ArrayList<>();
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT variable_item_id, value, created_at, updated_at FROM room_wired_variables WHERE room_id = ?")) {
statement.setInt(1, this.room.getId());
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
int definitionItemId = set.getInt("variable_item_id");
WiredExtraRoomVariable definition = this.getDefinition(definitionItemId);
if (definition == null || !definition.isPermanentAvailability()) {
staleDefinitionIds.add(definitionItemId);
continue;
}
int updatedAt = normalizeTimestamp(set.getInt("updated_at"), 0);
this.activeAssignmentsByDefinitionId.put(definitionItemId, new VariableAssignment(set.getInt("value"), 0, updatedAt));
}
}
} catch (SQLException e) {
LOGGER.error("Failed to restore wired room variables for room {}", this.room.getId(), e);
}
for (Integer definitionItemId : staleDefinitionIds) {
this.deletePersistentAssignment(definitionItemId);
}
this.persistentValuesLoaded = true;
}
}
public int getCurrentValue(int definitionItemId) {
this.ensurePersistentValuesLoaded();
WiredVariableLevelSystemSupport.DerivedDefinition derivedDefinition = WiredVariableLevelSystemSupport.resolveDerivedDefinition(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, definitionItemId);
if (derivedDefinition != null) {
Integer baseValue = this.getRawValue(derivedDefinition.getBaseDefinitionItemId());
Integer derivedValue = WiredVariableLevelSystemSupport.getDerivedValue(derivedDefinition.getLevelSystem(), derivedDefinition.getSubvariableType(), baseValue);
return (derivedValue != null) ? derivedValue : 0;
}
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
if (extra instanceof WiredExtraVariableEcho) {
return ((WiredExtraVariableEcho) extra).getCurrentValue(this.room, this.room.getId());
}
if (extra instanceof WiredExtraVariableReference) {
WiredVariableReferenceSupport.SharedRoomAssignment assignment = WiredVariableReferenceSupport.getSharedRoomAssignment((WiredExtraVariableReference) extra);
return assignment != null ? assignment.getValue() : 0;
}
VariableAssignment assignment = this.activeAssignmentsByDefinitionId.get(definitionItemId);
return (assignment != null) ? assignment.getValue() : 0;
}
public int getCreatedAt(int definitionItemId) {
this.ensurePersistentValuesLoaded();
WiredVariableLevelSystemSupport.DerivedDefinition derivedDefinition = WiredVariableLevelSystemSupport.resolveDerivedDefinition(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, definitionItemId);
if (derivedDefinition != null) {
VariableAssignment assignment = this.getRawAssignment(derivedDefinition.getBaseDefinitionItemId());
return (assignment != null) ? assignment.getCreatedAt() : 0;
}
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
if (extra instanceof WiredExtraVariableEcho) {
return ((WiredExtraVariableEcho) extra).getCreatedAt(this.room, this.room.getId());
}
if (extra instanceof WiredExtraVariableReference) {
return 0;
}
VariableAssignment assignment = this.activeAssignmentsByDefinitionId.get(definitionItemId);
return (assignment != null) ? assignment.getCreatedAt() : 0;
}
public int getUpdatedAt(int definitionItemId) {
this.ensurePersistentValuesLoaded();
WiredVariableLevelSystemSupport.DerivedDefinition derivedDefinition = WiredVariableLevelSystemSupport.resolveDerivedDefinition(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, definitionItemId);
if (derivedDefinition != null) {
VariableAssignment assignment = this.getRawAssignment(derivedDefinition.getBaseDefinitionItemId());
return (assignment != null) ? assignment.getUpdatedAt() : 0;
}
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
if (extra instanceof WiredExtraVariableEcho) {
return ((WiredExtraVariableEcho) extra).getUpdatedAt(this.room, this.room.getId());
}
if (extra instanceof WiredExtraVariableReference) {
WiredVariableReferenceSupport.SharedRoomAssignment assignment = WiredVariableReferenceSupport.getSharedRoomAssignment((WiredExtraVariableReference) extra);
return assignment != null ? assignment.getUpdatedAt() : 0;
}
VariableAssignment assignment = this.activeAssignmentsByDefinitionId.get(definitionItemId);
return (assignment != null) ? assignment.getUpdatedAt() : 0;
}
public boolean hasVariable(int definitionItemId) {
if (definitionItemId <= 0) {
return false;
}
WiredVariableLevelSystemSupport.DerivedDefinition derivedDefinition = WiredVariableLevelSystemSupport.resolveDerivedDefinition(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, definitionItemId);
if (derivedDefinition != null) {
return this.getRawAssignment(derivedDefinition.getBaseDefinitionItemId()) != null;
}
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
if (extra instanceof WiredExtraVariableEcho) {
return ((WiredExtraVariableEcho) extra).hasVariable(this.room, this.room.getId());
}
return this.getDefinitionInfo(definitionItemId) != null;
}
public boolean updateVariableValue(int definitionItemId, int value) {
this.ensurePersistentValuesLoaded();
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
WiredVariableDefinitionInfo definitionInfo = this.getDefinitionInfo(definitionItemId);
if (definitionInfo == null || definitionInfo.isReadOnly()) {
return false;
}
Integer previousValue = definitionInfo.hasValue() ? this.getCurrentValue(definitionItemId) : null;
if (extra instanceof WiredExtraVariableEcho) {
boolean changed = ((WiredExtraVariableEcho) extra).updateValue(this.room, this.room.getId(), value);
boolean shouldEmit = changed || (definitionInfo.hasValue() && previousValue != null && previousValue == value);
if (shouldEmit) {
Integer currentValue = definitionInfo.hasValue() ? this.getCurrentValue(definitionItemId) : null;
this.emitVariableChangedEvents(extra, definitionInfo, previousValue, currentValue);
}
if (changed) {
this.broadcastSnapshot();
}
return changed;
}
if (extra instanceof WiredExtraVariableReference) {
boolean changed = WiredVariableReferenceSupport.updateSharedRoomVariable((WiredExtraVariableReference) extra, value);
boolean shouldEmit = changed || (definitionInfo.hasValue() && previousValue != null && previousValue == value);
if (shouldEmit) {
Integer currentValue = definitionInfo.hasValue() ? this.getCurrentValue(definitionItemId) : null;
this.emitVariableChangedEvents(extra, definitionInfo, previousValue, currentValue);
}
if (changed) {
this.broadcastSnapshot();
}
return changed;
}
VariableAssignment assignment = this.activeAssignmentsByDefinitionId.get(definitionItemId);
if (assignment == null) {
assignment = new VariableAssignment(value, 0, Emulator.getIntUnixTimestamp());
this.activeAssignmentsByDefinitionId.put(definitionItemId, assignment);
} else if (assignment.getValue() == value) {
this.emitVariableChangedEvents(extra, definitionInfo, previousValue, assignment.getValue());
return false;
} else {
assignment.setValue(value, Emulator.getIntUnixTimestamp());
}
WiredExtraRoomVariable definition = (WiredExtraRoomVariable) extra;
if (definition.isPermanentAvailability()) {
this.upsertPersistentAssignment(definitionItemId, assignment);
}
if (definition.isSharedAvailability()) {
WiredVariableReferenceSupport.cacheSharedRoomAssignment(this.room.getId(), definitionItemId, assignment.getValue(), assignment.getUpdatedAt());
} else {
WiredVariableReferenceSupport.clearSharedRoomDefinition(this.room.getId(), definitionItemId);
}
this.emitVariableChangedEvents(extra, definitionInfo, previousValue, assignment.getValue());
this.broadcastSnapshot();
return true;
}
public boolean removeVariable(int definitionItemId) {
this.ensurePersistentValuesLoaded();
if (definitionItemId <= 0) {
return false;
}
WiredVariableDefinitionInfo definitionInfo = this.getDefinitionInfo(definitionItemId);
if (definitionInfo == null || definitionInfo.isReadOnly()) {
return false;
}
Integer previousValue = definitionInfo.hasValue() ? this.getCurrentValue(definitionItemId) : null;
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
if (extra instanceof WiredExtraVariableEcho) {
boolean changed = ((WiredExtraVariableEcho) extra).removeValue(this.room, this.room.getId());
if (changed) {
Integer currentValue = definitionInfo.hasValue() ? this.getCurrentValue(definitionItemId) : null;
this.emitVariableChangedEvents(extra, definitionInfo, previousValue, currentValue);
}
if (changed) {
this.broadcastSnapshot();
}
return changed;
}
if (extra instanceof WiredExtraVariableReference) {
boolean changed = WiredVariableReferenceSupport.removeSharedRoomVariable((WiredExtraVariableReference) extra);
if (changed) {
Integer currentValue = definitionInfo.hasValue() ? this.getCurrentValue(definitionItemId) : null;
this.emitVariableChangedEvents(extra, definitionInfo, previousValue, currentValue);
}
if (changed) {
this.broadcastSnapshot();
}
return changed;
}
VariableAssignment removed = this.activeAssignmentsByDefinitionId.remove(definitionItemId);
this.deletePersistentAssignment(definitionItemId);
WiredExtraRoomVariable definition = this.getDefinition(definitionItemId);
if (definition != null && definition.isSharedAvailability()) {
WiredVariableReferenceSupport.clearSharedRoomDefinition(this.room.getId(), definitionItemId);
}
if (removed != null) {
Integer currentValue = definitionInfo.hasValue() ? this.getCurrentValue(definitionItemId) : null;
this.emitVariableChangedEvents(extra, definitionInfo, previousValue, currentValue);
this.broadcastSnapshot();
return true;
}
return false;
}
public void clearTransientAssignments() {
this.ensurePersistentValuesLoaded();
boolean changed = false;
for (Integer definitionItemId : new ArrayList<>(this.activeAssignmentsByDefinitionId.keySet())) {
WiredExtraRoomVariable definition = this.getDefinition(definitionItemId);
if (definition != null && definition.isPermanentAvailability()) {
continue;
}
if (this.activeAssignmentsByDefinitionId.remove(definitionItemId) != null) {
changed = true;
}
}
if (changed) {
this.broadcastSnapshot();
}
}
public void removeDefinition(int definitionItemId) {
this.ensurePersistentValuesLoaded();
this.activeAssignmentsByDefinitionId.remove(definitionItemId);
this.deletePersistentAssignment(definitionItemId);
WiredExtraRoomVariable definition = this.getDefinition(definitionItemId);
if (definition != null && definition.isSharedAvailability()) {
WiredVariableReferenceSupport.clearSharedRoomDefinition(this.room.getId(), definitionItemId);
}
this.broadcastSnapshot();
}
public void handleDefinitionUpdated(WiredExtraRoomVariable definition) {
if (definition == null) {
return;
}
this.ensurePersistentValuesLoaded();
if (!definition.isPermanentAvailability()) {
this.deletePersistentAssignment(definition.getId());
} else {
VariableAssignment assignment = this.activeAssignmentsByDefinitionId.get(definition.getId());
if (assignment == null) {
return;
}
this.upsertPersistentAssignment(definition.getId(), assignment);
}
if (definition.isSharedAvailability()) {
VariableAssignment assignment = this.activeAssignmentsByDefinitionId.get(definition.getId());
if (assignment != null) {
WiredVariableReferenceSupport.cacheSharedRoomAssignment(this.room.getId(), definition.getId(), assignment.getValue(), assignment.getUpdatedAt());
}
} else {
WiredVariableReferenceSupport.clearSharedRoomDefinition(this.room.getId(), definition.getId());
}
this.broadcastSnapshot();
}
public Snapshot createSnapshot() {
this.ensurePersistentValuesLoaded();
List<DefinitionEntry> definitions = new ArrayList<>();
List<AssignmentEntry> assignments = new ArrayList<>();
List<Integer> derivedDefinitionIds = new ArrayList<>();
List<WiredExtraVariableEcho> roomEchoes = this.getRoomEchoes();
for (WiredVariableDefinitionInfo definition : this.getAllDefinitionInfos()) {
definitions.add(new DefinitionEntry(definition.getItemId(), definition.getName(), definition.hasValue(), definition.getAvailability(), definition.isTextConnected(), definition.isReadOnly()));
if (WiredVariableLevelSystemSupport.resolveDerivedDefinition(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, definition.getItemId()) != null) {
derivedDefinitionIds.add(definition.getItemId());
}
if (this.isReferenceDefinition(definition.getItemId())) {
WiredExtraVariableReference reference = (WiredExtraVariableReference) this.getDefinitionExtra(definition.getItemId());
WiredVariableReferenceSupport.SharedRoomAssignment assignment = WiredVariableReferenceSupport.getSharedRoomAssignment(reference);
assignments.add(new AssignmentEntry(definition.getItemId(), (assignment != null) ? assignment.getValue() : 0, 0, (assignment != null) ? assignment.getUpdatedAt() : 0));
continue;
}
if (derivedDefinitionIds.contains(definition.getItemId())) {
assignments.add(new AssignmentEntry(
definition.getItemId(),
this.getCurrentValue(definition.getItemId()),
this.getCreatedAt(definition.getItemId()),
this.getUpdatedAt(definition.getItemId())
));
continue;
}
if (roomEchoes.stream().anyMatch(echo -> echo.getId() == definition.getItemId())) {
assignments.add(new AssignmentEntry(
definition.getItemId(),
this.getCurrentValue(definition.getItemId()),
this.getCreatedAt(definition.getItemId()),
this.getUpdatedAt(definition.getItemId())
));
continue;
}
VariableAssignment assignment = this.activeAssignmentsByDefinitionId.get(definition.getItemId());
assignments.add(new AssignmentEntry(definition.getItemId(), (assignment != null) ? assignment.getValue() : 0, 0, (assignment != null) ? assignment.getUpdatedAt() : 0));
}
assignments.sort(Comparator.comparingInt(AssignmentEntry::getVariableItemId));
return new Snapshot(this.room.getId(), definitions, assignments);
}
public void sendSnapshot(Habbo habbo) {
if (habbo == null || habbo.getClient() == null || !this.room.canInspectWired(habbo)) {
return;
}
habbo.getClient().sendResponse(new WiredUserVariablesDataComposer(this.room.getUserVariableManager().createSnapshot(), this.room.getFurniVariableManager().createSnapshot(), this.createSnapshot()));
}
public void broadcastSnapshot() {
RoomUserVariableManager.Snapshot userSnapshot = this.room.getUserVariableManager().createSnapshot();
RoomFurniVariableManager.Snapshot furniSnapshot = this.room.getFurniVariableManager().createSnapshot();
Snapshot roomSnapshot = this.createSnapshot();
for (Habbo habbo : this.room.getCurrentHabbos().values()) {
if (habbo == null || habbo.getClient() == null || !this.room.canInspectWired(habbo)) {
continue;
}
habbo.getClient().sendResponse(new WiredUserVariablesDataComposer(userSnapshot, furniSnapshot, roomSnapshot));
}
}
public Collection<WiredExtraRoomVariable> getDefinitions() {
if (this.room.getRoomSpecialTypes() == null) {
return Collections.emptyList();
}
THashSet<InteractionWiredExtra> extras = this.room.getRoomSpecialTypes().getExtras();
List<WiredExtraRoomVariable> result = new ArrayList<>();
for (InteractionWiredExtra extra : extras) {
if (extra instanceof WiredExtraRoomVariable) {
result.add((WiredExtraRoomVariable) extra);
}
}
result.sort(Comparator.comparing(WiredExtraRoomVariable::getVariableName, String.CASE_INSENSITIVE_ORDER).thenComparingInt(WiredExtraRoomVariable::getId));
return result;
}
public Collection<WiredVariableDefinitionInfo> getAllDefinitionInfos() {
List<WiredVariableDefinitionInfo> result = new ArrayList<>();
List<WiredVariableDefinitionInfo> baseDefinitions = new ArrayList<>();
for (WiredExtraRoomVariable definition : this.getDefinitions()) {
baseDefinitions.add(new WiredVariableDefinitionInfo(
definition.getId(),
definition.getVariableName(),
definition.hasValue(),
definition.getAvailability(),
WiredVariableTextConnectorSupport.isTextConnected(this.room, definition),
false
));
}
for (WiredExtraVariableReference reference : this.getRoomReferences()) {
baseDefinitions.add(new WiredVariableDefinitionInfo(reference.getId(), reference.getVariableName(), reference.hasValue(), reference.getAvailability(), false, reference.isReadOnly()));
}
for (WiredExtraVariableEcho echo : this.getRoomEchoes()) {
baseDefinitions.add(echo.createDefinitionInfo(this.room));
}
result.addAll(baseDefinitions);
for (WiredVariableDefinitionInfo definition : baseDefinitions) {
result.addAll(WiredVariableLevelSystemSupport.getDerivedDefinitions(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, this.getDefinitionExtra(definition.getItemId()), definition));
}
result.sort(Comparator.comparing(WiredVariableDefinitionInfo::getName, String.CASE_INSENSITIVE_ORDER).thenComparingInt(WiredVariableDefinitionInfo::getItemId));
return result;
}
public WiredVariableDefinitionInfo getDefinitionInfo(int definitionItemId) {
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
if (extra instanceof WiredExtraRoomVariable) {
WiredExtraRoomVariable definition = (WiredExtraRoomVariable) extra;
return new WiredVariableDefinitionInfo(
definition.getId(),
definition.getVariableName(),
definition.hasValue(),
definition.getAvailability(),
WiredVariableTextConnectorSupport.isTextConnected(this.room, definition),
false
);
}
if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isRoomReference()) {
WiredExtraVariableReference reference = (WiredExtraVariableReference) extra;
return new WiredVariableDefinitionInfo(reference.getId(), reference.getVariableName(), reference.hasValue(), reference.getAvailability(), false, reference.isReadOnly());
}
if (extra instanceof WiredExtraVariableEcho && ((WiredExtraVariableEcho) extra).isRoomEcho()) {
return ((WiredExtraVariableEcho) extra).createDefinitionInfo(this.room);
}
return WiredVariableLevelSystemSupport.getDerivedDefinitionInfo(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, definitionItemId);
}
private WiredExtraRoomVariable getDefinition(int definitionItemId) {
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
if (!(extra instanceof WiredExtraRoomVariable)) {
return null;
}
return (WiredExtraRoomVariable) extra;
}
private InteractionWiredExtra getDefinitionExtra(int definitionItemId) {
if (this.room.getRoomSpecialTypes() == null || definitionItemId <= 0) {
return null;
}
return this.room.getRoomSpecialTypes().getExtra(definitionItemId);
}
private boolean isReferenceDefinition(int definitionItemId) {
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
return extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isRoomReference();
}
private List<WiredExtraVariableReference> getRoomReferences() {
if (this.room.getRoomSpecialTypes() == null) {
return Collections.emptyList();
}
List<WiredExtraVariableReference> result = new ArrayList<>();
for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) {
if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isRoomReference()) {
result.add((WiredExtraVariableReference) extra);
}
}
result.sort(Comparator.comparing(WiredExtraVariableReference::getVariableName, String.CASE_INSENSITIVE_ORDER).thenComparingInt(WiredExtraVariableReference::getId));
return result;
}
private List<WiredExtraVariableEcho> getRoomEchoes() {
if (this.room.getRoomSpecialTypes() == null) {
return Collections.emptyList();
}
List<WiredExtraVariableEcho> result = new ArrayList<>();
for (InteractionWiredExtra extra : this.room.getRoomSpecialTypes().getExtras()) {
if (extra instanceof WiredExtraVariableEcho && ((WiredExtraVariableEcho) extra).isRoomEcho()) {
result.add((WiredExtraVariableEcho) extra);
}
}
result.sort(Comparator.comparing(WiredExtraVariableEcho::getVariableName, String.CASE_INSENSITIVE_ORDER).thenComparingInt(WiredExtraVariableEcho::getId));
return result;
}
private VariableAssignment getRawAssignment(int definitionItemId) {
if (definitionItemId <= 0) {
return null;
}
InteractionWiredExtra extra = this.getDefinitionExtra(definitionItemId);
if (extra instanceof WiredExtraVariableReference) {
WiredVariableReferenceSupport.SharedRoomAssignment assignment = WiredVariableReferenceSupport.getSharedRoomAssignment((WiredExtraVariableReference) extra);
return (assignment != null) ? new VariableAssignment(assignment.getValue(), 0, assignment.getUpdatedAt()) : null;
}
if (extra instanceof WiredExtraVariableEcho) {
WiredExtraVariableEcho echo = (WiredExtraVariableEcho) extra;
if (!echo.hasVariable(this.room, this.room.getId())) {
return null;
}
return new VariableAssignment(echo.getCurrentValue(this.room, this.room.getId()), echo.getCreatedAt(this.room, this.room.getId()), echo.getUpdatedAt(this.room, this.room.getId()));
}
return this.activeAssignmentsByDefinitionId.get(definitionItemId);
}
private Integer getRawValue(int definitionItemId) {
VariableAssignment assignment = this.getRawAssignment(definitionItemId);
return (assignment != null) ? assignment.getValue() : null;
}
private void emitVariableChangedEvents(InteractionWiredExtra definitionExtra, WiredVariableDefinitionInfo definitionInfo, Integer previousValue, Integer currentValue) {
if (definitionInfo == null) {
return;
}
this.emitVariableChangedEvent(definitionInfo.getItemId(), definitionInfo.hasValue(), previousValue, currentValue);
for (WiredVariableDefinitionInfo derivedDefinition : WiredVariableLevelSystemSupport.getDerivedDefinitions(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, definitionExtra, definitionInfo)) {
WiredVariableLevelSystemSupport.DerivedDefinition resolvedDefinition = WiredVariableLevelSystemSupport.resolveDerivedDefinition(this.room, WiredVariableLevelSystemSupport.TARGET_ROOM, derivedDefinition.getItemId());
if (resolvedDefinition == null) {
continue;
}
Integer derivedPreviousValue = WiredVariableLevelSystemSupport.getDerivedValue(resolvedDefinition.getLevelSystem(), resolvedDefinition.getSubvariableType(), previousValue);
Integer derivedCurrentValue = WiredVariableLevelSystemSupport.getDerivedValue(resolvedDefinition.getLevelSystem(), resolvedDefinition.getSubvariableType(), currentValue);
this.emitVariableChangedEvent(derivedDefinition.getItemId(), true, derivedPreviousValue, derivedCurrentValue);
}
}
private void emitVariableChangedEvent(int definitionItemId, boolean hasValue, Integer previousValue, Integer currentValue) {
WiredEvent.VariableChangeKind changeKind = resolveVariableChangeKind(hasValue, previousValue, currentValue);
if (changeKind == WiredEvent.VariableChangeKind.NONE) {
return;
}
WiredManager.triggerRoomVariableChanged(this.room, definitionItemId, changeKind);
}
private static WiredEvent.VariableChangeKind resolveVariableChangeKind(boolean hasValue, Integer previousValue, Integer currentValue) {
if (!hasValue) {
return WiredEvent.VariableChangeKind.NONE;
}
if (Objects.equals(previousValue, currentValue)) {
return WiredEvent.VariableChangeKind.UNCHANGED;
}
int previousNumericValue = (previousValue != null) ? previousValue : 0;
int currentNumericValue = (currentValue != null) ? currentValue : 0;
return (currentNumericValue > previousNumericValue)
? WiredEvent.VariableChangeKind.INCREASED
: WiredEvent.VariableChangeKind.DECREASED;
}
private void upsertPersistentAssignment(int definitionItemId, VariableAssignment assignment) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("INSERT INTO room_wired_variables (room_id, variable_item_id, value, created_at, updated_at) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE value = VALUES(value), updated_at = VALUES(updated_at)")) {
statement.setInt(1, this.room.getId());
statement.setInt(2, definitionItemId);
statement.setInt(3, (assignment != null) ? assignment.getValue() : 0);
int now = Emulator.getIntUnixTimestamp();
statement.setInt(4, 0);
statement.setInt(5, (assignment != null) ? assignment.getUpdatedAt() : now);
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.error("Failed to store permanent wired room variable for room {} and item {}", this.room.getId(), definitionItemId, e);
}
}
private void deletePersistentAssignment(int definitionItemId) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("DELETE FROM room_wired_variables WHERE room_id = ? AND variable_item_id = ?")) {
statement.setInt(1, this.room.getId());
statement.setInt(2, definitionItemId);
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.error("Failed to delete permanent wired room variable for room {} and item {}", this.room.getId(), definitionItemId, e);
}
}
private static int normalizeTimestamp(int value, int fallback) {
if (value > 0) {
return value;
}
if (fallback > 0) {
return fallback;
}
return 0;
}
public static class Snapshot {
private final int roomId;
private final List<DefinitionEntry> definitions;
private final List<AssignmentEntry> assignments;
public Snapshot(int roomId, List<DefinitionEntry> definitions, List<AssignmentEntry> assignments) {
this.roomId = roomId;
this.definitions = definitions;
this.assignments = assignments;
}
public int getRoomId() {
return this.roomId;
}
public List<DefinitionEntry> getDefinitions() {
return this.definitions;
}
public List<AssignmentEntry> getAssignments() {
return this.assignments;
}
}
public static class DefinitionEntry {
private final int itemId;
private final String name;
private final boolean hasValue;
private final int availability;
private final boolean textConnected;
private final boolean readOnly;
public DefinitionEntry(int itemId, String name, boolean hasValue, int availability, boolean textConnected, boolean readOnly) {
this.itemId = itemId;
this.name = name;
this.hasValue = hasValue;
this.availability = availability;
this.textConnected = textConnected;
this.readOnly = readOnly;
}
public int getItemId() {
return this.itemId;
}
public String getName() {
return this.name;
}
public boolean hasValue() {
return this.hasValue;
}
public int getAvailability() {
return this.availability;
}
public boolean isTextConnected() {
return this.textConnected;
}
public boolean isReadOnly() {
return this.readOnly;
}
}
public static class AssignmentEntry {
private final int variableItemId;
private final Integer value;
private final int createdAt;
private final int updatedAt;
public AssignmentEntry(int variableItemId, Integer value, int createdAt, int updatedAt) {
this.variableItemId = variableItemId;
this.value = value;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public int getVariableItemId() {
return this.variableItemId;
}
public Integer getValue() {
return this.value;
}
public boolean hasValue() {
return this.value != null;
}
public int getCreatedAt() {
return this.createdAt;
}
public int getUpdatedAt() {
return this.updatedAt;
}
}
private static class VariableAssignment {
private int value;
private final int createdAt;
private int updatedAt;
public VariableAssignment(int value, int createdAt, int updatedAt) {
this.value = value;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public int getValue() {
return this.value;
}
public void setValue(int value, int updatedAt) {
this.value = value;
this.updatedAt = updatedAt;
}
public int getCreatedAt() {
return this.createdAt;
}
public int getUpdatedAt() {
return this.updatedAt;
}
}
}
@@ -0,0 +1,43 @@
package com.eu.habbo.habbohotel.rooms;
public class WiredVariableDefinitionInfo {
private final int itemId;
private final String name;
private final boolean hasValue;
private final int availability;
private final boolean textConnected;
private final boolean readOnly;
public WiredVariableDefinitionInfo(int itemId, String name, boolean hasValue, int availability, boolean textConnected, boolean readOnly) {
this.itemId = itemId;
this.name = name;
this.hasValue = hasValue;
this.availability = availability;
this.textConnected = textConnected;
this.readOnly = readOnly;
}
public int getItemId() {
return this.itemId;
}
public String getName() {
return this.name;
}
public boolean hasValue() {
return this.hasValue;
}
public int getAvailability() {
return this.availability;
}
public boolean isTextConnected() {
return this.textConnected;
}
public boolean isReadOnly() {
return this.readOnly;
}
}
@@ -38,7 +38,11 @@ public enum WiredConditionType {
MATCH_TIME(36),
MATCH_DATE(37),
ACTOR_DIR(38),
SLC_QUANTITY(39);
SLC_QUANTITY(39),
HAS_VAR(40),
NOT_HAS_VAR(41),
VAR_VAL_MATCH(42),
VAR_AGE_MATCH(43);
public final int code;
@@ -54,7 +54,12 @@ public enum WiredEffectType {
USERS_BY_NAME_SELECTOR(52),
USERS_ON_FURNI_SELECTOR(53),
USERS_GROUP_SELECTOR(54),
USERS_HANDITEM_SELECTOR(55);
USERS_HANDITEM_SELECTOR(55),
GIVE_VAR(69),
REMOVE_VAR(73),
CHANGE_VAR_VAL(74),
FURNI_WITH_VAR_SELECTOR(75),
USERS_WITH_VAR_SELECTOR(76);
public final int code;
@@ -22,6 +22,7 @@ public enum WiredTriggerType {
CLICKS_USER(20),
USER_PERFORMS_ACTION(21),
CLOCK_COUNTER(22),
VARIABLE_CHANGED(23),
SAY_COMMAND(0),
IDLES(11),
UNIDLES(11),
@@ -59,6 +59,9 @@ public final class WiredContext {
/** Extra settings from the trigger item (for legacy compatibility) */
private final Object[] legacySettings;
/** Runtime-local context variables shared through the current execution chain. */
private WiredContextVariableScope contextVariables;
/** Whether selector item resolution should include wired furniture too. */
private boolean includeWiredSelectorItems = false;
@@ -108,6 +111,9 @@ public final class WiredContext {
this.services = services;
this.state = state;
this.legacySettings = legacySettings;
this.contextVariables = (event.getContextVariableScope() != null)
? event.getContextVariableScope()
: new WiredContextVariableScope();
this.targets = new WiredTargets();
// Default targets: include actor and trigger item for backwards compatibility
@@ -259,6 +265,14 @@ public final class WiredContext {
return legacySettings != null ? legacySettings : new Object[0];
}
public WiredContextVariableScope contextVariables() {
return this.contextVariables;
}
public void forkContextVariables() {
this.contextVariables = this.contextVariables.copy();
}
public boolean includeWiredSelectorItems() {
return this.includeWiredSelectorItems;
}
@@ -0,0 +1,134 @@
package com.eu.habbo.habbohotel.wired.core;
import com.eu.habbo.Emulator;
import java.util.LinkedHashMap;
import java.util.Map;
public final class WiredContextVariableScope {
private final LinkedHashMap<Integer, VariableAssignment> assignments;
public WiredContextVariableScope() {
this.assignments = new LinkedHashMap<>();
}
private WiredContextVariableScope(Map<Integer, VariableAssignment> source) {
this.assignments = new LinkedHashMap<>();
if (source == null || source.isEmpty()) {
return;
}
for (Map.Entry<Integer, VariableAssignment> entry : source.entrySet()) {
if (entry == null || entry.getKey() == null || entry.getKey() <= 0 || entry.getValue() == null) {
continue;
}
this.assignments.put(entry.getKey(), entry.getValue().copy());
}
}
public WiredContextVariableScope copy() {
return new WiredContextVariableScope(this.assignments);
}
public boolean hasVariable(int definitionItemId) {
return definitionItemId > 0 && this.assignments.containsKey(definitionItemId);
}
public Integer getValue(int definitionItemId) {
VariableAssignment assignment = this.assignments.get(definitionItemId);
return assignment != null ? assignment.getValue() : null;
}
public int getCreatedAt(int definitionItemId) {
VariableAssignment assignment = this.assignments.get(definitionItemId);
return assignment != null ? assignment.getCreatedAt() : 0;
}
public int getUpdatedAt(int definitionItemId) {
VariableAssignment assignment = this.assignments.get(definitionItemId);
return assignment != null ? assignment.getUpdatedAt() : 0;
}
public boolean assignValue(int definitionItemId, Integer value, boolean overrideExisting) {
if (definitionItemId <= 0) {
return false;
}
VariableAssignment existingAssignment = this.assignments.get(definitionItemId);
if (existingAssignment != null && !overrideExisting) {
return false;
}
int now = Emulator.getIntUnixTimestamp();
if (existingAssignment == null || overrideExisting) {
this.assignments.put(definitionItemId, new VariableAssignment(value, now, now));
return true;
}
return false;
}
public boolean updateValue(int definitionItemId, Integer value) {
if (definitionItemId <= 0) {
return false;
}
VariableAssignment assignment = this.assignments.get(definitionItemId);
if (assignment == null) {
return false;
}
if ((assignment.getValue() == null && value == null)
|| (assignment.getValue() != null && assignment.getValue().equals(value))) {
return false;
}
assignment.setValue(value, Emulator.getIntUnixTimestamp());
return true;
}
public boolean removeValue(int definitionItemId) {
if (definitionItemId <= 0) {
return false;
}
return this.assignments.remove(definitionItemId) != null;
}
public static final class VariableAssignment {
private Integer value;
private final int createdAt;
private int updatedAt;
public VariableAssignment(Integer value, int createdAt, int updatedAt) {
this.value = value;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Integer getValue() {
return this.value;
}
public int getCreatedAt() {
return this.createdAt;
}
public int getUpdatedAt() {
return this.updatedAt;
}
public void setValue(Integer value, int updatedAt) {
this.value = value;
this.updatedAt = updatedAt;
}
private VariableAssignment copy() {
return new VariableAssignment(this.value, this.createdAt, this.updatedAt);
}
}
}
@@ -0,0 +1,152 @@
package com.eu.habbo.habbohotel.wired.core;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraContextVariable;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
import com.eu.habbo.messages.outgoing.wired.WiredUserVariablesDataComposer;
import gnu.trove.set.hash.THashSet;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public final class WiredContextVariableSupport {
private WiredContextVariableSupport() {
}
public static List<WiredExtraContextVariable> getDefinitions(Room room) {
List<WiredExtraContextVariable> definitions = new ArrayList<>();
if (room == null || room.getRoomSpecialTypes() == null) {
return definitions;
}
THashSet<InteractionWiredExtra> extras = room.getRoomSpecialTypes().getExtras();
if (extras == null || extras.isEmpty()) {
return definitions;
}
for (InteractionWiredExtra extra : extras) {
if (extra instanceof WiredExtraContextVariable) {
definitions.add((WiredExtraContextVariable) extra);
}
}
definitions.sort(Comparator
.comparing(WiredExtraContextVariable::getVariableName, String.CASE_INSENSITIVE_ORDER)
.thenComparingInt(WiredExtraContextVariable::getId));
return definitions;
}
public static List<WiredVariableDefinitionInfo> createDefinitionInfos(Room room) {
List<WiredVariableDefinitionInfo> definitions = new ArrayList<>();
for (WiredExtraContextVariable definition : getDefinitions(room)) {
if (definition == null || definition.getVariableName() == null || definition.getVariableName().isEmpty()) {
continue;
}
definitions.add(new WiredVariableDefinitionInfo(
definition.getId(),
definition.getVariableName(),
definition.hasValue(),
0,
WiredVariableTextConnectorSupport.isTextConnected(room, definition.getId()),
false));
}
return definitions;
}
public static WiredExtraContextVariable getDefinition(Room room, int definitionItemId) {
if (room == null || room.getRoomSpecialTypes() == null || definitionItemId <= 0) {
return null;
}
InteractionWiredExtra extra = room.getRoomSpecialTypes().getExtra(definitionItemId);
return (extra instanceof WiredExtraContextVariable) ? (WiredExtraContextVariable) extra : null;
}
public static WiredVariableDefinitionInfo getDefinitionInfo(Room room, int definitionItemId) {
WiredExtraContextVariable definition = getDefinition(room, definitionItemId);
if (definition == null) {
return null;
}
return new WiredVariableDefinitionInfo(
definition.getId(),
definition.getVariableName(),
definition.hasValue(),
0,
WiredVariableTextConnectorSupport.isTextConnected(room, definition.getId()),
false);
}
public static boolean hasDefinition(Room room, int definitionItemId) {
return getDefinition(room, definitionItemId) != null;
}
public static boolean assignVariable(WiredContext ctx, Room room, int definitionItemId, Integer value, boolean overrideExisting) {
WiredExtraContextVariable definition = getDefinition(room, definitionItemId);
if (ctx == null || definition == null) {
return false;
}
if (overrideExisting && ctx.contextVariables().hasVariable(definitionItemId)) {
ctx.forkContextVariables();
}
return ctx.contextVariables().assignValue(definitionItemId, definition.hasValue() ? value : null, overrideExisting);
}
public static boolean updateVariableValue(WiredContext ctx, Room room, int definitionItemId, Integer value) {
WiredExtraContextVariable definition = getDefinition(room, definitionItemId);
if (ctx == null || definition == null || !definition.hasValue()) {
return false;
}
return ctx.contextVariables().updateValue(definitionItemId, value);
}
public static boolean removeVariable(WiredContext ctx, Room room, int definitionItemId) {
return ctx != null && getDefinition(room, definitionItemId) != null && ctx.contextVariables().removeValue(definitionItemId);
}
public static boolean hasVariable(WiredContext ctx, int definitionItemId) {
return ctx != null && ctx.contextVariables().hasVariable(definitionItemId);
}
public static Integer getCurrentValue(WiredContext ctx, int definitionItemId) {
return ctx != null ? ctx.contextVariables().getValue(definitionItemId) : null;
}
public static int getCreatedAt(WiredContext ctx, int definitionItemId) {
return ctx != null ? ctx.contextVariables().getCreatedAt(definitionItemId) : 0;
}
public static int getUpdatedAt(WiredContext ctx, int definitionItemId) {
return ctx != null ? ctx.contextVariables().getUpdatedAt(definitionItemId) : 0;
}
public static void broadcastDefinitions(Room room) {
if (room == null) {
return;
}
WiredUserVariablesDataComposer composer = new WiredUserVariablesDataComposer(
room.getUserVariableManager().createSnapshot(),
room.getFurniVariableManager().createSnapshot(),
room.getRoomVariableManager().createSnapshot());
room.getHabbos().forEach(habbo ->
{
if (habbo == null || habbo.getClient() == null) {
return;
}
habbo.getClient().sendResponse(composer);
});
}
}
@@ -5,7 +5,9 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredCondition;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterFurni;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterFurniByVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterUser;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterUsersByVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraExecutionLimit;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraOrEval;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRandom;
@@ -20,6 +22,7 @@ import com.eu.habbo.habbohotel.wired.WiredConditionOperator;
import com.eu.habbo.habbohotel.wired.api.IWiredCondition;
import com.eu.habbo.habbohotel.wired.api.IWiredEffect;
import com.eu.habbo.habbohotel.wired.api.WiredStack;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer;
import com.eu.habbo.plugin.events.furniture.wired.WiredStackExecutedEvent;
@@ -77,6 +80,33 @@ public final class WiredEngine {
/** Duration to ban wired execution in a room after abuse detected (milliseconds) */
public static long WIRED_BAN_DURATION_MS = 600000;
/** Monitor usage window in milliseconds */
public static int MONITOR_USAGE_WINDOW_MS = 1000;
/** Monitor execution cap per room window */
public static int MONITOR_USAGE_LIMIT = 1000;
/** Maximum delayed events allowed per room at the same time */
public static int MONITOR_DELAYED_EVENTS_LIMIT = 100;
/** Average execution threshold that marks overload */
public static int MONITOR_OVERLOAD_AVERAGE_MS = 50;
/** Peak execution threshold that marks overload */
public static int MONITOR_OVERLOAD_PEAK_MS = 150;
/** Consecutive overloaded windows required before recording overload */
public static int MONITOR_OVERLOAD_CONSECUTIVE_WINDOWS = 2;
/** Usage percentage threshold that marks a room as heavy */
public static int MONITOR_HEAVY_USAGE_PERCENT = 70;
/** Consecutive windows above threshold before marking heavy */
public static int MONITOR_HEAVY_CONSECUTIVE_WINDOWS = 5;
/** Delayed queue percentage threshold that contributes to heavy state */
public static int MONITOR_HEAVY_DELAYED_PERCENT = 60;
private final WiredServices services;
private final WiredStackIndex index;
private final int maxStepsPerStack;
@@ -93,6 +123,9 @@ public final class WiredEngine {
/** Track rooms that are banned from wired execution: roomId -> ban expiry timestamp */
private final ConcurrentHashMap<Integer, Long> bannedRooms;
/** Track monitor diagnostics per room */
private final ConcurrentHashMap<Integer, WiredRoomDiagnostics> roomDiagnostics;
/**
* Create a new wired engine.
*
@@ -112,6 +145,7 @@ public final class WiredEngine {
this.roomRecursionDepth = new ConcurrentHashMap<>();
this.eventRateLimiters = new ConcurrentHashMap<>();
this.bannedRooms = new ConcurrentHashMap<>();
this.roomDiagnostics = new ConcurrentHashMap<>();
}
/**
@@ -146,6 +180,12 @@ public final class WiredEngine {
// Check and increment recursion depth to prevent infinite loops
int currentDepth = roomRecursionDepth.getOrDefault(roomId, 0);
if (currentDepth >= MAX_RECURSION_DEPTH) {
getDiagnostics(roomId).recordRecursionTimeout(
System.currentTimeMillis(),
String.format("Recursion depth %d/%d while handling %s", currentDepth, MAX_RECURSION_DEPTH, event.getType().name()),
event.getType().name(),
0
);
LOGGER.warn("Wired recursion limit reached in room {} (depth: {}). " +
"Possible infinite loop detected (e.g., collision + chase). Aborting.", roomId, currentDepth);
debug(room, "RECURSION LIMIT REACHED - aborting to prevent crash");
@@ -215,9 +255,10 @@ public final class WiredEngine {
*/
private boolean processStack(WiredStack stack, WiredEvent event, long currentTime) {
Room room = event.getRoom();
WiredTextInputCaptureSupport.CaptureResult captureResult = resolveTextInputCapture(stack, event);
// Check if trigger matches
if (!stack.trigger().matches(stack.triggerItem(), event)) {
if (!captureResult.matches()) {
return false;
}
@@ -226,13 +267,23 @@ public final class WiredEngine {
return false;
}
if (!stackHasExecutableOutcome(stack, event)) {
return false;
}
// Create execution context with stack reference
WiredState state = new WiredState(maxStepsPerStack);
WiredContext ctx = new WiredContext(event, stack.triggerItem(), stack, services, state, null);
WiredTextInputCaptureSupport.applyToContext(ctx, room, captureResult);
WiredRoomDiagnostics diagnostics = getDiagnostics(room.getId());
// Initial step for trigger
state.step();
int stackCost = estimateStackCost(stack, roomRecursionDepth.getOrDefault(room.getId(), 0));
String monitorSourceLabel = getMonitorSourceLabel(stack.triggerItem(), event);
int monitorSourceId = getMonitorSourceId(stack.triggerItem());
debug(room, "Trigger matched: {} at item {} (conditions: {}, effects: {})",
event.getType(),
stack.triggerItem() != null ? stack.triggerItem().getId() : "null",
@@ -274,6 +325,16 @@ public final class WiredEngine {
return false;
}
if (!diagnostics.tryConsumeExecutionBudget(
stackCost,
currentTime,
monitorSourceLabel,
monitorSourceId,
buildStackMonitorReason(stack, event, stackCost))) {
debug(room, "Execution cap blocked stack {}", stack.triggerItem() != null ? stack.triggerItem().getId() : "null");
return false;
}
if ((event.getType() == WiredEvent.Type.USER_CLICKS_USER)
&& (stack.triggerItem() instanceof WiredTriggerHabboClicksUser)
&& event.getActor().isPresent()) {
@@ -302,14 +363,22 @@ public final class WiredEngine {
// Fire executed event
fireExecutedEvent(stack, event);
diagnostics.recordExecution(
state.elapsedMs(),
System.currentTimeMillis(),
monitorSourceLabel,
monitorSourceId,
buildExecutionMonitorReason(stack, state.elapsedMs())
);
return true;
}
private boolean wouldTriggerStack(WiredStack stack, WiredEvent event, long currentTime) {
Room room = event.getRoom();
WiredTextInputCaptureSupport.CaptureResult captureResult = resolveTextInputCapture(stack, event);
if (!stack.trigger().matches(stack.triggerItem(), event)) {
if (!captureResult.matches()) {
return false;
}
@@ -317,8 +386,13 @@ public final class WiredEngine {
return false;
}
if (!stackHasExecutableOutcome(stack, event)) {
return false;
}
WiredState state = new WiredState(maxStepsPerStack);
WiredContext ctx = new WiredContext(event, stack.triggerItem(), stack, services, state, null);
WiredTextInputCaptureSupport.applyToContext(ctx, room, captureResult);
state.step();
@@ -336,6 +410,43 @@ public final class WiredEngine {
return executionLimitExtra == null || executionLimitExtra.canExecuteAt(currentTime);
}
private boolean stackHasExecutableOutcome(WiredStack stack, WiredEvent event) {
if (stack == null) {
return false;
}
if (stack.hasEffects()) {
return true;
}
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 WiredTextInputCaptureSupport.CaptureResult resolveTextInputCapture(WiredStack stack, WiredEvent event) {
if (stack == null || event == null) {
return WiredTextInputCaptureSupport.CaptureResult.noMatch();
}
if (event.getType() != WiredEvent.Type.USER_SAYS || !(stack.triggerItem() instanceof WiredTriggerHabboSaysKeyword)) {
return stack.trigger().matches(stack.triggerItem(), event)
? WiredTextInputCaptureSupport.CaptureResult.matched(new LinkedHashMap<>())
: WiredTextInputCaptureSupport.CaptureResult.noMatch();
}
return WiredTextInputCaptureSupport.resolve(stack, event);
}
/**
* Evaluate all conditions in a stack.
*/
@@ -462,38 +573,48 @@ public final class WiredEngine {
executeOrderedEffects(regulars, ctx, currentTime);
return;
} else {
// Normal mode: regular effects in random order
// Normal mode: preserve the physical stack order.
// This matches the legacy handler behavior and avoids visual/state races
// for combinations such as Move/Rotate + Match To Snapshot in the same stack.
toExecute = new ArrayList<>(regulars);
Collections.shuffle(toExecute);
}
// Execute selected effects
for (IWiredEffect effect : toExecute) {
// Check if effect requires actor
if (effect.requiresActor() && !ctx.hasActor()) {
continue;
}
WiredMoveCarryHelper.beginMovementCollection();
// Handle delay
int delay = effect.getDelay();
if (delay > 0) {
// Schedule delayed execution
scheduleDelayedEffect(effect, ctx, delay, currentTime);
} else {
// Execute immediately
ctx.state().step();
try {
effect.execute(ctx);
// Activate box animation after execution
if (effect instanceof InteractionWiredEffect) {
InteractionWiredEffect wiredEffect = (InteractionWiredEffect) effect;
wiredEffect.setCooldown(currentTime);
wiredEffect.activateBox(ctx.room(), ctx.actor().orElse(null), currentTime);
}
} catch (Exception e) {
LOGGER.warn("Error executing effect: {}", e.getMessage());
try {
// Execute selected effects
for (IWiredEffect effect : toExecute) {
// Check if effect requires actor
if (effect.requiresActor() && !ctx.hasActor()) {
continue;
}
// Handle delay
int delay = effect.getDelay();
if (delay > 0) {
// Schedule delayed execution
scheduleDelayedEffect(effect, ctx, delay, currentTime);
} else {
// Execute immediately
ctx.state().step();
try {
effect.execute(ctx);
// Activate box animation after execution
if (effect instanceof InteractionWiredEffect) {
InteractionWiredEffect wiredEffect = (InteractionWiredEffect) effect;
wiredEffect.setCooldown(currentTime);
wiredEffect.activateBox(ctx.room(), ctx.actor().orElse(null), currentTime);
}
} catch (Exception e) {
LOGGER.warn("Error executing effect: {}", e.getMessage());
}
}
}
} finally {
ServerMessage movementComposer = WiredMoveCarryHelper.finishMovementCollection();
if (movementComposer != null) {
ctx.room().sendComposer(movementComposer);
}
}
}
@@ -561,21 +682,50 @@ public final class WiredEngine {
int furniLimit = Integer.MAX_VALUE;
int userLimit = Integer.MAX_VALUE;
List<WiredExtraFilterFurniByVariable> furniVariableFilters = new ArrayList<>();
List<WiredExtraFilterUsersByVariable> userVariableFilters = new ArrayList<>();
for (InteractionWiredExtra extra : extras) {
if (extra instanceof WiredExtraFilterFurni) {
furniLimit = Math.min(furniLimit, ((WiredExtraFilterFurni) extra).getAmount());
} else if (extra instanceof WiredExtraFilterUser) {
userLimit = Math.min(userLimit, ((WiredExtraFilterUser) extra).getAmount());
} else if (extra instanceof WiredExtraFilterFurniByVariable) {
furniVariableFilters.add((WiredExtraFilterFurniByVariable) extra);
} else if (extra instanceof WiredExtraFilterUsersByVariable) {
userVariableFilters.add((WiredExtraFilterUsersByVariable) extra);
}
}
if (ctx.targets().isItemsModifiedBySelector() && furniLimit != Integer.MAX_VALUE) {
ctx.targets().setItems(limitIterable(ctx.targets().items(), furniLimit));
furniVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId()));
userVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId()));
if (ctx.targets().isItemsModifiedBySelector()) {
Iterable<HabboItem> filteredItems = ctx.targets().items();
for (WiredExtraFilterFurniByVariable extra : furniVariableFilters) {
filteredItems = extra.filterItems(room, ctx, filteredItems);
}
if (furniLimit != Integer.MAX_VALUE) {
filteredItems = limitIterable(filteredItems, furniLimit);
}
ctx.targets().setItems(filteredItems);
}
if (ctx.targets().isUsersModifiedBySelector() && userLimit != Integer.MAX_VALUE) {
ctx.targets().setUsers(limitIterable(ctx.targets().users(), userLimit));
if (ctx.targets().isUsersModifiedBySelector()) {
Iterable<RoomUnit> filteredUsers = ctx.targets().users();
for (WiredExtraFilterUsersByVariable extra : userVariableFilters) {
filteredUsers = extra.filterUsers(room, ctx, filteredUsers);
}
if (userLimit != Integer.MAX_VALUE) {
filteredUsers = limitIterable(filteredUsers, userLimit);
}
ctx.targets().setUsers(filteredUsers);
}
}
@@ -604,6 +754,19 @@ public final class WiredEngine {
* Schedule a delayed effect execution.
*/
private void scheduleDelayedEffect(IWiredEffect effect, WiredContext ctx, int delay, long triggerTime) {
WiredRoomDiagnostics diagnostics = getDiagnostics(ctx.room().getId());
String sourceLabel = getMonitorSourceLabel(ctx.triggerItem(), ctx.event());
int sourceId = getMonitorSourceId(ctx.triggerItem());
if (!diagnostics.tryScheduleDelayedEvent(
System.currentTimeMillis(),
sourceLabel,
sourceId,
String.format("Scheduling delayed effect %s with delay %d tick(s)", effect.getClass().getSimpleName(), delay))) {
debug(ctx.room(), "Delayed events cap blocked effect {}", effect.getClass().getSimpleName());
return;
}
// Delay is in 500ms ticks
long delayMs = delay * 500L;
long elapsedSinceTrigger = Math.max(0L, System.currentTimeMillis() - triggerTime);
@@ -613,6 +776,7 @@ public final class WiredEngine {
Emulator.getThreading().run(() -> {
if (!room.isLoaded() || room.getHabbos().isEmpty()) {
diagnostics.completeDelayedEvent();
return;
}
@@ -627,6 +791,8 @@ public final class WiredEngine {
}
} catch (Exception e) {
LOGGER.warn("Error executing delayed effect: {}", e.getMessage());
} finally {
diagnostics.completeDelayedEvent();
}
}, remainingDelayMs);
}
@@ -712,6 +878,19 @@ public final class WiredEngine {
}
private void scheduleOrderedEffectBatch(List<IWiredEffect> batch, WiredContext ctx, int delay, long triggerTime) {
WiredRoomDiagnostics diagnostics = getDiagnostics(ctx.room().getId());
String sourceLabel = getMonitorSourceLabel(ctx.triggerItem(), ctx.event());
int sourceId = getMonitorSourceId(ctx.triggerItem());
if (!diagnostics.tryScheduleDelayedEvent(
System.currentTimeMillis(),
sourceLabel,
sourceId,
String.format("Scheduling ordered batch with %d effect(s) and delay %d tick(s)", batch.size(), delay))) {
debug(ctx.room(), "Delayed events cap blocked ordered batch with {} effect(s)", batch.size());
return;
}
long delayMs = delay * 500L;
long elapsedSinceTrigger = Math.max(0L, System.currentTimeMillis() - triggerTime);
long remainingDelayMs = Math.max(0L, delayMs - elapsedSinceTrigger);
@@ -719,10 +898,15 @@ public final class WiredEngine {
Emulator.getThreading().run(() -> {
if (!room.isLoaded() || room.getHabbos().isEmpty()) {
diagnostics.completeDelayedEvent();
return;
}
executeOrderedEffectBatch(batch, ctx, System.currentTimeMillis(), true);
try {
executeOrderedEffectBatch(batch, ctx, System.currentTimeMillis(), true);
} finally {
diagnostics.completeDelayedEvent();
}
}, remainingDelayMs);
}
@@ -730,21 +914,30 @@ public final class WiredEngine {
Room room = ctx.room();
RoomUnit actor = ctx.actor().orElse(null);
for (IWiredEffect effect : batch) {
try {
if (!useExecutionTimeForCooldown) {
ctx.state().step();
}
WiredMoveCarryHelper.beginMovementCollection();
effect.execute(ctx);
try {
for (IWiredEffect effect : batch) {
try {
if (!useExecutionTimeForCooldown) {
ctx.state().step();
}
if (effect instanceof InteractionWiredEffect) {
InteractionWiredEffect wiredEffect = (InteractionWiredEffect) effect;
wiredEffect.setCooldown(executionTime);
wiredEffect.activateBox(room, actor, executionTime);
effect.execute(ctx);
if (effect instanceof InteractionWiredEffect) {
InteractionWiredEffect wiredEffect = (InteractionWiredEffect) effect;
wiredEffect.setCooldown(executionTime);
wiredEffect.activateBox(room, actor, executionTime);
}
} catch (Exception e) {
LOGGER.warn("Error executing ordered effect batch item: {}", e.getMessage());
}
} catch (Exception e) {
LOGGER.warn("Error executing ordered effect batch item: {}", e.getMessage());
}
} finally {
ServerMessage movementComposer = WiredMoveCarryHelper.finishMovementCollection();
if (movementComposer != null) {
room.sendComposer(movementComposer);
}
}
}
@@ -961,6 +1154,29 @@ public final class WiredEngine {
String prefix = roomId + ":";
eventRateLimiters.keySet().removeIf(key -> key.startsWith(prefix));
}
/**
* Clear monitor diagnostics for a specific room.
* @param roomId the room ID
*/
public void clearRoomDiagnostics(int roomId) {
roomDiagnostics.remove(roomId);
}
/**
* Clear all monitor diagnostics.
*/
public void clearAllDiagnostics() {
roomDiagnostics.clear();
}
public void clearRoomDiagnosticsLogs(int roomId) {
WiredRoomDiagnostics diagnostics = roomDiagnostics.get(roomId);
if (diagnostics != null) {
diagnostics.clearLogs();
}
}
/**
* Clear room ban for a specific room.
@@ -970,6 +1186,23 @@ public final class WiredEngine {
public void clearRoomBan(int roomId) {
bannedRooms.remove(roomId);
}
/**
* Get a monitor snapshot for a room.
* @param roomId the room ID
* @return the diagnostics snapshot
*/
public WiredRoomDiagnostics.Snapshot getDiagnosticsSnapshot(int roomId) {
long now = System.currentTimeMillis();
long killedUntil = bannedRooms.getOrDefault(roomId, 0L);
return getDiagnostics(roomId).snapshot(
getRecursionDepth(roomId),
MAX_RECURSION_DEPTH,
killedUntil,
now
);
}
/**
* Check if a room is currently banned from wired execution.
@@ -997,9 +1230,15 @@ public final class WiredEngine {
* @param roomId the room ID
* @param room the room object (for sending alerts)
*/
private void banRoom(int roomId, Room room) {
private void banRoom(int roomId, Room room, WiredEvent.Type eventType, int eventCount) {
long banExpiry = System.currentTimeMillis() + WIRED_BAN_DURATION_MS;
bannedRooms.put(roomId, banExpiry);
getDiagnostics(roomId).recordKilled(
System.currentTimeMillis(),
String.format("Rate limit exceeded for %s with %d event(s) in %dms", eventType.name(), eventCount, RATE_LIMIT_WINDOW_MS),
eventType.name(),
0
);
long banMinutes = WIRED_BAN_DURATION_MS / 60000;
@@ -1049,10 +1288,109 @@ public final class WiredEngine {
boolean limited = tracker.isRateLimited(now);
if (limited && tracker.shouldBan(now)) {
// First time hitting limit in this suppression window - ban the room
banRoom(roomId, room);
banRoom(roomId, room, eventType, tracker.getEventCount());
}
return limited;
}
private WiredRoomDiagnostics getDiagnostics(int roomId) {
return roomDiagnostics.computeIfAbsent(roomId, ignored -> new WiredRoomDiagnostics(
MONITOR_USAGE_WINDOW_MS,
MONITOR_USAGE_LIMIT,
MONITOR_DELAYED_EVENTS_LIMIT,
MONITOR_OVERLOAD_AVERAGE_MS,
MONITOR_OVERLOAD_PEAK_MS,
MONITOR_HEAVY_USAGE_PERCENT,
MONITOR_HEAVY_CONSECUTIVE_WINDOWS,
MONITOR_OVERLOAD_CONSECUTIVE_WINDOWS,
MONITOR_HEAVY_DELAYED_PERCENT,
200
));
}
private int estimateStackCost(WiredStack stack, int recursionDepth) {
int cost = 1;
if (stack == null) {
return cost;
}
cost += Math.max(0, stack.conditions().size());
for (IWiredEffect effect : stack.effects()) {
if (effect == null) {
continue;
}
cost += effect.isSelector() ? 2 : 3;
if (effect.getDelay() > 0) {
cost += 4;
}
}
cost += Math.max(0, recursionDepth) * 2;
return Math.max(1, cost);
}
private String getMonitorSourceLabel(HabboItem triggerItem, WiredEvent event) {
if (triggerItem != null && triggerItem.getBaseItem() != null && triggerItem.getBaseItem().getInteractionType() != null) {
return triggerItem.getBaseItem().getInteractionType().getName();
}
return (event != null && event.getType() != null) ? event.getType().name() : "room";
}
private int getMonitorSourceId(HabboItem triggerItem) {
return triggerItem != null ? triggerItem.getId() : 0;
}
private String buildStackMonitorReason(WiredStack stack, WiredEvent event, int stackCost) {
if (stack == null) {
return String.format("Processing %s with estimated cost %d", event.getType().name(), stackCost);
}
int selectors = 0;
int delayedEffects = 0;
for (IWiredEffect effect : stack.effects()) {
if (effect == null) {
continue;
}
if (effect.isSelector()) {
selectors++;
}
if (effect.getDelay() > 0) {
delayedEffects++;
}
}
return String.format(
"Trigger %s with %d condition(s), %d effect(s), %d selector(s), %d delayed effect(s) and estimated cost %d",
event.getType().name(),
stack.conditions().size(),
stack.effects().size(),
selectors,
delayedEffects,
stackCost
);
}
private String buildExecutionMonitorReason(WiredStack stack, long elapsedMs) {
if (stack == null) {
return String.format("Execution completed in %dms", elapsedMs);
}
return String.format(
"Stack with %d condition(s) and %d effect(s) completed in %dms",
stack.conditions().size(),
stack.effects().size(),
elapsedMs
);
}
/**
* Tracks event rate for a specific room + event type combination.
@@ -1094,5 +1432,9 @@ public final class WiredEngine {
}
return false;
}
synchronized int getEventCount() {
return eventCount;
}
}
}
@@ -66,6 +66,9 @@ public final class WiredEvent {
/** Up-counter reaches a configured elapsed time */
CLOCK_COUNTER_REACHED(WiredTriggerType.CLOCK_COUNTER),
/** A user, furni or global variable changed */
VARIABLE_CHANGED(WiredTriggerType.VARIABLE_CHANGED),
/** Long timer repeat */
TIMER_REPEAT_LONG(WiredTriggerType.PERIODICALLY_LONG),
@@ -150,6 +153,13 @@ public final class WiredEvent {
}
}
public enum VariableChangeKind {
NONE,
INCREASED,
DECREASED,
UNCHANGED
}
private final Type type;
private final Room room;
private final RoomUnit actor; // nullable - the user/bot that caused the event
@@ -164,6 +174,16 @@ public final class WiredEvent {
private final int signalChannel; // channel for signal routing (0-based)
private final int actionId; // user action id for USER_PERFORMS_ACTION
private final int actionParameter; // sign/dance parameter when relevant
private final int chatType; // RoomChatType metadata for USER_SAYS
private final int chatStyle; // bubble style for USER_SAYS
private final int signalUserCount; // forwarded users in SIGNAL_RECEIVED
private final int signalFurniCount; // forwarded furni in SIGNAL_RECEIVED
private final int variableTargetType;
private final int variableDefinitionItemId;
private final boolean variableCreated;
private final boolean variableDeleted;
private final VariableChangeKind variableChangeKind;
private final WiredContextVariableScope contextVariableScope;
private final long createdAtMs;
private WiredEvent(Builder builder) {
@@ -181,6 +201,16 @@ public final class WiredEvent {
this.signalChannel = builder.signalChannel;
this.actionId = builder.actionId;
this.actionParameter = builder.actionParameter;
this.chatType = builder.chatType;
this.chatStyle = builder.chatStyle;
this.signalUserCount = builder.signalUserCount;
this.signalFurniCount = builder.signalFurniCount;
this.variableTargetType = builder.variableTargetType;
this.variableDefinitionItemId = builder.variableDefinitionItemId;
this.variableCreated = builder.variableCreated;
this.variableDeleted = builder.variableDeleted;
this.variableChangeKind = builder.variableChangeKind;
this.contextVariableScope = builder.contextVariableScope;
this.createdAtMs = builder.createdAtMs;
}
@@ -291,6 +321,46 @@ public final class WiredEvent {
return actionParameter;
}
public int getChatType() {
return chatType;
}
public int getChatStyle() {
return chatStyle;
}
public int getSignalUserCount() {
return signalUserCount;
}
public int getSignalFurniCount() {
return signalFurniCount;
}
public int getVariableTargetType() {
return variableTargetType;
}
public int getVariableDefinitionItemId() {
return variableDefinitionItemId;
}
public boolean isVariableCreated() {
return variableCreated;
}
public boolean isVariableDeleted() {
return variableDeleted;
}
public VariableChangeKind getVariableChangeKind() {
return variableChangeKind;
}
public WiredContextVariableScope getContextVariableScope() {
return contextVariableScope;
}
/**
* Get the timestamp when this event was created.
* @return milliseconds since epoch
@@ -348,6 +418,16 @@ public final class WiredEvent {
private int signalChannel;
private int actionId;
private int actionParameter = -1;
private int chatType = -1;
private int chatStyle = -1;
private int signalUserCount;
private int signalFurniCount;
private int variableTargetType = -1;
private int variableDefinitionItemId;
private boolean variableCreated;
private boolean variableDeleted;
private VariableChangeKind variableChangeKind = VariableChangeKind.NONE;
private WiredContextVariableScope contextVariableScope;
private long createdAtMs = System.currentTimeMillis();
private Builder(Type type, Room room) {
@@ -462,6 +542,56 @@ public final class WiredEvent {
return this;
}
public Builder chatType(int chatType) {
this.chatType = chatType;
return this;
}
public Builder chatStyle(int chatStyle) {
this.chatStyle = chatStyle;
return this;
}
public Builder signalUserCount(int signalUserCount) {
this.signalUserCount = Math.max(0, signalUserCount);
return this;
}
public Builder signalFurniCount(int signalFurniCount) {
this.signalFurniCount = Math.max(0, signalFurniCount);
return this;
}
public Builder variableTargetType(int variableTargetType) {
this.variableTargetType = variableTargetType;
return this;
}
public Builder variableDefinitionItemId(int variableDefinitionItemId) {
this.variableDefinitionItemId = Math.max(0, variableDefinitionItemId);
return this;
}
public Builder variableCreated(boolean variableCreated) {
this.variableCreated = variableCreated;
return this;
}
public Builder variableDeleted(boolean variableDeleted) {
this.variableDeleted = variableDeleted;
return this;
}
public Builder variableChangeKind(VariableChangeKind variableChangeKind) {
this.variableChangeKind = (variableChangeKind != null) ? variableChangeKind : VariableChangeKind.NONE;
return this;
}
public Builder contextVariableScope(WiredContextVariableScope contextVariableScope) {
this.contextVariableScope = contextVariableScope;
return this;
}
/**
* Set a custom creation timestamp.
* @param createdAtMs milliseconds since epoch
@@ -0,0 +1,554 @@
package com.eu.habbo.habbohotel.wired.core;
import com.eu.habbo.habbohotel.bots.Bot;
import com.eu.habbo.habbohotel.games.Game;
import com.eu.habbo.habbohotel.games.GamePlayer;
import com.eu.habbo.habbohotel.games.GameTeam;
import com.eu.habbo.habbohotel.games.GameTeamColors;
import com.eu.habbo.habbohotel.items.FurnitureType;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.rooms.FurnitureMovementError;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomRightLevels;
import com.eu.habbo.habbohotel.rooms.RoomTile;
import com.eu.habbo.habbohotel.rooms.RoomTileState;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.rooms.RoomUnitStatus;
import com.eu.habbo.habbohotel.rooms.RoomUserRotation;
import com.eu.habbo.habbohotel.users.DanceType;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboGender;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.util.HotelDateTimeUtil;
import java.time.ZonedDateTime;
import java.time.temporal.WeekFields;
import java.util.Locale;
public final class WiredInternalVariableSupport {
private WiredInternalVariableSupport() {
}
public static String normalizeKey(String key) {
if (key == null) {
return "";
}
String normalized = key.trim();
return switch (normalized) {
case "@position.x" -> "@position_x";
case "@position.y" -> "@position_y";
case "@effect" -> "@effect_id";
case "@handitems" -> "@handitem_id";
case "@is_mute" -> "@is_muted";
case "@teams.red.score" -> "@team_red_score";
case "@teams.green.score" -> "@team_green_score";
case "@teams.blue.score" -> "@team_blue_score";
case "@teams.yellow.score" -> "@team_yellow_score";
case "@teams.red.size" -> "@team_red_size";
case "@teams.green.size" -> "@team_green_size";
case "@teams.blue.size" -> "@team_blue_size";
case "@teams.yellow.size" -> "@team_yellow_size";
default -> normalized;
};
}
public static boolean canUseUserDestination(String key) {
String normalized = normalizeKey(key);
return "@position_x".equals(normalized) || "@position_y".equals(normalized) || "@direction".equals(normalized);
}
public static boolean canUseFurniDestination(String key) {
String normalized = normalizeKey(key);
return "@state".equals(normalized) || "@position_x".equals(normalized) || "@position_y".equals(normalized)
|| "@rotation".equals(normalized) || "@altitude".equals(normalized);
}
public static boolean canUseUserReference(String key) {
String normalized = normalizeKey(key);
return "@index".equals(normalized) || "@type".equals(normalized) || "@gender".equals(normalized)
|| "@level".equals(normalized) || "@achievement_score".equals(normalized) || "@is_hc".equals(normalized)
|| "@has_rights".equals(normalized) || "@is_group_admin".equals(normalized) || "@is_owner".equals(normalized)
|| "@is_muted".equals(normalized) || "@is_trading".equals(normalized) || "@is_frozen".equals(normalized)
|| "@effect_id".equals(normalized) || "@team_score".equals(normalized) || "@team_color".equals(normalized)
|| "@team_type".equals(normalized) || "@sign".equals(normalized) || "@dance".equals(normalized)
|| "@is_idle".equals(normalized) || "@handitem_id".equals(normalized) || "@position_x".equals(normalized)
|| "@position_y".equals(normalized) || "@direction".equals(normalized) || "@altitude".equals(normalized)
|| "@favourite_group_id".equals(normalized) || "@room_entry.method".equals(normalized)
|| "@room_entry.teleport_id".equals(normalized) || "@user_id".equals(normalized)
|| "@bot_id".equals(normalized) || "@pet_id".equals(normalized) || "@pet_owner_id".equals(normalized);
}
public static boolean canUseFurniReference(String key) {
String normalized = normalizeKey(key);
return "~teleport.target_id".equals(normalized) || "@id".equals(normalized) || "@class_id".equals(normalized)
|| "@height".equals(normalized) || "@state".equals(normalized) || "@position_x".equals(normalized)
|| "@position_y".equals(normalized) || "@rotation".equals(normalized) || "@altitude".equals(normalized)
|| "@is_invisible".equals(normalized) || "@type".equals(normalized) || "@is_stackable".equals(normalized)
|| "@can_stand_on".equals(normalized) || "@can_sit_on".equals(normalized) || "@can_lay_on".equals(normalized)
|| "@owner_id".equals(normalized) || "@wallitem_offset".equals(normalized)
|| "@dimensions.x".equals(normalized) || "@dimensions.y".equals(normalized);
}
public static boolean canUseRoomReference(String key) {
String normalized = normalizeKey(key);
return "@furni_count".equals(normalized) || "@user_count".equals(normalized) || "@wired_timer".equals(normalized)
|| "@team_red_score".equals(normalized) || "@team_green_score".equals(normalized) || "@team_blue_score".equals(normalized)
|| "@team_yellow_score".equals(normalized) || "@team_red_size".equals(normalized) || "@team_green_size".equals(normalized)
|| "@team_blue_size".equals(normalized) || "@team_yellow_size".equals(normalized) || "@room_id".equals(normalized)
|| "@group_id".equals(normalized) || "@timezone_server".equals(normalized) || "@timezone_client".equals(normalized)
|| "@current_time".equals(normalized) || "@current_time.millisecond_of_second".equals(normalized)
|| "@current_time.seconds_of_minute".equals(normalized) || "@current_time.minute_of_hour".equals(normalized)
|| "@current_time.hour_of_day".equals(normalized) || "@current_time.day_of_week".equals(normalized)
|| "@current_time.day_of_month".equals(normalized) || "@current_time.day_of_year".equals(normalized)
|| "@current_time.week_of_year".equals(normalized) || "@current_time.month_of_year".equals(normalized)
|| "@current_time.year".equals(normalized);
}
public static boolean canUseContextReference(String key) {
String normalized = normalizeKey(key);
return "@selector_furni_count".equals(normalized) || "@selector_user_count".equals(normalized)
|| "@signal_furni_count".equals(normalized) || "@signal_user_count".equals(normalized)
|| "@antenna_id".equals(normalized) || "@chat_type".equals(normalized) || "@chat_style".equals(normalized);
}
public static boolean hasUserValue(Room room, RoomUnit roomUnit, String key) {
if (room == null || roomUnit == null) {
return false;
}
Habbo habbo = room.getHabbo(roomUnit);
Bot bot = room.getBot(roomUnit);
Pet pet = room.getPet(roomUnit);
String normalized = normalizeKey(key);
return switch (normalized) {
case "@index", "@type", "@level", "@achievement_score", "@position_x", "@position_y", "@direction", "@altitude" -> true;
case "@gender" -> habbo != null || bot != null;
case "@is_hc" -> habbo != null && habbo.getHabboStats().hasActiveClub();
case "@has_rights" -> habbo != null && (room.hasRights(habbo) || room.getGuildRightLevel(habbo).isEqualOrGreaterThan(RoomRightLevels.GUILD_RIGHTS));
case "@is_group_admin" -> habbo != null && room.getGuildRightLevel(habbo).isEqualOrGreaterThan(RoomRightLevels.GUILD_ADMIN);
case "@is_owner" -> habbo != null && room.isOwner(habbo);
case "@is_muted" -> (habbo != null && room.isMuted(habbo)) || (pet != null && pet.isMuted());
case "@is_trading" -> habbo != null && room.getActiveTradeForHabbo(habbo) != null;
case "@is_frozen" -> WiredFreezeUtil.isFrozen(roomUnit);
case "@effect_id" -> roomUnit.getEffectId() > 0;
case "@team_score", "@team_color", "@team_type" -> getTeamEffectData(roomUnit.getEffectId()) != null;
case "@sign" -> roomUnit.hasStatus(RoomUnitStatus.SIGN);
case "@dance" -> roomUnit.getDanceType() != null && roomUnit.getDanceType() != DanceType.NONE;
case "@is_idle" -> roomUnit.isIdle();
case "@handitem_id" -> roomUnit.getHandItem() > 0;
case "@favourite_group_id" -> habbo != null && habbo.getHabboStats().guild > 0;
case "@room_entry.method" -> habbo != null && hasRoomEntryMethod(habbo);
case "@room_entry.teleport_id" -> habbo != null && habbo.getHabboInfo().getRoomEntryTeleportId() > 0;
case "@user_id" -> habbo != null;
case "@bot_id" -> bot != null;
case "@pet_id" -> pet != null;
case "@pet_owner_id" -> pet != null && pet.getUserId() > 0;
default -> false;
};
}
public static boolean hasFurniValue(HabboItem item, String key) {
if (item == null || item.getBaseItem() == null) {
return false;
}
String normalized = normalizeKey(key);
return switch (normalized) {
case "@id", "@class_id", "@height", "@state", "@position_x", "@position_y", "@rotation", "@altitude",
"@is_invisible", "@type", "@owner_id", "@dimensions.x", "@dimensions.y" -> true;
case "~teleport.target_id" -> item.getTeleportTargetId() > 0;
case "@wallitem_offset" -> item.getBaseItem().getType() == FurnitureType.WALL;
case "@is_stackable" -> item.getBaseItem().allowStack();
case "@can_stand_on" -> item.getBaseItem().allowWalk();
case "@can_sit_on" -> item.getBaseItem().allowSit();
case "@can_lay_on" -> item.getBaseItem().allowLay();
default -> false;
};
}
public static boolean hasRoomValue(Room room, String key) {
return room != null && canUseRoomReference(key);
}
public static Integer readUserValue(Room room, RoomUnit roomUnit, String key) {
if (room == null || roomUnit == null) {
return null;
}
Habbo habbo = room.getHabbo(roomUnit);
Bot bot = room.getBot(roomUnit);
Pet pet = room.getPet(roomUnit);
String normalized = normalizeKey(key);
return switch (normalized) {
case "@index" -> roomUnit.getId();
case "@type" -> getUserTypeValue(habbo, bot, pet);
case "@gender" -> getGenderValue(habbo, bot);
case "@level" -> (roomUnit.getRightsLevel() != null) ? roomUnit.getRightsLevel().level : 0;
case "@achievement_score" -> (habbo != null) ? habbo.getHabboStats().getAchievementScore() : null;
case "@is_hc" -> (habbo != null && habbo.getHabboStats().hasActiveClub()) ? 1 : 0;
case "@has_rights" -> (habbo != null && (room.hasRights(habbo) || room.getGuildRightLevel(habbo).isEqualOrGreaterThan(RoomRightLevels.GUILD_RIGHTS))) ? 1 : 0;
case "@is_group_admin" -> (habbo != null && room.getGuildRightLevel(habbo).isEqualOrGreaterThan(RoomRightLevels.GUILD_ADMIN)) ? 1 : 0;
case "@is_owner" -> (habbo != null && room.isOwner(habbo)) ? 1 : 0;
case "@is_muted" -> ((habbo != null && room.isMuted(habbo)) || (pet != null && pet.isMuted())) ? 1 : 0;
case "@is_trading" -> (habbo != null && room.getActiveTradeForHabbo(habbo) != null) ? 1 : 0;
case "@is_frozen" -> WiredFreezeUtil.isFrozen(roomUnit) ? 1 : 0;
case "@effect_id" -> roomUnit.getEffectId();
case "@team_score" -> getUserTeamScore(room, habbo);
case "@team_color" -> getTeamColorId(roomUnit.getEffectId());
case "@team_type" -> getTeamTypeId(roomUnit.getEffectId());
case "@sign" -> parseStatusInteger(roomUnit, RoomUnitStatus.SIGN);
case "@dance" -> (roomUnit.getDanceType() != null) ? roomUnit.getDanceType().getType() : 0;
case "@is_idle" -> roomUnit.isIdle() ? 1 : 0;
case "@handitem_id" -> roomUnit.getHandItem();
case "@position_x" -> (int) roomUnit.getX();
case "@position_y" -> (int) roomUnit.getY();
case "@direction" -> (roomUnit.getBodyRotation() != null) ? (int) roomUnit.getBodyRotation().getValue() : 0;
case "@altitude" -> (int) Math.round(roomUnit.getZ() * 100);
case "@favourite_group_id" -> (habbo != null) ? habbo.getHabboStats().guild : null;
case "@room_entry.method" -> getRoomEntryMethodValue(habbo);
case "@room_entry.teleport_id" -> (habbo != null) ? habbo.getHabboInfo().getRoomEntryTeleportId() : null;
case "@user_id" -> (habbo != null) ? habbo.getHabboInfo().getId() : null;
case "@bot_id" -> (bot != null) ? bot.getId() : null;
case "@pet_id" -> (pet != null) ? pet.getId() : null;
case "@pet_owner_id" -> (pet != null) ? pet.getUserId() : null;
default -> null;
};
}
public static boolean writeUserValue(Room room, RoomUnit roomUnit, String key, int value) {
if (room == null || roomUnit == null) {
return false;
}
String normalized = normalizeKey(key);
return switch (normalized) {
case "@position_x" -> moveUserTo(room, roomUnit, value, roomUnit.getY());
case "@position_y" -> moveUserTo(room, roomUnit, roomUnit.getX(), value);
case "@direction" -> {
RoomUserRotation rotation = RoomUserRotation.fromValue(value);
yield WiredUserMovementHelper.updateUserDirection(room, roomUnit, rotation, rotation);
}
default -> false;
};
}
public static Integer readFurniValue(Room room, HabboItem item, String key) {
if (room == null || item == null) {
return null;
}
String normalized = normalizeKey(key);
return switch (normalized) {
case "~teleport.target_id" -> item.getTeleportTargetId();
case "@id" -> item.getId();
case "@class_id" -> (item.getBaseItem() != null) ? item.getBaseItem().getId() : null;
case "@height" -> (item.getBaseItem() != null) ? (int) Math.round(item.getBaseItem().getHeight() * 100) : null;
case "@state" -> parseInteger(item.getExtradata());
case "@position_x" -> (int) item.getX();
case "@position_y" -> (int) item.getY();
case "@rotation" -> item.getRotation();
case "@altitude" -> (int) Math.round(item.getZ() * 100);
case "@is_invisible" -> 0;
case "@type" -> 0;
case "@is_stackable" -> (item.getBaseItem() != null && item.getBaseItem().allowStack()) ? 1 : 0;
case "@can_stand_on" -> (item.getBaseItem() != null && item.getBaseItem().allowWalk()) ? 1 : 0;
case "@can_sit_on" -> (item.getBaseItem() != null && item.getBaseItem().allowSit()) ? 1 : 0;
case "@can_lay_on" -> (item.getBaseItem() != null && item.getBaseItem().allowLay()) ? 1 : 0;
case "@wallitem_offset" -> ((item.getBaseItem() != null) && item.getBaseItem().getType() == FurnitureType.WALL && item.getWallPosition() != null && !item.getWallPosition().trim().isEmpty()) ? 1 : 0;
case "@dimensions.x" -> (item.getBaseItem() != null) ? (int) item.getBaseItem().getWidth() : null;
case "@dimensions.y" -> (item.getBaseItem() != null) ? (int) item.getBaseItem().getLength() : null;
case "@owner_id" -> item.getUserId();
default -> null;
};
}
public static boolean writeFurniValue(Room room, HabboItem item, String key, int value) {
if (room == null || item == null) {
return false;
}
String normalized = normalizeKey(key);
if ("@state".equals(normalized)) {
item.setExtradata(String.valueOf(value));
room.updateItemState(item);
return true;
}
if (item.getBaseItem() == null || item.getBaseItem().getType() != FurnitureType.FLOOR) {
return false;
}
return switch (normalized) {
case "@position_x" -> moveFurniTo(room, item, value, item.getY(), item.getRotation(), item.getZ());
case "@position_y" -> moveFurniTo(room, item, item.getX(), value, item.getRotation(), item.getZ());
case "@rotation" -> moveFurniTo(room, item, item.getX(), item.getY(), value, item.getZ());
case "@altitude" -> moveFurniTo(room, item, item.getX(), item.getY(), item.getRotation(), value / 100.0);
default -> false;
};
}
public static Integer readRoomValue(Room room, String key) {
if (room == null) {
return null;
}
ZonedDateTime now = HotelDateTimeUtil.now();
String normalized = normalizeKey(key);
return switch (normalized) {
case "@furni_count" -> room.getFloorItems().size() + room.getWallItems().size();
case "@user_count" -> room.getUserCount();
case "@wired_timer" -> (int) (WiredManager.getTickService().getTickCount() / 10L);
case "@team_red_score" -> getTeamMetric(room, GameTeamColors.RED, true);
case "@team_green_score" -> getTeamMetric(room, GameTeamColors.GREEN, true);
case "@team_blue_score" -> getTeamMetric(room, GameTeamColors.BLUE, true);
case "@team_yellow_score" -> getTeamMetric(room, GameTeamColors.YELLOW, true);
case "@team_red_size" -> getTeamMetric(room, GameTeamColors.RED, false);
case "@team_green_size" -> getTeamMetric(room, GameTeamColors.GREEN, false);
case "@team_blue_size" -> getTeamMetric(room, GameTeamColors.BLUE, false);
case "@team_yellow_size" -> getTeamMetric(room, GameTeamColors.YELLOW, false);
case "@room_id" -> room.getId();
case "@group_id" -> room.getGuildId();
case "@timezone_server" -> now.getOffset().getTotalSeconds() / 60;
case "@timezone_client" -> 0;
case "@current_time" -> (int) now.toEpochSecond();
case "@current_time.millisecond_of_second" -> now.getNano() / 1_000_000;
case "@current_time.seconds_of_minute" -> now.getSecond();
case "@current_time.minute_of_hour" -> now.getMinute();
case "@current_time.hour_of_day" -> now.getHour();
case "@current_time.day_of_week" -> now.getDayOfWeek().getValue();
case "@current_time.day_of_month" -> now.getDayOfMonth();
case "@current_time.day_of_year" -> now.getDayOfYear();
case "@current_time.week_of_year" -> now.get(WeekFields.of(Locale.ITALY).weekOfWeekBasedYear());
case "@current_time.month_of_year" -> now.getMonthValue();
case "@current_time.year" -> now.getYear();
default -> null;
};
}
public static Integer readContextValue(WiredContext ctx, String key) {
if (ctx == null) {
return null;
}
String normalized = normalizeKey(key);
return switch (normalized) {
case "@selector_furni_count" -> countIterable(ctx.targets() != null ? ctx.targets().items() : null);
case "@selector_user_count" -> countIterable(ctx.targets() != null ? ctx.targets().users() : null);
case "@signal_furni_count" -> ctx.event().getSignalFurniCount();
case "@signal_user_count" -> ctx.event().getSignalUserCount();
case "@antenna_id" -> ctx.event().getSignalChannel();
case "@chat_type" -> ctx.event().getChatType();
case "@chat_style" -> ctx.event().getChatStyle();
default -> null;
};
}
private static Integer getUserTypeValue(Habbo habbo, Bot bot, Pet pet) {
if (habbo != null) return 1;
if (pet != null) return 2;
if (bot != null) return 4;
return null;
}
private static Integer getGenderValue(Habbo habbo, Bot bot) {
HabboGender gender = null;
if (habbo != null && habbo.getHabboInfo() != null) {
gender = habbo.getHabboInfo().getGender();
} else if (bot != null) {
gender = bot.getGender();
}
if (gender == null) {
return -1;
}
return (gender == HabboGender.F) ? 1 : 0;
}
private static Integer getUserTeamScore(Room room, Habbo habbo) {
if (room == null || habbo == null || habbo.getHabboInfo() == null || habbo.getHabboInfo().getGamePlayer() == null) {
return null;
}
Game game = resolveTeamGame(room, habbo);
GamePlayer gamePlayer = habbo.getHabboInfo().getGamePlayer();
if (game == null || gamePlayer.getTeamColor() == null) {
return gamePlayer.getScore();
}
GameTeam team = game.getTeam(gamePlayer.getTeamColor());
return (team != null) ? team.getTotalScore() : gamePlayer.getScore();
}
private static Integer getTeamColorId(int effectId) {
TeamEffectData data = getTeamEffectData(effectId);
return (data != null) ? data.colorId : null;
}
private static Integer getTeamTypeId(int effectId) {
TeamEffectData data = getTeamEffectData(effectId);
return (data != null) ? data.typeId : null;
}
private static TeamEffectData getTeamEffectData(int effectId) {
if (effectId <= 0) {
return null;
}
if (effectId >= 223 && effectId <= 226) return new TeamEffectData(effectId - 222, 0);
if (effectId >= 33 && effectId <= 36) return new TeamEffectData(effectId - 32, 1);
if (effectId >= 40 && effectId <= 43) return new TeamEffectData(effectId - 39, 2);
return null;
}
private static int getTeamMetric(Room room, GameTeamColors color, boolean score) {
Game game = resolveTeamGame(room, null);
if (game == null || color == null) {
return 0;
}
GameTeam team = game.getTeam(color);
if (team == null) {
return 0;
}
return score ? team.getTotalScore() : team.getMembers().size();
}
private static Game resolveTeamGame(Room room, Habbo habbo) {
if (room == null) {
return null;
}
if (habbo != null && habbo.getHabboInfo() != null && habbo.getHabboInfo().getCurrentGame() != null) {
Game game = room.getGame(habbo.getHabboInfo().getCurrentGame());
if (game != null) {
return game;
}
}
Game game = room.getGame(com.eu.habbo.habbohotel.games.battlebanzai.BattleBanzaiGame.class);
if (game != null) {
return game;
}
game = room.getGame(com.eu.habbo.habbohotel.games.freeze.FreezeGame.class);
if (game != null) {
return game;
}
return room.getGame(com.eu.habbo.habbohotel.games.wired.WiredGame.class);
}
private static boolean hasRoomEntryMethod(Habbo habbo) {
if (habbo == null || habbo.getHabboInfo() == null) {
return false;
}
String roomEntryMethod = habbo.getHabboInfo().getRoomEntryMethod();
return roomEntryMethod != null && !roomEntryMethod.trim().isEmpty() && !"unknown".equalsIgnoreCase(roomEntryMethod);
}
private static Integer getRoomEntryMethodValue(Habbo habbo) {
if (habbo == null || habbo.getHabboInfo() == null) {
return null;
}
String roomEntryMethod = habbo.getHabboInfo().getRoomEntryMethod();
if (roomEntryMethod == null || roomEntryMethod.trim().isEmpty()) {
return 0;
}
return switch (roomEntryMethod.trim().toLowerCase(Locale.ROOT)) {
case "door" -> 1;
case "teleport" -> 2;
default -> 3;
};
}
private static int parseStatusInteger(RoomUnit roomUnit, RoomUnitStatus status) {
if (roomUnit == null || status == null || !roomUnit.hasStatus(status)) {
return 0;
}
return parseInteger(roomUnit.getStatus(status));
}
private static boolean moveUserTo(Room room, RoomUnit roomUnit, int x, int y) {
if (room == null || roomUnit == null || room.getLayout() == null) {
return false;
}
RoomTile targetTile = room.getLayout().getTile((short) x, (short) y);
if (targetTile == null || targetTile.state == RoomTileState.INVALID) {
return false;
}
double targetZ = targetTile.getStackHeight() + ((targetTile.state == RoomTileState.SIT) ? -0.5 : 0);
return WiredUserMovementHelper.moveUser(room, roomUnit, targetTile, targetZ, roomUnit.getBodyRotation(), roomUnit.getHeadRotation(), 0, true);
}
private static boolean moveFurniTo(Room room, HabboItem item, int x, int y, int rotation, double z) {
if (room == null || item == null || room.getLayout() == null) {
return false;
}
RoomTile targetTile = room.getLayout().getTile((short) x, (short) y);
if (targetTile == null || targetTile.state == RoomTileState.INVALID) {
return false;
}
FurnitureMovementError error = room.moveFurniTo(item, targetTile, rotation, z, null, true, true);
return error == FurnitureMovementError.NONE;
}
private static int parseInteger(String value) {
try {
return (value == null || value.trim().isEmpty()) ? 0 : Integer.parseInt(value.trim());
} catch (NumberFormatException exception) {
return 0;
}
}
private static int countIterable(Iterable<?> values) {
if (values == null) {
return 0;
}
int count = 0;
for (Object ignored : values) {
count++;
}
return count;
}
private static class TeamEffectData {
final int colorId;
final int typeId;
TeamEffectData(int colorId, int typeId) {
this.colorId = colorId;
this.typeId = typeId;
}
}
}
@@ -40,29 +40,21 @@ import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Manager class for the new wired engine system.
* Manager class for the wired runtime.
* <p>
* This class serves as the integration point between the emulator and the new
* wired engine. It provides static methods for triggering events and manages
* the lifecycle of the engine.
* WiredManager is now the sole runtime entrypoint for wired execution. Legacy
* configuration keys are still read for backwards compatibility with existing
* databases, but they no longer switch execution back to {@code WiredHandler}.
* </p>
*
*
* <h3>Configuration Options:</h3>
* <ul>
* <li>{@code wired.engine.enabled} - Enable new engine (parallel mode)</li>
* <li>{@code wired.engine.exclusive} - Disable legacy handler when true</li>
* <li>{@code wired.engine.enabled} - Compatibility flag kept for old configs</li>
* <li>{@code wired.engine.exclusive} - Compatibility flag kept for old configs</li>
* <li>{@code wired.engine.maxStepsPerStack} - Loop protection limit</li>
* <li>{@code wired.engine.debug} - Verbose logging</li>
* </ul>
*
* <h3>Migration Strategy:</h3>
* <ol>
* <li>Set {@code wired.engine.enabled=true} to run both engines in parallel</li>
* <li>Test thoroughly to ensure identical behavior</li>
* <li>Set {@code wired.engine.exclusive=true} to disable legacy engine</li>
* <li>Full migration complete - WiredManager is now the only wired engine</li>
* </ol>
*
*
* @see WiredEngine
* @see WiredEvents
*/
@@ -80,8 +72,8 @@ public final class WiredManager {
public static final String CONFIG_DEBUG = "wired.engine.debug";
// Defaults
private static final boolean DEFAULT_ENABLED = false;
private static final boolean DEFAULT_EXCLUSIVE = false;
private static final boolean DEFAULT_ENABLED = true;
private static final boolean DEFAULT_EXCLUSIVE = true;
private static final int DEFAULT_MAX_STEPS = 100;
/** The singleton engine instance */
@@ -117,6 +109,7 @@ public final class WiredManager {
// Load configuration
boolean enabled = Emulator.getConfig().getBoolean(CONFIG_ENABLED, DEFAULT_ENABLED);
boolean exclusive = Emulator.getConfig().getBoolean(CONFIG_EXCLUSIVE, DEFAULT_EXCLUSIVE);
int maxSteps = Emulator.getConfig().getInt(CONFIG_MAX_STEPS, DEFAULT_MAX_STEPS);
boolean debug = Emulator.getConfig().getBoolean(CONFIG_DEBUG, false);
@@ -138,9 +131,13 @@ public final class WiredManager {
WiredTickService.getInstance().start();
initialized = true;
LOGGER.info("Wired Manager initialized - enabled: {}, maxSteps: {}, debug: {}",
enabled, maxSteps, debug);
if (!enabled || !exclusive) {
LOGGER.warn("wired.engine.enabled / wired.engine.exclusive are now compatibility-only flags. WiredManager runs as the exclusive engine runtime.");
}
LOGGER.info("Wired Manager initialized - exclusive runtime active, maxSteps: {}, debug: {}",
maxSteps, debug);
}
/**
@@ -163,6 +160,7 @@ public final class WiredManager {
if (engine != null) {
engine.clearUnseenCache();
engine.clearAllDiagnostics();
}
initialized = false;
@@ -174,7 +172,7 @@ public final class WiredManager {
* @return true if enabled
*/
public static boolean isEnabled() {
return Emulator.getConfig().getBoolean(CONFIG_ENABLED, DEFAULT_ENABLED);
return initialized && engine != null;
}
/**
@@ -182,7 +180,7 @@ public final class WiredManager {
* @return true if exclusive mode
*/
public static boolean isExclusive() {
return Emulator.getConfig().getBoolean(CONFIG_EXCLUSIVE, DEFAULT_EXCLUSIVE);
return true;
}
/**
@@ -201,6 +199,27 @@ public final class WiredManager {
return stackIndex;
}
/**
* Get the current monitor snapshot for a room.
* @param roomId the room ID
* @return the diagnostics snapshot, or null if the engine is unavailable
*/
public static WiredRoomDiagnostics.Snapshot getDiagnosticsSnapshot(int roomId) {
if (engine == null) {
return null;
}
return engine.getDiagnosticsSnapshot(roomId);
}
public static void clearDiagnosticsLogs(int roomId) {
if (engine == null) {
return;
}
engine.clearRoomDiagnosticsLogs(roomId);
}
// ========== Event Triggering Methods ==========
/**
@@ -308,20 +327,28 @@ public final class WiredManager {
* Trigger when a user says something.
*/
public static boolean triggerUserSays(Room room, RoomUnit user, String message) {
return triggerUserSays(room, user, message, -1, -1);
}
public static boolean triggerUserSays(Room room, RoomUnit user, String message, int chatType, int chatStyle) {
if (!isEnabled() || room == null || user == null) {
return false;
}
WiredEvent event = WiredEvents.userSays(room, user, message);
WiredEvent event = WiredEvents.userSays(room, user, message, chatType, chatStyle);
return handleEvent(event);
}
public static boolean shouldSuppressUserSaysOutput(Room room, RoomUnit user, String message) {
return shouldSuppressUserSaysOutput(room, user, message, -1, -1);
}
public static boolean shouldSuppressUserSaysOutput(Room room, RoomUnit user, String message, int chatType, int chatStyle) {
if (!isEnabled() || engine == null || room == null || user == null) {
return false;
}
WiredEvent event = WiredEvents.userSays(room, user, message);
WiredEvent event = WiredEvents.userSays(room, user, message, chatType, chatStyle);
return engine.shouldSuppressUserSaysOutput(event);
}
@@ -361,6 +388,36 @@ public final class WiredManager {
return handleEvent(event);
}
public static boolean triggerUserVariableChanged(Room room, int userId, int definitionItemId, boolean created, boolean deleted, WiredEvent.VariableChangeKind changeKind) {
if (!isEnabled() || room == null || definitionItemId <= 0) {
return false;
}
Habbo habbo = room.getHabbo(userId);
RoomUnit roomUnit = (habbo != null) ? habbo.getRoomUnit() : null;
WiredEvent event = WiredEvents.userVariableChanged(room, roomUnit, definitionItemId, created, deleted, changeKind);
return handleEvent(event);
}
public static boolean triggerFurniVariableChanged(Room room, int furniId, int definitionItemId, boolean created, boolean deleted, WiredEvent.VariableChangeKind changeKind) {
if (!isEnabled() || room == null || furniId <= 0 || definitionItemId <= 0) {
return false;
}
HabboItem item = room.getHabboItem(furniId);
WiredEvent event = WiredEvents.furniVariableChanged(room, item, definitionItemId, created, deleted, changeKind);
return handleEvent(event);
}
public static boolean triggerRoomVariableChanged(Room room, int definitionItemId, WiredEvent.VariableChangeKind changeKind) {
if (!isEnabled() || room == null || definitionItemId <= 0) {
return false;
}
WiredEvent event = WiredEvents.roomVariableChanged(room, definitionItemId, changeKind);
return handleEvent(event);
}
/**
* Trigger a timer tick.
*/
@@ -567,8 +624,8 @@ public final class WiredManager {
}
/**
* Trigger from legacy system for parallel running.
* This allows the new engine to run alongside the old one during migration.
* Compatibility bridge for code paths that still describe themselves as
* legacy-triggered. Execution still goes through the new engine only.
*/
public static boolean triggerFromLegacy(WiredTriggerType triggerType, RoomUnit roomUnit, Room room, Object[] stuff) {
if (!isEnabled() || room == null) {
@@ -725,6 +782,11 @@ public final class WiredManager {
*/
public static void unregisterRoomTickables(Room room) {
WiredTickService.getInstance().unregisterRoom(room);
if (room != null) {
room.getFurniVariableManager().clearTransientAssignments();
room.getRoomVariableManager().clearTransientAssignments();
}
}
/**
@@ -176,7 +176,25 @@ public final class WiredMoveCarryHelper {
&& !sendUpdates
&& oldLocation != null
&& (oldLocation.x != targetTile.x || oldLocation.y != targetTile.y || Double.compare(oldZ, movingItem.getZ()) != 0)) {
room.sendComposer(new FloorItemOnRollerComposer(movingItem, null, oldLocation, oldZ, targetTile, movingItem.getZ(), 0, room).compose());
List<WiredMovementsComposer.MovementData> collectedMovements = COLLECTED_MOVEMENTS.get();
if (collectedMovements != null) {
collectedMovements.add(WiredMovementsComposer.furniMovement(
movingItem.getId(),
oldLocation.x,
oldLocation.y,
targetTile.x,
targetTile.y,
oldZ,
movingItem.getZ(),
movingItem.getRotation(),
WiredMovementsComposer.DEFAULT_DURATION,
0,
WiredMovementsComposer.FURNI_ANCHOR_NONE,
0));
} else {
room.sendComposer(new FloorItemOnRollerComposer(movingItem, null, oldLocation, oldZ, targetTile, movingItem.getZ(), 0, room).compose());
}
}
return result;
@@ -0,0 +1,586 @@
package com.eu.habbo.habbohotel.wired.core;
import java.util.ArrayList;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
/**
* Tracks wired monitor data for a single room.
*/
public final class WiredRoomDiagnostics {
public enum Type {
EXECUTION_CAP,
DELAYED_EVENTS_CAP,
EXECUTOR_OVERLOAD,
MARKED_AS_HEAVY,
KILLED,
RECURSION_TIMEOUT
}
public enum Severity {
WARNING,
ERROR
}
public static final class LogEntry {
private final Type type;
private final Severity severity;
private int count;
private long firstOccurredAtMs;
private long lastOccurredAtMs;
private String latestReason;
private String latestSourceLabel;
private int latestSourceId;
private LogEntry(Type type, Severity severity) {
this.type = type;
this.severity = severity;
}
private void record(long now, String reason, String sourceLabel, int sourceId) {
if (this.count <= 0) {
this.firstOccurredAtMs = now;
}
this.count++;
this.lastOccurredAtMs = now;
this.latestReason = sanitizeReason(reason);
this.latestSourceLabel = sanitizeSourceLabel(sourceLabel);
this.latestSourceId = Math.max(0, sourceId);
}
public Type getType() {
return type;
}
public Severity getSeverity() {
return severity;
}
public int getCount() {
return count;
}
public long getFirstOccurredAtMs() {
return firstOccurredAtMs;
}
public long getLastOccurredAtMs() {
return lastOccurredAtMs;
}
public String getLatestReason() {
return latestReason;
}
public String getLatestSourceLabel() {
return latestSourceLabel;
}
public int getLatestSourceId() {
return latestSourceId;
}
}
public static final class Snapshot {
private final int usageCurrentWindow;
private final int usageLimitPerWindow;
private final boolean heavy;
private final int delayedEventsPending;
private final int delayedEventsLimit;
private final int averageExecutionMs;
private final int peakExecutionMs;
private final int recursionDepthCurrent;
private final int recursionDepthLimit;
private final int killedRemainingSeconds;
private final int usageWindowMs;
private final int overloadAverageThresholdMs;
private final int overloadPeakThresholdMs;
private final int heavyUsageThresholdPercent;
private final int heavyConsecutiveWindowsThreshold;
private final int overloadConsecutiveWindowsThreshold;
private final int heavyDelayedThresholdPercent;
private final List<LogEntry> logs;
private final List<HistoryEntry> history;
public Snapshot(int usageCurrentWindow, int usageLimitPerWindow, boolean heavy, int delayedEventsPending,
int delayedEventsLimit, int averageExecutionMs, int peakExecutionMs,
int recursionDepthCurrent, int recursionDepthLimit, int killedRemainingSeconds,
int usageWindowMs, int overloadAverageThresholdMs, int overloadPeakThresholdMs,
int heavyUsageThresholdPercent, int heavyConsecutiveWindowsThreshold,
int overloadConsecutiveWindowsThreshold, int heavyDelayedThresholdPercent,
List<LogEntry> logs, List<HistoryEntry> history) {
this.usageCurrentWindow = usageCurrentWindow;
this.usageLimitPerWindow = usageLimitPerWindow;
this.heavy = heavy;
this.delayedEventsPending = delayedEventsPending;
this.delayedEventsLimit = delayedEventsLimit;
this.averageExecutionMs = averageExecutionMs;
this.peakExecutionMs = peakExecutionMs;
this.recursionDepthCurrent = recursionDepthCurrent;
this.recursionDepthLimit = recursionDepthLimit;
this.killedRemainingSeconds = killedRemainingSeconds;
this.usageWindowMs = usageWindowMs;
this.overloadAverageThresholdMs = overloadAverageThresholdMs;
this.overloadPeakThresholdMs = overloadPeakThresholdMs;
this.heavyUsageThresholdPercent = heavyUsageThresholdPercent;
this.heavyConsecutiveWindowsThreshold = heavyConsecutiveWindowsThreshold;
this.overloadConsecutiveWindowsThreshold = overloadConsecutiveWindowsThreshold;
this.heavyDelayedThresholdPercent = heavyDelayedThresholdPercent;
this.logs = Collections.unmodifiableList(logs);
this.history = Collections.unmodifiableList(history);
}
public int getUsageCurrentWindow() {
return usageCurrentWindow;
}
public int getUsageLimitPerWindow() {
return usageLimitPerWindow;
}
public boolean isHeavy() {
return heavy;
}
public int getDelayedEventsPending() {
return delayedEventsPending;
}
public int getDelayedEventsLimit() {
return delayedEventsLimit;
}
public int getAverageExecutionMs() {
return averageExecutionMs;
}
public int getPeakExecutionMs() {
return peakExecutionMs;
}
public int getRecursionDepthCurrent() {
return recursionDepthCurrent;
}
public int getRecursionDepthLimit() {
return recursionDepthLimit;
}
public int getKilledRemainingSeconds() {
return killedRemainingSeconds;
}
public int getUsageWindowMs() {
return usageWindowMs;
}
public int getOverloadAverageThresholdMs() {
return overloadAverageThresholdMs;
}
public int getOverloadPeakThresholdMs() {
return overloadPeakThresholdMs;
}
public int getHeavyUsageThresholdPercent() {
return heavyUsageThresholdPercent;
}
public int getHeavyConsecutiveWindowsThreshold() {
return heavyConsecutiveWindowsThreshold;
}
public int getOverloadConsecutiveWindowsThreshold() {
return overloadConsecutiveWindowsThreshold;
}
public int getHeavyDelayedThresholdPercent() {
return heavyDelayedThresholdPercent;
}
public List<LogEntry> getLogs() {
return logs;
}
public List<HistoryEntry> getHistory() {
return history;
}
}
public static final class HistoryEntry {
private final Type type;
private final Severity severity;
private final long occurredAtMs;
private final String reason;
private final String sourceLabel;
private final int sourceId;
public HistoryEntry(Type type, Severity severity, long occurredAtMs, String reason, String sourceLabel, int sourceId) {
this.type = type;
this.severity = severity;
this.occurredAtMs = occurredAtMs;
this.reason = sanitizeReason(reason);
this.sourceLabel = sanitizeSourceLabel(sourceLabel);
this.sourceId = Math.max(0, sourceId);
}
public Type getType() {
return type;
}
public Severity getSeverity() {
return severity;
}
public long getOccurredAtMs() {
return occurredAtMs;
}
public String getReason() {
return reason;
}
public String getSourceLabel() {
return sourceLabel;
}
public int getSourceId() {
return sourceId;
}
}
private final int usageWindowMs;
private final int usageLimitPerWindow;
private final int delayedEventsLimit;
private final int overloadAverageThresholdMs;
private final int overloadPeakThresholdMs;
private final int heavyUsageThresholdPercent;
private final int heavyConsecutiveWindowsThreshold;
private final int overloadConsecutiveWindowsThreshold;
private final int heavyDelayedThresholdPercent;
private final EnumMap<Type, LogEntry> logs;
private final ArrayDeque<HistoryEntry> history;
private final int maxHistoryEntries;
private long windowStartedAt;
private int usageCurrentWindow;
private int delayedEventsPending;
private long totalExecutionMsCurrentWindow;
private int executionSamplesCurrentWindow;
private int averageExecutionMs;
private int peakExecutionMs;
private int consecutiveHeavyWindows;
private int consecutiveOverloadWindows;
private boolean heavy;
private String peakExecutionSourceLabel;
private int peakExecutionSourceId;
private String peakExecutionReason;
public WiredRoomDiagnostics(int usageWindowMs, int usageLimitPerWindow, int delayedEventsLimit,
int overloadAverageThresholdMs, int overloadPeakThresholdMs,
int heavyUsageThresholdPercent, int heavyConsecutiveWindowsThreshold) {
this(usageWindowMs, usageLimitPerWindow, delayedEventsLimit, overloadAverageThresholdMs, overloadPeakThresholdMs,
heavyUsageThresholdPercent, heavyConsecutiveWindowsThreshold, 2, 60, 200);
}
public WiredRoomDiagnostics(int usageWindowMs, int usageLimitPerWindow, int delayedEventsLimit,
int overloadAverageThresholdMs, int overloadPeakThresholdMs,
int heavyUsageThresholdPercent, int heavyConsecutiveWindowsThreshold,
int overloadConsecutiveWindowsThreshold, int heavyDelayedThresholdPercent,
int maxHistoryEntries) {
this.usageWindowMs = Math.max(250, usageWindowMs);
this.usageLimitPerWindow = Math.max(1, usageLimitPerWindow);
this.delayedEventsLimit = Math.max(1, delayedEventsLimit);
this.overloadAverageThresholdMs = Math.max(1, overloadAverageThresholdMs);
this.overloadPeakThresholdMs = Math.max(this.overloadAverageThresholdMs, overloadPeakThresholdMs);
this.heavyUsageThresholdPercent = Math.max(1, Math.min(100, heavyUsageThresholdPercent));
this.heavyConsecutiveWindowsThreshold = Math.max(1, heavyConsecutiveWindowsThreshold);
this.overloadConsecutiveWindowsThreshold = Math.max(1, overloadConsecutiveWindowsThreshold);
this.heavyDelayedThresholdPercent = Math.max(1, Math.min(100, heavyDelayedThresholdPercent));
this.maxHistoryEntries = Math.max(10, maxHistoryEntries);
this.logs = new EnumMap<>(Type.class);
this.history = new ArrayDeque<>(this.maxHistoryEntries);
for (Type type : Type.values()) {
this.logs.put(type, new LogEntry(type, defaultSeverity(type)));
}
}
public synchronized boolean tryConsumeExecutionBudget(int estimatedCost, long now, String sourceLabel, int sourceId, String reason) {
rollWindow(now);
int normalizedCost = Math.max(0, estimatedCost);
if ((this.usageCurrentWindow + normalizedCost) > this.usageLimitPerWindow) {
record(Type.EXECUTION_CAP, now,
buildExecutionCapReason(normalizedCost, reason),
sourceLabel,
sourceId);
return false;
}
this.usageCurrentWindow += normalizedCost;
return true;
}
public synchronized boolean tryScheduleDelayedEvent(long now, String sourceLabel, int sourceId, String reason) {
rollWindow(now);
if ((this.delayedEventsPending + 1) > this.delayedEventsLimit) {
record(Type.DELAYED_EVENTS_CAP, now,
buildDelayedCapReason(reason),
sourceLabel,
sourceId);
return false;
}
this.delayedEventsPending++;
return true;
}
public synchronized void completeDelayedEvent() {
if (this.delayedEventsPending > 0) {
this.delayedEventsPending--;
}
}
public synchronized void recordExecution(long elapsedMs, long now, String sourceLabel, int sourceId, String reason) {
rollWindow(now);
int normalizedElapsed = (int) Math.max(0L, elapsedMs);
this.totalExecutionMsCurrentWindow += normalizedElapsed;
this.executionSamplesCurrentWindow++;
this.averageExecutionMs = (int) Math.round(this.totalExecutionMsCurrentWindow / (double) this.executionSamplesCurrentWindow);
if (normalizedElapsed >= this.peakExecutionMs) {
this.peakExecutionMs = normalizedElapsed;
this.peakExecutionSourceLabel = sanitizeSourceLabel(sourceLabel);
this.peakExecutionSourceId = Math.max(0, sourceId);
this.peakExecutionReason = sanitizeReason(reason);
}
}
public synchronized void recordKilled(long now, String reason, String sourceLabel, int sourceId) {
rollWindow(now);
record(Type.KILLED, now, reason, sourceLabel, sourceId);
}
public synchronized void recordRecursionTimeout(long now, String reason, String sourceLabel, int sourceId) {
rollWindow(now);
record(Type.RECURSION_TIMEOUT, now, reason, sourceLabel, sourceId);
}
public synchronized void clearLogs() {
for (Type type : Type.values()) {
LogEntry entry = this.logs.get(type);
if (entry == null) {
continue;
}
entry.count = 0;
entry.firstOccurredAtMs = 0L;
entry.lastOccurredAtMs = 0L;
entry.latestReason = "";
entry.latestSourceLabel = "";
entry.latestSourceId = 0;
}
this.history.clear();
}
public synchronized Snapshot snapshot(int recursionDepthCurrent, int recursionDepthLimit, long killedUntilMs, long now) {
rollWindow(now);
List<LogEntry> logEntries = new ArrayList<>(Type.values().length);
List<HistoryEntry> historyEntries = new ArrayList<>(this.history.size());
for (Type type : Type.values()) {
LogEntry source = this.logs.get(type);
LogEntry copy = new LogEntry(source.getType(), source.getSeverity());
copy.count = source.getCount();
copy.firstOccurredAtMs = source.getFirstOccurredAtMs();
copy.lastOccurredAtMs = source.getLastOccurredAtMs();
copy.latestReason = source.getLatestReason();
copy.latestSourceLabel = source.getLatestSourceLabel();
copy.latestSourceId = source.getLatestSourceId();
logEntries.add(copy);
}
historyEntries.addAll(this.history);
int killedRemainingSeconds = 0;
if (killedUntilMs > now) {
killedRemainingSeconds = (int) Math.max(0L, Math.ceil((killedUntilMs - now) / 1000D));
}
return new Snapshot(
this.usageCurrentWindow,
this.usageLimitPerWindow,
this.heavy,
this.delayedEventsPending,
this.delayedEventsLimit,
this.averageExecutionMs,
this.peakExecutionMs,
recursionDepthCurrent,
recursionDepthLimit,
killedRemainingSeconds,
this.usageWindowMs,
this.overloadAverageThresholdMs,
this.overloadPeakThresholdMs,
this.heavyUsageThresholdPercent,
this.heavyConsecutiveWindowsThreshold,
this.overloadConsecutiveWindowsThreshold,
this.heavyDelayedThresholdPercent,
logEntries,
historyEntries
);
}
private void rollWindow(long now) {
if (this.windowStartedAt <= 0L) {
this.windowStartedAt = now;
return;
}
while ((now - this.windowStartedAt) >= this.usageWindowMs) {
evaluateWindow(this.windowStartedAt + this.usageWindowMs);
this.windowStartedAt += this.usageWindowMs;
this.usageCurrentWindow = 0;
this.totalExecutionMsCurrentWindow = 0L;
this.executionSamplesCurrentWindow = 0;
this.averageExecutionMs = 0;
this.peakExecutionMs = 0;
this.peakExecutionSourceLabel = null;
this.peakExecutionSourceId = 0;
this.peakExecutionReason = null;
}
}
private void evaluateWindow(long now) {
int usagePercent = (int) Math.round((this.usageCurrentWindow * 100D) / this.usageLimitPerWindow);
int delayedPercent = (int) Math.round((this.delayedEventsPending * 100D) / this.delayedEventsLimit);
boolean overloadWindow = (this.executionSamplesCurrentWindow > 0)
&& ((this.averageExecutionMs >= this.overloadAverageThresholdMs) || (this.peakExecutionMs >= this.overloadPeakThresholdMs));
boolean heavyWindow = (usagePercent >= this.heavyUsageThresholdPercent)
|| (delayedPercent >= this.heavyDelayedThresholdPercent)
|| overloadWindow;
if (overloadWindow) {
this.consecutiveOverloadWindows++;
if (this.consecutiveOverloadWindows >= this.overloadConsecutiveWindowsThreshold) {
record(Type.EXECUTOR_OVERLOAD, now,
buildExecutorOverloadReason(),
this.peakExecutionSourceLabel,
this.peakExecutionSourceId);
}
} else {
this.consecutiveOverloadWindows = 0;
}
if (heavyWindow) {
this.consecutiveHeavyWindows++;
if (!this.heavy && (this.consecutiveHeavyWindows >= this.heavyConsecutiveWindowsThreshold)) {
this.heavy = true;
record(Type.MARKED_AS_HEAVY, now,
buildHeavyReason(usagePercent, delayedPercent, overloadWindow),
overloadWindow ? this.peakExecutionSourceLabel : null,
overloadWindow ? this.peakExecutionSourceId : 0);
}
return;
}
this.consecutiveHeavyWindows = 0;
this.heavy = false;
}
private void record(Type type, long now, String reason, String sourceLabel, int sourceId) {
LogEntry entry = this.logs.get(type);
if (entry != null) {
entry.record(now, reason, sourceLabel, sourceId);
this.history.addFirst(new HistoryEntry(type, entry.getSeverity(), now, reason, sourceLabel, sourceId));
while (this.history.size() > this.maxHistoryEntries) {
this.history.removeLast();
}
}
}
private String buildExecutionCapReason(int normalizedCost, String reason) {
return joinReason(
reason,
String.format("Estimated stack cost %d would exceed usage budget %d/%d in %dms window",
normalizedCost,
this.usageCurrentWindow,
this.usageLimitPerWindow,
this.usageWindowMs)
);
}
private String buildDelayedCapReason(String reason) {
return joinReason(
reason,
String.format("Pending delayed events would exceed queue %d/%d",
this.delayedEventsPending,
this.delayedEventsLimit)
);
}
private String buildExecutorOverloadReason() {
return joinReason(
this.peakExecutionReason,
String.format("Average execution %dms (limit %dms), peak %dms (limit %dms) across %d execution(s) in %dms window",
this.averageExecutionMs,
this.overloadAverageThresholdMs,
this.peakExecutionMs,
this.overloadPeakThresholdMs,
this.executionSamplesCurrentWindow,
this.usageWindowMs)
);
}
private String buildHeavyReason(int usagePercent, int delayedPercent, boolean overloadWindow) {
return String.format(
"Room stayed above heavy thresholds for %d consecutive window(s): usage %d%%/%d%%, delayed %d%%/%d%%, overload %s",
this.consecutiveHeavyWindows,
usagePercent,
this.heavyUsageThresholdPercent,
delayedPercent,
this.heavyDelayedThresholdPercent,
overloadWindow ? "yes" : "no"
);
}
private static String joinReason(String primary, String fallback) {
String cleanPrimary = sanitizeReason(primary);
String cleanFallback = sanitizeReason(fallback);
if (cleanPrimary.isEmpty()) return cleanFallback;
if (cleanFallback.isEmpty()) return cleanPrimary;
if (cleanPrimary.equals(cleanFallback)) return cleanPrimary;
return cleanPrimary + ". " + cleanFallback;
}
private static String sanitizeReason(String reason) {
return (reason == null) ? "" : reason.trim();
}
private static String sanitizeSourceLabel(String sourceLabel) {
return (sourceLabel == null) ? "" : sourceLabel.trim();
}
private Severity defaultSeverity(Type type) {
return (type == Type.MARKED_AS_HEAVY) ? Severity.WARNING : Severity.ERROR;
}
}
@@ -4,7 +4,9 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterFurni;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterFurniByVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterUser;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterUsersByVariable;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.users.HabboItem;
@@ -233,21 +235,50 @@ public final class WiredSourceUtil {
int furniLimit = Integer.MAX_VALUE;
int userLimit = Integer.MAX_VALUE;
List<WiredExtraFilterFurniByVariable> furniVariableFilters = new ArrayList<>();
List<WiredExtraFilterUsersByVariable> userVariableFilters = new ArrayList<>();
for (InteractionWiredExtra extra : extras) {
if (extra instanceof WiredExtraFilterFurni) {
furniLimit = Math.min(furniLimit, ((WiredExtraFilterFurni) extra).getAmount());
} else if (extra instanceof WiredExtraFilterUser) {
userLimit = Math.min(userLimit, ((WiredExtraFilterUser) extra).getAmount());
} else if (extra instanceof WiredExtraFilterFurniByVariable) {
furniVariableFilters.add((WiredExtraFilterFurniByVariable) extra);
} else if (extra instanceof WiredExtraFilterUsersByVariable) {
userVariableFilters.add((WiredExtraFilterUsersByVariable) extra);
}
}
if (selectorCtx.targets().isItemsModifiedBySelector() && furniLimit != Integer.MAX_VALUE) {
selectorCtx.targets().setItems(limitIterable(selectorCtx.targets().items(), furniLimit));
furniVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId()));
userVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId()));
if (selectorCtx.targets().isItemsModifiedBySelector()) {
Iterable<HabboItem> filteredItems = selectorCtx.targets().items();
for (WiredExtraFilterFurniByVariable extra : furniVariableFilters) {
filteredItems = extra.filterItems(room, selectorCtx, filteredItems);
}
if (furniLimit != Integer.MAX_VALUE) {
filteredItems = limitIterable(filteredItems, furniLimit);
}
selectorCtx.targets().setItems(filteredItems);
}
if (selectorCtx.targets().isUsersModifiedBySelector() && userLimit != Integer.MAX_VALUE) {
selectorCtx.targets().setUsers(limitIterable(selectorCtx.targets().users(), userLimit));
if (selectorCtx.targets().isUsersModifiedBySelector()) {
Iterable<RoomUnit> filteredUsers = selectorCtx.targets().users();
for (WiredExtraFilterUsersByVariable extra : userVariableFilters) {
filteredUsers = extra.filterUsers(room, selectorCtx, filteredUsers);
}
if (userLimit != Integer.MAX_VALUE) {
filteredUsers = limitIterable(filteredUsers, userLimit);
}
selectorCtx.targets().setUsers(filteredUsers);
}
}
@@ -0,0 +1,246 @@
package com.eu.habbo.habbohotel.wired.core;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraTextInputVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.triggers.WiredTriggerHabboSaysKeyword;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.wired.api.WiredStack;
import gnu.trove.set.hash.THashSet;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class WiredTextInputCaptureSupport {
private static final int MATCH_CONTAINS = 0;
private static final int MATCH_EXACT = 1;
private static final int MATCH_ALL_WORDS = 2;
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("#([^#]+)#");
private WiredTextInputCaptureSupport() {
}
public static CaptureResult resolve(WiredStack stack, WiredEvent event) {
if (stack == null || event == null || !(stack.triggerItem() instanceof WiredTriggerHabboSaysKeyword)) {
return CaptureResult.noMatch();
}
WiredTriggerHabboSaysKeyword trigger = (WiredTriggerHabboSaysKeyword) stack.triggerItem();
Room room = event.getRoom();
RoomUnit actor = event.getActor().orElse(null);
String text = event.getText().orElse(null);
if (room == null || actor == null || text == null) {
return CaptureResult.noMatch();
}
List<WiredExtraTextInputVariable> capturers = getCapturers(room, trigger);
if (capturers.isEmpty()) {
return trigger.matches(stack.triggerItem(), event) ? CaptureResult.matched(new LinkedHashMap<>()) : CaptureResult.noMatch();
}
if (trigger.isOwnerOnly()) {
Habbo habbo = room.getHabbo(actor);
if (habbo == null || room.getOwnerId() != habbo.getHabboInfo().getId()) {
return CaptureResult.noMatch();
}
}
LinkedHashMap<String, WiredExtraTextInputVariable> capturersByName = new LinkedHashMap<>();
for (WiredExtraTextInputVariable capturer : capturers) {
if (capturer == null || capturer.getCapturerName() == null || capturer.getCapturerName().trim().isEmpty()) {
continue;
}
capturersByName.put(capturer.getCapturerName().trim().toLowerCase(), capturer);
}
if (capturersByName.isEmpty()) {
return trigger.matches(stack.triggerItem(), event) ? CaptureResult.matched(new LinkedHashMap<>()) : CaptureResult.noMatch();
}
MatchResult matchResult = matchTemplate(trigger, text, capturersByName);
if (!matchResult.matches) {
return CaptureResult.noMatch();
}
LinkedHashMap<Integer, Integer> capturedValues = new LinkedHashMap<>();
for (Map.Entry<String, String> capture : matchResult.captures.entrySet()) {
WiredExtraTextInputVariable capturer = capturersByName.get(capture.getKey());
if (capturer == null) {
continue;
}
Integer resolvedValue = capturer.resolveCapturedValue(room, capture.getValue());
if (resolvedValue == null) {
return CaptureResult.noMatch();
}
capturedValues.put(capturer.getVariableItemId(), resolvedValue);
}
return CaptureResult.matched(capturedValues);
}
private static List<WiredExtraTextInputVariable> getCapturers(Room room, WiredTriggerHabboSaysKeyword trigger) {
List<WiredExtraTextInputVariable> capturers = new ArrayList<>();
if (room == null || trigger == null || room.getRoomSpecialTypes() == null) {
return capturers;
}
THashSet<InteractionWiredExtra> extras = room.getRoomSpecialTypes().getExtras(trigger.getX(), trigger.getY());
if (extras == null || extras.isEmpty()) {
return capturers;
}
for (InteractionWiredExtra extra : WiredExecutionOrderUtil.sort(extras)) {
if (extra instanceof WiredExtraTextInputVariable) {
capturers.add((WiredExtraTextInputVariable) extra);
}
}
return capturers;
}
private static MatchResult matchTemplate(WiredTriggerHabboSaysKeyword trigger, String rawText, Map<String, WiredExtraTextInputVariable> capturersByName) {
String text = rawText != null ? rawText.trim() : "";
String template = trigger.getKey() != null ? trigger.getKey().trim() : "";
if (trigger.getMatchMode() == MATCH_ALL_WORDS && template.isEmpty()) {
if (capturersByName.size() != 1 || text.isEmpty()) {
return MatchResult.noMatch();
}
String placeholderName = capturersByName.keySet().iterator().next();
LinkedHashMap<String, String> captures = new LinkedHashMap<>();
captures.put(placeholderName, text);
return MatchResult.matched(captures);
}
TemplatePattern pattern = buildPattern(template);
if (pattern == null) {
return MatchResult.noMatch();
}
Matcher matcher = pattern.pattern.matcher(text);
boolean matches = (trigger.getMatchMode() == MATCH_CONTAINS) ? matcher.find() : matcher.matches();
if (!matches) {
return MatchResult.noMatch();
}
LinkedHashMap<String, String> captures = new LinkedHashMap<>();
for (int index = 0; index < pattern.placeholderNames.size(); index++) {
String placeholderName = pattern.placeholderNames.get(index);
if (!capturersByName.containsKey(placeholderName)) {
continue;
}
String capturedValue = matcher.group(index + 1);
captures.put(placeholderName, capturedValue != null ? capturedValue.trim() : "");
}
return MatchResult.matched(captures);
}
private static TemplatePattern buildPattern(String template) {
if (template == null || template.isEmpty()) {
return null;
}
Matcher matcher = PLACEHOLDER_PATTERN.matcher(template);
StringBuilder regex = new StringBuilder();
List<String> placeholderNames = new ArrayList<>();
int cursor = 0;
while (matcher.find()) {
regex.append(Pattern.quote(template.substring(cursor, matcher.start())));
regex.append("(.+?)");
String placeholderName = matcher.group(1) != null ? matcher.group(1).trim().toLowerCase() : "";
placeholderNames.add(placeholderName);
cursor = matcher.end();
}
regex.append(Pattern.quote(template.substring(cursor)));
if (placeholderNames.isEmpty()) {
regex = new StringBuilder(Pattern.quote(template));
}
return new TemplatePattern(Pattern.compile(regex.toString(), Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE), placeholderNames);
}
public static void applyToContext(WiredContext ctx, Room room, CaptureResult captureResult) {
if (ctx == null || room == null || captureResult == null || !captureResult.matches || captureResult.capturedValues.isEmpty()) {
return;
}
ctx.forkContextVariables();
for (Map.Entry<Integer, Integer> entry : captureResult.capturedValues.entrySet()) {
if (entry == null || entry.getKey() == null || entry.getKey() <= 0) {
continue;
}
if (!WiredContextVariableSupport.updateVariableValue(ctx, room, entry.getKey(), entry.getValue())) {
WiredContextVariableSupport.assignVariable(ctx, room, entry.getKey(), entry.getValue(), false);
}
}
}
public static final class CaptureResult {
private final boolean matches;
private final LinkedHashMap<Integer, Integer> capturedValues;
private CaptureResult(boolean matches, LinkedHashMap<Integer, Integer> capturedValues) {
this.matches = matches;
this.capturedValues = capturedValues;
}
public boolean matches() {
return this.matches;
}
public static CaptureResult matched(LinkedHashMap<Integer, Integer> capturedValues) {
return new CaptureResult(true, capturedValues);
}
public static CaptureResult noMatch() {
return new CaptureResult(false, new LinkedHashMap<>());
}
}
private static final class MatchResult {
private final boolean matches;
private final LinkedHashMap<String, String> captures;
private MatchResult(boolean matches, LinkedHashMap<String, String> captures) {
this.matches = matches;
this.captures = captures;
}
private static MatchResult matched(LinkedHashMap<String, String> captures) {
return new MatchResult(true, captures);
}
private static MatchResult noMatch() {
return new MatchResult(false, new LinkedHashMap<>());
}
}
private static final class TemplatePattern {
private final Pattern pattern;
private final List<String> placeholderNames;
private TemplatePattern(Pattern pattern, List<String> placeholderNames) {
this.pattern = pattern;
this.placeholderNames = placeholderNames;
}
}
}
@@ -1,20 +1,35 @@
package com.eu.habbo.habbohotel.wired.core;
import com.eu.habbo.habbohotel.bots.Bot;
import com.eu.habbo.habbohotel.games.Game;
import com.eu.habbo.habbohotel.games.GamePlayer;
import com.eu.habbo.habbohotel.games.GameTeam;
import com.eu.habbo.habbohotel.games.GameTeamColors;
import com.eu.habbo.habbohotel.games.battlebanzai.BattleBanzaiGame;
import com.eu.habbo.habbohotel.games.freeze.FreezeGame;
import com.eu.habbo.habbohotel.games.wired.WiredGame;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraTextOutputFurniName;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraTextOutputUsername;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraTextOutputVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraUserVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFurniVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRoomVariable;
import com.eu.habbo.habbohotel.pets.Pet;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.rooms.RoomUnitType;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.util.HotelDateTimeUtil;
import gnu.trove.set.hash.THashSet;
import java.time.ZonedDateTime;
import java.time.temporal.WeekFields;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
public final class WiredTextPlaceholderUtil {
private WiredTextPlaceholderUtil() {
@@ -58,6 +73,17 @@ public final class WiredTextPlaceholderUtil {
if (!placeholderToken.isEmpty() && resolvedText.contains(placeholderToken)) {
resolvedText = resolvedText.replace(placeholderToken, buildFurniNameReplacement(ctx, furniExtra));
}
continue;
}
if (extra instanceof WiredExtraTextOutputVariable) {
WiredExtraTextOutputVariable variableExtra = (WiredExtraTextOutputVariable) extra;
String placeholderToken = variableExtra.getPlaceholderToken();
if (!placeholderToken.isEmpty() && resolvedText.contains(placeholderToken)) {
resolvedText = resolvedText.replace(placeholderToken, buildVariableReplacement(ctx, variableExtra));
}
}
}
@@ -75,12 +101,14 @@ public final class WiredTextPlaceholderUtil {
}
for (InteractionWiredExtra extra : extras) {
if (!(extra instanceof WiredExtraTextOutputUsername)) {
continue;
if (extra instanceof WiredExtraTextOutputUsername) {
int userSource = ((WiredExtraTextOutputUsername) extra).getUserSource();
if (userSource == WiredSourceUtil.SOURCE_TRIGGER || userSource == WiredSourceUtil.SOURCE_CLICKED_USER) {
return true;
}
}
int userSource = ((WiredExtraTextOutputUsername) extra).getUserSource();
if ((userSource == WiredSourceUtil.SOURCE_TRIGGER) || (userSource == WiredSourceUtil.SOURCE_CLICKED_USER)) {
if (extra instanceof WiredExtraTextOutputVariable && ((WiredExtraTextOutputVariable) extra).requiresActor()) {
return true;
}
}
@@ -164,6 +192,186 @@ public final class WiredTextPlaceholderUtil {
return furniNames.get(0);
}
private static String buildVariableReplacement(WiredContext ctx, WiredExtraTextOutputVariable extra) {
List<String> values = switch (extra.getTargetType()) {
case WiredExtraTextOutputVariable.TARGET_FURNI -> collectFurniVariableValues(ctx, extra);
case WiredExtraTextOutputVariable.TARGET_CONTEXT -> collectContextVariableValues(ctx, extra);
case WiredExtraTextOutputVariable.TARGET_ROOM -> collectRoomVariableValues(ctx, extra);
default -> collectUserVariableValues(ctx, extra);
};
if (values.isEmpty()) {
return "";
}
if (extra.getPlaceholderType() == WiredExtraTextOutputVariable.TYPE_MULTIPLE) {
return String.join(extra.getDelimiter(), values);
}
return values.get(0);
}
private static List<String> collectUserVariableValues(WiredContext ctx, WiredExtraTextOutputVariable extra) {
Room room = ctx.room();
List<RoomUnit> users = WiredSourceUtil.resolveUsers(ctx, extra.getUserSource());
if (room == null || users.isEmpty()) {
return List.of();
}
LinkedHashSet<Integer> seenUserIds = new LinkedHashSet<>();
List<String> values = new ArrayList<>();
for (RoomUnit roomUnit : users) {
if (roomUnit == null || !seenUserIds.add(roomUnit.getId())) {
continue;
}
String value = resolveUserVariableValue(room, roomUnit, extra);
if (value != null && !value.isEmpty()) {
values.add(value);
}
}
return values;
}
private static List<String> collectFurniVariableValues(WiredContext ctx, WiredExtraTextOutputVariable extra) {
Room room = ctx.room();
if (room == null) {
return List.of();
}
extra.refresh(room);
List<HabboItem> items = (extra.getFurniSource() == WiredSourceUtil.SOURCE_SELECTOR)
? WiredSourceUtil.resolveSelectorItems(ctx, true)
: WiredSourceUtil.resolveItems(ctx, extra.getFurniSource(), extra.getItems());
if (items.isEmpty()) {
return List.of();
}
LinkedHashSet<Integer> seenItemIds = new LinkedHashSet<>();
List<String> values = new ArrayList<>();
for (HabboItem item : items) {
if (item == null || !seenItemIds.add(item.getId())) {
continue;
}
String value = resolveFurniVariableValue(room, item, extra);
if (value != null && !value.isEmpty()) {
values.add(value);
}
}
return values;
}
private static List<String> collectRoomVariableValues(WiredContext ctx, WiredExtraTextOutputVariable extra) {
Room room = ctx.room();
if (room == null) {
return List.of();
}
String value = resolveRoomVariableValue(room, extra);
return (value == null || value.isEmpty()) ? List.of() : List.of(value);
}
private static List<String> collectContextVariableValues(WiredContext ctx, WiredExtraTextOutputVariable extra) {
if (ctx == null) {
return List.of();
}
String value = resolveContextVariableValue(ctx, extra);
return (value == null || value.isEmpty()) ? List.of() : List.of(value);
}
private static String resolveUserVariableValue(Room room, RoomUnit roomUnit, WiredExtraTextOutputVariable extra) {
if (room == null || roomUnit == null) {
return null;
}
if (WiredExtraTextOutputVariable.isInternalVariableToken(extra.getVariableToken())) {
Integer value = readUserInternalValue(room, roomUnit, WiredExtraTextOutputVariable.getInternalVariableKey(extra.getVariableToken()));
return value != null ? String.valueOf(value) : null;
}
Habbo habbo = room.getHabbo(roomUnit);
if (habbo == null || !room.getUserVariableManager().hasVariable(habbo.getHabboInfo().getId(), extra.getVariableItemId())) {
return null;
}
Integer value = room.getUserVariableManager().getCurrentValue(habbo.getHabboInfo().getId(), extra.getVariableItemId());
if (extra.getDisplayType(room) == WiredExtraTextOutputVariable.DISPLAY_TEXTUAL) {
return WiredVariableTextConnectorSupport.toText(room, extra.getVariableItemId(), value);
}
return value != null ? String.valueOf(value) : null;
}
private static String resolveFurniVariableValue(Room room, HabboItem item, WiredExtraTextOutputVariable extra) {
if (room == null || item == null) {
return null;
}
if (WiredExtraTextOutputVariable.isInternalVariableToken(extra.getVariableToken())) {
Integer value = readFurniInternalValue(room, item, WiredExtraTextOutputVariable.getInternalVariableKey(extra.getVariableToken()));
return value != null ? String.valueOf(value) : null;
}
if (!room.getFurniVariableManager().hasVariable(item.getId(), extra.getVariableItemId())) {
return null;
}
Integer value = room.getFurniVariableManager().getCurrentValue(item.getId(), extra.getVariableItemId());
if (extra.getDisplayType(room) == WiredExtraTextOutputVariable.DISPLAY_TEXTUAL) {
return WiredVariableTextConnectorSupport.toText(room, extra.getVariableItemId(), value);
}
return value != null ? String.valueOf(value) : null;
}
private static String resolveRoomVariableValue(Room room, WiredExtraTextOutputVariable extra) {
if (room == null) {
return null;
}
if (WiredExtraTextOutputVariable.isInternalVariableToken(extra.getVariableToken())) {
Integer value = readRoomInternalValue(room, WiredExtraTextOutputVariable.getInternalVariableKey(extra.getVariableToken()));
return value != null ? String.valueOf(value) : null;
}
Integer value = room.getRoomVariableManager().getCurrentValue(extra.getVariableItemId());
if (extra.getDisplayType(room) == WiredExtraTextOutputVariable.DISPLAY_TEXTUAL) {
return WiredVariableTextConnectorSupport.toText(room, extra.getVariableItemId(), value);
}
return value != null ? String.valueOf(value) : null;
}
private static String resolveContextVariableValue(WiredContext ctx, WiredExtraTextOutputVariable extra) {
if (ctx == null) {
return null;
}
if (WiredExtraTextOutputVariable.isInternalVariableToken(extra.getVariableToken())) {
Integer value = WiredInternalVariableSupport.readContextValue(ctx, WiredExtraTextOutputVariable.getInternalVariableKey(extra.getVariableToken()));
return value != null ? String.valueOf(value) : null;
}
if (!WiredContextVariableSupport.hasVariable(ctx, extra.getVariableItemId())) {
return null;
}
Integer value = WiredContextVariableSupport.getCurrentValue(ctx, extra.getVariableItemId());
if (extra.getDisplayType(ctx.room()) == WiredExtraTextOutputVariable.DISPLAY_TEXTUAL) {
return WiredVariableTextConnectorSupport.toText(ctx.room(), extra.getVariableItemId(), value);
}
return value != null ? String.valueOf(value) : null;
}
private static String getRoomUnitName(Room room, RoomUnit roomUnit) {
if (room == null || roomUnit == null) {
return "";
@@ -186,4 +394,123 @@ public final class WiredTextPlaceholderUtil {
return "";
}
private static Integer readUserInternalValue(Room room, RoomUnit roomUnit, String key) {
return WiredInternalVariableSupport.readUserValue(room, roomUnit, key);
}
private static Integer readFurniInternalValue(Room room, HabboItem item, String key) {
return WiredInternalVariableSupport.readFurniValue(room, item, key);
}
private static Integer readRoomInternalValue(Room room, String key) {
return WiredInternalVariableSupport.readRoomValue(room, key);
}
private static Integer getUserTeamScore(Room room, Habbo habbo) {
if (room == null || habbo == null || habbo.getHabboInfo().getGamePlayer() == null) {
return null;
}
Game game = resolveTeamGame(room, habbo);
GamePlayer gamePlayer = habbo.getHabboInfo().getGamePlayer();
if (game == null || gamePlayer.getTeamColor() == null) {
return gamePlayer.getScore();
}
GameTeam team = game.getTeam(gamePlayer.getTeamColor());
return (team != null) ? team.getTotalScore() : gamePlayer.getScore();
}
private static Integer getTeamColorId(int effectId) {
TeamEffectData data = getTeamEffectData(effectId);
return data == null ? null : data.colorId;
}
private static Integer getTeamTypeId(int effectId) {
TeamEffectData data = getTeamEffectData(effectId);
return data == null ? null : data.typeId;
}
private static int getTeamMetric(Room room, GameTeamColors color, boolean score) {
Game game = resolveTeamGame(room, null);
if (game == null || color == null) {
return 0;
}
GameTeam team = game.getTeam(color);
if (team == null) {
return 0;
}
return score ? team.getTotalScore() : team.getMembers().size();
}
private static Game resolveTeamGame(Room room, Habbo habbo) {
if (room == null) {
return null;
}
if (habbo != null && habbo.getHabboInfo() != null && habbo.getHabboInfo().getCurrentGame() != null) {
Game game = room.getGame(habbo.getHabboInfo().getCurrentGame());
if (game != null) {
return game;
}
}
Game wiredGame = room.getGame(WiredGame.class);
if (wiredGame != null) {
return wiredGame;
}
Game freezeGame = room.getGame(FreezeGame.class);
if (freezeGame != null) {
return freezeGame;
}
return room.getGame(BattleBanzaiGame.class);
}
private static TeamEffectData getTeamEffectData(int effectValue) {
if (effectValue <= 0) {
return null;
}
if (effectValue >= 223 && effectValue <= 226) {
return new TeamEffectData(effectValue - 222, 0);
}
if (effectValue >= 33 && effectValue <= 36) {
return new TeamEffectData(effectValue - 32, 1);
}
if (effectValue >= 40 && effectValue <= 43) {
return new TeamEffectData(effectValue - 39, 2);
}
return null;
}
private static Integer parseInteger(String value) {
if (value == null || value.trim().isEmpty()) {
return null;
}
try {
return Integer.parseInt(value.trim());
} catch (NumberFormatException ignored) {
return null;
}
}
private static class TeamEffectData {
final int colorId;
final int typeId;
TeamEffectData(int colorId, int typeId) {
this.colorId = colorId;
this.typeId = typeId;
}
}
}
@@ -4,7 +4,9 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterFurni;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterFurniByVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterUser;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFilterUsersByVariable;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomUnit;
import com.eu.habbo.habbohotel.users.HabboItem;
@@ -172,21 +174,50 @@ public final class WiredTriggerSourceUtil {
int furniLimit = Integer.MAX_VALUE;
int userLimit = Integer.MAX_VALUE;
List<WiredExtraFilterFurniByVariable> furniVariableFilters = new ArrayList<>();
List<WiredExtraFilterUsersByVariable> userVariableFilters = new ArrayList<>();
for (InteractionWiredExtra extra : extras) {
if (extra instanceof WiredExtraFilterFurni) {
furniLimit = Math.min(furniLimit, ((WiredExtraFilterFurni) extra).getAmount());
} else if (extra instanceof WiredExtraFilterUser) {
userLimit = Math.min(userLimit, ((WiredExtraFilterUser) extra).getAmount());
} else if (extra instanceof WiredExtraFilterFurniByVariable) {
furniVariableFilters.add((WiredExtraFilterFurniByVariable) extra);
} else if (extra instanceof WiredExtraFilterUsersByVariable) {
userVariableFilters.add((WiredExtraFilterUsersByVariable) extra);
}
}
if (selectorCtx.targets().isItemsModifiedBySelector() && furniLimit != Integer.MAX_VALUE) {
selectorCtx.targets().setItems(limitIterable(selectorCtx.targets().items(), furniLimit));
furniVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId()));
userVariableFilters.sort((left, right) -> Integer.compare(left.getId(), right.getId()));
if (selectorCtx.targets().isItemsModifiedBySelector()) {
Iterable<HabboItem> filteredItems = selectorCtx.targets().items();
for (WiredExtraFilterFurniByVariable extra : furniVariableFilters) {
filteredItems = extra.filterItems(room, selectorCtx, filteredItems);
}
if (furniLimit != Integer.MAX_VALUE) {
filteredItems = limitIterable(filteredItems, furniLimit);
}
selectorCtx.targets().setItems(filteredItems);
}
if (selectorCtx.targets().isUsersModifiedBySelector() && userLimit != Integer.MAX_VALUE) {
selectorCtx.targets().setUsers(limitIterable(selectorCtx.targets().users(), userLimit));
if (selectorCtx.targets().isUsersModifiedBySelector()) {
Iterable<RoomUnit> filteredUsers = selectorCtx.targets().users();
for (WiredExtraFilterUsersByVariable extra : userVariableFilters) {
filteredUsers = extra.filterUsers(room, selectorCtx, filteredUsers);
}
if (userLimit != Integer.MAX_VALUE) {
filteredUsers = limitIterable(filteredUsers, userLimit);
}
selectorCtx.targets().setUsers(filteredUsers);
}
}
@@ -0,0 +1,564 @@
package com.eu.habbo.habbohotel.wired.core;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraFurniVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRoomVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraUserVariable;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableLevelUpSystem;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableReference;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
import gnu.trove.set.hash.THashSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public final class WiredVariableLevelSystemSupport {
public static final int TARGET_USER = 0;
public static final int TARGET_FURNI = 1;
public static final int TARGET_ROOM = 3;
private static final int SYNTHETIC_USER_OFFSET = 700_000_000;
private static final int SYNTHETIC_FURNI_OFFSET = 800_000_000;
private static final int SYNTHETIC_ROOM_OFFSET = 900_000_000;
private static final int SYNTHETIC_STRIDE = 16;
private WiredVariableLevelSystemSupport() {
}
public static WiredExtraVariableLevelUpSystem getLevelSystem(Room room, InteractionWiredExtra definition) {
if (room == null || definition == null || room.getRoomSpecialTypes() == null) {
return null;
}
THashSet<InteractionWiredExtra> extras = room.getRoomSpecialTypes().getExtras(definition.getX(), definition.getY());
if (extras == null || extras.isEmpty()) {
return null;
}
for (InteractionWiredExtra extra : WiredExecutionOrderUtil.sort(extras)) {
if (extra instanceof WiredExtraVariableLevelUpSystem) {
return (WiredExtraVariableLevelUpSystem) extra;
}
}
return null;
}
public static List<WiredVariableDefinitionInfo> getDerivedDefinitions(Room room, int targetType, InteractionWiredExtra definitionExtra, WiredVariableDefinitionInfo baseDefinition) {
if (room == null || definitionExtra == null || baseDefinition == null || !baseDefinition.hasValue()) {
return Collections.emptyList();
}
WiredExtraVariableLevelUpSystem levelSystem = getLevelSystem(room, definitionExtra);
if (levelSystem == null) {
return Collections.emptyList();
}
List<WiredVariableDefinitionInfo> result = new ArrayList<>();
for (int subvariableType : levelSystem.getSelectedSubvariables()) {
result.add(new WiredVariableDefinitionInfo(
createSyntheticItemId(targetType, baseDefinition.getItemId(), subvariableType),
baseDefinition.getName() + "." + getSubvariableKey(subvariableType),
true,
baseDefinition.getAvailability(),
false,
true
));
}
result.sort(Comparator.comparing(WiredVariableDefinitionInfo::getName, String.CASE_INSENSITIVE_ORDER).thenComparingInt(WiredVariableDefinitionInfo::getItemId));
return result;
}
public static WiredVariableDefinitionInfo getDerivedDefinitionInfo(Room room, int targetType, int syntheticItemId) {
DerivedDefinition derived = resolveDerivedDefinition(room, targetType, syntheticItemId);
if (derived == null) {
return null;
}
return new WiredVariableDefinitionInfo(
derived.syntheticItemId,
derived.variableName,
true,
derived.baseDefinition.getAvailability(),
false,
true
);
}
public static DerivedDefinition resolveDerivedDefinition(Room room, int targetType, int syntheticItemId) {
DecodedSyntheticId decoded = decodeSyntheticId(syntheticItemId);
if (decoded == null || decoded.targetType != targetType || room == null || room.getRoomSpecialTypes() == null) {
return null;
}
InteractionWiredExtra baseExtra = room.getRoomSpecialTypes().getExtra(decoded.baseDefinitionItemId);
if (!matchesTarget(baseExtra, targetType)) {
return null;
}
WiredVariableDefinitionInfo baseDefinition = createBaseDefinitionInfo(room, baseExtra, targetType);
if (baseDefinition == null || !baseDefinition.hasValue()) {
return null;
}
WiredExtraVariableLevelUpSystem levelSystem = getLevelSystem(room, baseExtra);
if (levelSystem == null || !levelSystem.hasSubvariable(decoded.subvariableType)) {
return null;
}
return new DerivedDefinition(
syntheticItemId,
decoded.baseDefinitionItemId,
decoded.subvariableType,
baseDefinition.getName() + "." + getSubvariableKey(decoded.subvariableType),
baseDefinition,
levelSystem
);
}
public static Integer getDerivedValue(WiredExtraVariableLevelUpSystem levelSystem, int subvariableType, Integer baseValue) {
if (levelSystem == null || baseValue == null) {
return null;
}
LevelProgress progress = calculateProgress(levelSystem, baseValue);
return switch (subvariableType) {
case WiredExtraVariableLevelUpSystem.SUB_CURRENT_LEVEL -> progress.currentLevel;
case WiredExtraVariableLevelUpSystem.SUB_CURRENT_XP -> progress.currentXp;
case WiredExtraVariableLevelUpSystem.SUB_LEVEL_PROGRESS -> progress.progressXp;
case WiredExtraVariableLevelUpSystem.SUB_LEVEL_PROGRESS_PERCENT -> progress.progressPercent;
case WiredExtraVariableLevelUpSystem.SUB_TOTAL_XP_REQUIRED -> progress.totalXpRequired;
case WiredExtraVariableLevelUpSystem.SUB_XP_REMAINING -> progress.xpRemaining;
case WiredExtraVariableLevelUpSystem.SUB_IS_AT_MAX -> progress.isAtMax ? 1 : 0;
case WiredExtraVariableLevelUpSystem.SUB_MAX_LEVEL -> progress.maxLevel;
default -> null;
};
}
public static List<LevelEntry> buildPreviewEntries(WiredExtraVariableLevelUpSystem levelSystem) {
if (levelSystem == null) {
return Collections.emptyList();
}
return buildThresholdEntries(levelSystem);
}
private static boolean matchesTarget(InteractionWiredExtra extra, int targetType) {
if (extra == null) {
return false;
}
return switch (targetType) {
case TARGET_FURNI -> extra instanceof WiredExtraFurniVariable;
case TARGET_ROOM -> (extra instanceof WiredExtraRoomVariable)
|| (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isRoomReference());
default -> (extra instanceof WiredExtraUserVariable)
|| (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isUserReference());
};
}
private static WiredVariableDefinitionInfo createBaseDefinitionInfo(Room room, InteractionWiredExtra extra, int targetType) {
if (room == null || extra == null) {
return null;
}
if (targetType == TARGET_FURNI && extra instanceof WiredExtraFurniVariable) {
WiredExtraFurniVariable definition = (WiredExtraFurniVariable) extra;
return new WiredVariableDefinitionInfo(
definition.getId(),
definition.getVariableName(),
definition.hasValue(),
definition.getAvailability(),
WiredVariableTextConnectorSupport.isTextConnected(room, definition),
false
);
}
if (targetType == TARGET_USER) {
if (extra instanceof WiredExtraUserVariable) {
WiredExtraUserVariable definition = (WiredExtraUserVariable) extra;
return new WiredVariableDefinitionInfo(
definition.getId(),
definition.getVariableName(),
definition.hasValue(),
definition.getAvailability(),
WiredVariableTextConnectorSupport.isTextConnected(room, definition),
false
);
}
if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isUserReference()) {
WiredExtraVariableReference reference = (WiredExtraVariableReference) extra;
return new WiredVariableDefinitionInfo(reference.getId(), reference.getVariableName(), reference.hasValue(), reference.getAvailability(), false, reference.isReadOnly());
}
}
if (targetType == TARGET_ROOM) {
if (extra instanceof WiredExtraRoomVariable) {
WiredExtraRoomVariable definition = (WiredExtraRoomVariable) extra;
return new WiredVariableDefinitionInfo(
definition.getId(),
definition.getVariableName(),
definition.hasValue(),
definition.getAvailability(),
WiredVariableTextConnectorSupport.isTextConnected(room, definition),
false
);
}
if (extra instanceof WiredExtraVariableReference && ((WiredExtraVariableReference) extra).isRoomReference()) {
WiredExtraVariableReference reference = (WiredExtraVariableReference) extra;
return new WiredVariableDefinitionInfo(reference.getId(), reference.getVariableName(), reference.hasValue(), reference.getAvailability(), false, reference.isReadOnly());
}
}
return null;
}
private static int createSyntheticItemId(int targetType, int baseDefinitionItemId, int subvariableType) {
int offset = switch (targetType) {
case TARGET_FURNI -> SYNTHETIC_FURNI_OFFSET;
case TARGET_ROOM -> SYNTHETIC_ROOM_OFFSET;
default -> SYNTHETIC_USER_OFFSET;
};
return offset + (baseDefinitionItemId * SYNTHETIC_STRIDE) + (subvariableType + 1);
}
private static DecodedSyntheticId decodeSyntheticId(int syntheticItemId) {
if (syntheticItemId >= SYNTHETIC_ROOM_OFFSET) {
return decodeSyntheticId(syntheticItemId, TARGET_ROOM, SYNTHETIC_ROOM_OFFSET);
}
if (syntheticItemId >= SYNTHETIC_FURNI_OFFSET) {
return decodeSyntheticId(syntheticItemId, TARGET_FURNI, SYNTHETIC_FURNI_OFFSET);
}
if (syntheticItemId >= SYNTHETIC_USER_OFFSET) {
return decodeSyntheticId(syntheticItemId, TARGET_USER, SYNTHETIC_USER_OFFSET);
}
return null;
}
private static DecodedSyntheticId decodeSyntheticId(int syntheticItemId, int targetType, int offset) {
int localValue = syntheticItemId - offset;
if (localValue < 0) {
return null;
}
int encodedSubvariable = localValue % SYNTHETIC_STRIDE;
int baseDefinitionItemId = localValue / SYNTHETIC_STRIDE;
int subvariableType = encodedSubvariable - 1;
if (baseDefinitionItemId <= 0 || subvariableType < 0 || subvariableType >= WiredExtraVariableLevelUpSystem.SUBVARIABLE_COUNT) {
return null;
}
return new DecodedSyntheticId(targetType, baseDefinitionItemId, subvariableType);
}
private static String getSubvariableKey(int subvariableType) {
return switch (subvariableType) {
case WiredExtraVariableLevelUpSystem.SUB_CURRENT_LEVEL -> "current_level";
case WiredExtraVariableLevelUpSystem.SUB_CURRENT_XP -> "current_xp";
case WiredExtraVariableLevelUpSystem.SUB_LEVEL_PROGRESS -> "level_progress";
case WiredExtraVariableLevelUpSystem.SUB_LEVEL_PROGRESS_PERCENT -> "level_progress_percent";
case WiredExtraVariableLevelUpSystem.SUB_TOTAL_XP_REQUIRED -> "total_xp_required";
case WiredExtraVariableLevelUpSystem.SUB_XP_REMAINING -> "xp_remaining";
case WiredExtraVariableLevelUpSystem.SUB_IS_AT_MAX -> "is_at_max";
case WiredExtraVariableLevelUpSystem.SUB_MAX_LEVEL -> "max_level";
default -> "value";
};
}
private static LevelProgress calculateProgress(WiredExtraVariableLevelUpSystem levelSystem, int rawBaseValue) {
int currentXp = Math.max(0, rawBaseValue);
List<LevelEntry> entries = buildThresholdEntries(levelSystem);
if (entries.isEmpty()) {
entries = new ArrayList<>();
entries.add(new LevelEntry(1, 0));
}
int maxLevel = entries.get(entries.size() - 1).level;
int currentLevel = 1;
int currentThreshold = 0;
int nextThreshold = 0;
for (int index = 0; index < entries.size(); index++) {
LevelEntry entry = entries.get(index);
if (currentXp >= entry.requiredXp) {
currentLevel = entry.level;
currentThreshold = entry.requiredXp;
nextThreshold = (index + 1 < entries.size()) ? entries.get(index + 1).requiredXp : entry.requiredXp;
continue;
}
nextThreshold = entry.requiredXp;
break;
}
boolean isAtMax = currentLevel >= maxLevel;
if (isAtMax) {
nextThreshold = currentThreshold;
}
int progressXp = Math.max(0, currentXp - currentThreshold);
int progressPercent;
if (isAtMax) {
progressPercent = 100;
} else {
int delta = Math.max(0, nextThreshold - currentThreshold);
progressPercent = (delta <= 0) ? 100 : Math.max(0, Math.min(100, (int) Math.floor((progressXp * 100D) / delta)));
}
int totalXpRequired = isAtMax ? currentThreshold : nextThreshold;
int xpRemaining = Math.max(0, totalXpRequired - currentXp);
return new LevelProgress(currentLevel, currentXp, progressXp, progressPercent, totalXpRequired, xpRemaining, isAtMax, maxLevel);
}
private static List<LevelEntry> buildThresholdEntries(WiredExtraVariableLevelUpSystem levelSystem) {
return switch (levelSystem.getMode()) {
case WiredExtraVariableLevelUpSystem.MODE_EXPONENTIAL -> buildExponentialEntries(levelSystem);
case WiredExtraVariableLevelUpSystem.MODE_MANUAL -> buildManualEntries(levelSystem);
default -> buildLinearEntries(levelSystem);
};
}
private static List<LevelEntry> buildLinearEntries(WiredExtraVariableLevelUpSystem levelSystem) {
List<LevelEntry> entries = new ArrayList<>();
int maxLevel = Math.max(1, levelSystem.getMaxLevel());
int stepSize = Math.max(0, levelSystem.getStepSize());
for (int level = 1; level <= maxLevel; level++) {
entries.add(new LevelEntry(level, clamp((long) (level - 1) * stepSize)));
}
return entries;
}
private static List<LevelEntry> buildExponentialEntries(WiredExtraVariableLevelUpSystem levelSystem) {
List<LevelEntry> entries = new ArrayList<>();
int maxLevel = Math.max(1, levelSystem.getMaxLevel());
int currentIncrement = Math.max(0, levelSystem.getFirstLevelXp());
int factor = Math.max(0, levelSystem.getIncreaseFactor());
long threshold = 0L;
entries.add(new LevelEntry(1, 0));
for (int level = 2; level <= maxLevel; level++) {
threshold += currentIncrement;
entries.add(new LevelEntry(level, clamp(threshold)));
currentIncrement = clamp(Math.round(currentIncrement * (100D + factor) / 100D));
}
return entries;
}
private static List<LevelEntry> buildManualEntries(WiredExtraVariableLevelUpSystem levelSystem) {
LinkedHashMap<Integer, Integer> anchors = parseAnchors(levelSystem.getInterpolationText());
if (!anchors.containsKey(1)) {
anchors.put(1, 0);
}
List<Map.Entry<Integer, Integer>> sortedAnchors = new ArrayList<>(anchors.entrySet());
sortedAnchors.sort(Map.Entry.comparingByKey());
if (sortedAnchors.isEmpty()) {
return Collections.singletonList(new LevelEntry(1, 0));
}
LinkedHashMap<Integer, Integer> result = new LinkedHashMap<>();
for (int index = 0; index < sortedAnchors.size(); index++) {
Map.Entry<Integer, Integer> current = sortedAnchors.get(index);
int currentLevel = Math.max(1, current.getKey());
int currentXp = Math.max(0, current.getValue());
result.put(currentLevel, currentXp);
if (index + 1 >= sortedAnchors.size()) {
continue;
}
Map.Entry<Integer, Integer> next = sortedAnchors.get(index + 1);
int nextLevel = Math.max(currentLevel, next.getKey());
int nextXp = Math.max(0, next.getValue());
if (nextLevel <= currentLevel) {
continue;
}
for (int level = currentLevel + 1; level < nextLevel; level++) {
double ratio = (double) (level - currentLevel) / (double) (nextLevel - currentLevel);
int interpolatedXp = clamp(Math.round(currentXp + ((nextXp - currentXp) * ratio)));
result.put(level, interpolatedXp);
}
}
List<LevelEntry> entries = new ArrayList<>();
for (Map.Entry<Integer, Integer> entry : result.entrySet()) {
entries.add(new LevelEntry(entry.getKey(), entry.getValue()));
}
entries.sort(Comparator.comparingInt(levelEntry -> levelEntry.level));
return entries;
}
private static LinkedHashMap<Integer, Integer> parseAnchors(String interpolationText) {
LinkedHashMap<Integer, Integer> result = new LinkedHashMap<>();
if (interpolationText == null || interpolationText.trim().isEmpty()) {
return result;
}
for (String rawLine : interpolationText.split("\n")) {
if (rawLine == null) {
continue;
}
String line = rawLine.trim();
if (line.isEmpty()) {
continue;
}
int separatorIndex = line.indexOf('=');
if (separatorIndex < 0) {
separatorIndex = line.indexOf(',');
}
if (separatorIndex <= 0) {
continue;
}
Integer level = parseInteger(line.substring(0, separatorIndex));
Integer xp = parseInteger(line.substring(separatorIndex + 1));
if (level == null || xp == null || level <= 0 || xp < 0) {
continue;
}
result.put(level, xp);
}
return result;
}
private static Integer parseInteger(String value) {
try {
return (value == null || value.trim().isEmpty()) ? null : Integer.parseInt(value.trim());
} catch (NumberFormatException ignored) {
return null;
}
}
private static int clamp(long value) {
if (value > Integer.MAX_VALUE) {
return Integer.MAX_VALUE;
}
if (value < Integer.MIN_VALUE) {
return Integer.MIN_VALUE;
}
return (int) value;
}
public static class DerivedDefinition {
private final int syntheticItemId;
private final int baseDefinitionItemId;
private final int subvariableType;
private final String variableName;
private final WiredVariableDefinitionInfo baseDefinition;
private final WiredExtraVariableLevelUpSystem levelSystem;
public DerivedDefinition(int syntheticItemId, int baseDefinitionItemId, int subvariableType, String variableName, WiredVariableDefinitionInfo baseDefinition, WiredExtraVariableLevelUpSystem levelSystem) {
this.syntheticItemId = syntheticItemId;
this.baseDefinitionItemId = baseDefinitionItemId;
this.subvariableType = subvariableType;
this.variableName = variableName;
this.baseDefinition = baseDefinition;
this.levelSystem = levelSystem;
}
public int getBaseDefinitionItemId() {
return this.baseDefinitionItemId;
}
public int getSubvariableType() {
return this.subvariableType;
}
public WiredVariableDefinitionInfo getBaseDefinition() {
return this.baseDefinition;
}
public WiredExtraVariableLevelUpSystem getLevelSystem() {
return this.levelSystem;
}
}
public static class LevelEntry {
private final int level;
private final int requiredXp;
public LevelEntry(int level, int requiredXp) {
this.level = level;
this.requiredXp = requiredXp;
}
public int getLevel() {
return this.level;
}
public int getRequiredXp() {
return this.requiredXp;
}
}
private static class LevelProgress {
private final int currentLevel;
private final int currentXp;
private final int progressXp;
private final int progressPercent;
private final int totalXpRequired;
private final int xpRemaining;
private final boolean isAtMax;
private final int maxLevel;
private LevelProgress(int currentLevel, int currentXp, int progressXp, int progressPercent, int totalXpRequired, int xpRemaining, boolean isAtMax, int maxLevel) {
this.currentLevel = currentLevel;
this.currentXp = currentXp;
this.progressXp = progressXp;
this.progressPercent = progressPercent;
this.totalXpRequired = totalXpRequired;
this.xpRemaining = xpRemaining;
this.isAtMax = isAtMax;
this.maxLevel = maxLevel;
}
}
private static class DecodedSyntheticId {
private final int targetType;
private final int baseDefinitionItemId;
private final int subvariableType;
private DecodedSyntheticId(int targetType, int baseDefinitionItemId, int subvariableType) {
this.targetType = targetType;
this.baseDefinitionItemId = baseDefinitionItemId;
this.subvariableType = subvariableType;
}
}
}
@@ -0,0 +1,61 @@
package com.eu.habbo.habbohotel.wired.core;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraVariableTextConnector;
import com.eu.habbo.habbohotel.rooms.Room;
import gnu.trove.set.hash.THashSet;
public final class WiredVariableTextConnectorSupport {
private WiredVariableTextConnectorSupport() {
}
public static boolean isTextConnected(Room room, InteractionWiredExtra definition) {
return getConnector(room, definition) != null;
}
public static boolean isTextConnected(Room room, int definitionItemId) {
return getConnector(room, definitionItemId) != null;
}
public static WiredExtraVariableTextConnector getConnector(Room room, int definitionItemId) {
if (room == null || room.getRoomSpecialTypes() == null || definitionItemId <= 0) {
return null;
}
InteractionWiredExtra extra = room.getRoomSpecialTypes().getExtra(definitionItemId);
return getConnector(room, extra);
}
public static WiredExtraVariableTextConnector getConnector(Room room, InteractionWiredExtra definition) {
if (room == null || definition == null || room.getRoomSpecialTypes() == null) {
return null;
}
THashSet<InteractionWiredExtra> extras = room.getRoomSpecialTypes().getExtras(definition.getX(), definition.getY());
if (extras == null || extras.isEmpty()) {
return null;
}
for (InteractionWiredExtra extra : WiredExecutionOrderUtil.sort(extras)) {
if (extra instanceof WiredExtraVariableTextConnector) {
return (WiredExtraVariableTextConnector) extra;
}
}
return null;
}
public static String toText(Room room, int definitionItemId, Integer value) {
if (value == null) {
return "";
}
WiredExtraVariableTextConnector connector = getConnector(room, definitionItemId);
return connector != null ? connector.resolveText(value) : String.valueOf(value);
}
public static Integer toValue(Room room, int definitionItemId, String text) {
WiredExtraVariableTextConnector connector = getConnector(room, definitionItemId);
return connector != null ? connector.resolveValue(text) : null;
}
}
@@ -167,9 +167,15 @@ public final class WiredEvents {
* @return the event
*/
public static WiredEvent userSays(Room room, RoomUnit user, String message) {
return userSays(room, user, message, -1, -1);
}
public static WiredEvent userSays(Room room, RoomUnit user, String message, int chatType, int chatStyle) {
return WiredEvent.builder(WiredEvent.Type.USER_SAYS, room)
.actor(user)
.text(message)
.chatType(chatType)
.chatStyle(chatStyle)
.tile(user.getCurrentLocation())
.build();
}
@@ -192,6 +198,42 @@ public final class WiredEvents {
.build();
}
public static WiredEvent userVariableChanged(Room room, RoomUnit user, int definitionItemId, boolean created, boolean deleted, WiredEvent.VariableChangeKind changeKind) {
return WiredEvent.builder(WiredEvent.Type.VARIABLE_CHANGED, room)
.actor(user)
.tile((user != null) ? user.getCurrentLocation() : null)
.variableTargetType(0)
.variableDefinitionItemId(definitionItemId)
.variableCreated(created)
.variableDeleted(deleted)
.variableChangeKind(changeKind)
.build();
}
public static WiredEvent furniVariableChanged(Room room, HabboItem item, int definitionItemId, boolean created, boolean deleted, WiredEvent.VariableChangeKind changeKind) {
RoomTile tile = (item != null) ? room.getLayout().getTile(item.getX(), item.getY()) : null;
return WiredEvent.builder(WiredEvent.Type.VARIABLE_CHANGED, room)
.sourceItem(item)
.tile(tile)
.variableTargetType(1)
.variableDefinitionItemId(definitionItemId)
.variableCreated(created)
.variableDeleted(deleted)
.variableChangeKind(changeKind)
.build();
}
public static WiredEvent roomVariableChanged(Room room, int definitionItemId, WiredEvent.VariableChangeKind changeKind) {
return WiredEvent.builder(WiredEvent.Type.VARIABLE_CHANGED, room)
.variableTargetType(3)
.variableDefinitionItemId(definitionItemId)
.variableCreated(false)
.variableDeleted(false)
.variableChangeKind(changeKind)
.build();
}
// ========== Timer Events ==========
/**
@@ -67,6 +67,12 @@ import com.eu.habbo.messages.incoming.users.*;
import com.eu.habbo.messages.incoming.wired.WiredApplySetConditionsEvent;
import com.eu.habbo.messages.incoming.wired.WiredConditionSaveDataEvent;
import com.eu.habbo.messages.incoming.wired.WiredEffectSaveDataEvent;
import com.eu.habbo.messages.incoming.wired.WiredMonitorRequestEvent;
import com.eu.habbo.messages.incoming.wired.WiredRoomSettingsRequestEvent;
import com.eu.habbo.messages.incoming.wired.WiredRoomSettingsSaveEvent;
import com.eu.habbo.messages.incoming.wired.WiredUserVariableManageEvent;
import com.eu.habbo.messages.incoming.wired.WiredUserVariableUpdateEvent;
import com.eu.habbo.messages.incoming.wired.WiredUserVariablesRequestEvent;
import com.eu.habbo.messages.incoming.wired.WiredTriggerSaveDataEvent;
import com.eu.habbo.plugin.EventHandler;
import com.eu.habbo.plugin.events.emulator.EmulatorConfigUpdatedEvent;
@@ -615,6 +621,12 @@ public class PacketManager {
this.registerHandler(Incoming.WiredEffectSaveDataEvent, WiredEffectSaveDataEvent.class);
this.registerHandler(Incoming.WiredConditionSaveDataEvent, WiredConditionSaveDataEvent.class);
this.registerHandler(Incoming.WiredApplySetConditionsEvent, WiredApplySetConditionsEvent.class);
this.registerHandler(Incoming.WiredMonitorRequestEvent, WiredMonitorRequestEvent.class);
this.registerHandler(Incoming.WiredRoomSettingsRequestEvent, WiredRoomSettingsRequestEvent.class);
this.registerHandler(Incoming.WiredRoomSettingsSaveEvent, WiredRoomSettingsSaveEvent.class);
this.registerHandler(Incoming.WiredUserVariablesRequestEvent, WiredUserVariablesRequestEvent.class);
this.registerHandler(Incoming.WiredUserVariableUpdateEvent, WiredUserVariableUpdateEvent.class);
this.registerHandler(Incoming.WiredUserVariableManageEvent, WiredUserVariableManageEvent.class);
}
void registerUnknown() throws Exception {
@@ -409,6 +409,12 @@ public class Incoming {
// CUSTOM
public static final int UpdateFurniturePositionEvent = 10019;
public static final int ClickUserEvent = 10020;
public static final int WiredMonitorRequestEvent = 10021;
public static final int WiredRoomSettingsRequestEvent = 10022;
public static final int WiredRoomSettingsSaveEvent = 10023;
public static final int WiredUserVariablesRequestEvent = 10024;
public static final int WiredUserVariableUpdateEvent = 10025;
public static final int WiredUserVariableManageEvent = 10026;
public static final int RequestInventoryPetDelete = 10030;
public static final int RequestInventoryBadgeDelete = 10031;
@@ -41,7 +41,7 @@ public class WiredApplySetConditionsEvent extends MessageHandler {
if (room != null) {
// Executing Habbo should be able to edit wireds
if (room.hasRights(this.client.getHabbo()) || room.isOwner(this.client.getHabbo())) {
if (room.canModifyWired(this.client.getHabbo())) {
List<HabboItem> wireds = new ArrayList<>();
wireds.addAll(room.getRoomSpecialTypes().getConditions());
@@ -4,7 +4,6 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.items.interactions.InteractionWired;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredCondition;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.messages.incoming.MessageHandler;
@@ -23,7 +22,7 @@ public class WiredConditionSaveDataEvent extends MessageHandler {
Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom();
if (room != null) {
if (room.hasRights(this.client.getHabbo()) || room.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || this.client.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER) || this.client.getHabbo().hasPermission(Permission.ACC_MOVEROTATE)) {
if (room.canModifyWired(this.client.getHabbo())) {
InteractionWiredCondition condition = room.getRoomSpecialTypes().getCondition(itemId);
if (condition != null) {
@@ -5,7 +5,6 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWired;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.messages.incoming.MessageHandler;
@@ -20,7 +19,7 @@ public class WiredEffectSaveDataEvent extends MessageHandler {
Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom();
if (room != null) {
if (room.hasRights(this.client.getHabbo()) || room.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || this.client.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER) || this.client.getHabbo().hasPermission(Permission.ACC_MOVEROTATE)) {
if (room.canModifyWired(this.client.getHabbo())) {
InteractionWiredEffect effect = room.getRoomSpecialTypes().getEffect(itemId);
InteractionWiredExtra extra = room.getRoomSpecialTypes().getExtra(itemId);
@@ -0,0 +1,41 @@
package com.eu.habbo.messages.incoming.wired;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.wired.WiredMonitorDataComposer;
public class WiredMonitorRequestEvent extends MessageHandler {
private static final int ACTION_FETCH = 0;
private static final int ACTION_CLEAR_LOGS = 1;
@Override
public void handle() throws Exception {
Room room = currentRoom();
if (room == null) {
return;
}
if (!room.canInspectWired(this.client.getHabbo())) {
return;
}
int action = ACTION_FETCH;
if (this.packet.bytesAvailable() >= 4) {
action = this.packet.readInt();
}
if ((action == ACTION_CLEAR_LOGS) && room.canModifyWired(this.client.getHabbo())) {
WiredManager.clearDiagnosticsLogs(room.getId());
}
this.client.sendResponse(new WiredMonitorDataComposer(WiredManager.getDiagnosticsSnapshot(room.getId())));
}
@Override
public int getRatelimit() {
return 50;
}
}
@@ -0,0 +1,23 @@
package com.eu.habbo.messages.incoming.wired;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.wired.WiredRoomSettingsDataComposer;
public class WiredRoomSettingsRequestEvent extends MessageHandler {
@Override
public void handle() throws Exception {
Room room = currentRoom();
if (room == null) {
return;
}
this.client.sendResponse(new WiredRoomSettingsDataComposer(room, this.client.getHabbo()));
}
@Override
public int getRatelimit() {
return 250;
}
}
@@ -0,0 +1,38 @@
package com.eu.habbo.messages.incoming.wired;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.wired.WiredRoomSettingsDataComposer;
public class WiredRoomSettingsSaveEvent extends MessageHandler {
@Override
public void handle() throws Exception {
Room room = currentRoom();
if (room == null) {
return;
}
if (this.packet.bytesAvailable() < 8) {
this.client.sendResponse(new WiredRoomSettingsDataComposer(room, this.client.getHabbo()));
return;
}
if (!room.canManageWiredSettings(this.client.getHabbo())) {
this.client.sendResponse(new WiredRoomSettingsDataComposer(room, this.client.getHabbo()));
return;
}
int inspectMask = this.packet.readInt();
int modifyMask = this.packet.readInt();
room.saveWiredSettings(inspectMask, modifyMask);
this.client.sendResponse(new WiredRoomSettingsDataComposer(room, this.client.getHabbo()));
}
@Override
public int getRatelimit() {
return 250;
}
}
@@ -4,7 +4,6 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.items.interactions.InteractionWired;
import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger;
import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.wired.core.WiredManager;
import com.eu.habbo.messages.incoming.MessageHandler;
@@ -19,7 +18,7 @@ public class WiredTriggerSaveDataEvent extends MessageHandler {
Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom();
if (room != null) {
if (room.hasRights(this.client.getHabbo()) || room.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || this.client.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER) || this.client.getHabbo().hasPermission(Permission.ACC_MOVEROTATE)) {
if (room.canModifyWired(this.client.getHabbo())) {
InteractionWiredTrigger trigger = room.getRoomSpecialTypes().getTrigger(itemId);
if (trigger != null) {
@@ -0,0 +1,74 @@
package com.eu.habbo.messages.incoming.wired;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboItem;
import com.eu.habbo.messages.incoming.MessageHandler;
public class WiredUserVariableManageEvent extends MessageHandler {
private static final int ACTION_ASSIGN = 0;
private static final int ACTION_REMOVE = 1;
private static final int TARGET_ROOM = 3;
@Override
public void handle() throws Exception {
Room room = currentRoom();
if (room == null) {
return;
}
if (!room.canModifyWired(this.client.getHabbo())) {
room.getRoomVariableManager().sendSnapshot(this.client.getHabbo());
return;
}
if (this.packet.bytesAvailable() < 20) {
room.getRoomVariableManager().sendSnapshot(this.client.getHabbo());
return;
}
int action = this.packet.readInt();
int targetType = this.packet.readInt();
int targetId = this.packet.readInt();
int definitionItemId = this.packet.readInt();
int value = this.packet.readInt();
switch (targetType) {
case com.eu.habbo.habbohotel.items.interactions.wired.effects.WiredEffectGiveVariable.TARGET_FURNI:
if (action == ACTION_REMOVE) {
room.getFurniVariableManager().removeVariable(targetId, definitionItemId);
} else {
HabboItem furni = room.getHabboItem(targetId);
if (furni != null) {
room.getFurniVariableManager().assignVariable(furni, definitionItemId, value, true);
}
}
break;
case TARGET_ROOM:
if (action == ACTION_REMOVE) {
room.getRoomVariableManager().removeVariable(definitionItemId);
} else {
room.getRoomVariableManager().updateVariableValue(definitionItemId, value);
}
break;
default:
if (action == ACTION_REMOVE) {
room.getUserVariableManager().removeVariable(targetId, definitionItemId);
} else {
Habbo habbo = room.getHabbo(targetId);
if (habbo != null) {
room.getUserVariableManager().assignVariable(habbo, definitionItemId, value, true);
}
}
break;
}
room.getRoomVariableManager().sendSnapshot(this.client.getHabbo());
}
@Override
public int getRatelimit() {
return 150;
}
}
@@ -0,0 +1,53 @@
package com.eu.habbo.messages.incoming.wired;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.items.interactions.wired.effects.WiredEffectGiveVariable;
import com.eu.habbo.messages.incoming.MessageHandler;
public class WiredUserVariableUpdateEvent extends MessageHandler {
private static final int TARGET_ROOM = 3;
@Override
public void handle() throws Exception {
Room room = currentRoom();
if (room == null) {
return;
}
if (!room.canModifyWired(this.client.getHabbo())) {
room.getUserVariableManager().sendSnapshot(this.client.getHabbo());
return;
}
if (this.packet.bytesAvailable() < 16) {
room.getUserVariableManager().sendSnapshot(this.client.getHabbo());
return;
}
int targetType = this.packet.readInt();
int targetId = this.packet.readInt();
int definitionItemId = this.packet.readInt();
int value = this.packet.readInt();
if (targetType == WiredEffectGiveVariable.TARGET_FURNI) {
room.getFurniVariableManager().updateVariableValue(targetId, definitionItemId, value);
room.getFurniVariableManager().sendSnapshot(this.client.getHabbo());
return;
}
if (targetType == TARGET_ROOM) {
room.getRoomVariableManager().updateVariableValue(definitionItemId, value);
room.getRoomVariableManager().sendSnapshot(this.client.getHabbo());
return;
}
room.getUserVariableManager().updateVariableValue(targetId, definitionItemId, value);
room.getUserVariableManager().sendSnapshot(this.client.getHabbo());
}
@Override
public int getRatelimit() {
return 150;
}
}
@@ -0,0 +1,22 @@
package com.eu.habbo.messages.incoming.wired;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.messages.incoming.MessageHandler;
public class WiredUserVariablesRequestEvent extends MessageHandler {
@Override
public void handle() throws Exception {
Room room = currentRoom();
if (room == null) {
return;
}
room.getUserVariableManager().sendSnapshot(this.client.getHabbo());
}
@Override
public int getRatelimit() {
return 50;
}
}
@@ -120,6 +120,9 @@ public class Outgoing {
public final static int EnableNotificationsComposer = 3284; // PRODUCTION-201611291003-338511768
public final static int HallOfFameComposer = 3005; // PRODUCTION-201611291003-338511768
public final static int WiredSavedComposer = 1155; // PRODUCTION-201611291003-338511768
public final static int WiredMonitorDataComposer = 5101; // CUSTOM
public final static int WiredRoomSettingsDataComposer = 5102; // CUSTOM
public final static int WiredUserVariablesDataComposer = 5103; // CUSTOM
public final static int RoomPaintComposer = 2454; // PRODUCTION-201611291003-338511768
public final static int MarketplaceConfigComposer = 1823; // PRODUCTION-201611291003-338511768
public final static int AddBotComposer = 1352; // PRODUCTION-201611291003-338511768
@@ -18,9 +18,11 @@ public class ModToolUserInfoComposer extends MessageComposer {
private static final Logger LOGGER = LoggerFactory.getLogger(ModToolUserInfoComposer.class);
private final ResultSet set;
private final boolean hideMail;
public ModToolUserInfoComposer(ResultSet set) {
public ModToolUserInfoComposer(ResultSet set, boolean hideMail) {
this.set = set;
this.hideMail = hideMail;
}
@Override
@@ -58,7 +60,7 @@ public class ModToolUserInfoComposer extends MessageComposer {
this.response.appendString(""); //Last Purchase Timestamp
this.response.appendInt(this.set.getInt("user_id")); //Personal Identification #
this.response.appendInt(0); // Number of account bans
this.response.appendString(this.set.getBoolean("hide_mail") ? "" : this.set.getString("mail"));
this.response.appendString(this.hideMail ? "" : this.set.getString("mail"));
this.response.appendString("Rank (" + this.set.getInt("rank_id") + "): " + this.set.getString("rank_name")); //user_class_txt
ModToolSanctions modToolSanctions = Emulator.getGameEnvironment().getModToolSanctions();
@@ -5,7 +5,9 @@ import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.UUID;
public class WiredMovementsComposer extends MessageComposer {
public static final int TYPE_USER_MOVE = 0;
@@ -24,7 +26,7 @@ public class WiredMovementsComposer extends MessageComposer {
private final List<MovementData> movements;
public WiredMovementsComposer(List<MovementData> movements) {
this.movements = movements == null ? new ArrayList<>() : movements;
this.movements = normalizeMovements(movements == null ? new ArrayList<>() : movements);
}
@Override
@@ -40,6 +42,107 @@ public class WiredMovementsComposer extends MessageComposer {
return this.response;
}
private static List<MovementData> normalizeMovements(List<MovementData> source)
{
if((source == null) || source.isEmpty()) return new ArrayList<>();
final LinkedHashMap<String, MovementData> normalized = new LinkedHashMap<>();
for(final MovementData movement : source)
{
if(movement == null) continue;
final String key = movementKey(movement);
if(key == null)
{
normalized.put(UUID.randomUUID().toString(), movement);
continue;
}
final MovementData existing = normalized.get(key);
if(existing == null)
{
normalized.put(key, movement);
continue;
}
normalized.put(key, mergeMovement(existing, movement));
}
return new ArrayList<>(normalized.values());
}
private static String movementKey(MovementData movement)
{
if(movement instanceof FurniMovementData)
{
return "furni:" + ((FurniMovementData) movement).id;
}
if(movement instanceof UserMovementData)
{
return "user:" + ((UserMovementData) movement).id;
}
if(movement instanceof UserDirectionData)
{
return "userdir:" + ((UserDirectionData) movement).id;
}
if(movement instanceof WallItemMovementData)
{
return "wall:" + ((WallItemMovementData) movement).id;
}
return null;
}
private static MovementData mergeMovement(MovementData previous, MovementData current)
{
if((previous instanceof FurniMovementData) && (current instanceof FurniMovementData))
{
final FurniMovementData oldMovement = (FurniMovementData) previous;
final FurniMovementData newMovement = (FurniMovementData) current;
return furniMovement(
oldMovement.id,
oldMovement.fromX,
oldMovement.fromY,
newMovement.toX,
newMovement.toY,
oldMovement.fromZ,
newMovement.toZ,
newMovement.rotation,
newMovement.duration,
newMovement.elapsed,
newMovement.anchorType,
newMovement.anchorId);
}
if((previous instanceof UserMovementData) && (current instanceof UserMovementData))
{
final UserMovementData oldMovement = (UserMovementData) previous;
final UserMovementData newMovement = (UserMovementData) current;
return new UserMovementData(
oldMovement.id,
oldMovement.fromX,
oldMovement.fromY,
newMovement.toX,
newMovement.toY,
oldMovement.fromZ,
newMovement.toZ,
newMovement.movementType,
newMovement.bodyDirection,
newMovement.headDirection,
newMovement.duration);
}
return current;
}
public static MovementData furniMovement(int id, int fromX, int fromY, int toX, int toY, double fromZ, double toZ) {
return furniMovement(id, fromX, fromY, toX, toY, fromZ, toZ, 0, DEFAULT_DURATION, 0, FURNI_ANCHOR_NONE, 0);
}
@@ -92,13 +195,13 @@ public class WiredMovementsComposer extends MessageComposer {
}
private static final class UserMovementData extends BaseMovementData {
private final int id;
private final int fromX;
private final int fromY;
private final int toX;
private final int toY;
private final double fromZ;
private final double toZ;
private final int id;
private final int movementType;
private final int bodyDirection;
private final int headDirection;
@@ -136,13 +239,13 @@ public class WiredMovementsComposer extends MessageComposer {
}
private static final class FurniMovementData extends BaseMovementData {
private final int id;
private final int fromX;
private final int fromY;
private final int toX;
private final int toY;
private final double fromZ;
private final double toZ;
private final int id;
private final int rotation;
private final int duration;
private final int elapsed;
@@ -0,0 +1,84 @@
package com.eu.habbo.messages.outgoing.wired;
import com.eu.habbo.habbohotel.wired.core.WiredRoomDiagnostics;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
public class WiredMonitorDataComposer extends MessageComposer {
private final WiredRoomDiagnostics.Snapshot snapshot;
public WiredMonitorDataComposer(WiredRoomDiagnostics.Snapshot snapshot) {
this.snapshot = snapshot;
}
@Override
protected ServerMessage composeInternal() {
this.response.init(Outgoing.WiredMonitorDataComposer);
if (this.snapshot == null) {
this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendBoolean(false);
this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendInt(0);
this.response.appendInt(0);
return this.response;
}
this.response.appendInt(this.snapshot.getUsageCurrentWindow());
this.response.appendInt(this.snapshot.getUsageLimitPerWindow());
this.response.appendBoolean(this.snapshot.isHeavy());
this.response.appendInt(this.snapshot.getDelayedEventsPending());
this.response.appendInt(this.snapshot.getDelayedEventsLimit());
this.response.appendInt(this.snapshot.getAverageExecutionMs());
this.response.appendInt(this.snapshot.getPeakExecutionMs());
this.response.appendInt(this.snapshot.getRecursionDepthCurrent());
this.response.appendInt(this.snapshot.getRecursionDepthLimit());
this.response.appendInt(this.snapshot.getKilledRemainingSeconds());
this.response.appendInt(this.snapshot.getUsageWindowMs());
this.response.appendInt(this.snapshot.getOverloadAverageThresholdMs());
this.response.appendInt(this.snapshot.getOverloadPeakThresholdMs());
this.response.appendInt(this.snapshot.getHeavyUsageThresholdPercent());
this.response.appendInt(this.snapshot.getHeavyConsecutiveWindowsThreshold());
this.response.appendInt(this.snapshot.getOverloadConsecutiveWindowsThreshold());
this.response.appendInt(this.snapshot.getHeavyDelayedThresholdPercent());
this.response.appendInt(this.snapshot.getLogs().size());
for (WiredRoomDiagnostics.LogEntry log : this.snapshot.getLogs()) {
this.response.appendString(log.getType().name());
this.response.appendString(log.getSeverity().name());
this.response.appendInt(log.getCount());
this.response.appendInt((int) (log.getLastOccurredAtMs() / 1000L));
this.response.appendString(log.getLatestReason());
this.response.appendString(log.getLatestSourceLabel());
this.response.appendInt(log.getLatestSourceId());
}
this.response.appendInt(this.snapshot.getHistory().size());
for (WiredRoomDiagnostics.HistoryEntry historyEntry : this.snapshot.getHistory()) {
this.response.appendString(historyEntry.getType().name());
this.response.appendString(historyEntry.getSeverity().name());
this.response.appendInt((int) (historyEntry.getOccurredAtMs() / 1000L));
this.response.appendString(historyEntry.getReason());
this.response.appendString(historyEntry.getSourceLabel());
this.response.appendInt(historyEntry.getSourceId());
}
return this.response;
}
}
@@ -0,0 +1,38 @@
package com.eu.habbo.messages.outgoing.wired;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
public class WiredRoomSettingsDataComposer extends MessageComposer {
private final Room room;
private final Habbo habbo;
public WiredRoomSettingsDataComposer(Room room, Habbo habbo) {
this.room = room;
this.habbo = habbo;
}
@Override
protected ServerMessage composeInternal() {
this.response.init(Outgoing.WiredRoomSettingsDataComposer);
int roomId = (this.room != null) ? this.room.getId() : 0;
boolean canInspect = this.room != null && this.room.canInspectWired(this.habbo);
boolean canModify = this.room != null && this.room.canModifyWired(this.habbo);
boolean canManageSettings = this.room != null && this.room.canManageWiredSettings(this.habbo);
int inspectMask = canInspect ? this.room.getWiredInspectMask() : 0;
int modifyMask = canInspect ? this.room.getWiredModifyMask() : 0;
this.response.appendInt(roomId);
this.response.appendInt(inspectMask);
this.response.appendInt(modifyMask);
this.response.appendBoolean(canInspect);
this.response.appendBoolean(canModify);
this.response.appendBoolean(canManageSettings);
return this.response;
}
}
@@ -0,0 +1,171 @@
package com.eu.habbo.messages.outgoing.wired;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomFurniVariableManager;
import com.eu.habbo.habbohotel.rooms.RoomVariableManager;
import com.eu.habbo.habbohotel.rooms.RoomUserVariableManager;
import com.eu.habbo.habbohotel.rooms.WiredVariableDefinitionInfo;
import com.eu.habbo.habbohotel.wired.core.WiredContextVariableSupport;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
import java.util.Collections;
import java.util.List;
public class WiredUserVariablesDataComposer extends MessageComposer {
private final RoomUserVariableManager.Snapshot userSnapshot;
private final RoomFurniVariableManager.Snapshot furniSnapshot;
private final RoomVariableManager.Snapshot roomSnapshot;
private final List<WiredVariableDefinitionInfo> contextDefinitions;
public WiredUserVariablesDataComposer(RoomUserVariableManager.Snapshot userSnapshot, RoomFurniVariableManager.Snapshot furniSnapshot, RoomVariableManager.Snapshot roomSnapshot) {
this(userSnapshot, furniSnapshot, roomSnapshot, resolveContextDefinitions(userSnapshot, furniSnapshot, roomSnapshot));
}
public WiredUserVariablesDataComposer(RoomUserVariableManager.Snapshot userSnapshot, RoomFurniVariableManager.Snapshot furniSnapshot, RoomVariableManager.Snapshot roomSnapshot, List<WiredVariableDefinitionInfo> contextDefinitions) {
this.userSnapshot = userSnapshot;
this.furniSnapshot = furniSnapshot;
this.roomSnapshot = roomSnapshot;
this.contextDefinitions = (contextDefinitions != null) ? contextDefinitions : Collections.emptyList();
}
@Override
protected ServerMessage composeInternal() {
this.response.init(Outgoing.WiredUserVariablesDataComposer);
int roomId = 0;
if (this.userSnapshot != null) {
roomId = this.userSnapshot.getRoomId();
} else if (this.furniSnapshot != null) {
roomId = this.furniSnapshot.getRoomId();
} else if (this.roomSnapshot != null) {
roomId = this.roomSnapshot.getRoomId();
}
this.response.appendInt(roomId);
this.response.appendInt((this.userSnapshot != null) ? this.userSnapshot.getDefinitions().size() : 0);
if (this.userSnapshot != null) {
for (RoomUserVariableManager.DefinitionEntry definition : this.userSnapshot.getDefinitions()) {
this.response.appendInt(definition.getItemId());
this.response.appendString(definition.getName());
this.response.appendBoolean(definition.hasValue());
this.response.appendInt(definition.getAvailability());
this.response.appendBoolean(definition.isTextConnected());
this.response.appendBoolean(definition.isReadOnly());
}
}
this.response.appendInt((this.userSnapshot != null) ? this.userSnapshot.getUsers().size() : 0);
if (this.userSnapshot != null) {
for (RoomUserVariableManager.UserAssignmentsEntry user : this.userSnapshot.getUsers()) {
this.response.appendInt(user.getUserId());
this.response.appendInt(user.getAssignments().size());
for (RoomUserVariableManager.AssignmentEntry assignment : user.getAssignments()) {
this.response.appendInt(assignment.getVariableItemId());
this.response.appendBoolean(assignment.hasValue());
this.response.appendInt((assignment.getValue() != null) ? assignment.getValue() : 0);
this.response.appendInt(assignment.getCreatedAt());
this.response.appendInt(assignment.getUpdatedAt());
}
}
}
this.response.appendInt((this.furniSnapshot != null) ? this.furniSnapshot.getDefinitions().size() : 0);
if (this.furniSnapshot != null) {
for (RoomFurniVariableManager.DefinitionEntry definition : this.furniSnapshot.getDefinitions()) {
this.response.appendInt(definition.getItemId());
this.response.appendString(definition.getName());
this.response.appendBoolean(definition.hasValue());
this.response.appendInt(definition.getAvailability());
this.response.appendBoolean(definition.isTextConnected());
this.response.appendBoolean(definition.isReadOnly());
}
}
this.response.appendInt((this.furniSnapshot != null) ? this.furniSnapshot.getFurnis().size() : 0);
if (this.furniSnapshot != null) {
for (RoomFurniVariableManager.FurniAssignmentsEntry furni : this.furniSnapshot.getFurnis()) {
this.response.appendInt(furni.getFurniId());
this.response.appendInt(furni.getAssignments().size());
for (RoomFurniVariableManager.AssignmentEntry assignment : furni.getAssignments()) {
this.response.appendInt(assignment.getVariableItemId());
this.response.appendBoolean(assignment.hasValue());
this.response.appendInt((assignment.getValue() != null) ? assignment.getValue() : 0);
this.response.appendInt(assignment.getCreatedAt());
this.response.appendInt(assignment.getUpdatedAt());
}
}
}
this.response.appendInt((this.roomSnapshot != null) ? this.roomSnapshot.getDefinitions().size() : 0);
if (this.roomSnapshot != null) {
for (RoomVariableManager.DefinitionEntry definition : this.roomSnapshot.getDefinitions()) {
this.response.appendInt(definition.getItemId());
this.response.appendString(definition.getName());
this.response.appendBoolean(definition.hasValue());
this.response.appendInt(definition.getAvailability());
this.response.appendBoolean(definition.isTextConnected());
this.response.appendBoolean(definition.isReadOnly());
}
}
this.response.appendInt((this.roomSnapshot != null) ? this.roomSnapshot.getAssignments().size() : 0);
if (this.roomSnapshot != null) {
for (RoomVariableManager.AssignmentEntry assignment : this.roomSnapshot.getAssignments()) {
this.response.appendInt(assignment.getVariableItemId());
this.response.appendBoolean(assignment.hasValue());
this.response.appendInt((assignment.getValue() != null) ? assignment.getValue() : 0);
this.response.appendInt(assignment.getCreatedAt());
this.response.appendInt(assignment.getUpdatedAt());
}
}
this.response.appendInt(this.contextDefinitions.size());
for (WiredVariableDefinitionInfo definition : this.contextDefinitions) {
if (definition == null) {
continue;
}
this.response.appendInt(definition.getItemId());
this.response.appendString(definition.getName());
this.response.appendBoolean(definition.hasValue());
this.response.appendInt(definition.getAvailability());
this.response.appendBoolean(definition.isTextConnected());
this.response.appendBoolean(definition.isReadOnly());
}
return this.response;
}
private static List<WiredVariableDefinitionInfo> resolveContextDefinitions(RoomUserVariableManager.Snapshot userSnapshot, RoomFurniVariableManager.Snapshot furniSnapshot, RoomVariableManager.Snapshot roomSnapshot) {
int roomId = 0;
if (userSnapshot != null) {
roomId = userSnapshot.getRoomId();
} else if (furniSnapshot != null) {
roomId = furniSnapshot.getRoomId();
} else if (roomSnapshot != null) {
roomId = roomSnapshot.getRoomId();
}
if (roomId <= 0) {
return Collections.emptyList();
}
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId);
return room != null ? WiredContextVariableSupport.createDefinitionInfos(room) : Collections.emptyList();
}
}
@@ -120,6 +120,20 @@ public class PluginManager {
WiredEngine.MAX_EVENTS_PER_WINDOW = Emulator.getConfig().getInt("wired.abuse.max.events.per.window", 100);
WiredEngine.RATE_LIMIT_WINDOW_MS = Emulator.getConfig().getInt("wired.abuse.rate.limit.window.ms", 10000);
WiredEngine.WIRED_BAN_DURATION_MS = Emulator.getConfig().getInt("wired.abuse.ban.duration.ms", 600000);
WiredEngine.MONITOR_USAGE_WINDOW_MS = Emulator.getConfig().getInt("wired.monitor.usage.window.ms", 1000);
WiredEngine.MONITOR_USAGE_LIMIT = Emulator.getConfig().getInt("wired.monitor.usage.limit", 1000);
WiredEngine.MONITOR_DELAYED_EVENTS_LIMIT = Emulator.getConfig().getInt("wired.monitor.delayed.events.limit", 100);
WiredEngine.MONITOR_OVERLOAD_AVERAGE_MS = Emulator.getConfig().getInt("wired.monitor.overload.average.ms", 50);
WiredEngine.MONITOR_OVERLOAD_PEAK_MS = Emulator.getConfig().getInt("wired.monitor.overload.peak.ms", 150);
WiredEngine.MONITOR_OVERLOAD_CONSECUTIVE_WINDOWS = Emulator.getConfig().getInt("wired.monitor.overload.consecutive.windows", 2);
WiredEngine.MONITOR_HEAVY_USAGE_PERCENT = Emulator.getConfig().getInt("wired.monitor.heavy.usage.percent", 70);
WiredEngine.MONITOR_HEAVY_CONSECUTIVE_WINDOWS = Emulator.getConfig().getInt("wired.monitor.heavy.consecutive.windows", 5);
WiredEngine.MONITOR_HEAVY_DELAYED_PERCENT = Emulator.getConfig().getInt("wired.monitor.heavy.delayed.percent", 60);
if (WiredManager.getEngine() != null) {
WiredManager.getEngine().clearAllDiagnostics();
}
NavigatorManager.MAXIMUM_RESULTS_PER_PAGE = Emulator.getConfig().getInt("hotel.navigator.search.maxresults");
NavigatorManager.CATEGORY_SORT_USING_ORDER_NUM = Emulator.getConfig().getBoolean("hotel.navigator.sort.ordernum");
RoomChatMessage.MAXIMUM_LENGTH = Emulator.getConfig().getInt("hotel.chat.max.length");