From e234debfbd26550705ca087239a89172213ddf58 Mon Sep 17 00:00:00 2001 From: Lorenzune Date: Thu, 19 Mar 2026 14:27:26 +0100 Subject: [PATCH] feat(wired): add advanced match conditions and hotel timezone --- Database Updates/19032026_hotel_timezone.sql | 22 ++ .../src/main/java/com/eu/habbo/Emulator.java | 87 ++++- .../eu/habbo/core/ConfigurationManager.java | 1 + .../habbo/habbohotel/items/ItemManager.java | 10 + .../WiredConditionHabboHasHandItem.java | 8 + .../conditions/WiredConditionHasAltitude.java | 285 ++++++++++++++ .../conditions/WiredConditionMatchDate.java | 253 ++++++++++++ .../conditions/WiredConditionMatchTime.java | 237 +++++++++++ .../WiredConditionNotHabboHasHandItem.java | 42 ++ .../WiredConditionNotTriggererMatch.java | 31 ++ .../WiredConditionNotUserPerformsAction.java | 46 +++ .../WiredConditionTeamGameBase.java | 197 ++++++++++ .../conditions/WiredConditionTeamHasRank.java | 175 +++++++++ .../WiredConditionTeamHasScore.java | 162 ++++++++ .../WiredConditionTriggererMatch.java | 368 ++++++++++++++++++ .../WiredConditionUserPerformsAction.java | 346 ++++++++++++++++ .../habbohotel/wired/WiredConditionType.java | 12 +- .../habbohotel/wired/core/WiredManager.java | 7 + .../highscores/WiredHighscoreManager.java | 17 +- .../WiredHighscoreMidnightUpdater.java | 6 +- .../HotelViewRequestSecondsUntilEvent.java | 4 +- .../com/eu/habbo/util/HotelDateTimeUtil.java | 59 +++ 22 files changed, 2350 insertions(+), 25 deletions(-) create mode 100644 Database Updates/19032026_hotel_timezone.sql create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHasAltitude.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchDate.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchTime.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboHasHandItem.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotTriggererMatch.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotUserPerformsAction.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamGameBase.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamHasRank.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamHasScore.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTriggererMatch.java create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionUserPerformsAction.java create mode 100644 Emulator/src/main/java/com/eu/habbo/util/HotelDateTimeUtil.java 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..455cb540 100644 --- a/Emulator/src/main/java/com/eu/habbo/Emulator.java +++ b/Emulator/src/main/java/com/eu/habbo/Emulator.java @@ -20,6 +20,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 +54,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 +106,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 +137,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 +221,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 +248,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; 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/items/ItemManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/ItemManager.java index 4e645d4b..265f160c 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 @@ -306,9 +306,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/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/wired/WiredConditionType.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/WiredConditionType.java index b2f435d0..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 @@ -26,7 +26,17 @@ public enum WiredConditionType { DATE_RANGE(24), ACTOR_HAS_HANDITEM(25), MOVEMENT_VALIDATION(26), // i dont know what type it is but its needed - COUNTER_TIME_MATCHES(27); + 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/core/WiredManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredManager.java index cfe08f6b..38464b93 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); @@ -279,6 +282,10 @@ public final class WiredManager { 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); } 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/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/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(); + } +}