Compare commits

..

29 Commits

Author SHA1 Message Date
github-actions[bot] fadec887cd 🆙 Bump version to 4.2.35 [skip ci] 2026-06-03 14:45:16 +00:00
DuckieTM e614c1d64f Merge pull request #150 from duckietm/dev
Merge pull request #149 from duckietm/main
2026-06-03 16:44:04 +02:00
DuckieTM e7deea7d9d Merge pull request #149 from duckietm/main
sync to dev
2026-06-03 16:39:01 +02:00
github-actions[bot] 44ea3abd4e 🆙 Bump version to 4.2.34 [skip ci] 2026-06-03 14:37:38 +00:00
DuckieTM 609cd20ab2 Merge pull request #143 from simoleo89/feat/command-autocomplete-refactor
Structure commands alert output
2026-06-03 16:36:33 +02:00
github-actions[bot] 717a7f184f 🆙 Bump version to 4.2.33 [skip ci] 2026-06-03 14:23:40 +00:00
DuckieTM 2862446686 Merge pull request #148 from duckietm/dev
🆙 More updates mentions
2026-06-03 16:22:39 +02:00
duckietm e97e680006 🆙 More updates mentions 2026-06-03 16:20:02 +02:00
github-actions[bot] 7e59dca192 🆙 Bump version to 4.2.32 [skip ci] 2026-06-03 12:20:44 +00:00
DuckieTM 1109d53720 Merge pull request #147 from duckietm/dev
Dev
2026-06-03 14:19:42 +02:00
duckietm f12363a5b7 Merge branch 'dev' of https://github.com/duckietm/Arcturus-Morningstar-Extended into dev 2026-06-03 14:17:28 +02:00
duckietm 7d4ffec74e 🆙 Small Fixes mention 2026-06-03 14:17:25 +02:00
github-actions[bot] 281fede58c 🆙 Bump version to 4.2.31 [skip ci] 2026-06-03 08:56:45 +00:00
DuckieTM edf152485b Merge pull request #145 from duckietm/dev
Dev
2026-06-03 10:55:46 +02:00
DuckieTM 18a1bfbe90 Merge branch 'main' into dev 2026-06-03 10:55:37 +02:00
duckietm 7c32bbfd2d 🆙 wordfilter to set specific settings to prefix 2026-06-03 10:39:44 +02:00
DuckieTM 4eae206b64 Merge pull request #140 from simoleo89/feat/mentions-system
feat(mentions): server-side mention detection, persistence & packets
2026-06-03 09:49:45 +02:00
github-actions[bot] 155b2202c7 🆙 Bump version to 4.2.30 [skip ci] 2026-06-03 07:48:08 +00:00
DuckieTM 10c291eb9f Merge pull request #144 from duckietm/dev
Dev
2026-06-03 09:47:03 +02:00
duckietm 349a8c727e 🆙 Update SQL 2026-06-03 09:46:43 +02:00
duckietm 68f2b71d14 🆙 Updated Prefixes : Now use wordfilter / table custom_prefix_blacklist can be droped 2026-06-03 09:42:43 +02:00
duckietm 69a6c0d060 🆙 Make group forums private, so only memeber can view it 2026-06-03 07:46:59 +02:00
simoleo89 9f36d95dbc fix(commands): structure commands alert output 2026-06-02 18:34:50 +02:00
github-actions[bot] 885bdca0c4 🆙 Bump version to 4.2.29 [skip ci] 2026-06-02 16:03:45 +00:00
DuckieTM db035294a7 Merge pull request #142 from duckietm/dev
🆙 Updated Group buy
2026-06-02 18:02:42 +02:00
duckietm 3216ba1df6 🆙 Updated Group buy 2026-06-02 18:02:25 +02:00
Life c9a47b1fac Merge branch 'duckietm:main' into feat/mentions-system 2026-06-02 17:38:25 +02:00
simoleo89 7624d3fbc3 feat(mentions): server-side delete packet + robust direct-nick resolution 2026-06-02 14:44:08 +02:00
simoleo89 e9129576a9 feat(mentions): server-side detection, persistence and packets 2026-05-31 21:47:56 +02:00
38 changed files with 1241 additions and 276 deletions
@@ -322,13 +322,6 @@ CREATE TABLE IF NOT EXISTS `custom_prefix_settings` (
PRIMARY KEY (`key_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `custom_prefix_blacklist` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`word` VARCHAR(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_custom_prefix_blacklist_word` (`word`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT IGNORE INTO `custom_prefix_settings` (`key_name`, `value`) VALUES
('max_length', '15'),
('min_rank_to_buy', '1'),
@@ -0,0 +1,76 @@
CREATE TABLE IF NOT EXISTS `habbo_mentions` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`target_user_id` INT(11) NOT NULL,
`sender_user_id` INT(11) NOT NULL,
`sender_username` VARCHAR(64) NOT NULL DEFAULT '',
`room_id` INT(11) NOT NULL DEFAULT 0,
`room_name` VARCHAR(64) NOT NULL DEFAULT '',
`message` VARCHAR(255) NOT NULL DEFAULT '',
`mention_type` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '0 = direct (@nick), 1 = broadcast (@all/@friends/@room)',
`timestamp` INT(11) NOT NULL DEFAULT 0,
`read` TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`),
KEY `idx_target_id` (`target_user_id`, `id`),
KEY `idx_target_unread` (`target_user_id`, `read`),
KEY `idx_target_timestamp` (`target_user_id`, `timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `permission_definitions`
(`permission_key`, `max_value`, `comment`,
`rank_1`, `rank_2`, `rank_3`, `rank_4`, `rank_5`, `rank_6`, `rank_7`, `rank_8`)
VALUES
('acc_mention_everyone', 1,
'Allow sending @all / @everyone / @tutti broadcast mentions (hotel-wide).',
0, 0, 0, 0, 1, 1, 1, 1),
('acc_mention_friends', 1,
'Allow sending @friends / @amici broadcast mentions (sender''s online buddies).',
0, 0, 0, 0, 1, 1, 1, 1),
('cmd_disablementions', 1,
'Allow toggling :disablementions to stop receiving any @mention notifications.',
1, 1, 1, 1, 1, 1, 1, 1),
('cmd_disablemassmentions', 1,
'Allow toggling :disablemassmentions to stop receiving broadcast mentions (direct @nick still works).',
1, 1, 1, 1, 1, 1, 1, 1)
ON DUPLICATE KEY UPDATE
`comment` = VALUES(`comment`);
-- ----------------------------------------------------------------------------
-- 3. Emulator settings: cooldowns, caps and alias lists
--
-- Only inserted when missing - existing tuned values are preserved.
-- ----------------------------------------------------------------------------
INSERT IGNORE INTO `emulator_settings` (`key`, `value`, `comment`) VALUES
('mentions.enabled', '1',
'Master switch. 1 = process @mentions, 0 = disable the feature entirely.'),
('mentions.max.targets', '50',
'Hard cap on how many users a single broadcast (@all / @friends / @room) can fan out to.'),
('mentions.cooldown.ms', '3000',
'Per-sender cooldown between any two mentions, in milliseconds.'),
('mentions.room.cooldown.ms', '15000',
'Extra per-sender cooldown for broadcast mentions (@all / @friends / @room) on top of mentions.cooldown.ms.'),
('mentions.store.limit', '50',
'Number of mentions returned in the initial RequestMentionsList response.'),
('mentions.request.cooldown.ms', '2000',
'Per-user cooldown between RequestMentionsList packets.'),
('mentions.markread.cooldown.ms', '500',
'Per-user cooldown between mark-single-as-read packets.'),
('mentions.markall.cooldown.ms', '5000',
'Per-user cooldown between mark-all-as-read packets (bulk DB update).'),
('mentions.delete.cooldown.ms', '500',
'Per-user cooldown between delete-mention packets.'),
('mentions.everyone.aliases', 'all,everyone,tutti',
'Comma-separated aliases that trigger an @everyone broadcast (requires acc_mention_everyone).'),
('mentions.friends.aliases', 'friends,amici',
'Comma-separated aliases that trigger an @friends broadcast (requires acc_mention_friends).'),
('mentions.room.aliases', 'room,stanza',
'Comma-separated aliases that trigger an @room broadcast (no permission required, room scope only).');
ALTER TABLE `wordfilter`
ADD COLUMN `prefix_only` ENUM('0','1') NOT NULL DEFAULT '0'
COMMENT 'When 1, this word only applies to custom prefixes, not to chat/motto/guild.' AFTER `mute`;
@@ -63,15 +63,6 @@ CREATE TABLE IF NOT EXISTS `custom_prefix_settings` (
PRIMARY KEY (`key_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ------------------------------------------------------------
-- 5. Blacklist table
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS `custom_prefix_blacklist` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`word` VARCHAR(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_word` (`word`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ============================================================
-- Schema upgrades for existing installations
@@ -296,14 +287,6 @@ INSERT IGNORE INTO `custom_prefixes_catalog`
(2, 'Legend', 'Legend', '#8B5CF6', '', 'discord-neon', '', 15, 0, 1, 2),
(3, 'Staff Pick', 'Staff', '#3B82F6', '*', 'cartoon', '', 20, 0, 1, 3);
-- ============================================================
-- Example blacklist entries
-- ============================================================
INSERT IGNORE INTO `custom_prefix_blacklist` (`word`) VALUES
('admin'),
('staff'),
('mod'),
('owner');
-- ============================================================
-- Notes
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.eu.habbo</groupId>
<artifactId>Habbo</artifactId>
<version>4.2.28</version>
<version>4.2.35</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -8,6 +8,7 @@ import com.eu.habbo.habbohotel.campaign.calendar.CalendarManager;
import com.eu.habbo.habbohotel.catalog.CatalogManager;
import com.eu.habbo.habbohotel.wheel.WheelManager;
import com.eu.habbo.habbohotel.soundboard.SoundboardManager;
import com.eu.habbo.habbohotel.mentions.MentionManager;
import com.eu.habbo.habbohotel.commands.CommandHandler;
import com.eu.habbo.habbohotel.crafting.CraftingManager;
import com.eu.habbo.habbohotel.guides.GuideManager;
@@ -68,6 +69,7 @@ public class GameEnvironment {
private InfostandBackgroundManager infostandBackgroundManager;
private WheelManager wheelManager;
private SoundboardManager soundboardManager;
private MentionManager mentionManager;
public void load() throws Exception {
LOGGER.info("GameEnvironment -> Loading...");
@@ -99,6 +101,7 @@ public class GameEnvironment {
this.infostandBackgroundManager = new InfostandBackgroundManager();
this.wheelManager = new WheelManager();
this.soundboardManager = new SoundboardManager();
this.mentionManager = new MentionManager();
this.roomManager.loadPublicRooms();
this.navigatorManager.loadNavigator();
@@ -202,6 +205,10 @@ public class GameEnvironment {
return this.petManager;
}
public MentionManager getMentionManager() {
return this.mentionManager;
}
public AchievementManager getAchievementManager() {
return this.achievementManager;
}
@@ -191,6 +191,8 @@ public class CommandHandler {
addCommand(new CreditsCommand());
addCommand(new DanceCommand());
addCommand(new DiagonalCommand());
addCommand(new DisableMassMentionsCommand());
addCommand(new DisableMentionsCommand());
addCommand(new DisconnectCommand());
addCommand(new EjectAllCommand());
addCommand(new EmptyInventoryCommand());
@@ -301,7 +303,6 @@ public class CommandHandler {
addCommand(new GivePrefixCommand());
addCommand(new ListPrefixesCommand());
addCommand(new RemovePrefixCommand());
addCommand(new PrefixBlacklistCommand());
addCommand(new WiredCommand());
addCommand(new TestCommand());
}
@@ -17,7 +17,22 @@ public class CommandsCommand extends Command {
message.append("(").append(commands.size()).append("):\r\n");
for (Command c : commands) {
message.append(Emulator.getTexts().getValue("commands.description." + c.permission, "commands.description." + c.permission)).append("\r");
String textKey = "commands.description." + c.permission;
String commandText = Emulator.getTexts().getValue(textKey, "");
String commandLine = ":" + c.keys[0];
String description = "";
if (commandText.startsWith(":")) {
commandLine = commandText;
} else if (!commandText.isEmpty() && !commandText.equals(textKey)) {
description = commandText;
}
message.append(commandLine).append("\r");
if (!description.isEmpty()) {
message.append(description).append("\r");
}
}
gameClient.getHabbo().alert(new String[]{message.toString()});
@@ -0,0 +1,25 @@
package com.eu.habbo.habbohotel.commands;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.users.Habbo;
public class DisableMassMentionsCommand extends Command {
public DisableMassMentionsCommand() {
super("cmd_disablemassmentions", new String[]{"disablemassmentions", "togglemassmentions"});
}
@Override
public boolean handle(GameClient gameClient, String[] params) throws Exception {
if (gameClient == null) return true;
Habbo habbo = gameClient.getHabbo();
if (habbo == null || habbo.getHabboStats() == null) return true;
boolean newState = !habbo.getHabboStats().massMentionsEnabled();
habbo.getHabboStats().setMassMentionsEnabled(newState);
habbo.whisper(newState
? "Broadcast mentions (@all / @friends / @room) are now ENABLED for you."
: "Broadcast mentions (@all / @friends / @room) are now DISABLED for you. Direct @nick mentions still work.");
return true;
}
}
@@ -0,0 +1,25 @@
package com.eu.habbo.habbohotel.commands;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.users.Habbo;
public class DisableMentionsCommand extends Command {
public DisableMentionsCommand() {
super("cmd_disablementions", new String[]{"disablementions", "togglementions"});
}
@Override
public boolean handle(GameClient gameClient, String[] params) throws Exception {
if (gameClient == null) return true;
Habbo habbo = gameClient.getHabbo();
if (habbo == null || habbo.getHabboStats() == null) return true;
boolean newState = !habbo.getHabboStats().mentionsEnabled();
habbo.getHabboStats().setMentionsEnabled(newState);
habbo.whisper(newState
? "@mention notifications are now ENABLED for you."
: "@mention notifications are now DISABLED for you. You will not receive direct or broadcast mentions.");
return true;
}
}
@@ -4,6 +4,7 @@ import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.modtool.WordFilter;
import com.eu.habbo.habbohotel.modtool.WordFilterWord;
import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -21,30 +22,44 @@ public class FilterWordCommand extends Command {
@Override
public boolean handle(GameClient gameClient, String[] params) throws Exception {
if (params.length < 2) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_filterword.missing_word"));
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_filterword.missing_word"), RoomChatMessageBubbles.ALERT);
return true;
}
String word = params[1];
// Optional trailing "prefix" keyword marks the word as prefix-only (blocks
// custom prefixes but not chat/motto/guild). Usage:
// :filterword <word> -> everywhere, default replacement
// :filterword <word> <replacement> -> everywhere
// :filterword <word> prefix -> prefix-only, default replacement
// :filterword <word> <replacement> prefix -> prefix-only
boolean prefixOnly = false;
String replacement = WordFilter.DEFAULT_REPLACEMENT;
if (params.length == 3) {
replacement = params[2];
if (params.length >= 3) {
if (params[params.length - 1].equalsIgnoreCase("prefix")) {
prefixOnly = true;
if (params.length >= 4) replacement = params[2];
} else {
replacement = params[2];
}
}
WordFilterWord wordFilterWord = new WordFilterWord(word, replacement);
WordFilterWord wordFilterWord = new WordFilterWord(word, replacement, prefixOnly);
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO wordfilter (`key`, `replacement`) VALUES (?, ?)")) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); PreparedStatement statement = connection.prepareStatement("INSERT INTO wordfilter (`key`, `replacement`, `prefix_only`) VALUES (?, ?, ?)")) {
statement.setString(1, word);
statement.setString(2, replacement);
statement.setString(3, prefixOnly ? "1" : "0");
statement.execute();
} catch (SQLException e) {
LOGGER.error("Caught SQL exception", e);
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_filterword.error"));
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_filterword.error"), RoomChatMessageBubbles.ALERT);
return true;
}
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.succes.cmd_filterword.added").replace("%word%", word).replace("%replacement%", replacement));
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.succes.cmd_filterword.added").replace("%word%", word).replace("%replacement%", replacement) + (prefixOnly ? " [prefix-only]" : ""), RoomChatMessageBubbles.ALERT);
Emulator.getGameEnvironment().getWordFilter().addWord(wordFilterWord);
return true;
@@ -1,98 +0,0 @@
package com.eu.habbo.habbohotel.commands;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.gameclients.GameClient;
import com.eu.habbo.habbohotel.rooms.RoomChatMessageBubbles;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class PrefixBlacklistCommand extends Command {
private static final Logger LOGGER = LoggerFactory.getLogger(PrefixBlacklistCommand.class);
public PrefixBlacklistCommand() {
super("cmd_prefix_blacklist", Emulator.getTexts().getValue("commands.keys.cmd_prefix_blacklist").split(";"));
}
@Override
public boolean handle(GameClient gameClient, String[] params) throws Exception {
if (params.length < 2) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.usage"), RoomChatMessageBubbles.ALERT);
return true;
}
String action = params[1].toLowerCase();
if (action.equals("list")) {
StringBuilder sb = new StringBuilder();
sb.append(Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.header")).append("\r");
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT word FROM custom_prefix_blacklist ORDER BY word")) {
try (ResultSet set = statement.executeQuery()) {
int count = 0;
while (set.next()) {
sb.append("- ").append(set.getString("word")).append("\r");
count++;
}
if (count == 0) {
sb.append(Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.empty"));
}
}
} catch (SQLException e) {
LOGGER.error("Error listing prefix blacklist", e);
}
gameClient.getHabbo().whisper(sb.toString(), RoomChatMessageBubbles.ALERT);
return true;
}
if (params.length < 3) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.usage"), RoomChatMessageBubbles.ALERT);
return true;
}
String word = params[2].toLowerCase().trim();
if (word.isEmpty()) {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.empty_word"), RoomChatMessageBubbles.ALERT);
return true;
}
if (action.equals("add")) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("INSERT INTO custom_prefix_blacklist (word) VALUES (?)")) {
statement.setString(1, word);
statement.execute();
} catch (SQLException e) {
LOGGER.error("Error adding prefix blacklist word", e);
}
gameClient.getHabbo().whisper(
Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.added").replace("%word%", word),
RoomChatMessageBubbles.ALERT
);
} else if (action.equals("remove")) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("DELETE FROM custom_prefix_blacklist WHERE word = ?")) {
statement.setString(1, word);
statement.execute();
} catch (SQLException e) {
LOGGER.error("Error removing prefix blacklist word", e);
}
gameClient.getHabbo().whisper(
Emulator.getTexts().getValue("commands.succes.cmd_prefix_blacklist.removed").replace("%word%", word),
RoomChatMessageBubbles.ALERT
);
} else {
gameClient.getHabbo().whisper(Emulator.getTexts().getValue("commands.error.cmd_prefix_blacklist.usage"), RoomChatMessageBubbles.ALERT);
}
return true;
}
}
@@ -252,6 +252,25 @@ public class Guild implements Runnable {
return this.readForum;
}
public boolean canHabboReadForum(int habboId, GuildMember member, boolean staff) {
if (staff || this.getOwnerId() == habboId) {
return true;
}
switch (this.readForum) {
case EVERYONE:
return true;
case MEMBERS:
return member != null && member.getRank().type <= GuildRank.MEMBER.type;
case ADMINS:
return member != null && member.getRank().type < GuildRank.MEMBER.type;
case OWNER:
return false;
default:
return true;
}
}
public void setReadForum(SettingsState readForum) {
this.readForum = readForum;
}
@@ -0,0 +1,90 @@
package com.eu.habbo.habbohotel.mentions;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.users.Habbo;
import java.sql.ResultSet;
import java.sql.SQLException;
public class HabboMention {
public static final int TYPE_DIRECT = 0;
public static final int TYPE_ROOM = 1;
private final int id;
private final int targetUserId;
private final int senderUserId;
private final String senderUsername;
private final int roomId;
private final String roomName;
private final String message;
private final int mentionType;
private final int timestamp;
private final boolean read;
public HabboMention(ResultSet set) throws SQLException {
this.id = set.getInt("id");
this.targetUserId = set.getInt("target_user_id");
this.senderUserId = set.getInt("sender_user_id");
this.senderUsername = set.getString("sender_username");
this.roomId = set.getInt("room_id");
this.roomName = set.getString("room_name");
this.message = set.getString("message");
this.mentionType = set.getInt("mention_type");
this.timestamp = set.getInt("timestamp");
this.read = set.getInt("read") == 1;
}
public HabboMention(int targetUserId, int id, Habbo sender, Room room, String roomName, String message, int mentionType, int timestamp) {
this.id = id;
this.targetUserId = targetUserId;
this.senderUserId = sender.getHabboInfo().getId();
this.senderUsername = sender.getHabboInfo().getUsername();
this.roomId = room.getId();
this.roomName = roomName;
this.message = message;
this.mentionType = mentionType;
this.timestamp = timestamp;
this.read = false;
}
public int getId() {
return this.id;
}
public int getTargetUserId() {
return this.targetUserId;
}
public int getSenderUserId() {
return this.senderUserId;
}
public String getSenderUsername() {
return this.senderUsername;
}
public int getRoomId() {
return this.roomId;
}
public String getRoomName() {
return this.roomName;
}
public String getMessage() {
return this.message;
}
public int getMentionType() {
return this.mentionType;
}
public int getTimestamp() {
return this.timestamp;
}
public boolean isRead() {
return this.read;
}
}
@@ -0,0 +1,437 @@
package com.eu.habbo.habbohotel.mentions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.messenger.MessengerBuddy;
import com.eu.habbo.habbohotel.rooms.Room;
import com.eu.habbo.habbohotel.rooms.RoomChatType;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class MentionManager {
private static final Logger LOGGER = LoggerFactory.getLogger(MentionManager.class);
private static final int ROOM_NAME_MAX_LENGTH = 64;
private static final int MESSAGE_MAX_LENGTH = 255;
private final ConcurrentHashMap<Integer, Long> cooldowns = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Integer, Long> roomBroadcastCooldowns = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Integer, Long> requestListCooldowns = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Integer, Long> markReadCooldowns = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Integer, Long> markAllCooldowns = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Integer, Long> deleteCooldowns = new ConcurrentHashMap<>();
private volatile long lastPrune = System.currentTimeMillis();
private static final long PRUNE_INTERVAL_MS = 5 * 60_000L;
public boolean isEnabled() {
return Emulator.getConfig().getInt("mentions.enabled", 1) == 1;
}
/** Broadcast category resolved from a mention alias. */
public enum BroadcastScope {
NONE,
ROOM,
FRIENDS,
EVERYONE
}
public static final String PERMISSION_EVERYONE = "acc_mention_everyone";
public static final String PERMISSION_FRIENDS = "acc_mention_friends";
private Set<String> parseAliases(String configKey, String defaultValue) {
Set<String> aliases = new HashSet<>();
String raw = Emulator.getConfig().getValue(configKey, defaultValue);
for (String alias : raw.split(",")) {
String trimmed = alias.trim().toLowerCase();
if (!trimmed.isEmpty()) {
aliases.add(trimmed);
}
}
return aliases;
}
private Set<String> roomAliases() {
return parseAliases("mentions.room.aliases", "room,stanza");
}
private Set<String> friendsAliases() {
return parseAliases("mentions.friends.aliases", "friends,amici");
}
private Set<String> everyoneAliases() {
return parseAliases("mentions.everyone.aliases", "all,everyone,tutti");
}
private BroadcastScope classifyAlias(String alias,
Set<String> everyone,
Set<String> friends,
Set<String> room) {
if (alias.isEmpty()) return BroadcastScope.NONE;
if (everyone.contains(alias)) return BroadcastScope.EVERYONE;
if (friends.contains(alias)) return BroadcastScope.FRIENDS;
if (room.contains(alias)) return BroadcastScope.ROOM;
return BroadcastScope.NONE;
}
public void process(Habbo sender, Room room, String message, RoomChatType type) {
try {
if (!this.isEnabled()) {
return;
}
if (sender == null || room == null || message == null) {
return;
}
if (message.isEmpty() || message.indexOf('@') < 0) {
return;
}
int senderId = sender.getHabboInfo().getId();
long now = System.currentTimeMillis();
long cooldownMs = Emulator.getConfig().getInt("mentions.cooldown.ms", 3000);
Long last = this.cooldowns.get(senderId);
if (last != null && (now - last) < cooldownMs) {
return;
}
Set<String> roomAliases = this.roomAliases();
Set<String> friendsAliases = this.friendsAliases();
Set<String> everyoneAliases = this.everyoneAliases();
BroadcastScope broadcastScope = BroadcastScope.NONE;
LinkedHashSet<String> directTokens = new LinkedHashSet<>();
for (String token : message.split("\\s+")) {
if (token.length() < 2 || token.charAt(0) != '@') {
continue;
}
String raw = token.substring(1);
String aliasCandidate = trimTrailingPunctuation(raw).toLowerCase();
BroadcastScope scope = this.classifyAlias(aliasCandidate, everyoneAliases, friendsAliases, roomAliases);
if (scope != BroadcastScope.NONE) {
if (scope.ordinal() > broadcastScope.ordinal()) {
broadcastScope = scope;
}
} else if (!raw.isEmpty()) {
directTokens.add(raw);
}
}
if (broadcastScope == BroadcastScope.EVERYONE && !sender.hasPermission(PERMISSION_EVERYONE)) {
broadcastScope = BroadcastScope.NONE;
} else if (broadcastScope == BroadcastScope.FRIENDS && !sender.hasPermission(PERMISSION_FRIENDS)) {
broadcastScope = BroadcastScope.NONE;
}
if (broadcastScope == BroadcastScope.NONE && directTokens.isEmpty()) {
return;
}
if (broadcastScope != BroadcastScope.NONE) {
long roomCooldownMs = Emulator.getConfig().getInt("mentions.room.cooldown.ms", 15000);
Long lastRoom = this.roomBroadcastCooldowns.get(senderId);
if (lastRoom != null && (now - lastRoom) < roomCooldownMs) {
return;
}
}
int maxTargets = Emulator.getConfig().getInt("mentions.max.targets", 50);
if (maxTargets <= 0) maxTargets = 1;
int maxDirectTokens = Math.min(directTokens.size(), maxTargets);
List<Habbo> targets = new ArrayList<>();
Set<Integer> seen = new HashSet<>();
switch (broadcastScope) {
case EVERYONE:
this.collectEveryoneTargets(senderId, targets, seen, maxTargets);
break;
case FRIENDS:
this.collectFriendsTargets(sender, senderId, targets, seen, maxTargets);
break;
case ROOM:
this.collectRoomTargets(room, senderId, targets, seen, maxTargets, true);
break;
case NONE:
default:
int processed = 0;
for (String token : directTokens) {
if (processed++ >= maxDirectTokens) break;
Habbo habbo = this.resolveHabbo(room, token);
if (habbo == null || habbo.getHabboInfo().getId() == senderId) {
continue;
}
if (!acceptsMention(habbo, false)) {
continue;
}
if (seen.add(habbo.getHabboInfo().getId())) {
targets.add(habbo);
}
if (targets.size() >= maxTargets) {
break;
}
}
break;
}
if (targets.isEmpty()) {
return;
}
this.cooldowns.put(senderId, now);
if (broadcastScope != BroadcastScope.NONE) this.roomBroadcastCooldowns.put(senderId, now);
this.pruneCooldownsIfDue(now);
int mentionType = (broadcastScope != BroadcastScope.NONE) ? HabboMention.TYPE_ROOM : HabboMention.TYPE_DIRECT;
int timestamp = Emulator.getIntUnixTimestamp();
String roomName = truncate(room.getName(), ROOM_NAME_MAX_LENGTH);
String storedMessage = truncate(message, MESSAGE_MAX_LENGTH);
for (Habbo target : targets) {
this.store(target, sender, room, storedMessage, mentionType, timestamp, roomName);
}
} catch (Exception e) {
LOGGER.error("Failed to process mentions.", e);
}
}
private void collectRoomTargets(Room room, int senderId, List<Habbo> targets, Set<Integer> seen, int maxTargets, boolean isBroadcast) {
for (Habbo habbo : room.getHabbos()) {
if (habbo == null || habbo.getHabboInfo().getId() == senderId) continue;
if (!acceptsMention(habbo, isBroadcast)) continue;
if (seen.add(habbo.getHabboInfo().getId())) targets.add(habbo);
if (targets.size() >= maxTargets) break;
}
}
private void collectFriendsTargets(Habbo sender, int senderId, List<Habbo> targets, Set<Integer> seen, int maxTargets) {
if (sender.getMessenger() == null) return;
HabboManager habboManager = Emulator.getGameEnvironment().getHabboManager();
for (MessengerBuddy buddy : sender.getMessenger().getFriends().values()) {
if (buddy == null) continue;
int buddyId = buddy.getId();
if (buddyId == senderId) continue;
Habbo online = habboManager.getHabbo(buddyId);
if (online == null) continue;
if (!acceptsMention(online, true)) continue;
if (seen.add(buddyId)) targets.add(online);
if (targets.size() >= maxTargets) break;
}
}
private void collectEveryoneTargets(int senderId, List<Habbo> targets, Set<Integer> seen, int maxTargets) {
for (Habbo habbo : Emulator.getGameEnvironment().getHabboManager().getOnlineHabbos().values()) {
if (habbo == null || habbo.getHabboInfo().getId() == senderId) continue;
if (!acceptsMention(habbo, true)) continue;
if (seen.add(habbo.getHabboInfo().getId())) targets.add(habbo);
if (targets.size() >= maxTargets) break;
}
}
private boolean acceptsMention(Habbo recipient, boolean isBroadcast) {
if (recipient == null || recipient.getHabboStats() == null) return true;
if (!recipient.getHabboStats().mentionsEnabled()) return false;
if (isBroadcast && !recipient.getHabboStats().massMentionsEnabled()) return false;
return true;
}
private void store(Habbo target, Habbo sender, Room room, String message, int mentionType, int timestamp, String roomName) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"INSERT INTO habbo_mentions (target_user_id, sender_user_id, sender_username, room_id, room_name, message, mention_type, timestamp, `read`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0)",
Statement.RETURN_GENERATED_KEYS)) {
statement.setInt(1, target.getHabboInfo().getId());
statement.setInt(2, sender.getHabboInfo().getId());
statement.setString(3, sender.getHabboInfo().getUsername());
statement.setInt(4, room.getId());
statement.setString(5, roomName);
statement.setString(6, message);
statement.setInt(7, mentionType);
statement.setInt(8, timestamp);
statement.executeUpdate();
int generatedId = 0;
try (ResultSet keys = statement.getGeneratedKeys()) {
if (keys.next()) {
generatedId = keys.getInt(1);
}
}
if (generatedId <= 0) {
return;
}
HabboMention mention = new HabboMention(target.getHabboInfo().getId(), generatedId, sender, room, roomName, message, mentionType, timestamp);
if (target.getClient() != null) {
target.getClient().sendResponse(new com.eu.habbo.messages.outgoing.mentions.MentionReceivedComposer(mention));
}
} catch (SQLException e) {
LOGGER.error("Failed to store mention.", e);
}
}
public List<HabboMention> getMentions(int userId, int limit) {
List<HabboMention> mentions = new ArrayList<>();
if (limit <= 0) limit = 50;
if (limit > 200) limit = 200;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"SELECT * FROM habbo_mentions WHERE target_user_id = ? ORDER BY id DESC LIMIT ?")) {
statement.setInt(1, userId);
statement.setInt(2, limit);
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
mentions.add(new HabboMention(set));
}
}
} catch (SQLException e) {
LOGGER.error("Failed to load mentions.", e);
}
return mentions;
}
public void markRead(int userId, int mode, int mentionId) {
if (mode != 0 && mode != 1) return;
if (mode == 1 && mentionId <= 0) return;
String query = mode == 1
? "UPDATE habbo_mentions SET `read` = 1 WHERE target_user_id = ? AND id = ? AND `read` = 0"
: "UPDATE habbo_mentions SET `read` = 1 WHERE target_user_id = ? AND `read` = 0";
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(query)) {
statement.setInt(1, userId);
if (mode == 1) {
statement.setInt(2, mentionId);
}
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.error("Failed to mark mentions as read.", e);
}
}
public void delete(int userId, int mentionId) {
if (mentionId <= 0) return;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(
"DELETE FROM habbo_mentions WHERE target_user_id = ? AND id = ?")) {
statement.setInt(1, userId);
statement.setInt(2, mentionId);
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.error("Failed to delete mention.", e);
}
}
public boolean tryAcquireRequestList(int userId) {
long cooldownMs = Emulator.getConfig().getInt("mentions.request.cooldown.ms", 2000);
return tryAcquire(this.requestListCooldowns, userId, cooldownMs);
}
public boolean tryAcquireMarkRead(int userId, int mode) {
long cooldownMs;
ConcurrentHashMap<Integer, Long> bucket;
if (mode == 1) {
cooldownMs = Emulator.getConfig().getInt("mentions.markread.cooldown.ms", 500);
bucket = this.markReadCooldowns;
} else {
cooldownMs = Emulator.getConfig().getInt("mentions.markall.cooldown.ms", 5000);
bucket = this.markAllCooldowns;
}
return tryAcquire(bucket, userId, cooldownMs);
}
public boolean tryAcquireDelete(int userId) {
long cooldownMs = Emulator.getConfig().getInt("mentions.delete.cooldown.ms", 500);
return tryAcquire(this.deleteCooldowns, userId, cooldownMs);
}
private boolean tryAcquire(ConcurrentHashMap<Integer, Long> bucket, int userId, long cooldownMs) {
long now = System.currentTimeMillis();
Long last = bucket.get(userId);
if (last != null && (now - last) < cooldownMs) {
return false;
}
bucket.put(userId, now);
this.pruneCooldownsIfDue(now);
return true;
}
private void pruneCooldownsIfDue(long now) {
if (now - this.lastPrune < PRUNE_INTERVAL_MS) return;
this.lastPrune = now;
long mentionWindow = Emulator.getConfig().getInt("mentions.cooldown.ms", 3000);
long roomWindow = Emulator.getConfig().getInt("mentions.room.cooldown.ms", 15000);
long requestWindow = Emulator.getConfig().getInt("mentions.request.cooldown.ms", 2000);
long markReadWindow = Emulator.getConfig().getInt("mentions.markread.cooldown.ms", 500);
long markAllWindow = Emulator.getConfig().getInt("mentions.markall.cooldown.ms", 5000);
long deleteWindow = Emulator.getConfig().getInt("mentions.delete.cooldown.ms", 500);
prune(this.cooldowns, now, mentionWindow);
prune(this.roomBroadcastCooldowns, now, roomWindow);
prune(this.requestListCooldowns, now, requestWindow);
prune(this.markReadCooldowns, now, markReadWindow);
prune(this.markAllCooldowns, now, markAllWindow);
prune(this.deleteCooldowns, now, deleteWindow);
}
private static void prune(ConcurrentHashMap<Integer, Long> bucket, long now, long windowMs) {
Iterator<Map.Entry<Integer, Long>> it = bucket.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, Long> entry = it.next();
Long value = entry.getValue();
if (value == null || (now - value) >= windowMs) {
it.remove();
}
}
}
private static final String TRAILING_PUNCTUATION = ".,!?;:)]}\"'";
private static String trimTrailingPunctuation(String value) {
int end = value.length();
while (end > 0 && TRAILING_PUNCTUATION.indexOf(value.charAt(end - 1)) >= 0) {
end--;
}
return value.substring(0, end);
}
private static String truncate(String value, int max) {
if (value == null) return "";
if (value.length() <= max) return value;
return value.substring(0, max);
}
private Habbo resolveHabbo(Room room, String rawToken) {
Habbo habbo = room.getHabbo(rawToken);
if (habbo != null) {
return habbo;
}
String trimmed = trimTrailingPunctuation(rawToken);
if (!trimmed.isEmpty() && !trimmed.equals(rawToken)) {
return room.getHabbo(trimmed);
}
return null;
}
}
@@ -23,7 +23,6 @@ public class WordFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(WordFilter.class);
private static final Pattern DIACRITICS_AND_FRIENDS = Pattern.compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+");
//Configuration. Loaded from database & updated accordingly.
public static boolean ENABLED_FRIENDCHAT = true;
public static String DEFAULT_REPLACEMENT = "bobba";
protected THashSet<WordFilterWord> autoReportWords = new THashSet<>();
@@ -63,10 +62,12 @@ public class WordFilter {
continue;
}
if (word.autoReport)
this.autoReportWords.add(word);
else if (word.hideMessage)
this.hideMessageWords.add(word);
if (!word.prefixOnly) {
if (word.autoReport)
this.autoReportWords.add(word);
else if (word.hideMessage)
this.hideMessageWords.add(word);
}
this.words.add(word);
}
@@ -146,6 +147,8 @@ public class WordFilter {
while (iterator.hasNext()) {
WordFilterWord word = (WordFilterWord) iterator.next();
if (word.prefixOnly) continue;
if (StringUtils.containsIgnoreCase(filteredMessage, word.key)) {
if (habbo != null) {
if (Emulator.getPluginManager().fireEvent(new UserTriggerWordFilterEvent(habbo, word)).isCancelled())
@@ -179,6 +182,8 @@ public class WordFilter {
while (iterator.hasNext()) {
WordFilterWord word = (WordFilterWord) iterator.next();
if (word.prefixOnly) continue;
if (StringUtils.containsIgnoreCase(message, word.key)) {
if (habbo != null) {
if (Emulator.getPluginManager().fireEvent(new UserTriggerWordFilterEvent(habbo, word)).isCancelled())
@@ -9,6 +9,7 @@ public class WordFilterWord {
public final boolean hideMessage;
public final boolean autoReport;
public final int muteTime;
public final boolean prefixOnly;
public WordFilterWord(ResultSet set) throws SQLException {
this.key = set.getString("key");
@@ -16,13 +17,27 @@ public class WordFilterWord {
this.hideMessage = set.getInt("hide") == 1;
this.autoReport = set.getInt("report") == 1;
this.muteTime = set.getInt("mute");
this.prefixOnly = readBooleanColumn(set, "prefix_only");
}
public WordFilterWord(String key, String replacement) {
this(key, replacement, false);
}
public WordFilterWord(String key, String replacement, boolean prefixOnly) {
this.key = key;
this.replacement = replacement;
this.hideMessage = false;
this.autoReport = false;
this.muteTime = 0;
this.prefixOnly = prefixOnly;
}
private static boolean readBooleanColumn(ResultSet set, String column) {
try {
return set.getInt(column) == 1;
} catch (SQLException e) {
return false;
}
}
}
@@ -94,6 +94,8 @@ public class HabboStats implements Runnable {
public boolean hasGottenDefaultSavedSearches;
private HabboInfo habboInfo;
private boolean allowTrade;
private boolean mentionsEnabled;
private boolean massMentionsEnabled;
private int clubExpireTimestamp;
private int muteEndTime;
public int maxFriends;
@@ -131,6 +133,8 @@ public class HabboStats implements Runnable {
this.guilds = new ArrayList<>();
this.tags = set.getString("tags").split(";");
this.allowTrade = set.getString("can_trade").equals("1");
this.mentionsEnabled = "1".equals(safeColumnString(set, "mentions_enabled", "1"));
this.massMentionsEnabled = "1".equals(safeColumnString(set, "mass_mentions_enabled", "1"));
this.votedRooms = new TIntArrayStack();
this.clubExpireTimestamp = set.getInt("club_expire_timestamp");
this.loginStreak = set.getInt("login_streak");
@@ -749,13 +753,6 @@ public class HabboStats implements Runnable {
return 0;
}
/**
* Ignore an user.
*
* @param gameClient The client to which this HabboStats instance belongs.
* @param userId The user to ignore.
* @return true if successfully ignored, false otherwise.
*/
public boolean ignoreUser(GameClient gameClient, int userId) {
final Habbo target = Emulator.getGameEnvironment().getHabboManager().getHabbo(userId);
@@ -805,6 +802,44 @@ public class HabboStats implements Runnable {
else return this.allowTrade;
}
public boolean mentionsEnabled() {
return this.mentionsEnabled;
}
public boolean massMentionsEnabled() {
return this.massMentionsEnabled;
}
public void setMentionsEnabled(boolean enabled) {
this.mentionsEnabled = enabled;
persistFlag("mentions_enabled", enabled);
}
public void setMassMentionsEnabled(boolean enabled) {
this.massMentionsEnabled = enabled;
persistFlag("mass_mentions_enabled", enabled);
}
private void persistFlag(String column, boolean enabled) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("UPDATE users_settings SET `" + column + "` = ? WHERE user_id = ? LIMIT 1")) {
statement.setString(1, enabled ? "1" : "0");
statement.setInt(2, this.habboInfo.getId());
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.error("Failed to persist users_settings.{} for user {}", column, this.habboInfo.getId(), e);
}
}
private static String safeColumnString(ResultSet set, String column, String defaultValue) {
try {
String value = set.getString(column);
return value == null ? defaultValue : value;
} catch (SQLException e) {
return defaultValue;
}
}
public void setAllowTrade(boolean allowTrade) {
this.allowTrade = allowTrade;
}
@@ -39,6 +39,7 @@ import com.eu.habbo.messages.incoming.hotelview.*;
import com.eu.habbo.messages.incoming.inventory.*;
import com.eu.habbo.messages.incoming.inventory.nickicons.*;
import com.eu.habbo.messages.incoming.inventory.prefixes.*;
import com.eu.habbo.messages.incoming.mentions.*;
import com.eu.habbo.messages.incoming.modtool.*;
import com.eu.habbo.messages.incoming.navigator.*;
import com.eu.habbo.messages.incoming.polls.AnswerPollEvent;
@@ -426,6 +427,9 @@ public class PacketManager {
}
void registerRooms() throws Exception {
this.registerHandler(Incoming.RequestMentionsEvent, RequestMentionsEvent.class);
this.registerHandler(Incoming.MarkMentionsReadEvent, MarkMentionsReadEvent.class);
this.registerHandler(Incoming.DeleteMentionEvent, DeleteMentionEvent.class);
this.registerHandler(Incoming.RequestRoomLoadEvent, RequestRoomLoadEvent.class);
this.registerHandler(Incoming.RequestHeightmapEvent, RequestRoomHeightmapEvent.class);
this.registerHandler(Incoming.RequestRoomHeightmapEvent, RequestRoomHeightmapEvent.class);
@@ -496,4 +496,7 @@ public class Incoming {
public static final int WheelAdminSavePrizesEvent = 9305;
public static final int SoundboardPlayEvent = 9306;
public static final int SoundboardSetEnabledEvent = 9307;
public static final int RequestMentionsEvent = 4803;
public static final int MarkMentionsReadEvent = 4804;
public static final int DeleteMentionEvent = 4805;
}
@@ -29,6 +29,11 @@ public class GuildDeclineMembershipEvent extends MessageHandler {
if (guild != null) {
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guild, this.client.getHabbo());
if (userId == this.client.getHabbo().getHabboInfo().getId() || guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || (member != null && (member.getRank().equals(GuildRank.ADMIN) || member.getRank().equals(GuildRank.OWNER))) || this.client.getHabbo().hasPermission(Permission.ACC_GUILD_ADMIN)) {
GuildMember target = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, userId);
if (target == null || target.getRank().type != GuildRank.REQUESTED.type) {
return;
}
guild.decreaseRequestCount();
Emulator.getGameEnvironment().getGuildManager().removeMember(guild, userId);
this.client.sendResponse(new GuildMembersComposer(guild, Emulator.getGameEnvironment().getGuildManager().getGuildMembers(guild, 0, 0, ""), this.client.getHabbo(), 0, 0, "", true, Emulator.getGameEnvironment().getGuildManager().getGuildMembersCount(guild, 0, 0, "")));
@@ -30,20 +30,69 @@ public class RequestGuildBuyEvent extends MessageHandler {
final String name = Emulator.getGameEnvironment().getWordFilter().filter(this.packet.readString(), this.client.getHabbo());
final String description = Emulator.getGameEnvironment().getWordFilter().filter(this.packet.readString(), this.client.getHabbo());
if(name.length() > 29){
if (name.length() == 0 || name.length() > 29) {
this.client.sendResponse(new GuildEditFailComposer(GuildEditFailComposer.INVALID_GUILD_NAME));
return;
}
if(description.length() > 254){
if (description.length() > 254) {
return;
}
if (Emulator.getConfig().getBoolean("catalog.guild.hc_required", true) && !this.client.getHabbo().getHabboStats().hasActiveClub()) {
this.client.sendResponse(new GuildEditFailComposer(GuildEditFailComposer.HC_REQUIRED));
return;
}
int roomId = this.packet.readInt();
Room r = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId);
if (r == null) {
this.client.sendResponse(new AlertPurchaseFailedComposer(AlertPurchaseFailedComposer.SERVER_ERROR));
return;
}
if (r.hasGuild() || r.getGuildId() != 0) {
this.client.sendResponse(new GuildEditFailComposer(GuildEditFailComposer.ROOM_ALREADY_IN_USE));
return;
}
if (r.getOwnerId() != this.client.getHabbo().getHabboInfo().getId()) {
String message = Emulator.getTexts().getValue("scripter.warning.guild.buy.owner").replace("%username%", this.client.getHabbo().getHabboInfo().getUsername()).replace("%roomname%", r.getName().replace("%owner%", r.getOwnerName()));
ScripterManager.scripterDetected(this.client, message);
LOGGER.info(message);
return;
}
int colorOne = this.packet.readInt();
int colorTwo = this.packet.readInt();
int count = this.packet.readInt();
StringBuilder badge = new StringBuilder();
byte base = 1;
while (base < count) {
int id = this.packet.readInt();
int color = this.packet.readInt();
int pos = this.packet.readInt();
if (base == 1) {
badge.append("b");
} else {
badge.append("s");
}
badge.append(id < 100 ? "0" : "").append(id < 10 ? "0" : "").append(id).append(color < 10 ? "0" : "").append(color).append(pos);
base += 3;
}
// Only charge the player once every step has been validated. Previously the
// credits were deducted before the room was checked, so a purchase that
// failed afterwards (missing room, room already used by a guild, not the
// owner) still took the credits without ever creating the group.
if (!this.client.getHabbo().hasPermission(Permission.ACC_INFINITE_CREDITS)) {
int guildPrice = Emulator.getConfig().getInt("catalog.guild.price");
if (this.client.getHabbo().getHabboInfo().getCredits() >= guildPrice) {
@@ -54,78 +103,34 @@ public class RequestGuildBuyEvent extends MessageHandler {
}
}
int roomId = this.packet.readInt();
Guild guild = Emulator.getGameEnvironment().getGuildManager().createGuild(this.client.getHabbo(), roomId, r.getName(), name, description, badge.toString(), colorOne, colorTwo);
Room r = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId);
r.setGuild(guild.getId());
r.removeAllRights();
r.setNeedsUpdate(true);
if (r != null) {
if (r.hasGuild()) {
this.client.sendResponse(new GuildEditFailComposer(GuildEditFailComposer.ROOM_ALREADY_IN_USE));
return;
Emulator.getGameEnvironment().getGuildManager().addGuild(guild);
if (Emulator.getConfig().getBoolean("imager.internal.enabled")) {
Emulator.getBadgeImager().generate(guild);
}
this.client.sendResponse(new PurchaseOKComposer());
this.client.sendResponse(new GuildBoughtComposer(guild));
r.refreshGuild(guild);
for (Habbo habbo : r.getHabbos()) {
if (habbo.getClient() == null) {
continue;
}
if (r.getOwnerId() == this.client.getHabbo().getHabboInfo().getId()) {
if (r.getGuildId() == 0) {
int colorOne = this.packet.readInt();
int colorTwo = this.packet.readInt();
habbo.getClient().sendResponse(new GuildInfoComposer(guild, habbo.getClient(), false, null));
int count = this.packet.readInt();
StringBuilder badge = new StringBuilder();
byte base = 1;
while (base < count) {
int id = this.packet.readInt();
int color = this.packet.readInt();
int pos = this.packet.readInt();
if (base == 1) {
badge.append("b");
} else {
badge.append("s");
}
badge.append(id < 100 ? "0" : "").append(id < 10 ? "0" : "").append(id).append(color < 10 ? "0" : "").append(color).append(pos);
base += 3;
}
Guild guild = Emulator.getGameEnvironment().getGuildManager().createGuild(this.client.getHabbo(), roomId, r.getName(), name, description, badge.toString(), colorOne, colorTwo);
r.setGuild(guild.getId());
r.removeAllRights();
r.setNeedsUpdate(true);
Emulator.getGameEnvironment().getGuildManager().addGuild(guild);
if (Emulator.getConfig().getBoolean("imager.internal.enabled")) {
Emulator.getBadgeImager().generate(guild);
}
this.client.sendResponse(new PurchaseOKComposer());
this.client.sendResponse(new GuildBoughtComposer(guild));
r.refreshGuild(guild);
for (Habbo habbo : r.getHabbos()) {
if (habbo.getClient() == null) {
continue;
}
habbo.getClient().sendResponse(new GuildInfoComposer(guild, habbo.getClient(), false, null));
if (habbo.getHabboInfo().getId() != this.client.getHabbo().getHabboInfo().getId()) {
habbo.getClient().sendResponse(new RoomDataComposer(r, habbo, true, false));
}
}
Emulator.getPluginManager().fireEvent(new GuildPurchasedEvent(guild, this.client.getHabbo()));
}
} else {
String message = Emulator.getTexts().getValue("scripter.warning.guild.buy.owner").replace("%username%", this.client.getHabbo().getHabboInfo().getUsername()).replace("%roomname%", r.getName().replace("%owner%", r.getOwnerName()));
ScripterManager.scripterDetected(this.client, message);
LOGGER.info(message);
if (habbo.getHabboInfo().getId() != this.client.getHabbo().getHabboInfo().getId()) {
habbo.getClient().sendResponse(new RoomDataComposer(r, habbo, true, false));
}
}
Emulator.getPluginManager().fireEvent(new GuildPurchasedEvent(guild, this.client.getHabbo()));
}
}
@@ -2,7 +2,11 @@ package com.eu.habbo.messages.incoming.guilds.forums;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.guilds.Guild;
import com.eu.habbo.habbohotel.guilds.GuildMember;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys;
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer;
public class GuildForumDataEvent extends MessageHandler {
@@ -20,10 +24,18 @@ public class GuildForumDataEvent extends MessageHandler {
if (guild == null) return;
if (!guild.hasForum()) return;
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId());
boolean staff = this.client.getHabbo().hasPermission(Permission.ACC_MODTOOL_TICKET_Q);
if (!guild.canHabboReadForum(this.client.getHabbo().getHabboInfo().getId(), member, staff)) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FORUMS_ACCESS_DENIED.key).compose());
return;
}
this.client.sendResponse(new GuildForumDataComposer(guild, this.client.getHabbo()));
if (!Emulator.getGameEnvironment().getGuildManager().hasViewedForum(this.client.getHabbo().getHabboInfo().getId(), guildId)) {
Emulator.getGameEnvironment().getGuildManager().addView(this.client.getHabbo().getHabboInfo().getId(), guildId);
}
}
}
}
@@ -2,7 +2,11 @@ package com.eu.habbo.messages.incoming.guilds.forums;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.guilds.Guild;
import com.eu.habbo.habbohotel.guilds.GuildMember;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys;
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumDataComposer;
import com.eu.habbo.messages.outgoing.guilds.forums.GuildForumThreadsComposer;
import com.eu.habbo.messages.outgoing.handshake.ConnectionErrorComposer;
@@ -24,8 +28,15 @@ public class GuildForumThreadsEvent extends MessageHandler {
this.client.sendResponse(new ConnectionErrorComposer(404));
return;
}
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId());
boolean staff = this.client.getHabbo().hasPermission(Permission.ACC_MODTOOL_TICKET_Q);
if (!guild.canHabboReadForum(this.client.getHabbo().getHabboInfo().getId(), member, staff)) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FORUMS_ACCESS_DENIED.key).compose());
return;
}
this.client.sendResponse(new GuildForumDataComposer(guild, this.client.getHabbo()));
this.client.sendResponse(new GuildForumThreadsComposer(guild, index));
}
}
}
@@ -38,7 +38,6 @@ public class GuildForumThreadsMessagesEvent extends MessageHandler {
return;
}
// Verify thread belongs to the requested guild
if (thread.getGuildId() != guildId) {
this.client.sendResponse(new ConnectionErrorComposer(403));
return;
@@ -47,6 +46,11 @@ public class GuildForumThreadsMessagesEvent extends MessageHandler {
GuildMember member = Emulator.getGameEnvironment().getGuildManager().getGuildMember(guildId, this.client.getHabbo().getHabboInfo().getId());
boolean isGuildAdministrator = (guild.getOwnerId() == this.client.getHabbo().getHabboInfo().getId() || (member != null && member.getRank().equals(GuildRank.ADMIN)));
if (!guild.canHabboReadForum(this.client.getHabbo().getHabboInfo().getId(), member, hasStaffPermissions)) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FORUMS_ACCESS_DENIED.key).compose());
return;
}
if (thread.getState() != ForumThreadState.HIDDEN_BY_GUILD_ADMIN || hasStaffPermissions || isGuildAdministrator) {
this.client.sendResponse(new GuildForumCommentsComposer(guildId, threadId, index, thread.getComments(limit, index)));
this.client.sendResponse(new GuildForumDataComposer(guild, this.client.getHabbo()));
@@ -6,6 +6,11 @@ import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.inventory.prefixes.UserPrefixesComposer;
public class DeletePrefixEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 500;
}
@Override
public void handle() throws Exception {
int prefixId = this.packet.readInt();
@@ -1,15 +1,20 @@
package com.eu.habbo.messages.incoming.inventory.prefixes;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.modtool.WordFilterWord;
import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys;
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertComposer;
import com.eu.habbo.messages.outgoing.generic.alerts.BubbleAlertKeys;
import com.eu.habbo.messages.outgoing.inventory.nickicons.UserNickIconsComposer;
import com.eu.habbo.messages.outgoing.inventory.prefixes.ActivePrefixUpdatedComposer;
import com.eu.habbo.messages.outgoing.inventory.prefixes.CustomPrefixPurchaseFailedComposer;
import com.eu.habbo.messages.outgoing.inventory.prefixes.PrefixReceivedComposer;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer;
import com.eu.habbo.messages.outgoing.users.UserCreditsComposer;
import com.eu.habbo.messages.outgoing.users.UserCurrencyComposer;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -17,10 +22,18 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
public class PurchasePrefixEvent extends MessageHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(PurchasePrefixEvent.class);
private static final String[] ALLOWED_FONTS = { "", "pixel", "cherry", "vampiro" };
private static final String[] ALLOWED_EFFECTS = {
"", "glow", "shadow", "italic", "outline", "underline", "pulse", "bounce", "wave", "shake",
"discord-neon", "cartoon", "toon", "pop", "bold-glow", "rainbow", "frost", "gold", "glitch",
"fire", "matrix", "sparkle"
};
private static final int MAX_ICON_LENGTH = 16;
@Override
public int getRatelimit() {
@@ -39,81 +52,101 @@ public class PurchasePrefixEvent extends MessageHandler {
if (habbo == null) return;
// Load settings
int maxLength = getSettingInt("max_length", 15);
int minRank = getSettingInt("min_rank_to_buy", 1);
int priceCredits = getSettingInt("price_credits", 5);
int pricePoints = getSettingInt("price_points", 0);
int pointsType = getSettingInt("points_type", 0);
int fontPriceCredits = getSettingInt("font_price_credits", 10);
int fontPricePoints = getSettingInt("font_price_points", 0);
int fontPointsType = getSettingInt("font_points_type", pointsType);
Map<String, Integer> settings = loadSettings();
int maxLength = setting(settings, "max_length", 15);
int minRank = setting(settings, "min_rank_to_buy", 1);
int priceCredits = setting(settings, "price_credits", 5);
int pricePoints = setting(settings, "price_points", 0);
int pointsType = setting(settings, "points_type", 0);
int fontPriceCredits = setting(settings, "font_price_credits", 10);
int fontPricePoints = setting(settings, "font_price_points", 0);
int fontPointsType = setting(settings, "font_points_type", pointsType);
int maxPrefixes = setting(settings, "max_prefixes", 60);
// Validate text
text = text.trim();
if (text.isEmpty() || text.length() > maxLength) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Prefix text is invalid or too long (max " + maxLength + " characters)."));
if (maxPrefixes > 0 && habbo.getInventory().getPrefixesComponent().getPrefixes().size() >= maxPrefixes) {
this.fail(habbo, "You already own the maximum number of prefixes (" + maxPrefixes + ").");
return;
}
text = text.trim();
if (text.isEmpty() || text.length() > maxLength) {
this.fail(habbo, "Prefix text is invalid or too long (max " + maxLength + " characters).");
return;
}
if (containsControlChars(text)) {
this.fail(habbo, "Prefix text contains invalid characters.");
return;
}
if (containsFilteredWord(text)) {
this.fail(habbo, "This prefix contains a blocked word.");
return;
}
// Validate color (single hex or comma-separated multi hex for per-letter colors)
String[] colorParts = color.split(",");
if (colorParts.length > text.length()) {
this.fail(habbo, "Invalid color format.");
return;
}
for (String part : colorParts) {
if (!part.matches("^#[0-9A-Fa-f]{6}$")) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Invalid color format."));
this.fail(habbo, "Invalid color format.");
return;
}
}
// Check rank
if (habbo.getHabboInfo().getRank().getId() < minRank) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Your rank is too low to purchase prefixes."));
return;
}
// Check blacklist
if (isBlacklisted(text)) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "This prefix contains a blocked word."));
this.fail(habbo, "Your rank is too low to purchase prefixes.");
return;
}
if (icon == null) icon = "";
icon = icon.trim();
if (!isValidIcon(icon)) {
this.fail(habbo, "Invalid prefix icon.");
return;
}
if (effect == null) effect = "";
effect = effect.trim();
effect = effect.trim().toLowerCase();
if (!isAllowedEffect(effect)) {
this.fail(habbo, "Invalid prefix effect.");
return;
}
if (font == null) font = "";
font = font.trim().toLowerCase();
if (!isAllowedFont(font)) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Invalid font format."));
this.fail(habbo, "Invalid font format.");
return;
}
int totalPriceCredits = priceCredits + (!font.isEmpty() ? fontPriceCredits : 0);
// Check credits
if (totalPriceCredits > 0 && habbo.getHabboInfo().getCredits() < totalPriceCredits) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Not enough credits."));
this.fail(habbo, "Not enough credits.");
return;
}
int totalPricePointsSameType = pricePoints + ((fontPricePoints > 0 && fontPointsType == pointsType && !font.isEmpty()) ? fontPricePoints : 0);
// Check points
if (totalPricePointsSameType > 0 && habbo.getHabboInfo().getCurrencyAmount(pointsType) < totalPricePointsSameType) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Not enough points."));
this.fail(habbo, "Not enough points.");
return;
}
if (!font.isEmpty() && fontPricePoints > 0 && fontPointsType != pointsType && habbo.getHabboInfo().getCurrencyAmount(fontPointsType) < fontPricePoints) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, "Not enough points."));
this.fail(habbo, "Not enough points.");
return;
}
// Deduct currency
if (totalPriceCredits > 0) {
habbo.getHabboInfo().addCredits(-totalPriceCredits);
this.client.sendResponse(new UserCreditsComposer(habbo));
@@ -129,47 +162,57 @@ public class PurchasePrefixEvent extends MessageHandler {
this.client.sendResponse(new UserCurrencyComposer(habbo));
}
// Create prefix
int storedPoints = totalPricePointsSameType;
int storedPointsType = (storedPoints > 0) ? pointsType : ((!font.isEmpty() && fontPricePoints > 0) ? fontPointsType : pointsType);
UserPrefix prefix = new UserPrefix(habbo.getHabboInfo().getId(), text, color, icon, effect, font, 0, text, storedPoints, storedPointsType, true);
prefix.run(); // Insert into DB synchronously to get the ID
habbo.getInventory().getPrefixesComponent().addPrefix(prefix);
habbo.getInventory().getPrefixesComponent().setActive(prefix.getId());
this.client.sendResponse(new PrefixReceivedComposer(prefix));
this.client.sendResponse(new ActivePrefixUpdatedComposer(prefix));
this.client.sendResponse(new UserNickIconsComposer(habbo));
}
private int getSettingInt(String key, int defaultValue) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT `value` FROM custom_prefix_settings WHERE key_name = ?")) {
statement.setString(1, key);
try (ResultSet set = statement.executeQuery()) {
if (set.next()) {
return Integer.parseInt(set.getString("value"));
}
}
} catch (SQLException | NumberFormatException e) {
LOGGER.error("Error reading prefix setting: " + key, e);
if (habbo.getHabboInfo().getCurrentRoom() != null) {
habbo.getHabboInfo().getCurrentRoom().sendComposer(new RoomUserDataComposer(habbo).compose());
}
return defaultValue;
}
private boolean isBlacklisted(String text) {
String lowerText = text.toLowerCase();
private void fail(Habbo habbo, String message) {
this.client.sendResponse(new BubbleAlertComposer(BubbleAlertKeys.FURNITURE_PLACEMENT_ERROR.key, message));
this.client.sendResponse(new CustomPrefixPurchaseFailedComposer(message));
}
private Map<String, Integer> loadSettings() {
Map<String, Integer> settings = new HashMap<>();
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement("SELECT word FROM custom_prefix_blacklist")) {
try (ResultSet set = statement.executeQuery()) {
while (set.next()) {
if (lowerText.contains(set.getString("word").toLowerCase())) {
return true;
}
}
PreparedStatement statement = connection.prepareStatement("SELECT key_name, `value` FROM custom_prefix_settings");
ResultSet set = statement.executeQuery()) {
while (set.next()) {
try {
settings.put(set.getString("key_name"), Integer.parseInt(set.getString("value")));
} catch (NumberFormatException ignored) {}
}
} catch (SQLException e) {
LOGGER.error("Error checking prefix blacklist", e);
LOGGER.error("Error reading prefix settings", e);
}
return settings;
}
private int setting(Map<String, Integer> settings, String key, int defaultValue) {
Integer value = settings.get(key);
return value != null ? value : defaultValue;
}
private boolean containsFilteredWord(String text) {
if (text == null || text.isEmpty()) return false;
for (WordFilterWord word : Emulator.getGameEnvironment().getWordFilter().getWords()) {
if (word.key != null && !word.key.isEmpty() && StringUtils.containsIgnoreCase(text, word.key)) {
return true;
}
}
return false;
}
@@ -182,4 +225,35 @@ public class PurchasePrefixEvent extends MessageHandler {
return false;
}
private boolean isAllowedEffect(String effect) {
for (String allowedEffect : ALLOWED_EFFECTS) {
if (allowedEffect.equals(effect)) {
return true;
}
}
return false;
}
private boolean isValidIcon(String icon) {
if (icon.isEmpty()) return true;
if (icon.length() > MAX_ICON_LENGTH) return false;
for (int i = 0; i < icon.length(); i++) {
char c = icon.charAt(i);
if (c < 0x20 || c == 0x7F || c == '<' || c == '>') return false;
}
return true;
}
private boolean containsControlChars(String text) {
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c < 0x20 || c == 0x7F) return true;
}
return false;
}
}
@@ -4,6 +4,11 @@ import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.inventory.prefixes.UserPrefixesComposer;
public class RequestUserPrefixesEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 500;
}
@Override
public void handle() throws Exception {
this.client.sendResponse(new UserPrefixesComposer(this.client.getHabbo()));
@@ -2,11 +2,16 @@ package com.eu.habbo.messages.incoming.inventory.prefixes;
import com.eu.habbo.habbohotel.users.UserPrefix;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.inventory.prefixes.ActivePrefixUpdatedComposer;
import com.eu.habbo.messages.outgoing.inventory.nickicons.UserNickIconsComposer;
import com.eu.habbo.messages.outgoing.inventory.prefixes.ActivePrefixUpdatedComposer;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer;
public class SetActivePrefixEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 1000;
}
@Override
public void handle() throws Exception {
int prefixId = this.packet.readInt();
@@ -7,6 +7,11 @@ import com.eu.habbo.messages.outgoing.inventory.nickicons.UserNickIconsComposer;
import com.eu.habbo.messages.outgoing.rooms.users.RoomUserDataComposer;
public class SetDisplayOrderEvent extends MessageHandler {
@Override
public int getRatelimit() {
return 1000;
}
@Override
public void handle() throws Exception {
Habbo habbo = this.client.getHabbo();
@@ -23,4 +28,4 @@ public class SetDisplayOrderEvent extends MessageHandler {
habbo.getHabboInfo().getCurrentRoom().sendComposer(new RoomUserDataComposer(habbo).compose());
}
}
}
}
@@ -0,0 +1,27 @@
package com.eu.habbo.messages.incoming.mentions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.mentions.MentionManager;
import com.eu.habbo.messages.incoming.MessageHandler;
public class DeleteMentionEvent extends MessageHandler {
@Override
public void handle() throws Exception {
if (this.client == null || this.client.getHabbo() == null) return;
int userId = this.client.getHabbo().getHabboInfo().getId();
int mentionId = this.packet.readInt();
if (mentionId <= 0) {
return;
}
MentionManager manager = Emulator.getGameEnvironment().getMentionManager();
if (!manager.tryAcquireDelete(userId)) {
return;
}
manager.delete(userId, mentionId);
}
}
@@ -0,0 +1,35 @@
package com.eu.habbo.messages.incoming.mentions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.mentions.MentionManager;
import com.eu.habbo.messages.incoming.MessageHandler;
public class MarkMentionsReadEvent extends MessageHandler {
@Override
public void handle() throws Exception {
if (this.client == null || this.client.getHabbo() == null) return;
int userId = this.client.getHabbo().getHabboInfo().getId();
int mode = this.packet.readInt();
int mentionId = this.packet.readInt();
// Only mode 0 (mark-all) and mode 1 (mark-single) are valid. Reject
// anything else so a crafted packet can't fall into the mark-all branch
// by accident.
if (mode != 0 && mode != 1) {
return;
}
if (mode == 1 && mentionId <= 0) {
return;
}
MentionManager manager = Emulator.getGameEnvironment().getMentionManager();
if (!manager.tryAcquireMarkRead(userId, mode)) {
return;
}
manager.markRead(userId, mode, mentionId);
}
}
@@ -0,0 +1,29 @@
package com.eu.habbo.messages.incoming.mentions;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.mentions.HabboMention;
import com.eu.habbo.habbohotel.mentions.MentionManager;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.mentions.MentionsListComposer;
import java.util.List;
public class RequestMentionsEvent extends MessageHandler {
@Override
public void handle() throws Exception {
if (this.client == null || this.client.getHabbo() == null) return;
int userId = this.client.getHabbo().getHabboInfo().getId();
MentionManager manager = Emulator.getGameEnvironment().getMentionManager();
if (!manager.tryAcquireRequestList(userId)) {
return;
}
int limit = Emulator.getConfig().getInt("mentions.store.limit", 50);
List<HabboMention> mentions = manager.getMentions(userId, limit);
this.client.sendResponse(new MentionsListComposer(mentions));
}
}
@@ -34,6 +34,9 @@ public class RoomUserShoutEvent extends MessageHandler {
if (RoomChatMessage.SAVE_ROOM_CHATS) {
Emulator.getThreading().run(message);
}
Emulator.getGameEnvironment().getMentionManager()
.process(this.client.getHabbo(), this.client.getHabbo().getHabboInfo().getCurrentRoom(), message.getMessage(), RoomChatType.SHOUT);
}
} else {
String reportMessage = Emulator.getTexts().getValue("scripter.warning.chat.length").replace("%username%", this.client.getHabbo().getHabboInfo().getUsername()).replace("%length%", message.getMessage().length() + "");
@@ -36,6 +36,9 @@ public class RoomUserTalkEvent extends MessageHandler {
if (RoomChatMessage.SAVE_ROOM_CHATS) {
Emulator.getThreading().run(message);
}
Emulator.getGameEnvironment().getMentionManager()
.process(this.client.getHabbo(), room, message.getMessage(), RoomChatType.TALK);
}
} else {
String reportMessage = Emulator.getTexts().getValue("scripter.warning.chat.length").replace("%username%", this.client.getHabbo().getHabboInfo().getUsername()).replace("%length%", message.getMessage().length() + "");
@@ -579,6 +579,7 @@ public class Outgoing {
public static final int PrefixReceivedComposer = 7002;
public static final int ActivePrefixUpdatedComposer = 7003;
public static final int UserNickIconsComposer = 7004;
public static final int CustomPrefixPurchaseFailedComposer = 7005;
public static final int AvailableCommandsComposer = 4050;
// YouTube Room Broadcast
@@ -602,5 +603,7 @@ public class Outgoing {
public static final int WheelAdminPrizesComposer = 9404;
public static final int SoundboardSettingsComposer = 9405;
public static final int SoundboardPlayComposer = 9406;
public static final int MentionReceivedComposer = 4801;
public static final int MentionsListComposer = 4802;
}
@@ -0,0 +1,20 @@
package com.eu.habbo.messages.outgoing.inventory.prefixes;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
public class CustomPrefixPurchaseFailedComposer extends MessageComposer {
private final String message;
public CustomPrefixPurchaseFailedComposer(String message) {
this.message = message != null ? message : "";
}
@Override
protected ServerMessage composeInternal() {
this.response.init(Outgoing.CustomPrefixPurchaseFailedComposer);
this.response.appendString(this.message);
return this.response;
}
}
@@ -0,0 +1,28 @@
package com.eu.habbo.messages.outgoing.mentions;
import com.eu.habbo.habbohotel.mentions.HabboMention;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
public class MentionReceivedComposer extends MessageComposer {
private final HabboMention mention;
public MentionReceivedComposer(HabboMention mention) {
this.mention = mention;
}
@Override
protected ServerMessage composeInternal() {
this.response.init(Outgoing.MentionReceivedComposer);
this.response.appendInt(this.mention.getId());
this.response.appendInt(this.mention.getSenderUserId());
this.response.appendString(this.mention.getSenderUsername());
this.response.appendInt(this.mention.getRoomId());
this.response.appendString(this.mention.getRoomName());
this.response.appendString(this.mention.getMessage());
this.response.appendInt(this.mention.getMentionType());
this.response.appendInt(this.mention.getTimestamp());
return this.response;
}
}
@@ -0,0 +1,36 @@
package com.eu.habbo.messages.outgoing.mentions;
import com.eu.habbo.habbohotel.mentions.HabboMention;
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 MentionsListComposer extends MessageComposer {
private final List<HabboMention> mentions;
public MentionsListComposer(List<HabboMention> mentions) {
this.mentions = mentions;
}
@Override
protected ServerMessage composeInternal() {
this.response.init(Outgoing.MentionsListComposer);
this.response.appendInt(this.mentions.size());
for (HabboMention mention : this.mentions) {
this.response.appendInt(mention.getId());
this.response.appendInt(mention.getSenderUserId());
this.response.appendString(mention.getSenderUsername());
this.response.appendInt(mention.getRoomId());
this.response.appendString(mention.getRoomName());
this.response.appendString(mention.getMessage());
this.response.appendInt(mention.getMentionType());
this.response.appendInt(mention.getTimestamp());
this.response.appendBoolean(mention.isRead());
}
return this.response;
}
}