diff --git a/Database Updates/001_optimize_gameserver.sql b/Database Updates/001_optimize_gameserver.sql index 88546176..ac8518a7 100644 --- a/Database Updates/001_optimize_gameserver.sql +++ b/Database Updates/001_optimize_gameserver.sql @@ -88,5 +88,10 @@ DROP PROCEDURE IF EXISTS `_add_id_pk_if_missing`; DROP PROCEDURE IF EXISTS `_add_index_if_missing`; DROP PROCEDURE IF EXISTS `_add_fk_if_missing`; +# Make sure thenb System account exists +INSERT INTO `users` (`id`, `username`, `password`, `ip_register`, `ip_current`, `motto`, `look`, `rank`, `credits`) +SELECT 0, '[SYSTEM]', '!', '127.0.0.1', '127.0.0.1', 'System sentinel - do not delete', '', 1, 0 +WHERE NOT EXISTS (SELECT 1 FROM `users` WHERE `id` = 0); + SET FOREIGN_KEY_CHECKS = 1; SET SQL_MODE = @OLD_SQL_MODE; \ No newline at end of file diff --git a/Database Updates/007_PackageRateLimit.sql b/Database Updates/007_PackageRateLimit.sql new file mode 100644 index 00000000..0e6ffbc8 --- /dev/null +++ b/Database Updates/007_PackageRateLimit.sql @@ -0,0 +1,3 @@ +INSERT INTO emulator_settings (`key`, `value`) VALUES ('packet.global.rate.limit', '50'); + +ALTER TABLE `rooms` ADD COLUMN IF NOT EXISTS `youtube_enabled` TINYINT(1) NOT NULL DEFAULT 0; \ No newline at end of file diff --git a/Emulator/pom.xml b/Emulator/pom.xml index e5e2d4f7..12411054 100644 --- a/Emulator/pom.xml +++ b/Emulator/pom.xml @@ -6,7 +6,7 @@ com.eu.habbo Habbo - 4.1.0 + 4.1.1 UTF-8 @@ -86,11 +86,11 @@ 2.11.0 - + - com.mysql - mysql-connector-j - 9.1.0 + org.mariadb.jdbc + mariadb-java-client + 3.5.1 runtime diff --git a/Emulator/src/main/java/com/eu/habbo/core/DatabaseLogger.java b/Emulator/src/main/java/com/eu/habbo/core/DatabaseLogger.java index 2f8806a9..47d8c73f 100644 --- a/Emulator/src/main/java/com/eu/habbo/core/DatabaseLogger.java +++ b/Emulator/src/main/java/com/eu/habbo/core/DatabaseLogger.java @@ -7,6 +7,10 @@ import org.slf4j.LoggerFactory; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentLinkedQueue; public class DatabaseLogger { @@ -24,19 +28,41 @@ public class DatabaseLogger { return; } - if (this.loggables.isEmpty()) { + // Drain the queue into a local snapshot so new loggables that arrive + // during this save cycle roll into the next flush instead of extending + // the current one indefinitely. + List snapshot = new ArrayList<>(); + DatabaseLoggable next; + while ((next = this.loggables.poll()) != null) { + snapshot.add(next); + } + + if (snapshot.isEmpty()) { return; } + // Group by SQL query so each distinct statement only prepares and + // executeBatches once. LinkedHashMap preserves first-seen order so + // auto-increment ids on chat/log tables correlate with the time the + // events actually happened. + Map> byQuery = new LinkedHashMap<>(); + for (DatabaseLoggable loggable : snapshot) { + byQuery.computeIfAbsent(loggable.getQuery(), k -> new ArrayList<>()).add(loggable); + } + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection()) { - while (!this.loggables.isEmpty()) { - DatabaseLoggable loggable = this.loggables.remove(); - - try (PreparedStatement statement = connection.prepareStatement(loggable.getQuery())) { - loggable.log(statement); + for (Map.Entry> group : byQuery.entrySet()) { + List entries = group.getValue(); + try (PreparedStatement statement = connection.prepareStatement(group.getKey())) { + for (DatabaseLoggable loggable : entries) { + loggable.log(statement); + } statement.executeBatch(); + } catch (SQLException e) { + // One bad group shouldn't prevent other groups from flushing. + LOGGER.error("Exception caught while saving loggable group of size {}: {}", + entries.size(), group.getKey(), e); } - } } catch (SQLException e) { LOGGER.error("Exception caught while saving loggables to database.", e); diff --git a/Emulator/src/main/java/com/eu/habbo/database/Database.java b/Emulator/src/main/java/com/eu/habbo/database/Database.java index 00be0fee..3866b968 100644 --- a/Emulator/src/main/java/com/eu/habbo/database/Database.java +++ b/Emulator/src/main/java/com/eu/habbo/database/Database.java @@ -4,16 +4,15 @@ import com.eu.habbo.Emulator; import com.eu.habbo.core.ConfigurationManager; import com.zaxxer.hikari.HikariDataSource; import gnu.trove.map.hash.THashMap; -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.SQLException; +import java.util.ArrayList; +import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class Database { @@ -48,11 +47,9 @@ public class Database { } public void dispose() { - if (this.databasePool != null) { - this.databasePool.getDatabase().close(); + if (this.dataSource != null && !this.dataSource.isClosed()) { + this.dataSource.close(); } - - this.dataSource.close(); } public HikariDataSource getDataSource() { @@ -63,51 +60,77 @@ public class Database { return this.databasePool; } - public static PreparedStatement preparedStatementWithParams(Connection connection, String query, THashMap queryParams) throws SQLException { - THashMap params = new THashMap(); - THashSet quotedParams = new THashSet<>(); + public static PreparedStatement preparedStatementWithParams(Connection connection, + String query, + Map queryParams) throws SQLException { + StringBuilder positional = new StringBuilder(query.length()); + List bindValues = new ArrayList<>(); - for(String key : queryParams.keySet()) { - quotedParams.add(Pattern.quote(key)); - } + int i = 0; + int n = query.length(); - String regex = "(" + String.join("|", quotedParams) + ")"; + while (i < n) { + char c = query.charAt(i); - Matcher m = Pattern.compile(regex).matcher(query); - - int i = 1; - - while (m.find()) { - try { - params.put(i, queryParams.get(m.group(1))); + if (c == '\'') { + positional.append(c); i++; + while (i < n) { + char inner = query.charAt(i); + positional.append(inner); + i++; + if (inner == '\'') { + if (i < n && query.charAt(i) == '\'') { + positional.append('\''); + i++; + } else { + break; + } + } + } + continue; } - catch (Exception ignored) { } + + if (c == '@' && i + 1 < n && isNameStart(query.charAt(i + 1))) { + int start = i; + int j = i + 1; + while (j < n && isNamePart(query.charAt(j))) { + j++; + } + String name = query.substring(start, j); + if (!queryParams.containsKey(name)) { + throw new IllegalArgumentException( + "SQL template references parameter '" + name + "' but no value was provided"); + } + positional.append('?'); + bindValues.add(queryParams.get(name)); + i = j; + continue; + } + + positional.append(c); + i++; } - PreparedStatement statement = connection.prepareStatement(query.replaceAll(regex, "?")); - - for(Map.Entry set : params.entrySet()) { - if(set.getValue().getClass() == String.class) { - statement.setString(set.getKey(), (String)set.getValue()); - } - else if(set.getValue().getClass() == Integer.class) { - statement.setInt(set.getKey(), (Integer)set.getValue()); - } - else if(set.getValue().getClass() == Double.class) { - statement.setDouble(set.getKey(), (Double)set.getValue()); - } - else if(set.getValue().getClass() == Float.class) { - statement.setFloat(set.getKey(), (Float)set.getValue()); - } - else if(set.getValue().getClass() == Long.class) { - statement.setLong(set.getKey(), (Long)set.getValue()); - } - else { - statement.setObject(set.getKey(), set.getValue()); - } + PreparedStatement statement = connection.prepareStatement(positional.toString()); + for (int k = 0; k < bindValues.size(); k++) { + statement.setObject(k + 1, bindValues.get(k)); } - return statement; } + + @Deprecated + public static PreparedStatement preparedStatementWithParams(Connection connection, + String query, + THashMap queryParams) throws SQLException { + return preparedStatementWithParams(connection, query, (Map) queryParams); + } + + private static boolean isNameStart(char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_'; + } + + private static boolean isNamePart(char c) { + return isNameStart(c) || (c >= '0' && c <= '9'); + } } diff --git a/Emulator/src/main/java/com/eu/habbo/database/DatabasePool.java b/Emulator/src/main/java/com/eu/habbo/database/DatabasePool.java index 68a3e86c..acd50764 100644 --- a/Emulator/src/main/java/com/eu/habbo/database/DatabasePool.java +++ b/Emulator/src/main/java/com/eu/habbo/database/DatabasePool.java @@ -8,41 +8,85 @@ import org.slf4j.LoggerFactory; class DatabasePool { private final Logger log = LoggerFactory.getLogger(DatabasePool.class); + + // Connection settings + private static final String DB_HOSTNAME_KEY = "db.hostname"; + private static final String DB_PORT_KEY = "db.port"; + private static final String DB_PASSWORD_KEY = "db.password"; + private static final String DB_NAME_KEY = "db.database"; + private static final String DB_USER_KEY = "db.username"; + private static final String DB_PARAMS_KEY = "db.params"; + + // Pool sizing private static final String DB_POOL_MAX_SIZE = "db.pool.maxsize"; private static final String DB_POOL_MIN_SIZE = "db.pool.minsize"; - private static final String DB_HOSTNAME_KEY = "db.hostname"; - private static final String DB_PORT_KEY = "db.port"; - private static final String DB_PASSWORD_KEY = "db.password"; - private static final String DB_NAME_KEY = "db.database"; - private static final String DB_USER_KEY = "db.username"; - private static final String DB_PARAMS_KEY = "db.params"; + + // Pool tuning (all overridable via config.ini; sensible MariaDB defaults apply otherwise) + private static final String DB_POOL_CONNECTION_TIMEOUT = "db.pool.connection_timeout_ms"; + private static final String DB_POOL_IDLE_TIMEOUT = "db.pool.idle_timeout_ms"; + private static final String DB_POOL_MAX_LIFETIME = "db.pool.max_lifetime_ms"; + private static final String DB_POOL_LEAK_THRESHOLD = "db.pool.leak_detection_ms"; + private static final String DB_POOL_VALIDATION_TIMEOUT = "db.pool.validation_timeout_ms"; + private HikariDataSource database; - private static DatabasePool instance; DatabasePool() { } - public static synchronized DatabasePool getInstance() { - if (instance == null) { - instance = new DatabasePool(); - } - return instance; - } - public boolean getStoragePooling(ConfigurationManager config) { try { HikariConfig databaseConfiguration = new HikariConfig(); + + // Pool sizing databaseConfiguration.setMaximumPoolSize(config.getInt(DB_POOL_MAX_SIZE, 50)); databaseConfiguration.setMinimumIdle(config.getInt(DB_POOL_MIN_SIZE, 10)); - databaseConfiguration.setJdbcUrl("jdbc:mysql://" + config.getValue(DB_HOSTNAME_KEY, "localhost") + ":" + config.getValue(DB_PORT_KEY, "3306") + "/" + config.getValue(DB_NAME_KEY) + config.getValue(DB_PARAMS_KEY)); - databaseConfiguration.addDataSourceProperty("serverName", config.getValue(DB_HOSTNAME_KEY, "localhost")); - databaseConfiguration.addDataSourceProperty("port", config.getValue(DB_PORT_KEY, "3306")); - databaseConfiguration.addDataSourceProperty("databaseName", config.getValue(DB_NAME_KEY)); - databaseConfiguration.addDataSourceProperty("user", config.getValue(DB_USER_KEY)); - databaseConfiguration.addDataSourceProperty("password", config.getValue(DB_PASSWORD_KEY)); + + // Pool timeouts (milliseconds) + databaseConfiguration.setConnectionTimeout(config.getInt(DB_POOL_CONNECTION_TIMEOUT, 10_000)); + databaseConfiguration.setIdleTimeout(config.getInt(DB_POOL_IDLE_TIMEOUT, 600_000)); + databaseConfiguration.setMaxLifetime(config.getInt(DB_POOL_MAX_LIFETIME, 1_800_000)); + databaseConfiguration.setValidationTimeout(config.getInt(DB_POOL_VALIDATION_TIMEOUT, 5_000)); + + // Leak detection: 0 disables it. Default 20s helps locate connections + // that weren't closed in a try-with-resources block. + int leakThreshold = config.getInt(DB_POOL_LEAK_THRESHOLD, 20_000); + if (leakThreshold > 0) { + databaseConfiguration.setLeakDetectionThreshold(leakThreshold); + } + + // Use the MariaDB Connector/J native protocol instead of the Oracle MySQL driver. + databaseConfiguration.setJdbcUrl("jdbc:mariadb://" + + config.getValue(DB_HOSTNAME_KEY, "localhost") + ":" + + config.getValue(DB_PORT_KEY, "3306") + "/" + + config.getValue(DB_NAME_KEY) + + config.getValue(DB_PARAMS_KEY)); + databaseConfiguration.setUsername(config.getValue(DB_USER_KEY)); + databaseConfiguration.setPassword(config.getValue(DB_PASSWORD_KEY)); + + // Prepared-statement caching. Without these, Hikari's cache is off entirely + // and every prepareStatement() call re-parses on the server side. + databaseConfiguration.addDataSourceProperty("cachePrepStmts", "true"); + databaseConfiguration.addDataSourceProperty("prepStmtCacheSize", "500"); + databaseConfiguration.addDataSourceProperty("prepStmtCacheSqlLimit", "2048"); + databaseConfiguration.addDataSourceProperty("useServerPrepStmts", "true"); + + // Bulk write throughput: rewrites batched INSERTs into a single multi-value + // INSERT statement. Huge win for item/room/inventory persistence paths. + databaseConfiguration.addDataSourceProperty("rewriteBatchedStatements", "true"); + + // Cut per-connection round-trips. + databaseConfiguration.addDataSourceProperty("cacheServerConfiguration", "true"); + databaseConfiguration.addDataSourceProperty("useLocalSessionState", "true"); + databaseConfiguration.addDataSourceProperty("cacheResultSetMetadata", "true"); + databaseConfiguration.addDataSourceProperty("elideSetAutoCommits", "true"); + databaseConfiguration.addDataSourceProperty("maintainTimeStats", "false"); + + databaseConfiguration.setPoolName("HabboHikariPool"); + log.info("INITIALIZING DATABASE SERVER: " + config.getValue(DB_HOSTNAME_KEY)); log.info("ON PORT: " + config.getValue(DB_PORT_KEY)); log.info("HABBO DATABASE: " + config.getValue(DB_NAME_KEY)); + log.info("USING DRIVER: MariaDB Connector/J"); this.database = new HikariDataSource(databaseConfiguration); } catch (Exception e) { @@ -58,4 +102,4 @@ class DatabasePool { } return database; } -} \ No newline at end of file +} diff --git a/Emulator/src/main/java/com/eu/habbo/database/SqlQueries.java b/Emulator/src/main/java/com/eu/habbo/database/SqlQueries.java new file mode 100644 index 00000000..0d0e12be --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/database/SqlQueries.java @@ -0,0 +1,113 @@ +package com.eu.habbo.database; + +import com.eu.habbo.Emulator; + +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.Optional; + +public final class SqlQueries { + + private SqlQueries() { + } + + @FunctionalInterface + public interface RowMapper { + T map(ResultSet rs) throws SQLException; + } + + @FunctionalInterface + public interface RowConsumer { + void accept(ResultSet rs) throws SQLException; + } + + @FunctionalInterface + public interface ParameterBinder

