diff --git a/Emulator/src/main/java/com/eu/habbo/core/Easter.java b/Emulator/src/main/java/com/eu/habbo/core/Easter.java index aa110823..f358dbc8 100644 --- a/Emulator/src/main/java/com/eu/habbo/core/Easter.java +++ b/Emulator/src/main/java/com/eu/habbo/core/Easter.java @@ -9,7 +9,7 @@ import com.eu.habbo.messages.outgoing.rooms.users.RoomUserWhisperComposer; import com.eu.habbo.plugin.EventHandler; import com.eu.habbo.plugin.events.users.UserSavedMottoEvent; -public final class Easter { +public class Easter { @EventHandler public static void onUserChangeMotto(UserSavedMottoEvent event) { if (Emulator.getConfig().getBoolean("easter_eggs.enabled") && event.newMotto.equalsIgnoreCase("crickey!")) { diff --git a/Emulator/src/main/java/com/eu/habbo/core/RoomUserPetComposer.java b/Emulator/src/main/java/com/eu/habbo/core/RoomUserPetComposer.java index d1bf6a75..d593d1ff 100644 --- a/Emulator/src/main/java/com/eu/habbo/core/RoomUserPetComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/core/RoomUserPetComposer.java @@ -25,9 +25,6 @@ public class RoomUserPetComposer extends MessageComposer { this.response.appendInt(this.habbo.getHabboInfo().getId()); this.response.appendString(this.habbo.getHabboInfo().getUsername()); this.response.appendString(""); - this.response.appendInt(0); - this.response.appendInt(0); - this.response.appendInt(0); this.response.appendString(this.petType + " " + this.race + " " + this.color + " 2 2 -1 0 3 -1 0"); this.response.appendInt(this.habbo.getRoomUnit().getId()); this.response.appendInt(this.habbo.getRoomUnit().getX()); diff --git a/Emulator/src/main/java/com/eu/habbo/crypto/HabboDiffieHellman.java b/Emulator/src/main/java/com/eu/habbo/crypto/HabboDiffieHellman.java index 25fb761b..f9db196d 100644 --- a/Emulator/src/main/java/com/eu/habbo/crypto/HabboDiffieHellman.java +++ b/Emulator/src/main/java/com/eu/habbo/crypto/HabboDiffieHellman.java @@ -88,11 +88,11 @@ public class HabboDiffieHellman { } if (this.DHPrime.compareTo(BigInteger.valueOf(2)) < 1) { - throw new HabboCryptoException("Prime cannot be <= 2!\nPrime: " + this.DHPrime); + throw new HabboCryptoException("Prime cannot be <= 2!\nPrime: " + this.DHPrime.toString()); } if (this.DHGenerator.compareTo(this.DHPrime) > -1) { - throw new HabboCryptoException("Generator cannot be >= Prime!\nPrime: " + this.DHPrime + "\nGenerator: " + this.DHGenerator.toString()); + throw new HabboCryptoException("Generator cannot be >= Prime!\nPrime: " + this.DHPrime.toString() + "\nGenerator: " + this.DHGenerator.toString()); } generateDHKeys(); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/GameEnvironment.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/GameEnvironment.java index 9cff9140..879e2d6b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/GameEnvironment.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/GameEnvironment.java @@ -1,10 +1,7 @@ package com.eu.habbo.habbohotel; import com.eu.habbo.Emulator; -import com.eu.habbo.core.CreditsScheduler; -import com.eu.habbo.core.GotwPointsScheduler; -import com.eu.habbo.core.PixelScheduler; -import com.eu.habbo.core.PointsScheduler; +import com.eu.habbo.core.*; import com.eu.habbo.habbohotel.achievements.AchievementManager; import com.eu.habbo.habbohotel.bots.BotManager; import com.eu.habbo.habbohotel.campaign.calendar.CalendarManager; @@ -22,6 +19,7 @@ import com.eu.habbo.habbohotel.navigation.NavigatorManager; import com.eu.habbo.habbohotel.permissions.PermissionsManager; import com.eu.habbo.habbohotel.pets.PetManager; import com.eu.habbo.habbohotel.polls.PollManager; +import com.eu.habbo.habbohotel.rooms.RoomChatBubbleManager; import com.eu.habbo.habbohotel.rooms.RoomManager; import com.eu.habbo.habbohotel.users.HabboManager; import com.eu.habbo.habbohotel.users.subscriptions.SubscriptionManager; @@ -59,6 +57,7 @@ public class GameEnvironment { private PollManager pollManager; private SubscriptionManager subscriptionManager; private CalendarManager calendarManager; + private RoomChatBubbleManager roomChatBubbleManager; public void load() throws Exception { LOGGER.info("GameEnvironment -> Loading..."); @@ -84,6 +83,7 @@ public class GameEnvironment { this.craftingManager = new CraftingManager(); this.pollManager = new PollManager(); this.calendarManager = new CalendarManager(); + this.roomChatBubbleManager = new RoomChatBubbleManager(); this.roomManager.loadPublicRooms(); this.navigatorManager.loadNavigator(); @@ -215,4 +215,8 @@ public class GameEnvironment { } public CalendarManager getCalendarManager() { return this.calendarManager; } + + public RoomChatBubbleManager getRoomChatBubbleManager() { + return roomChatBubbleManager; + } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/achievements/AchievementManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/achievements/AchievementManager.java index b8c45589..ff3700c4 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/achievements/AchievementManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/achievements/AchievementManager.java @@ -50,7 +50,8 @@ public class AchievementManager { progressAchievement(habbo, achievement, amount); } else { try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); - PreparedStatement statement = connection.prepareStatement("INSERT INTO users_achievements_queue (user_id, achievement_id, amount) VALUES (?, ?, ?) " + + PreparedStatement statement = connection.prepareStatement("" + + "INSERT INTO users_achievements_queue (user_id, achievement_id, amount) VALUES (?, ?, ?) " + "ON DUPLICATE KEY UPDATE amount = amount + ?")) { statement.setInt(1, habboId); statement.setInt(2, achievement.id); @@ -360,7 +361,7 @@ public class AchievementManager { } } - if (level.badges != null) { + if (level.badges != null && level.badges.length > 0) { for (String badge : level.badges) { if (!badge.isEmpty()) { if (!habbo.getInventory().getBadgesComponent().hasBadge(badge)) { @@ -373,11 +374,10 @@ public class AchievementManager { } } - if (level.perks != null) { + if (level.perks != null && level.perks.length > 0) { for (String perk : level.perks) { if (perk.equalsIgnoreCase("TRADE")) { habbo.getHabboStats().perkTrade = true; - break; } } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/bots/Bot.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/bots/Bot.java index 4c0c11e9..7f0a8df2 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/bots/Bot.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/bots/Bot.java @@ -4,8 +4,7 @@ import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.rooms.*; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboGender; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.outgoing.rooms.users.*; import com.eu.habbo.plugin.events.bots.BotChatEvent; import com.eu.habbo.plugin.events.bots.BotShoutEvent; @@ -21,7 +20,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; -import java.util.List; public class Bot implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(Bot.class); @@ -113,7 +111,7 @@ public class Bot implements Runnable { this.chatRandom = false; this.chatDelay = 10; this.chatTimeOut = Emulator.getIntUnixTimestamp() + this.chatDelay; - this.chatLines = new ArrayList<>(List.of("Default Message :D")); + this.chatLines = new ArrayList<>(Arrays.asList("Default Message :D")); this.type = bot.getType(); this.effect = bot.getEffect(); this.bubble = bot.getBubbleId(); @@ -140,26 +138,30 @@ public class Bot implements Runnable { @Override public void run() { if (this.needsUpdate) { - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE bots SET name = ?, motto = ?, figure = ?, gender = ?, user_id = ?, room_id = ?, dance = ?, freeroam = ?, chat_lines = ?, chat_auto = ?, chat_random = ?, chat_delay = ?, effect = ?, bubble_id = ? WHERE id = ?")) { + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE bots SET name = ?, motto = ?, figure = ?, gender = ?, user_id = ?, room_id = ?, x = ?, y = ?, z = ?, rot = ?, dance = ?, freeroam = ?, chat_lines = ?, chat_auto = ?, chat_random = ?, chat_delay = ?, effect = ?, bubble_id = ? WHERE id = ?")) { statement.setString(1, this.name); statement.setString(2, this.motto); statement.setString(3, this.figure); statement.setString(4, this.gender.toString()); statement.setInt(5, this.ownerId); statement.setInt(6, this.room == null ? 0 : this.room.getId()); - statement.setInt(7, this.roomUnit == null ? 0 : this.roomUnit.getDanceType().getType()); - statement.setString(8, this.canWalk ? "1" : "0"); + statement.setInt(7, this.roomUnit == null ? 0 : this.roomUnit.getX()); + statement.setInt(8, this.roomUnit == null ? 0 : this.roomUnit.getY()); + statement.setDouble(9, this.roomUnit == null ? 0 : this.roomUnit.getZ()); + statement.setInt(10, this.roomUnit == null ? 0 : this.roomUnit.getBodyRotation().getValue()); + statement.setInt(11, this.roomUnit == null ? 0 : this.roomUnit.getDanceType().getType()); + statement.setString(12, this.canWalk ? "1" : "0"); StringBuilder text = new StringBuilder(); for (String s : this.chatLines) { text.append(s).append("\r"); } - statement.setString(9, text.toString()); - statement.setString(10, this.chatAuto ? "1" : "0"); - statement.setString(11, this.chatRandom ? "1" : "0"); - statement.setInt(12, this.chatDelay); - statement.setInt(13, this.effect); - statement.setInt(14, this.bubble); - statement.setInt(15, this.id); + statement.setString(13, text.toString()); + statement.setString(14, this.chatAuto ? "1" : "0"); + statement.setString(15, this.chatRandom ? "1" : "0"); + statement.setInt(16, this.chatDelay); + statement.setInt(17, this.effect); + statement.setInt(18, this.bubble); + statement.setInt(19, this.id); statement.execute(); this.needsUpdate = false; } catch (SQLException e) { @@ -177,7 +179,7 @@ public class Bot implements Runnable { Bot.BOT_LIMIT_WALKING_DISTANCE ? this.room.getRandomWalkableTilesAround( this.getRoomUnit(), - this.room.getLayout().getTile(this.roomUnit.getX(), this.roomUnit.getY()), + this.room.getLayout().getTile(this.roomUnit.getBotStartLocation().x, this.roomUnit.getBotStartLocation().y), Bot.BOT_WALKING_DISTANCE_RADIUS) : this.room.getRandomWalkableTile() ); @@ -187,7 +189,7 @@ public class Bot implements Runnable { } }/* else { for (RoomTile t : this.room.getLayout().getTilesAround(this.room.getLayout().getTile(this.getRoomUnit().getX(), this.getRoomUnit().getY()))) { - WiredHandler.handle(WiredTriggerType.BOT_REACHED_STF, this.roomUnit, this.room, this.room.getItemsAt(t).toArray()); + WiredManager.handle(WiredTriggerType.BOT_REACHED_STF, this.roomUnit, this.room, this.room.getItemsAt(t).toArray()); } }*/ } @@ -205,7 +207,7 @@ public class Bot implements Runnable { .replace(Emulator.getTexts().getValue("wired.variable.roomname", "%roomname%"), this.room.getName()) .replace(Emulator.getTexts().getValue("wired.variable.user_count", "%user_count%"), this.room.getUserCount() + ""); - if(!WiredHandler.handle(WiredTriggerType.SAY_SOMETHING, this.getRoomUnit(), room, new Object[]{ message })) { + if(!WiredManager.triggerUserSays(room, this.getRoomUnit(), message)) { this.talk(message); } @@ -273,7 +275,7 @@ public class Bot implements Runnable { if(PLACEMENT_MESSAGES.length > 0) { String message = PLACEMENT_MESSAGES[Emulator.getRandom().nextInt(PLACEMENT_MESSAGES.length)]; - if (!WiredHandler.handle(WiredTriggerType.SAY_SOMETHING, this.getRoomUnit(), room, new Object[]{message})) { + if (!WiredManager.triggerUserSays(room, this.getRoomUnit(), message)) { this.talk(message); } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/bots/ButlerBot.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/bots/ButlerBot.java index b5acb9f2..0ae110e2 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/bots/ButlerBot.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/bots/ButlerBot.java @@ -3,8 +3,7 @@ package com.eu.habbo.habbohotel.bots; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.rooms.RoomChatMessage; import com.eu.habbo.habbohotel.rooms.RoomUnitStatus; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.plugin.events.bots.BotServerItemEvent; import com.eu.habbo.threading.runnables.RoomUnitGiveHanditem; import com.eu.habbo.threading.runnables.RoomUnitWalkToRoomUnit; @@ -100,7 +99,7 @@ public class ButlerBot extends Bot { .replace("%key%", key) .replace("%username%", serveEvent.habbo.getHabboInfo().getUsername()); - if (!WiredHandler.handle(WiredTriggerType.SAY_SOMETHING, this.getRoomUnit(), this.getRoom(), new Object[]{ botMessage })) { + if (!WiredManager.triggerUserSays(this.getRoom(), this.getRoomUnit(), botMessage)) { bot.talk(botMessage); } } @@ -128,7 +127,7 @@ public class ButlerBot extends Bot { this.getRoom().giveHandItem(serveEvent.habbo, serveEvent.itemId); String msg = Emulator.getTexts().getValue("bots.butler.given").replace("%key%", keyword).replace("%username%", serveEvent.habbo.getHabboInfo().getUsername()); - if (!WiredHandler.handle(WiredTriggerType.SAY_SOMETHING, this.getRoomUnit(), this.getRoom(), new Object[]{msg})) { + if (!WiredManager.triggerUserSays(this.getRoom(), this.getRoomUnit(), msg)) { this.talk(msg); } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/campaign/calendar/CalendarManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/campaign/calendar/CalendarManager.java index bfa1de13..1336f88f 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/campaign/calendar/CalendarManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/campaign/calendar/CalendarManager.java @@ -9,10 +9,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.sql.*; +import java.util.*; import java.util.Date; -import java.util.Map; -import java.util.Objects; -import java.util.Set; import static java.time.temporal.ChronoUnit.DAYS; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/CatalogPage.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/CatalogPage.java index 8a1a4865..6d36c279 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/CatalogPage.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/CatalogPage.java @@ -71,7 +71,7 @@ public abstract class CatalogPage implements Comparable, ISerialize if (!set.getString("includes").isEmpty()) { for (String id : set.getString("includes").split(";")) { try { - this.included.add(Integer.parseInt(id)); + this.included.add(Integer.valueOf(id)); } catch (Exception e) { LOGGER.error("Caught exception", e); LOGGER.error("Failed to parse includes column value of ({}) for catalog page ({})", id, this.id); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/marketplace/MarketPlace.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/marketplace/MarketPlace.java index 631658b3..be96d0eb 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/marketplace/MarketPlace.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/marketplace/MarketPlace.java @@ -26,7 +26,7 @@ import java.util.ArrayList; import java.util.List; -public final class MarketPlace { +public class MarketPlace { private static final Logger LOGGER = LoggerFactory.getLogger(MarketPlace.class); //Configuration. Loaded from database & updated accordingly. @@ -142,9 +142,10 @@ public final class MarketPlace { case 2: query += " ORDER BY minPrice ASC"; break; - case 1: default: + case 1: query += " ORDER BY minPrice DESC"; + break; } query += ")"; @@ -365,13 +366,15 @@ public final class MarketPlace { THashSet offers = new THashSet<>(); offers.addAll(client.getHabbo().getInventory().getMarketplaceItems()); - for (MarketPlaceOffer offer : offers) { - if (offer.getState().equals(MarketPlaceState.SOLD)) { - client.getHabbo().getInventory().removeMarketplaceOffer(offer); - credits += offer.getPrice(); - removeUser(offer); - offer.needsUpdate(true); - Emulator.getThreading().run(offer); + synchronized (client.getHabbo().getInventory()) { + for (MarketPlaceOffer offer : offers) { + if (offer.getState().equals(MarketPlaceState.SOLD)) { + client.getHabbo().getInventory().removeMarketplaceOffer(offer); + credits += offer.getPrice(); + removeUser(offer); + offer.needsUpdate(true); + Emulator.getThreading().run(offer); + } } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/marketplace/MarketPlaceOffer.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/marketplace/MarketPlaceOffer.java index ac5ae45f..37bd2ab1 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/marketplace/MarketPlaceOffer.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/marketplace/MarketPlaceOffer.java @@ -16,8 +16,8 @@ public class MarketPlaceOffer implements Runnable { public int avarage; public int count; private int offerId; - private final Item baseItem; - private final int itemId; + private Item baseItem; + private int itemId; private int price; private int limitedStack; private int limitedNumber; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/AboutCommand.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/AboutCommand.java index 5c37bf39..1b5a7c2e 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/AboutCommand.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/AboutCommand.java @@ -15,7 +15,7 @@ public class AboutCommand extends Command { } public static String credits = "Arcturus Morningstar is an opensource project based on Arcturus By TheGeneral \n" + "The Following people have all contributed to this emulator:\n" + - " TheGeneral\n Beny\n Alejandro\n Capheus\n Skeletor\n Harmonic\n Mike\n Remco\n zGrav \n Quadral \n Harmony\n Swirny\n ArpyAge\n Mikkel\n Rodolfo\n Rasmus\n Kitt Mustang\n Snaiker\n nttzx\n necmi\n Dome\n Jose Flores\n Cam\n Oliver\n Narzo\n Tenshie\n MartenM\n Ridge\n SenpaiDipper\n Snaiker\n Thijmen\n DuckieTM"; + " TheGeneral\n Beny\n Alejandro\n Capheus\n Skeletor\n Harmonic\n Mike\n Remco\n zGrav \n Quadral \n Harmony\n Swirny\n ArpyAge\n Mikkel\n Rodolfo\n Rasmus\n Kitt Mustang\n Snaiker\n nttzx\n necmi\n Dome\n Jose Flores\n Cam\n Oliver\n Narzo\n Tenshie\n MartenM\n Ridge\n SenpaiDipper\n Snaiker\n Thijmen"; @Override public boolean handle(GameClient gameClient, String[] params) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/CommandHandler.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/CommandHandler.java index 55a6a120..08eb4f6e 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/CommandHandler.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/CommandHandler.java @@ -191,7 +191,7 @@ public class CommandHandler { addCommand(new ControlCommand()); addCommand(new CoordsCommand()); addCommand(new CreditsCommand()); - addCommand(new DanceCommand()); + addCommand(new DanceCommand()); addCommand(new DiagonalCommand()); addCommand(new DisconnectCommand()); addCommand(new EjectAllCommand()); @@ -231,7 +231,7 @@ public class CommandHandler { addCommand(new MutePetsCommand()); addCommand(new PetInfoCommand()); addCommand(new PickallCommand()); - addCommand(new PingCommand()); + addCommand(new PingCommand()); addCommand(new PixelCommand()); addCommand(new PluginsCommand()); addCommand(new PointsCommand()); @@ -296,6 +296,7 @@ public class CommandHandler { addCommand(new AddYoutubePlaylistCommand()); addCommand(new SoftKickCommand()); addCommand(new SubscriptionCommand()); + addCommand(new UpdateChatBubblesCommand()); addCommand(new TestCommand()); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/InvisibleCommand.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/InvisibleCommand.java index e4337358..03ba22d1 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/InvisibleCommand.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/InvisibleCommand.java @@ -4,8 +4,7 @@ import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.gameclients.GameClient; import com.eu.habbo.habbohotel.rooms.RoomLayout; import com.eu.habbo.habbohotel.rooms.RoomUnit; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserRemoveComposer; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer; import com.eu.habbo.messages.outgoing.rooms.users.RoomUsersComposer; @@ -31,7 +30,7 @@ public class InvisibleCommand extends Command { roomUnit.getRoom().sendComposer(new RoomUsersComposer(gameClient.getHabbo()).compose()); roomUnit.getRoom().sendComposer(new RoomUserStatusComposer(roomUnit).compose()); - WiredHandler.handle(WiredTriggerType.ENTER_ROOM, roomUnit, roomUnit.getRoom(), null); + WiredManager.triggerUserEntersRoom(roomUnit.getRoom(), roomUnit); roomUnit.getRoom().habboEntered(gameClient.getHabbo()); gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.succes.cmd_invisible.updated.back")); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/PetInfoCommand.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/PetInfoCommand.java index 74150280..b368c11c 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/PetInfoCommand.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/PetInfoCommand.java @@ -24,7 +24,8 @@ public class PetInfoCommand extends Command { @Override public boolean execute(int a, Pet pet) { if (pet.getName().equalsIgnoreCase(name)) { - gameClient.getHabbo().alert(Emulator.getTexts().getValue("commands.generic.cmd_pet_info.title") + ": " + pet.getName() + "\r\n" + + gameClient.getHabbo().alert("" + + Emulator.getTexts().getValue("commands.generic.cmd_pet_info.title") + ": " + pet.getName() + "\r\n" + Emulator.getTexts().getValue("generic.pet.id") + ": " + pet.getId() + "\r" + Emulator.getTexts().getValue("generic.pet.name") + ": " + pet.getName() + "\r" + Emulator.getTexts().getValue("generic.pet.age") + ": " + pet.daysAlive() + " " + Emulator.getTexts().getValue("generic.pet.days.alive") + "\r" + @@ -37,7 +38,7 @@ public class PetInfoCommand extends Command { Emulator.getTexts().getValue("generic.pet.level.thirst") + ": " + pet.levelThirst + "\r" + Emulator.getTexts().getValue("generic.pet.level.hunger") + ": " + pet.levelHunger + "\r" + Emulator.getTexts().getValue("generic.pet.current_action") + ": " + (pet.getTask() == null ? Emulator.getTexts().getValue("generic.nothing") : pet.getTask().name()) + "\r" + - Emulator.getTexts().getValue("generic.can.walk") + ": " + (pet.getRoomUnit().canWalk() ? Emulator.getTexts().getValue("generic.yes") : Emulator.getTexts().getValue("generic.no")) + Emulator.getTexts().getValue("generic.can.walk") + ": " + (pet.getRoomUnit().canWalk() ? Emulator.getTexts().getValue("generic.yes") : Emulator.getTexts().getValue("generic.no")) + "" ); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/PixelCommand.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/PixelCommand.java index 11a7eb99..327dc4e9 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/PixelCommand.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/PixelCommand.java @@ -20,11 +20,11 @@ public class PixelCommand extends Command { if (Integer.parseInt(params[2]) != 0) { habbo.givePixels(Integer.parseInt(params[2])); if (habbo.getHabboInfo().getCurrentRoom() != null) - habbo.whisper(Emulator.getTexts().getValue("commands.generic.cmd_duckets.received").replace("%amount%", Integer.parseInt(params[2]) + ""), RoomChatMessageBubbles.ALERT); + habbo.whisper(Emulator.getTexts().getValue("commands.generic.cmd_duckets.received").replace("%amount%", Integer.valueOf(params[2]) + ""), RoomChatMessageBubbles.ALERT); else - habbo.alert(Emulator.getTexts().getValue("commands.generic.cmd_duckets.received").replace("%amount%", Integer.parseInt(params[2]) + "")); + habbo.alert(Emulator.getTexts().getValue("commands.generic.cmd_duckets.received").replace("%amount%", Integer.valueOf(params[2]) + "")); - gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.succes.cmd_duckets.send").replace("%amount%", Integer.parseInt(params[2]) + "").replace("%user%", params[1]), RoomChatMessageBubbles.ALERT); + gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.succes.cmd_duckets.send").replace("%amount%", Integer.valueOf(params[2]) + "").replace("%user%", params[1]), RoomChatMessageBubbles.ALERT); } else { gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_duckets.invalid_amount"), RoomChatMessageBubbles.ALERT); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/RedeemCommand.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/RedeemCommand.java index 663e1f65..9f2632f3 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/RedeemCommand.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/RedeemCommand.java @@ -85,7 +85,7 @@ public class RedeemCommand extends Command { if (pixels > 0) { message[0] += ", " + Emulator.getTexts().getValue("generic.pixels"); - message[0] += ": " + pixels; + message[0] += ": " + pixels + ""; } if (!points.isEmpty()) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/SetSpeedCommand.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/SetSpeedCommand.java index 46d52be9..ab9c1fe2 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/SetSpeedCommand.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/SetSpeedCommand.java @@ -26,27 +26,14 @@ public class SetSpeedCommand extends Command { return true; } - // First check against the config bounds - int configMax = Emulator.getConfig().getInt("hotel.rollers.speed.maximum"); - if (newSpeed < -1 || newSpeed > configMax) { + if (newSpeed < -1 || newSpeed > Emulator.getConfig().getInt("hotel.rollers.speed.maximum")) { gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_setspeed.bounds"), RoomChatMessageBubbles.ALERT); return true; } - // Enforce maximum speed of 10 regardless of config. - if (newSpeed > 10) { - newSpeed = 10; - gameClient.getHabbo().whisper("Speed cannot be set above 10. Setting speed to 10.", RoomChatMessageBubbles.ALERT); - } - room.setRollerSpeed(newSpeed); - gameClient.getHabbo().whisper( - Emulator.getTexts().getValue("commands.succes.cmd_setspeed") - .replace("%oldspeed%", oldSpeed + "") - .replace("%newspeed%", newSpeed + ""), - RoomChatMessageBubbles.ALERT - ); + gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.succes.cmd_setspeed").replace("%oldspeed%", oldSpeed + "").replace("%newspeed%", newSpeed + ""), RoomChatMessageBubbles.ALERT); return true; } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/TestCommand.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/TestCommand.java index 6c9eceab..a6fb01a8 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/TestCommand.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/TestCommand.java @@ -31,9 +31,9 @@ public class TestCommand extends Command { message.appendString(""); } } else if (data[0].equals("i")) { - message.appendInt(Integer.parseInt(data[1])); + message.appendInt(Integer.valueOf(data[1])); } else if (data[0].equalsIgnoreCase("by")) { - message.appendByte(Integer.parseInt(data[1])); + message.appendByte(Integer.valueOf(data[1])); } else if (data[0].equalsIgnoreCase("sh")) { message.appendShort(Integer.parseInt(data[1])); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/UpdateChatBubblesCommand.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/UpdateChatBubblesCommand.java new file mode 100644 index 00000000..d3be00ba --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/UpdateChatBubblesCommand.java @@ -0,0 +1,20 @@ +package com.eu.habbo.habbohotel.commands; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.gameclients.GameClient; +import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles; + +public class UpdateChatBubblesCommand extends Command { + + public UpdateChatBubblesCommand() { + super("cmd_update_chat_bubbles", Emulator.getTexts().getValue("commands.keys.cmd_update_chat_bubbles").split(";")); + } + + @Override + public boolean handle(GameClient gameClient, String[] params) { + RoomChatMessageBubbles.removeDynamicBubbles(); + Emulator.getGameEnvironment().getRoomChatBubbleManager().reload(); + gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.success.cmd_update_chat_bubbles"), RoomChatMessageBubbles.ALERT); + return true; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClient.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClient.java index 95202eb4..61c219cc 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClient.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClient.java @@ -7,6 +7,7 @@ import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.MessageComposer; +import com.eu.habbo.plugin.events.emulator.OutgoingPacketEvent; import io.netty.channel.Channel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,7 +21,8 @@ public class GameClient { private final Channel channel; private final HabboEncryption encryption; - private final LatencyTracker latencyTracker; + private final LatencyTracker latencyTracker; + private Habbo habbo; private boolean handshakeFinished; private String machineId = ""; @@ -37,7 +39,7 @@ public class GameClient { Emulator.getCrypto().getModulus(), Emulator.getCrypto().getPrivateExponent()) : null; - this.latencyTracker = new LatencyTracker(); + this.latencyTracker = new LatencyTracker(); } public Channel getChannel() { @@ -47,8 +49,10 @@ public class GameClient { public HabboEncryption getEncryption() { return encryption; } - - public LatencyTracker getLatencyTracker() { return latencyTracker; } + + public LatencyTracker getLatencyTracker() { + return latencyTracker; + } public Habbo getHabbo() { return this.habbo; @@ -88,6 +92,17 @@ public class GameClient { return; } + OutgoingPacketEvent event = new OutgoingPacketEvent(this.habbo, response.getComposer(), response); + Emulator.getPluginManager().fireEvent(event); + + if (event.isCancelled()) { + return; + } + + if (event.hasCustomMessage()) { + response = event.getCustomMessage(); + } + this.channel.write(response, this.channel.voidPromise()); this.channel.flush(); } @@ -100,14 +115,25 @@ public class GameClient { return; } + OutgoingPacketEvent event = new OutgoingPacketEvent(this.habbo, response.getComposer(), response); + Emulator.getPluginManager().fireEvent(event); + + if (event.isCancelled()) { + continue; + } + + if (event.hasCustomMessage()) { + response = event.getCustomMessage(); + } + this.channel.write(response); } this.channel.flush(); } } - - public void sendKeepAlive() { + + public void sendKeepAlive() { if (this.channel != null && this.channel.isOpen()) { this.channel.writeAndFlush(new ServerMessage(-1)); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/games/Game.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/games/Game.java index 337649fc..de1ec247 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/games/Game.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/games/Game.java @@ -5,13 +5,11 @@ import com.eu.habbo.habbohotel.achievements.AchievementManager; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredHighscore; import com.eu.habbo.habbohotel.items.interactions.games.InteractionGameTimer; import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredBlob; -import com.eu.habbo.habbohotel.items.interactions.wired.triggers.WiredTriggerTeamLoses; -import com.eu.habbo.habbohotel.items.interactions.wired.triggers.WiredTriggerTeamWins; +import com.eu.habbo.habbohotel.wired.highscores.WiredHighscoreDataEntry; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboItem; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.highscores.WiredHighscoreDataEntry; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.outgoing.guides.GuideSessionPartnerIsPlayingComposer; import com.eu.habbo.plugin.Event; import com.eu.habbo.plugin.events.games.GameHabboJoinEvent; @@ -153,7 +151,7 @@ public abstract class Game implements Runnable { if (winningTeam != null) { for (GamePlayer player : winningTeam.getMembers()) { - WiredHandler.handleCustomTrigger(WiredTriggerTeamWins.class, player.getHabbo().getRoomUnit(), this.room, new Object[]{this}); + WiredManager.triggerTeamWins(this.room, player.getHabbo().getRoomUnit()); Habbo winner = player.getHabbo(); if (winner != null) { @@ -171,7 +169,7 @@ public abstract class Game implements Runnable { if (team == winningTeam) continue; for (GamePlayer player : team.getMembers()) { - WiredHandler.handleCustomTrigger(WiredTriggerTeamLoses.class, player.getHabbo().getRoomUnit(), this.room, new Object[]{this}); + WiredManager.triggerTeamLoses(this.room, player.getHabbo().getRoomUnit()); } if (team.getMembers().size() > 0 && team.getTotalScore() > 0) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/games/GamePlayer.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/games/GamePlayer.java index 672ceaa2..608bb448 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/games/GamePlayer.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/games/GamePlayer.java @@ -1,15 +1,14 @@ package com.eu.habbo.habbohotel.games; import com.eu.habbo.habbohotel.users.Habbo; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; public class GamePlayer { private final Habbo habbo; - private final GameTeamColors teamColor; + private GameTeamColors teamColor; private int score; @@ -41,7 +40,7 @@ public class GamePlayer { this.wiredScore += amount; } - WiredHandler.handle(WiredTriggerType.SCORE_ACHIEVED, this.habbo.getRoomUnit(), this.habbo.getHabboInfo().getCurrentRoom(), new Object[]{this.habbo.getHabboInfo().getCurrentRoom().getGame(this.habbo.getHabboInfo().getCurrentGame()).getTeamForHabbo(this.habbo).getTotalScore(), amount}); + WiredManager.triggerScoreAchieved(this.habbo.getHabboInfo().getCurrentRoom(), this.habbo.getRoomUnit(), this.score); } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/games/football/FootballGame.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/games/football/FootballGame.java index b3f902c6..addadf8b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/games/football/FootballGame.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/games/football/FootballGame.java @@ -15,7 +15,7 @@ import java.util.Map; public class FootballGame extends Game { - private final Room room; + private Room room; public FootballGame(Room room) { super(null, null, room, true); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/hotelview/HallOfFameWinner.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/hotelview/HallOfFameWinner.java index 6f5d15f2..998de6f2 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/hotelview/HallOfFameWinner.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/hotelview/HallOfFameWinner.java @@ -5,16 +5,16 @@ import java.sql.SQLException; public class HallOfFameWinner implements Comparable { - private final int id; + private int id; - private final String username; + private String username; - private final String look; + private String look; - private final int points; + private int points; public HallOfFameWinner(ResultSet set) throws SQLException { this.id = set.getInt("id"); 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 b1481dd4..12baf283 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 @@ -3,10 +3,7 @@ package com.eu.habbo.habbohotel.items; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.items.interactions.*; import com.eu.habbo.habbohotel.items.interactions.games.InteractionGameTimer; -import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.InteractionBattleBanzaiPuck; -import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.InteractionBattleBanzaiSphere; -import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.InteractionBattleBanzaiTeleporter; -import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.InteractionBattleBanzaiTile; +import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.*; import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.gates.InteractionBattleBanzaiGateBlue; import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.gates.InteractionBattleBanzaiGateGreen; import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.gates.InteractionBattleBanzaiGateRed; @@ -48,12 +45,13 @@ import com.eu.habbo.habbohotel.items.interactions.totems.InteractionTotemPlanet; import com.eu.habbo.habbohotel.items.interactions.wired.conditions.*; import com.eu.habbo.habbohotel.items.interactions.wired.effects.*; import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredBlob; +import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraOrEval; import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRandom; import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraUnseen; +import com.eu.habbo.habbohotel.wired.highscores.WiredHighscoreManager; import com.eu.habbo.habbohotel.items.interactions.wired.triggers.*; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboItem; -import com.eu.habbo.habbohotel.wired.highscores.WiredHighscoreManager; import com.eu.habbo.messages.outgoing.inventory.AddHabboItemComposer; import com.eu.habbo.plugin.events.emulator.EmulatorLoadItemsManagerEvent; import com.eu.habbo.threading.runnables.QueryDeleteHabboItem; @@ -170,6 +168,7 @@ public class ItemManager { this.interactionsList.add(new ItemInteraction("youtube", InteractionYoutubeTV.class)); this.interactionsList.add(new ItemInteraction("jukebox", InteractionJukeBox.class)); this.interactionsList.add(new ItemInteraction("switch", InteractionSwitch.class)); + this.interactionsList.add(new ItemInteraction("switch_remote_control", InteractionSwitchRemoteControl.class)); this.interactionsList.add(new ItemInteraction("fx_box", InteractionFXBox.class)); this.interactionsList.add(new ItemInteraction("blackhole", InteractionBlackHole.class)); this.interactionsList.add(new ItemInteraction("effect_toggle", InteractionEffectToggle.class)); @@ -191,6 +190,7 @@ public class ItemManager { this.interactionsList.add(new ItemInteraction("crackable_subscription_box", InteractionRedeemableSubscriptionBox.class)); this.interactionsList.add(new ItemInteraction("random_state", InteractionRandomState.class)); this.interactionsList.add(new ItemInteraction("vendingmachine_no_sides", InteractionNoSidesVendingMachine.class)); + this.interactionsList.add(new ItemInteraction("tile_walkmagic", InteractionTileWalkMagic.class)); this.interactionsList.add(new ItemInteraction("game_timer", InteractionGameTimer.class)); @@ -268,11 +268,13 @@ public class ItemManager { 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_date_rng_active", WiredConditionDateRangeActive.class)); + this.interactionsList.add(new ItemInteraction("wf_cnd_valid_moves", WiredConditionMovementValidation.class)); this.interactionsList.add(new ItemInteraction("wf_xtra_random", WiredExtraRandom.class)); this.interactionsList.add(new ItemInteraction("wf_xtra_unseen", WiredExtraUnseen.class)); this.interactionsList.add(new ItemInteraction("wf_blob", WiredBlob.class)); + this.interactionsList.add(new ItemInteraction("wf_xtra_or_eval", WiredExtraOrEval.class)); this.interactionsList.add(new ItemInteraction("wf_highscore", InteractionWiredHighscore.class)); @@ -344,9 +346,7 @@ public class ItemManager { this.interactionsList.add(new ItemInteraction("totem_leg", InteractionTotemLegs.class)); this.interactionsList.add(new ItemInteraction("totem_head", InteractionTotemHead.class)); this.interactionsList.add(new ItemInteraction("totem_planet", InteractionTotemPlanet.class)); - - this.interactionsList.add(new ItemInteraction("tile_walkmagic", InteractionTileWalkMagic.class)); - } + } public void addItemInteraction(ItemInteraction itemInteraction) { @@ -519,20 +519,6 @@ public class ItemManager { } } - public HabboItem createItemNoSql(int habboId, Item item, int limitedStack, int limitedSells, String extraData) { - Class itemClass = item.getInteractionType().getType(); - int id = new Random().nextInt(10000000) + 1000000000; - if (itemClass != null) { - try { - return itemClass.getDeclaredConstructor(int.class, int.class, Item.class, String.class, int.class, int.class).newInstance(id, habboId, item, extraData, limitedStack, limitedSells); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - return new InteractionDefault(id, habboId, item, extraData, limitedStack, limitedSells); - } - } - return null; - } - public void addNewUserGift(NewUserGift gift) { this.newuserGifts.put(gift.getId(), gift); } @@ -784,7 +770,6 @@ public class ItemManager { return null; } - public YoutubeManager getYoutubeManager() { return this.youtubeManager; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/SoundTrack.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/SoundTrack.java index b214f0fc..53430c13 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/SoundTrack.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/SoundTrack.java @@ -4,12 +4,12 @@ import java.sql.ResultSet; import java.sql.SQLException; public class SoundTrack { - private final int id; - private final String name; - private final String author; - private final String code; - private final String data; - private final int length; + private int id; + private String name; + private String author; + private String code; + private String data; + private int length; public SoundTrack(ResultSet set) throws SQLException { this.id = set.getInt("id"); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/YoutubeManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/YoutubeManager.java index 0ca98b54..17e2f1cf 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/YoutubeManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/YoutubeManager.java @@ -16,10 +16,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.net.URI; import java.net.URL; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; +import java.sql.*; import java.time.Duration; import java.util.ArrayList; import java.util.concurrent.ExecutorService; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionBackgroundToner.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionBackgroundToner.java index 072eb40b..ab6bf36e 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionBackgroundToner.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionBackgroundToner.java @@ -28,10 +28,10 @@ public class InteractionBackgroundToner extends HabboItem { serverMessage.appendInt(4); if (this.getExtradata().split(":").length == 4) { String[] colorData = this.getExtradata().split(":"); - serverMessage.appendInt(Integer.parseInt(colorData[0])); - serverMessage.appendInt(Integer.parseInt(colorData[1])); - serverMessage.appendInt(Integer.parseInt(colorData[2])); - serverMessage.appendInt(Integer.parseInt(colorData[3])); + serverMessage.appendInt(Integer.valueOf(colorData[0])); + serverMessage.appendInt(Integer.valueOf(colorData[1])); + serverMessage.appendInt(Integer.valueOf(colorData[2])); + serverMessage.appendInt(Integer.valueOf(colorData[3])); } else { serverMessage.appendInt(0); serverMessage.appendInt(126); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionBuildArea.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionBuildArea.java index b0d2a23a..015ab451 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionBuildArea.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionBuildArea.java @@ -46,7 +46,7 @@ public class InteractionBuildArea extends InteractionCustomValues { } }; - private final THashSet tiles; + private THashSet tiles; public InteractionBuildArea(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem, defaultValues); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionCannon.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionCannon.java index d3a24ff4..00ea3d9b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionCannon.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionCannon.java @@ -4,6 +4,7 @@ import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.gameclients.GameClient; import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.messages.ServerMessage; @@ -12,6 +13,7 @@ import com.eu.habbo.threading.runnables.CannonResetCooldownAction; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.List; public class InteractionCannon extends HabboItem { public boolean cooldown = false; @@ -46,30 +48,33 @@ public class InteractionCannon extends HabboItem { @Override public void onClick(GameClient client, Room room, Object[] objects) throws Exception { + if (client != null) { + super.onClick(client, room, objects); + } + if (room == null) return; - if (client != null) { - RoomUnit roomUnit = client.getHabbo().getRoomUnit(); - int rotation = this.getRotation(); - int dx = (rotation == 4) ? -1 : (rotation == 0) ? 2 : 0; - int dy = (rotation == 2) ? 2 : (rotation == 6) ? -1 : 0; - int x = this.getX() + dx; - int y = this.getY() + dy; - if (roomUnit.getX() == x && roomUnit.getY() == y) { - this.shoot(room, client); - } - } else { - this.shoot(room, null); - } - } + RoomTile tile = room.getLayout().getTile(this.getX(), this.getY()); + RoomTile fuseTile = this.getRotation() >= 4 ? tile : room.getLayout().getTileInFront(tile, ((this.getRotation() % 2) + 2) % 8); + List tiles = room.getLayout().getTilesAround(fuseTile); + tiles.remove(room.getLayout().getTileInFront(tile, (this.getRotation() + (this.getRotation() >= 4 ? -1 : 0)) % 8)); + tiles.remove(room.getLayout().getTileInFront(tile, (this.getRotation() + (this.getRotation() >= 4 ? 5 : 4)) % 8)); - private void shoot(Room room, GameClient client) { - this.cooldown = true; - this.setExtradata(this.getExtradata().equals("1") ? "0" : "1"); - room.updateItemState(this); - Emulator.getThreading().run(new CannonKickAction(this, room, client), 750); - Emulator.getThreading().run(new CannonResetCooldownAction(this), 2000); + if ((client == null || (tiles.contains(client.getHabbo().getRoomUnit().getCurrentLocation())) && client.getHabbo().getRoomUnit().canWalk()) && !this.cooldown) { + if (client != null) { + client.getHabbo().getRoomUnit().setCanWalk(false); + client.getHabbo().getRoomUnit().setGoalLocation(client.getHabbo().getRoomUnit().getCurrentLocation()); + client.getHabbo().getRoomUnit().lookAtPoint(fuseTile); + client.getHabbo().getRoomUnit().statusUpdate(true); + } + + this.cooldown = true; + this.setExtradata(this.getExtradata().equals("1") ? "0" : "1"); + room.updateItemState(this); + Emulator.getThreading().run(new CannonKickAction(this, room, client), 750); + Emulator.getThreading().run(new CannonResetCooldownAction(this), 2000); + } } @Override diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionCrackable.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionCrackable.java index 2e7873c2..055d9faa 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionCrackable.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionCrackable.java @@ -39,7 +39,7 @@ public class InteractionCrackable extends HabboItem { serverMessage.appendInt(7 + (this.isLimited() ? 256 : 0)); serverMessage.appendString(Emulator.getGameEnvironment().getItemManager().calculateCrackState(Integer.parseInt(this.getExtradata()), Emulator.getGameEnvironment().getItemManager().getCrackableCount(this.getBaseItem().getId()), this.getBaseItem()) + ""); - serverMessage.appendInt(Integer.parseInt(this.getExtradata())); + serverMessage.appendInt(Integer.valueOf(this.getExtradata())); serverMessage.appendInt(Emulator.getGameEnvironment().getItemManager().getCrackableCount(this.getBaseItem().getId())); super.serializeExtradata(serverMessage); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionDefault.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionDefault.java index d234d471..4339706d 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionDefault.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionDefault.java @@ -143,7 +143,8 @@ public class InteractionDefault extends HabboItem { int nextEffectF = 0; if (objects != null && objects.length == 2) { - if (objects[0] instanceof RoomTile goalTile && objects[1] instanceof RoomTile) { + if (objects[0] instanceof RoomTile && objects[1] instanceof RoomTile) { + RoomTile goalTile = (RoomTile) objects[0]; HabboItem topItem = room.getTopItemAt(goalTile.x, goalTile.y, (objects[0] != objects[1]) ? this : null); if (topItem != null && (topItem.getBaseItem().getEffectM() == this.getBaseItem().getEffectM() || topItem.getBaseItem().getEffectF() == this.getBaseItem().getEffectF())) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionEffectTile.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionEffectTile.java index ac10dcc7..c3af560c 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionEffectTile.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionEffectTile.java @@ -8,8 +8,7 @@ import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.rooms.RoomUnitType; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboGender; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import java.sql.ResultSet; import java.sql.SQLException; @@ -38,7 +37,7 @@ public class InteractionEffectTile extends InteractionPressurePlate { Emulator.getThreading().run(() -> updateState(room), 100); if(objects != null && objects.length > 0) { - WiredHandler.handle(WiredTriggerType.WALKS_OFF_FURNI, roomUnit, room, new Object[]{this}); + WiredManager.triggerUserWalksOff(room, roomUnit, this); } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionEffectVendingMachine.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionEffectVendingMachine.java index 49261af4..ba49ed7a 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionEffectVendingMachine.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionEffectVendingMachine.java @@ -1,8 +1,7 @@ package com.eu.habbo.habbohotel.items.interactions; 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.rooms.*; import java.sql.ResultSet; import java.sql.SQLException; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionMuteArea.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionMuteArea.java index d8d676da..beed1131 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionMuteArea.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionMuteArea.java @@ -43,7 +43,7 @@ public class InteractionMuteArea extends InteractionCustomValues { } }; - private final THashSet tiles; + private THashSet tiles; public InteractionMuteArea(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem, defaultValues); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionObstacle.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionObstacle.java index 00bec0ac..a63dcad8 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionObstacle.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionObstacle.java @@ -17,7 +17,7 @@ import java.util.Objects; public class InteractionObstacle extends HabboItem implements ICycleable { - private final THashSet middleTiles; + private THashSet middleTiles; public InteractionObstacle(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionOneWayGate.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionOneWayGate.java index 17e661d4..346290f0 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionOneWayGate.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionOneWayGate.java @@ -7,8 +7,7 @@ import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.HabboItem; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.outgoing.rooms.items.ItemIntStateComposer; import com.eu.habbo.threading.runnables.RoomUnitWalkToLocation; @@ -89,7 +88,9 @@ public class InteractionOneWayGate extends HabboItem { unit.setGoalLocation(tile); Emulator.getThreading().run(new RoomUnitWalkToLocation(unit, tile, room, onFail, onFail)); - Emulator.getThreading().run(() -> WiredHandler.handle(WiredTriggerType.WALKS_ON_FURNI, unit, room, new Object[]{this}), 500); + Emulator.getThreading().run(() -> { + WiredManager.triggerUserWalksOn(room, unit, this); + }, 500); }); onFail.add(() -> { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionVoteCounter.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionVoteCounter.java index c4aee567..32f4b51f 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionVoteCounter.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionVoteCounter.java @@ -17,7 +17,7 @@ public class InteractionVoteCounter extends HabboItem { private boolean frozen; private int votes; - private final List votedUsers; + private List votedUsers; public InteractionVoteCounter(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWater.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWater.java index c69df8b0..d697ee4a 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWater.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWater.java @@ -274,7 +274,8 @@ public class InteractionWater extends InteractionDefault { private boolean isValidForMask(Room room, int x, int y, double z, boolean corner) { for (HabboItem item : room.getItemsAt(x, y, z)) { - if (item instanceof InteractionWater water) { + if (item instanceof InteractionWater) { + InteractionWater water = (InteractionWater) item; // Take out picked up water from the recalculation. if (!water.isInRoom) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWired.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWired.java index f53429eb..8250b6ea 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWired.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWired.java @@ -15,12 +15,54 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +/** + * Base abstract class for all wired furniture items (triggers, effects, conditions, extras). + *

+ * The wired system allows room owners to create automated behaviors in their rooms. + * It consists of: + *

    + *
  • Triggers ({@link InteractionWiredTrigger}) - Events that start the wired chain
  • + *
  • Conditions ({@link InteractionWiredCondition}) - Requirements that must be met
  • + *
  • Effects ({@link InteractionWiredEffect}) - Actions that are executed
  • + *
  • Extras ({@link InteractionWiredExtra}) - Modifiers like random selection
  • + *
+ *

+ *

+ * Wired items at the same tile coordinates form a "stack" and execute together. + * The {@link com.eu.habbo.habbohotel.wired.core.WiredManager} orchestrates execution. + *

+ *

+ * Key features: + *

    + *
  • Cooldown system to prevent spam triggering
  • + *
  • Per-user execution caching with automatic cleanup
  • + *
  • JSON-based data persistence via {@link #getWiredData()}
  • + *
+ *

+ * + * @see com.eu.habbo.habbohotel.wired.core.WiredManager + * @see com.eu.habbo.habbohotel.rooms.RoomSpecialTypes + */ public abstract class InteractionWired extends InteractionDefault { private static final Logger LOGGER = LoggerFactory.getLogger(InteractionWired.class); + + /** + * Maximum number of entries in the user execution cache to prevent memory leaks. + */ + private static final int MAX_USER_CACHE_SIZE = 500; + + /** + * Cache entries older than this (in milliseconds) will be cleaned up. + * Default: 5 minutes + */ + private static final long CACHE_EXPIRY_MS = 5 * 60 * 1000; + private long cooldown; - private final HashMap userExecutionCache = new HashMap<>(); + private final ConcurrentHashMap userExecutionCache = new ConcurrentHashMap<>(); InteractionWired(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -32,6 +74,14 @@ public abstract class InteractionWired extends InteractionDefault { this.setExtradata("0"); } + /** + * Executes this wired item's logic. + * + * @param roomUnit the room unit that triggered this (may be null for non-user triggers) + * @param room the room where this is happening + * @param stuff additional context data passed from the trigger + * @return true if execution was successful, false otherwise + */ public abstract boolean execute(RoomUnit roomUnit, Room room, Object[] stuff); public abstract String getWiredData(); @@ -72,7 +122,7 @@ public abstract class InteractionWired extends InteractionDefault { public abstract void onPickUp(); public void activateBox(Room room) { - this.activateBox(room, null, 0L); + this.activateBox(room, (RoomUnit)null, 0L); } public void activateBox(Room room, RoomUnit roomUnit, long millis) { @@ -126,7 +176,41 @@ public abstract class InteractionWired extends InteractionDefault { } public void addUserExecutionCache(int roomUnitId, long timestamp) { - this.userExecutionCache.put((long)roomUnitId, timestamp); + // Enforce max size limit to prevent memory leaks + if (this.userExecutionCache.size() >= MAX_USER_CACHE_SIZE) { + cleanExpiredCacheEntries(timestamp); + + // If still too large after cleanup, remove oldest entries + if (this.userExecutionCache.size() >= MAX_USER_CACHE_SIZE) { + // Remove approximately 10% of entries + int toRemove = MAX_USER_CACHE_SIZE / 10; + Iterator> iterator = this.userExecutionCache.entrySet().iterator(); + while (iterator.hasNext() && toRemove > 0) { + iterator.next(); + iterator.remove(); + toRemove--; + } + } + } + this.userExecutionCache.put((long) roomUnitId, timestamp); + } + + /** + * Removes cache entries older than CACHE_EXPIRY_MS. + * @param currentTimestamp the current timestamp to compare against + */ + public void cleanExpiredCacheEntries(long currentTimestamp) { + this.userExecutionCache.entrySet().removeIf( + entry -> currentTimestamp - entry.getValue() > CACHE_EXPIRY_MS + ); + } + + /** + * Gets the current size of the user execution cache. + * @return the number of cached entries + */ + public int getUserExecutionCacheSize() { + return this.userExecutionCache.size(); } public static WiredSettings readSettings(ClientMessage packet, boolean isEffect) @@ -159,4 +243,4 @@ public abstract class InteractionWired extends InteractionDefault { settings.setStuffTypeSelectionCode(packet.readInt()); return settings; } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWiredCondition.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWiredCondition.java index ef63491f..97018e8b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWiredCondition.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWiredCondition.java @@ -7,12 +7,21 @@ import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.wired.WiredConditionOperator; import com.eu.habbo.habbohotel.wired.WiredConditionType; +import com.eu.habbo.habbohotel.wired.api.IWiredCondition; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.outgoing.wired.WiredConditionDataComposer; import java.sql.ResultSet; import java.sql.SQLException; -public abstract class InteractionWiredCondition extends InteractionWired { +/** + * Base class for all wired conditions in the game. + *

+ * Conditions are evaluated before effects execute to determine if they should run. + * They now implement {@link IWiredCondition} for the new context-driven architecture. + *

+ */ +public abstract class InteractionWiredCondition extends InteractionWired implements IWiredCondition { public InteractionWiredCondition(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); } @@ -48,5 +57,17 @@ public abstract class InteractionWiredCondition extends InteractionWired { public WiredConditionOperator operator() { return WiredConditionOperator.AND; } + + // ========== IWiredCondition Implementation ========== + + /** + * Evaluates whether this condition passes. + * Subclasses must implement this to define their evaluation logic. + * + * @param ctx the wired context containing event data + * @return true if the condition passes + */ + @Override + public abstract boolean evaluate(WiredContext ctx); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWiredEffect.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWiredEffect.java index e58e3659..d303291d 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWiredEffect.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWiredEffect.java @@ -1,18 +1,60 @@ package com.eu.habbo.habbohotel.items.interactions; +import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.gameclients.GameClient; import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.items.interactions.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.WiredEffectType; +import com.eu.habbo.habbohotel.wired.api.IWiredEffect; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import com.eu.habbo.messages.outgoing.wired.WiredEffectDataComposer; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Collection; +import java.util.function.Predicate; -public abstract class InteractionWiredEffect extends InteractionWired { +/** + * Base class for all wired effects in the game. + *

+ * Wired effects are triggered by {@link InteractionWiredTrigger} when conditions + * (defined by {@link InteractionWiredCondition}) are met. Effects can perform + * various actions like moving furniture, teleporting users, toggling states, etc. + *

+ *

+ * Subclasses must implement: + *

    + *
  • {@link #execute(RoomUnit, Room, Object[])} - The actual effect logic
  • + *
  • {@link #getType()} - Returns the effect type enum
  • + *
  • {@link #saveData(WiredSettings, GameClient)} - Saves configuration from client
  • + *
  • {@link #getWiredData()} - Serializes data for database storage
  • + *
  • {@link #loadWiredData(java.sql.ResultSet, Room)} - Loads data from database
  • + *
  • {@link #serializeWiredData(com.eu.habbo.messages.ServerMessage, Room)} - Sends config to client
  • + *
+ *

+ * + * @see InteractionWiredTrigger + * @see InteractionWiredCondition + * @see com.eu.habbo.habbohotel.wired.core.WiredManager + */ +public abstract class InteractionWiredEffect extends InteractionWired implements IWiredEffect { + + // Common cooldown constants (in milliseconds) + /** No cooldown - effect can trigger as fast as possible */ + public static final long COOLDOWN_NONE = 0L; + /** Default cooldown for most effects */ + public static final long COOLDOWN_DEFAULT = 50L; + /** Cooldown for movement effects (move to, move towards, move away) */ + public static final long COOLDOWN_MOVEMENT = 495L; + /** Cooldown for trigger stacks effect to prevent rapid re-triggering */ + public static final long COOLDOWN_TRIGGER_STACKS = 250L; + /** Cooldown for teleport effect */ + public static final long COOLDOWN_TELEPORT = 500L; + private int delay; public InteractionWiredEffect(ResultSet set, Item baseItem) throws SQLException { @@ -55,8 +97,105 @@ public abstract class InteractionWiredEffect extends InteractionWired { public abstract WiredEffectType getType(); + // ========== IWiredEffect Implementation ========== + + /** + * Executes this effect with the given context. + * Subclasses must implement this to define their effect logic. + * + * @param ctx the wired context containing event data + */ + @Override + public abstract void execute(WiredContext ctx); + + /** + * Returns whether this effect requires an actor (user) to execute. + */ + @Override + public boolean requiresActor() { + return requiresTriggeringUser(); + } + /** + * Indicates whether this effect requires a triggering user (RoomUnit) to execute. + * Effects that require a user will not execute if no user triggered them. + * + * @return true if this effect requires a triggering user, false otherwise + */ public boolean requiresTriggeringUser() { return false; } + + /** + * Gets the room this wired effect is placed in. + * Convenience method to avoid repeated lookups. + * + * @return the Room, or null if not found + */ + protected Room getRoom() { + return Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()); + } + + /** + * Validates and cleans a collection of items, removing those that are no longer valid. + * An item is invalid if: + *
    + *
  • It's null
  • + *
  • Its room ID doesn't match this effect's room ID
  • + *
  • It no longer exists in the room
  • + *
+ * + * @param items the collection of items to validate + * @param the type extending HabboItem + * @return the number of items removed + */ + protected int validateItems(Collection items) { + if (items == null || items.isEmpty()) { + return 0; + } + + Room room = this.getRoom(); + if (room == null) { + int size = items.size(); + items.clear(); + return size; + } + + int roomId = this.getRoomId(); + int sizeBefore = items.size(); + items.removeIf(item -> item == null + || item.getRoomId() != roomId + || room.getHabboItem(item.getId()) == null); + return sizeBefore - items.size(); + } + + /** + * Validates and cleans a collection of items with an additional custom predicate. + * Items matching the predicate will be removed in addition to standard validation. + * + * @param items the collection of items to validate + * @param additionalRemoveCondition additional condition that, if true, causes removal + * @param the type extending HabboItem + * @return the number of items removed + */ + protected int validateItems(Collection items, Predicate additionalRemoveCondition) { + if (items == null || items.isEmpty()) { + return 0; + } + + Room room = this.getRoom(); + if (room == null) { + int size = items.size(); + items.clear(); + return size; + } + + int roomId = this.getRoomId(); + int sizeBefore = items.size(); + items.removeIf(item -> item == null + || item.getRoomId() != roomId + || room.getHabboItem(item.getId()) == null + || additionalRemoveCondition.test(item)); + return sizeBefore - items.size(); + } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWiredHighscore.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWiredHighscore.java index b8a638b2..c02b251a 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWiredHighscore.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWiredHighscore.java @@ -7,8 +7,7 @@ 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.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.habbohotel.wired.highscores.WiredHighscoreClearType; import com.eu.habbo.habbohotel.wired.highscores.WiredHighscoreRow; import com.eu.habbo.habbohotel.wired.highscores.WiredHighscoreScoreType; @@ -97,7 +96,7 @@ public class InteractionWiredHighscore extends HabboItem { } if(client != null && !(objects.length >= 2 && objects[1] instanceof WiredEffectType)) { - WiredHandler.handle(WiredTriggerType.STATE_CHANGED, client.getHabbo().getRoomUnit(), room, new Object[]{this}); + WiredManager.triggerFurniStateChanged(room, client.getHabbo().getRoomUnit(), this); } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWiredTrigger.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWiredTrigger.java index 528c6b80..e6410040 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWiredTrigger.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionWiredTrigger.java @@ -5,13 +5,23 @@ 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.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.api.IWiredTrigger; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; import com.eu.habbo.messages.outgoing.wired.WiredTriggerDataComposer; import java.sql.ResultSet; import java.sql.SQLException; -public abstract class InteractionWiredTrigger extends InteractionWired { +/** + * Base class for all wired triggers in the game. + *

+ * Triggers are the entry points for wired execution. They now implement + * {@link IWiredTrigger} for the new context-driven architecture. + *

+ */ +public abstract class InteractionWiredTrigger extends InteractionWired implements IWiredTrigger { private int delay; protected InteractionWiredTrigger(ResultSet set, Item baseItem) throws SQLException { @@ -57,5 +67,37 @@ public abstract class InteractionWiredTrigger extends InteractionWired { public boolean isTriggeredByRoomUnit() { return false; } + + // ========== IWiredTrigger Implementation ========== + + /** + * Returns the event type this trigger responds to. + * Maps the WiredTriggerType to the new WiredEvent.Type. + * + * @return the event type this trigger responds to + */ + @Override + public WiredEvent.Type listensTo() { + return WiredEvent.Type.fromLegacyType(this.getType()); + } + + /** + * Checks if this trigger matches the given event. + * Subclasses must implement this to define their matching logic. + * + * @param triggerItem the wired trigger furniture item + * @param event the event that occurred + * @return true if this trigger should activate + */ + @Override + public abstract boolean matches(HabboItem triggerItem, WiredEvent event); + + /** + * Returns whether this trigger requires an actor (user) to activate. + */ + @Override + public boolean requiresActor() { + return isTriggeredByRoomUnit(); + } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/InteractionGameTimer.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/InteractionGameTimer.java index 0fbe9e89..e93d5308 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/InteractionGameTimer.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/InteractionGameTimer.java @@ -11,8 +11,7 @@ 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.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.threading.runnables.games.GameTimer; import org.slf4j.Logger; @@ -208,7 +207,7 @@ public class InteractionGameTimer extends HabboItem { this.endGame(room, true); if(wasPaused) { - WiredHandler.handle(WiredTriggerType.GAME_ENDS, null, room, new Object[]{}); + WiredManager.triggerGameEnds(room); } this.createNewGame(room); @@ -218,7 +217,7 @@ public class InteractionGameTimer extends HabboItem { this.isPaused = false; room.updateItem(this); - WiredHandler.handle(WiredTriggerType.GAME_STARTS, null, room, new Object[]{}); + WiredManager.triggerGameStarts(room); if (!this.threadActive) { this.threadActive = true; @@ -255,7 +254,7 @@ public class InteractionGameTimer extends HabboItem { room.updateItem(this); this.createNewGame(room); - WiredHandler.handle(WiredTriggerType.GAME_STARTS, null, room, new Object[]{this}); + WiredManager.triggerGameStarts(room); if (!this.threadActive) { this.threadActive = true; @@ -271,7 +270,7 @@ public class InteractionGameTimer extends HabboItem { } else if (this.isPaused) { this.endGame(room); this.increaseTimer(room); - WiredHandler.handle(WiredTriggerType.GAME_ENDS, null, room, new Object[]{}); + WiredManager.triggerGameEnds(room); } break; @@ -296,7 +295,7 @@ public class InteractionGameTimer extends HabboItem { room.updateItem(this); } this.createNewGame(room); - WiredHandler.handle(WiredTriggerType.GAME_STARTS, null, room, new Object[]{this}); + WiredManager.triggerGameStarts(room); if (!threadActive) { threadActive = true; Emulator.getThreading().run(new GameTimer(this), 1000); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/battlebanzai/InteractionBattleBanzaiTeleporter.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/battlebanzai/InteractionBattleBanzaiTeleporter.java index f57850a5..98a627fe 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/battlebanzai/InteractionBattleBanzaiTeleporter.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/battlebanzai/InteractionBattleBanzaiTeleporter.java @@ -3,19 +3,15 @@ package com.eu.habbo.habbohotel.items.interactions.games.battlebanzai; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.gameclients.GameClient; import com.eu.habbo.habbohotel.items.Item; -import com.eu.habbo.habbohotel.pets.RideablePet; 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.rooms.RoomUserRotation; -import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.threading.runnables.BanzaiRandomTeleport; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; public class InteractionBattleBanzaiTeleporter extends HabboItem { public InteractionBattleBanzaiTeleporter(ResultSet set, Item baseItem) throws SQLException { @@ -60,43 +56,16 @@ public class InteractionBattleBanzaiTeleporter extends HabboItem { public void onWalkOn(RoomUnit roomUnit, Room room, Object[] objects) throws Exception { super.onWalkOn(roomUnit, room, objects); - if (objects.length < 3) { + if(objects.length < 3) { HabboItem target = room.getRoomSpecialTypes().getRandomTeleporter(null, this); - - if (target == null) - return; + if (target == null) return; this.setExtradata("1"); room.updateItemState(this); + roomUnit.removeStatus(RoomUnitStatus.MOVE); + roomUnit.setGoalLocation(roomUnit.getCurrentLocation()); roomUnit.setCanWalk(false); - - Habbo habbo = room.getHabbo(roomUnit); - RoomUserRotation rotation = RoomUserRotation.fromValue(Emulator.getRandom().nextInt(8)); - ArrayList roomUnitsToTeleport = new ArrayList<>(); - if (habbo != null) { - RideablePet pet = habbo.getHabboInfo().getRiding(); - if (pet != null) - roomUnitsToTeleport.add(pet.getRoomUnit()); - } - - roomUnitsToTeleport.add(roomUnit); - - Emulator.getThreading().run(() -> { - target.setExtradata("1"); - room.updateItemState(target); - - for (RoomUnit ru : roomUnitsToTeleport) { - ru.removeStatus(RoomUnitStatus.MOVE); - ru.setCanWalk(false); - - Emulator.getThreading().run(() -> { - this.setExtradata("0"); - room.updateItemState(this); - - new BanzaiRandomTeleport(this, target, ru, room, rotation).run(); - }, 500); - } - }, 500); + Emulator.getThreading().run(new BanzaiRandomTeleport(this, target, roomUnit, room), 500); } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/battlebanzai/gates/InteractionBattleBanzaiGate.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/battlebanzai/gates/InteractionBattleBanzaiGate.java index eab2817f..819a864e 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/battlebanzai/gates/InteractionBattleBanzaiGate.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/battlebanzai/gates/InteractionBattleBanzaiGate.java @@ -25,7 +25,7 @@ public class InteractionBattleBanzaiGate extends InteractionGameGate { @Override public boolean canWalkOn(RoomUnit roomUnit, Room room, Object[] objects) { - return room.getGame(BattleBanzaiGame.class) == null || room.getGame(BattleBanzaiGame.class).state.equals(GameState.IDLE); + return room.getGame(BattleBanzaiGame.class) == null || ((BattleBanzaiGame) room.getGame(BattleBanzaiGame.class)).state.equals(GameState.IDLE); } @Override diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/football/InteractionFootball.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/football/InteractionFootball.java index c897cdac..6bd21167 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/football/InteractionFootball.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/football/InteractionFootball.java @@ -167,7 +167,7 @@ public class InteractionFootball extends InteractionPushable { BigDecimal topItemHeight = BigDecimal.valueOf(topItem.getZ() + topItem.getBaseItem().getHeight()); BigDecimal ballHeight = BigDecimal.valueOf(this.getZ()); - if (topItemHeight.subtract(ballHeight).compareTo(new BigDecimal("1.65")) > 0) { + if (topItemHeight.subtract(ballHeight).compareTo(new BigDecimal(1.65)) > 0) { return false; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/freeze/gates/InteractionFreezeGate.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/freeze/gates/InteractionFreezeGate.java index b2d75882..ec583fde 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/freeze/gates/InteractionFreezeGate.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/games/freeze/gates/InteractionFreezeGate.java @@ -25,7 +25,7 @@ public class InteractionFreezeGate extends InteractionGameGate { @Override public boolean canWalkOn(RoomUnit roomUnit, Room room, Object[] objects) { - return room.getGame(FreezeGame.class) == null || room.getGame(FreezeGame.class).state.equals(GameState.IDLE); + return room.getGame(FreezeGame.class) == null || ((FreezeGame) room.getGame(FreezeGame.class)).state.equals(GameState.IDLE); } @Override diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/interfaces/ConditionalGate.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/interfaces/ConditionalGate.java index 58eea046..3f1b06cb 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/interfaces/ConditionalGate.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/interfaces/ConditionalGate.java @@ -4,5 +4,5 @@ import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; public interface ConditionalGate { - void onRejected(RoomUnit roomUnit, Room room, Object[] objects); + public void onRejected(RoomUnit roomUnit, Room room, Object[] objects); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionDateRangeActive.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionDateRangeActive.java index 142e00d7..0b92d4e6 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionDateRangeActive.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionDateRangeActive.java @@ -7,7 +7,8 @@ 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.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -58,14 +59,20 @@ public class WiredConditionDateRangeActive extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { int time = Emulator.getIntUnixTimestamp(); return this.startDate < time && this.endDate >= time; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.startDate, this.endDate )); @@ -76,7 +83,7 @@ public class WiredConditionDateRangeActive extends InteractionWiredCondition { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.startDate = data.startDate; this.endDate = data.endDate; } else { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionFurniHaveFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionFurniHaveFurni.java index 4d9b6a4b..546e9152 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionFurniHaveFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionFurniHaveFurni.java @@ -9,7 +9,8 @@ import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredConditionType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import gnu.trove.set.hash.THashSet; @@ -22,7 +23,7 @@ public class WiredConditionFurniHaveFurni extends InteractionWiredCondition { public static final WiredConditionType type = WiredConditionType.FURNI_HAS_FURNI; private boolean all; - private final THashSet items; + private THashSet items; public WiredConditionFurniHaveFurni(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -35,32 +36,51 @@ public class WiredConditionFurniHaveFurni extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { + Room room = ctx.room(); + this.refresh(); if(this.items.isEmpty()) return true; + if (room.getLayout() == null) + return false; + if(this.all) { return this.items.stream().allMatch(item -> { + if (item == null) return false; + RoomTile baseTile = room.getLayout().getTile(item.getX(), item.getY()); + if (baseTile == null) return false; double minZ = item.getZ() + Item.getCurrentHeight(item); - THashSet occupiedTiles = room.getLayout().getTilesAt(room.getLayout().getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); - return occupiedTiles.stream().anyMatch(tile -> room.getItemsAt(tile).stream().anyMatch(matchedItem -> matchedItem != item && matchedItem.getZ() >= minZ)); + THashSet occupiedTiles = room.getLayout().getTilesAt(baseTile, item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); + if (occupiedTiles == null) return false; + return occupiedTiles.stream().anyMatch(tile -> tile != null && room.getItemsAt(tile).stream().anyMatch(matchedItem -> matchedItem != item && matchedItem.getZ() >= minZ)); }); } else { return this.items.stream().anyMatch(item -> { + if (item == null) return false; + RoomTile baseTile = room.getLayout().getTile(item.getX(), item.getY()); + if (baseTile == null) return false; double minZ = item.getZ() + Item.getCurrentHeight(item); - THashSet occupiedTiles = room.getLayout().getTilesAt(room.getLayout().getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); - return occupiedTiles.stream().anyMatch(tile -> room.getItemsAt(tile).stream().anyMatch(matchedItem -> matchedItem != item && matchedItem.getZ() >= minZ)); + THashSet occupiedTiles = room.getLayout().getTilesAt(baseTile, item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); + if (occupiedTiles == null) return false; + return occupiedTiles.stream().anyMatch(tile -> tile != null && room.getItemsAt(tile).stream().anyMatch(matchedItem -> matchedItem != item && matchedItem.getZ() >= minZ)); }); } } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { this.refresh(); - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.all, this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); @@ -71,7 +91,7 @@ public class WiredConditionFurniHaveFurni extends InteractionWiredCondition { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.all = data.all; for(int id : data.itemIds) { @@ -118,7 +138,7 @@ public class WiredConditionFurniHaveFurni extends InteractionWiredCondition { this.refresh(); message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionFurniHaveHabbo.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionFurniHaveHabbo.java index 80b5225f..7b6bd83a 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionFurniHaveHabbo.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionFurniHaveHabbo.java @@ -12,7 +12,8 @@ import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredConditionType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import gnu.trove.set.hash.THashSet; @@ -25,7 +26,6 @@ import java.util.stream.Collectors; public class WiredConditionFurniHaveHabbo extends InteractionWiredCondition { public static final WiredConditionType type = WiredConditionType.FURNI_HAVE_HABBO; protected THashSet items; - private boolean all; public WiredConditionFurniHaveHabbo(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -40,44 +40,45 @@ public class WiredConditionFurniHaveHabbo extends InteractionWiredCondition { @Override public void onPickUp() { this.items.clear(); - this.all = false; } - @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { + Room room = ctx.room(); + this.refresh(); - if (this.items.isEmpty()) { + if (this.items.isEmpty()) return true; - } + + if (room.getLayout() == null) + return false; Collection habbos = room.getHabbos(); Collection bots = room.getCurrentBots().valueCollection(); Collection pets = room.getCurrentPets().valueCollection(); - if (this.all) { - return this.items.stream().allMatch(item -> { - THashSet occupiedTiles = room.getLayout().getTilesAt(room.getLayout().getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); - return habbos.stream().anyMatch(character -> occupiedTiles.contains(character.getRoomUnit().getCurrentLocation())) || - bots.stream().anyMatch(character -> occupiedTiles.contains(character.getRoomUnit().getCurrentLocation())) || - pets.stream().anyMatch(character -> occupiedTiles.contains(character.getRoomUnit().getCurrentLocation())); - }); - } else { - return this.items.stream().anyMatch(item -> { - THashSet occupiedTiles = room.getLayout().getTilesAt(room.getLayout().getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); - return habbos.stream().anyMatch(character -> occupiedTiles.contains(character.getRoomUnit().getCurrentLocation())) || - bots.stream().anyMatch(character -> occupiedTiles.contains(character.getRoomUnit().getCurrentLocation())) || - pets.stream().anyMatch(character -> occupiedTiles.contains(character.getRoomUnit().getCurrentLocation())); - }); - } + return this.items.stream().filter(item -> item != null).allMatch(item -> { + RoomTile baseTile = room.getLayout().getTile(item.getX(), item.getY()); + if (baseTile == null) return false; + + THashSet occupiedTiles = room.getLayout().getTilesAt(baseTile, item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); + return habbos.stream().anyMatch(character -> character.getRoomUnit() != null && occupiedTiles.contains(character.getRoomUnit().getCurrentLocation())) || + bots.stream().anyMatch(character -> character.getRoomUnit() != null && occupiedTiles.contains(character.getRoomUnit().getCurrentLocation())) || + pets.stream().anyMatch(character -> character.getRoomUnit() != null && occupiedTiles.contains(character.getRoomUnit().getCurrentLocation())); + }); + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; } @Override public String getWiredData() { this.refresh(); - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( - this.all, + return WiredManager.getGson().toJson(new JsonData( this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); } @@ -88,8 +89,7 @@ public class WiredConditionFurniHaveHabbo extends InteractionWiredCondition { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); - this.all = data.all; + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); for(int id : data.itemIds) { HabboItem item = room.getHabboItem(id); @@ -102,7 +102,6 @@ public class WiredConditionFurniHaveHabbo extends InteractionWiredCondition { String[] data = wiredData.split(":"); if (data.length >= 1) { - this.all = (data[0].equals("1")); String[] items = data[1].split(";"); @@ -126,7 +125,7 @@ public class WiredConditionFurniHaveHabbo extends InteractionWiredCondition { this.refresh(); message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) @@ -185,11 +184,9 @@ public class WiredConditionFurniHaveHabbo extends InteractionWiredCondition { } static class JsonData { - boolean all; List itemIds; - public JsonData(boolean all, List itemIds) { - this.all = all; + public JsonData(List itemIds) { this.itemIds = itemIds; } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionFurniTypeMatch.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionFurniTypeMatch.java index d4c202e3..be2fcb8c 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionFurniTypeMatch.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionFurniTypeMatch.java @@ -8,7 +8,8 @@ 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.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import gnu.trove.set.hash.THashSet; @@ -20,7 +21,7 @@ import java.util.stream.Collectors; public class WiredConditionFurniTypeMatch extends InteractionWiredCondition { public static final WiredConditionType type = WiredConditionType.STUFF_IS; - private final THashSet items = new THashSet<>(); + private THashSet items = new THashSet<>(); public WiredConditionFurniTypeMatch(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -36,27 +37,30 @@ public class WiredConditionFurniTypeMatch extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { this.refresh(); if(items.isEmpty()) return false; - if (stuff != null) { - if (stuff.length >= 1) { - if (stuff[0] instanceof HabboItem triggeringItem) { - return this.items.stream().anyMatch(item -> item == triggeringItem); - } - } + HabboItem triggeringItem = ctx.sourceItem().orElse(null); + if (triggeringItem != null) { + return this.items.stream().anyMatch(item -> item == triggeringItem); } return false; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { this.refresh(); - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); } @@ -67,7 +71,7 @@ public class WiredConditionFurniTypeMatch extends InteractionWiredCondition { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); for(int id : data.itemIds) { HabboItem item = room.getHabboItem(id); @@ -99,7 +103,7 @@ public class WiredConditionFurniTypeMatch extends InteractionWiredCondition { this.refresh(); message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionGroupMember.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionGroupMember.java index 189d4598..0364abce 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionGroupMember.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionGroupMember.java @@ -7,6 +7,7 @@ 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.WiredConditionType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -24,7 +25,9 @@ public class WiredConditionGroupMember extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { + RoomUnit roomUnit = ctx.actor().orElse(null); + Room room = ctx.room(); if (room.getGuildId() == 0) return false; @@ -33,6 +36,12 @@ public class WiredConditionGroupMember extends InteractionWiredCondition { return habbo != null && habbo.getHabboStats().hasGuild(room.getGuildId()); } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { return ""; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHabboCount.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHabboCount.java index 124cbee6..7747d9e0 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHabboCount.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHabboCount.java @@ -6,7 +6,8 @@ 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.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -27,15 +28,21 @@ public class WiredConditionHabboCount extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - int count = room.getUserCount(); + public boolean evaluate(WiredContext ctx) { + int count = ctx.room().getUserCount(); return count >= this.lowerLimit && count <= this.upperLimit; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.lowerLimit, this.upperLimit )); @@ -46,7 +53,7 @@ public class WiredConditionHabboCount extends InteractionWiredCondition { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.lowerLimit = data.lowerLimit; this.upperLimit = data.upperLimit; } else { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHabboHasEffect.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHabboHasEffect.java index bb85f578..5247e5c2 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHabboHasEffect.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHabboHasEffect.java @@ -6,7 +6,8 @@ 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.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -26,14 +27,21 @@ public class WiredConditionHabboHasEffect extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { + RoomUnit roomUnit = ctx.actor().orElse(null); if (roomUnit == null) return false; return roomUnit.getEffectId() == this.effectId; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.effectId )); } @@ -43,7 +51,7 @@ public class WiredConditionHabboHasEffect extends InteractionWiredCondition { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.effectId = data.effectId; } else { this.effectId = Integer.parseInt(wiredData); @@ -67,8 +75,9 @@ public class WiredConditionHabboHasEffect extends InteractionWiredCondition { message.appendInt(0); message.appendInt(this.getBaseItem().getSpriteId()); message.appendInt(this.getId()); - message.appendString(this.effectId + ""); - message.appendInt(0); + message.appendString(""); + message.appendInt(1); + message.appendInt(this.effectId); message.appendInt(0); message.appendInt(this.getType().code); message.appendInt(0); 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 7e90bf3b..0175aadf 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 @@ -6,7 +6,8 @@ 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.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,14 +60,21 @@ public class WiredConditionHabboHasHandItem extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { + RoomUnit roomUnit = ctx.actor().orElse(null); if (roomUnit == null) return false; return roomUnit.getHandItem() == this.handItem; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.handItem )); } @@ -77,7 +85,7 @@ public class WiredConditionHabboHasHandItem extends InteractionWiredCondition { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.handItem = data.handItemId; } else { this.handItem = Integer.parseInt(wiredData); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHabboWearsBadge.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHabboWearsBadge.java index c40d58d7..7cb6f29f 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHabboWearsBadge.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionHabboWearsBadge.java @@ -8,7 +8,8 @@ import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboBadge; import com.eu.habbo.habbohotel.wired.WiredConditionType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -28,7 +29,9 @@ public class WiredConditionHabboWearsBadge extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { + RoomUnit roomUnit = ctx.actor().orElse(null); + Room room = ctx.room(); Habbo habbo = room.getHabbo(roomUnit); if (habbo != null) { @@ -43,9 +46,15 @@ public class WiredConditionHabboWearsBadge extends InteractionWiredCondition { return false; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.badge )); } @@ -55,7 +64,7 @@ public class WiredConditionHabboWearsBadge extends InteractionWiredCondition { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.badge = data.badge; } else { this.badge = wiredData; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionLessTimeElapsed.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionLessTimeElapsed.java index 237216ff..7086ea7d 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionLessTimeElapsed.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionLessTimeElapsed.java @@ -7,7 +7,8 @@ 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.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -26,14 +27,20 @@ public class WiredConditionLessTimeElapsed extends InteractionWiredCondition { super(id, userId, item, extradata, limitedStack, limitedSells); } + @Override + public boolean evaluate(WiredContext ctx) { + return (Emulator.getIntUnixTimestamp() - ctx.room().getLastTimerReset()) / 0.5 < this.cycles; + } + + @Deprecated @Override public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - return (Emulator.getIntUnixTimestamp() - room.getLastTimerReset()) / 0.5 < this.cycles; + return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.cycles )); } @@ -44,7 +51,7 @@ public class WiredConditionLessTimeElapsed extends InteractionWiredCondition { try { if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.cycles = data.cycles; } else { if (!wiredData.equals("")) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchStatePosition.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchStatePosition.java index 96e9edd6..82f745c4 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchStatePosition.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMatchStatePosition.java @@ -8,8 +8,9 @@ import com.eu.habbo.habbohotel.items.interactions.wired.interfaces.InteractionWi 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.core.WiredContext; import com.eu.habbo.habbohotel.wired.WiredConditionType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.habbohotel.wired.WiredMatchFurniSetting; import com.eu.habbo.messages.ServerMessage; import gnu.trove.set.hash.THashSet; @@ -22,7 +23,7 @@ import java.util.List; public class WiredConditionMatchStatePosition extends InteractionWiredCondition implements InteractionWiredMatchFurniSettings { public static final WiredConditionType type = WiredConditionType.MATCH_SSHOT; - private final THashSet settings; + private THashSet settings; private boolean state; private boolean position; @@ -48,7 +49,7 @@ public class WiredConditionMatchStatePosition extends InteractionWiredCondition this.refresh(); message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.settings.size()); for (WiredMatchFurniSetting item : this.settings) @@ -97,7 +98,8 @@ public class WiredConditionMatchStatePosition extends InteractionWiredCondition } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { + Room room = ctx.room(); if (this.settings.isEmpty()) return true; @@ -135,9 +137,15 @@ public class WiredConditionMatchStatePosition extends InteractionWiredCondition return true; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.state, this.position, this.direction, @@ -150,7 +158,7 @@ public class WiredConditionMatchStatePosition extends InteractionWiredCondition String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.state = data.state; this.position = data.position; this.direction = data.direction; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMoreTimeElapsed.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMoreTimeElapsed.java index 2ff2c288..2ae00378 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMoreTimeElapsed.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMoreTimeElapsed.java @@ -7,7 +7,8 @@ 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.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -26,14 +27,20 @@ public class WiredConditionMoreTimeElapsed extends InteractionWiredCondition { super(id, userId, item, extradata, limitedStack, limitedSells); } + @Override + public boolean evaluate(WiredContext ctx) { + return (Emulator.getIntUnixTimestamp() - ctx.room().getLastTimerReset()) / 0.5 > this.cycles; + } + + @Deprecated @Override public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - return (Emulator.getIntUnixTimestamp() - room.getLastTimerReset()) / 0.5 > this.cycles; + return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.cycles )); } @@ -44,7 +51,7 @@ public class WiredConditionMoreTimeElapsed extends InteractionWiredCondition { try { if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.cycles = data.cycles; } else { if (!wiredData.equals("")) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMovementValidation.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMovementValidation.java new file mode 100644 index 00000000..44ce8ba2 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionMovementValidation.java @@ -0,0 +1,124 @@ +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.api.IWiredEffect; +import com.eu.habbo.habbohotel.wired.api.WiredStack; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredSimulation; +import com.eu.habbo.messages.ServerMessage; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Wired condition that validates all movement effects can complete successfully. + *

+ * When this condition is present in a wired stack, all movement effects are first + * simulated to verify they can complete. If ANY movement would fail (e.g., destination + * has a user, bot, furniture, or is a hole), this condition returns FALSE and the + * stack does not execute. + *

+ *

+ * Key Feature: The simulation tracks cumulative position changes. If Effect 1 + * moves an item from tile 0 to tile 1, and Effect 2 moves it forward again, the + * simulation correctly validates the move from tile 1 to tile 2 (not from tile 0). + *

+ *

+ * Use cases: + *

    + *
  • Moving furniture in formation - if one piece can't move, none should
  • + *
  • Puzzles where partial movement would break the puzzle state
  • + *
  • Chain movements where items depend on each other's positions
  • + *
+ *

+ * + * @see WiredSimulation + * @see IWiredEffect#simulate + */ +public class WiredConditionMovementValidation extends InteractionWiredCondition { + + public static final WiredConditionType type = WiredConditionType.MOVEMENT_VALIDATION; + + public WiredConditionMovementValidation(ResultSet set, Item baseItem) throws SQLException { + super(set, baseItem); + } + + public WiredConditionMovementValidation(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) { + WiredStack stack = ctx.stack(); + if (stack == null) { + return true; + } + + WiredSimulation simulation = new WiredSimulation(ctx.room()); + + for (IWiredEffect effect : stack.effects()) { + if (effect.requiresActor() && !ctx.hasActor()) { + continue; + } + + try { + boolean success = effect.simulate(ctx, simulation); + if (!success || simulation.hasFailed()) { + return false; + } + } catch (Exception e) { + return false; + } + } + + return true; + } + + @Override + public String getWiredData() { + return ""; + } + + @Override + public void loadWiredData(ResultSet set, Room room) throws SQLException { + } + + @Override + public void onPickUp() { + } + + @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(0); + message.appendInt(0); + message.appendInt(this.getType().code); + message.appendInt(0); + message.appendInt(0); + } + + @Override + public boolean saveData(WiredSettings settings) { + return true; + } + + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotFurniHaveFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotFurniHaveFurni.java index 8ef79e63..629a2702 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotFurniHaveFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotFurniHaveFurni.java @@ -10,7 +10,8 @@ import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredConditionOperator; import com.eu.habbo.habbohotel.wired.WiredConditionType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import gnu.trove.set.hash.THashSet; @@ -23,7 +24,7 @@ public class WiredConditionNotFurniHaveFurni extends InteractionWiredCondition { public static final WiredConditionType type = WiredConditionType.NOT_FURNI_HAVE_FURNI; private boolean all; - private final THashSet items; + private THashSet items; public WiredConditionNotFurniHaveFurni(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -36,32 +37,51 @@ public class WiredConditionNotFurniHaveFurni extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { + Room room = ctx.room(); + this.refresh(); if (this.items.isEmpty()) return true; + if (room.getLayout() == null) + return true; + if(this.all) { return this.items.stream().allMatch(item -> { + if (item == null) return true; + RoomTile baseTile = room.getLayout().getTile(item.getX(), item.getY()); + if (baseTile == null) return true; double minZ = item.getZ() + Item.getCurrentHeight(item); - THashSet occupiedTiles = room.getLayout().getTilesAt(room.getLayout().getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); - return occupiedTiles.stream().noneMatch(tile -> room.getItemsAt(tile).stream().anyMatch(matchedItem -> matchedItem != item && matchedItem.getZ() >= minZ)); + THashSet occupiedTiles = room.getLayout().getTilesAt(baseTile, item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); + if (occupiedTiles == null) return true; + return occupiedTiles.stream().noneMatch(tile -> tile != null && room.getItemsAt(tile).stream().anyMatch(matchedItem -> matchedItem != item && matchedItem.getZ() >= minZ)); }); } else { return this.items.stream().anyMatch(item -> { + if (item == null) return true; + RoomTile baseTile = room.getLayout().getTile(item.getX(), item.getY()); + if (baseTile == null) return true; double minZ = item.getZ() + Item.getCurrentHeight(item); - THashSet occupiedTiles = room.getLayout().getTilesAt(room.getLayout().getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); - return occupiedTiles.stream().noneMatch(tile -> room.getItemsAt(tile).stream().anyMatch(matchedItem -> matchedItem != item && matchedItem.getZ() >= minZ)); + THashSet occupiedTiles = room.getLayout().getTilesAt(baseTile, item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); + if (occupiedTiles == null) return true; + return occupiedTiles.stream().noneMatch(tile -> tile != null && room.getItemsAt(tile).stream().anyMatch(matchedItem -> matchedItem != item && matchedItem.getZ() >= minZ)); }); } } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { this.refresh(); - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.all, this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); @@ -73,7 +93,7 @@ public class WiredConditionNotFurniHaveFurni extends InteractionWiredCondition { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.all = data.all; for (int id : data.itemIds) { @@ -119,7 +139,7 @@ public class WiredConditionNotFurniHaveFurni extends InteractionWiredCondition { this.refresh(); message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotFurniHaveHabbo.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotFurniHaveHabbo.java index 23d17edc..02324a60 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotFurniHaveHabbo.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotFurniHaveHabbo.java @@ -12,7 +12,8 @@ import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredConditionType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import gnu.trove.set.hash.THashSet; @@ -43,28 +44,42 @@ public class WiredConditionNotFurniHaveHabbo extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { + Room room = ctx.room(); + this.refresh(); if (this.items.isEmpty()) return true; + if (room.getLayout() == null) + return false; + Collection habbos = room.getHabbos(); Collection bots = room.getCurrentBots().valueCollection(); Collection pets = room.getCurrentPets().valueCollection(); - return this.items.stream().noneMatch(item -> { - THashSet occupiedTiles = room.getLayout().getTilesAt(room.getLayout().getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); - return habbos.stream().anyMatch(character -> occupiedTiles.contains(character.getRoomUnit().getCurrentLocation())) || - bots.stream().anyMatch(character -> occupiedTiles.contains(character.getRoomUnit().getCurrentLocation())) || - pets.stream().anyMatch(character -> occupiedTiles.contains(character.getRoomUnit().getCurrentLocation())); + return this.items.stream().filter(item -> item != null).noneMatch(item -> { + RoomTile baseTile = room.getLayout().getTile(item.getX(), item.getY()); + if (baseTile == null) return false; + + THashSet occupiedTiles = room.getLayout().getTilesAt(baseTile, item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); + return habbos.stream().anyMatch(character -> character.getRoomUnit() != null && occupiedTiles.contains(character.getRoomUnit().getCurrentLocation())) || + bots.stream().anyMatch(character -> character.getRoomUnit() != null && occupiedTiles.contains(character.getRoomUnit().getCurrentLocation())) || + pets.stream().anyMatch(character -> character.getRoomUnit() != null && occupiedTiles.contains(character.getRoomUnit().getCurrentLocation())); }); } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { this.refresh(); - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); } @@ -75,7 +90,7 @@ public class WiredConditionNotFurniHaveHabbo extends InteractionWiredCondition { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - WiredConditionFurniHaveHabbo.JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, WiredConditionFurniHaveHabbo.JsonData.class); + WiredConditionFurniHaveHabbo.JsonData data = WiredManager.getGson().fromJson(wiredData, WiredConditionFurniHaveHabbo.JsonData.class); for(int id : data.itemIds) { HabboItem item = room.getHabboItem(id); @@ -110,7 +125,7 @@ public class WiredConditionNotFurniHaveHabbo extends InteractionWiredCondition { this.refresh(); message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotFurniTypeMatch.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotFurniTypeMatch.java index 49525576..19e841aa 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotFurniTypeMatch.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotFurniTypeMatch.java @@ -8,7 +8,8 @@ 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.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import gnu.trove.set.hash.THashSet; @@ -20,7 +21,7 @@ import java.util.stream.Collectors; public class WiredConditionNotFurniTypeMatch extends InteractionWiredCondition { public static final WiredConditionType type = WiredConditionType.NOT_STUFF_IS; - private final THashSet items = new THashSet<>(); + private THashSet items = new THashSet<>(); public WiredConditionNotFurniTypeMatch(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -31,27 +32,30 @@ public class WiredConditionNotFurniTypeMatch extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { this.refresh(); if(items.isEmpty()) return true; - if (stuff != null) { - if (stuff.length >= 1) { - if (stuff[0] instanceof HabboItem triggeringItem) { - return this.items.stream().noneMatch(item -> item == triggeringItem); - } - } + HabboItem triggeringItem = ctx.sourceItem().orElse(null); + if (triggeringItem != null) { + return this.items.stream().noneMatch(item -> item == triggeringItem); } return true; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { this.refresh(); - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); } @@ -62,7 +66,7 @@ public class WiredConditionNotFurniTypeMatch extends InteractionWiredCondition { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - WiredConditionFurniTypeMatch.JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, WiredConditionFurniTypeMatch.JsonData.class); + WiredConditionFurniTypeMatch.JsonData data = WiredManager.getGson().fromJson(wiredData, WiredConditionFurniTypeMatch.JsonData.class); for(int id : data.itemIds) { HabboItem item = room.getHabboItem(id); @@ -99,7 +103,7 @@ public class WiredConditionNotFurniTypeMatch extends InteractionWiredCondition { this.refresh(); message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboCount.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboCount.java index 109e1f68..cbdbb7b4 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboCount.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboCount.java @@ -6,7 +6,8 @@ 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.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -27,15 +28,21 @@ public class WiredConditionNotHabboCount extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - int count = room.getUserCount(); + public boolean evaluate(WiredContext ctx) { + int count = ctx.room().getUserCount(); return count < this.lowerLimit || count > this.upperLimit; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.lowerLimit, this.upperLimit )); @@ -46,7 +53,7 @@ public class WiredConditionNotHabboCount extends InteractionWiredCondition { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - WiredConditionHabboCount.JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, WiredConditionHabboCount.JsonData.class); + WiredConditionHabboCount.JsonData data = WiredManager.getGson().fromJson(wiredData, WiredConditionHabboCount.JsonData.class); this.lowerLimit = data.lowerLimit; this.upperLimit = data.upperLimit; } else { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboHasEffect.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboHasEffect.java index fb908728..1a622641 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboHasEffect.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboHasEffect.java @@ -6,7 +6,8 @@ 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.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -26,14 +27,21 @@ public class WiredConditionNotHabboHasEffect extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { + RoomUnit roomUnit = ctx.actor().orElse(null); if (roomUnit == null) return false; return roomUnit.getEffectId() != this.effectId; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.effectId )); } @@ -43,7 +51,7 @@ public class WiredConditionNotHabboHasEffect extends InteractionWiredCondition { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.effectId = data.effectId; } else { this.effectId = Integer.parseInt(wiredData); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboWearsBadge.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboWearsBadge.java index d806810c..81bc279b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboWearsBadge.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotHabboWearsBadge.java @@ -8,7 +8,8 @@ import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboBadge; import com.eu.habbo.habbohotel.wired.WiredConditionType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -28,7 +29,9 @@ public class WiredConditionNotHabboWearsBadge extends InteractionWiredCondition } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { + RoomUnit roomUnit = ctx.actor().orElse(null); + Room room = ctx.room(); Habbo habbo = room.getHabbo(roomUnit); if (habbo != null) { @@ -44,9 +47,15 @@ public class WiredConditionNotHabboWearsBadge extends InteractionWiredCondition return true; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.badge )); } @@ -56,7 +65,7 @@ public class WiredConditionNotHabboWearsBadge extends InteractionWiredCondition String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.badge = data.badge; } else { this.badge = wiredData; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotInGroup.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotInGroup.java index 75f2e5a9..4670912e 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotInGroup.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotInGroup.java @@ -7,6 +7,7 @@ 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.WiredConditionType; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -24,7 +25,9 @@ public class WiredConditionNotInGroup extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { + RoomUnit roomUnit = ctx.actor().orElse(null); + Room room = ctx.room(); if (room.getGuildId() == 0) return false; @@ -33,6 +36,12 @@ public class WiredConditionNotInGroup extends InteractionWiredCondition { return habbo == null || !habbo.getHabboStats().hasGuild(room.getGuildId()); } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { return ""; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotInTeam.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotInTeam.java index b7ef4a22..7ae8bb03 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotInTeam.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotInTeam.java @@ -7,8 +7,9 @@ import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.habbohotel.wired.WiredConditionType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -28,7 +29,9 @@ public class WiredConditionNotInTeam extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { + RoomUnit roomUnit = ctx.actor().orElse(null); + Room room = ctx.room(); Habbo habbo = room.getHabbo(roomUnit); if (habbo != null) { @@ -38,9 +41,15 @@ public class WiredConditionNotInTeam extends InteractionWiredCondition { return true; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.teamColor )); } @@ -51,7 +60,7 @@ public class WiredConditionNotInTeam extends InteractionWiredCondition { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.teamColor = data.teamColor; } else { if (!wiredData.equals("")) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotMatchStatePosition.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotMatchStatePosition.java index d6b11988..2121bf97 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotMatchStatePosition.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotMatchStatePosition.java @@ -3,6 +3,7 @@ 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.core.WiredContext; import com.eu.habbo.habbohotel.wired.WiredConditionType; import java.sql.ResultSet; @@ -19,9 +20,15 @@ public class WiredConditionNotMatchStatePosition extends WiredConditionMatchStat super(id, userId, item, extradata, limitedStack, limitedSells); } + @Override + public boolean evaluate(WiredContext ctx) { + return !super.evaluate(ctx); + } + + @Deprecated @Override public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - return !super.execute(roomUnit, room, stuff); + return false; } @Override diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotTriggerOnFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotTriggerOnFurni.java index 21ea027e..88558789 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotTriggerOnFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionNotTriggerOnFurni.java @@ -4,6 +4,7 @@ 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 java.sql.ResultSet; import java.sql.SQLException; @@ -20,7 +21,10 @@ public class WiredConditionNotTriggerOnFurni extends WiredConditionTriggerOnFurn } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { + RoomUnit roomUnit = ctx.actor().orElse(null); + Room room = ctx.room(); + if (roomUnit == null) return false; @@ -32,6 +36,12 @@ public class WiredConditionNotTriggerOnFurni extends WiredConditionTriggerOnFurn return !triggerOnFurni(roomUnit, room); } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public WiredConditionType getType() { return type; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamMember.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamMember.java index 486983fc..5f1ac3a6 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamMember.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTeamMember.java @@ -8,7 +8,8 @@ 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.WiredConditionType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -28,7 +29,9 @@ public class WiredConditionTeamMember extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { + RoomUnit roomUnit = ctx.actor().orElse(null); + Room room = ctx.room(); Habbo habbo = room.getHabbo(roomUnit); if (habbo != null) { @@ -40,9 +43,15 @@ public class WiredConditionTeamMember extends InteractionWiredCondition { return false; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.teamColor )); } @@ -53,7 +62,7 @@ public class WiredConditionTeamMember extends InteractionWiredCondition { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.teamColor = data.teamColor; } else { if (!wiredData.equals("")) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTriggerOnFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTriggerOnFurni.java index 20028f9d..3ced00ba 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTriggerOnFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/conditions/WiredConditionTriggerOnFurni.java @@ -9,7 +9,8 @@ import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredConditionOperator; import com.eu.habbo.habbohotel.wired.WiredConditionType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; import gnu.trove.set.hash.THashSet; @@ -32,7 +33,10 @@ public class WiredConditionTriggerOnFurni extends InteractionWiredCondition { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean evaluate(WiredContext ctx) { + RoomUnit roomUnit = ctx.actor().orElse(null); + Room room = ctx.room(); + if (roomUnit == null) return false; @@ -44,6 +48,12 @@ public class WiredConditionTriggerOnFurni extends InteractionWiredCondition { return triggerOnFurni(roomUnit, room); } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + protected boolean triggerOnFurni(RoomUnit roomUnit, Room room) { THashSet itemsAtUser = room.getItemsAt(roomUnit.getCurrentLocation()); return this.items.stream().anyMatch(itemsAtUser::contains); @@ -52,7 +62,7 @@ public class WiredConditionTriggerOnFurni extends InteractionWiredCondition { @Override public String getWiredData() { this.refresh(); - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); } @@ -63,7 +73,7 @@ public class WiredConditionTriggerOnFurni extends InteractionWiredCondition { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); for(int id : data.itemIds) { HabboItem item = room.getHabboItem(id); @@ -100,7 +110,7 @@ public class WiredConditionTriggerOnFurni extends InteractionWiredCondition { this.refresh(); message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectAlert.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectAlert.java index 14e83480..72869321 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectAlert.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectAlert.java @@ -3,8 +3,8 @@ package com.eu.habbo.habbohotel.items.interactions.wired.effects; import com.eu.habbo.Emulator; 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.users.Habbo; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import java.sql.ResultSet; import java.sql.SQLException; @@ -19,17 +19,15 @@ public class WiredEffectAlert extends WiredEffectWhisper { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - Habbo habbo = room.getHabbo(roomUnit); + public void execute(WiredContext ctx) { + Room room = ctx.room(); + Habbo habbo = ctx.actor().map(room::getHabbo).orElse(null); if (habbo != null) { habbo.alert(this.message .replace("%online%", Emulator.getGameEnvironment().getHabboManager().getOnlineCount() + "") .replace("%username%", habbo.getHabboInfo().getUsername()) .replace("%roomsloaded%", Emulator.getGameEnvironment().getRoomManager().loadedRoomsCount() + "")); - return true; } - - return false; } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotClothes.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotClothes.java index e7462244..6cc5af2b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotClothes.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotClothes.java @@ -9,7 +9,8 @@ import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +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.messages.incoming.wired.WiredSaveException; @@ -39,7 +40,7 @@ public class WiredEffectBotClothes extends InteractionWiredEffect { message.appendInt(0); message.appendInt(this.getBaseItem().getSpriteId()); message.appendInt(this.getId()); - message.appendString(this.botName + ((char) 9) + this.botLook); + message.appendString(this.botName + ((char) 9) + "" + this.botLook); message.appendInt(0); message.appendInt(0); message.appendInt(this.getType().code); @@ -77,20 +78,25 @@ public class WiredEffectBotClothes extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); List bots = room.getBots(this.botName); if (bots.size() == 1) { Bot bot = bots.get(0); bot.setFigure(this.botLook); } + } - return true; + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.botName, this.botLook, this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.botName, this.botLook, this.getDelay())); } @Override @@ -98,7 +104,7 @@ public class WiredEffectBotClothes extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.botName = data.bot_name; this.botLook = data.look; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotFollowHabbo.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotFollowHabbo.java index 7cb69764..28ba0c3a 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotFollowHabbo.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotFollowHabbo.java @@ -11,7 +11,8 @@ 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.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +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.messages.incoming.wired.WiredSaveException; import gnu.trove.procedure.TObjectProcedure; @@ -99,9 +100,12 @@ public class WiredEffectBotFollowHabbo extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - Habbo habbo = room.getHabbo(roomUnit); + public void execute(WiredContext ctx) { + Room room = ctx.room(); + RoomUnit roomUnit = ctx.actor().orElse(null); + if (roomUnit == null) return; + Habbo habbo = room.getHabbo(roomUnit); List bots = room.getBots(this.botName); if (habbo != null && bots.size() == 1) { @@ -112,16 +116,18 @@ public class WiredEffectBotFollowHabbo extends InteractionWiredEffect { } else { bot.stopFollowingHabbo(); } - - return true; } + } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.botName, this.mode, this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.botName, this.mode, this.getDelay())); } @Override @@ -129,7 +135,7 @@ public class WiredEffectBotFollowHabbo extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.mode = data.mode; this.botName = data.bot_name; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotGiveHandItem.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotGiveHandItem.java index 80537049..d39d1044 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotGiveHandItem.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotGiveHandItem.java @@ -12,8 +12,8 @@ import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import com.eu.habbo.threading.runnables.RoomUnitGiveHanditem; @@ -102,7 +102,11 @@ public class WiredEffectBotGiveHandItem extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); + RoomUnit roomUnit = ctx.actor().orElse(null); + if (roomUnit == null) return; + Habbo habbo = room.getHabbo(roomUnit); List bots = room.getBots(this.botName); @@ -114,7 +118,7 @@ public class WiredEffectBotGiveHandItem extends InteractionWiredEffect { tasks.add(new RoomUnitGiveHanditem(bot.getRoomUnit(), room, 0)); tasks.add(() -> { if(roomUnit.getRoom() != null && roomUnit.getRoom().getId() == room.getId() && roomUnit.getCurrentLocation().distance(bot.getRoomUnit().getCurrentLocation()) < 2) { - WiredHandler.handle(WiredTriggerType.BOT_REACHED_AVTR, bot.getRoomUnit(), room, new Object[]{}); + WiredManager.triggerBotReachedHabbo(room, bot.getRoomUnit(), roomUnit); } }); @@ -126,16 +130,18 @@ public class WiredEffectBotGiveHandItem extends InteractionWiredEffect { Emulator.getThreading().run(new RoomUnitGiveHanditem(bot.getRoomUnit(), room, this.itemId)); Emulator.getThreading().run(new RoomUnitWalkToLocation(bot.getRoomUnit(), tile, room, tasks, tasks)); - - return true; } + } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.botName, this.itemId, this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.botName, this.itemId, this.getDelay())); } @Override @@ -143,7 +149,7 @@ public class WiredEffectBotGiveHandItem extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.itemId = data.item_id; this.botName = data.bot_name; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTalk.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTalk.java index c42ef54e..8301f508 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTalk.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTalk.java @@ -10,8 +10,8 @@ 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.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; @@ -42,7 +42,7 @@ public class WiredEffectBotTalk extends InteractionWiredEffect { message.appendInt(0); message.appendInt(this.getBaseItem().getSpriteId()); message.appendInt(this.getId()); - message.appendString(this.botName + ((char) 9) + this.message); + message.appendString(this.botName + "" + ((char) 9) + "" + this.message); message.appendInt(1); message.appendInt(this.mode); message.appendInt(0); @@ -89,10 +89,12 @@ public class WiredEffectBotTalk extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); + RoomUnit roomUnit = ctx.actor().orElse(null); String message = this.message; - Habbo habbo = room.getHabbo(roomUnit); + Habbo habbo = roomUnit != null ? room.getHabbo(roomUnit) : null; if (habbo != null) { message = message.replace(Emulator.getTexts().getValue("wired.variable.username", "%username%"), habbo.getHabboInfo().getUsername()) @@ -111,7 +113,7 @@ public class WiredEffectBotTalk extends InteractionWiredEffect { if (bots.size() == 1) { Bot bot = bots.get(0); - if(!WiredHandler.handle(WiredTriggerType.SAY_SOMETHING, bot.getRoomUnit(), room, new Object[]{ message })) { + if(!WiredManager.triggerUserSays(room, bot.getRoomUnit(), message)) { if (this.mode == 1) { bot.shout(message); } else { @@ -119,13 +121,17 @@ public class WiredEffectBotTalk extends InteractionWiredEffect { } } } + } - return true; + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.botName, this.mode, this.message, this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.botName, this.mode, this.message, this.getDelay())); } @Override @@ -133,7 +139,7 @@ public class WiredEffectBotTalk extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.mode = data.mode; this.botName = data.bot_name; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTalkToHabbo.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTalkToHabbo.java index 469f666f..ec2222d5 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTalkToHabbo.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTalkToHabbo.java @@ -11,8 +11,8 @@ 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.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import gnu.trove.procedure.TObjectProcedure; @@ -45,7 +45,7 @@ public class WiredEffectBotTalkToHabbo extends InteractionWiredEffect { message.appendInt(0); message.appendInt(this.getBaseItem().getSpriteId()); message.appendInt(this.getId()); - message.appendString(this.botName + ((char) 9) + this.message); + message.appendString(this.botName + "" + ((char) 9) + "" + this.message); message.appendInt(1); message.appendInt(this.mode); message.appendInt(0); @@ -109,7 +109,11 @@ public class WiredEffectBotTalkToHabbo extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); + RoomUnit roomUnit = ctx.actor().orElse(null); + if (roomUnit == null) return; + Habbo habbo = room.getHabbo(roomUnit); if (habbo != null) { @@ -127,28 +131,30 @@ public class WiredEffectBotTalkToHabbo extends InteractionWiredEffect { List bots = room.getBots(this.botName); if (bots.size() != 1) { - return false; + return; } Bot bot = bots.get(0); - if(!WiredHandler.handle(WiredTriggerType.SAY_SOMETHING, bot.getRoomUnit(), room, new Object[]{ m })) { + if(!WiredManager.triggerUserSays(room, bot.getRoomUnit(), m)) { if (this.mode == 1) { bot.whisper(m, habbo); } else { bot.talk(habbo.getHabboInfo().getUsername() + ": " + m); } } - - return true; } + } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.botName, this.mode, this.message, this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.botName, this.mode, this.message, this.getDelay())); } @Override @@ -156,7 +162,7 @@ public class WiredEffectBotTalkToHabbo extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.mode = data.mode; this.botName = data.bot_name; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTeleport.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTeleport.java index d12fb444..5a72aac0 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTeleport.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotTeleport.java @@ -12,7 +12,8 @@ import com.eu.habbo.habbohotel.rooms.RoomTileState; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +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.messages.incoming.wired.WiredSaveException; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserEffectComposer; @@ -56,7 +57,7 @@ public class WiredEffectBotTeleport extends InteractionWiredEffect { roomUnit.getRoom().unIdle(roomUnit.getRoom().getHabbo(roomUnit)); room.sendComposer(new RoomUserEffectComposer(roomUnit, 4).compose()); - Emulator.getThreading().run(new SendRoomUnitEffectComposer(room, roomUnit), WiredHandler.TELEPORT_DELAY + 1000); + Emulator.getThreading().run(new SendRoomUnitEffectComposer(room, roomUnit), WiredManager.TELEPORT_DELAY + 1000); if (tile == roomUnit.getCurrentLocation()) { return; @@ -79,8 +80,8 @@ public class WiredEffectBotTeleport extends InteractionWiredEffect { } } - Emulator.getThreading().run(() -> { roomUnit.isWiredTeleporting = true; }, Math.max(0, WiredHandler.TELEPORT_DELAY - 500)); - Emulator.getThreading().run(new RoomUnitTeleport(roomUnit, room, tile.x, tile.y, tile.getStackHeight() + (tile.state == RoomTileState.SIT ? -0.5 : 0), roomUnit.getEffectId()), WiredHandler.TELEPORT_DELAY); + Emulator.getThreading().run(() -> { roomUnit.isWiredTeleporting = true; }, Math.max(0, WiredManager.TELEPORT_DELAY - 500)); + Emulator.getThreading().run(new RoomUnitTeleport(roomUnit, room, tile.x, tile.y, tile.getStackHeight() + (tile.state == RoomTileState.SIT ? -0.5 : 0), roomUnit.getEffectId()), WiredManager.TELEPORT_DELAY); } @Override @@ -97,7 +98,7 @@ public class WiredEffectBotTeleport extends InteractionWiredEffect { } message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) message.appendInt(item.getId()); @@ -152,14 +153,19 @@ public class WiredEffectBotTeleport extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); + if (this.items.isEmpty()) - return false; + return; + + if (room.getLayout() == null) + return; List bots = room.getBots(this.botName); if (bots.size() != 1) { - return false; + return; } Bot bot = bots.get(0); @@ -170,15 +176,22 @@ public class WiredEffectBotTeleport extends InteractionWiredEffect { for (HabboItem item : this.items) { if (item.getRoomId() != 0 && item.getRoomId() == bot.getRoom().getId()) { if (i == j) { - teleportUnitToTile(bot.getRoomUnit(), room.getLayout().getTile(item.getX(), item.getY())); - return true; + RoomTile tile = room.getLayout().getTile(item.getX(), item.getY()); + if (tile != null) { + teleportUnitToTile(bot.getRoomUnit(), tile); + } + return; } else { j++; } } } + } - return true; + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; } @Override @@ -193,7 +206,7 @@ public class WiredEffectBotTeleport extends InteractionWiredEffect { } } - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.botName, itemIds, this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.botName, itemIds, this.getDelay())); } @Override @@ -203,7 +216,7 @@ public class WiredEffectBotTeleport extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.botName = data.bot_name; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotWalkToFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotWalkToFurni.java index 461da90c..766744e3 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotWalkToFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectBotWalkToFurni.java @@ -10,7 +10,8 @@ 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.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +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.messages.incoming.wired.WiredSaveException; import gnu.trove.set.hash.THashSet; @@ -51,7 +52,7 @@ public class WiredEffectBotWalkToFurni extends InteractionWiredEffect { } message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) message.appendInt(item.getId()); @@ -106,11 +107,12 @@ public class WiredEffectBotWalkToFurni extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); List bots = room.getBots(this.botName); if (this.items.isEmpty() || bots.size() != 1) { - return true; + return; } Bot bot = bots.get(0); @@ -126,11 +128,17 @@ public class WiredEffectBotWalkToFurni extends InteractionWiredEffect { HabboItem item = possibleItems.get(Emulator.getRandom().nextInt(possibleItems.size())); if (item.getRoomId() != 0 && item.getRoomId() == bot.getRoom().getId()) { - bot.getRoomUnit().setGoalLocation(room.getLayout().getTile(item.getX(), item.getY())); + if (room.getLayout() != null) { + bot.getRoomUnit().setGoalLocation(room.getLayout().getTile(item.getX(), item.getY())); + } } } + } - return true; + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; } @Override @@ -145,7 +153,7 @@ public class WiredEffectBotWalkToFurni extends InteractionWiredEffect { } } - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.botName, itemIds, this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.botName, itemIds, this.getDelay())); } @Override @@ -155,7 +163,7 @@ public class WiredEffectBotWalkToFurni extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.botName = data.bot_name; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectChangeFurniDirection.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectChangeFurniDirection.java index 1f194598..7dc86a92 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectChangeFurniDirection.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectChangeFurniDirection.java @@ -7,10 +7,9 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.*; import com.eu.habbo.habbohotel.users.HabboItem; -import com.eu.habbo.habbohotel.wired.WiredChangeDirectionSetting; -import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.*; +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.messages.incoming.wired.WiredSaveException; import com.eu.habbo.messages.outgoing.rooms.items.FloorItemOnRollerComposer; @@ -47,11 +46,14 @@ public class WiredEffectChangeFurniDirection extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); + if (room == null || room.getLayout() == null) return; + THashSet items = new THashSet<>(); for (HabboItem item : this.items.keySet()) { - if (Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) + if (item == null || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) items.add(item); } @@ -59,17 +61,22 @@ public class WiredEffectChangeFurniDirection extends InteractionWiredEffect { this.items.remove(item); } - if (this.items.isEmpty()) return false; + if (this.items.isEmpty()) return; for (Map.Entry entry : this.items.entrySet()) { HabboItem item = entry.getKey(); - RoomTile targetTile = room.getLayout().getTileInFront(room.getLayout().getTile(item.getX(), item.getY()), entry.getValue().direction.getValue()); + if (item == null || entry.getValue() == null) continue; + + RoomTile itemTile = room.getLayout().getTile(item.getX(), item.getY()); + if (itemTile == null) continue; + + RoomTile targetTile = room.getLayout().getTileInFront(itemTile, entry.getValue().direction.getValue()); int count = 1; while ((targetTile == null || targetTile.state == RoomTileState.INVALID || room.furnitureFitsAt(targetTile, item, item.getRotation(), false) != FurnitureMovementError.NONE) && count < 8) { entry.getValue().direction = this.nextRotation(entry.getValue().direction); - RoomTile tile = room.getLayout().getTileInFront(room.getLayout().getTile(item.getX(), item.getY()), entry.getValue().direction.getValue()); + RoomTile tile = room.getLayout().getTileInFront(itemTile, entry.getValue().direction.getValue()); if (tile != null && tile.state != RoomTileState.INVALID) { targetTile = tile; } @@ -80,47 +87,60 @@ public class WiredEffectChangeFurniDirection extends InteractionWiredEffect { for (Map.Entry entry : this.items.entrySet()) { HabboItem item = entry.getKey(); + if (item == null || entry.getValue() == null) continue; + int newDirection = entry.getValue().direction.getValue(); - RoomTile targetTile = room.getLayout().getTileInFront(room.getLayout().getTile(item.getX(), item.getY()), newDirection); + RoomTile itemTile = room.getLayout().getTile(item.getX(), item.getY()); + if (itemTile == null) continue; + + RoomTile targetTile = room.getLayout().getTileInFront(itemTile, newDirection); if(item.getRotation() != entry.getValue().rotation) { - if(room.furnitureFitsAt(targetTile, item, entry.getValue().rotation, false) != FurnitureMovementError.NONE) + if(targetTile == null || room.furnitureFitsAt(targetTile, item, entry.getValue().rotation, false) != FurnitureMovementError.NONE) continue; room.moveFurniTo(entry.getKey(), targetTile, entry.getValue().rotation, null, true); } + if (targetTile == null) continue; + boolean hasRoomUnits = false; THashSet newOccupiedTiles = room.getLayout().getTilesAt(targetTile, item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); for(RoomTile tile : newOccupiedTiles) { for (RoomUnit _roomUnit : room.getRoomUnits(tile)) { hasRoomUnits = true; if(_roomUnit.getCurrentLocation() == targetTile) { - Emulator.getThreading().run(() -> WiredHandler.handle(WiredTriggerType.COLLISION, _roomUnit, room, new Object[]{entry.getKey()})); + Emulator.getThreading().run(() -> { + WiredManager.triggerBotCollision(room, _roomUnit); + }); break; } } } - if (targetTile != null && targetTile.state != RoomTileState.INVALID && room.furnitureFitsAt(targetTile, item, item.getRotation(), false) == FurnitureMovementError.NONE) { + if (targetTile.state != RoomTileState.INVALID && room.furnitureFitsAt(targetTile, item, item.getRotation(), false) == FurnitureMovementError.NONE) { if (!hasRoomUnits) { RoomTile oldLocation = room.getLayout().getTile(entry.getKey().getX(), entry.getKey().getY()); double oldZ = entry.getKey().getZ(); - if(room.moveFurniTo(entry.getKey(), targetTile, item.getRotation(), null, false) == FurnitureMovementError.NONE) { + if(oldLocation != null && room.moveFurniTo(entry.getKey(), targetTile, item.getRotation(), null, false) == FurnitureMovementError.NONE) { room.sendComposer(new FloorItemOnRollerComposer(entry.getKey(), null, oldLocation, oldZ, targetTile, entry.getKey().getZ(), 0, room).compose()); } } } } + } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { return false; } @Override public String getWiredData() { ArrayList settings = new ArrayList<>(this.items.values()); - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.startRotation, this.blockedAction, settings, this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.startRotation, this.blockedAction, settings, this.getDelay())); } @Override @@ -131,7 +151,7 @@ public class WiredEffectChangeFurniDirection extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.startRotation = data.start_direction; this.blockedAction = data.blocked_action; @@ -195,7 +215,7 @@ public class WiredEffectChangeFurniDirection extends InteractionWiredEffect { @Override public void serializeWiredData(ServerMessage message, Room room) { message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (Map.Entry item : this.items.entrySet()) { message.appendInt(item.getKey().getId()); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveEffect.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveEffect.java index 71345755..eece5048 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveEffect.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveEffect.java @@ -3,6 +3,7 @@ package com.eu.habbo.habbohotel.items.interactions.wired.effects; import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import java.sql.ResultSet; import java.sql.SQLException; @@ -17,20 +18,20 @@ public class WiredEffectGiveEffect extends WiredEffectWhisper { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { int effectId; try { effectId = Integer.parseInt(this.message); } catch (Exception e) { - return false; + return; } - if (effectId >= 0) { + Room room = ctx.room(); + RoomUnit roomUnit = ctx.actor().orElse(null); + + if (effectId >= 0 && roomUnit != null) { room.giveEffect(roomUnit, effectId, Integer.MAX_VALUE); - return true; } - - return false; } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveHandItem.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveHandItem.java index 468cadaa..da7261b1 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveHandItem.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveHandItem.java @@ -2,8 +2,8 @@ package com.eu.habbo.habbohotel.items.interactions.wired.effects; import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.rooms.Room; -import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import java.sql.ResultSet; import java.sql.SQLException; @@ -18,17 +18,17 @@ public class WiredEffectGiveHandItem extends WiredEffectWhisper { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { try { int itemId = Integer.parseInt(this.message); - Habbo habbo = room.getHabbo(roomUnit); + Room room = ctx.room(); + Habbo habbo = ctx.actor().map(room::getHabbo).orElse(null); if (habbo != null) { room.giveHandItem(habbo, itemId); } } catch (Exception e) { } - return false; } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveHotelviewBonusRarePoints.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveHotelviewBonusRarePoints.java index 7a54401f..0834f54d 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveHotelviewBonusRarePoints.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveHotelviewBonusRarePoints.java @@ -10,7 +10,8 @@ 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.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.outgoing.hotelview.BonusRareComposer; import gnu.trove.procedure.TObjectProcedure; @@ -85,23 +86,27 @@ public class WiredEffectGiveHotelviewBonusRarePoints extends InteractionWiredEff } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - Habbo habbo = room.getHabbo(roomUnit); + public void execute(WiredContext ctx) { + Habbo habbo = ctx.actor().map(unit -> ctx.room().getHabbo(unit)).orElse(null); if (habbo == null) - return false; + return; if (this.amount > 0) { habbo.givePoints(Emulator.getConfig().getInt("hotelview.promotional.points.type"), this.amount); habbo.getClient().sendResponse(new BonusRareComposer(habbo)); } + } - return true; + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.getDelay(), this.amount)); + return WiredManager.getGson().toJson(new JsonData(this.getDelay(), this.amount)); } @Override @@ -110,7 +115,7 @@ public class WiredEffectGiveHotelviewBonusRarePoints extends InteractionWiredEff this.amount = 0; if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.amount = data.amount; } else { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveHotelviewHofPoints.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveHotelviewHofPoints.java index 5af1cd99..56d1cf94 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveHotelviewHofPoints.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveHotelviewHofPoints.java @@ -10,7 +10,8 @@ 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.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import gnu.trove.procedure.TObjectProcedure; @@ -84,23 +85,27 @@ public class WiredEffectGiveHotelviewHofPoints extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - Habbo habbo = room.getHabbo(roomUnit); + public void execute(WiredContext ctx) { + Habbo habbo = ctx.actor().map(unit -> ctx.room().getHabbo(unit)).orElse(null); if (habbo == null) - return false; + return; if (this.amount > 0) { habbo.getHabboStats().hofPoints += this.amount; Emulator.getThreading().run(habbo.getHabboStats()); } + } - return true; + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.amount, this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.amount, this.getDelay())); } @Override @@ -108,7 +113,7 @@ public class WiredEffectGiveHotelviewHofPoints extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.amount = data.amount; this.setDelay(data.delay); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveRespect.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveRespect.java index a0aa38bf..94e40db0 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveRespect.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveRespect.java @@ -11,7 +11,8 @@ 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.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; import gnu.trove.procedure.TObjectProcedure; @@ -85,21 +86,26 @@ public class WiredEffectGiveRespect extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - Habbo habbo = room.getHabbo(roomUnit); + public void execute(WiredContext ctx) { + Room room = ctx.room(); + Habbo habbo = ctx.actor().map(room::getHabbo).orElse(null); if (habbo == null) - return false; + return; habbo.getHabboStats().respectPointsReceived += this.respects; AchievementManager.progressAchievement(habbo, Emulator.getGameEnvironment().getAchievementManager().getAchievement("RespectEarned"), this.respects); + } - return true; + @Override + @Deprecated + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.respects, this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.respects, this.getDelay())); } @Override @@ -107,7 +113,7 @@ public class WiredEffectGiveRespect extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.respects = data.amount; this.setDelay(data.delay); } @@ -148,4 +154,4 @@ public class WiredEffectGiveRespect extends InteractionWiredEffect { this.delay = delay; } } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveReward.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveReward.java index fb6f5a76..a6c2bae3 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveReward.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveReward.java @@ -13,7 +13,8 @@ import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.wired.WiredEffectType; import com.eu.habbo.habbohotel.wired.WiredGiveRewardItem; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import com.eu.habbo.messages.outgoing.generic.alerts.UpdateFailedComposer; @@ -26,19 +27,19 @@ import java.util.ArrayList; import java.util.List; public class WiredEffectGiveReward extends InteractionWiredEffect { - public final static int LIMIT_ONCE = 0; - public final static int LIMIT_N_DAY = 1; - public final static int LIMIT_N_HOURS = 2; - public final static int LIMIT_N_MINUTES = 3; + public static final int LIMIT_ONCE = 0; + public static final int LIMIT_N_DAY = 1; + public static final int LIMIT_N_HOURS = 2; + public static final int LIMIT_N_MINUTES = 3; - public final static WiredEffectType type = WiredEffectType.GIVE_REWARD; - public int limit; - public int limitationInterval; - public int given; - public int rewardTime; - public boolean uniqueRewards; - - public THashSet rewardItems = new THashSet<>(); + public static final WiredEffectType type = WiredEffectType.GIVE_REWARD; + + private int limit; + private int limitationInterval; + private int given; + private int rewardTime; + private boolean uniqueRewards; + private THashSet rewardItems = new THashSet<>(); public WiredEffectGiveReward(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -49,17 +50,29 @@ public class WiredEffectGiveReward extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); + RoomUnit roomUnit = ctx.actor().orElse(null); + if (roomUnit == null) return; + Habbo habbo = room.getHabbo(roomUnit); - return habbo != null && WiredHandler.getReward(habbo, this); + if (habbo != null) { + WiredManager.getReward(habbo, this); + } + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; } @Override public String getWiredData() { ArrayList rewards = new ArrayList<>(this.rewardItems); - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.limit, this.given, this.rewardTime, this.uniqueRewards, this.limitationInterval, rewards, this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.limit, this.given, this.rewardTime, this.uniqueRewards, this.limitationInterval, rewards, this.getDelay())); } @Override @@ -67,7 +80,7 @@ public class WiredEffectGiveReward extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.limit = data.limit; this.given = data.given; @@ -207,7 +220,7 @@ public class WiredEffectGiveReward extends InteractionWiredEffect { this.setDelay(settings.getDelay()); - WiredHandler.dropRewards(this.getId()); + WiredManager.dropRewards(this.getId()); return true; } @@ -222,7 +235,7 @@ public class WiredEffectGiveReward extends InteractionWiredEffect { @Override protected long requiredCooldown() { - return 0; + return COOLDOWN_NONE; } static class JsonData { @@ -244,4 +257,58 @@ public class WiredEffectGiveReward extends InteractionWiredEffect { this.delay = delay; } } + + // Getters and Setters + + public int getLimit() { + return this.limit; + } + + public void setLimit(int limit) { + this.limit = limit; + } + + public int getLimitationInterval() { + return this.limitationInterval; + } + + public void setLimitationInterval(int limitationInterval) { + this.limitationInterval = limitationInterval; + } + + public int getGiven() { + return this.given; + } + + public void setGiven(int given) { + this.given = given; + } + + public void incrementGiven() { + this.given++; + } + + public int getRewardTime() { + return this.rewardTime; + } + + public void setRewardTime(int rewardTime) { + this.rewardTime = rewardTime; + } + + public boolean isUniqueRewards() { + return this.uniqueRewards; + } + + public void setUniqueRewards(boolean uniqueRewards) { + this.uniqueRewards = uniqueRewards; + } + + public THashSet getRewardItems() { + return this.rewardItems; + } + + public void setRewardItems(THashSet rewardItems) { + this.rewardItems = rewardItems; + } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveScore.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveScore.java index 10a73a12..c5c86e74 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveScore.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveScore.java @@ -11,7 +11,8 @@ 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.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import gnu.trove.iterator.TObjectIntIterator; @@ -32,7 +33,7 @@ public class WiredEffectGiveScore extends InteractionWiredEffect { private int score; private int count; - private final TObjectIntMap> data = new TObjectIntHashMap<>(); + private TObjectIntMap> data = new TObjectIntHashMap<>(); public WiredEffectGiveScore(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -43,14 +44,15 @@ public class WiredEffectGiveScore extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - Habbo habbo = room.getHabbo(roomUnit); + public void execute(WiredContext ctx) { + Room room = ctx.room(); + Habbo habbo = ctx.actor().map(room::getHabbo).orElse(null); if (habbo != null && habbo.getHabboInfo().getCurrentGame() != null) { Game game = room.getGame(habbo.getHabboInfo().getCurrentGame()); if (game == null) - return false; + return; int gameStartTime = game.getStartTime(); @@ -70,7 +72,7 @@ public class WiredEffectGiveScore extends InteractionWiredEffect { habbo.getHabboInfo().getGamePlayer().addScore(this.score, true); - return true; + return; } } else { iterator.remove(); @@ -89,16 +91,18 @@ public class WiredEffectGiveScore extends InteractionWiredEffect { if (habbo.getHabboInfo().getGamePlayer() != null) { habbo.getHabboInfo().getGamePlayer().addScore(this.score, true); } - - return true; } + } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.score, this.count, this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.score, this.count, this.getDelay())); } @Override @@ -106,7 +110,7 @@ public class WiredEffectGiveScore extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.score = data.score; this.count = data.count; this.setDelay(data.delay); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveScoreToTeam.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveScoreToTeam.java index af27870e..7b2c0efd 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveScoreToTeam.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectGiveScoreToTeam.java @@ -12,7 +12,8 @@ import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import gnu.trove.map.hash.TIntIntHashMap; @@ -27,7 +28,7 @@ public class WiredEffectGiveScoreToTeam extends InteractionWiredEffect { private int count; private GameTeamColors teamColor = GameTeamColors.RED; - private final TIntIntHashMap startTimes = new TIntIntHashMap(); + private TIntIntHashMap startTimes = new TIntIntHashMap(); public WiredEffectGiveScoreToTeam(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { super(id, userId, item, extradata, limitedStack, limitedSells); @@ -38,7 +39,8 @@ public class WiredEffectGiveScoreToTeam extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); for (Game game : room.getGames()) { if (game != null && game.state.equals(GameState.RUNNING)) { int c = this.startTimes.get(game.getStartTime()); @@ -54,13 +56,17 @@ public class WiredEffectGiveScoreToTeam extends InteractionWiredEffect { } } } + } - return true; + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.points, this.count, this.teamColor, this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.points, this.count, this.teamColor, this.getDelay())); } @Override @@ -68,7 +74,7 @@ public class WiredEffectGiveScoreToTeam extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.points = data.score; this.count = data.count; this.teamColor = data.team; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectJoinTeam.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectJoinTeam.java index 4b4a686f..5664771a 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectJoinTeam.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectJoinTeam.java @@ -13,7 +13,8 @@ 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.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import gnu.trove.procedure.TObjectProcedure; @@ -37,8 +38,9 @@ public class WiredEffectJoinTeam extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - Habbo habbo = room.getHabbo(roomUnit); + public void execute(WiredContext ctx) { + Room room = ctx.room(); + Habbo habbo = ctx.actor().map(room::getHabbo).orElse(null); if (habbo != null) { WiredGame game = (WiredGame) room.getGameOrCreate(WiredGame.class); @@ -52,16 +54,18 @@ public class WiredEffectJoinTeam extends InteractionWiredEffect { if(habbo.getHabboInfo().getGamePlayer() == null) { game.addHabbo(habbo, this.teamColor); } - - return true; } + } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.teamColor, this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.teamColor, this.getDelay())); } @Override @@ -69,7 +73,7 @@ public class WiredEffectJoinTeam extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.teamColor = data.team; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectKickHabbo.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectKickHabbo.java index cb796b17..c4f765df 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectKickHabbo.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectKickHabbo.java @@ -13,7 +13,8 @@ import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserWhisperComposer; @@ -39,21 +40,19 @@ public class WiredEffectKickHabbo extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - if (room == null) - return false; - - Habbo habbo = room.getHabbo(roomUnit); + public void execute(WiredContext ctx) { + Room room = ctx.room(); + Habbo habbo = ctx.actor().map(room::getHabbo).orElse(null); if (habbo != null) { if (habbo.hasPermission(Permission.ACC_UNKICKABLE)) { habbo.whisper(Emulator.getTexts().getValue("hotel.wired.kickexception.unkickable")); - return true; + return; } if (habbo.getHabboInfo().getId() == room.getOwnerId()) { habbo.whisper(Emulator.getTexts().getValue("hotel.wired.kickexception.owner")); - return true; + return; } room.giveEffect(habbo, 4, 2); @@ -62,16 +61,18 @@ public class WiredEffectKickHabbo extends InteractionWiredEffect { habbo.getClient().sendResponse(new RoomUserWhisperComposer(new RoomChatMessage(this.message, habbo, habbo, RoomChatMessageBubbles.ALERT))); Emulator.getThreading().run(new RoomUnitKick(habbo, room, true), 2000); - - return true; } + } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.message, this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.message, this.getDelay())); } @Override @@ -79,7 +80,7 @@ public class WiredEffectKickHabbo extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.message = data.message; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectLeaveTeam.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectLeaveTeam.java index 500ef04d..881c9efd 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectLeaveTeam.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectLeaveTeam.java @@ -12,7 +12,8 @@ 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.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import gnu.trove.procedure.TObjectProcedure; @@ -34,8 +35,9 @@ public class WiredEffectLeaveTeam extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - Habbo habbo = room.getHabbo(roomUnit); + public void execute(WiredContext ctx) { + Room room = ctx.room(); + Habbo habbo = ctx.actor().map(room::getHabbo).orElse(null); if (habbo != null) { if (habbo.getHabboInfo().getCurrentGame() != null) { @@ -47,16 +49,20 @@ public class WiredEffectLeaveTeam extends InteractionWiredEffect { if (game != null) { game.removeHabbo(habbo); - return true; } } } + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.getDelay())); } @Override @@ -64,7 +70,7 @@ public class WiredEffectLeaveTeam extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); } else { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMatchFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMatchFurni.java index d793cc23..5c11be6f 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMatchFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMatchFurni.java @@ -8,8 +8,9 @@ import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.items.interactions.wired.interfaces.InteractionWiredMatchFurniSettings; import com.eu.habbo.habbohotel.rooms.*; import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.habbohotel.wired.WiredMatchFurniSetting; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; @@ -45,10 +46,14 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); if(this.settings.isEmpty()) - return true; + return; + + if (room.getLayout() == null) + return; for (WiredMatchFurniSetting setting : this.settings) { HabboItem item = room.getHabboItem(setting.item_id); @@ -61,6 +66,7 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int } RoomTile oldLocation = room.getLayout().getTile(item.getX(), item.getY()); + if (oldLocation == null) continue; double oldZ = item.getZ(); if(this.direction && !this.position) { @@ -84,14 +90,18 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int } } + } - return true; + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; } @Override public String getWiredData() { this.refresh(); - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.state, this.direction, this.position, new ArrayList(this.settings), this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.state, this.direction, this.position, new ArrayList(this.settings), this.getDelay())); } @Override @@ -99,7 +109,7 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.state = data.state; this.direction = data.direction; @@ -155,7 +165,7 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int this.refresh(); message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.settings.size()); for (WiredMatchFurniSetting item : this.settings) @@ -196,7 +206,7 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int for (int i = 0; i < itemsCount; i++) { int itemId = settings.getFurniIds()[i]; - HabboItem it = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(itemId); + HabboItem it = room.getHabboItem(itemId); if(it == null) throw new WiredSaveException(String.format("Item %s not found", itemId)); @@ -223,18 +233,8 @@ public class WiredEffectMatchFurni extends InteractionWiredEffect implements Int Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()); if (room != null && room.isLoaded()) { - THashSet remove = new THashSet<>(); - - for (WiredMatchFurniSetting setting : this.settings) { - HabboItem item = room.getHabboItem(setting.item_id); - if (item == null) { - remove.add(setting); - } - } - - for (WiredMatchFurniSetting setting : remove) { - this.settings.remove(setting); - } + // Use removeIf for O(n) instead of O(n²) with separate remove set + this.settings.removeIf(setting -> room.getHabboItem(setting.item_id) == null); } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniAway.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniAway.java index a4e7f11c..1cf00a46 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniAway.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniAway.java @@ -8,8 +8,9 @@ import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.*; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredSimulation; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import com.eu.habbo.messages.outgoing.rooms.items.FloorItemOnRollerComposer; @@ -36,7 +37,10 @@ public class WiredEffectMoveFurniAway extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); + if (room.getLayout() == null) return; + THashSet items = new THashSet<>(); for (HabboItem item : this.items) { @@ -47,13 +51,18 @@ public class WiredEffectMoveFurniAway extends InteractionWiredEffect { this.items.removeAll(items); for (HabboItem item : this.items) { + if (item == null) continue; + RoomTile t = room.getLayout().getTile(item.getX(), item.getY()); + if (t == null) continue; RoomUnit target = room.getRoomUnits().stream().min(Comparator.comparingDouble(a -> a.getCurrentLocation().distance(t))).orElse(null); if (target != null) { if (target.getCurrentLocation().distance(t) <= 1) { - Emulator.getThreading().run(() -> WiredHandler.handle(WiredTriggerType.COLLISION, target, room, new Object[]{item}), 500); + Emulator.getThreading().run(() -> { + WiredManager.triggerBotCollision(room, target); + }, 500); continue; } @@ -93,12 +102,63 @@ public class WiredEffectMoveFurniAway extends InteractionWiredEffect { } } } + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public boolean simulate(WiredContext ctx, WiredSimulation simulation) { + Room room = ctx.room(); + if (room.getLayout() == null) return true; + + for (HabboItem item : this.items) { + if (item == null) continue; + + WiredSimulation.SimulatedPosition currentPos = simulation.getItemPosition(item); + RoomTile t = room.getLayout().getTile(currentPos.x, currentPos.y); + if (t == null) continue; + + RoomUnit target = room.getRoomUnits().stream() + .min(Comparator.comparingDouble(a -> a.getCurrentLocation().distance(t))) + .orElse(null); + + if (target != null && target.getCurrentLocation().distance(t) > 1) { + int x = 0; + int y = 0; + + if (target.getX() == currentPos.x) { + y = currentPos.y < target.getY() ? -1 : 1; + } else if (target.getY() == currentPos.y) { + x = currentPos.x < target.getX() ? -1 : 1; + } else if (target.getX() - currentPos.x > target.getY() - currentPos.y) { + x = target.getX() - currentPos.x > 0 ? -1 : 1; + } else { + y = target.getY() - currentPos.y > 0 ? -1 : 1; + } + + short newX = (short) (currentPos.x + x); + short newY = (short) (currentPos.y + y); + + if (!simulation.isTileValidForItem(newX, newY, item)) { + return false; + } + + if (!simulation.moveItem(item, newX, newY, currentPos.z, currentPos.rotation)) { + return false; + } + } + } + return true; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.getDelay(), this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); @@ -110,7 +170,7 @@ public class WiredEffectMoveFurniAway extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); for (Integer id: data.itemIds) { HabboItem item = room.getHabboItem(id); @@ -161,7 +221,7 @@ public class WiredEffectMoveFurniAway extends InteractionWiredEffect { this.items.remove(item); } message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) message.appendInt(item.getId()); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniTo.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniTo.java index 90e5bdfe..5ebb283d 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniTo.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniTo.java @@ -10,7 +10,9 @@ import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredSimulation; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import com.eu.habbo.messages.outgoing.rooms.items.FloorItemOnRollerComposer; @@ -29,7 +31,7 @@ public class WiredEffectMoveFurniTo extends InteractionWiredEffect { private final List items = new ArrayList<>(); private int direction; private int spacing = 1; - private final Map indexOffset = new LinkedHashMap<>(); + private Map indexOffset = new LinkedHashMap<>(); public WiredEffectMoveFurniTo(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -69,11 +71,14 @@ public class WiredEffectMoveFurniTo extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); + if (room == null || room.getLayout() == null) return; + List items = new ArrayList<>(); for (HabboItem item : this.items) { - if (Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) + if (item == null || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) items.add(item); } @@ -82,9 +87,10 @@ public class WiredEffectMoveFurniTo extends InteractionWiredEffect { } if (this.items.isEmpty()) - return false; + return; - if (stuff != null) { + Object[] stuff = ctx.legacySettings(); + if (stuff != null && stuff.length > 0) { for (Object object : stuff) { if (object instanceof HabboItem) { HabboItem targetItem = this.items.get(Emulator.getRandom().nextInt(this.items.size())); @@ -100,7 +106,10 @@ public class WiredEffectMoveFurniTo extends InteractionWiredEffect { RoomTile objectTile = room.getLayout().getTile(targetItem.getX(), targetItem.getY()); if (objectTile != null) { - THashSet refreshTiles = room.getLayout().getTilesAt(room.getLayout().getTile(((HabboItem) object).getX(), ((HabboItem) object).getY()), ((HabboItem) object).getBaseItem().getWidth(), ((HabboItem) object).getBaseItem().getLength(), ((HabboItem) object).getRotation()); + RoomTile sourceTile = room.getLayout().getTile(((HabboItem) object).getX(), ((HabboItem) object).getY()); + if (sourceTile == null) continue; + + THashSet refreshTiles = room.getLayout().getTilesAt(sourceTile, ((HabboItem) object).getBaseItem().getWidth(), ((HabboItem) object).getBaseItem().getLength(), ((HabboItem) object).getRotation()); RoomTile tile = room.getLayout().getTileInFront(objectTile, this.direction, indexOffset); if (tile == null || !tile.getAllowStack()) { @@ -108,8 +117,16 @@ public class WiredEffectMoveFurniTo extends InteractionWiredEffect { tile = room.getLayout().getTileInFront(objectTile, this.direction, indexOffset); } + if(tile == null) { + continue; + } + room.sendComposer(new FloorItemOnRollerComposer((HabboItem) object, null, tile, tile.getStackHeight() - ((HabboItem) object).getZ(), room).compose()); - refreshTiles.addAll(room.getLayout().getTilesAt(room.getLayout().getTile(((HabboItem) object).getX(), ((HabboItem) object).getY()), ((HabboItem) object).getBaseItem().getWidth(), ((HabboItem) object).getBaseItem().getLength(), ((HabboItem) object).getRotation())); + + RoomTile newSourceTile = room.getLayout().getTile(((HabboItem) object).getX(), ((HabboItem) object).getY()); + if (newSourceTile != null) { + refreshTiles.addAll(room.getLayout().getTilesAt(newSourceTile, ((HabboItem) object).getBaseItem().getWidth(), ((HabboItem) object).getBaseItem().getLength(), ((HabboItem) object).getRotation())); + } room.updateTiles(refreshTiles); this.indexOffset.put(targetItem.getId(), indexOffset); } @@ -117,7 +134,47 @@ public class WiredEffectMoveFurniTo extends InteractionWiredEffect { } } } + } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public boolean simulate(WiredContext ctx, WiredSimulation simulation) { + Room room = ctx.room(); + if (room == null || room.getLayout() == null) return true; + + Object[] stuff = ctx.legacySettings(); + if (stuff == null || stuff.length == 0) return true; + + for (Object object : stuff) { + if (object instanceof HabboItem) { + HabboItem item = (HabboItem) object; + + if (this.items.isEmpty()) continue; + HabboItem targetItem = this.items.get(0); + if (targetItem == null) continue; + + WiredSimulation.SimulatedPosition targetPos = simulation.getItemPosition(targetItem); + RoomTile objectTile = room.getLayout().getTile(targetPos.x, targetPos.y); + if (objectTile == null) continue; + + RoomTile tile = room.getLayout().getTileInFront(objectTile, this.direction, 0); + if (tile == null) continue; + + WiredSimulation.SimulatedPosition currentPos = simulation.getItemPosition(item); + if (!simulation.isTileValidForItem(tile.x, tile.y, item)) { + return false; + } + if (!simulation.moveItem(item, tile.x, tile.y, tile.getStackHeight(), currentPos.rotation)) { + return false; + } + } + } + return true; } @@ -134,7 +191,7 @@ public class WiredEffectMoveFurniTo extends InteractionWiredEffect { this.items.remove(item); } - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.direction, this.spacing, this.getDelay(), @@ -156,7 +213,7 @@ public class WiredEffectMoveFurniTo extends InteractionWiredEffect { } message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) message.appendInt(item.getId()); @@ -178,7 +235,7 @@ public class WiredEffectMoveFurniTo extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.direction = data.direction; this.spacing = data.spacing; this.setDelay(data.delay); @@ -237,4 +294,4 @@ public class WiredEffectMoveFurniTo extends InteractionWiredEffect { this.itemIds = itemIds; } } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniTowards.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniTowards.java index aaadf032..aa49c097 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniTowards.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveFurniTowards.java @@ -8,7 +8,9 @@ import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.*; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredSimulation; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import com.eu.habbo.messages.outgoing.rooms.items.FloorItemOnRollerComposer; @@ -34,7 +36,7 @@ public class WiredEffectMoveFurniTowards extends InteractionWiredEffect { private THashSet items; - private final THashMap lastDirections; + private THashMap lastDirections; public WiredEffectMoveFurniTowards(ResultSet set, Item baseItem) throws SQLException { @@ -53,7 +55,10 @@ public class WiredEffectMoveFurniTowards extends InteractionWiredEffect { List availableDirections = new ArrayList<>(); RoomLayout layout = room.getLayout(); + if (layout == null) return availableDirections; + RoomTile currentTile = layout.getTile(item.getX(), item.getY()); + if (currentTile == null) return availableDirections; RoomUserRotation[] rotations = new RoomUserRotation[]{RoomUserRotation.NORTH, RoomUserRotation.EAST, RoomUserRotation.SOUTH, RoomUserRotation.WEST}; @@ -82,7 +87,8 @@ public class WiredEffectMoveFurniTowards extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); THashSet items = new THashSet<>(); @@ -135,7 +141,7 @@ public class WiredEffectMoveFurniTowards extends InteractionWiredEffect { target = roomUnitsAtTile.iterator().next(); if (i == 0) { // i = 0 means right next to it collided = true; - Emulator.getThreading().run(new WiredCollissionRunnable(target, room, new Object[]{item})); + Emulator.getThreading().run(new WiredCollissionRunnable(target, room)); } break; } @@ -213,9 +219,11 @@ public class WiredEffectMoveFurniTowards extends InteractionWiredEffect { } } - RoomTile newTile = room.getLayout().getTileInFront(room.getLayout().getTile(item.getX(), item.getY()), moveDirection.getValue()); - RoomTile oldLocation = room.getLayout().getTile(item.getX(), item.getY()); + if (oldLocation == null) continue; + + RoomTile newTile = room.getLayout().getTileInFront(oldLocation, moveDirection.getValue()); + double oldZ = item.getZ(); if(newTile != null) { @@ -227,13 +235,83 @@ public class WiredEffectMoveFurniTowards extends InteractionWiredEffect { } } } + } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + + @Override + public boolean simulate(WiredContext ctx, WiredSimulation simulation) { + Room room = ctx.room(); + RoomLayout layout = room.getLayout(); + if (layout == null) return true; + + for (HabboItem item : this.items) { + if (item == null) continue; + + WiredSimulation.SimulatedPosition currentPos = simulation.getItemPosition(item); + RoomTile currentTile = layout.getTile(currentPos.x, currentPos.y); + if (currentTile == null) continue; + + RoomUnit target = null; + + for (int i = 0; i < 3; i++) { + if (target != null) break; + + RoomUserRotation[] rotations = new RoomUserRotation[]{RoomUserRotation.NORTH, RoomUserRotation.EAST, RoomUserRotation.SOUTH, RoomUserRotation.WEST}; + + for (RoomUserRotation rot : rotations) { + RoomTile startTile = currentTile; + + for (int ii = 0; ii <= i; ii++) { + if (startTile == null) break; + startTile = layout.getTileInFront(startTile, rot.getValue()); + } + + if (startTile != null && layout.tileExists(startTile.x, startTile.y)) { + Collection roomUnitsAtTile = room.getRoomUnitsAt(startTile); + if (!roomUnitsAtTile.isEmpty()) { + target = roomUnitsAtTile.iterator().next(); + break; + } + } + } + } + + if (target != null) { + RoomUserRotation moveDirection; + + if (target.getX() == currentPos.x) { + moveDirection = currentPos.y < target.getY() ? RoomUserRotation.SOUTH : RoomUserRotation.NORTH; + } else if (target.getY() == currentPos.y) { + moveDirection = currentPos.x < target.getX() ? RoomUserRotation.EAST : RoomUserRotation.WEST; + } else if (target.getX() - currentPos.x > target.getY() - currentPos.y) { + moveDirection = target.getX() - currentPos.x > 0 ? RoomUserRotation.EAST : RoomUserRotation.WEST; + } else { + moveDirection = target.getY() - currentPos.y > 0 ? RoomUserRotation.SOUTH : RoomUserRotation.NORTH; + } + + RoomTile newTile = layout.getTileInFront(currentTile, moveDirection.getValue()); + if (newTile != null && newTile.state != RoomTileState.INVALID) { + if (!simulation.isTileValidForItem(newTile.x, newTile.y, item)) { + return false; + } + if (!simulation.moveItem(item, newTile.x, newTile.y, currentPos.z, currentPos.rotation)) { + return false; + } + } + } + } + return true; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.getDelay(), this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); @@ -245,7 +323,7 @@ public class WiredEffectMoveFurniTowards extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); for (Integer id: data.itemIds) { @@ -297,7 +375,7 @@ public class WiredEffectMoveFurniTowards extends InteractionWiredEffect { this.items.remove(item); } message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) message.appendInt(item.getId()); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveRotateFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveRotateFurni.java index 9aa91491..37557c37 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveRotateFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMoveRotateFurni.java @@ -9,7 +9,9 @@ import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.*; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredSimulation; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import com.eu.habbo.messages.outgoing.rooms.items.FloorItemOnRollerComposer; @@ -17,30 +19,33 @@ import gnu.trove.set.hash.THashSet; import java.sql.ResultSet; import java.sql.SQLException; -import java.util.Iterator; -import java.util.List; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; public class WiredEffectMoveRotateFurni extends InteractionWiredEffect implements ICycleable { public static final WiredEffectType type = WiredEffectType.MOVE_ROTATE; - private final THashSet items = new THashSet<>(WiredHandler.MAXIMUM_FURNI_SELECTION / 2); + // Use LinkedHashSet to preserve insertion order for consistent movement + private final Set items = new LinkedHashSet<>(WiredManager.MAXIMUM_FURNI_SELECTION / 2); private int direction; private int rotation; - private final THashSet itemCooldowns; + // Use thread-safe set for cooldowns since execute() can be called from async threads + private final Set itemCooldowns = ConcurrentHashMap.newKeySet(); + // Pre-selected directions from simulation (itemId -> direction) + private final Map preSelectedDirections = new ConcurrentHashMap<>(); public WiredEffectMoveRotateFurni(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); - this.itemCooldowns = new THashSet<>(); } public WiredEffectMoveRotateFurni(int id, int userId, Item item, String extradata, int limitedStack, int limitedSells) { super(id, userId, item, extradata, limitedStack, limitedSells); - this.itemCooldowns = new THashSet<>(); } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); // remove items that are no longer in the room this.items.removeIf(item -> Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null); @@ -54,7 +59,11 @@ public class WiredEffectMoveRotateFurni extends InteractionWiredEffect implement double oldZ = item.getZ(); if(this.direction > 0) { - RoomUserRotation moveDirection = this.getMovementDirection(); + // Use pre-selected direction if available, otherwise pick random + RoomUserRotation moveDirection = this.preSelectedDirections.remove(item.getId()); + if (moveDirection == null) { + moveDirection = this.getMovementDirection(); + } newLocation = room.getLayout().getTile( (short) (item.getX() + ((moveDirection == RoomUserRotation.WEST || moveDirection == RoomUserRotation.NORTH_WEST || moveDirection == RoomUserRotation.SOUTH_WEST) ? -1 : (((moveDirection == RoomUserRotation.EAST || moveDirection == RoomUserRotation.SOUTH_EAST || moveDirection == RoomUserRotation.NORTH_EAST) ? 1 : 0)))), (short) (item.getY() + ((moveDirection == RoomUserRotation.NORTH || moveDirection == RoomUserRotation.NORTH_EAST || moveDirection == RoomUserRotation.NORTH_WEST) ? 1 : ((moveDirection == RoomUserRotation.SOUTH || moveDirection == RoomUserRotation.SOUTH_EAST || moveDirection == RoomUserRotation.SOUTH_WEST) ? -1 : 0))) @@ -73,13 +82,54 @@ public class WiredEffectMoveRotateFurni extends InteractionWiredEffect implement } } } + } + @Override + public boolean simulate(WiredContext ctx, WiredSimulation simulation) { + // Clear any previous pre-selected directions + this.preSelectedDirections.clear(); + + for (HabboItem item : this.items) { + if (item == null) continue; + + WiredSimulation.SimulatedPosition currentPos = simulation.getItemPosition(item); + short newX = currentPos.x; + short newY = currentPos.y; + + if (this.direction > 0) { + // Pick the actual random direction now (same logic as getMovementDirection) + RoomUserRotation selectedDirection = this.getMovementDirection(); + + // Calculate target position for the selected direction + short testX = (short) (currentPos.x + ((selectedDirection == RoomUserRotation.WEST || selectedDirection == RoomUserRotation.NORTH_WEST || selectedDirection == RoomUserRotation.SOUTH_WEST) ? -1 : + (((selectedDirection == RoomUserRotation.EAST || selectedDirection == RoomUserRotation.SOUTH_EAST || selectedDirection == RoomUserRotation.NORTH_EAST) ? 1 : 0)))); + short testY = (short) (currentPos.y + ((selectedDirection == RoomUserRotation.NORTH || selectedDirection == RoomUserRotation.NORTH_EAST || selectedDirection == RoomUserRotation.NORTH_WEST) ? 1 : + ((selectedDirection == RoomUserRotation.SOUTH || selectedDirection == RoomUserRotation.SOUTH_EAST || selectedDirection == RoomUserRotation.SOUTH_WEST) ? -1 : 0))); + + // Validate this specific direction + if (!simulation.isTileValidForItem(testX, testY, item)) { + return false; // This specific move would fail + } + + // Store the pre-selected direction for execution + this.preSelectedDirections.put(item.getId(), selectedDirection); + newX = testX; + newY = testY; + } + + if (newX != currentPos.x || newY != currentPos.y) { + if (!simulation.moveItem(item, newX, newY, currentPos.z, currentPos.rotation)) { + return false; + } + } + } + return true; } @Override public String getWiredData() { - THashSet itemsToRemove = new THashSet<>(this.items.size() / 2); + List itemsToRemove = new ArrayList<>(); Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()); @@ -92,7 +142,7 @@ public class WiredEffectMoveRotateFurni extends InteractionWiredEffect implement this.items.remove(item); } - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.direction, this.rotation, this.getDelay(), @@ -106,7 +156,7 @@ public class WiredEffectMoveRotateFurni extends InteractionWiredEffect implement String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.direction = data.direction; this.rotation = data.rotation; @@ -153,19 +203,19 @@ public class WiredEffectMoveRotateFurni extends InteractionWiredEffect implement @Override public void serializeWiredData(ServerMessage message, Room room) { - THashSet items = new THashSet<>(); + List itemsToRemove = new ArrayList<>(); for (HabboItem item : this.items) { if (item.getRoomId() != this.getRoomId() || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null) - items.add(item); + itemsToRemove.add(item); } - for (HabboItem item : items) { + for (HabboItem item : itemsToRemove) { this.items.remove(item); } message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) message.appendInt(item.getId()); @@ -198,7 +248,10 @@ public class WiredEffectMoveRotateFurni extends InteractionWiredEffect implement this.items.clear(); for (int i = 0; i < count; i++) { - this.items.add(room.getHabboItem(settings.getFurniIds()[i])); + HabboItem item = room.getHabboItem(settings.getFurniIds()[i]); + if (item != null) { + this.items.add(item); + } } this.setDelay(settings.getDelay()); @@ -214,8 +267,6 @@ public class WiredEffectMoveRotateFurni extends InteractionWiredEffect implement * @return new rotation */ private int getNewRotation(HabboItem item) { - int rotationToAdd = 0; - if(item.getMaximumRotations() == 2) { return item.getRotation() == 0 ? 4 : 0; } @@ -310,6 +361,12 @@ public class WiredEffectMoveRotateFurni extends InteractionWiredEffect implement @Override public void cycle(Room room) { this.itemCooldowns.clear(); + this.preSelectedDirections.clear(); + } + + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; } static class JsonData { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMuteHabbo.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMuteHabbo.java index bfb81afa..ce053643 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMuteHabbo.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectMuteHabbo.java @@ -11,7 +11,8 @@ import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +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.messages.incoming.wired.WiredSaveException; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserWhisperComposer; @@ -62,27 +63,33 @@ public class WiredEffectMuteHabbo extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + RoomUnit roomUnit = ctx.actor().orElse(null); if (roomUnit == null) - return true; + return; + Room room = ctx.room(); Habbo habbo = room.getHabbo(roomUnit); if (habbo != null) { if (room.hasRights(habbo)) - return false; + return; room.muteHabbo(habbo, 60); habbo.getClient().sendResponse(new RoomUserWhisperComposer(new RoomChatMessage(this.message.replace("%user%", habbo.getHabboInfo().getUsername()).replace("%online_count%", Emulator.getGameEnvironment().getHabboManager().getOnlineCount() + "").replace("%room_count%", Emulator.getGameEnvironment().getRoomManager().getActiveRooms().size() + ""), habbo, habbo, RoomChatMessageBubbles.WIRED))); } + } - return true; + @Override + @Deprecated + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.getDelay(), this.length, this.message @@ -94,7 +101,7 @@ public class WiredEffectMuteHabbo extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.length = data.length; this.message = data.message; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectResetTimers.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectResetTimers.java index c11afb94..e0abd1da 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectResetTimers.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectResetTimers.java @@ -9,7 +9,8 @@ import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +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.threading.runnables.WiredResetTimers; import gnu.trove.procedure.TObjectProcedure; @@ -73,15 +74,20 @@ public class WiredEffectResetTimers extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); Emulator.getThreading().run(new WiredResetTimers(room), this.delay); + } - return true; + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.getDelay() )); } @@ -91,7 +97,7 @@ public class WiredEffectResetTimers extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.delay = data.delay; } else { try { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTeleport.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTeleport.java index edfe89ff..7e1ba81d 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTeleport.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTeleport.java @@ -10,9 +10,10 @@ import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.rooms.RoomTileState; import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserEffectComposer; @@ -57,7 +58,7 @@ public class WiredEffectTeleport extends InteractionWiredEffect { roomUnit.getRoom().unIdle(roomUnit.getRoom().getHabbo(roomUnit)); room.sendComposer(new RoomUserEffectComposer(roomUnit, 4).compose()); - Emulator.getThreading().run(new SendRoomUnitEffectComposer(room, roomUnit), WiredHandler.TELEPORT_DELAY + 1000); + Emulator.getThreading().run(new SendRoomUnitEffectComposer(room, roomUnit), WiredManager.TELEPORT_DELAY + 1000); if (tile == roomUnit.getCurrentLocation()) { return; @@ -80,8 +81,8 @@ public class WiredEffectTeleport extends InteractionWiredEffect { } } - Emulator.getThreading().run(() -> { roomUnit.isWiredTeleporting = true; }, Math.max(0, WiredHandler.TELEPORT_DELAY - 500)); - Emulator.getThreading().run(new RoomUnitTeleport(roomUnit, room, tile.x, tile.y, tile.getStackHeight() + (tile.state == RoomTileState.SIT ? -0.5 : 0), roomUnit.getEffectId()), WiredHandler.TELEPORT_DELAY); + Emulator.getThreading().run(() -> { roomUnit.isWiredTeleporting = true; }, Math.max(0, WiredManager.TELEPORT_DELAY - 500)); + Emulator.getThreading().run(new RoomUnitTeleport(roomUnit, room, tile.x, tile.y, tile.getStackHeight() + (tile.state == RoomTileState.SIT ? -0.5 : 0), roomUnit.getEffectId()), WiredManager.TELEPORT_DELAY); } @Override @@ -97,7 +98,7 @@ public class WiredEffectTeleport extends InteractionWiredEffect { this.items.remove(item); } message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) message.appendInt(item.getId()); @@ -162,24 +163,39 @@ public class WiredEffectTeleport extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); + RoomUnit roomUnit = ctx.actor().orElse(null); + + if (roomUnit == null || room == null || room.getLayout() == null) { + return; + } + this.items.removeIf(item -> item == null || item.getRoomId() != this.getRoomId() || Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(item.getId()) == null); if (!this.items.isEmpty()) { int i = Emulator.getRandom().nextInt(this.items.size()); HabboItem item = this.items.get(i); + + if (item == null) return; - teleportUnitToTile(roomUnit, room.getLayout().getTile(item.getX(), item.getY())); - return true; + RoomTile tile = room.getLayout().getTile(item.getX(), item.getY()); + if (tile != null) { + teleportUnitToTile(roomUnit, tile); + } } + } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.getDelay(), this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); @@ -191,7 +207,7 @@ public class WiredEffectTeleport extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); for (Integer id: data.itemIds) { HabboItem item = room.getHabboItem(id); @@ -236,7 +252,7 @@ public class WiredEffectTeleport extends InteractionWiredEffect { @Override protected long requiredCooldown() { - return 50L; + return COOLDOWN_DEFAULT; } static class JsonData { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleFurni.java index db6c1046..4daefe37 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleFurni.java @@ -19,9 +19,10 @@ import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import gnu.trove.procedure.TObjectProcedure; @@ -106,7 +107,7 @@ public class WiredEffectToggleFurni extends InteractionWiredEffect { } message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) { message.appendInt(item.getId()); @@ -172,8 +173,9 @@ public class WiredEffectToggleFurni extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - Habbo habbo = room.getHabbo(roomUnit); + public void execute(WiredContext ctx) { + Room room = ctx.room(); + Habbo habbo = ctx.actor().map(unit -> room.getHabbo(unit)).orElse(null); THashSet itemsToRemove = new THashSet<>(); for (HabboItem item : this.items) { @@ -200,13 +202,17 @@ public class WiredEffectToggleFurni extends InteractionWiredEffect { } this.items.removeAll(itemsToRemove); + } - return true; + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.getDelay(), this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); @@ -218,7 +224,7 @@ public class WiredEffectToggleFurni extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); for (Integer id: data.itemIds) { HabboItem item = room.getHabboItem(id); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleRandom.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleRandom.java index d35a9aa9..bf4c7a22 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleRandom.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectToggleRandom.java @@ -19,8 +19,9 @@ 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.core.WiredContext; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import gnu.trove.procedure.TObjectProcedure; @@ -31,7 +32,6 @@ import org.slf4j.LoggerFactory; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -42,47 +42,45 @@ public class WiredEffectToggleRandom extends InteractionWiredEffect { private final THashSet items = new THashSet<>(); - private static final List> FORBIDDEN_TYPES; - - static { - FORBIDDEN_TYPES = new ArrayList<>(Arrays.asList( - InteractionWired.class, - InteractionTeleport.class, - InteractionPushable.class, - InteractionTagPole.class, - InteractionTagField.class, - InteractionCrackable.class, - InteractionGameScoreboard.class, - InteractionGameGate.class, - InteractionFreezeTile.class, - InteractionFreezeBlock.class, - InteractionFreezeExitTile.class, - InteractionBattleBanzaiTeleporter.class, - InteractionBattleBanzaiTile.class, - InteractionMonsterPlantSeed.class, - InteractionPetBreedingNest.class, - InteractionPetDrink.class, - InteractionPetFood.class, - InteractionPetToy.class, - InteractionBadgeDisplay.class, - InteractionClothing.class, - InteractionVendingMachine.class, - InteractionGift.class, - InteractionPressurePlate.class, - InteractionMannequin.class, - InteractionGymEquipment.class, - InteractionHopper.class, - InteractionObstacle.class, - InteractionOneWayGate.class, - InteractionPuzzleBox.class, - InteractionRoller.class, - InteractionSwitch.class, - InteractionTent.class, - InteractionTrap.class, - InteractionTrophy.class, - InteractionWater.class - )); - } + private static final List> FORBIDDEN_TYPES = new ArrayList>() { + { + this.add(InteractionWired.class); + this.add(InteractionTeleport.class); + this.add(InteractionPushable.class); + this.add(InteractionTagPole.class); + this.add(InteractionTagField.class); + this.add(InteractionCrackable.class); + this.add(InteractionGameScoreboard.class); + this.add(InteractionGameGate.class); + this.add(InteractionFreezeTile.class); + this.add(InteractionFreezeBlock.class); + this.add(InteractionFreezeExitTile.class); + this.add(InteractionBattleBanzaiTeleporter.class); + this.add(InteractionBattleBanzaiTile.class); + this.add(InteractionMonsterPlantSeed.class); + this.add(InteractionPetBreedingNest.class); + this.add(InteractionPetDrink.class); + this.add(InteractionPetFood.class); + this.add(InteractionPetToy.class); + this.add(InteractionBadgeDisplay.class); + this.add(InteractionClothing.class); + this.add(InteractionVendingMachine.class); + this.add(InteractionGift.class); + this.add(InteractionPressurePlate.class); + this.add(InteractionMannequin.class); + this.add(InteractionGymEquipment.class); + this.add(InteractionHopper.class); + this.add(InteractionObstacle.class); + this.add(InteractionOneWayGate.class); + this.add(InteractionPuzzleBox.class); + this.add(InteractionRoller.class); + this.add(InteractionSwitch.class); + this.add(InteractionTent.class); + this.add(InteractionTrap.class); + this.add(InteractionTrophy.class); + this.add(InteractionWater.class); + } + }; public WiredEffectToggleRandom(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -106,7 +104,7 @@ public class WiredEffectToggleRandom extends InteractionWiredEffect { } message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) { message.appendInt(item.getId()); @@ -172,7 +170,8 @@ public class WiredEffectToggleRandom extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); THashSet items = this.items; for (HabboItem item : items) { @@ -188,12 +187,17 @@ public class WiredEffectToggleRandom extends InteractionWiredEffect { LOGGER.error("Caught exception", e); } } - return true; + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.getDelay(), this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); @@ -205,7 +209,7 @@ public class WiredEffectToggleRandom extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); for (Integer id: data.itemIds) { HabboItem item = room.getHabboItem(id); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTriggerStacks.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTriggerStacks.java index 9b7e9064..3a14c42c 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTriggerStacks.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectTriggerStacks.java @@ -9,9 +9,10 @@ import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import gnu.trove.procedure.TObjectProcedure; @@ -51,7 +52,7 @@ public class WiredEffectTriggerStacks extends InteractionWiredEffect { this.items.remove(item); } message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) { message.appendInt(item.getId()); @@ -116,44 +117,81 @@ public class WiredEffectTriggerStacks extends InteractionWiredEffect { return true; } + /** + * Maximum recursion depth to prevent infinite loops when trigger stacks call each other. + */ + private static final int MAX_STACK_DEPTH = 10; + @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - - if (stuff == null || (stuff.length >= 1 && stuff[stuff.length - 1] instanceof WiredEffectTriggerStacks)) { - return false; + public void execute(WiredContext ctx) { + Room room = ctx.room(); + RoomUnit roomUnit = ctx.actor().orElse(null); + Object[] stuff = ctx.legacyStuff(); + + // Prevent infinite recursion by checking for WiredEffectTriggerStacks in the call chain + // and limiting the recursion depth + int stackDepth = 0; + if (stuff != null) { + for (Object obj : stuff) { + if (obj instanceof WiredEffectTriggerStacks) { + stackDepth++; + // If this specific stack is already in the chain, prevent infinite loop + if (obj == this) { + return; + } + } + } + } + + // Prevent excessive recursion depth + if (stackDepth >= MAX_STACK_DEPTH) { + return; } THashSet usedTiles = new THashSet<>(); - boolean found; - for (HabboItem item : this.items) { - //if(item instanceof InteractionWiredTrigger) - { - found = false; - for (RoomTile tile : usedTiles) { - if (tile.x == item.getX() && tile.y == item.getY()) { - found = true; - break; - } + if (item == null) continue; + + boolean found = false; + for (RoomTile tile : usedTiles) { + if (tile.x == item.getX() && tile.y == item.getY()) { + found = true; + break; } + } - if (!found) { - usedTiles.add(room.getLayout().getTile(item.getX(), item.getY())); + if (!found) { + RoomTile tile = room.getLayout().getTile(item.getX(), item.getY()); + if (tile != null) { + usedTiles.add(tile); } } } - Object[] newStuff = new Object[stuff.length + 1]; + + // Create new stuff array with this trigger stack added for recursion tracking + Object[] newStuff; + if (stuff != null) { + newStuff = new Object[stuff.length + 1]; System.arraycopy(stuff, 0, newStuff, 0, stuff.length); newStuff[newStuff.length - 1] = this; - WiredHandler.executeEffectsAtTiles(usedTiles, roomUnit, room, newStuff); - - return true; + } else { + newStuff = new Object[] { this }; } + + WiredManager.executeEffectsAtTiles(usedTiles, roomUnit, room, newStuff); + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.getDelay(), this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); @@ -165,7 +203,7 @@ public class WiredEffectTriggerStacks extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); for (Integer id: data.itemIds) { HabboItem item = room.getHabboItem(id); @@ -205,7 +243,7 @@ public class WiredEffectTriggerStacks extends InteractionWiredEffect { @Override protected long requiredCooldown() { - return 250; + return COOLDOWN_TRIGGER_STACKS; } static class JsonData { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectWhisper.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectWhisper.java index 6514ac0d..45744c93 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectWhisper.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/effects/WiredEffectWhisper.java @@ -7,14 +7,11 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.permissions.Permission; -import com.eu.habbo.habbohotel.rooms.Room; -import com.eu.habbo.habbohotel.rooms.RoomChatMessage; -import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles; -import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.rooms.*; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.core.WiredContext; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.incoming.wired.WiredSaveException; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserWhisperComposer; @@ -91,35 +88,38 @@ public class WiredEffectWhisper extends InteractionWiredEffect { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public void execute(WiredContext ctx) { + Room room = ctx.room(); if (this.message.length() > 0) { + RoomUnit roomUnit = ctx.actor().orElse(null); if (roomUnit != null) { Habbo habbo = room.getHabbo(roomUnit); if (habbo != null) { String msg = this.message.replace("%user%", habbo.getHabboInfo().getUsername()).replace("%online_count%", Emulator.getGameEnvironment().getHabboManager().getOnlineCount() + "").replace("%room_count%", Emulator.getGameEnvironment().getRoomManager().getActiveRooms().size() + ""); habbo.getClient().sendResponse(new RoomUserWhisperComposer(new RoomChatMessage(msg, habbo, habbo, RoomChatMessageBubbles.WIRED))); - Emulator.getThreading().run(() -> WiredHandler.handle(WiredTriggerType.SAY_SOMETHING, roomUnit, room, new Object[]{ msg })); if (habbo.getRoomUnit().isIdle()) { habbo.getRoomUnit().getRoom().unIdle(habbo); } - return true; } } else { for (Habbo h : room.getHabbos()) { h.getClient().sendResponse(new RoomUserWhisperComposer(new RoomChatMessage(this.message.replace("%user%", h.getHabboInfo().getUsername()).replace("%online_count%", Emulator.getGameEnvironment().getHabboManager().getOnlineCount() + "").replace("%room_count%", Emulator.getGameEnvironment().getRoomManager().getActiveRooms().size() + ""), h, h, RoomChatMessageBubbles.WIRED))); } - - return true; } } + } + + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData(this.message, this.getDelay())); + return WiredManager.getGson().toJson(new JsonData(this.message, this.getDelay())); } @Override @@ -127,7 +127,7 @@ public class WiredEffectWhisper extends InteractionWiredEffect { String wiredData = set.getString("wired_data"); if(wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.setDelay(data.delay); this.message = data.message; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredBlob.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredBlob.java index a77c1189..8f859fd2 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredBlob.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredBlob.java @@ -23,7 +23,7 @@ public class WiredBlob extends InteractionDefault { ACTIVE("0"), USED("1"); - private final String state; + private String state; WiredBlobState(String state) { this.state = state; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraOrEval.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraOrEval.java index 0e58c51f..0f1feb67 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraOrEval.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraOrEval.java @@ -47,4 +47,4 @@ public class WiredExtraOrEval extends InteractionWiredExtra { public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) throws Exception { } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraRandom.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraRandom.java index b8d8e3bf..3a78591a 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraRandom.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraRandom.java @@ -47,4 +47,4 @@ public class WiredExtraRandom extends InteractionWiredExtra { public void onWalk(RoomUnit roomUnit, Room room, Object[] objects) throws Exception { } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraUnseen.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraUnseen.java index a7b04568..127021db 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraUnseen.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/extra/WiredExtraUnseen.java @@ -11,10 +11,22 @@ import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; public class WiredExtraUnseen extends InteractionWiredExtra { - public List seenList = new ArrayList<>(); + /** + * Maximum number of effect IDs to track to prevent memory leaks. + * When limit is reached, oldest entries are removed automatically. + */ + private static final int MAX_SEEN_LIST_SIZE = 1000; + + /** + * Thread-safe set of seen effect IDs. Uses LinkedHashSet for insertion order + * to support LRU-style eviction when max size is reached. + */ + private final Set seenList = new LinkedHashSet<>(); public WiredExtraUnseen(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -46,7 +58,9 @@ public class WiredExtraUnseen extends InteractionWiredExtra { @Override public void onPickUp() { - this.seenList.clear(); + synchronized (this.seenList) { + this.seenList.clear(); + } } @Override @@ -57,31 +71,60 @@ public class WiredExtraUnseen extends InteractionWiredExtra { @Override public void onMove(Room room, RoomTile oldLocation, RoomTile newLocation) { super.onMove(room, oldLocation, newLocation); - this.seenList.clear(); + synchronized (this.seenList) { + this.seenList.clear(); + } } public InteractionWiredEffect getUnseenEffect(List effects) { - List unseenEffects = new ArrayList<>(); - for (InteractionWiredEffect effect : effects) { - if (!this.seenList.contains(effect.getId())) { - unseenEffects.add(effect); + synchronized (this.seenList) { + List unseenEffects = new ArrayList<>(); + for (InteractionWiredEffect effect : effects) { + if (!this.seenList.contains(effect.getId())) { + unseenEffects.add(effect); + } } - } - InteractionWiredEffect effect = null; - if (!unseenEffects.isEmpty()) { - effect = unseenEffects.get(0); - } else { - this.seenList.clear(); + InteractionWiredEffect effect = null; + if (!unseenEffects.isEmpty()) { + effect = unseenEffects.get(0); + } else { + this.seenList.clear(); - if (!effects.isEmpty()) { - effect = effects.get(0); + if (!effects.isEmpty()) { + effect = effects.get(0); + } } - } - if (effect != null) { - this.seenList.add(effect.getId()); + if (effect != null) { + // Enforce max size limit to prevent memory leaks + if (this.seenList.size() >= MAX_SEEN_LIST_SIZE) { + // Remove oldest entry (first in insertion order) + Integer oldest = this.seenList.iterator().next(); + this.seenList.remove(oldest); + } + this.seenList.add(effect.getId()); + } + return effect; } - return effect; } -} \ No newline at end of file + + /** + * Gets the current size of the seen list. + * @return the number of tracked effect IDs + */ + public int getSeenListSize() { + synchronized (this.seenList) { + return this.seenList.size(); + } + } + + /** + * Clears the seen list. + */ + public void clearSeenList() { + synchronized (this.seenList) { + this.seenList.clear(); + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/interfaces/InteractionWiredMatchFurniSettings.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/interfaces/InteractionWiredMatchFurniSettings.java index 9f631c9d..6db447f7 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/interfaces/InteractionWiredMatchFurniSettings.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/interfaces/InteractionWiredMatchFurniSettings.java @@ -4,8 +4,8 @@ import com.eu.habbo.habbohotel.wired.WiredMatchFurniSetting; import gnu.trove.set.hash.THashSet; public interface InteractionWiredMatchFurniSettings { - THashSet getMatchFurniSettings(); - boolean shouldMatchState(); - boolean shouldMatchRotation(); - boolean shouldMatchPosition(); + public THashSet getMatchFurniSettings(); + public boolean shouldMatchState(); + public boolean shouldMatchRotation(); + public boolean shouldMatchPosition(); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtSetTime.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtSetTime.java index 7912173c..c1bfecef 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtSetTime.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtSetTime.java @@ -1,6 +1,5 @@ package com.eu.habbo.habbohotel.items.interactions.wired.triggers; -import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; @@ -8,10 +7,12 @@ import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.items.interactions.wired.WiredTriggerReset; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.tick.WiredTickable; import com.eu.habbo.messages.ServerMessage; -import com.eu.habbo.threading.runnables.WiredExecuteTask; import gnu.trove.procedure.TObjectProcedure; import java.sql.ResultSet; @@ -19,11 +20,24 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements WiredTriggerReset { - public final static WiredTriggerType type = WiredTriggerType.AT_GIVEN_TIME; +/** + * One-shot timer wired trigger that fires once after a set time. + *

+ * Uses the new 50ms tick system via {@link WiredTickable} for accurate + * timing. After firing, the timer automatically resets and starts again. + *

+ */ +public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements WiredTickable, WiredTriggerReset { + public static final WiredTriggerType type = WiredTriggerType.AT_GIVEN_TIME; + /** The time in milliseconds until the trigger fires */ public int executeTime; - public int taskId; + + /** Accumulated time since last reset (in milliseconds) */ + private long accumulatedTime = 0; + + /** Whether the timer has fired and is waiting for reset */ + private boolean hasFired = false; public WiredTriggerAtSetTime(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -33,16 +47,21 @@ public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements Wi super(id, userId, item, extradata, limitedStack, limitedSells); } + @Override + public boolean matches(HabboItem triggerItem, WiredEvent event) { + // Only match if this timer is the one that actually fired + return event.getSourceItem().map(item -> item.getId() == this.getId()).orElse(false); + } + + @Deprecated @Override public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - return true; + return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( - this.executeTime - )); + return WiredManager.getGson().toJson(new JsonData(this.executeTime)); } @Override @@ -50,7 +69,7 @@ public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements Wi String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.executeTime = data.executeTime; } else { if (wiredData.length() >= 1) { @@ -61,14 +80,17 @@ public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements Wi if (this.executeTime < 500) { this.executeTime = 20 * 500; } - this.taskId = 1; - Emulator.getThreading().run(new WiredExecuteTask(this, Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId())), this.executeTime); + + // Initialize for tick system - will be registered by RoomItemManager + this.accumulatedTime = 0; + this.hasFired = false; } @Override public void onPickUp() { this.executeTime = 0; - this.taskId = 0; + this.accumulatedTime = 0; + this.hasFired = false; } @Override @@ -111,7 +133,7 @@ public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements Wi @Override public boolean saveData(WiredSettings settings) { - if(settings.getIntParams().length < 1) return false; + if (settings.getIntParams().length < 1) return false; this.executeTime = settings.getIntParams()[0] * 500; this.resetTimer(); @@ -119,13 +141,54 @@ public class WiredTriggerAtSetTime extends InteractionWiredTrigger implements Wi return true; } + // ========== WiredTickable Implementation ========== + + @Override + public void onWiredTick(Room room, long tickCount, int tickIntervalMs) { + // Don't tick if already fired (waiting for manual reset) + if (this.hasFired) { + return; + } + + // Add fixed tick interval + this.accumulatedTime += tickIntervalMs; + + // Check if enough time has passed + if (this.accumulatedTime >= this.executeTime) { + this.hasFired = true; + this.accumulatedTime = 0; + + if (this.getRoomId() != 0 && room.isLoaded()) { + WiredManager.triggerTimerTick(room, this); + } + } + } + @Override public void resetTimer() { - this.taskId++; - - Emulator.getThreading().run(new WiredExecuteTask(this, Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId())), this.executeTime); + this.accumulatedTime = 0; + this.hasFired = false; } + @Override + public void onRegistered(Room room, long currentTimeMillis) { + this.accumulatedTime = 0; + this.hasFired = false; + } + + @Override + public void onUnregistered(Room room) { + this.accumulatedTime = 0; + this.hasFired = false; + } + + @Override + public boolean isOneShot() { + return true; // One-shot timer, fires once then waits for reset + } + + // ========== JSON Data ========== + static class JsonData { int executeTime; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtTimeLong.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtTimeLong.java index 185224cb..24475dcc 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtTimeLong.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerAtTimeLong.java @@ -1,6 +1,5 @@ package com.eu.habbo.habbohotel.items.interactions.wired.triggers; -import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; @@ -8,10 +7,12 @@ import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.items.interactions.wired.WiredTriggerReset; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.tick.WiredTickable; import com.eu.habbo.messages.ServerMessage; -import com.eu.habbo.threading.runnables.WiredExecuteTask; import gnu.trove.procedure.TObjectProcedure; import java.sql.ResultSet; @@ -19,10 +20,24 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -public class WiredTriggerAtTimeLong extends InteractionWiredTrigger implements WiredTriggerReset { +/** + * Long-interval one-shot timer wired trigger. + *

+ * Uses the new 50ms tick system via {@link WiredTickable} for accurate + * timing with 5-second increments. + *

+ */ +public class WiredTriggerAtTimeLong extends InteractionWiredTrigger implements WiredTickable, WiredTriggerReset { private static final WiredTriggerType type = WiredTriggerType.AT_GIVEN_TIME; - public int taskId; + + /** The time in milliseconds until the trigger fires */ private int executeTime; + + /** Accumulated time since last reset (in milliseconds) */ + private long accumulatedTime = 0; + + /** Whether the timer has fired and is waiting for reset */ + private boolean hasFired = false; public WiredTriggerAtTimeLong(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -32,16 +47,21 @@ public class WiredTriggerAtTimeLong extends InteractionWiredTrigger implements W super(id, userId, item, extradata, limitedStack, limitedSells); } + @Override + public boolean matches(HabboItem triggerItem, WiredEvent event) { + // Only match if this timer is the one that actually fired + return event.getSourceItem().map(item -> item.getId() == this.getId()).orElse(false); + } + + @Deprecated @Override public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - return true; + return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( - this.executeTime - )); + return WiredManager.getGson().toJson(new JsonData(this.executeTime)); } @Override @@ -49,7 +69,7 @@ public class WiredTriggerAtTimeLong extends InteractionWiredTrigger implements W String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.executeTime = data.executeTime; } else { if (wiredData.length() >= 1) { @@ -60,14 +80,17 @@ public class WiredTriggerAtTimeLong extends InteractionWiredTrigger implements W if (this.executeTime < 500) { this.executeTime = 20 * 500; } - this.taskId = 1; - Emulator.getThreading().run(new WiredExecuteTask(this, Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId())), this.executeTime); + + // Initialize for tick system + this.accumulatedTime = 0; + this.hasFired = false; } @Override public void onPickUp() { this.executeTime = 0; - this.taskId = 0; + this.accumulatedTime = 0; + this.hasFired = false; } @Override @@ -110,19 +133,62 @@ public class WiredTriggerAtTimeLong extends InteractionWiredTrigger implements W @Override public boolean saveData(WiredSettings settings) { - if(settings.getIntParams().length < 1) return false; + if (settings.getIntParams().length < 1) return false; this.executeTime = settings.getIntParams()[0] * 500; + + this.resetTimer(); return true; } + // ========== WiredTickable Implementation ========== + + @Override + public void onWiredTick(Room room, long tickCount, int tickIntervalMs) { + // Don't tick if already fired (waiting for manual reset) + if (this.hasFired) { + return; + } + + // Add fixed tick interval + this.accumulatedTime += tickIntervalMs; + + // Check if enough time has passed + if (this.accumulatedTime >= this.executeTime) { + this.hasFired = true; + this.accumulatedTime = 0; + + if (this.getRoomId() != 0 && room.isLoaded()) { + WiredManager.triggerTimerTick(room, this); + } + } + } + @Override public void resetTimer() { - this.taskId++; - - Emulator.getThreading().run(new WiredExecuteTask(this, Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId())), this.executeTime); + this.accumulatedTime = 0; + this.hasFired = false; } + @Override + public void onRegistered(Room room, long currentTimeMillis) { + this.accumulatedTime = 0; + this.hasFired = false; + } + + @Override + public void onUnregistered(Room room) { + this.accumulatedTime = 0; + this.hasFired = false; + } + + @Override + public boolean isOneShot() { + return true; // One-shot timer + } + + // ========== JSON Data ========== + static class JsonData { int executeTime; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerBotReachedFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerBotReachedFurni.java index 32a7a120..462d4b0e 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerBotReachedFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerBotReachedFurni.java @@ -8,8 +8,9 @@ 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.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; import com.eu.habbo.messages.ServerMessage; import gnu.trove.procedure.TObjectProcedure; import gnu.trove.set.hash.THashSet; @@ -27,7 +28,7 @@ public class WiredTriggerBotReachedFurni extends InteractionWiredTrigger { public final static WiredTriggerType type = WiredTriggerType.WALKS_ON_FURNI; - private final THashSet items; + private THashSet items; private String botName = ""; public WiredTriggerBotReachedFurni(ResultSet set, Item baseItem) throws SQLException { @@ -63,7 +64,7 @@ public class WiredTriggerBotReachedFurni extends InteractionWiredTrigger { } message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) { message.appendInt(item.getId()); @@ -102,16 +103,25 @@ public class WiredTriggerBotReachedFurni extends InteractionWiredTrigger { this.items.clear(); int count = settings.getFurniIds().length; + Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()); + if (room == null) return false; for (int i = 0; i < count; i++) { - this.items.add(Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(settings.getFurniIds()[i])); + HabboItem item = room.getHabboItem(settings.getFurniIds()[i]); + if (item != null) { + this.items.add(item); + } } return true; } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean matches(HabboItem triggerItem, WiredEvent event) { + RoomUnit roomUnit = event.getActor().orElse(null); + Room room = event.getRoom(); + Object[] stuff = event.getLegacyStuff(); + if (stuff.length >= 1) { if (stuff[0] instanceof HabboItem) { return this.items.contains(stuff[0]) && room.getBots(this.botName).stream().anyMatch(bot -> bot.getRoomUnit() == roomUnit); @@ -120,9 +130,15 @@ public class WiredTriggerBotReachedFurni extends InteractionWiredTrigger { return false; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.botName, this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); @@ -134,7 +150,7 @@ public class WiredTriggerBotReachedFurni extends InteractionWiredTrigger { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.botName = data.botName; for (Integer id: data.itemIds) { HabboItem item = room.getHabboItem(id); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerBotReachedHabbo.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerBotReachedHabbo.java index 346a5f38..50c39e71 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerBotReachedHabbo.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerBotReachedHabbo.java @@ -5,8 +5,10 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -53,13 +55,21 @@ public class WiredTriggerBotReachedHabbo extends InteractionWiredTrigger { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean matches(HabboItem triggerItem, WiredEvent event) { + RoomUnit roomUnit = event.getActor().orElse(null); + Room room = event.getRoom(); return room.getBots(this.botName).stream().anyMatch(bot -> bot.getRoomUnit() == roomUnit); } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.botName )); } @@ -69,7 +79,7 @@ public class WiredTriggerBotReachedHabbo extends InteractionWiredTrigger { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.botName = data.botName; } else { this.botName = wiredData; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerCollision.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerCollision.java index 1d255b5b..81e3f031 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerCollision.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerCollision.java @@ -6,8 +6,9 @@ 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.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -24,9 +25,17 @@ public class WiredTriggerCollision extends InteractionWiredTrigger { super(id, userId, item, extradata, limitedStack, limitedSells); } + @Override + public boolean matches(HabboItem triggerItem, WiredEvent event) { + // Collision trigger fires when a furniture item moves and collides with a room unit + // The actor is the room unit that was collided with + return event.getActor().isPresent(); + } + + @Deprecated @Override public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - return stuff.length > 0 && stuff[0] instanceof HabboItem; + return false; } @Override @@ -52,7 +61,7 @@ public class WiredTriggerCollision extends InteractionWiredTrigger { @Override public void serializeWiredData(ServerMessage message, Room room) { message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(0); message.appendInt(this.getBaseItem().getSpriteId()); message.appendInt(this.getId()); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerFurniStateToggled.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerFurniStateToggled.java index ac6347a0..5db6591a 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerFurniStateToggled.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerFurniStateToggled.java @@ -9,8 +9,9 @@ import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredEffectType; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; import com.eu.habbo.messages.ServerMessage; import gnu.trove.set.hash.THashSet; @@ -35,7 +36,11 @@ public class WiredTriggerFurniStateToggled extends InteractionWiredTrigger { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean matches(HabboItem triggerItem, WiredEvent event) { + RoomUnit roomUnit = event.getActor().orElse(null); + Room room = event.getRoom(); + Object[] stuff = event.getLegacyStuff(); + if (stuff.length >= 1) { Habbo habbo = room.getHabbo(roomUnit); @@ -46,17 +51,24 @@ public class WiredTriggerFurniStateToggled extends InteractionWiredTrigger { } } - if (stuff[0] instanceof HabboItem) { - return this.items.contains(stuff[0]); + HabboItem sourceItem = event.getSourceItem().orElse(null); + if (sourceItem != null) { + return this.items.contains(sourceItem); } } } return false; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); } @@ -67,7 +79,7 @@ public class WiredTriggerFurniStateToggled extends InteractionWiredTrigger { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); for (Integer id: data.itemIds) { HabboItem item = room.getHabboItem(id); if (item != null) { @@ -120,7 +132,7 @@ public class WiredTriggerFurniStateToggled extends InteractionWiredTrigger { } message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) { message.appendInt(item.getId()); @@ -141,7 +153,10 @@ public class WiredTriggerFurniStateToggled extends InteractionWiredTrigger { int count = settings.getFurniIds().length; for (int i = 0; i < count; i++) { - this.items.add(Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(settings.getFurniIds()[i])); + HabboItem item = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(settings.getFurniIds()[i]); + if (item != null) { + this.items.add(item); + } } return true; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerGameEnds.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerGameEnds.java index aada946d..b6ffb232 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerGameEnds.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerGameEnds.java @@ -6,7 +6,9 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; import com.eu.habbo.messages.ServerMessage; import gnu.trove.procedure.TObjectProcedure; @@ -27,10 +29,16 @@ public class WiredTriggerGameEnds extends InteractionWiredTrigger { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean matches(HabboItem triggerItem, WiredEvent event) { return true; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { return ""; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerGameStarts.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerGameStarts.java index 5b31dd65..6e1085d4 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerGameStarts.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerGameStarts.java @@ -6,7 +6,9 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; import com.eu.habbo.messages.ServerMessage; import gnu.trove.procedure.TObjectProcedure; @@ -27,10 +29,16 @@ public class WiredTriggerGameStarts extends InteractionWiredTrigger { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean matches(HabboItem triggerItem, WiredEvent event) { return true; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { return ""; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboEntersRoom.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboEntersRoom.java index bb1f5f1e..7f631d72 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboEntersRoom.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboEntersRoom.java @@ -6,8 +6,10 @@ import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.Habbo; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -27,7 +29,9 @@ public class WiredTriggerHabboEntersRoom extends InteractionWiredTrigger { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean matches(HabboItem triggerItem, WiredEvent event) { + RoomUnit roomUnit = event.getActor().orElse(null); + Room room = event.getRoom(); Habbo habbo = room.getHabbo(roomUnit); if (habbo != null) { @@ -40,9 +44,15 @@ public class WiredTriggerHabboEntersRoom extends InteractionWiredTrigger { return false; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.username )); } @@ -52,7 +62,7 @@ public class WiredTriggerHabboEntersRoom extends InteractionWiredTrigger { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.username = data.username; } else { this.username = wiredData; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboSaysKeyword.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboSaysKeyword.java index a2e10c71..b05746eb 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboSaysKeyword.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboSaysKeyword.java @@ -6,8 +6,10 @@ import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.users.Habbo; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -28,21 +30,28 @@ public class WiredTriggerHabboSaysKeyword extends InteractionWiredTrigger { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean matches(HabboItem triggerItem, WiredEvent event) { if (this.key.length() > 0) { - if (stuff[0] instanceof String) { - if (((String) stuff[0]).toLowerCase().contains(this.key.toLowerCase())) { - Habbo habbo = room.getHabbo(roomUnit); - return !this.ownerOnly || (habbo != null && room.getOwnerId() == habbo.getHabboInfo().getId()); - } + String text = event.getText().orElse(null); + if (text != null && text.toLowerCase().contains(this.key.toLowerCase())) { + RoomUnit roomUnit = event.getActor().orElse(null); + Room room = event.getRoom(); + Habbo habbo = room.getHabbo(roomUnit); + return !this.ownerOnly || (habbo != null && room.getOwnerId() == habbo.getHabboInfo().getId()); } } return false; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.ownerOnly, this.key )); @@ -53,7 +62,7 @@ public class WiredTriggerHabboSaysKeyword extends InteractionWiredTrigger { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.ownerOnly = data.ownerOnly; this.key = data.key; } else { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboWalkOffFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboWalkOffFurni.java index f4d2c7b2..55c2af03 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboWalkOffFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboWalkOffFurni.java @@ -7,8 +7,9 @@ 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.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; import com.eu.habbo.messages.ServerMessage; import gnu.trove.set.hash.THashSet; @@ -20,7 +21,7 @@ import java.util.stream.Collectors; public class WiredTriggerHabboWalkOffFurni extends InteractionWiredTrigger { public static final WiredTriggerType type = WiredTriggerType.WALKS_OFF_FURNI; - private final THashSet items; + private THashSet items; public WiredTriggerHabboWalkOffFurni(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -33,18 +34,23 @@ public class WiredTriggerHabboWalkOffFurni extends InteractionWiredTrigger { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - if (stuff.length >= 1) { - if (stuff[0] instanceof HabboItem) { - return this.items.contains(stuff[0]); - } + public boolean matches(HabboItem triggerItem, WiredEvent event) { + HabboItem sourceItem = event.getSourceItem().orElse(null); + if (sourceItem != null) { + return this.items.contains(sourceItem); } return false; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new WiredTriggerFurniStateToggled.JsonData( + return WiredManager.getGson().toJson(new WiredTriggerFurniStateToggled.JsonData( this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); } @@ -55,7 +61,7 @@ public class WiredTriggerHabboWalkOffFurni extends InteractionWiredTrigger { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); for (Integer id: data.itemIds) { HabboItem item = room.getHabboItem(id); if (item != null) { @@ -112,7 +118,7 @@ public class WiredTriggerHabboWalkOffFurni extends InteractionWiredTrigger { } message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) { message.appendInt(item.getId()); @@ -134,7 +140,10 @@ public class WiredTriggerHabboWalkOffFurni extends InteractionWiredTrigger { int count = settings.getFurniIds().length; for (int i = 0; i < count; i++) { - this.items.add(Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(settings.getFurniIds()[i])); + HabboItem item = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(settings.getFurniIds()[i]); + if (item != null) { + this.items.add(item); + } } return true; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboWalkOnFurni.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboWalkOnFurni.java index e6480ed9..40dd0170 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboWalkOnFurni.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerHabboWalkOnFurni.java @@ -7,8 +7,9 @@ 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.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; import com.eu.habbo.messages.ServerMessage; import gnu.trove.set.hash.THashSet; @@ -20,7 +21,7 @@ import java.util.stream.Collectors; public class WiredTriggerHabboWalkOnFurni extends InteractionWiredTrigger { public static final WiredTriggerType type = WiredTriggerType.WALKS_ON_FURNI; - private final THashSet items; + private THashSet items; public WiredTriggerHabboWalkOnFurni(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -33,15 +34,20 @@ public class WiredTriggerHabboWalkOnFurni extends InteractionWiredTrigger { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - if (stuff.length >= 1) { - if (stuff[0] instanceof HabboItem) { - return this.items.contains(stuff[0]); - } + public boolean matches(HabboItem triggerItem, WiredEvent event) { + HabboItem sourceItem = event.getSourceItem().orElse(null); + if (sourceItem != null) { + return this.items.contains(sourceItem); } return false; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public WiredTriggerType getType() { return type; @@ -65,7 +71,7 @@ public class WiredTriggerHabboWalkOnFurni extends InteractionWiredTrigger { } message.appendBoolean(false); - message.appendInt(WiredHandler.MAXIMUM_FURNI_SELECTION); + message.appendInt(WiredManager.MAXIMUM_FURNI_SELECTION); message.appendInt(this.items.size()); for (HabboItem item : this.items) { message.appendInt(item.getId()); @@ -87,7 +93,10 @@ public class WiredTriggerHabboWalkOnFurni extends InteractionWiredTrigger { int count = settings.getFurniIds().length; for (int i = 0; i < count; i++) { - this.items.add(Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(settings.getFurniIds()[i])); + HabboItem item = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()).getHabboItem(settings.getFurniIds()[i]); + if (item != null) { + this.items.add(item); + } } return true; @@ -95,7 +104,7 @@ public class WiredTriggerHabboWalkOnFurni extends InteractionWiredTrigger { @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.items.stream().map(HabboItem::getId).collect(Collectors.toList()) )); } @@ -106,7 +115,7 @@ public class WiredTriggerHabboWalkOnFurni extends InteractionWiredTrigger { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); for (Integer id: data.itemIds) { HabboItem item = room.getHabboItem(id); if (item != null) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeater.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeater.java index 94f3aeaa..e91a43f3 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeater.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeater.java @@ -1,7 +1,5 @@ package com.eu.habbo.habbohotel.items.interactions.wired.triggers; -import com.eu.habbo.Emulator; -import com.eu.habbo.habbohotel.items.ICycleable; import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; @@ -9,8 +7,11 @@ import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.items.interactions.wired.WiredTriggerReset; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.tick.WiredTickable; import com.eu.habbo.messages.ServerMessage; import gnu.trove.procedure.TObjectProcedure; @@ -19,12 +20,19 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -public class WiredTriggerRepeater extends InteractionWiredTrigger implements ICycleable, WiredTriggerReset { +/** + * Repeating wired trigger that fires periodically. + *

+ * Uses the new 50ms tick system via {@link WiredTickable} for higher-resolution + * timing compared to the old 500ms room cycle. + *

+ */ +public class WiredTriggerRepeater extends InteractionWiredTrigger implements WiredTickable, WiredTriggerReset { public static final WiredTriggerType type = WiredTriggerType.PERIODICALLY; - public static final int DEFAULT_DELAY = 10 * 500; + public static final int DEFAULT_DELAY = 10 * 500; // 5 seconds default + /** The interval in milliseconds between triggers */ protected int repeatTime = DEFAULT_DELAY; - protected int counter = 0; public WiredTriggerRepeater(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -34,16 +42,21 @@ public class WiredTriggerRepeater extends InteractionWiredTrigger implements ICy super(id, userId, item, extradata, limitedStack, limitedSells); } + @Override + public boolean matches(HabboItem triggerItem, WiredEvent event) { + // Only match if this repeater is the one that actually fired + return event.getSourceItem().map(item -> item.getId() == this.getId()).orElse(false); + } + + @Deprecated @Override public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - return true; + return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( - this.repeatTime - )); + return WiredManager.getGson().toJson(new JsonData(this.repeatTime)); } @Override @@ -51,7 +64,7 @@ public class WiredTriggerRepeater extends InteractionWiredTrigger implements ICy String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.repeatTime = data.repeatTime; } else { if (wiredData.length() >= 1) { @@ -67,7 +80,6 @@ public class WiredTriggerRepeater extends InteractionWiredTrigger implements ICy @Override public void onPickUp() { this.repeatTime = DEFAULT_DELAY; - this.counter = 0; } @Override @@ -110,47 +122,56 @@ public class WiredTriggerRepeater extends InteractionWiredTrigger implements ICy @Override public boolean saveData(WiredSettings settings) { - if(settings.getIntParams().length < 1) return false; - this.repeatTime = settings.getIntParams()[0] * 500; - this.counter = 0; + if (settings.getIntParams().length < 1) return false; + int newRepeatTime = settings.getIntParams()[0] * 500; - if (this.repeatTime < 500) { - this.repeatTime = 500; + if (newRepeatTime < 500) { + newRepeatTime = 500; } + this.repeatTime = newRepeatTime; + return true; } + // ========== WiredTickable Implementation ========== + @Override - public void cycle(Room room) { - this.counter += 500; - long currentMillis = System.currentTimeMillis(); - String Key = Double.toString(this.getX()) + Double.toString(this.getY()); - - room.repeatersLastTick.putIfAbsent(Key, currentMillis); - - if (this.counter >= this.repeatTime && room.repeatersLastTick.get(Key) < currentMillis - 450) { - this.counter = 0; - if (this.getRoomId() != 0) { - if (room.isLoaded()) { - room.repeatersLastTick.put(Key, currentMillis); - WiredHandler.handle(this, null, room, new Object[]{this}); - } + public void onWiredTick(Room room, long tickCount, int tickIntervalMs) { + // Calculate elapsed time based on global tick count + // All repeaters with the same interval fire on the exact same tick + long elapsedMs = tickCount * tickIntervalMs; + + // Fire when elapsed time is a multiple of repeatTime + if (elapsedMs % this.repeatTime == 0) { + if (this.getRoomId() != 0 && room.isLoaded()) { + WiredManager.triggerTimerRepeat(room, this); } } } @Override public void resetTimer() { - this.counter = 0; - if (this.getRoomId() != 0) { - Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()); - if (room != null && room.isLoaded()) { - WiredHandler.handle(this, null, room, new Object[]{this}); - } - } + // No-op - using global tick count, no local state to reset } + @Override + public void onRegistered(Room room, long currentTimeMillis) { + // No-op - using global tick count + } + + @Override + public void onUnregistered(Room room) { + // No-op - using global tick count + } + + @Override + public boolean isOneShot() { + return false; // Repeating timer + } + + // ========== JSON Data ========== + static class JsonData { int repeatTime; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java index 584c264b..f9094c09 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerRepeaterLong.java @@ -1,7 +1,5 @@ package com.eu.habbo.habbohotel.items.interactions.wired.triggers; -import com.eu.habbo.Emulator; -import com.eu.habbo.habbohotel.items.ICycleable; import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; @@ -9,8 +7,11 @@ import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.items.interactions.wired.WiredTriggerReset; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.tick.WiredTickable; import com.eu.habbo.messages.ServerMessage; import gnu.trove.procedure.TObjectProcedure; @@ -19,11 +20,19 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; -public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements ICycleable, WiredTriggerReset { - public static final int DEFAULT_DELAY = 10 * 5000; +/** + * Long-interval repeating wired trigger that fires periodically. + *

+ * Uses the new 50ms tick system via {@link WiredTickable} for accurate + * timing. Intervals are in 5-second increments. + *

+ */ +public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements WiredTickable, WiredTriggerReset { + public static final int DEFAULT_DELAY = 10 * 5000; // 50 seconds default private static final WiredTriggerType type = WiredTriggerType.PERIODICALLY_LONG; + + /** The interval in milliseconds between triggers */ private int repeatTime = DEFAULT_DELAY; - private int counter = 0; public WiredTriggerRepeaterLong(ResultSet set, Item baseItem) throws SQLException { super(set, baseItem); @@ -33,16 +42,21 @@ public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements super(id, userId, item, extradata, limitedStack, limitedSells); } + @Override + public boolean matches(HabboItem triggerItem, WiredEvent event) { + // Only match if this repeater is the one that actually fired + return event.getSourceItem().map(item -> item.getId() == this.getId()).orElse(false); + } + + @Deprecated @Override public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { - return true; + return false; } @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( - this.repeatTime - )); + return WiredManager.getGson().toJson(new JsonData(this.repeatTime)); } @Override @@ -50,7 +64,7 @@ public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.repeatTime = data.repeatTime; } else { if (wiredData.length() >= 1) { @@ -108,43 +122,50 @@ public class WiredTriggerRepeaterLong extends InteractionWiredTrigger implements @Override public boolean saveData(WiredSettings settings) { - if(settings.getIntParams().length < 1) return false; + if (settings.getIntParams().length < 1) return false; this.repeatTime = settings.getIntParams()[0] * 5000; - this.counter = 0; + // No accumulated time reset needed - using global tick count return true; } + // ========== WiredTickable Implementation ========== @Override - public void cycle(Room room) { - this.counter += 500; - long currentMillis = System.currentTimeMillis(); - String Key = Double.toString(this.getX()) + Double.toString(this.getY()); - - room.repeatersLastTick.putIfAbsent(Key, currentMillis); - - if (this.counter >= this.repeatTime && room.repeatersLastTick.get(Key) < currentMillis - 4950) { - this.counter = 0; - if (this.getRoomId() != 0) { - if (room.isLoaded()) { - room.repeatersLastTick.put(Key, currentMillis); - WiredHandler.handle(this, null, room, new Object[]{this}); - } + public void onWiredTick(Room room, long tickCount, int tickIntervalMs) { + // Use global tick counter - all repeaters with same interval fire together + // This ensures perfect synchronization regardless of when they were registered + long elapsedMs = tickCount * tickIntervalMs; + + // Fire when elapsed time is a multiple of repeat time + if (elapsedMs % this.repeatTime == 0) { + if (this.getRoomId() != 0 && room.isLoaded()) { + WiredManager.triggerTimerRepeat(room, this); } } } @Override public void resetTimer() { - this.counter = 0; - if (this.getRoomId() != 0) { - Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.getRoomId()); - if (room != null && room.isLoaded()) { - WiredHandler.handle(this, null, room, new Object[]{this}); - } - } + // No-op - using global tick count for synchronization } + @Override + public void onRegistered(Room room, long currentTimeMillis) { + // No-op - using global tick count + } + + @Override + public void onUnregistered(Room room) { + // No-op - using global tick count + } + + @Override + public boolean isOneShot() { + return false; // Repeating timer + } + + // ========== JSON Data ========== + static class JsonData { int repeatTime; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerScoreAchieved.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerScoreAchieved.java index 5fe4720f..fb3f1be0 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerScoreAchieved.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerScoreAchieved.java @@ -5,8 +5,10 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; import com.eu.habbo.messages.ServerMessage; import java.sql.ResultSet; @@ -25,7 +27,8 @@ public class WiredTriggerScoreAchieved extends InteractionWiredTrigger { } @Override - public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + public boolean matches(HabboItem triggerItem, WiredEvent event) { + Object[] stuff = event.getLegacyStuff(); if (stuff.length >= 2) { int points = (Integer) stuff[0]; int amountAdded = (Integer) stuff[1]; @@ -36,9 +39,15 @@ public class WiredTriggerScoreAchieved extends InteractionWiredTrigger { return false; } + @Deprecated + @Override + public boolean execute(RoomUnit roomUnit, Room room, Object[] stuff) { + return false; + } + @Override public String getWiredData() { - return WiredHandler.getGsonBuilder().create().toJson(new JsonData( + return WiredManager.getGson().toJson(new JsonData( this.score )); } @@ -48,7 +57,7 @@ public class WiredTriggerScoreAchieved extends InteractionWiredTrigger { String wiredData = set.getString("wired_data"); if (wiredData.startsWith("{")) { - JsonData data = WiredHandler.getGsonBuilder().create().fromJson(wiredData, JsonData.class); + JsonData data = WiredManager.getGson().fromJson(wiredData, JsonData.class); this.score = data.score; } else { try { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerTeamLoses.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerTeamLoses.java index feee2674..9a68311a 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerTeamLoses.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerTeamLoses.java @@ -19,4 +19,4 @@ public class WiredTriggerTeamLoses extends WiredTriggerGameStarts { public WiredTriggerType getType() { return WiredTriggerType.CUSTOM; } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerTeamWins.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerTeamWins.java index afe8124c..43bde787 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerTeamWins.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/wired/triggers/WiredTriggerTeamWins.java @@ -19,4 +19,4 @@ public class WiredTriggerTeamWins extends WiredTriggerGameStarts { public WiredTriggerType getType() { return WiredTriggerType.CUSTOM; } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/messenger/MessengerCategory.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/messenger/MessengerCategory.java index 76897835..b19a43b0 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/messenger/MessengerCategory.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/messenger/MessengerCategory.java @@ -1,7 +1,7 @@ package com.eu.habbo.habbohotel.messenger; public class MessengerCategory { - private final int user_id; + private int user_id; private String name; private int id; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolBan.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolBan.java index 69c7d434..3c8517da 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolBan.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/modtool/ModToolBan.java @@ -24,7 +24,7 @@ public class ModToolBan implements Runnable { public ModToolBanType type; public int cfhTopic; - private final boolean needsInsert; + private boolean needsInsert; public ModToolBan(ResultSet set) throws SQLException { this.userId = set.getInt("user_id"); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/navigation/EventCategory.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/navigation/EventCategory.java index f53e5a7f..674040d0 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/navigation/EventCategory.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/navigation/EventCategory.java @@ -3,9 +3,9 @@ package com.eu.habbo.habbohotel.navigation; import com.eu.habbo.messages.ServerMessage; public class EventCategory { - private final int id; - private final String caption; - private final boolean visible; + private int id; + private String caption; + private boolean visible; public EventCategory(int id, String caption, boolean visible) { this.id = id; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/navigation/NavigatorSavedSearch.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/navigation/NavigatorSavedSearch.java index f433fef6..8bcbc62c 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/navigation/NavigatorSavedSearch.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/navigation/NavigatorSavedSearch.java @@ -1,8 +1,8 @@ package com.eu.habbo.habbohotel.navigation; public class NavigatorSavedSearch { - private final String searchCode; - private final String filter; + private String searchCode; + private String filter; private int id; public NavigatorSavedSearch(String searchCode, String filter) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/permissions/PermissionsManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/permissions/PermissionsManager.java index 64f53ec0..b9d24931 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/permissions/PermissionsManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/permissions/PermissionsManager.java @@ -9,10 +9,7 @@ import gnu.trove.map.hash.TIntObjectHashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; +import java.sql.*; import java.util.ArrayList; import java.util.List; import java.util.Set; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/permissions/Rank.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/permissions/Rank.java index 20929cd7..cbbc01dc 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/permissions/Rank.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/permissions/Rank.java @@ -11,7 +11,7 @@ public class Rank { private final int id; - private final int level; + private int level; private final THashMap permissions; private final THashMap variables; private String name; @@ -55,6 +55,7 @@ public class Rank { this.logCommands = set.getString("log_commands").equals("1"); this.prefix = set.getString("prefix"); this.prefixColor = set.getString("prefix_color"); + this.level = set.getInt("level"); this.diamondsTimerAmount = set.getInt("auto_points_amount"); this.creditsTimerAmount = set.getInt("auto_credits_amount"); this.pixelsTimerAmount = set.getInt("auto_pixels_amount"); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/MonsterplantPet.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/MonsterplantPet.java index 6a3020d0..3dce2a58 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/MonsterplantPet.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/pets/MonsterplantPet.java @@ -69,8 +69,8 @@ public class MonsterplantPet extends Pet implements IPetLook { private final int mouth; private final int mouthColor; public String look; - private final int type; - private final int hue; + private int type; + private int hue; private int deathTimestamp = Emulator.getIntUnixTimestamp() + timeToLive; private boolean canBreed = true; private boolean publiclyBreedable = false; @@ -171,7 +171,6 @@ public class MonsterplantPet extends Pet implements IPetLook { for (RoomUnitStatus s : this.roomUnit.getStatusMap().keySet()) { if (s.equals(RoomUnitStatus.GROW)) { clear = true; - break; } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/polls/Poll.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/polls/Poll.java index a337c52d..8e7927dc 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/polls/Poll.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/polls/Poll.java @@ -20,7 +20,7 @@ public class Poll { public int lastQuestionId; - private final ArrayList questions; + private ArrayList questions; public Poll(ResultSet set) throws SQLException { this.id = set.getInt("id"); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/polls/PollQuestion.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/polls/PollQuestion.java index cbeb0381..b1c159e6 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/polls/PollQuestion.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/polls/PollQuestion.java @@ -32,7 +32,7 @@ public class PollQuestion implements ISerialize, Comparable { public final int order; - private final ArrayList subQuestions; + private ArrayList subQuestions; public PollQuestion(ResultSet set) throws SQLException { this.id = set.getInt("id"); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java index 9dc0eae1..ceeeba49 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/Room.java @@ -1,63 +1,67 @@ package com.eu.habbo.habbohotel.rooms; +import java.awt.Color; +import java.awt.Rectangle; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.eu.habbo.Emulator; -import com.eu.habbo.habbohotel.achievements.AchievementManager; import com.eu.habbo.habbohotel.bots.Bot; -import com.eu.habbo.habbohotel.bots.VisitorBot; -import com.eu.habbo.habbohotel.commands.CommandHandler; import com.eu.habbo.habbohotel.games.Game; import com.eu.habbo.habbohotel.guilds.Guild; import com.eu.habbo.habbohotel.guilds.GuildMember; import com.eu.habbo.habbohotel.items.FurnitureType; -import com.eu.habbo.habbohotel.items.ICycleable; import com.eu.habbo.habbohotel.items.Item; -import com.eu.habbo.habbohotel.items.interactions.*; -import com.eu.habbo.habbohotel.items.interactions.games.InteractionGameGate; -import com.eu.habbo.habbohotel.items.interactions.games.InteractionGameScoreboard; +import com.eu.habbo.habbohotel.items.interactions.InteractionBackgroundToner; +import com.eu.habbo.habbohotel.items.interactions.InteractionFireworks; +import com.eu.habbo.habbohotel.items.interactions.InteractionGuildFurni; +import com.eu.habbo.habbohotel.items.interactions.InteractionJukeBox; +import com.eu.habbo.habbohotel.items.interactions.InteractionMultiHeight; import com.eu.habbo.habbohotel.items.interactions.games.InteractionGameTimer; -import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.InteractionBattleBanzaiSphere; -import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.InteractionBattleBanzaiTeleporter; -import com.eu.habbo.habbohotel.items.interactions.games.freeze.InteractionFreezeExitTile; -import com.eu.habbo.habbohotel.items.interactions.games.tag.InteractionTagField; -import com.eu.habbo.habbohotel.items.interactions.games.tag.InteractionTagPole; -import com.eu.habbo.habbohotel.items.interactions.pets.InteractionNest; -import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetBreedingNest; -import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetDrink; -import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetFood; -import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredBlob; -import com.eu.habbo.habbohotel.messenger.MessengerBuddy; import com.eu.habbo.habbohotel.permissions.Permission; import com.eu.habbo.habbohotel.pets.Pet; import com.eu.habbo.habbohotel.pets.PetManager; -import com.eu.habbo.habbohotel.pets.RideablePet; -import com.eu.habbo.habbohotel.users.*; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.users.DanceType; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.messages.ISerialize; import com.eu.habbo.messages.ServerMessage; -import com.eu.habbo.messages.outgoing.MessageComposer; -import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer; -import com.eu.habbo.messages.outgoing.generic.alerts.GenericErrorMessagesComposer; import com.eu.habbo.messages.outgoing.guilds.GuildInfoComposer; import com.eu.habbo.messages.outgoing.hotelview.HotelViewComposer; import com.eu.habbo.messages.outgoing.inventory.AddHabboItemComposer; -import com.eu.habbo.messages.outgoing.inventory.AddPetComposer; import com.eu.habbo.messages.outgoing.inventory.InventoryRefreshComposer; -import com.eu.habbo.messages.outgoing.polls.infobus.SimplePollAnswerComposer; -import com.eu.habbo.messages.outgoing.polls.infobus.SimplePollStartComposer; -import com.eu.habbo.messages.outgoing.rooms.*; -import com.eu.habbo.messages.outgoing.rooms.items.*; -import com.eu.habbo.messages.outgoing.rooms.pets.RoomPetComposer; -import com.eu.habbo.messages.outgoing.rooms.users.*; -import com.eu.habbo.messages.outgoing.users.MutedWhisperComposer; +import com.eu.habbo.messages.outgoing.rooms.HideDoorbellComposer; +import com.eu.habbo.messages.outgoing.rooms.UpdateStackHeightComposer; +import com.eu.habbo.messages.outgoing.rooms.items.FloorItemUpdateComposer; +import com.eu.habbo.messages.outgoing.rooms.items.ItemStateComposer; +import com.eu.habbo.messages.outgoing.rooms.items.RemoveFloorItemComposer; +import com.eu.habbo.messages.outgoing.rooms.items.RemoveWallItemComposer; +import com.eu.habbo.messages.outgoing.rooms.items.RoomFloorItemsComposer; +import com.eu.habbo.messages.outgoing.rooms.items.WallItemUpdateComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer; import com.eu.habbo.plugin.Event; -import com.eu.habbo.plugin.events.furniture.*; +import com.eu.habbo.plugin.events.furniture.FurniturePickedUpEvent; import com.eu.habbo.plugin.events.rooms.RoomLoadedEvent; import com.eu.habbo.plugin.events.rooms.RoomUnloadedEvent; import com.eu.habbo.plugin.events.rooms.RoomUnloadingEvent; -import com.eu.habbo.plugin.events.users.*; -import com.eu.habbo.threading.runnables.YouAreAPirate; -import gnu.trove.TCollections; + import gnu.trove.iterator.TIntObjectIterator; import gnu.trove.list.array.TIntArrayList; import gnu.trove.map.TIntIntMap; @@ -65,5114 +69,2382 @@ import gnu.trove.map.TIntObjectMap; import gnu.trove.map.hash.THashMap; import gnu.trove.map.hash.TIntIntHashMap; import gnu.trove.map.hash.TIntObjectHashMap; -import gnu.trove.procedure.TIntObjectProcedure; -import gnu.trove.procedure.TObjectProcedure; import gnu.trove.set.hash.THashSet; -import org.apache.commons.math3.util.Pair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.awt.*; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.List; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.regex.Pattern; -import java.util.stream.Collectors; public class Room implements Comparable, ISerialize, Runnable { - private static final Logger LOGGER = LoggerFactory.getLogger(Room.class); + private static final Logger LOGGER = LoggerFactory.getLogger(Room.class); - public static final Comparator SORT_SCORE = (o1, o2) -> { + // Manager instances for better separation of concerns + private RoomTileManager tileManager; + private RoomGameManager gameManager; + private RoomTradeManager tradeManager; + private RoomPromotionManager promotionManager; + private RoomWordQuizManager wordQuizManager; + private RoomRightsManager rightsManager; + private RoomUnitManager unitManager; + private RoomItemManager itemManager; + private RoomChatManager chatManager; + private RoomRollerManager rollerManager; + private RoomMessagingManager messagingManager; + private RoomCycleManager cycleManager; - if (!(o1 instanceof Room && o2 instanceof Room)) - return 0; + public static final Comparator SORT_SCORE = (o1, o2) -> o2.getScore() - o1.getScore(); + public static final Comparator SORT_ID = (o1, o2) -> o2.getId() - o1.getId(); + private static final TIntObjectHashMap defaultMoodData = new TIntObjectHashMap<>(); + //Configuration. Loaded from database & updated accordingly. + public static boolean HABBO_CHAT_DELAY = false; + public static int MAXIMUM_BOTS = 10; + public static int MAXIMUM_PETS = 10; + public static int MAXIMUM_FURNI = 2500; + public static int MAXIMUM_POSTITNOTES = 200; + public static int HAND_ITEM_TIME = 10; + public static int IDLE_CYCLES = 240; + public static int IDLE_CYCLES_KICK = 480; + public static String PREFIX_FORMAT = "[%prefix%] "; + public static int ROLLERS_MAXIMUM_ROLL_AVATARS = 1; + public static boolean MUTEAREA_CAN_WHISPER = false; + public static double MAXIMUM_FURNI_HEIGHT = 40d; - return o2.getScore() - o1.getScore(); - }; - public static final Comparator SORT_ID = (o1, o2) -> { + static { + for (int i = 1; i <= 3; i++) { + RoomMoodlightData data = RoomMoodlightData.fromString(""); + data.setId(i); + defaultMoodData.put(i, data); + } + } - if (!(o1 instanceof Room && o2 instanceof Room)) - return 0; + public final Object roomUnitLock = new Object(); + public final ConcurrentHashMap> tileCache = new ConcurrentHashMap<>(); + public final List userVotes; + private final TIntArrayList rights; + private final TIntIntHashMap mutedHabbos; + private final TIntObjectHashMap bannedHabbos; + private final Set games; + private final TIntObjectMap moodlightData; + private final Object loadLock = new Object(); + //Use appropriately. Could potentially cause memory leaks when used incorrectly. + public volatile boolean preventUnloading = false; + public volatile boolean preventUncaching = false; + public Set scheduledComposers = ConcurrentHashMap.newKeySet(); + public Set scheduledTasks = ConcurrentHashMap.newKeySet(); + public String wordQuiz = ""; + public int noVotes = 0; + public int yesVotes = 0; + public int wordQuizEnd = 0; + public ScheduledFuture roomCycleTask; + private int id; + private int ownerId; + private String ownerName; + private String name; + private String description; + private RoomLayout layout; + private boolean overrideModel; + private String layoutName; + private String password; + private RoomState state; + private int usersMax; + private volatile int score; + private volatile int category; + private String floorPaint; + private String wallPaint; + private String backgroundPaint; + private int wallSize; + private int wallHeight; + private int floorSize; + private int guild; + private String tags; + private volatile boolean publicRoom; + private volatile boolean staffPromotedRoom; + private volatile boolean allowPets; + private volatile boolean allowPetsEat; + private volatile boolean allowWalkthrough; + private volatile boolean allowBotsWalk; + private volatile boolean allowEffects; + private volatile boolean hideWall; + private volatile int chatMode; + private volatile int chatWeight; + private volatile int chatSpeed; + private volatile int chatDistance; + private volatile int chatProtection; + private volatile int muteOption; + private volatile int kickOption; + private volatile int banOption; + private volatile int pollId; + private volatile boolean promoted; + private volatile int tradeMode; + private volatile boolean moveDiagonally; + private volatile boolean jukeboxActive; + private volatile boolean hideWired; + private RoomPromotion promotion; + private volatile boolean needsUpdate; + private volatile boolean loaded; + private volatile boolean preLoaded; + private volatile boolean loadingInProgress; + private volatile CompletableFuture loadingFuture; + private volatile int rollerSpeed; + private volatile int lastTimerReset = Emulator.getIntUnixTimestamp(); + private volatile boolean muted; + private RoomSpecialTypes roomSpecialTypes; + private TraxManager traxManager; + + public final THashMap cache; - return o2.getId() - o1.getId(); - }; - private static final TIntObjectHashMap defaultMoodData = new TIntObjectHashMap<>(); - //Configuration. Loaded from database & updated accordingly. - public static boolean HABBO_CHAT_DELAY = false; - public static int MAXIMUM_BOTS = 10; - public static int MAXIMUM_PETS = 10; - public static int MAXIMUM_FURNI = 2500; - public static int MAXIMUM_POSTITNOTES = 200; - public static int HAND_ITEM_TIME = 10; - public static int IDLE_CYCLES = 240; - public static int IDLE_CYCLES_KICK = 480; - public static String PREFIX_FORMAT = "[%prefix%] "; - public static int ROLLERS_MAXIMUM_ROLL_AVATARS = 1; - public static boolean MUTEAREA_CAN_WHISPER = false; - public static double MAXIMUM_FURNI_HEIGHT = 40d; + public Room(ResultSet set) throws SQLException { + this.cache = new THashMap<>(1000); + this.id = set.getInt("id"); + this.ownerId = set.getInt("owner_id"); + this.ownerName = set.getString("owner_name"); + this.name = set.getString("name"); + this.description = set.getString("description"); + this.password = set.getString("password"); + this.state = RoomState.valueOf(set.getString("state").toUpperCase()); + this.usersMax = set.getInt("users_max"); + this.score = set.getInt("score"); + this.category = set.getInt("category"); + this.floorPaint = set.getString("paper_floor"); + this.wallPaint = set.getString("paper_wall"); + this.backgroundPaint = set.getString("paper_landscape"); + this.wallSize = set.getInt("thickness_wall"); + this.wallHeight = set.getInt("wall_height"); + this.floorSize = set.getInt("thickness_floor"); + this.tags = set.getString("tags"); + this.publicRoom = set.getBoolean("is_public"); + this.staffPromotedRoom = set.getBoolean("is_staff_picked"); + this.allowPets = set.getBoolean("allow_other_pets"); + this.allowPetsEat = set.getBoolean("allow_other_pets_eat"); + this.allowWalkthrough = set.getBoolean("allow_walkthrough"); + this.hideWall = set.getBoolean("allow_hidewall"); + this.chatMode = set.getInt("chat_mode"); + this.chatWeight = set.getInt("chat_weight"); + this.chatSpeed = set.getInt("chat_speed"); + this.chatDistance = set.getInt("chat_hearing_distance"); + this.chatProtection = set.getInt("chat_protection"); + this.muteOption = set.getInt("who_can_mute"); + this.kickOption = set.getInt("who_can_kick"); + this.banOption = set.getInt("who_can_ban"); + this.pollId = set.getInt("poll_id"); + this.guild = set.getInt("guild_id"); + this.rollerSpeed = set.getInt("roller_speed"); + this.overrideModel = set.getString("override_model").equals("1"); + this.layoutName = set.getString("model"); + this.promoted = set.getString("promoted").equals("1"); + this.jukeboxActive = set.getString("jukebox_active").equals("1"); + this.hideWired = set.getString("hidewired").equals("1"); - static { - for (int i = 1; i <= 3; i++) { - RoomMoodlightData data = RoomMoodlightData.fromString(""); - data.setId(i); - defaultMoodData.put(i, data); + this.bannedHabbos = new TIntObjectHashMap<>(); + + try (Connection connection = Emulator.getDatabase().getDataSource() + .getConnection(); PreparedStatement statement = connection.prepareStatement( + "SELECT * FROM room_promotions WHERE room_id = ? AND end_timestamp > ? LIMIT 1")) { + if (this.promoted) { + statement.setInt(1, this.id); + statement.setInt(2, Emulator.getIntUnixTimestamp()); + + try (ResultSet promotionSet = statement.executeQuery()) { + this.promoted = false; + if (promotionSet.next()) { + this.promoted = true; + this.promotion = new RoomPromotion(this, promotionSet); + } } + } + + this.loadBans(connection); + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); } - public final Object roomUnitLock = new Object(); - public final ConcurrentHashMap> tileCache = new ConcurrentHashMap<>(); - public final List userVotes; - private final ConcurrentHashMap currentHabbos = new ConcurrentHashMap<>(3); - private final TIntObjectMap habboQueue = TCollections.synchronizedMap(new TIntObjectHashMap<>(0)); - private final TIntObjectMap currentBots = TCollections.synchronizedMap(new TIntObjectHashMap<>(0)); - private final TIntObjectMap currentPets = TCollections.synchronizedMap(new TIntObjectHashMap<>(0)); - private final THashSet activeTrades; - private final TIntArrayList rights; - private final TIntIntHashMap mutedHabbos; - private final TIntObjectHashMap bannedHabbos; - private final Set games; - private final TIntObjectMap furniOwnerNames; - private final TIntIntMap furniOwnerCount; - private final TIntObjectMap moodlightData; - private final THashSet wordFilterWords; - private final TIntObjectMap roomItems; - private final Object loadLock = new Object(); - //Use appropriately. Could potentially cause memory leaks when used incorrectly. - public volatile boolean preventUnloading = false; - public volatile boolean preventUncaching = false; - public Set scheduledComposers = ConcurrentHashMap.newKeySet(); - public Set scheduledTasks = ConcurrentHashMap.newKeySet(); - public String wordQuiz = ""; - public int noVotes = 0; - public int yesVotes = 0; - public int wordQuizEnd = 0; - public ScheduledFuture roomCycleTask; - private final int id; - private int ownerId; - private String ownerName; - private String name; - private String description; - private RoomLayout layout; - private boolean overrideModel; - private final String layoutName; - private String password; - private RoomState state; - private int usersMax; - private volatile int score; - private volatile int category; - private String floorPaint; - private String wallPaint; - private String backgroundPaint; - private int wallSize; - private int wallHeight; - private int floorSize; - private int guild; - private String tags; - private volatile boolean publicRoom; - private volatile boolean staffPromotedRoom; - private volatile boolean allowPets; - private volatile boolean allowPetsEat; - private volatile boolean allowWalkthrough; - private volatile boolean allowBotsWalk; - private volatile boolean allowEffects; - private volatile boolean hideWall; - private volatile int chatMode; - private volatile int chatWeight; - private volatile int chatSpeed; - private volatile int chatDistance; - private volatile int chatProtection; - private volatile int muteOption; - private volatile int kickOption; - private volatile int banOption; - private volatile int pollId; - private volatile boolean promoted; - private volatile int tradeMode; - private volatile boolean moveDiagonally; - private volatile boolean jukeboxActive; - private volatile boolean hideWired; - private RoomPromotion promotion; - private volatile boolean needsUpdate; - private volatile boolean loaded; - private volatile boolean preLoaded; - private int idleCycles; - private int idleHostingCycles; - private volatile int unitCounter; - private volatile int rollerSpeed; - private final int muteTime = Emulator.getConfig().getInt("hotel.flood.mute.time", 30); - private long rollerCycle = System.currentTimeMillis(); - private volatile int lastTimerReset = Emulator.getIntUnixTimestamp(); - private volatile boolean muted; - private RoomSpecialTypes roomSpecialTypes; - private TraxManager traxManager; - private boolean cycleOdd; - private long cycleTimestamp; - public Map repeatersLastTick = new HashMap<>(); + this.tradeMode = set.getInt("trade_mode"); + this.moveDiagonally = set.getString("move_diagonally").equals("1"); - public Room(ResultSet set) throws SQLException { - this.id = set.getInt("id"); - this.ownerId = set.getInt("owner_id"); - this.ownerName = set.getString("owner_name"); - this.name = set.getString("name"); - this.description = set.getString("description"); - this.password = set.getString("password"); - this.state = RoomState.valueOf(set.getString("state").toUpperCase()); - this.usersMax = set.getInt("users_max"); - this.score = set.getInt("score"); - this.category = set.getInt("category"); - this.floorPaint = set.getString("paper_floor"); - this.wallPaint = set.getString("paper_wall"); - this.backgroundPaint = set.getString("paper_landscape"); - this.wallSize = set.getInt("thickness_wall"); - this.wallHeight = set.getInt("wall_height"); - this.floorSize = set.getInt("thickness_floor"); - this.tags = set.getString("tags"); - this.publicRoom = set.getBoolean("is_public"); - this.staffPromotedRoom = set.getBoolean("is_staff_picked"); - this.allowPets = set.getBoolean("allow_other_pets"); - this.allowPetsEat = set.getBoolean("allow_other_pets_eat"); - this.allowWalkthrough = set.getBoolean("allow_walkthrough"); - this.hideWall = set.getBoolean("allow_hidewall"); - this.chatMode = set.getInt("chat_mode"); - this.chatWeight = set.getInt("chat_weight"); - this.chatSpeed = set.getInt("chat_speed"); - this.chatDistance = set.getInt("chat_hearing_distance"); - this.chatProtection = set.getInt("chat_protection"); - this.muteOption = set.getInt("who_can_mute"); - this.kickOption = set.getInt("who_can_kick"); - this.banOption = set.getInt("who_can_ban"); - this.pollId = set.getInt("poll_id"); - this.guild = set.getInt("guild_id"); - this.rollerSpeed = set.getInt("roller_speed"); - this.overrideModel = set.getString("override_model").equals("1"); - this.layoutName = set.getString("model"); - this.promoted = set.getString("promoted").equals("1"); - this.jukeboxActive = set.getString("jukebox_active").equals("1"); - this.hideWired = set.getString("hidewired").equals("1"); + this.preLoaded = true; + this.allowBotsWalk = true; + this.allowEffects = true; + this.moodlightData = new TIntObjectHashMap<>(defaultMoodData); - this.bannedHabbos = new TIntObjectHashMap<>(); - - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM room_promotions WHERE room_id = ? AND end_timestamp > ? LIMIT 1")) { - if (this.promoted) { - statement.setInt(1, this.id); - statement.setInt(2, Emulator.getIntUnixTimestamp()); - - try (ResultSet promotionSet = statement.executeQuery()) { - this.promoted = false; - if (promotionSet.next()) { - this.promoted = true; - this.promotion = new RoomPromotion(this, promotionSet); - } - } - } - - this.loadBans(connection); - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } - - this.tradeMode = set.getInt("trade_mode"); - this.moveDiagonally = set.getString("move_diagonally").equals("1"); - - this.preLoaded = true; - this.allowBotsWalk = true; - this.allowEffects = true; - this.furniOwnerNames = TCollections.synchronizedMap(new TIntObjectHashMap<>(0)); - this.furniOwnerCount = TCollections.synchronizedMap(new TIntIntHashMap(0)); - this.roomItems = TCollections.synchronizedMap(new TIntObjectHashMap<>(0)); - this.wordFilterWords = new THashSet<>(0); - this.moodlightData = new TIntObjectHashMap<>(defaultMoodData); - - for (String s : set.getString("moodlight_data").split(";")) { - RoomMoodlightData data = RoomMoodlightData.fromString(s); - this.moodlightData.put(data.getId(), data); - } - - this.mutedHabbos = new TIntIntHashMap(); - this.games = ConcurrentHashMap.newKeySet(); - - this.activeTrades = new THashSet<>(0); - this.rights = new TIntArrayList(); - this.userVotes = new ArrayList<>(); + for (String s : set.getString("moodlight_data").split(";")) { + RoomMoodlightData data = RoomMoodlightData.fromString(s); + this.moodlightData.put(data.getId(), data); } - public synchronized void loadData() { - synchronized (this.loadLock) { - if (!this.preLoaded || this.loaded) - return; + this.mutedHabbos = new TIntIntHashMap(); + this.games = ConcurrentHashMap.newKeySet(); - this.preLoaded = false; + this.rights = new TIntArrayList(); + this.userVotes = new ArrayList<>(); - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) { - synchronized (this.roomUnitLock) { - this.unitCounter = 0; - this.currentHabbos.clear(); - this.currentPets.clear(); - this.currentBots.clear(); - } + // Initialize managers + this.initializeManagers(); + } - this.roomSpecialTypes = new RoomSpecialTypes(); + /** + * Initializes all manager instances for this room. + */ + private void initializeManagers() { + this.tileManager = new RoomTileManager(this); + this.gameManager = new RoomGameManager(this); + this.tradeManager = new RoomTradeManager(this); + this.promotionManager = new RoomPromotionManager(this); + this.wordQuizManager = new RoomWordQuizManager(this); + this.rightsManager = new RoomRightsManager(this); + this.unitManager = new RoomUnitManager(this); + this.itemManager = new RoomItemManager(this); + this.chatManager = new RoomChatManager(this); + this.rollerManager = new RoomRollerManager(this); + this.messagingManager = new RoomMessagingManager(this); + this.cycleManager = new RoomCycleManager(this); + } - try { - this.loadLayout(); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } + // ==================== MANAGER GETTERS ==================== - try { - this.loadRights(connection); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } + /** + * Gets the tile manager for this room. + */ + public RoomTileManager getTileManager() { + return this.tileManager; + } - try { - this.loadItems(connection); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } + /** + * Gets the game manager for this room. + */ + public RoomGameManager getGameManager() { + return this.gameManager; + } - try { - this.loadHeightmap(); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } + /** + * Gets the trade manager for this room. + */ + public RoomTradeManager getTradeManager() { + return this.tradeManager; + } - try { - this.loadBots(connection); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } + /** + * Gets the promotion manager for this room. + */ + public RoomPromotionManager getPromotionManager() { + return this.promotionManager; + } - try { - this.loadPets(connection); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } + /** + * Gets the word quiz manager for this room. + */ + public RoomWordQuizManager getWordQuizManager() { + return this.wordQuizManager; + } - try { - this.loadWordFilter(connection); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } + /** + * Gets the rights manager for this room. + */ + public RoomRightsManager getRightsManager() { + return this.rightsManager; + } - try { - this.loadWiredData(connection); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } + /** + * Gets the unit manager for this room. + */ + public RoomUnitManager getUnitManager() { + return this.unitManager; + } - this.idleCycles = 0; - this.idleHostingCycles = 0; - this.loaded = true; + /** + * Gets the item manager for this room. + */ + public RoomItemManager getItemManager() { + return this.itemManager; + } - this.roomCycleTask = Emulator.getThreading().getService().scheduleAtFixedRate(this, 500, 500, TimeUnit.MILLISECONDS); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } + /** + * Gets the chat manager for this room. + */ + public RoomChatManager getChatManager() { + return this.chatManager; + } - this.traxManager = new TraxManager(this); + /** + * Gets the messaging manager for this room. + */ + public RoomMessagingManager getMessagingManager() { + return this.messagingManager; + } - if (this.jukeboxActive) { - this.traxManager.play(0); - for (HabboItem item : this.roomSpecialTypes.getItemsOfType(InteractionJukeBox.class)) { - item.setExtradata("1"); - this.updateItem(item); - } - } + /** + * Gets the cycle manager for this room. + */ + public RoomCycleManager getCycleManager() { + return this.cycleManager; + } - for (HabboItem item : this.roomSpecialTypes.getItemsOfType(InteractionFireworks.class)) { - item.setExtradata("1"); - this.updateItem(item); - } - } + /** + * Gets the roller manager for this room. + */ + public RoomRollerManager getRollerManager() { + return this.rollerManager; + } - Emulator.getPluginManager().fireEvent(new RoomLoadedEvent(this)); + /** + * Checks if the room is currently loading data. + */ + public boolean isLoadingInProgress() { + synchronized (this.loadLock) { + return this.loadingInProgress; + } + } + + /** + * Checks if the room data is loaded or is currently being loaded. + */ + public boolean isLoadedOrLoading() { + synchronized (this.loadLock) { + return this.loaded || this.loadingInProgress; + } + } + + /** + * Starts loading room data asynchronously in the background. + * This allows the room to start loading before the user fully enters, + * reducing perceived load time. + */ + public void startBackgroundLoad() { + synchronized (this.loadLock) { + if (this.loaded || this.loadingInProgress || !this.preLoaded) { + return; + } + + this.loadingInProgress = true; + this.loadingFuture = CompletableFuture.runAsync(() -> { + this.loadDataInternal(); + }, Emulator.getThreading().getService()); + } + } + + /** + * Waits for background loading to complete if it's in progress. + * If loading hasn't started yet, starts loading synchronously. + */ + public void waitForLoad() { + CompletableFuture future; + synchronized (this.loadLock) { + if (this.loaded) { + return; + } + future = this.loadingFuture; + } + + if (future != null) { + try { + future.join(); + } catch (Exception e) { + LOGGER.error("Error waiting for room load", e); + } + } else { + this.loadData(); + } + } + + public void loadData() { + CompletableFuture futureToWait = null; + boolean shouldLoad = false; + + synchronized (this.loadLock) { + if (this.loadingInProgress) { + // Get the future to wait on outside the lock + futureToWait = this.loadingFuture; + } else if (this.preLoaded && !this.loaded) { + this.loadingInProgress = true; + shouldLoad = true; + } + } + + // Wait for existing load outside the lock + if (futureToWait != null) { + try { + futureToWait.join(); + } catch (Exception e) { + LOGGER.error("Error waiting for room load", e); + } + return; + } + + // Load if needed + if (shouldLoad) { + this.loadDataInternal(); + } + } + + /** + * Internal method that performs the actual room data loading. + * Uses parallel loading for independent operations to reduce total load time. + */ + private void loadDataInternal() { + // Check if already loaded (with lock) + synchronized (this.loadLock) { + if (this.loaded) { + this.loadingInProgress = false; + return; + } + this.preLoaded = false; } - private synchronized void loadLayout() { - if (this.layout == null) { - if (this.overrideModel) { - this.layout = Emulator.getGameEnvironment().getRoomManager().loadCustomLayout(this); - } else { - this.layout = Emulator.getGameEnvironment().getRoomManager().loadLayout(this.layoutName, this); - } - } - } + // Perform loading WITHOUT holding the lock to avoid deadlocks + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) { + synchronized (this.roomUnitLock) { + this.unitManager.clear(); + } - private synchronized void loadHeightmap() { - if (this.layout != null) { - for (short x = 0; x < this.layout.getMapSizeX(); x++) { - for (short y = 0; y < this.layout.getMapSizeY(); y++) { - RoomTile tile = this.layout.getTile(x, y); - if (tile != null) { - this.updateTile(tile); - } - } - } - } else { - LOGGER.error("Unknown Room Layout for Room (ID: {})", this.id); - } - } + this.roomSpecialTypes = new RoomSpecialTypes(); - private synchronized void loadItems(Connection connection) { - this.roomItems.clear(); + // Phase 1: Load layout first (required for bots/pets positioning) + try { + this.loadLayout(); + } catch (Exception e) { + LOGGER.error("Caught exception loading layout", e); + } - try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM items WHERE room_id = ?")) { - statement.setInt(1, this.id); - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - this.addHabboItem(Emulator.getGameEnvironment().getItemManager().loadHabboItem(set)); - } - } - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } - - if (this.itemCount() > Room.MAXIMUM_FURNI) { - LOGGER.error("Room ID: {} has exceeded the furniture limit ({} > {}).", this.getId(), this.itemCount(), Room.MAXIMUM_FURNI); - } - } - - private synchronized void loadWiredData(Connection connection) { - try (PreparedStatement statement = connection.prepareStatement("SELECT id, wired_data FROM items WHERE room_id = ? AND wired_data<>''")) { - statement.setInt(1, this.id); - - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - try { - HabboItem item = this.getHabboItem(set.getInt("id")); - - if (item instanceof InteractionWired) { - ((InteractionWired) item).loadWiredData(set, this); - } - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } - } - } - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); + // Phase 2: Load items and rights in parallel (independent operations) + CompletableFuture itemsFuture = CompletableFuture.runAsync(() -> { + try (Connection itemConnection = Emulator.getDatabase().getDataSource().getConnection()) { + this.loadItems(itemConnection); } catch (Exception e) { - LOGGER.error("Caught exception", e); + LOGGER.error("Caught exception loading items", e); } - } + }, Emulator.getThreading().getService()); - private synchronized void loadBots(Connection connection) { - this.currentBots.clear(); - - try (PreparedStatement statement = connection.prepareStatement("SELECT users.username AS owner_name, bots.* FROM bots INNER JOIN users ON bots.user_id = users.id WHERE room_id = ?")) { - statement.setInt(1, this.id); - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - Bot b = Emulator.getGameEnvironment().getBotManager().loadBot(set); - - if (b != null) { - b.setRoom(this); - b.setRoomUnit(new RoomUnit()); - b.getRoomUnit().setPathFinderRoom(this); - b.getRoomUnit().setLocation(this.layout.getTile((short) set.getInt("x"), (short) set.getInt("y"))); - if (b.getRoomUnit().getCurrentLocation() == null) { - b.getRoomUnit().setLocation(this.getLayout().getDoorTile()); - b.getRoomUnit().setRotation(RoomUserRotation.fromValue(this.getLayout().getDoorDirection())); - } else { - b.getRoomUnit().setZ(set.getDouble("z")); - b.getRoomUnit().setPreviousLocationZ(set.getDouble("z")); - b.getRoomUnit().setRotation(RoomUserRotation.values()[set.getInt("rot")]); - } - b.getRoomUnit().setRoomUnitType(RoomUnitType.BOT); - b.getRoomUnit().setDanceType(DanceType.values()[set.getInt("dance")]); - //b.getRoomUnit().setCanWalk(set.getBoolean("freeroam")); - b.getRoomUnit().setInRoom(true); - this.giveEffect(b.getRoomUnit(), set.getInt("effect"), Integer.MAX_VALUE); - this.addBot(b); - } - } - } - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } - } - - private synchronized void loadPets(Connection connection) { - this.currentPets.clear(); - - try (PreparedStatement statement = connection.prepareStatement("SELECT users.username as pet_owner_name, users_pets.* FROM users_pets INNER JOIN users ON users_pets.user_id = users.id WHERE room_id = ?")) { - statement.setInt(1, this.id); - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - try { - Pet pet = PetManager.loadPet(set); - pet.setRoom(this); - pet.setRoomUnit(new RoomUnit()); - pet.getRoomUnit().setPathFinderRoom(this); - pet.getRoomUnit().setLocation(this.layout.getTile((short) set.getInt("x"), (short) set.getInt("y"))); - if (pet.getRoomUnit().getCurrentLocation() == null) { - pet.getRoomUnit().setLocation(this.getLayout().getDoorTile()); - pet.getRoomUnit().setRotation(RoomUserRotation.fromValue(this.getLayout().getDoorDirection())); - } else { - pet.getRoomUnit().setZ(set.getDouble("z")); - pet.getRoomUnit().setRotation(RoomUserRotation.values()[set.getInt("rot")]); - } - pet.getRoomUnit().setRoomUnitType(RoomUnitType.PET); - pet.getRoomUnit().setCanWalk(true); - this.addPet(pet); - - this.getFurniOwnerNames().put(pet.getUserId(), set.getString("pet_owner_name")); - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } - } - } - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } - } - - private synchronized void loadWordFilter(Connection connection) { - this.wordFilterWords.clear(); - - try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM room_wordfilter WHERE room_id = ?")) { - statement.setInt(1, this.id); - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - this.wordFilterWords.add(set.getString("word")); - } - } - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } - } - - public void updateTile(RoomTile tile) { - if (tile != null) { - tile.setStackHeight(this.getStackHeight(tile.x, tile.y, false)); - tile.setState(this.calculateTileState(tile)); - } - } - - public void updateTiles(THashSet tiles) { - for (RoomTile tile : tiles) { - this.tileCache.remove(tile); - tile.setStackHeight(this.getStackHeight(tile.x, tile.y, false)); - tile.setState(this.calculateTileState(tile)); - } - - this.sendComposer(new UpdateStackHeightComposer(this, tiles).compose()); - } - - private RoomTileState calculateTileState(RoomTile tile) { - return this.calculateTileState(tile, null); - } - - private RoomTileState calculateTileState(RoomTile tile, HabboItem exclude) { - if (tile == null || tile.state == RoomTileState.INVALID) - return RoomTileState.INVALID; - - RoomTileState result = RoomTileState.OPEN; - //HabboItem highestItem = null; - //HabboItem lowestChair = this.getLowestChair(tile); - THashSet items = this.getItemsAt(tile); - - if (items == null) return RoomTileState.INVALID; - - HabboItem tallestItem = null; - - for (HabboItem item : items) { - if (exclude != null && item == exclude) continue; - - if (item.getBaseItem().allowLay()) { - return RoomTileState.LAY; - } - - /*if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem) > item.getZ() + Item.getCurrentHeight(item)) - continue; - - highestItem = item;*/ - - if (tallestItem != null && tallestItem.getZ() + Item.getCurrentHeight(tallestItem) > item.getZ() + Item.getCurrentHeight(item)) - continue; - - result = this.checkStateForItem(item, tile); - tallestItem = item; - - /*if (lowestChair != null && item.getZ() > lowestChair.getZ() + 1.5) { - continue; - } - - if (lowestItem == null || lowestItem.getZ() < item.getZ()) { - lowestItem = item; - - result = this.checkStateForItem(lowestItem, tile); - } else if (lowestItem.getZ() == item.getZ()) { - if (result == RoomTileState.OPEN) { - result = this.checkStateForItem(item, tile); - } - }*/ - } - - //if (lowestChair != null) return RoomTileState.SIT; - - return result; - } - - private RoomTileState checkStateForItem(HabboItem item, RoomTile tile) { - RoomTileState result = RoomTileState.BLOCKED; - - if (item.isWalkable()) { - result = RoomTileState.OPEN; - } - - if (item.getBaseItem().allowSit()) { - result = RoomTileState.SIT; - } - - if (item.getBaseItem().allowLay()) { - result = RoomTileState.LAY; - } - - RoomTileState overriddenState = item.getOverrideTileState(tile, this); - if (overriddenState != null) { - result = overriddenState; - } - - if (this.getItemsAt(tile).stream().anyMatch(i -> i instanceof InteractionTileWalkMagic)) { - result = RoomTileState.OPEN; - } - - return result; - } - - public boolean tileWalkable(RoomTile t) { - return this.tileWalkable(t.x, t.y); - } - - public boolean tileWalkable(short x, short y) { - boolean walkable = this.layout.tileWalkable(x, y); - RoomTile tile = this.getLayout().getTile(x, y); - - if (walkable && tile != null) { - if (tile.hasUnits() && !this.allowWalkthrough) { - walkable = false; - } - } - - return walkable; - } - - public void pickUpItem(HabboItem item, Habbo picker) { - if (item == null) - return; - - if (Emulator.getPluginManager().isRegistered(FurniturePickedUpEvent.class, true)) { - Event furniturePickedUpEvent = new FurniturePickedUpEvent(item, picker); - Emulator.getPluginManager().fireEvent(furniturePickedUpEvent); - - if (furniturePickedUpEvent.isCancelled()) - return; - } - - this.removeHabboItem(item.getId()); - item.onPickUp(this); - item.setRoomId(0); - item.needsUpdate(true); - - if (item.getBaseItem().getType() == FurnitureType.FLOOR) { - this.sendComposer(new RemoveFloorItemComposer(item).compose()); - - THashSet updatedTiles = new THashSet<>(); - Rectangle rectangle = RoomLayout.getRectangle(item.getX(), item.getY(), item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); - - for (short x = (short) rectangle.x; x < rectangle.x + rectangle.getWidth(); x++) { - for (short y = (short) rectangle.y; y < rectangle.y + rectangle.getHeight(); y++) { - double stackHeight = this.getStackHeight(x, y, false); - RoomTile tile = this.layout.getTile(x, y); - - if (tile != null) { - tile.setStackHeight(stackHeight); - updatedTiles.add(tile); - } - } - } - this.sendComposer(new UpdateStackHeightComposer(this, updatedTiles).compose()); - this.updateTiles(updatedTiles); - for (RoomTile tile : updatedTiles) { - this.updateHabbosAt(tile.x, tile.y); - this.updateBotsAt(tile.x, tile.y); - } - } else if (item.getBaseItem().getType() == FurnitureType.WALL) { - this.sendComposer(new RemoveWallItemComposer(item).compose()); - } - - Habbo habbo = (picker != null && picker.getHabboInfo().getId() == item.getId() ? picker : Emulator.getGameServer().getGameClientManager().getHabbo(item.getUserId())); - if (habbo != null) { - habbo.getInventory().getItemsComponent().addItem(item); - habbo.getClient().sendResponse(new AddHabboItemComposer(item)); - habbo.getClient().sendResponse(new InventoryRefreshComposer()); - } - Emulator.getThreading().run(item); - } - - public void updateHabbosAt(Rectangle rectangle) { - for (short i = (short) rectangle.x; i < rectangle.x + rectangle.width; i++) { - for (short j = (short) rectangle.y; j < rectangle.y + rectangle.height; j++) { - this.updateHabbosAt(i, j); - } - } - } - - public void updateHabbo(Habbo habbo) { - this.updateRoomUnit(habbo.getRoomUnit()); - } - - public void updateRoomUnit(RoomUnit roomUnit) { - HabboItem item = this.getTopItemAt(roomUnit.getX(), roomUnit.getY()); - - if ((item == null && !roomUnit.cmdSit) || (item != null && !item.getBaseItem().allowSit())) - roomUnit.removeStatus(RoomUnitStatus.SIT); - - double oldZ = roomUnit.getZ(); - - if (item != null) { - if (item.getBaseItem().allowSit()) { - roomUnit.setZ(item.getZ()); - } else { - roomUnit.setZ(item.getZ() + Item.getCurrentHeight(item)); - } - - if (oldZ != roomUnit.getZ()) { - this.scheduledTasks.add(() -> { - try { - item.onWalkOn(roomUnit, Room.this, null); - } catch (Exception e) { - - } - }); - } - } - - this.sendComposer(new RoomUserStatusComposer(roomUnit).compose()); - } - - public void updateHabbosAt(short x, short y) { - this.updateHabbosAt(x, y, this.getHabbosAt(x, y)); - } - - public void updateHabbosAt(short x, short y, THashSet habbos) { - HabboItem item = this.getTopItemAt(x, y); - - for (Habbo habbo : habbos) { - - double oldZ = habbo.getRoomUnit().getZ(); - RoomUserRotation oldRotation = habbo.getRoomUnit().getBodyRotation(); - double z = habbo.getRoomUnit().getCurrentLocation().getStackHeight(); - boolean updated = false; - - if (habbo.getRoomUnit().hasStatus(RoomUnitStatus.SIT) && ((item == null && !habbo.getRoomUnit().cmdSit) || (item != null && !item.getBaseItem().allowSit()))) { - habbo.getRoomUnit().removeStatus(RoomUnitStatus.SIT); - updated = true; - } - - if (habbo.getRoomUnit().hasStatus(RoomUnitStatus.LAY) && ((item == null && !habbo.getRoomUnit().cmdLay) || (item != null && !item.getBaseItem().allowLay()))) { - habbo.getRoomUnit().removeStatus(RoomUnitStatus.LAY); - updated = true; - } - - if (item != null && (item.getBaseItem().allowSit() || item.getBaseItem().allowLay())) { - habbo.getRoomUnit().setZ(item.getZ()); - habbo.getRoomUnit().setPreviousLocationZ(item.getZ()); - habbo.getRoomUnit().setRotation(RoomUserRotation.fromValue(item.getRotation())); - } else { - habbo.getRoomUnit().setZ(z); - habbo.getRoomUnit().setPreviousLocationZ(z); - } - - if (habbo.getRoomUnit().getCurrentLocation().is(x, y) && (oldZ != z || updated || oldRotation != habbo.getRoomUnit().getBodyRotation())) { - habbo.getRoomUnit().statusUpdate(true); - //roomUnits.add(habbo.getRoomUnit()); - } - } - - /*if (!roomUnits.isEmpty()) { - this.sendComposer(new RoomUserStatusComposer(roomUnits, true).compose()); - }*/ - } - - public void updateBotsAt(short x, short y) { - HabboItem topItem = this.getTopItemAt(x, y); - - THashSet roomUnits = new THashSet<>(); - - for (Bot bot : this.getBotsAt(this.layout.getTile(x, y))) { - if (topItem != null) { - if (topItem.getBaseItem().allowSit()) { - bot.getRoomUnit().setZ(topItem.getZ()); - bot.getRoomUnit().setPreviousLocationZ(topItem.getZ()); - bot.getRoomUnit().setRotation(RoomUserRotation.fromValue(topItem.getRotation())); - } else { - bot.getRoomUnit().setZ(topItem.getZ() + Item.getCurrentHeight(topItem)); - - if (topItem.getBaseItem().allowLay()) { - bot.getRoomUnit().setStatus(RoomUnitStatus.LAY, (topItem.getZ() + topItem.getBaseItem().getHeight()) + ""); - } - } - } else { - bot.getRoomUnit().setZ(bot.getRoomUnit().getCurrentLocation().getStackHeight()); - bot.getRoomUnit().setPreviousLocationZ(bot.getRoomUnit().getCurrentLocation().getStackHeight()); - } - - roomUnits.add(bot.getRoomUnit()); - } - - if (!roomUnits.isEmpty()) { - this.sendComposer(new RoomUserStatusComposer(roomUnits, true).compose()); - } - } - - public void updatePetsAt(short x, short y) { - HabboItem topItem = this.getTopItemAt(x, y); - - THashSet roomUnits = new THashSet<>(); - - for (Pet pet : this.getPetsAt(this.layout.getTile(x, y))) { - if (topItem != null) { - if (topItem.getBaseItem().allowSit()) { - pet.getRoomUnit().setZ(topItem.getZ()); - pet.getRoomUnit().setPreviousLocationZ(topItem.getZ()); - pet.getRoomUnit().setRotation(RoomUserRotation.fromValue(topItem.getRotation())); - } else { - pet.getRoomUnit().setZ(topItem.getZ() + Item.getCurrentHeight(topItem)); - - if (topItem.getBaseItem().allowLay()) { - pet.getRoomUnit().setStatus(RoomUnitStatus.LAY, (topItem.getZ() + topItem.getBaseItem().getHeight()) + ""); - } - } - } else { - pet.getRoomUnit().setZ(pet.getRoomUnit().getCurrentLocation().getStackHeight()); - pet.getRoomUnit().setPreviousLocationZ(pet.getRoomUnit().getCurrentLocation().getStackHeight()); - } - - roomUnits.add(pet.getRoomUnit()); - } - - if (!roomUnits.isEmpty()) { - this.sendComposer(new RoomUserStatusComposer(roomUnits, true).compose()); - } - } - - public void pickupPetsForHabbo(Habbo habbo) { - THashSet pets = new THashSet<>(); - - synchronized (this.currentPets) { - for (Pet pet : this.currentPets.valueCollection()) { - if (pet.getUserId() == habbo.getHabboInfo().getId()) { - pets.add(pet); - } - } - } - - for (Pet pet : pets) { - pet.removeFromRoom(); - Emulator.getThreading().run(pet); - habbo.getInventory().getPetsComponent().addPet(pet); - habbo.getClient().sendResponse(new AddPetComposer(pet)); - this.currentPets.remove(pet.getId()); - } - - } - - public void startTrade(Habbo userOne, Habbo userTwo) { - RoomTrade trade = new RoomTrade(userOne, userTwo, this); - synchronized (this.activeTrades) { - this.activeTrades.add(trade); - } - - trade.start(); - } - - public void stopTrade(RoomTrade trade) { - synchronized (this.activeTrades) { - this.activeTrades.remove(trade); - } - } - - public RoomTrade getActiveTradeForHabbo(Habbo user) { - synchronized (this.activeTrades) { - for (RoomTrade trade : this.activeTrades) { - for (RoomTradeUser habbo : trade.getRoomTradeUsers()) { - if (habbo.getHabbo() == user) - return trade; - } - } - } - return null; - } - - public synchronized void dispose() { - synchronized (this.loadLock) { - if (this.preventUnloading) - return; - - if (Emulator.getPluginManager().fireEvent(new RoomUnloadingEvent(this)).isCancelled()) - return; - - if (this.loaded) { - try { - - if (this.traxManager != null && !this.traxManager.disposed()) { - this.traxManager.dispose(); - } - - this.roomCycleTask.cancel(false); - this.scheduledTasks.clear(); - this.scheduledComposers.clear(); - this.loaded = false; - - this.tileCache.clear(); - - synchronized (this.mutedHabbos) { - this.mutedHabbos.clear(); - } - - for (InteractionGameTimer timer : this.getRoomSpecialTypes().getGameTimers().values()) { - timer.setRunning(false); - } - - for (Game game : this.games) { - game.dispose(); - } - this.games.clear(); - - removeAllPets(ownerId); - - synchronized (this.roomItems) { - TIntObjectIterator iterator = this.roomItems.iterator(); - - for (int i = this.roomItems.size(); i-- > 0; ) { - try { - iterator.advance(); - - if (iterator.value().needsUpdate()) - iterator.value().run(); - } catch (NoSuchElementException e) { - break; - } - } - } - - if (this.roomSpecialTypes != null) { - this.roomSpecialTypes.dispose(); - } - - synchronized (this.roomItems) { - this.roomItems.clear(); - } - - synchronized (this.habboQueue) { - this.habboQueue.clear(); - } - - - for (Habbo habbo : this.currentHabbos.values()) { - Emulator.getGameEnvironment().getRoomManager().leaveRoom(habbo, this); - } - - this.sendComposer(new HotelViewComposer().compose()); - - this.currentHabbos.clear(); - - - TIntObjectIterator botIterator = this.currentBots.iterator(); - - for (int i = this.currentBots.size(); i-- > 0; ) { - try { - botIterator.advance(); - botIterator.value().needsUpdate(true); - Emulator.getThreading().run(botIterator.value()); - } catch (NoSuchElementException e) { - LOGGER.error("Caught exception", e); - break; - } - } - - this.currentBots.clear(); - this.currentPets.clear(); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } - } - - try { - this.wordQuiz = ""; - this.yesVotes = 0; - this.noVotes = 0; - this.updateDatabaseUserCount(); - this.preLoaded = true; - this.layout = null; - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } - } - - Emulator.getPluginManager().fireEvent(new RoomUnloadedEvent(this)); - } - - @Override - public int compareTo(Room o) { - if (o.getUserCount() != this.getUserCount()) { - return o.getCurrentHabbos().size() - this.getCurrentHabbos().size(); - } - - return this.id - o.id; - } - - @Override - public void serialize(ServerMessage message) { - message.appendInt(this.id); - message.appendString(this.name); - if (this.isPublicRoom()) { - message.appendInt(0); - message.appendString(""); - } else { - message.appendInt(this.ownerId); - message.appendString(this.ownerName); - } - message.appendInt(this.state.getState()); - message.appendInt(this.getUserCount()); - message.appendInt(this.usersMax); - message.appendString(this.description); - message.appendInt(0); - message.appendInt(this.score); - message.appendInt(0); - message.appendInt(this.category); - - String[] tags = Arrays.stream(this.tags.split(";")).filter(t -> !t.isEmpty()).toArray(String[]::new); - message.appendInt(tags.length); - for (String s : tags) { - message.appendString(s); - } - - int base = 0; - - if (this.getGuildId() > 0) { - base = base | 2; - } - - if (this.isPromoted()) { - base = base | 4; - } - - if (!this.isPublicRoom()) { - base = base | 8; - } - - if (this.isAllowPets()) { - base = base | 16; - } - - message.appendInt(base); - - if (this.getGuildId() > 0) { - Guild g = Emulator.getGameEnvironment().getGuildManager().getGuild(this.getGuildId()); - if (g != null) { - message.appendInt(g.getId()); - message.appendString(g.getName()); - message.appendString(g.getBadge()); - } else { - message.appendInt(0); - message.appendString(""); - message.appendString(""); - } - } - - if (this.promoted) { - message.appendString(this.promotion.getTitle()); - message.appendString(this.promotion.getDescription()); - message.appendInt((this.promotion.getEndTimestamp() - Emulator.getIntUnixTimestamp()) / 60); - } - - } - - @Override - public void run() { - synchronized (this.loadLock) { - if (this.loaded) { - try { - Emulator.getThreading().run( - Room.this::cycle); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } - } - } - - this.save(); - } - - public void save() { - if (this.needsUpdate) { - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE rooms SET name = ?, description = ?, password = ?, state = ?, users_max = ?, category = ?, score = ?, paper_floor = ?, paper_wall = ?, paper_landscape = ?, thickness_wall = ?, wall_height = ?, thickness_floor = ?, moodlight_data = ?, tags = ?, allow_other_pets = ?, allow_other_pets_eat = ?, allow_walkthrough = ?, allow_hidewall = ?, chat_mode = ?, chat_weight = ?, chat_speed = ?, chat_hearing_distance = ?, chat_protection =?, who_can_mute = ?, who_can_kick = ?, who_can_ban = ?, poll_id = ?, guild_id = ?, roller_speed = ?, override_model = ?, is_staff_picked = ?, promoted = ?, trade_mode = ?, move_diagonally = ?, owner_id = ?, owner_name = ?, jukebox_active = ?, hidewired = ? WHERE id = ?")) { - statement.setString(1, this.name); - statement.setString(2, this.description); - statement.setString(3, this.password); - statement.setString(4, this.state.name().toLowerCase()); - statement.setInt(5, this.usersMax); - statement.setInt(6, this.category); - statement.setInt(7, this.score); - statement.setString(8, this.floorPaint); - statement.setString(9, this.wallPaint); - statement.setString(10, this.backgroundPaint); - statement.setInt(11, this.wallSize); - statement.setInt(12, this.wallHeight); - statement.setInt(13, this.floorSize); - StringBuilder moodLightData = new StringBuilder(); - - int id = 1; - for (RoomMoodlightData data : this.moodlightData.valueCollection()) { - data.setId(id); - moodLightData.append(data).append(";"); - id++; - } - - statement.setString(14, moodLightData.toString()); - statement.setString(15, this.tags); - statement.setString(16, this.allowPets ? "1" : "0"); - statement.setString(17, this.allowPetsEat ? "1" : "0"); - statement.setString(18, this.allowWalkthrough ? "1" : "0"); - statement.setString(19, this.hideWall ? "1" : "0"); - statement.setInt(20, this.chatMode); - statement.setInt(21, this.chatWeight); - statement.setInt(22, this.chatSpeed); - statement.setInt(23, this.chatDistance); - statement.setInt(24, this.chatProtection); - statement.setInt(25, this.muteOption); - statement.setInt(26, this.kickOption); - statement.setInt(27, this.banOption); - statement.setInt(28, this.pollId); - statement.setInt(29, this.guild); - statement.setInt(30, this.rollerSpeed); - statement.setString(31, this.overrideModel ? "1" : "0"); - statement.setString(32, this.staffPromotedRoom ? "1" : "0"); - statement.setString(33, this.promoted ? "1" : "0"); - statement.setInt(34, this.tradeMode); - statement.setString(35, this.moveDiagonally ? "1" : "0"); - statement.setInt(36, this.ownerId); - statement.setString(37, this.ownerName); - statement.setString(38, this.jukeboxActive ? "1" : "0"); - statement.setString(39, this.hideWired ? "1" : "0"); - statement.setInt(40, this.id); - statement.executeUpdate(); - this.needsUpdate = false; - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } - } - } - - private void updateDatabaseUserCount() { - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE rooms SET users = ? WHERE id = ? LIMIT 1")) { - statement.setInt(1, this.currentHabbos.size()); - statement.setInt(2, this.id); - statement.executeUpdate(); - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } - } - - private boolean isOnRoller(RoomUnit unit) { - RoomTile currentTile = unit.getCurrentLocation(); - // Iterate over all rollers in the room. - for (HabboItem roller : this.roomSpecialTypes.getRollers().values()) { - if (roller.getX() == currentTile.x && roller.getY() == currentTile.y) { - return true; - } - } - return false; - } - - private final Map loopTrackers = new ConcurrentHashMap<>(); - - private static class LoopTracker { - double x, y, z; - int counter; - RoomTile nextTile; - - public LoopTracker(double x, double y, double z, RoomTile nextTile) { - this.x = x; - this.y = y; - this.z = z; - this.counter = 0; - this.nextTile = nextTile; - } - - public boolean isSamePosition(double newX, double newY, double newZ) { - // Use an epsilon for double comparison if necessary. - final double EPSILON = 0.001; - return Math.abs(newX - x) < EPSILON && Math.abs(newY - y) < EPSILON && Math.abs(newZ - z) < EPSILON; - } - - public void update(double newX, double newY, double newZ, RoomTile nextTile) { - this.x = newX; - this.y = newY; - this.z = newZ; - this.counter = 0; - this.nextTile = nextTile; - } - } - - private void cycle() { - this.cycleOdd = !this.cycleOdd; - this.cycleTimestamp = System.currentTimeMillis(); - final boolean[] foundRightHolder = {false}; - - boolean loaded; - synchronized (this.loadLock) { - loaded = this.loaded; - } - this.tileCache.clear(); - if (loaded) { - if (!this.scheduledTasks.isEmpty()) { - Set tasks = this.scheduledTasks; - this.scheduledTasks = ConcurrentHashMap.newKeySet(); - - for (Runnable runnable : tasks) { - Emulator.getThreading().run(runnable); - } - } - - for (ICycleable task : this.roomSpecialTypes.getCycleTasks()) { - task.cycle(this); - } - - if (Emulator.getConfig().getBoolean("hotel.rooms.deco_hosting")) { - if (this.idleHostingCycles < 120) { - this.idleHostingCycles++; - } else { - this.idleHostingCycles = 0; - - int amount = (int) this.currentHabbos.values().stream().filter(habbo -> habbo.getHabboInfo().getId() != this.ownerId).count(); - if (amount > 0) { - AchievementManager.progressAchievement(this.ownerId, Emulator.getGameEnvironment().getAchievementManager().getAchievement("RoomDecoHosting"), amount); - } - } - } - - if (!this.currentHabbos.isEmpty()) { - this.idleCycles = 0; - - THashSet updatedUnit = new THashSet<>(); - ArrayList toKick = new ArrayList<>(); - - final Room room = this; - - final long millis = System.currentTimeMillis(); - - for (Habbo habbo : this.currentHabbos.values()) { - RoomUnit unit = habbo.getRoomUnit(); - // Only run loop detection if the unit is on a roller. - if (!unit.hasStatus(RoomUnitStatus.MOVE) && isOnRoller(unit)) { - // Get the current coordinates of the unit on the roller: - double curX = unit.getX(); - double curY = unit.getY(); - double curZ = unit.getZ(); - - // Get or create a loop tracker for this unit. - LoopTracker tracker = loopTrackers.computeIfAbsent(unit, u -> { - RoomTile nextTile = null; - for (InteractionRoller roller : this.roomSpecialTypes.getRollers().values()) { - RoomTile rollerTile = this.getLayout().getTile(roller.getX(), roller.getY()); - RoomTile potentialNextTile = this.getLayout().getTileInFront(rollerTile, roller.getRotation()); - if (potentialNextTile != null) { - nextTile = potentialNextTile; - break; // use the first valid next tile found - } - } - return new LoopTracker(curX, curY, curZ, nextTile); - }); - - // Check if the unit’s current position is the same as last cycle. - if (tracker.isSamePosition(curX, curY, curZ)) { - // Find the roller that is on the current tile. - InteractionRoller currentRoller = null; - RoomTile currentTile = this.getLayout().getTile((short) curX, (short) curY); - for (InteractionRoller roller : this.roomSpecialTypes.getRollers().values()) { - if (roller.getX() == currentTile.x && roller.getY() == currentTile.y) { - currentRoller = roller; - break; - } - } - - // Calculate the next tile using the current roller. - RoomTile currentCalculatedNextTile = null; - if (currentRoller != null) { - RoomTile rollerTile = this.getLayout().getTile(currentRoller.getX(), currentRoller.getY()); - currentCalculatedNextTile = this.getLayout().getTileInFront(rollerTile, currentRoller.getRotation()); - } - - // Here we add a check: - // If the unit's goal is either on the roller itself or is within a small distance (epsilon) of the roller tile, - // then we assume the habbo is "almost there" and do not increment the loop counter. - if (currentTile != null && unit.getGoal() != null) { - double distanceToGoal = Math.hypot(unit.getGoal().x - currentTile.x, unit.getGoal().y - currentTile.y); - final double ACCEPTABLE_DISTANCE = 0.5; // adjust as needed - if (distanceToGoal <= ACCEPTABLE_DISTANCE) { - tracker.update(curX, curY, curZ, currentCalculatedNextTile); - tracker.counter = 0; - continue; - } - } - - // Otherwise, use your normal loop detection logic: - // (for example, checking if the next tile contains a roller, etc.) - boolean nextTileHasRoller = false; - if (currentCalculatedNextTile != null) { - for (InteractionRoller roller : this.roomSpecialTypes.getRollers().values()) { - if (roller.getX() == currentCalculatedNextTile.x && roller.getY() == currentCalculatedNextTile.y) { - nextTileHasRoller = true; - break; - } - } - } - - if (currentCalculatedNextTile != null && !nextTileHasRoller) { - tracker.update(curX, curY, curZ, currentCalculatedNextTile); - tracker.counter = 0; - } else { - if (unit.getGoal() != null && tracker.nextTile != null && - unit.getGoal().x == tracker.nextTile.x && unit.getGoal().y == tracker.nextTile.y) { - tracker.counter = 0; - } else { - tracker.counter++; - int loopThreshold = Math.max(10, 3 + this.getRollerSpeed()); - if (tracker.counter >= loopThreshold) { - RoomTile doorTile = this.getLayout().getDoorTile(); - unit.setBodyRotation(RoomUserRotation.values()[this.getLayout().getDoorDirection()]); - unit.setHeadRotation(RoomUserRotation.values()[this.getLayout().getDoorDirection()]); - this.teleportRoomUnitToLocation(unit, doorTile.x, doorTile.y, doorTile.z); - tracker.counter = 0; - } - } - } - } else { - // Unit has moved; update tracker accordingly. - InteractionRoller currentRoller = null; - RoomTile currentTile = this.getLayout().getTile((short) curX, (short) curY); - for (InteractionRoller roller : this.roomSpecialTypes.getRollers().values()) { - if (roller.getX() == currentTile.x && roller.getY() == currentTile.y) { - currentRoller = roller; - break; - } - } - RoomTile nextTile = null; - if (currentRoller != null) { - RoomTile rollerTile = this.getLayout().getTile(currentRoller.getX(), currentRoller.getY()); - nextTile = this.getLayout().getTileInFront(rollerTile, currentRoller.getRotation()); - } - tracker.update(curX, curY, curZ, nextTile); - } - } - - if (!foundRightHolder[0]) { - foundRightHolder[0] = habbo.getRoomUnit().getRightsLevel() != RoomRightLevels.NONE; - } - - if (habbo.getRoomUnit().getHandItem() > 0 && millis - habbo.getRoomUnit().getHandItemTimestamp() > (Room.HAND_ITEM_TIME * 1000L)) { - this.giveHandItem(habbo, 0); - } - - if (habbo.getRoomUnit().getEffectId() > 0 && millis / 1000 > habbo.getRoomUnit().getEffectEndTimestamp()) { - this.giveEffect(habbo, 0, -1); - } - - if (habbo.getRoomUnit().isKicked) { - habbo.getRoomUnit().kickCount++; - - if (habbo.getRoomUnit().kickCount >= 5) { - this.scheduledTasks.add(() -> Emulator.getGameEnvironment().getRoomManager().leaveRoom(habbo, room)); - continue; - } - } - - if (Emulator.getConfig().getBoolean("hotel.rooms.auto.idle")) { - if (!habbo.getRoomUnit().isIdle()) { - habbo.getRoomUnit().increaseIdleTimer(); - - if (habbo.getRoomUnit().isIdle()) { - boolean danceIsNone = (habbo.getRoomUnit().getDanceType() == DanceType.NONE); - if (danceIsNone) - this.sendComposer(new RoomUnitIdleComposer(habbo.getRoomUnit()).compose()); - if (danceIsNone && !Emulator.getConfig().getBoolean("hotel.roomuser.idle.not_dancing.ignore.wired_idle")) - WiredHandler.handle(WiredTriggerType.IDLES, habbo.getRoomUnit(), this, new Object[]{habbo}); - } - } else { - habbo.getRoomUnit().increaseIdleTimer(); - - if (!this.isOwner(habbo) && habbo.getRoomUnit().getIdleTimer() >= Room.IDLE_CYCLES_KICK) { - UserExitRoomEvent event = new UserExitRoomEvent(habbo, UserExitRoomEvent.UserExitRoomReason.KICKED_IDLE); - Emulator.getPluginManager().fireEvent(event); - - if (!event.isCancelled()) { - toKick.add(habbo); - } - } - } - } - - if (habbo.getHabboStats().mutedBubbleTracker && habbo.getHabboStats().allowTalk()) { - habbo.getHabboStats().mutedBubbleTracker = false; - this.sendComposer(new RoomUserIgnoredComposer(habbo, RoomUserIgnoredComposer.UNIGNORED).compose()); - } - - // Substract 1 from the chatCounter every odd cycle, which is every (500ms * 2). - if (this.cycleOdd && habbo.getHabboStats().chatCounter.get() > 0) { - habbo.getHabboStats().chatCounter.decrementAndGet(); - } - - if (this.cycleRoomUnit(habbo.getRoomUnit(), RoomUnitType.USER)) { - updatedUnit.add(habbo.getRoomUnit()); - } - } - - if (!toKick.isEmpty()) { - for (Habbo habbo : toKick) { - Emulator.getGameEnvironment().getRoomManager().leaveRoom(habbo, this); - } - } - - if (!this.currentBots.isEmpty()) { - TIntObjectIterator botIterator = this.currentBots.iterator(); - for (int i = this.currentBots.size(); i-- > 0; ) { - try { - final Bot bot; - try { - botIterator.advance(); - bot = botIterator.value(); - } catch (Exception e) { - break; - } - - if (!this.allowBotsWalk && bot.getRoomUnit().isWalking()) { - bot.getRoomUnit().stopWalking(); - updatedUnit.add(bot.getRoomUnit()); - continue; - } - - botIterator.value().cycle(this.allowBotsWalk); - - - if (this.cycleRoomUnit(bot.getRoomUnit(), RoomUnitType.BOT)) { - updatedUnit.add(bot.getRoomUnit()); - } - - - } catch (NoSuchElementException e) { - LOGGER.error("Caught exception", e); - break; - } - } - } - - if (!this.currentPets.isEmpty()) { - if (this.allowBotsWalk) { - TIntObjectIterator petIterator = this.currentPets.iterator(); - for (int i = this.currentPets.size(); i-- > 0; ) { - try { - petIterator.advance(); - } catch (NoSuchElementException e) { - LOGGER.error("Caught exception", e); - break; - } - - Pet pet = petIterator.value(); - if (this.cycleRoomUnit(pet.getRoomUnit(), RoomUnitType.PET)) { - updatedUnit.add(pet.getRoomUnit()); - } - - pet.cycle(); - - if (pet.packetUpdate) { - updatedUnit.add(pet.getRoomUnit()); - pet.packetUpdate = false; - } - - if (pet.getRoomUnit().isWalking() && pet.getRoomUnit().getPath().size() == 1 && pet.getRoomUnit().hasStatus(RoomUnitStatus.GESTURE)) { - pet.getRoomUnit().removeStatus(RoomUnitStatus.GESTURE); - updatedUnit.add(pet.getRoomUnit()); - } - } - } - } - - List rollers = new ArrayList<>(this.roomSpecialTypes.getRollers().values()); - - // Sort rollers using a custom comparator that uses a projection of the roller's position - rollers.sort((r1, r2) -> { - // Convert the roller's rotation into an angle in radians. - double angle1 = Math.toRadians(r1.getRotation() * 45); - double angle2 = Math.toRadians(r2.getRotation() * 45); - - // Compute the movement vector for each roller (a unit vector in its direction) - double vx1 = Math.cos(angle1); - double vy1 = Math.sin(angle1); - double vx2 = Math.cos(angle2); - double vy2 = Math.sin(angle2); - - // Calculate the projection of the roller's position along its movement vector - double proj1 = r1.getX() * vx1 + r1.getY() * vy1; - double proj2 = r2.getX() * vx2 + r2.getY() * vy2; - - return Double.compare(proj1, proj2); - }); - if (this.rollerSpeed != -1 && this.rollerCycle >= this.rollerSpeed) { - this.rollerCycle = 0; - - THashSet messages = new THashSet<>(); - - //Find alternative for this. - //Reason is that tile gets updated after every roller. - List rollerFurniIds = new ArrayList<>(); - List rolledUnitIds = new ArrayList<>(); - - - this.roomSpecialTypes.getRollers().forEachValue(roller -> { - - HabboItem newRoller = null; - - RoomTile rollerTile = this.getLayout().getTile(roller.getX(), roller.getY()); - - if (rollerTile == null) - return true; - - THashSet itemsOnRoller = new THashSet<>(); - - for (HabboItem item : getItemsAt(rollerTile)) { - if (item.getZ() >= roller.getZ() + Item.getCurrentHeight(roller)) { - itemsOnRoller.add(item); - } - } - - // itemsOnRoller.addAll(this.getItemsAt(rollerTile)); - itemsOnRoller.remove(roller); - - if (!rollerTile.hasUnits() && itemsOnRoller.isEmpty()) - return true; - - // RoomTile currentTile = Room.this.layout.getTile(roller.getX(), roller.getY()); - RoomTile tileInFront = Room.this.layout.getTileInFront(Room.this.layout.getTile(roller.getX(), roller.getY()), roller.getRotation()); - - // LOGGER.debug("Roller at: (" + roller.getX() + ", " + roller.getY() + "), Rotation: " + roller.getRotation()); - // LOGGER.debug("Next tile calculated: (" + (tileInFront != null ? tileInFront.x : "NULL") + ", " + (tileInFront != null ? tileInFront.y : "NULL") + ")"); - - if (tileInFront == null) - return true; - - if (!Room.this.layout.tileExists(tileInFront.x, tileInFront.y)) - return true; - - if (tileInFront.state == RoomTileState.INVALID) - return true; - - if (!tileInFront.getAllowStack() && !(tileInFront.isWalkable() || tileInFront.state == RoomTileState.SIT || tileInFront.state == RoomTileState.LAY)) - return true; - - if (tileInFront.hasUnits()) - return true; - - THashSet itemsNewTile = new THashSet<>(); - itemsNewTile.addAll(getItemsAt(tileInFront)); - itemsNewTile.removeAll(itemsOnRoller); - - List toRemove = new ArrayList<>(); - for (HabboItem item : itemsOnRoller) { - if (item.getX() != roller.getX() || item.getY() != roller.getY() || rollerFurniIds.contains(item.getId())) { - toRemove.add(item); - } - } - itemsOnRoller.removeAll(toRemove); - HabboItem topItem = Room.this.getTopItemAt(tileInFront.x, tileInFront.y); - - boolean allowUsers = true; - boolean allowFurniture = true; - boolean stackContainsRoller = false; - - for (HabboItem item : itemsNewTile) { - if (!(item.getBaseItem().allowWalk() || item.getBaseItem().allowSit()) && !(item instanceof InteractionGate && item.getExtradata().equals("1"))) { - allowUsers = false; - } - if (item instanceof InteractionRoller) { - newRoller = item; - stackContainsRoller = true; - - if ((item.getZ() != roller.getZ() || (itemsNewTile.size() > 1 && item != topItem)) && !InteractionRoller.NO_RULES) { - allowUsers = false; - allowFurniture = false; - continue; - } - - break; - } else { - allowFurniture = false; - } - } - - if (allowFurniture) { - allowFurniture = tileInFront.getAllowStack(); - } - - double zOffset = 0; - if (newRoller != null) { - if ((!itemsNewTile.isEmpty() && (itemsNewTile.size() > 1)) && !InteractionRoller.NO_RULES) { - return true; - } - } else { - zOffset = -Item.getCurrentHeight(roller) + tileInFront.getStackHeight() - rollerTile.z; - } - - if (allowUsers) { - Event roomUserRolledEvent = null; - - if (Emulator.getPluginManager().isRegistered(UserRolledEvent.class, true)) { - roomUserRolledEvent = new UserRolledEvent(null, null, null); - } - - ArrayList unitsOnTile = new ArrayList<>(rollerTile.getUnits()); - - for (RoomUnit unit : rollerTile.getUnits()) { - if (unit.getRoomUnitType() == RoomUnitType.PET) { - Pet pet = this.getPet(unit); - if (pet instanceof RideablePet && ((RideablePet) pet).getRider() != null) { - unitsOnTile.remove(unit); - } - } - } - - THashSet usersRolledThisTile = new THashSet<>(); - - for (RoomUnit unit : unitsOnTile) { - if (rolledUnitIds.contains(unit.getId())) continue; - - if (usersRolledThisTile.size() >= Room.ROLLERS_MAXIMUM_ROLL_AVATARS) break; - - if (stackContainsRoller && !allowFurniture && !(topItem != null && topItem.isWalkable())) - continue; - - if (unit.hasStatus(RoomUnitStatus.MOVE)) - continue; - - RoomTile tile = tileInFront.copy(); - tile.setStackHeight(unit.getZ() + zOffset); - - if (roomUserRolledEvent != null && unit.getRoomUnitType() == RoomUnitType.USER) { - roomUserRolledEvent = new UserRolledEvent(getHabbo(unit), roller, tile); - Emulator.getPluginManager().fireEvent(roomUserRolledEvent); - - if (roomUserRolledEvent.isCancelled()) - continue; - } - - // horse riding - boolean isRiding = false; - if (unit.getRoomUnitType() == RoomUnitType.USER) { - Habbo rollingHabbo = this.getHabbo(unit); - if (rollingHabbo != null && rollingHabbo.getHabboInfo() != null) { - RideablePet riding = rollingHabbo.getHabboInfo().getRiding(); - if (riding != null) { - RoomUnit ridingUnit = riding.getRoomUnit(); - tile.setStackHeight(ridingUnit.getZ() + zOffset); - rolledUnitIds.add(ridingUnit.getId()); - updatedUnit.remove(ridingUnit); - messages.add(new RoomUnitOnRollerComposer(ridingUnit, roller, ridingUnit.getCurrentLocation(), ridingUnit.getZ(), tile, tile.getStackHeight(), room)); - isRiding = true; - } - } - } - - usersRolledThisTile.add(unit.getId()); - rolledUnitIds.add(unit.getId()); - updatedUnit.remove(unit); - messages.add(new RoomUnitOnRollerComposer(unit, roller, unit.getCurrentLocation(), unit.getZ() + (isRiding ? 1 : 0), tile, tile.getStackHeight() + (isRiding ? 1 : 0), room)); - - if (itemsOnRoller.isEmpty()) { - HabboItem item = room.getTopItemAt(tileInFront.x, tileInFront.y); - - if (item != null && itemsNewTile.contains(item) && !itemsOnRoller.contains(item)) { - Emulator.getThreading().run(() -> { - if (unit.getGoal() == rollerTile) { - try { - item.onWalkOn(unit, room, new Object[]{rollerTile, tileInFront}); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } - } - }, this.getRollerSpeed() == 0 ? 250 : InteractionRoller.DELAY); - } - } - - if (unit.hasStatus(RoomUnitStatus.SIT)) { - unit.sitUpdate = true; - } - } - } - - if (!messages.isEmpty()) { - for (MessageComposer message : messages) { - room.sendComposer(message.compose()); - } - messages.clear(); - } - - if (allowFurniture || !stackContainsRoller || InteractionRoller.NO_RULES) { - Event furnitureRolledEvent = null; - - if (Emulator.getPluginManager().isRegistered(FurnitureRolledEvent.class, true)) { - furnitureRolledEvent = new FurnitureRolledEvent(null, null, null); - } - - if (newRoller == null || topItem == newRoller) { - List sortedItems = new ArrayList<>(itemsOnRoller); - sortedItems.sort((o1, o2) -> o1.getZ() > o2.getZ() ? -1 : 1); - - for (HabboItem item : sortedItems) { - if (item.getX() == roller.getX() && item.getY() == roller.getY() && zOffset <= 0) { - if (item != roller) { - if (furnitureRolledEvent != null) { - furnitureRolledEvent = new FurnitureRolledEvent(item, roller, tileInFront); - Emulator.getPluginManager().fireEvent(furnitureRolledEvent); - - if (furnitureRolledEvent.isCancelled()) - continue; - } - - messages.add(new FloorItemOnRollerComposer(item, roller, tileInFront, zOffset, room)); - rollerFurniIds.add(item.getId()); - } - } - } - } - } - - - if (!messages.isEmpty()) { - for (MessageComposer message : messages) { - room.sendComposer(message.compose()); - } - messages.clear(); - } - - return true; - }); - - - int currentTime = (int) (this.cycleTimestamp / 1000); - for (HabboItem pyramid : this.roomSpecialTypes.getItemsOfType(InteractionPyramid.class)) { - if (pyramid instanceof InteractionPyramid) { - - if (((InteractionPyramid) pyramid).getNextChange() < currentTime) { - ((InteractionPyramid) pyramid).change(this); - } - } - } - } else { - this.rollerCycle++; - } - - if (!updatedUnit.isEmpty()) { - this.sendComposer(new RoomUserStatusComposer(updatedUnit, true).compose()); - } - - this.traxManager.cycle(); - } else { - - if (this.idleCycles < 60) - this.idleCycles++; - else - this.dispose(); - } - } - - synchronized (this.habboQueue) { - if (!this.habboQueue.isEmpty() && !foundRightHolder[0]) { - this.habboQueue.forEachEntry(new TIntObjectProcedure() { - @Override - public boolean execute(int a, Habbo b) { - if (b.isOnline()) { - if (b.getHabboInfo().getRoomQueueId() == Room.this.getId()) { - b.getClient().sendResponse(new RoomAccessDeniedComposer("")); - } - } - - return true; - } - }); - - this.habboQueue.clear(); - } - } - - if (!this.scheduledComposers.isEmpty()) { - for (ServerMessage message : this.scheduledComposers) { - this.sendComposer(message); - } - - this.scheduledComposers.clear(); - } - } - - - private boolean cycleRoomUnit(RoomUnit unit, RoomUnitType type) { - boolean update = unit.needsStatusUpdate(); - if (unit.hasStatus(RoomUnitStatus.SIGN)) { - this.sendComposer(new RoomUserStatusComposer(unit).compose()); - unit.removeStatus(RoomUnitStatus.SIGN); - } - - if (unit.isWalking() && unit.getPath() != null && !unit.getPath().isEmpty()) { - if (!unit.cycle(this)) { - return true; - } - } else { - if ((unit.hasStatus(RoomUnitStatus.MOVE) || unit.hasStatus(RoomUnitStatus.SNOWWAR_RUN)) && !unit.animateWalk) { - unit.removeStatus(RoomUnitStatus.MOVE); - unit.removeStatus(RoomUnitStatus.SNOWWAR_RUN); - - update = true; - } - - if (!unit.isWalking() && !unit.cmdSit) { - RoomTile thisTile = this.getLayout().getTile(unit.getX(), unit.getY()); - HabboItem topItem = this.getTallestChair(thisTile); - - if (topItem == null || !topItem.getBaseItem().allowSit()) { - if (unit.hasStatus(RoomUnitStatus.SIT)) { - unit.removeStatus(RoomUnitStatus.SIT); - update = true; - } - } else if (thisTile.state == RoomTileState.SIT && (!unit.hasStatus(RoomUnitStatus.SIT) || unit.sitUpdate)) { - this.dance(unit, DanceType.NONE); - //int tileHeight = this.layout.getTile(topItem.getX(), topItem.getY()).z; - unit.setStatus(RoomUnitStatus.SIT, (Item.getCurrentHeight(topItem)) + ""); - unit.setZ(topItem.getZ()); - unit.setRotation(RoomUserRotation.values()[topItem.getRotation()]); - unit.sitUpdate = false; - return true; - } - } - } - - if (!unit.isWalking() && !unit.cmdLay && !unit.hasStatus(RoomUnitStatus.SNOWWAR_DIE_BACK)) { - HabboItem topItem = this.getTopItemAt(unit.getX(), unit.getY()); - - if (topItem == null || !topItem.getBaseItem().allowLay()) { - if (unit.hasStatus(RoomUnitStatus.LAY)) { - unit.removeStatus(RoomUnitStatus.LAY); - update = true; - } - } else { - if (!unit.hasStatus(RoomUnitStatus.LAY)) { - unit.setStatus(RoomUnitStatus.LAY, Item.getCurrentHeight(topItem) + ""); - unit.setRotation(RoomUserRotation.values()[topItem.getRotation() % 4]); - - if (topItem.getRotation() == 0 || topItem.getRotation() == 4) { - unit.setLocation(this.layout.getTile(unit.getX(), topItem.getY())); - //unit.setOldY(topItem.getY()); - } else { - unit.setLocation(this.layout.getTile(topItem.getX(), unit.getY())); - //unit.setOldX(topItem.getX()); - } - update = true; - } - } - } - - if (update) { - unit.statusUpdate(false); - } - - return update; - } - - public int getId() { - return this.id; - } - - public int getOwnerId() { - return this.ownerId; - } - - public void setOwnerId(int ownerId) { - this.ownerId = ownerId; - } - - public String getOwnerName() { - return this.ownerName; - } - - public void setOwnerName(String ownerName) { - this.ownerName = ownerName; - } - - public String getName() { - return this.name; - } - - public void setName(String name) { - this.name = name; - - if (this.name.length() > 50) { - this.name = this.name.substring(0, 50); - } - - if (this.hasGuild()) { - Guild guild = Emulator.getGameEnvironment().getGuildManager().getGuild(this.guild); - - if (guild != null) { - guild.setRoomName(name); - } - } - } - - public String getDescription() { - return this.description; - } - - public void setDescription(String description) { - this.description = description; - - if (this.description.length() > 250) { - this.description = this.description.substring(0, 250); - } - } - - public RoomLayout getLayout() { - return this.layout; - } - - public void setLayout(RoomLayout layout) { - this.layout = layout; - } - - public boolean hasCustomLayout() { - return this.overrideModel; - } - - public void setHasCustomLayout(boolean overrideModel) { - this.overrideModel = overrideModel; - } - - public String getPassword() { - return this.password; - } - - public void setPassword(String password) { - this.password = password; - - if (this.password.length() > 20) { - this.password = this.password.substring(0, 20); - } - } - - public RoomState getState() { - return this.state; - } - - public void setState(RoomState state) { - this.state = state; - } - - public int getUsersMax() { - return this.usersMax; - } - - public void setUsersMax(int usersMax) { - this.usersMax = usersMax; - } - - public int getScore() { - return this.score; - } - - public void setScore(int score) { - this.score = score; - } - - public int getCategory() { - return this.category; - } - - public void setCategory(int category) { - this.category = category; - } - - public String getFloorPaint() { - return this.floorPaint; - } - - public void setFloorPaint(String floorPaint) { - this.floorPaint = floorPaint; - } - - public String getWallPaint() { - return this.wallPaint; - } - - public void setWallPaint(String wallPaint) { - this.wallPaint = wallPaint; - } - - public String getBackgroundPaint() { - return this.backgroundPaint; - } - - public void setBackgroundPaint(String backgroundPaint) { - this.backgroundPaint = backgroundPaint; - } - - public int getWallSize() { - return this.wallSize; - } - - public void setWallSize(int wallSize) { - this.wallSize = wallSize; - } - - public int getWallHeight() { - return this.wallHeight; - } - - public void setWallHeight(int wallHeight) { - this.wallHeight = wallHeight; - } - - public int getFloorSize() { - return this.floorSize; - } - - public void setFloorSize(int floorSize) { - this.floorSize = floorSize; - } - - public String getTags() { - return this.tags; - } - - public void setTags(String tags) { - this.tags = tags; - } - - public int getTradeMode() { - return this.tradeMode; - } - - public void setTradeMode(int tradeMode) { - this.tradeMode = tradeMode; - } - - public boolean moveDiagonally() { - return this.moveDiagonally; - } - - public void moveDiagonally(boolean moveDiagonally) { - this.moveDiagonally = moveDiagonally; - this.layout.moveDiagonally(this.moveDiagonally); - this.needsUpdate = true; - } - - public int getGuildId() { - return this.guild; - } - - public boolean hasGuild() { - return this.guild != 0; - } - - public void setGuild(int guild) { - this.guild = guild; - } - - public String getGuildName() { - if (this.hasGuild()) { - Guild guild = Emulator.getGameEnvironment().getGuildManager().getGuild(this.guild); - - if (guild != null) { - return guild.getName(); - } - } - - return ""; - } - - public boolean isPublicRoom() { - return this.publicRoom; - } - - public void setPublicRoom(boolean publicRoom) { - this.publicRoom = publicRoom; - } - - public boolean isStaffPromotedRoom() { - return this.staffPromotedRoom; - } - - public void setStaffPromotedRoom(boolean staffPromotedRoom) { - this.staffPromotedRoom = staffPromotedRoom; - } - - public boolean isAllowPets() { - return this.allowPets; - } - - public void setAllowPets(boolean allowPets) { - this.allowPets = allowPets; - if (!allowPets) { - removeAllPets(ownerId); - } - } - - public boolean isAllowPetsEat() { - return this.allowPetsEat; - } - - public void setAllowPetsEat(boolean allowPetsEat) { - this.allowPetsEat = allowPetsEat; - } - - public boolean isAllowWalkthrough() { - return this.allowWalkthrough; - } - - public void setAllowWalkthrough(boolean allowWalkthrough) { - this.allowWalkthrough = allowWalkthrough; - } - - public boolean isAllowBotsWalk() { - return this.allowBotsWalk; - } - - public void setAllowBotsWalk(boolean allowBotsWalk) { - this.allowBotsWalk = allowBotsWalk; - } - - public boolean isAllowEffects() { - return this.allowEffects; - } - - public void setAllowEffects(boolean allowEffects) { - this.allowEffects = allowEffects; - } - - public boolean isHideWall() { - return this.hideWall; - } - - public void setHideWall(boolean hideWall) { - this.hideWall = hideWall; - } - - public Color getBackgroundTonerColor() { - Color color = new Color(0, 0, 0); - TIntObjectIterator iterator = this.roomItems.iterator(); - - for (int i = this.roomItems.size(); i > 0; i--) { - try { - iterator.advance(); - HabboItem object = iterator.value(); - - if (object instanceof InteractionBackgroundToner) { - String[] extraData = object.getExtradata().split(":"); - - if (extraData.length == 4) { - if (extraData[0].equalsIgnoreCase("1")) { - return Color.getHSBColor(Integer.parseInt(extraData[1]), Integer.parseInt(extraData[2]), Integer.parseInt(extraData[3])); - } - } - } - } catch (Exception e) { - } - } - - return color; - } - - public int getChatMode() { - return this.chatMode; - } - - public void setChatMode(int chatMode) { - this.chatMode = chatMode; - } - - public int getChatWeight() { - return this.chatWeight; - } - - public void setChatWeight(int chatWeight) { - this.chatWeight = chatWeight; - } - - public int getChatSpeed() { - return this.chatSpeed; - } - - public void setChatSpeed(int chatSpeed) { - this.chatSpeed = chatSpeed; - } - - public int getChatDistance() { - return this.chatDistance; - } - - public void setChatDistance(int chatDistance) { - this.chatDistance = chatDistance; - } - - public void removeAllPets() { - removeAllPets(-1); - } - - /** - * Removes all pets from the room except if the owner id is excludeUserId - * - * @param excludeUserId Habbo id to keep pets - */ - public void removeAllPets(int excludeUserId) { - ArrayList toRemovePets = new ArrayList<>(); - ArrayList removedPets = new ArrayList<>(); - synchronized (this.currentPets) { - for (Pet pet : this.currentPets.valueCollection()) { - try { - if (pet.getUserId() != excludeUserId) { - toRemovePets.add(pet); - } - - } catch (NoSuchElementException e) { - LOGGER.error("Caught exception", e); - break; - } - } - } - - for (Pet pet : toRemovePets) { - removedPets.add(pet); - - pet.removeFromRoom(); - - Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(pet.getUserId()); - if (habbo != null) { - habbo.getInventory().getPetsComponent().addPet(pet); - habbo.getClient().sendResponse(new AddPetComposer(pet)); - } - - pet.needsUpdate = true; - pet.run(); - } - - for (Pet pet : removedPets) { - this.currentPets.remove(pet.getId()); - } - } - - public int getChatProtection() { - return this.chatProtection; - } - - public void setChatProtection(int chatProtection) { - this.chatProtection = chatProtection; - } - - public int getMuteOption() { - return this.muteOption; - } - - public void setMuteOption(int muteOption) { - this.muteOption = muteOption; - } - - public int getKickOption() { - return this.kickOption; - } - - public void setKickOption(int kickOption) { - this.kickOption = kickOption; - } - - public int getBanOption() { - return this.banOption; - } - - public void setBanOption(int banOption) { - this.banOption = banOption; - } - - public int getPollId() { - return this.pollId; - } - - public void setPollId(int pollId) { - this.pollId = pollId; - } - - public int getRollerSpeed() { - return this.rollerSpeed; - } - - public void setRollerSpeed(int rollerSpeed) { - this.rollerSpeed = rollerSpeed; - this.rollerCycle = 0; - this.needsUpdate = true; - } - - public String[] filterAnything() { - return new String[]{this.getOwnerName(), this.getGuildName(), this.getDescription(), this.getPromotionDesc()}; - } - - public long getCycleTimestamp() { - return this.cycleTimestamp; - } - - public boolean isPromoted() { - this.promoted = this.promotion != null && this.promotion.getEndTimestamp() > Emulator.getIntUnixTimestamp(); - this.needsUpdate = true; - - return this.promoted; - } - - public RoomPromotion getPromotion() { - return this.promotion; - } - - public String getPromotionDesc() { - if (this.promotion != null) { - return this.promotion.getDescription(); - } - - return ""; - } - - public void createPromotion(String title, String description, int category) { - this.promoted = true; - - if (this.promotion == null) { - this.promotion = new RoomPromotion(this, title, description, Emulator.getIntUnixTimestamp() + (120 * 60), Emulator.getIntUnixTimestamp(), category); - } else { - this.promotion.setTitle(title); - this.promotion.setDescription(description); - this.promotion.setEndTimestamp(Emulator.getIntUnixTimestamp() + (120 * 60)); - this.promotion.setCategory(category); - } - - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO room_promotions (room_id, title, description, end_timestamp, start_timestamp, category) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE title = ?, description = ?, end_timestamp = ?, category = ?")) { - statement.setInt(1, this.id); - statement.setString(2, title); - statement.setString(3, description); - statement.setInt(4, this.promotion.getEndTimestamp()); - statement.setInt(5, this.promotion.getStartTimestamp()); - statement.setInt(6, category); - statement.setString(7, this.promotion.getTitle()); - statement.setString(8, this.promotion.getDescription()); - statement.setInt(9, this.promotion.getEndTimestamp()); - statement.setInt(10, this.promotion.getCategory()); - statement.execute(); - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } - - this.needsUpdate = true; - } - - public boolean addGame(Game game) { - synchronized (this.games) { - return this.games.add(game); - } - } - - public boolean deleteGame(Game game) { - game.stop(); - game.dispose(); - synchronized (this.games) { - return this.games.remove(game); - } - } - - public Game getGame(Class gameType) { - if (gameType == null) return null; - - synchronized (this.games) { - for (Game game : this.games) { - if (gameType.isInstance(game)) { - return game; - } - } - } - - return null; - } - - public Game getGameOrCreate(Class gameType) { - Game game = this.getGame(gameType); - if (game == null) { - try { - game = gameType.getDeclaredConstructor(Room.class).newInstance(this); - this.addGame(game); - } catch (Exception e) { - LOGGER.error("Error getting game {}", gameType.getName(), e); - } - } - - return game; - } - - public Set getGames() { - return this.games; - } - - public int getUserCount() { - return this.currentHabbos.size(); - } - - public ConcurrentHashMap getCurrentHabbos() { - return this.currentHabbos; - } - - public Collection getHabbos() { - return this.currentHabbos.values(); - } - - public TIntObjectMap getHabboQueue() { - return this.habboQueue; - } - - public TIntObjectMap getFurniOwnerNames() { - return this.furniOwnerNames; - } - - public String getFurniOwnerName(int userId) { - return this.furniOwnerNames.get(userId); - } - - public TIntIntMap getFurniOwnerCount() { - return this.furniOwnerCount; - } - - public TIntObjectMap getMoodlightData() { - return this.moodlightData; - } - - public int getLastTimerReset() { - return this.lastTimerReset; - } - - public void setLastTimerReset(int lastTimerReset) { - this.lastTimerReset = lastTimerReset; - } - - public void addToQueue(Habbo habbo) { - synchronized (this.habboQueue) { - this.habboQueue.put(habbo.getHabboInfo().getId(), habbo); - } - } - - public boolean removeFromQueue(Habbo habbo) { - try { - this.sendComposer(new HideDoorbellComposer(habbo.getHabboInfo().getUsername()).compose()); - - synchronized (this.habboQueue) { - return this.habboQueue.remove(habbo.getHabboInfo().getId()) != null; - } + CompletableFuture rightsFuture = CompletableFuture.runAsync(() -> { + try (Connection rightsConnection = Emulator.getDatabase().getDataSource().getConnection()) { + this.loadRights(rightsConnection); } catch (Exception e) { - LOGGER.error("Caught exception", e); + LOGGER.error("Caught exception loading rights", e); } + }, Emulator.getThreading().getService()); - return true; + CompletableFuture wordFilterFuture = CompletableFuture.runAsync(() -> { + try (Connection wordFilterConnection = Emulator.getDatabase().getDataSource().getConnection()) { + this.loadWordFilter(wordFilterConnection); + } catch (Exception e) { + LOGGER.error("Caught exception loading word filter", e); + } + }, Emulator.getThreading().getService()); + + // Wait for items to be loaded before loading wired data (wired depends on items) + try { + itemsFuture.join(); + } catch (Exception e) { + LOGGER.error("Error waiting for items to load", e); + } + + // Phase 3: Load heightmap after items are loaded (depends on items for stack heights) + try { + this.loadHeightmap(); + } catch (Exception e) { + LOGGER.error("Caught exception loading heightmap", e); + } + + // Phase 4: Load bots, pets, and wired data in parallel (all depend on layout + items) + CompletableFuture botsFuture = CompletableFuture.runAsync(() -> { + try (Connection botsConnection = Emulator.getDatabase().getDataSource().getConnection()) { + this.loadBots(botsConnection); + } catch (Exception e) { + LOGGER.error("Caught exception loading bots", e); + } + }, Emulator.getThreading().getService()); + + CompletableFuture petsFuture = CompletableFuture.runAsync(() -> { + try (Connection petsConnection = Emulator.getDatabase().getDataSource().getConnection()) { + this.loadPets(petsConnection); + } catch (Exception e) { + LOGGER.error("Caught exception loading pets", e); + } + }, Emulator.getThreading().getService()); + + CompletableFuture wiredFuture = CompletableFuture.runAsync(() -> { + try (Connection wiredConnection = Emulator.getDatabase().getDataSource().getConnection()) { + this.loadWiredData(wiredConnection); + } catch (Exception e) { + LOGGER.error("Caught exception loading wired data", e); + } + }, Emulator.getThreading().getService()); + + // Wait for all parallel operations to complete + try { + CompletableFuture.allOf(rightsFuture, wordFilterFuture, botsFuture, petsFuture, wiredFuture).join(); + } catch (Exception e) { + LOGGER.error("Error waiting for parallel room data loading", e); + } + + this.cycleManager.resetIdleCycles(); + + this.roomCycleTask = Emulator.getThreading().getService() + .scheduleAtFixedRate(this, 500, 500, TimeUnit.MILLISECONDS); + } catch (Exception e) { + LOGGER.error("Caught exception during room load", e); } - public TIntObjectMap getCurrentBots() { - return this.currentBots; + this.traxManager = new TraxManager(this); + + if (this.jukeboxActive) { + this.traxManager.play(0); + for (HabboItem item : this.roomSpecialTypes.getItemsOfType(InteractionJukeBox.class)) { + item.setExtradata("1"); + this.updateItem(item); + } } - public TIntObjectMap getCurrentPets() { - return this.currentPets; + for (HabboItem item : this.roomSpecialTypes.getItemsOfType(InteractionFireworks.class)) { + item.setExtradata("1"); + this.updateItem(item); + } + + // Set loaded flag with lock + synchronized (this.loadLock) { + this.loaded = true; + this.loadingInProgress = false; + this.loadingFuture = null; } - public THashSet getWordFilterWords() { - return this.wordFilterWords; + Emulator.getPluginManager().fireEvent(new RoomLoadedEvent(this)); + } + + private synchronized void loadLayout() { + if (this.layout == null) { + if (this.overrideModel) { + this.layout = Emulator.getGameEnvironment().getRoomManager().loadCustomLayout(this); + } else { + this.layout = Emulator.getGameEnvironment().getRoomManager() + .loadLayout(this.layoutName, this); + } } + } - public RoomSpecialTypes getRoomSpecialTypes() { - return this.roomSpecialTypes; + private synchronized void loadHeightmap() { + if (this.layout != null) { + for (short x = 0; x < this.layout.getMapSizeX(); x++) { + for (short y = 0; y < this.layout.getMapSizeY(); y++) { + RoomTile tile = this.layout.getTile(x, y); + if (tile != null) { + this.updateTile(tile); + } + } + } + } else { + LOGGER.error("Unknown Room Layout for Room (ID: {})", this.id); } + } - public boolean isPreLoaded() { - return this.preLoaded; - } + private synchronized void loadItems(Connection connection) { + this.itemManager.loadItems(connection); + } - public boolean isLoaded() { - return this.loaded; - } + private synchronized void loadWiredData(Connection connection) { + this.itemManager.loadWiredData(connection); + } - public void setNeedsUpdate(boolean needsUpdate) { - this.needsUpdate = needsUpdate; - } + private synchronized void loadBots(Connection connection) { + this.unitManager.clearBots(); - public TIntArrayList getRights() { - return this.rights; - } - - public boolean isMuted() { - return this.muted; - } - - public void setMuted(boolean muted) { - this.muted = muted; - } - - public TraxManager getTraxManager() { - return this.traxManager; - } - - public void addHabboItem(HabboItem item) { - if (item == null) - return; - - synchronized (this.roomItems) { - try { - this.roomItems.put(item.getId(), item); - } catch (Exception e) { + try (PreparedStatement statement = connection.prepareStatement( + "SELECT users.username AS owner_name, bots.* FROM bots INNER JOIN users ON bots.user_id = users.id WHERE room_id = ?")) { + statement.setInt(1, this.id); + try (ResultSet set = statement.executeQuery()) { + while (set.next()) { + Bot b = Emulator.getGameEnvironment().getBotManager().loadBot(set); + if (b != null) { + b.setRoom(this); + b.setRoomUnit(new RoomUnit()); + b.getRoomUnit().setPathFinderRoom(this); + b.getRoomUnit() + .setLocation(this.layout.getTile((short) set.getInt("x"), (short) set.getInt("y"))); + if (b.getRoomUnit().getCurrentLocation() == null) { + b.getRoomUnit().setLocation(this.getLayout().getDoorTile()); + b.getRoomUnit() + .setRotation(RoomUserRotation.fromValue(this.getLayout().getDoorDirection())); + } else { + b.getRoomUnit().setZ(set.getDouble("z")); + b.getRoomUnit().setPreviousLocationZ(set.getDouble("z")); + b.getRoomUnit().setRotation(RoomUserRotation.values()[set.getInt("rot")]); } + b.getRoomUnit().setRoomUnitType(RoomUnitType.BOT); + b.getRoomUnit().setDanceType(DanceType.values()[set.getInt("dance")]); + //b.getRoomUnit().setCanWalk(set.getBoolean("freeroam")); + b.getRoomUnit().setInRoom(true); + this.giveEffect(b.getRoomUnit(), set.getInt("effect"), Integer.MAX_VALUE); + this.addBot(b); + } } - - synchronized (this.furniOwnerCount) { - this.furniOwnerCount.put(item.getUserId(), this.furniOwnerCount.get(item.getUserId()) + 1); - } - - synchronized (this.furniOwnerNames) { - if (!this.furniOwnerNames.containsKey(item.getUserId())) { - HabboInfo habbo = HabboManager.getOfflineHabboInfo(item.getUserId()); - - if (habbo != null) { - this.furniOwnerNames.put(item.getUserId(), habbo.getUsername()); - } else { - LOGGER.error("Failed to find username for item (ID: {}, UserID: {})", item.getId(), item.getUserId()); - } - } - } - - //TODO: Move this list - synchronized (this.roomSpecialTypes) { - if (item instanceof ICycleable) { - this.roomSpecialTypes.addCycleTask((ICycleable) item); - } - - if (item instanceof InteractionWiredTrigger) { - this.roomSpecialTypes.addTrigger((InteractionWiredTrigger) item); - } else if (item instanceof InteractionWiredEffect) { - this.roomSpecialTypes.addEffect((InteractionWiredEffect) item); - } else if (item instanceof InteractionWiredCondition) { - this.roomSpecialTypes.addCondition((InteractionWiredCondition) item); - } else if (item instanceof InteractionWiredExtra) { - this.roomSpecialTypes.addExtra((InteractionWiredExtra) item); - } else if (item instanceof InteractionBattleBanzaiTeleporter) { - this.roomSpecialTypes.addBanzaiTeleporter((InteractionBattleBanzaiTeleporter) item); - } else if (item instanceof InteractionRoller) { - this.roomSpecialTypes.addRoller((InteractionRoller) item); - } else if (item instanceof InteractionGameScoreboard) { - this.roomSpecialTypes.addGameScoreboard((InteractionGameScoreboard) item); - } else if (item instanceof InteractionGameGate) { - this.roomSpecialTypes.addGameGate((InteractionGameGate) item); - } else if (item instanceof InteractionGameTimer) { - this.roomSpecialTypes.addGameTimer((InteractionGameTimer) item); - } else if (item instanceof InteractionFreezeExitTile) { - this.roomSpecialTypes.addFreezeExitTile((InteractionFreezeExitTile) item); - } else if (item instanceof InteractionNest) { - this.roomSpecialTypes.addNest((InteractionNest) item); - } else if (item instanceof InteractionPetDrink) { - this.roomSpecialTypes.addPetDrink((InteractionPetDrink) item); - } else if (item instanceof InteractionPetFood) { - this.roomSpecialTypes.addPetFood((InteractionPetFood) item); - } else if (item instanceof InteractionMoodLight) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionPyramid) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionMusicDisc) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionBattleBanzaiSphere) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionTalkingFurniture) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionWater) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionWaterItem) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionMuteArea) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionBuildArea) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionTagPole) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionTagField) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionJukeBox) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionPetBreedingNest) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionBlackHole) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionWiredHighscore) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionStickyPole) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof WiredBlob) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionTent) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionSnowboardSlope) { - this.roomSpecialTypes.addUndefined(item); - } else if (item instanceof InteractionFireworks) { - this.roomSpecialTypes.addUndefined(item); - } - - } + } + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); } - - public HabboItem getHabboItem(int id) { - if (this.roomItems == null || this.roomSpecialTypes == null) - return null; // room not loaded completely - - HabboItem item; - synchronized (this.roomItems) { - item = this.roomItems.get(id); - } - - if (item == null) - item = this.roomSpecialTypes.getBanzaiTeleporter(id); - - if (item == null) - item = this.roomSpecialTypes.getTrigger(id); - - if (item == null) - item = this.roomSpecialTypes.getEffect(id); - - if (item == null) - item = this.roomSpecialTypes.getCondition(id); - - if (item == null) - item = this.roomSpecialTypes.getGameGate(id); - - if (item == null) - item = this.roomSpecialTypes.getGameScorebord(id); - - if (item == null) - item = this.roomSpecialTypes.getGameTimer(id); - - if (item == null) - item = this.roomSpecialTypes.getFreezeExitTiles().get(id); - - if (item == null) - item = this.roomSpecialTypes.getRoller(id); - - if (item == null) - item = this.roomSpecialTypes.getNest(id); - - if (item == null) - item = this.roomSpecialTypes.getPetDrink(id); - - if (item == null) - item = this.roomSpecialTypes.getPetFood(id); - - return item; - } - - void removeHabboItem(int id) { - this.removeHabboItem(this.getHabboItem(id)); - } - - - public void removeHabboItem(HabboItem item) { - if (item != null) { - - HabboItem i; - synchronized (this.roomItems) { - i = this.roomItems.remove(item.getId()); - } - - if (i != null) { - synchronized (this.furniOwnerCount) { - synchronized (this.furniOwnerNames) { - int count = this.furniOwnerCount.get(i.getUserId()); - - if (count > 1) - this.furniOwnerCount.put(i.getUserId(), count - 1); - else { - this.furniOwnerCount.remove(i.getUserId()); - this.furniOwnerNames.remove(i.getUserId()); - } - } - } - - if (item instanceof ICycleable) { - this.roomSpecialTypes.removeCycleTask((ICycleable) item); - } - - if (item instanceof InteractionBattleBanzaiTeleporter) { - this.roomSpecialTypes.removeBanzaiTeleporter((InteractionBattleBanzaiTeleporter) item); - } else if (item instanceof InteractionWiredTrigger) { - this.roomSpecialTypes.removeTrigger((InteractionWiredTrigger) item); - } else if (item instanceof InteractionWiredEffect) { - this.roomSpecialTypes.removeEffect((InteractionWiredEffect) item); - } else if (item instanceof InteractionWiredCondition) { - this.roomSpecialTypes.removeCondition((InteractionWiredCondition) item); - } else if (item instanceof InteractionWiredExtra) { - this.roomSpecialTypes.removeExtra((InteractionWiredExtra) item); - } else if (item instanceof InteractionRoller) { - this.roomSpecialTypes.removeRoller((InteractionRoller) item); - } else if (item instanceof InteractionGameScoreboard) { - this.roomSpecialTypes.removeScoreboard((InteractionGameScoreboard) item); - } else if (item instanceof InteractionGameGate) { - this.roomSpecialTypes.removeGameGate((InteractionGameGate) item); - } else if (item instanceof InteractionGameTimer) { - this.roomSpecialTypes.removeGameTimer((InteractionGameTimer) item); - } else if (item instanceof InteractionFreezeExitTile) { - this.roomSpecialTypes.removeFreezeExitTile((InteractionFreezeExitTile) item); - } else if (item instanceof InteractionNest) { - this.roomSpecialTypes.removeNest((InteractionNest) item); - } else if (item instanceof InteractionPetDrink) { - this.roomSpecialTypes.removePetDrink((InteractionPetDrink) item); - } else if (item instanceof InteractionPetFood) { - this.roomSpecialTypes.removePetFood((InteractionPetFood) item); - } else if (item instanceof InteractionMoodLight) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof InteractionPyramid) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof InteractionMusicDisc) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof InteractionBattleBanzaiSphere) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof InteractionTalkingFurniture) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof InteractionWaterItem) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof InteractionWater) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof InteractionMuteArea) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof InteractionTagPole) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof InteractionTagField) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof InteractionJukeBox) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof InteractionPetBreedingNest) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof InteractionBlackHole) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof InteractionWiredHighscore) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof InteractionStickyPole) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof WiredBlob) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof InteractionTent) { - this.roomSpecialTypes.removeUndefined(item); - } else if (item instanceof InteractionSnowboardSlope) { - this.roomSpecialTypes.removeUndefined(item); - } - } - } - } - - public THashSet getFloorItems() { - THashSet items = new THashSet<>(); - TIntObjectIterator iterator = this.roomItems.iterator(); - - for (int i = this.roomItems.size(); i-- > 0; ) { - try { - iterator.advance(); - } catch (Exception e) { - break; - } - - if (iterator.value().getBaseItem().getType() == FurnitureType.FLOOR) - items.add(iterator.value()); - - } - - - return items; - - } - - public THashSet getWallItems() { - THashSet items = new THashSet<>(); - TIntObjectIterator iterator = this.roomItems.iterator(); - - for (int i = this.roomItems.size(); i-- > 0; ) { - try { - iterator.advance(); - } catch (Exception e) { - break; - } - - if (iterator.value().getBaseItem().getType() == FurnitureType.WALL) - items.add(iterator.value()); - } - - return items; - - } - - public THashSet getPostItNotes() { - THashSet items = new THashSet<>(); - TIntObjectIterator iterator = this.roomItems.iterator(); - - for (int i = this.roomItems.size(); i-- > 0; ) { - try { - iterator.advance(); - } catch (Exception e) { - break; - } - - if (iterator.value().getBaseItem().getInteractionType().getType() == InteractionPostIt.class) - items.add(iterator.value()); - } - - return items; - - } - - public void addHabbo(Habbo habbo) { - synchronized (this.roomUnitLock) { - habbo.getRoomUnit().setId(this.unitCounter); - this.currentHabbos.put(habbo.getHabboInfo().getId(), habbo); - this.unitCounter++; - this.updateDatabaseUserCount(); - } - } - - public void kickHabbo(Habbo habbo, boolean alert) { - if (alert) { - habbo.getClient().sendResponse(new GenericErrorMessagesComposer(GenericErrorMessagesComposer.KICKED_OUT_OF_THE_ROOM)); - } - - habbo.getRoomUnit().isKicked = true; - habbo.getRoomUnit().setGoalLocation(this.layout.getDoorTile()); - - if (habbo.getRoomUnit().getPath() == null || habbo.getRoomUnit().getPath().size() <= 1 || this.isPublicRoom()) { - habbo.getRoomUnit().setCanWalk(true); - Emulator.getGameEnvironment().getRoomManager().leaveRoom(habbo, this); - } - } - - public void removeHabbo(Habbo habbo) { - removeHabbo(habbo, false); - } - - public void removeHabbo(Habbo habbo, boolean sendRemovePacket) { - if (habbo.getRoomUnit() != null && habbo.getRoomUnit().getCurrentLocation() != null) { - habbo.getRoomUnit().getCurrentLocation().removeUnit(habbo.getRoomUnit()); - } - - synchronized (this.roomUnitLock) { - this.currentHabbos.remove(habbo.getHabboInfo().getId()); - } - - if (sendRemovePacket && habbo.getRoomUnit() != null && !habbo.getRoomUnit().isTeleporting) { - this.sendComposer(new RoomUserRemoveComposer(habbo.getRoomUnit()).compose()); - } - - if (habbo.getRoomUnit().getCurrentLocation() != null) { - HabboItem item = this.getTopItemAt(habbo.getRoomUnit().getX(), habbo.getRoomUnit().getY()); - - if (item != null) { - try { - item.onWalkOff(habbo.getRoomUnit(), this, new Object[]{}); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } - } - } - - if (habbo.getHabboInfo().getCurrentGame() != null) { - if (this.getGame(habbo.getHabboInfo().getCurrentGame()) != null) { - this.getGame(habbo.getHabboInfo().getCurrentGame()).removeHabbo(habbo); - } - } - - RoomTrade trade = this.getActiveTradeForHabbo(habbo); - - if (trade != null) { - trade.stopTrade(habbo); - } - - if (habbo.getHabboInfo().getId() != this.ownerId) { - this.pickupPetsForHabbo(habbo); - } - - this.updateDatabaseUserCount(); - } - - public void addBot(Bot bot) { - synchronized (this.roomUnitLock) { - bot.getRoomUnit().setId(this.unitCounter); - this.currentBots.put(bot.getId(), bot); - this.unitCounter++; - } - } - - public void addPet(Pet pet) { - synchronized (this.roomUnitLock) { - pet.getRoomUnit().setId(this.unitCounter); - this.currentPets.put(pet.getId(), pet); - this.unitCounter++; - - Habbo habbo = this.getHabbo(pet.getUserId()); - if (habbo != null) { - this.furniOwnerNames.put(pet.getUserId(), this.getHabbo(pet.getUserId()).getHabboInfo().getUsername()); - } - } - } - - public Bot getBot(int botId) { - return this.currentBots.get(botId); - } - - public Bot getBot(RoomUnit roomUnit) { - synchronized (this.currentBots) { - TIntObjectIterator iterator = this.currentBots.iterator(); - - for (int i = this.currentBots.size(); i-- > 0; ) { - try { - iterator.advance(); - } catch (NoSuchElementException e) { - LOGGER.error("Caught exception", e); - break; - } - - if (iterator.value().getRoomUnit() == roomUnit) - return iterator.value(); - } - } - - return null; - } - - public Bot getBotByRoomUnitId(int id) { - synchronized (this.currentBots) { - TIntObjectIterator iterator = this.currentBots.iterator(); - - for (int i = this.currentBots.size(); i-- > 0; ) { - try { - iterator.advance(); - } catch (NoSuchElementException e) { - LOGGER.error("Caught exception", e); - break; - } - - if (iterator.value().getRoomUnit().getId() == id) - return iterator.value(); - } - } - - return null; - } - - public List getBots(String name) { - List bots = new ArrayList<>(); - - synchronized (this.currentBots) { - TIntObjectIterator iterator = this.currentBots.iterator(); - - for (int i = this.currentBots.size(); i-- > 0; ) { - try { - iterator.advance(); - } catch (NoSuchElementException e) { - LOGGER.error("Caught exception", e); - break; - } - - if (iterator.value().getName().equalsIgnoreCase(name)) - bots.add(iterator.value()); - } - } - - return bots; - } - - public boolean hasBotsAt(final int x, final int y) { - final boolean[] result = {false}; - - synchronized (this.currentBots) { - this.currentBots.forEachValue(new TObjectProcedure() { - @Override - public boolean execute(Bot object) { - if (object.getRoomUnit().getX() == x && object.getRoomUnit().getY() == y) { - result[0] = true; - return false; - } - - return true; - } - }); - } - - return result[0]; - } - - public Pet getPet(int petId) { - return this.currentPets.get(petId); - } - - public Pet getPet(RoomUnit roomUnit) { - TIntObjectIterator petIterator = this.currentPets.iterator(); - - for (int i = this.currentPets.size(); i-- > 0; ) { - try { - petIterator.advance(); - } catch (NoSuchElementException e) { - LOGGER.error("Caught exception", e); - break; - } - - if (petIterator.value().getRoomUnit() == roomUnit) - return petIterator.value(); - } - - return null; - } - - public boolean removeBot(Bot bot) { - synchronized (this.currentBots) { - if (this.currentBots.containsKey(bot.getId())) { - if (bot.getRoomUnit() != null && bot.getRoomUnit().getCurrentLocation() != null) { - bot.getRoomUnit().getCurrentLocation().removeUnit(bot.getRoomUnit()); - } - - this.currentBots.remove(bot.getId()); - bot.getRoomUnit().setInRoom(false); - bot.setRoom(null); - this.sendComposer(new RoomUserRemoveComposer(bot.getRoomUnit()).compose()); - bot.setRoomUnit(null); - return true; - } - } - - return false; - } - - public void placePet(Pet pet, short x, short y, double z, int rot) { - synchronized (this.currentPets) { - RoomTile tile = this.layout.getTile(x, y); - - if (tile == null) { - tile = this.layout.getDoorTile(); - } - - pet.setRoomUnit(new RoomUnit()); + } + + private synchronized void loadPets(Connection connection) { + this.unitManager.clearPets(); + + try (PreparedStatement statement = connection.prepareStatement( + "SELECT users.username as pet_owner_name, users_pets.* FROM users_pets INNER JOIN users ON users_pets.user_id = users.id WHERE room_id = ?")) { + statement.setInt(1, this.id); + try (ResultSet set = statement.executeQuery()) { + while (set.next()) { + try { + Pet pet = PetManager.loadPet(set); pet.setRoom(this); - pet.getRoomUnit().setGoalLocation(tile); - pet.getRoomUnit().setLocation(tile); + pet.setRoomUnit(new RoomUnit()); + pet.getRoomUnit().setPathFinderRoom(this); + pet.getRoomUnit() + .setLocation(this.layout.getTile((short) set.getInt("x"), (short) set.getInt("y"))); + if (pet.getRoomUnit().getCurrentLocation() == null) { + pet.getRoomUnit().setLocation(this.getLayout().getDoorTile()); + pet.getRoomUnit() + .setRotation(RoomUserRotation.fromValue(this.getLayout().getDoorDirection())); + } else { + pet.getRoomUnit().setZ(set.getDouble("z")); + pet.getRoomUnit().setRotation(RoomUserRotation.values()[set.getInt("rot")]); + } pet.getRoomUnit().setRoomUnitType(RoomUnitType.PET); pet.getRoomUnit().setCanWalk(true); - pet.getRoomUnit().setPathFinderRoom(this); - pet.getRoomUnit().setPreviousLocationZ(z); - pet.getRoomUnit().setZ(z); - if (pet.getRoomUnit().getCurrentLocation() == null) { - pet.getRoomUnit().setLocation(this.getLayout().getDoorTile()); - pet.getRoomUnit().setRotation(RoomUserRotation.fromValue(this.getLayout().getDoorDirection())); - } - - pet.needsUpdate = true; - this.furniOwnerNames.put(pet.getUserId(), this.getHabbo(pet.getUserId()).getHabboInfo().getUsername()); this.addPet(pet); - this.sendComposer(new RoomPetComposer(pet).compose()); - } - } - public Pet removePet(int petId) { - return this.currentPets.remove(petId); - } - - public boolean hasHabbosAt(int x, int y) { - for (Habbo habbo : this.getHabbos()) { - if (habbo.getRoomUnit().getX() == x && habbo.getRoomUnit().getY() == y) - return true; - } - return false; - } - - public boolean hasPetsAt(int x, int y) { - synchronized (this.currentPets) { - TIntObjectIterator petIterator = this.currentPets.iterator(); - - for (int i = this.currentPets.size(); i-- > 0; ) { - try { - petIterator.advance(); - } catch (NoSuchElementException e) { - LOGGER.error("Caught exception", e); - break; - } - - if (petIterator.value().getRoomUnit().getX() == x && petIterator.value().getRoomUnit().getY() == y) - return true; - } - } - - return false; - } - - public THashSet getBotsAt(RoomTile tile) { - THashSet bots = new THashSet<>(); - synchronized (this.currentBots) { - TIntObjectIterator botIterator = this.currentBots.iterator(); - - for (int i = this.currentBots.size(); i-- > 0; ) { - try { - botIterator.advance(); - - if (botIterator.value().getRoomUnit().getCurrentLocation().equals(tile)) { - bots.add(botIterator.value()); - } - } catch (Exception e) { - break; - } - } - } - - return bots; - } - - public THashSet getPetsAt(RoomTile tile) { - THashSet pets = new THashSet<>(); - synchronized (this.currentPets) { - TIntObjectIterator petIterator = this.currentPets.iterator(); - - for (int i = this.currentPets.size(); i-- > 0; ) { - try { - petIterator.advance(); - - if (petIterator.value().getRoomUnit().getCurrentLocation().equals(tile)) { - pets.add(petIterator.value()); - } - } catch (Exception e) { - break; - } - } - } - - return pets; - } - - public THashSet getHabbosAt(short x, short y) { - return this.getHabbosAt(this.layout.getTile(x, y)); - } - - public THashSet getHabbosAt(RoomTile tile) { - THashSet habbos = new THashSet<>(); - - for (Habbo habbo : this.getHabbos()) { - if (habbo.getRoomUnit().getCurrentLocation().equals(tile)) - habbos.add(habbo); - } - - return habbos; - } - - public THashSet getHabbosAndBotsAt(short x, short y) { - return this.getHabbosAndBotsAt(this.layout.getTile(x, y)); - } - - public THashSet getHabbosAndBotsAt(RoomTile tile) { - THashSet list = new THashSet<>(); - - for (Bot bot : this.getBotsAt(tile)) { - list.add(bot.getRoomUnit()); - } - - for (Habbo habbo : this.getHabbosAt(tile)) { - list.add(habbo.getRoomUnit()); - } - - return list; - } - - public THashSet getHabbosOnItem(HabboItem item) { - THashSet habbos = new THashSet<>(); - for (short x = item.getX(); x < item.getX() + item.getBaseItem().getLength(); x++) { - for (short y = item.getY(); y < item.getY() + item.getBaseItem().getWidth(); y++) { - habbos.addAll(this.getHabbosAt(x, y)); - } - } - - return habbos; - } - - public THashSet getBotsOnItem(HabboItem item) { - THashSet bots = new THashSet<>(); - for (short x = item.getX(); x < item.getX() + item.getBaseItem().getLength(); x++) { - for (short y = item.getY(); y < item.getY() + item.getBaseItem().getWidth(); y++) { - bots.addAll(this.getBotsAt(this.getLayout().getTile(x, y))); - } - } - - return bots; - } - - public void teleportHabboToItem(Habbo habbo, HabboItem item) { - this.teleportRoomUnitToLocation(habbo.getRoomUnit(), item.getX(), item.getY(), item.getZ() + Item.getCurrentHeight(item)); - } - - public void teleportHabboToLocation(Habbo habbo, short x, short y) { - this.teleportRoomUnitToLocation(habbo.getRoomUnit(), x, y, 0.0); - } - - public void teleportRoomUnitToItem(RoomUnit roomUnit, HabboItem item) { - this.teleportRoomUnitToLocation(roomUnit, item.getX(), item.getY(), item.getZ() + Item.getCurrentHeight(item)); - } - - public void teleportRoomUnitToLocation(RoomUnit roomUnit, short x, short y) { - this.teleportRoomUnitToLocation(roomUnit, x, y, 0.0); - } - - public void teleportRoomUnitToLocation(RoomUnit roomUnit, short x, short y, double z) { - if (this.loaded) { - RoomTile tile = this.layout.getTile(x, y); - - if (z < tile.z) { - z = tile.z; - } - - roomUnit.setLocation(tile); - roomUnit.setGoalLocation(tile); - roomUnit.setZ(z); - roomUnit.setPreviousLocationZ(z); - this.updateRoomUnit(roomUnit); - - - } - } - - public void muteHabbo(Habbo habbo, int minutes) { - synchronized (this.mutedHabbos) { - this.mutedHabbos.put(habbo.getHabboInfo().getId(), Emulator.getIntUnixTimestamp() + (minutes * 60)); - } - } - - public boolean isMuted(Habbo habbo) { - if (this.isOwner(habbo) || this.hasRights(habbo)) - return false; - - if (this.mutedHabbos.containsKey(habbo.getHabboInfo().getId())) { - boolean time = this.mutedHabbos.get(habbo.getHabboInfo().getId()) > Emulator.getIntUnixTimestamp(); - - if (!time) { - this.mutedHabbos.remove(habbo.getHabboInfo().getId()); - } - - return time; - } - - return false; - } - - public void habboEntered(Habbo habbo) { - habbo.getRoomUnit().animateWalk = false; - - synchronized (this.currentBots) { - if (habbo.getHabboInfo().getId() != this.getOwnerId()) - return; - - TIntObjectIterator botIterator = this.currentBots.iterator(); - - for (int i = this.currentBots.size(); i-- > 0; ) { - try { - botIterator.advance(); - - if (botIterator.value() instanceof VisitorBot) { - ((VisitorBot) botIterator.value()).onUserEnter(habbo); - break; - } - } catch (Exception e) { - break; - } - } - } - - HabboItem doorTileTopItem = this.getTopItemAt(habbo.getRoomUnit().getX(), habbo.getRoomUnit().getY()); - if (doorTileTopItem != null && !(doorTileTopItem instanceof InteractionTeleportTile)) { - try { - doorTileTopItem.onWalkOn(habbo.getRoomUnit(), this, new Object[]{}); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } - } - } - - public void floodMuteHabbo(Habbo habbo, int timeOut) { - habbo.getHabboStats().mutedCount++; - timeOut += (timeOut * (int) Math.ceil(Math.pow(habbo.getHabboStats().mutedCount, 2))); - habbo.getHabboStats().chatCounter.set(0); - habbo.mute(timeOut, true); - } - - public void talk(Habbo habbo, RoomChatMessage roomChatMessage, RoomChatType chatType) { - this.talk(habbo, roomChatMessage, chatType, false); - } - - public void talk(final Habbo habbo, final RoomChatMessage roomChatMessage, RoomChatType chatType, boolean ignoreWired) { - if (!habbo.getHabboStats().allowTalk()) - return; - - if (habbo.getRoomUnit().isInvisible() && Emulator.getConfig().getBoolean("invisible.prevent.chat", false)) { - if (!CommandHandler.handleCommand(habbo.getClient(), roomChatMessage.getUnfilteredMessage())) { - habbo.whisper(Emulator.getTexts().getValue("invisible.prevent.chat.error")); - } - - return; - } - - if (habbo.getHabboInfo().getCurrentRoom() != this) - return; - - long millis = System.currentTimeMillis(); - if (HABBO_CHAT_DELAY) { - if (millis - habbo.getHabboStats().lastChat < 750) { - return; - } - } - habbo.getHabboStats().lastChat = millis; - if (roomChatMessage != null && Emulator.getConfig().getBoolean("easter_eggs.enabled") && roomChatMessage.getMessage().equalsIgnoreCase("i am a pirate")) { - habbo.getHabboStats().chatCounter.addAndGet(1); - Emulator.getThreading().run(new YouAreAPirate(habbo, this)); - return; - } - - UserIdleEvent event = new UserIdleEvent(habbo, UserIdleEvent.IdleReason.TALKED, false); - Emulator.getPluginManager().fireEvent(event); - - if (!event.isCancelled()) { - if (!event.idle) { - this.unIdle(habbo); - } - } - - this.sendComposer(new RoomUserTypingComposer(habbo.getRoomUnit(), false).compose()); - - if (roomChatMessage == null || roomChatMessage.getMessage() == null || roomChatMessage.getMessage().equals("")) - return; - - if (!habbo.hasPermission(Permission.ACC_NOMUTE) && (!MUTEAREA_CAN_WHISPER || chatType != RoomChatType.WHISPER)) { - for (HabboItem area : this.getRoomSpecialTypes().getItemsOfType(InteractionMuteArea.class)) { - if (((InteractionMuteArea) area).inSquare(habbo.getRoomUnit().getCurrentLocation())) { - return; - } - } - } - - if (!this.wordFilterWords.isEmpty()) { - if (!habbo.hasPermission(Permission.ACC_CHAT_NO_FILTER)) { - for (String string : this.wordFilterWords) { - roomChatMessage.setMessage(roomChatMessage.getMessage().replaceAll("(?i)" + Pattern.quote(string), "bobba")); - } - } - } - - if (!habbo.hasPermission(Permission.ACC_NOMUTE)) { - if (this.isMuted() && !this.hasRights(habbo)) { - return; - } - - if (this.isMuted(habbo)) { - habbo.getClient().sendResponse(new MutedWhisperComposer(this.mutedHabbos.get(habbo.getHabboInfo().getId()) - Emulator.getIntUnixTimestamp())); - return; - } - } - - if (!habbo.hasPermission(Permission.ACC_CHAT_NO_FLOOD)) { - final int chatCounter = habbo.getHabboStats().chatCounter.addAndGet(1); - - if (chatCounter > 3) { - final boolean floodRights = Emulator.getConfig().getBoolean("flood.with.rights"); - final boolean hasRights = this.hasRights(habbo); - - if (floodRights || !hasRights) { - this.floodMuteHabbo(habbo, muteTime); - return; - - /*if (this.chatProtection == 0) { - this.floodMuteHabbo(habbo, muteTime); - return; - } else if (this.chatProtection == 1 && chatCounter > 4) { - this.floodMuteHabbo(habbo, muteTime); - return; - } else if (this.chatProtection == 2 && chatCounter > 5) { - this.floodMuteHabbo(habbo, muteTime); - return; - }*/ - } - } - } - - if (chatType != RoomChatType.WHISPER) { - if (CommandHandler.handleCommand(habbo.getClient(), roomChatMessage.getUnfilteredMessage())) { - WiredHandler.handle(WiredTriggerType.SAY_COMMAND, habbo.getRoomUnit(), habbo.getHabboInfo().getCurrentRoom(), new Object[]{roomChatMessage.getMessage()}); - roomChatMessage.isCommand = true; - return; - } - - if (!ignoreWired) { - if (WiredHandler.handle(WiredTriggerType.SAY_SOMETHING, habbo.getRoomUnit(), habbo.getHabboInfo().getCurrentRoom(), new Object[]{roomChatMessage.getMessage()})) { - habbo.getClient().sendResponse(new RoomUserWhisperComposer(new RoomChatMessage(roomChatMessage.getMessage(), habbo, habbo, roomChatMessage.getBubble()))); - return; - } - } - } - - ServerMessage prefixMessage = null; - - if (Emulator.getPluginManager().isRegistered(UsernameTalkEvent.class, true)) { - UsernameTalkEvent usernameTalkEvent = Emulator.getPluginManager().fireEvent(new UsernameTalkEvent(habbo, roomChatMessage, chatType)); - if (usernameTalkEvent.hasCustomComposer()) { - prefixMessage = usernameTalkEvent.getCustomComposer(); - } - } - - if (prefixMessage == null) { - prefixMessage = roomChatMessage.getHabbo().getHabboInfo().getRank().hasPrefix() ? new RoomUserNameChangedComposer(habbo, true).compose() : null; - } - ServerMessage clearPrefixMessage = prefixMessage != null ? new RoomUserNameChangedComposer(habbo).compose() : null; - - Rectangle tentRectangle = this.roomSpecialTypes.tentAt(habbo.getRoomUnit().getCurrentLocation()); - - String trimmedMessage = roomChatMessage.getMessage().replaceAll("\\s+$", ""); - - if (trimmedMessage.isEmpty()) trimmedMessage = " "; - - roomChatMessage.setMessage(trimmedMessage); - - if (chatType == RoomChatType.WHISPER) { - if (roomChatMessage.getTargetHabbo() == null) { - return; - } - - RoomChatMessage staffChatMessage = new RoomChatMessage(roomChatMessage); - staffChatMessage.setMessage("To " + staffChatMessage.getTargetHabbo().getHabboInfo().getUsername() + ": " + staffChatMessage.getMessage()); - - final ServerMessage message = new RoomUserWhisperComposer(roomChatMessage).compose(); - final ServerMessage staffMessage = new RoomUserWhisperComposer(staffChatMessage).compose(); - - for (Habbo h : this.getHabbos()) { - if (h == roomChatMessage.getTargetHabbo() || h == habbo) { - if (!h.getHabboStats().userIgnored(habbo.getHabboInfo().getId())) { - if (prefixMessage != null) { - h.getClient().sendResponse(prefixMessage); - } - h.getClient().sendResponse(message); - - if (clearPrefixMessage != null) { - h.getClient().sendResponse(clearPrefixMessage); - } - } - - continue; - } - if (h.hasPermission(Permission.ACC_SEE_WHISPERS)) { - h.getClient().sendResponse(staffMessage); - } - } - } else if (chatType == RoomChatType.TALK) { - ServerMessage message = new RoomUserTalkComposer(roomChatMessage).compose(); - boolean noChatLimit = habbo.hasPermission(Permission.ACC_CHAT_NO_LIMIT); - - for (Habbo h : this.getHabbos()) { - if ((h.getRoomUnit().getCurrentLocation().distance(habbo.getRoomUnit().getCurrentLocation()) <= this.chatDistance || - h.equals(habbo) || - this.hasRights(h) || - noChatLimit) && (tentRectangle == null || RoomLayout.tileInSquare(tentRectangle, h.getRoomUnit().getCurrentLocation()))) { - if (!h.getHabboStats().userIgnored(habbo.getHabboInfo().getId())) { - if (prefixMessage != null && !h.getHabboStats().preferOldChat) { - h.getClient().sendResponse(prefixMessage); - } - h.getClient().sendResponse(message); - if (clearPrefixMessage != null && !h.getHabboStats().preferOldChat) { - h.getClient().sendResponse(clearPrefixMessage); - } - } - continue; - } - // Staff should be able to see the tent chat anyhow - showTentChatMessageOutsideTentIfPermitted(h, roomChatMessage, tentRectangle); - } - } else if (chatType == RoomChatType.SHOUT) { - ServerMessage message = new RoomUserShoutComposer(roomChatMessage).compose(); - - for (Habbo h : this.getHabbos()) { - // Show the message - // If the receiving Habbo has not ignored the sending Habbo - // AND the sending Habbo is NOT in a tent OR the receiving Habbo is in the same tent as the sending Habbo - if (!h.getHabboStats().userIgnored(habbo.getHabboInfo().getId()) && (tentRectangle == null || RoomLayout.tileInSquare(tentRectangle, h.getRoomUnit().getCurrentLocation()))) { - if (prefixMessage != null && !h.getHabboStats().preferOldChat) { - h.getClient().sendResponse(prefixMessage); - } - h.getClient().sendResponse(message); - if (clearPrefixMessage != null && !h.getHabboStats().preferOldChat) { - h.getClient().sendResponse(clearPrefixMessage); - } - continue; - } - // Staff should be able to see the tent chat anyhow, even when not in the same tent - showTentChatMessageOutsideTentIfPermitted(h, roomChatMessage, tentRectangle); - } - } - - if (chatType == RoomChatType.TALK || chatType == RoomChatType.SHOUT) { - synchronized (this.currentBots) { - TIntObjectIterator botIterator = this.currentBots.iterator(); - - for (int i = this.currentBots.size(); i-- > 0; ) { - try { - botIterator.advance(); - Bot bot = botIterator.value(); - bot.onUserSay(roomChatMessage); - - } catch (NoSuchElementException e) { - LOGGER.error("Caught exception", e); - break; - } - } - } - - if (roomChatMessage.getBubble().triggersTalkingFurniture()) { - THashSet items = this.roomSpecialTypes.getItemsOfType(InteractionTalkingFurniture.class); - - for (HabboItem item : items) { - if (this.layout.getTile(item.getX(), item.getY()).distance(habbo.getRoomUnit().getCurrentLocation()) <= Emulator.getConfig().getInt("furniture.talking.range")) { - int count = Emulator.getConfig().getInt(item.getBaseItem().getName() + ".message.count", 0); - - if (count > 0) { - int randomValue = Emulator.getRandom().nextInt(count + 1); - - RoomChatMessage itemMessage = new RoomChatMessage(Emulator.getTexts().getValue(item.getBaseItem().getName() + ".message." + randomValue, item.getBaseItem().getName() + ".message." + randomValue + " not found!"), habbo, RoomChatMessageBubbles.getBubble(Emulator.getConfig().getInt(item.getBaseItem().getName() + ".message.bubble", RoomChatMessageBubbles.PARROT.getType()))); - - this.sendComposer(new RoomUserTalkComposer(itemMessage).compose()); - - try { - item.onClick(habbo.getClient(), this, new Object[0]); - item.setExtradata("1"); - updateItemState(item); - - Emulator.getThreading().run(() -> { - item.setExtradata("0"); - updateItemState(item); - }, 2000); - - break; - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } - } - } - } - } - } - } - - /** - * Sends the given message to the receiving Habbo if the Habbo has the ACC_SEE_TENTCHAT permission and is not within the tent - * - * @param receivingHabbo The receiving Habbo - * @param roomChatMessage The message to receive - * @param tentRectangle The whole tent area from where the sending Habbo is saying something - */ - private void showTentChatMessageOutsideTentIfPermitted(Habbo receivingHabbo, RoomChatMessage roomChatMessage, Rectangle tentRectangle) { - if (receivingHabbo != null && receivingHabbo.hasPermission(Permission.ACC_SEE_TENTCHAT) && tentRectangle != null && !RoomLayout.tileInSquare(tentRectangle, receivingHabbo.getRoomUnit().getCurrentLocation())) { - RoomChatMessage staffChatMessage = new RoomChatMessage(roomChatMessage); - staffChatMessage.setMessage("[" + Emulator.getTexts().getValue("hotel.room.tent.prefix") + "] " + staffChatMessage.getMessage()); - final ServerMessage staffMessage = new RoomUserWhisperComposer(staffChatMessage).compose(); - receivingHabbo.getClient().sendResponse(staffMessage); - } - } - - public THashSet getLockedTiles() { - THashSet lockedTiles = new THashSet<>(); - - TIntObjectIterator iterator = this.roomItems.iterator(); - - for (int i = this.roomItems.size(); i-- > 0; ) { - HabboItem item; - try { - iterator.advance(); - item = iterator.value(); - } catch (Exception e) { - break; - } - - if (item.getBaseItem().getType() != FurnitureType.FLOOR) - continue; - - boolean found = false; - for (RoomTile tile : lockedTiles) { - if (tile.x == item.getX() && - tile.y == item.getY()) { - found = true; - break; - } - } - - if (!found) { - if (item.getRotation() == 0 || item.getRotation() == 4) { - for (short y = 0; y < item.getBaseItem().getLength(); y++) { - for (short x = 0; x < item.getBaseItem().getWidth(); x++) { - RoomTile tile = this.layout.getTile((short) (item.getX() + x), (short) (item.getY() + y)); - - if (tile != null) { - lockedTiles.add(tile); - } - } - } - } else { - for (short y = 0; y < item.getBaseItem().getWidth(); y++) { - for (short x = 0; x < item.getBaseItem().getLength(); x++) { - RoomTile tile = this.layout.getTile((short) (item.getX() + x), (short) (item.getY() + y)); - - if (tile != null) { - lockedTiles.add(tile); - } - } - } - } - } - } - - return lockedTiles; - } - - @Deprecated - public THashSet getItemsAt(int x, int y) { - RoomTile tile = this.getLayout().getTile((short) x, (short) y); - - if (tile != null) { - return this.getItemsAt(tile); - } - - return new THashSet<>(0); - } - - public THashSet getItemsAt(RoomTile tile) { - return getItemsAt(tile, false); - } - - public THashSet getItemsAt(RoomTile tile, boolean returnOnFirst) { - THashSet items = new THashSet<>(0); - - if (tile == null) - return items; - - if (this.loaded) { - THashSet cachedItems = this.tileCache.get(tile); - if (cachedItems != null) - return cachedItems; - } - - TIntObjectIterator iterator = this.roomItems.iterator(); - - for (int i = this.roomItems.size(); i-- > 0; ) { - HabboItem item; - try { - iterator.advance(); - item = iterator.value(); - } catch (Exception e) { - break; - } - - if (item == null) - continue; - - if (item.getBaseItem().getType() != FurnitureType.FLOOR) - continue; - - int width, length; - - if (item.getRotation() != 2 && item.getRotation() != 6) { - width = item.getBaseItem().getWidth() > 0 ? item.getBaseItem().getWidth() : 1; - length = item.getBaseItem().getLength() > 0 ? item.getBaseItem().getLength() : 1; - } else { - width = item.getBaseItem().getLength() > 0 ? item.getBaseItem().getLength() : 1; - length = item.getBaseItem().getWidth() > 0 ? item.getBaseItem().getWidth() : 1; - } - - if (!(tile.x >= item.getX() && tile.x <= item.getX() + width - 1 && tile.y >= item.getY() && tile.y <= item.getY() + length - 1)) - continue; - - items.add(item); - - if (returnOnFirst) { - return items; - } - } - - if (this.loaded) { - this.tileCache.put(tile, items); - } - - return items; - } - - public THashSet getItemsAt(int x, int y, double minZ) { - THashSet items = new THashSet<>(); - - for (HabboItem item : this.getItemsAt(x, y)) { - if (item.getZ() < minZ) - continue; - - items.add(item); - } - return items; - } - - public THashSet getItemsAt(Class type, int x, int y) { - THashSet items = new THashSet<>(); - - for (HabboItem item : this.getItemsAt(x, y)) { - if (!item.getClass().equals(type)) - continue; - - items.add(item); - } - return items; - } - - public boolean hasItemsAt(int x, int y) { - RoomTile tile = this.getLayout().getTile((short) x, (short) y); - - if (tile == null) - return false; - - return this.getItemsAt(tile, true).size() > 0; - } - - public HabboItem getTopItemAt(int x, int y) { - return this.getTopItemAt(x, y, null); - } - - public HabboItem getTopItemAt(int x, int y, HabboItem exclude) { - RoomTile tile = this.getLayout().getTile((short) x, (short) y); - - if (tile == null) - return null; - - HabboItem highestItem = null; - - for (HabboItem item : this.getItemsAt(x, y)) { - if (exclude != null && exclude == item) - continue; - - if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem) > item.getZ() + Item.getCurrentHeight(item)) - continue; - - highestItem = item; - } - - return highestItem; - } - - public HabboItem getTopItemAt(THashSet tiles, HabboItem exclude) { - HabboItem highestItem = null; - for (RoomTile tile : tiles) { - - if (tile == null) - continue; - - for (HabboItem item : this.getItemsAt(tile.x, tile.y)) { - if (exclude != null && exclude == item) - continue; - - if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem) > item.getZ() + Item.getCurrentHeight(item)) - continue; - - highestItem = item; - } - } - - return highestItem; - } - - public double getTopHeightAt(int x, int y) { - HabboItem item = this.getTopItemAt(x, y); - - if (item != null) - return (item.getZ() + Item.getCurrentHeight(item) - (item.getBaseItem().allowSit() ? 1 : 0)); - else - return this.layout.getHeightAtSquare(x, y); - } - - @Deprecated - public HabboItem getLowestChair(int x, int y) { - if (this.layout == null) return null; - - RoomTile tile = this.layout.getTile((short) x, (short) y); - - if (tile != null) { - return this.getLowestChair(tile); - } - - return null; - } - - public HabboItem getLowestChair(RoomTile tile) { - HabboItem lowestChair = null; - - THashSet items = this.getItemsAt(tile); - if (items != null && !items.isEmpty()) { - for (HabboItem item : items) { - - if (!item.getBaseItem().allowSit()) - continue; - - if (lowestChair != null && lowestChair.getZ() < item.getZ()) - continue; - - lowestChair = item; - } - } - - return lowestChair; - } - - public HabboItem getTallestChair(RoomTile tile) { - HabboItem lowestChair = null; - - THashSet items = this.getItemsAt(tile); - if (items != null && !items.isEmpty()) { - for (HabboItem item : items) { - - if (!item.getBaseItem().allowSit()) - continue; - - if (lowestChair != null && lowestChair.getZ() + Item.getCurrentHeight(lowestChair) > item.getZ() + Item.getCurrentHeight(item)) - continue; - - lowestChair = item; - } - } - - return lowestChair; - } - - public double getStackHeight(short x, short y, boolean calculateHeightmap, HabboItem exclude) { - - if (x < 0 || y < 0 || this.layout == null) - return calculateHeightmap ? Short.MAX_VALUE : 0.0; - - if (Emulator.getPluginManager().isRegistered(FurnitureStackHeightEvent.class, true)) { - FurnitureStackHeightEvent event = Emulator.getPluginManager().fireEvent(new FurnitureStackHeightEvent(x, y, this)); - if (event.hasPluginHelper()) { - return calculateHeightmap ? event.getHeight() * 256.0D : event.getHeight(); - } - } - - double height = this.layout.getHeightAtSquare(x, y); - boolean canStack = true; - - THashSet stackHelpers = this.getItemsAt(InteractionStackHelper.class, x, y); - stackHelpers.addAll(this.getItemsAt(InteractionTileWalkMagic.class, x, y)); - - if (stackHelpers.size() > 0) { - for (HabboItem item : stackHelpers) { - if (item == exclude) continue; - return calculateHeightmap ? item.getZ() * 256.0D : item.getZ(); - } - } - - HabboItem item = this.getTopItemAt(x, y, exclude); - if (item != null) { - canStack = item.getBaseItem().allowStack(); - height = item.getZ() + (item.getBaseItem().allowSit() ? 0 : Item.getCurrentHeight(item)); - } - - /*HabboItem lowestChair = this.getLowestChair(x, y); - if (lowestChair != null && lowestChair != exclude) { - canStack = true; - height = lowestChair.getZ(); - }*/ - - if (calculateHeightmap) { - return (canStack ? height * 256.0D : Short.MAX_VALUE); - } - - return canStack ? height : -1; - } - - public double getStackHeight(short x, short y, boolean calculateHeightmap) { - return this.getStackHeight(x, y, calculateHeightmap, null); - } - - public boolean hasObjectTypeAt(Class type, int x, int y) { - THashSet items = this.getItemsAt(x, y); - - for (HabboItem item : items) { - if (item.getClass() == type) { - return true; - } - } - - return false; - } - - public boolean canSitOrLayAt(int x, int y) { - if (this.hasHabbosAt(x, y)) - return false; - - THashSet items = this.getItemsAt(x, y); - - return this.canSitAt(items) || this.canLayAt(items); - } - - public boolean canSitAt(int x, int y) { - if (this.hasHabbosAt(x, y)) - return false; - - return this.canSitAt(this.getItemsAt(x, y)); - } - - boolean canWalkAt(RoomTile roomTile) { - if (roomTile == null) { - return false; - } - - if (roomTile.state == RoomTileState.INVALID) - return false; - - HabboItem topItem = null; - boolean canWalk = true; - THashSet items = this.getItemsAt(roomTile); - if (items != null) { - for (HabboItem item : items) { - if (topItem == null) { - topItem = item; - } - - if (item.getZ() > topItem.getZ()) { - topItem = item; - canWalk = topItem.isWalkable() || topItem.getBaseItem().allowWalk(); - } else if (item.getZ() == topItem.getZ() && canWalk) { - if ((!topItem.isWalkable() && !topItem.getBaseItem().allowWalk()) || (!item.getBaseItem().allowWalk() && !item.isWalkable())) { - canWalk = false; - } - } - } - } - - return canWalk; - } - - boolean canSitAt(THashSet items) { - if (items == null) - return false; - - HabboItem tallestItem = null; - - for (HabboItem item : items) { - if (tallestItem != null && tallestItem.getZ() + Item.getCurrentHeight(tallestItem) > item.getZ() + Item.getCurrentHeight(item)) - continue; - - tallestItem = item; - } - - if (tallestItem == null) - return false; - - return tallestItem.getBaseItem().allowSit(); - } - - public boolean canLayAt(int x, int y) { - return this.canLayAt(this.getItemsAt(x, y)); - } - - boolean canLayAt(THashSet items) { - if (items == null || items.isEmpty()) - return true; - - HabboItem topItem = null; - - for (HabboItem item : items) { - if ((topItem == null || item.getZ() > topItem.getZ())) { - topItem = item; - } - } - - return (topItem == null || topItem.getBaseItem().allowLay()); - } - - public RoomTile getRandomWalkableTile() { - for (int i = 0; i < 10; i++) { - RoomTile tile = this.layout.getTile((short) (Math.random() * this.layout.getMapSizeX()), (short) (Math.random() * this.layout.getMapSizeY())); - if (tile != null && tile.getState() != RoomTileState.BLOCKED && tile.getState() != RoomTileState.INVALID) { - return tile; - } - } - - return null; - } - - public RoomTile getRandomWalkableTilesAround(RoomUnit roomUnit, RoomTile tile, int radius) { - if (!layout.tileExists(tile.x, tile.y)) { - tile = layout.getTile(roomUnit.getX(), roomUnit.getY()); - this.getBot(roomUnit).needsUpdate(true); - } - - List walkableTiles = new ArrayList<>(); - - int minX = Math.max(0, tile.x - radius); - int minY = Math.max(0, tile.y - radius); - int maxX = Math.min(this.getLayout().getMapSizeX() - 1, tile.x + radius); - int maxY = Math.min(this.getLayout().getMapSizeY() - 1, tile.y + radius); - - for (int x = minX; x <= maxX; x++) { - for (int y = minY; y <= maxY; y++) { - RoomTile candidateTile = this.getLayout().getTile((short) x, (short) y); - - if (candidateTile != null && candidateTile.getState() != RoomTileState.BLOCKED && candidateTile.getState() != RoomTileState.INVALID) { - walkableTiles.add(candidateTile); - } - } - } - - if (walkableTiles.isEmpty()) { - return tile; - } - - Collections.shuffle(walkableTiles); - return walkableTiles.get(0); - } - - public Habbo getHabbo(String username) { - for (Habbo habbo : this.getHabbos()) { - if (habbo.getHabboInfo().getUsername().equalsIgnoreCase(username)) - return habbo; - } - return null; - } - - public Habbo getHabbo(RoomUnit roomUnit) { - for (Habbo habbo : this.getHabbos()) { - if (habbo.getRoomUnit() == roomUnit) - return habbo; - } - return null; - } - - public Habbo getHabbo(int userId) { - return this.currentHabbos.get(userId); - } - - public Habbo getHabboByRoomUnitId(int roomUnitId) { - for (Habbo habbo : this.getHabbos()) { - if (habbo.getRoomUnit().getId() == roomUnitId) - return habbo; - } - - return null; - } - - public void sendComposer(ServerMessage message) { - for (Habbo habbo : this.getHabbos()) { - if (habbo.getClient() == null) continue; - - habbo.getClient().sendResponse(message); - } - } - - public void sendComposerToHabbosWithRights(ServerMessage message) { - for (Habbo habbo : this.getHabbos()) { - if (this.hasRights(habbo)) { - habbo.getClient().sendResponse(message); - } - } - } - - public void petChat(ServerMessage message) { - for (Habbo habbo : this.getHabbos()) { - if (!habbo.getHabboStats().ignorePets) - habbo.getClient().sendResponse(message); - } - } - - public void botChat(ServerMessage message) { - if (message == null) { - return; - } - - for (Habbo habbo : this.getHabbos()) { - if (habbo == null) { continue; } - if (!habbo.getHabboStats().ignoreBots) - habbo.getClient().sendResponse(message); - } - } - - private void loadRights(Connection connection) { - this.rights.clear(); - try (PreparedStatement statement = connection.prepareStatement("SELECT user_id FROM room_rights WHERE room_id = ?")) { - statement.setInt(1, this.id); - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - this.rights.add(set.getInt("user_id")); - } - } - } catch (SQLException e) { + this.getFurniOwnerNames().put(pet.getUserId(), set.getString("pet_owner_name")); + } catch (SQLException e) { LOGGER.error("Caught SQL exception", e); + } } + } + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + } + + private synchronized void loadWordFilter(Connection connection) { + this.chatManager.loadWordFilter(connection); + } + + public void updateTile(RoomTile tile) { + this.tileManager.updateTile(tile); + } + + public void updateTiles(THashSet tiles) { + this.tileManager.updateTiles(tiles); + } + + public RoomTileState calculateTileState(RoomTile tile) { + return this.tileManager.calculateTileState(tile); + } + + public RoomTileState calculateTileState(RoomTile tile, HabboItem exclude) { + return this.tileManager.calculateTileState(tile, exclude); + } + + public boolean tileWalkable(RoomTile t) { + return this.tileManager.tileWalkable(t); + } + + public boolean tileWalkable(short x, short y) { + return this.tileManager.tileWalkable(x, y); + } + + public void pickUpItem(HabboItem item, Habbo picker) { + if (item == null) { + return; } - private void loadBans(Connection connection) { - this.bannedHabbos.clear(); + if (Emulator.getPluginManager().isRegistered(FurniturePickedUpEvent.class, true)) { + Event furniturePickedUpEvent = new FurniturePickedUpEvent(item, picker); + Emulator.getPluginManager().fireEvent(furniturePickedUpEvent); - try (PreparedStatement statement = connection.prepareStatement("SELECT users.username, users.id, room_bans.* FROM room_bans INNER JOIN users ON room_bans.user_id = users.id WHERE ends > ? AND room_bans.room_id = ?")) { - statement.setInt(1, Emulator.getIntUnixTimestamp()); - statement.setInt(2, this.id); - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - if (this.bannedHabbos.containsKey(set.getInt("user_id"))) - continue; + if (furniturePickedUpEvent.isCancelled()) { + return; + } + } - this.bannedHabbos.put(set.getInt("user_id"), new RoomBan(set)); - } + this.removeHabboItem(item.getId()); + item.onPickUp(this); + item.setRoomId(0); + item.needsUpdate(true); + + if (item.getBaseItem().getType() == FurnitureType.FLOOR) { + this.sendComposer(new RemoveFloorItemComposer(item).compose()); + + THashSet updatedTiles = new THashSet<>(); + Rectangle rectangle = RoomLayout.getRectangle(item.getX(), item.getY(), + item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); + + for (short x = (short) rectangle.x; x < rectangle.x + rectangle.getWidth(); x++) { + for (short y = (short) rectangle.y; y < rectangle.y + rectangle.getHeight(); y++) { + double stackHeight = this.getStackHeight(x, y, false); + RoomTile tile = this.layout.getTile(x, y); + + if (tile != null) { + tile.setStackHeight(stackHeight); + updatedTiles.add(tile); + } + } + } + this.sendComposer(new UpdateStackHeightComposer(this, updatedTiles).compose()); + this.updateTiles(updatedTiles); + for (RoomTile tile : updatedTiles) { + this.updateHabbosAt(tile.x, tile.y); + this.updateBotsAt(tile.x, tile.y); + } + } else if (item.getBaseItem().getType() == FurnitureType.WALL) { + this.sendComposer(new RemoveWallItemComposer(item).compose()); + } + + Habbo habbo = (picker != null && picker.getHabboInfo().getId() == item.getId() ? picker + : Emulator.getGameServer().getGameClientManager().getHabbo(item.getUserId())); + if (habbo != null) { + habbo.getInventory().getItemsComponent().addItem(item); + habbo.getClient().sendResponse(new AddHabboItemComposer(item)); + habbo.getClient().sendResponse(new InventoryRefreshComposer()); + } + Emulator.getThreading().run(item); + } + + public void updateHabbosAt(Rectangle rectangle) { + for (short i = (short) rectangle.x; i < rectangle.x + rectangle.width; i++) { + for (short j = (short) rectangle.y; j < rectangle.y + rectangle.height; j++) { + this.updateHabbosAt(i, j); + } + } + } + + public void updateHabbo(Habbo habbo) { + this.updateRoomUnit(habbo.getRoomUnit()); + } + + public void updateRoomUnit(RoomUnit roomUnit) { + HabboItem item = this.getTopItemAt(roomUnit.getX(), roomUnit.getY()); + + if ((item == null && !roomUnit.cmdSit) || (item != null && !item.getBaseItem().allowSit())) { + roomUnit.removeStatus(RoomUnitStatus.SIT); + } + + double oldZ = roomUnit.getZ(); + + if (item != null) { + if (item.getBaseItem().allowSit()) { + roomUnit.setZ(item.getZ()); + } else { + roomUnit.setZ(item.getZ() + Item.getCurrentHeight(item)); + } + + if (oldZ != roomUnit.getZ()) { + this.scheduledTasks.add(() -> { + try { + item.onWalkOn(roomUnit, Room.this, null); + } catch (Exception e) { + + } + }); + } + } + + this.sendComposer(new RoomUserStatusComposer(roomUnit).compose()); + } + + public void updateHabbosAt(short x, short y) { + this.unitManager.updateHabbosAt(x, y); + } + + public void updateHabbosAt(short x, short y, THashSet habbos) { + this.unitManager.updateHabbosAt(x, y, habbos); + } + + public void updateBotsAt(short x, short y) { + this.unitManager.updateBotsAt(x, y); + } + + public void updatePetsAt(short x, short y) { + this.unitManager.updatePetsAt(x, y); + } + + public void pickupPetsForHabbo(Habbo habbo) { + this.unitManager.pickupPetsForHabbo(habbo); + } + + public void startTrade(Habbo userOne, Habbo userTwo) { + this.tradeManager.startTrade(userOne, userTwo); + } + + public void stopTrade(RoomTrade trade) { + this.tradeManager.stopTrade(trade); + } + + public RoomTrade getActiveTradeForHabbo(Habbo user) { + return this.tradeManager.getActiveTradeForHabbo(user); + } + + public synchronized void dispose() { + synchronized (this.loadLock) { + if (this.preventUnloading) { + return; + } + + if (Emulator.getPluginManager().fireEvent(new RoomUnloadingEvent(this)).isCancelled()) { + return; + } + + if (this.loaded) { + try { + + if (this.traxManager != null && !this.traxManager.disposed()) { + this.traxManager.dispose(); + } + + this.roomCycleTask.cancel(false); + this.scheduledTasks.clear(); + this.scheduledComposers.clear(); + this.loaded = false; + + this.tileCache.clear(); + + synchronized (this.mutedHabbos) { + this.mutedHabbos.clear(); + } + + for (InteractionGameTimer timer : this.getRoomSpecialTypes().getGameTimers().values()) { + timer.setRunning(false); + } + + for (Game game : this.games) { + game.dispose(); + } + this.games.clear(); + + removeAllPets(ownerId); + + this.itemManager.saveAllPendingItems(); + + if (this.roomSpecialTypes != null) { + this.roomSpecialTypes.dispose(); + } + + // Unregister all wired tickables for this room from the tick service + com.eu.habbo.habbohotel.wired.core.WiredManager.unregisterRoomTickables(this); + + // Clear wired engine caches for this room + if (com.eu.habbo.habbohotel.wired.core.WiredManager.getStackIndex() != null) { + com.eu.habbo.habbohotel.wired.core.WiredManager.getStackIndex().invalidateAll(this); + } + if (com.eu.habbo.habbohotel.wired.core.WiredManager.getEngine() != null) { + com.eu.habbo.habbohotel.wired.core.WiredManager.getEngine().clearRoomRecursionDepth(this.id); + com.eu.habbo.habbohotel.wired.core.WiredManager.getEngine().clearRoomRateLimiters(this.id); + } + + this.itemManager.clear(); + + this.unitManager.clearQueue(); + + for (Habbo habbo : this.getCurrentHabbos().values()) { + Emulator.getGameEnvironment().getRoomManager().leaveRoom(habbo, this); + } + + this.sendComposer(new HotelViewComposer().compose()); + + // Save bots BEFORE clearing - must happen before unitManager.clear() + TIntObjectIterator botIterator = this.getCurrentBots().iterator(); + + for (int i = this.getCurrentBots().size(); i-- > 0; ) { + try { + botIterator.advance(); + botIterator.value().needsUpdate(true); + botIterator.value().run(); // Run synchronously to ensure DB is updated before room reload + } catch (NoSuchElementException e) { + LOGGER.error("Caught exception", e); + break; } - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); + } + + this.unitManager.clear(); + + this.unitManager.clearBots(); + this.unitManager.clearPets(); + } catch (Exception e) { + LOGGER.error("Caught exception", e); } + } + + try { + this.wordQuiz = ""; + this.yesVotes = 0; + this.noVotes = 0; + this.updateDatabaseUserCount(); + this.preLoaded = true; + this.layout = null; + } catch (Exception e) { + LOGGER.error("Caught exception", e); + } } - public RoomRightLevels getGuildRightLevel(Habbo habbo) { - if (this.guild > 0 && habbo.getHabboStats().hasGuild(this.guild)) { - Guild guild = Emulator.getGameEnvironment().getGuildManager().getGuild(this.guild); + Emulator.getPluginManager().fireEvent(new RoomUnloadedEvent(this)); + } - if (Emulator.getGameEnvironment().getGuildManager().getOnlyAdmins(guild).get(habbo.getHabboInfo().getId()) != null) - return RoomRightLevels.GUILD_ADMIN; + @Override + public int compareTo(Room o) { + if (o.getUserCount() != this.getUserCount()) { + return o.getCurrentHabbos().size() - this.getCurrentHabbos().size(); + } - if (guild.getRights()) { - return RoomRightLevels.GUILD_RIGHTS; + return this.id - o.id; + } + + @Override + public void serialize(ServerMessage message) { + message.appendInt(this.id); + message.appendString(this.name); + if (this.isPublicRoom()) { + message.appendInt(0); + message.appendString(""); + } else { + message.appendInt(this.ownerId); + message.appendString(this.ownerName); + } + message.appendInt(this.state.getState()); + message.appendInt(this.getUserCount()); + message.appendInt(this.usersMax); + message.appendString(this.description); + message.appendInt(0); + message.appendInt(this.score); + message.appendInt(0); + message.appendInt(this.category); + + String[] tags = Arrays.stream(this.tags.split(";")).filter(t -> !t.isEmpty()) + .toArray(String[]::new); + message.appendInt(tags.length); + for (String s : tags) { + message.appendString(s); + } + + int base = 0; + + if (this.getGuildId() > 0) { + base = base | 2; + } + + if (this.isPromoted()) { + base = base | 4; + } + + if (!this.isPublicRoom()) { + base = base | 8; + } + + message.appendInt(base); + + if (this.getGuildId() > 0) { + Guild g = Emulator.getGameEnvironment().getGuildManager().getGuild(this.getGuildId()); + if (g != null) { + message.appendInt(g.getId()); + message.appendString(g.getName()); + message.appendString(g.getBadge()); + } else { + message.appendInt(0); + message.appendString(""); + message.appendString(""); + } + } + + if (this.promoted) { + message.appendString(this.promotion.getTitle()); + message.appendString(this.promotion.getDescription()); + message.appendInt((this.promotion.getEndTimestamp() - Emulator.getIntUnixTimestamp()) / 60); + } + + } + + @Override + public void run() { + synchronized (this.loadLock) { + if (this.loaded) { + try { + // Run cycle directly instead of scheduling on thread pool + // This ensures all cycle tasks in the same tick execute synchronously + // preventing wired desync issues + this.cycle(); + } catch (Exception e) { + LOGGER.error("Caught exception", e); + } + } + } + + this.save(); + } + + public void save() { + if (this.needsUpdate) { + try (Connection connection = Emulator.getDatabase().getDataSource() + .getConnection(); PreparedStatement statement = connection.prepareStatement( + "UPDATE rooms SET name = ?, description = ?, password = ?, state = ?, users_max = ?, category = ?, score = ?, paper_floor = ?, paper_wall = ?, paper_landscape = ?, thickness_wall = ?, wall_height = ?, thickness_floor = ?, moodlight_data = ?, tags = ?, allow_other_pets = ?, allow_other_pets_eat = ?, allow_walkthrough = ?, allow_hidewall = ?, chat_mode = ?, chat_weight = ?, chat_speed = ?, chat_hearing_distance = ?, chat_protection =?, who_can_mute = ?, who_can_kick = ?, who_can_ban = ?, poll_id = ?, guild_id = ?, roller_speed = ?, override_model = ?, is_staff_picked = ?, promoted = ?, trade_mode = ?, move_diagonally = ?, owner_id = ?, owner_name = ?, jukebox_active = ?, hidewired = ? WHERE id = ?")) { + statement.setString(1, this.name); + statement.setString(2, this.description); + statement.setString(3, this.password); + statement.setString(4, this.state.name().toLowerCase()); + statement.setInt(5, this.usersMax); + statement.setInt(6, this.category); + statement.setInt(7, this.score); + statement.setString(8, this.floorPaint); + statement.setString(9, this.wallPaint); + statement.setString(10, this.backgroundPaint); + statement.setInt(11, this.wallSize); + statement.setInt(12, this.wallHeight); + statement.setInt(13, this.floorSize); + StringBuilder moodLightData = new StringBuilder(); + + int id = 1; + for (RoomMoodlightData data : this.moodlightData.valueCollection()) { + data.setId(id); + moodLightData.append(data.toString()).append(";"); + id++; + } + + statement.setString(14, moodLightData.toString()); + statement.setString(15, this.tags); + statement.setString(16, this.allowPets ? "1" : "0"); + statement.setString(17, this.allowPetsEat ? "1" : "0"); + statement.setString(18, this.allowWalkthrough ? "1" : "0"); + statement.setString(19, this.hideWall ? "1" : "0"); + statement.setInt(20, this.chatMode); + statement.setInt(21, this.chatWeight); + statement.setInt(22, this.chatSpeed); + statement.setInt(23, this.chatDistance); + statement.setInt(24, this.chatProtection); + statement.setInt(25, this.muteOption); + statement.setInt(26, this.kickOption); + statement.setInt(27, this.banOption); + statement.setInt(28, this.pollId); + statement.setInt(29, this.guild); + statement.setInt(30, this.rollerSpeed); + statement.setString(31, this.overrideModel ? "1" : "0"); + statement.setString(32, this.staffPromotedRoom ? "1" : "0"); + statement.setString(33, this.promoted ? "1" : "0"); + statement.setInt(34, this.tradeMode); + statement.setString(35, this.moveDiagonally ? "1" : "0"); + statement.setInt(36, this.ownerId); + statement.setString(37, this.ownerName); + statement.setString(38, this.jukeboxActive ? "1" : "0"); + statement.setString(39, this.hideWired ? "1" : "0"); + statement.setInt(40, this.id); + statement.executeUpdate(); + this.needsUpdate = false; + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + } + } + + /** + * Updates the user count in the database. + * Made public for access by RoomUnitManager. + */ + public void updateDatabaseUserCount() { + try (Connection connection = Emulator.getDatabase().getDataSource() + .getConnection(); PreparedStatement statement = connection.prepareStatement( + "UPDATE rooms SET users = ? WHERE id = ? LIMIT 1")) { + statement.setInt(1, this.getUserCount()); + statement.setInt(2, this.id); + statement.executeUpdate(); + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + } + + private void cycle() { + this.cycleManager.cycle(); + } + + public int getId() { + return this.id; + } + + public int getOwnerId() { + return this.ownerId; + } + + public void setOwnerId(int ownerId) { + this.ownerId = ownerId; + } + + public String getOwnerName() { + return this.ownerName; + } + + public void setOwnerName(String ownerName) { + this.ownerName = ownerName; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + + if (this.name.length() > 50) { + this.name = this.name.substring(0, 50); + } + + if (this.hasGuild()) { + Guild guild = Emulator.getGameEnvironment().getGuildManager().getGuild(this.guild); + + if (guild != null) { + guild.setRoomName(name); + } + } + } + + public String getDescription() { + return this.description; + } + + public void setDescription(String description) { + this.description = description; + + if (this.description.length() > 250) { + this.description = this.description.substring(0, 250); + } + } + + public RoomLayout getLayout() { + return this.layout; + } + + public void setLayout(RoomLayout layout) { + this.layout = layout; + } + + public boolean hasCustomLayout() { + return this.overrideModel; + } + + public void setHasCustomLayout(boolean overrideModel) { + this.overrideModel = overrideModel; + } + + public String getPassword() { + return this.password; + } + + public void setPassword(String password) { + this.password = password; + + if (this.password.length() > 20) { + this.password = this.password.substring(0, 20); + } + } + + public RoomState getState() { + return this.state; + } + + public void setState(RoomState state) { + this.state = state; + } + + public int getUsersMax() { + return this.usersMax; + } + + public void setUsersMax(int usersMax) { + this.usersMax = usersMax; + } + + public int getScore() { + return this.score; + } + + public void setScore(int score) { + this.score = score; + } + + public int getCategory() { + return this.category; + } + + public void setCategory(int category) { + this.category = category; + } + + public String getFloorPaint() { + return this.floorPaint; + } + + public void setFloorPaint(String floorPaint) { + this.floorPaint = floorPaint; + } + + public String getWallPaint() { + return this.wallPaint; + } + + public void setWallPaint(String wallPaint) { + this.wallPaint = wallPaint; + } + + public String getBackgroundPaint() { + return this.backgroundPaint; + } + + public void setBackgroundPaint(String backgroundPaint) { + this.backgroundPaint = backgroundPaint; + } + + public int getWallSize() { + return this.wallSize; + } + + public void setWallSize(int wallSize) { + this.wallSize = wallSize; + } + + public int getWallHeight() { + return this.wallHeight; + } + + public void setWallHeight(int wallHeight) { + this.wallHeight = wallHeight; + } + + public int getFloorSize() { + return this.floorSize; + } + + public void setFloorSize(int floorSize) { + this.floorSize = floorSize; + } + + public String getTags() { + return this.tags; + } + + public void setTags(String tags) { + this.tags = tags; + } + + public int getTradeMode() { + return this.tradeMode; + } + + public void setTradeMode(int tradeMode) { + this.tradeMode = tradeMode; + } + + public boolean moveDiagonally() { + return this.moveDiagonally; + } + + public void moveDiagonally(boolean moveDiagonally) { + this.moveDiagonally = moveDiagonally; + this.layout.moveDiagonally(this.moveDiagonally); + this.needsUpdate = true; + } + + public int getGuildId() { + return this.guild; + } + + public boolean hasGuild() { + return this.guild != 0; + } + + public void setGuild(int guild) { + this.guild = guild; + } + + public String getGuildName() { + if (this.hasGuild()) { + Guild guild = Emulator.getGameEnvironment().getGuildManager().getGuild(this.guild); + + if (guild != null) { + return guild.getName(); + } + } + + return ""; + } + + public boolean isPublicRoom() { + return this.publicRoom; + } + + public void setPublicRoom(boolean publicRoom) { + this.publicRoom = publicRoom; + } + + public boolean isStaffPromotedRoom() { + return this.staffPromotedRoom; + } + + public void setStaffPromotedRoom(boolean staffPromotedRoom) { + this.staffPromotedRoom = staffPromotedRoom; + } + + public boolean isAllowPets() { + return this.allowPets; + } + + public void setAllowPets(boolean allowPets) { + this.allowPets = allowPets; + if (!allowPets) { + removeAllPets(ownerId); + } + } + + public boolean isAllowPetsEat() { + return this.allowPetsEat; + } + + public void setAllowPetsEat(boolean allowPetsEat) { + this.allowPetsEat = allowPetsEat; + } + + public boolean isAllowWalkthrough() { + return this.allowWalkthrough; + } + + public void setAllowWalkthrough(boolean allowWalkthrough) { + this.allowWalkthrough = allowWalkthrough; + } + + public boolean isAllowBotsWalk() { + return this.allowBotsWalk; + } + + public void setAllowBotsWalk(boolean allowBotsWalk) { + this.allowBotsWalk = allowBotsWalk; + } + + public boolean isAllowEffects() { + return this.allowEffects; + } + + public void setAllowEffects(boolean allowEffects) { + this.allowEffects = allowEffects; + } + + public boolean isHideWall() { + return this.hideWall; + } + + public void setHideWall(boolean hideWall) { + this.hideWall = hideWall; + } + + public Color getBackgroundTonerColor() { + Color color = new Color(0, 0, 0); + TIntObjectMap items = this.itemManager.getRoomItems(); + TIntObjectIterator iterator = items.iterator(); + + for (int i = items.size(); i > 0; i--) { + try { + iterator.advance(); + HabboItem object = iterator.value(); + + if (object instanceof InteractionBackgroundToner) { + String[] extraData = object.getExtradata().split(":"); + + if (extraData.length == 4) { + if (extraData[0].equalsIgnoreCase("1")) { + return Color.getHSBColor(Integer.parseInt(extraData[1]), + Integer.parseInt(extraData[2]), Integer.parseInt(extraData[3])); } + } + } + } catch (Exception e) { + } + } + + return color; + } + + public int getChatMode() { + return this.chatMode; + } + + public void setChatMode(int chatMode) { + this.chatMode = chatMode; + } + + public int getChatWeight() { + return this.chatWeight; + } + + public void setChatWeight(int chatWeight) { + this.chatWeight = chatWeight; + } + + public int getChatSpeed() { + return this.chatSpeed; + } + + public void setChatSpeed(int chatSpeed) { + this.chatSpeed = chatSpeed; + } + + public int getChatDistance() { + return this.chatDistance; + } + + public void setChatDistance(int chatDistance) { + this.chatDistance = chatDistance; + } + + public void removeAllPets() { + this.unitManager.removeAllPets(); + } + + /** + * Removes all pets from the room except if the owner id is excludeUserId + * + * @param excludeUserId Habbo id to keep pets + */ + public void removeAllPets(int excludeUserId) { + this.unitManager.removeAllPets(excludeUserId); + } + + public int getChatProtection() { + return this.chatProtection; + } + + public void setChatProtection(int chatProtection) { + this.chatProtection = chatProtection; + } + + public int getMuteOption() { + return this.muteOption; + } + + public void setMuteOption(int muteOption) { + this.muteOption = muteOption; + } + + public int getKickOption() { + return this.kickOption; + } + + public void setKickOption(int kickOption) { + this.kickOption = kickOption; + } + + public int getBanOption() { + return this.banOption; + } + + public void setBanOption(int banOption) { + this.banOption = banOption; + } + + public int getPollId() { + return this.pollId; + } + + public void setPollId(int pollId) { + this.pollId = pollId; + } + + public int getRollerSpeed() { + return this.rollerSpeed; + } + + public void setRollerSpeed(int rollerSpeed) { + this.rollerSpeed = rollerSpeed; + this.needsUpdate = true; + } + + public String[] filterAnything() { + return new String[]{this.getOwnerName(), this.getGuildName(), this.getDescription(), + this.getPromotionDesc()}; + } + + public long getCycleTimestamp() { + return this.cycleManager.getCycleTimestamp(); + } + + public boolean isPromoted() { + return this.promotionManager.isPromoted(); + } + + public RoomPromotion getPromotion() { + return this.promotion; + } + + public String getPromotionDesc() { + if (this.promotion != null) { + return this.promotion.getDescription(); + } + + return ""; + } + + public void createPromotion(String title, String description, int category) { + this.promotionManager.createPromotion(title, description, category); + } + + public boolean addGame(Game game) { + return this.gameManager.addGame(game); + } + + public boolean deleteGame(Game game) { + return this.gameManager.deleteGame(game); + } + + public Game getGame(Class gameType) { + return this.gameManager.getGame(gameType); + } + + public Game getGameOrCreate(Class gameType) { + return this.gameManager.getGameOrCreate(gameType); + } + + public Set getGames() { + return this.gameManager.getGames(); + } + + public int getUserCount() { + return this.unitManager.getHabboCount(); + } + + public ConcurrentHashMap getCurrentHabbos() { + return this.unitManager.getCurrentHabbos(); + } + + public Collection getHabbos() { + return this.unitManager.getHabbos(); + } + + public TIntObjectMap getHabboQueue() { + return this.unitManager.getHabboQueue(); + } + + public TIntObjectMap getFurniOwnerNames() { + return this.itemManager.getFurniOwnerNames(); + } + + public String getFurniOwnerName(int userId) { + return this.itemManager.getFurniOwnerName(userId); + } + + public TIntIntMap getFurniOwnerCount() { + return this.itemManager.getFurniOwnerCount(); + } + + public TIntObjectMap getMoodlightData() { + return this.moodlightData; + } + + public int getLastTimerReset() { + return this.lastTimerReset; + } + + public void setLastTimerReset(int lastTimerReset) { + this.lastTimerReset = lastTimerReset; + } + + public void addToQueue(Habbo habbo) { + this.unitManager.addToQueue(habbo); + } + + public boolean removeFromQueue(Habbo habbo) { + try { + this.sendComposer(new HideDoorbellComposer(habbo.getHabboInfo().getUsername()).compose()); + + return this.unitManager.removeFromQueue(habbo.getHabboInfo().getId()) != null; + } catch (Exception e) { + LOGGER.error("Caught exception", e); + } + + return true; + } + + public TIntObjectMap getCurrentBots() { + return this.unitManager.getCurrentBots(); + } + + public TIntObjectMap getCurrentPets() { + return this.unitManager.getCurrentPets(); + } + + public THashSet getWordFilterWords() { + return this.chatManager.getWordFilterWords(); + } + + public RoomSpecialTypes getRoomSpecialTypes() { + return this.roomSpecialTypes; + } + + /** + * Alias for getRoomSpecialTypes() for shorter access. + */ + public RoomSpecialTypes getSpecialTypes() { + return this.roomSpecialTypes; + } + + public boolean isPreLoaded() { + return this.preLoaded; + } + + public boolean isLoaded() { + return this.loaded; + } + + public void setNeedsUpdate(boolean needsUpdate) { + this.needsUpdate = needsUpdate; + } + + public TIntArrayList getRights() { + return this.rights; + } + + public boolean isMuted() { + return this.muted; + } + + public void setMuted(boolean muted) { + this.muted = muted; + } + + public TraxManager getTraxManager() { + return this.traxManager; + } + + public void addHabboItem(HabboItem item) { + this.itemManager.addHabboItem(item); + } + + public HabboItem getHabboItem(int id) { + return this.itemManager.getHabboItem(id); + } + + void removeHabboItem(int id) { + this.itemManager.removeHabboItem(id); + } + + + public void removeHabboItem(HabboItem item) { + this.itemManager.removeHabboItem(item); + } + + public THashSet getFloorItems() { + return this.itemManager.getFloorItems(); + } + + public THashSet getWallItems() { + return this.itemManager.getWallItems(); + } + + public THashSet getPostItNotes() { + return this.itemManager.getPostItNotes(); + } + + public void addHabbo(Habbo habbo) { + this.unitManager.addHabbo(habbo); + } + + public void kickHabbo(Habbo habbo, boolean alert) { + this.unitManager.kickHabbo(habbo, alert); + } + + public void removeHabbo(Habbo habbo) { + this.unitManager.removeHabbo(habbo); + } + + public void removeHabbo(Habbo habbo, boolean sendRemovePacket) { + this.unitManager.removeHabbo(habbo, sendRemovePacket); + } + + public void addBot(Bot bot) { + this.unitManager.addBot(bot); + } + + public void addPet(Pet pet) { + this.unitManager.addPet(pet); + } + + public Bot getBot(int botId) { + return this.unitManager.getBot(botId); + } + + public Bot getBot(RoomUnit roomUnit) { + return this.unitManager.getBot(roomUnit); + } + + public Bot getBotByRoomUnitId(int id) { + return this.unitManager.getBotByRoomUnitId(id); + } + + public List getBots(String name) { + return this.unitManager.getBots(name); + } + + public boolean hasBotsAt(final int x, final int y) { + return this.unitManager.hasBotsAt(x, y); + } + + public Pet getPet(int petId) { + return this.unitManager.getPet(petId); + } + + public Pet getPet(RoomUnit roomUnit) { + return this.unitManager.getPet(roomUnit); + } + + public boolean removeBot(Bot bot) { + return this.unitManager.removeBot(bot); + } + + public void placePet(Pet pet, short x, short y, double z, int rot) { + this.unitManager.placePet(pet, x, y, z, rot); + } + + public Pet removePet(int petId) { + return this.unitManager.removePet(petId); + } + + public boolean hasHabbosAt(int x, int y) { + return this.unitManager.hasHabbosAt(x, y); + } + + public boolean hasPetsAt(int x, int y) { + return this.unitManager.hasPetsAt(x, y); + } + + public THashSet getBotsAt(RoomTile tile) { + return this.unitManager.getBotsAt(tile); + } + + public THashSet getPetsAt(RoomTile tile) { + return this.unitManager.getPetsAt(tile); + } + + public THashSet getHabbosAt(short x, short y) { + return this.unitManager.getHabbosAt(x, y); + } + + public THashSet getHabbosAt(RoomTile tile) { + return this.unitManager.getHabbosAt(tile); + } + + public THashSet getHabbosAndBotsAt(short x, short y) { + return this.unitManager.getHabbosAndBotsAt(x, y); + } + + public THashSet getHabbosAndBotsAt(RoomTile tile) { + return this.unitManager.getHabbosAndBotsAt(tile); + } + + public THashSet getHabbosOnItem(HabboItem item) { + return this.unitManager.getHabbosOnItem(item); + } + + public THashSet getBotsOnItem(HabboItem item) { + return this.unitManager.getBotsOnItem(item); + } + + public void teleportHabboToItem(Habbo habbo, HabboItem item) { + this.unitManager.teleportHabboToItem(habbo, item); + } + + public void teleportHabboToLocation(Habbo habbo, short x, short y) { + this.unitManager.teleportHabboToLocation(habbo, x, y); + } + + public void teleportRoomUnitToItem(RoomUnit roomUnit, HabboItem item) { + this.unitManager.teleportRoomUnitToItem(roomUnit, item); + } + + public void teleportRoomUnitToLocation(RoomUnit roomUnit, short x, short y) { + this.unitManager.teleportRoomUnitToLocation(roomUnit, x, y); + } + + public void teleportRoomUnitToLocation(RoomUnit roomUnit, short x, short y, double z) { + this.unitManager.teleportRoomUnitToLocation(roomUnit, x, y, z); + } + + public void muteHabbo(Habbo habbo, int minutes) { + this.rightsManager.muteHabbo(habbo, minutes); + } + + public boolean isMuted(Habbo habbo) { + return this.rightsManager.isMuted(habbo); + } + + public void habboEntered(Habbo habbo) { + this.unitManager.habboEntered(habbo); + } + + public void floodMuteHabbo(Habbo habbo, int timeOut) { + this.chatManager.floodMuteHabbo(habbo, timeOut); + } + + public void talk(Habbo habbo, RoomChatMessage roomChatMessage, RoomChatType chatType) { + this.chatManager.talk(habbo, roomChatMessage, chatType); + } + + public void talk(final Habbo habbo, final RoomChatMessage roomChatMessage, RoomChatType chatType, + boolean ignoreWired) { + this.chatManager.talk(habbo, roomChatMessage, chatType, ignoreWired); + } + + public THashSet getLockedTiles() { + return this.itemManager.getLockedTiles(); + } + + @Deprecated + public THashSet getItemsAt(int x, int y) { + return this.itemManager.getItemsAt(x, y); + } + + public THashSet getItemsAt(RoomTile tile) { + return this.itemManager.getItemsAt(tile); + } + + public THashSet getItemsAt(RoomTile tile, boolean returnOnFirst) { + return this.itemManager.getItemsAt(tile, returnOnFirst); + } + + public THashSet getItemsAt(int x, int y, double minZ) { + return this.itemManager.getItemsAt(x, y, minZ); + } + + public THashSet getItemsAt(Class type, int x, int y) { + return this.itemManager.getItemsAt(type, x, y); + } + + public boolean hasItemsAt(int x, int y) { + return this.itemManager.hasItemsAt(x, y); + } + + public HabboItem getTopItemAt(int x, int y) { + return this.itemManager.getTopItemAt(x, y); + } + + public HabboItem getTopItemAt(int x, int y, HabboItem exclude) { + return this.itemManager.getTopItemAt(x, y, exclude); + } + + public HabboItem getTopItemAt(THashSet tiles, HabboItem exclude) { + return this.itemManager.getTopItemAt(tiles, exclude); + } + + public double getTopHeightAt(int x, int y) { + return this.itemManager.getTopHeightAt(x, y); + } + + @Deprecated + public HabboItem getLowestChair(int x, int y) { + return this.itemManager.getLowestChair(x, y); + } + + public HabboItem getLowestChair(RoomTile tile) { + return this.itemManager.getLowestChair(tile); + } + + public HabboItem getTallestChair(RoomTile tile) { + return this.itemManager.getTallestChair(tile); + } + + public double getStackHeight(short x, short y, boolean calculateHeightmap, HabboItem exclude) { + return this.tileManager.getStackHeight(x, y, calculateHeightmap, exclude); + } + + public double getStackHeight(short x, short y, boolean calculateHeightmap) { + return this.tileManager.getStackHeight(x, y, calculateHeightmap); + } + + public boolean hasObjectTypeAt(Class type, int x, int y) { + return this.itemManager.hasObjectTypeAt(type, x, y); + } + + public boolean canSitOrLayAt(int x, int y) { + return this.tileManager.canSitOrLayAt(x, y); + } + + public boolean canSitAt(int x, int y) { + return this.tileManager.canSitAt(x, y); + } + + boolean canWalkAt(RoomTile roomTile) { + return this.tileManager.canWalkAt(roomTile); + } + + boolean canSitAt(THashSet items) { + return this.tileManager.canSitAt(items); + } + + public boolean canLayAt(int x, int y) { + return this.tileManager.canLayAt(x, y); + } + + boolean canLayAt(THashSet items) { + return this.tileManager.canLayAt(items); + } + + public RoomTile getRandomWalkableTile() { + return this.tileManager.getRandomWalkableTile(); + } + + public RoomTile getRandomWalkableTilesAround(RoomUnit roomUnit, RoomTile tile, int radius) { + return this.tileManager.getRandomWalkableTilesAround(roomUnit, tile, radius); + } + + public Habbo getHabbo(String username) { + return this.unitManager.getHabbo(username); + } + + public Habbo getHabbo(RoomUnit roomUnit) { + return this.unitManager.getHabboByRoomUnit(roomUnit); + } + + public Habbo getHabbo(int userId) { + return this.unitManager.getHabbo(userId); + } + + public Habbo getHabboByRoomUnitId(int roomUnitId) { + return this.unitManager.getHabboByRoomUnitId(roomUnitId); + } + + public void sendComposer(ServerMessage message) { + this.messagingManager.sendComposer(message); + } + + public void sendComposerToHabbosWithRights(ServerMessage message) { + this.messagingManager.sendComposerToHabbosWithRights(message); + } + + public void petChat(ServerMessage message) { + this.messagingManager.petChat(message); + } + + public void botChat(ServerMessage message) { + this.messagingManager.botChat(message); + } + + private void loadRights(Connection connection) { + this.rights.clear(); + try (PreparedStatement statement = connection.prepareStatement( + "SELECT user_id FROM room_rights WHERE room_id = ?")) { + statement.setInt(1, this.id); + try (ResultSet set = statement.executeQuery()) { + while (set.next()) { + this.rights.add(set.getInt("user_id")); + } + } + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + } + + private void loadBans(Connection connection) { + this.bannedHabbos.clear(); + + try (PreparedStatement statement = connection.prepareStatement( + "SELECT users.username, users.id, room_bans.* FROM room_bans INNER JOIN users ON room_bans.user_id = users.id WHERE ends > ? AND room_bans.room_id = ?")) { + statement.setInt(1, Emulator.getIntUnixTimestamp()); + statement.setInt(2, this.id); + try (ResultSet set = statement.executeQuery()) { + while (set.next()) { + if (this.bannedHabbos.containsKey(set.getInt("user_id"))) { + continue; + } + + this.bannedHabbos.put(set.getInt("user_id"), new RoomBan(set)); + } + } + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + } + + public RoomRightLevels getGuildRightLevel(Habbo habbo) { + if (this.guild > 0 && habbo.getHabboStats().hasGuild(this.guild)) { + Guild guild = Emulator.getGameEnvironment().getGuildManager().getGuild(this.guild); + + if (Emulator.getGameEnvironment().getGuildManager().getOnlyAdmins(guild) + .get(habbo.getHabboInfo().getId()) != null) { + return RoomRightLevels.GUILD_ADMIN; + } + + if (guild.getRights()) { + return RoomRightLevels.GUILD_RIGHTS; + } + } + + return RoomRightLevels.NONE; + } + + /** + * @deprecated Deprecated since 2.5.0. Use {@link #getGuildRightLevel(Habbo)} instead. + */ + @Deprecated + public int guildRightLevel(Habbo habbo) { + return this.rightsManager.guildRightLevel(habbo); + } + + public boolean isOwner(Habbo habbo) { + return this.rightsManager.isOwner(habbo); + } + + public boolean hasRights(Habbo habbo) { + return this.rightsManager.hasRights(habbo); + } + + public void giveRights(Habbo habbo) { + this.rightsManager.giveRights(habbo); + } + + public void giveRights(int userId) { + this.rightsManager.giveRights(userId); + } + + public void removeRights(int userId) { + this.rightsManager.removeRights(userId); + } + + public void removeAllRights() { + this.rightsManager.removeAllRights(); + } + + void refreshRightsInRoom() { + this.rightsManager.refreshRightsInRoom(); + } + + public void refreshRightsForHabbo(Habbo habbo) { + this.rightsManager.refreshRightsForHabbo(habbo); + } + + public THashMap getUsersWithRights() { + return this.rightsManager.getUsersWithRights(); + } + + public void unbanHabbo(int userId) { + this.rightsManager.unbanHabbo(userId); + } + + public boolean isBanned(Habbo habbo) { + return this.rightsManager.isBanned(habbo); + } + + public TIntObjectHashMap getBannedHabbos() { + return this.bannedHabbos; + } + + public void addRoomBan(RoomBan roomBan) { + this.rightsManager.addRoomBan(roomBan); + } + + public void makeSit(Habbo habbo) { + if (habbo.getRoomUnit() == null) { + return; + } + + if (habbo.getRoomUnit().hasStatus(RoomUnitStatus.SIT) || !habbo.getRoomUnit() + .canForcePosture()) { + return; + } + + this.dance(habbo, DanceType.NONE); + habbo.getRoomUnit().cmdSit = true; + habbo.getRoomUnit().setBodyRotation( + RoomUserRotation.values()[habbo.getRoomUnit().getBodyRotation().getValue() + - habbo.getRoomUnit().getBodyRotation().getValue() % 2]); + habbo.getRoomUnit().setStatus(RoomUnitStatus.SIT, 0.5 + ""); + this.sendComposer(new RoomUserStatusComposer(habbo.getRoomUnit()).compose()); + } + + public void makeStand(Habbo habbo) { + if (habbo.getRoomUnit() == null) { + return; + } + + HabboItem item = this.getTopItemAt(habbo.getRoomUnit().getX(), habbo.getRoomUnit().getY()); + if (item == null || !item.getBaseItem().allowSit() || !item.getBaseItem().allowLay()) { + habbo.getRoomUnit().cmdStand = true; + habbo.getRoomUnit().setBodyRotation( + RoomUserRotation.values()[habbo.getRoomUnit().getBodyRotation().getValue() + - habbo.getRoomUnit().getBodyRotation().getValue() % 2]); + habbo.getRoomUnit().removeStatus(RoomUnitStatus.SIT); + this.sendComposer(new RoomUserStatusComposer(habbo.getRoomUnit()).compose()); + } + } + + public void giveEffect(Habbo habbo, int effectId, int duration) { + this.unitManager.giveEffect(habbo, effectId, duration); + } + + public void giveEffect(RoomUnit roomUnit, int effectId, int duration) { + this.unitManager.giveEffect(roomUnit, effectId, duration); + } + + public void giveHandItem(Habbo habbo, int handItem) { + this.unitManager.giveHandItem(habbo, handItem); + } + + public void giveHandItem(RoomUnit roomUnit, int handItem) { + this.unitManager.giveHandItem(roomUnit, handItem); + } + + public void updateItem(HabboItem item) { + if (this.isLoaded()) { + if (item != null && item.getRoomId() == this.id) { + if (item.getBaseItem() != null) { + if (item.getBaseItem().getType() == FurnitureType.FLOOR) { + this.sendComposer(new FloorItemUpdateComposer(item).compose()); + this.updateTiles(this.getLayout() + .getTilesAt(this.layout.getTile(item.getX(), item.getY()), + item.getBaseItem().getWidth(), item.getBaseItem().getLength(), + item.getRotation())); + } else if (item.getBaseItem().getType() == FurnitureType.WALL) { + this.sendComposer(new WallItemUpdateComposer(item).compose()); + } + } + } + } + } + + public void updateItemState(HabboItem item) { + if (!item.isLimited()) { + this.sendComposer(new ItemStateComposer(item).compose()); + } else { + this.sendComposer(new FloorItemUpdateComposer(item).compose()); + } + + if (item.getBaseItem().getType() == FurnitureType.FLOOR) { + if (this.layout == null) { + return; + } + + this.updateTiles(this.getLayout() + .getTilesAt(this.layout.getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(), + item.getBaseItem().getLength(), item.getRotation())); + + if (item instanceof InteractionMultiHeight) { + ((InteractionMultiHeight) item).updateUnitsOnItem(this); + } + } + } + + public int getUserFurniCount(int userId) { + return this.itemManager.getFurniOwnerCount().get(userId); + } + + public int getUserUniqueFurniCount(int userId) { + return this.itemManager.getUserUniqueFurniCount(userId); + } + + public void ejectUserFurni(int userId) { + this.itemManager.ejectUserFurni(userId); + } + + public void ejectUserItem(HabboItem item) { + this.itemManager.ejectUserItem(item); + } + + + public void ejectAll() { + this.itemManager.ejectAll(); + } + + + public void ejectAll(Habbo habbo) { + this.itemManager.ejectAll(habbo); + } + + public void refreshGuild(Guild guild) { + if (guild.getRoomId() == this.id) { + THashSet members = Emulator.getGameEnvironment().getGuildManager() + .getGuildMembers(guild.getId()); + + for (Habbo habbo : this.getHabbos()) { + Optional member = members.stream() + .filter(m -> m.getUserId() == habbo.getHabboInfo().getId()).findAny(); + + if (!member.isPresent()) { + continue; } - return RoomRightLevels.NONE; + habbo.getClient() + .sendResponse(new GuildInfoComposer(guild, habbo.getClient(), false, member.get())); + } } - /** - * @deprecated Deprecated since 2.5.0. Use {@link #getGuildRightLevel(Habbo)} instead. - */ - @Deprecated - public int guildRightLevel(Habbo habbo) { - return this.getGuildRightLevel(habbo).level; - } + this.refreshGuildRightsInRoom(); + } - public boolean isOwner(Habbo habbo) { - return habbo.getHabboInfo().getId() == this.ownerId || habbo.hasPermission(Permission.ACC_ANYROOMOWNER); - } + public void refreshGuildColors(Guild guild) { + if (guild.getRoomId() == this.id) { + TIntObjectMap items = this.itemManager.getRoomItems(); + TIntObjectIterator iterator = items.iterator(); - public boolean hasRights(Habbo habbo) { - return this.isOwner(habbo) || this.rights.contains(habbo.getHabboInfo().getId()) || (habbo.getRoomUnit().getRightsLevel() != RoomRightLevels.NONE && this.currentHabbos.containsKey(habbo.getHabboInfo().getId())); - } - - public void giveRights(Habbo habbo) { - if (habbo != null) { - this.giveRights(habbo.getHabboInfo().getId()); - } - } - - public void giveRights(int userId) { - if (this.rights.contains(userId)) - return; - - if (this.rights.add(userId)) { - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO room_rights VALUES (?, ?)")) { - statement.setInt(1, this.id); - statement.setInt(2, userId); - statement.execute(); - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } + for (int i = items.size(); i-- > 0; ) { + try { + iterator.advance(); + } catch (Exception e) { + break; } - Habbo habbo = this.getHabbo(userId); + HabboItem habboItem = iterator.value(); - if (habbo != null) { + if (habboItem instanceof InteractionGuildFurni) { + if (((InteractionGuildFurni) habboItem).getGuildId() == guild.getId()) { + this.updateItem(habboItem); + } + } + } + } + } + + public void refreshGuildRightsInRoom() { + for (Habbo habbo : this.getHabbos()) { + if (habbo.getHabboInfo().getCurrentRoom() == this) { + if (habbo.getHabboInfo().getId() != this.ownerId) { + if (!(habbo.hasPermission(Permission.ACC_ANYROOMOWNER) || habbo.hasPermission( + Permission.ACC_MOVEROTATE))) { this.refreshRightsForHabbo(habbo); - - this.sendComposer(new RoomAddRightsListComposer(this, habbo.getHabboInfo().getId(), habbo.getHabboInfo().getUsername()).compose()); - } else { - Habbo owner = Emulator.getGameEnvironment().getHabboManager().getHabbo(this.ownerId); - - if (owner != null) { - MessengerBuddy buddy = owner.getMessenger().getFriend(userId); - - if (buddy != null) { - this.sendComposer(new RoomAddRightsListComposer(this, userId, buddy.getUsername()).compose()); - } - } + } } + } } + } - public void removeRights(int userId) { - Habbo habbo = this.getHabbo(userId); + public void idle(Habbo habbo) { + this.unitManager.idle(habbo); + } - if (Emulator.getPluginManager().fireEvent(new UserRightsTakenEvent(this.getHabbo(this.getOwnerId()), userId, habbo)).isCancelled()) - return; + public void unIdle(Habbo habbo) { + this.unitManager.unIdle(habbo); + } - this.sendComposer(new RoomRemoveRightsListComposer(this, userId).compose()); + public void dance(Habbo habbo, DanceType danceType) { + this.unitManager.dance(habbo, danceType); + } - if (this.rights.remove(userId)) { - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("DELETE FROM room_rights WHERE room_id = ? AND user_id = ?")) { - statement.setInt(1, this.id); - statement.setInt(2, userId); - statement.execute(); - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } - } + public void dance(RoomUnit unit, DanceType danceType) { + this.unitManager.dance(unit, danceType); + } - if (habbo != null) { - this.ejectUserFurni(habbo.getHabboInfo().getId()); - habbo.getRoomUnit().setRightsLevel(RoomRightLevels.NONE); - habbo.getRoomUnit().removeStatus(RoomUnitStatus.FLAT_CONTROL); - this.refreshRightsForHabbo(habbo); - } - } - - public void removeAllRights() { - for (int userId : rights.toArray()) { - this.ejectUserFurni(userId); - } - - this.rights.clear(); - - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("DELETE FROM room_rights WHERE room_id = ?")) { - statement.setInt(1, this.id); - statement.execute(); - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } - - this.refreshRightsInRoom(); - } - - void refreshRightsInRoom() { - Room room = this; - for (Habbo habbo : this.getHabbos()) { - if (habbo.getHabboInfo().getCurrentRoom() == room) { - this.refreshRightsForHabbo(habbo); - } - } - } - - public void refreshRightsForHabbo(Habbo habbo) { - HabboItem item; - RoomRightLevels flatCtrl = RoomRightLevels.NONE; - if (habbo.getHabboStats().isRentingSpace()) { - item = this.getHabboItem(habbo.getHabboStats().getRentedItemId()); - - if (item != null) { - return; - } - } - - if (habbo.hasPermission(Permission.ACC_ANYROOMOWNER)) { - habbo.getClient().sendResponse(new RoomOwnerComposer()); - flatCtrl = RoomRightLevels.MODERATOR; - } else if (this.isOwner(habbo)) { - habbo.getClient().sendResponse(new RoomOwnerComposer()); - flatCtrl = RoomRightLevels.MODERATOR; - } else if (this.hasRights(habbo) && !this.hasGuild()) { - flatCtrl = RoomRightLevels.RIGHTS; - } else if (this.hasGuild()) { - flatCtrl = this.getGuildRightLevel(habbo); - } - - habbo.getClient().sendResponse(new RoomRightsComposer(flatCtrl)); - habbo.getRoomUnit().setStatus(RoomUnitStatus.FLAT_CONTROL, flatCtrl.level + ""); - habbo.getRoomUnit().setRightsLevel(flatCtrl); - habbo.getRoomUnit().statusUpdate(true); - - if (flatCtrl.equals(RoomRightLevels.MODERATOR)) { - habbo.getClient().sendResponse(new RoomRightsListComposer(this)); - } - } - - public THashMap getUsersWithRights() { - THashMap rightsMap = new THashMap<>(); - - if (!this.rights.isEmpty()) { - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT users.username AS username, users.id as user_id FROM room_rights INNER JOIN users ON room_rights.user_id = users.id WHERE room_id = ?")) { - statement.setInt(1, this.id); - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - rightsMap.put(set.getInt("user_id"), set.getString("username")); - } - } - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } - } - - return rightsMap; - } - - public void unbanHabbo(int userId) { - RoomBan ban = this.bannedHabbos.remove(userId); - - if (ban != null) { - ban.delete(); - } - - this.sendComposer(new RoomUserUnbannedComposer(this, userId).compose()); - } - - public boolean isBanned(Habbo habbo) { - RoomBan ban = this.bannedHabbos.get(habbo.getHabboInfo().getId()); - - boolean banned = ban != null && ban.endTimestamp > Emulator.getIntUnixTimestamp() && !habbo.hasPermission(Permission.ACC_ANYROOMOWNER) && !habbo.hasPermission("acc_enteranyroom"); - - if (!banned && ban != null) { - this.unbanHabbo(habbo.getHabboInfo().getId()); - } - - return banned; - } - - public TIntObjectHashMap getBannedHabbos() { - return this.bannedHabbos; - } - - public void addRoomBan(RoomBan roomBan) { - this.bannedHabbos.put(roomBan.userId, roomBan); - } - - public void makeSit(Habbo habbo) { - if (habbo.getRoomUnit() == null) return; - - if (habbo.getRoomUnit().hasStatus(RoomUnitStatus.SIT) || !habbo.getRoomUnit().canForcePosture()) { - return; - } - - this.dance(habbo, DanceType.NONE); - habbo.getRoomUnit().cmdSit = true; - habbo.getRoomUnit().setBodyRotation(RoomUserRotation.values()[habbo.getRoomUnit().getBodyRotation().getValue() - habbo.getRoomUnit().getBodyRotation().getValue() % 2]); - habbo.getRoomUnit().setStatus(RoomUnitStatus.SIT, 0.5 + ""); - this.sendComposer(new RoomUserStatusComposer(habbo.getRoomUnit()).compose()); - } - - public void makeStand(Habbo habbo) { - if (habbo.getRoomUnit() == null) return; - - HabboItem item = this.getTopItemAt(habbo.getRoomUnit().getX(), habbo.getRoomUnit().getY()); - if (item == null || !item.getBaseItem().allowSit() || !item.getBaseItem().allowLay()) { - habbo.getRoomUnit().cmdStand = true; - habbo.getRoomUnit().setBodyRotation(RoomUserRotation.values()[habbo.getRoomUnit().getBodyRotation().getValue() - habbo.getRoomUnit().getBodyRotation().getValue() % 2]); - habbo.getRoomUnit().removeStatus(RoomUnitStatus.SIT); - this.sendComposer(new RoomUserStatusComposer(habbo.getRoomUnit()).compose()); - } - } - - public void giveEffect(Habbo habbo, int effectId, int duration) { - if (this.currentHabbos.containsKey(habbo.getHabboInfo().getId())) { - this.giveEffect(habbo.getRoomUnit(), effectId, duration); - } - } - - public void giveEffect(RoomUnit roomUnit, int effectId, int duration) { - if (duration == -1 || duration == Integer.MAX_VALUE) { - duration = Integer.MAX_VALUE; - } else { - duration += Emulator.getIntUnixTimestamp(); - } - - if (this.allowEffects && roomUnit != null) { - roomUnit.setEffectId(effectId, duration); - this.sendComposer(new RoomUserEffectComposer(roomUnit).compose()); - } - } - - public void giveHandItem(Habbo habbo, int handItem) { - this.giveHandItem(habbo.getRoomUnit(), handItem); - } - - public void giveHandItem(RoomUnit roomUnit, int handItem) { - roomUnit.setHandItem(handItem); - this.sendComposer(new RoomUserHandItemComposer(roomUnit).compose()); - } - - public void updateItem(HabboItem item) { - if (this.isLoaded()) { - if (item != null && item.getRoomId() == this.id) { - if (item.getBaseItem() != null) { - if (item.getBaseItem().getType() == FurnitureType.FLOOR) { - this.sendComposer(new FloorItemUpdateComposer(item).compose()); - this.updateTiles(this.getLayout().getTilesAt(this.layout.getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation())); - } else if (item.getBaseItem().getType() == FurnitureType.WALL) { - this.sendComposer(new WallItemUpdateComposer(item).compose()); - } - } - } - } - } - - public void updateItemState(HabboItem item) { - if (!item.isLimited()) { - this.sendComposer(new ItemStateComposer(item).compose()); - } else { - this.sendComposer(new FloorItemUpdateComposer(item).compose()); - } - - if (item.getBaseItem().getType() == FurnitureType.FLOOR) { - if (this.layout == null) return; - - this.updateTiles(this.getLayout().getTilesAt(this.layout.getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation())); - - if (item instanceof InteractionMultiHeight) { - ((InteractionMultiHeight) item).updateUnitsOnItem(this); - } - } - } - - public int getUserFurniCount(int userId) { - return this.furniOwnerCount.get(userId); - } - - public int getUserUniqueFurniCount(int userId) { - THashSet items = new THashSet<>(); - - for (HabboItem item : this.roomItems.valueCollection()) { - if (!items.contains(item.getBaseItem()) && item.getUserId() == userId) items.add(item.getBaseItem()); - } - - return items.size(); - } - - public void ejectUserFurni(int userId) { - THashSet items = new THashSet<>(); - - TIntObjectIterator iterator = this.roomItems.iterator(); - - for (int i = this.roomItems.size(); i-- > 0; ) { - try { - iterator.advance(); - } catch (Exception e) { - break; - } - - if (iterator.value().getUserId() == userId) { - items.add(iterator.value()); - iterator.value().setRoomId(0); - } - } - - Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId); - - if (habbo != null) { - habbo.getInventory().getItemsComponent().addItems(items); - habbo.getClient().sendResponse(new AddHabboItemComposer(items)); - } - - for (HabboItem i : items) { - this.pickUpItem(i, null); - } - } - - public void ejectUserItem(HabboItem item) { - this.pickUpItem(item, null); - } - - - public void ejectAll() { - this.ejectAll(null); - } - - - public void ejectAll(Habbo habbo) { - THashMap> userItemsMap = new THashMap<>(); - - synchronized (this.roomItems) { - TIntObjectIterator iterator = this.roomItems.iterator(); - - for (int i = this.roomItems.size(); i-- > 0; ) { - try { - iterator.advance(); - } catch (Exception e) { - break; - } - - if (habbo != null && iterator.value().getUserId() == habbo.getHabboInfo().getId()) - continue; - - if (iterator.value() instanceof InteractionPostIt) - continue; - - userItemsMap.computeIfAbsent(iterator.value().getUserId(), k -> new THashSet<>()).add(iterator.value()); - } - } - - for (Map.Entry> entrySet : userItemsMap.entrySet()) { - for (HabboItem i : entrySet.getValue()) { - this.pickUpItem(i, null); - } - - Habbo user = Emulator.getGameEnvironment().getHabboManager().getHabbo(entrySet.getKey()); - - if (user != null) { - user.getInventory().getItemsComponent().addItems(entrySet.getValue()); - user.getClient().sendResponse(new AddHabboItemComposer(entrySet.getValue())); - } - } - } - - public void refreshGuild(Guild guild) { - if (guild.getRoomId() == this.id) { - THashSet members = Emulator.getGameEnvironment().getGuildManager().getGuildMembers(guild.getId()); - - for (Habbo habbo : this.getHabbos()) { - Optional member = members.stream().filter(m -> m.getUserId() == habbo.getHabboInfo().getId()).findAny(); - - if (!member.isPresent()) continue; - - habbo.getClient().sendResponse(new GuildInfoComposer(guild, habbo.getClient(), false, member.get())); - } - } - - this.refreshGuildRightsInRoom(); - } - - public void refreshGuildColors(Guild guild) { - if (guild.getRoomId() == this.id) { - TIntObjectIterator iterator = this.roomItems.iterator(); - - for (int i = this.roomItems.size(); i-- > 0; ) { - try { - iterator.advance(); - } catch (Exception e) { - break; - } - - HabboItem habboItem = iterator.value(); - - if (habboItem instanceof InteractionGuildFurni) { - if (((InteractionGuildFurni) habboItem).getGuildId() == guild.getId()) - this.updateItem(habboItem); - } - } - } - } - - public void refreshGuildRightsInRoom() { - for (Habbo habbo : this.getHabbos()) { - if (habbo.getHabboInfo().getCurrentRoom() == this) { - if (habbo.getHabboInfo().getId() != this.ownerId) { - if (!(habbo.hasPermission(Permission.ACC_ANYROOMOWNER) || habbo.hasPermission(Permission.ACC_MOVEROTATE))) - this.refreshRightsForHabbo(habbo); - } - } - } - } - - public void idle(Habbo habbo) { - habbo.getRoomUnit().setIdle(); - - if (habbo.getRoomUnit().getDanceType() != DanceType.NONE) { - this.dance(habbo, DanceType.NONE); - } - - this.sendComposer(new RoomUnitIdleComposer(habbo.getRoomUnit()).compose()); - WiredHandler.handle(WiredTriggerType.IDLES, habbo.getRoomUnit(), this, new Object[]{habbo}); - } - - public void unIdle(Habbo habbo) { - if (habbo == null || habbo.getRoomUnit() == null) return; - habbo.getRoomUnit().resetIdleTimer(); - this.sendComposer(new RoomUnitIdleComposer(habbo.getRoomUnit()).compose()); - WiredHandler.handle(WiredTriggerType.UNIDLES, habbo.getRoomUnit(), this, new Object[]{habbo}); - } - - public void dance(Habbo habbo, DanceType danceType) { - this.dance(habbo.getRoomUnit(), danceType); - } - - public void dance(RoomUnit unit, DanceType danceType) { - if (unit.getDanceType() != danceType) { - boolean isDancing = !unit.getDanceType().equals(DanceType.NONE); - unit.setDanceType(danceType); - this.sendComposer(new RoomUserDanceComposer(unit).compose()); - - if (danceType.equals(DanceType.NONE) && isDancing) { - WiredHandler.handle(WiredTriggerType.STOPS_DANCING, unit, this, new Object[]{unit}); - } else if (!danceType.equals(DanceType.NONE) && !isDancing) { - WiredHandler.handle(WiredTriggerType.STARTS_DANCING, unit, this, new Object[]{unit}); - } - } - } - - public void addToWordFilter(String word) { - synchronized (this.wordFilterWords) { - if (this.wordFilterWords.contains(word)) - return; - - - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT IGNORE INTO room_wordfilter VALUES (?, ?)")) { - statement.setInt(1, this.getId()); - statement.setString(2, word); - statement.execute(); - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - return; - } - - this.wordFilterWords.add(word); - } - } - - public void removeFromWordFilter(String word) { - synchronized (this.wordFilterWords) { - this.wordFilterWords.remove(word); - - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("DELETE FROM room_wordfilter WHERE room_id = ? AND word = ?")) { - statement.setInt(1, this.getId()); - statement.setString(2, word); - statement.execute(); - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } - } - } - - public void handleWordQuiz(Habbo habbo, String answer) { - synchronized (this.userVotes) { - if (!this.wordQuiz.isEmpty() && !this.hasVotedInWordQuiz(habbo)) { - answer = answer.replace(":", ""); - - if (answer.equals("0")) { - this.noVotes++; - } else if (answer.equals("1")) { - this.yesVotes++; - } - - this.sendComposer(new SimplePollAnswerComposer(habbo.getHabboInfo().getId(), answer, this.noVotes, this.yesVotes).compose()); - this.userVotes.add(habbo.getHabboInfo().getId()); - } - } - } - - public void startWordQuiz(String question, int duration) { - if (!this.hasActiveWordQuiz()) { - this.wordQuiz = question; - this.noVotes = 0; - this.yesVotes = 0; - this.userVotes.clear(); - this.wordQuizEnd = Emulator.getIntUnixTimestamp() + (duration / 1000); - this.sendComposer(new SimplePollStartComposer(duration, question).compose()); - } - } - - public boolean hasActiveWordQuiz() { - return Emulator.getIntUnixTimestamp() < this.wordQuizEnd; - } - - public boolean hasVotedInWordQuiz(Habbo habbo) { - return this.userVotes.contains(habbo.getHabboInfo().getId()); - } - - public void alert(String message) { - this.sendComposer(new GenericAlertComposer(message).compose()); - } - - public int itemCount() { - return this.roomItems.size(); - } - - public void setJukeBoxActive(boolean jukeBoxActive) { - this.jukeboxActive = jukeBoxActive; - this.needsUpdate = true; - } - - public boolean isHideWired() { - return this.hideWired; - } - - public void setHideWired(boolean hideWired) { - this.hideWired = hideWired; - - if (this.hideWired) { - for (HabboItem item : this.roomSpecialTypes.getTriggers()) { - this.sendComposer(new RemoveFloorItemComposer(item).compose()); - } - - for (HabboItem item : this.roomSpecialTypes.getEffects()) { - this.sendComposer(new RemoveFloorItemComposer(item).compose()); - } - - for (HabboItem item : this.roomSpecialTypes.getConditions()) { - this.sendComposer(new RemoveFloorItemComposer(item).compose()); - } - - for (HabboItem item : this.roomSpecialTypes.getExtras()) { - this.sendComposer(new RemoveFloorItemComposer(item).compose()); - } - } else { - this.sendComposer(new RoomFloorItemsComposer(this.furniOwnerNames, this.roomSpecialTypes.getTriggers()).compose()); - this.sendComposer(new RoomFloorItemsComposer(this.furniOwnerNames, this.roomSpecialTypes.getEffects()).compose()); - this.sendComposer(new RoomFloorItemsComposer(this.furniOwnerNames, this.roomSpecialTypes.getConditions()).compose()); - this.sendComposer(new RoomFloorItemsComposer(this.furniOwnerNames, this.roomSpecialTypes.getExtras()).compose()); - } - } - - public FurnitureMovementError canPlaceFurnitureAt(HabboItem item, Habbo habbo, RoomTile tile, int rotation) { - if (this.itemCount() >= Room.MAXIMUM_FURNI) { - return FurnitureMovementError.MAX_ITEMS; - } - - if (tile == null || tile.state == RoomTileState.INVALID) { - return FurnitureMovementError.INVALID_MOVE; - } - - rotation %= 8; - if (this.hasRights(habbo) || this.getGuildRightLevel(habbo).isEqualOrGreaterThan(RoomRightLevels.GUILD_RIGHTS) || habbo.hasPermission(Permission.ACC_MOVEROTATE)) { - return FurnitureMovementError.NONE; - } - - if (habbo.getHabboStats().isRentingSpace()) { - HabboItem rentSpace = this.getHabboItem(habbo.getHabboStats().rentedItemId); - - if (rentSpace != null) { - if (!RoomLayout.squareInSquare(RoomLayout.getRectangle(rentSpace.getX(), rentSpace.getY(), rentSpace.getBaseItem().getWidth(), rentSpace.getBaseItem().getLength(), rentSpace.getRotation()), RoomLayout.getRectangle(tile.x, tile.y, item.getBaseItem().getWidth(), item.getBaseItem().getLength(), rotation))) { - return FurnitureMovementError.NO_RIGHTS; - } else { - return FurnitureMovementError.NONE; - } - } - } - - for (HabboItem area : this.getRoomSpecialTypes().getItemsOfType(InteractionBuildArea.class)) { - if (((InteractionBuildArea) area).inSquare(tile) && ((InteractionBuildArea) area).isBuilder(habbo.getHabboInfo().getUsername())) { - return FurnitureMovementError.NONE; - } - } - - return FurnitureMovementError.NO_RIGHTS; - } - - public FurnitureMovementError furnitureFitsAt(RoomTile tile, HabboItem item, int rotation) { - return furnitureFitsAt(tile, item, rotation, true); - } - - public FurnitureMovementError furnitureFitsAt(RoomTile tile, HabboItem item, int rotation, boolean checkForUnits) { - if (!this.layout.fitsOnMap(tile, item.getBaseItem().getWidth(), item.getBaseItem().getLength(), rotation)) - return FurnitureMovementError.INVALID_MOVE; - - if (item instanceof InteractionStackHelper || item instanceof InteractionTileWalkMagic) return FurnitureMovementError.NONE; - - THashSet occupiedTiles = this.layout.getTilesAt(tile, item.getBaseItem().getWidth(), item.getBaseItem().getLength(), rotation); - for (RoomTile t : occupiedTiles) { - if (t.state == RoomTileState.INVALID) return FurnitureMovementError.INVALID_MOVE; - if (!Emulator.getConfig().getBoolean("wired.place.under", false) || (Emulator.getConfig().getBoolean("wired.place.under", false) && !item.isWalkable() && !item.getBaseItem().allowSit() && !item.getBaseItem().allowLay())) { - if (checkForUnits && this.hasHabbosAt(t.x, t.y)) return FurnitureMovementError.TILE_HAS_HABBOS; - if (checkForUnits && this.hasBotsAt(t.x, t.y)) return FurnitureMovementError.TILE_HAS_BOTS; - if (checkForUnits && this.hasPetsAt(t.x, t.y)) return FurnitureMovementError.TILE_HAS_PETS; - } - } - - List>> tileFurniList = new ArrayList<>(); - for (RoomTile t : occupiedTiles) { - tileFurniList.add(Pair.create(t, this.getItemsAt(t))); - - HabboItem topItem = this.getTopItemAt(t.x, t.y, item); - if (topItem != null && !topItem.getBaseItem().allowStack() && !t.getAllowStack()) { - return FurnitureMovementError.CANT_STACK; - } - } - - if (!item.canStackAt(this, tileFurniList)) { - return FurnitureMovementError.CANT_STACK; - } - - return FurnitureMovementError.NONE; - } - - public FurnitureMovementError placeFloorFurniAt(HabboItem item, RoomTile tile, int rotation, Habbo owner) { - boolean pluginHelper = false; - if (Emulator.getPluginManager().isRegistered(FurniturePlacedEvent.class, true)) { - FurniturePlacedEvent event = Emulator.getPluginManager().fireEvent(new FurniturePlacedEvent(item, owner, tile)); - - if (event.isCancelled()) { - return FurnitureMovementError.CANCEL_PLUGIN_PLACE; - } - - pluginHelper = event.hasPluginHelper(); - } - - THashSet occupiedTiles = this.layout.getTilesAt(tile, item.getBaseItem().getWidth(), item.getBaseItem().getLength(), rotation); - - FurnitureMovementError fits = furnitureFitsAt(tile, item, rotation); - - if (!fits.equals(FurnitureMovementError.NONE) && !pluginHelper) { - return fits; - } - - double height = tile.getStackHeight(); - - for (RoomTile tile2 : occupiedTiles) { - double sHeight = tile2.getStackHeight(); - if (sHeight > height) { - height = sHeight; - } - } - - if (Emulator.getPluginManager().isRegistered(FurnitureBuildheightEvent.class, true)) { - FurnitureBuildheightEvent event = Emulator.getPluginManager().fireEvent(new FurnitureBuildheightEvent(item, owner, 0.00, height)); - if (event.hasChangedHeight()) { - height = event.getUpdatedHeight(); - } - } - - item.setZ(height); - item.setX(tile.x); - item.setY(tile.y); - item.setRotation(rotation); - if (!this.furniOwnerNames.containsKey(item.getUserId()) && owner != null) { - this.furniOwnerNames.put(item.getUserId(), owner.getHabboInfo().getUsername()); - } - - item.needsUpdate(true); - this.addHabboItem(item); - item.setRoomId(this.id); - item.onPlace(this); - this.updateTiles(occupiedTiles); - this.sendComposer(new AddFloorItemComposer(item, this.getFurniOwnerName(item.getUserId())).compose()); - - for (RoomTile t : occupiedTiles) { - this.updateHabbosAt(t.x, t.y); - this.updateBotsAt(t.x, t.y); - } - - Emulator.getThreading().run(item); - return FurnitureMovementError.NONE; - } - - public FurnitureMovementError placeWallFurniAt(HabboItem item, String wallPosition, Habbo owner) { - if (!(this.hasRights(owner) || this.getGuildRightLevel(owner).isEqualOrGreaterThan(RoomRightLevels.GUILD_RIGHTS))) { - return FurnitureMovementError.NO_RIGHTS; - } - - if (Emulator.getPluginManager().isRegistered(FurniturePlacedEvent.class, true)) { - Event furniturePlacedEvent = new FurniturePlacedEvent(item, owner, null); - Emulator.getPluginManager().fireEvent(furniturePlacedEvent); - - if (furniturePlacedEvent.isCancelled()) - return FurnitureMovementError.CANCEL_PLUGIN_PLACE; - } - - item.setWallPosition(wallPosition); - if (!this.furniOwnerNames.containsKey(item.getUserId()) && owner != null) { - this.furniOwnerNames.put(item.getUserId(), owner.getHabboInfo().getUsername()); - } - this.sendComposer(new AddWallItemComposer(item, this.getFurniOwnerName(item.getUserId())).compose()); - item.needsUpdate(true); - this.addHabboItem(item); - item.setRoomId(this.id); - item.onPlace(this); - Emulator.getThreading().run(item); - return FurnitureMovementError.NONE; - } - - public FurnitureMovementError moveFurniTo(HabboItem item, RoomTile tile, int rotation, Habbo actor) { - return moveFurniTo(item, tile, rotation, actor, true, true); - } - - public FurnitureMovementError moveFurniTo(HabboItem item, RoomTile tile, int rotation, Habbo actor, boolean sendUpdates) { - return moveFurniTo(item, tile, rotation, actor, sendUpdates, true); - } - - public FurnitureMovementError moveFurniTo(HabboItem item, RoomTile tile, int rotation, Habbo actor, boolean sendUpdates, boolean checkForUnits) { - RoomTile oldLocation = this.layout.getTile(item.getX(), item.getY()); - - boolean pluginHelper = false; - if (Emulator.getPluginManager().isRegistered(FurnitureMovedEvent.class, true)) { - FurnitureMovedEvent event = Emulator.getPluginManager().fireEvent(new FurnitureMovedEvent(item, actor, oldLocation, tile)); - if (event.isCancelled()) { - return FurnitureMovementError.CANCEL_PLUGIN_MOVE; - } - pluginHelper = event.hasPluginHelper(); - } - - boolean magicTile = item instanceof InteractionStackHelper || item instanceof InteractionTileWalkMagic; - - Optional stackHelper = this.getItemsAt(tile).stream().filter(i -> i instanceof InteractionStackHelper || i instanceof InteractionTileWalkMagic).findAny(); - - //Check if can be placed at new position - THashSet occupiedTiles = this.layout.getTilesAt(tile, item.getBaseItem().getWidth(), item.getBaseItem().getLength(), rotation); - THashSet newOccupiedTiles = this.layout.getTilesAt(tile, item.getBaseItem().getWidth(), item.getBaseItem().getLength(), rotation); - - HabboItem topItem = this.getTopItemAt(occupiedTiles, null); - - if ((stackHelper.isEmpty() && !pluginHelper)) { - if (oldLocation != tile) { - for (RoomTile t : occupiedTiles) { - HabboItem tileTopItem = this.getTopItemAt(t.x, t.y); - if (!magicTile && ((tileTopItem != null && tileTopItem != item ? (t.state.equals(RoomTileState.INVALID) || !t.getAllowStack() || !tileTopItem.getBaseItem().allowStack()) : this.calculateTileState(t, item).equals(RoomTileState.INVALID)))) - return FurnitureMovementError.CANT_STACK; - - if (!Emulator.getConfig().getBoolean("wired.place.under", false) || (Emulator.getConfig().getBoolean("wired.place.under", false) && !item.isWalkable() && !item.getBaseItem().allowSit() && !item.getBaseItem().allowLay())) { - if (checkForUnits) { - if (!magicTile && this.hasHabbosAt(t.x, t.y)) return FurnitureMovementError.TILE_HAS_HABBOS; - if (!magicTile && this.hasBotsAt(t.x, t.y)) return FurnitureMovementError.TILE_HAS_BOTS; - if (!magicTile && this.hasPetsAt(t.x, t.y)) return FurnitureMovementError.TILE_HAS_PETS; - } - } - } - } - - List>> tileFurniList = new ArrayList<>(); - for (RoomTile t : occupiedTiles) { - tileFurniList.add(Pair.create(t, this.getItemsAt(t))); - } - - if (!magicTile && !item.canStackAt(this, tileFurniList)) { - return FurnitureMovementError.CANT_STACK; - } - } - - THashSet oldOccupiedTiles = this.layout.getTilesAt(this.layout.getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); - - int oldRotation = item.getRotation(); - - if (oldRotation != rotation) { - item.setRotation(rotation); - if (Emulator.getPluginManager().isRegistered(FurnitureRotatedEvent.class, true)) { - Event furnitureRotatedEvent = new FurnitureRotatedEvent(item, actor, oldRotation); - Emulator.getPluginManager().fireEvent(furnitureRotatedEvent); - - if (furnitureRotatedEvent.isCancelled()) { - item.setRotation(oldRotation); - return FurnitureMovementError.CANCEL_PLUGIN_ROTATE; - } - } - - if ((!stackHelper.isPresent() && topItem != null && topItem != item && !topItem.getBaseItem().allowStack()) || (topItem != null && topItem != item && topItem.getZ() + Item.getCurrentHeight(topItem) + Item.getCurrentHeight(item) > MAXIMUM_FURNI_HEIGHT)) { - item.setRotation(oldRotation); - return FurnitureMovementError.CANT_STACK; - } - - // ) - } - //Place at new position - - double height; - - if (stackHelper.isPresent()) { - height = stackHelper.get().getExtradata().isEmpty() ? Double.parseDouble("0.0") : (Double.parseDouble(stackHelper.get().getExtradata()) / 100); - } else if (item == topItem) { - height = item.getZ(); - } else if(magicTile) { - if(topItem == null) { - height = this.getStackHeight(tile.x, tile.y, false, item); - for(RoomTile til : occupiedTiles) { - double sHeight = this.getStackHeight(til.x, til.y, false, item); - if(sHeight > height) { - height = sHeight; - } - } - } - else { - height = topItem.getZ() + topItem.getBaseItem().getHeight(); - } - } else { - height = this.getStackHeight(tile.x, tile.y, false, item); - for(RoomTile til : occupiedTiles) { - double sHeight = this.getStackHeight(til.x, til.y, false, item); - if(sHeight > height) { - height = sHeight; - } - } - } - - boolean cantStack = false; - boolean pluginHeight = false; - - if(height > MAXIMUM_FURNI_HEIGHT) { - cantStack = true; - } - if(height < this.getLayout().getHeightAtSquare(tile.x, tile.y)) { - cantStack = true; - } - - if (Emulator.getPluginManager().isRegistered(FurnitureBuildheightEvent.class, true)) { - FurnitureBuildheightEvent event = Emulator.getPluginManager().fireEvent(new FurnitureBuildheightEvent(item, actor, 0.00, height)); - if (event.hasChangedHeight()) { - height = event.getUpdatedHeight(); - pluginHeight = true; - } - } - - if(!pluginHeight && cantStack) { - return FurnitureMovementError.CANT_STACK; - } - - item.setX(tile.x); - item.setY(tile.y); - item.setZ(height); - if (magicTile) { - item.setZ(tile.z); - item.setExtradata("" + item.getZ() * 100); - } - if (item.getZ() > MAXIMUM_FURNI_HEIGHT) { - item.setZ(MAXIMUM_FURNI_HEIGHT); - } - - - //Update Furniture - item.onMove(this, oldLocation, tile); - item.needsUpdate(true); - Emulator.getThreading().run(item); - - if (sendUpdates) { - this.sendComposer(new FloorItemUpdateComposer(item).compose()); - } - - //Update old & new tiles - occupiedTiles.removeAll(oldOccupiedTiles); - occupiedTiles.addAll(oldOccupiedTiles); - this.updateTiles(occupiedTiles); - - //Update Habbos at old position - for (RoomTile t : occupiedTiles) { - this.updateHabbosAt( - t.x, - t.y, - this.getHabbosAt(t.x, t.y) - /*.stream() - .filter(h -> !h.getRoomUnit().hasStatus(RoomUnitStatus.MOVE) || h.getRoomUnit().getGoal() == t) - .collect(Collectors.toCollection(THashSet::new))*/ - ); - this.updateBotsAt(t.x, t.y); - } - if (Emulator.getConfig().getBoolean("wired.place.under", false)) { - for (RoomTile t : newOccupiedTiles) { - for (Habbo h : this.getHabbosAt(t.x, t.y)) { - try { - item.onWalkOn(h.getRoomUnit(), this, null); - } catch (Exception e) { - - } - } - } - } - return FurnitureMovementError.NONE; - } - - public FurnitureMovementError moveFurniTo(HabboItem item, RoomTile tile, int rotation, double z, Habbo actor, boolean sendUpdates, boolean checkForUnits) { - if (tile == null) { - return FurnitureMovementError.INVALID_MOVE; - } - RoomTile oldLocation = this.layout.getTile(item.getX(), item.getY()); - boolean pluginHelper = false; - - - if (Emulator.getPluginManager().isRegistered(FurnitureMovedEvent.class, true)) { - FurnitureMovedEvent event = Emulator.getPluginManager().fireEvent(new FurnitureMovedEvent(item, actor, oldLocation, tile)); - if(event.isCancelled()) { - return FurnitureMovementError.CANCEL_PLUGIN_MOVE; - } - pluginHelper = event.hasPluginHelper(); - } - - boolean magicTile = item instanceof InteractionStackHelper; - - THashSet occupiedTiles = this.layout.getTilesAt(tile, item.getBaseItem().getWidth(), item.getBaseItem().getLength(), rotation); - THashSet oldOccupiedTiles = this.layout.getTilesAt(this.layout.getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(), item.getBaseItem().getLength(), item.getRotation()); - - if (item.getRotation() != rotation) { - item.setRotation(rotation); - } - - if(z > MAXIMUM_FURNI_HEIGHT) return FurnitureMovementError.CANT_STACK; - if(z < this.getLayout().getHeightAtSquare(tile.x, tile.y)) return FurnitureMovementError.CANT_STACK; //prevent furni going under the floor - - if (Emulator.getPluginManager().isRegistered(FurnitureBuildheightEvent.class, true)) { - FurnitureBuildheightEvent event = Emulator.getPluginManager().fireEvent(new FurnitureBuildheightEvent(item, actor, 0.00, z)); - if (event.hasChangedHeight()) { - z = event.getUpdatedHeight(); - } - } - - item.setX(tile.x); - item.setY(tile.y); - item.setZ(z); - if (magicTile) { - item.setZ(tile.z); - item.setExtradata("" + item.getZ() * 100); - } - if (item.getZ() > MAXIMUM_FURNI_HEIGHT) { - item.setZ(MAXIMUM_FURNI_HEIGHT); - } - - //Update Furniture - item.onMove(this, oldLocation, tile); - item.needsUpdate(true); - Emulator.getThreading().run(item); - - if(sendUpdates) { - this.sendComposer(new FloorItemUpdateComposer(item).compose()); - } - - //Update old & new tiles - occupiedTiles.removeAll(oldOccupiedTiles); - occupiedTiles.addAll(oldOccupiedTiles); - this.updateTiles(occupiedTiles); - - //Update Habbos at old position - for (RoomTile t : occupiedTiles) { - this.updateHabbosAt( - t.x, - t.y, - this.getHabbosAt(t.x, t.y) - /*.stream() - .filter(h -> !h.getRoomUnit().hasStatus(RoomUnitStatus.MOVE) || h.getRoomUnit().getGoal() == t) - .collect(Collectors.toCollection(THashSet::new))*/ - ); - this.updateBotsAt(t.x, t.y); - } - return FurnitureMovementError.NONE; - } - - public THashSet getRoomUnits() { - return getRoomUnits(null); - } - - public THashSet getRoomUnits(RoomTile atTile) { - THashSet units = new THashSet<>(); - - for (Habbo habbo : this.currentHabbos.values()) { - if (habbo != null && habbo.getRoomUnit() != null && habbo.getRoomUnit().getRoom() != null && habbo.getRoomUnit().getRoom().getId() == this.getId() && (atTile == null || habbo.getRoomUnit().getCurrentLocation() == atTile)) { - units.add(habbo.getRoomUnit()); - } - } - - for (Pet pet : this.currentPets.valueCollection()) { - if (pet != null && pet.getRoomUnit() != null && pet.getRoomUnit().getRoom() != null && pet.getRoomUnit().getRoom().getId() == this.getId() && (atTile == null || pet.getRoomUnit().getCurrentLocation() == atTile)) { - units.add(pet.getRoomUnit()); - } - } - - for (Bot bot : this.currentBots.valueCollection()) { - if (bot != null && bot.getRoomUnit() != null && bot.getRoomUnit().getRoom() != null && bot.getRoomUnit().getRoom().getId() == this.getId() && (atTile == null || bot.getRoomUnit().getCurrentLocation() == atTile)) { - units.add(bot.getRoomUnit()); - } - } - - return units; - } - - public Collection getRoomUnitsAt(RoomTile tile) { - THashSet roomUnits = getRoomUnits(); - return roomUnits.stream().filter(unit -> unit.getCurrentLocation() == tile).collect(Collectors.toSet()); + public void addToWordFilter(String word) { + this.chatManager.addToWordFilter(word); + } + + public void removeFromWordFilter(String word) { + this.chatManager.removeFromWordFilter(word); + } + + public void handleWordQuiz(Habbo habbo, String answer) { + this.wordQuizManager.handleWordQuiz(habbo, answer); + } + + public void startWordQuiz(String question, int duration) { + this.wordQuizManager.startWordQuiz(question, duration); + } + + public boolean hasActiveWordQuiz() { + return this.wordQuizManager.hasActiveWordQuiz(); + } + + public boolean hasVotedInWordQuiz(Habbo habbo) { + return this.wordQuizManager.hasVotedInWordQuiz(habbo); + } + + public void alert(String message) { + this.messagingManager.alert(message); + } + + public int itemCount() { + return this.itemManager.itemCount(); + } + + public void setJukeBoxActive(boolean jukeBoxActive) { + this.jukeboxActive = jukeBoxActive; + this.needsUpdate = true; + } + + public boolean isHideWired() { + return this.hideWired; + } + + public void setHideWired(boolean hideWired) { + this.hideWired = hideWired; + + if (this.hideWired) { + for (HabboItem item : this.roomSpecialTypes.getTriggers()) { + this.sendComposer(new RemoveFloorItemComposer(item).compose()); + } + + for (HabboItem item : this.roomSpecialTypes.getEffects()) { + this.sendComposer(new RemoveFloorItemComposer(item).compose()); + } + + for (HabboItem item : this.roomSpecialTypes.getConditions()) { + this.sendComposer(new RemoveFloorItemComposer(item).compose()); + } + + for (HabboItem item : this.roomSpecialTypes.getExtras()) { + this.sendComposer(new RemoveFloorItemComposer(item).compose()); + } + } else { + this.sendComposer(new RoomFloorItemsComposer(this.itemManager.getFurniOwnerNames(), + this.roomSpecialTypes.getTriggers()).compose()); + this.sendComposer(new RoomFloorItemsComposer(this.itemManager.getFurniOwnerNames(), + this.roomSpecialTypes.getEffects()).compose()); + this.sendComposer(new RoomFloorItemsComposer(this.itemManager.getFurniOwnerNames(), + this.roomSpecialTypes.getConditions()).compose()); + this.sendComposer(new RoomFloorItemsComposer(this.itemManager.getFurniOwnerNames(), + this.roomSpecialTypes.getExtras()).compose()); } + } + + public FurnitureMovementError canPlaceFurnitureAt(HabboItem item, Habbo habbo, RoomTile tile, + int rotation) { + return this.itemManager.canPlaceFurnitureAt(item, habbo, tile, rotation); + } + + public FurnitureMovementError furnitureFitsAt(RoomTile tile, HabboItem item, int rotation) { + return this.itemManager.furnitureFitsAt(tile, item, rotation); + } + + public FurnitureMovementError furnitureFitsAt(RoomTile tile, HabboItem item, int rotation, + boolean checkForUnits) { + return this.itemManager.furnitureFitsAt(tile, item, rotation, checkForUnits); + } + + public FurnitureMovementError placeFloorFurniAt(HabboItem item, RoomTile tile, int rotation, + Habbo owner) { + return this.itemManager.placeFloorFurniAt(item, tile, rotation, owner); + } + + public FurnitureMovementError placeWallFurniAt(HabboItem item, String wallPosition, Habbo owner) { + return this.itemManager.placeWallFurniAt(item, wallPosition, owner); + } + + public FurnitureMovementError moveFurniTo(HabboItem item, RoomTile tile, int rotation, + Habbo actor) { + return this.itemManager.moveFurniTo(item, tile, rotation, actor); + } + + public FurnitureMovementError moveFurniTo(HabboItem item, RoomTile tile, int rotation, + Habbo actor, boolean sendUpdates) { + return this.itemManager.moveFurniTo(item, tile, rotation, actor, sendUpdates); + } + + public FurnitureMovementError moveFurniTo(HabboItem item, RoomTile tile, int rotation, + Habbo actor, boolean sendUpdates, boolean checkForUnits) { + return this.itemManager.moveFurniTo(item, tile, rotation, actor, sendUpdates, checkForUnits); + } + + public FurnitureMovementError slideFurniTo(HabboItem item, RoomTile tile, int rotation) { + return this.itemManager.slideFurniTo(item, tile, rotation); + } + + public THashSet getRoomUnits() { + return this.unitManager.getRoomUnits(); + } + + public THashSet getRoomUnits(RoomTile atTile) { + return this.unitManager.getRoomUnits(atTile); + } + + public Collection getRoomUnitsAt(RoomTile tile) { + return this.unitManager.getRoomUnitsAt(tile); + } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCategory.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCategory.java index c65484c5..59b7b920 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCategory.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCategory.java @@ -8,15 +8,15 @@ import java.sql.SQLException; @SuppressWarnings("NullableProblems") public class RoomCategory implements Comparable { - private final int id; - private final int minRank; - private final String caption; - private final String captionSave; - private final boolean canTrade; - private final int maxUserCount; - private final boolean official; - private final ListMode displayMode; - private final int order; + private int id; + private int minRank; + private String caption; + private String captionSave; + private boolean canTrade; + private int maxUserCount; + private boolean official; + private ListMode displayMode; + private int order; public RoomCategory(ResultSet set) throws SQLException { this.id = set.getInt("id"); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatBubbleManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatBubbleManager.java new file mode 100644 index 00000000..f84c97ad --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatBubbleManager.java @@ -0,0 +1,38 @@ +package com.eu.habbo.habbohotel.rooms; + +import com.eu.habbo.Emulator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +public class RoomChatBubbleManager { + private static final Logger LOGGER = LoggerFactory.getLogger(RoomChatBubbleManager.class); + + public RoomChatBubbleManager() { + this.reload(); + } + + public void reload() { + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); + PreparedStatement statement = connection.prepareStatement("SELECT * FROM chat_bubbles")) { + + try (ResultSet resultSet = statement.executeQuery()) { + while (resultSet.next()) { + int type = resultSet.getInt("type"); + String name = resultSet.getString("name"); + String permission = resultSet.getString("permission"); + boolean overridable = resultSet.getBoolean("overridable"); + boolean triggersTalkingFurniture = resultSet.getBoolean("triggers_talking_furniture"); + + RoomChatMessageBubbles.addDynamicBubble(type, name, permission, overridable, triggersTalkingFurniture); + } + } + } catch (SQLException e) { + LOGGER.error("Failed to load chat bubbles from database.", e); + } + } +} \ No newline at end of file diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatManager.java new file mode 100644 index 00000000..3665aca1 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatManager.java @@ -0,0 +1,642 @@ +package com.eu.habbo.habbohotel.rooms; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.bots.Bot; +import com.eu.habbo.habbohotel.commands.CommandHandler; +import com.eu.habbo.habbohotel.items.interactions.InteractionMuteArea; +import com.eu.habbo.habbohotel.items.interactions.InteractionTalkingFurniture; +import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserNameChangedComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserShoutComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserTalkComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserTypingComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserWhisperComposer; +import com.eu.habbo.messages.outgoing.users.MutedWhisperComposer; +import com.eu.habbo.plugin.events.users.UserIdleEvent; +import com.eu.habbo.plugin.events.users.UsernameTalkEvent; +import com.eu.habbo.threading.runnables.YouAreAPirate; +import com.eu.habbo.util.pathfinding.Rotation; +import gnu.trove.iterator.TIntObjectIterator; +import gnu.trove.map.hash.TIntIntHashMap; +import gnu.trove.set.hash.THashSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.awt.Rectangle; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.regex.Pattern; + +/** + * Manages all chat functionality within a room. + * Handles talking, shouting, whispering, word filtering, flood protection, and muting. + */ +public class RoomChatManager { + private static final Logger LOGGER = LoggerFactory.getLogger(RoomChatManager.class); + + private final Room room; + + // Word filter + private final THashSet wordFilterWords; + + // Muted Habbos: userId -> unmute timestamp + private final TIntIntHashMap mutedHabbos; + + // Flood protection settings + private final int muteTime; + + // Global chat delay setting + public static boolean HABBO_CHAT_DELAY = false; + + // Mute area can whisper setting + public static boolean MUTEAREA_CAN_WHISPER = false; + + public RoomChatManager(Room room) { + this.room = room; + this.wordFilterWords = new THashSet<>(0); + this.mutedHabbos = new TIntIntHashMap(); + this.muteTime = Emulator.getConfig().getInt("hotel.flood.mute.time", 30); + } + + // ==================== WORD FILTER ==================== + + /** + * Loads word filter from the database. + */ + public void loadWordFilter(Connection connection) { + synchronized (this.wordFilterWords) { + this.wordFilterWords.clear(); + + try (PreparedStatement statement = connection.prepareStatement( + "SELECT word FROM room_wordfilter WHERE room_id = ?")) { + statement.setInt(1, this.room.getId()); + try (ResultSet set = statement.executeQuery()) { + while (set.next()) { + this.wordFilterWords.add(set.getString("word")); + } + } + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + } + } + + /** + * Adds a word to the filter. + */ + public void addToWordFilter(String word) { + synchronized (this.wordFilterWords) { + if (this.wordFilterWords.contains(word)) { + return; + } + + try (Connection connection = Emulator.getDatabase().getDataSource() + .getConnection(); PreparedStatement statement = connection.prepareStatement( + "INSERT IGNORE INTO room_wordfilter VALUES (?, ?)")) { + statement.setInt(1, this.room.getId()); + statement.setString(2, word); + statement.execute(); + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + return; + } + + this.wordFilterWords.add(word); + } + } + + /** + * Removes a word from the filter. + */ + public void removeFromWordFilter(String word) { + synchronized (this.wordFilterWords) { + this.wordFilterWords.remove(word); + + try (Connection connection = Emulator.getDatabase().getDataSource() + .getConnection(); PreparedStatement statement = connection.prepareStatement( + "DELETE FROM room_wordfilter WHERE room_id = ? AND word = ?")) { + statement.setInt(1, this.room.getId()); + statement.setString(2, word); + statement.execute(); + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + } + } + + /** + * Gets the word filter words. + */ + public THashSet getWordFilterWords() { + return this.wordFilterWords; + } + + /** + * Checks if word filter is empty. + */ + public boolean hasWordFilter() { + return !this.wordFilterWords.isEmpty(); + } + + // ==================== MUTING ==================== + + /** + * Mutes a Habbo for a specified number of minutes. + */ + public void muteHabbo(Habbo habbo, int minutes) { + synchronized (this.mutedHabbos) { + this.mutedHabbos.put(habbo.getHabboInfo().getId(), + Emulator.getIntUnixTimestamp() + (minutes * 60)); + } + } + + /** + * Checks if a Habbo is muted. + */ + public boolean isMuted(Habbo habbo) { + if (this.room.isOwner(habbo) || this.room.hasRights(habbo)) { + return false; + } + + if (this.mutedHabbos.containsKey(habbo.getHabboInfo().getId())) { + boolean time = + this.mutedHabbos.get(habbo.getHabboInfo().getId()) > Emulator.getIntUnixTimestamp(); + + if (!time) { + this.mutedHabbos.remove(habbo.getHabboInfo().getId()); + } + + return time; + } + + return false; + } + + /** + * Gets remaining mute time for a Habbo. + */ + public int getMuteTimeRemaining(Habbo habbo) { + if (this.mutedHabbos.containsKey(habbo.getHabboInfo().getId())) { + return this.mutedHabbos.get(habbo.getHabboInfo().getId()) - Emulator.getIntUnixTimestamp(); + } + return 0; + } + + /** + * Gets the muted Habbos map. + */ + public TIntIntHashMap getMutedHabbos() { + return this.mutedHabbos; + } + + /** + * Applies flood mute to a Habbo. + */ + public void floodMuteHabbo(Habbo habbo, int timeOut) { + habbo.getHabboStats().mutedCount++; + timeOut += (timeOut * (int) Math.ceil(Math.pow(habbo.getHabboStats().mutedCount, 2))); + habbo.getHabboStats().chatCounter.set(0); + habbo.mute(timeOut, true); + } + + // ==================== CHAT METHODS ==================== + + /** + * Handles talking in the room. + */ + public void talk(Habbo habbo, RoomChatMessage roomChatMessage, RoomChatType chatType) { + this.talk(habbo, roomChatMessage, chatType, false); + } + + /** + * Handles talking in the room with wired ignore option. + */ + public void talk(final Habbo habbo, final RoomChatMessage roomChatMessage, RoomChatType chatType, + boolean ignoreWired) { + if (!habbo.getHabboStats().allowTalk()) { + return; + } + + if (habbo.getRoomUnit().isInvisible() && Emulator.getConfig() + .getBoolean("invisible.prevent.chat", false)) { + if (!CommandHandler.handleCommand(habbo.getClient(), + roomChatMessage.getUnfilteredMessage())) { + habbo.whisper(Emulator.getTexts().getValue("invisible.prevent.chat.error")); + } + + return; + } + + if (habbo.getHabboInfo().getCurrentRoom() != this.room) { + return; + } + + long millis = System.currentTimeMillis(); + if (HABBO_CHAT_DELAY) { + if (millis - habbo.getHabboStats().lastChat < 750) { + return; + } + } + habbo.getHabboStats().lastChat = millis; + + // Easter egg + if (roomChatMessage != null && Emulator.getConfig().getBoolean("easter_eggs.enabled") + && roomChatMessage.getMessage().equalsIgnoreCase("i am a pirate")) { + habbo.getHabboStats().chatCounter.addAndGet(1); + Emulator.getThreading().run(new YouAreAPirate(habbo, this.room)); + return; + } + + // Handle idle event + UserIdleEvent event = new UserIdleEvent(habbo, UserIdleEvent.IdleReason.TALKED, false); + Emulator.getPluginManager().fireEvent(event); + + if (!event.isCancelled()) { + if (!event.idle) { + this.room.unIdle(habbo); + } + } + + this.room.sendComposer(new RoomUserTypingComposer(habbo.getRoomUnit(), false).compose()); + + if (roomChatMessage == null || roomChatMessage.getMessage() == null + || roomChatMessage.getMessage().equals("")) { + return; + } + + // Check mute area + if (!habbo.hasPermission(Permission.ACC_NOMUTE) && (!MUTEAREA_CAN_WHISPER + || chatType != RoomChatType.WHISPER)) { + for (HabboItem area : this.room.getRoomSpecialTypes().getItemsOfType(InteractionMuteArea.class)) { + if (((InteractionMuteArea) area).inSquare(habbo.getRoomUnit().getCurrentLocation())) { + return; + } + } + } + + // Apply word filter + if (!this.wordFilterWords.isEmpty()) { + if (!habbo.hasPermission(Permission.ACC_CHAT_NO_FILTER)) { + for (String string : this.wordFilterWords) { + roomChatMessage.setMessage( + roomChatMessage.getMessage().replaceAll("(?i)" + Pattern.quote(string), "bobba")); + } + } + } + + // Check room/user mute + if (!habbo.hasPermission(Permission.ACC_NOMUTE)) { + if (this.room.isMuted() && !this.room.hasRights(habbo)) { + return; + } + + if (this.isMuted(habbo)) { + habbo.getClient().sendResponse(new MutedWhisperComposer( + this.mutedHabbos.get(habbo.getHabboInfo().getId()) - Emulator.getIntUnixTimestamp())); + return; + } + } + + // Handle commands and wired + if (chatType != RoomChatType.WHISPER) { + if (CommandHandler.handleCommand(habbo.getClient(), roomChatMessage.getUnfilteredMessage())) { + WiredManager.triggerUserSays(habbo.getHabboInfo().getCurrentRoom(), habbo.getRoomUnit(), roomChatMessage.getMessage()); + roomChatMessage.isCommand = true; + return; + } + + if (!ignoreWired) { + if (WiredManager.triggerUserSays(habbo.getHabboInfo().getCurrentRoom(), habbo.getRoomUnit(), roomChatMessage.getMessage())) { + habbo.getClient().sendResponse(new RoomUserWhisperComposer( + new RoomChatMessage(roomChatMessage.getMessage(), habbo, habbo, + roomChatMessage.getBubble()))); + return; + } + } + } + + // Flood protection + if (!habbo.hasPermission(Permission.ACC_CHAT_NO_FLOOD)) { + final int chatCounter = habbo.getHabboStats().chatCounter.addAndGet(1); + + if (chatCounter > 3) { + final boolean floodRights = Emulator.getConfig().getBoolean("flood.with.rights"); + final boolean hasRights = this.room.hasRights(habbo); + + if (floodRights || !hasRights) { + if (this.room.getChatProtection() == 0) { + this.floodMuteHabbo(habbo, this.muteTime); + return; + } else if (this.room.getChatProtection() == 1 && chatCounter > 4) { + this.floodMuteHabbo(habbo, this.muteTime); + return; + } else if (this.room.getChatProtection() == 2 && chatCounter > 5) { + this.floodMuteHabbo(habbo, this.muteTime); + return; + } + } + } + } + + // Build prefix messages + ServerMessage prefixMessage = null; + + if (Emulator.getPluginManager().isRegistered(UsernameTalkEvent.class, true)) { + UsernameTalkEvent usernameTalkEvent = Emulator.getPluginManager() + .fireEvent(new UsernameTalkEvent(habbo, roomChatMessage, chatType)); + if (usernameTalkEvent.hasCustomComposer()) { + prefixMessage = usernameTalkEvent.getCustomComposer(); + } + } + + if (prefixMessage == null) { + prefixMessage = roomChatMessage.getHabbo().getHabboInfo().getRank().hasPrefix() + ? new RoomUserNameChangedComposer(habbo, true).compose() : null; + } + ServerMessage clearPrefixMessage = + prefixMessage != null ? new RoomUserNameChangedComposer(habbo).compose() : null; + + Rectangle tentRectangle = this.room.getRoomSpecialTypes().tentAt( + habbo.getRoomUnit().getCurrentLocation()); + + // Trim message + String trimmedMessage = roomChatMessage.getMessage().replaceAll("\\s+$", ""); + + if (trimmedMessage.isEmpty()) { + trimmedMessage = " "; + } + + roomChatMessage.setMessage(trimmedMessage); + + // Send chat based on type + if (chatType == RoomChatType.WHISPER) { + this.handleWhisper(habbo, roomChatMessage, prefixMessage, clearPrefixMessage); + } else if (chatType == RoomChatType.TALK) { + this.handleTalk(habbo, roomChatMessage, prefixMessage, clearPrefixMessage, tentRectangle); + } else if (chatType == RoomChatType.SHOUT) { + this.handleShout(habbo, roomChatMessage, prefixMessage, clearPrefixMessage, tentRectangle); + } + + // Notify bots and talking furniture + if (chatType == RoomChatType.TALK || chatType == RoomChatType.SHOUT) { + this.notifyBots(roomChatMessage); + this.handleTalkingFurniture(habbo, roomChatMessage); + } + } + + /** + * Handles whisper chat. + */ + private void handleWhisper(Habbo habbo, RoomChatMessage roomChatMessage, + ServerMessage prefixMessage, ServerMessage clearPrefixMessage) { + if (roomChatMessage.getTargetHabbo() == null) { + return; + } + + RoomChatMessage staffChatMessage = new RoomChatMessage(roomChatMessage); + staffChatMessage.setMessage( + "To " + staffChatMessage.getTargetHabbo().getHabboInfo().getUsername() + ": " + + staffChatMessage.getMessage()); + + final ServerMessage message = new RoomUserWhisperComposer(roomChatMessage).compose(); + final ServerMessage staffMessage = new RoomUserWhisperComposer(staffChatMessage).compose(); + + for (Habbo h : this.room.getHabbos()) { + if (h == roomChatMessage.getTargetHabbo() || h == habbo) { + if (!h.getHabboStats().userIgnored(habbo.getHabboInfo().getId())) { + if (prefixMessage != null) { + h.getClient().sendResponse(prefixMessage); + } + h.getClient().sendResponse(message); + + if (clearPrefixMessage != null) { + h.getClient().sendResponse(clearPrefixMessage); + } + } + + continue; + } + if (h.hasPermission(Permission.ACC_SEE_WHISPERS)) { + h.getClient().sendResponse(staffMessage); + } + } + } + + /** + * Handles normal talk. + */ + private void handleTalk(Habbo habbo, RoomChatMessage roomChatMessage, + ServerMessage prefixMessage, ServerMessage clearPrefixMessage, Rectangle tentRectangle) { + ServerMessage message = new RoomUserTalkComposer(roomChatMessage).compose(); + boolean noChatLimit = habbo.hasPermission(Permission.ACC_CHAT_NO_LIMIT); + int chatDistance = this.room.getChatDistance(); + + for (Habbo h : this.room.getHabbos()) { + if ((h.getRoomUnit().getCurrentLocation().distance(habbo.getRoomUnit().getCurrentLocation()) + <= chatDistance || h.equals(habbo) || this.room.hasRights(h) || noChatLimit) && ( + tentRectangle == null || RoomLayout.tileInSquare(tentRectangle, + h.getRoomUnit().getCurrentLocation()))) { + if (!h.getHabboStats().userIgnored(habbo.getHabboInfo().getId())) { + if (prefixMessage != null && !h.getHabboStats().preferOldChat) { + h.getClient().sendResponse(prefixMessage); + } + h.getClient().sendResponse(message); + if (clearPrefixMessage != null && !h.getHabboStats().preferOldChat) { + h.getClient().sendResponse(clearPrefixMessage); + } + + // Turn head toward speaker if conditions are met + if (!h.equals(habbo)) { + RoomUnit roomUnit = h.getRoomUnit(); + if (!roomUnit.isWalking() && !roomUnit.hasStatus(RoomUnitStatus.MOVE) + && !roomUnit.hasStatus(RoomUnitStatus.LAY) && !roomUnit.isIdle() + && !roomUnit.isInvisible()) { + RoomUserRotation targetRotation = RoomUserRotation.values()[ + Rotation.Calculate(roomUnit.getX(), roomUnit.getY(), + habbo.getRoomUnit().getX(), habbo.getRoomUnit().getY())]; + // Only turn head if speaker is within peripheral vision (1 rotation step) + if (RoomUserRotation.rotationDistance(roomUnit.getBodyRotation().getValue(), + targetRotation.getValue()) <= 1) { + roomUnit.setHeadRotation(targetRotation); + roomUnit.statusUpdate(true); + + // Schedule head reset after 2 seconds + Emulator.getThreading().run(() -> { + if (roomUnit.isInRoom() && !roomUnit.isWalking() && !roomUnit.isIdle()) { + roomUnit.setHeadRotation(roomUnit.getBodyRotation()); + roomUnit.statusUpdate(true); + } + }, 2000); + } + } + } + } + continue; + } + // Staff should be able to see the tent chat anyhow + this.showTentChatMessageOutsideTentIfPermitted(h, roomChatMessage, tentRectangle); + } + } + + /** + * Handles shout chat. + */ + private void handleShout(Habbo habbo, RoomChatMessage roomChatMessage, + ServerMessage prefixMessage, ServerMessage clearPrefixMessage, Rectangle tentRectangle) { + ServerMessage message = new RoomUserShoutComposer(roomChatMessage).compose(); + + for (Habbo h : this.room.getHabbos()) { + if (!h.getHabboStats().userIgnored(habbo.getHabboInfo().getId()) && (tentRectangle == null + || RoomLayout.tileInSquare(tentRectangle, h.getRoomUnit().getCurrentLocation()))) { + if (prefixMessage != null && !h.getHabboStats().preferOldChat) { + h.getClient().sendResponse(prefixMessage); + } + h.getClient().sendResponse(message); + if (clearPrefixMessage != null && !h.getHabboStats().preferOldChat) { + h.getClient().sendResponse(clearPrefixMessage); + } + + // Turn head toward speaker if conditions are met + if (!h.equals(habbo)) { + RoomUnit roomUnit = h.getRoomUnit(); + if (!roomUnit.isWalking() && !roomUnit.hasStatus(RoomUnitStatus.MOVE) + && !roomUnit.hasStatus(RoomUnitStatus.LAY) && !roomUnit.isIdle() + && !roomUnit.isInvisible()) { + RoomUserRotation targetRotation = RoomUserRotation.values()[ + Rotation.Calculate(roomUnit.getX(), roomUnit.getY(), + habbo.getRoomUnit().getX(), habbo.getRoomUnit().getY())]; + // Only turn head if speaker is within peripheral vision (1 rotation step) + if (RoomUserRotation.rotationDistance(roomUnit.getBodyRotation().getValue(), + targetRotation.getValue()) <= 1) { + roomUnit.setHeadRotation(targetRotation); + roomUnit.statusUpdate(true); + + // Schedule head reset after 2 seconds + Emulator.getThreading().run(() -> { + if (roomUnit.isInRoom() && !roomUnit.isWalking() && !roomUnit.isIdle()) { + roomUnit.setHeadRotation(roomUnit.getBodyRotation()); + roomUnit.statusUpdate(true); + } + }, 2000); + } + } + } + continue; + } + // Staff should be able to see the tent chat anyhow + this.showTentChatMessageOutsideTentIfPermitted(h, roomChatMessage, tentRectangle); + } + } + + /** + * Shows tent chat to staff outside the tent. + */ + public void showTentChatMessageOutsideTentIfPermitted(Habbo receivingHabbo, + RoomChatMessage roomChatMessage, Rectangle tentRectangle) { + if (receivingHabbo != null && receivingHabbo.hasPermission(Permission.ACC_SEE_TENTCHAT) + && tentRectangle != null && !RoomLayout.tileInSquare(tentRectangle, + receivingHabbo.getRoomUnit().getCurrentLocation())) { + RoomChatMessage staffChatMessage = new RoomChatMessage(roomChatMessage); + staffChatMessage.setMessage( + "[" + Emulator.getTexts().getValue("hotel.room.tent.prefix") + "] " + + staffChatMessage.getMessage()); + final ServerMessage staffMessage = new RoomUserWhisperComposer(staffChatMessage).compose(); + receivingHabbo.getClient().sendResponse(staffMessage); + } + } + + /** + * Notifies bots of a chat message. + */ + private void notifyBots(RoomChatMessage roomChatMessage) { + synchronized (this.room.getUnitManager().getCurrentBots()) { + TIntObjectIterator botIterator = this.room.getUnitManager().getCurrentBots().iterator(); + + for (int i = this.room.getUnitManager().getCurrentBots().size(); i-- > 0; ) { + try { + botIterator.advance(); + Bot bot = botIterator.value(); + bot.onUserSay(roomChatMessage); + + } catch (Exception e) { + LOGGER.error("Caught exception", e); + break; + } + } + } + } + + /** + * Handles talking furniture responses. + */ + private void handleTalkingFurniture(Habbo habbo, RoomChatMessage roomChatMessage) { + if (roomChatMessage.getBubble().triggersTalkingFurniture()) { + THashSet items = this.room.getRoomSpecialTypes().getItemsOfType( + InteractionTalkingFurniture.class); + + for (HabboItem item : items) { + if (this.room.getLayout().getTile(item.getX(), item.getY()) + .distance(habbo.getRoomUnit().getCurrentLocation()) <= Emulator.getConfig() + .getInt("furniture.talking.range")) { + int count = Emulator.getConfig() + .getInt(item.getBaseItem().getName() + ".message.count", 0); + + if (count > 0) { + int randomValue = Emulator.getRandom().nextInt(count + 1); + + RoomChatMessage itemMessage = new RoomChatMessage(Emulator.getTexts() + .getValue(item.getBaseItem().getName() + ".message." + randomValue, + item.getBaseItem().getName() + ".message." + randomValue + " not found!"), + habbo, RoomChatMessageBubbles.getBubble(Emulator.getConfig() + .getInt(item.getBaseItem().getName() + ".message.bubble", + RoomChatMessageBubbles.PARROT.getType()))); + + this.room.sendComposer(new RoomUserTalkComposer(itemMessage).compose()); + + try { + item.onClick(habbo.getClient(), this.room, new Object[0]); + item.setExtradata("1"); + this.room.updateItemState(item); + + Emulator.getThreading().run(() -> { + item.setExtradata("0"); + this.room.updateItemState(item); + }, 2000); + + break; + } catch (Exception e) { + LOGGER.error("Caught exception", e); + } + } + } + } + } + } + + // ==================== DISPOSAL ==================== + + /** + * Clears chat manager state. + */ + public void clear() { + synchronized (this.wordFilterWords) { + this.wordFilterWords.clear(); + } + synchronized (this.mutedHabbos) { + this.mutedHabbos.clear(); + } + } + + /** + * Disposes the chat manager. + */ + public void dispose() { + this.clear(); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatMessage.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatMessage.java index 3386a3ea..3ea45086 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatMessage.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatMessage.java @@ -30,15 +30,14 @@ public class RoomChatMessage implements Runnable, ISerialize, DatabaseLoggable { public int roomId; public boolean isCommand = false; public boolean filtered = false; - private final int roomUnitId; + private int roomUnitId; private String message; - private final String unfilteredMessage; + private String unfilteredMessage; private int timestamp = 0; private RoomChatMessageBubbles bubble; private Habbo targetHabbo; private byte emotion; - private String RoomChatColour; - //Added ChatColor + private String RoomChatColour; // Added Chatcolor public RoomChatMessage(MessageHandler message) { if (message.packet.getMessageId() == Incoming.RoomUserWhisperEvent) { @@ -48,38 +47,28 @@ public class RoomChatMessage implements Runnable, ISerialize, DatabaseLoggable { } else { this.message = message.packet.readString(); } - - this.habbo = message.client.getHabbo(); - this.roomUnitId = this.habbo.getRoomUnit().getId(); - - RoomChatMessageBubbles userBubble = this.habbo.getHabboStats().chatColor; - - int bubbleId = message.packet.readInt(); - try { - this.bubble = RoomChatMessageBubbles.getBubble(bubbleId); + this.bubble = RoomChatMessageBubbles.getBubble(message.packet.readInt()); } catch (Exception e) { this.bubble = RoomChatMessageBubbles.NORMAL; } - if (userBubble != null && this.bubble.isOverridable()) { - this.bubble = userBubble; - } - - this.RoomChatColour = message.packet.readString(); - if (!message.client.getHabbo().hasPermission(Permission.ACC_ANYCHATCOLOR)) { for (Integer i : RoomChatMessage.BANNED_BUBBLES) { - if (i.equals(this.bubble.getType())) { + if (i == this.bubble.getType()) { this.bubble = RoomChatMessageBubbles.NORMAL; break; } } } + this.habbo = message.client.getHabbo(); + this.roomUnitId = this.habbo.getRoomUnit().getId(); this.unfilteredMessage = this.message; this.timestamp = Emulator.getIntUnixTimestamp(); + this.checkEmotion(); + this.filter(); } @@ -209,7 +198,7 @@ public class RoomChatMessage implements Runnable, ISerialize, DatabaseLoggable { message.appendInt(this.getEmotion()); message.appendInt(this.getBubble().getType()); message.appendInt(0); - message.appendString(this.RoomChatColour); //Added packet for room chat + message.appendString(this.RoomChatColour); //Added packet for room chat message.appendInt(this.getMessage().length()); } catch (Exception e) { LOGGER.error("Caught exception", e); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatMessageBubbles.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatMessageBubbles.java index e05f7ae3..05093e8e 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatMessageBubbles.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomChatMessageBubbles.java @@ -1,94 +1,177 @@ package com.eu.habbo.habbohotel.rooms; -public enum RoomChatMessageBubbles { - NORMAL(0, "", true, true), - ALERT(1, "", true, true), - BOT(2, "", true, true), - RED(3, "", true, true), - BLUE(4, "", true, true), - YELLOW(5, "", true, true), - GREEN(6, "", true, true), - BLACK(7, "", true, true), - FORTUNE_TELLER(8, "", false, false), - ZOMBIE_ARM(9, "", true, false), - SKELETON(10, "", true, false), - LIGHT_BLUE(11, "", true, true), - PINK(12, "", true, true), - PURPLE(13, "", true, true), - DARK_YEWLLOW(14, "", true, true), - DARK_BLUE(15, "", true, true), - HEARTS(16, "", true, true), - ROSES(17, "", true, true), - UNUSED(18, "", true, true), //? - PIG(19, "", true, true), - DOG(20, "", true, true), - BLAZE_IT(21, "", true, true), - DRAGON(22, "", true, true), - STAFF(23, "", false, true), - BATS(24, "", true, false), - MESSENGER(25, "", true, false), - STEAMPUNK(26, "", true, false), - THUNDER(27, "", true, true), - PARROT(28, "", false, false), - PIRATE(29, "", false, false), - BOT_GUIDE(30, "", true, true), - BOT_RENTABLE(31, "", true, true), - SCARY_THING(32, "", true, false), - FRANK(33, "", true, false), - WIRED(34, "", false, true), - GOAT(35, "", true, false), - SANTA(36, "", true, false), - AMBASSADOR(37, "acc_ambassador", false, true), - RADIO(38, "", true, false), - UNKNOWN_39(39, "", true, false), - UNKNOWN_40(40, "", true, false), - UNKNOWN_41(41, "", true, false), - UNKNOWN_42(42, "", true, false), - UNKNOWN_43(43, "", true, false), - UNKNOWN_44(44, "", true, false), - UNKNOWN_45(45, "", true, false), - UNKNOWN_46(46, "", true, false), - UNKNOWN_47(47, "", true, false), - UNKNOWN_48(48, "", true, false), - UNKNOWN_49(49, "", true, false), - UNKNOWN_50(50, "", true, false), - UNKNOWN_51(51, "", true, false), - UNKNOWN_52(52, "", true, false), - UNKNOWN_53(53, "", true, false); +import java.util.HashMap; +import java.util.Map; + +public class RoomChatMessageBubbles { + private static final Map BUBBLES = new HashMap<>(); + + public static final RoomChatMessageBubbles NORMAL = new RoomChatMessageBubbles(0, "NORMAL", "", true, true); + public static final RoomChatMessageBubbles ALERT = new RoomChatMessageBubbles(1, "ALERT", "", true, true); + public static final RoomChatMessageBubbles BOT = new RoomChatMessageBubbles(2, "BOT", "", true, true); + public static final RoomChatMessageBubbles RED = new RoomChatMessageBubbles(3, "RED", "", true, true); + public static final RoomChatMessageBubbles BLUE = new RoomChatMessageBubbles(4, "BLUE", "", true, true); + public static final RoomChatMessageBubbles YELLOW = new RoomChatMessageBubbles(5, "YELLOW", "", true, true); + public static final RoomChatMessageBubbles GREEN = new RoomChatMessageBubbles(6, "GREEN", "", true, true); + public static final RoomChatMessageBubbles BLACK = new RoomChatMessageBubbles(7, "BLACK", "", true, true); + public static final RoomChatMessageBubbles FORTUNE_TELLER = new RoomChatMessageBubbles(8, "FORTUNE_TELLER", "", false, false); + public static final RoomChatMessageBubbles ZOMBIE_ARM = new RoomChatMessageBubbles(9, "ZOMBIE_ARM", "", true, false); + public static final RoomChatMessageBubbles SKELETON = new RoomChatMessageBubbles(10, "SKELETON", "", true, false); + public static final RoomChatMessageBubbles LIGHT_BLUE = new RoomChatMessageBubbles(11, "LIGHT_BLUE", "", true, true); + public static final RoomChatMessageBubbles PINK = new RoomChatMessageBubbles(12, "PINK", "", true, true); + public static final RoomChatMessageBubbles PURPLE = new RoomChatMessageBubbles(13, "PURPLE", "", true, true); + public static final RoomChatMessageBubbles DARK_YELLOW = new RoomChatMessageBubbles(14, "DARK_YELLOW", "", true, true); + public static final RoomChatMessageBubbles DARK_BLUE = new RoomChatMessageBubbles(15, "DARK_BLUE", "", true, true); + public static final RoomChatMessageBubbles HEARTS = new RoomChatMessageBubbles(16, "HEARTS", "", true, true); + public static final RoomChatMessageBubbles ROSES = new RoomChatMessageBubbles(17, "ROSES", "", true, true); + public static final RoomChatMessageBubbles UNUSED = new RoomChatMessageBubbles(18, "UNUSED", "", true, true); + public static final RoomChatMessageBubbles PIG = new RoomChatMessageBubbles(19, "PIG", "", true, true); + public static final RoomChatMessageBubbles DOG = new RoomChatMessageBubbles(20, "DOG", "", true, true); + public static final RoomChatMessageBubbles BLAZE_IT = new RoomChatMessageBubbles(21, "BLAZE_IT", "", true, true); + public static final RoomChatMessageBubbles DRAGON = new RoomChatMessageBubbles(22, "DRAGON", "", true, true); + public static final RoomChatMessageBubbles STAFF = new RoomChatMessageBubbles(23, "STAFF", "", false, true); + public static final RoomChatMessageBubbles BATS = new RoomChatMessageBubbles(24, "BATS", "", true, false); + public static final RoomChatMessageBubbles MESSENGER = new RoomChatMessageBubbles(25, "MESSENGER", "", true, false); + public static final RoomChatMessageBubbles STEAMPUNK = new RoomChatMessageBubbles(26, "STEAMPUNK", "", true, false); + public static final RoomChatMessageBubbles THUNDER = new RoomChatMessageBubbles(27, "THUNDER", "", true, true); + public static final RoomChatMessageBubbles PARROT = new RoomChatMessageBubbles(28, "PARROT", "", false, false); + public static final RoomChatMessageBubbles PIRATE = new RoomChatMessageBubbles(29, "PIRATE", "", false, false); + public static final RoomChatMessageBubbles BOT_GUIDE = new RoomChatMessageBubbles(30, "BOT_GUIDE", "", true, true); + public static final RoomChatMessageBubbles BOT_RENTABLE = new RoomChatMessageBubbles(31, "BOT_RENTABLE", "", true, true); + public static final RoomChatMessageBubbles SCARY_THING = new RoomChatMessageBubbles(32, "SCARY_THING", "", true, false); + public static final RoomChatMessageBubbles FRANK = new RoomChatMessageBubbles(33, "FRANK", "", true, false); + public static final RoomChatMessageBubbles WIRED = new RoomChatMessageBubbles(34, "WIRED", "", false, true); + public static final RoomChatMessageBubbles GOAT = new RoomChatMessageBubbles(35, "GOAT", "", true, false); + public static final RoomChatMessageBubbles SANTA = new RoomChatMessageBubbles(36, "SANTA", "", true, false); + public static final RoomChatMessageBubbles AMBASSADOR = new RoomChatMessageBubbles(37, "AMBASSADOR", "acc_ambassador", false, true); + public static final RoomChatMessageBubbles RADIO = new RoomChatMessageBubbles(38, "RADIO", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_39 = new RoomChatMessageBubbles(39, "UNKNOWN_39", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_40 = new RoomChatMessageBubbles(40, "UNKNOWN_40", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_41 = new RoomChatMessageBubbles(41, "UNKNOWN_41", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_42 = new RoomChatMessageBubbles(42, "UNKNOWN_42", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_43 = new RoomChatMessageBubbles(43, "UNKNOWN_43", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_44 = new RoomChatMessageBubbles(44, "UNKNOWN_44", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_45 = new RoomChatMessageBubbles(45, "UNKNOWN_45", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_46 = new RoomChatMessageBubbles(45, "UNKNOWN_46", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_47 = new RoomChatMessageBubbles(45, "UNKNOWN_47", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_48 = new RoomChatMessageBubbles(45, "UNKNOWN_48", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_49 = new RoomChatMessageBubbles(45, "UNKNOWN_49", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_50 = new RoomChatMessageBubbles(45, "UNKNOWN_50", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_51 = new RoomChatMessageBubbles(45, "UNKNOWN_51", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_52 = new RoomChatMessageBubbles(45, "UNKNOWN_52", "", true, false); + public static final RoomChatMessageBubbles UNKNOWN_53 = new RoomChatMessageBubbles(45, "UNKNOWN_53", "", true, false); + + + static { + registerBubble(NORMAL); + registerBubble(ALERT); + registerBubble(BOT); + registerBubble(RED); + registerBubble(BLUE); + registerBubble(YELLOW); + registerBubble(GREEN); + registerBubble(BLACK); + registerBubble(FORTUNE_TELLER); + registerBubble(ZOMBIE_ARM); + registerBubble(SKELETON); + registerBubble(LIGHT_BLUE); + registerBubble(PINK); + registerBubble(PURPLE); + registerBubble(DARK_YELLOW); + registerBubble(DARK_BLUE); + registerBubble(HEARTS); + registerBubble(ROSES); + registerBubble(UNUSED); + registerBubble(PIG); + registerBubble(DOG); + registerBubble(BLAZE_IT); + registerBubble(DRAGON); + registerBubble(STAFF); + registerBubble(BATS); + registerBubble(MESSENGER); + registerBubble(STEAMPUNK); + registerBubble(THUNDER); + registerBubble(PARROT); + registerBubble(PIRATE); + registerBubble(BOT_GUIDE); + registerBubble(BOT_RENTABLE); + registerBubble(SCARY_THING); + registerBubble(FRANK); + registerBubble(WIRED); + registerBubble(GOAT); + registerBubble(SANTA); + registerBubble(AMBASSADOR); + registerBubble(RADIO); + registerBubble(UNKNOWN_39); + registerBubble(UNKNOWN_40); + registerBubble(UNKNOWN_41); + registerBubble(UNKNOWN_42); + registerBubble(UNKNOWN_43); + registerBubble(UNKNOWN_44); + registerBubble(UNKNOWN_45); + registerBubble(UNKNOWN_46); + registerBubble(UNKNOWN_47); + registerBubble(UNKNOWN_48); + registerBubble(UNKNOWN_49); + registerBubble(UNKNOWN_50); + registerBubble(UNKNOWN_51); + registerBubble(UNKNOWN_52); + registerBubble(UNKNOWN_53); + } private final int type; + private final String name; private final String permission; private final boolean overridable; private final boolean triggersTalkingFurniture; - RoomChatMessageBubbles(int type, String permission, boolean overridable, boolean triggersTalkingFurniture) { + private RoomChatMessageBubbles(int type, String name, String permission, boolean overridable, boolean triggersTalkingFurniture) { this.type = type; + this.name = name; this.permission = permission; this.overridable = overridable; this.triggersTalkingFurniture = triggersTalkingFurniture; } - public static RoomChatMessageBubbles getBubble(int bubbleId) { - try { - return values()[bubbleId]; - } catch (Exception e) { - return NORMAL; - } + public static RoomChatMessageBubbles getBubble(int id) { + return BUBBLES.getOrDefault(id, NORMAL); + } + + private static void registerBubble(RoomChatMessageBubbles bubble) { + BUBBLES.put(bubble.getType(), bubble); } public int getType() { - return this.type; + return type; + } + + public String name() { + return name; } public String getPermission() { - return this.permission; + return permission; } public boolean isOverridable() { - return this.overridable; + return overridable; } public boolean triggersTalkingFurniture() { - return this.triggersTalkingFurniture; + return triggersTalkingFurniture; } -} + + public static void addDynamicBubble(int type, String name, String permission, boolean overridable, boolean triggersTalkingFurniture) { + registerBubble(new RoomChatMessageBubbles(type, name, permission, overridable, triggersTalkingFurniture)); + } + + public static void removeDynamicBubbles() { + synchronized (BUBBLES) { + BUBBLES.entrySet().removeIf(entry -> entry.getKey() > 45); + } + } + + public static RoomChatMessageBubbles[] values() { + return BUBBLES.values().toArray(new RoomChatMessageBubbles[0]); + } +} \ No newline at end of file diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCycleManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCycleManager.java new file mode 100644 index 00000000..be8755fd --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomCycleManager.java @@ -0,0 +1,486 @@ +package com.eu.habbo.habbohotel.rooms; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.achievements.AchievementManager; +import com.eu.habbo.habbohotel.bots.Bot; +import com.eu.habbo.habbohotel.items.ICycleable; +import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.pets.Pet; +import com.eu.habbo.habbohotel.users.DanceType; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.messages.outgoing.rooms.RoomAccessDeniedComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUnitIdleComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserIgnoredComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer; +import com.eu.habbo.plugin.events.users.UserExitRoomEvent; +import gnu.trove.iterator.TIntObjectIterator; +import gnu.trove.map.TIntObjectMap; +import gnu.trove.procedure.TIntObjectProcedure; +import gnu.trove.set.hash.THashSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manages the room cycle/tick logic. + * Handles the periodic updates for habbos, bots, pets, and other room entities. + */ +public class RoomCycleManager { + private static final Logger LOGGER = LoggerFactory.getLogger(RoomCycleManager.class); + + private final Room room; + private boolean cycleOdd; + private long cycleTimestamp; + private int idleCycles; + private int idleHostingCycles; + private long rollerCycle = System.currentTimeMillis(); + + public RoomCycleManager(Room room) { + this.room = room; + this.cycleOdd = false; + this.cycleTimestamp = 0; + this.idleCycles = 0; + this.idleHostingCycles = 0; + } + + /** + * Gets the current cycle timestamp. + */ + public long getCycleTimestamp() { + return this.cycleTimestamp; + } + + /** + * Resets idle cycles when room becomes active. + */ + public void resetIdleCycles() { + this.idleCycles = 0; + } + + /** + * Main cycle method - called every 500ms. + * Processes all room entities and scheduled tasks. + */ + public void cycle() { + this.cycleOdd = !this.cycleOdd; + this.cycleTimestamp = System.currentTimeMillis(); + final boolean[] foundRightHolder = {false}; + + boolean loaded = this.room.isLoaded(); + this.room.tileCache.clear(); + + if (loaded) { + processScheduledTasks(); + processCycleTasks(); + processDecoHosting(); + + if (!this.room.getCurrentHabbos().isEmpty()) { + this.idleCycles = 0; + + THashSet updatedUnit = new THashSet<>(); + ArrayList toKick = new ArrayList<>(); + + final long millis = System.currentTimeMillis(); + + // Process all habbos + for (Habbo habbo : this.room.getCurrentHabbos().values()) { + if (!foundRightHolder[0]) { + foundRightHolder[0] = habbo.getRoomUnit().getRightsLevel() != RoomRightLevels.NONE; + } + + processHabboHandItem(habbo, millis); + processHabboEffect(habbo, millis); + processHabboKick(habbo); + processHabboIdle(habbo, toKick); + processHabboMute(habbo); + processHabboChatCounter(habbo); + + if (this.cycleRoomUnit(habbo.getRoomUnit(), RoomUnitType.USER)) { + updatedUnit.add(habbo.getRoomUnit()); + } + } + + // Kick idle habbos + for (Habbo habbo : toKick) { + Emulator.getGameEnvironment().getRoomManager().leaveRoom(habbo, this.room); + } + + // Process bots + processBots(updatedUnit); + + // Process pets + processPets(updatedUnit); + + // Process rollers + processRollers(updatedUnit); + + // Send status updates + if (!updatedUnit.isEmpty()) { + this.room.sendComposer(new RoomUserStatusComposer(updatedUnit, true).compose()); + } + + // Cycle trax manager + if (this.room.getTraxManager() != null) { + this.room.getTraxManager().cycle(); + } + } else { + // Room is empty - check for disposal + if (this.idleCycles < 60) { + this.idleCycles++; + } else { + this.room.dispose(); + } + } + } + + // Process habbo queue + processHabboQueue(foundRightHolder[0]); + + // Send scheduled composers + processScheduledComposers(); + } + + /** + * Processes scheduled tasks. + */ + private void processScheduledTasks() { + if (!this.room.scheduledTasks.isEmpty()) { + Set tasks = this.room.scheduledTasks; + this.room.scheduledTasks = ConcurrentHashMap.newKeySet(); + + for (Runnable runnable : tasks) { + Emulator.getThreading().run(runnable); + } + } + } + + /** + * Processes cycleable tasks. + */ + private void processCycleTasks() { + if (this.room.getRoomSpecialTypes() != null) { + for (ICycleable task : this.room.getRoomSpecialTypes().getCycleTasks()) { + task.cycle(this.room); + } + } + } + + /** + * Processes deco hosting achievement. + */ + private void processDecoHosting() { + if (Emulator.getConfig().getBoolean("hotel.rooms.deco_hosting")) { + if (this.idleHostingCycles < 120) { + this.idleHostingCycles++; + } else { + this.idleHostingCycles = 0; + + int amount = (int) this.room.getCurrentHabbos().values().stream() + .filter(habbo -> habbo.getHabboInfo().getId() != this.room.getOwnerId()).count(); + if (amount > 0) { + AchievementManager.progressAchievement(this.room.getOwnerId(), + Emulator.getGameEnvironment().getAchievementManager() + .getAchievement("RoomDecoHosting"), amount); + } + } + } + } + + /** + * Processes habbo hand item expiry. + */ + private void processHabboHandItem(Habbo habbo, long millis) { + if (Room.HAND_ITEM_TIME > 0 && habbo.getRoomUnit().getHandItem() > 0 + && millis - habbo.getRoomUnit().getHandItemTimestamp() > (Room.HAND_ITEM_TIME * 1000L)) { + this.room.giveHandItem(habbo, 0); + } + } + + /** + * Processes habbo effect expiry. + */ + private void processHabboEffect(Habbo habbo, long millis) { + if (habbo.getRoomUnit().getEffectId() > 0 && millis / 1000 > habbo.getRoomUnit().getEffectEndTimestamp()) { + this.room.giveEffect(habbo, 0, -1); + } + } + + /** + * Processes habbo kick status. + */ + private void processHabboKick(Habbo habbo) { + if (habbo.getRoomUnit().isKicked) { + habbo.getRoomUnit().kickCount++; + + if (habbo.getRoomUnit().kickCount >= 5) { + final Room room = this.room; + this.room.scheduledTasks.add( + () -> Emulator.getGameEnvironment().getRoomManager().leaveRoom(habbo, room)); + } + } + } + + /** + * Processes habbo idle status. + */ + private void processHabboIdle(Habbo habbo, ArrayList toKick) { + if (Emulator.getConfig().getBoolean("hotel.rooms.auto.idle")) { + if (!habbo.getRoomUnit().isIdle()) { + habbo.getRoomUnit().increaseIdleTimer(); + + if (habbo.getRoomUnit().isIdle()) { + boolean danceIsNone = (habbo.getRoomUnit().getDanceType() == DanceType.NONE); + if (danceIsNone) { + this.room.sendComposer(new RoomUnitIdleComposer(habbo.getRoomUnit()).compose()); + } + if (danceIsNone && !Emulator.getConfig() + .getBoolean("hotel.roomuser.idle.not_dancing.ignore.wired_idle")) { + WiredManager.triggerUserIdles(this.room, habbo.getRoomUnit()); + } + } + } else { + habbo.getRoomUnit().increaseIdleTimer(); + + if (!this.room.isOwner(habbo) + && habbo.getRoomUnit().getIdleTimer() >= Room.IDLE_CYCLES_KICK) { + UserExitRoomEvent event = new UserExitRoomEvent(habbo, + UserExitRoomEvent.UserExitRoomReason.KICKED_IDLE); + Emulator.getPluginManager().fireEvent(event); + + if (!event.isCancelled()) { + toKick.add(habbo); + } + } + } + } + } + + /** + * Processes habbo mute status. + */ + private void processHabboMute(Habbo habbo) { + if (habbo.getHabboStats().mutedBubbleTracker && habbo.getHabboStats().allowTalk()) { + habbo.getHabboStats().mutedBubbleTracker = false; + this.room.sendComposer( + new RoomUserIgnoredComposer(habbo, RoomUserIgnoredComposer.UNIGNORED).compose()); + } + } + + /** + * Processes habbo chat counter. + */ + private void processHabboChatCounter(Habbo habbo) { + // Subtract 1 from the chatCounter every odd cycle, which is every (500ms * 2). + if (this.cycleOdd && habbo.getHabboStats().chatCounter.get() > 0) { + habbo.getHabboStats().chatCounter.decrementAndGet(); + } + } + + /** + * Processes all bots in the room. + */ + private void processBots(THashSet updatedUnit) { + TIntObjectMap currentBots = this.room.getCurrentBots(); + if (currentBots.isEmpty()) { + return; + } + + TIntObjectIterator botIterator = currentBots.iterator(); + for (int i = currentBots.size(); i-- > 0; ) { + try { + final Bot bot; + try { + botIterator.advance(); + bot = botIterator.value(); + } catch (Exception e) { + break; + } + + if (!this.room.isAllowBotsWalk() && bot.getRoomUnit().isWalking()) { + bot.getRoomUnit().stopWalking(); + updatedUnit.add(bot.getRoomUnit()); + continue; + } + + bot.cycle(this.room.isAllowBotsWalk()); + + if (this.cycleRoomUnit(bot.getRoomUnit(), RoomUnitType.BOT)) { + updatedUnit.add(bot.getRoomUnit()); + } + + } catch (NoSuchElementException e) { + LOGGER.error("Caught exception", e); + break; + } + } + } + + /** + * Processes all pets in the room. + */ + private void processPets(THashSet updatedUnit) { + TIntObjectMap currentPets = this.room.getCurrentPets(); + if (currentPets.isEmpty() || !this.room.isAllowBotsWalk()) { + return; + } + + TIntObjectIterator petIterator = currentPets.iterator(); + for (int i = currentPets.size(); i-- > 0; ) { + try { + petIterator.advance(); + } catch (NoSuchElementException e) { + LOGGER.error("Caught exception", e); + break; + } + + Pet pet = petIterator.value(); + if (this.cycleRoomUnit(pet.getRoomUnit(), RoomUnitType.PET)) { + updatedUnit.add(pet.getRoomUnit()); + } + + pet.cycle(); + + if (pet.packetUpdate) { + updatedUnit.add(pet.getRoomUnit()); + pet.packetUpdate = false; + } + + if (pet.getRoomUnit().isWalking() && pet.getRoomUnit().getPath().size() == 1 + && pet.getRoomUnit().hasStatus(RoomUnitStatus.GESTURE)) { + pet.getRoomUnit().removeStatus(RoomUnitStatus.GESTURE); + updatedUnit.add(pet.getRoomUnit()); + } + } + } + + /** + * Processes roller cycle. + */ + private void processRollers(THashSet updatedUnit) { + int rollerSpeed = this.room.getRollerSpeed(); + if (rollerSpeed != -1 && this.rollerCycle >= rollerSpeed) { + this.rollerCycle = 0; + this.room.getRollerManager().processRollerCycle(updatedUnit, this.cycleTimestamp); + } else { + this.rollerCycle++; + } + } + + /** + * Processes the habbo queue. + */ + private void processHabboQueue(boolean foundRightHolder) { + TIntObjectMap habboQueue = this.room.getHabboQueue(); + synchronized (habboQueue) { + if (!habboQueue.isEmpty() && !foundRightHolder) { + final Room room = this.room; + habboQueue.forEachEntry(new TIntObjectProcedure() { + @Override + public boolean execute(int a, Habbo b) { + if (b.isOnline()) { + if (b.getHabboInfo().getRoomQueueId() == room.getId()) { + b.getClient().sendResponse(new RoomAccessDeniedComposer("")); + } + } + return true; + } + }); + habboQueue.clear(); + } + } + } + + /** + * Processes scheduled composers. + */ + private void processScheduledComposers() { + if (!this.room.scheduledComposers.isEmpty()) { + for (ServerMessage message : this.room.scheduledComposers) { + this.room.sendComposer(message); + } + this.room.scheduledComposers.clear(); + } + } + + /** + * Cycles a room unit (handles movement, sitting, laying, etc.) + * @param unit The room unit to cycle + * @param type The type of room unit + * @return true if the unit needs a status update + */ + public boolean cycleRoomUnit(RoomUnit unit, RoomUnitType type) { + boolean update = unit.needsStatusUpdate(); + + if (unit.hasStatus(RoomUnitStatus.SIGN)) { + this.room.sendComposer(new RoomUserStatusComposer(unit).compose()); + unit.removeStatus(RoomUnitStatus.SIGN); + } + + if (unit.isWalking() && unit.getPath() != null && !unit.getPath().isEmpty()) { + if (!unit.cycle(this.room)) { + return true; + } + } else { + if (unit.hasStatus(RoomUnitStatus.MOVE) && !unit.animateWalk) { + unit.removeStatus(RoomUnitStatus.MOVE); + update = true; + } + + if (!unit.isWalking() && !unit.cmdSit) { + RoomTile thisTile = this.room.getLayout().getTile(unit.getX(), unit.getY()); + HabboItem topItem = this.room.getTallestChair(thisTile); + + if (topItem == null || !topItem.getBaseItem().allowSit()) { + if (unit.hasStatus(RoomUnitStatus.SIT)) { + unit.removeStatus(RoomUnitStatus.SIT); + update = true; + } + } else if (thisTile.state == RoomTileState.SIT && (!unit.hasStatus(RoomUnitStatus.SIT) + || unit.sitUpdate)) { + this.room.dance(unit, DanceType.NONE); + unit.setStatus(RoomUnitStatus.SIT, (Item.getCurrentHeight(topItem) * 1.0D) + ""); + unit.setZ(topItem.getZ()); + unit.setRotation(RoomUserRotation.values()[topItem.getRotation()]); + unit.sitUpdate = false; + return true; + } + } + } + + if (!unit.isWalking() && !unit.cmdLay) { + HabboItem topItem = this.room.getTopItemAt(unit.getX(), unit.getY()); + + if (topItem == null || !topItem.getBaseItem().allowLay()) { + if (unit.hasStatus(RoomUnitStatus.LAY)) { + unit.removeStatus(RoomUnitStatus.LAY); + update = true; + } + } else { + if (!unit.hasStatus(RoomUnitStatus.LAY)) { + unit.setStatus(RoomUnitStatus.LAY, Item.getCurrentHeight(topItem) * 1.0D + ""); + unit.setRotation(RoomUserRotation.values()[topItem.getRotation() % 4]); + + if (topItem.getRotation() == 0 || topItem.getRotation() == 4) { + unit.setLocation(this.room.getLayout().getTile(unit.getX(), topItem.getY())); + } else { + unit.setLocation(this.room.getLayout().getTile(topItem.getX(), unit.getY())); + } + update = true; + } + } + } + + if (update) { + unit.statusUpdate(false); + } + + return update; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomGameManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomGameManager.java new file mode 100644 index 00000000..9d2db1e9 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomGameManager.java @@ -0,0 +1,96 @@ +package com.eu.habbo.habbohotel.rooms; + +import com.eu.habbo.habbohotel.games.Game; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manages games within a room. + */ +public class RoomGameManager { + private static final Logger LOGGER = LoggerFactory.getLogger(RoomGameManager.class); + + private final Room room; + private final Set games; + + public RoomGameManager(Room room) { + this.room = room; + this.games = ConcurrentHashMap.newKeySet(); + } + + /** + * Adds a game to the room. + */ + public boolean addGame(Game game) { + synchronized (this.games) { + return this.games.add(game); + } + } + + /** + * Removes and disposes a game from the room. + */ + public boolean deleteGame(Game game) { + game.stop(); + game.dispose(); + synchronized (this.games) { + return this.games.remove(game); + } + } + + /** + * Gets a game by its type. + */ + public Game getGame(Class gameType) { + if (gameType == null) { + return null; + } + + synchronized (this.games) { + for (Game game : this.games) { + if (gameType.isInstance(game)) { + return game; + } + } + } + + return null; + } + + /** + * Gets or creates a game of the specified type. + */ + public Game getGameOrCreate(Class gameType) { + Game game = this.getGame(gameType); + if (game == null) { + try { + game = gameType.getDeclaredConstructor(Room.class).newInstance(this.room); + this.addGame(game); + } catch (Exception e) { + LOGGER.error("Error getting game {}", gameType.getName(), e); + } + } + + return game; + } + + /** + * Gets all games in the room. + */ + public Set getGames() { + return this.games; + } + + /** + * Disposes all games. + */ + public void dispose() { + for (Game game : this.games) { + game.dispose(); + } + this.games.clear(); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomItemManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomItemManager.java new file mode 100644 index 00000000..2a66275a --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomItemManager.java @@ -0,0 +1,1629 @@ +package com.eu.habbo.habbohotel.rooms; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.items.FurnitureType; +import com.eu.habbo.habbohotel.items.ICycleable; +import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.items.interactions.*; +import com.eu.habbo.habbohotel.items.interactions.games.InteractionGameGate; +import com.eu.habbo.habbohotel.items.interactions.games.InteractionGameScoreboard; +import com.eu.habbo.habbohotel.items.interactions.games.InteractionGameTimer; +import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.InteractionBattleBanzaiSphere; +import com.eu.habbo.habbohotel.items.interactions.games.battlebanzai.InteractionBattleBanzaiTeleporter; +import com.eu.habbo.habbohotel.items.interactions.games.freeze.InteractionFreezeExitTile; +import com.eu.habbo.habbohotel.items.interactions.games.tag.InteractionTagField; +import com.eu.habbo.habbohotel.items.interactions.games.tag.InteractionTagPole; +import com.eu.habbo.habbohotel.items.interactions.pets.InteractionNest; +import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetBreedingNest; +import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetDrink; +import com.eu.habbo.habbohotel.items.interactions.pets.InteractionPetFood; +import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredBlob; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.habbohotel.users.HabboInfo; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.users.HabboManager; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import com.eu.habbo.habbohotel.wired.tick.WiredTickable; +import com.eu.habbo.messages.outgoing.inventory.AddHabboItemComposer; +import com.eu.habbo.messages.outgoing.rooms.items.FloorItemUpdateComposer; +import com.eu.habbo.messages.outgoing.rooms.items.ItemStateComposer; +import com.eu.habbo.messages.outgoing.rooms.items.RemoveFloorItemComposer; +import com.eu.habbo.messages.outgoing.rooms.items.RemoveWallItemComposer; +import com.eu.habbo.messages.outgoing.rooms.items.WallItemUpdateComposer; +import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.messages.outgoing.rooms.items.AddFloorItemComposer; +import com.eu.habbo.messages.outgoing.rooms.items.AddWallItemComposer; +import com.eu.habbo.messages.outgoing.rooms.items.FloorItemOnRollerComposer; +import com.eu.habbo.plugin.Event; +import com.eu.habbo.plugin.events.furniture.FurnitureBuildheightEvent; +import com.eu.habbo.plugin.events.furniture.FurnitureMovedEvent; +import com.eu.habbo.plugin.events.furniture.FurniturePickedUpEvent; +import com.eu.habbo.plugin.events.furniture.FurniturePlacedEvent; +import com.eu.habbo.plugin.events.furniture.FurnitureRotatedEvent; +import gnu.trove.TCollections; +import org.apache.commons.math3.util.Pair; +import gnu.trove.iterator.TIntObjectIterator; +import gnu.trove.map.TIntIntMap; +import gnu.trove.map.TIntObjectMap; +import gnu.trove.map.hash.THashMap; +import gnu.trove.map.hash.TIntIntHashMap; +import gnu.trove.map.hash.TIntObjectHashMap; +import gnu.trove.set.hash.THashSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manages all items/furniture within a room. + * Handles loading, adding, removing, querying, and picking up items. + */ +public class RoomItemManager { + private static final Logger LOGGER = LoggerFactory.getLogger(RoomItemManager.class); + + private final Room room; + + // Item storage + private final TIntObjectMap roomItems; + + // Furniture owner tracking + private final TIntObjectMap furniOwnerNames; + private final TIntIntMap furniOwnerCount; + + // Tile cache for item lookups + public final ConcurrentHashMap> tileCache; + + public RoomItemManager(Room room) { + this.room = room; + this.roomItems = TCollections.synchronizedMap(new TIntObjectHashMap<>(0)); + this.furniOwnerNames = TCollections.synchronizedMap(new TIntObjectHashMap<>(0)); + this.furniOwnerCount = TCollections.synchronizedMap(new TIntIntHashMap(0)); + this.tileCache = new ConcurrentHashMap<>(); + } + + // ==================== LOADING ==================== + + /** + * Loads items from the database. + */ + public void loadItems(Connection connection) { + synchronized (this.roomItems) { + this.roomItems.clear(); + } + + try (PreparedStatement statement = connection.prepareStatement( + "SELECT * FROM items WHERE room_id = ?")) { + statement.setInt(1, this.room.getId()); + try (ResultSet set = statement.executeQuery()) { + while (set.next()) { + this.addHabboItem(Emulator.getGameEnvironment().getItemManager().loadHabboItem(set)); + } + } + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + + if (this.itemCount() > Room.MAXIMUM_FURNI) { + LOGGER.error("Room ID: {} has exceeded the furniture limit ({} > {}).", + this.room.getId(), this.itemCount(), Room.MAXIMUM_FURNI); + } + } + + /** + * Loads wired data for items. + */ + public void loadWiredData(Connection connection) { + try (PreparedStatement statement = connection.prepareStatement( + "SELECT id, wired_data FROM items WHERE room_id = ? AND wired_data<>''")) { + statement.setInt(1, this.room.getId()); + + try (ResultSet set = statement.executeQuery()) { + while (set.next()) { + try { + HabboItem item = this.getHabboItem(set.getInt("id")); + + if (item instanceof InteractionWired) { + ((InteractionWired) item).loadWiredData(set, this.room); + } + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + } + } + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } catch (Exception e) { + LOGGER.error("Caught exception", e); + } + } + + // ==================== ITEM RETRIEVAL ==================== + + /** + * Gets an item by ID. + */ + public HabboItem getHabboItem(int id) { + if (this.roomItems == null || this.room.getRoomSpecialTypes() == null) { + return null; + } + + HabboItem item; + synchronized (this.roomItems) { + item = this.roomItems.get(id); + } + + // Check special types if not found in main storage + RoomSpecialTypes specialTypes = this.room.getRoomSpecialTypes(); + + if (item == null) { + item = specialTypes.getBanzaiTeleporter(id); + } + + if (item == null) { + item = specialTypes.getTrigger(id); + } + + if (item == null) { + item = specialTypes.getEffect(id); + } + + if (item == null) { + item = specialTypes.getCondition(id); + } + + if (item == null) { + item = specialTypes.getGameGate(id); + } + + if (item == null) { + item = specialTypes.getGameScorebord(id); + } + + if (item == null) { + item = specialTypes.getGameTimer(id); + } + + if (item == null) { + item = specialTypes.getFreezeExitTiles().get(id); + } + + if (item == null) { + item = specialTypes.getRoller(id); + } + + if (item == null) { + item = specialTypes.getNest(id); + } + + if (item == null) { + item = specialTypes.getPetDrink(id); + } + + if (item == null) { + item = specialTypes.getPetFood(id); + } + + return item; + } + + /** + * Gets the total item count. + */ + public int itemCount() { + return this.roomItems.size(); + } + + /** + * Gets all floor items. + */ + public THashSet getFloorItems() { + THashSet items = new THashSet<>(); + TIntObjectIterator iterator = this.roomItems.iterator(); + + for (int i = this.roomItems.size(); i-- > 0; ) { + try { + iterator.advance(); + } catch (Exception e) { + break; + } + + if (iterator.value().getBaseItem().getType() == FurnitureType.FLOOR) { + items.add(iterator.value()); + } + } + + return items; + } + + /** + * Gets all wall items. + */ + public THashSet getWallItems() { + THashSet items = new THashSet<>(); + TIntObjectIterator iterator = this.roomItems.iterator(); + + for (int i = this.roomItems.size(); i-- > 0; ) { + try { + iterator.advance(); + } catch (Exception e) { + break; + } + + if (iterator.value().getBaseItem().getType() == FurnitureType.WALL) { + items.add(iterator.value()); + } + } + + return items; + } + + /** + * Gets all post-it notes. + */ + public THashSet getPostItNotes() { + THashSet items = new THashSet<>(); + TIntObjectIterator iterator = this.roomItems.iterator(); + + for (int i = this.roomItems.size(); i-- > 0; ) { + try { + iterator.advance(); + } catch (Exception e) { + break; + } + + if (iterator.value().getBaseItem().getInteractionType().getType() + == InteractionPostIt.class) { + items.add(iterator.value()); + } + } + + return items; + } + + /** + * Gets the room items map. + */ + public TIntObjectMap getRoomItems() { + return this.roomItems; + } + + // ==================== ITEM POSITION QUERIES ==================== + + /** + * Gets items at a position (deprecated version using int). + */ + @Deprecated + public THashSet getItemsAt(int x, int y) { + RoomTile tile = this.room.getLayout().getTile((short) x, (short) y); + + if (tile != null) { + return this.getItemsAt(tile); + } + + return new THashSet<>(0); + } + + /** + * Gets items at a tile. + */ + public THashSet getItemsAt(RoomTile tile) { + return getItemsAt(tile, false); + } + + /** + * Gets items at a tile with option to return on first match. + */ + public THashSet getItemsAt(RoomTile tile, boolean returnOnFirst) { + THashSet items = new THashSet<>(0); + + if (tile == null) { + return items; + } + + if (this.room.isLoaded()) { + THashSet cachedItems = this.tileCache.get(tile); + if (cachedItems != null) { + return cachedItems; + } + } + + TIntObjectIterator iterator = this.roomItems.iterator(); + + for (int i = this.roomItems.size(); i-- > 0; ) { + HabboItem item; + try { + iterator.advance(); + item = iterator.value(); + } catch (Exception e) { + break; + } + + if (item == null) { + continue; + } + + if (item.getBaseItem().getType() != FurnitureType.FLOOR) { + continue; + } + + int width, length; + + if (item.getRotation() != 2 && item.getRotation() != 6) { + width = item.getBaseItem().getWidth() > 0 ? item.getBaseItem().getWidth() : 1; + length = item.getBaseItem().getLength() > 0 ? item.getBaseItem().getLength() : 1; + } else { + width = item.getBaseItem().getLength() > 0 ? item.getBaseItem().getLength() : 1; + length = item.getBaseItem().getWidth() > 0 ? item.getBaseItem().getWidth() : 1; + } + + if (!(tile.x >= item.getX() && tile.x <= item.getX() + width - 1 && tile.y >= item.getY() + && tile.y <= item.getY() + length - 1)) { + continue; + } + + items.add(item); + + if (returnOnFirst) { + return items; + } + } + + if (this.room.isLoaded()) { + this.tileCache.put(tile, items); + } + + return items; + } + + /** + * Gets items at a position above a minimum Z height. + */ + public THashSet getItemsAt(int x, int y, double minZ) { + THashSet items = new THashSet<>(); + + for (HabboItem item : this.getItemsAt(x, y)) { + if (item.getZ() < minZ) { + continue; + } + + items.add(item); + } + return items; + } + + /** + * Gets items of a specific type at a position. + */ + public THashSet getItemsAt(Class type, int x, int y) { + THashSet items = new THashSet<>(); + + for (HabboItem item : this.getItemsAt(x, y)) { + if (!item.getClass().equals(type)) { + continue; + } + + items.add(item); + } + return items; + } + + /** + * Checks if there are items at a position. + */ + public boolean hasItemsAt(int x, int y) { + RoomTile tile = this.room.getLayout().getTile((short) x, (short) y); + + if (tile == null) { + return false; + } + + return this.getItemsAt(tile, true).size() > 0; + } + + /** + * Gets the top item at a position. + */ + public HabboItem getTopItemAt(int x, int y) { + return this.getTopItemAt(x, y, null); + } + + /** + * Gets the top item at a position excluding a specific item. + */ + public HabboItem getTopItemAt(int x, int y, HabboItem exclude) { + RoomTile tile = this.room.getLayout().getTile((short) x, (short) y); + + if (tile == null) { + return null; + } + + HabboItem highestItem = null; + + for (HabboItem item : this.getItemsAt(x, y)) { + if (exclude != null && exclude == item) { + continue; + } + + if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem) + > item.getZ() + Item.getCurrentHeight(item)) { + continue; + } + + highestItem = item; + } + + return highestItem; + } + + /** + * Gets the top item from a set of tiles. + */ + public HabboItem getTopItemAt(THashSet tiles, HabboItem exclude) { + HabboItem highestItem = null; + for (RoomTile tile : tiles) { + + if (tile == null) { + continue; + } + + for (HabboItem item : this.getItemsAt(tile.x, tile.y)) { + if (exclude != null && exclude == item) { + continue; + } + + if (highestItem != null && highestItem.getZ() + Item.getCurrentHeight(highestItem) + > item.getZ() + Item.getCurrentHeight(item)) { + continue; + } + + highestItem = item; + } + } + + return highestItem; + } + + /** + * Gets the top height at a position including items. + */ + public double getTopHeightAt(int x, int y) { + HabboItem item = this.getTopItemAt(x, y); + + if (item != null) { + return (item.getZ() + Item.getCurrentHeight(item) - (item.getBaseItem().allowSit() ? 1 : 0)); + } else { + return this.room.getLayout().getHeightAtSquare(x, y); + } + } + + /** + * Gets the lowest chair at a position. + */ + @Deprecated + public HabboItem getLowestChair(int x, int y) { + if (this.room.getLayout() == null) { + return null; + } + + RoomTile tile = this.room.getLayout().getTile((short) x, (short) y); + + if (tile != null) { + return this.getLowestChair(tile); + } + + return null; + } + + /** + * Gets the lowest chair at a tile. + */ + public HabboItem getLowestChair(RoomTile tile) { + HabboItem lowestChair = null; + + THashSet items = this.getItemsAt(tile); + if (items != null && !items.isEmpty()) { + for (HabboItem item : items) { + + if (!item.getBaseItem().allowSit()) { + continue; + } + + if (lowestChair != null && lowestChair.getZ() < item.getZ()) { + continue; + } + + lowestChair = item; + } + } + + return lowestChair; + } + + /** + * Gets the tallest chair at a tile. + */ + public HabboItem getTallestChair(RoomTile tile) { + HabboItem lowestChair = null; + + THashSet items = this.getItemsAt(tile); + if (items != null && !items.isEmpty()) { + for (HabboItem item : items) { + + if (!item.getBaseItem().allowSit()) { + continue; + } + + if (lowestChair != null && lowestChair.getZ() + Item.getCurrentHeight(lowestChair) + > item.getZ() + Item.getCurrentHeight(item)) { + continue; + } + + lowestChair = item; + } + } + + return lowestChair; + } + + // ==================== ITEM MANIPULATION ==================== + + /** + * Adds an item to the room. + */ + public void addHabboItem(HabboItem item) { + if (item == null) { + return; + } + + synchronized (this.roomItems) { + try { + this.roomItems.put(item.getId(), item); + } catch (Exception e) { + // Ignore + } + } + + synchronized (this.furniOwnerCount) { + this.furniOwnerCount.put(item.getUserId(), this.furniOwnerCount.get(item.getUserId()) + 1); + } + + synchronized (this.furniOwnerNames) { + if (!this.furniOwnerNames.containsKey(item.getUserId())) { + HabboInfo habbo = HabboManager.getOfflineHabboInfo(item.getUserId()); + + if (habbo != null) { + this.furniOwnerNames.put(item.getUserId(), habbo.getUsername()); + } else { + LOGGER.error("Failed to find username for item (ID: {}, UserID: {})", + item.getId(), item.getUserId()); + } + } + } + + // Register with special types + this.registerItemWithSpecialTypes(item); + } + + /** + * Registers an item with room special types. + */ + private void registerItemWithSpecialTypes(HabboItem item) { + RoomSpecialTypes specialTypes = this.room.getRoomSpecialTypes(); + if (specialTypes == null) { + return; + } + + boolean isWiredItem = false; + + synchronized (specialTypes) { + // Register with tick service for time-based wired triggers (new 50ms tick system) + // This replaces ICycleable for wired items + if (item instanceof WiredTickable) { + WiredManager.registerTickable(this.room, (WiredTickable) item); + } + // Still register non-wired ICycleable items with the old system + else if (item instanceof ICycleable) { + specialTypes.addCycleTask((ICycleable) item); + } + + if (item instanceof InteractionWiredTrigger) { + specialTypes.addTrigger((InteractionWiredTrigger) item); + isWiredItem = true; + } else if (item instanceof InteractionWiredEffect) { + specialTypes.addEffect((InteractionWiredEffect) item); + isWiredItem = true; + } else if (item instanceof InteractionWiredCondition) { + specialTypes.addCondition((InteractionWiredCondition) item); + isWiredItem = true; + } else if (item instanceof InteractionWiredExtra) { + specialTypes.addExtra((InteractionWiredExtra) item); + isWiredItem = true; + } else if (item instanceof InteractionBattleBanzaiTeleporter) { + specialTypes.addBanzaiTeleporter((InteractionBattleBanzaiTeleporter) item); + } else if (item instanceof InteractionRoller) { + specialTypes.addRoller((InteractionRoller) item); + } else if (item instanceof InteractionGameScoreboard) { + specialTypes.addGameScoreboard((InteractionGameScoreboard) item); + } else if (item instanceof InteractionGameGate) { + specialTypes.addGameGate((InteractionGameGate) item); + } else if (item instanceof InteractionGameTimer) { + specialTypes.addGameTimer((InteractionGameTimer) item); + } else if (item instanceof InteractionFreezeExitTile) { + specialTypes.addFreezeExitTile((InteractionFreezeExitTile) item); + } else if (item instanceof InteractionNest) { + specialTypes.addNest((InteractionNest) item); + } else if (item instanceof InteractionPetDrink) { + specialTypes.addPetDrink((InteractionPetDrink) item); + } else if (item instanceof InteractionPetFood) { + specialTypes.addPetFood((InteractionPetFood) item); + } else if (item instanceof InteractionMoodLight || + item instanceof InteractionPyramid || + item instanceof InteractionMusicDisc || + item instanceof InteractionBattleBanzaiSphere || + item instanceof InteractionTalkingFurniture || + item instanceof InteractionWater || + item instanceof InteractionWaterItem || + item instanceof InteractionMuteArea || + item instanceof InteractionBuildArea || + item instanceof InteractionTagPole || + item instanceof InteractionTagField || + item instanceof InteractionJukeBox || + item instanceof InteractionPetBreedingNest || + item instanceof InteractionBlackHole || + item instanceof InteractionWiredHighscore || + item instanceof InteractionStickyPole || + item instanceof WiredBlob || + item instanceof InteractionTent || + item instanceof InteractionSnowboardSlope || + item instanceof InteractionFireworks) { + specialTypes.addUndefined(item); + } + } + + // Invalidate wired cache when wired items are added + if (isWiredItem) { + WiredManager.invalidateRoom(this.room); + } + } + + /** + * Removes an item by ID. + */ + public void removeHabboItem(int id) { + this.removeHabboItem(this.getHabboItem(id)); + } + + /** + * Removes an item from the room. + */ + public void removeHabboItem(HabboItem item) { + if (item == null) { + return; + } + + HabboItem i; + synchronized (this.roomItems) { + i = this.roomItems.remove(item.getId()); + } + + if (i != null) { + synchronized (this.furniOwnerCount) { + synchronized (this.furniOwnerNames) { + int count = this.furniOwnerCount.get(i.getUserId()); + + if (count > 1) { + this.furniOwnerCount.put(i.getUserId(), count - 1); + } else { + this.furniOwnerCount.remove(i.getUserId()); + this.furniOwnerNames.remove(i.getUserId()); + } + } + } + + // Unregister from special types + this.unregisterItemFromSpecialTypes(item); + } + } + + /** + * Unregisters an item from room special types. + */ + private void unregisterItemFromSpecialTypes(HabboItem item) { + RoomSpecialTypes specialTypes = this.room.getRoomSpecialTypes(); + if (specialTypes == null) { + return; + } + + boolean isWiredItem = false; + + // Unregister from tick service for time-based wired triggers (new 50ms tick system) + if (item instanceof WiredTickable) { + WiredManager.unregisterTickable(this.room, (WiredTickable) item); + } + // Still handle non-wired ICycleable items with the old system + else if (item instanceof ICycleable) { + specialTypes.removeCycleTask((ICycleable) item); + } + + if (item instanceof InteractionBattleBanzaiTeleporter) { + specialTypes.removeBanzaiTeleporter((InteractionBattleBanzaiTeleporter) item); + } else if (item instanceof InteractionWiredTrigger) { + specialTypes.removeTrigger((InteractionWiredTrigger) item); + isWiredItem = true; + } else if (item instanceof InteractionWiredEffect) { + specialTypes.removeEffect((InteractionWiredEffect) item); + isWiredItem = true; + } else if (item instanceof InteractionWiredCondition) { + specialTypes.removeCondition((InteractionWiredCondition) item); + isWiredItem = true; + } else if (item instanceof InteractionWiredExtra) { + specialTypes.removeExtra((InteractionWiredExtra) item); + isWiredItem = true; + } else if (item instanceof InteractionRoller) { + specialTypes.removeRoller((InteractionRoller) item); + } else if (item instanceof InteractionGameScoreboard) { + specialTypes.removeScoreboard((InteractionGameScoreboard) item); + } else if (item instanceof InteractionGameGate) { + specialTypes.removeGameGate((InteractionGameGate) item); + } else if (item instanceof InteractionGameTimer) { + specialTypes.removeGameTimer((InteractionGameTimer) item); + } else if (item instanceof InteractionFreezeExitTile) { + specialTypes.removeFreezeExitTile((InteractionFreezeExitTile) item); + } else if (item instanceof InteractionNest) { + specialTypes.removeNest((InteractionNest) item); + } else if (item instanceof InteractionPetDrink) { + specialTypes.removePetDrink((InteractionPetDrink) item); + } else if (item instanceof InteractionPetFood) { + specialTypes.removePetFood((InteractionPetFood) item); + } else if (item instanceof InteractionMoodLight || + item instanceof InteractionPyramid || + item instanceof InteractionMusicDisc || + item instanceof InteractionBattleBanzaiSphere || + item instanceof InteractionTalkingFurniture || + item instanceof InteractionWaterItem || + item instanceof InteractionWater || + item instanceof InteractionMuteArea || + item instanceof InteractionTagPole || + item instanceof InteractionTagField || + item instanceof InteractionJukeBox || + item instanceof InteractionPetBreedingNest || + item instanceof InteractionBlackHole || + item instanceof InteractionWiredHighscore || + item instanceof InteractionStickyPole || + item instanceof WiredBlob || + item instanceof InteractionTent || + item instanceof InteractionSnowboardSlope) { + specialTypes.removeUndefined(item); + } + + // Invalidate wired cache when wired items are removed + if (isWiredItem) { + WiredManager.invalidateRoom(this.room); + } + } + + // ==================== ITEM UPDATES ==================== + + /** + * Updates an item's display. + */ + public void updateItem(HabboItem item) { + if (this.room.isLoaded()) { + if (item != null && item.getRoomId() == this.room.getId()) { + if (item.getBaseItem() != null) { + if (item.getBaseItem().getType() == FurnitureType.FLOOR) { + this.room.sendComposer(new FloorItemUpdateComposer(item).compose()); + this.room.updateTiles(this.room.getLayout() + .getTilesAt(this.room.getLayout().getTile(item.getX(), item.getY()), + item.getBaseItem().getWidth(), item.getBaseItem().getLength(), + item.getRotation())); + } else if (item.getBaseItem().getType() == FurnitureType.WALL) { + this.room.sendComposer(new WallItemUpdateComposer(item).compose()); + } + } + } + } + } + + /** + * Updates an item's state. + */ + public void updateItemState(HabboItem item) { + if (!item.isLimited()) { + this.room.sendComposer(new ItemStateComposer(item).compose()); + } else { + this.room.sendComposer(new FloorItemUpdateComposer(item).compose()); + } + + if (item.getBaseItem().getType() == FurnitureType.FLOOR) { + if (this.room.getLayout() == null) { + return; + } + + this.room.updateTiles(this.room.getLayout() + .getTilesAt(this.room.getLayout().getTile(item.getX(), item.getY()), + item.getBaseItem().getWidth(), item.getBaseItem().getLength(), + item.getRotation())); + + if (item instanceof InteractionMultiHeight) { + ((InteractionMultiHeight) item).updateUnitsOnItem(this.room); + } + } + } + + // ==================== FURNITURE OWNER MANAGEMENT ==================== + + /** + * Gets furniture owner names map. + */ + public TIntObjectMap getFurniOwnerNames() { + return this.furniOwnerNames; + } + + /** + * Gets furniture owner count map. + */ + public TIntIntMap getFurniOwnerCount() { + return this.furniOwnerCount; + } + + /** + * Gets the username for a furniture owner. + */ + public String getFurniOwnerName(int oduserId) { + return this.furniOwnerNames.get(oduserId); + } + + /** + * Gets the furniture count for a user. + */ + public int getUserFurniCount(int userId) { + return this.furniOwnerCount.get(userId); + } + + /** + * Gets the unique furniture count for a user. + */ + public int getUserUniqueFurniCount(int userId) { + THashSet items = new THashSet<>(); + + for (HabboItem item : this.roomItems.valueCollection()) { + if (!items.contains(item.getBaseItem()) && item.getUserId() == userId) { + items.add(item.getBaseItem()); + } + } + + return items.size(); + } + + // ==================== PICKUP AND EJECT ==================== + + /** + * Picks up an item from the room. + */ + public void pickUpItem(HabboItem item, Habbo picker) { + if (item == null) { + return; + } + + if (Emulator.getPluginManager().isRegistered(FurniturePickedUpEvent.class, true)) { + FurniturePickedUpEvent event = Emulator.getPluginManager() + .fireEvent(new FurniturePickedUpEvent(item, picker)); + + if (event.isCancelled()) { + return; + } + } + + this.removeHabboItem(item); + item.onPickUp(this.room); + item.setRoomId(0); + item.needsUpdate(true); + + if (item.getBaseItem().getType() == FurnitureType.FLOOR) { + this.room.sendComposer(new RemoveFloorItemComposer(item).compose()); + + THashSet updatedTiles = this.room.getLayout().getTilesAt( + this.room.getLayout().getTile(item.getX(), item.getY()), + item.getBaseItem().getWidth(), + item.getBaseItem().getLength(), + item.getRotation()); + this.room.updateTiles(updatedTiles); + + for (RoomTile tile : updatedTiles) { + this.room.updateHabbosAt(tile.x, tile.y); + this.room.updateBotsAt(tile.x, tile.y); + } + } else if (item.getBaseItem().getType() == FurnitureType.WALL) { + this.room.sendComposer(new RemoveWallItemComposer(item).compose()); + } + + Emulator.getThreading().run(item); + } + + /** + * Ejects all furniture belonging to a user. + */ + public void ejectUserFurni(int userId) { + THashSet items = new THashSet<>(); + + TIntObjectIterator iterator = this.roomItems.iterator(); + + for (int i = this.roomItems.size(); i-- > 0; ) { + try { + iterator.advance(); + } catch (Exception e) { + break; + } + + if (iterator.value().getUserId() == userId) { + items.add(iterator.value()); + iterator.value().setRoomId(0); + } + } + + Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId); + + if (habbo != null) { + habbo.getInventory().getItemsComponent().addItems(items); + habbo.getClient().sendResponse(new AddHabboItemComposer(items)); + } + + for (HabboItem i : items) { + this.pickUpItem(i, null); + } + } + + /** + * Ejects a single user item. + */ + public void ejectUserItem(HabboItem item) { + this.pickUpItem(item, null); + } + + /** + * Ejects all items from the room. + */ + public void ejectAll() { + this.ejectAll(null); + } + + /** + * Ejects all items from the room except those belonging to the specified Habbo. + */ + public void ejectAll(Habbo habbo) { + THashMap> userItemsMap = new THashMap<>(); + + synchronized (this.roomItems) { + TIntObjectIterator iterator = this.roomItems.iterator(); + + for (int i = this.roomItems.size(); i-- > 0; ) { + try { + iterator.advance(); + } catch (Exception e) { + break; + } + + if (habbo != null && iterator.value().getUserId() == habbo.getHabboInfo().getId()) { + continue; + } + + if (iterator.value() instanceof InteractionPostIt) { + continue; + } + + userItemsMap.computeIfAbsent(iterator.value().getUserId(), k -> new THashSet<>()) + .add(iterator.value()); + } + } + + for (Map.Entry> entrySet : userItemsMap.entrySet()) { + for (HabboItem i : entrySet.getValue()) { + this.pickUpItem(i, null); + } + + Habbo user = Emulator.getGameEnvironment().getHabboManager().getHabbo(entrySet.getKey()); + + if (user != null) { + user.getInventory().getItemsComponent().addItems(entrySet.getValue()); + user.getClient().sendResponse(new AddHabboItemComposer(entrySet.getValue())); + } + } + } + + // ==================== LOCKED TILES ==================== + + /** + * Gets all tiles that are locked by furniture. + */ + public THashSet getLockedTiles() { + THashSet lockedTiles = new THashSet<>(); + + TIntObjectIterator iterator = this.roomItems.iterator(); + + for (int i = this.roomItems.size(); i-- > 0; ) { + HabboItem item; + try { + iterator.advance(); + item = iterator.value(); + } catch (Exception e) { + break; + } + + if (item.getBaseItem().getType() != FurnitureType.FLOOR) { + continue; + } + + boolean found = false; + for (RoomTile tile : lockedTiles) { + if (tile.x == item.getX() && tile.y == item.getY()) { + found = true; + break; + } + } + + if (!found) { + if (item.getRotation() == 0 || item.getRotation() == 4) { + for (short y = 0; y < item.getBaseItem().getLength(); y++) { + for (short x = 0; x < item.getBaseItem().getWidth(); x++) { + RoomTile tile = this.room.getLayout().getTile( + (short) (item.getX() + x), (short) (item.getY() + y)); + + if (tile != null) { + lockedTiles.add(tile); + } + } + } + } else { + for (short y = 0; y < item.getBaseItem().getWidth(); y++) { + for (short x = 0; x < item.getBaseItem().getLength(); x++) { + RoomTile tile = this.room.getLayout().getTile( + (short) (item.getX() + x), (short) (item.getY() + y)); + + if (tile != null) { + lockedTiles.add(tile); + } + } + } + } + } + } + + return lockedTiles; + } + + // ==================== DISPOSAL ==================== + + /** + * Saves all items that need updates to the database. + */ + public void saveAllPendingItems() { + synchronized (this.roomItems) { + TIntObjectIterator iterator = this.roomItems.iterator(); + + for (int i = this.roomItems.size(); i-- > 0; ) { + try { + iterator.advance(); + + if (iterator.value().needsUpdate()) { + iterator.value().run(); + } + } catch (java.util.NoSuchElementException e) { + break; + } + } + } + } + + /** + * Clears the item manager state. + */ + public void clear() { + synchronized (this.roomItems) { + this.roomItems.clear(); + } + synchronized (this.furniOwnerCount) { + this.furniOwnerCount.clear(); + } + synchronized (this.furniOwnerNames) { + this.furniOwnerNames.clear(); + } + this.tileCache.clear(); + } + + /** + * Disposes the item manager. + */ + public void dispose() { + this.clear(); + } + + // ==================== FURNITURE PLACEMENT ==================== + + /** + * Checks if an item has a certain object type at a position. + */ + public boolean hasObjectTypeAt(Class type, int x, int y) { + THashSet items = this.getItemsAt(x, y); + + for (HabboItem item : items) { + if (item.getClass() == type) { + return true; + } + } + + return false; + } + + /** + * Checks if furniture can be placed at a position. + */ + public FurnitureMovementError canPlaceFurnitureAt(HabboItem item, Habbo habbo, RoomTile tile, int rotation) { + if (this.itemCount() >= Room.MAXIMUM_FURNI) { + return FurnitureMovementError.MAX_ITEMS; + } + + if (tile == null || tile.state == RoomTileState.INVALID) { + return FurnitureMovementError.INVALID_MOVE; + } + + rotation %= 8; + if (this.room.hasRights(habbo) || this.room.getGuildRightLevel(habbo) + .isEqualOrGreaterThan(RoomRightLevels.GUILD_RIGHTS) || habbo.hasPermission( + Permission.ACC_MOVEROTATE)) { + return FurnitureMovementError.NONE; + } + + if (habbo.getHabboStats().isRentingSpace()) { + HabboItem rentSpace = this.getHabboItem(habbo.getHabboStats().rentedItemId); + + if (rentSpace != null) { + if (!RoomLayout.squareInSquare(RoomLayout.getRectangle(rentSpace.getX(), rentSpace.getY(), + rentSpace.getBaseItem().getWidth(), rentSpace.getBaseItem().getLength(), + rentSpace.getRotation()), + RoomLayout.getRectangle(tile.x, tile.y, item.getBaseItem().getWidth(), + item.getBaseItem().getLength(), rotation))) { + return FurnitureMovementError.NO_RIGHTS; + } else { + return FurnitureMovementError.NONE; + } + } + } + + for (HabboItem area : this.room.getRoomSpecialTypes().getItemsOfType(InteractionBuildArea.class)) { + if (((InteractionBuildArea) area).inSquare(tile) && ((InteractionBuildArea) area).isBuilder( + habbo.getHabboInfo().getUsername())) { + return FurnitureMovementError.NONE; + } + } + + return FurnitureMovementError.NO_RIGHTS; + } + + /** + * Checks if furniture fits at a location. + */ + public FurnitureMovementError furnitureFitsAt(RoomTile tile, HabboItem item, int rotation) { + return furnitureFitsAt(tile, item, rotation, true); + } + + /** + * Checks if furniture fits at a location with unit check option. + */ + public FurnitureMovementError furnitureFitsAt(RoomTile tile, HabboItem item, int rotation, boolean checkForUnits) { + RoomLayout layout = this.room.getLayout(); + if (!layout.fitsOnMap(tile, item.getBaseItem().getWidth(), item.getBaseItem().getLength(), rotation)) { + return FurnitureMovementError.INVALID_MOVE; + } + + if (item instanceof InteractionStackHelper || item instanceof InteractionTileWalkMagic) { + return FurnitureMovementError.NONE; + } + + THashSet occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(), + item.getBaseItem().getLength(), rotation); + for (RoomTile t : occupiedTiles) { + if (t.state == RoomTileState.INVALID) { + return FurnitureMovementError.INVALID_MOVE; + } + if (!Emulator.getConfig().getBoolean("wired.place.under", false) || ( + Emulator.getConfig().getBoolean("wired.place.under", false) && !item.isWalkable() + && !item.getBaseItem().allowSit() && !item.getBaseItem().allowLay())) { + if (checkForUnits && this.room.hasHabbosAt(t.x, t.y)) { + return FurnitureMovementError.TILE_HAS_HABBOS; + } + if (checkForUnits && this.room.hasBotsAt(t.x, t.y)) { + return FurnitureMovementError.TILE_HAS_BOTS; + } + if (checkForUnits && this.room.hasPetsAt(t.x, t.y)) { + return FurnitureMovementError.TILE_HAS_PETS; + } + } + } + + java.util.List>> tileFurniList = new java.util.ArrayList<>(); + for (RoomTile t : occupiedTiles) { + tileFurniList.add(Pair.create(t, this.getItemsAt(t))); + + HabboItem topItem = this.getTopItemAt(t.x, t.y, item); + if (topItem != null && !topItem.getBaseItem().allowStack() && !t.getAllowStack()) { + return FurnitureMovementError.CANT_STACK; + } + } + + if (!item.canStackAt(this.room, tileFurniList)) { + return FurnitureMovementError.CANT_STACK; + } + + return FurnitureMovementError.NONE; + } + + /** + * Places a floor furniture item at a position. + */ + public FurnitureMovementError placeFloorFurniAt(HabboItem item, RoomTile tile, int rotation, Habbo owner) { + boolean pluginHelper = false; + if (Emulator.getPluginManager().isRegistered(FurniturePlacedEvent.class, true)) { + FurniturePlacedEvent event = Emulator.getPluginManager() + .fireEvent(new FurniturePlacedEvent(item, owner, tile)); + + if (event.isCancelled()) { + return FurnitureMovementError.CANCEL_PLUGIN_PLACE; + } + + pluginHelper = event.hasPluginHelper(); + } + + RoomLayout layout = this.room.getLayout(); + THashSet occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(), + item.getBaseItem().getLength(), rotation); + + FurnitureMovementError fits = furnitureFitsAt(tile, item, rotation); + + if (!fits.equals(FurnitureMovementError.NONE) && !pluginHelper) { + return fits; + } + + double height = tile.getStackHeight(); + + for (RoomTile tile2 : occupiedTiles) { + double sHeight = tile2.getStackHeight(); + if (sHeight > height) { + height = sHeight; + } + } + + if (Emulator.getPluginManager().isRegistered(FurnitureBuildheightEvent.class, true)) { + FurnitureBuildheightEvent event = Emulator.getPluginManager() + .fireEvent(new FurnitureBuildheightEvent(item, owner, 0.00, height)); + if (event.hasChangedHeight()) { + height = layout.getHeightAtSquare(tile.x, tile.y) + event.getUpdatedHeight(); + } + } + + item.setZ(height); + item.setX(tile.x); + item.setY(tile.y); + item.setRotation(rotation); + if (!this.furniOwnerNames.containsKey(item.getUserId()) && owner != null) { + this.furniOwnerNames.put(item.getUserId(), owner.getHabboInfo().getUsername()); + } + + item.needsUpdate(true); + this.addHabboItem(item); + item.setRoomId(this.room.getId()); + item.onPlace(this.room); + this.room.updateTiles(occupiedTiles); + this.room.sendComposer( + new AddFloorItemComposer(item, this.getFurniOwnerName(item.getUserId())).compose()); + + for (RoomTile t : occupiedTiles) { + this.room.updateHabbosAt(t.x, t.y); + this.room.updateBotsAt(t.x, t.y); + } + + Emulator.getThreading().run(item); + return FurnitureMovementError.NONE; + } + + /** + * Places a wall furniture item at a position. + */ + public FurnitureMovementError placeWallFurniAt(HabboItem item, String wallPosition, Habbo owner) { + if (!(this.room.hasRights(owner) || this.room.getGuildRightLevel(owner) + .isEqualOrGreaterThan(RoomRightLevels.GUILD_RIGHTS))) { + return FurnitureMovementError.NO_RIGHTS; + } + + if (Emulator.getPluginManager().isRegistered(FurniturePlacedEvent.class, true)) { + Event furniturePlacedEvent = new FurniturePlacedEvent(item, owner, null); + Emulator.getPluginManager().fireEvent(furniturePlacedEvent); + + if (furniturePlacedEvent.isCancelled()) { + return FurnitureMovementError.CANCEL_PLUGIN_PLACE; + } + } + + item.setWallPosition(wallPosition); + if (!this.furniOwnerNames.containsKey(item.getUserId()) && owner != null) { + this.furniOwnerNames.put(item.getUserId(), owner.getHabboInfo().getUsername()); + } + this.room.sendComposer( + new AddWallItemComposer(item, this.getFurniOwnerName(item.getUserId())).compose()); + item.needsUpdate(true); + this.addHabboItem(item); + item.setRoomId(this.room.getId()); + item.onPlace(this.room); + Emulator.getThreading().run(item); + return FurnitureMovementError.NONE; + } + + /** + * Moves furniture to a new position. + */ + public FurnitureMovementError moveFurniTo(HabboItem item, RoomTile tile, int rotation, Habbo actor) { + return moveFurniTo(item, tile, rotation, actor, true, true); + } + + /** + * Moves furniture to a new position with send updates option. + */ + public FurnitureMovementError moveFurniTo(HabboItem item, RoomTile tile, int rotation, Habbo actor, boolean sendUpdates) { + return moveFurniTo(item, tile, rotation, actor, sendUpdates, true); + } + + /** + * Moves furniture to a new position with full options. + */ + public FurnitureMovementError moveFurniTo(HabboItem item, RoomTile tile, int rotation, Habbo actor, boolean sendUpdates, boolean checkForUnits) { + RoomLayout layout = this.room.getLayout(); + RoomTile oldLocation = layout.getTile(item.getX(), item.getY()); + + boolean pluginHelper = false; + if (Emulator.getPluginManager().isRegistered(FurnitureMovedEvent.class, true)) { + FurnitureMovedEvent event = Emulator.getPluginManager() + .fireEvent(new FurnitureMovedEvent(item, actor, oldLocation, tile)); + if (event.isCancelled()) { + return FurnitureMovementError.CANCEL_PLUGIN_MOVE; + } + pluginHelper = event.hasPluginHelper(); + } + + boolean magicTile = item instanceof InteractionStackHelper || item instanceof InteractionTileWalkMagic; + + java.util.Optional stackHelper = this.getItemsAt(tile).stream() + .filter(i -> i instanceof InteractionStackHelper).findAny(); + + // Check if can be placed at new position + THashSet occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(), + item.getBaseItem().getLength(), rotation); + THashSet newOccupiedTiles = layout.getTilesAt(tile, + item.getBaseItem().getWidth(), item.getBaseItem().getLength(), rotation); + + HabboItem topItem = this.getTopItemAt(occupiedTiles, null); + + if (!stackHelper.isPresent() && !pluginHelper) { + if (oldLocation != tile) { + for (RoomTile t : occupiedTiles) { + HabboItem tileTopItem = this.getTopItemAt(t.x, t.y); + if (!magicTile && ((tileTopItem != null && tileTopItem != item ? ( + t.state.equals(RoomTileState.INVALID) || !t.getAllowStack() + || !tileTopItem.getBaseItem().allowStack()) + : this.room.calculateTileState(t, item).equals(RoomTileState.INVALID)))) { + return FurnitureMovementError.CANT_STACK; + } + + if (!Emulator.getConfig().getBoolean("wired.place.under", false) || ( + Emulator.getConfig().getBoolean("wired.place.under", false) && !item.isWalkable() + && !item.getBaseItem().allowSit() && !item.getBaseItem().allowLay())) { + if (checkForUnits) { + if (!magicTile && this.room.hasHabbosAt(t.x, t.y)) { + return FurnitureMovementError.TILE_HAS_HABBOS; + } + if (!magicTile && this.room.hasBotsAt(t.x, t.y)) { + return FurnitureMovementError.TILE_HAS_BOTS; + } + if (!magicTile && this.room.hasPetsAt(t.x, t.y)) { + return FurnitureMovementError.TILE_HAS_PETS; + } + } + } + } + } + + java.util.List>> tileFurniList = new java.util.ArrayList<>(); + for (RoomTile t : occupiedTiles) { + tileFurniList.add(Pair.create(t, this.getItemsAt(t))); + } + + if (!magicTile && !item.canStackAt(this.room, tileFurniList)) { + return FurnitureMovementError.CANT_STACK; + } + } + + THashSet oldOccupiedTiles = layout.getTilesAt( + layout.getTile(item.getX(), item.getY()), item.getBaseItem().getWidth(), + item.getBaseItem().getLength(), item.getRotation()); + + int oldRotation = item.getRotation(); + + if (oldRotation != rotation) { + item.setRotation(rotation); + if (Emulator.getPluginManager().isRegistered(FurnitureRotatedEvent.class, true)) { + Event furnitureRotatedEvent = new FurnitureRotatedEvent(item, actor, oldRotation); + Emulator.getPluginManager().fireEvent(furnitureRotatedEvent); + + if (furnitureRotatedEvent.isCancelled()) { + item.setRotation(oldRotation); + return FurnitureMovementError.CANCEL_PLUGIN_ROTATE; + } + } + + if ((!stackHelper.isPresent() && topItem != null && topItem != item && !topItem.getBaseItem() + .allowStack()) || (topItem != null && topItem != item + && topItem.getZ() + Item.getCurrentHeight(topItem) + Item.getCurrentHeight(item) + > Room.MAXIMUM_FURNI_HEIGHT)) { + item.setRotation(oldRotation); + return FurnitureMovementError.CANT_STACK; + } + } + + // Place at new position + double height; + + if (stackHelper.isPresent()) { + height = stackHelper.get().getExtradata().isEmpty() ? Double.parseDouble("0.0") + : (Double.parseDouble(stackHelper.get().getExtradata()) / 100); + } else if (item == topItem) { + height = item.getZ(); + } else if (magicTile) { + if (topItem == null) { + height = this.room.getStackHeight(tile.x, tile.y, false, item); + for (RoomTile til : occupiedTiles) { + double sHeight = this.room.getStackHeight(til.x, til.y, false, item); + if (sHeight > height) { + height = sHeight; + } + } + } else { + height = topItem.getZ() + topItem.getBaseItem().getHeight(); + } + } else { + height = this.room.getStackHeight(tile.x, tile.y, false, item); + for (RoomTile til : occupiedTiles) { + double sHeight = this.room.getStackHeight(til.x, til.y, false, item); + if (sHeight > height) { + height = sHeight; + } + } + } + + boolean cantStack = false; + boolean pluginHeight = false; + + if (height > Room.MAXIMUM_FURNI_HEIGHT) { + cantStack = true; + } + if (height < layout.getHeightAtSquare(tile.x, tile.y)) { + cantStack = true; + } + + if (Emulator.getPluginManager().isRegistered(FurnitureBuildheightEvent.class, true)) { + FurnitureBuildheightEvent event = Emulator.getPluginManager() + .fireEvent(new FurnitureBuildheightEvent(item, actor, 0.00, height)); + if (event.hasChangedHeight()) { + height = layout.getHeightAtSquare(tile.x, tile.y) + event.getUpdatedHeight(); + pluginHeight = true; + } + } + + if (!pluginHeight && cantStack) { + return FurnitureMovementError.CANT_STACK; + } + + item.setX(tile.x); + item.setY(tile.y); + item.setZ(height); + if (magicTile) { + item.setZ(tile.z); + item.setExtradata("" + item.getZ() * 100); + } + if (item.getZ() > Room.MAXIMUM_FURNI_HEIGHT) { + item.setZ(Room.MAXIMUM_FURNI_HEIGHT); + } + + // Update wired spatial index and invalidate cache when wired items are moved + if (item instanceof InteractionWiredTrigger) { + this.room.getRoomSpecialTypes().updateTriggerLocation((InteractionWiredTrigger) item, oldLocation.x, oldLocation.y); + WiredManager.invalidateRoom(this.room); + } else if (item instanceof InteractionWiredEffect) { + this.room.getRoomSpecialTypes().updateEffectLocation((InteractionWiredEffect) item, oldLocation.x, oldLocation.y); + WiredManager.invalidateRoom(this.room); + } else if (item instanceof InteractionWiredCondition) { + this.room.getRoomSpecialTypes().updateConditionLocation((InteractionWiredCondition) item, oldLocation.x, oldLocation.y); + WiredManager.invalidateRoom(this.room); + } else if (item instanceof InteractionWiredExtra) { + this.room.getRoomSpecialTypes().updateExtraLocation((InteractionWiredExtra) item, oldLocation.x, oldLocation.y); + WiredManager.invalidateRoom(this.room); + } + + // Update Furniture + item.onMove(this.room, oldLocation, tile); + item.needsUpdate(true); + Emulator.getThreading().run(item); + + if (sendUpdates) { + this.room.sendComposer(new FloorItemUpdateComposer(item).compose()); + } + + // Update old & new tiles + occupiedTiles.removeAll(oldOccupiedTiles); + occupiedTiles.addAll(oldOccupiedTiles); + this.room.updateTiles(occupiedTiles); + + // Update Habbos at old position + for (RoomTile t : occupiedTiles) { + this.room.updateHabbosAt(t.x, t.y, this.room.getHabbosAt(t.x, t.y)); + this.room.updateBotsAt(t.x, t.y); + } + if (Emulator.getConfig().getBoolean("wired.place.under", false)) { + for (RoomTile t : newOccupiedTiles) { + for (Habbo h : this.room.getHabbosAt(t.x, t.y)) { + try { + item.onWalkOn(h.getRoomUnit(), this.room, null); + } catch (Exception e) { + // Ignore + } + } + } + } + return FurnitureMovementError.NONE; + } + + /** + * Slides furniture to a new position. + */ + public FurnitureMovementError slideFurniTo(HabboItem item, RoomTile tile, int rotation) { + boolean magicTile = item instanceof InteractionStackHelper; + + RoomLayout layout = this.room.getLayout(); + + // Check if can be placed at new position + THashSet occupiedTiles = layout.getTilesAt(tile, item.getBaseItem().getWidth(), + item.getBaseItem().getLength(), rotation); + + java.util.List>> tileFurniList = new java.util.ArrayList<>(); + for (RoomTile t : occupiedTiles) { + tileFurniList.add(Pair.create(t, this.getItemsAt(t))); + } + + if (!magicTile && !item.canStackAt(this.room, tileFurniList)) { + return FurnitureMovementError.CANT_STACK; + } + + item.setRotation(rotation); + + // Place at new position + if (magicTile) { + item.setZ(tile.z); + item.setExtradata("" + item.getZ() * 100); + } + if (item.getZ() > Room.MAXIMUM_FURNI_HEIGHT) { + item.setZ(Room.MAXIMUM_FURNI_HEIGHT); + } + double offset = this.room.getStackHeight(tile.x, tile.y, false, item) - item.getZ(); + this.room.sendComposer(new FloorItemOnRollerComposer(item, null, tile, offset, this.room).compose()); + + // Update Habbos at old position + for (RoomTile t : occupiedTiles) { + this.room.updateHabbosAt(t.x, t.y); + this.room.updateBotsAt(t.x, t.y); + } + return FurnitureMovementError.NONE; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomLayout.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomLayout.java index d49d8b7f..576ebf2e 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomLayout.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomLayout.java @@ -1,683 +1,459 @@ package com.eu.habbo.habbohotel.rooms; import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.rooms.pathfinding.Pathfinder; +import com.eu.habbo.habbohotel.rooms.pathfinding.impl.PathfinderImpl; import gnu.trove.set.hash.THashSet; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.awt.*; +import java.awt.Rectangle; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Deque; -import java.util.LinkedList; import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class RoomLayout { - private static final Logger LOGGER = LoggerFactory.getLogger(RoomLayout.class); - protected static final int BASICMOVEMENTCOST = 10; - protected static final int DIAGONALMOVEMENTCOST = 14; - public static double MAXIMUM_STEP_HEIGHT = 1.1; - public static boolean ALLOW_FALLING = true; - public boolean CANMOVEDIAGONALY = true; - private String name; - private short doorX; - private short doorY; - private short doorZ; - private int doorDirection; - private String heightmap; - private int mapSize; - private int mapSizeX; - private int mapSizeY; - private RoomTile[][] roomTiles; - private RoomTile doorTile; - private final Room room; - boolean canMoveDiagonally = PathfinderConfig.canMoveDiagonally(); - public RoomLayout(ResultSet set, Room room) throws SQLException { - this.room = room; - try { - this.name = set.getString("name"); - this.doorX = set.getShort("door_x"); - this.doorY = set.getShort("door_y"); + private static final Logger LOGGER = LoggerFactory.getLogger(RoomLayout.class); + protected static final int BASICMOVEMENTCOST = 10; + protected static final int DIAGONALMOVEMENTCOST = 14; + public static double MAXIMUM_STEP_HEIGHT = 1.5; + public static boolean ALLOW_FALLING = true; + public boolean CANMOVEDIAGONALY = true; + private String name; + private short doorX; + private short doorY; + private short doorZ; + private int doorDirection; + private String heightmap; + private int mapSize; + private int mapSizeX; + private int mapSizeY; + private RoomTile[][] roomTiles; + private RoomTile doorTile; + private Room room; + private Pathfinder pathfinder; - this.doorDirection = set.getInt("door_dir"); - this.heightmap = set.getString("heightmap"); + public RoomLayout(ResultSet set, Room room) throws SQLException { + this.room = room; + try { + this.name = set.getString("name"); + this.doorX = set.getShort("door_x"); + this.doorY = set.getShort("door_y"); - this.parse(); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } + this.doorDirection = set.getInt("door_dir"); + this.heightmap = set.getString("heightmap"); + + this.parse(); + this.pathfinder = new PathfinderImpl(this.room, MAXIMUM_STEP_HEIGHT, + Emulator.getConfig().getBoolean("pathfinder.step.allow.falling", true), + Emulator.getConfig().getBoolean("pathfinder.retro-style.diagonals", false)); + } catch (Exception e) { + LOGGER.error("Caught exception", e); + } + } + + public static boolean squareInSquare(Rectangle outerSquare, Rectangle innerSquare) { + if (outerSquare.x > innerSquare.x) { + return false; } - public class PathfinderConfig { - private static final boolean canMoveDiagonally = Emulator.getConfig().getBoolean("pathfinder.diagonal.enabled", false); - public static boolean canMoveDiagonally() { - return canMoveDiagonally; - } + if (outerSquare.y > innerSquare.y) { + return false; } - public static boolean squareInSquare(Rectangle outerSquare, Rectangle innerSquare) { - if (outerSquare.x > innerSquare.x) - return false; - - if (outerSquare.y > innerSquare.y) - return false; - - if (outerSquare.x + outerSquare.width < innerSquare.x + innerSquare.width) - return false; - - return outerSquare.y + outerSquare.height >= innerSquare.y + innerSquare.height; + if (outerSquare.x + outerSquare.width < innerSquare.x + innerSquare.width) { + return false; } - public static boolean tileInSquare(Rectangle square, RoomTile tile) { - return (square.contains(tile.x, tile.y)); + return outerSquare.y + outerSquare.height >= innerSquare.y + innerSquare.height; + } + + public static boolean tileInSquare(Rectangle square, RoomTile tile) { + return (square.contains(tile.x, tile.y)); + } + + public static boolean pointInSquare(int x1, int y1, int x2, int y2, int pointX, int pointY) { + return (pointX >= x1 && pointY >= y1) && (pointX <= x2 && pointY <= y2); + } + + public static boolean tilesAdjecent(RoomTile one, RoomTile two) { + return !(one == null || two == null) && !(Math.abs(one.x - two.x) > 1) && !( + Math.abs(one.y - two.y) > 1); + } + + public static Rectangle getRectangle(int x, int y, int width, int length, int rotation) { + rotation = (rotation % 8); + + if (rotation == 2 || rotation == 6) { + return new Rectangle(x, y, length, width); } - public static boolean pointInSquare(int x1, int y1, int x2, int y2, int pointX, int pointY) { - return (pointX >= x1 && pointY >= y1) && (pointX <= x2 && pointY <= y2); - } + return new Rectangle(x, y, width, length); + } - public static boolean tilesAdjecent(RoomTile one, RoomTile two) { - return !(one == null || two == null) && !(Math.abs(one.x - two.x) > 1) && !(Math.abs(one.y - two.y) > 1); - } + public static boolean tilesAdjecent(RoomTile tile, RoomTile comparator, int width, int length, + int rotation) { + Rectangle rectangle = getRectangle(comparator.x, comparator.y, width, length, rotation); + rectangle = new Rectangle(rectangle.x - 1, rectangle.y - 1, rectangle.width + 2, + rectangle.height + 2); - public static Rectangle getRectangle(int x, int y, int width, int length, int rotation) { - rotation = (rotation % 8); + return rectangle.contains(tile.x, tile.y); + } - if (rotation == 2 || rotation == 6) { - return new Rectangle(x, y, length, width); + public void parse() { + String[] modelTemp = this.heightmap.replace("\n", "").split(Character.toString('\r')); + + this.mapSize = 0; + this.mapSizeX = modelTemp[0].length(); + this.mapSizeY = modelTemp.length; + this.roomTiles = new RoomTile[this.mapSizeX][this.mapSizeY]; + + for (short y = 0; y < this.mapSizeY; y++) { + if (modelTemp[y].isEmpty() || modelTemp[y].equalsIgnoreCase("\r")) { + continue; + } + + for (short x = 0; x < this.mapSizeX; x++) { + if (modelTemp[y].length() != this.mapSizeX) { + break; } - return new Rectangle(x, y, width, length); + String square = modelTemp[y].substring(x, x + 1).trim().toLowerCase(); + RoomTileState state = RoomTileState.OPEN; + short height = 0; + if (square.equalsIgnoreCase("x")) { + state = RoomTileState.INVALID; + } else { + if (square.isEmpty()) { + height = 0; + } else if (Emulator.isNumeric(square)) { + height = Short.parseShort(square); + } else { + height = (short) (10 + "ABCDEFGHIJKLMNOPQRSTUVWXYZ".indexOf(square.toUpperCase())); + } + } + this.mapSize += 1; + + this.roomTiles[x][y] = new RoomTile(x, y, height, state, true); + } } - public static boolean tilesAdjecent(RoomTile tile, RoomTile comparator, int width, int length, int rotation) { - Rectangle rectangle = getRectangle(comparator.x, comparator.y, width, length, rotation); - rectangle = new Rectangle(rectangle.x - 1, rectangle.y - 1, rectangle.width + 2, rectangle.height + 2); + this.doorTile = this.roomTiles[this.doorX][this.doorY]; - return rectangle.contains(tile.x, tile.y); + if (this.doorTile != null) { + this.doorTile.setAllowStack(false); + RoomTile doorFrontTile = this.getTileInFront(this.doorTile, this.doorDirection); + + if (doorFrontTile != null && this.tileExists(doorFrontTile.x, doorFrontTile.y)) { + if (this.roomTiles[doorFrontTile.x][doorFrontTile.y].state != RoomTileState.INVALID) { + if (this.doorZ != this.roomTiles[doorFrontTile.x][doorFrontTile.y].z + || this.roomTiles[this.doorX][this.doorY].state + != this.roomTiles[doorFrontTile.x][doorFrontTile.y].state) { + this.doorZ = this.roomTiles[doorFrontTile.x][doorFrontTile.y].z; + this.roomTiles[this.doorX][this.doorY].state = RoomTileState.OPEN; + } + } + } + } + } + + public String getName() { + return this.name; + } + + public short getDoorX() { + return this.doorX; + } + + public void setDoorX(short doorX) { + this.doorX = doorX; + } + + public short getDoorY() { + return this.doorY; + } + + public void setDoorY(short doorY) { + this.doorY = doorY; + } + + public int getDoorZ() { + return this.doorZ; + } + + public RoomTile getDoorTile() { + return this.doorTile; + } + + public int getDoorDirection() { + return this.doorDirection; + } + + public void setDoorDirection(int doorDirection) { + this.doorDirection = doorDirection; + } + + public String getHeightmap() { + return this.heightmap; + } + + public void setHeightmap(String heightMap) { + this.heightmap = heightMap; + } + + public int getMapSize() { + return this.mapSize; + } + + public int getMapSizeX() { + return this.mapSizeX; + } + + public int getMapSizeY() { + return this.mapSizeY; + } + + public short getHeightAtSquare(int x, int y) { + if (x < 0 || y < 0 || x >= this.getMapSizeX() || y >= this.getMapSizeY()) { + return 0; } - public void parse() { - String[] modelTemp = this.heightmap.replace("\n", "").split(Character.toString('\r')); + return this.roomTiles[x][y].z; + } - this.mapSize = 0; - this.mapSizeX = modelTemp[0].length(); - this.mapSizeY = modelTemp.length; - this.roomTiles = new RoomTile[this.mapSizeX][this.mapSizeY]; + public double getStackHeightAtSquare(int x, int y) { + if (x < 0 || y < 0 || x >= this.getMapSizeX() || y >= this.getMapSizeY()) { + return 0; + } - for (short y = 0; y < this.mapSizeY; y++) { - if (modelTemp[y].isEmpty() || modelTemp[y].equalsIgnoreCase("\r")) { - continue; - } - - for (short x = 0; x < this.mapSizeX; x++) { - if (modelTemp[y].length() != this.mapSizeX) { - break; - } - - String square = modelTemp[y].substring(x, x + 1).trim().toLowerCase(); - RoomTileState state = RoomTileState.OPEN; - short height = 0; - if (square.equalsIgnoreCase("x")) { - state = RoomTileState.INVALID; - } else { - if (square.isEmpty()) { - height = 0; - } else if (Emulator.isNumeric(square)) { - height = Short.parseShort(square); - } else { - height = (short) (10 + "ABCDEFGHIJKLMNOPQRSTUVWXYZ".indexOf(square.toUpperCase())); - } - } - this.mapSize += 1; - - this.roomTiles[x][y] = new RoomTile(x, y, height, state, true); + return this.roomTiles[x][y].getStackHeight(); + } + + public double getRelativeHeightAtSquare(int x, int y) { + if (x < 0 || y < 0 || x >= this.getMapSizeX() || y >= this.getMapSizeY()) { + return 0; + } + + return this.roomTiles[x][y].relativeHeight(); + } + + public RoomTile getTile(short x, short y) { + if (this.tileExists(x, y)) { + return this.roomTiles[x][y]; + } + + return null; + } + + public boolean tileExists(short x, short y) { + return !(x < 0 || y < 0 || x >= this.getMapSizeX() || y >= this.getMapSizeY()); + } + + public boolean tileWalkable(short x, short y) { + return this.tileExists(x, y) && this.roomTiles[x][y].state == RoomTileState.OPEN + && this.roomTiles[x][y].isWalkable(); + } + + public boolean isVoidTile(short x, short y) { + if (!this.tileExists(x, y)) { + return true; + } + return this.roomTiles[x][y].state == RoomTileState.INVALID; + } + + public String getRelativeMap() { + return this.heightmap.replace("\r\n", "\r"); + }//re + + public void moveDiagonally(boolean value) { + this.CANMOVEDIAGONALY = value; + } + + public RoomTile getTileInFront(RoomTile tile, int rotation) { + return this.getTileInFront(tile, rotation, 0); + } + + public RoomTile getTileInFront(RoomTile tile, int rotation, int offset) { + int offsetX = 0; + int offsetY = 0; + + rotation = rotation % 8; + switch (rotation) { + case 0: + offsetY--; + break; + case 1: + offsetX++; + offsetY--; + break; + case 2: + offsetX++; + break; + case 3: + offsetX++; + offsetY++; + break; + case 4: + offsetY++; + break; + case 5: + offsetX--; + offsetY++; + break; + case 6: + offsetX--; + break; + case 7: + offsetX--; + offsetY--; + break; + } + + short x = tile.x; + short y = tile.y; + + for (int i = 0; i <= offset; i++) { + x += offsetX; + y += offsetY; + } + + return this.getTile(x, y); + } + + public List getTilesInFront(RoomTile tile, int rotation, int amount) { + List tiles = new ArrayList<>(amount); + RoomTile previous = tile; + for (int i = 0; i < amount; i++) { + RoomTile t = this.getTileInFront(previous, rotation, i); + + if (t != null) { + tiles.add(t); + } else { + break; + } + } + + return tiles; + } + + public List getTilesAround(RoomTile tile) { + return getTilesAround(tile, 0); + } + + public List getTilesAround(RoomTile tile, int directionOffset) { + return getTilesAround(tile, directionOffset, true); + } + + public List getTilesAround(RoomTile tile, int directionOffset, boolean diagonal) { + List tiles = new ArrayList<>(diagonal ? 8 : 4); + + if (tile != null) { + for (int i = 0; i < 8; i += (diagonal ? 1 : 2)) { + RoomTile t = this.getTileInFront(tile, (i + directionOffset) % 8); + if (t != null) { + tiles.add(t); + } + } + } + + return tiles; + } + + public List getWalkableTilesAround(RoomTile tile) { + return getWalkableTilesAround(tile, 0); + } + + public List getWalkableTilesAround(RoomTile tile, int directionOffset) { + List availableTiles = new ArrayList<>(this.getTilesAround(tile, directionOffset)); + + List toRemove = new ArrayList<>(); + + for (RoomTile t : availableTiles) { + if (t == null || t.state != RoomTileState.OPEN || !t.isWalkable()) { + toRemove.add(t); + } + } + + for (RoomTile t : toRemove) { + availableTiles.remove(t); + } + + return availableTiles; + } + + public boolean fitsOnMap(RoomTile tile, int width, int length, int rotation) { + if (tile != null) { + if (rotation == 0 || rotation == 4) { + for (short i = tile.x; i <= (tile.x + (width - 1)); i++) { + for (short j = tile.y; j <= (tile.y + (length - 1)); j++) { + RoomTile t = this.getTile(i, j); + + if (t == null || t.getState() == RoomTileState.INVALID) { + return false; } + } } + } else if (rotation == 2 || rotation == 6) { + for (short i = tile.x; i <= (tile.x + (length - 1)); i++) { + for (short j = tile.y; j <= (tile.y + (width - 1)); j++) { + RoomTile t = this.getTile(i, j); - this.doorTile = this.roomTiles[this.doorX][this.doorY]; - - if (this.doorTile != null) { - this.doorTile.setAllowStack(false); - RoomTile doorFrontTile = this.getTileInFront(this.doorTile, this.doorDirection); - - if (doorFrontTile != null && this.tileExists(doorFrontTile.x, doorFrontTile.y)) { - if (this.roomTiles[doorFrontTile.x][doorFrontTile.y].state != RoomTileState.INVALID) { - if (this.doorZ != this.roomTiles[doorFrontTile.x][doorFrontTile.y].z || this.roomTiles[this.doorX][this.doorY].state != this.roomTiles[doorFrontTile.x][doorFrontTile.y].state) { - this.doorZ = this.roomTiles[doorFrontTile.x][doorFrontTile.y].z; - this.roomTiles[this.doorX][this.doorY].state = RoomTileState.OPEN; - } - } + if (t == null || t.getState() == RoomTileState.INVALID) { + return false; } + } } - } - - public String getName() { - return this.name; - } - - public short getDoorX() { - return this.doorX; - } - - public void setDoorX(short doorX) { - this.doorX = doorX; - } - - public short getDoorY() { - return this.doorY; - } - - public void setDoorY(short doorY) { - this.doorY = doorY; - } - - public int getDoorZ() { - return this.doorZ; - } - - public RoomTile getDoorTile() { - return this.doorTile; - } - - public int getDoorDirection() { - return this.doorDirection; - } - - public void setDoorDirection(int doorDirection) { - this.doorDirection = doorDirection; - } - - public String getHeightmap() { - return this.heightmap; - } - - public void setHeightmap(String heightMap) { - this.heightmap = heightMap; - } - - public int getMapSize() { - return this.mapSize; - } - - public int getMapSizeX() { - return this.mapSizeX; - } - - public int getMapSizeY() { - return this.mapSizeY; - } - - public short getHeightAtSquare(int x, int y) { - if (x < 0 || - y < 0 || - x >= this.getMapSizeX() || - y >= this.getMapSizeY()) - return 0; - - return this.roomTiles[x][y].z; - } - - public double getStackHeightAtSquare(int x, int y) { - if (x < 0 || - y < 0 || - x >= this.getMapSizeX() || - y >= this.getMapSizeY()) - return 0; - - return this.roomTiles[x][y].getStackHeight(); - } - - public double getRelativeHeightAtSquare(int x, int y) { - if (x < 0 || - y < 0 || - x >= this.getMapSizeX() || - y >= this.getMapSizeY()) - return 0; - - return this.roomTiles[x][y].relativeHeight(); - } - - public RoomTile getTile(short x, short y) { - if (this.tileExists(x, y)) { - return this.roomTiles[x][y]; + } else if (rotation == 1 || rotation == 3 || rotation == 5 || rotation == 7) { + RoomTile t = this.getTile(tile.x, tile.y); + if (t == null || t.getState() == RoomTileState.INVALID) { + return false; } - - return null; + } } - public boolean tileExists(short x, short y) { - return !(x < 0 || y < 0 || x >= this.getMapSizeX() || y >= this.getMapSizeY()); - } + return true; + } - public boolean tileWalkable(short x, short y) { - return this.tileExists(x, y) && this.roomTiles[x][y].state == RoomTileState.OPEN && this.roomTiles[x][y].isWalkable(); - } + public THashSet getTilesAt(RoomTile tile, int width, int length, int rotation) { + THashSet pointList = new THashSet<>(width * length, 0.1f); - public boolean isVoidTile(short x, short y) { - if (!this.tileExists(x, y)) return true; - return this.roomTiles[x][y].state == RoomTileState.INVALID; - } - - public String getRelativeMap() { - return this.heightmap.replace("\r\n", "\r"); - } - - public final Deque findPath(RoomTile oldTile, RoomTile newTile, RoomTile goalLocation, RoomUnit roomUnit) { - return this.findPath(oldTile, newTile, goalLocation, roomUnit, false); - } - - /// Pathfinder Reworked By Quadral, thanks buddy!! You Saved Morningstar <3 - public final Deque findPath(RoomTile oldTile, RoomTile newTile, RoomTile goalLocation, RoomUnit roomUnit, boolean isWalktroughRetry) { - if (this.room == null || !this.room.isLoaded() || oldTile == null || newTile == null || oldTile.equals(newTile) || newTile.state == RoomTileState.INVALID) - return new LinkedList<>(); - - LinkedList openList = new LinkedList<>(); - List closedList = new LinkedList<>(); - openList.add(oldTile.copy()); - - RoomTile doorTile = this.room.getLayout().getDoorTile(); - - long startMillis = System.currentTimeMillis(); - - while (!openList.isEmpty()) { - if (System.currentTimeMillis() - startMillis > Emulator.getConfig().getInt("pathfinder.execution_time.milli", 25) && Emulator.getConfig().getBoolean("pathfinder.max_execution_time.enabled", false)) { - return null; - } - - RoomTile current = this.lowestFInOpen(openList); - if (current.x == newTile.x && current.y == newTile.y) { - return this.calcPath(this.findTile(openList, oldTile.x, oldTile.y), current); - } - - closedList.add(current); - openList.remove(current); - - List adjacentNodes = this.getAdjacent(openList, current, newTile, roomUnit); - for (RoomTile currentAdj : adjacentNodes) { - if (closedList.contains(currentAdj)) continue; - - if (roomUnit.canOverrideTile(currentAdj)) { - currentAdj.setPrevious(current); - currentAdj.sethCosts(this.findTile(openList, newTile.x, newTile.y)); - currentAdj.setgCosts(current); - openList.add(currentAdj); - continue; - } - - if (currentAdj.state == RoomTileState.BLOCKED || ((currentAdj.state == RoomTileState.SIT || currentAdj.state == RoomTileState.LAY) && !currentAdj.equals(goalLocation))) { - closedList.add(currentAdj); - openList.remove(currentAdj); - continue; - } - - double height = currentAdj.getStackHeight() - current.getStackHeight(); - - if ((!ALLOW_FALLING && height < -MAXIMUM_STEP_HEIGHT) || (currentAdj.state == RoomTileState.OPEN && height > MAXIMUM_STEP_HEIGHT)) { - closedList.add(currentAdj); - openList.remove(currentAdj); - continue; - } - - if (currentAdj.hasUnits() && doorTile.distance(currentAdj) > 2 && (!isWalktroughRetry || !this.room.isAllowWalkthrough() || currentAdj.equals(goalLocation))) { - closedList.add(currentAdj); - openList.remove(currentAdj); - continue; - } - - if (!openList.contains(currentAdj)) { - currentAdj.setPrevious(current); - currentAdj.sethCosts(this.findTile(openList, newTile.x, newTile.y)); - currentAdj.setgCosts(current); - openList.add(currentAdj); - } else if (currentAdj.getgCosts() > currentAdj.calculategCosts(current)) { - currentAdj.setPrevious(current); - currentAdj.setgCosts(current); - } - } - } - - if (this.room.isAllowWalkthrough() && !isWalktroughRetry) { - return this.findPath(oldTile, newTile, goalLocation, roomUnit, true); - } - - return null; - } - - private RoomTile findTile(List tiles, short x, short y) { - for (RoomTile tile : tiles) { - if (x == tile.x && y == tile.y) { - return tile; - } - } - - RoomTile tile = this.getTile(x, y); - - if (tile != null) { - return tile.copy(); - } - return null; - } - - public Deque calcPath(RoomTile start, RoomTile goal) { - LinkedList path = new LinkedList<>(); - if (start == null) - return path; - - RoomTile curr = goal; - while (curr != null) { - path.addFirst(this.getTile(curr.x, curr.y)); - curr = curr.getPrevious(); - if ((curr != null) && (curr.equals(start))) { - return path; - } - } - return path; - } - - private RoomTile lowestFInOpen(List openList) { - if (openList == null) - return null; - - RoomTile cheapest = openList.get(0); - for (RoomTile anOpenList : openList) { - if (anOpenList.getfCosts() < cheapest.getfCosts()) { - cheapest = anOpenList; - } - } - return cheapest; - } - - private List getAdjacent(List openList, RoomTile node, RoomTile nextTile, RoomUnit unit) { - short x = node.x; - short y = node.y; - List adj = new LinkedList<>(); - if (x > 0) { - RoomTile temp = this.findTile(openList, (short) (x - 1), y); - if (this.canWalkOn(temp, unit)) { - if (temp.state != RoomTileState.SIT || nextTile.getStackHeight() - node.getStackHeight() <= 2.0) { - temp.isDiagonally(false); - if (!adj.contains(temp)) - adj.add(temp); - } - } - } - if (x < this.mapSizeX) { - RoomTile temp = this.findTile(openList, (short) (x + 1), y); - if (this.canWalkOn(temp, unit)) { - if (temp.state != RoomTileState.SIT || nextTile.getStackHeight() - node.getStackHeight() <= 2.0) { - temp.isDiagonally(false); - if (!adj.contains(temp)) - adj.add(temp); - } - } - } - if (y > 0) { - RoomTile temp = this.findTile(openList, x, (short) (y - 1)); - if (this.canWalkOn(temp, unit)) { - if (temp.state != RoomTileState.SIT || nextTile.getStackHeight() - node.getStackHeight() <= 2.0) { - temp.isDiagonally(false); - if (!adj.contains(temp)) - adj.add(temp); - } - } - } - if (y < this.mapSizeY) { - RoomTile temp = this.findTile(openList, x, (short) (y + 1)); - if (this.canWalkOn(temp, unit)) { - if (temp.state != RoomTileState.SIT || nextTile.getStackHeight() - node.getStackHeight() <= 2.0) { - temp.isDiagonally(false); - if (!adj.contains(temp)) - adj.add(temp); - } - } - } - - if (this.CANMOVEDIAGONALY) { - // Diagonal: bottom-right - if ((x < this.mapSizeX) && (y < this.mapSizeY)) { - RoomTile offX = this.findTile(openList, (short) (x + 1), y); - RoomTile offY = this.findTile(openList, x, (short) (y + 1)); - if (offX != null && offY != null && (canMoveDiagonally || offX.isWalkable() || offY.isWalkable())) { - RoomTile temp = this.findTile(openList, (short) (x + 1), (short) (y + 1)); - if (this.canWalkOn(temp, unit)) { - if (temp.state != RoomTileState.SIT || nextTile.getStackHeight() - node.getStackHeight() <= 2.0) { - temp.isDiagonally(true); - if (!adj.contains(temp)) - adj.add(temp); - } - } - } - } - - // Diagonal: top-left - if ((x > 0) && (y > 0)) { - RoomTile offX = this.findTile(openList, (short) (x - 1), y); - RoomTile offY = this.findTile(openList, x, (short) (y - 1)); - if (offX != null && offY != null && (canMoveDiagonally || offX.isWalkable() || offY.isWalkable())) { - RoomTile temp = this.findTile(openList, (short) (x - 1), (short) (y - 1)); - if (this.canWalkOn(temp, unit)) { - if (temp.state != RoomTileState.SIT || nextTile.getStackHeight() - node.getStackHeight() <= 2.0) { - temp.isDiagonally(true); - if (!adj.contains(temp)) - adj.add(temp); - } - } - } - } - - // Diagonal: bottom-left - if ((x > 0) && (y < this.mapSizeY)) { - RoomTile offX = this.findTile(openList, (short) (x - 1), y); - RoomTile offY = this.findTile(openList, x, (short) (y + 1)); - if (offX != null && offY != null && (canMoveDiagonally || offX.isWalkable() || offY.isWalkable())) { - RoomTile temp = this.findTile(openList, (short) (x - 1), (short) (y + 1)); - if (this.canWalkOn(temp, unit)) { - if (temp.state != RoomTileState.SIT || nextTile.getStackHeight() - node.getStackHeight() <= 2.0) { - temp.isDiagonally(true); - if (!adj.contains(temp)) - adj.add(temp); - } - } - } - } - - // Diagonal: top-right - if ((x < this.mapSizeX) && (y > 0)) { - RoomTile offX = this.findTile(openList, (short) (x + 1), y); - RoomTile offY = this.findTile(openList, x, (short) (y - 1)); - if (offX != null && offY != null && (canMoveDiagonally || offX.isWalkable() || offY.isWalkable())) { - RoomTile temp = this.findTile(openList, (short) (x + 1), (short) (y - 1)); - if (this.canWalkOn(temp, unit)) { - if (temp.state != RoomTileState.SIT || nextTile.getStackHeight() - node.getStackHeight() <= 2.0) { - temp.isDiagonally(true); - if (!adj.contains(temp)) - adj.add(temp); - } - } - } - } - } - return adj; - } - - private boolean canWalkOn(RoomTile tile, RoomUnit unit) { - return tile != null && (unit.canOverrideTile(tile) || (tile.state != RoomTileState.BLOCKED && tile.state != RoomTileState.INVALID)); - } - - public void moveDiagonally(boolean value) { - this.CANMOVEDIAGONALY = value; - } - - public RoomTile getTileInFront(RoomTile tile, int rotation) { - return this.getTileInFront(tile, rotation, 0); - } - - public RoomTile getTileInFront(RoomTile tile, int rotation, int offset) { - int offsetX = 0; - int offsetY = 0; - - rotation = rotation % 8; - switch (rotation) { - case 0: - offsetY--; - break; - case 1: - offsetX++; - offsetY--; - break; - case 2: - offsetX++; - break; - case 3: - offsetX++; - offsetY++; - break; - case 4: - offsetY++; - break; - case 5: - offsetX--; - offsetY++; - break; - case 6: - offsetX--; - break; - case 7: - offsetX--; - offsetY--; - break; - } - - short x = tile.x; - short y = tile.y; - - for (int i = 0; i <= offset; i++) { - x += offsetX; - y += offsetY; - } - - return this.getTile(x, y); - } - - public List getTilesInFront(RoomTile tile, int rotation, int amount) { - List tiles = new ArrayList<>(amount); - RoomTile previous = tile; - for (int i = 0; i < amount; i++) { - RoomTile t = this.getTileInFront(previous, rotation, i); + if (tile != null) { + if (rotation == 0 || rotation == 4) { + for (short i = tile.x; i <= (tile.x + (width - 1)); i++) { + for (short j = tile.y; j <= (tile.y + (length - 1)); j++) { + RoomTile t = this.getTile(i, j); if (t != null) { - tiles.add(t); - } else { - break; + pointList.add(t); } + } } + } else if (rotation == 2 || rotation == 6) { + for (short i = tile.x; i <= (tile.x + (length - 1)); i++) { + for (short j = tile.y; j <= (tile.y + (width - 1)); j++) { + RoomTile t = this.getTile(i, j); - return tiles; - } - - public List getTilesAround(RoomTile tile) { - return getTilesAround(tile, 0); - } - - public List getTilesAround(RoomTile tile, int directionOffset) { - return getTilesAround(tile, directionOffset, true); - } - - public List getTilesAround(RoomTile tile, int directionOffset, boolean diagonal) { - List tiles = new ArrayList<>(diagonal ? 8 : 4); - - if (tile != null) { - for (int i = 0; i < 8; i += (diagonal ? 1 : 2)) { - RoomTile t = this.getTileInFront(tile, (i + directionOffset) % 8); - if (t != null) { - tiles.add(t); - } + if (t != null) { + pointList.add(t); } + } } - - return tiles; - } - - public List getWalkableTilesAround(RoomTile tile) { - return getWalkableTilesAround(tile, 0); - } - - public List getWalkableTilesAround(RoomTile tile, int directionOffset) { - List availableTiles = new ArrayList<>(this.getTilesAround(tile, directionOffset)); - - List toRemove = new ArrayList<>(); - - for (RoomTile t : availableTiles) { - if (t == null || t.state != RoomTileState.OPEN || !t.isWalkable()) { - toRemove.add(t); - } + } else if (rotation == 1 || rotation == 3 || rotation == 5 || rotation == 7) { + RoomTile t = this.getTile(tile.x, tile.y); + if (t != null) { + pointList.add(t); } - - for (RoomTile t : toRemove) { - availableTiles.remove(t); - } - - return availableTiles; + } } + return pointList; + } - public boolean fitsOnMap(RoomTile tile, int width, int length, int rotation) { - if (tile != null) { - if (rotation == 0 || rotation == 4) { - for (short i = tile.x; i <= (tile.x + (width - 1)); i++) { - for (short j = tile.y; j <= (tile.y + (length - 1)); j++) { - RoomTile t = this.getTile(i, j); + public Pathfinder getPathfinder() { + return pathfinder; + } - if (t == null || t.state == RoomTileState.INVALID) { - return false; - } - } - } - } else if (rotation == 2 || rotation == 6) { - for (short i = tile.x; i <= (tile.x + (length - 1)); i++) { - for (short j = tile.y; j <= (tile.y + (width - 1)); j++) { - RoomTile t = this.getTile(i, j); - - if (t == null || t.state == RoomTileState.INVALID) { - return false; - } - } - } - } - } - - return true; - } - - public THashSet getTilesAt(RoomTile tile, int width, int length, int rotation) { - THashSet pointList = new THashSet<>(width * length, 0.1f); - - if (tile != null) { - if (rotation == 0 || rotation == 4) { - for (short i = tile.x; i <= (tile.x + (width - 1)); i++) { - for (short j = tile.y; j <= (tile.y + (length - 1)); j++) { - RoomTile t = this.getTile(i, j); - - if (t != null) { - pointList.add(t); - } - } - } - } else if (rotation == 2 || rotation == 6) { - for (short i = tile.x; i <= (tile.x + (length - 1)); i++) { - for (short j = tile.y; j <= (tile.y + (width - 1)); j++) { - RoomTile t = this.getTile(i, j); - - if (t != null) { - pointList.add(t); - } - } - } - } - } - - return pointList; - } + public void setPathfinder(Pathfinder pathfinder) { + this.pathfinder = pathfinder; + } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomManager.java index 219c5e8a..0b652170 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomManager.java @@ -14,7 +14,6 @@ import com.eu.habbo.habbohotel.games.tag.IceTagGame; import com.eu.habbo.habbohotel.games.tag.RollerskateGame; import com.eu.habbo.habbohotel.games.wired.WiredGame; import com.eu.habbo.habbohotel.guilds.Guild; -import com.eu.habbo.habbohotel.items.interactions.InteractionTileWalkMagic; import com.eu.habbo.habbohotel.items.interactions.InteractionWired; import com.eu.habbo.habbohotel.messenger.MessengerBuddy; import com.eu.habbo.habbohotel.navigation.NavigatorFilterComparator; @@ -27,8 +26,7 @@ import com.eu.habbo.habbohotel.pets.PetTasks; import com.eu.habbo.habbohotel.polls.Poll; import com.eu.habbo.habbohotel.polls.PollManager; import com.eu.habbo.habbohotel.users.*; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.incoming.users.UserNuxEvent; import com.eu.habbo.messages.outgoing.generic.alerts.GenericErrorMessagesComposer; import com.eu.habbo.messages.outgoing.hotelview.HotelViewComposer; @@ -52,6 +50,7 @@ import com.eu.habbo.plugin.events.users.UserExitRoomEvent; import gnu.trove.iterator.TIntObjectIterator; import gnu.trove.map.hash.THashMap; import gnu.trove.procedure.TIntProcedure; +import gnu.trove.procedure.TObjectProcedure; import gnu.trove.set.hash.THashSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -153,7 +152,7 @@ public class RoomManager { public THashMap> findRooms(NavigatorFilterField filterField, String value, int category, boolean showInvisible) { THashMap> rooms = new THashMap<>(); - String query = filterField.databaseQuery + " AND rooms.state NOT LIKE " + (showInvisible ? "''" : "'invisible'") + (category >= 0 ? "AND rooms.category = '" + category + "'" : "") + " ORDER BY rooms.users, rooms.id DESC LIMIT " + (page * NavigatorManager.MAXIMUM_RESULTS_PER_PAGE) + ((page * NavigatorManager.MAXIMUM_RESULTS_PER_PAGE) + NavigatorManager.MAXIMUM_RESULTS_PER_PAGE); + String query = filterField.databaseQuery + " AND rooms.state NOT LIKE " + (showInvisible ? "''" : "'invisible'") + (category >= 0 ? "AND rooms.category = '" + category + "'" : "") + " ORDER BY rooms.users, rooms.id DESC LIMIT " + (page * NavigatorManager.MAXIMUM_RESULTS_PER_PAGE) + "" + ((page * NavigatorManager.MAXIMUM_RESULTS_PER_PAGE) + NavigatorManager.MAXIMUM_RESULTS_PER_PAGE); try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement(query)) { statement.setString(1, (filterField.comparator == NavigatorFilterComparator.EQUALS ? value : "%" + value + "%")); try (ResultSet set = statement.executeQuery()) { @@ -290,6 +289,14 @@ public class RoomManager { return loadRoom(id, false); } + /** + * Loads a room, optionally loading its data. + * If the room is already being loaded in the background, this will wait for that to complete. + * + * @param id The room ID + * @param loadData Whether to load room data (items, bots, pets, etc.) + * @return The loaded room, or null if not found + */ public Room loadRoom(int id, boolean loadData) { Room room = null; @@ -301,7 +308,10 @@ public class RoomManager { room = this.activeRooms.get(id); if (loadData) { - if (room.isPreLoaded() && !room.isLoaded()) { + if (room.isLoadingInProgress()) { + // Wait for background loading to complete + room.waitForLoad(); + } else if (room.isPreLoaded() && !room.isLoaded()) { room.loadData(); } } @@ -530,7 +540,6 @@ public class RoomManager { } if (overrideChecks || - habbo.roomBypass || room.isOwner(habbo) || room.getState() == RoomState.OPEN || habbo.hasPermission(Permission.ACC_ANYROOMOWNER) || @@ -803,16 +812,20 @@ public class RoomManager { } } - allFloorItems.forEach(object -> { - if (room.isHideWired() && object instanceof InteractionWired | object instanceof InteractionTileWalkMagic) - return true; + allFloorItems.forEach(new TObjectProcedure() { + @Override + public boolean execute(HabboItem object) { + if (room.isHideWired() && object instanceof InteractionWired) + return true; - floorItems.add(object); - if (floorItems.size() == 250) { - habbo.getClient().sendResponse(new RoomFloorItemsComposer(room.getFurniOwnerNames(), floorItems)); - floorItems.clear(); + floorItems.add(object); + if (floorItems.size() == 250) { + habbo.getClient().sendResponse(new RoomFloorItemsComposer(room.getFurniOwnerNames(), floorItems)); + floorItems.clear(); + } + + return true; } - return true; }); habbo.getClient().sendResponse(new RoomFloorItemsComposer(room.getFurniOwnerNames(), floorItems)); @@ -912,7 +925,7 @@ public class RoomManager { } } - WiredHandler.handle(WiredTriggerType.ENTER_ROOM, habbo.getRoomUnit(), room, null); + WiredManager.triggerUserEntersRoom(room, habbo.getRoomUnit()); room.habboEntered(habbo); if (!habbo.getHabboStats().nux && (room.isOwner(habbo) || room.isPublicRoom())) { @@ -1136,7 +1149,6 @@ public class RoomManager { return rooms; } - public ArrayList getGroupRoomsWithName(String name) { ArrayList rooms = new ArrayList<>(); @@ -1222,7 +1234,7 @@ public class RoomManager { public ArrayList getRoomsVisited(Habbo habbo, boolean includeSelf, int limit) { ArrayList rooms = new ArrayList<>(); - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT rooms.* FROM room_enter_log INNER JOIN rooms ON room_enter_log.room_id = rooms.id WHERE user_id = ? AND timestamp >= ? AND rooms.owner_id != ? GROUP BY rooms.id ORDER BY timestamp DESC LIMIT " + limit)) { + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT rooms.* FROM room_enter_log INNER JOIN rooms ON room_enter_log.room_id = rooms.id WHERE user_id = ? AND timestamp >= ? AND rooms.owner_id != ? GROUP BY rooms.id ORDER BY MAX(timestamp) DESC LIMIT " + limit)) { statement.setInt(1, habbo.getHabboInfo().getId()); statement.setInt(2, Emulator.getIntUnixTimestamp() - 259200); statement.setInt(3, (includeSelf ? 0 : habbo.getHabboInfo().getId())); @@ -1444,6 +1456,8 @@ public class RoomManager { ArrayList r = new ArrayList<>(); for (Room room : rooms) { + if (room.getTags().split(";").length == 0) + continue; for (String s : room.getTags().split(";")) { if (s.equalsIgnoreCase(filter)) diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomMessagingManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomMessagingManager.java new file mode 100644 index 00000000..10c8173c --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomMessagingManager.java @@ -0,0 +1,83 @@ +package com.eu.habbo.habbohotel.rooms; + +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer; + +/** + * Manages all messaging and communication within a room. + * Handles sending messages to Habbos, pet/bot chat, and alerts. + */ +public class RoomMessagingManager { + private final Room room; + + public RoomMessagingManager(Room room) { + this.room = room; + } + + // ==================== SEND MESSAGES ==================== + + /** + * Sends a message to all Habbos in the room. + */ + public void sendComposer(ServerMessage message) { + for (Habbo habbo : this.room.getHabbos()) { + if (habbo.getClient() == null) { + continue; + } + + habbo.getClient().sendResponse(message); + } + } + + /** + * Sends a message to all Habbos with rights in the room. + */ + public void sendComposerToHabbosWithRights(ServerMessage message) { + for (Habbo habbo : this.room.getHabbos()) { + if (this.room.hasRights(habbo)) { + habbo.getClient().sendResponse(message); + } + } + } + + // ==================== PET AND BOT CHAT ==================== + + /** + * Sends a pet chat message to all Habbos who don't ignore pets. + */ + public void petChat(ServerMessage message) { + for (Habbo habbo : this.room.getHabbos()) { + if (!habbo.getHabboStats().ignorePets) { + habbo.getClient().sendResponse(message); + } + } + } + + /** + * Sends a bot chat message to all Habbos who don't ignore bots. + */ + public void botChat(ServerMessage message) { + if (message == null) { + return; + } + + for (Habbo habbo : this.room.getHabbos()) { + if (habbo == null) { + continue; + } + if (!habbo.getHabboStats().ignoreBots) { + habbo.getClient().sendResponse(message); + } + } + } + + // ==================== ALERTS ==================== + + /** + * Sends an alert message to all Habbos in the room. + */ + public void alert(String message) { + this.sendComposer(new GenericAlertComposer(message).compose()); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomPromotionManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomPromotionManager.java new file mode 100644 index 00000000..2ed24e99 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomPromotionManager.java @@ -0,0 +1,127 @@ +package com.eu.habbo.habbohotel.rooms; + +import com.eu.habbo.Emulator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Manages room promotions. + */ +public class RoomPromotionManager { + private static final Logger LOGGER = LoggerFactory.getLogger(RoomPromotionManager.class); + + private final Room room; + private RoomPromotion promotion; + private volatile boolean promoted; + + public RoomPromotionManager(Room room) { + this.room = room; + this.promoted = false; + this.promotion = null; + } + + /** + * Loads the promotion from database. + */ + public void loadPromotion(boolean isPromoted, Connection connection) throws SQLException { + this.promoted = isPromoted; + + if (this.promoted) { + try (PreparedStatement statement = connection.prepareStatement( + "SELECT * FROM room_promotions WHERE room_id = ? AND end_timestamp > ? LIMIT 1")) { + statement.setInt(1, this.room.getId()); + statement.setInt(2, Emulator.getIntUnixTimestamp()); + + try (ResultSet promotionSet = statement.executeQuery()) { + this.promoted = false; + if (promotionSet.next()) { + this.promoted = true; + this.promotion = new RoomPromotion(this.room, promotionSet); + } + } + } + } + } + + /** + * Checks if the room is promoted. + */ + public boolean isPromoted() { + this.promoted = this.promotion != null && this.promotion.getEndTimestamp() > Emulator.getIntUnixTimestamp(); + this.room.setNeedsUpdate(true); + return this.promoted; + } + + /** + * Gets the current promotion. + */ + public RoomPromotion getPromotion() { + return this.promotion; + } + + /** + * Gets the promotion description. + */ + public String getPromotionDesc() { + if (this.promotion != null) { + return this.promotion.getDescription(); + } + return ""; + } + + /** + * Creates or updates a room promotion. + */ + public void createPromotion(String title, String description, int category) { + this.promoted = true; + + if (this.promotion == null) { + this.promotion = new RoomPromotion(this.room, title, description, + Emulator.getIntUnixTimestamp() + (120 * 60), Emulator.getIntUnixTimestamp(), category); + } else { + this.promotion.setTitle(title); + this.promotion.setDescription(description); + this.promotion.setEndTimestamp(Emulator.getIntUnixTimestamp() + (120 * 60)); + this.promotion.setCategory(category); + } + + try (Connection connection = Emulator.getDatabase().getDataSource() + .getConnection(); PreparedStatement statement = connection.prepareStatement( + "INSERT INTO room_promotions (room_id, title, description, end_timestamp, start_timestamp, category) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE title = ?, description = ?, end_timestamp = ?, category = ?")) { + statement.setInt(1, this.room.getId()); + statement.setString(2, title); + statement.setString(3, description); + statement.setInt(4, this.promotion.getEndTimestamp()); + statement.setInt(5, this.promotion.getStartTimestamp()); + statement.setInt(6, category); + statement.setString(7, this.promotion.getTitle()); + statement.setString(8, this.promotion.getDescription()); + statement.setInt(9, this.promotion.getEndTimestamp()); + statement.setInt(10, this.promotion.getCategory()); + statement.execute(); + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + + this.room.setNeedsUpdate(true); + } + + /** + * Sets the promoted flag. + */ + public void setPromoted(boolean promoted) { + this.promoted = promoted; + } + + /** + * Gets the raw promoted flag value. + */ + public boolean getPromotedFlag() { + return this.promoted; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomRightsManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomRightsManager.java new file mode 100644 index 00000000..f21b5289 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomRightsManager.java @@ -0,0 +1,433 @@ +package com.eu.habbo.habbohotel.rooms; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.guilds.Guild; +import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.messages.outgoing.rooms.RoomAddRightsListComposer; +import com.eu.habbo.messages.outgoing.rooms.RoomOwnerComposer; +import com.eu.habbo.messages.outgoing.rooms.RoomRemoveRightsListComposer; +import com.eu.habbo.messages.outgoing.rooms.RoomRightsComposer; +import com.eu.habbo.messages.outgoing.rooms.RoomRightsListComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserUnbannedComposer; +import com.eu.habbo.habbohotel.messenger.MessengerBuddy; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.plugin.events.users.UserRightsTakenEvent; +import gnu.trove.list.array.TIntArrayList; +import gnu.trove.map.hash.THashMap; +import gnu.trove.map.hash.TIntIntHashMap; +import gnu.trove.map.hash.TIntObjectHashMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Manages room rights, bans, and mutes. + */ +public class RoomRightsManager { + private static final Logger LOGGER = LoggerFactory.getLogger(RoomRightsManager.class); + + private final Room room; + private final TIntArrayList rights; + private final TIntObjectHashMap bannedHabbos; + private final TIntIntHashMap mutedHabbos; + + public RoomRightsManager(Room room) { + this.room = room; + this.rights = new TIntArrayList(); + this.bannedHabbos = new TIntObjectHashMap<>(); + this.mutedHabbos = new TIntIntHashMap(); + } + + /** + * Loads rights from database. + */ + public void loadRights(Connection connection) { + this.rights.clear(); + try (PreparedStatement statement = connection.prepareStatement( + "SELECT user_id FROM room_rights WHERE room_id = ?")) { + statement.setInt(1, this.room.getId()); + try (ResultSet set = statement.executeQuery()) { + while (set.next()) { + this.rights.add(set.getInt("user_id")); + } + } + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + } + + /** + * Loads bans from database. + */ + public void loadBans(Connection connection) { + this.bannedHabbos.clear(); + + try (PreparedStatement statement = connection.prepareStatement( + "SELECT users.username, users.id, room_bans.* FROM room_bans INNER JOIN users ON room_bans.user_id = users.id WHERE ends > ? AND room_bans.room_id = ?")) { + statement.setInt(1, Emulator.getIntUnixTimestamp()); + statement.setInt(2, this.room.getId()); + try (ResultSet set = statement.executeQuery()) { + while (set.next()) { + if (this.bannedHabbos.containsKey(set.getInt("user_id"))) { + continue; + } + + this.bannedHabbos.put(set.getInt("user_id"), new RoomBan(set)); + } + } + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + } + + /** + * Gets the guild right level for a habbo. + */ + public RoomRightLevels getGuildRightLevel(Habbo habbo) { + int guildId = this.room.getGuildId(); + if (guildId > 0 && habbo.getHabboStats().hasGuild(guildId)) { + Guild guild = Emulator.getGameEnvironment().getGuildManager().getGuild(guildId); + + if (Emulator.getGameEnvironment().getGuildManager().getOnlyAdmins(guild) + .get(habbo.getHabboInfo().getId()) != null) { + return RoomRightLevels.GUILD_ADMIN; + } + + if (guild.getRights()) { + return RoomRightLevels.GUILD_RIGHTS; + } + } + + return RoomRightLevels.NONE; + } + + /** + * @deprecated Use getGuildRightLevel instead. + */ + @Deprecated + public int guildRightLevel(Habbo habbo) { + return this.getGuildRightLevel(habbo).level; + } + + /** + * Checks if a habbo is the room owner. + */ + public boolean isOwner(Habbo habbo) { + return habbo.getHabboInfo().getId() == this.room.getOwnerId() || habbo.hasPermission( + Permission.ACC_ANYROOMOWNER); + } + + /** + * Checks if a habbo has rights in the room. + */ + public boolean hasRights(Habbo habbo) { + return this.isOwner(habbo) || this.rights.contains(habbo.getHabboInfo().getId()) || ( + habbo.getRoomUnit().getRightsLevel() != RoomRightLevels.NONE + && this.room.getCurrentHabbos().containsKey(habbo.getHabboInfo().getId())); + } + + /** + * Gives rights to a habbo. + */ + public void giveRights(Habbo habbo) { + if (habbo != null) { + this.giveRights(habbo.getHabboInfo().getId()); + } + } + + /** + * Gives rights to a user by ID. + */ + public void giveRights(int userId) { + if (this.rights.contains(userId)) { + return; + } + + if (this.rights.add(userId)) { + try (Connection connection = Emulator.getDatabase().getDataSource() + .getConnection(); PreparedStatement statement = connection.prepareStatement( + "INSERT INTO room_rights VALUES (?, ?)")) { + statement.setInt(1, this.room.getId()); + statement.setInt(2, userId); + statement.execute(); + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + } + + Habbo habbo = this.room.getHabbo(userId); + + if (habbo != null) { + this.refreshRightsForHabbo(habbo); + + this.room.sendComposer(new RoomAddRightsListComposer(this.room, habbo.getHabboInfo().getId(), + habbo.getHabboInfo().getUsername()).compose()); + } else { + Habbo owner = Emulator.getGameEnvironment().getHabboManager().getHabbo(this.room.getOwnerId()); + + if (owner != null) { + MessengerBuddy buddy = owner.getMessenger().getFriend(userId); + + if (buddy != null) { + this.room.sendComposer( + new RoomAddRightsListComposer(this.room, userId, buddy.getUsername()).compose()); + } + } + } + } + + /** + * Removes rights from a user. + */ + public void removeRights(int userId) { + Habbo habbo = this.room.getHabbo(userId); + + if (Emulator.getPluginManager() + .fireEvent(new UserRightsTakenEvent(this.room.getHabbo(this.room.getOwnerId()), userId, habbo)) + .isCancelled()) { + return; + } + + this.room.sendComposer(new RoomRemoveRightsListComposer(this.room, userId).compose()); + + if (this.rights.remove(userId)) { + try (Connection connection = Emulator.getDatabase().getDataSource() + .getConnection(); PreparedStatement statement = connection.prepareStatement( + "DELETE FROM room_rights WHERE room_id = ? AND user_id = ?")) { + statement.setInt(1, this.room.getId()); + statement.setInt(2, userId); + statement.execute(); + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + } + + if (habbo != null) { + this.room.getItemManager().ejectUserFurni(habbo.getHabboInfo().getId()); + habbo.getRoomUnit().setRightsLevel(RoomRightLevels.NONE); + habbo.getRoomUnit().removeStatus(RoomUnitStatus.FLAT_CONTROL); + this.refreshRightsForHabbo(habbo); + } + } + + /** + * Removes all rights from the room. + */ + public void removeAllRights() { + for (int userId : rights.toArray()) { + this.room.getItemManager().ejectUserFurni(userId); + } + + this.rights.clear(); + + try (Connection connection = Emulator.getDatabase().getDataSource() + .getConnection(); PreparedStatement statement = connection.prepareStatement( + "DELETE FROM room_rights WHERE room_id = ?")) { + statement.setInt(1, this.room.getId()); + statement.execute(); + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + + this.refreshRightsInRoom(); + } + + /** + * Refreshes rights for all users in the room. + */ + public void refreshRightsInRoom() { + Room room = this.room; + for (Habbo habbo : this.room.getHabbos()) { + if (habbo.getHabboInfo().getCurrentRoom() == room) { + this.refreshRightsForHabbo(habbo); + } + } + } + + /** + * Refreshes rights for a specific habbo. + */ + public void refreshRightsForHabbo(Habbo habbo) { + HabboItem item; + RoomRightLevels flatCtrl = RoomRightLevels.NONE; + if (habbo.getHabboStats().isRentingSpace()) { + item = this.room.getHabboItem(habbo.getHabboStats().getRentedItemId()); + + if (item != null) { + return; + } + } + + if (habbo.hasPermission(Permission.ACC_ANYROOMOWNER)) { + habbo.getClient().sendResponse(new RoomOwnerComposer()); + flatCtrl = RoomRightLevels.MODERATOR; + } else if (this.isOwner(habbo)) { + habbo.getClient().sendResponse(new RoomOwnerComposer()); + flatCtrl = RoomRightLevels.MODERATOR; + } else if (this.hasRights(habbo) && !this.room.hasGuild()) { + flatCtrl = RoomRightLevels.RIGHTS; + } else if (this.room.hasGuild()) { + flatCtrl = this.getGuildRightLevel(habbo); + } + + habbo.getClient().sendResponse(new RoomRightsComposer(flatCtrl)); + habbo.getRoomUnit().setStatus(RoomUnitStatus.FLAT_CONTROL, flatCtrl.level + ""); + habbo.getRoomUnit().setRightsLevel(flatCtrl); + habbo.getRoomUnit().statusUpdate(true); + + if (flatCtrl.equals(RoomRightLevels.MODERATOR)) { + habbo.getClient().sendResponse(new RoomRightsListComposer(this.room)); + } + } + + /** + * Gets all users with rights in the room. + */ + public THashMap getUsersWithRights() { + THashMap rightsMap = new THashMap<>(); + + if (!this.rights.isEmpty()) { + try (Connection connection = Emulator.getDatabase().getDataSource() + .getConnection(); PreparedStatement statement = connection.prepareStatement( + "SELECT users.username AS username, users.id as user_id FROM room_rights INNER JOIN users ON room_rights.user_id = users.id WHERE room_id = ?")) { + statement.setInt(1, this.room.getId()); + try (ResultSet set = statement.executeQuery()) { + while (set.next()) { + rightsMap.put(set.getInt("user_id"), set.getString("username")); + } + } + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + } + + return rightsMap; + } + + /** + * Unbans a user from the room. + */ + public void unbanHabbo(int userId) { + RoomBan ban = this.bannedHabbos.remove(userId); + + if (ban != null) { + ban.delete(); + } + + this.room.sendComposer(new RoomUserUnbannedComposer(this.room, userId).compose()); + } + + /** + * Checks if a habbo is banned from the room. + */ + public boolean isBanned(Habbo habbo) { + RoomBan ban = this.bannedHabbos.get(habbo.getHabboInfo().getId()); + + boolean banned = + ban != null && ban.endTimestamp > Emulator.getIntUnixTimestamp() && !habbo.hasPermission( + Permission.ACC_ANYROOMOWNER) && !habbo.hasPermission("acc_enteranyroom"); + + if (!banned && ban != null) { + this.unbanHabbo(habbo.getHabboInfo().getId()); + } + + return banned; + } + + /** + * Gets all banned users. + */ + public TIntObjectHashMap getBannedHabbos() { + return this.bannedHabbos; + } + + /** + * Adds a room ban. + */ + public void addRoomBan(RoomBan roomBan) { + this.bannedHabbos.put(roomBan.userId, roomBan); + } + + /** + * Mutes a habbo for a specified number of minutes. + */ + public void muteHabbo(Habbo habbo, int minutes) { + synchronized (this.mutedHabbos) { + this.mutedHabbos.put(habbo.getHabboInfo().getId(), + Emulator.getIntUnixTimestamp() + (minutes * 60)); + } + } + + /** + * Checks if a habbo is muted. + */ + public boolean isMuted(Habbo habbo) { + if (this.isOwner(habbo) || this.hasRights(habbo)) { + return false; + } + + if (this.mutedHabbos.containsKey(habbo.getHabboInfo().getId())) { + boolean time = + this.mutedHabbos.get(habbo.getHabboInfo().getId()) > Emulator.getIntUnixTimestamp(); + + if (!time) { + this.mutedHabbos.remove(habbo.getHabboInfo().getId()); + } + + return time; + } + + return false; + } + + /** + * Gets the mute end time for a habbo. + */ + public int getMuteEndTime(int habboId) { + return this.mutedHabbos.get(habboId); + } + + /** + * Gets the rights list. + */ + public TIntArrayList getRights() { + return this.rights; + } + + /** + * Gets the muted habbos map. + */ + public TIntIntHashMap getMutedHabbos() { + return this.mutedHabbos; + } + + /** + * Clears all mutes. + */ + public void clearMutes() { + synchronized (this.mutedHabbos) { + this.mutedHabbos.clear(); + } + } + + /** + * Refreshes guild rights for all users in the room. + */ + public void refreshGuildRightsInRoom() { + for (Habbo habbo : this.room.getHabbos()) { + if (habbo.getHabboInfo().getCurrentRoom() == this.room) { + if (habbo.getHabboInfo().getId() != this.room.getOwnerId()) { + if (!(habbo.hasPermission(Permission.ACC_ANYROOMOWNER) || habbo.hasPermission( + Permission.ACC_MOVEROTATE))) { + this.refreshRightsForHabbo(habbo); + } + } + } + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomRollerManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomRollerManager.java new file mode 100644 index 00000000..9306333c --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomRollerManager.java @@ -0,0 +1,377 @@ +package com.eu.habbo.habbohotel.rooms; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.items.interactions.InteractionGate; +import com.eu.habbo.habbohotel.items.interactions.InteractionPyramid; +import com.eu.habbo.habbohotel.items.interactions.InteractionRoller; +import com.eu.habbo.habbohotel.pets.Pet; +import com.eu.habbo.habbohotel.pets.RideablePet; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.messages.outgoing.MessageComposer; +import com.eu.habbo.messages.outgoing.rooms.items.FloorItemOnRollerComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUnitOnRollerComposer; +import com.eu.habbo.plugin.Event; +import com.eu.habbo.plugin.events.furniture.FurnitureRolledEvent; +import com.eu.habbo.plugin.events.users.UserRolledEvent; +import gnu.trove.set.hash.THashSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * Manages roller mechanics within a room. + * Handles roller cycling, moving items and units on rollers. + */ +public class RoomRollerManager { + private static final Logger LOGGER = LoggerFactory.getLogger(RoomRollerManager.class); + + private final Room room; + private long rollerCycle = System.currentTimeMillis(); + + public RoomRollerManager(Room room) { + this.room = room; + } + + /** + * Processes roller cycle for the room. + * @param updatedUnit Set to add updated room units to + * @param cycleTimestamp Current cycle timestamp + * @return true if roller cycle was processed + */ + public boolean processRollerCycle(THashSet updatedUnit, long cycleTimestamp) { + int rollerSpeed = this.room.getRollerSpeed(); + + if (rollerSpeed == -1) { + return false; + } + + if (this.rollerCycle < rollerSpeed) { + this.rollerCycle++; + return false; + } + + this.rollerCycle = 0; + + THashSet messages = new THashSet<>(); + List rollerFurniIds = new ArrayList<>(); + List rolledUnitIds = new ArrayList<>(); + + this.room.getRoomSpecialTypes().getRollers().forEachValue(roller -> { + processRoller(roller, messages, rollerFurniIds, rolledUnitIds, updatedUnit); + return true; + }); + + // Process pyramids + int currentTime = (int) (cycleTimestamp / 1000); + for (HabboItem pyramid : this.room.getRoomSpecialTypes().getItemsOfType(InteractionPyramid.class)) { + if (pyramid instanceof InteractionPyramid) { + if (((InteractionPyramid) pyramid).getNextChange() < currentTime) { + ((InteractionPyramid) pyramid).change(this.room); + } + } + } + + return true; + } + + /** + * Processes a single roller and its contents. + */ + private void processRoller(InteractionRoller roller, THashSet messages, + List rollerFurniIds, List rolledUnitIds, + THashSet updatedUnit) { + + HabboItem newRoller = null; + RoomLayout layout = this.room.getLayout(); + + RoomTile rollerTile = layout.getTile(roller.getX(), roller.getY()); + if (rollerTile == null) { + return; + } + + THashSet itemsOnRoller = new THashSet<>(); + for (HabboItem item : this.room.getItemsAt(rollerTile)) { + if (item.getZ() >= roller.getZ() + Item.getCurrentHeight(roller)) { + itemsOnRoller.add(item); + } + } + itemsOnRoller.remove(roller); + + if (!rollerTile.hasUnits() && itemsOnRoller.isEmpty()) { + return; + } + + RoomTile tileInFront = layout.getTileInFront(layout.getTile(roller.getX(), roller.getY()), roller.getRotation()); + + if (tileInFront == null || !layout.tileExists(tileInFront.x, tileInFront.y)) { + return; + } + + if (tileInFront.state == RoomTileState.INVALID) { + return; + } + + if (!tileInFront.getAllowStack() && !(tileInFront.isWalkable() + || tileInFront.state == RoomTileState.SIT + || tileInFront.state == RoomTileState.LAY)) { + return; + } + + if (tileInFront.hasUnits()) { + return; + } + + THashSet itemsNewTile = new THashSet<>(); + itemsNewTile.addAll(this.room.getItemsAt(tileInFront)); + itemsNewTile.removeAll(itemsOnRoller); + + List toRemove = new ArrayList<>(); + for (HabboItem item : itemsOnRoller) { + if (item.getX() != roller.getX() || item.getY() != roller.getY() + || rollerFurniIds.contains(item.getId())) { + toRemove.add(item); + } + } + itemsOnRoller.removeAll(toRemove); + HabboItem topItem = this.room.getTopItemAt(tileInFront.x, tileInFront.y); + + boolean allowUsers = true; + boolean allowFurniture = true; + boolean stackContainsRoller = false; + + for (HabboItem item : itemsNewTile) { + if (!(item.getBaseItem().allowWalk() || item.getBaseItem().allowSit()) && !( + item instanceof InteractionGate && item.getExtradata().equals("1"))) { + allowUsers = false; + } + if (item instanceof InteractionRoller) { + newRoller = item; + stackContainsRoller = true; + + if ((item.getZ() != roller.getZ() || (itemsNewTile.size() > 1 && item != topItem)) + && !InteractionRoller.NO_RULES) { + allowUsers = false; + allowFurniture = false; + continue; + } + + break; + } else { + allowFurniture = false; + } + } + + if (allowFurniture) { + allowFurniture = tileInFront.getAllowStack(); + } + + double zOffset = 0; + if (newRoller != null) { + if ((!itemsNewTile.isEmpty() && (itemsNewTile.size() > 1)) && !InteractionRoller.NO_RULES) { + return; + } + } else { + zOffset = -Item.getCurrentHeight(roller) + tileInFront.getStackHeight() - rollerTile.z; + } + + // Process units on roller + if (allowUsers) { + processUnitsOnRoller(roller, rollerTile, tileInFront, topItem, + itemsOnRoller, itemsNewTile, stackContainsRoller, allowFurniture, + zOffset, messages, rolledUnitIds, updatedUnit); + } + + // Send unit messages + if (!messages.isEmpty()) { + for (MessageComposer message : messages) { + this.room.sendComposer(message.compose()); + } + messages.clear(); + } + + // Process furniture on roller + if (allowFurniture || !stackContainsRoller || InteractionRoller.NO_RULES) { + processFurnitureOnRoller(roller, itemsOnRoller, newRoller, topItem, + tileInFront, zOffset, messages, rollerFurniIds); + } + + // Send furniture messages + if (!messages.isEmpty()) { + for (MessageComposer message : messages) { + this.room.sendComposer(message.compose()); + } + messages.clear(); + } + } + + /** + * Processes units (Habbos, Pets) on a roller. + */ + private void processUnitsOnRoller(InteractionRoller roller, RoomTile rollerTile, + RoomTile tileInFront, HabboItem topItem, + THashSet itemsOnRoller, + THashSet itemsNewTile, + boolean stackContainsRoller, boolean allowFurniture, + double zOffset, THashSet messages, + List rolledUnitIds, THashSet updatedUnit) { + + Event roomUserRolledEvent = null; + + if (Emulator.getPluginManager().isRegistered(UserRolledEvent.class, true)) { + roomUserRolledEvent = new UserRolledEvent(null, null, null); + } + + ArrayList unitsOnTile = new ArrayList<>(rollerTile.getUnits()); + + for (RoomUnit unit : rollerTile.getUnits()) { + if (unit.getRoomUnitType() == RoomUnitType.PET) { + Pet pet = this.room.getPet(unit); + if (pet instanceof RideablePet && ((RideablePet) pet).getRider() != null) { + unitsOnTile.remove(unit); + } + } + } + + this.room.getTallestChair(tileInFront); + + THashSet usersRolledThisTile = new THashSet<>(); + + for (RoomUnit unit : unitsOnTile) { + if (rolledUnitIds.contains(unit.getId())) { + continue; + } + + if (usersRolledThisTile.size() >= Room.ROLLERS_MAXIMUM_ROLL_AVATARS) { + break; + } + + if (stackContainsRoller && !allowFurniture && !(topItem != null && topItem.isWalkable())) { + continue; + } + + if (unit.hasStatus(RoomUnitStatus.MOVE)) { + continue; + } + + // Prevent rolling if the unit is still in roller cooldown (prevents desync/bungie effect) + if (!unit.canBeRolled()) { + continue; + } + + double newZ = unit.getZ() + zOffset; + + if (roomUserRolledEvent != null && unit.getRoomUnitType() == RoomUnitType.USER) { + roomUserRolledEvent = new UserRolledEvent(this.room.getHabbo(unit), roller, tileInFront); + Emulator.getPluginManager().fireEvent(roomUserRolledEvent); + + if (roomUserRolledEvent.isCancelled()) { + continue; + } + } + + // Horse riding + boolean isRiding = false; + if (unit.getRoomUnitType() == RoomUnitType.USER) { + Habbo rollingHabbo = this.room.getHabbo(unit); + if (rollingHabbo != null && rollingHabbo.getHabboInfo() != null) { + RideablePet riding = rollingHabbo.getHabboInfo().getRiding(); + if (riding != null) { + RoomUnit ridingUnit = riding.getRoomUnit(); + newZ = ridingUnit.getZ() + zOffset; + rolledUnitIds.add(ridingUnit.getId()); + updatedUnit.remove(ridingUnit); + messages.add(new RoomUnitOnRollerComposer(ridingUnit, roller, + ridingUnit.getCurrentLocation(), ridingUnit.getZ(), tileInFront, newZ, + this.room)); + isRiding = true; + } + } + } + + usersRolledThisTile.add(unit.getId()); + rolledUnitIds.add(unit.getId()); + updatedUnit.remove(unit); + messages.add(new RoomUnitOnRollerComposer(unit, roller, unit.getCurrentLocation(), + unit.getZ() + (isRiding ? 1 : 0), tileInFront, newZ + (isRiding ? 1 : 0), + this.room)); + + if (itemsOnRoller.isEmpty()) { + HabboItem item = this.room.getTopItemAt(tileInFront.x, tileInFront.y); + + if (item != null && itemsNewTile.contains(item) && !itemsOnRoller.contains(item)) { + final RoomUnit finalUnit = unit; + final RoomTile finalRollerTile = rollerTile; + Emulator.getThreading().run(() -> { + if (finalUnit.getGoal() == finalRollerTile) { + try { + item.onWalkOn(finalUnit, this.room, new Object[]{finalRollerTile, tileInFront}); + } catch (Exception e) { + LOGGER.error("Caught exception", e); + } + } + }, this.room.getRollerSpeed() == 0 ? 250 : InteractionRoller.DELAY); + } + } + + if (unit.hasStatus(RoomUnitStatus.SIT)) { + unit.sitUpdate = true; + } + } + } + + /** + * Processes furniture items on a roller. + */ + private void processFurnitureOnRoller(InteractionRoller roller, THashSet itemsOnRoller, + HabboItem newRoller, HabboItem topItem, RoomTile tileInFront, + double zOffset, THashSet messages, + List rollerFurniIds) { + + Event furnitureRolledEvent = null; + + if (Emulator.getPluginManager().isRegistered(FurnitureRolledEvent.class, true)) { + furnitureRolledEvent = new FurnitureRolledEvent(null, null, null); + } + + if (newRoller == null || topItem == newRoller) { + List sortedItems = new ArrayList<>(itemsOnRoller); + sortedItems.sort((o1, o2) -> o1.getZ() > o2.getZ() ? -1 : 1); + + for (HabboItem item : sortedItems) { + if (item.getX() == roller.getX() && item.getY() == roller.getY() && zOffset <= 0) { + if (item != roller) { + if (furnitureRolledEvent != null) { + furnitureRolledEvent = new FurnitureRolledEvent(item, roller, tileInFront); + Emulator.getPluginManager().fireEvent(furnitureRolledEvent); + + if (furnitureRolledEvent.isCancelled()) { + continue; + } + } + + messages.add(new FloorItemOnRollerComposer(item, roller, tileInFront, zOffset, this.room)); + rollerFurniIds.add(item.getId()); + } + } + } + } + } + + /** + * Gets the current roller cycle value. + */ + public long getRollerCycle() { + return this.rollerCycle; + } + + /** + * Resets the roller cycle. + */ + public void resetRollerCycle() { + this.rollerCycle = 0; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomSpecialTypes.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomSpecialTypes.java index 3ce54325..3cc1745d 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomSpecialTypes.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomSpecialTypes.java @@ -31,7 +31,14 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +/** + * Manages special room item types including wired components, game elements, and pets. + * This class is thread-safe and uses concurrent collections for wired components + * to support high-frequency access patterns. + */ public class RoomSpecialTypes { private final THashMap banzaiTeleporters; private final THashMap nests; @@ -40,10 +47,17 @@ public class RoomSpecialTypes { private final THashMap petToys; private final THashMap rollers; - private final THashMap> wiredTriggers; - private final THashMap> wiredEffects; - private final THashMap> wiredConditions; - private final THashMap wiredExtras; + // Thread-safe wired collections using ConcurrentHashMap for better concurrency + private final ConcurrentHashMap> wiredTriggers; + private final ConcurrentHashMap> wiredEffects; + private final ConcurrentHashMap> wiredConditions; + private final ConcurrentHashMap wiredExtras; + + // Spatial index for O(1) coordinate-based lookups of wired components + private final ConcurrentHashMap> wiredTriggersByLocation; + private final ConcurrentHashMap> wiredEffectsByLocation; + private final ConcurrentHashMap> wiredConditionsByLocation; + private final ConcurrentHashMap> wiredExtrasByLocation; private final THashMap gameScoreboards; private final THashMap gameGates; @@ -51,7 +65,7 @@ public class RoomSpecialTypes { private final THashMap freezeExitTile; private final THashMap undefined; - private final THashSet cycleTasks; + private final Set cycleTasks; public RoomSpecialTypes() { this.banzaiTeleporters = new THashMap<>(0); @@ -61,10 +75,16 @@ public class RoomSpecialTypes { this.petToys = new THashMap<>(0); this.rollers = new THashMap<>(0); - this.wiredTriggers = new THashMap<>(0); - this.wiredEffects = new THashMap<>(0); - this.wiredConditions = new THashMap<>(0); - this.wiredExtras = new THashMap<>(0); + this.wiredTriggers = new ConcurrentHashMap<>(); + this.wiredEffects = new ConcurrentHashMap<>(); + this.wiredConditions = new ConcurrentHashMap<>(); + this.wiredExtras = new ConcurrentHashMap<>(); + + // Initialize spatial indexes + this.wiredTriggersByLocation = new ConcurrentHashMap<>(); + this.wiredEffectsByLocation = new ConcurrentHashMap<>(); + this.wiredConditionsByLocation = new ConcurrentHashMap<>(); + this.wiredExtrasByLocation = new ConcurrentHashMap<>(); this.gameScoreboards = new THashMap<>(0); this.gameGates = new THashMap<>(0); @@ -72,7 +92,15 @@ public class RoomSpecialTypes { this.freezeExitTile = new THashMap<>(0); this.undefined = new THashMap<>(0); - this.cycleTasks = new THashSet<>(0); + this.cycleTasks = ConcurrentHashMap.newKeySet(); + } + + /** + * Generates a unique key for spatial indexing based on x,y coordinates. + * Uses bit shifting to combine two shorts into a single long for efficient lookups. + */ + private static long coordinateKey(int x, int y) { + return ((long) x << 32) | (y & 0xFFFFFFFFL); } @@ -227,247 +255,463 @@ public class RoomSpecialTypes { } + /** + * Finds a wired trigger by its item ID. + * @param itemId The item ID to search for + * @return The trigger if found, null otherwise + */ public InteractionWiredTrigger getTrigger(int itemId) { - synchronized (this.wiredTriggers) { - for (Map.Entry> map : this.wiredTriggers.entrySet()) { - for (InteractionWiredTrigger trigger : map.getValue()) { - if (trigger.getId() == itemId) - return trigger; + for (Set triggers : this.wiredTriggers.values()) { + for (InteractionWiredTrigger trigger : triggers) { + if (trigger.getId() == itemId) { + return trigger; } } - - return null; } + return null; } + /** + * Gets all wired triggers in the room. + * @return A new set containing all triggers (safe for iteration) + */ public THashSet getTriggers() { - synchronized (this.wiredTriggers) { - THashSet triggers = new THashSet<>(); - - for (Map.Entry> map : this.wiredTriggers.entrySet()) { - triggers.addAll(map.getValue()); - } - - return triggers; + THashSet result = new THashSet<>(); + for (Set triggers : this.wiredTriggers.values()) { + result.addAll(triggers); } + return result; } + /** + * Gets all wired triggers of a specific type. + * @param type The trigger type to filter by + * @return A new set containing matching triggers (safe for iteration) + */ public THashSet getTriggers(WiredTriggerType type) { - return this.wiredTriggers.get(type); + Set triggers = this.wiredTriggers.get(type); + if (triggers == null) { + return new THashSet<>(0); + } + return new THashSet<>(triggers); } + /** + * Gets all wired triggers at specific coordinates using spatial index. + * @param x The X coordinate + * @param y The Y coordinate + * @return A new set containing triggers at the location (safe for iteration) + */ public THashSet getTriggers(int x, int y) { - synchronized (this.wiredTriggers) { - THashSet triggers = new THashSet<>(); - - for (Map.Entry> map : this.wiredTriggers.entrySet()) { - for (InteractionWiredTrigger trigger : map.getValue()) { - if (trigger.getX() == x && trigger.getY() == y) - triggers.add(trigger); - } - } - - return triggers; + long key = coordinateKey(x, y); + Set triggers = this.wiredTriggersByLocation.get(key); + if (triggers == null) { + return new THashSet<>(0); } + return new THashSet<>(triggers); } + /** + * Adds a wired trigger to the room. + * @param trigger The trigger to add + */ public void addTrigger(InteractionWiredTrigger trigger) { - synchronized (this.wiredTriggers) { - if (!this.wiredTriggers.containsKey(trigger.getType())) - this.wiredTriggers.put(trigger.getType(), new THashSet<>()); - - this.wiredTriggers.get(trigger.getType()).add(trigger); - } + // Add to type-based index + this.wiredTriggers.computeIfAbsent(trigger.getType(), k -> ConcurrentHashMap.newKeySet()) + .add(trigger); + + // Add to spatial index + long key = coordinateKey(trigger.getX(), trigger.getY()); + this.wiredTriggersByLocation.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet()) + .add(trigger); } + /** + * Removes a wired trigger from the room. + * @param trigger The trigger to remove + */ public void removeTrigger(InteractionWiredTrigger trigger) { - synchronized (this.wiredTriggers) { - this.wiredTriggers.get(trigger.getType()).remove(trigger); - - if (this.wiredTriggers.get(trigger.getType()).isEmpty()) { + // Remove from type-based index + Set triggers = this.wiredTriggers.get(trigger.getType()); + if (triggers != null) { + triggers.remove(trigger); + if (triggers.isEmpty()) { this.wiredTriggers.remove(trigger.getType()); } } + + // Remove from spatial index + long key = coordinateKey(trigger.getX(), trigger.getY()); + Set locationTriggers = this.wiredTriggersByLocation.get(key); + if (locationTriggers != null) { + locationTriggers.remove(trigger); + if (locationTriggers.isEmpty()) { + this.wiredTriggersByLocation.remove(key); + } + } + } + + /** + * Updates the spatial index when a trigger is moved. + * @param trigger The trigger that was moved + * @param oldX The old X coordinate + * @param oldY The old Y coordinate + */ + public void updateTriggerLocation(InteractionWiredTrigger trigger, int oldX, int oldY) { + // Remove from old location + long oldKey = coordinateKey(oldX, oldY); + Set oldLocationTriggers = this.wiredTriggersByLocation.get(oldKey); + if (oldLocationTriggers != null) { + oldLocationTriggers.remove(trigger); + if (oldLocationTriggers.isEmpty()) { + this.wiredTriggersByLocation.remove(oldKey); + } + } + + // Add to new location + long newKey = coordinateKey(trigger.getX(), trigger.getY()); + this.wiredTriggersByLocation.computeIfAbsent(newKey, k -> ConcurrentHashMap.newKeySet()) + .add(trigger); } + /** + * Finds a wired effect by its item ID. + * @param itemId The item ID to search for + * @return The effect if found, null otherwise + */ public InteractionWiredEffect getEffect(int itemId) { - synchronized (this.wiredEffects) { - for (Map.Entry> map : this.wiredEffects.entrySet()) { - for (InteractionWiredEffect effect : map.getValue()) { - if (effect.getId() == itemId) - return effect; + for (Set effects : this.wiredEffects.values()) { + for (InteractionWiredEffect effect : effects) { + if (effect.getId() == itemId) { + return effect; } } } - return null; } + /** + * Gets all wired effects in the room. + * @return A new set containing all effects (safe for iteration) + */ public THashSet getEffects() { - synchronized (this.wiredEffects) { - THashSet effects = new THashSet<>(); - - for (Map.Entry> map : this.wiredEffects.entrySet()) { - effects.addAll(map.getValue()); - } - - return effects; + THashSet result = new THashSet<>(); + for (Set effects : this.wiredEffects.values()) { + result.addAll(effects); } + return result; } + /** + * Gets all wired effects of a specific type. + * @param type The effect type to filter by + * @return A new set containing matching effects (safe for iteration) + */ public THashSet getEffects(WiredEffectType type) { - return this.wiredEffects.get(type); + Set effects = this.wiredEffects.get(type); + if (effects == null) { + return new THashSet<>(0); + } + return new THashSet<>(effects); } + /** + * Gets all wired effects at specific coordinates using spatial index. + * @param x The X coordinate + * @param y The Y coordinate + * @return A new set containing effects at the location (safe for iteration) + */ public THashSet getEffects(int x, int y) { - synchronized (this.wiredEffects) { - THashSet effects = new THashSet<>(); - - for (Map.Entry> map : this.wiredEffects.entrySet()) { - for (InteractionWiredEffect effect : map.getValue()) { - if (effect.getX() == x && effect.getY() == y) - effects.add(effect); - } - } - - return effects; + long key = coordinateKey(x, y); + Set effects = this.wiredEffectsByLocation.get(key); + if (effects == null) { + return new THashSet<>(0); } + return new THashSet<>(effects); } + /** + * Adds a wired effect to the room. + * @param effect The effect to add + */ public void addEffect(InteractionWiredEffect effect) { - synchronized (this.wiredEffects) { - if (!this.wiredEffects.containsKey(effect.getType())) - this.wiredEffects.put(effect.getType(), new THashSet<>()); - - this.wiredEffects.get(effect.getType()).add(effect); - } + // Add to type-based index + this.wiredEffects.computeIfAbsent(effect.getType(), k -> ConcurrentHashMap.newKeySet()) + .add(effect); + + // Add to spatial index + long key = coordinateKey(effect.getX(), effect.getY()); + this.wiredEffectsByLocation.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet()) + .add(effect); } + /** + * Removes a wired effect from the room. + * @param effect The effect to remove + */ public void removeEffect(InteractionWiredEffect effect) { - synchronized (this.wiredEffects) { - this.wiredEffects.get(effect.getType()).remove(effect); - - if (this.wiredEffects.get(effect.getType()).isEmpty()) { + // Remove from type-based index + Set effects = this.wiredEffects.get(effect.getType()); + if (effects != null) { + effects.remove(effect); + if (effects.isEmpty()) { this.wiredEffects.remove(effect.getType()); } } + + // Remove from spatial index + long key = coordinateKey(effect.getX(), effect.getY()); + Set locationEffects = this.wiredEffectsByLocation.get(key); + if (locationEffects != null) { + locationEffects.remove(effect); + if (locationEffects.isEmpty()) { + this.wiredEffectsByLocation.remove(key); + } + } + } + + /** + * Updates the spatial index when an effect is moved. + * @param effect The effect that was moved + * @param oldX The old X coordinate + * @param oldY The old Y coordinate + */ + public void updateEffectLocation(InteractionWiredEffect effect, int oldX, int oldY) { + // Remove from old location + long oldKey = coordinateKey(oldX, oldY); + Set oldLocationEffects = this.wiredEffectsByLocation.get(oldKey); + if (oldLocationEffects != null) { + oldLocationEffects.remove(effect); + if (oldLocationEffects.isEmpty()) { + this.wiredEffectsByLocation.remove(oldKey); + } + } + + // Add to new location + long newKey = coordinateKey(effect.getX(), effect.getY()); + this.wiredEffectsByLocation.computeIfAbsent(newKey, k -> ConcurrentHashMap.newKeySet()) + .add(effect); } + /** + * Finds a wired condition by its item ID. + * @param itemId The item ID to search for + * @return The condition if found, null otherwise + */ public InteractionWiredCondition getCondition(int itemId) { - synchronized (this.wiredConditions) { - for (Map.Entry> map : this.wiredConditions.entrySet()) { - for (InteractionWiredCondition condition : map.getValue()) { - if (condition.getId() == itemId) - return condition; + for (Set conditions : this.wiredConditions.values()) { + for (InteractionWiredCondition condition : conditions) { + if (condition.getId() == itemId) { + return condition; } } } - return null; } + /** + * Gets all wired conditions in the room. + * @return A new set containing all conditions (safe for iteration) + */ public THashSet getConditions() { - synchronized (this.wiredConditions) { - THashSet conditions = new THashSet<>(); - - for (Map.Entry> map : this.wiredConditions.entrySet()) { - conditions.addAll(map.getValue()); - } - - return conditions; + THashSet result = new THashSet<>(); + for (Set conditions : this.wiredConditions.values()) { + result.addAll(conditions); } + return result; } + /** + * Gets all wired conditions of a specific type. + * @param type The condition type to filter by + * @return A new set containing matching conditions (safe for iteration) + */ public THashSet getConditions(WiredConditionType type) { - synchronized (this.wiredConditions) { - return this.wiredConditions.get(type); + Set conditions = this.wiredConditions.get(type); + if (conditions == null) { + return new THashSet<>(0); } + return new THashSet<>(conditions); } + /** + * Gets all wired conditions at specific coordinates using spatial index. + * @param x The X coordinate + * @param y The Y coordinate + * @return A new set containing conditions at the location (safe for iteration) + */ public THashSet getConditions(int x, int y) { - synchronized (this.wiredConditions) { - THashSet conditions = new THashSet<>(); - - for (Map.Entry> map : this.wiredConditions.entrySet()) { - for (InteractionWiredCondition condition : map.getValue()) { - if (condition.getX() == x && condition.getY() == y) - conditions.add(condition); - } - } - - return conditions; + long key = coordinateKey(x, y); + Set conditions = this.wiredConditionsByLocation.get(key); + if (conditions == null) { + return new THashSet<>(0); } + return new THashSet<>(conditions); } + /** + * Adds a wired condition to the room. + * @param condition The condition to add + */ public void addCondition(InteractionWiredCondition condition) { - synchronized (this.wiredConditions) { - if (!this.wiredConditions.containsKey(condition.getType())) - this.wiredConditions.put(condition.getType(), new THashSet<>()); - - this.wiredConditions.get(condition.getType()).add(condition); - } + // Add to type-based index + this.wiredConditions.computeIfAbsent(condition.getType(), k -> ConcurrentHashMap.newKeySet()) + .add(condition); + + // Add to spatial index + long key = coordinateKey(condition.getX(), condition.getY()); + this.wiredConditionsByLocation.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet()) + .add(condition); } + /** + * Removes a wired condition from the room. + * @param condition The condition to remove + */ public void removeCondition(InteractionWiredCondition condition) { - synchronized (this.wiredConditions) { - this.wiredConditions.get(condition.getType()).remove(condition); - - if (this.wiredConditions.get(condition.getType()).isEmpty()) { + // Remove from type-based index + Set conditions = this.wiredConditions.get(condition.getType()); + if (conditions != null) { + conditions.remove(condition); + if (conditions.isEmpty()) { this.wiredConditions.remove(condition.getType()); } } + + // Remove from spatial index + long key = coordinateKey(condition.getX(), condition.getY()); + Set locationConditions = this.wiredConditionsByLocation.get(key); + if (locationConditions != null) { + locationConditions.remove(condition); + if (locationConditions.isEmpty()) { + this.wiredConditionsByLocation.remove(key); + } + } + } + + /** + * Updates the spatial index when a condition is moved. + * @param condition The condition that was moved + * @param oldX The old X coordinate + * @param oldY The old Y coordinate + */ + public void updateConditionLocation(InteractionWiredCondition condition, int oldX, int oldY) { + // Remove from old location + long oldKey = coordinateKey(oldX, oldY); + Set oldLocationConditions = this.wiredConditionsByLocation.get(oldKey); + if (oldLocationConditions != null) { + oldLocationConditions.remove(condition); + if (oldLocationConditions.isEmpty()) { + this.wiredConditionsByLocation.remove(oldKey); + } + } + + // Add to new location + long newKey = coordinateKey(condition.getX(), condition.getY()); + this.wiredConditionsByLocation.computeIfAbsent(newKey, k -> ConcurrentHashMap.newKeySet()) + .add(condition); } + /** + * Gets all wired extras in the room. + * @return A new set containing all extras (safe for iteration) + */ public THashSet getExtras() { - synchronized (this.wiredExtras) { - THashSet conditions = new THashSet<>(); - - for (Map.Entry map : this.wiredExtras.entrySet()) { - conditions.add(map.getValue()); - } - - return conditions; - } + THashSet result = new THashSet<>(); + result.addAll(this.wiredExtras.values()); + return result; } + /** + * Gets all wired extras at specific coordinates using spatial index. + * @param x The X coordinate + * @param y The Y coordinate + * @return A new set containing extras at the location (safe for iteration) + */ public THashSet getExtras(int x, int y) { - synchronized (this.wiredExtras) { - THashSet extras = new THashSet<>(); - - for (Map.Entry map : this.wiredExtras.entrySet()) { - if (map.getValue().getX() == x && map.getValue().getY() == y) { - extras.add(map.getValue()); - } - } - - return extras; + long key = coordinateKey(x, y); + Set extras = this.wiredExtrasByLocation.get(key); + if (extras == null) { + return new THashSet<>(0); } + return new THashSet<>(extras); } + /** + * Adds a wired extra to the room. + * @param extra The extra to add + */ public void addExtra(InteractionWiredExtra extra) { - synchronized (this.wiredExtras) { - this.wiredExtras.put(extra.getId(), extra); - } + this.wiredExtras.put(extra.getId(), extra); + + // Add to spatial index + long key = coordinateKey(extra.getX(), extra.getY()); + this.wiredExtrasByLocation.computeIfAbsent(key, k -> ConcurrentHashMap.newKeySet()) + .add(extra); } + /** + * Removes a wired extra from the room. + * @param extra The extra to remove + */ public void removeExtra(InteractionWiredExtra extra) { - synchronized (this.wiredExtras) { - this.wiredExtras.remove(extra.getId()); - } - } - - public boolean hasExtraType(short x, short y, Class type) { - synchronized (this.wiredExtras) { - for (Map.Entry map : this.wiredExtras.entrySet()) { - if (map.getValue().getX() == x && map.getValue().getY() == y && map.getValue().getClass().isAssignableFrom(type)) { - return true; - } + this.wiredExtras.remove(extra.getId()); + + // Remove from spatial index + long key = coordinateKey(extra.getX(), extra.getY()); + Set locationExtras = this.wiredExtrasByLocation.get(key); + if (locationExtras != null) { + locationExtras.remove(extra); + if (locationExtras.isEmpty()) { + this.wiredExtrasByLocation.remove(key); } } + } + + /** + * Updates the spatial index when an extra is moved. + * @param extra The extra that was moved + * @param oldX The old X coordinate + * @param oldY The old Y coordinate + */ + public void updateExtraLocation(InteractionWiredExtra extra, int oldX, int oldY) { + // Remove from old location + long oldKey = coordinateKey(oldX, oldY); + Set oldLocationExtras = this.wiredExtrasByLocation.get(oldKey); + if (oldLocationExtras != null) { + oldLocationExtras.remove(extra); + if (oldLocationExtras.isEmpty()) { + this.wiredExtrasByLocation.remove(oldKey); + } + } + + // Add to new location + long newKey = coordinateKey(extra.getX(), extra.getY()); + this.wiredExtrasByLocation.computeIfAbsent(newKey, k -> ConcurrentHashMap.newKeySet()) + .add(extra); + } + /** + * Checks if there's a wired extra of a specific type at given coordinates. + * @param x The X coordinate + * @param y The Y coordinate + * @param type The extra type to check for + * @return true if an extra of the given type exists at the location + */ + public boolean hasExtraType(short x, short y, Class type) { + long key = coordinateKey(x, y); + Set extras = this.wiredExtrasByLocation.get(key); + if (extras == null) { + return false; + } + for (InteractionWiredExtra extra : extras) { + if (type.isAssignableFrom(extra.getClass())) { + return true; + } + } return false; } @@ -504,7 +748,7 @@ public class RoomSpecialTypes { for (Map.Entry set : this.gameScoreboards.entrySet()) { if (set.getValue() instanceof InteractionFreezeScoreboard) { - if (set.getValue().teamColor.equals(teamColor)) + if (((InteractionFreezeScoreboard) set.getValue()).teamColor.equals(teamColor)) boards.put(set.getValue().getId(), (InteractionFreezeScoreboard) set.getValue()); } } @@ -533,7 +777,7 @@ public class RoomSpecialTypes { for (Map.Entry set : this.gameScoreboards.entrySet()) { if (set.getValue() instanceof InteractionBattleBanzaiScoreboard) { - if (set.getValue().teamColor.equals(teamColor)) + if (((InteractionBattleBanzaiScoreboard) set.getValue()).teamColor.equals(teamColor)) boards.put(set.getValue().getId(), (InteractionBattleBanzaiScoreboard) set.getValue()); } } @@ -562,7 +806,7 @@ public class RoomSpecialTypes { for (Map.Entry set : this.gameScoreboards.entrySet()) { if (set.getValue() instanceof InteractionFootballScoreboard) { - if (set.getValue().teamColor.equals(teamColor)) + if (((InteractionFootballScoreboard) set.getValue()).teamColor.equals(teamColor)) boards.put(set.getValue().getId(), (InteractionFootballScoreboard) set.getValue()); } } @@ -694,7 +938,11 @@ public class RoomSpecialTypes { return i; } - public THashSet getCycleTasks() { + /** + * Gets the set of cycle tasks. + * @return The set of cycle tasks (thread-safe) + */ + public Set getCycleTasks() { return this.cycleTasks; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTile.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTile.java index 9d42b47e..9fcf31f8 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTile.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTile.java @@ -61,10 +61,6 @@ public class RoomTile { this.units = null; } - public double getStackHeight() { - return this.stackHeight; - } - public void setStackHeight(double stackHeight) { if (this.state == RoomTileState.INVALID) { this.stackHeight = Short.MAX_VALUE; @@ -89,10 +85,6 @@ public class RoomTile { return this.allowStack; } - public void setAllowStack(boolean allowStack) { - this.allowStack = allowStack; - } - public short relativeHeight() { if (this.state == RoomTileState.INVALID) { return Short.MAX_VALUE; @@ -124,14 +116,6 @@ public class RoomTile { this.diagonally = isDiagonally; } - public RoomTile getPrevious() { - return this.previous; - } - - public void setPrevious(RoomTile previous) { - this.previous = previous; - } - public int getfCosts() { return this.gCosts + this.hCosts; } @@ -148,7 +132,7 @@ public class RoomTile { this.gCosts = gCosts; } - void setgCosts(RoomTile previousRoomTile, int basicCost) { + public void setgCosts(RoomTile previousRoomTile, int basicCost) { this.setgCosts((short) (previousRoomTile.getgCosts() + basicCost)); } @@ -160,8 +144,8 @@ public class RoomTile { return previousRoomTile.getgCosts() + 10; } - public void sethCosts(RoomTile parent) { - this.hCosts = (short) ((Math.abs(this.x - parent.x) + Math.abs(this.y - parent.y)) * (parent.diagonally ? RoomLayout.DIAGONALMOVEMENTCOST : RoomLayout.BASICMOVEMENTCOST)); + public void sethCosts(RoomTile parent, int cost) { + this.hCosts = (short) ((Math.abs(this.x - parent.x) + Math.abs(this.y - parent.y)) * (cost)); } public String toString() { @@ -172,21 +156,13 @@ public class RoomTile { return this.state == RoomTileState.OPEN; } - public RoomTileState getState() { - return this.state; - } - - public void setState(RoomTileState state) { - this.state = state; - } - public boolean is(short x, short y) { return this.x == x && this.y == y; } public List getUnits() { synchronized (this.units) { - return new ArrayList(this.units); + return new ArrayList<>(this.units); } } @@ -216,4 +192,64 @@ public class RoomTile { public boolean unitIsOnFurniOnTile(RoomUnit unit, Item item) { return (unit.getX() >= this.x && unit.getX() < this.x + item.getLength()) && (unit.getY() >= this.y && unit.getY() < this.y + item.getWidth()); } + + public short getX() { + return this.x; + } + + public short getY() { + return this.y; + } + + public short getZ() { + return this.z; + } + + public short getGCost() { + return this.gCosts; + } + + public short getHCost() { + return this.hCosts; + } + + public void setDiagonally(boolean diagonally) { + this.diagonally = diagonally; + } + + public void setGCost(short gCost) { + this.gCosts = gCost; + } + + public void setHCost(short hCost) { + this.hCosts = hCost; + } + + public RoomTileState getState() { + return this.state; + } + + public double getStackHeight() { + return this.stackHeight; + } + + public RoomTile getPrevious() { + return this.previous; + } + + public boolean isDiagonally() { + return this.diagonally; + } + + public void setState(RoomTileState state) { + this.state = state; + } + + public void setAllowStack(boolean allowStack) { + this.allowStack = allowStack; + } + + public void setPrevious(RoomTile previous) { + this.previous = previous; + } } \ No newline at end of file diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTileManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTileManager.java new file mode 100644 index 00000000..05b8557c --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTileManager.java @@ -0,0 +1,481 @@ +package com.eu.habbo.habbohotel.rooms; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.bots.Bot; +import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.items.interactions.InteractionStackHelper; +import com.eu.habbo.habbohotel.items.interactions.InteractionTileWalkMagic; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.plugin.events.furniture.FurnitureStackHeightEvent; +import gnu.trove.set.hash.THashSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Manages tile state calculations and heightmap operations for a room. + */ +public class RoomTileManager { + private static final Logger LOGGER = LoggerFactory.getLogger(RoomTileManager.class); + + private final Room room; + + public RoomTileManager(Room room) { + this.room = room; + } + + /** + * Updates a single tile's stack height and state. + */ + public void updateTile(RoomTile tile) { + if (tile != null) { + this.room.tileCache.remove(tile); + this.room.getItemManager().tileCache.remove(tile); + tile.setStackHeight(this.getStackHeight(tile.x, tile.y, false)); + tile.setState(this.calculateTileState(tile)); + } + } + + /** + * Updates multiple tiles and sends the update to clients. + */ + public void updateTiles(THashSet tiles) { + for (RoomTile tile : tiles) { + this.room.tileCache.remove(tile); + this.room.getItemManager().tileCache.remove(tile); + tile.setStackHeight(this.getStackHeight(tile.x, tile.y, false)); + tile.setState(this.calculateTileState(tile)); + } + + this.room.sendComposer(new com.eu.habbo.messages.outgoing.rooms.UpdateStackHeightComposer(this.room, tiles).compose()); + } + + /** + * Calculates the state of a tile based on items on it. + */ + public RoomTileState calculateTileState(RoomTile tile) { + return this.calculateTileState(tile, null); + } + + /** + * Calculates the state of a tile, optionally excluding an item. + */ + public RoomTileState calculateTileState(RoomTile tile, HabboItem exclude) { + if (tile == null || tile.state == RoomTileState.INVALID) { + return RoomTileState.INVALID; + } + + RoomTileState result = RoomTileState.OPEN; + THashSet items = this.room.getItemManager().getItemsAt(tile); + + if (items == null) { + return RoomTileState.INVALID; + } + + HabboItem tallestItem = null; + + for (HabboItem item : items) { + if (exclude != null && item == exclude) { + continue; + } + + if (item.getBaseItem().allowLay()) { + return RoomTileState.LAY; + } + + if (tallestItem != null && tallestItem.getZ() + Item.getCurrentHeight(tallestItem) + > item.getZ() + Item.getCurrentHeight(item)) { + continue; + } + + result = this.checkStateForItem(item, tile); + tallestItem = item; + } + + return result; + } + + /** + * Determines the tile state based on a specific item. + */ + private RoomTileState checkStateForItem(HabboItem item, RoomTile tile) { + RoomTileState result = RoomTileState.BLOCKED; + + if (item.isWalkable()) { + result = RoomTileState.OPEN; + } + + if (item.getBaseItem().allowSit()) { + result = RoomTileState.SIT; + } + + if (item.getBaseItem().allowLay()) { + result = RoomTileState.LAY; + } + + RoomTileState overriddenState = item.getOverrideTileState(tile, this.room); + if (overriddenState != null) { + result = overriddenState; + } + + if (this.room.getItemManager().getItemsAt(tile).stream().anyMatch(i -> i instanceof InteractionTileWalkMagic)) { + result = RoomTileState.OPEN; + } + + return result; + } + + /** + * Checks if a tile is walkable. + */ + public boolean tileWalkable(RoomTile t) { + return this.tileWalkable(t.x, t.y); + } + + /** + * Checks if coordinates are walkable. + */ + public boolean tileWalkable(short x, short y) { + RoomLayout layout = this.room.getLayout(); + if (layout == null) return false; + + boolean walkable = layout.tileWalkable(x, y); + RoomTile tile = layout.getTile(x, y); + + if (walkable && tile != null) { + if (tile.hasUnits() && !this.room.isAllowWalkthrough()) { + walkable = false; + } + } + + return walkable; + } + + /** + * Gets the stack height at a position. + */ + public double getStackHeight(short x, short y, boolean calculateHeightmap) { + return this.getStackHeight(x, y, calculateHeightmap, null); + } + + /** + * Gets the stack height at a position, optionally excluding an item. + */ + public double getStackHeight(short x, short y, boolean calculateHeightmap, HabboItem exclude) { + RoomLayout layout = this.room.getLayout(); + + if (x < 0 || y < 0 || layout == null) { + return calculateHeightmap ? Short.MAX_VALUE : 0.0; + } + + if (Emulator.getPluginManager().isRegistered(FurnitureStackHeightEvent.class, true)) { + FurnitureStackHeightEvent event = Emulator.getPluginManager() + .fireEvent(new FurnitureStackHeightEvent(x, y, this.room)); + if (event.hasPluginHelper()) { + return calculateHeightmap ? event.getHeight() * 256.0D : event.getHeight(); + } + } + + double height = layout.getHeightAtSquare(x, y); + boolean canStack = true; + + THashSet stackHelpers = this.room.getItemManager().getItemsAt(InteractionStackHelper.class, x, y); + stackHelpers.addAll(this.room.getItemManager().getItemsAt(InteractionTileWalkMagic.class, x, y)); + + if (stackHelpers.size() > 0) { + for (HabboItem item : stackHelpers) { + if (item == exclude) { + continue; + } + return calculateHeightmap ? item.getZ() * 256.0D : item.getZ(); + } + } + + HabboItem item = this.room.getItemManager().getTopItemAt(x, y, exclude); + if (item != null) { + canStack = item.getBaseItem().allowStack(); + height = item.getZ() + (item.getBaseItem().allowSit() ? 0 : Item.getCurrentHeight(item)); + } + + if (calculateHeightmap) { + return (canStack ? height * 256.0D : Short.MAX_VALUE); + } + + return canStack ? height : -1; + } + + /** + * Gets the top height at a position. + */ + public double getTopHeightAt(int x, int y) { + HabboItem item = this.room.getItemManager().getTopItemAt(x, y); + + if (item != null) { + return (item.getZ() + Item.getCurrentHeight(item) - (item.getBaseItem().allowSit() ? 1 : 0)); + } else { + RoomLayout layout = this.room.getLayout(); + return layout != null ? layout.getHeightAtSquare(x, y) : 0; + } + } + + /** + * Gets the lowest chair at a position. + */ + public HabboItem getLowestChair(int x, int y) { + RoomLayout layout = this.room.getLayout(); + if (layout == null) { + return null; + } + + RoomTile tile = layout.getTile((short) x, (short) y); + + if (tile != null) { + return this.getLowestChair(tile); + } + + return null; + } + + /** + * Gets the lowest chair at a tile. + */ + public HabboItem getLowestChair(RoomTile tile) { + HabboItem lowestChair = null; + + THashSet items = this.room.getItemManager().getItemsAt(tile); + if (items != null && !items.isEmpty()) { + for (HabboItem item : items) { + if (!item.getBaseItem().allowSit()) { + continue; + } + + if (lowestChair != null && lowestChair.getZ() < item.getZ()) { + continue; + } + + lowestChair = item; + } + } + + return lowestChair; + } + + /** + * Gets the tallest chair at a tile. + */ + public HabboItem getTallestChair(RoomTile tile) { + HabboItem tallestChair = null; + + THashSet items = this.room.getItemManager().getItemsAt(tile); + if (items != null && !items.isEmpty()) { + for (HabboItem item : items) { + if (!item.getBaseItem().allowSit()) { + continue; + } + + if (tallestChair != null && tallestChair.getZ() + Item.getCurrentHeight(tallestChair) + > item.getZ() + Item.getCurrentHeight(item)) { + continue; + } + + tallestChair = item; + } + } + + return tallestChair; + } + + /** + * Checks if a user can sit or lay at a position. + */ + public boolean canSitOrLayAt(int x, int y) { + if (this.room.getUnitManager().hasHabbosAt(x, y)) { + return false; + } + + RoomTile tile = this.room.getLayout().getTile((short) x, (short) y); + THashSet items = this.room.getItemManager().getItemsAt(tile); + + return this.canSitAt(items) || this.canLayAt(items); + } + + /** + * Checks if a user can sit at a position. + */ + public boolean canSitAt(int x, int y) { + if (this.room.getUnitManager().hasHabbosAt(x, y)) { + return false; + } + + RoomTile tile = this.room.getLayout().getTile((short) x, (short) y); + return this.canSitAt(this.room.getItemManager().getItemsAt(tile)); + } + + /** + * Checks if items allow sitting. + */ + public boolean canSitAt(THashSet items) { + if (items == null) { + return false; + } + + HabboItem tallestItem = null; + + for (HabboItem item : items) { + if (tallestItem != null && tallestItem.getZ() + Item.getCurrentHeight(tallestItem) + > item.getZ() + Item.getCurrentHeight(item)) { + continue; + } + + tallestItem = item; + } + + if (tallestItem == null) { + return false; + } + + return tallestItem.getBaseItem().allowSit(); + } + + /** + * Checks if a user can lay at a position. + */ + public boolean canLayAt(int x, int y) { + RoomTile tile = this.room.getLayout().getTile((short) x, (short) y); + return this.canLayAt(this.room.getItemManager().getItemsAt(tile)); + } + + /** + * Checks if items allow laying. + */ + public boolean canLayAt(THashSet items) { + if (items == null || items.isEmpty()) { + return true; + } + + HabboItem topItem = null; + + for (HabboItem item : items) { + if ((topItem == null || item.getZ() > topItem.getZ())) { + topItem = item; + } + } + + return (topItem == null || topItem.getBaseItem().allowLay()); + } + + /** + * Checks if a tile can be walked on. + */ + public boolean canWalkAt(RoomTile roomTile) { + if (roomTile == null) { + return false; + } + + if (roomTile.state == RoomTileState.INVALID) { + return false; + } + + HabboItem topItem = null; + boolean canWalk = true; + THashSet items = this.room.getItemManager().getItemsAt(roomTile); + if (items != null) { + for (HabboItem item : items) { + if (topItem == null) { + topItem = item; + } + + if (item.getZ() > topItem.getZ()) { + topItem = item; + canWalk = topItem.isWalkable() || topItem.getBaseItem().allowWalk(); + } else if (item.getZ() == topItem.getZ() && canWalk) { + if ((!topItem.isWalkable() && !topItem.getBaseItem().allowWalk()) || ( + !item.getBaseItem().allowWalk() && !item.isWalkable())) { + canWalk = false; + } + } + } + } + + return canWalk; + } + + /** + * Gets a random walkable tile in the room. + */ + public RoomTile getRandomWalkableTile() { + RoomLayout layout = this.room.getLayout(); + if (layout == null) return null; + + for (int i = 0; i < 10; i++) { + RoomTile tile = layout.getTile((short) (Math.random() * layout.getMapSizeX()), + (short) (Math.random() * layout.getMapSizeY())); + if (tile != null && tile.getState() != RoomTileState.BLOCKED + && tile.getState() != RoomTileState.INVALID) { + return tile; + } + } + + return null; + } + + /** + * Gets a random walkable tile around a position within a radius. + */ + public RoomTile getRandomWalkableTilesAround(RoomUnit roomUnit, RoomTile tile, int radius) { + RoomLayout layout = this.room.getLayout(); + if (layout == null) return tile; + + if (tile == null || !layout.tileExists(tile.x, tile.y)) { + tile = layout.getTile(roomUnit.getX(), roomUnit.getY()); + roomUnit.setBotStartLocation(tile); + Bot bot = this.room.getUnitManager().getBot(roomUnit); + if (bot != null) { + bot.needsUpdate(true); + } + } + + java.util.List walkableTiles = new java.util.ArrayList<>(); + + int minX = Math.max(0, tile.x - radius); + int minY = Math.max(0, tile.y - radius); + int maxX = Math.min(layout.getMapSizeX() - 1, tile.x + radius); + int maxY = Math.min(layout.getMapSizeY() - 1, tile.y + radius); + + for (int x = minX; x <= maxX; x++) { + for (int y = minY; y <= maxY; y++) { + RoomTile candidateTile = layout.getTile((short) x, (short) y); + + if (candidateTile != null && candidateTile.getState() != RoomTileState.BLOCKED + && candidateTile.getState() != RoomTileState.INVALID) { + walkableTiles.add(candidateTile); + } + } + } + + if (walkableTiles.isEmpty()) { + return tile; + } + + java.util.Collections.shuffle(walkableTiles); + return walkableTiles.get(0); + } + + /** + * Loads the heightmap for the room. + */ + public void loadHeightmap() { + RoomLayout layout = this.room.getLayout(); + if (layout != null) { + for (short x = 0; x < layout.getMapSizeX(); x++) { + for (short y = 0; y < layout.getMapSizeY(); y++) { + RoomTile tile = layout.getTile(x, y); + if (tile != null) { + this.updateTile(tile); + } + } + } + } else { + LOGGER.error("Unknown Room Layout for Room (ID: {})", this.room.getId()); + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTrade.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTrade.java index 5f398dcd..46138a3c 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTrade.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTrade.java @@ -29,6 +29,7 @@ public class RoomTrade { public RoomTrade(Habbo userOne, Habbo userTwo, Room room) { this.users = new ArrayList<>(); + this.users.add(new RoomTradeUser(userOne)); this.users.add(new RoomTradeUser(userTwo)); this.room = room; @@ -101,10 +102,8 @@ public class RoomTrade { this.sendMessageToUsers(new TradeAcceptedComposer(user)); boolean accepted = true; for (RoomTradeUser roomTradeUser : this.users) { - if (!roomTradeUser.getAccepted()) { + if (!roomTradeUser.getAccepted()) accepted = false; - break; - } } if (accepted) { this.sendMessageToUsers(new TradingWaitingConfirmComposer()); @@ -119,10 +118,8 @@ public class RoomTrade { this.sendMessageToUsers(new TradeAcceptedComposer(user)); boolean accepted = true; for (RoomTradeUser roomTradeUser : this.users) { - if (!roomTradeUser.getConfirmed()) { + if (!roomTradeUser.getConfirmed()) accepted = false; - break; - } } if (accepted) { if (this.tradeItems()) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTradeManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTradeManager.java new file mode 100644 index 00000000..6b4790ec --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomTradeManager.java @@ -0,0 +1,61 @@ +package com.eu.habbo.habbohotel.rooms; + +import com.eu.habbo.habbohotel.users.Habbo; +import gnu.trove.set.hash.THashSet; + +/** + * Manages trading operations within a room. + */ +public class RoomTradeManager { + private final Room room; + private final THashSet activeTrades; + + public RoomTradeManager(Room room) { + this.room = room; + this.activeTrades = new THashSet<>(0); + } + + /** + * Starts a trade between two users. + */ + public void startTrade(Habbo userOne, Habbo userTwo) { + RoomTrade trade = new RoomTrade(userOne, userTwo, this.room); + synchronized (this.activeTrades) { + this.activeTrades.add(trade); + } + + trade.start(); + } + + /** + * Stops a trade. + */ + public void stopTrade(RoomTrade trade) { + synchronized (this.activeTrades) { + this.activeTrades.remove(trade); + } + } + + /** + * Gets the active trade for a user. + */ + public RoomTrade getActiveTradeForHabbo(Habbo user) { + synchronized (this.activeTrades) { + for (RoomTrade trade : this.activeTrades) { + for (RoomTradeUser habbo : trade.getRoomTradeUsers()) { + if (habbo.getHabbo() == user) { + return trade; + } + } + } + } + return null; + } + + /** + * Gets all active trades. + */ + public THashSet getActiveTrades() { + return this.activeTrades; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnit.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnit.java index b5f03a1e..a58debc5 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnit.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnit.java @@ -23,816 +23,850 @@ import com.eu.habbo.util.pathfinding.Rotation; import gnu.trove.map.TMap; import gnu.trove.map.hash.THashMap; import gnu.trove.set.hash.THashSet; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledFuture; import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class RoomUnit { - private static final Logger LOGGER = LoggerFactory.getLogger(RoomUnit.class); + private static final Logger LOGGER = LoggerFactory.getLogger(RoomUnit.class); - public boolean isWiredTeleporting = false; - public boolean isLeavingTeleporter = false; - private final ConcurrentHashMap status; - private final THashMap cacheable; - public boolean canRotate = true; - public boolean animateWalk = false; - public boolean cmdTeleport = false; - public boolean cmdSit = false; - public boolean cmdStand = false; - public boolean cmdLay = false; - public boolean sitUpdate = false; - public boolean isTeleporting = false; - public boolean isKicked; - public int kickCount = 0; - private int id; - private RoomTile startLocation; - private RoomTile previousLocation; - private double previousLocationZ; - private RoomTile currentLocation; - private RoomTile goalLocation; - private double z; - private int tilesWalked; - private boolean inRoom; - private boolean canWalk; - private boolean fastWalk = false; - private boolean statusUpdate = false; - private boolean invisible = false; - private boolean canLeaveRoomByDoor = true; - private RoomUserRotation bodyRotation = RoomUserRotation.NORTH; - private RoomUserRotation headRotation = RoomUserRotation.NORTH; - private DanceType danceType; - private RoomUnitType roomUnitType; - private Deque path = new LinkedList<>(); - private int handItem; - private long handItemTimestamp; - private int walkTimeOut; - private int effectId; - private int effectEndTimestamp; - private ScheduledFuture moveBlockingTask; + public boolean isWiredTeleporting = false; + public boolean isLeavingTeleporter = false; + private final ConcurrentHashMap status; + private final THashMap cacheable; + public boolean canRotate = true; + public boolean animateWalk = false; + public boolean cmdTeleport = false; + public boolean cmdSit = false; + public boolean cmdStand = false; + public boolean cmdLay = false; + public boolean sitUpdate = false; + public boolean isTeleporting = false; + public boolean isKicked; + public int kickCount = 0; + private int id; + private RoomTile startLocation; + private RoomTile botStartLocation; + private RoomTile previousLocation; + private double previousLocationZ; + private RoomTile currentLocation; + private RoomTile goalLocation; + private double z; + private int tilesWalked; + private boolean inRoom; + private boolean canWalk; + private boolean fastWalk = false; + private boolean statusUpdate = false; + private boolean invisible = false; + private boolean canLeaveRoomByDoor = true; + private RoomUserRotation bodyRotation = RoomUserRotation.NORTH; + private RoomUserRotation headRotation = RoomUserRotation.NORTH; + private DanceType danceType; + private RoomUnitType roomUnitType; + private Deque path = new LinkedList<>(); + private int handItem; + private long handItemTimestamp; + private long lastRollerTime; + private int walkTimeOut; + private int effectId; + private int effectEndTimestamp; + private ScheduledFuture moveBlockingTask; - private int idleTimer; - private Room room; - private RoomRightLevels rightsLevel = RoomRightLevels.NONE; - private final THashSet overridableTiles; - private boolean isGameSnow; + private int idleTimer; + private Room room; + private RoomRightLevels rightsLevel = RoomRightLevels.NONE; + private THashSet overridableTiles; - public RoomUnit() { - this.id = 0; - this.inRoom = false; - this.canWalk = true; - this.status = new ConcurrentHashMap<>(); - this.cacheable = new THashMap<>(); - this.roomUnitType = RoomUnitType.UNKNOWN; - this.danceType = DanceType.NONE; - this.handItem = 0; - this.handItemTimestamp = 0; - this.walkTimeOut = Emulator.getIntUnixTimestamp(); - this.effectId = 0; - this.isKicked = false; - this.overridableTiles = new THashSet<>(); - this.isGameSnow = false; + public RoomUnit() { + this.id = 0; + this.inRoom = false; + this.canWalk = true; + this.status = new ConcurrentHashMap<>(); + this.cacheable = new THashMap<>(); + this.roomUnitType = RoomUnitType.UNKNOWN; + this.danceType = DanceType.NONE; + this.handItem = 0; + this.handItemTimestamp = 0; + this.walkTimeOut = Emulator.getIntUnixTimestamp(); + this.effectId = 0; + this.isKicked = false; + this.overridableTiles = new THashSet<>(); + } + + public void clearWalking() { + this.goalLocation = null; + this.startLocation = this.currentLocation; + this.inRoom = false; + + this.status.clear(); + + this.cacheable.clear(); + } + + public void stopWalking() { + synchronized (this.status) { + this.status.remove(RoomUnitStatus.MOVE); + this.setGoalLocation(this.currentLocation); } + } - public void clearWalking() { - this.goalLocation = null; - this.startLocation = this.currentLocation; - this.inRoom = false; - - this.status.clear(); - - this.cacheable.clear(); - } - - public void stopWalking() { - synchronized (this.status) { - this.status.remove(RoomUnitStatus.MOVE); - this.setGoalLocation(this.currentLocation); + public boolean cycle(Room room) { + try { + Habbo rider = null; + if (this.getRoomUnitType() == RoomUnitType.PET) { + Pet pet = room.getPet(this); + if (pet instanceof RideablePet) { + rider = ((RideablePet) pet).getRider(); } - } + } - public boolean cycle(Room room) { - try { - Habbo rider; - if (this.getRoomUnitType() == RoomUnitType.PET) { - Pet pet = room.getPet(this); - if (pet instanceof RideablePet) { - rider = ((RideablePet) pet).getRider(); + if (rider != null) { + // copy things from rider + if (this.status.containsKey(RoomUnitStatus.MOVE) && !rider.getRoomUnit().getStatusMap() + .containsKey(RoomUnitStatus.MOVE)) { + this.status.remove(RoomUnitStatus.MOVE); + } - if (rider != null) { - if (this.status.containsKey(RoomUnitStatus.MOVE) && !rider.getRoomUnit().getStatusMap().containsKey(RoomUnitStatus.MOVE)) - this.status.remove(RoomUnitStatus.MOVE); + if (rider.getRoomUnit().getCurrentLocation().x != this.getX() + || rider.getRoomUnit().getCurrentLocation().y != this.getY()) { + this.status.put(RoomUnitStatus.MOVE, + rider.getRoomUnit().getCurrentLocation().x + "," + rider.getRoomUnit() + .getCurrentLocation().y + "," + (rider.getRoomUnit().getCurrentLocation() + .getStackHeight())); + this.setPreviousLocation(rider.getRoomUnit().getPreviousLocation()); + this.setPreviousLocationZ(rider.getRoomUnit().getPreviousLocation().getStackHeight()); + this.setCurrentLocation(rider.getRoomUnit().getCurrentLocation()); + this.setZ(rider.getRoomUnit().getCurrentLocation().getStackHeight()); + } - if (rider.getRoomUnit().getCurrentLocation().x != this.getX() || rider.getRoomUnit().getCurrentLocation().y != this.getY()) { - this.status.put(RoomUnitStatus.MOVE, rider.getRoomUnit().getCurrentLocation().x + "," + rider.getRoomUnit().getCurrentLocation().y + "," + (rider.getRoomUnit().getCurrentLocation().getStackHeight())); - this.setPreviousLocation(rider.getRoomUnit().getPreviousLocation()); - this.setPreviousLocationZ(rider.getRoomUnit().getPreviousLocation().getStackHeight()); - this.setCurrentLocation(rider.getRoomUnit().getCurrentLocation()); - this.setZ(rider.getRoomUnit().getCurrentLocation().getStackHeight()); - } + return this.statusUpdate; + } - return this.statusUpdate; - } - } + if (!this.isWalking() && !this.isKicked) { + if (this.status.remove(RoomUnitStatus.MOVE) == null) { + Habbo habboT = room.getHabbo(this); + if (habboT != null) { + habboT.getHabboInfo().getRiding().getRoomUnit().status.remove(RoomUnitStatus.MOVE); + + } + return true; + } + } + + if (this.status.remove(RoomUnitStatus.SIT) != null) { + this.statusUpdate = true; + } + if (this.status.remove(RoomUnitStatus.MOVE) != null) { + this.statusUpdate = true; + } + if (this.status.remove(RoomUnitStatus.LAY) != null) { + this.statusUpdate = true; + } + + for (Map.Entry set : this.status.entrySet()) { + if (set.getKey().removeWhenWalking) { + this.status.remove(set.getKey()); + } + } + + if (this.path == null || this.path.isEmpty()) { + return true; + } + + boolean canfastwalk = true; + Habbo habboT = room.getHabbo(this); + if (habboT != null) { + if (habboT.getHabboInfo().getRiding() != null) { + canfastwalk = false; + } + } + + RoomTile next = this.path.poll(); + boolean overrideChecks = next != null && this.canOverrideTile(next); + + if (this.path.isEmpty()) { + this.sitUpdate = true; + + if (next != null && next.hasUnits() && !overrideChecks) { + return false; + } + } + + if (canfastwalk && this.fastWalk && this.path.size() > 1) { + next = this.path.poll(); + } + + if (next == null) { + return true; + } + + Habbo habbo = room.getHabbo(this); + + this.status.remove(RoomUnitStatus.DEAD); + + if (habbo != null) { + if (this.isIdle()) { + UserIdleEvent event = new UserIdleEvent(habbo, UserIdleEvent.IdleReason.WALKED, false); + Emulator.getPluginManager().fireEvent(event); + + if (!event.isCancelled()) { + if (!event.idle) { + room.unIdle(habbo); + this.idleTimer = 0; } + } + } - if (!this.isWalking() && !this.isKicked) { - if (this.status.remove(RoomUnitStatus.MOVE) == null) { - Habbo habboT = room.getHabbo(this); - if (habboT != null) { - habboT.getHabboInfo().getRiding().getRoomUnit().status.remove(RoomUnitStatus.MOVE); + if (Emulator.getPluginManager().isRegistered(UserTakeStepEvent.class, false)) { + Event e = new UserTakeStepEvent(habbo, room.getLayout().getTile(this.getX(), this.getY()), + next); + Emulator.getPluginManager().fireEvent(e); - } - return true; - } - } + if (e.isCancelled()) { + return true; + } + } + } - if (this.status.remove(RoomUnitStatus.SIT) != null) this.statusUpdate = true; - if (this.status.remove(RoomUnitStatus.MOVE) != null) this.statusUpdate = true; - if (this.status.remove(RoomUnitStatus.SNOWWAR_RUN) != null) this.statusUpdate = true; - if (this.status.remove(RoomUnitStatus.LAY) != null) this.statusUpdate = true; + HabboItem item = room.getTopItemAt(next.x, next.y); + boolean canSitNextTile = room.canSitAt(next.x, next.y); + boolean canLayNextTile = room.canLayAt(next.x, next.y); - if (this.status.remove(RoomUnitStatus.SNOWWAR_PICK) != null) this.statusUpdate = true; - if (this.status.remove(RoomUnitStatus.SNOWWAR_DIE_BACK) != null) this.statusUpdate = true; - if (this.status.remove(RoomUnitStatus.SNOWWAR_DIE_FRONT) != null) this.statusUpdate = true; + if (!(this.path.isEmpty() && (canSitNextTile || canLayNextTile))) { + double height = next.getStackHeight() - this.currentLocation.getStackHeight(); + if (canMoveToTile(room, next, height, canSitNextTile, canLayNextTile)) { + this.path.clear(); + this.status.remove(RoomUnitStatus.MOVE); + return false; + } + } - for (Map.Entry set : this.status.entrySet()) { - if (set.getKey().removeWhenWalking) { - this.status.remove(set.getKey()); - } - } + if (canSitNextTile) { + HabboItem tallestChair = room.getTallestChair(next); - if (this.path == null || this.path.isEmpty()) - return true; + if (tallestChair != null) { + item = tallestChair; + } + } - boolean canfastwalk = true; - Habbo habboT = room.getHabbo(this); - if (habboT != null) { - if (habboT.getHabboInfo().getRiding() != null) - canfastwalk = false; - } + if (next.equals(this.goalLocation) && (next.state == RoomTileState.SIT || next.state == RoomTileState.LAY) && !overrideChecks && ( + item == null || item.getZ() - this.getZ() > RoomLayout.MAXIMUM_STEP_HEIGHT)) { + this.status.remove(RoomUnitStatus.MOVE); + return false; + } - RoomTile next = this.path.poll(); - boolean overrideChecks = next != null && this.canOverrideTile(next); - - if (this.path.isEmpty()) { - this.sitUpdate = true; - - if (next != null && next.hasUnits() && !overrideChecks) { - return false; - } - } - - Deque peekPath = room.getLayout().findPath(this.currentLocation, this.path.peek(), this.goalLocation, this); - - if (peekPath == null) peekPath = new LinkedList<>(); - - if (peekPath.size() >= 3) { - if (path.isEmpty()) return true; - - path.pop(); - //peekPath.pop(); //Start - peekPath.removeLast(); //End - - if (peekPath.peek() != next) { - next = peekPath.poll(); - for (int i = 0; i < peekPath.size(); i++) { - this.path.addFirst(peekPath.removeLast()); - } - } - } - - if (canfastwalk && this.fastWalk) { - if (this.path.size() > 1) { - next = this.path.poll(); - } - } - - if (next == null) - return true; - - Habbo habbo = room.getHabbo(this); - - this.status.remove(RoomUnitStatus.DEAD); - - if (habbo != null) { - if (this.isIdle()) { - UserIdleEvent event = new UserIdleEvent(habbo, UserIdleEvent.IdleReason.WALKED, false); - Emulator.getPluginManager().fireEvent(event); - - if (!event.isCancelled()) { - if (!event.idle) { - room.unIdle(habbo); - this.idleTimer = 0; - } - } - } - - if (Emulator.getPluginManager().isRegistered(UserTakeStepEvent.class, false)) { - Event e = new UserTakeStepEvent(habbo, room.getLayout().getTile(this.getX(), this.getY()), next); - Emulator.getPluginManager().fireEvent(e); - - if (e.isCancelled()) - return true; - } - } - - HabboItem item = room.getTopItemAt(next.x, next.y); - - //if(!(this.path.size() == 0 && canSitNextTile)) - { - double height = next.getStackHeight() - this.currentLocation.getStackHeight(); - if (!room.tileWalkable(next) || (!RoomLayout.ALLOW_FALLING && height < -RoomLayout.MAXIMUM_STEP_HEIGHT) || - (next.state == RoomTileState.OPEN && height > RoomLayout.MAXIMUM_STEP_HEIGHT)) { - this.room = room; - this.path.clear(); - this.findPath(); - - if (this.path.isEmpty()) { - this.status.remove(RoomUnitStatus.MOVE); - return false; - } - next = this.path.pop(); - } - } - - boolean canSitNextTile = room.canSitAt(next.x, next.y); - - if (canSitNextTile) { - HabboItem tallestChair = room.getTallestChair(next); - - if (tallestChair != null) - item = tallestChair; - } - - if (next.equals(this.goalLocation) && next.state == RoomTileState.SIT && !overrideChecks) { - if (item == null || item.getZ() - this.getZ() > RoomLayout.MAXIMUM_STEP_HEIGHT) { - this.status.remove(RoomUnitStatus.MOVE); - return false; - } - } - - double zHeight = 0.0D; + double zHeight = 0.0D; /*if (((habbo != null && habbo.getHabboInfo().getRiding() != null) || isRiding) && next.equals(this.goalLocation) && (next.state == RoomTileState.SIT || next.state == RoomTileState.LAY)) { this.status.remove(RoomUnitStatus.MOVE); return false; }*/ - if (habbo != null) { - if (habbo.getHabboInfo().getRiding() != null) { - zHeight += 1.0D; - } - } + if (habbo != null) { + if (habbo.getHabboInfo().getRiding() != null) { + zHeight += 1.0D; + } + } - HabboItem habboItem = room.getTopItemAt(this.getX(), this.getY()); - if (habboItem != null) { - if (habboItem != item || !RoomLayout.pointInSquare(habboItem.getX(), habboItem.getY(), habboItem.getX() + habboItem.getBaseItem().getWidth() - 1, habboItem.getY() + habboItem.getBaseItem().getLength() - 1, next.x, next.y)) - habboItem.onWalkOff(this, room, new Object[]{this.getCurrentLocation(), next}); - } + HabboItem habboItem = room.getTopItemAt(this.getX(), this.getY()); + if (habboItem != null) { + if (habboItem != item || !RoomLayout.pointInSquare(habboItem.getX(), habboItem.getY(), + habboItem.getX() + habboItem.getBaseItem().getWidth() - 1, + habboItem.getY() + habboItem.getBaseItem().getLength() - 1, next.x, next.y)) { + habboItem.onWalkOff(this, room, new Object[]{this.getCurrentLocation(), next}); + } + } - this.tilesWalked++; + this.tilesWalked++; - RoomUserRotation oldRotation = this.getBodyRotation(); - this.setRotation(RoomUserRotation.values()[Rotation.Calculate(this.getX(), this.getY(), next.x, next.y)]); - if (item != null) { - if (item != habboItem || !RoomLayout.pointInSquare(item.getX(), item.getY(), item.getX() + item.getBaseItem().getWidth() - 1, item.getY() + item.getBaseItem().getLength() - 1, this.getX(), this.getY())) { - if (item.canWalkOn(this, room, null)) { - item.onWalkOn(this, room, new Object[]{this.getCurrentLocation(), next}); - } else if (item instanceof ConditionalGate) { - this.setRotation(oldRotation); - this.tilesWalked--; - this.setGoalLocation(this.currentLocation); - this.status.remove(RoomUnitStatus.MOVE); - room.sendComposer(new RoomUserStatusComposer(this).compose()); - - if (habbo != null) { - ((ConditionalGate) item).onRejected(this, this.getRoom(), new Object[]{}); - } - return false; - } - } else { - item.onWalk(this, room, new Object[]{this.getCurrentLocation(), next}); - } - - zHeight += item.getZ(); - - if (!item.getBaseItem().allowSit() && !item.getBaseItem().allowLay()) { - zHeight += Item.getCurrentHeight(item); - } - } else { - zHeight += room.getLayout().getHeightAtSquare(next.x, next.y); - } - - Optional stackHelper = this.room.getItemsAt(next).stream().filter(i -> i instanceof InteractionTileWalkMagic).findAny(); - if (stackHelper.isPresent()) { - zHeight = stackHelper.get().getZ(); - } - - this.setPreviousLocation(this.getCurrentLocation()); - - if(this.getIsGameSnow()){ - this.setStatus(RoomUnitStatus.SNOWWAR_RUN, next.x + "," + next.y + "," + zHeight); - } - - this.setStatus(RoomUnitStatus.MOVE, next.x + "," + next.y + "," + zHeight); - - if(this.getStatusMap().containsKey(RoomUnitStatus.SNOWWAR_RUN)){ - this.removeStatus(RoomUnitStatus.MOVE); - this.removeStatus(RoomUnitStatus.SNOWWAR_RUN); - this.setStatus(RoomUnitStatus.SNOWWAR_RUN, next.x + "," + next.y + "," + zHeight); - } + RoomUserRotation oldRotation = this.getBodyRotation(); + this.setRotation( + RoomUserRotation.values()[Rotation.Calculate(this.getX(), this.getY(), next.x, next.y)]); + if (item != null) { + if (item != habboItem || !RoomLayout.pointInSquare(item.getX(), item.getY(), + item.getX() + item.getBaseItem().getWidth() - 1, + item.getY() + item.getBaseItem().getLength() - 1, this.getX(), this.getY())) { + if (item.canWalkOn(this, room, null)) { + item.onWalkOn(this, room, new Object[]{this.getCurrentLocation(), next}); + } else if (item instanceof ConditionalGate) { + this.setRotation(oldRotation); + this.tilesWalked--; + this.setGoalLocation(this.currentLocation); + this.status.remove(RoomUnitStatus.MOVE); + room.sendComposer(new RoomUserStatusComposer(this).compose()); if (habbo != null) { - if (habbo.getHabboInfo().getRiding() != null) { - RoomUnit ridingUnit = habbo.getHabboInfo().getRiding().getRoomUnit(); - - if (ridingUnit != null) { - ridingUnit.setPreviousLocationZ(this.getZ()); - this.setZ(zHeight - 1.0); - ridingUnit.setRotation(RoomUserRotation.values()[Rotation.Calculate(this.getX(), this.getY(), next.x, next.y)]); - ridingUnit.setPreviousLocation(this.getCurrentLocation()); - ridingUnit.setGoalLocation(this.getGoal()); - ridingUnit.setStatus(RoomUnitStatus.MOVE, next.x + "," + next.y + "," + (zHeight - 1.0)); - room.sendComposer(new RoomUserStatusComposer(ridingUnit).compose()); - //ridingUnit.setZ(zHeight - 1.0); - } - } + ((ConditionalGate) item).onRejected(this, this.getRoom(), new Object[]{}); } - //room.sendComposer(new RoomUserStatusComposer(this).compose()); - - this.setZ(zHeight); - this.setCurrentLocation(room.getLayout().getTile(next.x, next.y)); - this.resetIdleTimer(); - - if (habbo != null) { - HabboItem topItem = room.getTopItemAt(next.x, next.y); - - boolean isAtDoor = next.x == room.getLayout().getDoorX() && next.y == room.getLayout().getDoorY(); - boolean publicRoomKicks = !room.isPublicRoom() || Emulator.getConfig().getBoolean("hotel.room.public.doortile.kick"); - boolean invalidated = topItem != null && topItem.invalidatesToRoomKick(); - - if (this.canLeaveRoomByDoor && isAtDoor && publicRoomKicks && !invalidated) { - Emulator.getThreading().run(new RoomUnitKick(habbo, room, false), 500); - } - } - return false; - - } catch (Exception e) { - LOGGER.error("Caught exception", e); - return false; - } - } - - public int getId() { - return this.id; - } - - public void setId(int id) { - this.id = id; - } - - public RoomTile getCurrentLocation() { - return this.currentLocation; - } - - public void setCurrentLocation(RoomTile location) { - if (location != null) { - if (this.currentLocation != null) { - this.currentLocation.removeUnit(this); - } - this.currentLocation = location; - location.addUnit(this); - } - } - - public short getX() { - return this.currentLocation.x; - } - - public short getY() { - return this.currentLocation.y; - } - - public double getZ() { - return this.z; - } - - public void setZ(double z) { - this.z = z; - - if (this.room != null) { - Bot bot = this.room.getBot(this); - if (bot != null) { - bot.needsUpdate(true); - } - } - } - - public boolean isInRoom() { - return this.inRoom; - } - - public synchronized void setInRoom(boolean inRoom) { - this.inRoom = inRoom; - } - - public RoomUnitType getRoomUnitType() { - return this.roomUnitType; - } - - public synchronized void setRoomUnitType(RoomUnitType roomUnitType) { - this.roomUnitType = roomUnitType; - } - - public void setRotation(RoomUserRotation rotation) { - this.bodyRotation = rotation; - this.headRotation = rotation; - } - - public RoomUserRotation getBodyRotation() { - return this.bodyRotation; - } - - public void setBodyRotation(RoomUserRotation bodyRotation) { - this.bodyRotation = bodyRotation; - } - - public RoomUserRotation getHeadRotation() { - return this.headRotation; - } - - public void setHeadRotation(RoomUserRotation headRotation) { - this.headRotation = headRotation; - } - - public DanceType getDanceType() { - return this.danceType; - } - - public synchronized void setDanceType(DanceType danceType) { - this.danceType = danceType; - } - - public void setCanWalk(boolean value) { - this.canWalk = value; - } - - public boolean canWalk() { - return this.canWalk; - } - - public boolean isFastWalk() { - return this.fastWalk; - } - - public void setFastWalk(boolean fastWalk) { - this.fastWalk = fastWalk; - } - - public RoomTile getStartLocation() { - return this.startLocation; - } - - public int tilesWalked() { - return this.tilesWalked; - } - - public RoomTile getGoal() { - return this.goalLocation; - } - - public void setGoalLocation(RoomTile goalLocation) { - if (goalLocation != null) { - // if (goalLocation.state != RoomTileState.INVALID) { - this.setGoalLocation(goalLocation, false); - } - //} - } - - public void setGoalLocation(RoomTile goalLocation, boolean noReset) { - if (Emulator.getPluginManager().isRegistered(RoomUnitSetGoalEvent.class, false)) - { - Event event = new RoomUnitSetGoalEvent(this.room, this, goalLocation); - Emulator.getPluginManager().fireEvent(event); - - if (event.isCancelled()) - return; + } + } else { + item.onWalk(this, room, new Object[]{this.getCurrentLocation(), next}); } - /// Set start location - this.startLocation = this.currentLocation; + zHeight += item.getZ(); - if (goalLocation != null && !noReset) { - boolean isWalking = this.hasStatus(RoomUnitStatus.MOVE); - this.goalLocation = goalLocation; - this.findPath(); ///< Quadral: this is where we start formulating a path - if (!this.path.isEmpty()) { - this.tilesWalked = isWalking ? this.tilesWalked : 0; - this.cmdSit = false; - } else { - this.goalLocation = this.currentLocation; - } + if (!item.getBaseItem().allowSit() && !item.getBaseItem().allowLay()) { + zHeight += Item.getCurrentHeight(item); } - } + } else { + zHeight += room.getLayout().getHeightAtSquare(next.x, next.y); + } - public void setLocation(RoomTile location) { - if (location != null) { - this.startLocation = location; - setPreviousLocation(location); - setCurrentLocation(location); - this.goalLocation = location; + Optional stackHelper = room.getItemsAt(next).stream() + .filter(i -> i instanceof InteractionTileWalkMagic).findAny(); + if (stackHelper.isPresent()) { + zHeight = stackHelper.get().getZ(); + } + + this.setPreviousLocation(this.getCurrentLocation()); + + this.setStatus(RoomUnitStatus.MOVE, next.x + "," + next.y + "," + zHeight); + if (habbo != null) { + if (habbo.getHabboInfo().getRiding() != null) { + RoomUnit ridingUnit = habbo.getHabboInfo().getRiding().getRoomUnit(); + + if (ridingUnit != null) { + ridingUnit.setPreviousLocationZ(this.getZ()); + this.setZ(zHeight - 1.0); + ridingUnit.setRotation( + RoomUserRotation.values()[Rotation.Calculate(this.getX(), this.getY(), next.x, + next.y)]); + ridingUnit.setPreviousLocation(this.getCurrentLocation()); + ridingUnit.setGoalLocation(this.getGoal()); + ridingUnit.setStatus(RoomUnitStatus.MOVE, + next.x + "," + next.y + "," + (zHeight - 1.0)); + room.sendComposer(new RoomUserStatusComposer(ridingUnit).compose()); + //ridingUnit.setZ(zHeight - 1.0); + } } - } + } + //room.sendComposer(new RoomUserStatusComposer(this).compose()); - public RoomTile getPreviousLocation() { - return this.previousLocation; - } + this.setZ(zHeight); + this.setCurrentLocation(room.getLayout().getTile(next.x, next.y)); + this.resetIdleTimer(); - public void setPreviousLocation(RoomTile previousLocation) { - this.previousLocation = previousLocation; - this.previousLocationZ = this.z; - } + if (habbo != null) { + HabboItem topItem = room.getTopItemAt(next.x, next.y); - public double getPreviousLocationZ() { - return this.previousLocationZ; - } + boolean isAtDoor = + next.x == room.getLayout().getDoorX() && next.y == room.getLayout().getDoorY(); + boolean publicRoomKicks = !room.isPublicRoom() || Emulator.getConfig() + .getBoolean("hotel.room.public.doortile.kick"); + boolean invalidated = topItem != null && topItem.invalidatesToRoomKick(); - public void setPreviousLocationZ(double z) { - this.previousLocationZ = z; - } - - public void setPathFinderRoom(Room room) { - this.room = room; - } - - public void findPath() - { - if (this.room != null && this.room.getLayout() != null && this.goalLocation != null && (this.goalLocation.isWalkable() || this.room.canSitOrLayAt(this.goalLocation.x, this.goalLocation.y) || this.canOverrideTile(this.goalLocation))) { - Deque path = this.room.getLayout().findPath(this.currentLocation, this.goalLocation, this.goalLocation, this); - if (path != null) this.path = path; + if (this.canLeaveRoomByDoor && isAtDoor && publicRoomKicks && !invalidated) { + Emulator.getThreading().run(new RoomUnitKick(habbo, room, false), 500); } + } + + return false; + + } catch (Exception e) { + LOGGER.error("Caught exception", e); + return false; + } + } + + private static boolean canMoveToTile(Room room, RoomTile next, double height, + boolean canSitNextTile, boolean canLayNextTile) { + return (!room.tileWalkable(next) || (!RoomLayout.ALLOW_FALLING + && height < -RoomLayout.MAXIMUM_STEP_HEIGHT) || (next.state == RoomTileState.OPEN + && height > RoomLayout.MAXIMUM_STEP_HEIGHT)) && !canSitNextTile && !canLayNextTile; + } + + public int getId() { + return this.id; + } + + public void setId(int id) { + this.id = id; + } + + public RoomTile getCurrentLocation() { + return this.currentLocation; + } + + public void setCurrentLocation(RoomTile location) { + if (location != null) { + if (this.currentLocation != null) { + this.currentLocation.removeUnit(this); + } + this.currentLocation = location; + location.addUnit(this); + } + } + + public short getX() { + return this.currentLocation.x; + } + + public short getY() { + return this.currentLocation.y; + } + + public double getZ() { + return this.z; + } + + public void setZ(double z) { + this.z = z; + + if (this.room != null) { + Bot bot = this.room.getBot(this); + if (bot != null) { + bot.needsUpdate(true); + } + } + } + + public boolean isInRoom() { + return this.inRoom; + } + + public synchronized void setInRoom(boolean inRoom) { + this.inRoom = inRoom; + } + + public RoomUnitType getRoomUnitType() { + return this.roomUnitType; + } + + public synchronized void setRoomUnitType(RoomUnitType roomUnitType) { + this.roomUnitType = roomUnitType; + } + + public void setRotation(RoomUserRotation rotation) { + this.bodyRotation = rotation; + this.headRotation = rotation; + } + + public RoomUserRotation getBodyRotation() { + return this.bodyRotation; + } + + public void setBodyRotation(RoomUserRotation bodyRotation) { + this.bodyRotation = bodyRotation; + } + + public RoomUserRotation getHeadRotation() { + return this.headRotation; + } + + public void setHeadRotation(RoomUserRotation headRotation) { + this.headRotation = headRotation; + } + + public DanceType getDanceType() { + return this.danceType; + } + + public synchronized void setDanceType(DanceType danceType) { + this.danceType = danceType; + } + + public void setCanWalk(boolean value) { + this.canWalk = value; + } + + public boolean canWalk() { + return this.canWalk; + } + + public boolean isFastWalk() { + return this.fastWalk; + } + + public void setFastWalk(boolean fastWalk) { + this.fastWalk = fastWalk; + } + + public RoomTile getStartLocation() { + return this.startLocation; + } + + public int tilesWalked() { + return this.tilesWalked; + } + + public RoomTile getGoal() { + return this.goalLocation; + } + + public void setGoalLocation(RoomTile goalLocation) { + if (goalLocation != null) { + // if (goalLocation.state != RoomTileState.INVALID) { + this.setGoalLocation(goalLocation, false); + } + //} + } + + public void setGoalLocation(RoomTile goalLocation, boolean noReset) { + if (Emulator.getPluginManager().isRegistered(RoomUnitSetGoalEvent.class, false)) { + Event event = new RoomUnitSetGoalEvent(this.room, this, goalLocation); + Emulator.getPluginManager().fireEvent(event); + + if (event.isCancelled()) { + return; + } } - public boolean isAtGoal() { - return this.currentLocation.equals(this.goalLocation); + /// Set start location + this.startLocation = this.currentLocation; + + if (goalLocation != null && !noReset) { + boolean isWalking = this.hasStatus(RoomUnitStatus.MOVE); + this.goalLocation = goalLocation; + this.findPath(); ///< Quadral: this is where we start formulating a path + if (!this.path.isEmpty()) { + this.tilesWalked = isWalking ? this.tilesWalked : 0; + this.cmdSit = false; + } else { + this.goalLocation = this.currentLocation; + } + } + } + + public void setLocation(RoomTile location) { + if (location != null) { + this.startLocation = location; + setPreviousLocation(location); + setCurrentLocation(location); + this.goalLocation = location; + this.botStartLocation = location; + } + } + + public RoomTile getBotStartLocation() { + return this.botStartLocation; + } + + public void setBotStartLocation(RoomTile botStartLocation) { + this.botStartLocation = botStartLocation; + } + + public RoomTile getPreviousLocation() { + return this.previousLocation; + } + + public void setPreviousLocation(RoomTile previousLocation) { + this.previousLocation = previousLocation; + this.previousLocationZ = this.z; + } + + public double getPreviousLocationZ() { + return this.previousLocationZ; + } + + public void setPreviousLocationZ(double z) { + this.previousLocationZ = z; + } + + public void setPathFinderRoom(Room room) { + this.room = room; + } + + public void findPath() { + if (!canFindPath()) { + return; } - public boolean isWalking() { - return !this.isAtGoal() && this.canWalk; + Deque newPath = this.room.getLayout().getPathfinder() + .findPath(this.currentLocation, this.goalLocation, this.goalLocation, this); + if (newPath != null && !newPath.isEmpty()) { + this.path = newPath; + } + } + + private boolean canFindPath() { + return this.room != null && this.room.getLayout() != null && this.goalLocation != null && ( + this.goalLocation.isWalkable() || this.room.canSitOrLayAt(this.goalLocation.x, + this.goalLocation.y) || this.canOverrideTile(this.goalLocation)); + } + + public boolean isAtGoal() { + return this.currentLocation.equals(this.goalLocation); + } + + public boolean isWalking() { + return !this.isAtGoal() && this.canWalk; + } + + public String getStatus(RoomUnitStatus key) { + return this.status.get(key); + } + + public ConcurrentHashMap getStatusMap() { + return this.status; + } + + public void removeStatus(RoomUnitStatus key) { + this.status.remove(key); + } + + public void setStatus(RoomUnitStatus key, String value) { + if (key != null && value != null) { + this.status.put(key, value); + } + } + + public boolean hasStatus(RoomUnitStatus key) { + return this.status.containsKey(key); + } + + public void clearStatus() { + this.status.clear(); + } + + public void statusUpdate(boolean update) { + this.statusUpdate = update; + } + + public boolean needsStatusUpdate() { + return this.statusUpdate; + } + + public TMap getCacheable() { + return this.cacheable; + } + + public int getHandItem() { + return this.handItem; + } + + public long getLastRollerTime() { + return this.lastRollerTime; + } + + public void setLastRollerTime(long lastRollerTime) { + this.lastRollerTime = lastRollerTime; + } + + /** + * Checks if enough time has passed since the last roller movement to allow rolling again. + * This prevents desync issues where the client hasn't finished the roller animation. + * @return true if the unit can be rolled, false if still in roller cooldown + */ + public boolean canBeRolled() { + return System.currentTimeMillis() - this.lastRollerTime >= 480; + } + + public void setHandItem(int handItem) { + this.handItem = handItem; + this.handItemTimestamp = System.currentTimeMillis(); + } + + public long getHandItemTimestamp() { + return this.handItemTimestamp; + } + + public int getEffectId() { + return this.effectId; + } + + public void setEffectId(int effectId, int endTimestamp) { + this.effectId = effectId; + this.effectEndTimestamp = endTimestamp; + } + + public int getEffectEndTimestamp() { + return this.effectEndTimestamp; + } + + public int getWalkTimeOut() { + return this.walkTimeOut; + } + + public void setWalkTimeOut(int walkTimeOut) { + this.walkTimeOut = walkTimeOut; + } + + public void increaseIdleTimer() { + this.idleTimer++; + } + + public boolean isIdle() { + return this.idleTimer > Room.IDLE_CYCLES; //Amount of room cycles / 2 = seconds. + } + + public int getIdleTimer() { + return this.idleTimer; + } + + public void resetIdleTimer() { + this.idleTimer = 0; + } + + public void setIdle() { + this.idleTimer = Room.IDLE_CYCLES + 1; + } + + public void lookAtPoint(RoomTile location) { + if (!this.canRotate) { + return; } - public String getStatus(RoomUnitStatus key) { - return this.status.get(key); + if (Emulator.getPluginManager().isRegistered(RoomUnitLookAtPointEvent.class, false)) { + Event lookAtPointEvent = new RoomUnitLookAtPointEvent(this.room, this, location); + Emulator.getPluginManager().fireEvent(lookAtPointEvent); + + if (lookAtPointEvent.isCancelled()) { + return; + } } - public ConcurrentHashMap getStatusMap() { - return this.status; + if (this.status.containsKey(RoomUnitStatus.LAY)) { + return; } - public void removeStatus(RoomUnitStatus key) { - this.status.remove(key); + if (!this.status.containsKey(RoomUnitStatus.SIT)) { + this.bodyRotation = (RoomUserRotation.values()[Rotation.Calculate(this.getX(), this.getY(), + location.x, location.y)]); } - public void setStatus(RoomUnitStatus key, String value) { - if (key != null && value != null) { - this.status.put(key, value); - } + RoomUserRotation rotation = (RoomUserRotation.values()[Rotation.Calculate(this.getX(), + this.getY(), location.x, location.y)]); + + if (Math.abs(rotation.getValue() - this.bodyRotation.getValue()) <= 1) { + this.headRotation = rotation; + } + } + + public Deque getPath() { + return this.path; + } + + public void setPath(Deque path) { + this.path = path; + } + + public RoomRightLevels getRightsLevel() { + return this.rightsLevel; + } + + public void setRightsLevel(RoomRightLevels rightsLevel) { + this.rightsLevel = rightsLevel; + } + + public boolean isInvisible() { + return this.invisible; + } + + public void setInvisible(boolean invisible) { + this.invisible = invisible; + } + + public Room getRoom() { + return room; + } + + public void setRoom(Room room) { + this.room = room; + } + + public boolean canOverrideTile(RoomTile tile) { + if (tile == null || room == null || room.getLayout() == null) { + return false; } - public boolean hasStatus(RoomUnitStatus key) { - return this.status.containsKey(key); + if (room.getItemsAt(tile).stream().anyMatch(i -> i.canOverrideTile(this, room, tile))) { + return true; } - public void clearStatus() { - this.status.clear(); + int tileIndex = (tile.x & 0xFF) | (tile.y << 12); + return this.overridableTiles.contains(tileIndex); + } + + public void addOverrideTile(RoomTile tile) { + int tileIndex = (tile.x & 0xFF) | (tile.y << 12); + if (!this.overridableTiles.contains(tileIndex)) { + this.overridableTiles.add(tileIndex); + } + } + + public void removeOverrideTile(RoomTile tile) { + if (room == null || room.getLayout() == null) { + return; } - public void statusUpdate(boolean update) { - this.statusUpdate = update; + int tileIndex = (tile.x & 0xFF) | (tile.y << 12); + this.overridableTiles.remove(tileIndex); + } + + public void clearOverrideTiles() { + this.overridableTiles.clear(); + } + + public boolean canLeaveRoomByDoor() { + return canLeaveRoomByDoor; + } + + public void setCanLeaveRoomByDoor(boolean canLeaveRoomByDoor) { + this.canLeaveRoomByDoor = canLeaveRoomByDoor; + } + + public boolean canForcePosture() { + if (this.room == null) { + return false; } - public boolean needsStatusUpdate() { - return this.statusUpdate; + HabboItem topItem = this.room.getTopItemAt(this.getX(), this.getY()); + + return topItem == null || (!(topItem instanceof InteractionWater) + && !(topItem instanceof InteractionWaterItem)); + } + + public RoomTile getClosestTile(List tiles) { + return tiles.stream() + .min(Comparator.comparingDouble(a -> a.distance(this.getCurrentLocation()))).orElse(null); + } + + public RoomTile getClosestAdjacentTile(short x, short y, boolean diagonal) { + if (room == null) { + return null; } - public TMap getCacheable() { - return this.cacheable; + RoomTile baseTile = room.getLayout().getTile(x, y); + + if (baseTile == null) { + return null; } - public int getHandItem() { - return this.handItem; + List rotations = new ArrayList<>(); + rotations.add(RoomUserRotation.SOUTH.getValue()); + rotations.add(RoomUserRotation.NORTH.getValue()); + rotations.add(RoomUserRotation.EAST.getValue()); + rotations.add(RoomUserRotation.WEST.getValue()); + + if (diagonal) { + rotations.add(RoomUserRotation.NORTH_EAST.getValue()); + rotations.add(RoomUserRotation.NORTH_WEST.getValue()); + rotations.add(RoomUserRotation.SOUTH_EAST.getValue()); + rotations.add(RoomUserRotation.SOUTH_WEST.getValue()); } - public void setHandItem(int handItem) { - this.handItem = handItem; - this.handItemTimestamp = System.currentTimeMillis(); - } + return this.getClosestTile( + rotations.stream().map(rotation -> room.getLayout().getTileInFront(baseTile, rotation)) + .filter(t -> t != null && t.isWalkable() && (this.getCurrentLocation().equals(t) + || !room.hasHabbosAt(t.x, t.y))).collect(Collectors.toList())); + } - public long getHandItemTimestamp() { - return this.handItemTimestamp; - } + public ScheduledFuture getMoveBlockingTask() { + return moveBlockingTask; + } - public int getEffectId() { - return this.effectId; - } - - public void setEffectId(int effectId, int endTimestamp) { - this.effectId = effectId; - this.effectEndTimestamp = endTimestamp; - } - - public int getEffectEndTimestamp() { - return this.effectEndTimestamp; - } - - public int getWalkTimeOut() { - return this.walkTimeOut; - } - - public void setWalkTimeOut(int walkTimeOut) { - this.walkTimeOut = walkTimeOut; - } - - public void increaseIdleTimer() { - this.idleTimer++; - } - - public boolean isIdle() { - return this.idleTimer > Room.IDLE_CYCLES; //Amount of room cycles / 2 = seconds. - } - - public int getIdleTimer() { - return this.idleTimer; - } - - public void resetIdleTimer() { - this.idleTimer = 0; - } - - public void setIdle() { - this.idleTimer = Room.IDLE_CYCLES + 1; - } - - public void lookAtPoint(RoomTile location) { - if (!this.canRotate) return; - - if (Emulator.getPluginManager().isRegistered(RoomUnitLookAtPointEvent.class, false)) { - Event lookAtPointEvent = new RoomUnitLookAtPointEvent(this.room, this, location); - Emulator.getPluginManager().fireEvent(lookAtPointEvent); - - if (lookAtPointEvent.isCancelled()) - return; - } - - if (this.status.containsKey(RoomUnitStatus.LAY)) { - return; - } - - if (this.status.containsKey(RoomUnitStatus.SNOWWAR_DIE_BACK) || this.status.containsKey(RoomUnitStatus.SNOWWAR_DIE_FRONT)) { - return; - } - - if (this.status.containsKey(RoomUnitStatus.SNOWWAR_PICK)) { - return; - } - - if (!this.status.containsKey(RoomUnitStatus.SIT)) { - this.bodyRotation = (RoomUserRotation.values()[Rotation.Calculate(this.getX(), this.getY(), location.x, location.y)]); - } - - RoomUserRotation rotation = (RoomUserRotation.values()[Rotation.Calculate(this.getX(), this.getY(), location.x, location.y)]); - - if (Math.abs(rotation.getValue() - this.bodyRotation.getValue()) <= 1) { - this.headRotation = rotation; - } - } - - public Deque getPath() { - return this.path; - } - - public void setPath(Deque path) { - this.path = path; - } - - public RoomRightLevels getRightsLevel() { - return this.rightsLevel; - } - - public void setRightsLevel(RoomRightLevels rightsLevel) { - this.rightsLevel = rightsLevel; - } - - public boolean isInvisible() { - return this.invisible; - } - - public void setInvisible(boolean invisible) { - this.invisible = invisible; - } - - public Room getRoom() { - return room; - } - - public void setRoom(Room room) { - this.room = room; - } - - public boolean canOverrideTile(RoomTile tile) { - if (tile == null || room == null || room.getLayout() == null) return false; - - if (room.getItemsAt(tile).stream().anyMatch(i -> i.canOverrideTile(this, room, tile))) - return true; - - int tileIndex = (tile.x & 0xFF) | (tile.y << 12); - return this.overridableTiles.contains(tileIndex); - } - - public void addOverrideTile(RoomTile tile) { - int tileIndex = (tile.x & 0xFF) | (tile.y << 12); - if (!this.overridableTiles.contains(tileIndex)) { - this.overridableTiles.add(tileIndex); - } - } - - public void removeOverrideTile(RoomTile tile) { - if (room == null || room.getLayout() == null) return; - - int tileIndex = (tile.x & 0xFF) | (tile.y << 12); - this.overridableTiles.remove(tileIndex); - } - - public void clearOverrideTiles() { - this.overridableTiles.clear(); - } - - public boolean canLeaveRoomByDoor() { - return canLeaveRoomByDoor; - } - - public void setCanLeaveRoomByDoor(boolean canLeaveRoomByDoor) { - this.canLeaveRoomByDoor = canLeaveRoomByDoor; - } - - public boolean canForcePosture() { - if (this.room == null) return false; - - HabboItem topItem = this.room.getTopItemAt(this.getX(), this.getY()); - - return topItem == null || (!(topItem instanceof InteractionWater) && !(topItem instanceof InteractionWaterItem)); - } - - public RoomTile getClosestTile(List tiles) { - return tiles.stream().min(Comparator.comparingDouble(a -> a.distance(this.getCurrentLocation()))).orElse(null); - } - - public RoomTile getClosestAdjacentTile(short x, short y, boolean diagonal) { - if(room == null) return null; - - RoomTile baseTile = room.getLayout().getTile(x, y); - - if (baseTile == null) return null; - - List rotations = new ArrayList<>(); - rotations.add(RoomUserRotation.SOUTH.getValue()); - rotations.add(RoomUserRotation.NORTH.getValue()); - rotations.add(RoomUserRotation.EAST.getValue()); - rotations.add(RoomUserRotation.WEST.getValue()); - - if (diagonal) { - rotations.add(RoomUserRotation.NORTH_EAST.getValue()); - rotations.add(RoomUserRotation.NORTH_WEST.getValue()); - rotations.add(RoomUserRotation.SOUTH_EAST.getValue()); - rotations.add(RoomUserRotation.SOUTH_WEST.getValue()); - } - - return this.getClosestTile( - rotations.stream() - .map(rotation -> room.getLayout().getTileInFront(baseTile, rotation)) - .filter(t -> t != null && t.isWalkable() && (this.getCurrentLocation().equals(t) || !room.hasHabbosAt(t.x, t.y))) - .collect(Collectors.toList()) - ); - } - - public ScheduledFuture getMoveBlockingTask() { - return moveBlockingTask; - } - - public void setMoveBlockingTask(ScheduledFuture moveBlockingTask) { - this.moveBlockingTask = moveBlockingTask; - } - - public boolean getIsGameSnow() { - return isGameSnow; - } - public void setGameSnow(boolean gameSnow) { - isGameSnow = gameSnow; - } + public void setMoveBlockingTask(ScheduledFuture moveBlockingTask) { + this.moveBlockingTask = moveBlockingTask; + } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnitManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnitManager.java new file mode 100644 index 00000000..90cced32 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUnitManager.java @@ -0,0 +1,1360 @@ +package com.eu.habbo.habbohotel.rooms; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.bots.Bot; +import com.eu.habbo.habbohotel.bots.VisitorBot; +import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.pets.Pet; +import com.eu.habbo.habbohotel.pets.PetManager; +import com.eu.habbo.habbohotel.pets.RideablePet; +import com.eu.habbo.habbohotel.users.DanceType; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.messages.outgoing.generic.alerts.GenericErrorMessagesComposer; +import com.eu.habbo.messages.outgoing.inventory.AddPetComposer; +import com.eu.habbo.messages.outgoing.rooms.pets.RoomPetComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUnitIdleComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDanceComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserEffectComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserHandItemComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserRemoveComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +import gnu.trove.TCollections; +import gnu.trove.iterator.TIntObjectIterator; +import gnu.trove.map.TIntObjectMap; +import gnu.trove.map.hash.TIntObjectHashMap; +import gnu.trove.procedure.TObjectProcedure; +import gnu.trove.set.hash.THashSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * Manages all room units (Habbos, Bots, Pets) within a room. + * Handles adding, removing, and querying units, as well as effects and hand items. + */ +public class RoomUnitManager { + private static final Logger LOGGER = LoggerFactory.getLogger(RoomUnitManager.class); + + private final Room room; + + // Unit collections - these are the actual data stores + private final ConcurrentHashMap currentHabbos = new ConcurrentHashMap<>(3); + private final TIntObjectMap habboQueue = TCollections.synchronizedMap(new TIntObjectHashMap<>(0)); + private final TIntObjectMap currentBots = TCollections.synchronizedMap(new TIntObjectHashMap<>(0)); + private final TIntObjectMap currentPets = TCollections.synchronizedMap(new TIntObjectHashMap<>(0)); + + // Unit counter for assigning IDs + private volatile int unitCounter; + + public RoomUnitManager(Room room) { + this.room = room; + } + + // ==================== INITIALIZATION ==================== + + /** + * Clears all units and resets the counter. + */ + public void clear() { + synchronized (this.room.roomUnitLock) { + this.unitCounter = 0; + this.currentHabbos.clear(); + this.currentPets.clear(); + this.currentBots.clear(); + } + } + + /** + * Clears all bots from the room. + */ + public void clearBots() { + synchronized (this.room.roomUnitLock) { + this.currentBots.clear(); + } + } + + /** + * Clears all pets from the room. + */ + public void clearPets() { + synchronized (this.room.roomUnitLock) { + this.currentPets.clear(); + } + } + + /** + * Clears the habbo queue. + */ + public void clearQueue() { + synchronized (this.habboQueue) { + this.habboQueue.clear(); + } + } + + /** + * Gets the current unit counter value. + */ + public int getUnitCounter() { + return this.unitCounter; + } + + /** + * Increments and returns the next unit ID. + */ + public int getNextUnitId() { + synchronized (this.room.roomUnitLock) { + return this.unitCounter++; + } + } + + // ==================== HABBO MANAGEMENT ==================== + + /** + * Gets a Habbo by their user ID. + */ + public Habbo getHabbo(int habboId) { + return this.currentHabbos.get(habboId); + } + + /** + * Gets a Habbo by their username. + */ + public Habbo getHabbo(String username) { + for (Habbo habbo : this.currentHabbos.values()) { + if (habbo.getHabboInfo().getUsername().equalsIgnoreCase(username)) { + return habbo; + } + } + return null; + } + + /** + * Gets a Habbo by their RoomUnit. + */ + public Habbo getHabboByRoomUnit(RoomUnit roomUnit) { + for (Habbo habbo : this.currentHabbos.values()) { + if (habbo.getRoomUnit() == roomUnit) { + return habbo; + } + } + return null; + } + + /** + * Gets a Habbo by their RoomUnit ID. + */ + public Habbo getHabboByRoomUnitId(int roomUnitId) { + for (Habbo habbo : this.currentHabbos.values()) { + if (habbo.getRoomUnit().getId() == roomUnitId) { + return habbo; + } + } + return null; + } + + /** + * Gets all Habbos in the room as a map. + */ + public ConcurrentHashMap getCurrentHabbos() { + return this.currentHabbos; + } + + /** + * Gets all Habbos in the room. + */ + public Collection getHabbos() { + return this.currentHabbos.values(); + } + + /** + * Gets the number of Habbos in the room. + */ + public int getHabboCount() { + return this.currentHabbos.size(); + } + + /** + * Checks if a Habbo is in the room. + */ + public boolean hasHabbo(int habboId) { + return this.currentHabbos.containsKey(habboId); + } + + /** + * Adds a Habbo to the room. + */ + public void addHabbo(Habbo habbo) { + synchronized (this.room.roomUnitLock) { + habbo.getRoomUnit().setId(this.unitCounter); + this.currentHabbos.put(habbo.getHabboInfo().getId(), habbo); + this.unitCounter++; + this.room.updateDatabaseUserCount(); + } + } + + /** + * Removes a Habbo from the room. + */ + public void removeHabbo(Habbo habbo) { + this.removeHabbo(habbo, false); + } + + /** + * Removes a Habbo from the room with option to send remove packet. + */ + public void removeHabbo(Habbo habbo, boolean sendRemovePacket) { + if (habbo == null) { + return; + } + + if (habbo.getRoomUnit() != null && habbo.getRoomUnit().getCurrentLocation() != null) { + habbo.getRoomUnit().getCurrentLocation().removeUnit(habbo.getRoomUnit()); + } + + synchronized (this.room.roomUnitLock) { + this.currentHabbos.remove(habbo.getHabboInfo().getId()); + } + + if (sendRemovePacket && habbo.getRoomUnit() != null && !habbo.getRoomUnit().isTeleporting) { + this.room.sendComposer(new RoomUserRemoveComposer(habbo.getRoomUnit()).compose()); + } + + if (habbo.getRoomUnit().getCurrentLocation() != null) { + HabboItem item = this.room.getTopItemAt(habbo.getRoomUnit().getX(), habbo.getRoomUnit().getY()); + + if (item != null) { + try { + item.onWalkOff(habbo.getRoomUnit(), this.room, new Object[]{}); + } catch (Exception e) { + LOGGER.error("Caught exception", e); + } + } + } + + if (habbo.getHabboInfo().getCurrentGame() != null) { + if (this.room.getGame(habbo.getHabboInfo().getCurrentGame()) != null) { + this.room.getGame(habbo.getHabboInfo().getCurrentGame()).removeHabbo(habbo); + } + } + + RoomTrade trade = this.room.getActiveTradeForHabbo(habbo); + + if (trade != null) { + trade.stopTrade(habbo); + } + + if (habbo.getHabboInfo().getId() != this.room.getOwnerId()) { + this.pickupPetsForHabbo(habbo); + } + + this.room.updateDatabaseUserCount(); + } + + /** + * Kicks a Habbo from the room. + */ + public void kickHabbo(Habbo habbo, boolean alert) { + if (alert) { + habbo.getClient().sendResponse( + new GenericErrorMessagesComposer(GenericErrorMessagesComposer.KICKED_OUT_OF_THE_ROOM)); + } + + habbo.getRoomUnit().isKicked = true; + habbo.getRoomUnit().setGoalLocation(this.room.getLayout().getDoorTile()); + + if (habbo.getRoomUnit().getPath() == null || habbo.getRoomUnit().getPath().size() <= 1 + || this.room.isPublicRoom()) { + habbo.getRoomUnit().setCanWalk(true); + Emulator.getGameEnvironment().getRoomManager().leaveRoom(habbo, this.room); + } + } + + /** + * Checks if there are Habbos at the specified position. + */ + public boolean hasHabbosAt(int x, int y) { + for (Habbo habbo : this.getHabbos()) { + if (habbo.getRoomUnit().getX() == x && habbo.getRoomUnit().getY() == y) { + return true; + } + } + return false; + } + + /** + * Gets all Habbos at a specific position. + */ + public THashSet getHabbosAt(short x, short y) { + return this.getHabbosAt(this.room.getLayout().getTile(x, y)); + } + + /** + * Gets all Habbos at a specific tile. + */ + public THashSet getHabbosAt(RoomTile tile) { + THashSet habbos = new THashSet<>(); + + for (Habbo habbo : this.getHabbos()) { + if (habbo.getRoomUnit().getCurrentLocation().equals(tile)) { + habbos.add(habbo); + } + } + + return habbos; + } + + /** + * Gets all Habbos on a specific item. + */ + public THashSet getHabbosOnItem(HabboItem item) { + THashSet habbos = new THashSet<>(); + for (short x = item.getX(); x < item.getX() + item.getBaseItem().getLength(); x++) { + for (short y = item.getY(); y < item.getY() + item.getBaseItem().getWidth(); y++) { + habbos.addAll(this.getHabbosAt(x, y)); + } + } + + return habbos; + } + + /** + * Updates all Habbos at a position. + */ + public void updateHabbosAt(short x, short y) { + this.updateHabbosAt(x, y, this.getHabbosAt(x, y)); + } + + /** + * Updates specific Habbos at a position. + */ + public void updateHabbosAt(short x, short y, THashSet habbos) { + RoomTile tile = this.room.getLayout().getTile(x, y); + + if (tile == null) { + return; + } + + HabboItem topItem = this.room.getTopItemAt(x, y); + + for (Habbo habbo : habbos) { + if (habbo.getRoomUnit() == null) { + continue; + } + + double z = habbo.getRoomUnit().getCurrentLocation().getStackHeight(); + + if (habbo.getRoomUnit().hasStatus(RoomUnitStatus.SIT) + || (topItem != null && topItem.getBaseItem().allowSit())) { + if (topItem != null && topItem.getBaseItem().allowSit()) { + if (!habbo.getRoomUnit().hasStatus(RoomUnitStatus.SIT)) { + this.dance(habbo, DanceType.NONE); + } + habbo.getRoomUnit().setZ(topItem.getZ()); + habbo.getRoomUnit().setPreviousLocationZ(topItem.getZ()); + habbo.getRoomUnit().setRotation(RoomUserRotation.fromValue(topItem.getRotation())); + habbo.getRoomUnit().setStatus(RoomUnitStatus.SIT, + String.valueOf(Item.getCurrentHeight(topItem))); + habbo.getRoomUnit().cmdSit = false; + } else if (habbo.getRoomUnit().cmdSit) { + habbo.getRoomUnit().setZ(z - 0.5); + habbo.getRoomUnit().setPreviousLocationZ(z - 0.5); + } else { + habbo.getRoomUnit().removeStatus(RoomUnitStatus.SIT); + habbo.getRoomUnit().setZ(z); + habbo.getRoomUnit().setPreviousLocationZ(z); + } + } else if (topItem != null && topItem.getBaseItem().allowLay()) { + habbo.getRoomUnit().setZ(topItem.getZ()); + habbo.getRoomUnit().setPreviousLocationZ(topItem.getZ()); + habbo.getRoomUnit().setRotation(RoomUserRotation.fromValue(topItem.getRotation() % 4)); + habbo.getRoomUnit().setStatus(RoomUnitStatus.LAY, + String.valueOf(Item.getCurrentHeight(topItem))); + } else { + if (habbo.getRoomUnit().hasStatus(RoomUnitStatus.SIT)) { + habbo.getRoomUnit().removeStatus(RoomUnitStatus.SIT); + } + if (habbo.getRoomUnit().hasStatus(RoomUnitStatus.LAY)) { + habbo.getRoomUnit().removeStatus(RoomUnitStatus.LAY); + } + habbo.getRoomUnit().setZ(z); + habbo.getRoomUnit().setPreviousLocationZ(z); + } + + habbo.getRoomUnit().statusUpdate(true); + } + + if (!habbos.isEmpty()) { + THashSet roomUnits = new THashSet<>(); + for (Habbo habbo : habbos) { + roomUnits.add(habbo.getRoomUnit()); + } + this.room.sendComposer(new RoomUserStatusComposer(roomUnits, true).compose()); + } + } + + // ==================== HABBO QUEUE ==================== + + /** + * Adds a Habbo to the queue. + */ + public void addToQueue(Habbo habbo) { + synchronized (this.habboQueue) { + this.habboQueue.put(habbo.getHabboInfo().getId(), habbo); + } + } + + /** + * Removes a Habbo from the queue. + */ + public Habbo removeFromQueue(int habboId) { + synchronized (this.habboQueue) { + return this.habboQueue.remove(habboId); + } + } + + /** + * Checks if a Habbo is in the queue. + */ + public boolean isInQueue(int habboId) { + return this.habboQueue.containsKey(habboId); + } + + /** + * Gets the Habbo queue. + */ + public TIntObjectMap getHabboQueue() { + return this.habboQueue; + } + + // ==================== BOT MANAGEMENT ==================== + + /** + * Loads bots from the database. + */ + public void loadBots(Connection connection) { + this.currentBots.clear(); + + try (PreparedStatement statement = connection.prepareStatement( + "SELECT users.username AS owner_name, bots.* FROM bots INNER JOIN users ON bots.user_id = users.id WHERE room_id = ?")) { + statement.setInt(1, this.room.getId()); + try (ResultSet set = statement.executeQuery()) { + while (set.next()) { + Bot bot = Emulator.getGameEnvironment().getBotManager().loadBot(set); + + if (bot != null) { + bot.setRoom(this.room); + bot.setRoomUnit(new RoomUnit()); + bot.getRoomUnit().setRoomUnitType(RoomUnitType.BOT); + bot.getRoomUnit().setBodyRotation(RoomUserRotation.fromValue(set.getInt("rot"))); + bot.getRoomUnit().setHeadRotation(RoomUserRotation.fromValue(set.getInt("rot"))); + bot.getRoomUnit().setDanceType(DanceType.values()[set.getInt("dance")]); + bot.getRoomUnit().setLocation(this.room.getLayout().getTile( + (short) set.getInt("x"), (short) set.getInt("y"))); + bot.getRoomUnit().setZ(set.getDouble("z")); + bot.getRoomUnit().setPreviousLocationZ(set.getDouble("z")); + bot.getRoomUnit().setPathFinderRoom(this.room); + bot.getRoomUnit().setCanWalk(set.getBoolean("freeroam")); + this.addBot(bot); + + if (!this.room.getFurniOwnerNames().containsKey(bot.getOwnerId())) { + this.room.getFurniOwnerNames().put(bot.getOwnerId(), set.getString("owner_name")); + } + } + } + } + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + } + + /** + * Gets a Bot by ID. + */ + public Bot getBot(int botId) { + return this.currentBots.get(botId); + } + + /** + * Gets a Bot by RoomUnit. + */ + public Bot getBot(RoomUnit roomUnit) { + synchronized (this.currentBots) { + TIntObjectIterator iterator = this.currentBots.iterator(); + + for (int i = this.currentBots.size(); i-- > 0; ) { + try { + iterator.advance(); + } catch (NoSuchElementException e) { + LOGGER.error("Caught exception", e); + break; + } + + if (iterator.value().getRoomUnit() == roomUnit) { + return iterator.value(); + } + } + } + + return null; + } + + /** + * Gets a Bot by RoomUnit ID. + */ + public Bot getBotByRoomUnitId(int id) { + synchronized (this.currentBots) { + TIntObjectIterator iterator = this.currentBots.iterator(); + + for (int i = this.currentBots.size(); i-- > 0; ) { + try { + iterator.advance(); + } catch (NoSuchElementException e) { + LOGGER.error("Caught exception", e); + break; + } + + if (iterator.value().getRoomUnit().getId() == id) { + return iterator.value(); + } + } + } + + return null; + } + + /** + * Gets all Bots with a specific name. + */ + public List getBots(String name) { + List bots = new ArrayList<>(); + + synchronized (this.currentBots) { + TIntObjectIterator iterator = this.currentBots.iterator(); + + for (int i = this.currentBots.size(); i-- > 0; ) { + try { + iterator.advance(); + } catch (NoSuchElementException e) { + LOGGER.error("Caught exception", e); + break; + } + + if (iterator.value().getName().equalsIgnoreCase(name)) { + bots.add(iterator.value()); + } + } + } + + return bots; + } + + /** + * Gets all Bots in the room. + */ + public Collection getBots() { + return this.currentBots.valueCollection(); + } + + /** + * Gets the Bot map. + */ + public TIntObjectMap getCurrentBots() { + return this.currentBots; + } + + /** + * Adds a Bot to the room. + */ + public void addBot(Bot bot) { + synchronized (this.room.roomUnitLock) { + bot.getRoomUnit().setId(this.unitCounter); + this.currentBots.put(bot.getId(), bot); + this.unitCounter++; + } + } + + /** + * Removes a Bot from the room. + */ + public boolean removeBot(Bot bot) { + synchronized (this.currentBots) { + if (this.currentBots.containsKey(bot.getId())) { + if (bot.getRoomUnit() != null && bot.getRoomUnit().getCurrentLocation() != null) { + bot.getRoomUnit().getCurrentLocation().removeUnit(bot.getRoomUnit()); + } + + this.currentBots.remove(bot.getId()); + bot.getRoomUnit().setInRoom(false); + bot.setRoom(null); + this.room.sendComposer(new RoomUserRemoveComposer(bot.getRoomUnit()).compose()); + bot.setRoomUnit(null); + return true; + } + } + + return false; + } + + /** + * Checks if there are Bots at the specified position. + */ + public boolean hasBotsAt(final int x, final int y) { + final boolean[] result = {false}; + + synchronized (this.currentBots) { + this.currentBots.forEachValue(new TObjectProcedure() { + @Override + public boolean execute(Bot object) { + if (object.getRoomUnit().getX() == x && object.getRoomUnit().getY() == y) { + result[0] = true; + return false; + } + return true; + } + }); + } + + return result[0]; + } + + /** + * Gets all Bots at a specific tile. + */ + public THashSet getBotsAt(RoomTile tile) { + THashSet bots = new THashSet<>(); + synchronized (this.currentBots) { + TIntObjectIterator botIterator = this.currentBots.iterator(); + + for (int i = this.currentBots.size(); i-- > 0; ) { + try { + botIterator.advance(); + + if (botIterator.value().getRoomUnit().getCurrentLocation().equals(tile)) { + bots.add(botIterator.value()); + } + } catch (Exception e) { + break; + } + } + } + + return bots; + } + + /** + * Gets all Bots on a specific item. + */ + public THashSet getBotsOnItem(HabboItem item) { + THashSet bots = new THashSet<>(); + for (short x = item.getX(); x < item.getX() + item.getBaseItem().getLength(); x++) { + for (short y = item.getY(); y < item.getY() + item.getBaseItem().getWidth(); y++) { + bots.addAll(this.getBotsAt(this.room.getLayout().getTile(x, y))); + } + } + + return bots; + } + + /** + * Updates all Bots at a position. + */ + public void updateBotsAt(short x, short y) { + RoomTile tile = this.room.getLayout().getTile(x, y); + + if (tile == null) { + return; + } + + THashSet bots = this.getBotsAt(tile); + HabboItem topItem = this.room.getTopItemAt(x, y); + + for (Bot bot : bots) { + if (bot.getRoomUnit() == null) { + continue; + } + + double z = bot.getRoomUnit().getCurrentLocation().getStackHeight(); + + if (topItem != null && topItem.getBaseItem().allowSit()) { + bot.getRoomUnit().setZ(topItem.getZ()); + bot.getRoomUnit().setPreviousLocationZ(topItem.getZ()); + bot.getRoomUnit().setRotation(RoomUserRotation.fromValue(topItem.getRotation())); + bot.getRoomUnit().setStatus(RoomUnitStatus.SIT, + String.valueOf(Item.getCurrentHeight(topItem))); + } else if (topItem != null && topItem.getBaseItem().allowLay()) { + bot.getRoomUnit().setZ(topItem.getZ()); + bot.getRoomUnit().setPreviousLocationZ(topItem.getZ()); + bot.getRoomUnit().setStatus(RoomUnitStatus.LAY, + String.valueOf(Item.getCurrentHeight(topItem))); + } else { + if (bot.getRoomUnit().hasStatus(RoomUnitStatus.SIT)) { + bot.getRoomUnit().removeStatus(RoomUnitStatus.SIT); + } + if (bot.getRoomUnit().hasStatus(RoomUnitStatus.LAY)) { + bot.getRoomUnit().removeStatus(RoomUnitStatus.LAY); + } + bot.getRoomUnit().setZ(z); + bot.getRoomUnit().setPreviousLocationZ(z); + } + + bot.getRoomUnit().statusUpdate(true); + } + + if (!bots.isEmpty()) { + this.room.sendComposer(new RoomUserStatusComposer( + bots.stream().map(Bot::getRoomUnit).collect(Collectors.toCollection(THashSet::new)), + true).compose()); + } + } + + // ==================== PET MANAGEMENT ==================== + + /** + * Loads pets from the database. + */ + public void loadPets(Connection connection) { + this.currentPets.clear(); + + try (PreparedStatement statement = connection.prepareStatement( + "SELECT users.username as pet_owner_name, users_pets.* FROM users_pets INNER JOIN users ON users_pets.user_id = users.id WHERE room_id = ?")) { + statement.setInt(1, this.room.getId()); + try (ResultSet set = statement.executeQuery()) { + while (set.next()) { + Pet pet = PetManager.loadPet(set); + pet.setRoom(this.room); + pet.setRoomUnit(new RoomUnit()); + pet.getRoomUnit().setRoomUnitType(RoomUnitType.PET); + pet.getRoomUnit().setBodyRotation(RoomUserRotation.fromValue(set.getInt("rot"))); + pet.getRoomUnit().setHeadRotation(RoomUserRotation.fromValue(set.getInt("rot"))); + pet.getRoomUnit().setLocation(this.room.getLayout().getTile( + (short) set.getInt("x"), (short) set.getInt("y"))); + pet.getRoomUnit().setZ(set.getDouble("z")); + pet.getRoomUnit().setPreviousLocationZ(set.getDouble("z")); + pet.getRoomUnit().setPathFinderRoom(this.room); + pet.getRoomUnit().setCanWalk(true); + this.addPet(pet); + + if (!this.room.getFurniOwnerNames().containsKey(pet.getUserId())) { + this.room.getFurniOwnerNames().put(pet.getUserId(), set.getString("pet_owner_name")); + } + } + } + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + } + + /** + * Gets a Pet by ID. + */ + public Pet getPet(int petId) { + return this.currentPets.get(petId); + } + + /** + * Gets a Pet by RoomUnit. + */ + public Pet getPet(RoomUnit roomUnit) { + TIntObjectIterator petIterator = this.currentPets.iterator(); + + for (int i = this.currentPets.size(); i-- > 0; ) { + try { + petIterator.advance(); + } catch (NoSuchElementException e) { + LOGGER.error("Caught exception", e); + break; + } + + if (petIterator.value().getRoomUnit() == roomUnit) { + return petIterator.value(); + } + } + + return null; + } + + /** + * Gets all Pets in the room. + */ + public Collection getPets() { + return this.currentPets.valueCollection(); + } + + /** + * Gets the Pet map. + */ + public TIntObjectMap getCurrentPets() { + return this.currentPets; + } + + /** + * Adds a Pet to the room. + */ + public void addPet(Pet pet) { + synchronized (this.room.roomUnitLock) { + pet.getRoomUnit().setId(this.unitCounter); + this.currentPets.put(pet.getId(), pet); + this.unitCounter++; + + Habbo habbo = this.getHabbo(pet.getUserId()); + if (habbo != null) { + this.room.getFurniOwnerNames().put(pet.getUserId(), + this.getHabbo(pet.getUserId()).getHabboInfo().getUsername()); + } + } + } + + /** + * Removes a Pet from the room. + */ + public Pet removePet(int petId) { + return this.currentPets.remove(petId); + } + + /** + * Places a Pet in the room. + */ + public void placePet(Pet pet, short x, short y, double z, int rot) { + synchronized (this.currentPets) { + RoomTile tile = this.room.getLayout().getTile(x, y); + + if (tile == null) { + tile = this.room.getLayout().getDoorTile(); + } + + pet.setRoomUnit(new RoomUnit()); + pet.setRoom(this.room); + pet.getRoomUnit().setGoalLocation(tile); + pet.getRoomUnit().setLocation(tile); + pet.getRoomUnit().setRoomUnitType(RoomUnitType.PET); + pet.getRoomUnit().setCanWalk(true); + pet.getRoomUnit().setPathFinderRoom(this.room); + pet.getRoomUnit().setPreviousLocationZ(z); + pet.getRoomUnit().setZ(z); + if (pet.getRoomUnit().getCurrentLocation() == null) { + pet.getRoomUnit().setLocation(this.room.getLayout().getDoorTile()); + pet.getRoomUnit().setRotation(RoomUserRotation.fromValue( + this.room.getLayout().getDoorDirection())); + } + + pet.needsUpdate = true; + + Habbo owner = this.getHabbo(pet.getUserId()); + if (owner != null) { + this.room.getFurniOwnerNames().put(pet.getUserId(), + owner.getHabboInfo().getUsername()); + } + + this.addPet(pet); + this.room.sendComposer(new RoomPetComposer(pet).compose()); + } + } + + /** + * Checks if there are Pets at the specified position. + */ + public boolean hasPetsAt(int x, int y) { + synchronized (this.currentPets) { + TIntObjectIterator petIterator = this.currentPets.iterator(); + + for (int i = this.currentPets.size(); i-- > 0; ) { + try { + petIterator.advance(); + } catch (NoSuchElementException e) { + LOGGER.error("Caught exception", e); + break; + } + + if (petIterator.value().getRoomUnit().getX() == x + && petIterator.value().getRoomUnit().getY() == y) { + return true; + } + } + } + + return false; + } + + /** + * Gets all Pets at a specific tile. + */ + public THashSet getPetsAt(RoomTile tile) { + THashSet pets = new THashSet<>(); + synchronized (this.currentPets) { + TIntObjectIterator petIterator = this.currentPets.iterator(); + + for (int i = this.currentPets.size(); i-- > 0; ) { + try { + petIterator.advance(); + + if (petIterator.value().getRoomUnit().getCurrentLocation().equals(tile)) { + pets.add(petIterator.value()); + } + } catch (Exception e) { + break; + } + } + } + + return pets; + } + + /** + * Updates all Pets at a position. + */ + public void updatePetsAt(short x, short y) { + RoomTile tile = this.room.getLayout().getTile(x, y); + + if (tile == null) { + return; + } + + THashSet pets = this.getPetsAt(tile); + HabboItem topItem = this.room.getTopItemAt(x, y); + + for (Pet pet : pets) { + if (pet.getRoomUnit() == null) { + continue; + } + + double z = pet.getRoomUnit().getCurrentLocation().getStackHeight(); + + if (topItem != null && topItem.getBaseItem().allowSit()) { + pet.getRoomUnit().setZ(topItem.getZ()); + pet.getRoomUnit().setPreviousLocationZ(topItem.getZ()); + } else if (topItem != null && topItem.getBaseItem().allowLay()) { + pet.getRoomUnit().setZ(topItem.getZ()); + pet.getRoomUnit().setPreviousLocationZ(topItem.getZ()); + } else { + pet.getRoomUnit().setZ(z); + pet.getRoomUnit().setPreviousLocationZ(z); + } + + pet.getRoomUnit().statusUpdate(true); + } + + if (!pets.isEmpty()) { + this.room.sendComposer(new RoomUserStatusComposer( + pets.stream().map(Pet::getRoomUnit).collect(Collectors.toCollection(THashSet::new)), + true).compose()); + } + } + + /** + * Picks up all pets belonging to a Habbo. + */ + public void pickupPetsForHabbo(Habbo habbo) { + THashSet pets = new THashSet<>(); + + synchronized (this.currentPets) { + TIntObjectIterator petIterator = this.currentPets.iterator(); + + for (int i = this.currentPets.size(); i-- > 0; ) { + try { + petIterator.advance(); + } catch (NoSuchElementException e) { + LOGGER.error("Caught exception", e); + break; + } + + if (petIterator.value().getUserId() == habbo.getHabboInfo().getId()) { + pets.add(petIterator.value()); + } + } + } + + for (Pet pet : pets) { + pet.setRoom(null); + pet.needsUpdate = true; + + if (pet instanceof RideablePet) { + ((RideablePet) pet).setRider(null); + } + + Emulator.getThreading().run(pet); + habbo.getInventory().getPetsComponent().addPet(pet); + habbo.getClient().sendResponse(new AddPetComposer(pet)); + this.currentPets.remove(pet.getId()); + this.room.sendComposer(new RoomUserRemoveComposer(pet.getRoomUnit()).compose()); + } + } + + /** + * Removes all pets from the room. + */ + public void removeAllPets() { + removeAllPets(-1); + } + + /** + * Removes all pets from the room, optionally keeping one Habbo's pets. + * @param excludeUserId User ID whose pets should NOT be removed, -1 to remove all + */ + public void removeAllPets(int excludeUserId) { + THashSet toRemove = new THashSet<>(); + + synchronized (this.currentPets) { + TIntObjectIterator petIterator = this.currentPets.iterator(); + + for (int i = this.currentPets.size(); i-- > 0; ) { + try { + petIterator.advance(); + } catch (NoSuchElementException e) { + LOGGER.error("Caught exception", e); + break; + } + + if (petIterator.value().getUserId() != excludeUserId) { + toRemove.add(petIterator.value()); + } + } + } + + for (Pet pet : toRemove) { + pet.setRoom(null); + pet.needsUpdate = true; + + if (pet instanceof RideablePet) { + ((RideablePet) pet).setRider(null); + } + + pet.run(); // Run synchronously to ensure DB is updated before room reload + + Habbo owner = Emulator.getGameEnvironment().getHabboManager().getHabbo(pet.getUserId()); + if (owner != null) { + owner.getInventory().getPetsComponent().addPet(pet); + owner.getClient().sendResponse(new AddPetComposer(pet)); + } + + this.currentPets.remove(pet.getId()); + this.room.sendComposer(new RoomUserRemoveComposer(pet.getRoomUnit()).compose()); + } + } + + // ==================== COMBINED UNIT METHODS ==================== + + /** + * Gets all Habbos and Bots at a position. + */ + public THashSet getHabbosAndBotsAt(short x, short y) { + return this.getHabbosAndBotsAt(this.room.getLayout().getTile(x, y)); + } + + /** + * Gets all Habbos and Bots at a tile. + */ + public THashSet getHabbosAndBotsAt(RoomTile tile) { + THashSet list = new THashSet<>(); + + for (Bot bot : this.getBotsAt(tile)) { + list.add(bot.getRoomUnit()); + } + + for (Habbo habbo : this.getHabbosAt(tile)) { + list.add(habbo.getRoomUnit()); + } + + return list; + } + + /** + * Gets all room units (Habbos, Bots, Pets). + */ + public THashSet getRoomUnits() { + return getRoomUnits(null); + } + + /** + * Gets all room units at a specific tile. + */ + public THashSet getRoomUnits(RoomTile atTile) { + THashSet units = new THashSet<>(); + + for (Habbo habbo : this.currentHabbos.values()) { + if (habbo != null && habbo.getRoomUnit() != null && habbo.getRoomUnit().getRoom() != null + && habbo.getRoomUnit().getRoom().getId() == this.room.getId() && (atTile == null + || habbo.getRoomUnit().getCurrentLocation() == atTile)) { + units.add(habbo.getRoomUnit()); + } + } + + for (Pet pet : this.currentPets.valueCollection()) { + if (pet != null && pet.getRoomUnit() != null && pet.getRoomUnit().getRoom() != null + && pet.getRoomUnit().getRoom().getId() == this.room.getId() && (atTile == null + || pet.getRoomUnit().getCurrentLocation() == atTile)) { + units.add(pet.getRoomUnit()); + } + } + + for (Bot bot : this.currentBots.valueCollection()) { + if (bot != null && bot.getRoomUnit() != null && bot.getRoomUnit().getRoom() != null + && bot.getRoomUnit().getRoom().getId() == this.room.getId() && (atTile == null + || bot.getRoomUnit().getCurrentLocation() == atTile)) { + units.add(bot.getRoomUnit()); + } + } + + return units; + } + + /** + * Gets room units at a specific tile as a collection. + */ + public Collection getRoomUnitsAt(RoomTile tile) { + THashSet roomUnits = getRoomUnits(); + return roomUnits.stream().filter(unit -> unit.getCurrentLocation().equals(tile)) + .collect(Collectors.toSet()); + } + + // ==================== EFFECTS AND HAND ITEMS ==================== + + /** + * Gives an effect to a Habbo. + */ + public void giveEffect(Habbo habbo, int effectId, int duration) { + if (this.currentHabbos.containsKey(habbo.getHabboInfo().getId())) { + this.giveEffect(habbo.getRoomUnit(), effectId, duration); + } + } + + /** + * Gives an effect to a RoomUnit. + */ + public void giveEffect(RoomUnit roomUnit, int effectId, int duration) { + if (duration == -1 || duration == Integer.MAX_VALUE) { + duration = Integer.MAX_VALUE; + } else { + duration += Emulator.getIntUnixTimestamp(); + } + + if (this.room.isAllowEffects() && roomUnit != null) { + roomUnit.setEffectId(effectId, duration); + this.room.sendComposer(new RoomUserEffectComposer(roomUnit).compose()); + } + } + + /** + * Gives a hand item to a Habbo. + */ + public void giveHandItem(Habbo habbo, int handItem) { + this.giveHandItem(habbo.getRoomUnit(), handItem); + } + + /** + * Gives a hand item to a RoomUnit. + */ + public void giveHandItem(RoomUnit roomUnit, int handItem) { + roomUnit.setHandItem(handItem); + this.room.sendComposer(new RoomUserHandItemComposer(roomUnit).compose()); + } + + // ==================== IDLE AND DANCE ==================== + + /** + * Sets a Habbo to idle state. + */ + public void idle(Habbo habbo) { + habbo.getRoomUnit().setIdle(); + + if (habbo.getRoomUnit().getDanceType() != DanceType.NONE) { + this.dance(habbo, DanceType.NONE); + } + + this.room.sendComposer(new RoomUnitIdleComposer(habbo.getRoomUnit()).compose()); + WiredManager.triggerUserIdles(this.room, habbo.getRoomUnit()); + } + + /** + * Removes idle state from a Habbo. + */ + public void unIdle(Habbo habbo) { + if (habbo == null || habbo.getRoomUnit() == null) { + return; + } + habbo.getRoomUnit().resetIdleTimer(); + this.room.sendComposer(new RoomUnitIdleComposer(habbo.getRoomUnit()).compose()); + WiredManager.triggerUserUnidles(this.room, habbo.getRoomUnit()); + } + + /** + * Makes a Habbo dance. + */ + public void dance(Habbo habbo, DanceType danceType) { + this.dance(habbo.getRoomUnit(), danceType); + } + + /** + * Makes a RoomUnit dance. + */ + public void dance(RoomUnit unit, DanceType danceType) { + if (unit.getDanceType() != danceType) { + boolean isDancing = !unit.getDanceType().equals(DanceType.NONE); + unit.setDanceType(danceType); + this.room.sendComposer(new RoomUserDanceComposer(unit).compose()); + + if (danceType.equals(DanceType.NONE) && isDancing) { + WiredManager.triggerUserStopsDancing(this.room, unit); + } else if (!danceType.equals(DanceType.NONE) && !isDancing) { + WiredManager.triggerUserStartsDancing(this.room, unit); + } + } + } + + // ==================== TELEPORTATION ==================== + + /** + * Teleports a Habbo to an item. + */ + public void teleportHabboToItem(Habbo habbo, HabboItem item) { + this.teleportRoomUnitToLocation(habbo.getRoomUnit(), item.getX(), item.getY(), + item.getZ() + Item.getCurrentHeight(item)); + } + + /** + * Teleports a Habbo to a location. + */ + public void teleportHabboToLocation(Habbo habbo, short x, short y) { + this.teleportRoomUnitToLocation(habbo.getRoomUnit(), x, y, 0.0); + } + + /** + * Teleports a RoomUnit to an item. + */ + public void teleportRoomUnitToItem(RoomUnit roomUnit, HabboItem item) { + this.teleportRoomUnitToLocation(roomUnit, item.getX(), item.getY(), + item.getZ() + Item.getCurrentHeight(item)); + } + + /** + * Teleports a RoomUnit to a location. + */ + public void teleportRoomUnitToLocation(RoomUnit roomUnit, short x, short y) { + this.teleportRoomUnitToLocation(roomUnit, x, y, 0.0); + } + + /** + * Teleports a RoomUnit to a location with specific height. + */ + public void teleportRoomUnitToLocation(RoomUnit roomUnit, short x, short y, double z) { + if (this.room.isLoaded()) { + RoomTile tile = this.room.getLayout().getTile(x, y); + + if (z < tile.z) { + z = tile.z; + } + + roomUnit.setLocation(tile); + roomUnit.setGoalLocation(tile); + roomUnit.setZ(z); + roomUnit.setPreviousLocationZ(z); + this.room.updateRoomUnit(roomUnit); + } + } + + // ==================== VISITOR BOT HANDLING ==================== + + /** + * Handles Habbo entering the room (visitor bot notification). + */ + public void habboEntered(Habbo habbo) { + habbo.getRoomUnit().animateWalk = false; + + synchronized (this.currentBots) { + if (habbo.getHabboInfo().getId() != this.room.getOwnerId()) { + return; + } + + TIntObjectIterator botIterator = this.currentBots.iterator(); + + for (int i = this.currentBots.size(); i-- > 0; ) { + try { + botIterator.advance(); + + if (botIterator.value() instanceof VisitorBot) { + ((VisitorBot) botIterator.value()).onUserEnter(habbo); + break; + } + } catch (Exception e) { + break; + } + } + } + + HabboItem doorTileTopItem = this.room.getTopItemAt(habbo.getRoomUnit().getX(), + habbo.getRoomUnit().getY()); + if (doorTileTopItem != null + && !(doorTileTopItem instanceof com.eu.habbo.habbohotel.items.interactions.InteractionTeleportTile)) { + try { + doorTileTopItem.onWalkOn(habbo.getRoomUnit(), this.room, new Object[]{}); + } catch (Exception e) { + LOGGER.error("Caught exception", e); + } + } + } + + // ==================== SIT AND STAND ==================== + + /** + * Makes a Habbo sit. + */ + public void makeSit(Habbo habbo) { + if (habbo.getRoomUnit() == null) { + return; + } + + if (habbo.getRoomUnit().hasStatus(RoomUnitStatus.SIT) || !habbo.getRoomUnit().canForcePosture()) { + return; + } + + this.dance(habbo, DanceType.NONE); + habbo.getRoomUnit().cmdSit = true; + habbo.getRoomUnit().setBodyRotation( + RoomUserRotation.values()[habbo.getRoomUnit().getBodyRotation().getValue() + - habbo.getRoomUnit().getBodyRotation().getValue() % 2]); + habbo.getRoomUnit().setStatus(RoomUnitStatus.SIT, 0.5 + ""); + this.room.sendComposer(new RoomUserStatusComposer(habbo.getRoomUnit()).compose()); + } + + /** + * Makes a Habbo stand. + */ + public void makeStand(Habbo habbo) { + if (habbo.getRoomUnit() == null) { + return; + } + + HabboItem item = this.room.getTopItemAt(habbo.getRoomUnit().getX(), habbo.getRoomUnit().getY()); + if (item == null || !item.getBaseItem().allowSit() || !item.getBaseItem().allowLay()) { + habbo.getRoomUnit().cmdStand = true; + habbo.getRoomUnit().setBodyRotation( + RoomUserRotation.values()[habbo.getRoomUnit().getBodyRotation().getValue() + - habbo.getRoomUnit().getBodyRotation().getValue() % 2]); + habbo.getRoomUnit().removeStatus(RoomUnitStatus.SIT); + this.room.sendComposer(new RoomUserStatusComposer(habbo.getRoomUnit()).compose()); + } + } + + // ==================== DISPOSAL ==================== + + /** + * Disposes the unit manager. + */ + public void dispose() { + this.currentHabbos.clear(); + this.currentBots.clear(); + this.currentPets.clear(); + this.habboQueue.clear(); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUserRotation.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUserRotation.java index 11f9f775..fbbc1c07 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUserRotation.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomUserRotation.java @@ -60,4 +60,9 @@ public enum RoomUserRotation { } return null; } + + public static int rotationDistance(int a, int b) { + int diff = Math.abs(a - b); + return Math.min(diff, 8 - diff); + } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomWordQuizManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomWordQuizManager.java new file mode 100644 index 00000000..7d075949 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomWordQuizManager.java @@ -0,0 +1,124 @@ +package com.eu.habbo.habbohotel.rooms; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.messages.outgoing.polls.infobus.SimplePollAnswerComposer; +import com.eu.habbo.messages.outgoing.polls.infobus.SimplePollStartComposer; + +import java.util.ArrayList; +import java.util.List; + +/** + * Manages word quizzes/polls within a room. + */ +public class RoomWordQuizManager { + private final Room room; + private final List userVotes; + + private String wordQuiz = ""; + private int noVotes = 0; + private int yesVotes = 0; + private int wordQuizEnd = 0; + + public RoomWordQuizManager(Room room) { + this.room = room; + this.userVotes = new ArrayList<>(); + } + + /** + * Handles a user's quiz answer. + */ + public void handleWordQuiz(Habbo habbo, String answer) { + synchronized (this.userVotes) { + if (!this.wordQuiz.isEmpty() && !this.hasVotedInWordQuiz(habbo)) { + answer = answer.replace(":", ""); + + if (answer.equals("0")) { + this.noVotes++; + } else if (answer.equals("1")) { + this.yesVotes++; + } + + this.room.sendComposer( + new SimplePollAnswerComposer(habbo.getHabboInfo().getId(), answer, this.noVotes, + this.yesVotes).compose()); + this.userVotes.add(habbo.getHabboInfo().getId()); + } + } + } + + /** + * Starts a word quiz. + */ + public void startWordQuiz(String question, int duration) { + if (!this.hasActiveWordQuiz()) { + this.wordQuiz = question; + this.noVotes = 0; + this.yesVotes = 0; + this.userVotes.clear(); + this.wordQuizEnd = Emulator.getIntUnixTimestamp() + (duration / 1000); + this.room.sendComposer(new SimplePollStartComposer(duration, question).compose()); + } + } + + /** + * Checks if there is an active word quiz. + */ + public boolean hasActiveWordQuiz() { + return Emulator.getIntUnixTimestamp() < this.wordQuizEnd; + } + + /** + * Checks if a user has voted in the current quiz. + */ + public boolean hasVotedInWordQuiz(Habbo habbo) { + return this.userVotes.contains(habbo.getHabboInfo().getId()); + } + + /** + * Resets the quiz state. + */ + public void reset() { + this.wordQuiz = ""; + this.yesVotes = 0; + this.noVotes = 0; + this.userVotes.clear(); + } + + // Getters and setters for backward compatibility + public String getWordQuiz() { + return wordQuiz; + } + + public void setWordQuiz(String wordQuiz) { + this.wordQuiz = wordQuiz; + } + + public int getNoVotes() { + return noVotes; + } + + public void setNoVotes(int noVotes) { + this.noVotes = noVotes; + } + + public int getYesVotes() { + return yesVotes; + } + + public void setYesVotes(int yesVotes) { + this.yesVotes = yesVotes; + } + + public int getWordQuizEnd() { + return wordQuizEnd; + } + + public void setWordQuizEnd(int wordQuizEnd) { + this.wordQuizEnd = wordQuizEnd; + } + + public List getUserVotes() { + return userVotes; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/Pathfinder.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/Pathfinder.java new file mode 100644 index 00000000..603d2094 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/Pathfinder.java @@ -0,0 +1,74 @@ +package com.eu.habbo.habbohotel.rooms.pathfinding; + +import com.eu.habbo.habbohotel.rooms.RoomTile; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import java.util.Deque; +import java.util.concurrent.CompletableFuture; + +/** + * The Pathfinder interface defines the contract for any class that will implement pathfinding logic. + */ +public interface Pathfinder { + + /** + * Asynchronously finds a path from the old tile to the new tile. + * + * @param oldTile The starting tile. + * @param newTile The destination tile. + * @param goalLocation The goal location tile. + * @param roomUnit The room unit for which the path is being found. + * @return A deque of RoomTile objects representing the path from the old tile to the new tile. + */ + CompletableFuture> findPathAsync(RoomTile oldTile, RoomTile newTile, RoomTile goalLocation, RoomUnit roomUnit); + + /** + * Finds a path from the old tile to the new tile. + * + * @param oldTile The starting tile. + * @param newTile The destination tile. + * @param goalLocation The goal location tile. + * @param roomUnit The room unit for which the path is being found. + * @return A deque of RoomTile objects representing the path from the old tile to the new tile. + */ + Deque findPath(RoomTile oldTile, RoomTile newTile, RoomTile goalLocation, RoomUnit roomUnit); + + /** + * Finds a path from the old tile to the new tile, with an option to retry if the first attempt fails. + * + * @param oldTile The starting tile. + * @param newTile The destination tile. + * @param goalLocation The goal location tile. + * @param roomUnit The room unit for which the path is being found. + * @param isWalkthroughRetry If true, the method will retry finding a path if the first attempt fails. + * @return A deque of RoomTile objects representing the path from the old tile to the new tile. + */ + Deque findPath(RoomTile oldTile, RoomTile newTile, RoomTile goalLocation, RoomUnit roomUnit, boolean isWalkthroughRetry); + + /** + * Checks if falling is allowed. + * + * @return True if falling is allowed, false otherwise. + */ + boolean isAllowFalling(); + + /** + * Sets the falling allowance. + * + * @param allow If true, falling is allowed. + */ + void setAllowFalling(boolean allow); + + /** + * Gets the maximum step height. + * + * @return The maximum step height. + */ + double getMaxStepHeight(); + + /** + * Sets the maximum step height. + * + * @param value The value to set as the maximum step height. + */ + void setMaxStepHeight(double value); +} \ No newline at end of file diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/AdjacentTileFinder.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/AdjacentTileFinder.java new file mode 100644 index 00000000..d3981d62 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/AdjacentTileFinder.java @@ -0,0 +1,176 @@ +package com.eu.habbo.habbohotel.rooms.pathfinding.impl; + +import static com.eu.habbo.habbohotel.rooms.pathfinding.impl.PathfinderConstants.BASIC_MOVEMENT_COST; +import static com.eu.habbo.habbohotel.rooms.pathfinding.impl.PathfinderConstants.DIAGONAL_MOVEMENT_COST; +import static com.eu.habbo.habbohotel.rooms.pathfinding.impl.Rotation.DIAGONAL_DIRECTIONS; +import static com.eu.habbo.habbohotel.rooms.pathfinding.impl.Rotation.DIRECTIONS; +import static com.eu.habbo.habbohotel.rooms.pathfinding.impl.TileValidator.isOutOfBounds; + +import com.eu.habbo.habbohotel.rooms.RoomTile; +import com.eu.habbo.habbohotel.rooms.RoomTileState; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import java.util.HashSet; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.Set; + +public class AdjacentTileFinder { + + AdjacentTileFinder() { + } + + public static Set getAdjacent(PathfinderContext context, RoomTile node, + RoomTile nextTile, RoomUnit unit, boolean canMoveDiagonally, boolean retroStyleDiagonals) { + short x = node.getX(); + short y = node.getY(); + Set adj = new HashSet<>(); + addDirectionAdjacent(context, node, nextTile, unit, x, y, adj); + + if (canMoveDiagonally) { + addDiagonalAdjacent(context, node, nextTile, unit, x, y, adj, retroStyleDiagonals); + } + + return adj; + } + + public static void addDirectionAdjacent(PathfinderContext context, RoomTile node, + RoomTile nextTile, RoomUnit unit, short x, short y, Set adj) { + for (short[] direction : DIRECTIONS) { + short newX = (short) (x + direction[0]); + short newY = (short) (y + direction[1]); + + if (isOutOfBounds(context.getRoom().getLayout(), newX, newY)) { + continue; + } + RoomTile temp = findTile(context, newX, newY); + if (temp != null) { + addAdjacent(node, nextTile, unit, temp, adj, false); + } + } + } + + + public static void addDiagonalAdjacent(PathfinderContext context, RoomTile node, + RoomTile nextTile, RoomUnit unit, short x, short y, Set adj, + boolean retroStyleDiagonals) { + for (short[] direction : DIAGONAL_DIRECTIONS) { + short newX = (short) (x + direction[0]); + short newY = (short) (y + direction[1]); + + if (isOutOfBounds(context.getRoom().getLayout(), newX, newY) || (!retroStyleDiagonals + && isBlockedDiagonal(context, x, y, newX, newY))) { + continue; + } + + RoomTile temp = findTile(context, newX, newY); + if (TileValidator.isWalkableOrGoal(context, temp)) { + addAdjacent(node, nextTile, unit, temp, adj, true); + } + } + } + + public static boolean isBlockedDiagonal(PathfinderContext context, short x, short y, short newX, + short newY) { + + RoomTile offX = findTile(context, newX, y); + RoomTile offY = findTile(context, x, newY); + + return offX == null || offY == null || (!offX.isWalkable() && !offY.isWalkable()); + } + + private static void addAdjacent(RoomTile node, RoomTile nextTile, RoomUnit unit, RoomTile temp, + Set adj, boolean isDiagonal) { + if (temp != null && (temp.getState() != RoomTileState.SIT + || nextTile.getStackHeight() - node.getStackHeight() <= 2.0) && canWalkOn(temp, unit)) { + temp.isDiagonally(isDiagonal); + adj.add(temp); + } + } + + private static boolean canWalkOn(RoomTile tile, RoomUnit unit) { + return tile != null && ( + (tile.getState() != RoomTileState.BLOCKED && tile.getState() != RoomTileState.INVALID) + || unit.canOverrideTile(tile)); + } + + /** + * Finds and returns a RoomTile based on the provided coordinates, using caching for efficiency. + * If the tile is found in the cache, it is returned directly. Otherwise, the tile is retrieved + * from the room layout, copied, cached, and then returned. + * + * @param context The pathfinder context containing the tile cache and other data structures. + * @param x The x-coordinate of the tile to be found. + * @param y The y-coordinate of the tile to be found. + * @return The RoomTile corresponding to the given coordinates, or null if no tile exists at the + * specified location. + */ + public static RoomTile findTile(PathfinderContext context, short x, short y) { + long key = generateTileKey(x, y); + Map tileCache = context.getTileCache(); + RoomTile tile = tileCache.get(key); + if (tile != null) { + return tile; + } + + tile = context.getRoom().getLayout().getTile(x, y); + if (tile == null) { + return null; + } + + tile = tile.copy(); + tileCache.put(key, tile); + return tile; + } + + + /** + * Generates a unique long key for a tile based on its x and y coordinates. This key can be used + * for identifying tiles in a map or cache efficiently. + * + * @param x The x-coordinate of the tile. + * @param y The y-coordinate of the tile. + * @return A long value that uniquely identifies the tile based on the provided x and y + * coordinates. + */ + private static long generateTileKey(short x, short y) { + return ((long) x << 32) | (y & 0xFFFFFFFFL); + } + + public static void calculateCost(PathfinderContext context, RoomTile currentAdj, RoomTile current, + PriorityQueue openList) { + if (!openList.contains(currentAdj)) { + updateAdj(context, currentAdj, current, openList); + return; + } + + if (currentAdj.getgCosts() > calculateGCosts(currentAdj, current)) { + currentAdj.setPrevious(current); + currentAdj.setgCosts(current, getCost(currentAdj)); + } + } + + public static int calculateGCosts(RoomTile tile, RoomTile previousRoomTile) { + if (tile.isDiagonally()) { + return previousRoomTile.getgCosts() + DIAGONAL_MOVEMENT_COST; + } + + return previousRoomTile.getgCosts() + BASIC_MOVEMENT_COST; + } + + public static void updateAdj(PathfinderContext context, RoomTile currentAdj, RoomTile current, + PriorityQueue openList) { + currentAdj.setPrevious(current); + RoomTile tile = AdjacentTileFinder.findTile(context, context.getNewTile().getX(), + context.getNewTile().getY()); + if (tile == null) { + return; + } + currentAdj.sethCosts(tile, getCost(current)); + currentAdj.setgCosts(current, getCost(currentAdj)); + openList.add(currentAdj); + } + + private static int getCost(RoomTile tile) { + return tile.isDiagonally() ? DIAGONAL_MOVEMENT_COST : BASIC_MOVEMENT_COST; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/PathFinderException.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/PathFinderException.java new file mode 100644 index 00000000..90fb2693 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/PathFinderException.java @@ -0,0 +1,7 @@ +package com.eu.habbo.habbohotel.rooms.pathfinding.impl; + +public class PathFinderException extends Exception { + public PathFinderException(String message, Throwable error) { + super(message, error); + } +} \ No newline at end of file diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/PathfinderConstants.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/PathfinderConstants.java new file mode 100644 index 00000000..ae7c7994 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/PathfinderConstants.java @@ -0,0 +1,16 @@ +package com.eu.habbo.habbohotel.rooms.pathfinding.impl; + +public final class PathfinderConstants { + public static final int BASIC_MOVEMENT_COST = 10; + public static final int DIAGONAL_MOVEMENT_COST = 11; + public static final int DISTANCE_DOOR_THRESHOLD = 2; + public static final int TIMEOUT_CHECK_INTERVAL = 64; + + // Configuration keys + public static final String CONFIG_EXECUTION_TIME = "pathfinder.execution_time.milli"; + public static final String CONFIG_TIMEOUT_ENABLED = "pathfinder.max_execution_time.enabled"; + + private PathfinderConstants() { + throw new IllegalStateException("Utility class"); + } +} \ No newline at end of file diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/PathfinderContext.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/PathfinderContext.java new file mode 100644 index 00000000..5013b91f --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/PathfinderContext.java @@ -0,0 +1,85 @@ +package com.eu.habbo.habbohotel.rooms.pathfinding.impl; + +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomTile; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import java.util.HashMap; +import java.util.Map; + +public class PathfinderContext { + + private final Room room; + private final RoomTile newTile; + private RoomTile goalLocation; + private RoomUnit roomUnit; + private final boolean isWalkthroughRetry; + private final boolean canMoveDiagonally; + private final RoomTile doorTile; + private final boolean allowWalkthrough; + private final Map tileCache; + + public PathfinderContext(Room room, RoomTile newTile, RoomTile goalLocation, RoomUnit roomUnit, + boolean isWalkthroughRetry, boolean canMoveDiagonally, RoomTile doorTile, + boolean allowWalkthrough, Map tileCache) { + this.newTile = newTile; + this.goalLocation = goalLocation; + this.roomUnit = roomUnit; + this.isWalkthroughRetry = isWalkthroughRetry; + this.canMoveDiagonally = canMoveDiagonally; + this.doorTile = doorTile; + this.allowWalkthrough = allowWalkthrough; + this.tileCache = tileCache; + this.room = room; + } + + public static PathfinderContext buildContext(Room room, RoomTile newTile, RoomTile goalLocation, + RoomUnit roomUnit, boolean isWalkthroughRetry) { + return new PathfinderContext(room, newTile, goalLocation, roomUnit, isWalkthroughRetry, + room.moveDiagonally(), room.getLayout().getDoorTile(), room.isAllowWalkthrough(), + new HashMap<>()); + } + + public RoomTile getNewTile() { + return this.newTile; + } + + public RoomTile getGoalLocation() { + return this.goalLocation; + } + + public RoomUnit getRoomUnit() { + return this.roomUnit; + } + + public boolean isWalkthroughRetry() { + return this.isWalkthroughRetry; + } + + public boolean isCanMoveDiagonally() { + return this.canMoveDiagonally; + } + + public RoomTile getDoorTile() { + return this.doorTile; + } + + public boolean isAllowWalkthrough() { + return this.allowWalkthrough; + } + + public Map getTileCache() { + return this.tileCache; + } + + public void setGoalLocation(RoomTile goalLocation) { + this.goalLocation = goalLocation; + } + + public void setRoomUnit(RoomUnit roomUnit) { + this.roomUnit = roomUnit; + } + + public Room getRoom() { + return room; + } +} \ No newline at end of file diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/PathfinderImpl.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/PathfinderImpl.java new file mode 100644 index 00000000..b6bd7df5 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/PathfinderImpl.java @@ -0,0 +1,240 @@ +package com.eu.habbo.habbohotel.rooms.pathfinding.impl; + +import static com.eu.habbo.habbohotel.rooms.pathfinding.impl.PathfinderConstants.CONFIG_EXECUTION_TIME; +import static com.eu.habbo.habbohotel.rooms.pathfinding.impl.PathfinderConstants.CONFIG_TIMEOUT_ENABLED; +import static com.eu.habbo.habbohotel.rooms.pathfinding.impl.PathfinderConstants.TIMEOUT_CHECK_INTERVAL; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomLayout; +import com.eu.habbo.habbohotel.rooms.RoomTile; +import com.eu.habbo.habbohotel.rooms.RoomTileState; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.rooms.pathfinding.Pathfinder; +import java.util.ArrayDeque; +import java.util.Comparator; +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.PriorityQueue; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +public class PathfinderImpl implements Pathfinder { + + private static final int CACHED_TIMEOUT_MS = Emulator.getConfig() + .getInt(CONFIG_EXECUTION_TIME, 25); + private static final boolean CACHED_TIMEOUT_ENABLED = Emulator.getConfig() + .getBoolean(CONFIG_TIMEOUT_ENABLED, false); + private static final long CACHED_TIMEOUT_NANOS = CACHED_TIMEOUT_MS * 1_000_000L; + + private final Room room; + private double maximumStepHeight; + private boolean allowFalling; + private final boolean retroStyleDiagonals; + + public PathfinderImpl(Room room, double maximumStepHeight, boolean allowFalling, + boolean retroStyleDiagonals) { + this.room = room; + this.maximumStepHeight = maximumStepHeight; + this.allowFalling = allowFalling; + this.retroStyleDiagonals = retroStyleDiagonals; + } + + + @Override + public CompletableFuture> findPathAsync(RoomTile oldTile, RoomTile newTile, + RoomTile goalLocation, RoomUnit roomUnit) { + return CompletableFuture.supplyAsync(() -> findPath(oldTile, newTile, goalLocation, roomUnit)) + .exceptionally(error -> { + throw new RuntimeException(new PathFinderException("Failed to find path", error)); + }); + } + + + @Override + public Deque findPath(RoomTile oldTile, RoomTile newTile, RoomTile goalLocation, + RoomUnit roomUnit) { + return this.findPath(oldTile, newTile, goalLocation, roomUnit, false); + } + + private boolean processCurrent(PathfinderContext context, RoomTile current, + PriorityQueue openList, HashSet closedList) { + if (current.getX() == context.getNewTile().getX() && current.getY() == context.getNewTile() + .getY()) { + return true; + } + + TileValidator.swapList(current, closedList, openList); + + Set adjacentNodes = AdjacentTileFinder.getAdjacent(context, current, + context.getNewTile(), context.getRoomUnit(), context.isCanMoveDiagonally(), + retroStyleDiagonals); + adjacentNodes.forEach( + currentAdj -> processAdjacent(context, currentAdj, closedList, current, openList)); + return false; + } + + private void processAdjacent(PathfinderContext context, RoomTile currentAdj, + HashSet closedList, RoomTile current, PriorityQueue openList) { + if (closedList.contains(currentAdj)) { + return; + } + + if (context.getRoomUnit().canOverrideTile(currentAdj)) { + AdjacentTileFinder.updateAdj(context, currentAdj, current, openList); + return; + } + + if (currentAdj.getState() == RoomTileState.BLOCKED || ( + (currentAdj.getState() == RoomTileState.SIT || currentAdj.getState() == RoomTileState.LAY) + && !currentAdj.equals(context.getGoalLocation()))) { + TileValidator.swapList(currentAdj, closedList, openList); + return; + } + + if (isInvalidHeight(context, currentAdj, current) || TileValidator.isAnyUnitAt(this.room, + context, currentAdj, closedList, openList)) { + return; + } + + AdjacentTileFinder.calculateCost(context, currentAdj, current, openList); + } + + @Override + public Deque findPath(RoomTile oldTile, RoomTile newTile, RoomTile goalLocation, + RoomUnit roomUnit, boolean isWalkthroughRetry) { + if (this.room == null || !this.room.isLoaded() || oldTile == null || newTile == null + || oldTile.equals(newTile) || newTile.getState() == RoomTileState.INVALID) { + return new LinkedList<>(); + } + long startTime = CACHED_TIMEOUT_ENABLED ? System.nanoTime() : 0; + int iterationCount = 0; + + PriorityQueue openList = new PriorityQueue<>( + Comparator.comparingInt(RoomTile::getfCosts)); + HashSet closedList = new HashSet<>(); + + openList.add(oldTile.copy()); + PathfinderContext context = PathfinderContext.buildContext(this.room, newTile, goalLocation, + roomUnit, isWalkthroughRetry); + + try { + while (!openList.isEmpty()) { + if (CACHED_TIMEOUT_ENABLED && (++iterationCount & (TIMEOUT_CHECK_INTERVAL - 1)) == 0 + && System.nanoTime() - startTime > CACHED_TIMEOUT_NANOS) { + return new LinkedList<>(); + } + + RoomTile current = openList.poll(); + if (current == null) { + break; + } + if (processCurrent(context, current, openList, closedList)) { + return this.tracePath( + AdjacentTileFinder.findTile(context, oldTile.getX(), oldTile.getY()), current); + } + } + + if (context.isAllowWalkthrough() && !isWalkthroughRetry) { + return this.findPath(oldTile, newTile, goalLocation, roomUnit, true); + } + + return new LinkedList<>(); + } finally { + // Optional: Clear collections for immediate memory release + // (GC will handle this anyway, but clearing can help in high-frequency scenarios) + openList.clear(); + closedList.clear(); + if (context.getTileCache() != null) { + context.getTileCache().clear(); + } + } + } + + + private boolean isInvalidHeight(PathfinderContext context, RoomTile currentAdj, + RoomTile current) { + double height = currentAdj.getStackHeight() - current.getStackHeight(); + return (!this.allowFalling && height < -this.maximumStepHeight) + || (currentAdj.getState() == RoomTileState.OPEN && height > this.maximumStepHeight) + && (findPathAroundAdjacentTile(context, currentAdj, current, height)); + } + + /** + * Check for intermediate tiles with smaller height differences + * + * @param context The pathfinder context + * @param currentAdj The current adjacent tile + * @param current The current tile + * @param height The height difference + * @return True if the path is around the current adjacent tile, false otherwise + */ + private boolean findPathAroundAdjacentTile(PathfinderContext context, RoomTile currentAdj, + RoomTile current, double height) { + PriorityQueue adjacentTiles = new PriorityQueue<>(Comparator.comparingDouble( + tile -> Math.abs(tile.getStackHeight() - current.getStackHeight()))); + + adjacentTiles.addAll(AdjacentTileFinder.getAdjacent(context, current, context.getNewTile(), + context.getRoomUnit(), context.isCanMoveDiagonally(), retroStyleDiagonals)); + RoomTile intermediateTile = adjacentTiles.peek(); + + if (intermediateTile == null || Math.abs(height) > this.maximumStepHeight) { + return true; + } + + currentAdj.setPrevious(intermediateTile); + intermediateTile.setPrevious(current); + return false; + } + + + /** + * Traces the path from the goal tile to the start tile by following the 'previous' references in + * the tiles. The traced path is returned as a deque where each element represents a tile on the + * path. + * + * @param start The starting tile of the path. + * @param goal The goal tile where the path tracing begins. + * @return A deque of RoomTile objects representing the traced path from the goal to the start. If + * the start tile is null or the path cannot be traced, an empty deque is returned. + */ + public Deque tracePath(RoomTile start, RoomTile goal) { + Deque path = new ArrayDeque<>(); + RoomLayout layout = this.room.getLayout(); + if (start == null) { + return path; + } + + RoomTile curr = goal; + while (curr != null) { + path.addFirst(layout.getTile(curr.getX(), curr.getY())); + curr = curr.getPrevious(); + if ((curr != null) && (curr.equals(start))) { + return path; + } + } + return path; + } + + + @Override + public boolean isAllowFalling() { + return this.allowFalling; + } + + @Override + public void setAllowFalling(boolean allow) { + this.allowFalling = allow; + } + + @Override + public double getMaxStepHeight() { + return this.maximumStepHeight; + } + + @Override + public void setMaxStepHeight(double value) { + this.maximumStepHeight = value; + } +} \ No newline at end of file diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/Rotation.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/Rotation.java new file mode 100644 index 00000000..adf8d0ad --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/Rotation.java @@ -0,0 +1,20 @@ +package com.eu.habbo.habbohotel.rooms.pathfinding.impl; +public class Rotation { + /** + * The directions to move in: Left, right, up, down + */ + protected static final short[][] DIRECTIONS = { + {-1, 0}, {1, 0}, {0, -1}, {0, 1} + }; + + /** + * The diagonal directions to move in: NE, SW, NW, SE + */ + protected static final short[][] DIAGONAL_DIRECTIONS = { + {1, 1}, {-1, -1}, {-1, 1}, {1, -1} + }; + + private Rotation() { + throw new IllegalStateException("Utility class"); + } +} \ No newline at end of file diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/TileValidator.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/TileValidator.java new file mode 100644 index 00000000..27d01024 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/pathfinding/impl/TileValidator.java @@ -0,0 +1,73 @@ +package com.eu.habbo.habbohotel.rooms.pathfinding.impl; + +import static com.eu.habbo.habbohotel.rooms.pathfinding.impl.PathfinderConstants.DISTANCE_DOOR_THRESHOLD; + +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomLayout; +import com.eu.habbo.habbohotel.rooms.RoomTile; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.rooms.RoomUnitType; +import com.eu.habbo.habbohotel.users.Habbo; +import java.util.Collection; +import java.util.PriorityQueue; +import java.util.Set; + +public class TileValidator { + + private TileValidator() { + throw new IllegalStateException("Utility class"); + } + + public static boolean isWalkableOrGoal(PathfinderContext context, RoomTile temp) { + return temp != null && (temp.isWalkable() || context.getGoalLocation().equals(temp)); + } + + public static boolean isOutOfBounds(RoomLayout layout, short x, short y) { + return x < 0 || y < 0 || x >= layout.getMapSizeX() || y >= layout.getMapSizeY(); + } + + public static boolean isAnyUnitAt(Room room, PathfinderContext context, RoomTile currentAdj, + Set closedList, PriorityQueue openList) { + RoomUnit exception = null; + + if (context.getRoomUnit().getRoomUnitType().equals(RoomUnitType.USER)) { + Habbo habbo = room.getHabbo(context.getRoomUnit().getId()); + exception = (habbo != null && habbo.getHabboInfo().getRiding() != null) ? habbo.getHabboInfo() + .getRiding().getRoomUnit() : null; + } + + if (isTileWalkable(room, context, currentAdj, exception)) { + swapList(currentAdj, closedList, openList); + return true; + } + return false; + } + + public static void swapList(RoomTile currentAdj, Set closedList, + PriorityQueue openList) { + closedList.add(currentAdj); + openList.remove(currentAdj); + } + + private static boolean isTileWalkable(Room room, PathfinderContext context, RoomTile currentAdj, + RoomUnit exception) { + return TileValidator.hasBlockingUnits(room, currentAdj, exception) + && context.getDoorTile().distance(currentAdj) > DISTANCE_DOOR_THRESHOLD && ( + !context.isWalkthroughRetry() || !context.isAllowWalkthrough() || currentAdj.equals( + context.getGoalLocation())); + } + + public static boolean hasBlockingUnits(Room room, RoomTile tile, RoomUnit exception) { + Collection units = room.getRoomUnitsAt(tile); + if (units.isEmpty()) { + return false; + } + + for (RoomUnit unit : units) { + if (unit != exception) { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboBadge.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboBadge.java index c9bba2c4..5903cdc5 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboBadge.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboBadge.java @@ -11,7 +11,7 @@ public class HabboBadge implements Runnable { private int id; private String code; private int slot; - private final Habbo habbo; + private Habbo habbo; private boolean needsUpdate; private boolean needsInsert; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboStats.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboStats.java index 87b4e259..74d59e75 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboStats.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboStats.java @@ -1,12 +1,12 @@ package com.eu.habbo.habbohotel.users; import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.campaign.calendar.CalendarRewardClaimed; +import com.eu.habbo.habbohotel.gameclients.GameClient; import com.eu.habbo.habbohotel.achievements.Achievement; import com.eu.habbo.habbohotel.achievements.AchievementManager; import com.eu.habbo.habbohotel.achievements.TalentTrackType; -import com.eu.habbo.habbohotel.campaign.calendar.CalendarRewardClaimed; import com.eu.habbo.habbohotel.catalog.CatalogItem; -import com.eu.habbo.habbohotel.gameclients.GameClient; import com.eu.habbo.habbohotel.permissions.Permission; import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles; import com.eu.habbo.habbohotel.rooms.RoomTrade; @@ -25,10 +25,7 @@ import org.slf4j.LoggerFactory; import java.lang.reflect.Constructor; import java.sql.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; public class HabboStats implements Runnable { @@ -46,7 +43,7 @@ public class HabboStats implements Runnable { private final THashMap recentPurchases; private final TIntArrayList favoriteRooms; private final TIntArrayList ignoredUsers; - private final TIntArrayList roomsVists; + private TIntArrayList roomsVists; public int achievementScore; public int respectPointsReceived; public int respectPointsGiven; @@ -96,7 +93,7 @@ public class HabboStats implements Runnable { public boolean hasGottenDefaultSavedSearches; private HabboInfo habboInfo; private boolean allowTrade; - private final int clubExpireTimestamp; + private int clubExpireTimestamp; private int muteEndTime; public int maxFriends; public int maxRooms; @@ -746,6 +743,7 @@ public class HabboStats implements Runnable { gameClient.getHabbo().whisper(Emulator.getTexts().getValue("generic.error.ignore_higher_rank"), RoomChatMessageBubbles.ALERT); return false; } + if (!this.userIgnored(userId)) { this.ignoredUsers.add(userId); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/cache/HabboOfferPurchase.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/cache/HabboOfferPurchase.java index e3591750..cf8f0bfa 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/cache/HabboOfferPurchase.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/cache/HabboOfferPurchase.java @@ -12,7 +12,6 @@ import java.sql.SQLException; public class HabboOfferPurchase { private static final Logger LOGGER = LoggerFactory.getLogger(HabboOfferPurchase.class); - private final int userId; private final int offerId; private int state; private int amount; @@ -20,7 +19,6 @@ public class HabboOfferPurchase { private boolean needsUpdate = false; public HabboOfferPurchase(ResultSet set) throws SQLException { - this.userId = set.getInt("user_id"); this.offerId = set.getInt("offer_id"); this.state = set.getInt("state"); this.amount = set.getInt("amount"); @@ -28,7 +26,6 @@ public class HabboOfferPurchase { } private HabboOfferPurchase(int userId, int offerId) { - this.userId = userId; this.offerId = offerId; } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/subscriptions/SubscriptionHabboClub.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/subscriptions/SubscriptionHabboClub.java index 01d61f91..5e1efddd 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/subscriptions/SubscriptionHabboClub.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/subscriptions/SubscriptionHabboClub.java @@ -13,9 +13,7 @@ import com.eu.habbo.habbohotel.users.clothingvalidation.ClothingValidationManage import com.eu.habbo.messages.outgoing.catalog.ClubCenterDataComposer; import com.eu.habbo.messages.outgoing.generic.PickMonthlyClubGiftNotificationComposer; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer; -import com.eu.habbo.messages.outgoing.users.UpdateUserLookComposer; -import com.eu.habbo.messages.outgoing.users.UserClubComposer; -import com.eu.habbo.messages.outgoing.users.UserPermissionsComposer; +import com.eu.habbo.messages.outgoing.users.*; import gnu.trove.map.hash.THashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -267,26 +265,9 @@ public class SubscriptionHabboClub extends Subscription { try (ResultSet set = statement.executeQuery()) { while (set.next()) { - int userId = set.getInt("user_id"); try { + int userId = set.getInt("user_id"); HabboInfo habboInfo = Emulator.getGameEnvironment().getHabboManager().getHabboInfo(userId); - - if (habboInfo == null) { - SubscriptionManager.LOGGER.error("HabboInfo is null for user #" + userId + ". Removing subscription."); - - // Remove subscription from the database - try (PreparedStatement removeStatement = connection.prepareStatement( - "DELETE FROM users_subscriptions WHERE user_id = ? AND subscription_type = ?")) { - removeStatement.setInt(1, userId); - removeStatement.setString(2, Subscription.HABBO_CLUB); - removeStatement.executeUpdate(); - } catch (SQLException e) { - SubscriptionManager.LOGGER.error("SQL exception when trying to remove subscription for user #" + userId, e); - } - - continue; - } - HabboStats stats = habboInfo.getHabboStats(); ClubCenterDataComposer calculated = calculatePayday(habboInfo); int totalReward = (calculated.creditRewardForMonthlySpent + calculated.creditRewardForStreakBonus); @@ -298,7 +279,7 @@ public class SubscriptionHabboClub extends Subscription { stats.lastHCPayday = timestampNow; Emulator.getThreading().run(stats); } catch (Exception e) { - SubscriptionManager.LOGGER.error("Exception processing HC payday for user #" + userId, e); + SubscriptionManager.LOGGER.error("Exception processing HC payday for user #{}", set.getInt("user_id"), e); } } } @@ -327,7 +308,6 @@ public class SubscriptionHabboClub extends Subscription { isExecuting = false; } - /** * Called when a user logs in. Checks for any unclaimed HC Pay day rewards and issues rewards. * @@ -349,7 +329,7 @@ public class SubscriptionHabboClub extends Subscription { while (set.next()) { try { int logId = set.getInt("id"); - set.getInt("user_id"); + set.getInt("user_id"); // consumed but unused - user_id is in logs_hc_payday int totalPayout = set.getInt("total_payout"); String currency = set.getString("currency"); 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 86ffa573..e620b4e0 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 @@ -24,7 +24,8 @@ public enum WiredConditionType { NOT_ACTOR_WEARS_BADGE(22), NOT_ACTOR_WEARS_EFFECT(23), DATE_RANGE(24), - ACTOR_HAS_HANDITEM(25); + ACTOR_HAS_HANDITEM(25), + MOVEMENT_VALIDATION(26); // i dont know what type it is but its needed public final int code; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/api/IWiredCondition.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/api/IWiredCondition.java new file mode 100644 index 00000000..18645338 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/api/IWiredCondition.java @@ -0,0 +1,58 @@ +package com.eu.habbo.habbohotel.wired.api; + +import com.eu.habbo.habbohotel.wired.WiredConditionOperator; +import com.eu.habbo.habbohotel.wired.core.WiredContext; + +/** + * Interface for wired conditions in the new context-driven architecture. + *

+ * Conditions are evaluated after a trigger matches to determine if effects + * should execute. All conditions must pass (AND logic by default) unless + * modified by extras like WiredExtraOrEval. + *

+ * + *

Evaluation:

+ *
    + *
  • Conditions receive a {@link WiredContext} with all relevant data
  • + *
  • Return true if the condition passes, false to block execution
  • + *
  • Use {@code ctx.actor()} for the triggering user
  • + *
  • Use {@code ctx.room()} for room state checks
  • + *
+ * + *

Example Implementation:

+ *
{@code
+ * public class UserHasEffectCondition implements IWiredCondition {
+ *     private final int requiredEffectId;
+ *     
+ *     public boolean evaluate(WiredContext ctx) {
+ *         return ctx.actor()
+ *             .map(user -> user.getEffectId() == requiredEffectId)
+ *             .orElse(false);
+ *     }
+ * }
+ * }
+ * + * @see WiredContext + * @see IWiredTrigger + * @see IWiredEffect + */ +public interface IWiredCondition { + + /** + * Evaluate this condition against the current context. + * + * @param ctx the wired context containing event data, room, actor, etc. + * @return true if the condition passes, false to block effect execution + */ + boolean evaluate(WiredContext ctx); + + /** + * Get the operator for combining this condition with others. + * Default is AND, meaning all conditions must pass. + * + * @return the condition operator + */ + default WiredConditionOperator operator() { + return WiredConditionOperator.AND; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/api/IWiredEffect.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/api/IWiredEffect.java new file mode 100644 index 00000000..68cd68e4 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/api/IWiredEffect.java @@ -0,0 +1,102 @@ +package com.eu.habbo.habbohotel.wired.api; + +import com.eu.habbo.habbohotel.wired.core.WiredContext; +import com.eu.habbo.habbohotel.wired.core.WiredSimulation; + +/** + * Interface for wired effects in the new context-driven architecture. + *

+ * Effects are the actions performed when a trigger fires and all conditions pass. + * They receive a {@link WiredContext} containing all relevant data and should use + * {@link WiredContext#services()} for all side effects. + *

+ * + *

Best Practices:

+ *
    + *
  • Use {@code ctx.services()} for all room mutations (teleport, toggle, etc.)
  • + *
  • Use {@code ctx.targets()} to get users/items to affect
  • + *
  • Check {@code ctx.actor()} before operations requiring a user
  • + *
  • Call {@code ctx.state().step()} before expensive operations (automatic in engine)
  • + *
+ * + *

Simulation Mode:

+ *

+ * When WiredExtraRequireFullExecution is present, movement effects are first run in + * simulation mode via {@link #simulate(WiredContext, WiredSimulation)}. Movement effects + * should record their intended position changes to the simulation. If all effects pass + * simulation, they are then executed for real. + *

+ * + * @see WiredContext + * @see WiredSimulation + * @see IWiredTrigger + * @see IWiredCondition + */ +public interface IWiredEffect { + + /** + * Execute this effect with the given context. + * + * @param ctx the wired context containing event data, room, actor, services, etc. + */ + void execute(WiredContext ctx); + + /** + * Get the delay in ticks (500ms each) before this effect executes. + * Default is 0 (immediate execution). + * + * @return delay in 500ms ticks + */ + default int getDelay() { + return 0; + } + + /** + * Check if this effect requires an actor (RoomUnit) to execute. + * If true and no actor is present, the effect will be skipped. + * Default is false for backwards compatibility. + * + * @return true if an actor is required + */ + default boolean requiresActor() { + return false; + } + + /** + * Get the cooldown for this effect in milliseconds. + * The effect won't execute again until the cooldown expires. + * Default is 0 (no cooldown). + * + * @return cooldown in milliseconds + */ + default long getCooldown() { + return 0L; + } + + /** + * Simulate this effect's execution and record intended state changes. + *

+ * This method is called when WiredExtraRequireFullExecution is present. + * Movement effects should record their intended position changes to the + * simulation WITHOUT modifying the real room. The simulation tracks cumulative + * position changes, so if Effect 1 moves an item to tile X, Effect 2 will see + * the item at tile X when calculating its move. + *

+ *

+ * If this effect would fail (e.g., moving to a hole), call + * {@code simulation.fail("reason")} and return false. + *

+ *

+ * The default implementation returns true (assumes success). Only movement + * effects need to override this method. + *

+ * + * @param ctx the wired context + * @param simulation the simulation state tracker for recording moves + * @return true if simulation succeeded, false if the move would fail + */ + default boolean simulate(WiredContext ctx, WiredSimulation simulation) { + // Default: effect doesn't involve movement, assume success + return true; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/api/IWiredTrigger.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/api/IWiredTrigger.java new file mode 100644 index 00000000..e3bd8d9d --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/api/IWiredTrigger.java @@ -0,0 +1,72 @@ +package com.eu.habbo.habbohotel.wired.api; + +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; + +/** + * Interface for wired triggers in the new context-driven architecture. + *

+ * Triggers are the entry point for wired execution. They listen for specific + * event types and determine whether to activate based on their configuration. + *

+ * + *

Lifecycle:

+ *
    + *
  1. Engine receives a {@link WiredEvent}
  2. + *
  3. Engine finds triggers that listen to that event type via {@link #listensTo()}
  4. + *
  5. Engine calls {@link #matches(HabboItem, WiredEvent)} for quick filtering
  6. + *
  7. If matched, conditions are evaluated and effects executed
  8. + *
+ * + *

Example Implementation:

+ *
{@code
+ * public class UserSaysTrigger implements IWiredTrigger {
+ *     private final String keyword;
+ *     
+ *     public WiredEvent.Type listensTo() {
+ *         return WiredEvent.Type.USER_SAYS;
+ *     }
+ *     
+ *     public boolean matches(HabboItem triggerItem, WiredEvent event) {
+ *         return event.getText()
+ *             .map(text -> text.toLowerCase().contains(keyword.toLowerCase()))
+ *             .orElse(false);
+ *     }
+ * }
+ * }
+ * + * @see WiredEvent + * @see IWiredCondition + * @see IWiredEffect + */ +public interface IWiredTrigger { + + /** + * Get the event type this trigger listens to. + * The engine uses this for fast filtering before calling {@link #matches}. + * + * @return the event type this trigger responds to + */ + WiredEvent.Type listensTo(); + + /** + * Determine if this trigger matches the given event. + * This is called after event type filtering to check trigger-specific conditions. + * + * @param triggerItem the wired trigger furniture item + * @param event the event that occurred + * @return true if this trigger should activate for this event + */ + boolean matches(HabboItem triggerItem, WiredEvent event); + + /** + * Check if this trigger requires an actor (RoomUnit) to fire. + * If true and no actor is present in the event, the trigger won't match. + * Default is false for backwards compatibility. + * + * @return true if an actor is required + */ + default boolean requiresActor() { + return false; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/api/WiredStack.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/api/WiredStack.java new file mode 100644 index 00000000..0713715a --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/api/WiredStack.java @@ -0,0 +1,188 @@ +package com.eu.habbo.habbohotel.wired.api; + +import com.eu.habbo.habbohotel.users.HabboItem; + +import java.util.Collections; +import java.util.List; + +/** + * Represents a wired stack - a trigger with its associated conditions and effects. + *

+ * In Habbo, wired items stacked on the same tile form a logical unit: + *

    + *
  • One trigger at the base (what starts the chain)
  • + *
  • Zero or more conditions (requirements that must be met)
  • + *
  • One or more effects (actions to perform)
  • + *
  • Optional extras (modifiers like random selection)
  • + *
+ *

+ * + *

Execution Flow:

+ *
    + *
  1. Trigger receives an event and calls {@link IWiredTrigger#matches}
  2. + *
  3. If matched, all conditions are evaluated via {@link IWiredCondition#evaluate}
  4. + *
  5. If conditions pass, effects execute via {@link IWiredEffect#execute}
  6. + *
+ * + * @see IWiredTrigger + * @see IWiredCondition + * @see IWiredEffect + */ +public final class WiredStack { + + private final HabboItem triggerItem; + private final IWiredTrigger trigger; + private final List conditions; + private final List effects; + + // Extra modifiers + private final boolean useOrMode; // WiredExtraOrEval present + private final boolean useRandom; // WiredExtraRandom present + private final boolean useUnseen; // WiredExtraUnseen present + + /** + * Create a new wired stack. + * + * @param triggerItem the wired trigger furniture item + * @param trigger the trigger implementation + * @param conditions list of conditions (may be empty) + * @param effects list of effects (should have at least one) + */ + public WiredStack(HabboItem triggerItem, + IWiredTrigger trigger, + List conditions, + List effects) { + this(triggerItem, trigger, conditions, effects, false, false, false); + } + + /** + * Create a new wired stack with modifiers. + * + * @param triggerItem the wired trigger furniture item + * @param trigger the trigger implementation + * @param conditions list of conditions + * @param effects list of effects + * @param useOrMode if true, conditions use OR logic (any pass = success) + * @param useRandom if true, select one random effect instead of all + * @param useUnseen if true, execute effects in "unseen" order (round-robin) + */ + public WiredStack(HabboItem triggerItem, + IWiredTrigger trigger, + List conditions, + List effects, + boolean useOrMode, + boolean useRandom, + boolean useUnseen) { + this.triggerItem = triggerItem; + this.trigger = trigger; + this.conditions = conditions != null ? Collections.unmodifiableList(conditions) : Collections.emptyList(); + this.effects = effects != null ? Collections.unmodifiableList(effects) : Collections.emptyList(); + this.useOrMode = useOrMode; + this.useRandom = useRandom; + this.useUnseen = useUnseen; + } + + /** + * Get the wired trigger furniture item. + * @return the trigger item + */ + public HabboItem triggerItem() { + return triggerItem; + } + + /** + * Get the trigger implementation. + * @return the trigger + */ + public IWiredTrigger trigger() { + return trigger; + } + + /** + * Get the list of conditions. + * @return unmodifiable list of conditions + */ + public List conditions() { + return conditions; + } + + /** + * Get the list of effects. + * @return unmodifiable list of effects + */ + public List effects() { + return effects; + } + + /** + * Check if this stack has any conditions. + * @return true if there are conditions + */ + public boolean hasConditions() { + return !conditions.isEmpty(); + } + + /** + * Check if this stack has any effects. + * @return true if there are effects + */ + public boolean hasEffects() { + return !effects.isEmpty(); + } + + /** + * Check if OR mode is enabled (WiredExtraOrEval). + * When true, any condition passing means all pass. + * @return true if OR mode is enabled + */ + public boolean useOrMode() { + return useOrMode; + } + + /** + * Check if random mode is enabled (WiredExtraRandom). + * When true, only one random effect is executed. + * @return true if random mode is enabled + */ + public boolean useRandom() { + return useRandom; + } + + /** + * Check if unseen mode is enabled (WiredExtraUnseen). + * When true, effects execute in round-robin order. + * @return true if unseen mode is enabled + */ + public boolean useUnseen() { + return useUnseen; + } + + /** + * Get the number of conditions. + * @return condition count + */ + public int conditionCount() { + return conditions.size(); + } + + /** + * Get the number of effects. + * @return effect count + */ + public int effectCount() { + return effects.size(); + } + + @Override + public String toString() { + return "WiredStack{" + + "triggerItem=" + (triggerItem != null ? triggerItem.getId() : "null") + + ", trigger=" + (trigger != null ? trigger.listensTo() : "null") + + ", conditions=" + conditions.size() + + ", effects=" + effects.size() + + ", orMode=" + useOrMode + + ", random=" + useRandom + + ", unseen=" + useUnseen + + '}'; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/DefaultWiredServices.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/DefaultWiredServices.java new file mode 100644 index 00000000..f75379c3 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/DefaultWiredServices.java @@ -0,0 +1,351 @@ +package com.eu.habbo.habbohotel.wired.core; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.bots.Bot; +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomChatMessage; +import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles; +import com.eu.habbo.habbohotel.rooms.RoomTile; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserEffectComposer; +import com.eu.habbo.messages.outgoing.rooms.users.RoomUserWhisperComposer; +import com.eu.habbo.threading.runnables.RoomUnitTeleport; +import com.eu.habbo.threading.runnables.SendRoomUnitEffectComposer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * Default implementation of {@link WiredServices} that wraps existing room operations. + *

+ * This implementation forwards all operations to the existing Room/Emulator methods, + * providing a clean abstraction layer for wired effects while maintaining full + * backwards compatibility. + *

+ * + * @see WiredServices + */ +public final class DefaultWiredServices implements WiredServices { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultWiredServices.class); + + /** Singleton instance */ + private static final DefaultWiredServices INSTANCE = new DefaultWiredServices(); + + private DefaultWiredServices() { + // Singleton + } + + /** + * Get the singleton instance. + * @return the default wired services instance + */ + public static DefaultWiredServices getInstance() { + return INSTANCE; + } + + // ========== Debug/Logging ========== + + @Override + public void debug(Room room, String message) { + if (WiredManager.isDebugEnabled()) { + LOGGER.info("[Wired][Room {}] {}", room != null ? room.getId() : "null", message); + } + } + + @Override + public void debug(Room room, String format, Object... args) { + if (WiredManager.isDebugEnabled()) { + String message = String.format(format.replace("{}", "%s"), args); + LOGGER.info("[Wired][Room {}] {}", room != null ? room.getId() : "null", message); + } + } + + // ========== User Operations ========== + + @Override + public void teleportUser(Room room, RoomUnit user, RoomTile tile) { + if (room == null || user == null || tile == null) return; + + // Show teleport effect + room.sendComposer(new RoomUserEffectComposer(user, 4).compose()); + Emulator.getThreading().run(new SendRoomUnitEffectComposer(room, user), WiredManager.TELEPORT_DELAY + 1000); + + // Execute teleport + double height = tile.getStackHeight(); + Emulator.getThreading().run(() -> user.isWiredTeleporting = true, Math.max(0, WiredManager.TELEPORT_DELAY - 500)); + Emulator.getThreading().run( + new RoomUnitTeleport(user, room, tile.x, tile.y, height, user.getEffectId()), + WiredManager.TELEPORT_DELAY + ); + } + + @Override + public void moveUser(Room room, RoomUnit user, RoomTile tile) { + if (room == null || user == null || tile == null) return; + + user.setGoalLocation(tile); + user.setCanWalk(true); + } + + @Override + public void kickUser(Room room, RoomUnit user) { + if (room == null || user == null) return; + + Habbo habbo = room.getHabbo(user); + if (habbo != null && !room.isOwner(habbo)) { + room.kickHabbo(habbo, false); + } + } + + @Override + public void giveEffect(Room room, RoomUnit user, int effectId) { + giveEffect(room, user, effectId, -1); + } + + @Override + public void giveEffect(Room room, RoomUnit user, int effectId, int duration) { + if (room == null || user == null) return; + + room.giveEffect(user, effectId, duration); + } + + @Override + public void whisperToUser(Room room, RoomUnit user, String message) { + if (room == null || user == null || message == null) return; + + Habbo habbo = room.getHabbo(user); + if (habbo != null) { + habbo.getClient().sendResponse(new RoomUserWhisperComposer( + new RoomChatMessage(message, habbo, habbo, RoomChatMessageBubbles.WIRED) + )); + } + } + + @Override + public void giveHandItem(Room room, RoomUnit user, int handItemId) { + if (room == null || user == null) return; + + user.setHandItem(handItemId); + room.sendComposer(new com.eu.habbo.messages.outgoing.rooms.users.RoomUserHandItemComposer(user).compose()); + } + + @Override + public void muteUser(Room room, RoomUnit user, int durationMinutes) { + if (room == null || user == null) return; + + Habbo habbo = room.getHabbo(user); + if (habbo != null) { + room.muteHabbo(habbo, durationMinutes); + } + } + + // ========== Furniture Operations ========== + + @Override + public void toggleFurni(Room room, HabboItem item) { + if (room == null || item == null) return; + + // Try to toggle state + try { + int maxState = item.getBaseItem().getStateCount(); + if (maxState > 1) { + int currentState = 0; + try { + currentState = Integer.parseInt(item.getExtradata()); + } catch (NumberFormatException ignored) { + } + + int newState = (currentState + 1) % maxState; + item.setExtradata(String.valueOf(newState)); + room.updateItemState(item); + } + } catch (Exception e) { + LOGGER.warn("Failed to toggle furni {}: {}", item.getId(), e.getMessage()); + } + } + + @Override + public void setFurniState(Room room, HabboItem item, int state) { + if (room == null || item == null) return; + + item.setExtradata(String.valueOf(state)); + room.updateItemState(item); + } + + @Override + public void moveFurni(Room room, HabboItem item, RoomTile tile, int rotation) { + if (room == null || item == null || tile == null) return; + + room.moveFurniTo(item, tile, rotation, null, true); + } + + @Override + public void resetFurniState(Room room, HabboItem item) { + if (room == null || item == null) return; + + // Reset to state 0 + item.setExtradata("0"); + room.updateItemState(item); + } + + // ========== Game Operations ========== + + @Override + public void giveScore(Room room, RoomUnit user, int score) { + if (room == null || user == null) return; + + Habbo habbo = room.getHabbo(user); + if (habbo != null && habbo.getHabboInfo().getGamePlayer() != null) { + habbo.getHabboInfo().getGamePlayer().addScore(score); + } + } + + @Override + public void giveScoreToTeam(Room room, int teamId, int score) { + if (room == null) return; + + // This would need access to game manager - implementation depends on game context + debug(room, "giveScoreToTeam called: team={}, score={}", teamId, score); + } + + @Override + public void joinTeam(Room room, RoomUnit user, int teamId) { + if (room == null || user == null) return; + + // Team joining logic depends on active game + debug(room, "joinTeam called: user={}, team={}", user.getId(), teamId); + } + + @Override + public void leaveTeam(Room room, RoomUnit user) { + if (room == null || user == null) return; + + Habbo habbo = room.getHabbo(user); + if (habbo != null && habbo.getHabboInfo().getGamePlayer() != null) { + // Leave team logic + debug(room, "leaveTeam called: user={}", user.getId()); + } + } + + // ========== Bot Operations ========== + + @Override + public void botTalk(Room room, String botName, String message, boolean shout) { + if (room == null || botName == null || message == null) return; + + List bots = room.getBots(botName); + for (Bot bot : bots) { + if (shout) { + bot.shout(message); + } else { + bot.talk(message); + } + } + } + + @Override + public void botWhisperTo(Room room, String botName, RoomUnit user, String message) { + if (room == null || botName == null || user == null || message == null) return; + + Habbo habbo = room.getHabbo(user); + if (habbo != null) { + List bots = room.getBots(botName); + for (Bot bot : bots) { + habbo.getClient().sendResponse( + new RoomUserWhisperComposer( + new RoomChatMessage(message, bot.getRoomUnit(), RoomChatMessageBubbles.getBubble(bot.getBubbleId())) + ) + ); + } + } + } + + @Override + public void botWalkToFurni(Room room, String botName, HabboItem item) { + if (room == null || botName == null || item == null) return; + + RoomTile tile = room.getLayout().getTile(item.getX(), item.getY()); + if (tile != null) { + List bots = room.getBots(botName); + for (Bot bot : bots) { + bot.getRoomUnit().setGoalLocation(tile); + } + } + } + + @Override + public void botTeleport(Room room, String botName, RoomTile tile) { + if (room == null || botName == null || tile == null) return; + + List bots = room.getBots(botName); + for (Bot bot : bots) { + room.teleportRoomUnitToLocation(bot.getRoomUnit(), tile.x, tile.y, tile.getStackHeight()); + } + } + + @Override + public void botFollowUser(Room room, String botName, RoomUnit user) { + if (room == null || botName == null || user == null) return; + + Habbo habbo = room.getHabbo(user); + if (habbo != null) { + List bots = room.getBots(botName); + for (Bot bot : bots) { + bot.startFollowingHabbo(habbo); + } + } + } + + @Override + public void botSetClothes(Room room, String botName, String figure) { + if (room == null || botName == null || figure == null) return; + + List bots = room.getBots(botName); + for (Bot bot : bots) { + bot.setFigure(figure); + bot.needsUpdate(true); + room.sendComposer(new com.eu.habbo.messages.outgoing.rooms.users.RoomUsersComposer(bot).compose()); + } + } + + @Override + public void botGiveHandItem(Room room, String botName, RoomUnit user, int handItemId) { + if (room == null || botName == null || user == null) return; + + // Bot gives hand item by walking to user and transferring + Habbo habbo = room.getHabbo(user); + if (habbo != null) { + giveHandItem(room, user, handItemId); + } + } + + // ========== Messaging ========== + + @Override + public void showAlert(Room room, RoomUnit user, String message) { + if (room == null || user == null || message == null) return; + + Habbo habbo = room.getHabbo(user); + if (habbo != null) { + habbo.alert(message); + } + } + + // ========== Room Operations ========== + + @Override + public void resetTimers(Room room) { + if (room == null) return; + + // Reset all wired triggers that are timers + room.getRoomSpecialTypes().getTriggers().forEach(trigger -> { + if (trigger instanceof com.eu.habbo.habbohotel.items.interactions.wired.WiredTriggerReset) { + ((com.eu.habbo.habbohotel.items.interactions.wired.WiredTriggerReset) trigger).resetTimer(); + } + }); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/RoomWiredStackIndex.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/RoomWiredStackIndex.java new file mode 100644 index 00000000..1002a7ce --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/RoomWiredStackIndex.java @@ -0,0 +1,235 @@ +package com.eu.habbo.habbohotel.wired.core; + +import com.eu.habbo.habbohotel.items.interactions.InteractionWiredCondition; +import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; +import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; +import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraOrEval; +import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraRandom; +import com.eu.habbo.habbohotel.items.interactions.wired.extra.WiredExtraUnseen; +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomSpecialTypes; +import com.eu.habbo.habbohotel.rooms.RoomTile; +import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.api.IWiredCondition; +import com.eu.habbo.habbohotel.wired.api.IWiredEffect; +import com.eu.habbo.habbohotel.wired.api.IWiredTrigger; +import com.eu.habbo.habbohotel.wired.api.WiredStack; +import gnu.trove.set.hash.THashSet; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Implementation of {@link WiredStackIndex} that builds stacks from {@link RoomSpecialTypes}. + *

+ * This implementation reads wired items from RoomSpecialTypes and builds + * WiredStack objects on-demand. Since wired items already implement the + * IWiredTrigger, IWiredCondition, and IWiredEffect interfaces, no adapters + * are needed. + *

+ * + *

Stack Building:

+ *
    + *
  1. Find all triggers of the requested event type
  2. + *
  3. For each trigger, get conditions and effects at the same tile
  4. + *
  5. Check for extras (OR mode, random, unseen) at the tile
  6. + *
  7. Build a WiredStack with the collected components
  8. + *
+ * + * @see RoomSpecialTypes + * @see WiredStack + */ +public final class RoomWiredStackIndex implements WiredStackIndex { + + /** Cache of built stacks per room and event type */ + private final ConcurrentHashMap>> cache; + + /** Whether to use caching (can be disabled for testing) */ + private final boolean useCache; + + /** + * Create a new index with caching enabled. + */ + public RoomWiredStackIndex() { + this(true); + } + + /** + * Create a new index with optional caching. + * @param useCache true to cache built stacks, false to rebuild on every request + */ + public RoomWiredStackIndex(boolean useCache) { + this.useCache = useCache; + this.cache = new ConcurrentHashMap<>(); + } + + @Override + public List getStacks(Room room, WiredEvent.Type type) { + if (room == null || type == null) { + return Collections.emptyList(); + } + + // Check cache first + if (useCache) { + Map> roomCache = cache.get(room.getId()); + if (roomCache != null) { + List cached = roomCache.get(type); + if (cached != null) { + return cached; + } + } + } + + // Build stacks for this event type + List stacks = buildStacks(room, type); + + // Cache the result + if (useCache) { + cache.computeIfAbsent(room.getId(), k -> new ConcurrentHashMap<>()) + .put(type, stacks); + } + + return stacks; + } + + @Override + public void rebuild(Room room) { + if (room == null) return; + invalidateAll(room); + } + + @Override + public void invalidate(Room room, RoomTile tile) { + if (room == null) return; + // For simplicity, invalidate all cached stacks for the room + // A more sophisticated implementation could track which stacks use which tiles + invalidateAll(room); + } + + @Override + public void invalidateAll(Room room) { + if (room == null) return; + cache.remove(room.getId()); + } + + @Override + public boolean isCached(Room room) { + return room != null && cache.containsKey(room.getId()); + } + + /** + * Build all wired stacks for a specific event type in a room. + */ + private List buildStacks(Room room, WiredEvent.Type type) { + RoomSpecialTypes specialTypes = room.getRoomSpecialTypes(); + if (specialTypes == null) { + return Collections.emptyList(); + } + + // Get legacy trigger type + WiredTriggerType legacyType = type.toLegacyType(); + if (legacyType == null) { + return Collections.emptyList(); + } + + // Get all triggers of this type + THashSet triggers = specialTypes.getTriggers(legacyType); + if (triggers == null || triggers.isEmpty()) { + return Collections.emptyList(); + } + + List stacks = new ArrayList<>(); + + for (InteractionWiredTrigger trigger : triggers) { + // Avoid processing multiple triggers at the same tile multiple times + // (we build one stack per trigger, but share conditions/effects at same location) + short x = trigger.getX(); + short y = trigger.getY(); + + // Build the stack for this trigger + WiredStack stack = buildStack(room, specialTypes, trigger, x, y); + if (stack != null) { + stacks.add(stack); + } + } + + return stacks.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(stacks); + } + + /** + * Build a single wired stack for a trigger at a specific location. + */ + private WiredStack buildStack(Room room, RoomSpecialTypes specialTypes, + InteractionWiredTrigger trigger, short x, short y) { + // The trigger already implements IWiredTrigger + IWiredTrigger wrappedTrigger = trigger; + + // Get conditions at this location + THashSet rawConditions = specialTypes.getConditions(x, y); + List conditions = collectConditions(rawConditions); + + // Get effects at this location + THashSet rawEffects = specialTypes.getEffects(x, y); + List effects = collectEffects(rawEffects); + + // Check for extras + boolean useOrMode = specialTypes.hasExtraType(x, y, WiredExtraOrEval.class); + boolean useRandom = specialTypes.hasExtraType(x, y, WiredExtraRandom.class); + boolean useUnseen = specialTypes.hasExtraType(x, y, WiredExtraUnseen.class); + + return new WiredStack( + trigger, + wrappedTrigger, + conditions, + effects, + useOrMode, + useRandom, + useUnseen + ); + } + + /** + * Collect conditions into a list (they already implement IWiredCondition). + */ + private List collectConditions(THashSet rawConditions) { + if (rawConditions == null || rawConditions.isEmpty()) { + return Collections.emptyList(); + } + + List conditions = new ArrayList<>(rawConditions.size()); + for (InteractionWiredCondition condition : rawConditions) { + conditions.add(condition); + } + return conditions; + } + + /** + * Collect effects into a list (they already implement IWiredEffect). + */ + private List collectEffects(THashSet rawEffects) { + if (rawEffects == null || rawEffects.isEmpty()) { + return Collections.emptyList(); + } + + List effects = new ArrayList<>(rawEffects.size()); + for (InteractionWiredEffect effect : rawEffects) { + effects.add(effect); + } + return effects; + } + + /** + * Clear all cached data. + */ + public void clearAll() { + cache.clear(); + } + + /** + * Get the number of rooms currently cached. + * @return cached room count + */ + public int getCachedRoomCount() { + return cache.size(); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredContext.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredContext.java new file mode 100644 index 00000000..4a84cb01 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredContext.java @@ -0,0 +1,319 @@ +package com.eu.habbo.habbohotel.wired.core; + +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomTile; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.api.WiredStack; + +import java.util.Optional; + +/** + * Context object passed to conditions and effects during wired execution. + *

+ * This is the "backbone" of the new wired system. It provides: + *

    + *
  • Access to the triggering event ({@link #event()})
  • + *
  • Convenience accessors for common event data
  • + *
  • Mutable target collection ({@link #targets()})
  • + *
  • Services for performing side effects ({@link #services()})
  • + *
  • Execution state for loop safety ({@link #state()})
  • + *
+ *

+ * + *

Usage in Conditions:

+ *
{@code
+ * public boolean evaluate(WiredContext ctx) {
+ *     return ctx.actor()
+ *         .map(user -> user.getEffectId() == requiredEffect)
+ *         .orElse(false);
+ * }
+ * }
+ * + *

Usage in Effects:

+ *
{@code
+ * public void execute(WiredContext ctx) {
+ *     ctx.actor().ifPresent(user -> 
+ *         ctx.services().teleportUser(ctx.room(), user, targetTile));
+ * }
+ * }
+ * + * @see WiredEvent + * @see WiredServices + * @see WiredState + * @see WiredTargets + */ +public final class WiredContext { + + private final WiredEvent event; + private final WiredServices services; + private final WiredState state; + private final WiredTargets targets; + + /** The wired trigger furniture item executing this stack */ + private final HabboItem triggerItem; + + /** The wired stack being executed (for conditions to access effects) */ + private final WiredStack stack; + + /** Extra settings from the trigger item (for legacy compatibility) */ + private final Object[] legacySettings; + + /** + * Create a new wired context. + * + * @param event the triggering event (required) + * @param triggerItem the wired trigger item (may be null for programmatic triggers) + * @param services the services for performing side effects + * @param state the execution state for loop safety + */ + public WiredContext(WiredEvent event, HabboItem triggerItem, WiredServices services, WiredState state) { + this(event, triggerItem, null, services, state, null); + } + + /** + * Create a new wired context with legacy settings. + * + * @param event the triggering event (required) + * @param triggerItem the wired trigger item (may be null) + * @param services the services for performing side effects + * @param state the execution state + * @param legacySettings extra settings array for legacy adapter compatibility + */ + public WiredContext(WiredEvent event, HabboItem triggerItem, WiredServices services, WiredState state, Object[] legacySettings) { + this(event, triggerItem, null, services, state, legacySettings); + } + + /** + * Create a new wired context with stack and legacy settings. + * + * @param event the triggering event (required) + * @param triggerItem the wired trigger item (may be null) + * @param stack the wired stack being executed (may be null) + * @param services the services for performing side effects + * @param state the execution state + * @param legacySettings extra settings array for legacy adapter compatibility + */ + public WiredContext(WiredEvent event, HabboItem triggerItem, WiredStack stack, WiredServices services, WiredState state, Object[] legacySettings) { + if (event == null) throw new IllegalArgumentException("Event cannot be null"); + if (services == null) throw new IllegalArgumentException("Services cannot be null"); + if (state == null) throw new IllegalArgumentException("State cannot be null"); + + this.event = event; + this.triggerItem = triggerItem; + this.stack = stack; + this.services = services; + this.state = state; + this.legacySettings = legacySettings; + this.targets = new WiredTargets(); + + // Default targets: include actor and trigger item for backwards compatibility + event.getActor().ifPresent(targets::addUser); + if (triggerItem != null) { + targets.addItem(triggerItem); + } + } + + // ========== Event Access ========== + + /** + * Get the triggering event. + * @return the wired event + */ + public WiredEvent event() { + return event; + } + + /** + * Get the event type. + * @return the event type + */ + public WiredEvent.Type eventType() { + return event.getType(); + } + + // ========== Convenience Accessors ========== + + /** + * Get the room where this event occurred. + * @return the room (never null) + */ + public Room room() { + return event.getRoom(); + } + + /** + * Get the actor (user) that caused this event. + * @return optional containing the actor + */ + public Optional actor() { + return event.getActor(); + } + + /** + * Get the source item that was involved in this event. + * @return optional containing the source item + */ + public Optional sourceItem() { + return event.getSourceItem(); + } + + /** + * Get the tile where this event occurred. + * @return optional containing the tile + */ + public Optional tile() { + return event.getTile(); + } + + /** + * Get the text associated with this event. + * @return optional containing the text + */ + public Optional text() { + return event.getText(); + } + + // ========== Trigger Item ========== + + /** + * Get the wired trigger item that initiated this execution. + * This is different from {@link #sourceItem()} which is the item the user interacted with. + * @return the trigger item, or null if this is a programmatic trigger + */ + public HabboItem triggerItem() { + return triggerItem; + } + + /** + * Check if there is a trigger item. + * @return true if a trigger item is present + */ + public boolean hasTriggerItem() { + return triggerItem != null; + } + + // ========== Stack ========== + + /** + * Get the wired stack being executed. + * This is useful for conditions that need to access all effects in the stack + * (e.g., WiredConditionMovementValidation). + * @return the wired stack, or null if not available + */ + public WiredStack stack() { + return stack; + } + + /** + * Check if there is a stack available. + * @return true if a stack is present + */ + public boolean hasStack() { + return stack != null; + } + + // ========== Targets ========== + + /** + * Get the mutable targets collection. + * Effects should use this to determine which users/items to affect. + * Selectors (future feature) can modify this before effects run. + * @return the targets collection + */ + public WiredTargets targets() { + return targets; + } + + // ========== Services ========== + + /** + * Get the services for performing side effects. + * @return the wired services + */ + public WiredServices services() { + return services; + } + + // ========== State ========== + + /** + * Get the execution state. + * @return the wired state + */ + public WiredState state() { + return state; + } + + // ========== Legacy Support ========== + + /** + * Get the legacy settings array for adapter compatibility. + * This allows legacy effects/conditions to receive their expected parameters. + * @return the legacy settings, or empty array if not set + */ + public Object[] legacySettings() { + return legacySettings != null ? legacySettings : new Object[0]; + } + + /** + * Get the legacy stuff array from the event. + * @return the legacy stuff array + */ + public Object[] legacyStuff() { + return event.getLegacyStuff(); + } + + // ========== Utility Methods ========== + + /** + * Check if there is an actor for this context. + * @return true if an actor is present + */ + public boolean hasActor() { + return event.getActor().isPresent(); + } + + /** + * Check if there is a source item for this context. + * @return true if a source item is present + */ + public boolean hasSourceItem() { + return event.getSourceItem().isPresent(); + } + + /** + * Check if there is text for this context. + * @return true if text is present + */ + public boolean hasText() { + return event.getText().isPresent(); + } + + /** + * Log a debug message through services. + * @param message the message to log + */ + public void debug(String message) { + services.debug(room(), message); + } + + /** + * Log a debug message with format arguments. + * @param format the format string + * @param args the arguments + */ + public void debug(String format, Object... args) { + services.debug(room(), format, args); + } + + @Override + public String toString() { + return "WiredContext{" + + "event=" + event + + ", triggerItem=" + (triggerItem != null ? triggerItem.getId() : "null") + + ", targets=" + targets + + ", state=" + state + + '}'; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java new file mode 100644 index 00000000..350fae31 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEngine.java @@ -0,0 +1,736 @@ +package com.eu.habbo.habbohotel.wired.core; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.items.interactions.InteractionWiredCondition; +import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; +import com.eu.habbo.habbohotel.items.interactions.InteractionWiredExtra; +import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; +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.WiredConditionOperator; +import com.eu.habbo.habbohotel.wired.api.IWiredCondition; +import com.eu.habbo.habbohotel.wired.api.IWiredEffect; +import com.eu.habbo.habbohotel.wired.api.WiredStack; +import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer; +import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer; +import com.eu.habbo.plugin.events.furniture.wired.WiredStackExecutedEvent; +import com.eu.habbo.plugin.events.furniture.wired.WiredStackTriggeredEvent; +import gnu.trove.map.hash.THashMap; +import gnu.trove.set.hash.THashSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * The central engine for processing wired events. + *

+ * This is the single entry point for all wired execution in the new architecture. + * It receives {@link WiredEvent} objects, finds matching stacks via {@link WiredStackIndex}, + * evaluates conditions, and executes effects. + *

+ * + *

Execution Flow:

+ *
    + *
  1. Receive event via {@link #handleEvent(WiredEvent)}
  2. + *
  3. Find candidate stacks for the event type
  4. + *
  5. For each stack, check if trigger matches
  6. + *
  7. Evaluate all conditions (respecting AND/OR mode)
  8. + *
  9. Execute effects (respecting random/unseen modifiers)
  10. + *
  11. Handle delays for timed effects
  12. + *
+ * + *

Safety Features:

+ *
    + *
  • Step limits via {@link WiredState} prevent infinite loops
  • + *
  • Effect cooldowns prevent rapid re-triggering
  • + *
  • Exceptions are caught and logged, not propagated
  • + *
+ * + * @see WiredEvent + * @see WiredContext + * @see WiredStackIndex + */ +public final class WiredEngine { + + private static final Logger LOGGER = LoggerFactory.getLogger(WiredEngine.class); + + /** Maximum recursion depth to prevent infinite loops (e.g., collision + chase) */ + public static int MAX_RECURSION_DEPTH = 10; + + /** Maximum events of same type per room within rate limit window before banning */ + public static int MAX_EVENTS_PER_WINDOW = 100; + + /** Time window for counting rapid events (milliseconds) */ + public static long RATE_LIMIT_WINDOW_MS = 10000; + + /** Duration to ban wired execution in a room after abuse detected (milliseconds) */ + public static long WIRED_BAN_DURATION_MS = 600000; + + private final WiredServices services; + private final WiredStackIndex index; + private final int maxStepsPerStack; + + /** Track unseen effect indices per room+tile for round-robin selection */ + private final ConcurrentHashMap unseenIndices; + + /** Track recursion depth per room to prevent infinite loops */ + private final ConcurrentHashMap roomRecursionDepth; + + /** Track event timestamps per room+eventType for rate limiting: key = "roomId:eventType" */ + private final ConcurrentHashMap eventRateLimiters; + + /** Track rooms that are banned from wired execution: roomId -> ban expiry timestamp */ + private final ConcurrentHashMap bannedRooms; + + /** + * Create a new wired engine. + * + * @param services the services for performing side effects + * @param index the stack index for finding matching stacks + * @param maxStepsPerStack maximum steps per stack execution (loop protection) + */ + public WiredEngine(WiredServices services, WiredStackIndex index, int maxStepsPerStack) { + if (services == null) throw new IllegalArgumentException("Services cannot be null"); + if (index == null) throw new IllegalArgumentException("Index cannot be null"); + if (maxStepsPerStack <= 0) throw new IllegalArgumentException("Max steps must be positive"); + + this.services = services; + this.index = index; + this.maxStepsPerStack = maxStepsPerStack; + this.unseenIndices = new ConcurrentHashMap<>(); + this.roomRecursionDepth = new ConcurrentHashMap<>(); + this.eventRateLimiters = new ConcurrentHashMap<>(); + this.bannedRooms = new ConcurrentHashMap<>(); + } + + /** + * Handle a wired event by finding and executing matching stacks. + * + * @param event the event to handle + * @return true if any stack was triggered (useful for SAY_SOMETHING to suppress message) + */ + public boolean handleEvent(WiredEvent event) { + if (event == null) { + return false; + } + + Room room = event.getRoom(); + if (room == null || !room.isLoaded()) { + return false; + } + + int roomId = room.getId(); + + // Check if room is banned from wired execution + if (isRoomBanned(roomId)) { + return false; + } + + // Check rate limiting to prevent rapid-fire event spam (e.g., collision + chase loop) + if (isRateLimited(roomId, room, event.getType())) { + // Room has been banned, all events will be dropped + return false; + } + + // Check and increment recursion depth to prevent infinite loops + int currentDepth = roomRecursionDepth.getOrDefault(roomId, 0); + if (currentDepth >= MAX_RECURSION_DEPTH) { + LOGGER.warn("Wired recursion limit reached in room {} (depth: {}). " + + "Possible infinite loop detected (e.g., collision + chase). Aborting.", roomId, currentDepth); + debug(room, "RECURSION LIMIT REACHED - aborting to prevent crash"); + return false; + } + roomRecursionDepth.put(roomId, currentDepth + 1); + + try { + return handleEventInternal(event, room); + } finally { + // Decrement recursion depth + int newDepth = roomRecursionDepth.getOrDefault(roomId, 1) - 1; + if (newDepth <= 0) { + roomRecursionDepth.remove(roomId); + } else { + roomRecursionDepth.put(roomId, newDepth); + } + } + } + + /** + * Internal event handling after recursion check. + */ + private boolean handleEventInternal(WiredEvent event, Room room) { + + // Find candidate stacks for this event type + List stacks = index.getStacks(room, event.getType()); + if (stacks.isEmpty()) { + return false; + } + + debug(room, "Processing {} stacks for event type {}", stacks.size(), event.getType()); + + boolean anyTriggered = false; + long currentTime = System.currentTimeMillis(); + + for (WiredStack stack : stacks) { + try { + boolean triggered = processStack(stack, event, currentTime); + if (triggered) { + anyTriggered = true; + } + } catch (WiredLimitException limitEx) { + debug(room, "Stack execution stopped (limit): {}", limitEx.getMessage()); + } catch (Exception ex) { + LOGGER.error("Error processing wired stack in room {}: {}", room.getId(), ex.getMessage(), ex); + debug(room, "Stack error: {}", ex.getMessage()); + } + } + + return anyTriggered; + } + + /** + * Process a single wired stack. + */ + private boolean processStack(WiredStack stack, WiredEvent event, long currentTime) { + Room room = event.getRoom(); + + // Check if trigger matches + if (!stack.trigger().matches(stack.triggerItem(), event)) { + return false; + } + + // Check if trigger requires actor + if (stack.trigger().requiresActor() && !event.getActor().isPresent()) { + return false; + } + + // Create execution context with stack reference + WiredState state = new WiredState(maxStepsPerStack); + WiredContext ctx = new WiredContext(event, stack.triggerItem(), stack, services, state, null); + + // Initial step for trigger + state.step(); + + // Activate the trigger box animation + if (stack.triggerItem() instanceof InteractionWiredTrigger) { + InteractionWiredTrigger trigger = (InteractionWiredTrigger) stack.triggerItem(); + trigger.activateBox(room, event.getActor().orElse(null), currentTime); + } + + debug(room, "Trigger matched: {} at item {} (conditions: {}, effects: {})", + event.getType(), + stack.triggerItem() != null ? stack.triggerItem().getId() : "null", + stack.conditions().size(), + stack.effects().size()); + + // Activate extras (for their animation) + activateExtras(room, stack.triggerItem(), event.getActor().orElse(null), currentTime); + + // Evaluate conditions + if (stack.hasConditions()) { + debug(room, "Evaluating {} conditions...", stack.conditions().size()); + boolean conditionsPassed = evaluateConditions(stack, ctx); + debug(room, "Conditions result: {}", conditionsPassed ? "PASSED" : "FAILED"); + if (!conditionsPassed) { + debug(room, "Conditions failed, aborting stack"); + return false; + } + } else { + debug(room, "No conditions in stack, proceeding to effects"); + } + + // Fire plugin event (WiredStackTriggeredEvent) + if (!fireTriggeredEvent(stack, event)) { + debug(room, "Stack cancelled by plugin"); + return false; + } + + // Execute effects + if (stack.hasEffects()) { + executeEffects(stack, ctx, currentTime); + } + + // Fire executed event + fireExecutedEvent(stack, event); + + return true; + } + + /** + * Evaluate all conditions in a stack. + */ + private boolean evaluateConditions(WiredStack stack, WiredContext ctx) { + List conditions = stack.conditions(); + + if (stack.useOrMode()) { + // OR mode: at least one condition must pass + return evaluateOrMode(conditions, ctx); + } else { + // Standard mode: use individual operators + return evaluateStandardMode(conditions, ctx); + } + } + + /** + * Evaluate conditions in OR mode (any pass = success). + */ + private boolean evaluateOrMode(List conditions, WiredContext ctx) { + // Group by condition type (for legacy compatibility) + Map typeResults = new HashMap<>(); + + for (IWiredCondition condition : conditions) { + ctx.state().step(); + + String typeName = condition.getClass().getSimpleName(); + if (!typeResults.containsKey(typeName) && condition.evaluate(ctx)) { + typeResults.put(typeName, true); + } + } + + // At least one condition type must have passed + return !typeResults.isEmpty(); + } + + /** + * Evaluate conditions in standard mode using operators. + */ + private boolean evaluateStandardMode(List conditions, WiredContext ctx) { + Room room = ctx.room(); + + // First pass: collect all OR conditions that passed + Map orResults = new HashMap<>(); + for (IWiredCondition condition : conditions) { + if (condition.operator() == WiredConditionOperator.OR) { + ctx.state().step(); + String typeName = condition.getClass().getSimpleName(); + boolean result = condition.evaluate(ctx); + debug(room, " Condition (OR) {}: {}", typeName, result ? "PASS" : "FAIL"); + if (!orResults.containsKey(typeName) && result) { + orResults.put(typeName, true); + } + } + } + + // Second pass: verify all conditions + for (IWiredCondition condition : conditions) { + boolean passes; + String typeName = condition.getClass().getSimpleName(); + + if (condition.operator() == WiredConditionOperator.OR) { + // OR: passes if any of same type passed + passes = orResults.containsKey(typeName); + debug(room, " Condition (OR check) {}: {}", typeName, passes ? "PASS" : "FAIL"); + } else { + // AND: must evaluate and pass + ctx.state().step(); + passes = condition.evaluate(ctx); + debug(room, " Condition (AND) {}: {}", typeName, passes ? "PASS" : "FAIL"); + } + + if (!passes) { + return false; + } + } + + return true; + } + + /** + * Execute effects in a stack. + */ + private void executeEffects(WiredStack stack, WiredContext ctx, long currentTime) { + List effects = stack.effects(); + + if (effects.isEmpty()) { + return; + } + + // Determine which effects to execute + List toExecute; + + if (stack.useRandom()) { + // Random mode: pick one random effect + int randomIndex = new Random().nextInt(effects.size()); + toExecute = Collections.singletonList(effects.get(randomIndex)); + debug(ctx.room(), "Random mode: selected effect {}/{}", randomIndex + 1, effects.size()); + } else if (stack.useUnseen()) { + // Unseen mode: round-robin selection + int index = getNextUnseenIndex(stack, effects.size()); + toExecute = Collections.singletonList(effects.get(index)); + debug(ctx.room(), "Unseen mode: selected effect {}/{}", index + 1, effects.size()); + } else { + // Normal mode: execute all in random order + toExecute = new ArrayList<>(effects); + Collections.shuffle(toExecute); + } + + // Execute selected effects + for (IWiredEffect effect : toExecute) { + // Check if effect requires actor + if (effect.requiresActor() && !ctx.hasActor()) { + continue; + } + + // Handle delay + int delay = effect.getDelay(); + if (delay > 0) { + // Schedule delayed execution + scheduleDelayedEffect(effect, ctx, delay, currentTime); + } else { + // Execute immediately + ctx.state().step(); + try { + effect.execute(ctx); + + // Activate box animation after execution + if (effect instanceof InteractionWiredEffect) { + InteractionWiredEffect wiredEffect = (InteractionWiredEffect) effect; + wiredEffect.setCooldown(currentTime); + wiredEffect.activateBox(ctx.room(), ctx.actor().orElse(null), currentTime); + } + } catch (Exception e) { + LOGGER.warn("Error executing effect: {}", e.getMessage()); + } + } + } + } + + /** + * Schedule a delayed effect execution. + */ + private void scheduleDelayedEffect(IWiredEffect effect, WiredContext ctx, int delay, long currentTime) { + // Delay is in 500ms ticks + long delayMs = delay * 500L; + Room room = ctx.room(); + RoomUnit actor = ctx.actor().orElse(null); + + Emulator.getThreading().run(() -> { + if (!room.isLoaded() || room.getHabbos().isEmpty()) { + return; + } + + try { + effect.execute(ctx); + + // Activate box animation after execution + if (effect instanceof InteractionWiredEffect) { + InteractionWiredEffect wiredEffect = (InteractionWiredEffect) effect; + wiredEffect.setCooldown(System.currentTimeMillis()); + wiredEffect.activateBox(room, actor, System.currentTimeMillis()); + } + } catch (Exception e) { + LOGGER.warn("Error executing delayed effect: {}", e.getMessage()); + } + }, delayMs); + } + + /** + * Get the next unseen index for round-robin selection. + */ + private int getNextUnseenIndex(WiredStack stack, int effectCount) { + String key = stack.triggerItem() != null + ? String.valueOf(stack.triggerItem().getId()) + : "default"; + + int current = unseenIndices.getOrDefault(key, -1); + int next = (current + 1) % effectCount; + unseenIndices.put(key, next); + + return next; + } + + /** + * Fire the WiredStackTriggeredEvent for plugin compatibility. + */ + private boolean fireTriggeredEvent(WiredStack stack, WiredEvent event) { + // Build legacy collections for event + if (stack.triggerItem() instanceof InteractionWiredTrigger) { + // This event is checked for cancellation + THashSet legacyEffects = new THashSet<>(); + THashSet legacyConditions = new THashSet<>(); + + // Extract effects (all effects should now implement both interfaces) + for (IWiredEffect eff : stack.effects()) { + if (eff instanceof InteractionWiredEffect) { + legacyEffects.add((InteractionWiredEffect) eff); + } + } + for (IWiredCondition cond : stack.conditions()) { + if (cond instanceof InteractionWiredCondition) { + legacyConditions.add((InteractionWiredCondition) cond); + } + } + + WiredStackTriggeredEvent triggeredEvent = new WiredStackTriggeredEvent( + event.getRoom(), + event.getActor().orElse(null), + (InteractionWiredTrigger) stack.triggerItem(), + legacyEffects, + legacyConditions + ); + + return !Emulator.getPluginManager().fireEvent(triggeredEvent).isCancelled(); + } + return true; + } + + /** + * Fire the WiredStackExecutedEvent for plugin compatibility. + */ + private void fireExecutedEvent(WiredStack stack, WiredEvent event) { + if (stack.triggerItem() instanceof InteractionWiredTrigger) { + THashSet legacyEffects = new THashSet<>(); + THashSet legacyConditions = new THashSet<>(); + + for (IWiredEffect eff : stack.effects()) { + if (eff instanceof InteractionWiredEffect) { + legacyEffects.add((InteractionWiredEffect) eff); + } + } + for (IWiredCondition cond : stack.conditions()) { + if (cond instanceof InteractionWiredCondition) { + legacyConditions.add((InteractionWiredCondition) cond); + } + } + + Emulator.getPluginManager().fireEvent(new WiredStackExecutedEvent( + event.getRoom(), + event.getActor().orElse(null), + (InteractionWiredTrigger) stack.triggerItem(), + legacyEffects, + legacyConditions + )); + } + } + + /** + * Log a debug message if debug mode is enabled. + */ + private void debug(Room room, String format, Object... args) { + if (WiredManager.isDebugEnabled()) { + String message = String.format(format.replace("{}", "%s"), args); + LOGGER.info("[WiredEngine][Room {}] {}", room.getId(), message); + } + } + + /** + * Activate all extras at the trigger item's location for their animation. + */ + private void activateExtras(Room room, HabboItem triggerItem, RoomUnit roomUnit, long millis) { + if (triggerItem == null || room.getRoomSpecialTypes() == null) { + return; + } + + THashSet extras = room.getRoomSpecialTypes().getExtras( + triggerItem.getX(), triggerItem.getY()); + + if (extras != null) { + for (InteractionWiredExtra extra : extras) { + extra.activateBox(room, roomUnit, millis); + } + } + } + + /** + * Get the services used by this engine. + * @return the wired services + */ + public WiredServices getServices() { + return services; + } + + /** + * Get the stack index used by this engine. + * @return the stack index + */ + public WiredStackIndex getIndex() { + return index; + } + + /** + * Get the maximum steps per stack. + * @return max steps + */ + public int getMaxStepsPerStack() { + return maxStepsPerStack; + } + + /** + * Clear all cached unseen indices. + */ + public void clearUnseenCache() { + unseenIndices.clear(); + } + + /** + * Clear recursion tracking for a specific room. + * Should be called when a room is unloaded. + * @param roomId the room ID + */ + public void clearRoomRecursionDepth(int roomId) { + roomRecursionDepth.remove(roomId); + } + + /** + * Clear all recursion tracking. + */ + public void clearAllRecursionDepth() { + roomRecursionDepth.clear(); + } + + /** + * Get the current recursion depth for a room (for debugging). + * @param roomId the room ID + * @return the current recursion depth, or 0 if not tracked + */ + public int getRecursionDepth(int roomId) { + return roomRecursionDepth.getOrDefault(roomId, 0); + } + + /** + * Clear rate limiters for a specific room. + * Should be called when a room is unloaded. + * @param roomId the room ID + */ + public void clearRoomRateLimiters(int roomId) { + String prefix = roomId + ":"; + eventRateLimiters.keySet().removeIf(key -> key.startsWith(prefix)); + } + + /** + * Clear room ban for a specific room. + * Should be called when a room is unloaded. + * @param roomId the room ID + */ + public void clearRoomBan(int roomId) { + bannedRooms.remove(roomId); + } + + /** + * Check if a room is currently banned from wired execution. + * @param roomId the room ID + * @return true if wired is banned in this room + */ + private boolean isRoomBanned(int roomId) { + Long banExpiry = bannedRooms.get(roomId); + if (banExpiry == null) { + return false; + } + + if (System.currentTimeMillis() >= banExpiry) { + // Ban expired, remove it + bannedRooms.remove(roomId); + return false; + } + + return true; + } + + /** + * Ban wired execution in a room for WIRED_BAN_DURATION_MS. + * Sends alerts to all users in the room and a scripter alert to staff. + * @param roomId the room ID + * @param room the room object (for sending alerts) + */ + private void banRoom(int roomId, Room room) { + long banExpiry = System.currentTimeMillis() + WIRED_BAN_DURATION_MS; + bannedRooms.put(roomId, banExpiry); + + long banMinutes = WIRED_BAN_DURATION_MS / 60000; + + // Send alert to all users in the room + String roomAlertMessage = Emulator.getTexts().getValue("wired.abuse.room.alert") + .replace("%minutes%", String.valueOf(banMinutes)); + room.sendComposer(new GenericAlertComposer(roomAlertMessage).compose()); + + // Send scripter bubble alert to staff with room link + THashMap keys = new THashMap<>(); + keys.put("title", Emulator.getTexts().getValue("wired.abuse.staff.title")); + keys.put("message", Emulator.getTexts().getValue("wired.abuse.staff.message") + .replace("%roomname%", room.getName()) + .replace("%owner%", room.getOwnerName()) + .replace("%minutes%", String.valueOf(banMinutes))); + keys.put("linkUrl", "event:navigator/goto/" + roomId); + keys.put("linkTitle", Emulator.getTexts().getValue("wired.abuse.staff.link")); + Emulator.getGameEnvironment().getHabboManager().sendPacketToHabbosWithPermission( + new BubbleAlertComposer("admin.staffalert", keys).compose(), + "acc_modtool_room_info" + ); + + LOGGER.warn("Wired abuse detected in room {} ({}). Owner: {}. Wired banned for {} minutes.", + roomId, room.getName(), room.getOwnerName(), banMinutes); + } + + /** + * Check if an event should be rate-limited. + * If rate limit exceeded, bans the room and sends alerts. + * @param roomId the room ID + * @param room the room object (for sending alerts if banned) + * @param eventType the event type + * @return true if the event should be blocked due to rate limiting + */ + private boolean isRateLimited(int roomId, Room room, WiredEvent.Type eventType) { + String key = roomId + ":" + eventType.name(); + long now = System.currentTimeMillis(); + + EventRateTracker tracker = eventRateLimiters.compute(key, (k, existing) -> { + if (existing == null) { + return new EventRateTracker(now); + } + existing.recordEvent(now); + return existing; + }); + + boolean limited = tracker.isRateLimited(now); + if (limited && tracker.shouldBan(now)) { + // First time hitting limit in this suppression window - ban the room + banRoom(roomId, room); + } + return limited; + } + + /** + * Tracks event rate for a specific room + event type combination. + */ + private static final class EventRateTracker { + private long windowStart; + private int eventCount; + private boolean banned; + + EventRateTracker(long now) { + this.windowStart = now; + this.eventCount = 1; + this.banned = false; + } + + synchronized void recordEvent(long now) { + // Reset window if expired + if (now - windowStart > RATE_LIMIT_WINDOW_MS) { + windowStart = now; + eventCount = 1; + // Don't reset banned here - room ban is checked separately + } else { + eventCount++; + } + } + + synchronized boolean isRateLimited(long now) { + return eventCount > MAX_EVENTS_PER_WINDOW; + } + + /** + * Check if this is the first time we've hit the limit (to trigger ban). + * Returns true only once per suppression window. + */ + synchronized boolean shouldBan(long now) { + if (eventCount > MAX_EVENTS_PER_WINDOW && !banned) { + banned = true; + return true; + } + return false; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEvent.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEvent.java new file mode 100644 index 00000000..56a92cda --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredEvent.java @@ -0,0 +1,337 @@ +package com.eu.habbo.habbohotel.wired.core; + +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomTile; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.WiredTriggerType; + +import java.util.Optional; + +/** + * Immutable event representing what happened in the room that triggered wired execution. + *

+ * This replaces the scattered {@code Object[] stuff} parameter pattern with a strongly-typed, + * immutable event object. Triggers produce/receive this event. + *

+ * + *

Usage Example:

+ *
{@code
+ * WiredEvent event = WiredEvent.builder(WiredEvent.Type.USER_WALKS_ON, room)
+ *     .actor(roomUnit)
+ *     .sourceItem(steppedItem)
+ *     .tile(roomUnit.getCurrentLocation())
+ *     .build();
+ * }
+ * + * @see WiredContext + * @see WiredEngine + */ +public final class WiredEvent { + + /** + * Types of wired events that can occur in a room. + * Maps to the legacy {@link WiredTriggerType} but with cleaner naming. + */ + public enum Type { + /** User says something in chat */ + USER_SAYS(WiredTriggerType.SAY_SOMETHING), + + /** User walks onto furniture */ + USER_WALKS_ON(WiredTriggerType.WALKS_ON_FURNI), + + /** User walks off furniture */ + USER_WALKS_OFF(WiredTriggerType.WALKS_OFF_FURNI), + + /** Furniture state is toggled/changed */ + FURNI_STATE_CHANGED(WiredTriggerType.STATE_CHANGED), + + /** Timer fires at a given time */ + TIMER_TICK(WiredTriggerType.AT_GIVEN_TIME), + + /** Timer fires periodically/repeatedly */ + TIMER_REPEAT(WiredTriggerType.PERIODICALLY), + + /** Long timer repeat */ + TIMER_REPEAT_LONG(WiredTriggerType.PERIODICALLY_LONG), + + /** User enters the room */ + USER_ENTERS_ROOM(WiredTriggerType.ENTER_ROOM), + + /** Game starts */ + GAME_STARTS(WiredTriggerType.GAME_STARTS), + + /** Game ends */ + GAME_ENDS(WiredTriggerType.GAME_ENDS), + + /** Bot collision */ + BOT_COLLISION(WiredTriggerType.COLLISION), + + /** Bot reached furniture (STF = stack tile furni) */ + BOT_REACHED_FURNI(WiredTriggerType.BOT_REACHED_STF), + + /** Bot reached habbo (AVTR = avatar) */ + BOT_REACHED_HABBO(WiredTriggerType.BOT_REACHED_AVTR), + + /** Score threshold achieved */ + SCORE_ACHIEVED(WiredTriggerType.SCORE_ACHIEVED), + + /** User starts idling */ + USER_IDLES(WiredTriggerType.IDLES), + + /** User stops idling */ + USER_UNIDLES(WiredTriggerType.UNIDLES), + + /** User starts dancing */ + USER_STARTS_DANCING(WiredTriggerType.STARTS_DANCING), + + /** User stops dancing */ + USER_STOPS_DANCING(WiredTriggerType.STOPS_DANCING), + + /** Team wins a game */ + TEAM_WINS(WiredTriggerType.CUSTOM), + + /** Team loses a game */ + TEAM_LOSES(WiredTriggerType.CUSTOM), + + /** Custom trigger type for plugins */ + CUSTOM(WiredTriggerType.CUSTOM); + + private final WiredTriggerType legacyType; + + Type(WiredTriggerType legacyType) { + this.legacyType = legacyType; + } + + /** + * Get the legacy trigger type for backwards compatibility. + * @return the corresponding {@link WiredTriggerType} + */ + public WiredTriggerType toLegacyType() { + return legacyType; + } + + /** + * Convert from legacy trigger type to new event type. + * @param legacyType the legacy trigger type + * @return the corresponding event type + */ + public static Type fromLegacyType(WiredTriggerType legacyType) { + for (Type type : values()) { + if (type.legacyType == legacyType) { + return type; + } + } + return CUSTOM; + } + } + + private final Type type; + private final Room room; + private final RoomUnit actor; // nullable + private final HabboItem sourceItem; // nullable + private final RoomTile tile; // nullable + private final String text; // nullable + private final Object[] legacyStuff; // for adapter compatibility + private final long createdAtMs; + + private WiredEvent(Builder builder) { + this.type = builder.type; + this.room = builder.room; + this.actor = builder.actor; + this.sourceItem = builder.sourceItem; + this.tile = builder.tile; + this.text = builder.text; + this.legacyStuff = builder.legacyStuff; + this.createdAtMs = builder.createdAtMs; + } + + // Getters + + /** + * Get the type of this event. + * @return the event type + */ + public Type getType() { + return type; + } + + /** + * Get the room where this event occurred. + * @return the room (never null) + */ + public Room getRoom() { + return room; + } + + /** + * Get the actor (user) that caused this event. + * @return optional containing the actor, or empty if no user involved + */ + public Optional getActor() { + return Optional.ofNullable(actor); + } + + /** + * Get the source item that was involved in this event. + * For example, the furniture that was clicked or stepped on. + * @return optional containing the source item, or empty if no item involved + */ + public Optional getSourceItem() { + return Optional.ofNullable(sourceItem); + } + + /** + * Get the tile where this event occurred. + * @return optional containing the tile, or empty if no specific tile + */ + public Optional getTile() { + return Optional.ofNullable(tile); + } + + /** + * Get the text associated with this event. + * For example, the message in a SAY_SOMETHING trigger. + * @return optional containing the text, or empty if no text + */ + public Optional getText() { + return Optional.ofNullable(text); + } + + /** + * Get the legacy stuff array for adapter compatibility. + * This allows legacy triggers/conditions/effects to work with the new system. + * @return the legacy stuff array, or empty array if not set + */ + public Object[] getLegacyStuff() { + return legacyStuff != null ? legacyStuff : new Object[0]; + } + + /** + * Get the timestamp when this event was created. + * @return milliseconds since epoch + */ + public long getCreatedAtMs() { + return createdAtMs; + } + + /** + * Create a new builder for constructing events. + * @param type the event type (required) + * @param room the room where event occurred (required) + * @return a new builder instance + */ + public static Builder builder(Type type, Room room) { + return new Builder(type, room); + } + + /** + * Create a new builder from a legacy trigger type. + * @param legacyType the legacy trigger type + * @param room the room where event occurred + * @return a new builder instance + */ + public static Builder fromLegacy(WiredTriggerType legacyType, Room room) { + return new Builder(Type.fromLegacyType(legacyType), room); + } + + @Override + public String toString() { + return "WiredEvent{" + + "type=" + type + + ", room=" + (room != null ? room.getId() : "null") + + ", actor=" + (actor != null ? actor.getId() : "null") + + ", sourceItem=" + (sourceItem != null ? sourceItem.getId() : "null") + + ", createdAtMs=" + createdAtMs + + '}'; + } + + /** + * Builder for constructing immutable WiredEvent instances. + */ + public static final class Builder { + private final Type type; + private final Room room; + private RoomUnit actor; + private HabboItem sourceItem; + private RoomTile tile; + private String text; + private Object[] legacyStuff; + private long createdAtMs = System.currentTimeMillis(); + + private Builder(Type type, Room room) { + if (type == null) throw new IllegalArgumentException("Event type cannot be null"); + if (room == null) throw new IllegalArgumentException("Room cannot be null"); + this.type = type; + this.room = room; + } + + /** + * Set the actor (user) that caused this event. + * @param actor the room unit + * @return this builder + */ + public Builder actor(RoomUnit actor) { + this.actor = actor; + return this; + } + + /** + * Set the source item involved in this event. + * @param sourceItem the habbo item + * @return this builder + */ + public Builder sourceItem(HabboItem sourceItem) { + this.sourceItem = sourceItem; + return this; + } + + /** + * Set the tile where this event occurred. + * @param tile the room tile + * @return this builder + */ + public Builder tile(RoomTile tile) { + this.tile = tile; + return this; + } + + /** + * Set the text associated with this event. + * @param text the text content + * @return this builder + */ + public Builder text(String text) { + this.text = text; + return this; + } + + /** + * Set the legacy stuff array for adapter compatibility. + * @param stuff the legacy object array + * @return this builder + */ + public Builder legacyStuff(Object[] stuff) { + this.legacyStuff = stuff; + return this; + } + + /** + * Set a custom creation timestamp. + * @param createdAtMs milliseconds since epoch + * @return this builder + */ + public Builder createdAtMs(long createdAtMs) { + this.createdAtMs = createdAtMs; + return this; + } + + /** + * Build the immutable WiredEvent. + * @return the constructed event + */ + public WiredEvent build() { + return new WiredEvent(this); + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredLimitException.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredLimitException.java new file mode 100644 index 00000000..624d8ef4 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredLimitException.java @@ -0,0 +1,41 @@ +package com.eu.habbo.habbohotel.wired.core; + +/** + * Exception thrown when wired execution exceeds configured limits. + *

+ * This is a safety mechanism to prevent infinite loops or excessive + * resource consumption in wired stacks. When thrown, the current + * stack execution is aborted and an error is logged. + *

+ * + *

Common Causes:

+ *
    + *
  • Infinite loops via "Trigger Stacks" effect
  • + *
  • Excessively complex wired setups
  • + *
  • Malicious room configurations
  • + *
+ * + * @see WiredState#step() + * @see WiredEngine + */ +public final class WiredLimitException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * Create a new limit exception with a message. + * @param message description of the limit violation + */ + public WiredLimitException(String message) { + super(message); + } + + /** + * Create a new limit exception with a message and cause. + * @param message description of the limit violation + * @param cause the underlying cause + */ + public WiredLimitException(String message, Throwable cause) { + super(message, cause); + } +} 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 new file mode 100644 index 00000000..b73680a7 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredManager.java @@ -0,0 +1,860 @@ +package com.eu.habbo.habbohotel.wired.core; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.catalog.CatalogItem; +import com.eu.habbo.habbohotel.items.Item; +import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; +import com.eu.habbo.habbohotel.items.interactions.wired.effects.WiredEffectGiveReward; +import com.eu.habbo.habbohotel.items.interactions.wired.effects.WiredEffectTriggerStacks; +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomTile; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.habbohotel.users.HabboBadge; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.WiredGiveRewardItem; +import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.migrate.WiredEvents; +import com.eu.habbo.habbohotel.wired.tick.WiredTickService; +import com.eu.habbo.habbohotel.wired.tick.WiredTickable; +import com.eu.habbo.messages.outgoing.catalog.PurchaseOKComposer; +import com.eu.habbo.messages.outgoing.inventory.AddHabboItemComposer; +import com.eu.habbo.messages.outgoing.inventory.InventoryRefreshComposer; +import com.eu.habbo.messages.outgoing.users.AddUserBadgeComposer; +import com.eu.habbo.messages.outgoing.wired.WiredRewardAlertComposer; +import com.eu.habbo.plugin.EventHandler; +import com.eu.habbo.plugin.events.emulator.EmulatorLoadedEvent; +import com.eu.habbo.plugin.events.users.UserWiredRewardReceived; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import gnu.trove.set.hash.THashSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * Manager class for the new wired engine system. + *

+ * This class serves as the integration point between the emulator and the new + * wired engine. It provides static methods for triggering events and manages + * the lifecycle of the engine. + *

+ * + *

Configuration Options:

+ *
    + *
  • {@code wired.engine.enabled} - Enable new engine (parallel mode)
  • + *
  • {@code wired.engine.exclusive} - Disable legacy handler when true
  • + *
  • {@code wired.engine.maxStepsPerStack} - Loop protection limit
  • + *
  • {@code wired.engine.debug} - Verbose logging
  • + *
+ * + *

Migration Strategy:

+ *
    + *
  1. Set {@code wired.engine.enabled=true} to run both engines in parallel
  2. + *
  3. Test thoroughly to ensure identical behavior
  4. + *
  5. Set {@code wired.engine.exclusive=true} to disable legacy engine
  6. + *
  7. Full migration complete - WiredManager is now the only wired engine
  8. + *
+ * + * @see WiredEngine + * @see WiredEvents + */ +public final class WiredManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(WiredManager.class); + + // Configuration keys + public static final String CONFIG_ENABLED = "wired.engine.enabled"; + public static final String CONFIG_EXCLUSIVE = "wired.engine.exclusive"; + public static final String CONFIG_MAX_STEPS = "wired.engine.maxStepsPerStack"; + public static final String CONFIG_DEBUG = "wired.engine.debug"; + + // Defaults + private static final boolean DEFAULT_ENABLED = false; + private static final boolean DEFAULT_EXCLUSIVE = false; + private static final int DEFAULT_MAX_STEPS = 100; + + /** The singleton engine instance */ + private static volatile WiredEngine engine; + + /** The stack index */ + private static volatile RoomWiredStackIndex stackIndex; + + /** Whether the engine is initialized */ + private static volatile boolean initialized = false; + + private WiredManager() { + // Static utility class + } + /** + * Event handler called when the emulator is loaded. + * Initializes the wired manager. + */ + @EventHandler + public static void onEmulatorLoaded(EmulatorLoadedEvent event) { + initialize(); + } + + /** + * Initialize the wired manager and engine. + * Called during emulator startup. + */ + public static synchronized void initialize() { + if (initialized) { + return; + } + + LOGGER.info("Initializing Wired Manager..."); + + // Load configuration + boolean enabled = Emulator.getConfig().getBoolean(CONFIG_ENABLED, DEFAULT_ENABLED); + int maxSteps = Emulator.getConfig().getInt(CONFIG_MAX_STEPS, DEFAULT_MAX_STEPS); + boolean debug = Emulator.getConfig().getBoolean(CONFIG_DEBUG, false); + + // Load additional configuration + MAXIMUM_FURNI_SELECTION = Emulator.getConfig().getInt("hotel.wired.furni.selection.count", 5); + TELEPORT_DELAY = Emulator.getConfig().getInt("wired.effect.teleport.delay", 500); + + // Set debug mode + if (debug) { + setDebugEnabled(true); + } + + // Create components + stackIndex = new RoomWiredStackIndex(); + WiredServices services = DefaultWiredServices.getInstance(); + engine = new WiredEngine(services, stackIndex, maxSteps); + + // Start the centralized tick service (50ms interval) + WiredTickService.getInstance().start(); + + initialized = true; + + LOGGER.info("Wired Manager initialized - enabled: {}, maxSteps: {}, debug: {}", + enabled, maxSteps, debug); + } + + /** + * Shutdown the wired manager. + * Called during emulator shutdown. + */ + public static synchronized void shutdown() { + if (!initialized) { + return; + } + + LOGGER.info("Shutting down Wired Manager..."); + + // Stop the tick service first + WiredTickService.getInstance().stop(); + + if (stackIndex != null) { + stackIndex.clearAll(); + } + + if (engine != null) { + engine.clearUnseenCache(); + } + + initialized = false; + LOGGER.info("Wired Manager shutdown complete"); + } + + /** + * Check if the new wired engine is enabled. + * @return true if enabled + */ + public static boolean isEnabled() { + return Emulator.getConfig().getBoolean(CONFIG_ENABLED, DEFAULT_ENABLED); + } + + /** + * Check if the new engine is exclusive (legacy disabled). + * @return true if exclusive mode + */ + public static boolean isExclusive() { + return Emulator.getConfig().getBoolean(CONFIG_EXCLUSIVE, DEFAULT_EXCLUSIVE); + } + + /** + * Get the wired engine instance. + * @return the engine, or null if not initialized + */ + public static WiredEngine getEngine() { + return engine; + } + + /** + * Get the stack index instance. + * @return the stack index, or null if not initialized + */ + public static RoomWiredStackIndex getStackIndex() { + return stackIndex; + } + + // ========== Event Triggering Methods ========== + + /** + * Handle a wired event using the new engine. + * @param event the event to handle + * @return true if any stack was triggered + */ + public static boolean handleEvent(WiredEvent event) { + if (!isEnabled() || engine == null) { + return false; + } + + return engine.handleEvent(event); + } + + /** + * Trigger when a user walks onto furniture. + */ + public static boolean triggerUserWalksOn(Room room, RoomUnit user, HabboItem item) { + if (!isEnabled() || room == null || user == null || item == null) { + return false; + } + + WiredEvent event = WiredEvents.userWalksOn(room, user, item); + return handleEvent(event); + } + + /** + * Trigger when a user walks off furniture. + */ + public static boolean triggerUserWalksOff(Room room, RoomUnit user, HabboItem item) { + if (!isEnabled() || room == null || user == null || item == null) { + return false; + } + + WiredEvent event = WiredEvents.userWalksOff(room, user, item); + return handleEvent(event); + } + + /** + * Trigger when a user says something. + */ + public static boolean triggerUserSays(Room room, RoomUnit user, String message) { + if (!isEnabled() || room == null || user == null) { + return false; + } + + WiredEvent event = WiredEvents.userSays(room, user, message); + return handleEvent(event); + } + + /** + * Trigger when a user enters the room. + */ + public static boolean triggerUserEntersRoom(Room room, RoomUnit user) { + if (!isEnabled() || room == null || user == null) { + return false; + } + + WiredEvent event = WiredEvents.userEntersRoom(room, user); + return handleEvent(event); + } + + /** + * Trigger when furniture state changes. + */ + public static boolean triggerFurniStateChanged(Room room, RoomUnit user, HabboItem item) { + if (!isEnabled() || room == null || item == null) { + return false; + } + + WiredEvent event = WiredEvents.furniStateChanged(room, user, item); + return handleEvent(event); + } + + /** + * Trigger a timer tick. + */ + public static boolean triggerTimerTick(Room room, HabboItem timerItem) { + if (!isEnabled() || room == null) { + return false; + } + + WiredEvent event = WiredEvents.timerTick(room, timerItem); + return handleEvent(event); + } + + /** + * Trigger a periodic timer. + */ + public static boolean triggerTimerRepeat(Room room, HabboItem timerItem) { + if (!isEnabled() || room == null) { + return false; + } + + WiredEvent event = WiredEvents.timerRepeat(room, timerItem); + return handleEvent(event); + } + + /** + * Trigger game start. + */ + public static boolean triggerGameStarts(Room room) { + if (!isEnabled() || room == null) { + return false; + } + + WiredEvent event = WiredEvents.gameStarts(room); + return handleEvent(event); + } + + /** + * Trigger game end. + */ + public static boolean triggerGameEnds(Room room) { + if (!isEnabled() || room == null) { + return false; + } + + WiredEvent event = WiredEvents.gameEnds(room); + return handleEvent(event); + } + + /** + * Trigger bot collision. + */ + public static boolean triggerBotCollision(Room room, RoomUnit botUnit) { + if (!isEnabled() || room == null || botUnit == null) { + return false; + } + + WiredEvent event = WiredEvents.botCollision(room, botUnit); + return handleEvent(event); + } + + /** + * Trigger when bot reaches furniture. + */ + public static boolean triggerBotReachedFurni(Room room, RoomUnit botUnit, HabboItem item) { + if (!isEnabled() || room == null || botUnit == null) { + return false; + } + + WiredEvent event = WiredEvents.botReachedFurni(room, botUnit, item); + return handleEvent(event); + } + + /** + * Trigger when bot reaches a habbo. + */ + public static boolean triggerBotReachedHabbo(Room room, RoomUnit botUnit, RoomUnit targetUser) { + if (!isEnabled() || room == null || botUnit == null) { + return false; + } + + WiredEvent event = WiredEvents.botReachedHabbo(room, botUnit, targetUser); + return handleEvent(event); + } + + /** + * Trigger when score is achieved. + */ + public static boolean triggerScoreAchieved(Room room, RoomUnit user, int score) { + if (!isEnabled() || room == null || user == null) { + return false; + } + + WiredEvent event = WiredEvents.scoreAchieved(room, user, score); + return handleEvent(event); + } + + /** + * Trigger when user starts idling. + */ + public static boolean triggerUserIdles(Room room, RoomUnit user) { + if (!isEnabled() || room == null || user == null) { + return false; + } + + WiredEvent event = WiredEvents.userIdles(room, user); + return handleEvent(event); + } + + /** + * Trigger when user stops idling. + */ + public static boolean triggerUserUnidles(Room room, RoomUnit user) { + if (!isEnabled() || room == null || user == null) { + return false; + } + + WiredEvent event = WiredEvents.userUnidles(room, user); + return handleEvent(event); + } + + /** + * Trigger when user starts dancing. + */ + public static boolean triggerUserStartsDancing(Room room, RoomUnit user) { + if (!isEnabled() || room == null || user == null) { + return false; + } + + WiredEvent event = WiredEvents.userStartsDancing(room, user); + return handleEvent(event); + } + + /** + * Trigger when user stops dancing. + */ + public static boolean triggerUserStopsDancing(Room room, RoomUnit user) { + if (!isEnabled() || room == null || user == null) { + return false; + } + + WiredEvent event = WiredEvents.userStopsDancing(room, user); + return handleEvent(event); + } + + /** + * Trigger when a team wins a game. + */ + public static boolean triggerTeamWins(Room room, RoomUnit user) { + if (!isEnabled() || room == null) { + return false; + } + + WiredEvent event = WiredEvents.teamWins(room, user); + return handleEvent(event); + } + + /** + * Trigger when a team loses a game. + */ + public static boolean triggerTeamLoses(Room room, RoomUnit user) { + if (!isEnabled() || room == null) { + return false; + } + + WiredEvent event = WiredEvents.teamLoses(room, user); + return handleEvent(event); + } + + /** + * Trigger from legacy system for parallel running. + * This allows the new engine to run alongside the old one during migration. + */ + public static boolean triggerFromLegacy(WiredTriggerType triggerType, RoomUnit roomUnit, Room room, Object[] stuff) { + if (!isEnabled() || room == null) { + return false; + } + + WiredEvent event = WiredEvents.fromLegacy(triggerType, room, roomUnit, stuff); + return handleEvent(event); + } + + // ========== Index Management ========== + + /** + * Invalidate the wired index for a room. + * Call this when wired items are added/removed/moved. + */ + public static void invalidateRoom(Room room) { + if (stackIndex != null && room != null) { + stackIndex.invalidateAll(room); + if (debugEnabled) { + LOGGER.info("[Wired] Cache invalidated for room {}", room.getId()); + } + } + } + + /** + * Invalidate the wired index for a specific tile. + */ + public static void invalidateTile(Room room, RoomTile tile) { + if (stackIndex != null && room != null && tile != null) { + stackIndex.invalidate(room, tile); + } + } + + /** + * Rebuild the wired index for a room. + */ + public static void rebuildRoom(Room room) { + if (stackIndex != null && room != null) { + stackIndex.rebuild(room); + } + } + + // ========== Configuration Constants (moved from WiredHandler) ========== + + /** Maximum number of furniture items that can be selected in a single wired component */ + public static int MAXIMUM_FURNI_SELECTION = 5; + + /** Delay in milliseconds between teleport executions */ + public static int TELEPORT_DELAY = 500; + + // ========== Debug Mode ========== + + /** Debug mode - when enabled, logs detailed wired execution flow */ + private static boolean debugEnabled = false; + + /** + * Enables or disables wired debug mode. + * When enabled, detailed execution logs are written to help troubleshoot wired stacks. + * + * @param enabled true to enable debug logging, false to disable + */ + public static void setDebugEnabled(boolean enabled) { + debugEnabled = enabled; + if (enabled) { + LOGGER.info("Wired debug mode ENABLED"); + } + } + + /** + * Checks if wired debug mode is enabled. + * + * @return true if debug mode is active + */ + public static boolean isDebugEnabled() { + return debugEnabled; + } + + /** + * Logs a debug message if debug mode is enabled. + * + * @param message the message to log + * @param args optional format arguments + */ + public static void debug(String message, Object... args) { + if (debugEnabled) { + LOGGER.info("[WIRED DEBUG] " + message, args); + } + } + + // ========== JSON Utilities ========== + + private static GsonBuilder gsonBuilder = null; + private static Gson cachedGson = null; + + public static GsonBuilder getGsonBuilder() { + if (gsonBuilder == null) { + gsonBuilder = new GsonBuilder(); + } + return gsonBuilder; + } + + /** + * Gets a cached Gson instance. This is more efficient than calling + * getGsonBuilder().create() multiple times, as Gson instances are thread-safe + * and can be reused. + * + * @return a cached Gson instance + */ + public static Gson getGson() { + if (cachedGson == null) { + cachedGson = getGsonBuilder().create(); + } + return cachedGson; + } + + // ========== Tick Service Integration ========== + + /** + * Registers a tickable wired item with the centralized tick service. + *

+ * Call this when a time-based wired trigger is placed in a room or when + * a room is loaded. + *

+ * + * @param room the room the item is in + * @param tickable the tickable item (e.g., WiredTriggerRepeater) + */ + public static void registerTickable(Room room, WiredTickable tickable) { + WiredTickService.getInstance().register(room, tickable); + } + + /** + * Unregisters a tickable wired item from the tick service. + *

+ * Call this when a time-based wired trigger is picked up or when + * a room is unloaded. + *

+ * + * @param room the room the item was in + * @param tickable the tickable item + */ + public static void unregisterTickable(Room room, WiredTickable tickable) { + WiredTickService.getInstance().unregister(room, tickable); + } + + /** + * Unregisters all tickables for a room. + *

+ * Call this when a room is unloaded to clean up all tick registrations. + *

+ * + * @param room the room + */ + public static void unregisterRoomTickables(Room room) { + WiredTickService.getInstance().unregisterRoom(room); + } + + /** + * Gets the tick service instance. + * + * @return the WiredTickService + */ + public static WiredTickService getTickService() { + return WiredTickService.getInstance(); + } + + // ========== Timer Management ========== + + /** + * Resets all wired timers in a room. + *

+ * This uses the new tick service for managing timer resets. + *

+ * + * @param room the room + */ + public static void resetTimers(Room room) { + if (!room.isLoaded()) + return; + + // Use the centralized tick service for timer resets + WiredTickService.getInstance().resetRoomTimers(room); + + room.setLastTimerReset(Emulator.getIntUnixTimestamp()); + } + + // ========== Effect Execution ========== + + public static boolean executeEffectsAtTiles(THashSet tiles, final RoomUnit roomUnit, final Room room, final Object[] stuff) { + for (RoomTile tile : tiles) { + if (room != null) { + THashSet items = room.getItemsAt(tile); + + long millis = room.getCycleTimestamp(); + for (final HabboItem item : items) { + if (item instanceof InteractionWiredEffect && !(item instanceof WiredEffectTriggerStacks)) { + InteractionWiredEffect effect = (InteractionWiredEffect) item; + WiredEvent event = WiredEvent.builder(WiredEvent.Type.CUSTOM, room) + .actor(roomUnit) + .legacyStuff(stuff) + .build(); + WiredContext ctx = new WiredContext(event, effect, DefaultWiredServices.getInstance(), new WiredState(100)); + effect.execute(ctx); + effect.setCooldown(millis); + } + } + } + } + + return true; + } + + // ========== Reward System ========== + + /** + * Asynchronously drops/deletes all rewards given by a specific wired item. + * Used when a wired reward box is picked up or reset. + * + * @param wiredId The ID of the wired item whose rewards should be deleted + */ + public static void dropRewards(int wiredId) { + Emulator.getThreading().run(() -> { + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); + PreparedStatement statement = connection.prepareStatement("DELETE FROM wired_rewards_given WHERE wired_item = ?")) { + statement.setInt(1, wiredId); + statement.execute(); + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + }); + } + + private static void giveReward(Habbo habbo, WiredEffectGiveReward wiredBox, WiredGiveRewardItem reward) { + if (wiredBox.getLimit() > 0) + wiredBox.incrementGiven(); + + final int wiredId = wiredBox.getId(); + final int habboId = habbo.getHabboInfo().getId(); + final int rewardId = reward.id; + final int timestamp = Emulator.getIntUnixTimestamp(); + + Emulator.getThreading().run(() -> { + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); + PreparedStatement statement = connection.prepareStatement("INSERT INTO wired_rewards_given (wired_item, user_id, reward_id, timestamp) VALUES ( ?, ?, ?, ?)")) { + statement.setInt(1, wiredId); + statement.setInt(2, habboId); + statement.setInt(3, rewardId); + statement.setInt(4, timestamp); + statement.execute(); + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + }); + + if (reward.badge) { + UserWiredRewardReceived rewardReceived = new UserWiredRewardReceived(habbo, wiredBox, "badge", reward.data); + if (Emulator.getPluginManager().fireEvent(rewardReceived).isCancelled()) + return; + + if (rewardReceived.value.isEmpty()) + return; + + if (habbo.getInventory().getBadgesComponent().hasBadge(rewardReceived.value)) + return; + + HabboBadge badge = new HabboBadge(0, rewardReceived.value, 0, habbo); + Emulator.getThreading().run(badge); + habbo.getInventory().getBadgesComponent().addBadge(badge); + habbo.getClient().sendResponse(new AddUserBadgeComposer(badge)); + habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_RECEIVED_BADGE)); + } else { + String[] data = reward.data.split("#"); + + if (data.length == 2) { + UserWiredRewardReceived rewardReceived = new UserWiredRewardReceived(habbo, wiredBox, data[0], data[1]); + if (Emulator.getPluginManager().fireEvent(rewardReceived).isCancelled()) + return; + + if (rewardReceived.value.isEmpty()) + return; + + if (rewardReceived.type.equalsIgnoreCase("credits")) { + int credits = Integer.parseInt(rewardReceived.value); + habbo.giveCredits(credits); + } else if (rewardReceived.type.equalsIgnoreCase("pixels")) { + int pixels = Integer.parseInt(rewardReceived.value); + habbo.givePixels(pixels); + } else if (rewardReceived.type.startsWith("points")) { + int points = Integer.parseInt(rewardReceived.value); + int type = 5; + + try { + type = Integer.parseInt(rewardReceived.type.replace("points", "")); + } catch (Exception e) { + } + + habbo.givePoints(type, points); + } else if (rewardReceived.type.equalsIgnoreCase("furni")) { + Item baseItem = Emulator.getGameEnvironment().getItemManager().getItem(Integer.parseInt(rewardReceived.value)); + if (baseItem != null) { + HabboItem item = Emulator.getGameEnvironment().getItemManager().createItem(habbo.getHabboInfo().getId(), baseItem, 0, 0, ""); + + if (item != null) { + habbo.getClient().sendResponse(new AddHabboItemComposer(item)); + habbo.getClient().getHabbo().getInventory().getItemsComponent().addItem(item); + habbo.getClient().sendResponse(new PurchaseOKComposer(null)); + habbo.getClient().sendResponse(new InventoryRefreshComposer()); + habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_RECEIVED_ITEM)); + } + } + } else if (rewardReceived.type.equalsIgnoreCase("respect")) { + habbo.getHabboStats().respectPointsReceived += Integer.parseInt(rewardReceived.value); + } else if (rewardReceived.type.equalsIgnoreCase("cata")) { + CatalogItem item = Emulator.getGameEnvironment().getCatalogManager().getCatalogItem(Integer.parseInt(rewardReceived.value)); + + if (item != null) { + Emulator.getGameEnvironment().getCatalogManager().purchaseItem(null, item, habbo, 1, "", true); + } + habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_RECEIVED_ITEM)); + } + } + } + } + + public static boolean getReward(Habbo habbo, WiredEffectGiveReward wiredBox) { + if (wiredBox.getLimit() > 0) { + if (wiredBox.getLimit() - wiredBox.getGiven() == 0) { + habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.LIMITED_NO_MORE_AVAILABLE)); + return false; + } + } + + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT COUNT(*) as row_count, wired_rewards_given.* FROM wired_rewards_given WHERE user_id = ? AND wired_item = ? ORDER BY timestamp DESC LIMIT ?", ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY)) { + statement.setInt(1, habbo.getHabboInfo().getId()); + statement.setInt(2, wiredBox.getId()); + statement.setInt(3, wiredBox.getRewardItems().size()); + + try (ResultSet set = statement.executeQuery()) { + if (set.first()) { + if (set.getInt("row_count") >= 1) { + if (wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_ONCE) { + habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED)); + return false; + } + } + + set.beforeFirst(); + if (set.next()) { + if (wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_N_MINUTES) { + if (Emulator.getIntUnixTimestamp() - set.getInt("timestamp") <= 60) { + habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_MINUTE)); + return false; + } + } + + if (wiredBox.isUniqueRewards()) { + if (set.getInt("row_count") == wiredBox.getRewardItems().size()) { + habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALL_COLLECTED)); + return false; + } + } + + if (wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_N_HOURS) { + if (!(Emulator.getIntUnixTimestamp() - set.getInt("timestamp") >= (3600 * wiredBox.getLimitationInterval()))) { + habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_HOUR)); + return false; + } + } + + if (wiredBox.getRewardTime() == WiredEffectGiveReward.LIMIT_N_DAY) { + if (!(Emulator.getIntUnixTimestamp() - set.getInt("timestamp") >= (86400 * wiredBox.getLimitationInterval()))) { + habbo.getClient().sendResponse(new WiredRewardAlertComposer(WiredRewardAlertComposer.REWARD_ALREADY_RECEIVED_THIS_TODAY)); + return false; + } + } + } + + if (wiredBox.isUniqueRewards()) { + for (WiredGiveRewardItem item : wiredBox.getRewardItems()) { + set.beforeFirst(); + boolean found = false; + + while (set.next()) { + if (set.getInt("reward_id") == item.id) + found = true; + } + + if (!found) { + giveReward(habbo, wiredBox, item); + return true; + } + } + } else { + int randomNumber = Emulator.getRandom().nextInt(101); + + int count = 0; + for (WiredGiveRewardItem item : wiredBox.getRewardItems()) { + if (randomNumber >= count && randomNumber <= (count + item.probability)) { + giveReward(habbo, wiredBox, item); + return true; + } + + count += item.probability; + } + } + } + } + } catch (SQLException e) { + LOGGER.error("Caught SQL exception", e); + } + + return false; + } +} + diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredServices.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredServices.java new file mode 100644 index 00000000..340b34c3 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredServices.java @@ -0,0 +1,260 @@ +package com.eu.habbo.habbohotel.wired.core; + +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomTile; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.users.HabboItem; + +/** + * Interface abstracting side effects for wired execution. + *

+ * This abstraction layer provides several benefits: + *

    + *
  • Testability - mock implementations for unit tests
  • + *
  • Extensibility - add queueing, RNG, signals later
  • + *
  • Consistency - all room mutations go through one place
  • + *
  • Safety - can add rate limiting, validation, etc.
  • + *
+ *

+ * + *

Usage in Effects:

+ *
{@code
+ * public void execute(WiredContext ctx) {
+ *     ctx.actor().ifPresent(user -> 
+ *         ctx.services().teleportUser(ctx.room(), user, targetTile));
+ * }
+ * }
+ * + * @see DefaultWiredServices + * @see WiredContext + */ +public interface WiredServices { + + // ========== Debug/Logging ========== + + /** + * Log a debug message for wired execution tracing. + * @param room the room context + * @param message the message to log + */ + void debug(Room room, String message); + + /** + * Log a debug message with format arguments. + * @param room the room context + * @param format the format string + * @param args format arguments + */ + void debug(Room room, String format, Object... args); + + // ========== User Operations ========== + + /** + * Teleport a user to a specific tile instantly. + * @param room the room + * @param user the user to teleport + * @param tile the destination tile + */ + void teleportUser(Room room, RoomUnit user, RoomTile tile); + + /** + * Move a user to a specific tile (walks there). + * @param room the room + * @param user the user to move + * @param tile the destination tile + */ + void moveUser(Room room, RoomUnit user, RoomTile tile); + + /** + * Kick a user from the room. + * @param room the room + * @param user the user to kick + */ + void kickUser(Room room, RoomUnit user); + + /** + * Give an effect to a user. + * @param room the room + * @param user the user + * @param effectId the effect ID to apply + */ + void giveEffect(Room room, RoomUnit user, int effectId); + + /** + * Give an effect to a user for a limited duration. + * @param room the room + * @param user the user + * @param effectId the effect ID + * @param duration duration in seconds (0 = permanent) + */ + void giveEffect(Room room, RoomUnit user, int effectId, int duration); + + /** + * Whisper a message to a user (only they see it). + * @param room the room + * @param user the user + * @param message the message to whisper + */ + void whisperToUser(Room room, RoomUnit user, String message); + + /** + * Give a hand item to a user. + * @param room the room + * @param user the user + * @param handItemId the hand item ID + */ + void giveHandItem(Room room, RoomUnit user, int handItemId); + + /** + * Mute a user. + * @param room the room + * @param user the user + * @param durationMinutes mute duration in minutes + */ + void muteUser(Room room, RoomUnit user, int durationMinutes); + + // ========== Furniture Operations ========== + + /** + * Toggle a furniture item's state. + * @param room the room + * @param item the item to toggle + */ + void toggleFurni(Room room, HabboItem item); + + /** + * Set a furniture item to a specific state. + * @param room the room + * @param item the item + * @param state the state value + */ + void setFurniState(Room room, HabboItem item, int state); + + /** + * Move furniture to a specific tile and rotation. + * @param room the room + * @param item the item to move + * @param tile the destination tile + * @param rotation the new rotation + */ + void moveFurni(Room room, HabboItem item, RoomTile tile, int rotation); + + /** + * Reset furniture to its original state (from match furni). + * @param room the room + * @param item the item to reset + */ + void resetFurniState(Room room, HabboItem item); + + // ========== Game Operations ========== + + /** + * Give score to a user in a game context. + * @param room the room + * @param user the user + * @param score the score to add + */ + void giveScore(Room room, RoomUnit user, int score); + + /** + * Give score to a team. + * @param room the room + * @param teamId the team ID + * @param score the score to add + */ + void giveScoreToTeam(Room room, int teamId, int score); + + /** + * Add a user to a team. + * @param room the room + * @param user the user + * @param teamId the team to join + */ + void joinTeam(Room room, RoomUnit user, int teamId); + + /** + * Remove a user from their team. + * @param room the room + * @param user the user + */ + void leaveTeam(Room room, RoomUnit user); + + // ========== Bot Operations ========== + + /** + * Make a bot say something. + * @param room the room + * @param botName the bot's name + * @param message the message + * @param shout true to shout, false to talk + */ + void botTalk(Room room, String botName, String message, boolean shout); + + /** + * Make a bot whisper to a user. + * @param room the room + * @param botName the bot's name + * @param user the target user + * @param message the message + */ + void botWhisperTo(Room room, String botName, RoomUnit user, String message); + + /** + * Make a bot walk to a furniture item. + * @param room the room + * @param botName the bot's name + * @param item the target item + */ + void botWalkToFurni(Room room, String botName, HabboItem item); + + /** + * Make a bot teleport to a tile. + * @param room the room + * @param botName the bot's name + * @param tile the destination tile + */ + void botTeleport(Room room, String botName, RoomTile tile); + + /** + * Make a bot follow a user. + * @param room the room + * @param botName the bot's name + * @param user the user to follow + */ + void botFollowUser(Room room, String botName, RoomUnit user); + + /** + * Change a bot's clothes. + * @param room the room + * @param botName the bot's name + * @param figure the new figure string + */ + void botSetClothes(Room room, String botName, String figure); + + /** + * Make a bot give a hand item to a user. + * @param room the room + * @param botName the bot's name + * @param user the target user + * @param handItemId the hand item ID + */ + void botGiveHandItem(Room room, String botName, RoomUnit user, int handItemId); + + // ========== Messaging ========== + + /** + * Show an alert message to a user. + * @param room the room + * @param user the user + * @param message the alert message + */ + void showAlert(Room room, RoomUnit user, String message); + + // ========== Room Operations ========== + + /** + * Reset all wired timers in the room. + * @param room the room + */ + void resetTimers(Room room); +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredSimulation.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredSimulation.java new file mode 100644 index 00000000..d91621db --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredSimulation.java @@ -0,0 +1,202 @@ +package com.eu.habbo.habbohotel.wired.core; + +import com.eu.habbo.habbohotel.rooms.FurnitureMovementError; +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomTile; +import com.eu.habbo.habbohotel.rooms.RoomTileState; +import com.eu.habbo.habbohotel.users.HabboItem; + +import java.util.HashMap; +import java.util.Map; + +/** + * Tracks simulated room state changes for pre-validation of wired movement effects. + *

+ * When the Movement Validation condition is present, movement effects are first simulated + * to verify all moves can complete successfully. This class tracks item position changes + * without modifying the actual room. If all movements succeed in simulation, they are + * then executed for real. + *

+ * + *

Key Feature:

+ *

+ * The simulation tracks cumulative position changes. If Effect 1 moves an item from + * tile 0 to tile 1, and Effect 2 moves it forward again, the simulation knows the + * item is now at tile 1 (not tile 0) and validates the move to tile 2 correctly. + *

+ * + *

Usage:

+ *
{@code
+ * WiredSimulation sim = new WiredSimulation(room);
+ * 
+ * // Movement effects call simulation methods
+ * if (!sim.moveItem(item, newX, newY, newZ, rotation)) {
+ *     // Move would fail - simulation is now marked as failed
+ * }
+ * 
+ * // Check if all simulated moves succeeded
+ * if (!sim.hasFailed()) {
+ *     // Safe to execute real moves
+ * }
+ * }
+ */ +public final class WiredSimulation { + + private final Room room; + private final Map itemPositions; + private boolean failed; + private String failureReason; + + public WiredSimulation(Room room) { + this.room = room; + this.itemPositions = new HashMap<>(); + this.failed = false; + this.failureReason = null; + } + + public Room getRoom() { + return room; + } + + public boolean hasFailed() { + return failed; + } + + public String getFailureReason() { + return failureReason; + } + + public void fail(String reason) { + this.failed = true; + this.failureReason = reason; + } + + /** + * Simulate moving an item to a new position. + * Validates the destination tile using the room's furnitureFitsAt method, + * which checks for users, bots, pets, other furniture, and tile state. + * + * @param item the item to move + * @param newX new X coordinate + * @param newY new Y coordinate + * @param newZ new Z height + * @param newRotation new rotation + * @return true if the move would succeed, false if it would fail + */ + public boolean moveItem(HabboItem item, short newX, short newY, double newZ, int newRotation) { + if (item == null) { + fail("Cannot move null item"); + return false; + } + + if (room.getLayout() == null) { + fail("Room has no layout"); + return false; + } + + RoomTile destTile = room.getLayout().getTile(newX, newY); + if (destTile == null) { + fail("Destination tile (" + newX + "," + newY + ") does not exist"); + return false; + } + + if (destTile.getState() == RoomTileState.INVALID) { + fail("Destination tile (" + newX + "," + newY + ") is invalid/hole"); + return false; + } + + FurnitureMovementError moveError = room.furnitureFitsAt(destTile, item, newRotation, true); + if (moveError != FurnitureMovementError.NONE) { + fail("Destination tile (" + newX + "," + newY + ") blocked: " + moveError.name()); + return false; + } + + itemPositions.put(item.getId(), new SimulatedPosition(newX, newY, newZ, newRotation)); + return true; + } + + /** + * Simulate moving an item by a relative offset. + */ + public boolean moveItemRelative(HabboItem item, int offsetX, int offsetY) { + if (item == null) { + fail("Cannot move null item"); + return false; + } + + SimulatedPosition currentPos = getItemPosition(item); + short newX = (short) (currentPos.x + offsetX); + short newY = (short) (currentPos.y + offsetY); + + return moveItem(item, newX, newY, currentPos.z, currentPos.rotation); + } + + /** + * Get the current (possibly simulated) position of an item. + * Returns simulated position if one exists, otherwise the real position. + */ + public SimulatedPosition getItemPosition(HabboItem item) { + if (item == null) { + return null; + } + + SimulatedPosition simPos = itemPositions.get(item.getId()); + if (simPos != null) { + return simPos; + } + + return new SimulatedPosition(item.getX(), item.getY(), item.getZ(), item.getRotation()); + } + + /** + * Check if a tile is valid for an item to move to. + * Uses furnitureFitsAt for full validation including users, bots, furniture, etc. + */ + public boolean isTileValidForItem(short x, short y, HabboItem item) { + if (room.getLayout() == null) { + return false; + } + + RoomTile tile = room.getLayout().getTile(x, y); + if (tile == null) { + return false; + } + if (tile.getState() == RoomTileState.INVALID) { + return false; + } + + FurnitureMovementError moveError = room.furnitureFitsAt(tile, item, item.getRotation(), true); + return moveError == FurnitureMovementError.NONE; + } + + /** + * Reset the simulation state for reuse. + */ + public void reset() { + itemPositions.clear(); + failed = false; + failureReason = null; + } + + /** + * Represents a simulated item position. + */ + public static final class SimulatedPosition { + public final short x; + public final short y; + public final double z; + public final int rotation; + + public SimulatedPosition(short x, short y, double z, int rotation) { + this.x = x; + this.y = y; + this.z = z; + this.rotation = rotation; + } + + @Override + public String toString() { + return "SimPos{x=" + x + ", y=" + y + ", z=" + z + ", rot=" + rotation + "}"; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredStackIndex.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredStackIndex.java new file mode 100644 index 00000000..021c8d67 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredStackIndex.java @@ -0,0 +1,77 @@ +package com.eu.habbo.habbohotel.wired.core; + +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomTile; +import com.eu.habbo.habbohotel.wired.api.WiredStack; + +import java.util.List; + +/** + * Interface for indexing and retrieving wired stacks by event type. + *

+ * The index provides fast lookup of wired stacks that listen to a specific event type, + * avoiding the need to scan all wired items in a room on every event. + *

+ * + *

Implementation Notes:

+ *
    + *
  • Index should be rebuilt when wired items are added/removed/moved
  • + *
  • Index can be invalidated per-tile for granular updates
  • + *
  • Implementations should be thread-safe if used concurrently
  • + *
+ * + * @see WiredStack + * @see WiredEngine + */ +public interface WiredStackIndex { + + /** + * Get all wired stacks in a room that listen to the specified event type. + * + * @param room the room to search + * @param type the event type to match + * @return list of matching stacks (may be empty, never null) + */ + List getStacks(Room room, WiredEvent.Type type); + + /** + * Rebuild the entire index for a room. + * Call this when the room is loaded or when major changes occur. + * + * @param room the room to rebuild index for + */ + default void rebuild(Room room) { + // Default no-op for on-demand implementations + } + + /** + * Invalidate the index for a specific tile. + * Call this when a wired item is added, removed, or moved on that tile. + * + * @param room the room + * @param tile the tile that changed + */ + default void invalidate(Room room, RoomTile tile) { + // Default no-op for on-demand implementations + } + + /** + * Invalidate the entire index for a room. + * Call this when the room is unloaded or major changes occur. + * + * @param room the room to invalidate + */ + default void invalidateAll(Room room) { + // Default no-op for on-demand implementations + } + + /** + * Check if the index has cached data for a room. + * + * @param room the room to check + * @return true if cached data exists + */ + default boolean isCached(Room room) { + return false; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredState.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredState.java new file mode 100644 index 00000000..7bd8e4b0 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredState.java @@ -0,0 +1,167 @@ +package com.eu.habbo.habbohotel.wired.core; + +import java.util.UUID; + +/** + * Tracks execution state for a wired stack run, providing loop safety and metadata. + *

+ * Each wired stack execution gets its own WiredState instance that tracks: + *

    + *
  • A unique run ID for debugging/tracing
  • + *
  • Step count to prevent infinite loops
  • + *
  • Maximum allowed steps before throwing {@link WiredLimitException}
  • + *
+ *

+ * + *

Usage:

+ *
{@code
+ * WiredState state = new WiredState(100); // max 100 steps
+ * state.step(); // must call before each condition/effect
+ * // ... execute condition/effect ...
+ * }
+ * + * @see WiredLimitException + * @see WiredContext + */ +public final class WiredState { + + private final UUID runId; + private final int maxSteps; + private int steps = 0; + private long startTimeMs; + private boolean aborted = false; + private String abortReason; + + /** + * Create a new wired state with the specified step limit. + * @param maxSteps maximum number of steps allowed (triggers, conditions, effects) + */ + public WiredState(int maxSteps) { + this.runId = UUID.randomUUID(); + this.maxSteps = maxSteps; + this.startTimeMs = System.currentTimeMillis(); + } + + /** + * Get the unique identifier for this execution run. + * Useful for debugging and tracing wired execution across logs. + * @return the run UUID + */ + public UUID runId() { + return runId; + } + + /** + * Get the current step count. + * @return number of steps executed so far + */ + public int steps() { + return steps; + } + + /** + * Get the maximum allowed steps. + * @return the step limit + */ + public int maxSteps() { + return maxSteps; + } + + /** + * Get the time when this execution started. + * @return start time in milliseconds since epoch + */ + public long startTimeMs() { + return startTimeMs; + } + + /** + * Get the elapsed time since execution started. + * @return elapsed time in milliseconds + */ + public long elapsedMs() { + return System.currentTimeMillis() - startTimeMs; + } + + /** + * Check if the execution has been aborted. + * @return true if aborted + */ + public boolean isAborted() { + return aborted; + } + + /** + * Get the reason for abortion, if any. + * @return the abort reason, or null if not aborted + */ + public String abortReason() { + return abortReason; + } + + /** + * Increment the step counter and check for limit violation. + * Call this before each trigger match, condition evaluation, or effect execution. + * + * @throws WiredLimitException if the step limit has been exceeded + */ + public void step() { + if (aborted) { + throw new WiredLimitException("Wired execution was aborted: " + abortReason); + } + + steps++; + if (steps > maxSteps) { + throw new WiredLimitException( + "Wired execution exceeded max steps: " + maxSteps + + " (runId: " + runId + ")"); + } + } + + /** + * Check if we can still execute more steps without throwing. + * @return true if more steps are allowed + */ + public boolean canStep() { + return !aborted && steps < maxSteps; + } + + /** + * Get remaining steps before hitting the limit. + * @return number of remaining steps + */ + public int remainingSteps() { + return Math.max(0, maxSteps - steps); + } + + /** + * Abort this execution with a reason. + * Subsequent calls to {@link #step()} will throw. + * @param reason the reason for aborting + */ + public void abort(String reason) { + this.aborted = true; + this.abortReason = reason; + } + + /** + * Reset the step counter (use with caution). + * This is mainly for testing purposes. + */ + public void reset() { + this.steps = 0; + this.aborted = false; + this.abortReason = null; + this.startTimeMs = System.currentTimeMillis(); + } + + @Override + public String toString() { + return "WiredState{" + + "runId=" + runId + + ", steps=" + steps + "/" + maxSteps + + ", elapsed=" + elapsedMs() + "ms" + + (aborted ? ", ABORTED: " + abortReason : "") + + '}'; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredTargets.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredTargets.java new file mode 100644 index 00000000..757c75ac --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/core/WiredTargets.java @@ -0,0 +1,198 @@ +package com.eu.habbo.habbohotel.wired.core; + +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.users.HabboItem; + +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; + +/** + * Represents the target users and items for wired effect execution. + *

+ * This class provides a mutable collection of targets that can be modified + * by selectors (future feature) before effects are executed. By default, + * targets include the triggering actor and trigger item. + *

+ * + *

Future Selector Support:

+ *
{@code
+ * // Selectors will be able to filter/modify targets:
+ * targets.setUsers(targets.users().stream()
+ *     .filter(u -> u.hasEffect(123))
+ *     .collect(Collectors.toList()));
+ * }
+ * + * @see WiredContext + */ +public final class WiredTargets { + + private final Set users = new LinkedHashSet<>(); + private final Set items = new LinkedHashSet<>(); + + /** + * Get all targeted users (read-only view). + * @return unmodifiable set of room units + */ + public Set users() { + return Collections.unmodifiableSet(users); + } + + /** + * Get all targeted items (read-only view). + * @return unmodifiable set of habbo items + */ + public Set items() { + return Collections.unmodifiableSet(items); + } + + /** + * Check if there are any user targets. + * @return true if at least one user is targeted + */ + public boolean hasUsers() { + return !users.isEmpty(); + } + + /** + * Check if there are any item targets. + * @return true if at least one item is targeted + */ + public boolean hasItems() { + return !items.isEmpty(); + } + + /** + * Check if there are any targets at all. + * @return true if there are users or items + */ + public boolean hasTargets() { + return hasUsers() || hasItems(); + } + + /** + * Get the first user target, if any. + * Useful when you expect exactly one user target. + * @return the first user, or null if no users + */ + public RoomUnit firstUser() { + return users.isEmpty() ? null : users.iterator().next(); + } + + /** + * Get the first item target, if any. + * Useful when you expect exactly one item target. + * @return the first item, or null if no items + */ + public HabboItem firstItem() { + return items.isEmpty() ? null : items.iterator().next(); + } + + // Mutators (used by engine/selector layer) + + /** + * Clear all targets. + */ + public void clear() { + users.clear(); + items.clear(); + } + + /** + * Clear all user targets. + */ + public void clearUsers() { + users.clear(); + } + + /** + * Clear all item targets. + */ + public void clearItems() { + items.clear(); + } + + /** + * Replace all user targets with the given users. + * @param newUsers the new user targets + */ + public void setUsers(Iterable newUsers) { + users.clear(); + if (newUsers != null) { + for (RoomUnit u : newUsers) { + if (u != null) users.add(u); + } + } + } + + /** + * Replace all item targets with the given items. + * @param newItems the new item targets + */ + public void setItems(Iterable newItems) { + items.clear(); + if (newItems != null) { + for (HabboItem i : newItems) { + if (i != null) items.add(i); + } + } + } + + /** + * Add a user to the targets. + * @param user the user to add (null is ignored) + */ + public void addUser(RoomUnit user) { + if (user != null) users.add(user); + } + + /** + * Add an item to the targets. + * @param item the item to add (null is ignored) + */ + public void addItem(HabboItem item) { + if (item != null) items.add(item); + } + + /** + * Remove a user from the targets. + * @param user the user to remove + * @return true if the user was removed + */ + public boolean removeUser(RoomUnit user) { + return users.remove(user); + } + + /** + * Remove an item from the targets. + * @param item the item to remove + * @return true if the item was removed + */ + public boolean removeItem(HabboItem item) { + return items.remove(item); + } + + /** + * Get the count of user targets. + * @return number of users + */ + public int userCount() { + return users.size(); + } + + /** + * Get the count of item targets. + * @return number of items + */ + public int itemCount() { + return items.size(); + } + + @Override + public String toString() { + return "WiredTargets{" + + "users=" + users.size() + + ", items=" + items.size() + + '}'; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/migrate/WiredEvents.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/migrate/WiredEvents.java new file mode 100644 index 00000000..a6df356a --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/migrate/WiredEvents.java @@ -0,0 +1,357 @@ +package com.eu.habbo.habbohotel.wired.migrate; + +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.rooms.RoomTile; +import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.users.HabboItem; +import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredEvent; + +/** + * Factory methods for creating {@link WiredEvent} instances from various room events. + *

+ * This class provides convenient builder methods for creating events that can be + * passed to the {@link com.eu.habbo.habbohotel.wired.core.WiredEngine}. + *

+ * + *

Usage:

+ *
{@code
+ * // When user walks on furniture
+ * WiredEvent event = WiredEvents.userWalksOn(room, user, steppedItem);
+ * engine.handleEvent(event);
+ * 
+ * // When user says something
+ * WiredEvent event = WiredEvents.userSays(room, user, message);
+ * engine.handleEvent(event);
+ * }
+ * + * @see WiredEvent + */ +public final class WiredEvents { + + private WiredEvents() { + // Static utility class + } + + // ========== User Movement Events ========== + + /** + * Create an event for when a user walks onto furniture. + * @param room the room + * @param user the user who walked + * @param item the furniture walked onto + * @return the event + */ + public static WiredEvent userWalksOn(Room room, RoomUnit user, HabboItem item) { + RoomTile tile = room.getLayout().getTile(item.getX(), item.getY()); + return WiredEvent.builder(WiredEvent.Type.USER_WALKS_ON, room) + .actor(user) + .sourceItem(item) + .tile(tile) + .build(); + } + + /** + * Create an event for when a user walks off furniture. + * @param room the room + * @param user the user who walked + * @param item the furniture walked off of + * @return the event + */ + public static WiredEvent userWalksOff(Room room, RoomUnit user, HabboItem item) { + RoomTile tile = room.getLayout().getTile(item.getX(), item.getY()); + return WiredEvent.builder(WiredEvent.Type.USER_WALKS_OFF, room) + .actor(user) + .sourceItem(item) + .tile(tile) + .build(); + } + + /** + * Create an event for when a user enters the room. + * @param room the room + * @param user the user who entered + * @return the event + */ + public static WiredEvent userEntersRoom(Room room, RoomUnit user) { + return WiredEvent.builder(WiredEvent.Type.USER_ENTERS_ROOM, room) + .actor(user) + .tile(user.getCurrentLocation()) + .build(); + } + + // ========== User Interaction Events ========== + + /** + * Create an event for when a user says something. + * @param room the room + * @param user the user who spoke + * @param message the message said + * @return the event + */ + public static WiredEvent userSays(Room room, RoomUnit user, String message) { + return WiredEvent.builder(WiredEvent.Type.USER_SAYS, room) + .actor(user) + .text(message) + .tile(user.getCurrentLocation()) + .build(); + } + + // ========== Furniture Events ========== + + /** + * Create an event for when furniture state is toggled/changed. + * @param room the room + * @param user the user who changed it (may be null for automatic changes) + * @param item the furniture that changed + * @return the event + */ + public static WiredEvent furniStateChanged(Room room, RoomUnit user, HabboItem item) { + RoomTile tile = room.getLayout().getTile(item.getX(), item.getY()); + return WiredEvent.builder(WiredEvent.Type.FURNI_STATE_CHANGED, room) + .actor(user) + .sourceItem(item) + .tile(tile) + .build(); + } + + // ========== Timer Events ========== + + /** + * Create an event for a timer tick (AT_GIVEN_TIME). + * @param room the room + * @param timerItem the timer furniture + * @return the event + */ + public static WiredEvent timerTick(Room room, HabboItem timerItem) { + return WiredEvent.builder(WiredEvent.Type.TIMER_TICK, room) + .sourceItem(timerItem) + .build(); + } + + /** + * Create an event for a periodic timer (PERIODICALLY). + * @param room the room + * @param timerItem the timer furniture + * @return the event + */ + public static WiredEvent timerRepeat(Room room, HabboItem timerItem) { + return WiredEvent.builder(WiredEvent.Type.TIMER_REPEAT, room) + .sourceItem(timerItem) + .build(); + } + + /** + * Create an event for a long periodic timer. + * @param room the room + * @param timerItem the timer furniture + * @return the event + */ + public static WiredEvent timerRepeatLong(Room room, HabboItem timerItem) { + return WiredEvent.builder(WiredEvent.Type.TIMER_REPEAT_LONG, room) + .sourceItem(timerItem) + .build(); + } + + // ========== Game Events ========== + + /** + * Create an event for when a game starts. + * @param room the room + * @return the event + */ + public static WiredEvent gameStarts(Room room) { + return WiredEvent.builder(WiredEvent.Type.GAME_STARTS, room) + .build(); + } + + /** + * Create an event for when a game ends. + * @param room the room + * @return the event + */ + public static WiredEvent gameEnds(Room room) { + return WiredEvent.builder(WiredEvent.Type.GAME_ENDS, room) + .build(); + } + + /** + * Create an event for when a team wins a game. + * @param room the room + * @param user the user on the winning team + * @return the event + */ + public static WiredEvent teamWins(Room room, RoomUnit user) { + return WiredEvent.builder(WiredEvent.Type.TEAM_WINS, room) + .actor(user) + .build(); + } + + /** + * Create an event for when a team loses a game. + * @param room the room + * @param user the user on the losing team + * @return the event + */ + public static WiredEvent teamLoses(Room room, RoomUnit user) { + return WiredEvent.builder(WiredEvent.Type.TEAM_LOSES, room) + .actor(user) + .build(); + } + + /** + * Create an event for when a score is achieved. + /** + * Create an event for when a score threshold is achieved. + * @param room the room + * @param user the user who achieved the score + * @param score the score achieved + * @return the event + */ + public static WiredEvent scoreAchieved(Room room, RoomUnit user, int score) { + return WiredEvent.builder(WiredEvent.Type.SCORE_ACHIEVED, room) + .actor(user) + .legacyStuff(new Object[]{score}) + .build(); + } + + // ========== Bot Events ========== + + /** + * Create an event for a bot collision. + * @param room the room + * @param botUnit the bot's room unit + * @return the event + */ + public static WiredEvent botCollision(Room room, RoomUnit botUnit) { + return WiredEvent.builder(WiredEvent.Type.BOT_COLLISION, room) + .actor(botUnit) + .tile(botUnit.getCurrentLocation()) + .build(); + } + + /** + * Create an event for when a bot reaches furniture. + * @param room the room + * @param botUnit the bot's room unit + * @param item the furniture reached + * @return the event + */ + public static WiredEvent botReachedFurni(Room room, RoomUnit botUnit, HabboItem item) { + RoomTile tile = room.getLayout().getTile(item.getX(), item.getY()); + return WiredEvent.builder(WiredEvent.Type.BOT_REACHED_FURNI, room) + .actor(botUnit) + .sourceItem(item) + .tile(tile) + .build(); + } + + /** + * Create an event for when a bot reaches a habbo. + * @param room the room + * @param botUnit the bot's room unit + * @param targetUser the habbo reached + * @return the event + */ + public static WiredEvent botReachedHabbo(Room room, RoomUnit botUnit, RoomUnit targetUser) { + return WiredEvent.builder(WiredEvent.Type.BOT_REACHED_HABBO, room) + .actor(botUnit) + .tile(targetUser.getCurrentLocation()) + .legacyStuff(new Object[]{targetUser}) + .build(); + } + + // ========== User State Events ========== + + /** + * Create an event for when a user starts idling. + * @param room the room + * @param user the user who started idling + * @return the event + */ + public static WiredEvent userIdles(Room room, RoomUnit user) { + return WiredEvent.builder(WiredEvent.Type.USER_IDLES, room) + .actor(user) + .tile(user.getCurrentLocation()) + .build(); + } + + /** + * Create an event for when a user stops idling. + * @param room the room + * @param user the user who stopped idling + * @return the event + */ + public static WiredEvent userUnidles(Room room, RoomUnit user) { + return WiredEvent.builder(WiredEvent.Type.USER_UNIDLES, room) + .actor(user) + .tile(user.getCurrentLocation()) + .build(); + } + + /** + * Create an event for when a user starts dancing. + * @param room the room + * @param user the user who started dancing + * @return the event + */ + public static WiredEvent userStartsDancing(Room room, RoomUnit user) { + return WiredEvent.builder(WiredEvent.Type.USER_STARTS_DANCING, room) + .actor(user) + .tile(user.getCurrentLocation()) + .build(); + } + + /** + * Create an event for when a user stops dancing. + * @param room the room + * @param user the user who stopped dancing + * @return the event + */ + public static WiredEvent userStopsDancing(Room room, RoomUnit user) { + return WiredEvent.builder(WiredEvent.Type.USER_STOPS_DANCING, room) + .actor(user) + .tile(user.getCurrentLocation()) + .build(); + } + + // ========== Legacy Compatibility ========== + + /** + * Create an event from legacy trigger type and parameters. + * This is for backwards compatibility during migration. + * + * @param triggerType the legacy trigger type + * @param room the room + * @param roomUnit the triggering unit (may be null) + * @param stuff legacy stuff array + * @return the event + */ + public static WiredEvent fromLegacy(WiredTriggerType triggerType, Room room, RoomUnit roomUnit, Object[] stuff) { + WiredEvent.Type eventType = WiredEvent.Type.fromLegacyType(triggerType); + + WiredEvent.Builder builder = WiredEvent.builder(eventType, room) + .actor(roomUnit) + .legacyStuff(stuff); + + // Try to extract common data from stuff array + if (stuff != null) { + for (Object obj : stuff) { + if (obj instanceof HabboItem) { + builder.sourceItem((HabboItem) obj); + } else if (obj instanceof RoomTile) { + builder.tile((RoomTile) obj); + } else if (obj instanceof String) { + builder.text((String) obj); + } + } + } + + // Add current tile from room unit if available + if (roomUnit != null && roomUnit.getCurrentLocation() != null) { + builder.tile(roomUnit.getCurrentLocation()); + } + + return builder.build(); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/tick/WiredTickService.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/tick/WiredTickService.java new file mode 100644 index 00000000..45ca8de0 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/tick/WiredTickService.java @@ -0,0 +1,464 @@ +package com.eu.habbo.habbohotel.wired.tick; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.rooms.Room; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.Set; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Centralized tick service for all wired timing operations. + *

+ * This service runs a single 50ms tick loop that processes all registered + * {@link WiredTickable} items across all rooms. This replaces the old + * per-room 500ms cycle approach and provides: + *

+ * + *
    + *
  • Higher resolution timing (50ms vs 500ms)
  • + *
  • Centralized management - single thread for all rooms
  • + *
  • Proper room lifecycle handling
  • + *
  • Efficient registration/unregistration
  • + *
+ * + *

Architecture:

+ *
+ * WiredTickService (singleton)
+ *   └── ScheduledExecutorService (50ms tick)
+ *         └── For each room with tickables:
+ *               └── For each WiredTickable:
+ *                     └── onWiredTick(room, currentTime)
+ * 
+ * + *

Thread Safety:

+ * All collections are thread-safe. The tick loop catches and logs exceptions + * to prevent one bad item from crashing the entire service. + * + * @see WiredTickable + */ +public final class WiredTickService { + + private static final Logger LOGGER = LoggerFactory.getLogger(WiredTickService.class); + + /** Default tick interval in milliseconds */ + public static final int DEFAULT_TICK_INTERVAL_MS = 50; + + /** Minimum allowed tick interval (prevents CPU overload) */ + public static final int MIN_TICK_INTERVAL_MS = 10; + + /** Maximum allowed tick interval */ + public static final int MAX_TICK_INTERVAL_MS = 500; + + /** Singleton instance */ + private static volatile WiredTickService instance; + + /** The configured tick interval in milliseconds */ + private int tickIntervalMs = DEFAULT_TICK_INTERVAL_MS; + + /** Whether debug logging is enabled */ + private boolean debugEnabled = false; + + /** Thread priority for the tick service */ + private int threadPriority = Thread.NORM_PRIORITY + 1; + + /** + * Global tick counter - increments every tick. + * All repeaters use this to stay synchronized. + * Repeaters fire when (tickCount * tickIntervalMs) % repeatTime == 0 + */ + private volatile long tickCount = 0; + + /** The scheduled executor for the tick loop */ + private ScheduledExecutorService scheduler; + + /** The scheduled future for the tick task */ + private ScheduledFuture tickTask; + + /** Map of room ID to set of registered tickables */ + private final ConcurrentHashMap> roomTickables; + + /** Whether the service is running */ + private final AtomicBoolean running; + + /** + * Private constructor for singleton. + */ + private WiredTickService() { + this.roomTickables = new ConcurrentHashMap<>(); + this.running = new AtomicBoolean(false); + } + + /** + * Loads configuration from emulator settings. + */ + private void loadConfiguration() { + // Load tick interval + int configuredInterval = Emulator.getConfig().getInt("wired.tick.interval.ms", DEFAULT_TICK_INTERVAL_MS); + this.tickIntervalMs = Math.max(MIN_TICK_INTERVAL_MS, Math.min(MAX_TICK_INTERVAL_MS, configuredInterval)); + + if (configuredInterval != this.tickIntervalMs) { + LOGGER.warn("wired.tick.interval.ms value {} is out of range [{}-{}], using {}", + configuredInterval, MIN_TICK_INTERVAL_MS, MAX_TICK_INTERVAL_MS, this.tickIntervalMs); + } + + // Load debug flag + this.debugEnabled = Emulator.getConfig().getBoolean("wired.tick.debug", false); + + // Load thread priority + int configuredPriority = Emulator.getConfig().getInt("wired.tick.thread.priority", Thread.NORM_PRIORITY + 1); + this.threadPriority = Math.max(Thread.MIN_PRIORITY, Math.min(Thread.MAX_PRIORITY, configuredPriority)); + } + + /** + * Gets the configured tick interval in milliseconds. + * + * @return the tick interval + */ + public int getTickIntervalMs() { + return tickIntervalMs; + } + + /** + * Checks if debug logging is enabled. + * + * @return true if debug is enabled + */ + public boolean isDebugEnabled() { + return debugEnabled; + } + + /** + * Gets the singleton instance. + * + * @return the WiredTickService instance + */ + public static WiredTickService getInstance() { + if (instance == null) { + synchronized (WiredTickService.class) { + if (instance == null) { + instance = new WiredTickService(); + } + } + } + return instance; + } + + /** + * Starts the tick service. + *

+ * Should be called during emulator startup after WiredManager.initialize(). + *

+ */ + public synchronized void start() { + if (running.get()) { + LOGGER.warn("WiredTickService already running"); + return; + } + + // Load configuration from emulator settings + loadConfiguration(); + + LOGGER.info("Starting WiredTickService with {}ms tick interval (debug={}, priority={})...", + tickIntervalMs, debugEnabled, threadPriority); + + this.scheduler = Executors.newSingleThreadScheduledExecutor(r -> { + Thread t = new Thread(r, "WiredTickService"); + t.setDaemon(true); + t.setPriority(threadPriority); + return t; + }); + + this.tickTask = scheduler.scheduleAtFixedRate( + this::tick, + tickIntervalMs, + tickIntervalMs, + TimeUnit.MILLISECONDS + ); + + running.set(true); + LOGGER.info("WiredTickService started successfully"); + } + + /** + * Stops the tick service. + *

+ * Should be called during emulator shutdown. + *

+ */ + public synchronized void stop() { + if (!running.get()) { + return; + } + + LOGGER.info("Stopping WiredTickService..."); + + running.set(false); + + if (tickTask != null) { + tickTask.cancel(false); + tickTask = null; + } + + if (scheduler != null) { + scheduler.shutdown(); + try { + if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) { + scheduler.shutdownNow(); + } + } catch (InterruptedException e) { + scheduler.shutdownNow(); + Thread.currentThread().interrupt(); + } + scheduler = null; + } + + roomTickables.clear(); + LOGGER.info("WiredTickService stopped"); + } + + /** + * Checks if the service is running. + * + * @return true if running + */ + public boolean isRunning() { + return running.get(); + } + + /** + * Registers a tickable item with the service. + *

+ * The item will start receiving {@link WiredTickable#onWiredTick} calls + * on the next tick cycle. + *

+ * + * @param room the room the item is in + * @param tickable the tickable item + */ + public void register(Room room, WiredTickable tickable) { + if (room == null || tickable == null) { + return; + } + + int roomId = room.getId(); + Set tickables = roomTickables.computeIfAbsent( + roomId, + k -> ConcurrentHashMap.newKeySet() + ); + + if (tickables.add(tickable)) { + tickable.onRegistered(room, System.currentTimeMillis()); + LOGGER.debug("Registered tickable {} in room {}", tickable.getId(), roomId); + } + } + + /** + * Unregisters a tickable item from the service. + * + * @param room the room the item was in + * @param tickable the tickable item + */ + public void unregister(Room room, WiredTickable tickable) { + if (room == null || tickable == null) { + return; + } + + int roomId = room.getId(); + Set tickables = roomTickables.get(roomId); + + if (tickables != null) { + if (tickables.remove(tickable)) { + tickable.onUnregistered(room); + LOGGER.debug("Unregistered tickable {} from room {}", tickable.getId(), roomId); + } + + // Clean up empty sets + if (tickables.isEmpty()) { + roomTickables.remove(roomId); + } + } + } + + /** + * Unregisters a tickable by ID. + * + * @param roomId the room ID + * @param tickableId the tickable item ID + */ + public void unregister(int roomId, int tickableId) { + Set tickables = roomTickables.get(roomId); + + if (tickables != null) { + tickables.removeIf(t -> { + if (t.getId() == tickableId) { + Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId); + if (room != null) { + t.onUnregistered(room); + } + return true; + } + return false; + }); + + if (tickables.isEmpty()) { + roomTickables.remove(roomId); + } + } + } + + /** + * Unregisters all tickables for a room. + *

+ * Should be called when a room is unloaded. + *

+ * + * @param room the room + */ + public void unregisterRoom(Room room) { + if (room == null) { + return; + } + + Set tickables = roomTickables.remove(room.getId()); + + if (tickables != null) { + for (WiredTickable tickable : tickables) { + tickable.onUnregistered(room); + } + LOGGER.debug("Unregistered {} tickables from room {}", tickables.size(), room.getId()); + } + } + + /** + * Resets all timers in a room. + * + * @param room the room + */ + public void resetRoomTimers(Room room) { + if (room == null) { + return; + } + + Set tickables = roomTickables.get(room.getId()); + + if (tickables != null) { + for (WiredTickable tickable : tickables) { + try { + tickable.resetTimer(); + } catch (Exception e) { + LOGGER.error("Error resetting timer for tickable {} in room {}", + tickable.getId(), room.getId(), e); + } + } + } + } + + /** + * Gets the count of registered tickables for a room. + * + * @param roomId the room ID + * @return the count + */ + public int getTickableCount(int roomId) { + Set tickables = roomTickables.get(roomId); + return tickables != null ? tickables.size() : 0; + } + + /** + * Gets the total count of registered tickables across all rooms. + * + * @return the total count + */ + public int getTotalTickableCount() { + return roomTickables.values().stream() + .mapToInt(Set::size) + .sum(); + } + + /** + * Gets the count of rooms with registered tickables. + * + * @return the room count + */ + public int getActiveRoomCount() { + return roomTickables.size(); + } + + /** + * The main tick loop. + *

+ * Called at the configured interval by the scheduler. Processes all registered tickables + * across all rooms. + *

+ */ + private void tick() { + if (!running.get() || Emulator.isShuttingDown) { + return; + } + + // Increment global tick counter + tickCount++; + + long startTime = System.currentTimeMillis(); + int tickablesProcessed = 0; + + for (Map.Entry> entry : roomTickables.entrySet()) { + int roomId = entry.getKey(); + Set tickables = entry.getValue(); + + if (tickables.isEmpty()) { + continue; + } + + // Get the room - skip if not loaded + Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId); + if (room == null || !room.isLoaded()) { + continue; + } + + // Skip if room is empty (optimization) + if (room.getCurrentHabbos().isEmpty() && room.getCurrentBots().isEmpty()) { + continue; + } + + // Process each tickable + for (WiredTickable tickable : tickables) { + try { + // Verify item still belongs to this room + if (tickable.getRoomId() != roomId) { + // Item moved to another room, unregister it + tickables.remove(tickable); + continue; + } + + // Pass global tick count - all tickables see the same counter + // This keeps repeaters with the same interval perfectly synchronized + tickable.onWiredTick(room, tickCount, tickIntervalMs); + tickablesProcessed++; + } catch (Exception e) { + LOGGER.error("Error in wired tick for tickable {} in room {}: {}", + tickable.getId(), roomId, e.getMessage(), e); + } + } + } + + // Debug logging if enabled + if (debugEnabled && tickablesProcessed > 0) { + LOGGER.debug("Wired tick #{} completed: {} tickables processed in {}ms", + tickCount, tickablesProcessed, System.currentTimeMillis() - startTime); + } + } + + /** + * Gets the current global tick count. + * + * @return the tick count + */ + public long getTickCount() { + return tickCount; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/tick/WiredTickable.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/tick/WiredTickable.java new file mode 100644 index 00000000..aa2a0308 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/wired/tick/WiredTickable.java @@ -0,0 +1,99 @@ +package com.eu.habbo.habbohotel.wired.tick; + +import com.eu.habbo.habbohotel.rooms.Room; + +/** + * Interface for wired items that need to participate in the 50ms tick system. + *

+ * This replaces the old {@link com.eu.habbo.habbohotel.items.ICycleable} interface + * for wired items, providing higher-resolution timing (50ms vs 500ms) and + * centralized management. + *

+ * + *

Lifecycle:

+ *
    + *
  1. Item is placed in room or room is loaded
  2. + *
  3. {@link WiredTickService#register(Room, WiredTickable)} is called
  4. + *
  5. {@link #onWiredTick(Room, long)} is called every 50ms while registered
  6. + *
  7. Item is picked up or room unloaded
  8. + *
  9. {@link WiredTickService#unregister(Room, WiredTickable)} is called
  10. + *
+ * + * @see WiredTickService + */ +public interface WiredTickable { + + /** + * Called every tick by the WiredTickService. + *

+ * Implementations should check if they should fire based on the global tick count. + * For repeaters: fire when (tickCount * tickIntervalMs) % repeatTime == 0 + *

+ * + * @param room the room this item is in + * @param tickCount the global tick counter (increments each tick) + * @param tickIntervalMs the tick interval in milliseconds (e.g., 50) + */ + void onWiredTick(Room room, long tickCount, int tickIntervalMs); + + /** + * Called when the timer should be reset. + *

+ * This resets any internal counters so the timer starts fresh. + * Used when game is reset or user resets timers. + *

+ */ + void resetTimer(); + + /** + * Gets the unique identifier for this tickable item. + *

+ * Used for efficient registration/unregistration. + *

+ * + * @return the item ID + */ + int getId(); + + /** + * Gets the room ID this item belongs to. + * + * @return the room ID + */ + int getRoomId(); + + /** + * Checks if this is a one-shot timer (triggers once then stops) + * or a repeating timer. + * + * @return true if this timer only fires once per activation + */ + default boolean isOneShot() { + return false; + } + + /** + * Called when the item is first registered with the tick service. + *

+ * Can be used to initialize timing state. + *

+ * + * @param room the room + * @param currentTimeMillis current system time + */ + default void onRegistered(Room room, long currentTimeMillis) { + // Default: no-op + } + + /** + * Called when the item is unregistered from the tick service. + *

+ * Can be used to cleanup timing state. + *

+ * + * @param room the room + */ + default void onUnregistered(Room room) { + // Default: no-op + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/CatalogBuyItemAsGiftEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/CatalogBuyItemAsGiftEvent.java index fe4f25eb..f932c370 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/CatalogBuyItemAsGiftEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/CatalogBuyItemAsGiftEvent.java @@ -163,7 +163,6 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler { this.client.sendResponse(new AlertLimitedSoldOutComposer()); return; } - item.sellRare(); } int totalCredits = item.getCredits(); @@ -174,15 +173,30 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler { return; } - CatalogLimitedConfiguration limitedConfiguration; + CatalogLimitedConfiguration limitedConfiguration = null; int limitedStack = 0; int limitedNumber = 0; if (item.isLimited()) { - if (Emulator.getGameEnvironment().getCatalogManager().getLimitedConfig(item).available() == 0 && habbo != null) { - habbo.getClient().sendResponse(new AlertLimitedSoldOutComposer()); + if (Emulator.getGameEnvironment().getCatalogManager().getLimitedConfig(item).available() == 0) { + this.client.sendResponse(new AlertLimitedSoldOutComposer()); return; } + // Check daily LTD limits for the buyer (sender of the gift) + if (Emulator.getConfig().getBoolean("hotel.catalog.ltd.limit.enabled")) { + int ltdLimit = Emulator.getConfig().getInt("hotel.purchase.ltd.limit.daily.total"); + if (this.client.getHabbo().getHabboStats().totalLtds() >= ltdLimit) { + this.client.getHabbo().alert(Emulator.getTexts().getValue("error.catalog.buy.limited.daily.total").replace("%itemname%", item.getBaseItems().iterator().next().getFullName()).replace("%limit%", ltdLimit + "")); + return; + } + + ltdLimit = Emulator.getConfig().getInt("hotel.purchase.ltd.limit.daily.item"); + if (this.client.getHabbo().getHabboStats().totalLtds(item.getId()) >= ltdLimit) { + this.client.getHabbo().alert(Emulator.getTexts().getValue("error.catalog.buy.limited.daily.item").replace("%itemname%", item.getBaseItems().iterator().next().getFullName()).replace("%limit%", ltdLimit + "")); + return; + } + } + limitedConfiguration = Emulator.getGameEnvironment().getCatalogManager().getLimitedConfig(item); if (limitedConfiguration == null) { @@ -191,6 +205,9 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler { limitedNumber = limitedConfiguration.getNumber(); limitedStack = limitedConfiguration.getTotalSet(); + + // Log the LTD purchase for daily limits + this.client.getHabbo().getHabboStats().addLtdLog(item.getId(), Emulator.getIntUnixTimestamp()); } THashSet itemsList = new THashSet<>(); @@ -326,6 +343,13 @@ public class CatalogBuyItemAsGiftEvent extends MessageHandler { return; } + // Mark limited items as sold in the database to prevent duplication after catalog reload + if (limitedConfiguration != null) { + for (HabboItem itm : itemsList) { + limitedConfiguration.limitedSold(item.getId(), this.client.getHabbo(), itm); + } + } + if (this.client.getHabbo().getHabboInfo().getId() != userId) { AchievementManager.progressAchievement(this.client.getHabbo(), Emulator.getGameEnvironment().getAchievementManager().getAchievement("GiftGiver")); } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/CatalogSelectClubGiftEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/CatalogSelectClubGiftEvent.java index 57397944..e4d6794d 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/CatalogSelectClubGiftEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/CatalogSelectClubGiftEvent.java @@ -6,7 +6,7 @@ import com.eu.habbo.habbohotel.catalog.CatalogPage; import com.eu.habbo.habbohotel.catalog.CatalogPageLayouts; import com.eu.habbo.habbohotel.items.Item; import com.eu.habbo.messages.incoming.MessageHandler; -import com.eu.habbo.messages.outgoing.catalog.AlertPurchaseFailedComposer; +import com.eu.habbo.messages.outgoing.catalog.*; import com.eu.habbo.messages.outgoing.users.ClubGiftReceivedComposer; import gnu.trove.set.hash.THashSet; import org.slf4j.Logger; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/marketplace/SellItemEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/marketplace/SellItemEvent.java index 0e5a60e8..30fc10d8 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/marketplace/SellItemEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/catalog/marketplace/SellItemEvent.java @@ -22,7 +22,7 @@ public class SellItemEvent extends MessageHandler { int credits = this.packet.readInt(); - this.packet.readInt(); + this.packet.readInt(); // unknown - not used int itemId = this.packet.readInt(); HabboItem item = this.client.getHabbo().getInventory().getItemsComponent().getHabboItem(itemId); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/floorplaneditor/FloorPlanEditorSaveEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/floorplaneditor/FloorPlanEditorSaveEvent.java index c915a9e9..c2c90ed2 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/floorplaneditor/FloorPlanEditorSaveEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/floorplaneditor/FloorPlanEditorSaveEvent.java @@ -12,10 +12,7 @@ import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer; import com.eu.habbo.messages.outgoing.rooms.ForwardToRoomComposer; import gnu.trove.set.hash.THashSet; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.StringJoiner; +import java.util.*; public class FloorPlanEditorSaveEvent extends MessageHandler { public static int MAXIMUM_FLOORPLAN_WIDTH_LENGTH = 64; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/friends/FriendRequestEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/friends/FriendRequestEvent.java index 1365a154..f038e811 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/friends/FriendRequestEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/friends/FriendRequestEvent.java @@ -65,13 +65,13 @@ public class FriendRequestEvent extends MessageHandler { } // You can only have x friends - if (this.client.getHabbo().getMessenger().getFriends().size() >= this.client.getHabbo().getHabboStats().maxFriends && !this.client.getHabbo().hasPermission("acc_infinite_friends")) { + if (this.client.getHabbo().getMessenger().getFriends().values().size() >= this.client.getHabbo().getHabboStats().maxFriends && !this.client.getHabbo().hasPermission("acc_infinite_friends")) { this.client.sendResponse(new FriendRequestErrorComposer(FriendRequestErrorComposer.FRIEND_LIST_OWN_FULL)); return; } // Check if targets friendlist is full - if (targetHabbo.getMessenger().getFriends().size() >= targetHabbo.getHabboStats().maxFriends && !targetHabbo.hasPermission("acc_infinite_friends")) { + if (targetHabbo.getMessenger().getFriends().values().size() >= targetHabbo.getHabboStats().maxFriends && !targetHabbo.hasPermission("acc_infinite_friends")) { this.client.sendResponse(new FriendRequestErrorComposer(FriendRequestErrorComposer.FRIEND_LIST_TARGET_FULL)); return; } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/GuildAcceptMembershipEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/GuildAcceptMembershipEvent.java index e3d35743..068aba2e 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/GuildAcceptMembershipEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/GuildAcceptMembershipEvent.java @@ -28,11 +28,13 @@ public class GuildAcceptMembershipEvent extends MessageHandler { if (habbo != null) { if (habbo.getHabboStats().hasGuild(guild.getId())) { this.client.sendResponse(new GuildAcceptMemberErrorComposer(guild.getId(), GuildAcceptMemberErrorComposer.ALREADY_ACCEPTED)); + return; } else { //Check the user has requested GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guild, habbo); if (member == null || member.getRank().type != GuildRank.REQUESTED.type) { this.client.sendResponse(new GuildAcceptMemberErrorComposer(guild.getId(), GuildAcceptMemberErrorComposer.NO_LONGER_MEMBER)); + return; } else { GuildAcceptedMembershipEvent event = new GuildAcceptedMembershipEvent(guild, userId, habbo); Emulator.getPluginManager().fireEvent(event); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/GuildChangeBadgeEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/GuildChangeBadgeEvent.java index 0a43d25b..06e9d1b0 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/GuildChangeBadgeEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/GuildChangeBadgeEvent.java @@ -37,7 +37,7 @@ public class GuildChangeBadgeEvent extends MessageHandler { badge += "s"; } - badge += (id < 100 ? "0" : "") + (id < 10 ? "0" : "") + id + (color < 10 ? "0" : "") + color + pos; + badge += (id < 100 ? "0" : "") + (id < 10 ? "0" : "") + id + (color < 10 ? "0" : "") + color + "" + pos; base += 3; } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/RequestGuildBuyEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/RequestGuildBuyEvent.java index 2d66e557..96b73e6b 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/RequestGuildBuyEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/guilds/RequestGuildBuyEvent.java @@ -74,7 +74,7 @@ public class RequestGuildBuyEvent extends MessageHandler { badge += "s"; } - badge += (id < 100 ? "0" : "") + (id < 10 ? "0" : "") + id + (color < 10 ? "0" : "") + color + pos; + badge += (id < 100 ? "0" : "") + (id < 10 ? "0" : "") + id + (color < 10 ? "0" : "") + color + "" + pos; base += 3; } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/CompleteDiffieHandshakeEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/CompleteDiffieHandshakeEvent.java index 66f3cb2d..f4c218eb 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/CompleteDiffieHandshakeEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/CompleteDiffieHandshakeEvent.java @@ -5,9 +5,9 @@ import com.eu.habbo.crypto.HabboRC4; import com.eu.habbo.messages.NoAuthMessage; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.handshake.CompleteDiffieHandshakeComposer; -import com.eu.habbo.networking.gameserver.GameServerAttributes; import com.eu.habbo.networking.gameserver.decoders.GameByteDecryption; import com.eu.habbo.networking.gameserver.encoders.GameByteEncryption; +import com.eu.habbo.networking.gameserver.GameServerAttributes; @NoAuthMessage public class CompleteDiffieHandshakeEvent extends MessageHandler { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java index 251d681a..3df5ba20 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java @@ -19,18 +19,18 @@ import com.eu.habbo.messages.outgoing.gamecenter.GameCenterGameListComposer; import com.eu.habbo.messages.outgoing.generic.alerts.GenericAlertComposer; import com.eu.habbo.messages.outgoing.generic.alerts.MessagesForYouComposer; import com.eu.habbo.messages.outgoing.habboway.nux.NewUserIdentityComposer; -import com.eu.habbo.messages.outgoing.handshake.AvailabilityStatusMessageComposer; import com.eu.habbo.messages.outgoing.handshake.EnableNotificationsComposer; -import com.eu.habbo.messages.outgoing.handshake.PingComposer; import com.eu.habbo.messages.outgoing.handshake.SecureLoginOKComposer; +import com.eu.habbo.messages.outgoing.handshake.AvailabilityStatusMessageComposer; +import com.eu.habbo.messages.outgoing.handshake.PingComposer; import com.eu.habbo.messages.outgoing.inventory.InventoryAchievementsComposer; import com.eu.habbo.messages.outgoing.inventory.UserEffectsListComposer; import com.eu.habbo.messages.outgoing.modtool.CfhTopicsMessageComposer; import com.eu.habbo.messages.outgoing.modtool.ModToolComposer; import com.eu.habbo.messages.outgoing.modtool.ModToolSanctionInfoComposer; -import com.eu.habbo.messages.outgoing.mysterybox.MysteryBoxKeysComposer; -import com.eu.habbo.messages.outgoing.navigator.NewNavigatorSavedSearchesComposer; +import com.eu.habbo.messages.outgoing.navigator.*; import com.eu.habbo.messages.outgoing.unknown.BuildersClubExpiredComposer; +import com.eu.habbo.messages.outgoing.mysterybox.MysteryBoxKeysComposer; import com.eu.habbo.messages.outgoing.users.*; import com.eu.habbo.plugin.events.emulator.SSOAuthenticationEvent; import com.eu.habbo.plugin.events.users.UserLoginEvent; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/navigator/RequestDeleteRoomEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/navigator/RequestDeleteRoomEvent.java index ccf76b07..aaea208d 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/navigator/RequestDeleteRoomEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/navigator/RequestDeleteRoomEvent.java @@ -49,7 +49,8 @@ public class RequestDeleteRoomEvent extends MessageHandler { List pets = new ArrayList<>(room.getCurrentPets().valueCollection()); for (Pet pet : pets) { - if (pet instanceof RideablePet rideablePet) { + if (pet instanceof RideablePet) { + RideablePet rideablePet = (RideablePet) pet; if (rideablePet.getRider() != null) { rideablePet.getRider().getHabboInfo().dismountPet(true); } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestHeightmapEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestHeightmapEvent.java index e6311df8..980a1d5d 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestHeightmapEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestHeightmapEvent.java @@ -11,8 +11,12 @@ public class RequestHeightmapEvent extends MessageHandler { Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(this.client.getHabbo().getHabboInfo().getLoadingRoom()); if (room != null) { + // If room is loading in background, wait for it to complete + if (room.isLoadingInProgress()) { + room.waitForLoad(); + } + Emulator.getGameEnvironment().getRoomManager().enterRoom(this.client.getHabbo(), room); - } } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestRoomDataEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestRoomDataEvent.java index 0109a2b2..137c0444 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestRoomDataEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestRoomDataEvent.java @@ -15,6 +15,13 @@ public class RequestRoomDataEvent extends MessageHandler { if (room != null) { boolean unknown = something != 0 || something2 != 1; + // Start background loading of room data to reduce perceived load time + // This allows the room to start loading while the client is still processing the room info + if (room.isPreLoaded() && !room.isLoadedOrLoading()) { + room.startBackgroundLoad(); + } + + //this.client.getHabbo().getHabboInfo().getCurrentRoom() != room this.client.sendResponse(new RoomDataComposer(room, this.client.getHabbo(), true, unknown)); } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestRoomHeightmapEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestRoomHeightmapEvent.java index 294f2873..0d5acbbc 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestRoomHeightmapEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestRoomHeightmapEvent.java @@ -12,12 +12,19 @@ public class RequestRoomHeightmapEvent extends MessageHandler { if (this.client.getHabbo().getHabboInfo().getLoadingRoom() > 0) { Room room = Emulator.getGameEnvironment().getRoomManager().loadRoom(this.client.getHabbo().getHabboInfo().getLoadingRoom()); - if (room != null && room.getLayout() != null) { - this.client.sendResponse(new RoomRelativeMapComposer(room)); + if (room != null) { + // If room is loading in background, wait for it to complete + if (room.isLoadingInProgress()) { + room.waitForLoad(); + } + + if (room.getLayout() != null) { + this.client.sendResponse(new RoomRelativeMapComposer(room)); - this.client.sendResponse(new RoomHeightMapComposer(room)); + this.client.sendResponse(new RoomHeightMapComposer(room)); - Emulator.getGameEnvironment().getRoomManager().enterRoom(this.client.getHabbo(), room); + Emulator.getGameEnvironment().getRoomManager().enterRoom(this.client.getHabbo(), room); + } } } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestRoomLoadEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestRoomLoadEvent.java index 93c904be..b5cfffa9 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestRoomLoadEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RequestRoomLoadEvent.java @@ -11,8 +11,23 @@ public class RequestRoomLoadEvent extends MessageHandler { int roomId = this.packet.readInt(); String password = this.packet.readString(); + // Reset stale loadingRoom if timestamp has expired (indicates failed/stuck load) + if (this.client.getHabbo().getHabboInfo().getLoadingRoom() != 0 + && this.client.getHabbo().getHabboStats().roomEnterTimestamp + 5000 < System.currentTimeMillis()) { + this.client.getHabbo().getHabboInfo().setLoadingRoom(0); + } + if (this.client.getHabbo().getHabboInfo().getLoadingRoom() == 0 && this.client.getHabbo().getHabboStats().roomEnterTimestamp + 1000 < System.currentTimeMillis()) { + // Start background loading early to reduce perceived load time + Room roomToLoad = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId); + if (roomToLoad == null) { + roomToLoad = Emulator.getGameEnvironment().getRoomManager().loadRoom(roomId); + } + if (roomToLoad != null && roomToLoad.isPreLoaded() && !roomToLoad.isLoadedOrLoading()) { + roomToLoad.startBackgroundLoad(); + } + Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); if (room != null) { Emulator.getGameEnvironment().getRoomManager().logExit(this.client.getHabbo()); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RoomBackgroundEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RoomBackgroundEvent.java index ae5476f4..a53c8f1b 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RoomBackgroundEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RoomBackgroundEvent.java @@ -26,7 +26,7 @@ public class RoomBackgroundEvent extends MessageHandler { int saturation = this.packet.readInt(); int brightness = this.packet.readInt(); - FurnitureRoomTonerEvent event = Emulator.getPluginManager().fireEvent(new FurnitureRoomTonerEvent(item, this.client.getHabbo(), hue, saturation, brightness)); + FurnitureRoomTonerEvent event = (FurnitureRoomTonerEvent) Emulator.getPluginManager().fireEvent(new FurnitureRoomTonerEvent(item, this.client.getHabbo(), hue, saturation, brightness)); if (event.isCancelled()) return; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RoomSettingsSaveEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RoomSettingsSaveEvent.java index 4795045a..eda4253e 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RoomSettingsSaveEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/RoomSettingsSaveEvent.java @@ -10,6 +10,9 @@ import com.eu.habbo.messages.outgoing.rooms.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.HashSet; +import java.util.Set; + public class RoomSettingsSaveEvent extends MessageHandler { private static final Logger LOGGER = LoggerFactory.getLogger(RoomSettingsSaveEvent.class); @@ -55,6 +58,7 @@ public class RoomSettingsSaveEvent extends MessageHandler { int usersMax = this.packet.readInt(); int categoryId = this.packet.readInt(); StringBuilder tags = new StringBuilder(); + Set uniqueTags = new HashSet<>(); int count = Math.min(this.packet.readInt(), 2); for (int i = 0; i < count; i++) { String tag = this.packet.readString(); @@ -63,7 +67,10 @@ public class RoomSettingsSaveEvent extends MessageHandler { this.client.sendResponse(new RoomEditSettingsErrorComposer(room.getId(), RoomEditSettingsErrorComposer.TAGS_TOO_LONG, "")); return; } - tags.append(tag).append(";"); + if(!uniqueTags.contains(tag)) { + uniqueTags.add(tag); + tags.append(tag).append(";"); + } } if (!Emulator.getGameEnvironment().getWordFilter().filter(tags.toString(), this.client.getHabbo()).contentEquals(tags)) { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/PostItSaveDataEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/PostItSaveDataEvent.java index 629c2f70..24f2cbf7 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/PostItSaveDataEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/PostItSaveDataEvent.java @@ -12,13 +12,13 @@ import java.util.Arrays; import java.util.List; public class PostItSaveDataEvent extends MessageHandler { - private static final List COLORS = Arrays.asList("9CCEFF", "FF9CFF", "9CFF9C", "FFFF33"); + private static List COLORS = Arrays.asList("9CCEFF", "FF9CFF", "9CFF9C", "FFFF33"); @Override public void handle() throws Exception { int itemId = this.packet.readInt(); String color = this.packet.readString(); - String text = this.packet.readString().replace(((char) 9) + "", ""); + String text = Emulator.getGameEnvironment().getWordFilter().filter(this.packet.readString().replace(((char) 9) + "", ""), this.client.getHabbo()); if (text.length() > Emulator.getConfig().getInt("postit.charlimit")) { ScripterManager.scripterDetected(this.client, Emulator.getTexts().getValue("scripter.warning.sticky.size").replace("%username%", this.client.getHabbo().getHabboInfo().getUsername()).replace("%amount%", text.length() + "").replace("%limit%", Emulator.getConfig().getInt("postit.charlimit") + "")); @@ -51,7 +51,8 @@ public class PostItSaveDataEvent extends MessageHandler { if (color.isEmpty()) color = PostItColor.YELLOW.hexColor; - item.setUserId(room.getOwnerId()); + // Removed on Oct 15th, 2024: The owner of this item should not be altered when editing the text of a post-it. The original owner must always remain unchanged. + // item.setUserId(room.getOwnerId()); item.setExtradata(color + " " + text); item.needsUpdate(true); room.updateItem(item); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RoomPlaceItemEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RoomPlaceItemEvent.java index 516d0389..0f293184 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RoomPlaceItemEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/RoomPlaceItemEvent.java @@ -3,10 +3,7 @@ package com.eu.habbo.messages.incoming.rooms.items; import com.eu.habbo.habbohotel.items.FurnitureType; import com.eu.habbo.habbohotel.items.interactions.*; import com.eu.habbo.habbohotel.modtool.ScripterManager; -import com.eu.habbo.habbohotel.rooms.FurnitureMovementError; -import com.eu.habbo.habbohotel.rooms.Room; -import com.eu.habbo.habbohotel.rooms.RoomLayout; -import com.eu.habbo.habbohotel.rooms.RoomTile; +import com.eu.habbo.habbohotel.rooms.*; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/ToggleFloorItemEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/ToggleFloorItemEvent.java index 4bc70025..3cf6eec0 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/ToggleFloorItemEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/ToggleFloorItemEvent.java @@ -22,7 +22,7 @@ import java.util.HashSet; public class ToggleFloorItemEvent extends MessageHandler { private static final Logger LOGGER = LoggerFactory.getLogger(ToggleFloorItemEvent.class); - private static final HashSet PET_BOXES = new HashSet<>(Arrays.asList("val11_present", "gnome_box", "leprechaun_box", "velociraptor_egg", "pterosaur_egg", "petbox_epic")); + private static HashSet PET_BOXES = new HashSet<>(Arrays.asList("val11_present", "gnome_box", "leprechaun_box", "velociraptor_egg", "pterosaur_egg", "petbox_epic")); @Override public void handle() throws Exception { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/UseRandomStateItemEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/UseRandomStateItemEvent.java index 8978ed10..5c337f7d 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/UseRandomStateItemEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/UseRandomStateItemEvent.java @@ -20,9 +20,10 @@ public class UseRandomStateItemEvent extends MessageHandler { HabboItem item = room.getHabboItem(itemId); - if (item == null || !(item instanceof InteractionRandomState randomStateItem)) + if (item == null || !(item instanceof InteractionRandomState)) return; + InteractionRandomState randomStateItem = (InteractionRandomState)item; randomStateItem.onRandomStateClick(this.client, room); } catch (Exception e) { LOGGER.error("Caught exception", e); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylistChange.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylistChange.java index a30523c1..437298dd 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylistChange.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylistChange.java @@ -34,7 +34,7 @@ public class YoutubeRequestPlaylistChange extends MessageHandler { if (item == null || !(item instanceof InteractionYoutubeTV)) return; - Optional playlist = Emulator.getGameEnvironment().getItemManager().getYoutubeManager().getPlaylistsForItemId(itemId).stream().filter(p -> p.getId().equals(playlistId)).findAny(); + Optional playlist = Emulator.getGameEnvironment().getItemManager().getYoutubeManager().getPlaylistsForItemId(item.getBaseItem().getId()).stream().filter(p -> p.getId().equals(playlistId)).findAny(); if (playlist.isPresent()) { YoutubeManager.YoutubeVideo video = playlist.get().getVideos().get(0); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylists.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylists.java index 22b4ddfd..7121f58e 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylists.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestPlaylists.java @@ -22,9 +22,10 @@ public class YoutubeRequestPlaylists extends MessageHandler { if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != null) { HabboItem item = this.client.getHabbo().getHabboInfo().getCurrentRoom().getHabboItem(itemId); - if (item instanceof InteractionYoutubeTV tv) { + if (item instanceof InteractionYoutubeTV) { + InteractionYoutubeTV tv = (InteractionYoutubeTV) item; - ArrayList playlists = Emulator.getGameEnvironment().getItemManager().getYoutubeManager().getPlaylistsForItemId(itemId); + ArrayList playlists = Emulator.getGameEnvironment().getItemManager().getYoutubeManager().getPlaylistsForItemId(item.getBaseItem().getId()); if (playlists == null) { LOGGER.error("No YouTube playlists set for base item #{}", item.getBaseItem().getId()); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestStateChange.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestStateChange.java index a96ce978..fd3740a7 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestStateChange.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/items/youtube/YoutubeRequestStateChange.java @@ -18,7 +18,7 @@ public class YoutubeRequestStateChange extends MessageHandler { PAUSE(2), RESUME(3); - private final int state; + private int state; YoutubeState(int state) { this.state = state; @@ -64,7 +64,9 @@ public class YoutubeRequestStateChange extends MessageHandler { HabboItem item = this.client.getHabbo().getHabboInfo().getCurrentRoom().getHabboItem(itemId); - if (!(item instanceof InteractionYoutubeTV tv)) return; + if (!(item instanceof InteractionYoutubeTV)) return; + + InteractionYoutubeTV tv = (InteractionYoutubeTV) item; if(tv.currentPlaylist == null || tv.currentPlaylist.getVideos().isEmpty()) return; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/HorseRemoveSaddleEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/HorseRemoveSaddleEvent.java index 20a6a655..7f7c5b4e 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/HorseRemoveSaddleEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/HorseRemoveSaddleEvent.java @@ -26,7 +26,9 @@ public class HorseRemoveSaddleEvent extends MessageHandler { Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); Pet pet = room.getPet(this.packet.readInt()); - if (pet == null || !(pet instanceof HorsePet horse) || pet.getUserId() != this.client.getHabbo().getHabboInfo().getId()) return; + if (pet == null || !(pet instanceof HorsePet) || pet.getUserId() != this.client.getHabbo().getHabboInfo().getId()) return; + + HorsePet horse = (HorsePet) pet; if (!horse.hasSaddle()) return; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPickupEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPickupEvent.java index 168854d1..211375c3 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPickupEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetPickupEvent.java @@ -29,7 +29,8 @@ public class PetPickupEvent extends MessageHandler { return; } - if (pet instanceof RideablePet rideablePet) { + if (pet instanceof RideablePet) { + RideablePet rideablePet = (RideablePet) pet; if (rideablePet.getRider() != null) { rideablePet.getRider().getHabboInfo().dismountPet(true); } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetRideEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetRideEvent.java index 3663faa3..fd3a315a 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetRideEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetRideEvent.java @@ -23,9 +23,11 @@ public class PetRideEvent extends MessageHandler { Pet pet = room.getPet(petId); - if (!(pet instanceof RideablePet rideablePet)) + if (!(pet instanceof RideablePet)) return; + RideablePet rideablePet = (RideablePet) pet; + //dismount if (habbo.getHabboInfo().getRiding() != null) { habbo.getHabboInfo().dismountPet(); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetRideSettingsEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetRideSettingsEvent.java index 4278f915..791a628b 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetRideSettingsEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetRideSettingsEvent.java @@ -16,9 +16,11 @@ public class PetRideSettingsEvent extends MessageHandler { Pet pet = this.client.getHabbo().getHabboInfo().getCurrentRoom().getPet(petId); - if (pet == null || pet.getUserId() != this.client.getHabbo().getHabboInfo().getId() || !(pet instanceof RideablePet rideablePet)) + if (pet == null || pet.getUserId() != this.client.getHabbo().getHabboInfo().getId() || !(pet instanceof RideablePet)) return; + RideablePet rideablePet = ((RideablePet) pet); + rideablePet.setAnyoneCanRide(!rideablePet.anyoneCanRide()); rideablePet.needsUpdate = true; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetUseItemEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetUseItemEvent.java index 407f8ca2..126343b9 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetUseItemEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/pets/PetUseItemEvent.java @@ -44,7 +44,7 @@ public class PetUseItemEvent extends MessageHandler { raceType = 0; pet.setRace(raceType); - pet.needsUpdate = true; + ((HorsePet) pet).needsUpdate = true; } else if (item.getBaseItem().getName().toLowerCase().startsWith("horse_hairdye")) { int splittedHairdye = Integer.parseInt(item.getBaseItem().getName().toLowerCase().split("_")[2]); int newHairdye = 48; @@ -60,7 +60,7 @@ public class PetUseItemEvent extends MessageHandler { } ((HorsePet) pet).setHairColor(newHairdye); - pet.needsUpdate = true; + ((HorsePet) pet).needsUpdate = true; } else if (item.getBaseItem().getName().toLowerCase().startsWith("horse_hairstyle")) { int splittedHairstyle = Integer.parseInt(item.getBaseItem().getName().toLowerCase().split("_")[2]); int newHairstyle = 100; @@ -72,14 +72,14 @@ public class PetUseItemEvent extends MessageHandler { } ((HorsePet) pet).setHairStyle(newHairstyle); - pet.needsUpdate = true; + ((HorsePet) pet).needsUpdate = true; } else if (item.getBaseItem().getName().toLowerCase().startsWith("horse_saddle")) { ((HorsePet) pet).hasSaddle(true); ((HorsePet) pet).setSaddleItemId(item.getBaseItem().getId()); - pet.needsUpdate = true; + ((HorsePet) pet).needsUpdate = true; } - if (pet.needsUpdate) { + if (((HorsePet) pet).needsUpdate) { Emulator.getThreading().run(pet); this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new RoomPetHorseFigureComposer((HorsePet) pet).compose()); @@ -94,7 +94,7 @@ public class PetUseItemEvent extends MessageHandler { ((MonsterplantPet) pet).setDeathTimestamp(Emulator.getIntUnixTimestamp() + MonsterplantPet.timeToLive); pet.getRoomUnit().clearStatus(); pet.getRoomUnit().setStatus(RoomUnitStatus.GESTURE, "rev"); - pet.packetUpdate = true; + ((MonsterplantPet) pet).packetUpdate = true; this.client.getHabbo().getHabboInfo().getCurrentRoom().removeHabboItem(item); this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new RemoveFloorItemComposer(item).compose()); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserGiveRespectEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserGiveRespectEvent.java index d2a46a40..c693d883 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserGiveRespectEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserGiveRespectEvent.java @@ -10,6 +10,10 @@ public class RoomUserGiveRespectEvent extends MessageHandler { public void handle() throws Exception { int userId = this.packet.readInt(); + if (userId == client.getHabbo().getHabboInfo().getId()) { + return; + } + if (this.client.getHabbo().getHabboStats().respectPointsToGive > 0) { Habbo target = this.client.getHabbo().getHabboInfo().getCurrentRoom().getHabbo(userId); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserWalkEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserWalkEvent.java index beb385f7..a83a418d 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserWalkEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserWalkEvent.java @@ -7,6 +7,7 @@ import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.habbohotel.rooms.RoomUnitStatus; import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.habbohotel.users.HabboInfo; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.rooms.users.RoomUnitOnRollerComposer; @@ -16,150 +17,183 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class RoomUserWalkEvent extends MessageHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(RoomUserWalkEvent.class); - @Override - public int getRatelimit() { - return 500; + private static final Logger LOGGER = LoggerFactory.getLogger(RoomUserWalkEvent.class); + public static final String CONTROL_KEY = "control"; + + @Override + public int getRatelimit() { + return Emulator.getConfig().getInt("pathfinder.click.delay", 0); + } + + @Override + public void handle() throws Exception { + if (this.client.getHabbo().getHabboInfo().getCurrentRoom() == null) { + return; } - @Override - public void handle() throws Exception { - if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != null) { - int x = this.packet.readInt(); // Position X - int y = this.packet.readInt(); // Position Y + int x = this.packet.readInt(); // Position X + int y = this.packet.readInt(); // Position Y - // Get Habbo object - Habbo habbo = this.client.getHabbo(); + Habbo habbo = getControlledHabbo(); + if (habbo == null) { + return; + } - // Get Room Habbo object (Unique GUID?) - RoomUnit roomUnit = this.client.getHabbo().getRoomUnit(); + RoomUnit roomUnit = habbo.getRoomUnit(); + HabboInfo habboInfo = habbo.getHabboInfo(); + Room room = habboInfo.getCurrentRoom(); - // If habbo is teleporting, dont calculate a new path - if (roomUnit.isTeleporting) - return; - - // If habbo is being kicked dont calculate a new path - if (roomUnit.isKicked) - return; - - // If habbo has control (im assuming admin, do something else, but we dont care about this part here) - if (roomUnit.getCacheable().get("control") != null) { - habbo = (Habbo) roomUnit.getCacheable().get("control"); - - if (habbo.getHabboInfo().getCurrentRoom() != this.client.getHabbo().getHabboInfo().getCurrentRoom()) { - habbo.getRoomUnit().getCacheable().remove("controller"); - this.client.getHabbo().getRoomUnit().getCacheable().remove("control"); - habbo = this.client.getHabbo(); - } - } - - // Get room unit? - roomUnit = habbo.getRoomUnit(); - - // Get the room the habbo is in - Room room = habbo.getHabboInfo().getCurrentRoom(); - - try { - // If our room unit is not nullptr and we are in a room and we can walk, then calculate a new path - if (roomUnit != null && roomUnit.isInRoom() && roomUnit.canWalk()) { - // If we are not teleporting calcualte a new path - if (!roomUnit.cmdTeleport) { - // Don't calculate a new path if we are on a horse - if (habbo.getHabboInfo().getRiding() != null && habbo.getHabboInfo().getRiding().getTask() != null && habbo.getHabboInfo().getRiding().getTask().equals(PetTasks.JUMP)) - return; - - // Don't calulcate a new path if are already at the end position - if (x == roomUnit.getX() && y == roomUnit.getY()) - return; - - if (room == null || room.getLayout() == null) - return; - - // Reset idle status - if (roomUnit.isIdle()) { - UserIdleEvent event = new UserIdleEvent(habbo, UserIdleEvent.IdleReason.WALKED, false); - Emulator.getPluginManager().fireEvent(event); - - if (!event.isCancelled()) { - if (!event.idle) { - if (roomUnit.getRoom() != null) roomUnit.getRoom().unIdle(habbo); - roomUnit.resetIdleTimer(); - } - } - } - - // Get room height map - RoomTile tile = room.getLayout().getTile((short) x, (short) y); - - // this should never happen, if it does it would be a design flaw - if (tile == null) { - return; - } - - // Don't care - if (habbo.getRoomUnit().hasStatus(RoomUnitStatus.LAY) || habbo.getRoomUnit().hasStatus(RoomUnitStatus.SNOWWAR_PICK) || habbo.getRoomUnit().hasStatus(RoomUnitStatus.SNOWWAR_DIE_FRONT) || habbo.getRoomUnit().hasStatus(RoomUnitStatus.SNOWWAR_DIE_BACK)) { - if (room.getLayout().getTilesInFront(habbo.getRoomUnit().getCurrentLocation(), habbo.getRoomUnit().getBodyRotation().getValue(), 2).contains(tile)) - return; - } - if (room.canLayAt(tile.x, tile.y)) { - HabboItem bed = room.getTopItemAt(tile.x, tile.y); - - if (bed != null && bed.getBaseItem().allowLay()) { - RoomTile pillow = room.getLayout().getTile(bed.getX(), bed.getY()); - switch (bed.getRotation()) { - case 0: - case 4: - pillow = room.getLayout().getTile((short) x, bed.getY()); - break; - case 2: - case 8: - pillow = room.getLayout().getTile(bed.getX(), (short) y); - break; - } - - if (pillow != null && room.canLayAt(pillow.x, pillow.y)) { - roomUnit.setGoalLocation(pillow); - return; - } - } - } - - THashSet items = room.getItemsAt(tile); - - if (items.size() > 0) { - for (HabboItem item : items) { - RoomTile overriddenTile = item.getOverrideGoalTile(roomUnit, room, tile); - - if (overriddenTile == null) { - return; // null cancels the entire event - } - - if (!overriddenTile.equals(tile) && overriddenTile.isWalkable()) { - tile = overriddenTile; - break; - } - } - } - - // This is where we set the end location and begin finding a path - if (tile.isWalkable() || room.canSitOrLayAt(tile.x, tile.y)) { - if (roomUnit.getMoveBlockingTask() != null) roomUnit.getMoveBlockingTask().get(); - - roomUnit.setGoalLocation(tile); - } - } else { - RoomTile t = room.getLayout().getTile((short) x, (short) y); - room.sendComposer(new RoomUnitOnRollerComposer(roomUnit, t, room).compose()); - - if (habbo.getHabboInfo().getRiding() != null) { - room.sendComposer(new RoomUnitOnRollerComposer(habbo.getHabboInfo().getRiding().getRoomUnit(), t, room).compose()); - } - } - } - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } + try { + if (roomUnit != null && roomUnit.isInRoom() && roomUnit.canWalk()) { + if (roomUnit.cmdTeleport) { + handleTeleport(room, (short) x, (short) y, roomUnit, habboInfo); + return; } + + // Don't calculate a new path if we are on a horse + if (habboInfo.getRiding() != null && habboInfo.getRiding().getTask() != null + && habboInfo.getRiding().getTask().equals(PetTasks.JUMP)) { + return; + } + + // Don't calulcate a new path if are already at the end position + if (x == roomUnit.getX() && y == roomUnit.getY()) { + return; + } + + if (room == null || room.getLayout() == null) { + return; + } + + if (roomUnit.isIdle()) { + fireIdleEvent(habbo, roomUnit); + } + + RoomTile tile = room.getLayout().getTile((short) x, (short) y); + + // this should never happen, if it does it would be a design flaw + if (tile == null) { + return; + } + + if (habbo.getRoomUnit().hasStatus(RoomUnitStatus.LAY) && room.getLayout() + .getTilesInFront(habbo.getRoomUnit().getCurrentLocation(), + habbo.getRoomUnit().getBodyRotation().getValue(), 2).contains(tile)) { + return; + } + + if (room.canLayAt(tile.x, tile.y) && handleLay(room, tile, (short) x, (short) y, + roomUnit)) { + return; + } + + THashSet items = room.getItemsAt(tile); + + if (!items.isEmpty()) { + for (HabboItem item : items) { + RoomTile overriddenTile = item.getOverrideGoalTile(roomUnit, room, tile); + + if (overriddenTile == null) { + return; // null cancels the entire event + } + + if (!overriddenTile.equals(tile) && overriddenTile.isWalkable()) { + tile = overriddenTile; + break; + } + } + } + + // This is where we set the end location and begin finding a path + if (tile.isWalkable() || room.canSitOrLayAt(tile.x, tile.y)) { + if (roomUnit.getMoveBlockingTask() != null) { + roomUnit.getMoveBlockingTask().get(); + } + + roomUnit.setGoalLocation(tile); + } + } + } catch (Exception e) { + LOGGER.error("Caught exception", e); } + } + + private static boolean handleLay(Room room, RoomTile tile, short x, short y, RoomUnit roomUnit) { + HabboItem bed = room.getTopItemAt(tile.x, tile.y); + + if (bed != null && bed.getBaseItem().allowLay()) { + RoomTile pillow = getPillow(room, x, y, bed); + + if (pillow != null && room.canLayAt(pillow.x, pillow.y)) { + roomUnit.setGoalLocation(pillow); + return true; + } + } + return false; + } + + private static RoomTile getPillow(Room room, short x, short y, HabboItem bed) { + RoomTile pillow = room.getLayout().getTile(bed.getX(), bed.getY()); + switch (bed.getRotation()) { + case 0: + case 4: + pillow = room.getLayout().getTile(x, bed.getY()); + break; + case 2: + case 8: + pillow = room.getLayout().getTile(bed.getX(), y); + break; + } + return pillow; + } + + private static void fireIdleEvent(Habbo habbo, RoomUnit roomUnit) { + UserIdleEvent event = new UserIdleEvent(habbo, UserIdleEvent.IdleReason.WALKED, false); + Emulator.getPluginManager().fireEvent(event); + + if (!event.isCancelled() && !event.idle) { + if (roomUnit.getRoom() != null) { + roomUnit.getRoom().unIdle(habbo); + } + roomUnit.resetIdleTimer(); + } + } + + private static void handleTeleport(Room room, short x, short y, RoomUnit roomUnit, + HabboInfo habboInfo) { + RoomTile t = room.getLayout().getTile(x, y); + room.sendComposer(new RoomUnitOnRollerComposer(roomUnit, t, room).compose()); + + if (habboInfo.getRiding() != null) { + room.sendComposer( + new RoomUnitOnRollerComposer(habboInfo.getRiding().getRoomUnit(), t, room).compose()); + } + } + + private Habbo getControlledHabbo() { + Habbo habbo = this.client.getHabbo(); + + RoomUnit roomUnit = this.client.getHabbo().getRoomUnit(); + + if (roomUnit.isTeleporting) { + return null; + } + + if (roomUnit.isKicked) { + return null; + } + + if (roomUnit.getCacheable().get(CONTROL_KEY) != null) { + habbo = (Habbo) roomUnit.getCacheable().get(CONTROL_KEY); + + if (habbo.getHabboInfo().getCurrentRoom() != this.client.getHabbo().getHabboInfo() + .getCurrentRoom()) { + habbo.getRoomUnit().getCacheable().remove("controller"); + this.client.getHabbo().getRoomUnit().getCacheable().remove(CONTROL_KEY); + habbo = this.client.getHabbo(); + } + } + return habbo; + } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredApplySetConditionsEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredApplySetConditionsEvent.java index ca03346c..1d9266f3 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredApplySetConditionsEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredApplySetConditionsEvent.java @@ -56,7 +56,9 @@ public class WiredApplySetConditionsEvent extends MessageHandler { HabboItem wiredItem = item.get(); // The item should have settings to match furni state, position and rotation - if (wiredItem instanceof InteractionWiredMatchFurniSettings wired) { + if (wiredItem instanceof InteractionWiredMatchFurniSettings) { + + InteractionWiredMatchFurniSettings wired = (InteractionWiredMatchFurniSettings) wiredItem; // Try to apply the set settings to each item wired.getMatchFurniSettings().forEach(setting -> { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredConditionSaveDataEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredConditionSaveDataEvent.java index 92742f84..b5e51094 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredConditionSaveDataEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredConditionSaveDataEvent.java @@ -6,6 +6,7 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredCondition; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.permissions.Permission; import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.generic.alerts.UpdateFailedComposer; import com.eu.habbo.messages.outgoing.wired.WiredSavedComposer; @@ -39,6 +40,9 @@ public class WiredConditionSaveDataEvent extends MessageHandler { condition.needsUpdate(true); Emulator.getThreading().run(condition); + + // Invalidate wired cache when condition is saved + WiredManager.invalidateRoom(room); } else { this.client.sendResponse(new UpdateFailedComposer("There was an error while saving that condition")); } @@ -47,6 +51,9 @@ public class WiredConditionSaveDataEvent extends MessageHandler { this.client.sendResponse(new WiredSavedComposer()); condition.needsUpdate(true); Emulator.getThreading().run(condition); + + // Invalidate wired cache when condition is saved + WiredManager.invalidateRoom(room); } else { this.client.sendResponse(new UpdateFailedComposer("There was an error while saving that condition")); } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredEffectSaveDataEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredEffectSaveDataEvent.java index e6e46c9f..aac7ec7a 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredEffectSaveDataEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredEffectSaveDataEvent.java @@ -6,6 +6,7 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredEffect; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.permissions.Permission; import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.generic.alerts.UpdateFailedComposer; import com.eu.habbo.messages.outgoing.wired.WiredSavedComposer; @@ -38,6 +39,9 @@ public class WiredEffectSaveDataEvent extends MessageHandler { this.client.sendResponse(new WiredSavedComposer()); effect.needsUpdate(true); Emulator.getThreading().run(effect); + + // Invalidate wired cache when effect is saved + WiredManager.invalidateRoom(room); } } else { @@ -45,6 +49,9 @@ public class WiredEffectSaveDataEvent extends MessageHandler { this.client.sendResponse(new WiredSavedComposer()); effect.needsUpdate(true); Emulator.getThreading().run(effect); + + // Invalidate wired cache when effect is saved + WiredManager.invalidateRoom(room); } } } else { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredTriggerSaveDataEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredTriggerSaveDataEvent.java index 611de232..83da31e5 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredTriggerSaveDataEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/wired/WiredTriggerSaveDataEvent.java @@ -6,6 +6,7 @@ import com.eu.habbo.habbohotel.items.interactions.InteractionWiredTrigger; import com.eu.habbo.habbohotel.items.interactions.wired.WiredSettings; import com.eu.habbo.habbohotel.permissions.Permission; import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.generic.alerts.UpdateFailedComposer; import com.eu.habbo.messages.outgoing.wired.WiredSavedComposer; @@ -39,6 +40,9 @@ public class WiredTriggerSaveDataEvent extends MessageHandler { trigger.needsUpdate(true); Emulator.getThreading().run(trigger); + + // Invalidate wired cache when trigger is saved + WiredManager.invalidateRoom(room); } else { this.client.sendResponse(new UpdateFailedComposer("There was an error while saving that trigger")); } @@ -47,6 +51,9 @@ public class WiredTriggerSaveDataEvent extends MessageHandler { this.client.sendResponse(new WiredSavedComposer()); trigger.needsUpdate(true); Emulator.getThreading().run(trigger); + + // Invalidate wired cache when trigger is saved + WiredManager.invalidateRoom(room); } else { this.client.sendResponse(new UpdateFailedComposer("There was an error while saving that trigger")); } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/achievements/AchievementListComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/achievements/AchievementListComposer.java index d8dd1c70..c992603c 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/achievements/AchievementListComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/achievements/AchievementListComposer.java @@ -46,7 +46,7 @@ public class AchievementListComposer extends MessageComposer { this.response.appendInt(Math.max(achievementProgress, 0)); //Current progress this.response.appendBoolean(AchievementManager.hasAchieved(this.habbo, achievement)); //Achieved? (Current Progress == MaxLevel.Progress) this.response.appendString(achievement.category.toString().toLowerCase()); //Category - this.response.appendString(""); //Empty, completely unused in client code + this.response.appendString(""); //Empty, completly unused in client code this.response.appendInt(achievement.levels.size()); //Count of total levels in this achievement this.response.appendInt(AchievementManager.hasAchieved(this.habbo, achievement) ? 1 : 0); //1 = Progressbar visible if the achievement is completed } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/catalog/RecyclerLogicComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/catalog/RecyclerLogicComposer.java index 888e069a..8d1890a2 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/catalog/RecyclerLogicComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/catalog/RecyclerLogicComposer.java @@ -16,7 +16,7 @@ public class RecyclerLogicComposer extends MessageComposer { this.response.appendInt(Emulator.getGameEnvironment().getCatalogManager().prizes.size()); for (Map.Entry> map : Emulator.getGameEnvironment().getCatalogManager().prizes.entrySet()) { this.response.appendInt(map.getKey()); - this.response.appendInt(Integer.parseInt(Emulator.getConfig().getValue("hotel.ecotron.rarity.chance." + map.getKey()))); + this.response.appendInt(Integer.valueOf(Emulator.getConfig().getValue("hotel.ecotron.rarity.chance." + map.getKey()))); this.response.appendInt(map.getValue().size()); for (Item item : map.getValue()) { this.response.appendString(item.getName()); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/friends/UpdateFriendComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/friends/UpdateFriendComposer.java index 226f2bce..96cd5090 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/friends/UpdateFriendComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/friends/UpdateFriendComposer.java @@ -1,5 +1,9 @@ package com.eu.habbo.messages.outgoing.friends; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + import com.eu.habbo.habbohotel.messenger.MessengerBuddy; import com.eu.habbo.habbohotel.messenger.MessengerCategory; import com.eu.habbo.habbohotel.users.Habbo; @@ -8,15 +12,11 @@ import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.outgoing.MessageComposer; import com.eu.habbo.messages.outgoing.Outgoing; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - public class UpdateFriendComposer extends MessageComposer { - private final Collection buddies; + private Collection buddies; - private final Habbo habbo; - private final int action; + private Habbo habbo; + private int action; public UpdateFriendComposer(Habbo habbo, MessengerBuddy buddy, Integer action) { this.habbo = habbo; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/guilds/GuildConfirmRemoveMemberComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/guilds/GuildConfirmRemoveMemberComposer.java index 65d61c77..b52971f8 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/guilds/GuildConfirmRemoveMemberComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/guilds/GuildConfirmRemoveMemberComposer.java @@ -5,8 +5,8 @@ import com.eu.habbo.messages.outgoing.MessageComposer; import com.eu.habbo.messages.outgoing.Outgoing; public class GuildConfirmRemoveMemberComposer extends MessageComposer { - private final int userId; - private final int furniCount; + private int userId; + private int furniCount; public GuildConfirmRemoveMemberComposer(int userId, int furniCount) { this.userId = userId; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/guilds/GuildEditFailComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/guilds/GuildEditFailComposer.java index 3db0989a..c122f434 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/guilds/GuildEditFailComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/guilds/GuildEditFailComposer.java @@ -10,7 +10,7 @@ public class GuildEditFailComposer extends MessageComposer { public static final int HC_REQUIRED = 2; public static final int MAX_GUILDS_JOINED = 3; - private final int errorCode; + private int errorCode; public GuildEditFailComposer(int errorCode) { this.errorCode = errorCode; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/guilds/GuildFavoriteRoomUserUpdateComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/guilds/GuildFavoriteRoomUserUpdateComposer.java index e19f042c..3a7a3071 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/guilds/GuildFavoriteRoomUserUpdateComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/guilds/GuildFavoriteRoomUserUpdateComposer.java @@ -7,8 +7,8 @@ import com.eu.habbo.messages.outgoing.MessageComposer; import com.eu.habbo.messages.outgoing.Outgoing; public class GuildFavoriteRoomUserUpdateComposer extends MessageComposer { - private final RoomUnit roomUnit; - private final Guild guild; + private RoomUnit roomUnit; + private Guild guild; public GuildFavoriteRoomUserUpdateComposer(RoomUnit roomUnit, Guild guild) { this.roomUnit = roomUnit; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/guilds/RemoveGuildFromRoomComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/guilds/RemoveGuildFromRoomComposer.java index 4b05b2a4..d401d349 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/guilds/RemoveGuildFromRoomComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/guilds/RemoveGuildFromRoomComposer.java @@ -5,7 +5,7 @@ import com.eu.habbo.messages.outgoing.MessageComposer; import com.eu.habbo.messages.outgoing.Outgoing; public class RemoveGuildFromRoomComposer extends MessageComposer { - private final int guildId; + private int guildId; public RemoveGuildFromRoomComposer(int guildId) { this.guildId = guildId; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/inventory/InventoryItemsComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/inventory/InventoryItemsComposer.java index 6ad0ba90..0c80ca4f 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/inventory/InventoryItemsComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/inventory/InventoryItemsComposer.java @@ -91,7 +91,7 @@ public class InventoryItemsComposer extends MessageComposer implements TIntObjec this.response.appendString(""); if(habboItem.getBaseItem().getName().equals("song_disk")) { List extraDataAsList = Arrays.asList(habboItem.getExtradata().split("\n")); - this.response.appendInt(Integer.parseInt(extraDataAsList.get(extraDataAsList.size() - 1))); + this.response.appendInt(Integer.valueOf(extraDataAsList.get(extraDataAsList.size() - 1))); return true; } this.response.appendInt(habboItem instanceof InteractionGift ? ((((InteractionGift) habboItem).getColorId() * 1000) + ((InteractionGift) habboItem).getRibbonId()) : 1); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/modtool/ModToolUserInfoComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/modtool/ModToolUserInfoComposer.java index adffcdff..5eba2421 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/modtool/ModToolUserInfoComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/modtool/ModToolUserInfoComposer.java @@ -11,10 +11,7 @@ import gnu.trove.map.hash.THashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; +import java.sql.*; import java.util.ArrayList; public class ModToolUserInfoComposer extends MessageComposer { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/items/FloorItemUpdateComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/items/FloorItemUpdateComposer.java index 8a138615..5ce45b95 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/items/FloorItemUpdateComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/items/FloorItemUpdateComposer.java @@ -18,7 +18,7 @@ public class FloorItemUpdateComposer extends MessageComposer { protected ServerMessage composeInternal() { this.response.init(Outgoing.FloorItemUpdateComposer); this.item.serializeFloorData(this.response); - this.response.appendInt(this.item instanceof InteractionGift ? ((((InteractionGift) this.item).getColorId() * 1000) + ((InteractionGift) this.item).getRibbonId()) : (this.item instanceof InteractionMusicDisc ? ((InteractionMusicDisc) this.item).getSongId() : 0)); + this.response.appendInt(this.item instanceof InteractionGift ? ((((InteractionGift) this.item).getColorId() * 1000) + ((InteractionGift) this.item).getRibbonId()) : (this.item instanceof InteractionMusicDisc ? ((InteractionMusicDisc) this.item).getSongId() : item.isUsable() ? 0 : 0)); this.item.serializeExtradata(this.response); this.response.appendInt(-1); this.response.appendInt(0); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUnitOnRollerComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUnitOnRollerComposer.java index 0a24db89..f2f54bfd 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUnitOnRollerComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUnitOnRollerComposer.java @@ -1,10 +1,13 @@ package com.eu.habbo.messages.outgoing.rooms.users; import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.bots.Bot; import com.eu.habbo.habbohotel.items.interactions.InteractionRoller; +import com.eu.habbo.habbohotel.pets.Pet; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.rooms.RoomUnitType; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.outgoing.MessageComposer; @@ -52,6 +55,24 @@ public class RoomUnitOnRollerComposer extends MessageComposer { if (!this.room.isLoaded()) return null; + // Early validation: Check if the roller movement is still valid before composing the packet + if (this.roller != null && room.getLayout() != null) { + // Check if the destination tile is blocked by another unit that moved there + if (this.newLocation.hasUnits() && !this.newLocation.getUnits().contains(this.roomUnit)) { + return null; + } + + // Check if the unit is still at the expected old location (they might have walked away) + if (this.roomUnit.getCurrentLocation() != this.oldLocation) { + return null; + } + + // Check if the unit started walking (user input should take priority over rollers) + if (this.roomUnit.isWalking()) { + return null; + } + } + this.response.init(Outgoing.ObjectOnRollerComposer); this.response.appendInt(this.oldLocation.x); this.response.appendInt(this.oldLocation.y); @@ -65,14 +86,30 @@ public class RoomUnitOnRollerComposer extends MessageComposer { this.response.appendString(this.newZ + ""); if (this.roller != null && room.getLayout() != null) { - // Update location immediately to prevent desync issues where the unit gets + // Mark the unit as recently rolled to prevent desync/bungie effect + this.roomUnit.setLastRollerTime(System.currentTimeMillis()); + + // Update location immediately to prevent desync issues where the unit gets // "stuck" rolling because subsequent roller cycles see the unit at the old position if (!this.roomUnit.isWalking() && this.roomUnit.getCurrentLocation() == this.oldLocation) { this.roomUnit.setLocation(this.newLocation); this.roomUnit.setZ(this.newZ); this.roomUnit.setPreviousLocationZ(this.newZ); + + // Mark bots and pets for database update when moved by rollers + if (this.roomUnit.getRoomUnitType() == RoomUnitType.BOT) { + Bot bot = this.room.getBot(this.roomUnit); + if (bot != null) { + bot.needsUpdate(true); + } + } else if (this.roomUnit.getRoomUnitType() == RoomUnitType.PET) { + Pet pet = this.room.getPet(this.roomUnit); + if (pet != null) { + pet.needsUpdate = true; + } + } } - + // Delay the walk on/off events to allow the visual animation to complete Emulator.getThreading().run(() -> { if (!this.roomUnit.isWalking()) { diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserActionComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserActionComposer.java index 8f62cde1..b1e6d942 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserActionComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserActionComposer.java @@ -7,8 +7,8 @@ import com.eu.habbo.messages.outgoing.MessageComposer; import com.eu.habbo.messages.outgoing.Outgoing; public class RoomUserActionComposer extends MessageComposer { - private final RoomUserAction action; - private final RoomUnit roomUnit; + private RoomUserAction action; + private RoomUnit roomUnit; public RoomUserActionComposer(RoomUnit roomUnit, RoomUserAction action) { this.roomUnit = roomUnit; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserDataComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserDataComposer.java index 47e16184..3ed858d3 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserDataComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserDataComposer.java @@ -17,12 +17,13 @@ public class RoomUserDataComposer extends MessageComposer { this.response.init(Outgoing.RoomUserDataComposer); this.response.appendInt(this.habbo.getRoomUnit() == null ? -1 : this.habbo.getRoomUnit().getId()); this.response.appendString(this.habbo.getHabboInfo().getLook()); - this.response.appendString(this.habbo.getHabboInfo().getGender().name()); + this.response.appendString(this.habbo.getHabboInfo().getGender().name() + ""); this.response.appendString(this.habbo.getHabboInfo().getMotto()); this.response.appendInt(this.habbo.getHabboStats().getAchievementScore()); - this.response.appendInt(this.habbo.getHabboInfo().getInfostandBg()); - this.response.appendInt(this.habbo.getHabboInfo().getInfostandStand()); - this.response.appendInt(this.habbo.getHabboInfo().getInfostandOverlay()); return this.response; } + + public Habbo getHabbo() { + return habbo; + } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserReceivedHandItemComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserReceivedHandItemComposer.java index a2d6f04e..adf3ea2d 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserReceivedHandItemComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserReceivedHandItemComposer.java @@ -6,8 +6,8 @@ import com.eu.habbo.messages.outgoing.MessageComposer; import com.eu.habbo.messages.outgoing.Outgoing; public class RoomUserReceivedHandItemComposer extends MessageComposer { - private final RoomUnit from; - private final int handItem; + private RoomUnit from; + private int handItem; public RoomUserReceivedHandItemComposer(RoomUnit from, int handItem) { this.from = from; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserShoutComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserShoutComposer.java index b45c8b6b..840229fa 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserShoutComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/users/RoomUserShoutComposer.java @@ -6,7 +6,7 @@ import com.eu.habbo.messages.outgoing.MessageComposer; import com.eu.habbo.messages.outgoing.Outgoing; public class RoomUserShoutComposer extends MessageComposer { - private final RoomChatMessage roomChatMessage; + private RoomChatMessage roomChatMessage; public RoomUserShoutComposer(RoomChatMessage roomChatMessage) { this.roomChatMessage = roomChatMessage; diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/users/UserProfileComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/users/UserProfileComposer.java index 044af5b7..c58fbe75 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/users/UserProfileComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/users/UserProfileComposer.java @@ -26,7 +26,7 @@ public class UserProfileComposer extends MessageComposer { private final HabboInfo habboInfo; private Habbo habbo; - private final GameClient viewer; + private GameClient viewer; public UserProfileComposer(HabboInfo habboInfo, GameClient viewer) { this.habboInfo = habboInfo; diff --git a/Emulator/src/main/java/com/eu/habbo/networking/camera/CameraOutgoingMessage.java b/Emulator/src/main/java/com/eu/habbo/networking/camera/CameraOutgoingMessage.java index b6940a54..a1b3f083 100644 --- a/Emulator/src/main/java/com/eu/habbo/networking/camera/CameraOutgoingMessage.java +++ b/Emulator/src/main/java/com/eu/habbo/networking/camera/CameraOutgoingMessage.java @@ -5,7 +5,7 @@ import io.netty.buffer.ByteBufOutputStream; import io.netty.channel.Channel; import java.io.IOException; -import java.nio.charset.StandardCharsets; +import java.nio.charset.Charset; public abstract class CameraOutgoingMessage extends CameraMessage { private final ByteBufOutputStream stream; @@ -107,7 +107,7 @@ public abstract class CameraOutgoingMessage extends CameraMessage { buffer.setInt(0, buffer.writerIndex() - 4); - String consoleText = buffer.toString(StandardCharsets.UTF_8); + String consoleText = buffer.toString(Charset.forName("UTF-8")); for (int i = 0; i < 14; i++) { consoleText = consoleText.replace(Character.toString((char) i), "[" + i + "]"); diff --git a/Emulator/src/main/java/com/eu/habbo/plugin/PluginManager.java b/Emulator/src/main/java/com/eu/habbo/plugin/PluginManager.java index 3323fedd..00154dde 100644 --- a/Emulator/src/main/java/com/eu/habbo/plugin/PluginManager.java +++ b/Emulator/src/main/java/com/eu/habbo/plugin/PluginManager.java @@ -8,7 +8,6 @@ import com.eu.habbo.habbohotel.bots.BotManager; import com.eu.habbo.habbohotel.catalog.CatalogManager; import com.eu.habbo.habbohotel.catalog.TargetOffer; import com.eu.habbo.habbohotel.catalog.marketplace.MarketPlace; -import com.eu.habbo.habbohotel.gameclients.GameClient; import com.eu.habbo.habbohotel.games.freeze.FreezeGame; import com.eu.habbo.habbohotel.games.tag.TagGame; import com.eu.habbo.habbohotel.items.ItemManager; @@ -21,11 +20,12 @@ import com.eu.habbo.habbohotel.navigation.EventCategory; import com.eu.habbo.habbohotel.navigation.NavigatorManager; import com.eu.habbo.habbohotel.pets.PetManager; import com.eu.habbo.habbohotel.rooms.*; +import com.eu.habbo.habbohotel.users.clothingvalidation.ClothingValidationManager; import com.eu.habbo.habbohotel.users.HabboInventory; import com.eu.habbo.habbohotel.users.HabboManager; -import com.eu.habbo.habbohotel.users.clothingvalidation.ClothingValidationManager; import com.eu.habbo.habbohotel.users.subscriptions.SubscriptionHabboClub; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredEngine; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import com.eu.habbo.habbohotel.wired.highscores.WiredHighscoreManager; import com.eu.habbo.messages.PacketManager; import com.eu.habbo.messages.incoming.camera.CameraPublishToWebEvent; @@ -66,7 +66,7 @@ import java.util.stream.Collectors; public class PluginManager { - private static final Logger LOGGER = LoggerFactory.getLogger(GameClient.class); + private static final Logger LOGGER = LoggerFactory.getLogger(PluginManager.class); private final THashSet plugins = new THashSet<>(); private final THashSet methods = new THashSet<>(); @@ -99,6 +99,8 @@ public class PluginManager { BotManager.MAXIMUM_NAME_LENGTH = Emulator.getConfig().getInt("hotel.bot.max.namelength"); BotManager.MAXIMUM_CHAT_SPEED = Emulator.getConfig().getInt("hotel.bot.max.chatdelay"); Bot.PLACEMENT_MESSAGES = Emulator.getConfig().getValue("hotel.bot.placement.messages", "Yo!;Hello I'm a real party animal!;Hello!").split(";"); + Bot.BOT_LIMIT_WALKING_DISTANCE = Emulator.getConfig().getBoolean("hotel.bot.limit.walking.distance", true); + Bot.BOT_WALKING_DISTANCE_RADIUS = Emulator.getConfig().getInt("hotel.bot.limit.walking.distance.radius", 5); HabboInventory.MAXIMUM_ITEMS = Emulator.getConfig().getInt("hotel.inventory.max.items"); Messenger.MAXIMUM_FRIENDS = Emulator.getConfig().getInt("hotel.users.max.friends", 300); @@ -114,8 +116,12 @@ public class PluginManager { RoomManager.MAXIMUM_ROOMS_USER = Emulator.getConfig().getInt("hotel.users.max.rooms", 50); RoomManager.MAXIMUM_ROOMS_HC = Emulator.getConfig().getInt("hotel.users.max.rooms.hc", 75); RoomManager.HOME_ROOM_ID = Emulator.getConfig().getInt("hotel.home.room"); - WiredHandler.MAXIMUM_FURNI_SELECTION = Emulator.getConfig().getInt("hotel.wired.furni.selection.count"); - WiredHandler.TELEPORT_DELAY = Emulator.getConfig().getInt("wired.effect.teleport.delay", 500); + WiredManager.MAXIMUM_FURNI_SELECTION = Emulator.getConfig().getInt("hotel.wired.furni.selection.count"); + WiredManager.TELEPORT_DELAY = Emulator.getConfig().getInt("wired.effect.teleport.delay", 500); + WiredEngine.MAX_RECURSION_DEPTH = Emulator.getConfig().getInt("wired.abuse.max.recursion.depth", 10); + WiredEngine.MAX_EVENTS_PER_WINDOW = Emulator.getConfig().getInt("wired.abuse.max.events.per.window", 100); + WiredEngine.RATE_LIMIT_WINDOW_MS = Emulator.getConfig().getInt("wired.abuse.rate.limit.window.ms", 10000); + WiredEngine.WIRED_BAN_DURATION_MS = Emulator.getConfig().getInt("wired.abuse.ban.duration.ms", 600000); NavigatorManager.MAXIMUM_RESULTS_PER_PAGE = Emulator.getConfig().getInt("hotel.navigator.search.maxresults"); NavigatorManager.CATEGORY_SORT_USING_ORDER_NUM = Emulator.getConfig().getBoolean("hotel.navigator.sort.ordernum"); RoomChatMessage.MAXIMUM_LENGTH = Emulator.getConfig().getInt("hotel.chat.max.length"); @@ -284,70 +290,6 @@ public class PluginManager { } } - public void updatePluginByName(String name) { - File loc = new File("plugins"); - HabboPlugin pluginReload = null; - - for(HabboPlugin p : this.plugins){ - if(p.configuration.name.equalsIgnoreCase(name)){ - pluginReload = p; - } - } - - if (!loc.exists()) { - if (loc.mkdirs()) { - LOGGER.info("Created plugins directory!"); - } - } - - for (File file : Objects.requireNonNull(loc.listFiles(file -> file.getPath().toLowerCase().endsWith(".jar")))) { - URLClassLoader urlClassLoader; - InputStream stream; - try { - urlClassLoader = URLClassLoader.newInstance(new URL[]{file.toURI().toURL()}); - stream = urlClassLoader.getResourceAsStream("plugin.json"); - - if (stream == null) { - throw new RuntimeException("Invalid Jar! Missing plugin.json in: " + file.getName()); - } - - byte[] content = new byte[stream.available()]; - - if (stream.read(content) > 0) { - String body = new String(content); - - Gson gson = new GsonBuilder().create(); - HabboPluginConfiguration pluginConfigurtion = gson.fromJson(body, HabboPluginConfiguration.class); - - try { - Class clazz = urlClassLoader.loadClass(pluginConfigurtion.main); - Class stackClazz = clazz.asSubclass(HabboPlugin.class); - Constructor constructor = stackClazz.getConstructor(); - HabboPlugin plugin = constructor.newInstance(); - plugin.configuration = pluginConfigurtion; - plugin.classLoader = urlClassLoader; - plugin.stream = stream; - - if(plugin.configuration.name.equalsIgnoreCase(name) && pluginReload != null){ - if(this.plugins.contains(pluginReload) && this.plugins.remove(pluginReload)){ - this.plugins.add(plugin); - plugin.onEnable(); - LOGGER.info("Plugin: " + plugin.configuration.name + " updated!"); - } - } - } catch (Exception e) { - LOGGER.error("Could not load plugin {}!", pluginConfigurtion.name); - LOGGER.error("Caught exception", e); - } - } - } catch (Exception e) { - LOGGER.error("Caught exception", e); - } - } - - - } - public void registerEvents(HabboPlugin plugin, EventListener listener) { synchronized (plugin.registeredEvents) { Method[] methods = listener.getClass().getMethods(); @@ -488,6 +430,7 @@ public class PluginManager { this.methods.add(InteractionFootballGate.class.getMethod("onUserSavedLookEvent", UserSavedLookEvent.class)); this.methods.add(PluginManager.class.getMethod("globalOnConfigurationUpdated", EmulatorConfigUpdatedEvent.class)); this.methods.add(WiredHighscoreManager.class.getMethod("onEmulatorLoaded", EmulatorLoadedEvent.class)); + this.methods.add(WiredManager.class.getMethod("onEmulatorLoaded", EmulatorLoadedEvent.class)); } catch (NoSuchMethodException e) { LOGGER.info("Failed to define default events!"); LOGGER.error("Caught exception", e); diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/BotFollowHabbo.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/BotFollowHabbo.java index 11642074..2064a5ca 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/BotFollowHabbo.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/BotFollowHabbo.java @@ -5,8 +5,7 @@ import com.eu.habbo.habbohotel.bots.Bot; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.users.Habbo; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; public class BotFollowHabbo implements Runnable { private final Bot bot; @@ -36,7 +35,7 @@ public class BotFollowHabbo implements Runnable { if(this.habbo.getRoomUnit().getCurrentLocation().distance(this.bot.getRoomUnit().getCurrentLocation()) < 2) { if(!hasReached) { - WiredHandler.handle(WiredTriggerType.BOT_REACHED_AVTR, bot.getRoomUnit(), room, new Object[]{}); + WiredManager.triggerBotReachedHabbo(room, bot.getRoomUnit(), habbo.getRoomUnit()); hasReached = true; } } diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/CannonKickAction.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/CannonKickAction.java index 63313e14..7154f940 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/CannonKickAction.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/CannonKickAction.java @@ -26,16 +26,15 @@ public class CannonKickAction implements Runnable { @Override public void run() { - if (this.client != null) + if (this.client != null) { this.client.getHabbo().getRoomUnit().setCanWalk(true); - + } THashMap dater = new THashMap<>(); dater.put("title", "${notification.room.kick.cannonball.title}"); dater.put("message", "${notification.room.kick.cannonball.message}"); - int rotation = ((this.cannon.getRotation() - 2) + 8) % 8; - int amount = (rotation == 2 || rotation == 4) ? 4 : 3; - List tiles = this.room.getLayout().getTilesInFront(this.room.getLayout().getTile(this.cannon.getX(), this.cannon.getY()), rotation, amount); + int rotation = this.cannon.getRotation(); + List tiles = this.room.getLayout().getTilesInFront(this.room.getLayout().getTile(this.cannon.getX(), this.cannon.getY()), rotation + 6, 3); ServerMessage message = new BubbleAlertComposer("cannon.png", dater).compose(); @@ -43,7 +42,7 @@ public class CannonKickAction implements Runnable { for (Habbo habbo : this.room.getHabbosAt(t.x, t.y)) { if (!habbo.hasPermission(Permission.ACC_UNKICKABLE) && !this.room.isOwner(habbo)) { Emulator.getGameEnvironment().getRoomManager().leaveRoom(habbo, this.room); - habbo.getClient().sendResponse(message); + habbo.getClient().sendResponse(message); //kicked composer } } } diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/GuardianVotingFinish.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/GuardianVotingFinish.java index 96fe155a..15a361ea 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/GuardianVotingFinish.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/GuardianVotingFinish.java @@ -4,7 +4,7 @@ import com.eu.habbo.habbohotel.guides.GuardianTicket; public class GuardianVotingFinish implements Runnable { private final GuardianTicket ticket; - private final int checkSum; + private int checkSum; public GuardianVotingFinish(GuardianTicket ticket) { this.ticket = ticket; diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/KickBallAction.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/KickBallAction.java index d1305f7e..0bc85056 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/KickBallAction.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/KickBallAction.java @@ -63,7 +63,7 @@ public class KickBallAction implements Runnable { this.room.sendComposer(new FloorItemOnRollerComposer(this.ball, null, next, next.getStackHeight() - this.ball.getZ(), this.room).compose()); - Emulator.getThreading().run(this, delay); + Emulator.getThreading().run(this, (long) delay); } else { this.currentStep = this.totalSteps; //End the move sequence, the ball can't bounce anywhere this.run(); diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/OneWayGateActionOne.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/OneWayGateActionOne.java index f4901c5f..0cb6ae50 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/OneWayGateActionOne.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/OneWayGateActionOne.java @@ -8,9 +8,9 @@ import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer; public class OneWayGateActionOne implements Runnable { - private final HabboItem oneWayGate; - private final Room room; - private final GameClient client; + private HabboItem oneWayGate; + private Room room; + private GameClient client; public OneWayGateActionOne(GameClient client, Room room, HabboItem item) { this.oneWayGate = item; diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/PetEatAction.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/PetEatAction.java index 85f099cc..4d7a4a96 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/PetEatAction.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/PetEatAction.java @@ -27,7 +27,7 @@ public class PetEatAction implements Runnable { this.pet.setTask(PetTasks.EAT); this.pet.getRoomUnit().setCanWalk(false); - this.food.setExtradata(Integer.parseInt(this.food.getExtradata()) + 1 + ""); + this.food.setExtradata(Integer.valueOf(this.food.getExtradata()) + 1 + ""); this.pet.getRoom().updateItem(this.food); if (this.pet instanceof GnomePet) { diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/QueryDeleteHabboBadge.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/QueryDeleteHabboBadge.java index 1522ff05..c938f8f3 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/QueryDeleteHabboBadge.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/QueryDeleteHabboBadge.java @@ -22,7 +22,7 @@ class QueryDeleteHabboBadge implements Runnable { @Override public void run() { - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("DELETE FROM user_badges WHERE user_id = ? AND badge_code = ?")) { + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("DELETE FROM users_badges WHERE user_id = ? AND badge_code = ?")) { statement.setInt(1, this.habbo.getHabboInfo().getId()); statement.setString(2, this.name); statement.execute(); diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/QueryDeleteHabboItems.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/QueryDeleteHabboItems.java index 157ad494..71e30aca 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/QueryDeleteHabboItems.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/QueryDeleteHabboItems.java @@ -13,7 +13,7 @@ import java.sql.SQLException; public class QueryDeleteHabboItems implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(QueryDeleteHabboItems.class); - private final TIntObjectMap items; + private TIntObjectMap items; public QueryDeleteHabboItems(TIntObjectMap items) { this.items = items; diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/RandomDiceNumber.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/RandomDiceNumber.java index f621e0ba..e6c83600 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/RandomDiceNumber.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/RandomDiceNumber.java @@ -28,7 +28,7 @@ public class RandomDiceNumber implements Runnable { @Override public void run() { if (this.result <= 0) - this.result = (Emulator.getRandomDice().nextInt(this.maxNumber) + 1); + this.result = (Emulator.getRandom().nextInt(this.maxNumber) + 1); this.item.setExtradata(this.result + ""); this.item.needsUpdate(true); diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitRidePet.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitRidePet.java index f28306b4..bcfd8ef2 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitRidePet.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitRidePet.java @@ -9,9 +9,9 @@ import com.eu.habbo.messages.outgoing.rooms.users.RoomUserEffectComposer; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer; public class RoomUnitRidePet implements Runnable { - private final RideablePet pet; - private final Habbo habbo; - private final RoomTile goalTile; + private RideablePet pet; + private Habbo habbo; + private RoomTile goalTile; public RoomUnitRidePet(RideablePet pet, Habbo habbo, RoomTile goalTile) { this.pet = pet; diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitTeleport.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitTeleport.java index 435e9221..9747026e 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitTeleport.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitTeleport.java @@ -13,11 +13,11 @@ import java.util.LinkedList; public class RoomUnitTeleport implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(RoomUnitTeleport.class); - private final RoomUnit roomUnit; - private final Room room; - private final int x; - private final int y; - private final double z; + private RoomUnit roomUnit; + private Room room; + private int x; + private int y; + private double z; public RoomUnitTeleport(RoomUnit roomUnit, Room room, int x, int y, double z, int newEffect) { this.roomUnit = roomUnit; @@ -25,6 +25,7 @@ public class RoomUnitTeleport implements Runnable { this.x = x; this.y = y; this.z = z; + // newEffect parameter is unused } @Override diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitWalkToLocation.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitWalkToLocation.java index 2b614e2a..d13fa417 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitWalkToLocation.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitWalkToLocation.java @@ -10,11 +10,11 @@ import java.util.ArrayList; import java.util.List; public class RoomUnitWalkToLocation implements Runnable { - private final RoomUnit walker; - private final RoomTile goalTile; - private final Room room; - private final List targetReached; - private final List failedReached; + private RoomUnit walker; + private RoomTile goalTile; + private Room room; + private List targetReached; + private List failedReached; public RoomUnitWalkToLocation(RoomUnit walker, RoomTile goalTile, Room room, Runnable targetReached, Runnable failedReached) { this.walker = walker; @@ -48,12 +48,8 @@ public class RoomUnitWalkToLocation implements Runnable { return; } - if (!this.walker.getGoal().equals(this.goalTile) || (this.walker.getPath().size() == 0 && !this.walker.hasStatus(RoomUnitStatus.MOVE))) { - onFail(); - return; - } - - if (!this.walker.getGoal().equals(this.goalTile) || (this.walker.getPath().size() == 0 && (!this.walker.hasStatus(RoomUnitStatus.MOVE) || !this.walker.hasStatus(RoomUnitStatus.SNOWWAR_RUN)))) { + RoomTile currentGoal = this.walker.getGoal(); + if (currentGoal == null || !currentGoal.equals(this.goalTile) || (this.walker.getPath().size() == 0 && !this.walker.hasStatus(RoomUnitStatus.MOVE))) { onFail(); return; } diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitWalkToRoomUnit.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitWalkToRoomUnit.java index 7c8a1679..3be0526e 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitWalkToRoomUnit.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/RoomUnitWalkToRoomUnit.java @@ -4,18 +4,17 @@ import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.rooms.RoomUnit; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; import java.util.List; public class RoomUnitWalkToRoomUnit implements Runnable { private final int minDistance; - private final RoomUnit walker; - private final RoomUnit target; - private final Room room; - private final List targetReached; - private final List failedReached; + private RoomUnit walker; + private RoomUnit target; + private Room room; + private List targetReached; + private List failedReached; private RoomTile goalTile = null; @@ -50,7 +49,7 @@ public class RoomUnitWalkToRoomUnit implements Runnable { for (Runnable r : this.targetReached) { Emulator.getThreading().run(r); - WiredHandler.handle(WiredTriggerType.BOT_REACHED_AVTR, this.target, this.room, new Object[]{ this.walker }); + WiredManager.triggerBotReachedHabbo(this.room, this.walker, this.target); } } else { Emulator.getThreading().run(this, 500); diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/WiredCollissionRunnable.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/WiredCollissionRunnable.java index 26fb4c90..42a30717 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/WiredCollissionRunnable.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/WiredCollissionRunnable.java @@ -2,23 +2,25 @@ package com.eu.habbo.threading.runnables; import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomUnit; -import com.eu.habbo.habbohotel.wired.WiredHandler; -import com.eu.habbo.habbohotel.wired.WiredTriggerType; +import com.eu.habbo.habbohotel.wired.core.WiredManager; public class WiredCollissionRunnable implements Runnable { public final RoomUnit roomUnit; public final Room room; - public final Object[] objects; - public WiredCollissionRunnable(RoomUnit roomUnit, Room room, Object[] objects) { + public WiredCollissionRunnable(RoomUnit roomUnit, Room room) { this.roomUnit = roomUnit; this.room = room; - this.objects = objects; } @Override public void run() { - WiredHandler.handle(WiredTriggerType.COLLISION, roomUnit, room, objects); + if (this.roomUnit == null || this.room == null || !this.room.isLoaded()) return; + try { + WiredManager.triggerBotCollision(room, roomUnit); + } catch (Exception e) { + // Prevent task from crashing the thread pool + } } } diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/WiredRepeatEffectTask.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/WiredRepeatEffectTask.java index a6ccde40..8742bdf2 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/WiredRepeatEffectTask.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/WiredRepeatEffectTask.java @@ -18,10 +18,15 @@ class WiredRepeatEffectTask implements Runnable { @Override public void run() { if (!Emulator.isShuttingDown && Emulator.isReady) { - if (this.room != null && this.room.getId() == this.effect.getRoomId()) { - this.effect.execute(null, this.room, null); - - Emulator.getThreading().run(this, this.delay); + if (this.effect == null) return; + + if (this.room != null && this.room.isLoaded() && this.room.getId() == this.effect.getRoomId()) { + try { + this.effect.execute(null, this.room, null); + Emulator.getThreading().run(this, this.delay); + } catch (Exception e) { + // Prevent task from crashing the thread pool + } } } } diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/WiredResetTimers.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/WiredResetTimers.java index cfa9e109..8427bddc 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/WiredResetTimers.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/WiredResetTimers.java @@ -2,8 +2,14 @@ package com.eu.habbo.threading.runnables; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.rooms.Room; -import com.eu.habbo.habbohotel.wired.WiredHandler; +import com.eu.habbo.habbohotel.wired.core.WiredManager; +/** + * Runnable task that resets all wired timers in a room. + *

+ * Uses the new centralized WiredTickService for timer management. + *

+ */ public class WiredResetTimers implements Runnable { private final Room room; @@ -14,7 +20,13 @@ public class WiredResetTimers implements Runnable { @Override public void run() { if (!Emulator.isShuttingDown && Emulator.isReady) { - WiredHandler.resetTimers(this.room); + if (this.room != null && this.room.isLoaded()) { + try { + WiredManager.resetTimers(this.room); + } catch (Exception e) { + // Prevent task from crashing the thread pool + } + } } } } diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/YouAreAPirate.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/YouAreAPirate.java index b500ffb4..db87ef88 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/YouAreAPirate.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/YouAreAPirate.java @@ -75,7 +75,7 @@ public class YouAreAPirate implements Runnable { public final Room room; private int index = 0; - private final int oldEffect; + private int oldEffect; public YouAreAPirate(Habbo habbo, Room room) { this.habbo = habbo; diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/hopper/HopperActionThree.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/hopper/HopperActionThree.java index 724a426c..58f89627 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/hopper/HopperActionThree.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/hopper/HopperActionThree.java @@ -41,6 +41,7 @@ class HopperActionThree implements Runnable { if (targetTeleport == null) { this.client.getHabbo().getRoomUnit().removeStatus(RoomUnitStatus.MOVE); + this.client.getHabbo().getRoomUnit().isTeleporting = false; this.client.getHabbo().getRoomUnit().setCanWalk(true); return; } diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionFive.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionFive.java index bcb48a44..9fab330b 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionFive.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionFive.java @@ -3,9 +3,7 @@ package com.eu.habbo.threading.runnables.teleport; import com.eu.habbo.Emulator; import com.eu.habbo.habbohotel.gameclients.GameClient; import com.eu.habbo.habbohotel.items.interactions.InteractionTeleportTile; -import com.eu.habbo.habbohotel.rooms.Room; -import com.eu.habbo.habbohotel.rooms.RoomTile; -import com.eu.habbo.habbohotel.rooms.RoomUnit; +import com.eu.habbo.habbohotel.rooms.*; import com.eu.habbo.habbohotel.users.HabboItem; import com.eu.habbo.threading.runnables.HabboItemNewState; import com.eu.habbo.threading.runnables.RoomUnitWalkToLocation; @@ -32,8 +30,10 @@ class TeleportActionFive implements Runnable { unit.isTeleporting = false; unit.setCanWalk(true); - if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != this.room) + if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != this.room) { + this.client.getHabbo().getHabboInfo().setLoadingRoom(0); return; + } //if (!(this.currentTeleport instanceof InteractionTeleportTile)) diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionFour.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionFour.java index 6379fa98..5fea22ef 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionFour.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionFour.java @@ -19,6 +19,8 @@ class TeleportActionFour implements Runnable { @Override public void run() { if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != this.room) { + this.client.getHabbo().getHabboInfo().setLoadingRoom(0); + this.client.getHabbo().getRoomUnit().isTeleporting = false; this.client.getHabbo().getRoomUnit().setCanWalk(true); this.currentTeleport.setExtradata("0"); this.room.updateItem(this.currentTeleport); diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionOne.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionOne.java index 760c2d75..757db39c 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionOne.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionOne.java @@ -22,8 +22,12 @@ public class TeleportActionOne implements Runnable { @Override public void run() { - if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != this.room) + if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != this.room) { + this.client.getHabbo().getHabboInfo().setLoadingRoom(0); + this.client.getHabbo().getRoomUnit().isTeleporting = false; + this.client.getHabbo().getRoomUnit().setCanWalk(true); return; + } int delay = 500; diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionThree.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionThree.java index ad3cee43..ed3a5d84 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionThree.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionThree.java @@ -23,8 +23,12 @@ class TeleportActionThree implements Runnable { @Override public void run() { - if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != this.room) + if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != this.room) { + this.client.getHabbo().getHabboInfo().setLoadingRoom(0); + this.client.getHabbo().getRoomUnit().isTeleporting = false; + this.client.getHabbo().getRoomUnit().setCanWalk(true); return; + } HabboItem targetTeleport; Room targetRoom = this.room; diff --git a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionTwo.java b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionTwo.java index feedd769..eb5c4bb0 100644 --- a/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionTwo.java +++ b/Emulator/src/main/java/com/eu/habbo/threading/runnables/teleport/TeleportActionTwo.java @@ -38,8 +38,12 @@ class TeleportActionTwo implements Runnable { delayOffset = 0; } - if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != this.room) + if (this.client.getHabbo().getHabboInfo().getCurrentRoom() != this.room) { + this.client.getHabbo().getHabboInfo().setLoadingRoom(0); + this.client.getHabbo().getRoomUnit().isTeleporting = false; + this.client.getHabbo().getRoomUnit().setCanWalk(true); return; + } this.client.getHabbo().getRoomUnit().removeStatus(RoomUnitStatus.MOVE); this.room.sendComposer(new RoomUserStatusComposer(this.client.getHabbo().getRoomUnit()).compose()); diff --git a/Emulator/src/main/java/com/eu/habbo/util/HexUtils.java b/Emulator/src/main/java/com/eu/habbo/util/HexUtils.java index a2145324..2e5bccb4 100644 --- a/Emulator/src/main/java/com/eu/habbo/util/HexUtils.java +++ b/Emulator/src/main/java/com/eu/habbo/util/HexUtils.java @@ -35,7 +35,7 @@ public class HexUtils { sb.append(Integer.toHexString(r.nextInt())); } - return sb.substring(0, length); + return sb.toString().substring(0, length); } } diff --git a/Emulator/src/main/java/com/eu/habbo/util/figure/FigureUtil.java b/Emulator/src/main/java/com/eu/habbo/util/figure/FigureUtil.java index 14117676..efbb779a 100644 --- a/Emulator/src/main/java/com/eu/habbo/util/figure/FigureUtil.java +++ b/Emulator/src/main/java/com/eu/habbo/util/figure/FigureUtil.java @@ -33,7 +33,7 @@ public class FigureUtil { String[] pieces = set.split("-"); try { - if (pieces.length >= 2 && blacklist.contains(Integer.parseInt(pieces[1]))) { + if (pieces.length >= 2 && blacklist.contains(Integer.valueOf(pieces[1]))) { return true; } } catch (NumberFormatException ignored) {