diff --git a/Database Updates/008_soundboard_fortune_wheel.sql b/Database Updates/008_soundboard_fortune_wheel.sql index b5dba5ba..49e2c59b 100644 --- a/Database Updates/008_soundboard_fortune_wheel.sql +++ b/Database Updates/008_soundboard_fortune_wheel.sql @@ -1,78 +1,89 @@ --- Soundboard --- The room flag column + sounds table are also created at boot by - -ALTER TABLE `rooms` ADD COLUMN IF NOT EXISTS `soundboard_enabled` TINYINT(1) NOT NULL DEFAULT 0; +ALTER TABLE `rooms` + ADD COLUMN IF NOT EXISTS `soundboard_enabled` TINYINT(1) NOT NULL DEFAULT 0; CREATE TABLE IF NOT EXISTS `soundboard_sounds` ( - `id` INT(11) NOT NULL AUTO_INCREMENT, - `name` VARCHAR(64) NOT NULL DEFAULT '', -- pad label shown in the client - `url` VARCHAR(255) NOT NULL DEFAULT '', -- audio url (uploaded via CMS, like custom badges) - `enabled` TINYINT(1) NOT NULL DEFAULT 1, - `sort_order` INT(11) NOT NULL DEFAULT 0, + `id` INT(11) NOT NULL AUTO_INCREMENT, + `name` VARCHAR(64) NOT NULL DEFAULT '', -- pad label shown in the client + `url` VARCHAR(255) NOT NULL DEFAULT '', -- audio url (uploaded via CMS, like custom badges) + `enabled` TINYINT(1) NOT NULL DEFAULT 1, + `sort_order` INT(11) NOT NULL DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- Fortune Wheel --- Tables are also created at boot by WheelManager (CREATE TABLE IF NOT EXISTS), --- so applying this file is only needed to seed prizes + settings. - +-- ---------------------------------------------------------------------------- +-- Fortune Wheel — tables +-- ---------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS `wheel_prizes` ( - `id` INT(11) NOT NULL AUTO_INCREMENT, - `type` VARCHAR(16) NOT NULL DEFAULT 'nothing', -- item | badge | credits | points | spin | nothing - `value` VARCHAR(64) NOT NULL DEFAULT '', -- item: base item id ; badge: badge code ; others: unused - `amount` INT(11) NOT NULL DEFAULT 1, -- item qty / credits / points / extra spins - `points_type` INT(11) NOT NULL DEFAULT 5, -- for type=points (diamond default 5) - `weight` INT(11) NOT NULL DEFAULT 1, -- relative probability - `label` VARCHAR(64) NOT NULL DEFAULT '', -- slice label override (optional) - `enabled` TINYINT(1) NOT NULL DEFAULT 1, - `sort_order` INT(11) NOT NULL DEFAULT 0, + `id` INT(11) NOT NULL AUTO_INCREMENT, + `type` VARCHAR(16) NOT NULL DEFAULT 'nothing', -- item | badge | credits | points | spin | nothing + `value` VARCHAR(64) NOT NULL DEFAULT '', -- item: base item id ; badge: badge code ; others: unused + `amount` INT(11) NOT NULL DEFAULT 1, -- item qty / credits / points / extra spins + `points_type` INT(11) NOT NULL DEFAULT 5, -- for type=points (diamond default 5) + `weight` INT(11) NOT NULL DEFAULT 1, -- relative probability + `label` VARCHAR(64) NOT NULL DEFAULT '', -- slice label override (optional) + `enabled` TINYINT(1) NOT NULL DEFAULT 1, + `sort_order` INT(11) NOT NULL DEFAULT 0, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `wheel_user_state` ( - `user_id` INT(11) NOT NULL, - `free_spins` INT(11) NOT NULL DEFAULT 0, -- remaining free spins for the current day - `extra_spins` INT(11) NOT NULL DEFAULT 0, -- bought / won spins - `last_reset` INT(11) NOT NULL DEFAULT 0, -- day index of last daily reset (unix / 86400) + `user_id` INT(11) NOT NULL, + `free_spins` INT(11) NOT NULL DEFAULT 0, -- remaining free spins for the current day + `extra_spins` INT(11) NOT NULL DEFAULT 0, -- bought / won spins + `last_reset` INT(11) NOT NULL DEFAULT 0, -- day index of last daily reset (unix / 86400) PRIMARY KEY (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `wheel_recent_wins` ( - `id` INT(11) NOT NULL AUTO_INCREMENT, - `user_id` INT(11) NOT NULL, - `username` VARCHAR(64) NOT NULL DEFAULT '', - `look` VARCHAR(255) NOT NULL DEFAULT '', - `prize_label` VARCHAR(64) NOT NULL DEFAULT '', - `won_at` INT(11) NOT NULL DEFAULT 0, - PRIMARY KEY (`id`), - KEY `idx_wheel_recent_wins_id` (`id`) + `id` INT(11) NOT NULL AUTO_INCREMENT, + `user_id` INT(11) NOT NULL, + `username` VARCHAR(64) NOT NULL DEFAULT '', + `look` VARCHAR(255) NOT NULL DEFAULT '', + `prize_label` VARCHAR(64) NOT NULL DEFAULT '', + `won_at` INT(11) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO `emulator_settings` (`key`, `value`, `comment`) VALUES - ('wheel.free_spins_per_day', '1', 'Fortune wheel: free spins granted each day.') - ON DUPLICATE KEY UPDATE `comment` = VALUES(`comment`); -INSERT INTO `emulator_settings` (`key`, `value`, `comment`) VALUES - ('wheel.spin_cost', '50', 'Fortune wheel: cost of one extra spin.') - ON DUPLICATE KEY UPDATE `comment` = VALUES(`comment`); -INSERT INTO `emulator_settings` (`key`, `value`, `comment`) VALUES - ('wheel.spin_cost_type', '5', 'Fortune wheel: currency type for the spin cost (5 = diamonds; -1 = credits).') - ON DUPLICATE KEY UPDATE `comment` = VALUES(`comment`); - - -INSERT INTO `wheel_prizes` (`type`, `amount`, `points_type`, `weight`, `label`, `sort_order`) VALUES - ('points',25, 5, 20, '25 diamonds',1), - ('points',50, 5, 12, '50 diamonds',2), - ('points',200, 5, 3, '200 diamonds',3), - ('credits',100, 0, 15, '100 credits',4), - ('spin',1, 0, 15, '1 Extra spin', 5), - ('spin',2, 0, 6, '2 Extra spins',6), - ('nothing',0, 0, 29, 'Oh to bad!',7); - -INSERT INTO `permission_definitions` - (`permission_key`, `max_value`, `comment`, - `rank_1`, `rank_2`, `rank_3`, `rank_4`, `rank_5`, `rank_6`, `rank_7`) -VALUES - ('acc_wheeladmin', 1, 'Required to open the Fortune Wheel settings popup and edit prize rows.', - 0, 0, 0, 0, 0, 0, 1) + ('wheel.free_spins_per_day', '1', 'Fortune wheel: free spins granted each day.'), + ('wheel.spin_cost', '50', 'Fortune wheel: cost of one extra spin.'), + ('wheel.spin_cost_type', '5', 'Fortune wheel: currency type for the spin cost (5 = diamonds; -1 = credits).') ON DUPLICATE KEY UPDATE `comment` = VALUES(`comment`); + +INSERT INTO `wheel_prizes` (`type`, `amount`, `points_type`, `weight`, `label`, `sort_order`) +SELECT `type`, `amount`, `points_type`, `weight`, `label`, `sort_order` +FROM ( + SELECT 'points' AS `type`, 25 AS `amount`, 5 AS `points_type`, 20 AS `weight`, '25 diamonds' AS `label`, 1 AS `sort_order` + UNION ALL SELECT 'points', 50, 5, 12, '50 diamonds', 2 + UNION ALL SELECT 'points', 200, 5, 3, '200 diamonds', 3 + UNION ALL SELECT 'credits', 100, 0, 15, '100 credits', 4 + UNION ALL SELECT 'spin', 1, 0, 15, '1 Extra spin', 5 + UNION ALL SELECT 'spin', 2, 0, 6, '2 Extra spins', 6 + UNION ALL SELECT 'nothing', 0, 0, 29, 'Oh to bad!', 7 +) AS seed +WHERE NOT EXISTS (SELECT 1 FROM `wheel_prizes`); + +INSERT IGNORE INTO `permission_definitions` (`permission_key`, `max_value`, `comment`) +VALUES ( + 'acc_wheeladmin', + 1, + 'Allows opening the Fortune Wheel prize editor (FortuneWheelSettingsView) to add/edit prize slices. Gated server-side by the same key.' +); + +SET @cols := NULL; +SELECT GROUP_CONCAT(CONCAT('dst.`', `column_name`, '` = src.`', `column_name`, '`') SEPARATOR ', ') + INTO @cols +FROM `information_schema`.`columns` +WHERE `table_schema` = DATABASE() + AND `table_name` = 'permission_definitions' + AND `column_name` REGEXP '^rank_[0-9]+$'; + +SET @sql := CONCAT( + 'UPDATE `permission_definitions` dst ', + 'JOIN `permission_definitions` src ON src.`permission_key` = ''acc_ads_background'' ', + 'SET ', @cols, ' ', + 'WHERE dst.`permission_key` = ''acc_wheeladmin''' +); +PREPARE stmt FROM @sql; +EXECUTE stmt; +DEALLOCATE PREPARE stmt; \ No newline at end of file diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/CatalogManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/CatalogManager.java index c89bf959..f0dbccec 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/CatalogManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/CatalogManager.java @@ -202,8 +202,8 @@ public class CatalogManager { public final Item ecotronItem; public final THashMap limitedNumbers; private final List vouchers; - // spriteId -> [credits, points, pointsType], derived from catalog_items (see loadFurnitureValues) public final TIntObjectMap furnitureValues; + private volatile byte[] rareValuesPayloadCache; public CatalogManager() { long millis = System.currentTimeMillis(); @@ -249,10 +249,6 @@ public class CatalogManager { this.loadFurnitureValues(); } - // Builds spriteId -> [credits, points, pointsType] from catalog_items so the - // client can show a furni's "value" (toolbar price guide + infostand line). - // Only single-item, single-amount FLOOR/WALL sales are considered, so bundles - // and multi-packs don't pollute the per-rare price. First clean entry wins. private synchronized void loadFurnitureValues() { this.furnitureValues.clear(); final int diamondType = Emulator.getConfig().getInt("seasonal.currency.diamond", 5); @@ -266,8 +262,6 @@ public class CatalogManager { int points = catalogItem.getPoints(); int pointsType = catalogItem.getPointsType(); - // Only diamond-priced items — both the "Valore Rari" panel and the - // infostand value line show diamonds only. if (points <= 0 || pointsType != diamondType) continue; @@ -291,13 +285,39 @@ public class CatalogManager { } } + this.rebuildRareValuesPayloadCache(); + LOGGER.info("Furniture Values -> Loaded! ({} entries)", this.furnitureValues.size()); } + private void rebuildRareValuesPayloadCache() { + try (java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(this.furnitureValues.size() * 16 + 8); + java.io.DataOutputStream out = new java.io.DataOutputStream(baos)) { + out.writeInt(this.furnitureValues.size()); + TIntObjectIterator iterator = this.furnitureValues.iterator(); + while (iterator.hasNext()) { + iterator.advance(); + int[] value = iterator.value(); + out.writeInt(iterator.key()); // spriteId + out.writeInt(value[0]); // credits + out.writeInt(value[1]); // points + out.writeInt(value[2]); // pointsType + } + this.rareValuesPayloadCache = baos.toByteArray(); + } catch (java.io.IOException e) { + LOGGER.error("Failed to build rare values payload cache", e); + this.rareValuesPayloadCache = null; + } + } + public TIntObjectMap getFurnitureValues() { return this.furnitureValues; } + public byte[] getRareValuesPayloadSnapshot() { + return this.rareValuesPayloadCache; + } + private synchronized void loadLimitedNumbers() { this.limitedNumbers.clear(); @@ -1104,9 +1124,6 @@ public class CatalogManager { type = type.replace("bot_", ""); type = type.replace("visitor_logger", "visitor_log"); - // Permission gate keyed on the canonical base-item name - // (admin-controlled but stable), not the catalog page name - // which can be renamed and bypass the check. if (("bot_" + com.eu.habbo.habbohotel.bots.FrankBot.BOT_TYPE).equals(baseName) || ("rentable_bot_" + com.eu.habbo.habbohotel.bots.FrankBot.BOT_TYPE).equals(baseName)) { if (!habbo.getClient().getHabbo().hasPermission(com.eu.habbo.habbohotel.bots.FrankBot.PERMISSION_USE)) { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/layouts/RoomBundleLayout.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/layouts/RoomBundleLayout.java index d4f6147c..1b0c475a 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/layouts/RoomBundleLayout.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/catalog/layouts/RoomBundleLayout.java @@ -42,14 +42,18 @@ public class RoomBundleLayout extends SingleBundle { } if (this.room == null) { - if (this.roomId > 0) { - this.room = Emulator.getGameEnvironment().getRoomManager().loadRoom(this.roomId); + RoomManager roomManager = Emulator.getGameEnvironment().getRoomManager(); + if (this.roomId > 0 && roomManager != null) { + this.room = roomManager.loadRoom(this.roomId); if (this.room != null) this.room.preventUnloading = true; - } else { + } else if (this.roomId <= 0) { LOGGER.error("No room id specified for room bundle {}({})", this.getPageName(), this.getId()); } + // roomManager can be null when CatalogManager.loadFurnitureValues() runs + // during GameEnvironment.load() before RoomManager is constructed; in that + // case skip eager room loading — the bundle resolves lazily at runtime. } if (this.room == null) { 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 f526c15b..7ffe5228 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 @@ -153,7 +153,13 @@ public class GameClient { this.channel.close(); if (this.habbo != null) { - if (this.habbo.isOnline()) { + // Agisci sull'Habbo SOLO se è ancora attaccato a QUESTO client. Su un + // reconnect veloce (drop Cloudflare → il client riconnette) l'Habbo può + // essere già stato riassegnato alla NUOVA connessione (session resume): + // in quel caso questo dispose della vecchia connessione NON deve + // parcheggiarlo né disconnetterlo, altrimenti ucciderebbe la sessione + // appena ripristinata (era la causa del "Bye"/kick al 2° reconnect). + if (this.habbo.getClient() == this && this.habbo.isOnline()) { // Try to park the habbo in the grace period instead of immediate disconnect boolean parked = SessionResumeManager.getInstance().parkHabbo(this.habbo, this.ssoTicket); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/SessionResumeManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/SessionResumeManager.java index 1f654d35..e8099bbc 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/SessionResumeManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/SessionResumeManager.java @@ -118,16 +118,32 @@ public class SessionResumeManager { LOGGER.error("[SessionResume] Error during deferred disconnect", e); } - clearSsoTicket(habbo.getHabboInfo().getId()); + // NON svuotare il ticket SSO qui. Dietro Cloudflare la pagina si ricarica + // lentamente (~15s) e la grace (5s) scade prima che la nuova connessione + // arrivi: svuotando il ticket si cancellava quello NUOVO appena scritto dal + // CMS per il refresh → "non-existing SSO token" → bisognava refreshare 2 volte. + // Il ticket vive col suo TTL (auth_ticket_expires_at) e viene sovrascritto dal + // CMS al prossimo /client o azzerato al logout. } private void restoreSsoTicket(int userId, String ssoTicket) { + // Restore the old ticket ONLY if no fresh ticket has been written in the + // meantime. On a hard-refresh the CMS writes a NEW auth_ticket for the same + // user before this parking restore runs; without the guard we'd clobber it + // with the old ticket, so the new connection's SSO wouldn't be found and the + // client would get "session expired" on the first attempt. The guard means: + // normal reconnect (ticket cleared to '' after login) -> restore; hard-refresh + // (CMS already wrote a new ticket) -> leave the new ticket untouched. try (var connection = Emulator.getDatabase().getDataSource().getConnection(); - var statement = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) { + var statement = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? AND (auth_ticket = '' OR auth_ticket IS NULL) LIMIT 1")) { statement.setString(1, ssoTicket); statement.setInt(2, userId); - statement.execute(); - LOGGER.info("[SessionResume] Restored SSO ticket for user {} during grace period", userId); + int updated = statement.executeUpdate(); + if (updated > 0) { + LOGGER.info("[SessionResume] Restored SSO ticket for user {} during grace period", userId); + } else { + LOGGER.info("[SessionResume] Skipped SSO restore for user {} — a newer ticket is already present (likely a fresh login/hard-refresh)", userId); + } } catch (Exception e) { LOGGER.error("[SessionResume] Failed to restore SSO ticket for user " + userId, e); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionRoomAds.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionRoomAds.java index d37dea17..198df0d5 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionRoomAds.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/items/interactions/InteractionRoomAds.java @@ -29,6 +29,10 @@ public class InteractionRoomAds extends InteractionCustomValues { { this.put("offsetZ", "0"); } + + { + this.put("scale", "100"); + } }; public InteractionRoomAds(ResultSet set, Item baseItem) throws SQLException { diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboManager.java index 4413163d..48261f66 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboManager.java @@ -132,15 +132,12 @@ public class HabboManager { Emulator.getPluginManager().fireEvent(new UserRegisteredEvent(habbo)); } - if (!Emulator.debugging) { - try (PreparedStatement stmt = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) { - stmt.setString(1, ""); - stmt.setInt(2, habbo.getHabboInfo().getId()); - stmt.execute(); - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } - } + // NB: il ticket SSO NON viene svuotato qui di proposito. Dietro + // Cloudflare il WebSocket viene droppato e il client ritenta più + // volte con lo STESSO ticket: se lo consumassimo al primo uso, i + // retry (e l'hard-refresh) fallirebbero con "non-existing SSO token". + // Il ticket resta valido fino alla scadenza (auth_ticket_expires_at, + // TTL gestito dal CMS) o finché il CMS non ne scrive uno nuovo / logout. } } } catch (SQLException e) { 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 076a6795..7c7dcf3c 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 @@ -133,17 +133,10 @@ public class SecureLoginEvent extends MessageHandler { this.client.setHabbo(habbo); this.client.setMachineId(habbo.getHabboInfo().getMachineID()); - // Clear the SSO ticket now that session is resumed (prevent reuse) - if (!Emulator.debugging) { - try (java.sql.Connection conn = Emulator.getDatabase().getDataSource().getConnection(); - java.sql.PreparedStatement stmt = conn.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) { - stmt.setString(1, ""); - stmt.setInt(2, habbo.getHabboInfo().getId()); - stmt.execute(); - } catch (Exception e) { - LOGGER.error("Failed to clear SSO ticket after session resume", e); - } - } + // NB: NON svuotiamo il ticket SSO qui (vedi HabboManager.loadHabbo): + // dietro Cloudflare il client ritenta la connessione con lo stesso + // ticket, quindi deve restare valido fino alla scadenza TTL. Consumarlo + // farebbe fallire i retry / l'hard-refresh con "non-existing SSO token". } else { // Normal login — load from database habbo = Emulator.getGameEnvironment().getHabboManager().loadHabbo(sso); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rarevalues/RequestRareValuesEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rarevalues/RequestRareValuesEvent.java index 117043a4..0ca00ad0 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rarevalues/RequestRareValuesEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rarevalues/RequestRareValuesEvent.java @@ -1,11 +1,10 @@ package com.eu.habbo.messages.incoming.rarevalues; import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.catalog.CatalogManager; import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.outgoing.rarevalues.RareValuesComposer; -// Client requests the furni value map once on load. Public info (catalog prices), -// no permission gate. Rate limited since the payload is large. public class RequestRareValuesEvent extends MessageHandler { @Override public int getRatelimit() { @@ -14,8 +13,15 @@ public class RequestRareValuesEvent extends MessageHandler { @Override public void handle() throws Exception { - this.client.sendResponse(new RareValuesComposer( - Emulator.getGameEnvironment().getCatalogManager().getFurnitureValues() - )); + if (this.client.getHabbo() == null) return; + + CatalogManager catalog = Emulator.getGameEnvironment().getCatalogManager(); + byte[] snapshot = catalog.getRareValuesPayloadSnapshot(); + if (snapshot != null) { + this.client.sendResponse(new RareValuesComposer(snapshot)); + return; + } + + this.client.sendResponse(new RareValuesComposer(catalog.getFurnitureValues())); } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rarevalues/RareValuesComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rarevalues/RareValuesComposer.java index f713c8c9..85778e77 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rarevalues/RareValuesComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rarevalues/RareValuesComposer.java @@ -6,18 +6,29 @@ import com.eu.habbo.messages.outgoing.Outgoing; import gnu.trove.iterator.TIntObjectIterator; import gnu.trove.map.TIntObjectMap; -// Sends the full spriteId -> value map to the client. Consumed by the toolbar -// price guide and the furni infostand "value" line. See CatalogManager#loadFurnitureValues. public class RareValuesComposer extends MessageComposer { private final TIntObjectMap values; + private final byte[] snapshot; + + public RareValuesComposer(byte[] snapshot) { + this.values = null; + this.snapshot = snapshot; + } public RareValuesComposer(TIntObjectMap values) { this.values = values; + this.snapshot = null; } @Override protected ServerMessage composeInternal() { this.response.init(Outgoing.RareValuesComposer); + + if (this.snapshot != null) { + this.response.appendRawBytes(this.snapshot); + return this.response; + } + this.response.appendInt(this.values.size()); TIntObjectIterator iterator = this.values.iterator(); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/UpdateSoundboard.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/UpdateSoundboard.java new file mode 100644 index 00000000..59b796c8 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/UpdateSoundboard.java @@ -0,0 +1,21 @@ +package com.eu.habbo.messages.rcon; + +import com.eu.habbo.Emulator; +import com.google.gson.Gson; + +// Ricarica i suoni della Soundboard dal DB (live), così i suoni aggiunti/caricati +// dal CMS (/admin/soundboard) si applicano senza riavviare l'emulatore. +public class UpdateSoundboard extends RCONMessage { + + public UpdateSoundboard() { + super(SoundboardJSON.class); + } + + @Override + public void handle(Gson gson, SoundboardJSON object) { + Emulator.getGameEnvironment().getSoundboardManager().reload(); + } + + static class SoundboardJSON { + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/rcon/UpdateWheel.java b/Emulator/src/main/java/com/eu/habbo/messages/rcon/UpdateWheel.java new file mode 100644 index 00000000..aa9b2be4 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/rcon/UpdateWheel.java @@ -0,0 +1,21 @@ +package com.eu.habbo.messages.rcon; + +import com.eu.habbo.Emulator; +import com.google.gson.Gson; + +// Ricarica i premi/settings della Ruota della Fortuna dal DB (live), così le +// modifiche fatte dal CMS (/admin/wheel) si applicano senza riavviare l'emulatore. +public class UpdateWheel extends RCONMessage { + + public UpdateWheel() { + super(WheelJSON.class); + } + + @Override + public void handle(Gson gson, WheelJSON object) { + Emulator.getGameEnvironment().getWheelManager().reload(); + } + + static class WheelJSON { + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/networking/gameserver/decoders/GameMessageHandler.java b/Emulator/src/main/java/com/eu/habbo/networking/gameserver/decoders/GameMessageHandler.java index bbe9540f..a80be5a5 100644 --- a/Emulator/src/main/java/com/eu/habbo/networking/gameserver/decoders/GameMessageHandler.java +++ b/Emulator/src/main/java/com/eu/habbo/networking/gameserver/decoders/GameMessageHandler.java @@ -11,6 +11,7 @@ import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.TooLongFrameException; import io.netty.handler.codec.UnsupportedMessageTypeException; import io.netty.handler.ssl.NotSslRecordException; +import io.netty.util.ReferenceCountUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,6 +40,19 @@ public class GameMessageHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (!(msg instanceof ClientMessage)) { + try { + if (Emulator.getConfig().getBoolean("debug.mode")) { + LOGGER.debug("Discarding non-game message {} from {}", + msg.getClass().getSimpleName(), ctx.channel().remoteAddress()); + } + } finally { + ReferenceCountUtil.release(msg); + ctx.channel().close(); + } + return; + } + ClientMessage message = (ClientMessage) msg; try { diff --git a/Emulator/src/main/java/com/eu/habbo/networking/rconserver/RCONServer.java b/Emulator/src/main/java/com/eu/habbo/networking/rconserver/RCONServer.java index c5c1ad8a..9eaeeef4 100644 --- a/Emulator/src/main/java/com/eu/habbo/networking/rconserver/RCONServer.java +++ b/Emulator/src/main/java/com/eu/habbo/networking/rconserver/RCONServer.java @@ -45,6 +45,8 @@ public class RCONServer extends Server { this.addRCONMessage("sendroombundle", SendRoomBundle.class); this.addRCONMessage("setrank", SetRank.class); this.addRCONMessage("updatewordfilter", UpdateWordfilter.class); + this.addRCONMessage("updatewheel", UpdateWheel.class); + this.addRCONMessage("updatesoundboard", UpdateSoundboard.class); this.addRCONMessage("updatecatalog", UpdateCatalog.class); this.addRCONMessage("executecommand", ExecuteCommand.class); this.addRCONMessage("progressachievement", ProgressAchievement.class);