{ + void bind(PreparedStatement ps, P value) throws SQLException; + } + + public static class DataAccessException extends RuntimeException { + public DataAccessException(String message, Throwable cause) { + super(message, cause); + } + } + + public static List query(String sql, RowMapper mapper, Object... params) { + try (Connection c = Emulator.getDatabase().getDataSource().getConnection(); + PreparedStatement ps = c.prepareStatement(sql)) { + bindAll(ps, params); + try (ResultSet rs = ps.executeQuery()) { + List out = new ArrayList<>(); + while (rs.next()) { + out.add(mapper.map(rs)); + } + return out; + } + } catch (SQLException e) { + throw new DataAccessException("query failed: " + sql, e); + } + } + + public static Optional queryOne(String sql, RowMapper mapper, Object... params) { + try (Connection c = Emulator.getDatabase().getDataSource().getConnection(); + PreparedStatement ps = c.prepareStatement(sql)) { + bindAll(ps, params); + try (ResultSet rs = ps.executeQuery()) { + return rs.next() ? Optional.ofNullable(mapper.map(rs)) : Optional.empty(); + } + } catch (SQLException e) { + throw new DataAccessException("queryOne failed: " + sql, e); + } + } + + public static void forEach(String sql, RowConsumer consumer, Object... params) { + try (Connection c = Emulator.getDatabase().getDataSource().getConnection(); + PreparedStatement ps = c.prepareStatement(sql)) { + bindAll(ps, params); + try (ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + consumer.accept(rs); + } + } + } catch (SQLException e) { + throw new DataAccessException("forEach failed: " + sql, e); + } + } + + public static int update(String sql, Object... params) { + try (Connection c = Emulator.getDatabase().getDataSource().getConnection(); + PreparedStatement ps = c.prepareStatement(sql)) { + bindAll(ps, params); + return ps.executeUpdate(); + } catch (SQLException e) { + throw new DataAccessException("update failed: " + sql, e); + } + } + + public static

