Merge pull request #136 from duckietm/dev

Dev
This commit is contained in:
DuckieTM
2026-05-30 07:52:43 +02:00
committed by GitHub
14 changed files with 228 additions and 105 deletions
@@ -1,78 +1,89 @@
-- Soundboard ALTER TABLE `rooms`
-- The room flag column + sounds table are also created at boot by 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` ( CREATE TABLE IF NOT EXISTS `soundboard_sounds` (
`id` INT(11) NOT NULL AUTO_INCREMENT, `id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(64) NOT NULL DEFAULT '', -- pad label shown in the client `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) `url` VARCHAR(255) NOT NULL DEFAULT '', -- audio url (uploaded via CMS, like custom badges)
`enabled` TINYINT(1) NOT NULL DEFAULT 1, `enabled` TINYINT(1) NOT NULL DEFAULT 1,
`sort_order` INT(11) NOT NULL DEFAULT 0, `sort_order` INT(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ----------------------------------------------------------------------------
-- Fortune Wheel -- Fortune Wheel — tables
-- Tables are also created at boot by WheelManager (CREATE TABLE IF NOT EXISTS), -- ----------------------------------------------------------------------------
-- so applying this file is only needed to seed prizes + settings.
CREATE TABLE IF NOT EXISTS `wheel_prizes` ( CREATE TABLE IF NOT EXISTS `wheel_prizes` (
`id` INT(11) NOT NULL AUTO_INCREMENT, `id` INT(11) NOT NULL AUTO_INCREMENT,
`type` VARCHAR(16) NOT NULL DEFAULT 'nothing', -- item | badge | credits | points | spin | nothing `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 `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 `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) `points_type` INT(11) NOT NULL DEFAULT 5, -- for type=points (diamond default 5)
`weight` INT(11) NOT NULL DEFAULT 1, -- relative probability `weight` INT(11) NOT NULL DEFAULT 1, -- relative probability
`label` VARCHAR(64) NOT NULL DEFAULT '', -- slice label override (optional) `label` VARCHAR(64) NOT NULL DEFAULT '', -- slice label override (optional)
`enabled` TINYINT(1) NOT NULL DEFAULT 1, `enabled` TINYINT(1) NOT NULL DEFAULT 1,
`sort_order` INT(11) NOT NULL DEFAULT 0, `sort_order` INT(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `wheel_user_state` ( CREATE TABLE IF NOT EXISTS `wheel_user_state` (
`user_id` INT(11) NOT NULL, `user_id` INT(11) NOT NULL,
`free_spins` INT(11) NOT NULL DEFAULT 0, -- remaining free spins for the current day `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 `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) `last_reset` INT(11) NOT NULL DEFAULT 0, -- day index of last daily reset (unix / 86400)
PRIMARY KEY (`user_id`) PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `wheel_recent_wins` ( CREATE TABLE IF NOT EXISTS `wheel_recent_wins` (
`id` INT(11) NOT NULL AUTO_INCREMENT, `id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) NOT NULL, `user_id` INT(11) NOT NULL,
`username` VARCHAR(64) NOT NULL DEFAULT '', `username` VARCHAR(64) NOT NULL DEFAULT '',
`look` VARCHAR(255) NOT NULL DEFAULT '', `look` VARCHAR(255) NOT NULL DEFAULT '',
`prize_label` VARCHAR(64) NOT NULL DEFAULT '', `prize_label` VARCHAR(64) NOT NULL DEFAULT '',
`won_at` INT(11) NOT NULL DEFAULT 0, `won_at` INT(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`), PRIMARY KEY (`id`)
KEY `idx_wheel_recent_wins_id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `emulator_settings` (`key`, `value`, `comment`) VALUES INSERT INTO `emulator_settings` (`key`, `value`, `comment`) VALUES
('wheel.free_spins_per_day', '1', 'Fortune wheel: free spins granted each day.') ('wheel.free_spins_per_day', '1', 'Fortune wheel: free spins granted each day.'),
ON DUPLICATE KEY UPDATE `comment` = VALUES(`comment`); ('wheel.spin_cost', '50', 'Fortune wheel: cost of one extra spin.'),
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).')
('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)
ON DUPLICATE KEY UPDATE `comment` = VALUES(`comment`); 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;
@@ -202,8 +202,8 @@ public class CatalogManager {
public final Item ecotronItem; public final Item ecotronItem;
public final THashMap<Integer, CatalogLimitedConfiguration> limitedNumbers; public final THashMap<Integer, CatalogLimitedConfiguration> limitedNumbers;
private final List<Voucher> vouchers; private final List<Voucher> vouchers;
// spriteId -> [credits, points, pointsType], derived from catalog_items (see loadFurnitureValues)
public final TIntObjectMap<int[]> furnitureValues; public final TIntObjectMap<int[]> furnitureValues;
private volatile byte[] rareValuesPayloadCache;
public CatalogManager() { public CatalogManager() {
long millis = System.currentTimeMillis(); long millis = System.currentTimeMillis();
@@ -249,10 +249,6 @@ public class CatalogManager {
this.loadFurnitureValues(); 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() { private synchronized void loadFurnitureValues() {
this.furnitureValues.clear(); this.furnitureValues.clear();
final int diamondType = Emulator.getConfig().getInt("seasonal.currency.diamond", 5); final int diamondType = Emulator.getConfig().getInt("seasonal.currency.diamond", 5);
@@ -266,8 +262,6 @@ public class CatalogManager {
int points = catalogItem.getPoints(); int points = catalogItem.getPoints();
int pointsType = catalogItem.getPointsType(); 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) if (points <= 0 || pointsType != diamondType)
continue; continue;
@@ -291,13 +285,39 @@ public class CatalogManager {
} }
} }
this.rebuildRareValuesPayloadCache();
LOGGER.info("Furniture Values -> Loaded! ({} entries)", this.furnitureValues.size()); 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<int[]> 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<int[]> getFurnitureValues() { public TIntObjectMap<int[]> getFurnitureValues() {
return this.furnitureValues; return this.furnitureValues;
} }
public byte[] getRareValuesPayloadSnapshot() {
return this.rareValuesPayloadCache;
}
private synchronized void loadLimitedNumbers() { private synchronized void loadLimitedNumbers() {
this.limitedNumbers.clear(); this.limitedNumbers.clear();
@@ -1104,9 +1124,6 @@ public class CatalogManager {
type = type.replace("bot_", ""); type = type.replace("bot_", "");
type = type.replace("visitor_logger", "visitor_log"); 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) if (("bot_" + com.eu.habbo.habbohotel.bots.FrankBot.BOT_TYPE).equals(baseName)
|| ("rentable_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)) { if (!habbo.getClient().getHabbo().hasPermission(com.eu.habbo.habbohotel.bots.FrankBot.PERMISSION_USE)) {
@@ -42,14 +42,18 @@ public class RoomBundleLayout extends SingleBundle {
} }
if (this.room == null) { if (this.room == null) {
if (this.roomId > 0) { RoomManager roomManager = Emulator.getGameEnvironment().getRoomManager();
this.room = Emulator.getGameEnvironment().getRoomManager().loadRoom(this.roomId); if (this.roomId > 0 && roomManager != null) {
this.room = roomManager.loadRoom(this.roomId);
if (this.room != null) if (this.room != null)
this.room.preventUnloading = true; this.room.preventUnloading = true;
} else { } else if (this.roomId <= 0) {
LOGGER.error("No room id specified for room bundle {}({})", this.getPageName(), this.getId()); 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) { if (this.room == null) {
@@ -153,7 +153,13 @@ public class GameClient {
this.channel.close(); this.channel.close();
if (this.habbo != null) { 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 // Try to park the habbo in the grace period instead of immediate disconnect
boolean parked = SessionResumeManager.getInstance().parkHabbo(this.habbo, this.ssoTicket); boolean parked = SessionResumeManager.getInstance().parkHabbo(this.habbo, this.ssoTicket);
@@ -118,16 +118,32 @@ public class SessionResumeManager {
LOGGER.error("[SessionResume] Error during deferred disconnect", e); 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) { 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(); 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.setString(1, ssoTicket);
statement.setInt(2, userId); statement.setInt(2, userId);
statement.execute(); int updated = statement.executeUpdate();
LOGGER.info("[SessionResume] Restored SSO ticket for user {} during grace period", userId); 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) { } catch (Exception e) {
LOGGER.error("[SessionResume] Failed to restore SSO ticket for user " + userId, e); LOGGER.error("[SessionResume] Failed to restore SSO ticket for user " + userId, e);
} }
@@ -29,6 +29,10 @@ public class InteractionRoomAds extends InteractionCustomValues {
{ {
this.put("offsetZ", "0"); this.put("offsetZ", "0");
} }
{
this.put("scale", "100");
}
}; };
public InteractionRoomAds(ResultSet set, Item baseItem) throws SQLException { public InteractionRoomAds(ResultSet set, Item baseItem) throws SQLException {
@@ -132,15 +132,12 @@ public class HabboManager {
Emulator.getPluginManager().fireEvent(new UserRegisteredEvent(habbo)); Emulator.getPluginManager().fireEvent(new UserRegisteredEvent(habbo));
} }
if (!Emulator.debugging) { // NB: il ticket SSO NON viene svuotato qui di proposito. Dietro
try (PreparedStatement stmt = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) { // Cloudflare il WebSocket viene droppato e il client ritenta più
stmt.setString(1, ""); // volte con lo STESSO ticket: se lo consumassimo al primo uso, i
stmt.setInt(2, habbo.getHabboInfo().getId()); // retry (e l'hard-refresh) fallirebbero con "non-existing SSO token".
stmt.execute(); // Il ticket resta valido fino alla scadenza (auth_ticket_expires_at,
} catch (SQLException e) { // TTL gestito dal CMS) o finché il CMS non ne scrive uno nuovo / logout.
LOGGER.error("Caught SQL exception", e);
}
}
} }
} }
} catch (SQLException e) { } catch (SQLException e) {
@@ -133,17 +133,10 @@ public class SecureLoginEvent extends MessageHandler {
this.client.setHabbo(habbo); this.client.setHabbo(habbo);
this.client.setMachineId(habbo.getHabboInfo().getMachineID()); this.client.setMachineId(habbo.getHabboInfo().getMachineID());
// Clear the SSO ticket now that session is resumed (prevent reuse) // NB: NON svuotiamo il ticket SSO qui (vedi HabboManager.loadHabbo):
if (!Emulator.debugging) { // dietro Cloudflare il client ritenta la connessione con lo stesso
try (java.sql.Connection conn = Emulator.getDatabase().getDataSource().getConnection(); // ticket, quindi deve restare valido fino alla scadenza TTL. Consumarlo
java.sql.PreparedStatement stmt = conn.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) { // farebbe fallire i retry / l'hard-refresh con "non-existing SSO token".
stmt.setString(1, "");
stmt.setInt(2, habbo.getHabboInfo().getId());
stmt.execute();
} catch (Exception e) {
LOGGER.error("Failed to clear SSO ticket after session resume", e);
}
}
} else { } else {
// Normal login — load from database // Normal login — load from database
habbo = Emulator.getGameEnvironment().getHabboManager().loadHabbo(sso); habbo = Emulator.getGameEnvironment().getHabboManager().loadHabbo(sso);
@@ -1,11 +1,10 @@
package com.eu.habbo.messages.incoming.rarevalues; package com.eu.habbo.messages.incoming.rarevalues;
import com.eu.habbo.Emulator; import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.catalog.CatalogManager;
import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.rarevalues.RareValuesComposer; 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 { public class RequestRareValuesEvent extends MessageHandler {
@Override @Override
public int getRatelimit() { public int getRatelimit() {
@@ -14,8 +13,15 @@ public class RequestRareValuesEvent extends MessageHandler {
@Override @Override
public void handle() throws Exception { public void handle() throws Exception {
this.client.sendResponse(new RareValuesComposer( if (this.client.getHabbo() == null) return;
Emulator.getGameEnvironment().getCatalogManager().getFurnitureValues()
)); 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()));
} }
} }
@@ -6,18 +6,29 @@ import com.eu.habbo.messages.outgoing.Outgoing;
import gnu.trove.iterator.TIntObjectIterator; import gnu.trove.iterator.TIntObjectIterator;
import gnu.trove.map.TIntObjectMap; 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 { public class RareValuesComposer extends MessageComposer {
private final TIntObjectMap<int[]> values; private final TIntObjectMap<int[]> values;
private final byte[] snapshot;
public RareValuesComposer(byte[] snapshot) {
this.values = null;
this.snapshot = snapshot;
}
public RareValuesComposer(TIntObjectMap<int[]> values) { public RareValuesComposer(TIntObjectMap<int[]> values) {
this.values = values; this.values = values;
this.snapshot = null;
} }
@Override @Override
protected ServerMessage composeInternal() { protected ServerMessage composeInternal() {
this.response.init(Outgoing.RareValuesComposer); this.response.init(Outgoing.RareValuesComposer);
if (this.snapshot != null) {
this.response.appendRawBytes(this.snapshot);
return this.response;
}
this.response.appendInt(this.values.size()); this.response.appendInt(this.values.size());
TIntObjectIterator<int[]> iterator = this.values.iterator(); TIntObjectIterator<int[]> iterator = this.values.iterator();
@@ -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<UpdateSoundboard.SoundboardJSON> {
public UpdateSoundboard() {
super(SoundboardJSON.class);
}
@Override
public void handle(Gson gson, SoundboardJSON object) {
Emulator.getGameEnvironment().getSoundboardManager().reload();
}
static class SoundboardJSON {
}
}
@@ -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<UpdateWheel.WheelJSON> {
public UpdateWheel() {
super(WheelJSON.class);
}
@Override
public void handle(Gson gson, WheelJSON object) {
Emulator.getGameEnvironment().getWheelManager().reload();
}
static class WheelJSON {
}
}
@@ -11,6 +11,7 @@ import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.TooLongFrameException; import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.UnsupportedMessageTypeException; import io.netty.handler.codec.UnsupportedMessageTypeException;
import io.netty.handler.ssl.NotSslRecordException; import io.netty.handler.ssl.NotSslRecordException;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -39,6 +40,19 @@ public class GameMessageHandler extends ChannelInboundHandlerAdapter {
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 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; ClientMessage message = (ClientMessage) msg;
try { try {
@@ -45,6 +45,8 @@ public class RCONServer extends Server {
this.addRCONMessage("sendroombundle", SendRoomBundle.class); this.addRCONMessage("sendroombundle", SendRoomBundle.class);
this.addRCONMessage("setrank", SetRank.class); this.addRCONMessage("setrank", SetRank.class);
this.addRCONMessage("updatewordfilter", UpdateWordfilter.class); this.addRCONMessage("updatewordfilter", UpdateWordfilter.class);
this.addRCONMessage("updatewheel", UpdateWheel.class);
this.addRCONMessage("updatesoundboard", UpdateSoundboard.class);
this.addRCONMessage("updatecatalog", UpdateCatalog.class); this.addRCONMessage("updatecatalog", UpdateCatalog.class);
this.addRCONMessage("executecommand", ExecuteCommand.class); this.addRCONMessage("executecommand", ExecuteCommand.class);
this.addRCONMessage("progressachievement", ProgressAchievement.class); this.addRCONMessage("progressachievement", ProgressAchievement.class);