diff --git a/Database Updates/19032026_hotel_timezone.sql b/Database Updates/19032026_hotel_timezone.sql new file mode 100644 index 00000000..8c08a899 --- /dev/null +++ b/Database Updates/19032026_hotel_timezone.sql @@ -0,0 +1,22 @@ +SET NAMES utf8mb4; + +-- Create the hotel timezone setting if it does not exist yet. +INSERT INTO `emulator_settings` (`key`, `value`) +SELECT 'hotel.timezone', 'Europe/Rome' +WHERE NOT EXISTS ( + SELECT 1 + FROM `emulator_settings` + WHERE `key` = 'hotel.timezone' +); + +-- Keep the default/example value aligned for existing installs too. +UPDATE `emulator_settings` +SET `value` = 'Europe/Rome' +WHERE `key` = 'hotel.timezone'; + +-- Helper query for a timezone selector. +-- If MySQL/MariaDB timezone tables are populated, this returns the available timezone ids. +SELECT `Name` AS `timezone_id` +FROM `mysql`.`time_zone_name` +WHERE `Name` IS NOT NULL +ORDER BY `Name`; diff --git a/Emulator/src/main/java/com/eu/habbo/Emulator.java b/Emulator/src/main/java/com/eu/habbo/Emulator.java index 69f1deab..11f29086 100644 --- a/Emulator/src/main/java/com/eu/habbo/Emulator.java +++ b/Emulator/src/main/java/com/eu/habbo/Emulator.java @@ -7,6 +7,7 @@ import com.eu.habbo.core.*; import com.eu.habbo.core.consolecommands.ConsoleCommand; import com.eu.habbo.database.Database; import com.eu.habbo.habbohotel.GameEnvironment; +import com.eu.habbo.habbohotel.gameclients.SessionResumeManager; import com.eu.habbo.networking.gameserver.GameServer; import com.eu.habbo.networking.rconserver.RCONServer; import com.eu.habbo.plugin.PluginManager; @@ -20,6 +21,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; +import java.net.JarURLConnection; +import java.net.URL; import java.security.MessageDigest; import java.security.SecureRandom; import java.sql.Timestamp; @@ -52,6 +55,7 @@ public final class Emulator { "Still Rocking in 2026.\n"; public static String build = ""; + public static long buildTimestamp = -1L; public static boolean isReady = false; public static boolean isShuttingDown = false; public static boolean stopped = false; @@ -103,13 +107,6 @@ public final class Emulator { System.out.println(logo); - System.out.println(); - LOGGER.info("https://github.com/duckietm/Arcturus-Morningstar-Extended, "); - System.out.println(); - LOGGER.info("This project is for educational purposes only. This Emulator is an open-source fork of Arcturus created by TheGeneral."); - LOGGER.info("Version: {}", version); - LOGGER.info("Build: {}", build); - long startTime = System.nanoTime(); Emulator.runtime = Runtime.getRuntime(); @@ -141,6 +138,15 @@ public final class Emulator { Emulator.config.register("camera.price.points", "0"); Emulator.config.register("camera.price.points.type", "5"); Emulator.config.register("camera.render.delay", "5"); + Emulator.config.register("hotel.timezone", java.time.ZoneId.systemDefault().getId()); + String hotelTimezoneId = Emulator.getConfig().getValue("hotel.timezone", java.time.ZoneId.systemDefault().getId()); + System.out.println(); + LOGGER.info("https://github.com/duckietm/Arcturus-Morningstar-Extended, "); + System.out.println(); + LOGGER.info("This project is for educational purposes only. This Emulator is an open-source fork of Arcturus created by TheGeneral."); + LOGGER.info("Version: {}", version); + LOGGER.info("Build: {}", build); + LOGGER.info("Build Timestamp: {} [{}]", formatBuildTimestamp(buildTimestamp, hotelTimezoneId), hotelTimezoneId); Emulator.texts.register("camera.permission", "You don't have permission to use the camera!"); Emulator.texts.register("camera.wait", "Please wait %seconds% seconds before making another picture."); Emulator.texts.register("camera.error.creation", "Failed to create your picture. *sadpanda*"); @@ -216,12 +222,21 @@ public final class Emulator { private static void setBuild() { if (Emulator.class.getProtectionDomain().getCodeSource() == null) { build = "UNKNOWN"; + buildTimestamp = -1L; return; } StringBuilder sb = new StringBuilder(); try { - String filepath = new File(Emulator.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getAbsolutePath(); + File buildFile = new File(Emulator.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + buildTimestamp = resolveBuildTimestamp(buildFile); + + if (!buildFile.isFile()) { + build = "DEV"; + return; + } + + String filepath = buildFile.getAbsolutePath(); MessageDigest md = MessageDigest.getInstance("MD5"); try (FileInputStream fis = new FileInputStream(filepath)) { byte[] dataBytes = new byte[1024]; @@ -234,14 +249,69 @@ public final class Emulator { } } catch (Exception e) { build = "UNKNOWN"; + buildTimestamp = -1L; return; } build = sb.toString(); } + private static long resolveBuildTimestamp(File buildFile) { + if (buildFile != null && buildFile.exists() && buildFile.isFile()) { + return buildFile.lastModified(); + } + + try { + URL classUrl = Emulator.class.getResource("Emulator.class"); + + if (classUrl != null) { + if ("file".equalsIgnoreCase(classUrl.getProtocol())) { + File classFile = new File(classUrl.toURI()); + + if (classFile.exists()) { + return classFile.lastModified(); + } + } + + if ("jar".equalsIgnoreCase(classUrl.getProtocol())) { + JarURLConnection connection = (JarURLConnection) classUrl.openConnection(); + File jarFile = new File(connection.getJarFileURL().toURI()); + + if (jarFile.exists()) { + return jarFile.lastModified(); + } + } + } + } catch (Exception ignored) { + } + + if (buildFile != null && buildFile.exists()) { + return buildFile.lastModified(); + } + + return -1L; + } + + private static String formatBuildTimestamp(long buildTimestamp, String timezoneId) { + if (buildTimestamp <= 0) { + return "UNKNOWN"; + } + + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + try { + format.setTimeZone(TimeZone.getTimeZone(java.time.ZoneId.of(timezoneId))); + } catch (Exception ignored) { + format.setTimeZone(TimeZone.getDefault()); + } + + return format.format(new Timestamp(buildTimestamp)); + } + private static void dispose() { - Emulator.getThreading().setCanAdd(false); + if (Emulator.threading != null) { + Emulator.threading.setCanAdd(false); + } Emulator.isShuttingDown = true; Emulator.isReady = false; @@ -250,6 +320,7 @@ public final class Emulator { if (Emulator.pluginManager != null) tryShutdown(() -> Emulator.pluginManager.fireEvent(new EmulatorStartShutdownEvent())); if (Emulator.rconServer != null) tryShutdown(() -> Emulator.rconServer.stop()); + tryShutdown(() -> SessionResumeManager.getInstance().disposeAll()); if (Emulator.gameEnvironment != null) tryShutdown(() -> Emulator.gameEnvironment.dispose()); if (Emulator.pluginManager != null) tryShutdown(() -> Emulator.pluginManager.fireEvent(new EmulatorStoppedEvent())); diff --git a/Emulator/src/main/java/com/eu/habbo/core/ConfigurationManager.java b/Emulator/src/main/java/com/eu/habbo/core/ConfigurationManager.java index 3a6ba211..2e4819a4 100644 --- a/Emulator/src/main/java/com/eu/habbo/core/ConfigurationManager.java +++ b/Emulator/src/main/java/com/eu/habbo/core/ConfigurationManager.java @@ -87,6 +87,7 @@ public class ConfigurationManager { // Runtime envMapping.put("runtime.threads", "RT_THREADS"); envMapping.put("logging.errors.runtime", "RT_LOG_ERRORS"); + envMapping.put("hotel.timezone", "HOTEL_TIMEZONE"); for (Map.Entry entry : envMapping.entrySet()) { String envValue = System.getenv(entry.getValue()); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/CommandHandler.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/CommandHandler.java index bd2a1f2d..3f26738b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/CommandHandler.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/CommandHandler.java @@ -301,7 +301,7 @@ public class CommandHandler { addCommand(new ListPrefixesCommand()); addCommand(new RemovePrefixCommand()); addCommand(new PrefixBlacklistCommand()); - + addCommand(new WiredCommand()); addCommand(new TestCommand()); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/UnmuteCommand.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/UnmuteCommand.java index 376f8b94..7cd922e9 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/UnmuteCommand.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/UnmuteCommand.java @@ -29,7 +29,7 @@ public class UnmuteCommand extends Command { } if (habbo.getHabboInfo().getCurrentRoom() != null && habbo.getHabboInfo().getCurrentRoom().isMuted(habbo)) { - habbo.getHabboInfo().getCurrentRoom().muteHabbo(habbo, 1); + habbo.getHabboInfo().getCurrentRoom().unmuteHabbo(habbo); } gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.succes.cmd_unmute").replace("%user%", params[1]), RoomChatMessageBubbles.ALERT); @@ -41,4 +41,4 @@ public class UnmuteCommand extends Command { return true; } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/WiredCommand.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/WiredCommand.java new file mode 100644 index 00000000..d8d869b9 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/WiredCommand.java @@ -0,0 +1,35 @@ +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"}); + } + + @Override + public boolean handle(GameClient gameClient, String[] params) throws Exception { + Room room = gameClient.getHabbo().getHabboInfo().getCurrentRoom(); + + if (room == null) { + gameClient.getHabbo().whisper("You need to be inside a room to use :wired.", RoomChatMessageBubbles.ALERT); + 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); + return true; + } + + gameClient.sendResponse(new InClientLinkComposer("wired-tools/show")); + return true; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClient.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClient.java index 61c219cc..f526c15b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClient.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClient.java @@ -26,6 +26,7 @@ public class GameClient { private Habbo habbo; private boolean handshakeFinished; private String machineId = ""; + private String ssoTicket = ""; public final ConcurrentHashMap incomingPacketCounter = new ConcurrentHashMap<>(25); public final ConcurrentHashMap, Long> messageTimestamps = new ConcurrentHashMap<>(); @@ -82,6 +83,14 @@ public class GameClient { this.machineId = machineId; } + public String getSsoTicket() { + return this.ssoTicket; + } + + public void setSsoTicket(String ssoTicket) { + this.ssoTicket = ssoTicket != null ? ssoTicket : ""; + } + public void sendResponse(MessageComposer composer) { this.sendResponse(composer.compose()); } @@ -145,8 +154,15 @@ public class GameClient { if (this.habbo != null) { if (this.habbo.isOnline()) { - this.habbo.getHabboInfo().setOnline(false); - this.habbo.disconnect(); + // Try to park the habbo in the grace period instead of immediate disconnect + boolean parked = SessionResumeManager.getInstance().parkHabbo(this.habbo, this.ssoTicket); + + if (!parked) { + // No grace period configured — immediate disconnect as before + this.habbo.getHabboInfo().setOnline(false); + this.habbo.disconnect(); + } + // If parked, do NOT call disconnect() — the habbo stays in the room } this.habbo = null; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClientManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClientManager.java index 68366a0a..cd0602cb 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClientManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClientManager.java @@ -116,6 +116,22 @@ public class GameClientManager { } + /** + * Find an existing GameClient that authenticated with the given SSO ticket. + * Used to detect reconnections where the old connection hasn't been closed yet. + */ + public GameClient findClientBySsoTicket(String ssoTicket) { + if (ssoTicket == null || ssoTicket.isEmpty()) return null; + + for (GameClient client : this.clients.values()) { + if (ssoTicket.equals(client.getSsoTicket()) && client.getHabbo() != null) { + return client; + } + } + return null; + } + + public List getHabbosWithMachineId(String machineId) { List habbos = new ArrayList<>(); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/SessionResumeManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/SessionResumeManager.java new file mode 100644 index 00000000..f2724578 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/SessionResumeManager.java @@ -0,0 +1,173 @@ +package com.eu.habbo.habbohotel.gameclients; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.users.Habbo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; + +/** + * Manages a grace period for disconnected users. Instead of immediately + * disposing a Habbo when their WebSocket drops, the Habbo is held in + * a "ghost" state for a configurable number of seconds. If the same + * user reconnects (via SSO ticket) within the grace window, their + * existing Habbo object is resumed on the new connection — keeping + * them in their room, preserving inventory state, etc. + * + * Config key: session.reconnect.grace.seconds (default: 30) + */ +public class SessionResumeManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(SessionResumeManager.class); + + private static SessionResumeManager instance; + + private final ConcurrentHashMap ghostSessions = new ConcurrentHashMap<>(); + + public static SessionResumeManager getInstance() { + if (instance == null) { + instance = new SessionResumeManager(); + } + return instance; + } + + public int getGracePeriodSeconds() { + return Emulator.getConfig().getInt("session.reconnect.grace.seconds", 30); + } + + /** + * Park a disconnected Habbo in ghost mode. Their room presence is + * preserved, but the old GameClient channel is closed. + * + * @return true if the habbo was parked (grace period > 0), false if immediate dispose should happen + */ + public boolean parkHabbo(Habbo habbo, String ssoTicket) { + int graceSeconds = getGracePeriodSeconds(); + if (graceSeconds <= 0) { + return false; + } + + int userId = habbo.getHabboInfo().getId(); + + // Cancel any existing ghost session for this user + GhostSession existing = ghostSessions.remove(userId); + if (existing != null && existing.disposeFuture != null) { + existing.disposeFuture.cancel(false); + } + + LOGGER.info("[SessionResume] Parking {} (id={}) for {}s grace period", + habbo.getHabboInfo().getUsername(), userId, graceSeconds); + + // Restore the SSO ticket so the client can reconnect with the same ticket + if (ssoTicket != null && !ssoTicket.isEmpty()) { + restoreSsoTicket(userId, ssoTicket); + } + + // Schedule the final disconnect after the grace period + ScheduledFuture future = Emulator.getThreading().run(() -> { + GhostSession ghost = ghostSessions.remove(userId); + if (ghost != null) { + LOGGER.info("[SessionResume] Grace period expired for {} (id={}) - performing full disconnect", + ghost.habbo.getHabboInfo().getUsername(), userId); + performFullDisconnect(ghost.habbo); + } + }, graceSeconds * 1000); + + ghostSessions.put(userId, new GhostSession(habbo, ssoTicket, future)); + return true; + } + + /** + * Try to resume a ghost session for the given user ID. + * + * @return the parked Habbo if found within grace period, null otherwise + */ + public Habbo resumeSession(int userId) { + GhostSession ghost = ghostSessions.remove(userId); + if (ghost == null) { + return null; + } + + // Cancel the scheduled dispose + if (ghost.disposeFuture != null) { + ghost.disposeFuture.cancel(false); + } + + LOGGER.info("[SessionResume] Resuming session for {} (id={})", + ghost.habbo.getHabboInfo().getUsername(), userId); + + return ghost.habbo; + } + + /** + * Check if a user has a ghost session (is in grace period). + */ + public boolean hasGhostSession(int userId) { + return ghostSessions.containsKey(userId); + } + + /** + * Immediately expire all ghost sessions (e.g. on emulator shutdown). + */ + public void disposeAll() { + for (GhostSession ghost : ghostSessions.values()) { + if (ghost.disposeFuture != null) { + ghost.disposeFuture.cancel(false); + } + performFullDisconnect(ghost.habbo); + } + ghostSessions.clear(); + } + + /** + * Perform the actual full disconnect that normally happens in Habbo.disconnect(). + */ + private void performFullDisconnect(Habbo habbo) { + try { + habbo.getHabboInfo().setOnline(false); + habbo.disconnect(); + } catch (Exception e) { + LOGGER.error("[SessionResume] Error during deferred disconnect", e); + } + + // Clear the SSO ticket now that the grace period is truly over + clearSsoTicket(habbo.getHabboInfo().getId()); + } + + private void restoreSsoTicket(int userId, String ssoTicket) { + try (var connection = Emulator.getDatabase().getDataSource().getConnection(); + var statement = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) { + statement.setString(1, ssoTicket); + statement.setInt(2, userId); + statement.execute(); + LOGGER.info("[SessionResume] Restored SSO ticket for user {} during grace period", userId); + } catch (Exception e) { + LOGGER.error("[SessionResume] Failed to restore SSO ticket for user " + userId, e); + } + } + + private void clearSsoTicket(int userId) { + try (var connection = Emulator.getDatabase().getDataSource().getConnection(); + var statement = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) { + statement.setString(1, ""); + statement.setInt(2, userId); + statement.execute(); + } catch (Exception e) { + LOGGER.error("[SessionResume] Failed to clear SSO ticket for user " + userId, e); + } + } + + private static class GhostSession { + final Habbo habbo; + final String ssoTicket; + final ScheduledFuture disposeFuture; + + GhostSession(Habbo habbo, String ssoTicket, ScheduledFuture disposeFuture) { + this.habbo = habbo; + this.ssoTicket = ssoTicket; + this.disposeFuture = disposeFuture; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java index 50928f05..ac2eab14 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java @@ -48,15 +48,11 @@ import com.eu.habbo.habbohotel.items.interactions.totems.InteractionTotemLegs; import com.eu.habbo.habbohotel.items.interactions.totems.InteractionTotemPlanet; import com.eu.habbo.habbohotel.items.interactions.wired.conditions.*; import com.eu.habbo.habbohotel.items.interactions.wired.effects.*; -import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectFurniArea; -import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectUsersArea; -import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectUsersNeighborhood; -import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectFurniNeighborhood; -import com.eu.habbo.habbohotel.items.interactions.wired.selector.WiredEffectFurniByType; import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredBlob; 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.WiredExtraUnseen; +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; import com.eu.habbo.habbohotel.users.HabboItem; @@ -202,16 +198,25 @@ public class ItemManager { this.interactionsList.add(new ItemInteraction("random_state", InteractionRandomState.class)); this.interactionsList.add(new ItemInteraction("vendingmachine_no_sides", InteractionNoSidesVendingMachine.class)); this.interactionsList.add(new ItemInteraction("tile_walkmagic", InteractionTileWalkMagic.class)); + this.interactionsList.add(new ItemInteraction("antenna", InteractionDefault.class)); + this.interactionsList.add(new ItemInteraction("room_invisible_click_tile", InteractionDefault.class)); this.interactionsList.add(new ItemInteraction("game_timer", InteractionGameTimer.class)); this.interactionsList.add(new ItemInteraction("wf_trg_walks_on_furni", WiredTriggerHabboWalkOnFurni.class)); this.interactionsList.add(new ItemInteraction("wf_trg_walks_off_furni", WiredTriggerHabboWalkOffFurni.class)); + this.interactionsList.add(new ItemInteraction("wf_trg_click_furni", WiredTriggerHabboClicksFurni.class)); + this.interactionsList.add(new ItemInteraction("wf_trg_click_tile", WiredTriggerHabboClicksTile.class)); + this.interactionsList.add(new ItemInteraction("wf_trg_click_user", WiredTriggerHabboClicksUser.class)); + this.interactionsList.add(new ItemInteraction("wf_trg_user_performs_action", WiredTriggerHabboPerformsAction.class)); this.interactionsList.add(new ItemInteraction("wf_trg_enter_room", WiredTriggerHabboEntersRoom.class)); + 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_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)); this.interactionsList.add(new ItemInteraction("wf_trg_state_changed", WiredTriggerFurniStateToggled.class)); + this.interactionsList.add(new ItemInteraction("wf_trg_stuff_state", WiredTriggerFurniStateToggled.class)); this.interactionsList.add(new ItemInteraction("wf_trg_at_given_time", WiredTriggerAtSetTime.class)); this.interactionsList.add(new ItemInteraction("wf_trg_at_time_long", WiredTriggerAtTimeLong.class)); this.interactionsList.add(new ItemInteraction("wf_trg_collision", WiredTriggerCollision.class)); @@ -255,6 +260,13 @@ public class ItemManager { this.interactionsList.add(new ItemInteraction("wf_act_alert", WiredEffectAlert.class)); this.interactionsList.add(new ItemInteraction("wf_act_give_handitem", WiredEffectGiveHandItem.class)); this.interactionsList.add(new ItemInteraction("wf_act_give_effect", WiredEffectGiveEffect.class)); + this.interactionsList.add(new ItemInteraction("wf_act_freeze", WiredEffectFreeze.class)); + this.interactionsList.add(new ItemInteraction("wf_act_unfreeze", WiredEffectUnfreeze.class)); + this.interactionsList.add(new ItemInteraction("wf_act_furni_to_user", WiredEffectFurniToUser.class)); + this.interactionsList.add(new ItemInteraction("wf_act_user_to_furni", WiredEffectUserToFurni.class)); + this.interactionsList.add(new ItemInteraction("wf_act_furni_to_furni", WiredEffectFurniToFurni.class)); + this.interactionsList.add(new ItemInteraction("wf_act_set_altitude", WiredEffectSetAltitude.class)); + this.interactionsList.add(new ItemInteraction("wf_act_rel_mov", WiredEffectRelativeMove.class)); this.interactionsList.add(new ItemInteraction("wf_slc_furni_area", WiredEffectFurniArea.class)); this.interactionsList.add(new ItemInteraction("wf_slc_furni_neighborhood", WiredEffectFurniNeighborhood.class)); this.interactionsList.add(new ItemInteraction("wf_slc_furni_bytype", WiredEffectFurniByType.class)); @@ -285,8 +297,19 @@ public class ItemManager { this.interactionsList.add(new ItemInteraction("wf_cnd_actor_in_team", WiredConditionTeamMember.class)); this.interactionsList.add(new ItemInteraction("wf_cnd_trggrer_on_frn", WiredConditionTriggerOnFurni.class)); this.interactionsList.add(new ItemInteraction("wf_cnd_has_handitem", WiredConditionHabboHasHandItem.class)); + this.interactionsList.add(new ItemInteraction("wf_cnd_not_has_handitem", WiredConditionNotHabboHasHandItem.class)); this.interactionsList.add(new ItemInteraction("wf_cnd_date_rng_active", WiredConditionDateRangeActive.class)); this.interactionsList.add(new ItemInteraction("wf_cnd_valid_moves", WiredConditionMovementValidation.class)); + // this.interactionsList.add(new ItemInteraction("wf_cnd_counter_time_matches", WiredConditionCounterTimeMatches.class)); + this.interactionsList.add(new ItemInteraction("wf_cnd_match_time", WiredConditionMatchTime.class)); + this.interactionsList.add(new ItemInteraction("wf_cnd_match_date", WiredConditionMatchDate.class)); + this.interactionsList.add(new ItemInteraction("wf_cnd_user_performs_action", WiredConditionUserPerformsAction.class)); + this.interactionsList.add(new ItemInteraction("wf_cnd_not_user_performs_action", WiredConditionNotUserPerformsAction.class)); + this.interactionsList.add(new ItemInteraction("wf_cnd_has_altitude", WiredConditionHasAltitude.class)); + this.interactionsList.add(new ItemInteraction("wf_cnd_triggerer_match", WiredConditionTriggererMatch.class)); + 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_xtra_random", WiredExtraRandom.class)); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHabboHasHandItem.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHabboHasHandItem.java index 1a064793..64aade52 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHabboHasHandItem.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHabboHasHandItem.java @@ -115,6 +115,14 @@ public class WiredConditionHabboHasHandItem extends InteractionWiredCondition { this.userSource = WiredSourceUtil.SOURCE_TRIGGER; } + protected int getHandItem() { + return this.handItem; + } + + protected int getUserSource() { + return this.userSource; + } + static class JsonData { int handItemId; int userSource; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHasAltitude.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHasAltitude.java new file mode 100644 index 00000000..155da036 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHasAltitude.java @@ -0,0 +1,285 @@ +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.InteractionWiredCondition; +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.users.HabboItem; +import com.eu.habbo.habbohotel.wired.WiredConditionType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil; +import com.eu.habbo.messages.ServerMessage; +import gnu.trove.set.hash.THashSet; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.stream.Collectors; + +public class WiredConditionHasAltitude extends InteractionWiredCondition { + private static final int COMPARISON_LESS = 0; + private static final int COMPARISON_EQUAL = 1; + private static final int COMPARISON_GREATER = 2; + private static final int QUANTIFIER_ALL = 0; + private static final int QUANTIFIER_ANY = 1; + + public static final WiredConditionType type = WiredConditionType.HAS_ALTITUDE; + + private final THashSet items; + private int comparison = COMPARISON_EQUAL; + private double altitude = 0.0D; + private int furniSource = WiredSourceUtil.SOURCE_TRIGGER; + private int quantifier = QUANTIFIER_ALL; + + public WiredConditionHasAltitude(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + this.items = new THashSet<>(); + } + + public WiredConditionHasAltitude(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 evaluate(WiredContext ctx) { + Room room = ctx.room(); + if (room == null) { + return false; + } + + this.refresh(room); + + List targets = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.items); + if (targets.isEmpty()) { + return false; + } + + if (this.quantifier == QUANTIFIER_ANY) { + return targets.stream().anyMatch(this::matchesAltitude); + } + + return targets.stream().allMatch(this::matchesAltitude); + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson(new JsonData( + this.comparison, + this.formatAltitude(this.altitude), + this.furniSource, + this.quantifier, + this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) + )); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + this.items.clear(); + this.comparison = COMPARISON_EQUAL; + this.altitude = 0.0D; + this.furniSource = WiredSourceUtil.SOURCE_TRIGGER; + this.quantifier = QUANTIFIER_ALL; + + String wiredData = set.getString("wired_data"); + if (wiredData == null || !wiredData.startsWith("{")) { + return; + } + + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + if (data == null) { + return; + } + + this.comparison = this.normalizeComparison(data.comparison); + this.altitude = this.parseAltitudeOrDefault(data.altitude); + this.furniSource = this.normalizeFurniSource(data.furniSource); + this.quantifier = this.normalizeQuantifier(data.quantifier); + + if (data.itemIds == null) { + return; + } + + for (Integer id : data.itemIds) { + HabboItem item = room.getHabboItem(id); + if (item != null) { + this.items.add(item); + } + } + } + + @Override + public void onPickUp() { + this.items.clear(); + this.comparison = COMPARISON_EQUAL; + this.altitude = 0.0D; + this.furniSource = WiredSourceUtil.SOURCE_TRIGGER; + this.quantifier = QUANTIFIER_ALL; + } + + @Override + public WiredConditionType getType() { + return type; + } + + @Override + public void serializeWiredData(ServerMessage message, Room room) { + this.refresh(room); + + message.appendBoolean(false); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); + message.appendInt(this.items.size()); + + for (HabboItem item : this.items) { + message.appendInt(item.getId()); + } + + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(this.formatAltitude(this.altitude)); + message.appendInt(3); + message.appendInt(this.comparison); + 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(); + this.comparison = (params.length > 0) ? this.normalizeComparison(params[0]) : COMPARISON_EQUAL; + this.furniSource = (params.length > 1) ? this.normalizeFurniSource(params[1]) : WiredSourceUtil.SOURCE_TRIGGER; + this.quantifier = (params.length > 2) ? this.normalizeQuantifier(params[2]) : QUANTIFIER_ALL; + this.altitude = this.parseAltitudeOrDefault(settings.getStringParam()); + + int count = settings.getFurniIds().length; + if (count > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) { + return false; + } + + this.items.clear(); + + if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) { + Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()); + if (room == null) { + return false; + } + + for (int itemId : settings.getFurniIds()) { + HabboItem item = room.getHabboItem(itemId); + if (item != null) { + this.items.add(item); + } + } + } + + return true; + } + + private boolean matchesAltitude(HabboItem item) { + if (item == null) { + return false; + } + + double normalizedAltitude = this.normalizeAltitude(item.getZ()); + + switch (this.comparison) { + case COMPARISON_LESS: + return normalizedAltitude < this.altitude; + case COMPARISON_GREATER: + return normalizedAltitude > this.altitude; + default: + return BigDecimal.valueOf(normalizedAltitude).compareTo(BigDecimal.valueOf(this.altitude)) == 0; + } + } + + private void refresh(Room room) { + THashSet remove = new THashSet<>(); + + for (HabboItem item : this.items) { + if (room.getHabboItem(item.getId()) == null) { + remove.add(item); + } + } + + for (HabboItem item : remove) { + this.items.remove(item); + } + } + + private int normalizeComparison(int value) { + if (value < COMPARISON_LESS || value > COMPARISON_GREATER) { + return COMPARISON_EQUAL; + } + + return value; + } + + private int normalizeQuantifier(int value) { + return (value == QUANTIFIER_ANY) ? QUANTIFIER_ANY : QUANTIFIER_ALL; + } + + private int normalizeFurniSource(int value) { + switch (value) { + case WiredSourceUtil.SOURCE_SELECTED: + case WiredSourceUtil.SOURCE_SELECTOR: + case WiredSourceUtil.SOURCE_SIGNAL: + case WiredSourceUtil.SOURCE_TRIGGER: + return value; + default: + return WiredSourceUtil.SOURCE_TRIGGER; + } + } + + private double normalizeAltitude(double value) { + double clampedValue = Math.max(0.0D, Math.min(Room.MAXIMUM_FURNI_HEIGHT, value)); + return BigDecimal.valueOf(clampedValue).setScale(2, RoundingMode.HALF_UP).doubleValue(); + } + + private double parseAltitudeOrDefault(String value) { + if (value == null || value.trim().isEmpty()) { + return 0.0D; + } + + try { + return this.normalizeAltitude(new BigDecimal(value.trim()).doubleValue()); + } catch (NumberFormatException exception) { + return 0.0D; + } + } + + private String formatAltitude(double value) { + BigDecimal decimal = BigDecimal.valueOf(this.normalizeAltitude(value)).stripTrailingZeros(); + return (decimal.scale() < 0 ? decimal.setScale(0, RoundingMode.DOWN) : decimal).toPlainString(); + } + + static class JsonData { + int comparison; + String altitude; + int furniSource; + int quantifier; + List itemIds; + + public JsonData(int comparison, String altitude, int furniSource, int quantifier, List itemIds) { + this.comparison = comparison; + this.altitude = altitude; + this.furniSource = furniSource; + this.quantifier = quantifier; + this.itemIds = itemIds; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchDate.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchDate.java new file mode 100644 index 00000000..3574480b --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchDate.java @@ -0,0 +1,253 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.conditions; + +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.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.wired.WiredConditionType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.util.HotelDateTimeUtil; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalDate; + +public class WiredConditionMatchDate extends InteractionWiredCondition { + private static final int MODE_SKIP = 0; + private static final int MODE_EXACT = 1; + private static final int MODE_RANGE = 2; + private static final int ALL_WEEKDAYS_MASK = createMask(1, 7); + private static final int ALL_MONTHS_MASK = createMask(1, 12); + + public static final WiredConditionType type = WiredConditionType.MATCH_DATE; + + private int weekdayMask = ALL_WEEKDAYS_MASK; + private int dayMode = MODE_SKIP; + private int dayFrom = 1; + private int dayTo = 31; + private int monthMask = ALL_MONTHS_MASK; + private int yearMode = MODE_SKIP; + private int yearFrom = HotelDateTimeUtil.localDateNow().getYear(); + private int yearTo = HotelDateTimeUtil.localDateNow().getYear(); + + public WiredConditionMatchDate(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredConditionMatchDate(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) { + message.appendBoolean(false); + message.appendInt(5); + message.appendInt(0); + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(""); + message.appendInt(8); + message.appendInt(this.weekdayMask); + message.appendInt(this.dayMode); + message.appendInt(this.dayFrom); + message.appendInt(this.dayTo); + message.appendInt(this.monthMask); + message.appendInt(this.yearMode); + message.appendInt(this.yearFrom); + message.appendInt(this.yearTo); + message.appendInt(0); + message.appendInt(this.getType().code); + message.appendInt(0); + message.appendInt(0); + } + + @Override + public boolean saveData(WiredSettings settings) { + int[] params = settings.getIntParams(); + + this.weekdayMask = (params.length > 0) ? this.normalizeWeekdayMask(params[0]) : ALL_WEEKDAYS_MASK; + this.dayMode = (params.length > 1) ? this.normalizeMode(params[1]) : MODE_SKIP; + this.dayFrom = (params.length > 2) ? this.normalizeDay(params[2]) : 1; + this.dayTo = (params.length > 3) ? this.normalizeDay(params[3]) : this.dayFrom; + this.monthMask = (params.length > 4) ? this.normalizeMonthMask(params[4]) : ALL_MONTHS_MASK; + this.yearMode = (params.length > 5) ? this.normalizeMode(params[5]) : MODE_SKIP; + this.yearFrom = (params.length > 6) ? this.normalizeYear(params[6]) : HotelDateTimeUtil.localDateNow().getYear(); + this.yearTo = (params.length > 7) ? this.normalizeYear(params[7]) : this.yearFrom; + + return true; + } + + @Override + public boolean evaluate(WiredContext ctx) { + LocalDate now = HotelDateTimeUtil.localDateNow(); + + return this.matchesMask(now.getDayOfWeek().getValue(), this.weekdayMask) + && this.matchesMask(now.getMonthValue(), this.monthMask) + && this.matchesDatePart(now.getDayOfMonth(), this.dayMode, this.dayFrom, this.dayTo) + && this.matchesDatePart(now.getYear(), this.yearMode, this.yearFrom, this.yearTo); + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson(new JsonData( + this.weekdayMask, + this.dayMode, + this.dayFrom, + this.dayTo, + this.monthMask, + this.yearMode, + this.yearFrom, + this.yearTo + )); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + this.reset(); + + 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) { + return; + } + + this.weekdayMask = this.normalizeWeekdayMask(data.weekdayMask); + this.dayMode = this.normalizeMode(data.dayMode); + this.dayFrom = this.normalizeDay(data.dayFrom); + this.dayTo = this.normalizeDay(data.dayTo); + this.monthMask = this.normalizeMonthMask(data.monthMask); + this.yearMode = this.normalizeMode(data.yearMode); + this.yearFrom = this.normalizeYear(data.yearFrom); + this.yearTo = this.normalizeYear(data.yearTo); + return; + } + + String[] data = wiredData.split("\t"); + if (data.length != 8) { + return; + } + + try { + this.weekdayMask = this.normalizeWeekdayMask(Integer.parseInt(data[0])); + this.dayMode = this.normalizeMode(Integer.parseInt(data[1])); + this.dayFrom = this.normalizeDay(Integer.parseInt(data[2])); + this.dayTo = this.normalizeDay(Integer.parseInt(data[3])); + this.monthMask = this.normalizeMonthMask(Integer.parseInt(data[4])); + this.yearMode = this.normalizeMode(Integer.parseInt(data[5])); + this.yearFrom = this.normalizeYear(Integer.parseInt(data[6])); + this.yearTo = this.normalizeYear(Integer.parseInt(data[7])); + } catch (NumberFormatException ignored) { + this.reset(); + } + } + + @Override + public void onPickUp() { + this.reset(); + } + + private void reset() { + int currentYear = HotelDateTimeUtil.localDateNow().getYear(); + + this.weekdayMask = ALL_WEEKDAYS_MASK; + this.dayMode = MODE_SKIP; + this.dayFrom = 1; + this.dayTo = 31; + this.monthMask = ALL_MONTHS_MASK; + this.yearMode = MODE_SKIP; + this.yearFrom = currentYear; + this.yearTo = currentYear; + } + + private boolean matchesMask(int value, int mask) { + return (mask & (1 << value)) != 0; + } + + private boolean matchesDatePart(int currentValue, int mode, int fromValue, int toValue) { + switch (mode) { + case MODE_EXACT: + return currentValue == fromValue; + case MODE_RANGE: + return currentValue >= Math.min(fromValue, toValue) && currentValue <= Math.max(fromValue, toValue); + default: + return true; + } + } + + private int normalizeMode(int value) { + if (value < MODE_SKIP || value > MODE_RANGE) { + return MODE_SKIP; + } + + return value; + } + + private int normalizeDay(int value) { + return Math.max(1, Math.min(31, value)); + } + + private int normalizeYear(int value) { + return Math.max(1, Math.min(9999, value)); + } + + private int normalizeWeekdayMask(int value) { + int normalized = value & ALL_WEEKDAYS_MASK; + return (normalized == 0) ? ALL_WEEKDAYS_MASK : normalized; + } + + private int normalizeMonthMask(int value) { + int normalized = value & ALL_MONTHS_MASK; + return (normalized == 0) ? ALL_MONTHS_MASK : normalized; + } + + private static int createMask(int startValue, int endValue) { + int mask = 0; + + for (int value = startValue; value <= endValue; value++) { + mask |= (1 << value); + } + + return mask; + } + + static class JsonData { + int weekdayMask; + int dayMode; + int dayFrom; + int dayTo; + int monthMask; + int yearMode; + int yearFrom; + int yearTo; + + public JsonData(int weekdayMask, int dayMode, int dayFrom, int dayTo, int monthMask, int yearMode, int yearFrom, int yearTo) { + this.weekdayMask = weekdayMask; + this.dayMode = dayMode; + this.dayFrom = dayFrom; + this.dayTo = dayTo; + this.monthMask = monthMask; + this.yearMode = yearMode; + this.yearFrom = yearFrom; + this.yearTo = yearTo; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchStatePosition.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchStatePosition.java index bc965875..7966d563 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchStatePosition.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchStatePosition.java @@ -19,9 +19,7 @@ import gnu.trove.set.hash.THashSet; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; public class WiredConditionMatchStatePosition extends InteractionWiredCondition implements InteractionWiredMatchFurniSettings { public static final WiredConditionType type = WiredConditionType.MATCH_SSHOT; @@ -92,14 +90,12 @@ public class WiredConditionMatchStatePosition extends InteractionWiredCondition this.settings.clear(); - if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) { - for (int i = 0; i < count; i++) { - int itemId = settings.getFurniIds()[i]; - HabboItem item = room.getHabboItem(itemId); + for (int i = 0; i < count; i++) { + int itemId = settings.getFurniIds()[i]; + HabboItem item = room.getHabboItem(itemId); - if (item != null) - this.settings.add(new WiredMatchFurniSetting(item.getId(), item.getExtradata(), item.getRotation(), item.getX(), item.getY())); - } + if (item != null) + this.settings.add(new WiredMatchFurniSetting(item.getId(), item.getExtradata(), item.getRotation(), item.getX(), item.getY())); } return true; @@ -108,65 +104,71 @@ public class WiredConditionMatchStatePosition extends InteractionWiredCondition @Override public boolean evaluate(WiredContext ctx) { Room room = ctx.room(); + this.refresh(); + if (this.settings.isEmpty()) return true; - List targets = null; - Set targetIds = null; - if (this.furniSource != WiredSourceUtil.SOURCE_SELECTED) { - targets = WiredSourceUtil.resolveItems(ctx, this.furniSource, null); + List targets = WiredSourceUtil.resolveItems(ctx, this.furniSource, null); if (targets.isEmpty()) return false; - targetIds = new HashSet<>(); - for (HabboItem item : targets) { - if (item != null) targetIds.add(item.getId()); - } - if (targetIds.isEmpty()) return false; - } - THashSet toRemove = new THashSet<>(); - Set settingsIds = new HashSet<>(); + for (HabboItem item : targets) { + if (item == null) return false; + + WiredMatchFurniSetting setting = this.resolveSettingForTarget(room, item); + if (setting == null) { + return false; + } + + if (!this.matchesSetting(item, setting)) { + return false; + } + } + + return true; + } for (WiredMatchFurniSetting setting : this.settings) { - if (targetIds != null && !targetIds.contains(setting.item_id)) { - continue; - } HabboItem item = room.getHabboItem(setting.item_id); - - if (item != null) { - settingsIds.add(setting.item_id); - if (this.state) { - if (!item.getExtradata().equals(setting.state)) - return false; - } - - if (this.position) { - if (!(setting.x == item.getX() && setting.y == item.getY())) - return false; - } - - if (this.direction) { - if (setting.rotation != item.getRotation()) - return false; - } - } else { - toRemove.add(setting); - } - } - - if (targetIds != null && !settingsIds.containsAll(targetIds)) { - return false; - } - - if (!toRemove.isEmpty()) { - for (WiredMatchFurniSetting setting : toRemove) { - this.settings.remove(setting); - } + if (item == null) continue; + if (!this.matchesSetting(item, setting)) + return false; } return true; } + private WiredMatchFurniSetting resolveSettingForTarget(Room room, HabboItem target) { + WiredMatchFurniSetting fallback = null; + + for (WiredMatchFurniSetting setting : this.settings) { + HabboItem sourceItem = room.getHabboItem(setting.item_id); + if (sourceItem == null) continue; + if (sourceItem.getBaseItem().getId() != target.getBaseItem().getId()) continue; + + if (setting.state.equals(target.getExtradata())) { + return setting; + } + + if (fallback == null) { + fallback = setting; + } + } + + return fallback; + } + + private boolean matchesSetting(HabboItem item, WiredMatchFurniSetting setting) { + if (this.state && !item.getExtradata().equals(setting.state)) + return false; + + if (this.position && !(setting.x == item.getX() && setting.y == item.getY())) + return false; + + return !this.direction || setting.rotation == item.getRotation(); + } + @Deprecated @Override public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { @@ -214,9 +216,6 @@ public class WiredConditionMatchStatePosition extends InteractionWiredCondition this.position = data[4].equals("1"); this.furniSource = this.settings.isEmpty() ? WiredSourceUtil.SOURCE_TRIGGER : WiredSourceUtil.SOURCE_SELECTED; } - if (this.furniSource == WiredSourceUtil.SOURCE_TRIGGER && !this.settings.isEmpty()) { - this.furniSource = WiredSourceUtil.SOURCE_SELECTED; - } } @Override diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchTime.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchTime.java new file mode 100644 index 00000000..982b56ec --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchTime.java @@ -0,0 +1,237 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.conditions; + +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.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.wired.WiredConditionType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.util.HotelDateTimeUtil; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.LocalTime; + +public class WiredConditionMatchTime extends InteractionWiredCondition { + private static final int MODE_SKIP = 0; + private static final int MODE_EXACT = 1; + private static final int MODE_RANGE = 2; + + public static final WiredConditionType type = WiredConditionType.MATCH_TIME; + + private int hourMode = MODE_SKIP; + private int hourFrom = 0; + private int hourTo = 0; + private int minuteMode = MODE_SKIP; + private int minuteFrom = 0; + private int minuteTo = 0; + private int secondMode = MODE_SKIP; + private int secondFrom = 0; + private int secondTo = 0; + + public WiredConditionMatchTime(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredConditionMatchTime(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) { + message.appendBoolean(false); + message.appendInt(5); + message.appendInt(0); + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(""); + message.appendInt(9); + message.appendInt(this.hourMode); + message.appendInt(this.hourFrom); + message.appendInt(this.hourTo); + message.appendInt(this.minuteMode); + message.appendInt(this.minuteFrom); + message.appendInt(this.minuteTo); + message.appendInt(this.secondMode); + message.appendInt(this.secondFrom); + message.appendInt(this.secondTo); + message.appendInt(0); + message.appendInt(this.getType().code); + message.appendInt(0); + message.appendInt(0); + } + + @Override + public boolean saveData(WiredSettings settings) { + int[] params = settings.getIntParams(); + + this.hourMode = (params.length > 0) ? this.normalizeMode(params[0]) : MODE_SKIP; + this.hourFrom = (params.length > 1) ? this.normalizeHour(params[1]) : 0; + this.hourTo = (params.length > 2) ? this.normalizeHour(params[2]) : this.hourFrom; + this.minuteMode = (params.length > 3) ? this.normalizeMode(params[3]) : MODE_SKIP; + this.minuteFrom = (params.length > 4) ? this.normalizeMinuteOrSecond(params[4]) : 0; + this.minuteTo = (params.length > 5) ? this.normalizeMinuteOrSecond(params[5]) : this.minuteFrom; + this.secondMode = (params.length > 6) ? this.normalizeMode(params[6]) : MODE_SKIP; + this.secondFrom = (params.length > 7) ? this.normalizeMinuteOrSecond(params[7]) : 0; + this.secondTo = (params.length > 8) ? this.normalizeMinuteOrSecond(params[8]) : this.secondFrom; + + return true; + } + + @Override + public boolean evaluate(WiredContext ctx) { + LocalTime now = HotelDateTimeUtil.localTimeNow(); + + return this.matchesTimePart(now.getHour(), this.hourMode, this.hourFrom, this.hourTo) + && this.matchesTimePart(now.getMinute(), this.minuteMode, this.minuteFrom, this.minuteTo) + && this.matchesTimePart(now.getSecond(), this.secondMode, this.secondFrom, this.secondTo); + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson(new JsonData( + this.hourMode, + this.hourFrom, + this.hourTo, + this.minuteMode, + this.minuteFrom, + this.minuteTo, + this.secondMode, + this.secondFrom, + this.secondTo + )); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + this.reset(); + + 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) { + return; + } + + this.hourMode = this.normalizeMode(data.hourMode); + this.hourFrom = this.normalizeHour(data.hourFrom); + this.hourTo = this.normalizeHour(data.hourTo); + this.minuteMode = this.normalizeMode(data.minuteMode); + this.minuteFrom = this.normalizeMinuteOrSecond(data.minuteFrom); + this.minuteTo = this.normalizeMinuteOrSecond(data.minuteTo); + this.secondMode = this.normalizeMode(data.secondMode); + this.secondFrom = this.normalizeMinuteOrSecond(data.secondFrom); + this.secondTo = this.normalizeMinuteOrSecond(data.secondTo); + return; + } + + String[] data = wiredData.split("\t"); + if (data.length != 9) { + return; + } + + try { + this.hourMode = this.normalizeMode(Integer.parseInt(data[0])); + this.hourFrom = this.normalizeHour(Integer.parseInt(data[1])); + this.hourTo = this.normalizeHour(Integer.parseInt(data[2])); + this.minuteMode = this.normalizeMode(Integer.parseInt(data[3])); + this.minuteFrom = this.normalizeMinuteOrSecond(Integer.parseInt(data[4])); + this.minuteTo = this.normalizeMinuteOrSecond(Integer.parseInt(data[5])); + this.secondMode = this.normalizeMode(Integer.parseInt(data[6])); + this.secondFrom = this.normalizeMinuteOrSecond(Integer.parseInt(data[7])); + this.secondTo = this.normalizeMinuteOrSecond(Integer.parseInt(data[8])); + } catch (NumberFormatException ignored) { + this.reset(); + } + } + + @Override + public void onPickUp() { + this.reset(); + } + + private void reset() { + this.hourMode = MODE_SKIP; + this.hourFrom = 0; + this.hourTo = 0; + this.minuteMode = MODE_SKIP; + this.minuteFrom = 0; + this.minuteTo = 0; + this.secondMode = MODE_SKIP; + this.secondFrom = 0; + this.secondTo = 0; + } + + private boolean matchesTimePart(int currentValue, int mode, int fromValue, int toValue) { + switch (mode) { + case MODE_EXACT: + return currentValue == fromValue; + case MODE_RANGE: + if (fromValue <= toValue) { + return currentValue >= fromValue && currentValue <= toValue; + } + + return currentValue >= fromValue || currentValue <= toValue; + default: + return true; + } + } + + private int normalizeMode(int value) { + if (value < MODE_SKIP || value > MODE_RANGE) { + return MODE_SKIP; + } + + return value; + } + + private int normalizeHour(int value) { + return Math.max(0, Math.min(23, value)); + } + + private int normalizeMinuteOrSecond(int value) { + return Math.max(0, Math.min(59, value)); + } + + static class JsonData { + int hourMode; + int hourFrom; + int hourTo; + int minuteMode; + int minuteFrom; + int minuteTo; + int secondMode; + int secondFrom; + int secondTo; + + public JsonData(int hourMode, int hourFrom, int hourTo, int minuteMode, int minuteFrom, int minuteTo, int secondMode, int secondFrom, int secondTo) { + this.hourMode = hourMode; + this.hourFrom = hourFrom; + this.hourTo = hourTo; + this.minuteMode = minuteMode; + this.minuteFrom = minuteFrom; + this.minuteTo = minuteTo; + this.secondMode = secondMode; + this.secondFrom = secondFrom; + this.secondTo = secondTo; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboHasHandItem.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboHasHandItem.java new file mode 100644 index 00000000..24421924 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboHasHandItem.java @@ -0,0 +1,42 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.conditions; + +import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.wired.WiredConditionType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +public class WiredConditionNotHabboHasHandItem extends WiredConditionHabboHasHandItem { + public static final WiredConditionType type = WiredConditionType.NOT_ACTOR_HAS_HANDITEM; + + public WiredConditionNotHabboHasHandItem(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredConditionNotHabboHasHandItem(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { + super(id, userId, item, extradata, limitedStack, limitedSells); + } + + @Override + public boolean evaluate(WiredContext ctx) { + List targets = WiredSourceUtil.resolveUsers(ctx, this.getUserSource()); + if (targets.isEmpty()) return false; + + for (RoomUnit roomUnit : targets) { + if (roomUnit == null || roomUnit.getHandItem() == this.getHandItem()) { + return false; + } + } + + return true; + } + + @Override + public WiredConditionType getType() { + return type; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotTriggererMatch.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotTriggererMatch.java new file mode 100644 index 00000000..3e945329 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotTriggererMatch.java @@ -0,0 +1,31 @@ +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 WiredConditionNotTriggererMatch extends WiredConditionTriggererMatch { + public static final WiredConditionType type = WiredConditionType.NOT_TRIGGERER_MATCH; + + public WiredConditionNotTriggererMatch(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredConditionNotTriggererMatch(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { + super(id, userId, item, extradata, limitedStack, limitedSells); + } + + @Override + public boolean evaluate(WiredContext ctx) { + MatchResult result = this.evaluateMatch(ctx); + return result.valid && !result.matched; + } + + @Override + public WiredConditionType getType() { + return type; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotUserPerformsAction.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotUserPerformsAction.java new file mode 100644 index 00000000..d4ff4f38 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotUserPerformsAction.java @@ -0,0 +1,46 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.conditions; + +import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.wired.WiredConditionType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +public class WiredConditionNotUserPerformsAction extends WiredConditionUserPerformsAction { + private static final int QUANTIFIER_ANY_NOT_MATCH = 0; + private static final int QUANTIFIER_NONE_MATCH = 1; + + public static final WiredConditionType type = WiredConditionType.NOT_USER_PERFORMS_ACTION; + + public WiredConditionNotUserPerformsAction(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredConditionNotUserPerformsAction(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { + super(id, userId, item, extradata, limitedStack, limitedSells); + } + + @Override + public boolean evaluate(WiredContext ctx) { + List targets = WiredSourceUtil.resolveUsers(ctx, this.getUserSource()); + if (targets.isEmpty()) { + return false; + } + + if (this.getQuantifier() == QUANTIFIER_NONE_MATCH) { + return targets.stream().noneMatch(roomUnit -> this.matchesAction(ctx, roomUnit)); + } + + return targets.stream().anyMatch(roomUnit -> !this.matchesAction(ctx, roomUnit)); + } + + @Override + public WiredConditionType getType() { + return type; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamGameBase.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamGameBase.java new file mode 100644 index 00000000..8e05c562 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamGameBase.java @@ -0,0 +1,197 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.conditions; + +import com.eu.habbo.habbohotel.games.Game; +import com.eu.habbo.habbohotel.games.GameState; +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.items.Item; +import com.eu.habbo.habbohotel.items.interactions.InteractionWiredCondition; +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.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +abstract class WiredConditionTeamGameBase extends InteractionWiredCondition { + protected static final int QUANTIFIER_ALL = 0; + protected static final int QUANTIFIER_ANY = 1; + protected static final int COMPARISON_LOWER = 0; + protected static final int COMPARISON_EQUAL = 1; + protected static final int COMPARISON_HIGHER = 2; + protected static final int TEAM_TRIGGERER = 0; + + private static final GameTeamColors[] SUPPORTED_TEAM_COLORS = new GameTeamColors[] { + GameTeamColors.RED, + GameTeamColors.GREEN, + GameTeamColors.BLUE, + GameTeamColors.YELLOW + }; + + protected WiredConditionTeamGameBase(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + protected WiredConditionTeamGameBase(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { + super(id, userId, item, extradata, limitedStack, limitedSells); + } + + protected List resolveUsers(WiredContext ctx, int userSource) { + Map deduplicated = new LinkedHashMap<>(); + + for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, userSource)) { + if (roomUnit != null) { + deduplicated.putIfAbsent(roomUnit.getId(), roomUnit); + } + } + + return new ArrayList<>(deduplicated.values()); + } + + protected boolean matchesQuantifier(List users, int quantifier, Predicate predicate) { + if (users.isEmpty()) { + return false; + } + + if (quantifier == QUANTIFIER_ANY) { + return users.stream().anyMatch(predicate); + } + + return users.stream().allMatch(predicate); + } + + protected int normalizeQuantifier(int value) { + return (value == QUANTIFIER_ANY) ? QUANTIFIER_ANY : QUANTIFIER_ALL; + } + + protected int normalizeComparison(int value) { + switch (value) { + case COMPARISON_LOWER: + case COMPARISON_HIGHER: + return value; + default: + return COMPARISON_EQUAL; + } + } + + protected int normalizeUserSource(int value) { + switch (value) { + case WiredSourceUtil.SOURCE_SELECTOR: + case WiredSourceUtil.SOURCE_SIGNAL: + case WiredSourceUtil.SOURCE_TRIGGER: + return value; + default: + return WiredSourceUtil.SOURCE_TRIGGER; + } + } + + protected int normalizePlacement(int value) { + if (value < 1 || value > 4) { + return 1; + } + + return value; + } + + protected int normalizeScore(int value) { + return Math.max(0, value); + } + + protected int normalizeExplicitTeamType(int value) { + GameTeamColors color = GameTeamColors.fromType(value); + return (color.type >= GameTeamColors.RED.type && color.type <= GameTeamColors.YELLOW.type) + ? color.type + : GameTeamColors.RED.type; + } + + protected int normalizeRankTeamType(int value) { + if (value == TEAM_TRIGGERER) { + return TEAM_TRIGGERER; + } + + return this.normalizeExplicitTeamType(value); + } + + protected GameTeamColors resolveConfiguredTeamColor(int value) { + return GameTeamColors.fromType(this.normalizeExplicitTeamType(value)); + } + + protected boolean compareValue(int actual, int expected, int comparison) { + switch (comparison) { + case COMPARISON_LOWER: + return actual < expected; + case COMPARISON_HIGHER: + return actual > expected; + default: + return actual == expected; + } + } + + protected UserGameContext resolveUserGameContext(Room room, RoomUnit roomUnit) { + if (room == null || roomUnit == null) { + return null; + } + + Habbo habbo = room.getHabbo(roomUnit); + if (habbo == null || habbo.getHabboInfo() == null || habbo.getHabboInfo().getCurrentGame() == null) { + return null; + } + + Game game = room.getGame(habbo.getHabboInfo().getCurrentGame()); + if (!this.isSupportedGame(game)) { + return null; + } + + GameTeam team = game.getTeamForHabbo(habbo); + if (team == null) { + return null; + } + + return new UserGameContext(habbo, game, team); + } + + protected int getTeamRank(Game game, GameTeam team) { + if (game == null || team == null) { + return Integer.MAX_VALUE; + } + + int rank = 1; + int targetScore = team.getTotalScore(); + + for (GameTeamColors teamColor : SUPPORTED_TEAM_COLORS) { + GameTeam otherTeam = game.getTeam(teamColor); + if (otherTeam != null && otherTeam != team && otherTeam.getTotalScore() > targetScore) { + rank++; + } + } + + return rank; + } + + private boolean isSupportedGame(Game game) { + return game != null + && game.getState() != GameState.IDLE + && (game instanceof FreezeGame || game instanceof BattleBanzaiGame); + } + + protected static class UserGameContext { + protected final Habbo habbo; + protected final Game game; + protected final GameTeam team; + + protected UserGameContext(Habbo habbo, Game game, GameTeam team) { + this.habbo = habbo; + this.game = game; + this.team = team; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamHasRank.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamHasRank.java new file mode 100644 index 00000000..58272197 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamHasRank.java @@ -0,0 +1,175 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.conditions; + +import com.eu.habbo.habbohotel.games.GameTeam; +import com.eu.habbo.habbohotel.games.GameTeamColors; +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.wired.WiredConditionType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil; +import com.eu.habbo.messages.ServerMessage; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +public class WiredConditionTeamHasRank extends WiredConditionTeamGameBase { + public static final WiredConditionType type = WiredConditionType.TEAM_HAS_RANK; + + private int teamType = GameTeamColors.RED.type; + private int placement = 1; + private int userSource = WiredSourceUtil.SOURCE_TRIGGER; + private int quantifier = QUANTIFIER_ALL; + + public WiredConditionTeamHasRank(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredConditionTeamHasRank(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { + super(id, userId, item, extradata, limitedStack, limitedSells); + } + + @Override + public boolean evaluate(WiredContext ctx) { + Room room = ctx.room(); + List users = this.resolveUsers(ctx, this.userSource); + + return this.matchesQuantifier(users, this.quantifier, roomUnit -> this.matchesUser(ctx, room, roomUnit)); + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson(new JsonData( + this.teamType, + this.placement, + this.userSource, + this.quantifier + )); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + this.resetSettings(); + + String wiredData = set.getString("wired_data"); + if (wiredData == null || !wiredData.startsWith("{")) { + return; + } + + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + if (data == null) { + return; + } + + this.teamType = this.normalizeRankTeamType(data.teamType); + this.placement = this.normalizePlacement(data.placement); + this.userSource = this.normalizeUserSource(data.userSource); + this.quantifier = this.normalizeQuantifier(data.quantifier); + } + + @Override + public void onPickUp() { + this.resetSettings(); + } + + @Override + public WiredConditionType getType() { + return type; + } + + @Override + public void serializeWiredData(ServerMessage message, Room room) { + message.appendBoolean(false); + message.appendInt(5); + message.appendInt(0); + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(""); + message.appendInt(4); + message.appendInt(this.teamType); + message.appendInt(this.placement); + message.appendInt(this.userSource); + 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(); + this.resetSettings(); + + if (params.length > 0) this.teamType = this.normalizeRankTeamType(params[0]); + if (params.length > 1) this.placement = this.normalizePlacement(params[1]); + if (params.length > 2) this.userSource = this.normalizeUserSource(params[2]); + if (params.length > 3) this.quantifier = this.normalizeQuantifier(params[3]); + + return true; + } + + private boolean matchesUser(WiredContext ctx, Room room, RoomUnit roomUnit) { + UserGameContext context = this.resolveUserGameContext(room, roomUnit); + if (context == null) { + return false; + } + + GameTeamColors requiredTeam = this.resolveRequiredTeamColor(ctx, room, context.game); + if (requiredTeam == GameTeamColors.NONE || context.team.teamColor != requiredTeam) { + return false; + } + + GameTeam team = context.game.getTeam(requiredTeam); + if (team == null) { + return false; + } + + return this.getTeamRank(context.game, team) == this.placement; + } + + private GameTeamColors resolveRequiredTeamColor(WiredContext ctx, Room room, com.eu.habbo.habbohotel.games.Game game) { + if (this.teamType == TEAM_TRIGGERER) { + RoomUnit actor = ctx.actor().orElse(null); + UserGameContext triggererContext = this.resolveUserGameContext(room, actor); + + if (triggererContext == null || triggererContext.game != game) { + return GameTeamColors.NONE; + } + + return triggererContext.team.teamColor; + } + + return this.resolveConfiguredTeamColor(this.teamType); + } + + private void resetSettings() { + this.teamType = GameTeamColors.RED.type; + this.placement = 1; + this.userSource = WiredSourceUtil.SOURCE_TRIGGER; + this.quantifier = QUANTIFIER_ALL; + } + + static class JsonData { + int teamType; + int placement; + int userSource; + int quantifier; + + public JsonData(int teamType, int placement, int userSource, int quantifier) { + this.teamType = teamType; + this.placement = placement; + this.userSource = userSource; + this.quantifier = quantifier; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamHasScore.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamHasScore.java new file mode 100644 index 00000000..e85741a6 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamHasScore.java @@ -0,0 +1,162 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.conditions; + +import com.eu.habbo.habbohotel.games.GameTeamColors; +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.wired.WiredConditionType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil; +import com.eu.habbo.messages.ServerMessage; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +public class WiredConditionTeamHasScore extends WiredConditionTeamGameBase { + public static final WiredConditionType type = WiredConditionType.TEAM_HAS_SCORE; + + private int teamType = GameTeamColors.RED.type; + private int comparison = COMPARISON_EQUAL; + private int score = 0; + private int userSource = WiredSourceUtil.SOURCE_TRIGGER; + private int quantifier = QUANTIFIER_ALL; + + public WiredConditionTeamHasScore(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredConditionTeamHasScore(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { + super(id, userId, item, extradata, limitedStack, limitedSells); + } + + @Override + public boolean evaluate(WiredContext ctx) { + Room room = ctx.room(); + List users = this.resolveUsers(ctx, this.userSource); + + return this.matchesQuantifier(users, this.quantifier, roomUnit -> this.matchesUser(room, roomUnit)); + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson(new JsonData( + this.teamType, + this.comparison, + this.score, + this.userSource, + this.quantifier + )); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + this.resetSettings(); + + String wiredData = set.getString("wired_data"); + if (wiredData == null || !wiredData.startsWith("{")) { + return; + } + + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + if (data == null) { + return; + } + + this.teamType = this.normalizeExplicitTeamType(data.teamType); + this.comparison = this.normalizeComparison(data.comparison); + this.score = this.normalizeScore(data.score); + this.userSource = this.normalizeUserSource(data.userSource); + this.quantifier = this.normalizeQuantifier(data.quantifier); + } + + @Override + public void onPickUp() { + this.resetSettings(); + } + + @Override + public WiredConditionType getType() { + return type; + } + + @Override + public void serializeWiredData(ServerMessage message, Room room) { + message.appendBoolean(false); + message.appendInt(5); + message.appendInt(0); + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(""); + message.appendInt(5); + message.appendInt(this.teamType); + message.appendInt(this.comparison); + message.appendInt(this.score); + message.appendInt(this.userSource); + 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(); + this.resetSettings(); + + if (params.length > 0) this.teamType = this.normalizeExplicitTeamType(params[0]); + if (params.length > 1) this.comparison = this.normalizeComparison(params[1]); + if (params.length > 2) this.score = this.normalizeScore(params[2]); + if (params.length > 3) this.userSource = this.normalizeUserSource(params[3]); + if (params.length > 4) this.quantifier = this.normalizeQuantifier(params[4]); + + return true; + } + + private boolean matchesUser(Room room, RoomUnit roomUnit) { + UserGameContext context = this.resolveUserGameContext(room, roomUnit); + if (context == null) { + return false; + } + + GameTeamColors requiredTeam = this.resolveConfiguredTeamColor(this.teamType); + if (context.team.teamColor != requiredTeam) { + return false; + } + + return this.compareValue(context.team.getTotalScore(), this.score, this.comparison); + } + + private void resetSettings() { + this.teamType = GameTeamColors.RED.type; + this.comparison = COMPARISON_EQUAL; + this.score = 0; + this.userSource = WiredSourceUtil.SOURCE_TRIGGER; + this.quantifier = QUANTIFIER_ALL; + } + + static class JsonData { + int teamType; + int comparison; + int score; + int userSource; + int quantifier; + + public JsonData(int teamType, int comparison, int score, int userSource, int quantifier) { + this.teamType = teamType; + this.comparison = comparison; + this.score = score; + this.userSource = userSource; + this.quantifier = quantifier; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTriggererMatch.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTriggererMatch.java new file mode 100644 index 00000000..f9997436 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTriggererMatch.java @@ -0,0 +1,368 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.conditions; + +import com.eu.habbo.habbohotel.bots.Bot; +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.RoomUnit; +import com.eu.habbo.habbohotel.rooms.RoomUnitType; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.habbohotel.wired.WiredConditionType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil; +import com.eu.habbo.messages.ServerMessage; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class WiredConditionTriggererMatch extends InteractionWiredCondition { + protected static final int ENTITY_HABBO = 1; + protected static final int ENTITY_PET = 2; + protected static final int ENTITY_BOT = 4; + protected static final int AVATAR_MODE_ANY = 0; + protected static final int AVATAR_MODE_CERTAIN = 1; + protected static final int QUANTIFIER_ALL = 0; + protected static final int QUANTIFIER_ANY = 1; + protected static final int SOURCE_SPECIFIED_USERNAME = 101; + + public static final WiredConditionType type = WiredConditionType.TRIGGERER_MATCH; + + private int entityType = ENTITY_HABBO; + private int avatarMode = AVATAR_MODE_ANY; + private int matchUserSource = WiredSourceUtil.SOURCE_TRIGGER; + private int compareUserSource = WiredSourceUtil.SOURCE_TRIGGER; + private int quantifier = QUANTIFIER_ALL; + private String username = ""; + + public WiredConditionTriggererMatch(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredConditionTriggererMatch(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { + super(id, userId, item, extradata, limitedStack, limitedSells); + } + + @Override + public boolean evaluate(WiredContext ctx) { + MatchResult result = this.evaluateMatch(ctx); + return result.valid && result.matched; + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson(new JsonData( + this.entityType, + this.avatarMode, + this.matchUserSource, + this.compareUserSource, + this.quantifier, + this.username + )); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + this.resetSettings(); + + String wiredData = set.getString("wired_data"); + if (wiredData == null || !wiredData.startsWith("{")) { + return; + } + + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + if (data == null) { + return; + } + + this.entityType = this.normalizeEntityType(data.entityType); + this.avatarMode = this.normalizeAvatarMode(data.avatarMode); + this.matchUserSource = this.normalizePrimaryUserSource(data.matchUserSource); + this.compareUserSource = this.normalizeCompareUserSource(data.compareUserSource); + this.quantifier = this.normalizeQuantifier(data.quantifier); + this.username = this.normalizeUsername(data.username); + } + + @Override + public void onPickUp() { + this.resetSettings(); + } + + @Override + public WiredConditionType getType() { + return type; + } + + @Override + public void serializeWiredData(ServerMessage message, Room room) { + message.appendBoolean(true); + message.appendInt(5); + message.appendInt(0); + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(this.username); + message.appendInt(5); + message.appendInt(this.entityType); + message.appendInt(this.avatarMode); + message.appendInt(this.matchUserSource); + message.appendInt(this.compareUserSource); + 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(); + + this.resetSettings(); + + if (params.length > 0) this.entityType = this.normalizeEntityType(params[0]); + if (params.length > 1) this.avatarMode = this.normalizeAvatarMode(params[1]); + if (params.length > 2) this.matchUserSource = this.normalizePrimaryUserSource(params[2]); + if (params.length > 3) this.compareUserSource = this.normalizeCompareUserSource(params[3]); + if (params.length > 4) this.quantifier = this.normalizeQuantifier(params[4]); + + this.username = this.normalizeUsername(settings.getStringParam()); + + return true; + } + + protected MatchResult evaluateMatch(WiredContext ctx) { + List matchUsers = this.resolvePrimaryUsers(ctx); + if (matchUsers.isEmpty()) { + return MatchResult.invalid(); + } + + List compareUsers = this.resolveCompareUsers(ctx); + if (compareUsers.isEmpty()) { + return MatchResult.valid(false); + } + + Set compareUserIds = compareUsers.stream() + .filter(this::matchesEntityType) + .map(RoomUnit::getId) + .collect(Collectors.toSet()); + + if (compareUserIds.isEmpty()) { + return MatchResult.valid(false); + } + + boolean matched; + if (this.quantifier == QUANTIFIER_ANY) { + matched = matchUsers.stream().anyMatch(roomUnit -> this.matchesCandidate(roomUnit, compareUserIds)); + } else { + matched = matchUsers.stream().allMatch(roomUnit -> this.matchesCandidate(roomUnit, compareUserIds)); + } + + return MatchResult.valid(matched); + } + + protected int getQuantifier() { + return this.quantifier; + } + + private void resetSettings() { + this.entityType = ENTITY_HABBO; + this.avatarMode = AVATAR_MODE_ANY; + this.matchUserSource = WiredSourceUtil.SOURCE_TRIGGER; + this.compareUserSource = WiredSourceUtil.SOURCE_TRIGGER; + this.quantifier = QUANTIFIER_ALL; + this.username = ""; + } + + private List resolvePrimaryUsers(WiredContext ctx) { + return this.deduplicate(WiredSourceUtil.resolveUsers(ctx, this.matchUserSource)); + } + + private List resolveCompareUsers(WiredContext ctx) { + List resolved; + + if (this.compareUserSource == SOURCE_SPECIFIED_USERNAME) { + resolved = this.resolveUsersByName(ctx.room(), this.username); + } else { + resolved = WiredSourceUtil.resolveUsers(ctx, this.compareUserSource); + } + + if (this.avatarMode == AVATAR_MODE_CERTAIN) { + String normalizedName = this.normalizeUsername(this.username); + if (normalizedName.isEmpty()) { + return new ArrayList<>(); + } + + resolved = resolved.stream() + .filter(roomUnit -> normalizedName.equalsIgnoreCase(this.getRoomUnitName(ctx.room(), roomUnit))) + .collect(Collectors.toList()); + } + + return this.deduplicate(resolved); + } + + private List resolveUsersByName(Room room, String username) { + List result = new ArrayList<>(); + String normalizedName = this.normalizeUsername(username); + if (room == null || normalizedName.isEmpty()) { + return result; + } + + Habbo habbo = room.getHabbo(normalizedName); + if (habbo != null && habbo.getRoomUnit() != null) { + result.add(habbo.getRoomUnit()); + } + + for (Bot bot : room.getBots(normalizedName)) { + if (bot != null && bot.getRoomUnit() != null) { + result.add(bot.getRoomUnit()); + } + } + + for (Pet pet : room.getUnitManager().getPets()) { + if (pet != null && pet.getRoomUnit() != null && normalizedName.equalsIgnoreCase(pet.getName())) { + result.add(pet.getRoomUnit()); + } + } + + return result; + } + + private List deduplicate(List users) { + Map deduplicated = new LinkedHashMap<>(); + + for (RoomUnit user : users) { + if (user != null) { + deduplicated.putIfAbsent(user.getId(), user); + } + } + + return new ArrayList<>(deduplicated.values()); + } + + private boolean matchesCandidate(RoomUnit roomUnit, Set compareUserIds) { + return roomUnit != null && this.matchesEntityType(roomUnit) && compareUserIds.contains(roomUnit.getId()); + } + + private boolean matchesEntityType(RoomUnit roomUnit) { + return roomUnit != null && roomUnit.getRoomUnitType().getTypeId() == this.entityType; + } + + private String getRoomUnitName(Room room, RoomUnit roomUnit) { + if (room == null || roomUnit == null) { + return ""; + } + + if (roomUnit.getRoomUnitType() == RoomUnitType.USER) { + Habbo habbo = room.getHabbo(roomUnit); + return (habbo != null && habbo.getHabboInfo() != null) ? habbo.getHabboInfo().getUsername() : ""; + } + + if (roomUnit.getRoomUnitType() == RoomUnitType.BOT) { + Bot bot = room.getBot(roomUnit); + return (bot != null) ? bot.getName() : ""; + } + + if (roomUnit.getRoomUnitType() == RoomUnitType.PET) { + Pet pet = room.getPet(roomUnit); + return (pet != null) ? pet.getName() : ""; + } + + return ""; + } + + private int normalizeEntityType(int value) { + switch (value) { + case ENTITY_HABBO: + case ENTITY_PET: + case ENTITY_BOT: + return value; + default: + return ENTITY_HABBO; + } + } + + private int normalizeAvatarMode(int value) { + return (value == AVATAR_MODE_CERTAIN) ? AVATAR_MODE_CERTAIN : AVATAR_MODE_ANY; + } + + private int normalizeQuantifier(int value) { + return (value == QUANTIFIER_ANY) ? QUANTIFIER_ANY : QUANTIFIER_ALL; + } + + private int normalizePrimaryUserSource(int value) { + switch (value) { + case WiredSourceUtil.SOURCE_SELECTOR: + case WiredSourceUtil.SOURCE_SIGNAL: + case WiredSourceUtil.SOURCE_TRIGGER: + return value; + default: + return WiredSourceUtil.SOURCE_TRIGGER; + } + } + + private int normalizeCompareUserSource(int value) { + switch (value) { + case WiredSourceUtil.SOURCE_SELECTOR: + case WiredSourceUtil.SOURCE_SIGNAL: + case WiredSourceUtil.SOURCE_TRIGGER: + case SOURCE_SPECIFIED_USERNAME: + return value; + default: + return WiredSourceUtil.SOURCE_TRIGGER; + } + } + + private String normalizeUsername(String value) { + return (value == null) ? "" : value.trim(); + } + + protected static class MatchResult { + protected final boolean valid; + protected final boolean matched; + + private MatchResult(boolean valid, boolean matched) { + this.valid = valid; + this.matched = matched; + } + + private static MatchResult invalid() { + return new MatchResult(false, false); + } + + private static MatchResult valid(boolean matched) { + return new MatchResult(true, matched); + } + } + + static class JsonData { + int entityType; + int avatarMode; + int matchUserSource; + int compareUserSource; + int quantifier; + String username; + + public JsonData(int entityType, int avatarMode, int matchUserSource, int compareUserSource, int quantifier, String username) { + this.entityType = entityType; + this.avatarMode = avatarMode; + this.matchUserSource = matchUserSource; + this.compareUserSource = compareUserSource; + this.quantifier = quantifier; + this.username = username; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionUserPerformsAction.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionUserPerformsAction.java new file mode 100644 index 00000000..a24ce86e --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionUserPerformsAction.java @@ -0,0 +1,346 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.conditions; + +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.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.rooms.RoomUnitStatus; +import com.eu.habbo.habbohotel.wired.WiredConditionType; +import com.eu.habbo.habbohotel.wired.WiredUserActionType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil; +import com.eu.habbo.messages.ServerMessage; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; + +public class WiredConditionUserPerformsAction extends InteractionWiredCondition { + private static final String CACHE_LAST_ACTION_ID = "wired.last_user_action.id"; + private static final String CACHE_LAST_ACTION_PARAMETER = "wired.last_user_action.parameter"; + private static final String CACHE_LAST_ACTION_TIMESTAMP = "wired.last_user_action.timestamp"; + private static final long TRANSIENT_ACTION_WINDOW_MS = 5_000L; + protected static final int DEFAULT_ACTION = WiredUserActionType.WAVE; + protected static final int QUANTIFIER_ALL = 0; + protected static final int QUANTIFIER_ANY = 1; + + public static final WiredConditionType type = WiredConditionType.USER_PERFORMS_ACTION; + + private int selectedAction = DEFAULT_ACTION; + private boolean signFilterEnabled = false; + private int signId = 0; + private boolean danceFilterEnabled = false; + private int danceId = 1; + private int userSource = WiredSourceUtil.SOURCE_TRIGGER; + private int quantifier = QUANTIFIER_ALL; + + public WiredConditionUserPerformsAction(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredConditionUserPerformsAction(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { + super(id, userId, item, extradata, limitedStack, limitedSells); + } + + @Override + public boolean evaluate(WiredContext ctx) { + List targets = WiredSourceUtil.resolveUsers(ctx, this.userSource); + if (targets.isEmpty()) { + return false; + } + + if (this.quantifier == QUANTIFIER_ANY) { + return targets.stream().anyMatch(roomUnit -> this.matchesAction(ctx, roomUnit)); + } + + return targets.stream().allMatch(roomUnit -> this.matchesAction(ctx, roomUnit)); + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson(new JsonData( + this.selectedAction, + this.signFilterEnabled, + this.signId, + this.danceFilterEnabled, + this.danceId, + this.userSource, + this.quantifier + )); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + this.resetSettings(); + + String wiredData = set.getString("wired_data"); + + if (wiredData == null || !wiredData.startsWith("{")) { + return; + } + + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + + if (data == null) { + return; + } + + this.selectedAction = normalizeAction(data.selectedAction); + this.signFilterEnabled = data.signFilterEnabled; + this.signId = normalizeSignId(data.signId); + this.danceFilterEnabled = data.danceFilterEnabled; + this.danceId = normalizeDanceId(data.danceId); + this.userSource = this.normalizeUserSource(data.userSource); + this.quantifier = normalizeQuantifier(data.quantifier); + } + + @Override + public void onPickUp() { + this.resetSettings(); + } + + @Override + public WiredConditionType getType() { + return type; + } + + @Override + public void serializeWiredData(ServerMessage message, Room room) { + message.appendBoolean(true); + message.appendInt(5); + message.appendInt(0); + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(""); + message.appendInt(7); + message.appendInt(this.selectedAction); + message.appendInt(this.signFilterEnabled ? 1 : 0); + message.appendInt(this.signId); + message.appendInt(this.danceFilterEnabled ? 1 : 0); + message.appendInt(this.danceId); + message.appendInt(this.userSource); + 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[] intParams = settings.getIntParams(); + + this.resetSettings(); + + if (intParams.length > 0) this.selectedAction = normalizeAction(intParams[0]); + if (intParams.length > 1) this.signFilterEnabled = (intParams[1] == 1); + if (intParams.length > 2) this.signId = normalizeSignId(intParams[2]); + if (intParams.length > 3) this.danceFilterEnabled = (intParams[3] == 1); + if (intParams.length > 4) this.danceId = normalizeDanceId(intParams[4]); + if (intParams.length > 5) this.userSource = this.normalizeUserSource(intParams[5]); + if (intParams.length > 6) this.quantifier = normalizeQuantifier(intParams[6]); + + return true; + } + + protected void resetSettings() { + this.selectedAction = DEFAULT_ACTION; + this.signFilterEnabled = false; + this.signId = 0; + this.danceFilterEnabled = false; + this.danceId = 1; + this.userSource = WiredSourceUtil.SOURCE_TRIGGER; + this.quantifier = QUANTIFIER_ALL; + } + + protected int normalizeAction(int action) { + switch (action) { + case WiredUserActionType.WAVE: + case WiredUserActionType.BLOW_KISS: + case WiredUserActionType.LAUGH: + case WiredUserActionType.AWAKE: + case WiredUserActionType.RELAX: + case WiredUserActionType.SIT: + case WiredUserActionType.STAND: + case WiredUserActionType.LAY: + case WiredUserActionType.SIGN: + case WiredUserActionType.DANCE: + case WiredUserActionType.THUMB_UP: + return action; + default: + return DEFAULT_ACTION; + } + } + + protected int normalizeQuantifier(int value) { + return (value == QUANTIFIER_ANY) ? QUANTIFIER_ANY : QUANTIFIER_ALL; + } + + protected int normalizeUserSource(int value) { + switch (value) { + case WiredSourceUtil.SOURCE_SELECTOR: + case WiredSourceUtil.SOURCE_SIGNAL: + case WiredSourceUtil.SOURCE_TRIGGER: + return value; + default: + return WiredSourceUtil.SOURCE_TRIGGER; + } + } + + protected int normalizeSignId(int value) { + return (value < 0 || value > 17) ? 0 : value; + } + + protected int normalizeDanceId(int value) { + return (value < 1 || value > 4) ? 1 : value; + } + + protected boolean matchesAction(WiredContext ctx, RoomUnit roomUnit) { + if (roomUnit == null) { + return false; + } + + if (this.matchesEventAction(ctx, roomUnit)) { + return true; + } + + if (this.matchesCurrentState(roomUnit)) { + return true; + } + + return this.matchesRecentAction(roomUnit); + } + + protected boolean matchesEventAction(WiredContext ctx, RoomUnit roomUnit) { + RoomUnit actor = ctx.actor().orElse(null); + + if (actor == null || actor.getId() != roomUnit.getId()) { + return false; + } + + if (ctx.eventType() != com.eu.habbo.habbohotel.wired.core.WiredEvent.Type.USER_PERFORMS_ACTION) { + return false; + } + + return this.matchesConfiguredAction(ctx.event().getActionId(), ctx.event().getActionParameter()); + } + + protected boolean matchesCurrentState(RoomUnit roomUnit) { + switch (this.selectedAction) { + case WiredUserActionType.SIT: + return roomUnit.hasStatus(RoomUnitStatus.SIT); + case WiredUserActionType.LAY: + return roomUnit.hasStatus(RoomUnitStatus.LAY); + case WiredUserActionType.RELAX: + return roomUnit.isIdle(); + case WiredUserActionType.SIGN: + return this.matchesSignState(roomUnit); + case WiredUserActionType.DANCE: + return this.matchesDanceState(roomUnit); + default: + return false; + } + } + + protected boolean matchesRecentAction(RoomUnit roomUnit) { + Object actionValue = roomUnit.getCacheable().get(CACHE_LAST_ACTION_ID); + Object parameterValue = roomUnit.getCacheable().get(CACHE_LAST_ACTION_PARAMETER); + Object timestampValue = roomUnit.getCacheable().get(CACHE_LAST_ACTION_TIMESTAMP); + + if (!(actionValue instanceof Integer) || !(timestampValue instanceof Long)) { + return false; + } + + long timestamp = (Long) timestampValue; + if ((System.currentTimeMillis() - timestamp) > TRANSIENT_ACTION_WINDOW_MS) { + return false; + } + + int actionId = (Integer) actionValue; + int parameter = (parameterValue instanceof Integer) ? (Integer) parameterValue : -1; + + return this.matchesConfiguredAction(actionId, parameter); + } + + protected boolean matchesConfiguredAction(int actionId, int actionParameter) { + if (actionId != this.selectedAction) { + return false; + } + + if (this.selectedAction == WiredUserActionType.SIGN && this.signFilterEnabled) { + return actionParameter == this.signId; + } + + if (this.selectedAction == WiredUserActionType.DANCE && this.danceFilterEnabled) { + return actionParameter == this.danceId; + } + + return true; + } + + protected boolean matchesSignState(RoomUnit roomUnit) { + String signStatus = roomUnit.getStatus(RoomUnitStatus.SIGN); + if (signStatus == null) { + return false; + } + + if (!this.signFilterEnabled) { + return true; + } + + try { + return Integer.parseInt(signStatus) == this.signId; + } catch (NumberFormatException ignored) { + return false; + } + } + + protected boolean matchesDanceState(RoomUnit roomUnit) { + int currentDance = roomUnit.getDanceType().getType(); + if (currentDance <= 0) { + return false; + } + + if (!this.danceFilterEnabled) { + return true; + } + + return currentDance == this.danceId; + } + + protected int getUserSource() { + return this.userSource; + } + + protected int getQuantifier() { + return this.quantifier; + } + + static class JsonData { + int selectedAction; + boolean signFilterEnabled; + int signId; + boolean danceFilterEnabled; + int danceId; + int userSource; + int quantifier; + + public JsonData(int selectedAction, boolean signFilterEnabled, int signId, boolean danceFilterEnabled, int danceId, int userSource, int quantifier) { + this.selectedAction = selectedAction; + this.signFilterEnabled = signFilterEnabled; + this.signId = signId; + this.danceFilterEnabled = danceFilterEnabled; + this.danceId = danceId; + this.userSource = userSource; + this.quantifier = quantifier; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectFreeze.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectFreeze.java new file mode 100644 index 00000000..66f11eae --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectFreeze.java @@ -0,0 +1,175 @@ +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.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.wired.WiredEffectType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil; +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.procedure.TObjectProcedure; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class WiredEffectFreeze extends InteractionWiredEffect { + private static final Set ALLOWED_EFFECT_IDS = Set.of(218, 12, 11, 53, 163); + public static final WiredEffectType type = WiredEffectType.FREEZE; + + private int effectId = 218; + private boolean cancelOnTeleport = false; + private int userSource = WiredSourceUtil.SOURCE_TRIGGER; + + public WiredEffectFreeze(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredEffectFreeze(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(); + + for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.userSource)) { + if (room.getHabbo(roomUnit) == null) { + continue; + } + + WiredFreezeUtil.freeze(room, roomUnit, this.effectId, this.cancelOnTeleport); + } + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson(new JsonData(this.effectId, this.cancelOnTeleport, this.getDelay(), this.userSource)); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + String wiredData = set.getString("wired_data"); + + if (wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + this.effectId = ALLOWED_EFFECT_IDS.contains(data.effectId) ? data.effectId : 218; + this.cancelOnTeleport = data.cancelOnTeleport; + this.setDelay(data.delay); + this.userSource = data.userSource; + } else { + this.effectId = 218; + this.cancelOnTeleport = false; + this.setDelay(0); + this.userSource = WiredSourceUtil.SOURCE_TRIGGER; + } + } + + @Override + public void onPickUp() { + this.effectId = 218; + this.cancelOnTeleport = false; + this.setDelay(0); + this.userSource = WiredSourceUtil.SOURCE_TRIGGER; + } + + @Override + public WiredEffectType getType() { + return type; + } + + @Override + public void serializeWiredData(ServerMessage message, Room room) { + message.appendBoolean(false); + message.appendInt(5); + message.appendInt(0); + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(""); + message.appendInt(3); + message.appendInt(this.effectId); + message.appendInt(this.cancelOnTeleport ? 1 : 0); + message.appendInt(this.userSource); + message.appendInt(0); + message.appendInt(this.getType().code); + message.appendInt(this.getDelay()); + + if (this.requiresTriggeringUser()) { + List invalidTriggers = new ArrayList<>(); + room.getRoomSpecialTypes().getTriggers(this.getX(), this.getY()).forEach(new TObjectProcedure() { + @Override + public boolean execute(InteractionWiredTrigger object) { + if (!object.isTriggeredByRoomUnit()) { + invalidTriggers.add(object.getBaseItem().getSpriteId()); + } + return true; + } + }); + message.appendInt(invalidTriggers.size()); + for (Integer i : invalidTriggers) { + message.appendInt(i); + } + } else { + message.appendInt(0); + } + } + + @Override + public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException { + if (settings.getIntParams().length < 3) { + throw new WiredSaveException("Invalid data"); + } + + int nextEffectId = settings.getIntParams()[0]; + if (!ALLOWED_EFFECT_IDS.contains(nextEffectId)) { + throw new WiredSaveException("Invalid freeze effect"); + } + + int delay = settings.getDelay(); + if (delay > Emulator.getConfig().getInt("hotel.wired.max_delay", 20)) { + throw new WiredSaveException("Delay too long"); + } + + this.effectId = nextEffectId; + this.cancelOnTeleport = settings.getIntParams()[1] == 1; + this.userSource = settings.getIntParams()[2]; + this.setDelay(delay); + + return true; + } + + @Override + public boolean requiresTriggeringUser() { + return this.userSource == WiredSourceUtil.SOURCE_TRIGGER; + } + + static class JsonData { + int effectId; + boolean cancelOnTeleport; + int delay; + int userSource; + + public JsonData(int effectId, boolean cancelOnTeleport, int delay, int userSource) { + this.effectId = effectId; + this.cancelOnTeleport = cancelOnTeleport; + this.delay = delay; + this.userSource = userSource; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectFurniToFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectFurniToFurni.java new file mode 100644 index 00000000..bdaa461b --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectFurniToFurni.java @@ -0,0 +1,354 @@ +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.FurnitureMovementError; +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomTile; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.WiredEffectType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil; +import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.messages.incoming.wired.WiredSaveException; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class WiredEffectFurniToFurni extends InteractionWiredEffect { + private static final int SOURCE_SECONDARY_SELECTED = 101; + private static final String FURNI_SPLIT_REGEX = "[;,\\t]"; + private static final String FURNI_DELIMITER = ";"; + + public static final WiredEffectType type = WiredEffectType.FURNI_TO_FURNI; + + private final List moveItems = new ArrayList<>(); + private final List targetItems = new ArrayList<>(); + private int moveSource = WiredSourceUtil.SOURCE_TRIGGER; + private int targetSource = WiredSourceUtil.SOURCE_TRIGGER; + + public WiredEffectFurniToFurni(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredEffectFurniToFurni(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; + } + + HabboItem moveItem = this.resolveLastMoveItem(ctx); + HabboItem targetItem = this.resolveLastTargetItem(ctx); + + if (moveItem == null || targetItem == null || moveItem.getId() == targetItem.getId()) { + return; + } + + RoomTile targetTile = room.getLayout().getTile(targetItem.getX(), targetItem.getY()); + if (targetTile == null) { + return; + } + + FurnitureMovementError error = room.moveFurniTo(moveItem, targetTile, moveItem.getRotation(), null, true, false); + if (error != FurnitureMovementError.NONE) { + room.moveFurniTo(moveItem, targetTile, moveItem.getRotation(), targetItem.getZ(), null, true, false); + } + } + + @Deprecated + @Override + public boolean execute(com.eu.habbo.habbohotel.rooms.RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson(new JsonData( + this.getDelay(), + this.moveItems.stream().map(HabboItem::getId).collect(Collectors.toList()), + this.targetItems.stream().map(HabboItem::getId).collect(Collectors.toList()), + this.moveSource, + this.targetSource + )); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + this.moveItems.clear(); + this.targetItems.clear(); + + String wiredData = set.getString("wired_data"); + + if (wiredData != null && wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + + this.setDelay(data.delay); + this.moveSource = data.moveSource; + this.targetSource = this.normalizeTargetSource(data.targetSource); + + this.loadItems(room, data.itemIds, this.moveItems); + this.loadItems(room, data.targetItemIds, this.targetItems); + + if (this.moveSource == WiredSourceUtil.SOURCE_TRIGGER && !this.moveItems.isEmpty()) { + this.moveSource = WiredSourceUtil.SOURCE_SELECTED; + } + + if (this.targetSource == WiredSourceUtil.SOURCE_TRIGGER && !this.targetItems.isEmpty()) { + this.targetSource = SOURCE_SECONDARY_SELECTED; + } + + return; + } + + if (wiredData != null && !wiredData.isEmpty()) { + String[] wiredDataOld = wiredData.split("\t"); + + if (wiredDataOld.length >= 1) { + this.setDelay(Integer.parseInt(wiredDataOld[0])); + } + + if (wiredDataOld.length >= 2 && !wiredDataOld[1].trim().isEmpty()) { + this.loadItems(room, this.parseIds(wiredDataOld[1], room), this.moveItems); + } + } + + this.moveSource = this.moveItems.isEmpty() ? WiredSourceUtil.SOURCE_TRIGGER : WiredSourceUtil.SOURCE_SELECTED; + this.targetSource = this.targetItems.isEmpty() ? WiredSourceUtil.SOURCE_TRIGGER : SOURCE_SECONDARY_SELECTED; + } + + @Override + public void onPickUp() { + this.moveItems.clear(); + this.targetItems.clear(); + this.moveSource = WiredSourceUtil.SOURCE_TRIGGER; + this.targetSource = WiredSourceUtil.SOURCE_TRIGGER; + this.setDelay(0); + } + + @Override + public void serializeWiredData(ServerMessage message, Room room) { + this.validateItems(this.moveItems); + this.validateItems(this.targetItems); + + message.appendBoolean(false); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); + message.appendInt(this.moveItems.size()); + + for (HabboItem item : this.moveItems) { + message.appendInt(item.getId()); + } + + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(this.serializeIds(this.targetItems)); + message.appendInt(2); + message.appendInt(this.moveSource); + message.appendInt(this.targetSource); + 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 { + this.moveSource = (settings.getIntParams().length > 0) ? settings.getIntParams()[0] : WiredSourceUtil.SOURCE_TRIGGER; + this.targetSource = this.normalizeTargetSource((settings.getIntParams().length > 1) ? settings.getIntParams()[1] : WiredSourceUtil.SOURCE_TRIGGER); + + Room room = this.getRoom(); + if (room == null) { + throw new WiredSaveException("Room not found"); + } + + List newMoveItems = new ArrayList<>(); + if (this.moveSource == WiredSourceUtil.SOURCE_SELECTED) { + for (int itemId : settings.getFurniIds()) { + HabboItem item = room.getHabboItem(itemId); + + if (item == null) { + throw new WiredSaveException(String.format("Item %s not found", itemId)); + } + + newMoveItems.add(item); + } + } + + if (newMoveItems.size() > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) { + throw new WiredSaveException("Too many furni selected"); + } + + List newTargetItems = new ArrayList<>(); + if (this.targetSource == SOURCE_SECONDARY_SELECTED) { + newTargetItems.addAll(this.parseItems(settings.getStringParam(), room)); + } + + if (newTargetItems.size() > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) { + throw new WiredSaveException("Too many furni selected"); + } + + int delay = settings.getDelay(); + if (delay > Emulator.getConfig().getInt("hotel.wired.max_delay", 20)) { + throw new WiredSaveException("Delay too long"); + } + + this.moveItems.clear(); + this.moveItems.addAll(newMoveItems); + + this.targetItems.clear(); + this.targetItems.addAll(newTargetItems); + + this.setDelay(delay); + + return true; + } + + @Override + public WiredEffectType getType() { + return type; + } + + @Override + protected long requiredCooldown() { + return COOLDOWN_MOVEMENT; + } + + private HabboItem resolveLastMoveItem(WiredContext ctx) { + return this.resolveLastItem(ctx, this.moveSource, this.moveItems); + } + + private HabboItem resolveLastTargetItem(WiredContext ctx) { + int source = (this.targetSource == SOURCE_SECONDARY_SELECTED) ? WiredSourceUtil.SOURCE_SELECTED : this.targetSource; + return this.resolveLastItem(ctx, source, this.targetItems); + } + + private HabboItem resolveLastItem(WiredContext ctx, int source, List items) { + if (source == WiredSourceUtil.SOURCE_SELECTED) { + this.validateItems(items); + } + + List resolvedItems = WiredSourceUtil.resolveItems(ctx, source, items); + + if (resolvedItems.isEmpty()) { + return null; + } + + for (int index = resolvedItems.size() - 1; index >= 0; index--) { + HabboItem item = resolvedItems.get(index); + + if (item != null) { + return item; + } + } + + return null; + } + + private List parseItems(String data, Room room) throws WiredSaveException { + List items = new ArrayList<>(); + if (data == null || data.trim().isEmpty() || room == null) { + return items; + } + + Set seen = new HashSet<>(); + + for (String part : data.split(FURNI_SPLIT_REGEX)) { + if (part == null) { + continue; + } + + String trimmed = part.trim(); + if (trimmed.isEmpty()) { + continue; + } + + int itemId; + try { + itemId = Integer.parseInt(trimmed); + } catch (NumberFormatException e) { + continue; + } + + if (itemId <= 0 || !seen.add(itemId)) { + continue; + } + + HabboItem item = room.getHabboItem(itemId); + if (item == null) { + throw new WiredSaveException(String.format("Item %s not found", itemId)); + } + + items.add(item); + } + + return items; + } + + private List parseIds(String data, Room room) { + try { + return this.parseItems(data, room).stream().map(HabboItem::getId).collect(Collectors.toList()); + } catch (WiredSaveException e) { + return new ArrayList<>(); + } + } + + private void loadItems(Room room, List itemIds, List target) { + if (room == null || itemIds == null || itemIds.isEmpty()) { + return; + } + + for (Integer itemId : itemIds) { + HabboItem item = room.getHabboItem(itemId); + + if (item != null) { + target.add(item); + } + } + } + + private int normalizeTargetSource(int source) { + return (source == WiredSourceUtil.SOURCE_SELECTED) ? SOURCE_SECONDARY_SELECTED : source; + } + + private String serializeIds(List items) { + if (items == null || items.isEmpty()) { + return ""; + } + + return items.stream() + .map(HabboItem::getId) + .distinct() + .map(String::valueOf) + .collect(Collectors.joining(FURNI_DELIMITER)); + } + + static class JsonData { + int delay; + List itemIds; + List targetItemIds; + int moveSource; + int targetSource; + + public JsonData(int delay, List itemIds, List targetItemIds, int moveSource, int targetSource) { + this.delay = delay; + this.itemIds = itemIds; + this.targetItemIds = targetItemIds; + this.moveSource = moveSource; + this.targetSource = targetSource; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectFurniToUser.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectFurniToUser.java new file mode 100644 index 00000000..62df4762 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectFurniToUser.java @@ -0,0 +1,57 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.effects; + +import com.eu.habbo.habbohotel.items.Item; +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.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 java.sql.ResultSet; +import java.sql.SQLException; + +public class WiredEffectFurniToUser extends WiredEffectUserFurniBase { + public static final WiredEffectType type = WiredEffectType.FURNI_TO_USER; + + public WiredEffectFurniToUser(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredEffectFurniToUser(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(); + HabboItem item = this.resolveLastItem(ctx); + Habbo habbo = this.resolveLastHabbo(room, ctx); + + if (room == null || item == null || habbo == null || habbo.getRoomUnit() == null) { + return; + } + + RoomTile targetTile = habbo.getRoomUnit().getCurrentLocation(); + if (targetTile == null) { + return; + } + + FurnitureMovementError error = room.moveFurniTo(item, targetTile, item.getRotation(), null, true, false); + if (error != FurnitureMovementError.NONE && item.getBaseItem().getStateCount() > 0) { + room.moveFurniTo(item, targetTile, item.getRotation(), item.getZ(), null, true, false); + } + } + + @Deprecated + @Override + public boolean execute(com.eu.habbo.habbohotel.rooms.RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public WiredEffectType getType() { + return type; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveReward.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveReward.java index a2df0c12..e57bc582 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveReward.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveReward.java @@ -140,15 +140,6 @@ public class WiredEffectGiveReward extends InteractionWiredEffect { return type; } - @Override - public void onClick(GameClient client, Room room, Object[] objects) throws Exception { - super.onClick(client, room, objects); - - if (client.getHabbo().hasPermission(Permission.ACC_SUPERWIRED)) { - client.getHabbo().whisper(Emulator.getTexts().getValue("hotel.wired.superwired.info"), RoomChatMessageBubbles.BOT); - } - } - @Override public void serializeWiredData(ServerMessage message, Room room) { message.appendBoolean(false); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMatchFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMatchFurni.java index bab1896a..e075f486 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMatchFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMatchFurni.java @@ -50,6 +50,7 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int @Override public void execute(WiredContext ctx) { Room room = ctx.room(); + this.refresh(); if(this.settings.isEmpty()) return; @@ -57,54 +58,81 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int if (room.getLayout() == null) return; - java.util.Set allowedItemIds = null; - if (this.furniSource != WiredSourceUtil.SOURCE_SELECTED) { - allowedItemIds = new java.util.HashSet<>(); - for (HabboItem si : WiredSourceUtil.resolveItems(ctx, this.furniSource, null)) { - if (si != null) { - allowedItemIds.add(si.getId()); + if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) { + for (WiredMatchFurniSetting setting : this.settings) { + HabboItem item = room.getHabboItem(setting.item_id); + if (item != null) { + this.applySetting(room, item, setting); } } - if (allowedItemIds.isEmpty()) { - return; + + return; + } + + List targets = WiredSourceUtil.resolveItems(ctx, this.furniSource, null); + if (targets.isEmpty()) { + return; + } + + for (HabboItem item : targets) { + if (item == null) continue; + + WiredMatchFurniSetting setting = this.resolveSettingForTarget(room, item); + if (setting == null) continue; + + this.applySetting(room, item, setting); + } + } + + private WiredMatchFurniSetting resolveSettingForTarget(Room room, HabboItem target) { + WiredMatchFurniSetting fallback = null; + + for (WiredMatchFurniSetting setting : this.settings) { + HabboItem sourceItem = room.getHabboItem(setting.item_id); + if (sourceItem == null) continue; + if (sourceItem.getBaseItem().getId() != target.getBaseItem().getId()) continue; + + if (setting.state.equals(target.getExtradata())) { + return setting; + } + + if (fallback == null) { + fallback = setting; } } - for (WiredMatchFurniSetting setting : this.settings) { - if (allowedItemIds != null && !allowedItemIds.contains(setting.item_id)) continue; + return fallback; + } - HabboItem item = room.getHabboItem(setting.item_id); - if (item != null) { - if (this.state && (this.checkForWiredResetPermission && item.allowWiredResetState())) { - if (!setting.state.equals(" ") && !item.getExtradata().equals(setting.state)) { - item.setExtradata(setting.state); - room.updateItemState(item); + private void applySetting(Room room, HabboItem item, WiredMatchFurniSetting setting) { + if (this.state && (this.checkForWiredResetPermission && item.allowWiredResetState())) { + if (!setting.state.equals(" ") && !item.getExtradata().equals(setting.state)) { + item.setExtradata(setting.state); + item.needsUpdate(true); + room.updateItemState(item); + } + } + + RoomTile oldLocation = room.getLayout().getTile(item.getX(), item.getY()); + if (oldLocation == null) return; + double oldZ = item.getZ(); + + if(this.direction && !this.position) { + if(item.getRotation() != setting.rotation && room.furnitureFitsAt(oldLocation, item, setting.rotation, false) == FurnitureMovementError.NONE) { + room.moveFurniTo(item, oldLocation, setting.rotation, null, true); + } + } + else if(this.position) { + boolean slideAnimation = !this.direction || item.getRotation() == setting.rotation; + RoomTile newLocation = room.getLayout().getTile((short) setting.x, (short) setting.y); + int newRotation = this.direction ? setting.rotation : item.getRotation(); + + if(newLocation != null && newLocation.state != RoomTileState.INVALID && (newLocation != oldLocation || newRotation != item.getRotation()) && room.furnitureFitsAt(newLocation, item, newRotation, true) == FurnitureMovementError.NONE) { + if(room.moveFurniTo(item, newLocation, newRotation, null, !slideAnimation) == FurnitureMovementError.NONE) { + if(slideAnimation) { + room.sendComposer(new FloorItemOnRollerComposer(item, null, oldLocation, oldZ, newLocation, item.getZ(), 0, room).compose()); } } - - RoomTile oldLocation = room.getLayout().getTile(item.getX(), item.getY()); - if (oldLocation == null) continue; - double oldZ = item.getZ(); - - if(this.direction && !this.position) { - if(item.getRotation() != setting.rotation && room.furnitureFitsAt(oldLocation, item, setting.rotation, false) == FurnitureMovementError.NONE) { - room.moveFurniTo(item, oldLocation, setting.rotation, null, true); - } - } - else if(this.position) { - boolean slideAnimation = !this.direction || item.getRotation() == setting.rotation; - RoomTile newLocation = room.getLayout().getTile((short) setting.x, (short) setting.y); - int newRotation = this.direction ? setting.rotation : item.getRotation(); - - if(newLocation != null && newLocation.state != RoomTileState.INVALID && (newLocation != oldLocation || newRotation != item.getRotation()) && room.furnitureFitsAt(newLocation, item, newRotation, true) == FurnitureMovementError.NONE) { - if(room.moveFurniTo(item, newLocation, newRotation, null, !slideAnimation) == FurnitureMovementError.NONE) { - if(slideAnimation) { - room.sendComposer(new FloorItemOnRollerComposer(item, null, oldLocation, oldZ, newLocation, item.getZ(), 0, room).compose()); - } - } - } - } - } } } @@ -134,9 +162,6 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int this.settings.clear(); this.settings.addAll(data.items); this.furniSource = data.furniSource; - if (this.furniSource == WiredSourceUtil.SOURCE_TRIGGER && !this.settings.isEmpty()) { - this.furniSource = WiredSourceUtil.SOURCE_SELECTED; - } } else { String[] data = set.getString("wired_data").split(":"); @@ -221,23 +246,22 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int if (room == null) throw new WiredSaveException("Trying to save wired in unloaded room"); + int itemsCount = settings.getFurniIds().length; + + if(itemsCount > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) { + throw new WiredSaveException("Too many furni selected"); + } + List newSettings = new ArrayList<>(); - if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) { - int itemsCount = settings.getFurniIds().length; - if(itemsCount > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) { - throw new WiredSaveException("Too many furni selected"); - } + for (int i = 0; i < itemsCount; i++) { + int itemId = settings.getFurniIds()[i]; + HabboItem it = room.getHabboItem(itemId); - for (int i = 0; i < itemsCount; i++) { - int itemId = settings.getFurniIds()[i]; - HabboItem it = room.getHabboItem(itemId); + if(it == null) + throw new WiredSaveException(String.format("Item %s not found", itemId)); - if(it == null) - throw new WiredSaveException(String.format("Item %s not found", itemId)); - - newSettings.add(new WiredMatchFurniSetting(it.getId(), this.checkForWiredResetPermission && it.allowWiredResetState() ? it.getExtradata() : " ", it.getRotation(), it.getX(), it.getY())); - } + newSettings.add(new WiredMatchFurniSetting(it.getId(), this.checkForWiredResetPermission && it.allowWiredResetState() ? it.getExtradata() : " ", it.getRotation(), it.getX(), it.getY())); } int delay = settings.getDelay(); @@ -249,9 +273,7 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int this.direction = setDirection; this.position = setPosition; this.settings.clear(); - if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) { - this.settings.addAll(newSettings); - } + this.settings.addAll(newSettings); this.setDelay(delay); return true; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMuteHabbo.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMuteHabbo.java index 41acb60e..443cb24c 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMuteHabbo.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMuteHabbo.java @@ -76,7 +76,7 @@ public class WiredEffectMuteHabbo extends InteractionWiredEffect { if (room.hasRights(habbo)) continue; - room.muteHabbo(habbo, 60); + room.muteHabbo(habbo, Math.max(1, this.length)); habbo.getClient().sendResponse(new RoomUserWhisperComposer(new RoomChatMessage(this.message.replace("%user%", habbo.getHabboInfo().getUsername()).replace("%online_count%", Emulator.getGameEnvironment().getHabboManager().getOnlineCount() + "").replace("%room_count%", Emulator.getGameEnvironment().getRoomManager().getActiveRooms().size() + ""), habbo, habbo, RoomChatMessageBubbles.WIRED))); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectRelativeMove.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectRelativeMove.java new file mode 100644 index 00000000..e2fcf249 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectRelativeMove.java @@ -0,0 +1,283 @@ +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.RoomTile; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.WiredEffectType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil; +import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.messages.incoming.wired.WiredSaveException; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class WiredEffectRelativeMove extends InteractionWiredEffect { + private static final int HORIZONTAL_NEGATIVE = 0; + private static final int HORIZONTAL_POSITIVE = 1; + private static final int VERTICAL_NEGATIVE = 0; + private static final int VERTICAL_POSITIVE = 1; + private static final int MAX_DISTANCE = 20; + + public static final WiredEffectType type = WiredEffectType.RELATIVE_MOVE; + + private final List items = new ArrayList<>(); + private int horizontalDirection = HORIZONTAL_POSITIVE; + private int horizontalDistance = 0; + private int verticalDirection = VERTICAL_POSITIVE; + private int verticalDistance = 0; + private int furniSource = WiredSourceUtil.SOURCE_TRIGGER; + + public WiredEffectRelativeMove(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredEffectRelativeMove(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 || room.getLayout() == null) { + return; + } + + List effectiveItems = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.items); + + if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) { + this.items.removeIf(item -> item == null + || item.getRoomId() != this.getRoomId() + || room.getHabboItem(item.getId()) == null); + } + + int deltaX = this.getHorizontalOffset(); + int deltaY = this.getVerticalOffset(); + + if (deltaX == 0 && deltaY == 0) { + return; + } + + for (HabboItem item : effectiveItems) { + if (item == null || item.getRoomId() != this.getRoomId()) { + continue; + } + + short targetX = (short) (item.getX() + deltaX); + short targetY = (short) (item.getY() + deltaY); + + RoomTile targetTile = room.getLayout().getTile(targetX, targetY); + if (targetTile == null) { + continue; + } + + room.moveFurniTo(item, targetTile, item.getRotation(), null, true, false); + } + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson(new JsonData( + this.getDelay(), + this.items.stream().map(HabboItem::getId).collect(Collectors.toList()), + this.horizontalDirection, + this.horizontalDistance, + this.verticalDirection, + this.verticalDistance, + this.furniSource + )); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + this.items.clear(); + String wiredData = set.getString("wired_data"); + + if (wiredData != null && wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + this.setDelay(data.delay); + this.horizontalDirection = this.normalizeBinary(data.horizontalDirection, HORIZONTAL_POSITIVE); + this.horizontalDistance = this.normalizeDistance(data.horizontalDistance); + this.verticalDirection = this.normalizeBinary(data.verticalDirection, VERTICAL_POSITIVE); + this.verticalDistance = this.normalizeDistance(data.verticalDistance); + this.furniSource = data.furniSource; + + if (data.itemIds != null) { + for (Integer id : data.itemIds) { + HabboItem item = room.getHabboItem(id); + + if (item != null) { + this.items.add(item); + } + } + } + + return; + } + + this.horizontalDirection = HORIZONTAL_POSITIVE; + this.horizontalDistance = 0; + this.verticalDirection = VERTICAL_POSITIVE; + this.verticalDistance = 0; + this.furniSource = WiredSourceUtil.SOURCE_TRIGGER; + this.setDelay(0); + } + + @Override + public void onPickUp() { + this.items.clear(); + this.horizontalDirection = HORIZONTAL_POSITIVE; + this.horizontalDistance = 0; + this.verticalDirection = VERTICAL_POSITIVE; + this.verticalDistance = 0; + this.furniSource = WiredSourceUtil.SOURCE_TRIGGER; + this.setDelay(0); + } + + @Override + public WiredEffectType getType() { + return type; + } + + @Override + public void serializeWiredData(ServerMessage message, Room room) { + List itemsSnapshot = new ArrayList<>(this.items); + itemsSnapshot.removeIf(item -> item == null + || item.getRoomId() != this.getRoomId() + || room.getHabboItem(item.getId()) == null); + + this.items.clear(); + this.items.addAll(itemsSnapshot); + + message.appendBoolean(false); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); + message.appendInt(itemsSnapshot.size()); + for (HabboItem item : itemsSnapshot) { + message.appendInt(item.getId()); + } + + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(""); + message.appendInt(5); + message.appendInt(this.horizontalDirection); + message.appendInt(this.horizontalDistance); + message.appendInt(this.verticalDirection); + message.appendInt(this.verticalDistance); + 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 { + int[] params = settings.getIntParams(); + + if (params.length < 5) { + throw new WiredSaveException("Invalid data"); + } + + this.horizontalDirection = this.normalizeBinary(params[0], HORIZONTAL_POSITIVE); + this.horizontalDistance = this.normalizeDistance(params[1]); + this.verticalDirection = this.normalizeBinary(params[2], VERTICAL_POSITIVE); + this.verticalDistance = this.normalizeDistance(params[3]); + this.furniSource = params[4]; + + int delay = settings.getDelay(); + if (delay > Emulator.getConfig().getInt("hotel.wired.max_delay", 20)) { + throw new WiredSaveException("Delay too long"); + } + + Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()); + if (room == null) { + throw new WiredSaveException("Room not found"); + } + + if (settings.getFurniIds().length > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) { + throw new WiredSaveException("Too many furni selected"); + } + + List newItems = new ArrayList<>(); + for (int itemId : settings.getFurniIds()) { + HabboItem item = room.getHabboItem(itemId); + + if (item == null) { + throw new WiredSaveException(String.format("Item %s not found", itemId)); + } + + newItems.add(item); + } + + this.items.clear(); + this.items.addAll(newItems); + this.setDelay(delay); + + return true; + } + + private int getHorizontalOffset() { + if (this.horizontalDistance <= 0) { + return 0; + } + + return (this.horizontalDirection == HORIZONTAL_NEGATIVE) ? -this.horizontalDistance : this.horizontalDistance; + } + + private int getVerticalOffset() { + if (this.verticalDistance <= 0) { + return 0; + } + + return (this.verticalDirection == VERTICAL_NEGATIVE) ? -this.verticalDistance : this.verticalDistance; + } + + private int normalizeBinary(int value, int fallback) { + if (value == 0 || value == 1) { + return value; + } + + return fallback; + } + + private int normalizeDistance(int value) { + return Math.max(0, Math.min(MAX_DISTANCE, value)); + } + + static class JsonData { + int delay; + List itemIds; + int horizontalDirection; + int horizontalDistance; + int verticalDirection; + int verticalDistance; + int furniSource; + + public JsonData(int delay, List itemIds, int horizontalDirection, int horizontalDistance, int verticalDirection, int verticalDistance, int furniSource) { + this.delay = delay; + this.itemIds = itemIds; + this.horizontalDirection = horizontalDirection; + this.horizontalDistance = horizontalDistance; + this.verticalDirection = verticalDirection; + this.verticalDistance = verticalDistance; + this.furniSource = furniSource; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectSendSignal.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectSendSignal.java index 66a4429d..82bdd99c 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectSendSignal.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectSendSignal.java @@ -24,6 +24,7 @@ import org.slf4j.LoggerFactory; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -36,14 +37,16 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { private static final int ANTENNA_PICKED = 0; private static final int ANTENNA_TRIGGER = 1; - - private static final int FORWARD_NONE = 0; - private static final int FORWARD_TRIGGER = 1; + private static final String ANTENNA_INTERACTION = "antenna"; + private static final String FORWARD_ITEM_SPLIT_REGEX = "[;,\\t]"; + private static final long ANTENNA_PULSE_MS = 300L; + private static final ConcurrentHashMap ANTENNA_PULSE_TOKENS = new ConcurrentHashMap<>(); private THashSet items; + private THashSet forwardItems; private int antennaSource = ANTENNA_PICKED; - private int furniForward = FORWARD_NONE; - private int userForward = FORWARD_NONE; + private int furniForward = WiredSourceUtil.SOURCE_TRIGGER; + private int userForward = WiredSourceUtil.SOURCE_TRIGGER; private boolean signalPerFurni = false; private boolean signalPerUser = false; private int channel = 0; @@ -51,11 +54,13 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { public WiredEffectSendSignal(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); this.items = new THashSet<>(); + this.forwardItems = new THashSet<>(); } public WiredEffectSendSignal(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { super(id, userId, item, extradata, limitedStack, limitedSells); this.items = new THashSet<>(); + this.forwardItems = new THashSet<>(); } @Override @@ -77,73 +82,68 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { .map(Collections::singleton) .orElse(Collections.emptySet()); } else { - antennas = ctx.targets().isItemsModifiedBySelector() - ? new ArrayList<>(ctx.targets().items()) - : new ArrayList<>(this.items); + Collection baseAntennas = new ArrayList<>(this.items); + + if (baseAntennas.isEmpty() && antennaSource > ANTENNA_TRIGGER) { + HabboItem antenna = room.getHabboItem(antennaSource); + antennas = (antenna != null) ? Collections.singleton(antenna) : Collections.emptySet(); + } else { + antennas = baseAntennas; + } } - if (antennas.isEmpty()) { + List resolvedAntennas = antennas.stream() + .filter(Objects::nonNull) + .filter(this::isAntennaItem) + .collect(Collectors.toList()); + + if (resolvedAntennas.isEmpty()) { LOGGER.debug("[SendSignal] No antennas resolved, aborting. antennaSource={}, selectorModified={}", antennaSource, ctx.targets().isItemsModifiedBySelector()); return; } - LOGGER.debug("[SendSignal] Resolved {} antenna(s), firing signals", antennas.size()); + LOGGER.debug("[SendSignal] Resolved {} antenna(s), firing signals", resolvedAntennas.size()); - RoomUnit forwardedUser = null; - if (userForward == FORWARD_TRIGGER) { - forwardedUser = ctx.actor().orElse(null); - } + List forwardedUsers = WiredSourceUtil.resolveUsers(ctx, this.userForward); + List forwardedFurni = WiredSourceUtil.resolveItems(ctx, this.furniForward, this.forwardItems); - HabboItem forwardedFurni = null; - if (furniForward == FORWARD_TRIGGER) { - forwardedFurni = ctx.sourceItem().orElse(null); - } + RoomUnit defaultUser = forwardedUsers.isEmpty() ? null : forwardedUsers.get(0); + HabboItem defaultFurni = forwardedFurni.isEmpty() ? null : forwardedFurni.get(0); - Set visitedTiles = new HashSet<>(); - List antennaTiles = new ArrayList<>(); - for (HabboItem antenna : antennas) { - if (antenna == null) continue; - String key = antenna.getX() + "," + antenna.getY(); - if (visitedTiles.add(key)) { - RoomTile tile = room.getLayout().getTile(antenna.getX(), antenna.getY()); - if (tile != null) { - antennaTiles.add(tile); - } - } - } + Collection usersToSend = (signalPerUser && !forwardedUsers.isEmpty()) + ? forwardedUsers + : Collections.singletonList(defaultUser); + + Collection furniToSend = (signalPerFurni && !forwardedFurni.isEmpty()) + ? forwardedFurni + : Collections.singletonList(defaultFurni); int nextDepth = currentDepth + 1; - if (signalPerFurni || signalPerUser) { - if (signalPerFurni) { - for (RoomTile tile : antennaTiles) { - fireSignalAtTile(room, tile, forwardedUser, forwardedFurni, nextDepth); + for (RoomUnit user : usersToSend) { + for (HabboItem sourceItem : furniToSend) { + for (HabboItem antenna : resolvedAntennas) { + fireSignalAtAntenna(room, antenna, user, sourceItem, nextDepth); } } - if (signalPerUser && ctx.targets().hasUsers()) { - for (RoomUnit user : ctx.targets().users()) { - for (RoomTile tile : antennaTiles) { - fireSignalAtTile(room, tile, user, forwardedFurni, nextDepth); - } - } - } else if (!signalPerFurni) { - for (RoomTile tile : antennaTiles) { - fireSignalAtTile(room, tile, forwardedUser, forwardedFurni, nextDepth); - } - } - } else { - for (RoomTile tile : antennaTiles) { - fireSignalAtTile(room, tile, forwardedUser, forwardedFurni, nextDepth); - } } } - private void fireSignalAtTile(Room room, RoomTile tile, RoomUnit actor, HabboItem sourceItem, int depth) { - LOGGER.debug("[SendSignal] fireSignalAtTile: tile={},{} depth={} channel={} actor={} sourceItem={}", tile.x, tile.y, depth, channel, actor != null ? actor.getId() : "null", sourceItem != null ? sourceItem.getId() : "null"); + private void fireSignalAtAntenna(Room room, HabboItem antenna, RoomUnit actor, HabboItem sourceItem, int depth) { + if (antenna == null) return; + RoomTile tile = room.getLayout().getTile(antenna.getX(), antenna.getY()); + if (tile == null) return; + + pulseAntenna(room, antenna); + + int signalChannel = antenna.getId(); + + LOGGER.debug("[SendSignal] fireSignalAtAntenna: antennaId={} tile={},{} depth={} channel={} actor={} sourceItem={}", + signalChannel, tile.x, tile.y, depth, signalChannel, actor != null ? actor.getId() : "null", sourceItem != null ? sourceItem.getId() : "null"); WiredEvent.Builder builder = WiredEvent.builder(WiredEvent.Type.SIGNAL_RECEIVED, room) .tile(tile) .callStackDepth(depth) - .signalChannel(this.channel) + .signalChannel(signalChannel) .triggeredByEffect(true); if (actor != null) builder.actor(actor); @@ -153,6 +153,33 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { LOGGER.debug("[SendSignal] handleEvent returned: {}", result); } + private void pulseAntenna(Room room, HabboItem antenna) { + if (room == null || antenna == null || antenna.getBaseItem() == null) return; + if (antenna.getBaseItem().getStateCount() <= 1) return; + + final long token = System.currentTimeMillis(); + ANTENNA_PULSE_TOKENS.put(antenna.getId(), token); + + if ("1".equals(antenna.getExtradata())) { + antenna.setExtradata("0"); + room.updateItemState(antenna); + } + + antenna.setExtradata("1"); + room.updateItemState(antenna); + + Emulator.getThreading().run(() -> { + if (!room.isLoaded()) return; + + Long currentToken = ANTENNA_PULSE_TOKENS.get(antenna.getId()); + if (currentToken == null || currentToken.longValue() != token) return; + + antenna.setExtradata("0"); + room.updateItemState(antenna); + ANTENNA_PULSE_TOKENS.remove(antenna.getId(), token); + }, ANTENNA_PULSE_MS); + } + @Override public void serializeWiredData(ServerMessage message, Room room) { List itemsSnapshot = new ArrayList<>(this.items); @@ -161,6 +188,16 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { item.getRoomId() != this.getRoomId() || room.getHabboItem(item.getId()) == null); this.items.retainAll(itemsSnapshot); + List forwardSnapshot = new ArrayList<>(this.forwardItems); + forwardSnapshot.removeIf(item -> + item.getRoomId() != this.getRoomId() || room.getHabboItem(item.getId()) == null); + this.forwardItems.retainAll(forwardSnapshot); + + String forwardString = forwardSnapshot.stream() + .filter(Objects::nonNull) + .map(item -> Integer.toString(item.getId())) + .collect(Collectors.joining(";")); + message.appendBoolean(false); message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(itemsSnapshot.size()); @@ -169,7 +206,7 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { } message.appendInt(this.getBaseItem().getSpriteId()); message.appendInt(this.getId()); - message.appendString(""); + message.appendString(forwardString); message.appendInt(6); message.appendInt(antennaSource); @@ -219,6 +256,12 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { newItems.add(it); } + for (HabboItem receiver : newItems) { + if (!isAntennaItem(receiver)) { + throw new WiredSaveException("Only antenna furni can be selected"); + } + } + if (room != null && room.getRoomSpecialTypes() != null) { for (HabboItem receiver : newItems) { int count = room.getRoomSpecialTypes().countSendersTargetingReceiver(receiver.getId(), this); @@ -234,18 +277,36 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { } int[] params = settings.getIntParams(); - this.antennaSource = params.length > 0 ? params[0] : ANTENNA_PICKED; - this.furniForward = params.length > 1 ? params[1] : FORWARD_NONE; - this.userForward = params.length > 2 ? params[2] : FORWARD_NONE; + int requestedAntennaSource = params.length > 0 ? params[0] : ANTENNA_PICKED; + this.furniForward = normalizeSource(params.length > 1 ? params[1] : WiredSourceUtil.SOURCE_TRIGGER); + this.userForward = normalizeSource(params.length > 2 ? params[2] : WiredSourceUtil.SOURCE_TRIGGER); this.signalPerFurni = params.length > 3 && params[3] == 1; this.signalPerUser = params.length > 4 && params[4] == 1; this.channel = params.length > 5 ? params[5] : 0; + this.antennaSource = requestedAntennaSource; + if (!newItems.isEmpty()) { + this.antennaSource = newItems.get(0).getId(); + } + + List newForwardItems = new ArrayList<>(); + if (this.furniForward == WiredSourceUtil.SOURCE_SELECTED && room != null) { + newForwardItems = parseForwardItems(settings.getStringParam(), room); + } + if (newForwardItems.size() > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) { + throw new WiredSaveException("Too many furni selected"); + } + this.items.clear(); this.items.addAll(newItems); + + this.forwardItems.clear(); + if (this.furniForward == WiredSourceUtil.SOURCE_SELECTED) { + this.forwardItems.addAll(newForwardItems); + } this.setDelay(delay); - LOGGER.debug("[SendSignal] saveData: antennaSource={}, furniForward={}, userForward={}, signalPerFurni={}, signalPerUser={}, channel={}, items={}", - antennaSource, furniForward, userForward, signalPerFurni, signalPerUser, channel, items.size()); + LOGGER.debug("[SendSignal] saveData: antennaSource={}, furniForward={}, userForward={}, signalPerFurni={}, signalPerUser={}, channel={}, items={}, forwardItems={}", + antennaSource, furniForward, userForward, signalPerFurni, signalPerUser, channel, items.size(), forwardItems.size()); return true; } @@ -259,9 +320,11 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { @Override public String getWiredData() { List itemsSnapshot = new ArrayList<>(this.items); + List forwardSnapshot = new ArrayList<>(this.forwardItems); return WiredManager.getGson().toJson(new JsonData( this.getDelay(), itemsSnapshot.stream().map(HabboItem::getId).collect(Collectors.toList()), + forwardSnapshot.stream().map(HabboItem::getId).collect(Collectors.toList()), antennaSource, furniForward, userForward, signalPerFurni, signalPerUser, channel )); } @@ -269,14 +332,15 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { @Override public void loadWiredData(ResultSet set, Room room) throws SQLException { this.items = new THashSet<>(); + this.forwardItems = new THashSet<>(); String wiredData = set.getString("wired_data"); if (wiredData != null && wiredData.startsWith("{")) { JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.antennaSource = data.antennaSource; - this.furniForward = data.furniForward; - this.userForward = data.userForward; + this.furniForward = normalizeSource(data.furniForward); + this.userForward = normalizeSource(data.userForward); this.signalPerFurni = data.signalPerFurni; this.signalPerUser = data.signalPerUser; this.channel = data.channel; @@ -286,21 +350,84 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { if (item != null) this.items.add(item); } } + if (data.forwardItemIds != null) { + for (Integer id : data.forwardItemIds) { + HabboItem item = room.getHabboItem(id); + if (item != null) this.forwardItems.add(item); + } + } + + if (this.antennaSource <= ANTENNA_TRIGGER && !this.items.isEmpty()) { + HabboItem first = this.items.iterator().next(); + if (first != null) this.antennaSource = first.getId(); + } } } @Override public void onPickUp() { this.items.clear(); + this.forwardItems.clear(); this.antennaSource = ANTENNA_PICKED; - this.furniForward = FORWARD_NONE; - this.userForward = FORWARD_NONE; + this.furniForward = WiredSourceUtil.SOURCE_TRIGGER; + this.userForward = WiredSourceUtil.SOURCE_TRIGGER; this.signalPerFurni = false; this.signalPerUser = false; this.channel = 0; this.setDelay(0); } + private int normalizeSource(int source) { + if (source == 1) return WiredSourceUtil.SOURCE_TRIGGER; + if (source == WiredSourceUtil.SOURCE_TRIGGER + || source == WiredSourceUtil.SOURCE_SELECTED + || source == WiredSourceUtil.SOURCE_SELECTOR + || source == WiredSourceUtil.SOURCE_SIGNAL) { + return source; + } + return WiredSourceUtil.SOURCE_TRIGGER; + } + + private List parseForwardItems(String data, Room room) throws WiredSaveException { + List results = new ArrayList<>(); + if (data == null || data.trim().isEmpty() || room == null) return results; + + Set seen = new HashSet<>(); + String[] parts = data.split(FORWARD_ITEM_SPLIT_REGEX); + + for (String part : parts) { + if (part == null) continue; + + String trimmed = part.trim(); + if (trimmed.isEmpty()) continue; + + int itemId; + try { + itemId = Integer.parseInt(trimmed); + } catch (NumberFormatException e) { + continue; + } + + if (itemId <= 0 || !seen.add(itemId)) continue; + + HabboItem item = room.getHabboItem(itemId); + if (item == null) throw new WiredSaveException(String.format("Item %s not found", itemId)); + + results.add(item); + } + + return results; + } + + private boolean isAntennaItem(HabboItem item) { + if (item == null || item.getBaseItem() == null || item.getBaseItem().getInteractionType() == null) return false; + String interaction = item.getBaseItem().getInteractionType().getName(); + if (interaction == null) return false; + + String normalized = interaction.toLowerCase(); + return normalized.equals(ANTENNA_INTERACTION); + } + @Override public WiredEffectType getType() { return type; @@ -328,6 +455,7 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { static class JsonData { int delay; List itemIds; + List forwardItemIds; int antennaSource; int furniForward; int userForward; @@ -335,10 +463,11 @@ public class WiredEffectSendSignal extends InteractionWiredEffect { boolean signalPerUser; int channel; - public JsonData(int delay, List itemIds, int antennaSource, int furniForward, + public JsonData(int delay, List itemIds, List forwardItemIds, int antennaSource, int furniForward, int userForward, boolean signalPerFurni, boolean signalPerUser, int channel) { this.delay = delay; this.itemIds = itemIds; + this.forwardItemIds = forwardItemIds; this.antennaSource = antennaSource; this.furniForward = furniForward; this.userForward = userForward; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectSetAltitude.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectSetAltitude.java new file mode 100644 index 00000000..e43a4a68 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectSetAltitude.java @@ -0,0 +1,288 @@ +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.RoomTile; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.WiredEffectType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredSourceUtil; +import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.messages.incoming.wired.WiredSaveException; + +import java.math.BigDecimal; +import java.math.RoundingMode; +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 WiredEffectSetAltitude extends InteractionWiredEffect { + private static final Pattern ALTITUDE_PATTERN = Pattern.compile("^\\d+(\\.\\d{1,2})?$"); + + private static final int OPERATOR_INCREASE = 0; + private static final int OPERATOR_DECREASE = 1; + private static final int OPERATOR_SET = 2; + + public static final WiredEffectType type = WiredEffectType.SET_ALTITUDE; + + private final List items = new ArrayList<>(); + private int operator = OPERATOR_SET; + private double altitude = 0.0D; + private int furniSource = WiredSourceUtil.SOURCE_TRIGGER; + + public WiredEffectSetAltitude(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredEffectSetAltitude(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; + } + + List effectiveItems = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.items); + + if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) { + this.items.removeIf(item -> item == null + || item.getRoomId() != this.getRoomId() + || room.getHabboItem(item.getId()) == null); + } + + for (HabboItem item : effectiveItems) { + if (item == null || item.getRoomId() != this.getRoomId()) { + continue; + } + + RoomTile tile = room.getLayout().getTile(item.getX(), item.getY()); + if (tile == null) { + continue; + } + + double nextAltitude = this.computeAltitude(item.getZ()); + room.moveFurniTo(item, tile, item.getRotation(), nextAltitude, null, true, false); + } + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson(new JsonData( + this.getDelay(), + this.items.stream().map(HabboItem::getId).collect(Collectors.toList()), + this.operator, + this.formatAltitude(this.altitude), + this.furniSource + )); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + this.items.clear(); + String wiredData = set.getString("wired_data"); + + if (wiredData != null && wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + this.setDelay(data.delay); + this.operator = this.normalizeOperator(data.operator); + this.altitude = this.parseAltitudeOrDefault(data.altitude); + this.furniSource = data.furniSource; + + if (data.itemIds != null) { + for (Integer id : data.itemIds) { + HabboItem item = room.getHabboItem(id); + + if (item != null) { + this.items.add(item); + } + } + } + + return; + } + + this.operator = OPERATOR_SET; + this.altitude = 0.0D; + this.furniSource = WiredSourceUtil.SOURCE_TRIGGER; + this.setDelay(0); + } + + @Override + public void onPickUp() { + this.items.clear(); + this.operator = OPERATOR_SET; + this.altitude = 0.0D; + this.furniSource = WiredSourceUtil.SOURCE_TRIGGER; + this.setDelay(0); + } + + @Override + public WiredEffectType getType() { + return type; + } + + @Override + public void serializeWiredData(ServerMessage message, Room room) { + List itemsSnapshot = new ArrayList<>(this.items); + itemsSnapshot.removeIf(item -> item == null + || item.getRoomId() != this.getRoomId() + || room.getHabboItem(item.getId()) == null); + + this.items.clear(); + this.items.addAll(itemsSnapshot); + + message.appendBoolean(false); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); + message.appendInt(itemsSnapshot.size()); + for (HabboItem item : itemsSnapshot) { + message.appendInt(item.getId()); + } + + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(this.formatAltitude(this.altitude)); + message.appendInt(2); + message.appendInt(this.operator); + 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 { + int[] params = settings.getIntParams(); + this.operator = (params.length > 0) ? this.normalizeOperator(params[0]) : OPERATOR_SET; + this.furniSource = (params.length > 1) ? params[1] : WiredSourceUtil.SOURCE_TRIGGER; + + int delay = settings.getDelay(); + if (delay > Emulator.getConfig().getInt("hotel.wired.max_delay", 20)) { + throw new WiredSaveException("Delay too long"); + } + + Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()); + if (room == null) { + throw new WiredSaveException("Room not found"); + } + + if (settings.getFurniIds().length > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) { + throw new WiredSaveException("Too many furni selected"); + } + + List newItems = new ArrayList<>(); + for (int itemId : settings.getFurniIds()) { + HabboItem item = room.getHabboItem(itemId); + + if (item == null) { + throw new WiredSaveException(String.format("Item %s not found", itemId)); + } + + newItems.add(item); + } + + this.altitude = this.parseAltitude(settings.getStringParam()); + this.items.clear(); + this.items.addAll(newItems); + this.setDelay(delay); + + return true; + } + + private int normalizeOperator(int value) { + if (value < OPERATOR_INCREASE || value > OPERATOR_SET) { + return OPERATOR_SET; + } + + return value; + } + + private double computeAltitude(double currentAltitude) { + double nextAltitude; + + switch (this.operator) { + case OPERATOR_INCREASE: + nextAltitude = currentAltitude + this.altitude; + break; + case OPERATOR_DECREASE: + nextAltitude = currentAltitude - this.altitude; + break; + case OPERATOR_SET: + default: + nextAltitude = this.altitude; + break; + } + + return this.normalizeAltitude(nextAltitude); + } + + private double parseAltitude(String value) throws WiredSaveException { + String normalized = (value != null) ? value.trim() : ""; + + if (normalized.isEmpty()) { + return 0.0D; + } + + if (!ALTITUDE_PATTERN.matcher(normalized).matches()) { + throw new WiredSaveException("Invalid altitude value"); + } + + try { + return this.normalizeAltitude(new BigDecimal(normalized).doubleValue()); + } catch (NumberFormatException exception) { + throw new WiredSaveException("Invalid altitude value"); + } + } + + private double parseAltitudeOrDefault(String value) { + try { + return this.parseAltitude(value); + } catch (WiredSaveException exception) { + return 0.0D; + } + } + + private double normalizeAltitude(double value) { + double clampedValue = Math.max(0.0D, Math.min(Room.MAXIMUM_FURNI_HEIGHT, value)); + return BigDecimal.valueOf(clampedValue).setScale(2, RoundingMode.HALF_UP).doubleValue(); + } + + private String formatAltitude(double value) { + BigDecimal decimal = BigDecimal.valueOf(this.normalizeAltitude(value)).stripTrailingZeros(); + return (decimal.scale() < 0 ? decimal.setScale(0, RoundingMode.DOWN) : decimal).toPlainString(); + } + + static class JsonData { + int delay; + List itemIds; + int operator; + String altitude; + int furniSource; + + public JsonData(int delay, List itemIds, int operator, String altitude, int furniSource) { + this.delay = delay; + this.itemIds = itemIds; + this.operator = operator; + this.altitude = altitude; + this.furniSource = furniSource; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleRandom.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleRandom.java index 370655af..8ba458c2 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleRandom.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleRandom.java @@ -197,6 +197,7 @@ public class WiredEffectToggleRandom extends InteractionWiredEffect { try { item.setExtradata(Emulator.getRandom().nextInt(item.getBaseItem().getStateCount() + 1) + ""); + item.needsUpdate(true); room.updateItem(item); } catch (Exception e) { LOGGER.error("Caught exception", e); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUnfreeze.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUnfreeze.java new file mode 100644 index 00000000..40978a4b --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUnfreeze.java @@ -0,0 +1,149 @@ +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.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.wired.WiredEffectType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil; +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.procedure.TObjectProcedure; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +public class WiredEffectUnfreeze extends InteractionWiredEffect { + public static final WiredEffectType type = WiredEffectType.UNFREEZE; + + private int userSource = WiredSourceUtil.SOURCE_TRIGGER; + + public WiredEffectUnfreeze(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredEffectUnfreeze(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(); + + for (RoomUnit roomUnit : WiredSourceUtil.resolveUsers(ctx, this.userSource)) { + if (room.getHabbo(roomUnit) == null || !WiredFreezeUtil.isFrozen(roomUnit)) { + continue; + } + + WiredFreezeUtil.unfreeze(room, roomUnit); + } + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson(new JsonData(this.getDelay(), this.userSource)); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + String wiredData = set.getString("wired_data"); + + if (wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + this.setDelay(data.delay); + this.userSource = data.userSource; + } else { + this.setDelay(0); + this.userSource = WiredSourceUtil.SOURCE_TRIGGER; + } + } + + @Override + public void onPickUp() { + this.setDelay(0); + this.userSource = WiredSourceUtil.SOURCE_TRIGGER; + } + + @Override + public WiredEffectType getType() { + return type; + } + + @Override + public void serializeWiredData(ServerMessage message, Room room) { + message.appendBoolean(false); + message.appendInt(5); + message.appendInt(0); + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(""); + message.appendInt(1); + message.appendInt(this.userSource); + message.appendInt(0); + message.appendInt(this.getType().code); + message.appendInt(this.getDelay()); + + if (this.requiresTriggeringUser()) { + List invalidTriggers = new ArrayList<>(); + room.getRoomSpecialTypes().getTriggers(this.getX(), this.getY()).forEach(new TObjectProcedure() { + @Override + public boolean execute(InteractionWiredTrigger object) { + if (!object.isTriggeredByRoomUnit()) { + invalidTriggers.add(object.getBaseItem().getSpriteId()); + } + return true; + } + }); + message.appendInt(invalidTriggers.size()); + for (Integer i : invalidTriggers) { + message.appendInt(i); + } + } else { + message.appendInt(0); + } + } + + @Override + public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException { + int[] params = settings.getIntParams(); + this.userSource = (params.length > 0) ? params[0] : WiredSourceUtil.SOURCE_TRIGGER; + + int delay = settings.getDelay(); + if (delay > Emulator.getConfig().getInt("hotel.wired.max_delay", 20)) { + throw new WiredSaveException("Delay too long"); + } + + this.setDelay(delay); + return true; + } + + @Override + public boolean requiresTriggeringUser() { + return this.userSource == WiredSourceUtil.SOURCE_TRIGGER; + } + + static class JsonData { + int delay; + int userSource; + + public JsonData(int delay, int userSource) { + this.delay = delay; + this.userSource = userSource; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUserFurniBase.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUserFurniBase.java new file mode 100644 index 00000000..bc86f16d --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUserFurniBase.java @@ -0,0 +1,260 @@ +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.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.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.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.procedure.TObjectProcedure; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public abstract class WiredEffectUserFurniBase extends InteractionWiredEffect { + protected final List items = new ArrayList<>(); + protected int furniSource = WiredSourceUtil.SOURCE_TRIGGER; + protected int userSource = WiredSourceUtil.SOURCE_TRIGGER; + + public WiredEffectUserFurniBase(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredEffectUserFurniBase(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { + super(id, userId, item, extradata, limitedStack, limitedSells); + } + + protected HabboItem resolveLastItem(WiredContext ctx) { + Room room = ctx.room(); + List effectiveItems = WiredSourceUtil.resolveItems(ctx, this.furniSource, this.items); + + if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED && room != null) { + this.items.removeIf(item -> item == null + || item.getRoomId() != this.getRoomId() + || room.getHabboItem(item.getId()) == null); + } + + if (effectiveItems.isEmpty()) { + return null; + } + + for (int index = effectiveItems.size() - 1; index >= 0; index--) { + HabboItem item = effectiveItems.get(index); + + if (item != null) { + return item; + } + } + + return null; + } + + protected Habbo resolveLastHabbo(Room room, WiredContext ctx) { + Habbo targetHabbo = null; + + for (RoomUnit unit : WiredSourceUtil.resolveUsers(ctx, this.userSource)) { + Habbo habbo = room.getHabbo(unit); + + if (habbo != null) { + targetHabbo = habbo; + } + } + + return targetHabbo; + } + + protected List resolveHabbos(Room room, WiredContext ctx) { + List habbos = new ArrayList<>(); + + for (RoomUnit unit : WiredSourceUtil.resolveUsers(ctx, this.userSource)) { + Habbo habbo = room.getHabbo(unit); + + if (habbo != null) { + habbos.add(habbo); + } + } + + return habbos; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson(new JsonData( + this.getDelay(), + this.items.stream().map(HabboItem::getId).collect(Collectors.toList()), + this.furniSource, + this.userSource + )); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + this.items.clear(); + String wiredData = set.getString("wired_data"); + + if (wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + this.setDelay(data.delay); + this.furniSource = data.furniSource; + this.userSource = data.userSource; + + if (data.itemIds != null) { + for (Integer id : data.itemIds) { + HabboItem item = room.getHabboItem(id); + + if (item != null) { + this.items.add(item); + } + } + } + + if (this.furniSource == WiredSourceUtil.SOURCE_TRIGGER && !this.items.isEmpty()) { + this.furniSource = WiredSourceUtil.SOURCE_SELECTED; + } + } else { + String[] wiredDataOld = wiredData.split("\t"); + + if (wiredDataOld.length >= 1) { + this.setDelay(Integer.parseInt(wiredDataOld[0])); + } + + if (wiredDataOld.length == 2 && wiredDataOld[1].contains(";")) { + for (String s : wiredDataOld[1].split(";")) { + HabboItem item = room.getHabboItem(Integer.parseInt(s)); + + if (item != null) { + this.items.add(item); + } + } + } + + this.furniSource = this.items.isEmpty() ? WiredSourceUtil.SOURCE_TRIGGER : WiredSourceUtil.SOURCE_SELECTED; + this.userSource = WiredSourceUtil.SOURCE_TRIGGER; + } + } + + @Override + public void onPickUp() { + this.items.clear(); + this.furniSource = WiredSourceUtil.SOURCE_TRIGGER; + this.userSource = WiredSourceUtil.SOURCE_TRIGGER; + this.setDelay(0); + } + + @Override + public void serializeWiredData(ServerMessage message, Room room) { + List itemsSnapshot = new ArrayList<>(this.items); + itemsSnapshot.removeIf(item -> item == null + || item.getRoomId() != this.getRoomId() + || room.getHabboItem(item.getId()) == null); + this.items.clear(); + this.items.addAll(itemsSnapshot); + + message.appendBoolean(false); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); + message.appendInt(itemsSnapshot.size()); + for (HabboItem item : itemsSnapshot) { + message.appendInt(item.getId()); + } + + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(""); + message.appendInt(2); + message.appendInt(this.furniSource); + message.appendInt(this.userSource); + message.appendInt(0); + message.appendInt(this.getType().code); + message.appendInt(this.getDelay()); + + if (this.requiresTriggeringUser()) { + List invalidTriggers = new ArrayList<>(); + room.getRoomSpecialTypes().getTriggers(this.getX(), this.getY()).forEach(new TObjectProcedure() { + @Override + public boolean execute(InteractionWiredTrigger object) { + if (!object.isTriggeredByRoomUnit()) { + invalidTriggers.add(object.getId()); + } + return true; + } + }); + message.appendInt(invalidTriggers.size()); + for (Integer i : invalidTriggers) { + message.appendInt(i); + } + } else { + message.appendInt(0); + } + } + + @Override + public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException { + this.furniSource = (settings.getIntParams().length > 0) ? settings.getIntParams()[0] : WiredSourceUtil.SOURCE_TRIGGER; + this.userSource = (settings.getIntParams().length > 1) ? settings.getIntParams()[1] : WiredSourceUtil.SOURCE_TRIGGER; + + if (settings.getFurniIds().length > Emulator.getConfig().getInt("hotel.wired.furni.selection.count")) { + throw new WiredSaveException("Too many furni selected"); + } + + Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()); + if (room == null) { + throw new WiredSaveException("Room not found"); + } + + List newItems = new ArrayList<>(); + if (this.furniSource == WiredSourceUtil.SOURCE_SELECTED) { + for (int itemId : settings.getFurniIds()) { + HabboItem item = room.getHabboItem(itemId); + + if (item == null) { + throw new WiredSaveException(String.format("Item %s not found", itemId)); + } + + newItems.add(item); + } + } + + int delay = settings.getDelay(); + if (delay > Emulator.getConfig().getInt("hotel.wired.max_delay", 20)) { + throw new WiredSaveException("Delay too long"); + } + + this.items.clear(); + this.items.addAll(newItems); + this.setDelay(delay); + + return true; + } + + @Override + public boolean requiresTriggeringUser() { + return this.userSource == WiredSourceUtil.SOURCE_TRIGGER; + } + + static class JsonData { + int delay; + List itemIds; + int furniSource; + int userSource; + + public JsonData(int delay, List itemIds, int furniSource, int userSource) { + this.delay = delay; + this.itemIds = itemIds; + this.furniSource = furniSource; + this.userSource = userSource; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUserToFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUserToFurni.java new file mode 100644 index 00000000..b5d50cc3 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectUserToFurni.java @@ -0,0 +1,48 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.effects; + +import com.eu.habbo.habbohotel.items.Item; +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.habbohotel.wired.WiredEffectType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class WiredEffectUserToFurni extends WiredEffectUserFurniBase { + public static final WiredEffectType type = WiredEffectType.USER_TO_FURNI; + + public WiredEffectUserToFurni(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredEffectUserToFurni(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(); + HabboItem item = this.resolveLastItem(ctx); + + if (room == null || item == null) { + return; + } + + for (Habbo habbo : this.resolveHabbos(room, ctx)) { + room.teleportHabboToItem(habbo, item); + } + } + + @Deprecated + @Override + public boolean execute(com.eu.habbo.habbohotel.rooms.RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public WiredEffectType getType() { + return type; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniByType.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniByType.java index 5edfed63..14bae162 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniByType.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/selector/WiredEffectFurniByType.java @@ -112,17 +112,17 @@ public class WiredEffectFurniByType extends InteractionWiredEffect { @Override public boolean saveData(WiredSettings settings, GameClient gameClient) throws WiredSaveException { int[] params = settings.getIntParams(); - if (params == null || params.length < 1) { - throw new WiredSaveException("wf_slc_furni_bytype: intParams must have at least 1 element"); + if (params == null || params.length < 4) { + throw new WiredSaveException("wf_slc_furni_bytype: intParams must have at least 4 elements"); } - this.sourceType = params[0]; + this.sourceType = SOURCE_FURNI_PICKED; this.matchState = params.length > 1 && params[1] == 1; this.filterExisting = params.length > 2 && params[2] == 1; this.invert = params.length > 3 && params[3] == 1; this.pickedFurniIds = new ArrayList<>(); - if (this.sourceType == SOURCE_FURNI_PICKED && settings.getFurniIds() != null) { + if (settings.getFurniIds() != null) { for (int id : settings.getFurniIds()) { if (pickedFurniIds.size() >= MAX_PICKED_FURNI) break; pickedFurniIds.add(id); @@ -135,12 +135,10 @@ public class WiredEffectFurniByType extends InteractionWiredEffect { @Override public void serializeWiredData(ServerMessage message, Room room) { - boolean pickMode = (sourceType == SOURCE_FURNI_PICKED); + message.appendBoolean(true); + message.appendInt(MAX_PICKED_FURNI); - message.appendBoolean(pickMode); - message.appendInt(pickMode ? MAX_PICKED_FURNI : 0); - - if (pickMode && !pickedFurniIds.isEmpty()) { + if (!pickedFurniIds.isEmpty()) { message.appendInt(pickedFurniIds.size()); pickedFurniIds.forEach(message::appendInt); } else { @@ -152,7 +150,7 @@ public class WiredEffectFurniByType extends InteractionWiredEffect { message.appendString(""); message.appendInt(4); - message.appendInt(sourceType); + message.appendInt(SOURCE_FURNI_PICKED); message.appendInt(matchState ? 1 : 0); message.appendInt(filterExisting ? 1 : 0); message.appendInt(invert ? 1 : 0); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerFurniStateToggled.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerFurniStateToggled.java index 4f4a82f4..6c15531f 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerFurniStateToggled.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerFurniStateToggled.java @@ -15,36 +15,48 @@ 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.stream.Collectors; public class WiredTriggerFurniStateToggled extends InteractionWiredTrigger { private static final WiredTriggerType type = WiredTriggerType.STATE_CHANGED; + private static final int MODE_ALL_STATES = 0; + private static final int MODE_SAVED_STATE = 1; - private THashSet items; + private THashSet snapshots; + private int triggerMode = MODE_ALL_STATES; public WiredTriggerFurniStateToggled(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); - this.items = new THashSet<>(); + this.snapshots = new THashSet<>(); } public WiredTriggerFurniStateToggled(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { super(id, userId, item, extradata, limitedStack, limitedSells); - this.items = new THashSet<>(); + this.snapshots = new THashSet<>(); } @Override public boolean matches(HabboItem triggerItem, WiredEvent event) { - // Reject if this was triggered by a wired effect (to prevent loops) if (event.isTriggeredByEffect()) { return false; } HabboItem sourceItem = event.getSourceItem().orElse(null); - if (sourceItem != null) { - return this.items.contains(sourceItem); + if (sourceItem == null) { + return false; } - return false; + + StateSnapshot snapshot = this.getSnapshot(sourceItem.getId()); + if (snapshot == null) { + return false; + } + + if (this.triggerMode == MODE_SAVED_STATE) { + return snapshot.state.equals(this.normalizeState(sourceItem.getExtradata())); + } + + return true; } @Deprecated @@ -56,21 +68,36 @@ public class WiredTriggerFurniStateToggled extends InteractionWiredTrigger { @Override public String getWiredData() { return WiredManager.getGson().toJson(new JsonData( - this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) + this.triggerMode, + new ArrayList<>(this.snapshots) )); } @Override public void loadWiredData(ResultSet set, Room room) throws SQLException { - this.items = new THashSet<>(); + this.snapshots = new THashSet<>(); + this.triggerMode = MODE_ALL_STATES; String wiredData = set.getString("wired_data"); - if (wiredData.startsWith("{")) { + if (wiredData != null && wiredData.startsWith("{")) { JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); - for (Integer id: data.itemIds) { - HabboItem item = room.getHabboItem(id); - if (item != null) { - this.items.add(item); + this.triggerMode = (data != null) ? data.triggerMode : MODE_ALL_STATES; + + if (data != null && data.snapshots != null && !data.snapshots.isEmpty()) { + for (StateSnapshot snapshot : data.snapshots) { + if (snapshot == null) continue; + + HabboItem item = room.getHabboItem(snapshot.itemId); + if (item != null) { + this.snapshots.add(new StateSnapshot(item.getId(), snapshot.state)); + } + } + } else if (data != null && data.itemIds != null) { + for (Integer id : data.itemIds) { + HabboItem item = room.getHabboItem(id); + if (item != null) { + this.snapshots.add(this.captureSnapshot(item)); + } } } } else { @@ -79,10 +106,15 @@ public class WiredTriggerFurniStateToggled extends InteractionWiredTrigger { if (!wiredData.split(":")[2].equals("\t")) { for (String s : wiredData.split(":")[2].split(";")) { + if (s.isEmpty()) { + continue; + } + HabboItem item = room.getHabboItem(Integer.parseInt(s)); - if (item != null) - this.items.add(item); + if (item != null) { + this.snapshots.add(this.captureSnapshot(item)); + } } } } @@ -91,7 +123,8 @@ public class WiredTriggerFurniStateToggled extends InteractionWiredTrigger { @Override public void onPickUp() { - this.items.clear(); + this.snapshots.clear(); + this.triggerMode = MODE_ALL_STATES; } @Override @@ -101,33 +134,31 @@ public class WiredTriggerFurniStateToggled extends InteractionWiredTrigger { @Override public void serializeWiredData(ServerMessage message, Room room) { - THashSet items = new THashSet<>(); + THashSet snapshotsToRemove = new THashSet<>(); - for (HabboItem item : this.items) { - if (item.getRoomId() != this.getRoomId()) { - items.add(item); + for (StateSnapshot snapshot : this.snapshots) { + HabboItem item = room.getHabboItem(snapshot.itemId); + if (item == null || item.getRoomId() != this.getRoomId()) { + snapshotsToRemove.add(snapshot); continue; } - - if (room.getHabboItem(item.getId()) == null) { - items.add(item); - } } - for (HabboItem item : items) { - this.items.remove(item); + for (StateSnapshot snapshot : snapshotsToRemove) { + this.snapshots.remove(snapshot); } message.appendBoolean(false); message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); - message.appendInt(this.items.size()); - for (HabboItem item : this.items) { - message.appendInt(item.getId()); + message.appendInt(this.snapshots.size()); + for (StateSnapshot snapshot : this.snapshots) { + message.appendInt(snapshot.itemId); } message.appendInt(this.getBaseItem().getSpriteId()); message.appendInt(this.getId()); message.appendString(""); - message.appendInt(0); + message.appendInt(1); + message.appendInt(this.triggerMode); message.appendInt(0); message.appendInt(this.getType().code); message.appendInt(0); @@ -135,14 +166,22 @@ public class WiredTriggerFurniStateToggled extends InteractionWiredTrigger { @Override public boolean saveData(WiredSettings settings) { - this.items.clear(); + this.snapshots.clear(); + this.triggerMode = (settings.getIntParams().length > 0 && settings.getIntParams()[0] == MODE_SAVED_STATE) + ? MODE_SAVED_STATE + : MODE_ALL_STATES; + + Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()); + if (room == null) { + return true; + } int count = settings.getFurniIds().length; for (int i = 0; i < count; i++) { - HabboItem item = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(settings.getFurniIds()[i]); + HabboItem item = room.getHabboItem(settings.getFurniIds()[i]); if (item != null) { - this.items.add(item); + this.snapshots.add(this.captureSnapshot(item)); } } @@ -154,11 +193,71 @@ public class WiredTriggerFurniStateToggled extends InteractionWiredTrigger { return true; } + private StateSnapshot captureSnapshot(HabboItem item) { + return new StateSnapshot(item.getId(), this.normalizeState(item.getExtradata())); + } + + private StateSnapshot getSnapshot(int itemId) { + for (StateSnapshot snapshot : this.snapshots) { + if (snapshot.itemId == itemId) { + return snapshot; + } + } + + return null; + } + + private String normalizeState(String state) { + return (state == null) ? "" : state; + } + static class JsonData { + int triggerMode; + List snapshots; List itemIds; + public JsonData() { + } + public JsonData(List itemIds) { this.itemIds = itemIds; } + + public JsonData(int triggerMode, List snapshots) { + this.triggerMode = triggerMode; + this.snapshots = snapshots; + } + } + + static class StateSnapshot { + int itemId; + String state; + + public StateSnapshot() { + } + + public StateSnapshot(int itemId, String state) { + this.itemId = itemId; + this.state = (state == null) ? "" : state; + } + + @Override + public int hashCode() { + return Integer.hashCode(this.itemId); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + + if (!(object instanceof StateSnapshot)) { + return false; + } + + StateSnapshot that = (StateSnapshot) object; + return this.itemId == that.itemId; + } } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboClicksFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboClicksFurni.java new file mode 100644 index 00000000..9bf25e62 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboClicksFurni.java @@ -0,0 +1,164 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.triggers; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; +import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.messages.ServerMessage; +import gnu.trove.set.hash.THashSet; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import java.util.stream.Collectors; + +public class WiredTriggerHabboClicksFurni extends InteractionWiredTrigger { + public static final WiredTriggerType type = WiredTriggerType.CLICKS_FURNI; + + private THashSet items; + + public WiredTriggerHabboClicksFurni(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + this.items = new THashSet<>(); + } + + public WiredTriggerHabboClicksFurni(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { + super(id, userId, item, extradata, limitedStack, limitedSells); + this.items = new THashSet<>(); + } + + @Override + public boolean matches(HabboItem triggerItem, WiredEvent event) { + HabboItem sourceItem = event.getSourceItem().orElse(null); + return sourceItem != null && this.items.contains(sourceItem); + } + + @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) { + THashSet items = new THashSet<>(); + + if (Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()) == null) { + items.addAll(this.items); + } else { + for (HabboItem item : this.items) { + if (Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) { + items.add(item); + } + } + } + + for (HabboItem item : items) { + this.items.remove(item); + } + + message.appendBoolean(false); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); + message.appendInt(this.items.size()); + for (HabboItem item : this.items) { + message.appendInt(item.getId()); + } + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(""); + message.appendInt(0); + message.appendInt(0); + message.appendInt(this.getType().code); + message.appendInt(0); + message.appendInt(0); + } + + @Override + public boolean saveData(WiredSettings settings) { + this.items.clear(); + + int count = settings.getFurniIds().length; + + for (int i = 0; i < count; i++) { + HabboItem item = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(settings.getFurniIds()[i]); + if (item != null) { + this.items.add(item); + } + } + + return true; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson(new JsonData( + this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) + )); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + this.items.clear(); + String wiredData = set.getString("wired_data"); + + if (wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + for (Integer id : data.itemIds) { + HabboItem item = room.getHabboItem(id); + if (item != null) { + this.items.add(item); + } + } + } else { + if (wiredData.split(":").length >= 3) { + super.setDelay(Integer.parseInt(wiredData.split(":")[0])); + + if (!wiredData.split(":")[2].equals("\t")) { + for (String s : wiredData.split(":")[2].split(";")) { + if (s.isEmpty()) { + continue; + } + + try { + HabboItem item = room.getHabboItem(Integer.parseInt(s)); + + if (item != null) { + this.items.add(item); + } + } catch (Exception e) { + } + } + } + } + } + } + + @Override + public void onPickUp() { + this.items.clear(); + } + + @Override + public boolean isTriggeredByRoomUnit() { + return true; + } + + static class JsonData { + List itemIds; + + public JsonData(List itemIds) { + this.itemIds = itemIds; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboClicksTile.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboClicksTile.java new file mode 100644 index 00000000..6ddcc503 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboClicksTile.java @@ -0,0 +1,47 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.triggers; + +import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class WiredTriggerHabboClicksTile extends WiredTriggerHabboClicksFurni { + public static final WiredTriggerType type = WiredTriggerType.CLICKS_TILE; + + private static final String CLICK_TILE_INTERACTION = "room_invisible_click_tile"; + + public WiredTriggerHabboClicksTile(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredTriggerHabboClicksTile(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 (!super.matches(triggerItem, event)) { + return false; + } + + HabboItem sourceItem = event.getSourceItem().orElse(null); + return isClickTileItem(sourceItem); + } + + @Override + public WiredTriggerType getType() { + return type; + } + + private boolean isClickTileItem(HabboItem item) { + if (item == null || item.getBaseItem() == null || item.getBaseItem().getInteractionType() == null) { + return false; + } + + String interaction = item.getBaseItem().getInteractionType().getName(); + return interaction != null && interaction.equalsIgnoreCase(CLICK_TILE_INTERACTION); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboClicksUser.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboClicksUser.java new file mode 100644 index 00000000..f64ca1c0 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboClicksUser.java @@ -0,0 +1,80 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.triggers; + +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.users.HabboItem; +import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; +import com.eu.habbo.messages.ServerMessage; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class WiredTriggerHabboClicksUser extends InteractionWiredTrigger { + public static final WiredTriggerType type = WiredTriggerType.CLICKS_USER; + + public WiredTriggerHabboClicksUser(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredTriggerHabboClicksUser(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) { + return event.getActor().isPresent(); + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public String getWiredData() { + return ""; + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + } + + @Override + public void onPickUp() { + } + + @Override + public WiredTriggerType getType() { + return type; + } + + @Override + public void serializeWiredData(ServerMessage message, Room room) { + message.appendBoolean(false); + message.appendInt(5); + message.appendInt(0); + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(""); + message.appendInt(0); + message.appendInt(0); + message.appendInt(this.getType().code); + message.appendInt(0); + message.appendInt(0); + } + + @Override + public boolean saveData(WiredSettings settings) { + return true; + } + + @Override + public boolean isTriggeredByRoomUnit() { + return true; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboLeavesRoom.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboLeavesRoom.java new file mode 100644 index 00000000..36583ce3 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboLeavesRoom.java @@ -0,0 +1,116 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.triggers; + +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.users.Habbo; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; +import com.eu.habbo.messages.ServerMessage; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class WiredTriggerHabboLeavesRoom extends InteractionWiredTrigger { + public static final WiredTriggerType type = WiredTriggerType.LEAVE_ROOM; + + private String username = ""; + + public WiredTriggerHabboLeavesRoom(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredTriggerHabboLeavesRoom(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) { + RoomUnit roomUnit = event.getActor().orElse(null); + Room room = event.getRoom(); + Habbo habbo = room.getHabbo(roomUnit); + + if (habbo != null) { + if (this.username.length() > 0) { + return habbo.getHabboInfo().getUsername().equalsIgnoreCase(this.username); + } + + return true; + } + return false; + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson(new JsonData( + this.username + )); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + String wiredData = set.getString("wired_data"); + + if (wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + this.username = data.username; + } else { + this.username = wiredData; + } + } + + @Override + public void onPickUp() { + this.username = ""; + } + + @Override + public WiredTriggerType getType() { + return type; + } + + @Override + public void serializeWiredData(ServerMessage message, Room room) { + message.appendBoolean(false); + message.appendInt(5); + message.appendInt(0); + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(this.username); + message.appendInt(0); + message.appendInt(0); + message.appendInt(this.getType().code); + message.appendInt(0); + message.appendInt(0); + } + + @Override + public boolean saveData(WiredSettings settings) { + this.username = settings.getStringParam(); + + return true; + } + + @Override + public boolean isTriggeredByRoomUnit() { + return true; + } + + static class JsonData { + String username; + + public JsonData(String username) { + this.username = username; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboPerformsAction.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboPerformsAction.java new file mode 100644 index 00000000..07b2195d --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboPerformsAction.java @@ -0,0 +1,217 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.triggers; + +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.users.HabboItem; +import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.WiredUserActionType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.messages.ServerMessage; + +import java.sql.ResultSet; +import java.sql.SQLException; + +public class WiredTriggerHabboPerformsAction extends InteractionWiredTrigger { + private static final WiredTriggerType type = WiredTriggerType.USER_PERFORMS_ACTION; + private static final int DEFAULT_ACTION = WiredUserActionType.WAVE; + + private int selectedAction = DEFAULT_ACTION; + private boolean signFilterEnabled = false; + private int signId = 0; + private boolean danceFilterEnabled = false; + private int danceId = 1; + + public WiredTriggerHabboPerformsAction(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredTriggerHabboPerformsAction(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.getActor().isPresent()) { + return false; + } + + if (event.getActionId() != this.selectedAction) { + return false; + } + + if (this.selectedAction == WiredUserActionType.SIGN && this.signFilterEnabled) { + return event.getActionParameter() == this.signId; + } + + if (this.selectedAction == WiredUserActionType.DANCE && this.danceFilterEnabled) { + return event.getActionParameter() == this.danceId; + } + + return true; + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public String getWiredData() { + return WiredManager.getGson().toJson(new JsonData( + this.selectedAction, + this.signFilterEnabled, + this.signId, + this.danceFilterEnabled, + this.danceId + )); + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + this.resetSettings(); + + String wiredData = set.getString("wired_data"); + + if (wiredData != null && wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + + if (data == null) { + return; + } + + this.selectedAction = normalizeAction(data.selectedAction); + this.signFilterEnabled = data.signFilterEnabled; + this.signId = normalizeSignId(data.signId); + this.danceFilterEnabled = data.danceFilterEnabled; + this.danceId = normalizeDanceId(data.danceId); + } + } + + @Override + public void onPickUp() { + this.resetSettings(); + } + + @Override + public WiredTriggerType getType() { + return type; + } + + @Override + public void serializeWiredData(ServerMessage message, Room room) { + message.appendBoolean(false); + message.appendInt(5); + message.appendInt(0); + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(""); + message.appendInt(5); + message.appendInt(this.selectedAction); + message.appendInt(this.signFilterEnabled ? 1 : 0); + message.appendInt(this.signId); + message.appendInt(this.danceFilterEnabled ? 1 : 0); + message.appendInt(this.danceId); + message.appendInt(0); + message.appendInt(this.getType().code); + message.appendInt(0); + message.appendInt(0); + } + + @Override + public boolean saveData(WiredSettings settings) { + int[] intParams = settings.getIntParams(); + + this.resetSettings(); + + if (intParams.length > 0) { + this.selectedAction = normalizeAction(intParams[0]); + } + + if (intParams.length > 1) { + this.signFilterEnabled = (intParams[1] == 1); + } + + if (intParams.length > 2) { + this.signId = normalizeSignId(intParams[2]); + } + + if (intParams.length > 3) { + this.danceFilterEnabled = (intParams[3] == 1); + } + + if (intParams.length > 4) { + this.danceId = normalizeDanceId(intParams[4]); + } + + return true; + } + + @Override + public boolean isTriggeredByRoomUnit() { + return true; + } + + private void resetSettings() { + this.selectedAction = DEFAULT_ACTION; + this.signFilterEnabled = false; + this.signId = 0; + this.danceFilterEnabled = false; + this.danceId = 1; + } + + private int normalizeAction(int action) { + switch (action) { + case WiredUserActionType.WAVE: + case WiredUserActionType.BLOW_KISS: + case WiredUserActionType.LAUGH: + case WiredUserActionType.AWAKE: + case WiredUserActionType.RELAX: + case WiredUserActionType.SIT: + case WiredUserActionType.STAND: + case WiredUserActionType.LAY: + case WiredUserActionType.SIGN: + case WiredUserActionType.DANCE: + case WiredUserActionType.THUMB_UP: + return action; + default: + return DEFAULT_ACTION; + } + } + + private int normalizeSignId(int signId) { + if (signId < 0 || signId > 17) { + return 0; + } + + return signId; + } + + private int normalizeDanceId(int danceId) { + if (danceId < 1 || danceId > 4) { + return 1; + } + + return danceId; + } + + static class JsonData { + int selectedAction; + boolean signFilterEnabled; + int signId; + boolean danceFilterEnabled; + int danceId; + + public JsonData(int selectedAction, boolean signFilterEnabled, int signId, boolean danceFilterEnabled, int danceId) { + this.selectedAction = selectedAction; + this.signFilterEnabled = signFilterEnabled; + this.signId = signId; + this.danceFilterEnabled = danceFilterEnabled; + this.danceId = danceId; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerReceiveSignal.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerReceiveSignal.java index c2e96e93..13463c7b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerReceiveSignal.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerReceiveSignal.java @@ -1,5 +1,6 @@ package com.eu.habbo.habbohotel.items.interactions.wired.triggers; +import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; @@ -11,27 +12,49 @@ import com.eu.habbo.habbohotel.wired.WiredTriggerType; import com.eu.habbo.habbohotel.wired.core.WiredEvent; import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.messages.incoming.wired.WiredTriggerSaveException; +import com.eu.habbo.messages.outgoing.rooms.items.ItemStateComposer; +import gnu.trove.set.hash.THashSet; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; public class WiredTriggerReceiveSignal extends InteractionWiredTrigger { public static final WiredTriggerType type = WiredTriggerType.RECEIVE_SIGNAL; + private static final String ANTENNA_INTERACTION = "antenna"; + private static final long ACTIVATION_PULSE_MS = 300L; + private int channel = 0; // signal channel (0-based) + private THashSet items; + private final AtomicLong activationToken = new AtomicLong(); public WiredTriggerReceiveSignal(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); + this.items = new THashSet<>(); } public WiredTriggerReceiveSignal(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { super(id, userId, item, extradata, limitedStack, limitedSells); + this.items = new THashSet<>(); } @Override public boolean matches(HabboItem triggerItem, WiredEvent event) { - return event.getType() == WiredEvent.Type.SIGNAL_RECEIVED - && event.getSignalChannel() == this.channel; + if (event.getType() != WiredEvent.Type.SIGNAL_RECEIVED) return false; + + if (!this.items.isEmpty()) { + int signalChannel = event.getSignalChannel(); + for (HabboItem antenna : this.items) { + if (antenna != null && antenna.getId() == signalChannel) return true; + } + return false; + } + + return event.getSignalChannel() == this.channel; } public int getChannel() { @@ -59,14 +82,33 @@ public class WiredTriggerReceiveSignal extends InteractionWiredTrigger { int senderCount = 0; try { if (room != null && room.getRoomSpecialTypes() != null) { - senderCount = room.getRoomSpecialTypes().countSendersTargetingReceiver(this.getId()); + if (!this.items.isEmpty()) { + for (HabboItem item : this.items) { + senderCount += room.getRoomSpecialTypes().countSendersTargetingReceiver(item.getId()); + } + } else { + senderCount = room.getRoomSpecialTypes().countSendersTargetingReceiver(this.getId()); + } } } catch (Exception e) { } + THashSet itemsToRemove = new THashSet<>(); + for (HabboItem item : this.items) { + if (item.getRoomId() != this.getRoomId() || room.getHabboItem(item.getId()) == null) { + itemsToRemove.add(item); + } + } + for (HabboItem item : itemsToRemove) { + this.items.remove(item); + } + message.appendBoolean(false); - message.appendInt(0); - message.appendInt(0); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); + message.appendInt(this.items.size()); + for (HabboItem item : this.items) { + message.appendInt(item.getId()); + } message.appendInt(this.getBaseItem().getSpriteId()); message.appendInt(this.getId()); message.appendString(""); @@ -82,37 +124,100 @@ public class WiredTriggerReceiveSignal extends InteractionWiredTrigger { @Override public boolean saveData(WiredSettings settings) { + this.items.clear(); + + Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()); + int count = settings.getFurniIds().length; + + for (int i = 0; i < count; i++) { + HabboItem item = room.getHabboItem(settings.getFurniIds()[i]); + if (item == null) continue; + if (!isAntennaItem(item)) throw new WiredTriggerSaveException("wiredfurni.error.require_antenna_furni"); + this.items.add(item); + } + int[] params = settings.getIntParams(); this.channel = params.length > 0 ? params[0] : 0; return true; } + @Override + public void activateBox(Room room, RoomUnit roomUnit, long millis) { + if (roomUnit != null) { + this.addUserExecutionCache(roomUnit.getId(), millis); + } + + if (room == null || room.isHideWired() || this.getBaseItem().getStateCount() <= 1) { + return; + } + + final long token = this.activationToken.incrementAndGet(); + + if ("1".equals(this.getExtradata())) { + this.setExtradata("0"); + room.sendComposer(new ItemStateComposer(this).compose()); + } + + this.setExtradata("1"); + room.sendComposer(new ItemStateComposer(this).compose()); + + Emulator.getThreading().run(() -> { + if (!room.isLoaded()) return; + if (this.activationToken.get() != token) return; + + this.setExtradata("0"); + room.sendComposer(new ItemStateComposer(this).compose()); + }, ACTIVATION_PULSE_MS); + } + @Override public String getWiredData() { - return WiredManager.getGson().toJson(new JsonData(channel)); + return WiredManager.getGson().toJson(new JsonData( + channel, + this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) + )); } @Override public void loadWiredData(ResultSet set, Room room) throws SQLException { + this.items = new THashSet<>(); String wiredData = set.getString("wired_data"); if (wiredData != null && wiredData.startsWith("{")) { JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.channel = data.channel; + if (data.itemIds != null) { + for (Integer id : data.itemIds) { + HabboItem item = room.getHabboItem(id); + if (item != null) this.items.add(item); + } + } } } @Override public void onPickUp() { this.channel = 0; + this.items.clear(); + } + + private boolean isAntennaItem(HabboItem item) { + if (item == null || item.getBaseItem() == null || item.getBaseItem().getInteractionType() == null) return false; + String interaction = item.getBaseItem().getInteractionType().getName(); + if (interaction == null) return false; + + String normalized = interaction.toLowerCase(); + return normalized.equals(ANTENNA_INTERACTION); } static class JsonData { int channel; + List itemIds; public JsonData() {} - public JsonData(int channel) { + public JsonData(int channel, List itemIds) { this.channel = channel; + this.itemIds = itemIds; } } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java index f9094c09..dadac6be 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java @@ -139,7 +139,7 @@ public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements // Fire when elapsed time is a multiple of repeat time if (elapsedMs % this.repeatTime == 0) { if (this.getRoomId() != 0 && room.isLoaded()) { - WiredManager.triggerTimerRepeat(room, this); + WiredManager.triggerTimerRepeatLong(room, this); } } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterShort.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterShort.java new file mode 100644 index 00000000..00800e67 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterShort.java @@ -0,0 +1,125 @@ +package com.eu.habbo.habbohotel.items.interactions.wired.triggers; + +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.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.messages.ServerMessage; +import gnu.trove.procedure.TObjectProcedure; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +public class WiredTriggerRepeaterShort extends WiredTriggerRepeater { + public static final WiredTriggerType type = WiredTriggerType.PERIODICALLY_SHORT; + public static final int STEP_MS = 50; + public static final int DEFAULT_DELAY = 10 * STEP_MS; + public static final int MIN_DELAY = STEP_MS; + public static final int MAX_DELAY = 10 * STEP_MS; + + public WiredTriggerRepeaterShort(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + this.repeatTime = DEFAULT_DELAY; + } + + public WiredTriggerRepeaterShort(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { + super(id, userId, item, extradata, limitedStack, limitedSells); + this.repeatTime = DEFAULT_DELAY; + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + String wiredData = set.getString("wired_data"); + + if (wiredData != null && wiredData.startsWith("{")) { + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); + this.repeatTime = (data != null) ? data.repeatTime : DEFAULT_DELAY; + } else if (wiredData != null && wiredData.length() >= 1) { + this.repeatTime = Integer.parseInt(wiredData); + } else { + this.repeatTime = DEFAULT_DELAY; + } + + this.repeatTime = clampRepeatTime(this.repeatTime); + } + + @Override + public void onPickUp() { + this.repeatTime = DEFAULT_DELAY; + } + + @Override + public WiredTriggerType getType() { + return type; + } + + @Override + public void serializeWiredData(ServerMessage message, Room room) { + message.appendBoolean(false); + message.appendInt(5); + message.appendInt(0); + message.appendInt(this.getBaseItem().getSpriteId()); + message.appendInt(this.getId()); + message.appendString(""); + message.appendInt(1); + message.appendInt(this.repeatTime / STEP_MS); + message.appendInt(0); + message.appendInt(this.getType().code); + + if (!this.isTriggeredByRoomUnit()) { + List invalidTriggers = new ArrayList<>(); + room.getRoomSpecialTypes().getEffects(this.getX(), this.getY()).forEach(new TObjectProcedure() { + @Override + public boolean execute(InteractionWiredEffect object) { + if (object.requiresTriggeringUser()) { + invalidTriggers.add(object.getBaseItem().getSpriteId()); + } + return true; + } + }); + message.appendInt(invalidTriggers.size()); + for (Integer i : invalidTriggers) { + message.appendInt(i); + } + } else { + message.appendInt(0); + } + } + + @Override + public boolean saveData(WiredSettings settings) { + if (settings.getIntParams().length < 1) return false; + + int newRepeatTime = settings.getIntParams()[0] * STEP_MS; + this.repeatTime = clampRepeatTime(newRepeatTime); + + return true; + } + + @Override + public void onWiredTick(Room room, long tickCount, int tickIntervalMs) { + long elapsedMs = tickCount * tickIntervalMs; + + if (elapsedMs % this.repeatTime == 0) { + if (this.getRoomId() != 0 && room.isLoaded()) { + WiredManager.triggerTimerRepeatShort(room, this); + } + } + } + + private int clampRepeatTime(int repeatTime) { + if (repeatTime < MIN_DELAY) { + return DEFAULT_DELAY; + } + + if (repeatTime > MAX_DELAY) { + return MAX_DELAY; + } + + return repeatTime; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java index 01e81ce6..323abce6 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java @@ -15,6 +15,8 @@ import com.eu.habbo.habbohotel.pets.PetManager; 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.WiredUserActionType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ISerialize; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.outgoing.guilds.GuildInfoComposer; @@ -1874,11 +1876,15 @@ public class Room implements Comparable, ISerialize, Runnable { } public void muteHabbo(Habbo habbo, int minutes) { - this.rightsManager.muteHabbo(habbo, minutes); + this.chatManager.muteHabbo(habbo, minutes); + } + + public void unmuteHabbo(Habbo habbo) { + this.chatManager.unmuteHabbo(habbo); } public boolean isMuted(Habbo habbo) { - return this.rightsManager.isMuted(habbo); + return this.chatManager.isMuted(habbo); } public void habboEntered(Habbo habbo) { @@ -2162,6 +2168,7 @@ public class Room implements Comparable, ISerialize, Runnable { - habbo.getRoomUnit().getBodyRotation().getValue() % 2]); habbo.getRoomUnit().setStatus(RoomUnitStatus.SIT, 0.5 + ""); this.sendComposer(new RoomUserStatusComposer(habbo.getRoomUnit()).compose()); + WiredManager.triggerUserPerformsAction(this, habbo.getRoomUnit(), WiredUserActionType.SIT, -1); } public void makeStand(Habbo habbo) { @@ -2171,12 +2178,19 @@ public class Room implements Comparable, ISerialize, Runnable { HabboItem item = this.getTopItemAt(habbo.getRoomUnit().getX(), habbo.getRoomUnit().getY()); if (item == null || !item.getBaseItem().allowSit() || !item.getBaseItem().allowLay()) { + boolean wasSittingOrLaying = habbo.getRoomUnit().hasStatus(RoomUnitStatus.SIT) + || habbo.getRoomUnit().hasStatus(RoomUnitStatus.LAY); habbo.getRoomUnit().cmdStand = true; habbo.getRoomUnit().setBodyRotation( RoomUserRotation.values()[habbo.getRoomUnit().getBodyRotation().getValue() - habbo.getRoomUnit().getBodyRotation().getValue() % 2]); habbo.getRoomUnit().removeStatus(RoomUnitStatus.SIT); + habbo.getRoomUnit().removeStatus(RoomUnitStatus.LAY); this.sendComposer(new RoomUserStatusComposer(habbo.getRoomUnit()).compose()); + + if (wasSittingOrLaying) { + WiredManager.triggerUserPerformsAction(this, habbo.getRoomUnit(), WiredUserActionType.STAND, -1); + } } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatManager.java index 3665aca1..dae39207 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatManager.java @@ -156,6 +156,15 @@ public class RoomChatManager { } } + /** + * Removes a room mute from a Habbo. + */ + public void unmuteHabbo(Habbo habbo) { + synchronized (this.mutedHabbos) { + this.mutedHabbos.remove(habbo.getHabboInfo().getId()); + } + } + /** * Checks if a Habbo is muted. */ @@ -183,7 +192,8 @@ public class RoomChatManager { */ public int getMuteTimeRemaining(Habbo habbo) { if (this.mutedHabbos.containsKey(habbo.getHabboInfo().getId())) { - return this.mutedHabbos.get(habbo.getHabboInfo().getId()) - Emulator.getIntUnixTimestamp(); + return Math.max(0, + this.mutedHabbos.get(habbo.getHabboInfo().getId()) - Emulator.getIntUnixTimestamp()); } return 0; } @@ -298,7 +308,7 @@ public class RoomChatManager { if (this.isMuted(habbo)) { habbo.getClient().sendResponse(new MutedWhisperComposer( - this.mutedHabbos.get(habbo.getHabboInfo().getId()) - Emulator.getIntUnixTimestamp())); + Math.max(1, this.getMuteTimeRemaining(habbo)))); return; } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomManager.java index 0b652170..6348ca40 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomManager.java @@ -292,7 +292,7 @@ public class RoomManager { /** * Loads a room, optionally loading its data. * If the room is already being loaded in the background, this will wait for that to complete. - * + * * @param id The room ID * @param loadData Whether to load room data (items, bots, pets, etc.) * @return The loaded room, or null if not found @@ -499,14 +499,18 @@ public class RoomManager { } public void enterRoom(Habbo habbo, int roomId, String password) { - this.enterRoom(habbo, roomId, password, false, null); + this.enterRoom(habbo, roomId, password, false, null, false); } public void enterRoom(Habbo habbo, int roomId, String password, boolean overrideChecks) { - this.enterRoom(habbo, roomId, password, overrideChecks, null); + this.enterRoom(habbo, roomId, password, overrideChecks, null, false); } public void enterRoom(Habbo habbo, int roomId, String password, boolean overrideChecks, RoomTile doorLocation) { + this.enterRoom(habbo, roomId, password, overrideChecks, doorLocation, false); + } + + public void enterRoom(Habbo habbo, int roomId, String password, boolean overrideChecks, RoomTile doorLocation, boolean isReconnectSpawn) { Room room = this.loadRoom(roomId, true); if (room == null) @@ -547,7 +551,7 @@ public class RoomManager { room.hasRights(habbo) || (room.getState().equals(RoomState.INVISIBLE) && room.hasRights(habbo)) || (room.hasGuild() && room.getGuildRightLevel(habbo).isGreaterThan(RoomRightLevels.GUILD_RIGHTS))) { - this.openRoom(habbo, room, doorLocation); + this.openRoom(habbo, room, doorLocation, isReconnectSpawn); } else if (room.getState() == RoomState.LOCKED) { boolean rightsFound = false; @@ -572,7 +576,7 @@ public class RoomManager { room.addToQueue(habbo); } else if (room.getState() == RoomState.PASSWORD) { if (room.getPassword().equalsIgnoreCase(password)) - this.openRoom(habbo, room, doorLocation); + this.openRoom(habbo, room, doorLocation, isReconnectSpawn); else { habbo.getClient().sendResponse(new GenericErrorMessagesComposer(GenericErrorMessagesComposer.WRONG_PASSWORD_USED)); habbo.getClient().sendResponse(new HotelViewComposer()); @@ -585,6 +589,10 @@ public class RoomManager { } void openRoom(Habbo habbo, Room room, RoomTile doorLocation) { + this.openRoom(habbo, room, doorLocation, false); + } + + void openRoom(Habbo habbo, Room room, RoomTile doorLocation, boolean isReconnectSpawn) { if (room == null || room.getLayout() == null) return; @@ -623,7 +631,13 @@ public class RoomManager { if (doorLocation == null) { habbo.getRoomUnit().setBodyRotation(RoomUserRotation.values()[room.getLayout().getDoorDirection()]); habbo.getRoomUnit().setHeadRotation(RoomUserRotation.values()[room.getLayout().getDoorDirection()]); + } else if (isReconnectSpawn) { + // Reconnect spawn: place at tile but keep normal room behavior + // (user can still leave by door, no teleport flags) + habbo.getRoomUnit().setBodyRotation(RoomUserRotation.values()[room.getLayout().getDoorDirection()]); + habbo.getRoomUnit().setHeadRotation(RoomUserRotation.values()[room.getLayout().getDoorDirection()]); } else { + // Furniture teleport spawn habbo.getRoomUnit().setCanLeaveRoomByDoor(false); habbo.getRoomUnit().isTeleporting = true; HabboItem topItem = room.getTopItemAt(doorLocation.x, doorLocation.y); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnitManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnitManager.java index 9ac9c726..22333091 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnitManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnitManager.java @@ -12,7 +12,9 @@ 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.habbohotel.wired.core.WiredFreezeUtil; import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.WiredUserActionType; import com.eu.habbo.messages.outgoing.generic.alerts.GenericErrorMessagesComposer; import com.eu.habbo.messages.outgoing.inventory.AddPetComposer; import com.eu.habbo.messages.outgoing.rooms.pets.RoomPetComposer; @@ -217,6 +219,10 @@ public class RoomUnitManager { return; } + if (habbo.getRoomUnit() != null) { + WiredManager.triggerUserLeavesRoom(this.room, habbo.getRoomUnit()); + } + if (habbo.getRoomUnit() != null && habbo.getRoomUnit().getCurrentLocation() != null) { habbo.getRoomUnit().getCurrentLocation().removeUnit(habbo.getRoomUnit()); } @@ -352,6 +358,7 @@ public class RoomUnitManager { } double z = habbo.getRoomUnit().getCurrentLocation().getStackHeight(); + boolean hadLayStatus = habbo.getRoomUnit().hasStatus(RoomUnitStatus.LAY); if (habbo.getRoomUnit().hasStatus(RoomUnitStatus.SIT) || (topItem != null && topItem.getBaseItem().allowSit())) { @@ -413,6 +420,10 @@ public class RoomUnitManager { } habbo.getRoomUnit().statusUpdate(true); + + if (!hadLayStatus && habbo.getRoomUnit().hasStatus(RoomUnitStatus.LAY)) { + WiredManager.triggerUserPerformsAction(this.room, habbo.getRoomUnit(), WiredUserActionType.LAY, -1); + } } if (!habbos.isEmpty()) { @@ -1299,6 +1310,8 @@ public class RoomUnitManager { */ public void teleportRoomUnitToLocation(RoomUnit roomUnit, short x, short y, double z) { if (this.room.isLoaded()) { + WiredFreezeUtil.onTeleport(this.room, roomUnit); + RoomTile tile = this.room.getLayout().getTile(x, y); if (z < tile.z) { @@ -1310,6 +1323,7 @@ public class RoomUnitManager { roomUnit.setZ(z); roomUnit.setPreviousLocationZ(z); this.room.updateRoomUnit(roomUnit); + WiredFreezeUtil.restoreWalkState(roomUnit); } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredConditionType.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredConditionType.java index e620b4e0..96172f84 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredConditionType.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredConditionType.java @@ -25,7 +25,18 @@ public enum WiredConditionType { NOT_ACTOR_WEARS_EFFECT(23), DATE_RANGE(24), ACTOR_HAS_HANDITEM(25), - MOVEMENT_VALIDATION(26); // i dont know what type it is but its needed + MOVEMENT_VALIDATION(26), // i dont know what type it is but its needed + COUNTER_TIME_MATCHES(27), + USER_PERFORMS_ACTION(28), + HAS_ALTITUDE(29), + NOT_USER_PERFORMS_ACTION(30), + NOT_ACTOR_HAS_HANDITEM(31), + TRIGGERER_MATCH(32), + NOT_TRIGGERER_MATCH(33), + TEAM_HAS_SCORE(34), + TEAM_HAS_RANK(35), + MATCH_TIME(36), + MATCH_DATE(37); public final int code; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredEffectType.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredEffectType.java index 0d5e74d2..8ece3410 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredEffectType.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredEffectType.java @@ -32,7 +32,14 @@ public enum WiredEffectType { FURNI_BYTYPE_SELECTOR(30), USERS_AREA_SELECTOR(31), USERS_NEIGHBORHOOD_SELECTOR(32), - SEND_SIGNAL(33); + SEND_SIGNAL(33), + FREEZE(34), + UNFREEZE(35), + FURNI_TO_USER(36), + USER_TO_FURNI(37), + FURNI_TO_FURNI(38), + SET_ALTITUDE(39), + RELATIVE_MOVE(40); public final int code; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredHandler.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredHandler.java index 6aab1031..9490bcf1 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredHandler.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredHandler.java @@ -285,88 +285,115 @@ public class WiredHandler { } } - private static void giveReward(Habbo habbo, WiredEffectGiveReward wiredBox, WiredGiveRewardItem reward) { - if (wiredBox.limit > 0) - wiredBox.given++; - + private static void persistReward(int wiredId, int habboId, int rewardId, int timestamp) { try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO wired_rewards_given (wired_item, user_id, reward_id, timestamp) VALUES ( ?, ?, ?, ?)")) { - statement.setInt(1, wiredBox.getId()); - statement.setInt(2, habbo.getHabboInfo().getId()); - statement.setInt(3, reward.id); - statement.setInt(4, Emulator.getIntUnixTimestamp()); + statement.setInt(1, wiredId); + statement.setInt(2, habboId); + statement.setInt(3, rewardId); + statement.setInt(4, timestamp); statement.execute(); } catch (SQLException e) { LOGGER.error("Caught SQL exception", e); } + } + private static void completeReward(Habbo habbo, WiredEffectGiveReward wiredBox, WiredGiveRewardItem reward, int successCode) { + if (wiredBox.limit > 0) + wiredBox.given++; + + persistReward(wiredBox.getId(), habbo.getHabboInfo().getId(), reward.id, Emulator.getIntUnixTimestamp()); + habbo.getClient().sendResponse(new WiredRewardAlertComposer(successCode)); + } + + private static boolean giveReward(Habbo habbo, WiredEffectGiveReward wiredBox, WiredGiveRewardItem reward) { if (reward.badge) { UserWiredRewardReceived rewardReceived = new UserWiredRewardReceived(habbo, wiredBox, "badge", reward.data); if (Emulator.getPluginManager().fireEvent(rewardReceived).isCancelled()) - return; + return false; if (rewardReceived.value.isEmpty()) - return; + return false; - if (habbo.getInventory().getBadgesComponent().hasBadge(rewardReceived.value)) - return; + if (habbo.getInventory().getBadgesComponent().hasBadge(rewardReceived.value)) { + habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED)); + return false; + } HabboBadge badge = new HabboBadge(0, rewardReceived.value, 0, habbo); Emulator.getThreading().run(badge); habbo.getInventory().getBadgesComponent().addBadge(badge); habbo.getClient().sendResponse(new AddUserBadgeComposer(badge)); - habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_RECEIVED_BADGE)); - } else { - String[] data = reward.data.split("#"); - - if (data.length == 2) { - UserWiredRewardReceived rewardReceived = new UserWiredRewardReceived(habbo, wiredBox, data[0], data[1]); - if (Emulator.getPluginManager().fireEvent(rewardReceived).isCancelled()) - return; - - if (rewardReceived.value.isEmpty()) - return; - - if (rewardReceived.type.equalsIgnoreCase("credits")) { - int credits = Integer.parseInt(rewardReceived.value); - habbo.giveCredits(credits); - } else if (rewardReceived.type.equalsIgnoreCase("pixels")) { - int pixels = Integer.parseInt(rewardReceived.value); - habbo.givePixels(pixels); - } else if (rewardReceived.type.startsWith("points")) { - int points = Integer.parseInt(rewardReceived.value); - int type = 5; - - try { - type = Integer.parseInt(rewardReceived.type.replace("points", "")); - } catch (Exception e) { - } - - habbo.givePoints(type, points); - } else if (rewardReceived.type.equalsIgnoreCase("furni")) { - Item baseItem = Emulator.getGameEnvironment().getItemManager().getItem(Integer.parseInt(rewardReceived.value)); - if (baseItem != null) { - HabboItem item = Emulator.getGameEnvironment().getItemManager().createItem(habbo.getHabboInfo().getId(), baseItem, 0, 0, ""); - - if (item != null) { - habbo.getClient().sendResponse(new AddHabboItemComposer(item)); - habbo.getClient().getHabbo().getInventory().getItemsComponent().addItem(item); - habbo.getClient().sendResponse(new PurchaseOKComposer(null)); - habbo.getClient().sendResponse(new InventoryRefreshComposer()); - habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_RECEIVED_ITEM)); - } - } - } else if (rewardReceived.type.equalsIgnoreCase("respect")) { - habbo.getHabboStats().respectPointsReceived += Integer.parseInt(rewardReceived.value); - } else if (rewardReceived.type.equalsIgnoreCase("cata")) { - CatalogItem item = Emulator.getGameEnvironment().getCatalogManager().getCatalogItem(Integer.parseInt(rewardReceived.value)); - - if (item != null) { - Emulator.getGameEnvironment().getCatalogManager().purchaseItem(null, item, habbo, 1, "", true); - } - habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_RECEIVED_ITEM)); - } - } + completeReward(habbo, wiredBox, reward, WiredRewardAlertComposer.REWARD_RECEIVED_BADGE); + return true; } + + String[] data = reward.data.split("#"); + + if (data.length != 2) + return false; + + UserWiredRewardReceived rewardReceived = new UserWiredRewardReceived(habbo, wiredBox, data[0], data[1]); + if (Emulator.getPluginManager().fireEvent(rewardReceived).isCancelled()) + return false; + + if (rewardReceived.value.isEmpty()) + return false; + + if (rewardReceived.type.equalsIgnoreCase("credits")) { + habbo.giveCredits(Integer.parseInt(rewardReceived.value)); + completeReward(habbo, wiredBox, reward, WiredRewardAlertComposer.REWARD_RECEIVED_ITEM); + return true; + } else if (rewardReceived.type.equalsIgnoreCase("diamonds") || rewardReceived.type.equalsIgnoreCase("diamond")) { + habbo.givePoints(5, Integer.parseInt(rewardReceived.value)); + completeReward(habbo, wiredBox, reward, WiredRewardAlertComposer.REWARD_RECEIVED_ITEM); + return true; + } else if (rewardReceived.type.equalsIgnoreCase("pixels")) { + habbo.givePixels(Integer.parseInt(rewardReceived.value)); + completeReward(habbo, wiredBox, reward, WiredRewardAlertComposer.REWARD_RECEIVED_ITEM); + return true; + } else if (rewardReceived.type.startsWith("points")) { + int points = Integer.parseInt(rewardReceived.value); + int type = 5; + + try { + type = Integer.parseInt(rewardReceived.type.replace("points", "")); + } catch (Exception e) { + } + + habbo.givePoints(type, points); + completeReward(habbo, wiredBox, reward, WiredRewardAlertComposer.REWARD_RECEIVED_ITEM); + return true; + } else if (rewardReceived.type.equalsIgnoreCase("furni")) { + Item baseItem = Emulator.getGameEnvironment().getItemManager().getItem(Integer.parseInt(rewardReceived.value)); + if (baseItem == null) + return false; + + HabboItem item = Emulator.getGameEnvironment().getItemManager().createItem(habbo.getHabboInfo().getId(), baseItem, 0, 0, ""); + if (item == null) + return false; + + habbo.getClient().sendResponse(new AddHabboItemComposer(item)); + habbo.getClient().getHabbo().getInventory().getItemsComponent().addItem(item); + habbo.getClient().sendResponse(new PurchaseOKComposer(null)); + habbo.getClient().sendResponse(new InventoryRefreshComposer()); + completeReward(habbo, wiredBox, reward, WiredRewardAlertComposer.REWARD_RECEIVED_ITEM); + return true; + } else if (rewardReceived.type.equalsIgnoreCase("respect")) { + habbo.getHabboStats().respectPointsReceived += Integer.parseInt(rewardReceived.value); + completeReward(habbo, wiredBox, reward, WiredRewardAlertComposer.REWARD_RECEIVED_ITEM); + return true; + } else if (rewardReceived.type.equalsIgnoreCase("cata")) { + CatalogItem item = Emulator.getGameEnvironment().getCatalogManager().getCatalogItem(Integer.parseInt(rewardReceived.value)); + + if (item == null) + return false; + + Emulator.getGameEnvironment().getCatalogManager().purchaseItem(null, item, habbo, 1, "", true); + completeReward(habbo, wiredBox, reward, WiredRewardAlertComposer.REWARD_RECEIVED_ITEM); + return true; + } + + return false; } public static boolean getReward(Habbo habbo, WiredEffectGiveReward wiredBox) { @@ -433,22 +460,26 @@ public class WiredHandler { } if (!found) { - giveReward(habbo, wiredBox, item); - return true; + return giveReward(habbo, wiredBox, item); } } + + habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALL_COLLECTED)); + return false; } else { int randomNumber = Emulator.getRandom().nextInt(101); int count = 0; for (WiredGiveRewardItem item : wiredBox.rewardItems) { if (randomNumber >= count && randomNumber <= (count + item.probability)) { - giveReward(habbo, wiredBox, item); - return true; + return giveReward(habbo, wiredBox, item); } count += item.probability; } + + habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.UNLUCKY_NO_REWARD)); + return false; } } } @@ -466,7 +497,7 @@ public class WiredHandler { room.getRoomSpecialTypes().getTriggers().forEach(t -> { if (t == null) return; - if (t.getType() == WiredTriggerType.AT_GIVEN_TIME || t.getType() == WiredTriggerType.PERIODICALLY || t.getType() == WiredTriggerType.PERIODICALLY_LONG) { + if (t.getType() == WiredTriggerType.AT_GIVEN_TIME || t.getType() == WiredTriggerType.PERIODICALLY || t.getType() == WiredTriggerType.PERIODICALLY_LONG || t.getType() == WiredTriggerType.PERIODICALLY_SHORT) { ((WiredTriggerReset) t).resetTimer(); } }); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredTriggerType.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredTriggerType.java index e1767c1b..2672758b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredTriggerType.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredTriggerType.java @@ -15,6 +15,12 @@ public enum WiredTriggerType { PERIODICALLY_LONG(12), BOT_REACHED_STF(13), BOT_REACHED_AVTR(14), + LEAVE_ROOM(16), + PERIODICALLY_SHORT(17), + CLICKS_FURNI(18), + CLICKS_TILE(19), + CLICKS_USER(20), + USER_PERFORMS_ACTION(21), SAY_COMMAND(0), IDLES(11), UNIDLES(11), diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredUserActionType.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredUserActionType.java new file mode 100644 index 00000000..eaf12377 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredUserActionType.java @@ -0,0 +1,18 @@ +package com.eu.habbo.habbohotel.wired; + +public final class WiredUserActionType { + public static final int WAVE = 1; + public static final int BLOW_KISS = 2; + public static final int LAUGH = 3; + public static final int AWAKE = 4; + public static final int RELAX = 5; + public static final int SIT = 6; + public static final int STAND = 7; + public static final int LAY = 8; + public static final int SIGN = 9; + public static final int DANCE = 10; + public static final int THUMB_UP = 11; + + private WiredUserActionType() { + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java index 28300ca6..0cc66859 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java @@ -214,24 +214,16 @@ public final class WiredEngine { // Initial step for trigger state.step(); - // Activate the trigger box animation - if (stack.triggerItem() instanceof InteractionWiredTrigger) { - InteractionWiredTrigger trigger = (InteractionWiredTrigger) stack.triggerItem(); - trigger.activateBox(room, event.getActor().orElse(null), currentTime); - } - debug(room, "Trigger matched: {} at item {} (conditions: {}, effects: {})", event.getType(), stack.triggerItem() != null ? stack.triggerItem().getId() : "null", stack.conditions().size(), stack.effects().size()); - - // Activate extras (for their animation) - activateExtras(room, stack.triggerItem(), event.getActor().orElse(null), currentTime); // Run selectors before conditions so targets are available + List executedSelectors = Collections.emptyList(); if (stack.hasEffects()) { - executeSelectors(stack, ctx, currentTime); + executedSelectors = executeSelectors(stack, ctx); } // Evaluate conditions @@ -253,6 +245,17 @@ public final class WiredEngine { return false; } + RoomUnit actor = event.getActor().orElse(null); + + // Only show the trigger/selector activation when the stack is actually allowed to continue. + if (stack.triggerItem() instanceof InteractionWiredTrigger) { + InteractionWiredTrigger trigger = (InteractionWiredTrigger) stack.triggerItem(); + trigger.activateBox(room, actor, currentTime); + } + + activateExtras(room, stack.triggerItem(), actor, currentTime); + finalizeSelectors(executedSelectors, ctx, currentTime); + // Execute effects if (stack.hasEffects()) { executeEffects(stack, ctx, currentTime); @@ -420,9 +423,11 @@ public final class WiredEngine { /** * Execute selector effects before conditions so ctx.targets() is populated. */ - private void executeSelectors(WiredStack stack, WiredContext ctx, long currentTime) { + private List executeSelectors(WiredStack stack, WiredContext ctx) { List effects = stack.effects(); - if (effects.isEmpty()) return; + if (effects.isEmpty()) return Collections.emptyList(); + + List executedSelectors = new ArrayList<>(); for (IWiredEffect effect : effects) { if (!effect.isSelector()) continue; @@ -433,16 +438,29 @@ public final class WiredEngine { ctx.state().step(); try { effect.execute(ctx); - if (effect instanceof InteractionWiredEffect) { - InteractionWiredEffect wiredEffect = (InteractionWiredEffect) effect; - wiredEffect.setCooldown(currentTime); - wiredEffect.activateBox(ctx.room(), ctx.actor().orElse(null), currentTime); + executedSelectors.add((InteractionWiredEffect) effect); } } catch (Exception e) { LOGGER.warn("Error executing selector: {}", e.getMessage()); } } + + return executedSelectors; + } + + private void finalizeSelectors(List executedSelectors, WiredContext ctx, long currentTime) { + if (executedSelectors == null || executedSelectors.isEmpty()) { + return; + } + + Room room = ctx.room(); + RoomUnit actor = ctx.actor().orElse(null); + + for (InteractionWiredEffect wiredEffect : executedSelectors) { + wiredEffect.setCooldown(currentTime); + wiredEffect.activateBox(room, actor, currentTime); + } } /** diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEvent.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEvent.java index ca3c543b..369d7fba 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEvent.java @@ -42,6 +42,18 @@ public final class WiredEvent { /** User walks off furniture */ USER_WALKS_OFF(WiredTriggerType.WALKS_OFF_FURNI), + + /** User clicks furniture */ + USER_CLICKS_FURNI(WiredTriggerType.CLICKS_FURNI), + + /** User clicks invisible click tile furniture */ + USER_CLICKS_TILE(WiredTriggerType.CLICKS_TILE), + + /** User clicks another user */ + USER_CLICKS_USER(WiredTriggerType.CLICKS_USER), + + /** User performs an avatar action */ + USER_PERFORMS_ACTION(WiredTriggerType.USER_PERFORMS_ACTION), /** Furniture state is toggled/changed */ FURNI_STATE_CHANGED(WiredTriggerType.STATE_CHANGED), @@ -54,9 +66,15 @@ public final class WiredEvent { /** Long timer repeat */ TIMER_REPEAT_LONG(WiredTriggerType.PERIODICALLY_LONG), + + /** Short timer repeat */ + TIMER_REPEAT_SHORT(WiredTriggerType.PERIODICALLY_SHORT), /** User enters the room */ USER_ENTERS_ROOM(WiredTriggerType.ENTER_ROOM), + + /** User leaves the room */ + USER_LEAVES_ROOM(WiredTriggerType.LEAVE_ROOM), /** Game starts */ GAME_STARTS(WiredTriggerType.GAME_STARTS), @@ -141,6 +159,8 @@ public final class WiredEvent { private final boolean triggeredByEffect; // true if triggered by a wired effect (to prevent loops) private final int callStackDepth; // recursion depth for trigger stacks effect 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 long createdAtMs; private WiredEvent(Builder builder) { @@ -156,6 +176,8 @@ public final class WiredEvent { this.triggeredByEffect = builder.triggeredByEffect; this.callStackDepth = builder.callStackDepth; this.signalChannel = builder.signalChannel; + this.actionId = builder.actionId; + this.actionParameter = builder.actionParameter; this.createdAtMs = builder.createdAtMs; } @@ -258,6 +280,14 @@ public final class WiredEvent { return signalChannel; } + public int getActionId() { + return actionId; + } + + public int getActionParameter() { + return actionParameter; + } + /** * Get the timestamp when this event was created. * @return milliseconds since epoch @@ -313,6 +343,8 @@ public final class WiredEvent { private boolean triggeredByEffect; private int callStackDepth; private int signalChannel; + private int actionId; + private int actionParameter = -1; private long createdAtMs = System.currentTimeMillis(); private Builder(Type type, Room room) { @@ -417,6 +449,16 @@ public final class WiredEvent { return this; } + public Builder actionId(int actionId) { + this.actionId = actionId; + return this; + } + + public Builder actionParameter(int actionParameter) { + this.actionParameter = actionParameter; + return this; + } + /** * Set a custom creation timestamp. * @param createdAtMs milliseconds since epoch diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredFreezeUtil.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredFreezeUtil.java new file mode 100644 index 00000000..70b73254 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredFreezeUtil.java @@ -0,0 +1,74 @@ +package com.eu.habbo.habbohotel.wired.core; + +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer; + +public final class WiredFreezeUtil { + private static final String CACHE_ACTIVE = "wired.freeze.active"; + private static final String CACHE_EFFECT_ID = "wired.freeze.effect_id"; + private static final String CACHE_CANCEL_ON_TELEPORT = "wired.freeze.cancel_on_teleport"; + + private WiredFreezeUtil() { + } + + public static boolean isFrozen(RoomUnit roomUnit) { + return roomUnit != null && Boolean.TRUE.equals(roomUnit.getCacheable().get(CACHE_ACTIVE)); + } + + public static void freeze(Room room, RoomUnit roomUnit, int effectId, boolean cancelOnTeleport) { + if (room == null || roomUnit == null || effectId <= 0) { + return; + } + + roomUnit.getCacheable().put(CACHE_ACTIVE, true); + roomUnit.getCacheable().put(CACHE_EFFECT_ID, effectId); + roomUnit.getCacheable().put(CACHE_CANCEL_ON_TELEPORT, cancelOnTeleport); + + roomUnit.stopWalking(); + roomUnit.setCanWalk(false); + roomUnit.statusUpdate(true); + + room.giveEffect(roomUnit, effectId, Integer.MAX_VALUE); + room.sendComposer(new RoomUserStatusComposer(roomUnit).compose()); + } + + public static void unfreeze(Room room, RoomUnit roomUnit) { + if (roomUnit == null) { + return; + } + + roomUnit.getCacheable().remove(CACHE_ACTIVE); + roomUnit.getCacheable().remove(CACHE_EFFECT_ID); + roomUnit.getCacheable().remove(CACHE_CANCEL_ON_TELEPORT); + + roomUnit.stopWalking(); + roomUnit.setCanWalk(true); + roomUnit.statusUpdate(true); + + if (room != null) { + room.giveEffect(roomUnit, 0, -1); + room.sendComposer(new RoomUserStatusComposer(roomUnit).compose()); + } else { + roomUnit.setEffectId(0, 0); + } + } + + public static void onTeleport(Room room, RoomUnit roomUnit) { + if (!isFrozen(roomUnit)) { + return; + } + + if (Boolean.TRUE.equals(roomUnit.getCacheable().get(CACHE_CANCEL_ON_TELEPORT))) { + unfreeze(room, roomUnit); + } + } + + public static void restoreWalkState(RoomUnit roomUnit) { + if (roomUnit == null) { + return; + } + + roomUnit.setCanWalk(!isFrozen(roomUnit)); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredManager.java index e31b948c..38bbfd54 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredManager.java @@ -64,6 +64,9 @@ import java.sql.SQLException; * @see WiredEvents */ public final class WiredManager { + private static final String CACHE_LAST_ACTION_ID = "wired.last_user_action.id"; + private static final String CACHE_LAST_ACTION_PARAMETER = "wired.last_user_action.parameter"; + private static final String CACHE_LAST_ACTION_TIMESTAMP = "wired.last_user_action.timestamp"; private static final Logger LOGGER = LoggerFactory.getLogger(WiredManager.class); @@ -235,6 +238,58 @@ public final class WiredManager { return handleEvent(event); } + /** + * Trigger when a user clicks furniture. + */ + public static boolean triggerUserClicksFurni(Room room, RoomUnit user, HabboItem item) { + if (!isEnabled() || room == null || user == null || item == null) { + return false; + } + + WiredEvent event = WiredEvents.userClicksFurni(room, user, item); + return handleEvent(event); + } + + /** + * Trigger when a user clicks invisible click tile furniture. + */ + public static boolean triggerUserClicksTile(Room room, RoomUnit user, HabboItem item) { + if (!isEnabled() || room == null || user == null || item == null) { + return false; + } + + WiredEvent event = WiredEvents.userClicksTile(room, user, item); + return handleEvent(event); + } + + /** + * Trigger when a user clicks another user. + */ + public static boolean triggerUserClicksUser(Room room, RoomUnit clickingUser, RoomUnit clickedUser) { + if (!isEnabled() || room == null || clickingUser == null || clickedUser == null) { + return false; + } + + WiredEvent event = WiredEvents.userClicksUser(room, clickingUser, clickedUser); + return handleEvent(event); + } + + /** + * Trigger when a user performs an avatar action. + */ + public static boolean triggerUserPerformsAction(Room room, RoomUnit user, int actionId, int actionParameter) { + if (!isEnabled() || room == null || user == null) { + return false; + } + + user.getCacheable().put(CACHE_LAST_ACTION_ID, actionId); + user.getCacheable().put(CACHE_LAST_ACTION_PARAMETER, actionParameter); + user.getCacheable().put(CACHE_LAST_ACTION_TIMESTAMP, System.currentTimeMillis()); + + WiredEvent event = WiredEvents.userPerformsAction(room, user, actionId, actionParameter); + return handleEvent(event); + } + /** * Trigger when a user says something. */ @@ -259,6 +314,18 @@ public final class WiredManager { return handleEvent(event); } + /** + * Trigger when a user leaves the room. + */ + public static boolean triggerUserLeavesRoom(Room room, RoomUnit user) { + if (!isEnabled() || room == null || user == null) { + return false; + } + + WiredEvent event = WiredEvents.userLeavesRoom(room, user); + return handleEvent(event); + } + /** * Trigger when furniture state changes. */ @@ -295,6 +362,30 @@ public final class WiredManager { return handleEvent(event); } + /** + * Trigger a long periodic timer. + */ + public static boolean triggerTimerRepeatLong(Room room, HabboItem timerItem) { + if (!isEnabled() || room == null) { + return false; + } + + WiredEvent event = WiredEvents.timerRepeatLong(room, timerItem); + return handleEvent(event); + } + + /** + * Trigger a short periodic timer. + */ + public static boolean triggerTimerRepeatShort(Room room, HabboItem timerItem) { + if (!isEnabled() || room == null) { + return false; + } + + WiredEvent event = WiredEvents.timerRepeatShort(room, timerItem); + return handleEvent(event); + } + /** * Trigger game start. */ @@ -687,17 +778,9 @@ public final class WiredManager { }); } - private static void giveReward(Habbo habbo, WiredEffectGiveReward wiredBox, WiredGiveRewardItem reward) { - if (wiredBox.getLimit() > 0) - wiredBox.incrementGiven(); - - final int wiredId = wiredBox.getId(); - final int habboId = habbo.getHabboInfo().getId(); - final int rewardId = reward.id; - final int timestamp = Emulator.getIntUnixTimestamp(); - + private static void persistReward(int wiredId, int habboId, int rewardId, int timestamp) { Emulator.getThreading().run(() -> { - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO wired_rewards_given (wired_item, user_id, reward_id, timestamp) VALUES ( ?, ?, ?, ?)")) { statement.setInt(1, wiredId); statement.setInt(2, habboId); @@ -708,75 +791,125 @@ public final class WiredManager { LOGGER.error("Caught SQL exception", e); } }); + } + private static void completeReward(Habbo habbo, WiredEffectGiveReward wiredBox, WiredGiveRewardItem reward, int successCode) { + if (wiredBox.getLimit() > 0) { + wiredBox.incrementGiven(); + } + + persistReward(wiredBox.getId(), habbo.getHabboInfo().getId(), reward.id, Emulator.getIntUnixTimestamp()); + habbo.getClient().sendResponse(new WiredRewardAlertComposer(successCode)); + } + + private static boolean giveReward(Habbo habbo, WiredEffectGiveReward wiredBox, WiredGiveRewardItem reward) { if (reward.badge) { UserWiredRewardReceived rewardReceived = new UserWiredRewardReceived(habbo, wiredBox, "badge", reward.data); - if (Emulator.getPluginManager().fireEvent(rewardReceived).isCancelled()) - return; + if (Emulator.getPluginManager().fireEvent(rewardReceived).isCancelled()) { + return false; + } - if (rewardReceived.value.isEmpty()) - return; - - if (habbo.getInventory().getBadgesComponent().hasBadge(rewardReceived.value)) - return; + if (rewardReceived.value.isEmpty()) { + return false; + } + + if (habbo.getInventory().getBadgesComponent().hasBadge(rewardReceived.value)) { + habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED)); + return false; + } HabboBadge badge = new HabboBadge(0, rewardReceived.value, 0, habbo); Emulator.getThreading().run(badge); habbo.getInventory().getBadgesComponent().addBadge(badge); habbo.getClient().sendResponse(new AddUserBadgeComposer(badge)); - habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_RECEIVED_BADGE)); - } else { - String[] data = reward.data.split("#"); - - if (data.length == 2) { - UserWiredRewardReceived rewardReceived = new UserWiredRewardReceived(habbo, wiredBox, data[0], data[1]); - if (Emulator.getPluginManager().fireEvent(rewardReceived).isCancelled()) - return; - - if (rewardReceived.value.isEmpty()) - return; - - if (rewardReceived.type.equalsIgnoreCase("credits")) { - int credits = Integer.parseInt(rewardReceived.value); - habbo.giveCredits(credits); - } else if (rewardReceived.type.equalsIgnoreCase("pixels")) { - int pixels = Integer.parseInt(rewardReceived.value); - habbo.givePixels(pixels); - } else if (rewardReceived.type.startsWith("points")) { - int points = Integer.parseInt(rewardReceived.value); - int type = 5; - - try { - type = Integer.parseInt(rewardReceived.type.replace("points", "")); - } catch (Exception e) { - } - - habbo.givePoints(type, points); - } else if (rewardReceived.type.equalsIgnoreCase("furni")) { - Item baseItem = Emulator.getGameEnvironment().getItemManager().getItem(Integer.parseInt(rewardReceived.value)); - if (baseItem != null) { - HabboItem item = Emulator.getGameEnvironment().getItemManager().createItem(habbo.getHabboInfo().getId(), baseItem, 0, 0, ""); - - if (item != null) { - habbo.getClient().sendResponse(new AddHabboItemComposer(item)); - habbo.getClient().getHabbo().getInventory().getItemsComponent().addItem(item); - habbo.getClient().sendResponse(new PurchaseOKComposer(null)); - habbo.getClient().sendResponse(new InventoryRefreshComposer()); - habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_RECEIVED_ITEM)); - } - } - } else if (rewardReceived.type.equalsIgnoreCase("respect")) { - habbo.getHabboStats().respectPointsReceived += Integer.parseInt(rewardReceived.value); - } else if (rewardReceived.type.equalsIgnoreCase("cata")) { - CatalogItem item = Emulator.getGameEnvironment().getCatalogManager().getCatalogItem(Integer.parseInt(rewardReceived.value)); - - if (item != null) { - Emulator.getGameEnvironment().getCatalogManager().purchaseItem(null, item, habbo, 1, "", true); - } - habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_RECEIVED_ITEM)); - } - } + completeReward(habbo, wiredBox, reward, WiredRewardAlertComposer.REWARD_RECEIVED_BADGE); + return true; } + + String[] data = reward.data.split("#"); + + if (data.length != 2) { + return false; + } + + UserWiredRewardReceived rewardReceived = new UserWiredRewardReceived(habbo, wiredBox, data[0], data[1]); + if (Emulator.getPluginManager().fireEvent(rewardReceived).isCancelled()) { + return false; + } + + if (rewardReceived.value.isEmpty()) { + return false; + } + + if (rewardReceived.type.equalsIgnoreCase("credits")) { + habbo.giveCredits(Integer.parseInt(rewardReceived.value)); + completeReward(habbo, wiredBox, reward, WiredRewardAlertComposer.REWARD_RECEIVED_ITEM); + return true; + } + + if (rewardReceived.type.equalsIgnoreCase("diamonds") || rewardReceived.type.equalsIgnoreCase("diamond")) { + habbo.givePoints(5, Integer.parseInt(rewardReceived.value)); + completeReward(habbo, wiredBox, reward, WiredRewardAlertComposer.REWARD_RECEIVED_ITEM); + return true; + } + + if (rewardReceived.type.equalsIgnoreCase("pixels")) { + habbo.givePixels(Integer.parseInt(rewardReceived.value)); + completeReward(habbo, wiredBox, reward, WiredRewardAlertComposer.REWARD_RECEIVED_ITEM); + return true; + } + + if (rewardReceived.type.startsWith("points")) { + int points = Integer.parseInt(rewardReceived.value); + int type = 5; + + try { + type = Integer.parseInt(rewardReceived.type.replace("points", "")); + } catch (Exception e) { + } + + habbo.givePoints(type, points); + completeReward(habbo, wiredBox, reward, WiredRewardAlertComposer.REWARD_RECEIVED_ITEM); + return true; + } + + if (rewardReceived.type.equalsIgnoreCase("furni")) { + Item baseItem = Emulator.getGameEnvironment().getItemManager().getItem(Integer.parseInt(rewardReceived.value)); + if (baseItem == null) { + return false; + } + + HabboItem item = Emulator.getGameEnvironment().getItemManager().createItem(habbo.getHabboInfo().getId(), baseItem, 0, 0, ""); + if (item == null) { + return false; + } + + habbo.getClient().sendResponse(new AddHabboItemComposer(item)); + habbo.getClient().getHabbo().getInventory().getItemsComponent().addItem(item); + habbo.getClient().sendResponse(new PurchaseOKComposer(null)); + habbo.getClient().sendResponse(new InventoryRefreshComposer()); + completeReward(habbo, wiredBox, reward, WiredRewardAlertComposer.REWARD_RECEIVED_ITEM); + return true; + } + + if (rewardReceived.type.equalsIgnoreCase("respect")) { + habbo.getHabboStats().respectPointsReceived += Integer.parseInt(rewardReceived.value); + completeReward(habbo, wiredBox, reward, WiredRewardAlertComposer.REWARD_RECEIVED_ITEM); + return true; + } + + if (rewardReceived.type.equalsIgnoreCase("cata")) { + CatalogItem item = Emulator.getGameEnvironment().getCatalogManager().getCatalogItem(Integer.parseInt(rewardReceived.value)); + if (item == null) { + return false; + } + + Emulator.getGameEnvironment().getCatalogManager().purchaseItem(null, item, habbo, 1, "", true); + completeReward(habbo, wiredBox, reward, WiredRewardAlertComposer.REWARD_RECEIVED_ITEM); + return true; + } + + return false; } public static boolean getReward(Habbo habbo, WiredEffectGiveReward wiredBox) { @@ -843,22 +976,26 @@ public final class WiredManager { } if (!found) { - giveReward(habbo, wiredBox, item); - return true; + return giveReward(habbo, wiredBox, item); } } + + habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALL_COLLECTED)); + return false; } else { int randomNumber = Emulator.getRandom().nextInt(101); int count = 0; for (WiredGiveRewardItem item : wiredBox.getRewardItems()) { if (randomNumber >= count && randomNumber <= (count + item.probability)) { - giveReward(habbo, wiredBox, item); - return true; + return giveReward(habbo, wiredBox, item); } count += item.probability; } + + habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.UNLUCKY_NO_REWARD)); + return false; } } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/highscores/WiredHighscoreManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/highscores/WiredHighscoreManager.java index f00380ff..f39363d0 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/highscores/WiredHighscoreManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/highscores/WiredHighscoreManager.java @@ -3,6 +3,7 @@ package com.eu.habbo.habbohotel.wired.highscores; import com.eu.habbo.Emulator; import com.eu.habbo.plugin.EventHandler; import com.eu.habbo.plugin.events.emulator.EmulatorLoadedEvent; +import com.eu.habbo.util.HotelDateTimeUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -11,9 +12,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.time.DayOfWeek; -import java.time.LocalDateTime; import java.time.LocalTime; -import java.time.ZoneId; import java.time.temporal.TemporalAdjusters; import java.time.temporal.WeekFields; import java.util.*; @@ -31,8 +30,6 @@ public class WiredHighscoreManager { private final static DayOfWeek firstDayOfWeek = WeekFields.of(Locale.of(locale, country)).getFirstDayOfWeek(); private final static DayOfWeek lastDayOfWeek = DayOfWeek.of(((firstDayOfWeek.getValue() + 5) % DayOfWeek.values().length) + 1); - private final static ZoneId zoneId = ZoneId.systemDefault(); - public static ScheduledFuture midnightUpdater = null; public void load() { @@ -183,26 +180,26 @@ public class WiredHighscoreManager { } private long getTodayStartTimestamp() { - return LocalDateTime.now().with(LocalTime.MIDNIGHT).atZone(zoneId).toEpochSecond(); + return HotelDateTimeUtil.toEpochSecond(HotelDateTimeUtil.localDateTimeNow().with(LocalTime.MIDNIGHT)); } private long getTodayEndTimestamp() { - return LocalDateTime.now().with(LocalTime.MIDNIGHT).plusDays(1).plusSeconds(-1).atZone(zoneId).toEpochSecond(); + return HotelDateTimeUtil.toEpochSecond(HotelDateTimeUtil.localDateTimeNow().with(LocalTime.MIDNIGHT).plusDays(1).plusSeconds(-1)); } private long getWeekStartTimestamp() { - return LocalDateTime.now().with(LocalTime.MIDNIGHT).with(TemporalAdjusters.previousOrSame(firstDayOfWeek)).atZone(zoneId).toEpochSecond(); + return HotelDateTimeUtil.toEpochSecond(HotelDateTimeUtil.localDateTimeNow().with(LocalTime.MIDNIGHT).with(TemporalAdjusters.previousOrSame(firstDayOfWeek))); } private long getWeekEndTimestamp() { - return LocalDateTime.now().with(LocalTime.MIDNIGHT).plusDays(1).plusSeconds(-1).with(TemporalAdjusters.nextOrSame(lastDayOfWeek)).atZone(zoneId).toEpochSecond(); + return HotelDateTimeUtil.toEpochSecond(HotelDateTimeUtil.localDateTimeNow().with(LocalTime.MIDNIGHT).plusDays(1).plusSeconds(-1).with(TemporalAdjusters.nextOrSame(lastDayOfWeek))); } private long getMonthStartTimestamp() { - return LocalDateTime.now().with(LocalTime.MIDNIGHT).with(TemporalAdjusters.firstDayOfMonth()).atZone(zoneId).toEpochSecond(); + return HotelDateTimeUtil.toEpochSecond(HotelDateTimeUtil.localDateTimeNow().with(LocalTime.MIDNIGHT).with(TemporalAdjusters.firstDayOfMonth())); } private long getMonthEndTimestamp() { - return LocalDateTime.now().with(LocalTime.MIDNIGHT).plusDays(1).plusSeconds(-1).with(TemporalAdjusters.lastDayOfMonth()).atZone(zoneId).toEpochSecond(); + return HotelDateTimeUtil.toEpochSecond(HotelDateTimeUtil.localDateTimeNow().with(LocalTime.MIDNIGHT).plusDays(1).plusSeconds(-1).with(TemporalAdjusters.lastDayOfMonth())); } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/highscores/WiredHighscoreMidnightUpdater.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/highscores/WiredHighscoreMidnightUpdater.java index 7b60b22c..bb53dd4b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/highscores/WiredHighscoreMidnightUpdater.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/highscores/WiredHighscoreMidnightUpdater.java @@ -4,11 +4,10 @@ import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredHighscore; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.util.HotelDateTimeUtil; import gnu.trove.set.hash.THashSet; -import java.time.LocalDateTime; import java.time.LocalTime; -import java.time.ZoneId; import java.util.List; public class WiredHighscoreMidnightUpdater implements Runnable { @@ -30,6 +29,7 @@ public class WiredHighscoreMidnightUpdater implements Runnable { } public static int getNextUpdaterRun() { - return Math.toIntExact(LocalDateTime.now().with(LocalTime.MIDNIGHT).plusDays(1).plusSeconds(-1).atZone(ZoneId.systemDefault()).toEpochSecond() - Emulator.getIntUnixTimestamp()) + 5; + long nextRunTimestamp = HotelDateTimeUtil.toEpochSecond(HotelDateTimeUtil.localDateTimeNow().with(LocalTime.MIDNIGHT).plusDays(1).plusSeconds(-1)); + return Math.toIntExact(nextRunTimestamp - Emulator.getIntUnixTimestamp()) + 5; } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/migrate/WiredEvents.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/migrate/WiredEvents.java index 91bba744..e93072b2 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/migrate/WiredEvents.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/migrate/WiredEvents.java @@ -67,6 +67,70 @@ public final class WiredEvents { .build(); } + /** + * Create an event for when a user clicks furniture. + * @param room the room + * @param user the clicking user + * @param item the clicked furniture + * @return the event + */ + public static WiredEvent userClicksFurni(Room room, RoomUnit user, HabboItem item) { + RoomTile tile = room.getLayout().getTile(item.getX(), item.getY()); + return WiredEvent.builder(WiredEvent.Type.USER_CLICKS_FURNI, room) + .actor(user) + .sourceItem(item) + .tile(tile) + .build(); + } + + /** + * Create an event for when a user clicks invisible click tile furniture. + * @param room the room + * @param user the clicking user + * @param item the clicked furniture + * @return the event + */ + public static WiredEvent userClicksTile(Room room, RoomUnit user, HabboItem item) { + RoomTile tile = room.getLayout().getTile(item.getX(), item.getY()); + return WiredEvent.builder(WiredEvent.Type.USER_CLICKS_TILE, room) + .actor(user) + .sourceItem(item) + .tile(tile) + .build(); + } + + /** + * Create an event for when a user clicks another user. + * @param room the room + * @param clickingUser the user performing the click + * @param clickedUser the user who was clicked + * @return the event + */ + public static WiredEvent userClicksUser(Room room, RoomUnit clickingUser, RoomUnit clickedUser) { + return WiredEvent.builder(WiredEvent.Type.USER_CLICKS_USER, room) + .actor(clickedUser) + .targetUnit(clickingUser) + .tile(clickedUser.getCurrentLocation()) + .build(); + } + + /** + * Create an event for when a user performs an avatar action. + * @param room the room + * @param user the acting user + * @param actionId the wired action id + * @param actionParameter sign/dance parameter, or -1 when unused + * @return the event + */ + public static WiredEvent userPerformsAction(Room room, RoomUnit user, int actionId, int actionParameter) { + return WiredEvent.builder(WiredEvent.Type.USER_PERFORMS_ACTION, room) + .actor(user) + .tile(user.getCurrentLocation()) + .actionId(actionId) + .actionParameter(actionParameter) + .build(); + } + /** * Create an event for when a user enters the room. * @param room the room @@ -80,6 +144,19 @@ public final class WiredEvents { .build(); } + /** + * Create an event for when a user leaves the room. + * @param room the room + * @param user the user who left + * @return the event + */ + public static WiredEvent userLeavesRoom(Room room, RoomUnit user) { + return WiredEvent.builder(WiredEvent.Type.USER_LEAVES_ROOM, room) + .actor(user) + .tile(user.getCurrentLocation()) + .build(); + } + // ========== User Interaction Events ========== /** @@ -153,6 +230,18 @@ public final class WiredEvents { .build(); } + /** + * Create an event for a short periodic timer. + * @param room the room + * @param timerItem the timer furniture + * @return the event + */ + public static WiredEvent timerRepeatShort(Room room, HabboItem timerItem) { + return WiredEvent.builder(WiredEvent.Type.TIMER_REPEAT_SHORT, room) + .sourceItem(timerItem) + .build(); + } + // ========== Game Events ========== /** diff --git a/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java b/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java index 02f11baa..3fc3dcff 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java @@ -394,6 +394,8 @@ public class PacketManager { this.registerHandler(Incoming.RoomPlacePaintEvent, RoomPlacePaintEvent.class); this.registerHandler(Incoming.RoomUserStartTypingEvent, RoomUserStartTypingEvent.class); this.registerHandler(Incoming.RoomUserStopTypingEvent, RoomUserStopTypingEvent.class); + this.registerHandler(Incoming.ClickFurniEvent, ClickFurniEvent.class); + this.registerHandler(Incoming.ClickUserEvent, ClickUserEvent.class); this.registerHandler(Incoming.ToggleFloorItemEvent, ToggleFloorItemEvent.class); this.registerHandler(Incoming.ToggleWallItemEvent, ToggleWallItemEvent.class); this.registerHandler(Incoming.RoomBackgroundEvent, RoomBackgroundEvent.class); @@ -654,4 +656,4 @@ public class PacketManager { this.registerHandler(Incoming.GameCenterEvent, GameCenterEvent.class); this.registerHandler(Incoming.GameCenterRequestGameStatusEvent, GameCenterRequestGameStatusEvent.class); } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java index f0d2c76c..b0b43567 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java @@ -205,6 +205,7 @@ public class Incoming { public static final int RequestRoomDataEvent = 2230; public static final int RequestRoomHeightmapEvent = 2300; public static final int RequestGuildFurniWidgetEvent = 2651; + public static final int ClickFurniEvent = 6002; public static final int RequestOwnItemsEvent = 2105; public static final int RequestReportRoomEvent = 3267; public static final int ReportEvent = 1691; @@ -379,7 +380,7 @@ public class Incoming { public static final int UNKNOWN_SNOWSTORM_6000 = 6000; public static final int UNKNOWN_SNOWSTORM_6001 = 6001; - public static final int UNKNOWN_SNOWSTORM_6002 = 6002; + // public static final int UNKNOWN_SNOWSTORM_6002 = 6002; public static final int UNKNOWN_SNOWSTORM_6003 = 6003; public static final int UNKNOWN_SNOWSTORM_6004 = 6004; public static final int UNKNOWN_SNOWSTORM_6005 = 6005; @@ -407,6 +408,7 @@ public class Incoming { // CUSTOM public static final int UpdateFurniturePositionEvent = 10019; + public static final int ClickUserEvent = 10020; public static final int RequestInventoryPetDelete = 10030; public static final int RequestInventoryBadgeDelete = 10031; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java index 00b469ba..f2afd72c 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java @@ -2,10 +2,13 @@ package com.eu.habbo.messages.incoming.handshake; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.messenger.Messenger; +import com.eu.habbo.habbohotel.gameclients.GameClient; +import com.eu.habbo.habbohotel.gameclients.SessionResumeManager; import com.eu.habbo.habbohotel.modtool.ModToolSanctionItem; import com.eu.habbo.habbohotel.modtool.ModToolSanctions; import com.eu.habbo.habbohotel.navigation.NavigatorSavedSearch; import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomManager; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboManager; @@ -81,31 +84,94 @@ public class SecureLoginEvent extends MessageHandler { } if (this.client.getHabbo() == null) { - Habbo habbo = Emulator.getGameEnvironment().getHabboManager().loadHabbo(sso); + // Store SSO ticket on client for grace period tracking + this.client.setSsoTicket(sso); + + // Race condition fix: if the old WebSocket connection is still alive on the + // server when the client reconnects, the SSO ticket won't be in the DB yet + // (it was cleared on first login, and parkHabbo hasn't run because the old + // channel hasn't closed). Find the old client by SSO ticket and force-dispose + // it, which parks the habbo and restores the ticket to the DB. + GameClient existingClient = Emulator.getGameServer().getGameClientManager().findClientBySsoTicket(sso); + if (existingClient != null && existingClient != this.client) { + LOGGER.info("[SessionResume] Found existing client with same SSO ticket — disposing old connection to trigger parking"); + Emulator.getGameServer().getGameClientManager().disposeClient(existingClient); + } + + // First, look up the user ID to check for ghost sessions + int lookupUserId = 0; + try (java.sql.Connection conn = Emulator.getDatabase().getDataSource().getConnection(); + java.sql.PreparedStatement stmt = conn.prepareStatement("SELECT id FROM users WHERE auth_ticket = ? LIMIT 1")) { + stmt.setString(1, sso); + try (java.sql.ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + lookupUserId = rs.getInt("id"); + } + } + } catch (Exception e) { + LOGGER.error("Caught exception looking up user for session resume", e); + } + + // Check if this user has a ghost session (disconnected within grace period) + Habbo habbo = null; + boolean isSessionResume = false; + + if (lookupUserId > 0) { + habbo = SessionResumeManager.getInstance().resumeSession(lookupUserId); + } + if (habbo != null) { - try { - habbo.setClient(this.client); - this.client.setHabbo(habbo); - if(!this.client.getHabbo().connect()) { + // Session resume — reattach the existing Habbo to the new client + isSessionResume = true; + LOGGER.info("[SessionResume] Resuming session for {} (id={})", + habbo.getHabboInfo().getUsername(), habbo.getHabboInfo().getId()); + + habbo.setClient(this.client); + this.client.setHabbo(habbo); + this.client.setMachineId(habbo.getHabboInfo().getMachineID()); + + // Clear the SSO ticket now that session is resumed (prevent reuse) + if (!Emulator.debugging) { + try (java.sql.Connection conn = Emulator.getDatabase().getDataSource().getConnection(); + java.sql.PreparedStatement stmt = conn.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) { + stmt.setString(1, ""); + stmt.setInt(2, habbo.getHabboInfo().getId()); + stmt.execute(); + } catch (Exception e) { + LOGGER.error("Failed to clear SSO ticket after session resume", e); + } + } + } else { + // Normal login — load from database + habbo = Emulator.getGameEnvironment().getHabboManager().loadHabbo(sso); + } + + if (habbo != null) { + if (!isSessionResume) { + try { + habbo.setClient(this.client); + this.client.setHabbo(habbo); + if(!this.client.getHabbo().connect()) { + Emulator.getGameServer().getGameClientManager().disposeClient(this.client); + return; + } + + if (this.client.getHabbo().getHabboInfo() == null) { + Emulator.getGameServer().getGameClientManager().disposeClient(this.client); + return; + } + + if (this.client.getHabbo().getHabboInfo().getRank() == null) { + throw new NullPointerException(habbo.getHabboInfo().getUsername() + " has a NON EXISTING RANK!"); + } + + Emulator.getThreading().run(habbo); + Emulator.getGameEnvironment().getHabboManager().addHabbo(habbo); + } catch (Exception e) { + LOGGER.error("Caught exception", e); Emulator.getGameServer().getGameClientManager().disposeClient(this.client); return; } - - if (this.client.getHabbo().getHabboInfo() == null) { - Emulator.getGameServer().getGameClientManager().disposeClient(this.client); - return; - } - - if (this.client.getHabbo().getHabboInfo().getRank() == null) { - throw new NullPointerException(habbo.getHabboInfo().getUsername() + " has a NON EXISTING RANK!"); - } - - Emulator.getThreading().run(habbo); - Emulator.getGameEnvironment().getHabboManager().addHabbo(habbo); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - Emulator.getGameServer().getGameClientManager().disposeClient(this.client); - return; } if(ClothingValidationManager.VALIDATE_ON_LOGIN) { @@ -121,7 +187,18 @@ public class SecureLoginEvent extends MessageHandler { int roomIdToEnter = 0; - if (!this.client.getHabbo().getHabboStats().nux || Emulator.getConfig().getBoolean("retro.style.homeroom") && this.client.getHabbo().getHabboInfo().getHomeRoom() != 0) + if (isSessionResume) { + // On session resume, DON'T set roomIdToEnter. The client keeps its + // existing room view alive and the habbo is already in the room on + // the server. Setting roomIdToEnter = 0 prevents UserHomeRoomComposer + // from triggering a full room re-entry on the client (which would + // tear down and rebuild the room view). + Room currentRoom = habbo.getHabboInfo().getCurrentRoom(); + if (currentRoom != null) { + LOGGER.info("[SessionResume] {} is still in room {} — client will resume in-place", + habbo.getHabboInfo().getUsername(), currentRoom.getId()); + } + } else if (!this.client.getHabbo().getHabboStats().nux || Emulator.getConfig().getBoolean("retro.style.homeroom") && this.client.getHabbo().getHabboInfo().getHomeRoom() != 0) roomIdToEnter = this.client.getHabbo().getHabboInfo().getHomeRoom(); else if (!this.client.getHabbo().getHabboStats().nux || Emulator.getConfig().getBoolean("retro.style.homeroom") && RoomManager.HOME_ROOM_ID > 0) roomIdToEnter = RoomManager.HOME_ROOM_ID; @@ -189,42 +266,45 @@ public class SecureLoginEvent extends MessageHandler { } } - UserLoginEvent userLoginEvent = new UserLoginEvent(habbo, this.client.getHabbo().getHabboInfo().getIpLogin()); - Emulator.getPluginManager().fireEvent(userLoginEvent); + // Skip login-only events on session resume (welcome alerts, login events, etc.) + if (!isSessionResume) { + UserLoginEvent userLoginEvent = new UserLoginEvent(habbo, this.client.getHabbo().getHabboInfo().getIpLogin()); + Emulator.getPluginManager().fireEvent(userLoginEvent); - if(userLoginEvent.isCancelled()) { - Emulator.getGameServer().getGameClientManager().disposeClient(this.client); - return; - } + if(userLoginEvent.isCancelled()) { + Emulator.getGameServer().getGameClientManager().disposeClient(this.client); + return; + } - if (Emulator.getConfig().getBoolean("hotel.welcome.alert.enabled")) { - final Habbo finalHabbo = habbo; - Emulator.getThreading().run(() -> { - if (Emulator.getConfig().getBoolean("hotel.welcome.alert.oldstyle")) { - SecureLoginEvent.this.client.sendResponse(new MessagesForYouComposer(HabboManager.WELCOME_MESSAGE.replace("%username%", finalHabbo.getHabboInfo().getUsername()).replace("%user%", finalHabbo.getHabboInfo().getUsername()).split("
"))); - } else { - SecureLoginEvent.this.client.sendResponse(new GenericAlertComposer(HabboManager.WELCOME_MESSAGE.replace("%username%", finalHabbo.getHabboInfo().getUsername()).replace("%user%", finalHabbo.getHabboInfo().getUsername()))); - } - }, Emulator.getConfig().getInt("hotel.welcome.alert.delay", 5000)); - } + if (Emulator.getConfig().getBoolean("hotel.welcome.alert.enabled")) { + final Habbo finalHabbo = habbo; + Emulator.getThreading().run(() -> { + if (Emulator.getConfig().getBoolean("hotel.welcome.alert.oldstyle")) { + SecureLoginEvent.this.client.sendResponse(new MessagesForYouComposer(HabboManager.WELCOME_MESSAGE.replace("%username%", finalHabbo.getHabboInfo().getUsername()).replace("%user%", finalHabbo.getHabboInfo().getUsername()).split("
"))); + } else { + SecureLoginEvent.this.client.sendResponse(new GenericAlertComposer(HabboManager.WELCOME_MESSAGE.replace("%username%", finalHabbo.getHabboInfo().getUsername()).replace("%user%", finalHabbo.getHabboInfo().getUsername()))); + } + }, Emulator.getConfig().getInt("hotel.welcome.alert.delay", 5000)); + } - if(SubscriptionHabboClub.HC_PAYDAY_ENABLED) { - SubscriptionHabboClub.processUnclaimed(habbo); - } + if(SubscriptionHabboClub.HC_PAYDAY_ENABLED) { + SubscriptionHabboClub.processUnclaimed(habbo); + } - SubscriptionHabboClub.processClubBadge(habbo); + SubscriptionHabboClub.processClubBadge(habbo); - Messenger.checkFriendSizeProgress(habbo); + Messenger.checkFriendSizeProgress(habbo); - if (!habbo.getHabboStats().hasGottenDefaultSavedSearches) { - habbo.getHabboStats().hasGottenDefaultSavedSearches = true; - Emulator.getThreading().run(habbo.getHabboStats()); + if (!habbo.getHabboStats().hasGottenDefaultSavedSearches) { + habbo.getHabboStats().hasGottenDefaultSavedSearches = true; + Emulator.getThreading().run(habbo.getHabboStats()); - habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("official-root", "")); - habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("my", "")); - habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("favorites", "")); + habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("official-root", "")); + habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("my", "")); + habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("favorites", "")); - this.client.sendResponse(new NewNavigatorSavedSearchesComposer(this.client.getHabbo().getHabboInfo().getSavedSearches())); + this.client.sendResponse(new NewNavigatorSavedSearchesComposer(this.client.getHabbo().getHabboInfo().getSavedSearches())); + } } } else { Emulator.getGameServer().getGameClientManager().disposeClient(this.client); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/hotelview/HotelViewRequestSecondsUntilEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/hotelview/HotelViewRequestSecondsUntilEvent.java index d948ffc8..9856b076 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/hotelview/HotelViewRequestSecondsUntilEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/hotelview/HotelViewRequestSecondsUntilEvent.java @@ -3,9 +3,9 @@ package com.eu.habbo.messages.incoming.hotelview; import com.eu.habbo.Emulator; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.hotelview.HotelViewSecondsUntilComposer; +import com.eu.habbo.util.HotelDateTimeUtil; import java.time.LocalDateTime; -import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -18,7 +18,7 @@ public class HotelViewRequestSecondsUntilEvent extends MessageHandler { try { LocalDateTime dt = LocalDateTime.parse(date, formatter); - int secondsUntil = Math.max(0, (int) dt.atZone(ZoneId.systemDefault()).toEpochSecond() - Emulator.getIntUnixTimestamp()); + int secondsUntil = Math.max(0, (int) HotelDateTimeUtil.toEpochSecond(dt) - Emulator.getIntUnixTimestamp()); this.client.sendResponse(new HotelViewSecondsUntilComposer(date, secondsUntil)); } catch (DateTimeParseException ignored) { } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestRoomLoadEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestRoomLoadEvent.java index b5cfffa9..45e2cf47 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestRoomLoadEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestRoomLoadEvent.java @@ -2,18 +2,38 @@ package com.eu.habbo.messages.incoming.rooms; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.messages.incoming.MessageHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class RequestRoomLoadEvent extends MessageHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(RequestRoomLoadEvent.class); + @Override public void handle() throws Exception { int roomId = this.packet.readInt(); String password = this.packet.readString(); + // Optional spawn coordinates from the client (for future reconnection support). + int spawnX = -1; + int spawnY = -1; + + try { + int remaining = this.packet.getBuffer().readableBytes(); + if (remaining >= 8) { + spawnX = this.packet.readInt(); + spawnY = this.packet.readInt(); + } + } catch (Exception e) { + spawnX = -1; + spawnY = -1; + } + // Reset stale loadingRoom if timestamp has expired (indicates failed/stuck load) - if (this.client.getHabbo().getHabboInfo().getLoadingRoom() != 0 - && this.client.getHabbo().getHabboStats().roomEnterTimestamp + 5000 < System.currentTimeMillis()) { + if (this.client.getHabbo().getHabboInfo().getLoadingRoom() != 0 + && this.client.getHabbo().getHabboStats().roomEnterTimestamp + 5000 < System.currentTimeMillis()) { this.client.getHabbo().getHabboInfo().setLoadingRoom(0); } @@ -30,6 +50,18 @@ public class RequestRoomLoadEvent extends MessageHandler { Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); if (room != null) { + // If re-entering the same room (session resume / reconnect), capture + // the user's current position before removal so we can respawn there. + if (room.getId() == roomId && spawnX < 0 && spawnY < 0 + && this.client.getHabbo().getRoomUnit() != null + && this.client.getHabbo().getRoomUnit().getCurrentLocation() != null) { + RoomTile currentLoc = this.client.getHabbo().getRoomUnit().getCurrentLocation(); + spawnX = currentLoc.x; + spawnY = currentLoc.y; + LOGGER.info("[RequestRoomLoadEvent] Re-entering same room {} — preserving position ({}, {})", + roomId, spawnX, spawnY); + } + Emulator.getGameEnvironment().getRoomManager().logExit(this.client.getHabbo()); room.removeHabbo(this.client.getHabbo(), true); @@ -41,7 +73,28 @@ public class RequestRoomLoadEvent extends MessageHandler { this.client.getHabbo().getRoomUnit().isTeleporting = false; } - Emulator.getGameEnvironment().getRoomManager().enterRoom(this.client.getHabbo(), roomId, password); + // Resolve spawn tile from coordinates (either from client or from saved position above) + RoomTile spawnTile = null; + + if (spawnX >= 0 && spawnY >= 0) { + Room targetRoom = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId); + if (targetRoom == null) { + targetRoom = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId); + } + if (targetRoom != null && targetRoom.getLayout() != null) { + RoomTile tile = targetRoom.getLayout().getTile((short) spawnX, (short) spawnY); + if (tile != null && tile.isWalkable()) { + spawnTile = tile; + } + } + } + + boolean isReconnect = spawnTile != null; + LOGGER.debug("[RequestRoomLoadEvent] Entering room {} (spawnTile={}, isReconnect={})", + roomId, + spawnTile != null ? "(" + spawnTile.x + "," + spawnTile.y + ")" : "door", + isReconnect); + Emulator.getGameEnvironment().getRoomManager().enterRoom(this.client.getHabbo(), roomId, password, false, spawnTile, isReconnect); } } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/ClickFurniEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/ClickFurniEvent.java new file mode 100644 index 00000000..6ae99006 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/ClickFurniEvent.java @@ -0,0 +1,43 @@ +package com.eu.habbo.messages.incoming.rooms.items; + +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.messages.incoming.MessageHandler; + +public class ClickFurniEvent extends MessageHandler { + private static final String CLICK_TILE_INTERACTION = "room_invisible_click_tile"; + + @Override + public void handle() throws Exception { + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); + + if (room == null) { + return; + } + + int itemId = Math.abs(this.packet.readInt()); + this.packet.readInt(); + + HabboItem item = room.getHabboItem(itemId); + + if (item == null) { + return; + } + + WiredManager.triggerUserClicksFurni(room, this.client.getHabbo().getRoomUnit(), item); + + if (isClickTileItem(item)) { + WiredManager.triggerUserClicksTile(room, this.client.getHabbo().getRoomUnit(), item); + } + } + + private boolean isClickTileItem(HabboItem item) { + if (item == null || item.getBaseItem() == null || item.getBaseItem().getInteractionType() == null) { + return false; + } + + String interaction = item.getBaseItem().getInteractionType().getName(); + return interaction != null && interaction.equalsIgnoreCase(CLICK_TILE_INTERACTION); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/ClickUserEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/ClickUserEvent.java new file mode 100644 index 00000000..5426e842 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/ClickUserEvent.java @@ -0,0 +1,33 @@ +package com.eu.habbo.messages.incoming.rooms.users; + +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.core.WiredManager; +import com.eu.habbo.messages.incoming.MessageHandler; + +public class ClickUserEvent extends MessageHandler { + @Override + public void handle() throws Exception { + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); + + if (room == null) { + return; + } + + RoomUnit clickingUser = this.client.getHabbo().getRoomUnit(); + + if (clickingUser == null) { + return; + } + + int roomUnitId = this.packet.readInt(); + Habbo clickedHabbo = room.getHabboByRoomUnitId(roomUnitId); + + if (clickedHabbo == null || clickedHabbo.getRoomUnit() == null) { + return; + } + + WiredManager.triggerUserClicksUser(room, clickingUser, clickedHabbo.getRoomUnit()); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserActionEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserActionEvent.java index 41c29f25..bc32465b 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserActionEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserActionEvent.java @@ -4,6 +4,8 @@ import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUserAction; import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.habbohotel.wired.WiredUserActionType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserActionComposer; import com.eu.habbo.plugin.events.users.UserIdleEvent; @@ -26,6 +28,7 @@ public class RoomUserActionEvent extends MessageHandler { } int action = this.packet.readInt(); + int wiredAction = 0; if (action == 5) { UserIdleEvent event = new UserIdleEvent(this.client.getHabbo(), UserIdleEvent.IdleReason.ACTION, true); @@ -34,8 +37,10 @@ public class RoomUserActionEvent extends MessageHandler { if (!event.isCancelled()) { if (event.idle) { room.idle(habbo); + wiredAction = WiredUserActionType.RELAX; } else { room.unIdle(habbo); + wiredAction = WiredUserActionType.AWAKE; } } } else { @@ -51,6 +56,29 @@ public class RoomUserActionEvent extends MessageHandler { } room.sendComposer(new RoomUserActionComposer(habbo.getRoomUnit(), RoomUserAction.fromValue(action)).compose()); + + if (wiredAction == 0) { + switch (action) { + case 1: + wiredAction = WiredUserActionType.WAVE; + break; + case 2: + wiredAction = WiredUserActionType.BLOW_KISS; + break; + case 3: + wiredAction = WiredUserActionType.LAUGH; + break; + case 7: + wiredAction = WiredUserActionType.THUMB_UP; + break; + default: + break; + } + } + + if (wiredAction != 0) { + WiredManager.triggerUserPerformsAction(room, habbo.getRoomUnit(), wiredAction, -1); + } } } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserDanceEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserDanceEvent.java index bafd54dd..bda9bd4e 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserDanceEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserDanceEvent.java @@ -3,8 +3,9 @@ package com.eu.habbo.messages.incoming.rooms.users; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.users.DanceType; import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.habbohotel.wired.WiredUserActionType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.incoming.MessageHandler; -import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDanceComposer; import com.eu.habbo.plugin.events.users.UserIdleEvent; public class RoomUserDanceEvent extends MessageHandler { @@ -14,7 +15,7 @@ public class RoomUserDanceEvent extends MessageHandler { return; int danceId = this.packet.readInt(); - if (danceId >= 0 && danceId <= 5) { + if (danceId >= 0 && danceId <= 4) { if (this.client.getHabbo().getRoomUnit().isInRoom()) { Habbo habbo = this.client.getHabbo(); @@ -29,8 +30,6 @@ public class RoomUserDanceEvent extends MessageHandler { } } - habbo.getRoomUnit().setDanceType(DanceType.values()[danceId]); - UserIdleEvent event = new UserIdleEvent(this.client.getHabbo(), UserIdleEvent.IdleReason.DANCE, false); Emulator.getPluginManager().fireEvent(event); @@ -40,7 +39,11 @@ public class RoomUserDanceEvent extends MessageHandler { } } - this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new RoomUserDanceComposer(habbo.getRoomUnit()).compose()); + this.client.getHabbo().getHabboInfo().getCurrentRoom().dance(habbo, DanceType.values()[danceId]); + + if (danceId > 0) { + WiredManager.triggerUserPerformsAction(this.client.getHabbo().getHabboInfo().getCurrentRoom(), habbo.getRoomUnit(), WiredUserActionType.DANCE, danceId); + } } } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSignEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSignEvent.java index 3b2aaf93..fba5fccf 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSignEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSignEvent.java @@ -5,6 +5,8 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionVoteCounter; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnitStatus; import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.WiredUserActionType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.plugin.events.users.UserSignEvent; @@ -22,6 +24,7 @@ public class RoomUserSignEvent extends MessageHandler { if (!Emulator.getPluginManager().fireEvent(event).isCancelled()) { this.client.getHabbo().getRoomUnit().setStatus(RoomUnitStatus.SIGN, event.sign + ""); this.client.getHabbo().getHabboInfo().getCurrentRoom().unIdle(this.client.getHabbo()); + WiredManager.triggerUserPerformsAction(room, this.client.getHabbo().getRoomUnit(), WiredUserActionType.SIGN, event.sign); if(signId <= 10) { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSitEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSitEvent.java index f3c8df7f..debdb49c 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSitEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserSitEvent.java @@ -7,11 +7,18 @@ import com.eu.habbo.plugin.events.users.UserIdleEvent; public class RoomUserSitEvent extends MessageHandler { @Override public void handle() throws Exception { + int posture = this.packet.readInt(); + if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != null) { if (this.client.getHabbo().getRoomUnit().isWalking()) { this.client.getHabbo().getRoomUnit().stopWalking(); } - this.client.getHabbo().getHabboInfo().getCurrentRoom().makeSit(this.client.getHabbo()); + + if (posture == 0) { + this.client.getHabbo().getHabboInfo().getCurrentRoom().makeStand(this.client.getHabbo()); + } else { + this.client.getHabbo().getHabboInfo().getCurrentRoom().makeSit(this.client.getHabbo()); + } UserIdleEvent event = new UserIdleEvent(this.client.getHabbo(), UserIdleEvent.IdleReason.WALKED, false); Emulator.getPluginManager().fireEvent(event); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserWalkEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserWalkEvent.java index 2b5d5a5e..0e174672 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserWalkEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserWalkEvent.java @@ -10,6 +10,7 @@ import com.eu.habbo.habbohotel.rooms.BedProfile; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboInfo; import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.rooms.users.RoomUnitOnRollerComposer; import com.eu.habbo.plugin.events.users.UserIdleEvent; @@ -46,7 +47,7 @@ public class RoomUserWalkEvent extends MessageHandler { Room room = habboInfo.getCurrentRoom(); try { - if (roomUnit != null && roomUnit.isInRoom() && roomUnit.canWalk()) { + if (roomUnit != null && roomUnit.isInRoom() && roomUnit.canWalk() && !WiredFreezeUtil.isFrozen(roomUnit)) { if (roomUnit.cmdTeleport) { handleTeleport(room, (short) x, (short) y, roomUnit, habboInfo); return; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredTriggerSaveDataEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredTriggerSaveDataEvent.java index 83da31e5..bf27dc0e 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredTriggerSaveDataEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredTriggerSaveDataEvent.java @@ -34,17 +34,21 @@ public class WiredTriggerSaveDataEvent extends MessageHandler { if (saveMethod.get().getParameterTypes()[0] == WiredSettings.class) { WiredSettings settings = InteractionWired.readSettings(this.packet, false); - if (trigger.saveData(settings)) { - this.client.sendResponse(new WiredSavedComposer()); + try { + if (trigger.saveData(settings)) { + this.client.sendResponse(new WiredSavedComposer()); - trigger.needsUpdate(true); + trigger.needsUpdate(true); - Emulator.getThreading().run(trigger); - - // Invalidate wired cache when trigger is saved - WiredManager.invalidateRoom(room); - } else { - this.client.sendResponse(new UpdateFailedComposer("There was an error while saving that trigger")); + Emulator.getThreading().run(trigger); + + // Invalidate wired cache when trigger is saved + WiredManager.invalidateRoom(room); + } else { + this.client.sendResponse(new UpdateFailedComposer("There was an error while saving that trigger")); + } + } catch (WiredTriggerSaveException e) { + this.client.sendResponse(new UpdateFailedComposer(e.getMessage())); } } else { if ((boolean) saveMethod.get().invoke(trigger, this.packet)) { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredTriggerSaveException.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredTriggerSaveException.java new file mode 100644 index 00000000..63fc33a2 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredTriggerSaveException.java @@ -0,0 +1,7 @@ +package com.eu.habbo.messages.incoming.wired; + +public class WiredTriggerSaveException extends RuntimeException { + public WiredTriggerSaveException(String message) { + super(message); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/Outgoing.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/Outgoing.java index b3cf8729..3305bad8 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/Outgoing.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/Outgoing.java @@ -504,6 +504,7 @@ public class Outgoing { public final static int WiredOpenComposer = 1830; public final static int UnknownCatalogPageOfferComposer = 1889; public final static int NuxAlertComposer = 2023; + public final static int InClientLinkComposer = 2023; public final static int HotelViewExpiringCatalogPageCommposer = 2515; public final static int UnknownHabboWayQuizComposer = 2772; public final static int PetLevelUpdatedComposer = 2824; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/users/InClientLinkComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/users/InClientLinkComposer.java new file mode 100644 index 00000000..61c8548f --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/users/InClientLinkComposer.java @@ -0,0 +1,20 @@ +package com.eu.habbo.messages.outgoing.users; + +import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.messages.outgoing.MessageComposer; +import com.eu.habbo.messages.outgoing.Outgoing; + +public class InClientLinkComposer extends MessageComposer { + private final String link; + + public InClientLinkComposer(String link) { + this.link = link; + } + + @Override + protected ServerMessage composeInternal() { + this.response.init(Outgoing.InClientLinkComposer); + this.response.appendString(this.link); + return this.response; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/users/MutedWhisperComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/users/MutedWhisperComposer.java index bae0cc7a..ef24051a 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/users/MutedWhisperComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/users/MutedWhisperComposer.java @@ -8,7 +8,7 @@ public class MutedWhisperComposer extends MessageComposer { private final int seconds; public MutedWhisperComposer(int seconds) { - this.seconds = seconds; + this.seconds = Math.max(0, seconds); } @Override diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitTeleport.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitTeleport.java index 9747026e..43ddeb97 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitTeleport.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitTeleport.java @@ -5,6 +5,7 @@ import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.rooms.RoomUnitStatus; import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,6 +39,8 @@ public class RoomUnitTeleport implements Runnable { return; } + WiredFreezeUtil.onTeleport(this.room, this.roomUnit); + RoomTile lastLocation = this.roomUnit.getCurrentLocation(); RoomTile newLocation = this.room.getLayout().getTile((short) this.x, (short) this.y); @@ -60,6 +63,7 @@ public class RoomUnitTeleport implements Runnable { //this.room.sendComposer(teleportMessage); this.roomUnit.statusUpdate(true); roomUnit.isWiredTeleporting = false; + WiredFreezeUtil.restoreWalkState(this.roomUnit); this.room.updateHabbosAt(newLocation.x, newLocation.y); this.room.updateBotsAt(newLocation.x, newLocation.y); diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionFive.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionFive.java index 9fab330b..ea5d1ee8 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionFive.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionFive.java @@ -5,6 +5,7 @@ import com.eu.habbo.habbohotel.gameclients.GameClient; import com.eu.habbo.habbohotel.items.interactions.InteractionTeleportTile; import com.eu.habbo.habbohotel.rooms.*; import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil; import com.eu.habbo.threading.runnables.HabboItemNewState; import com.eu.habbo.threading.runnables.RoomUnitWalkToLocation; @@ -46,6 +47,7 @@ class TeleportActionFive implements Runnable { List onSuccess = new ArrayList(); onSuccess.add(() -> { unit.setCanLeaveRoomByDoor(true); + WiredFreezeUtil.restoreWalkState(unit); Emulator.getThreading().run(() -> { unit.isLeavingTeleporter = false; @@ -57,6 +59,8 @@ class TeleportActionFive implements Runnable { unit.statusUpdate(true); unit.isLeavingTeleporter = true; Emulator.getThreading().run(new RoomUnitWalkToLocation(unit, tile, room, onSuccess, onSuccess)); + } else { + WiredFreezeUtil.restoreWalkState(unit); } this.currentTeleport.setExtradata("1"); diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionFour.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionFour.java index 5fea22ef..7b13dec4 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionFour.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionFour.java @@ -4,6 +4,7 @@ import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.gameclients.GameClient; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil; class TeleportActionFour implements Runnable { private final HabboItem currentTeleport; @@ -21,7 +22,7 @@ class TeleportActionFour implements Runnable { if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != this.room) { this.client.getHabbo().getHabboInfo().setLoadingRoom(0); this.client.getHabbo().getRoomUnit().isTeleporting = false; - this.client.getHabbo().getRoomUnit().setCanWalk(true); + WiredFreezeUtil.restoreWalkState(this.client.getHabbo().getRoomUnit()); this.currentTeleport.setExtradata("0"); this.room.updateItem(this.currentTeleport); return; diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionOne.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionOne.java index 757db39c..8ba24c37 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionOne.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionOne.java @@ -7,6 +7,7 @@ import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnitStatus; import com.eu.habbo.habbohotel.rooms.RoomUserRotation; import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer; public class TeleportActionOne implements Runnable { @@ -25,7 +26,7 @@ public class TeleportActionOne implements Runnable { if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != this.room) { this.client.getHabbo().getHabboInfo().setLoadingRoom(0); this.client.getHabbo().getRoomUnit().isTeleporting = false; - this.client.getHabbo().getRoomUnit().setCanWalk(true); + WiredFreezeUtil.restoreWalkState(this.client.getHabbo().getRoomUnit()); return; } diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionThree.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionThree.java index ed3a5d84..efcd9ca7 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionThree.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionThree.java @@ -9,6 +9,7 @@ import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.rooms.RoomUnitStatus; import com.eu.habbo.habbohotel.rooms.RoomUserRotation; import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil; class TeleportActionThree implements Runnable { private final HabboItem currentTeleport; @@ -26,7 +27,7 @@ class TeleportActionThree implements Runnable { if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != this.room) { this.client.getHabbo().getHabboInfo().setLoadingRoom(0); this.client.getHabbo().getRoomUnit().isTeleporting = false; - this.client.getHabbo().getRoomUnit().setCanWalk(true); + WiredFreezeUtil.restoreWalkState(this.client.getHabbo().getRoomUnit()); return; } diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionTwo.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionTwo.java index eb5c4bb0..53cdc9c7 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionTwo.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionTwo.java @@ -7,6 +7,7 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionTeleportTile; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnitStatus; import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredFreezeUtil; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer; import com.eu.habbo.threading.runnables.HabboItemNewState; import org.slf4j.Logger; @@ -41,7 +42,7 @@ class TeleportActionTwo implements Runnable { if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != this.room) { this.client.getHabbo().getHabboInfo().setLoadingRoom(0); this.client.getHabbo().getRoomUnit().isTeleporting = false; - this.client.getHabbo().getRoomUnit().setCanWalk(true); + WiredFreezeUtil.restoreWalkState(this.client.getHabbo().getRoomUnit()); return; } diff --git a/Emulator/src/main/java/com/eu/habbo/util/HotelDateTimeUtil.java b/Emulator/src/main/java/com/eu/habbo/util/HotelDateTimeUtil.java new file mode 100644 index 00000000..0abb37f3 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/util/HotelDateTimeUtil.java @@ -0,0 +1,59 @@ +package com.eu.habbo.util; + +import com.eu.habbo.Emulator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +public final class HotelDateTimeUtil { + private static final Logger LOGGER = LoggerFactory.getLogger(HotelDateTimeUtil.class); + private static final String CONFIG_KEY = "hotel.timezone"; + private static volatile String lastInvalidTimezoneId = null; + + private HotelDateTimeUtil() { + } + + public static String getTimezoneId() { + return getZoneId().getId(); + } + + public static ZoneId getZoneId() { + String configuredZoneId = Emulator.getConfig().getValue(CONFIG_KEY, ZoneId.systemDefault().getId()); + + try { + lastInvalidTimezoneId = null; + return ZoneId.of(configuredZoneId.trim()); + } catch (Exception e) { + if (!configuredZoneId.equals(lastInvalidTimezoneId)) { + LOGGER.warn("Invalid {} '{}', falling back to system timezone '{}'.", CONFIG_KEY, configuredZoneId, ZoneId.systemDefault().getId()); + lastInvalidTimezoneId = configuredZoneId; + } + return ZoneId.systemDefault(); + } + } + + public static ZonedDateTime now() { + return ZonedDateTime.now(getZoneId()); + } + + public static LocalDateTime localDateTimeNow() { + return LocalDateTime.now(getZoneId()); + } + + public static LocalDate localDateNow() { + return LocalDate.now(getZoneId()); + } + + public static LocalTime localTimeNow() { + return LocalTime.now(getZoneId()); + } + + public static long toEpochSecond(LocalDateTime dateTime) { + return dateTime.atZone(getZoneId()).toEpochSecond(); + } +} diff --git a/Latest_Compiled_Version/Habbo-4.0.5-jar-with-dependencies.jar b/Latest_Compiled_Version/Habbo-4.0.5-jar-with-dependencies.jar deleted file mode 100644 index 336d428e..00000000 Binary files a/Latest_Compiled_Version/Habbo-4.0.5-jar-with-dependencies.jar and /dev/null differ