int[] batchUpdate(String sql, Collection items, ParameterBinder

binder) { + try (Connection c = Emulator.getDatabase().getDataSource().getConnection(); + PreparedStatement ps = c.prepareStatement(sql)) { + for (P item : items) { + binder.bind(ps, item); + ps.addBatch(); + } + return ps.executeBatch(); + } catch (SQLException e) { + throw new DataAccessException("batchUpdate failed: " + sql, e); + } + } + + private static void bindAll(PreparedStatement ps, Object[] params) throws SQLException { + if (params == null) { + return; + } + for (int i = 0; i < params.length; i++) { + ps.setObject(i + 1, params[i]); + } + } +} 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 1336f88f..99c238c3 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 @@ -1,6 +1,7 @@ package com.eu.habbo.habbohotel.campaign.calendar; import com.eu.habbo.Emulator; +import com.eu.habbo.database.SqlQueries; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.messages.outgoing.events.calendar.AdventCalendarProductComposer; import com.eu.habbo.plugin.events.users.calendar.UserClaimRewardEvent; @@ -33,27 +34,27 @@ public class CalendarManager { public boolean reload() { this.dispose(); - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM calendar_campaigns WHERE enabled = 1")) { - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - calendarCampaigns.put(set.getInt("id"), new CalendarCampaign(set)); - } - } - } catch (SQLException e) { + try { + SqlQueries.query( + "SELECT * FROM calendar_campaigns WHERE enabled = 1", + CalendarCampaign::new) + .forEach(c -> calendarCampaigns.put(c.getId(), c)); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); return false; } - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM calendar_rewards")) { - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - CalendarCampaign campaign = calendarCampaigns.get(set.getInt("campaign_id")); - if(campaign != null){ - campaign.addReward(new CalendarRewardObject(set)); - } - } - } - } catch (SQLException e) { + try { + SqlQueries.query( + "SELECT * FROM calendar_rewards", + rs -> Map.entry(rs.getInt("campaign_id"), new CalendarRewardObject(rs))) + .forEach(entry -> { + CalendarCampaign campaign = calendarCampaigns.get(entry.getKey()); + if (campaign != null) { + campaign.addReward(entry.getValue()); + } + }); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); return false; } @@ -94,14 +95,12 @@ public class CalendarManager { public boolean deleteCampaign(CalendarCampaign campaign) { calendarCampaigns.remove(campaign.getId()); - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("DELETE FROM calendar_campaigns WHERE id = ? LIMIT 1")) { - statement.setInt(1, campaign.getId()); - return statement.execute(); - } catch (SQLException e) { + try { + return SqlQueries.update("DELETE FROM calendar_campaigns WHERE id = ? LIMIT 1", campaign.getId()) > 0; + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); + return false; } - - return false; } public CalendarCampaign getCalendarCampaign(String campaignName) { @@ -136,14 +135,15 @@ public class CalendarManager { habbo.getHabboStats().calendarRewardsClaimed.add(new CalendarRewardClaimed(habbo.getHabboInfo().getId(), campaign.getId(), day, object.getId(), new Timestamp(System.currentTimeMillis()))); habbo.getClient().sendResponse(new AdventCalendarProductComposer(true, object, habbo)); object.give(habbo); - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO calendar_rewards_claimed (user_id, campaign_id, day, reward_id, timestamp) VALUES (?, ?, ?, ?, ?)")) { - statement.setInt(1, habbo.getHabboInfo().getId()); - statement.setInt(2, campaign.getId()); - statement.setInt(3, day); - statement.setInt(4, object.getId()); - statement.setInt(5, Emulator.getIntUnixTimestamp()); - statement.execute(); - } catch (SQLException e) { + try { + SqlQueries.update( + "INSERT INTO calendar_rewards_claimed (user_id, campaign_id, day, reward_id, timestamp) VALUES (?, ?, ?, ?, ?)", + habbo.getHabboInfo().getId(), + campaign.getId(), + day, + object.getId(), + Emulator.getIntUnixTimestamp()); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/BadgeCommand.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/BadgeCommand.java index 1328ed19..8abf0b25 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/BadgeCommand.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/BadgeCommand.java @@ -36,7 +36,8 @@ public class BadgeCommand extends Command { Habbo habbo = Emulator.getGameEnvironment().getHabboManager().getHabbo(params[1]); if (habbo != null) { - if (habbo.addBadge(params[2])) { + String senderName = gameClient.getHabbo().getHabboInfo().getUsername(); + if (habbo.addBadge(params[2], senderName)) { gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.succes.cmd_badge.given").replace("%user%", params[1]).replace("%badge%", params[2]), RoomChatMessageBubbles.ALERT); } else { gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_badge.already_owned").replace("%user%", params[1]).replace("%badge%", params[2]), RoomChatMessageBubbles.ALERT); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/UserInfoCommand.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/UserInfoCommand.java index b4a3ca78..c2a244cc 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/UserInfoCommand.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/commands/UserInfoCommand.java @@ -84,7 +84,7 @@ public class UserInfoCommand extends Command { if (onlineHabbo != null) { message.append("\r" + "Other accounts ("); - ArrayList users = Emulator.getGameEnvironment().getHabboManager().getCloneAccounts(onlineHabbo, 10); + List users = Emulator.getGameEnvironment().getHabboManager().getCloneAccounts(onlineHabbo, 10); users.sort(new Comparator() { @Override public int compare(HabboInfo o1, HabboInfo o2) { 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 0fc8a660..9280c25d 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 @@ -683,7 +683,7 @@ public class ItemManager { statement.execute(); try (ResultSet set = statement.getGeneratedKeys()) { - try (PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO items_presents VALUES (?, ?)")) { + try (PreparedStatement preparedStatement = connection.prepareStatement("INSERT INTO items_presents (item_id, base_item_reward) VALUES (?, ?)")) { while (set.next() && item == null) { preparedStatement.setInt(1, set.getInt(1)); preparedStatement.setInt(2, Integer.parseInt(itemId)); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/messenger/Message.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/messenger/Message.java index a081bfc5..727729b6 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/messenger/Message.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/messenger/Message.java @@ -1,15 +1,14 @@ package com.eu.habbo.habbohotel.messenger; import com.eu.habbo.Emulator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import com.eu.habbo.core.DatabaseLoggable; -import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; -public class Message implements Runnable { - private static final Logger LOGGER = LoggerFactory.getLogger(Message.class); +public class Message implements Runnable, DatabaseLoggable { + + private static final String QUERY = "INSERT INTO chatlogs_private (user_from_id, user_to_id, message, timestamp) VALUES (?, ?, ?, ?)"; private final int fromId; private final int toId; @@ -26,20 +25,25 @@ public class Message implements Runnable { @Override public void run() { - //TODO Turn into scheduler if (Messenger.SAVE_PRIVATE_CHATS) { - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO chatlogs_private (user_from_id, user_to_id, message, timestamp) VALUES (?, ?, ?, ?)")) { - statement.setInt(1, this.fromId); - statement.setInt(2, this.toId); - statement.setString(3, this.message); - statement.setInt(4, this.timestamp); - statement.execute(); - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } + Emulator.getDatabaseLogger().store(this); } } + @Override + public String getQuery() { + return QUERY; + } + + @Override + public void log(PreparedStatement statement) throws SQLException { + statement.setInt(1, this.fromId); + statement.setInt(2, this.toId); + statement.setString(3, this.message); + statement.setInt(4, this.timestamp); + statement.addBatch(); + } + public int getToId() { return this.toId; } 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 77ee4857..ebc7cd05 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 @@ -194,7 +194,33 @@ public class Room implements Comparable, ISerialize, Runnable { private volatile boolean wiredSettingsLoaded; private int wiredInspectMask = WIRED_ACCESS_DEFAULT_INSPECT_MASK; private int wiredModifyMask = WIRED_ACCESS_DEFAULT_MODIFY_MASK; - + + private boolean youtubeEnabled = false; + private String youtubeCurrentVideo = ""; + private String youtubeSenderName = ""; + private final java.util.List youtubePlaylist = new java.util.concurrent.CopyOnWriteArrayList<>(); + private final java.util.Set youtubeWatchers = java.util.concurrent.ConcurrentHashMap.newKeySet(); + + public boolean isYoutubeEnabled() { return this.youtubeEnabled; } + public void setYoutubeEnabled(boolean enabled) { this.youtubeEnabled = enabled; } + public String getYoutubeCurrentVideo() { return this.youtubeCurrentVideo; } + public String getYoutubeSenderName() { return this.youtubeSenderName; } + public java.util.List getYoutubePlaylist() { return this.youtubePlaylist; } + public java.util.Set getYoutubeWatchers() { return this.youtubeWatchers; } + + public void setYoutubeVideo(String videoId, String senderName, java.util.List playlist) { + this.youtubeCurrentVideo = videoId; + this.youtubeSenderName = senderName; + this.youtubePlaylist.clear(); + if (playlist != null) this.youtubePlaylist.addAll(playlist); + } + + public void clearYoutubeVideo() { + this.youtubeCurrentVideo = ""; + this.youtubeSenderName = ""; + this.youtubePlaylist.clear(); + } + public final THashMap cache; public Room(ResultSet set) throws SQLException { @@ -222,6 +248,7 @@ public class Room implements Comparable, ISerialize, Runnable { this.allowPetsEat = set.getBoolean("allow_other_pets_eat"); this.allowWalkthrough = set.getBoolean("allow_walkthrough"); this.hideWall = set.getBoolean("allow_hidewall"); + try { this.youtubeEnabled = set.getBoolean("youtube_enabled"); } catch (Exception e) { this.youtubeEnabled = false; } this.chatMode = set.getInt("chat_mode"); this.chatWeight = set.getInt("chat_weight"); this.chatSpeed = set.getInt("chat_speed"); @@ -1151,7 +1178,7 @@ public class Room implements Comparable, ISerialize, Runnable { 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 = ?, allow_underpass = ?, builders_club_trial_locked = ?, builders_club_original_state = ? WHERE id = ?")) { + "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 = ?, allow_underpass = ?, youtube_enabled = ?, builders_club_trial_locked = ?, builders_club_original_state = ? WHERE id = ?")) { statement.setString(1, this.name); statement.setString(2, this.description); statement.setString(3, this.password); @@ -1201,9 +1228,10 @@ public class Room implements Comparable, ISerialize, Runnable { statement.setString(38, this.jukeboxActive ? "1" : "0"); statement.setString(39, this.hideWired ? "1" : "0"); statement.setString(40, this.allowUnderpass ? "1" : "0"); - statement.setString(41, this.buildersClubTrialLocked ? "1" : "0"); - statement.setString(42, (this.buildersClubOriginalState != null ? this.buildersClubOriginalState : RoomState.OPEN).name().toLowerCase()); - statement.setInt(43, this.id); + statement.setString(41, this.youtubeEnabled ? "1" : "0"); + statement.setString(42, this.buildersClubTrialLocked ? "1" : "0"); + statement.setString(43, (this.buildersClubOriginalState != null ? this.buildersClubOriginalState : RoomState.OPEN).name().toLowerCase()); + statement.setInt(44, this.id); statement.executeUpdate(); this.needsUpdate = false; } catch (SQLException e) { @@ -1865,13 +1893,31 @@ public class Room implements Comparable, ISerialize, Runnable { } public void removeHabbo(Habbo habbo) { + this.cleanupYoutubeWatcher(habbo); this.unitManager.removeHabbo(habbo); } public void removeHabbo(Habbo habbo, boolean sendRemovePacket) { + this.cleanupYoutubeWatcher(habbo); this.unitManager.removeHabbo(habbo, sendRemovePacket); } + private void cleanupYoutubeWatcher(Habbo habbo) { + if (habbo == null) return; + int userId = habbo.getHabboInfo().getId(); + + // If the broadcast sender leaves, stop the broadcast for everyone + if (!this.youtubeCurrentVideo.isEmpty() + && habbo.getHabboInfo().getUsername().equals(this.youtubeSenderName)) { + this.clearYoutubeVideo(); + this.sendComposer(new com.eu.habbo.messages.outgoing.rooms.youtube.YouTubeRoomBroadcastComposer("", "", java.util.Collections.emptyList()).compose()); + } + + if (this.youtubeWatchers.remove(userId)) { + this.sendComposer(new com.eu.habbo.messages.outgoing.rooms.youtube.YouTubeRoomWatchersComposer(this.youtubeWatchers).compose()); + } + } + public void addBot(Bot bot) { this.unitManager.addBot(bot); } @@ -2337,7 +2383,7 @@ public class Room implements Comparable, ISerialize, Runnable { this.rightsManager.refreshRightsForHabbo(habbo); } - public THashMap getUsersWithRights() { + public Map getUsersWithRights() { return this.rightsManager.getUsersWithRights(); } 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 a22414e9..a9699d0f 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 @@ -498,7 +498,7 @@ public class RoomManager { h.getClient().sendResponse(new RoomScoreComposer(room.getScore(), !this.hasVotedForRoom(h, room))); } - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO room_votes VALUES (?, ?)")) { + try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO room_votes (user_id, room_id) VALUES (?, ?)")) { statement.setInt(1, habbo.getHabboInfo().getId()); statement.setInt(2, room.getId()); statement.execute(); @@ -789,6 +789,15 @@ public class RoomManager { habbo.getRoomUnit().setHeadRotation(RoomUserRotation.values()[room.getLayout().getDoorDirection()]); } + if (habbo.getRoomUnit().getCurrentLocation() == null) { + LOGGER.warn("Failed to resolve a valid door tile for room {} ({}) while {} was entering; sending user back to hotel view", + room.getId(), room.getName(), habbo.getHabboInfo().getUsername()); + habbo.getHabboInfo().setLoadingRoom(0); + habbo.getHabboInfo().setCurrentRoom(null); + habbo.getClient().sendResponse(new HotelViewComposer()); + return; + } + habbo.getRoomUnit().setPathFinderRoom(room); habbo.getRoomUnit().resetIdleTimer(); @@ -797,7 +806,6 @@ public class RoomManager { BuildersClubRoomSupport.sendCurrentRoomPlacementStatus(room); room.getUserVariableManager().restorePermanentAssignments(habbo); - // Pre-send own wearing badges so the client cache is populated before the user clicks themselves habbo.getClient().sendResponse(new UserBadgesComposer(habbo.getInventory().getBadgesComponent().getWearingBadges(), habbo.getHabboInfo().getId())); List habbos = new ArrayList<>(); @@ -998,6 +1006,20 @@ public class RoomManager { } } + habbo.getClient().sendResponse(new com.eu.habbo.messages.outgoing.rooms.youtube.YouTubeRoomSettingsComposer( + room.isYoutubeEnabled()).compose()); + + if (!room.getYoutubeCurrentVideo().isEmpty()) { + habbo.getClient().sendResponse(new com.eu.habbo.messages.outgoing.rooms.youtube.YouTubeRoomBroadcastComposer( + room.getYoutubeCurrentVideo(), + room.getYoutubeSenderName(), + room.getYoutubePlaylist()).compose()); + } + if (!room.getYoutubeWatchers().isEmpty()) { + habbo.getClient().sendResponse(new com.eu.habbo.messages.outgoing.rooms.youtube.YouTubeRoomWatchersComposer( + room.getYoutubeWatchers()).compose()); + } + WiredManager.triggerUserEntersRoom(room, habbo.getRoomUnit()); room.habboEntered(habbo); 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 index b50edef8..c9eb266d 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomRightsManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/rooms/RoomRightsManager.java @@ -1,6 +1,7 @@ package com.eu.habbo.habbohotel.rooms; import com.eu.habbo.Emulator; +import com.eu.habbo.database.SqlQueries; import com.eu.habbo.habbohotel.guilds.Guild; import com.eu.habbo.habbohotel.guilds.GuildMember; import com.eu.habbo.habbohotel.guilds.GuildRank; @@ -16,7 +17,6 @@ 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; @@ -26,6 +26,9 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Collections; +import java.util.Map; +import java.util.stream.Collectors; /** * Manages room rights, bans, and mutes. @@ -156,13 +159,11 @@ public class RoomRightsManager { } 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) { + try { + SqlQueries.update( + "INSERT INTO room_rights (room_id, user_id) VALUES (?, ?)", + this.room.getId(), userId); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); } } @@ -203,13 +204,11 @@ public class RoomRightsManager { 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) { + try { + SqlQueries.update( + "DELETE FROM room_rights WHERE room_id = ? AND user_id = ?", + this.room.getId(), userId); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); } } @@ -232,12 +231,9 @@ public class RoomRightsManager { 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) { + try { + SqlQueries.update("DELETE FROM room_rights WHERE room_id = ?", this.room.getId()); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); } @@ -295,25 +291,22 @@ public class RoomRightsManager { /** * 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); - } + public Map getUsersWithRights() { + if (this.rights.isEmpty()) { + return Collections.emptyMap(); } - return rightsMap; + try { + return SqlQueries.query( + "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 = ?", + rs -> Map.entry(rs.getInt("user_id"), rs.getString("username")), + this.room.getId()) + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> b)); + } catch (SqlQueries.DataAccessException e) { + LOGGER.error("Caught SQL exception", e); + return Collections.emptyMap(); + } } /** 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 983ef85b..7b9326e0 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 @@ -411,11 +411,11 @@ public class RoomUnit { } public short getX() { - return this.currentLocation.x; + return this.currentLocation == null ? 0 : this.currentLocation.x; } public short getY() { - return this.currentLocation.y; + return this.currentLocation == null ? 0 : this.currentLocation.y; } public double getZ() { @@ -598,6 +598,7 @@ public class RoomUnit { } public boolean isAtGoal() { + if (this.currentLocation == null) return true; return this.currentLocation.equals(this.goalLocation); } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/Habbo.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/Habbo.java index 399fc1c0..61f8075e 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/Habbo.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/Habbo.java @@ -273,7 +273,7 @@ public class Habbo implements Runnable { return; this.getHabboInfo().addPixels(event.points); - if (this.client != null) this.client.sendResponse(new UserCurrencyComposer(this.client.getHabbo())); + if (this.client != null) this.client.sendResponse(new UserCurrencyComposer(this)); } @@ -292,7 +292,7 @@ public class Habbo implements Runnable { this.getHabboInfo().addCurrencyAmount(event.type, event.points); if (this.client != null) - this.client.sendResponse(new UserPointsComposer(this.client.getHabbo().getHabboInfo().getCurrencyAmount(type), event.points, event.type)); + this.client.sendResponse(new UserPointsComposer(this.getHabboInfo().getCurrencyAmount(type), event.points, event.type)); } @@ -303,7 +303,7 @@ public class Habbo implements Runnable { public void whisper(String message, RoomChatMessageBubbles bubble) { if (this.getRoomUnit().isInRoom()) { - this.client.sendResponse(new RoomUserWhisperComposer(new RoomChatMessage(message, this.client.getHabbo().getRoomUnit(), bubble))); + this.client.sendResponse(new RoomUserWhisperComposer(new RoomChatMessage(message, this.getRoomUnit(), bubble))); } } @@ -315,7 +315,7 @@ public class Habbo implements Runnable { public void talk(String message, RoomChatMessageBubbles bubble) { if (this.getRoomUnit().isInRoom()) { - this.getHabboInfo().getCurrentRoom().sendComposer(new RoomUserTalkComposer(new RoomChatMessage(message, this.client.getHabbo().getRoomUnit(), bubble)).compose()); + this.getHabboInfo().getCurrentRoom().sendComposer(new RoomUserTalkComposer(new RoomChatMessage(message, this.getRoomUnit(), bubble)).compose()); } } @@ -327,7 +327,7 @@ public class Habbo implements Runnable { public void shout(String message, RoomChatMessageBubbles bubble) { if (this.getRoomUnit().isInRoom()) { - this.getHabboInfo().getCurrentRoom().sendComposer(new RoomUserShoutComposer(new RoomChatMessage(message, this.client.getHabbo().getRoomUnit(), bubble)).compose()); + this.getHabboInfo().getCurrentRoom().sendComposer(new RoomUserShoutComposer(new RoomChatMessage(message, this.getRoomUnit(), bubble)).compose()); } } @@ -408,10 +408,14 @@ public class Habbo implements Runnable { public boolean addBadge(String code) { + return this.addBadge(code, ""); + } + + public boolean addBadge(String code, String senderName) { if (!this.habboInventory.getBadgesComponent().hasBadge(code)) { HabboBadge badge = BadgesComponent.createBadge(code, this); this.habboInventory.getBadgesComponent().addBadge(badge); - this.client.sendResponse(new AddUserBadgeComposer(badge)); + this.client.sendResponse(new AddUserBadgeComposer(badge, senderName)); this.client.sendResponse(new AddHabboItemComposer(badge.getId(), AddHabboItemComposer.AddHabboItemCategory.BADGE)); THashMap keys = new THashMap<>(); @@ -446,7 +450,7 @@ public class Habbo implements Runnable { this.client.sendResponse(new FloodCounterComposer(remaining)); this.client.sendResponse(new MutedWhisperComposer(remaining)); - Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); + Room room = this.getHabboInfo().getCurrentRoom(); if (room != null && !isFlood) { room.sendComposer(new RoomUserIgnoredComposer(this, RoomUserIgnoredComposer.MUTED).compose()); } @@ -456,7 +460,7 @@ public class Habbo implements Runnable { public void unMute() { this.habboStats.unMute(); this.client.sendResponse(new FloodCounterComposer(3)); - Room room = this.client.getHabbo().getHabboInfo().getCurrentRoom(); + Room room = this.getHabboInfo().getCurrentRoom(); if (room != null) { room.sendComposer(new RoomUserIgnoredComposer(this, RoomUserIgnoredComposer.UNIGNORED).compose()); } @@ -493,18 +497,18 @@ public class Habbo implements Runnable { public void respect(Habbo target) { - if (target != null && target != this.client.getHabbo()) { + if (target != null && target != this) { target.getHabboStats().respectPointsReceived++; - this.client.getHabbo().getHabboStats().respectPointsGiven++; - this.client.getHabbo().getHabboStats().respectPointsToGive--; - this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new RoomUserRespectComposer(target).compose()); - this.client.getHabbo().getHabboInfo().getCurrentRoom().sendComposer(new RoomUserActionComposer(this.client.getHabbo().getRoomUnit(), RoomUserAction.THUMB_UP).compose()); + this.getHabboStats().respectPointsGiven++; + this.getHabboStats().respectPointsToGive--; + this.getHabboInfo().getCurrentRoom().sendComposer(new RoomUserRespectComposer(target).compose()); + this.getHabboInfo().getCurrentRoom().sendComposer(new RoomUserActionComposer(this.getRoomUnit(), RoomUserAction.THUMB_UP).compose()); - AchievementManager.progressAchievement(this.client.getHabbo(), Emulator.getGameEnvironment().getAchievementManager().getAchievement("RespectGiven")); + AchievementManager.progressAchievement(this, Emulator.getGameEnvironment().getAchievementManager().getAchievement("RespectGiven")); AchievementManager.progressAchievement(target, Emulator.getGameEnvironment().getAchievementManager().getAchievement("RespectEarned")); - this.client.getHabbo().getHabboInfo().getCurrentRoom().unIdle(this.client.getHabbo()); - this.client.getHabbo().getHabboInfo().getCurrentRoom().dance(this.client.getHabbo().getRoomUnit(), DanceType.NONE); + this.getHabboInfo().getCurrentRoom().unIdle(this); + this.getHabboInfo().getCurrentRoom().dance(this.getRoomUnit(), DanceType.NONE); } } diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboInfo.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboInfo.java index 5c21f4ca..e53aee11 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboInfo.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/users/HabboInfo.java @@ -1,6 +1,7 @@ package com.eu.habbo.habbohotel.users; import com.eu.habbo.Emulator; +import com.eu.habbo.database.SqlQueries; import com.eu.habbo.habbohotel.catalog.CatalogItem; import com.eu.habbo.habbohotel.games.Game; import com.eu.habbo.habbohotel.games.GamePlayer; @@ -14,7 +15,6 @@ import com.eu.habbo.habbohotel.rooms.RoomTile; import com.eu.habbo.habbohotel.rooms.RoomUnit; import com.eu.habbo.messages.outgoing.rooms.users.RoomUserStatusComposer; import gnu.trove.map.hash.TIntIntHashMap; -import gnu.trove.procedure.TIntIntProcedure; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -104,53 +104,47 @@ public class HabboInfo implements Runnable { private void loadCurrencies() { this.currencies = new TIntIntHashMap(); - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM users_currency WHERE user_id = ?")) { - statement.setInt(1, this.id); - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - this.currencies.put(set.getInt("type"), set.getInt("amount")); - } - } - } catch (SQLException e) { + try { + SqlQueries.forEach( + "SELECT * FROM users_currency WHERE user_id = ?", + rs -> this.currencies.put(rs.getInt("type"), rs.getInt("amount")), + this.id); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); } } private void saveCurrencies() { - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO users_currency (user_id, type, amount) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE amount = ?")) { - this.currencies.forEachEntry(new TIntIntProcedure() { - @Override - public boolean execute(int a, int b) { - try { - statement.setInt(1, HabboInfo.this.getId()); - statement.setInt(2, a); - statement.setInt(3, b); - statement.setInt(4, b); - statement.addBatch(); - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); - } - return true; - } - }); - statement.executeBatch(); - } catch (SQLException e) { - LOGGER.error("Caught SQL exception", e); + List entries = new ArrayList<>(this.currencies.size()); + this.currencies.forEachEntry((type, amount) -> { + entries.add(new int[]{type, amount}); + return true; + }); + + try { + SqlQueries.batchUpdate( + "INSERT INTO users_currency (user_id, type, amount) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE amount = ?", + entries, + (ps, e) -> { + ps.setInt(1, this.id); + ps.setInt(2, e[0]); + ps.setInt(3, e[1]); + ps.setInt(4, e[1]); + }); + } catch (SqlQueries.DataAccessException ex) { + LOGGER.error("Caught SQL exception", ex); } } private void loadSavedSearches() { - this.savedSearches = new ArrayList<>(); - - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM users_saved_searches WHERE user_id = ?")) { - statement.setInt(1, this.id); - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - this.savedSearches.add(new NavigatorSavedSearch(set.getString("search_code"), set.getString("filter"), set.getInt("id"))); - } - } - } catch (SQLException e) { + try { + this.savedSearches = SqlQueries.query( + "SELECT * FROM users_saved_searches WHERE user_id = ?", + rs -> new NavigatorSavedSearch(rs.getString("search_code"), rs.getString("filter"), rs.getInt("id")), + this.id); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); + this.savedSearches = new ArrayList<>(); } } @@ -182,26 +176,22 @@ public class HabboInfo implements Runnable { public void deleteSavedSearch(NavigatorSavedSearch search) { this.savedSearches.remove(search); - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("DELETE FROM users_saved_searches WHERE id = ?")) { - statement.setInt(1, search.getId()); - statement.execute(); - } catch (SQLException e) { + try { + SqlQueries.update("DELETE FROM users_saved_searches WHERE id = ?", search.getId()); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); } } private void loadMessengerCategories() { - this.messengerCategories = new ArrayList<>(); - - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM messenger_categories WHERE user_id = ?")) { - statement.setInt(1, this.id); - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - this.messengerCategories.add(new MessengerCategory(set.getString("name"), set.getInt("user_id"), set.getInt("id"))); - } - } - } catch (SQLException e) { + try { + this.messengerCategories = SqlQueries.query( + "SELECT * FROM messenger_categories WHERE user_id = ?", + rs -> new MessengerCategory(rs.getString("name"), rs.getInt("user_id"), rs.getInt("id")), + this.id); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); + this.messengerCategories = new ArrayList<>(); } } @@ -232,10 +222,9 @@ public class HabboInfo implements Runnable { public void deleteMessengerCategory(MessengerCategory category) { this.messengerCategories.remove(category); - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("DELETE FROM messenger_categories WHERE id = ?")) { - statement.setInt(1, category.getId()); - statement.execute(); - } catch (SQLException e) { + try { + SqlQueries.update("DELETE FROM messenger_categories WHERE id = ?", category.getId()); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); } } @@ -389,12 +378,10 @@ public class HabboInfo implements Runnable { public void setPixels(int pixels) { this.setCurrencyAmount(0, pixels); - this.run(); } public void addPixels(int pixels) { this.addCurrencyAmount(0, pixels); - this.run(); } public int getLastOnline() { @@ -588,25 +575,26 @@ public class HabboInfo implements Runnable { public void run() { this.saveCurrencies(); - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE users SET motto = ?, online = ?, look = ?, gender = ?, credits = ?, last_login = ?, last_online = ?, home_room = ?, ip_current = ?, `rank` = ?, machine_id = ?, username = ?, background_id = ?, background_stand_id = ?, background_overlay_id = ? WHERE id = ?")) { - statement.setString(1, this.motto); - statement.setString(2, this.online ? "1" : "0"); - statement.setString(3, this.look); - statement.setString(4, this.gender.name()); - statement.setInt(5, this.credits); - statement.setInt(7, this.lastOnline); - statement.setInt(6, Emulator.getIntUnixTimestamp()); - statement.setInt(8, this.homeRoom); - statement.setString(9, this.ipLogin); - statement.setInt(10, this.rank != null ? this.rank.getId() : 1); - statement.setString(11, this.machineID); - statement.setString(12, this.username); - statement.setInt(13, this.InfostandBg); - statement.setInt(14, this.InfostandStand); - statement.setInt(15, this.InfostandOverlay); - statement.setInt(16, this.id); - statement.executeUpdate(); - } catch (SQLException e) { + try { + SqlQueries.update( + "UPDATE users SET motto = ?, online = ?, look = ?, gender = ?, credits = ?, last_login = ?, last_online = ?, home_room = ?, ip_current = ?, `rank` = ?, machine_id = ?, username = ?, background_id = ?, background_stand_id = ?, background_overlay_id = ? WHERE id = ?", + this.motto, + this.online ? "1" : "0", + this.look, + this.gender.name(), + this.credits, + Emulator.getIntUnixTimestamp(), + this.lastOnline, + this.homeRoom, + this.ipLogin, + this.rank != null ? this.rank.getId() : 1, + this.machineID, + this.username, + this.InfostandBg, + this.InfostandStand, + this.InfostandOverlay, + this.id); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); } } 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 8fc6aeb6..14fd99bf 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 @@ -1,6 +1,7 @@ package com.eu.habbo.habbohotel.users; import com.eu.habbo.Emulator; +import com.eu.habbo.database.SqlQueries; import com.eu.habbo.habbohotel.modtool.ModToolBan; import com.eu.habbo.habbohotel.permissions.Permission; import com.eu.habbo.habbohotel.permissions.Rank; @@ -22,6 +23,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.AbstractMap; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -47,37 +49,27 @@ public class HabboManager { } public static HabboInfo getOfflineHabboInfo(int id) { - HabboInfo info = null; - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM users WHERE id = ? LIMIT 1")) { - statement.setInt(1, id); - try (ResultSet set = statement.executeQuery()) { - if (set.next()) { - info = new HabboInfo(set); - } - } - } catch (SQLException e) { + try { + return SqlQueries.queryOne( + "SELECT * FROM users WHERE id = ? LIMIT 1", + HabboInfo::new, + id).orElse(null); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); + return null; } - - return info; } public static HabboInfo getOfflineHabboInfo(String username) { - HabboInfo info = null; - - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM users WHERE username = ? LIMIT 1")) { - statement.setString(1, username); - - try (ResultSet set = statement.executeQuery()) { - if (set.next()) { - info = new HabboInfo(set); - } - } - } catch (SQLException e) { + try { + return SqlQueries.queryOne( + "SELECT * FROM users WHERE username = ? LIMIT 1", + HabboInfo::new, + username).orElse(null); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); + return null; } - - return info; } public void addHabbo(Habbo habbo) { @@ -195,43 +187,32 @@ public class HabboManager { LOGGER.info("Habbo Manager -> Disposed!"); } - public ArrayList getCloneAccounts(Habbo habbo, int limit) { - ArrayList habboInfo = new ArrayList<>(); - - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT * FROM users WHERE (ip_register = ? OR ip_current = ?) AND id != ? ORDER BY id DESC LIMIT ?")) { - statement.setString(1, habbo.getHabboInfo().getIpRegister()); - statement.setString(2, habbo.getHabboInfo().getIpLogin()); - statement.setInt(3, habbo.getHabboInfo().getId()); - statement.setInt(4, limit); - - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - habboInfo.add(new HabboInfo(set)); - } - } - } catch (SQLException e) { + public List getCloneAccounts(Habbo habbo, int limit) { + try { + return SqlQueries.query( + "SELECT * FROM users WHERE (ip_register = ? OR ip_current = ?) AND id != ? ORDER BY id DESC LIMIT ?", + HabboInfo::new, + habbo.getHabboInfo().getIpRegister(), + habbo.getHabboInfo().getIpLogin(), + habbo.getHabboInfo().getId(), + limit); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); + return new ArrayList<>(); } - - return habboInfo; } public List> getNameChanges(int userId, int limit) { - List> nameChanges = new ArrayList<>(); - - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("SELECT timestamp, new_name FROM namechange_log WHERE user_id = ? ORDER by timestamp DESC LIMIT ?")) { - statement.setInt(1, userId); - statement.setInt(2, limit); - try (ResultSet set = statement.executeQuery()) { - while (set.next()) { - nameChanges.add(new AbstractMap.SimpleEntry<>(set.getInt("timestamp"), set.getString("new_name"))); - } - } - } catch (SQLException e) { + try { + return SqlQueries.query( + "SELECT timestamp, new_name FROM namechange_log WHERE user_id = ? ORDER by timestamp DESC LIMIT ?", + rs -> new AbstractMap.SimpleEntry<>(rs.getInt("timestamp"), rs.getString("new_name")), + userId, + limit); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); + return Collections.emptyList(); } - - return nameChanges; } @@ -277,11 +258,9 @@ public class HabboManager { habbo.getClient().sendResponse(new RecyclerLogicComposer()); habbo.alert(Emulator.getTexts().getValue("commands.generic.cmd_give_rank.new_rank").replace("id", newRank.getName())); } else { - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE users SET `rank` = ? WHERE id = ? LIMIT 1")) { - statement.setInt(1, rankId); - statement.setInt(2, userId); - statement.execute(); - } catch (SQLException e) { + try { + SqlQueries.update("UPDATE users SET `rank` = ? WHERE id = ? LIMIT 1", rankId, userId); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); } } @@ -294,11 +273,9 @@ public class HabboManager { if (habbo != null) { habbo.giveCredits(credits); } else { - try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("UPDATE users SET credits = credits + ? WHERE id = ? LIMIT 1")) { - statement.setInt(1, credits); - statement.setInt(2, userId); - statement.execute(); - } catch (SQLException e) { + try { + SqlQueries.update("UPDATE users SET credits = credits + ? WHERE id = ? LIMIT 1", credits, userId); + } catch (SqlQueries.DataAccessException e) { LOGGER.error("Caught SQL exception", e); } } 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 5e1efddd..83716292 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 @@ -14,7 +14,6 @@ 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.*; -import gnu.trove.map.hash.THashMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,6 +23,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.HashMap; import java.util.Map; import java.util.TreeMap; @@ -215,7 +215,7 @@ public class SubscriptionHabboClub extends Subscription { } } - THashMap queryParams = new THashMap<>(); + Map queryParams = new HashMap<>(); queryParams.put("@user_id", habbo.getId()); queryParams.put("@timestamp_start", habbo.getHabboStats().lastHCPayday); queryParams.put("@timestamp_end", HC_PAYDAY_NEXT_DATE); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java b/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java index 3297bf91..225ea04c 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java @@ -709,5 +709,10 @@ public class PacketManager { this.registerHandler(Incoming.GameCenterLeaveGameEvent, GameCenterLeaveGameEvent.class); this.registerHandler(Incoming.GameCenterEvent, GameCenterEvent.class); this.registerHandler(Incoming.GameCenterRequestGameStatusEvent, GameCenterRequestGameStatusEvent.class); + + // YouTube Room Broadcast + this.registerHandler(Incoming.YouTubeRoomPlayEvent, com.eu.habbo.messages.incoming.rooms.youtube.YouTubeRoomPlayEvent.class); + this.registerHandler(Incoming.YouTubeRoomWatchingEvent, com.eu.habbo.messages.incoming.rooms.youtube.YouTubeRoomWatchingEvent.class); + this.registerHandler(Incoming.YouTubeRoomSettingsEvent, com.eu.habbo.messages.incoming.rooms.youtube.YouTubeRoomSettingsEvent.class); } } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java index 071ba4a4..ed0ecdc7 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/Incoming.java @@ -453,4 +453,9 @@ public class Incoming { public static final int SetActiveNickIconEvent = 7017; public static final int PurchaseCatalogPrefixEvent = 7018; public static final int SetDisplayOrderEvent = 7019; + + // YouTube Room Broadcast + public static final int YouTubeRoomPlayEvent = 8001; + public static final int YouTubeRoomWatchingEvent = 8002; + public static final int YouTubeRoomSettingsEvent = 8003; } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/youtube/YouTubeRoomPlayEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/youtube/YouTubeRoomPlayEvent.java new file mode 100644 index 00000000..1dec152a --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/youtube/YouTubeRoomPlayEvent.java @@ -0,0 +1,63 @@ +package com.eu.habbo.messages.incoming.rooms.youtube; + +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.outgoing.rooms.youtube.YouTubeRoomBroadcastComposer; + +import java.util.ArrayList; +import java.util.List; + +public class YouTubeRoomPlayEvent extends MessageHandler { + + private static final int MAX_VIDEO_ID_LENGTH = 100; + private static final int MAX_PLAYLIST_ITEM_LENGTH = 200; + private static final int MAX_PLAYLIST_SIZE = 50; + + @Override + public int getRatelimit() { + // Max 1 broadcast every 2 seconds per client + return 2000; + } + + @Override + public void handle() throws Exception { + Habbo habbo = this.client.getHabbo(); + if (habbo == null) return; + + Room room = habbo.getHabboInfo().getCurrentRoom(); + if (room == null) return; + if (!room.isYoutubeEnabled()) return; + if (!room.isOwner(habbo) && !room.hasRights(habbo)) return; + + String videoId = this.packet.readString(); + if (videoId.length() > MAX_VIDEO_ID_LENGTH) { + videoId = videoId.substring(0, MAX_VIDEO_ID_LENGTH); + } + + int playlistCount = this.packet.readInt(); + if (playlistCount > MAX_PLAYLIST_SIZE) playlistCount = MAX_PLAYLIST_SIZE; + if (playlistCount < 0) playlistCount = 0; + + List playlist = new ArrayList<>(); + for (int i = 0; i < playlistCount; i++) { + String item = this.packet.readString(); + if (item.length() > MAX_PLAYLIST_ITEM_LENGTH) { + item = item.substring(0, MAX_PLAYLIST_ITEM_LENGTH); + } + playlist.add(item); + } + + // Store the current video + playlist on the room, or clear if empty + if (videoId.isEmpty()) { + room.clearYoutubeVideo(); + } else { + room.setYoutubeVideo(videoId, habbo.getHabboInfo().getUsername(), playlist); + } + + // Broadcast to everyone in the room (empty videoId = stop) + room.sendComposer( + new YouTubeRoomBroadcastComposer(videoId, habbo.getHabboInfo().getUsername(), playlist).compose() + ); + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/youtube/YouTubeRoomSettingsEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/youtube/YouTubeRoomSettingsEvent.java new file mode 100644 index 00000000..b3f8ceb5 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/youtube/YouTubeRoomSettingsEvent.java @@ -0,0 +1,35 @@ +package com.eu.habbo.messages.incoming.rooms.youtube; + +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.outgoing.rooms.youtube.YouTubeRoomBroadcastComposer; +import com.eu.habbo.messages.outgoing.rooms.youtube.YouTubeRoomSettingsComposer; + +public class YouTubeRoomSettingsEvent extends MessageHandler { + + @Override + public int getRatelimit() { + return 200; + } + + @Override + public void handle() throws Exception { + Habbo habbo = this.client.getHabbo(); + if (habbo == null) return; + + Room room = habbo.getHabboInfo().getCurrentRoom(); + if (room == null) return; + if (!room.isOwner(habbo)) return; + + boolean enabled = this.packet.readInt() == 1; + room.setYoutubeEnabled(enabled); + room.setNeedsUpdate(true); + room.sendComposer(new YouTubeRoomSettingsComposer(enabled).compose()); + + if (!enabled && !room.getYoutubeCurrentVideo().isEmpty()) { + room.clearYoutubeVideo(); + room.sendComposer(new YouTubeRoomBroadcastComposer("", "", java.util.Collections.emptyList()).compose()); + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/youtube/YouTubeRoomWatchingEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/youtube/YouTubeRoomWatchingEvent.java new file mode 100644 index 00000000..3de11651 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/youtube/YouTubeRoomWatchingEvent.java @@ -0,0 +1,39 @@ +package com.eu.habbo.messages.incoming.rooms.youtube; + +import com.eu.habbo.habbohotel.rooms.Room; +import com.eu.habbo.habbohotel.users.Habbo; +import com.eu.habbo.messages.incoming.MessageHandler; +import com.eu.habbo.messages.outgoing.rooms.youtube.YouTubeRoomWatchersComposer; + +public class YouTubeRoomWatchingEvent extends MessageHandler { + + @Override + public int getRatelimit() { + return 500; + } + + @Override + public void handle() throws Exception { + Habbo habbo = this.client.getHabbo(); + if (habbo == null) return; + + Room room = habbo.getHabboInfo().getCurrentRoom(); + if (room == null) return; + + boolean watching = this.packet.readInt() == 1; + int userId = habbo.getHabboInfo().getId(); + + boolean changed; + if (watching) { + changed = room.getYoutubeWatchers().add(userId); + } else { + changed = room.getYoutubeWatchers().remove(userId); + } + + if (changed) { + room.sendComposer( + new YouTubeRoomWatchersComposer(room.getYoutubeWatchers()).compose() + ); + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/Outgoing.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/Outgoing.java index 8d245894..45d8c986 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/Outgoing.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/Outgoing.java @@ -581,4 +581,9 @@ public class Outgoing { public static final int UserNickIconsComposer = 7004; public static final int AvailableCommandsComposer = 4050; + // YouTube Room Broadcast + public static final int YouTubeRoomBroadcastComposer = 8001; + public static final int YouTubeRoomWatchersComposer = 8002; + public static final int YouTubeRoomSettingsComposer = 8003; + } diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/RoomRightsListComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/RoomRightsListComposer.java index 42e2c571..f9626bc1 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/RoomRightsListComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/RoomRightsListComposer.java @@ -4,7 +4,6 @@ import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.messages.ServerMessage; import com.eu.habbo.messages.outgoing.MessageComposer; import com.eu.habbo.messages.outgoing.Outgoing; -import gnu.trove.map.hash.THashMap; import java.util.Map; @@ -20,7 +19,7 @@ public class RoomRightsListComposer extends MessageComposer { this.response.init(Outgoing.RoomRightsListComposer); this.response.appendInt(this.room.getId()); - THashMap rightsMap = this.room.getUsersWithRights(); + Map rightsMap = this.room.getUsersWithRights(); this.response.appendInt(rightsMap.size()); diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/youtube/YouTubeRoomBroadcastComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/youtube/YouTubeRoomBroadcastComposer.java new file mode 100644 index 00000000..7f8680b3 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/youtube/YouTubeRoomBroadcastComposer.java @@ -0,0 +1,31 @@ +package com.eu.habbo.messages.outgoing.rooms.youtube; + +import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.messages.outgoing.MessageComposer; +import com.eu.habbo.messages.outgoing.Outgoing; + +import java.util.List; + +public class YouTubeRoomBroadcastComposer extends MessageComposer { + private final String videoId; + private final String senderName; + private final List playlist; + + public YouTubeRoomBroadcastComposer(String videoId, String senderName, List playlist) { + this.videoId = videoId; + this.senderName = senderName; + this.playlist = playlist; + } + + @Override + protected ServerMessage composeInternal() { + this.response.init(Outgoing.YouTubeRoomBroadcastComposer); + this.response.appendString(this.videoId); + this.response.appendString(this.senderName); + this.response.appendInt(this.playlist.size()); + for (String id : this.playlist) { + this.response.appendString(id); + } + return this.response; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/youtube/YouTubeRoomSettingsComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/youtube/YouTubeRoomSettingsComposer.java new file mode 100644 index 00000000..64ff91cb --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/youtube/YouTubeRoomSettingsComposer.java @@ -0,0 +1,20 @@ +package com.eu.habbo.messages.outgoing.rooms.youtube; + +import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.messages.outgoing.MessageComposer; +import com.eu.habbo.messages.outgoing.Outgoing; + +public class YouTubeRoomSettingsComposer extends MessageComposer { + private final boolean youtubeEnabled; + + public YouTubeRoomSettingsComposer(boolean youtubeEnabled) { + this.youtubeEnabled = youtubeEnabled; + } + + @Override + protected ServerMessage composeInternal() { + this.response.init(Outgoing.YouTubeRoomSettingsComposer); + this.response.appendInt(this.youtubeEnabled ? 1 : 0); + return this.response; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/youtube/YouTubeRoomWatchersComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/youtube/YouTubeRoomWatchersComposer.java new file mode 100644 index 00000000..d57a3463 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/rooms/youtube/YouTubeRoomWatchersComposer.java @@ -0,0 +1,25 @@ +package com.eu.habbo.messages.outgoing.rooms.youtube; + +import com.eu.habbo.messages.ServerMessage; +import com.eu.habbo.messages.outgoing.MessageComposer; +import com.eu.habbo.messages.outgoing.Outgoing; + +import java.util.Set; + +public class YouTubeRoomWatchersComposer extends MessageComposer { + private final Set watcherIds; + + public YouTubeRoomWatchersComposer(Set watcherIds) { + this.watcherIds = watcherIds; + } + + @Override + protected ServerMessage composeInternal() { + this.response.init(Outgoing.YouTubeRoomWatchersComposer); + this.response.appendInt(this.watcherIds.size()); + for (int id : this.watcherIds) { + this.response.appendInt(id); + } + return this.response; + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/users/AddUserBadgeComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/users/AddUserBadgeComposer.java index d8802b20..1e4216bf 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/users/AddUserBadgeComposer.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/users/AddUserBadgeComposer.java @@ -7,9 +7,15 @@ import com.eu.habbo.messages.outgoing.Outgoing; public class AddUserBadgeComposer extends MessageComposer { private final HabboBadge badge; + private final String senderName; public AddUserBadgeComposer(HabboBadge badge) { + this(badge, ""); + } + + public AddUserBadgeComposer(HabboBadge badge, String senderName) { this.badge = badge; + this.senderName = senderName == null ? "" : senderName; } @Override @@ -17,10 +23,15 @@ public class AddUserBadgeComposer extends MessageComposer { this.response.init(Outgoing.AddUserBadgeComposer); this.response.appendInt(this.badge.getId()); this.response.appendString(this.badge.getCode()); + this.response.appendString(this.senderName); return this.response; } public HabboBadge getBadge() { return badge; } + + public String getSenderName() { + return senderName; + } } diff --git a/Emulator/src/main/java/com/eu/habbo/networking/gameserver/decoders/GameMessageRateLimit.java b/Emulator/src/main/java/com/eu/habbo/networking/gameserver/decoders/GameMessageRateLimit.java index 7a4c8de2..1b83a3ee 100644 --- a/Emulator/src/main/java/com/eu/habbo/networking/gameserver/decoders/GameMessageRateLimit.java +++ b/Emulator/src/main/java/com/eu/habbo/networking/gameserver/decoders/GameMessageRateLimit.java @@ -6,13 +6,18 @@ import com.eu.habbo.messages.ClientMessage; import com.eu.habbo.networking.gameserver.GameServerAttributes; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageDecoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.List; public class GameMessageRateLimit extends MessageToMessageDecoder { + private static final Logger LOGGER = LoggerFactory.getLogger(GameMessageRateLimit.class); + private static final int RESET_TIME = 1; private static final int MAX_COUNTER = 10; + private static final int DEFAULT_GLOBAL_MAX = 50; @Override protected void decode(ChannelHandlerContext ctx, ClientMessage message, List out) throws Exception { @@ -23,26 +28,36 @@ public class GameMessageRateLimit extends MessageToMessageDecoder } int count = 0; + int globalCount = 0; - // Check if reset time has passed. int timestamp = Emulator.getIntUnixTimestamp(); if (timestamp - client.lastPacketCounterCleared > RESET_TIME) { - // Reset counter. client.incomingPacketCounter.clear(); client.lastPacketCounterCleared = timestamp; } else { - // Get stored count for message id. count = client.incomingPacketCounter.getOrDefault(message.getMessageId(), 0); + for (int c : client.incomingPacketCounter.values()) { + globalCount += c; + } } - // If we exceeded the counter, drop the packet. if (count > MAX_COUNTER) { return; } + int globalMax = Emulator.getConfig().getInt("packet.global.rate.limit", DEFAULT_GLOBAL_MAX); + if (globalCount > globalMax) { + if (globalCount == globalMax + 1) { + String username = (client.getHabbo() != null && client.getHabbo().getHabboInfo() != null) + ? client.getHabbo().getHabboInfo().getUsername() : "unauthenticated"; + LOGGER.warn("Global packet rate limit exceeded for {} ({} packets/sec) — dropping excess packets", + username, globalCount); + } + return; + } + client.incomingPacketCounter.put(message.getMessageId(), ++count); - // Continue processing. out.add(message); } diff --git a/Emulator/src/main/resources/logback.xml b/Emulator/src/main/resources/logback.xml index 7c9f1ce7..f4a62d3f 100644 --- a/Emulator/src/main/resources/logback.xml +++ b/Emulator/src/main/resources/logback.xml @@ -56,6 +56,8 @@ + + diff --git a/Latest_Compiled_Version/Habbo-4.1.0-jar-with-dependencies.jar b/Latest_Compiled_Version/Habbo-4.1.1-jar-with-dependencies.jar similarity index 82% rename from Latest_Compiled_Version/Habbo-4.1.0-jar-with-dependencies.jar rename to Latest_Compiled_Version/Habbo-4.1.1-jar-with-dependencies.jar index 60f89e8f..6d32e973 100644 Binary files a/Latest_Compiled_Version/Habbo-4.1.0-jar-with-dependencies.jar and b/Latest_Compiled_Version/Habbo-4.1.1-jar-with-dependencies.jar differ diff --git a/Latest_Compiled_Version/config.ini.example b/Latest_Compiled_Version/config.ini.example index 1d05c91c..e1eee315 100644 --- a/Latest_Compiled_Version/config.ini.example +++ b/Latest_Compiled_Version/config.ini.example @@ -29,6 +29,14 @@ ws.whitelist=localhost #Header name for real client IP when behind a proxy (e.g., X-Forwarded-For, CF-Connecting-IP). Leave empty if not using a proxy. ws.ip.header= +# Databse configuration +db.pool.connection_timeout_ms = 10000 +db.pool.idle_timeout_ms = 600000 +db.pool.max_lifetime_ms = 1800000 +db.pool.validation_timeout_ms = 5000 +# set db.pool.leak_detection_ms to 0 to disable +db.pool.leak_detection_ms = 20000 set to 0 to disable + enc.enabled=false enc.e=3 enc.n=86851dd364d5c5cece3c883171cc6ddc5760779b992482bd1e20dd296888df91b33b936a7b93f06d29e8870f703a216257dec7c81de0058fea4cc5116f75e6efc4e9113513e45357dc3fd43d4efab5963ef178b78bd61e81a14c603b24c8bcce0a12230b320045498edc29282ff0603bc7b7dae8fc1b05b52b2f301a9dc783b7