diff --git a/Database Updates/009_mentions.sql b/Database Updates/009_mentions.sql
new file mode 100644
index 00000000..d9b72c56
--- /dev/null
+++ b/Database Updates/009_mentions.sql
@@ -0,0 +1,21 @@
+CREATE TABLE IF NOT EXISTS `habbo_mentions` (
+ `id` INT NOT NULL AUTO_INCREMENT,
+ `target_user_id` INT NOT NULL,
+ `sender_user_id` INT NOT NULL,
+ `sender_username` VARCHAR(64) NOT NULL DEFAULT '',
+ `room_id` INT NOT NULL,
+ `room_name` VARCHAR(255) NOT NULL DEFAULT '',
+ `message` VARCHAR(255) NOT NULL DEFAULT '',
+ `mention_type` TINYINT NOT NULL DEFAULT 0,
+ `timestamp` INT NOT NULL DEFAULT 0,
+ `read` TINYINT NOT NULL DEFAULT 0,
+ PRIMARY KEY (`id`),
+ INDEX `idx_target_read` (`target_user_id`, `read`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+INSERT IGNORE INTO `emulator_settings` (`key`, `value`) VALUES
+ ('mentions.enabled', '1'),
+ ('mentions.room.aliases', 'amici,friends,all,everyone,tutti,room,stanza'),
+ ('mentions.max.targets', '50'),
+ ('mentions.cooldown.ms', '3000'),
+ ('mentions.store.limit', '50');
diff --git a/Emulator/pom.xml b/Emulator/pom.xml
index 17628061..07ebff7c 100644
--- a/Emulator/pom.xml
+++ b/Emulator/pom.xml
@@ -6,7 +6,7 @@
com.eu.habbo
Habbo
- 4.2.26
+ 4.2.28
UTF-8
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/GameEnvironment.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/GameEnvironment.java
index 8fdb7842..8d45809c 100644
--- a/Emulator/src/main/java/com/eu/habbo/habbohotel/GameEnvironment.java
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/GameEnvironment.java
@@ -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;
}
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/mentions/HabboMention.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/mentions/HabboMention.java
new file mode 100644
index 00000000..96ff7a4d
--- /dev/null
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/mentions/HabboMention.java
@@ -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;
+ }
+}
diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/mentions/MentionManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/mentions/MentionManager.java
new file mode 100644
index 00000000..b95c7be4
--- /dev/null
+++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/mentions/MentionManager.java
@@ -0,0 +1,249 @@
+package com.eu.habbo.habbohotel.mentions;
+
+import com.eu.habbo.Emulator;
+import com.eu.habbo.habbohotel.rooms.Room;
+import com.eu.habbo.habbohotel.rooms.RoomChatType;
+import com.eu.habbo.habbohotel.users.Habbo;
+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.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class MentionManager {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(MentionManager.class);
+
+ private final ConcurrentHashMap cooldowns = new ConcurrentHashMap<>();
+
+ public boolean isEnabled() {
+ return Emulator.getConfig().getInt("mentions.enabled", 1) == 1;
+ }
+
+ private Set roomAliases() {
+ Set aliases = new HashSet<>();
+ String raw = Emulator.getConfig().getValue("mentions.room.aliases", "amici,friends,all,everyone,tutti,room,stanza");
+ for (String alias : raw.split(",")) {
+ String trimmed = alias.trim().toLowerCase();
+ if (!trimmed.isEmpty()) {
+ aliases.add(trimmed);
+ }
+ }
+ return aliases;
+ }
+
+ 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 aliases = this.roomAliases();
+ boolean roomBroadcast = false;
+ LinkedHashSet 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();
+
+ if (!aliasCandidate.isEmpty() && aliases.contains(aliasCandidate)) {
+ roomBroadcast = true;
+ } else if (!raw.isEmpty()) {
+ directTokens.add(raw);
+ }
+ }
+
+ if (!roomBroadcast && directTokens.isEmpty()) {
+ return;
+ }
+
+ int maxTargets = Emulator.getConfig().getInt("mentions.max.targets", 50);
+
+ List targets = new ArrayList<>();
+ Set seen = new HashSet<>();
+
+ if (roomBroadcast) {
+ for (Habbo habbo : room.getHabbos()) {
+ if (habbo == null || habbo.getHabboInfo().getId() == senderId) {
+ continue;
+ }
+ if (seen.add(habbo.getHabboInfo().getId())) {
+ targets.add(habbo);
+ }
+ if (targets.size() >= maxTargets) {
+ break;
+ }
+ }
+ } else {
+ for (String token : directTokens) {
+ Habbo habbo = this.resolveHabbo(room, token);
+ if (habbo == null || habbo.getHabboInfo().getId() == senderId) {
+ continue;
+ }
+ if (seen.add(habbo.getHabboInfo().getId())) {
+ targets.add(habbo);
+ }
+ if (targets.size() >= maxTargets) {
+ break;
+ }
+ }
+ }
+
+ if (targets.isEmpty()) {
+ return;
+ }
+
+ this.cooldowns.put(senderId, now);
+
+ int mentionType = roomBroadcast ? HabboMention.TYPE_ROOM : HabboMention.TYPE_DIRECT;
+ int timestamp = Emulator.getIntUnixTimestamp();
+ String roomName = room.getName();
+
+ String storedMessage = message;
+ if (storedMessage.length() > 255) {
+ storedMessage = storedMessage.substring(0, 255);
+ }
+
+ 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 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);
+ }
+ }
+
+ 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 getMentions(int userId, int limit) {
+ List mentions = new ArrayList<>();
+ 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) {
+ String query = mode == 1
+ ? "UPDATE habbo_mentions SET `read` = 1 WHERE target_user_id = ? AND id = ?"
+ : "UPDATE habbo_mentions SET `read` = 1 WHERE target_user_id = ?";
+ 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) {
+ 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);
+ }
+ }
+
+ 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);
+ }
+
+ /**
+ * Resolve a present room occupant from a raw mention token. Tries the token
+ * verbatim first (so usernames containing allowed punctuation such as '-',
+ * '.', '!' still match), then falls back to a trailing-punctuation-trimmed
+ * form so a mention written as "@Bob!" still resolves the user "Bob".
+ */
+ 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;
+ }
+}
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 0b54d15e..1a09d3a5 100644
--- a/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java
+++ b/Emulator/src/main/java/com/eu/habbo/messages/PacketManager.java
@@ -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);
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 9d372215..77f72fe3 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
@@ -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;
}
diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/mentions/DeleteMentionEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/mentions/DeleteMentionEvent.java
new file mode 100644
index 00000000..6be3622b
--- /dev/null
+++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/mentions/DeleteMentionEvent.java
@@ -0,0 +1,14 @@
+package com.eu.habbo.messages.incoming.mentions;
+
+import com.eu.habbo.Emulator;
+import com.eu.habbo.messages.incoming.MessageHandler;
+
+public class DeleteMentionEvent extends MessageHandler {
+ @Override
+ public void handle() throws Exception {
+ int userId = this.client.getHabbo().getHabboInfo().getId();
+ int mentionId = this.packet.readInt();
+
+ Emulator.getGameEnvironment().getMentionManager().delete(userId, mentionId);
+ }
+}
diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/mentions/MarkMentionsReadEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/mentions/MarkMentionsReadEvent.java
new file mode 100644
index 00000000..5abfddda
--- /dev/null
+++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/mentions/MarkMentionsReadEvent.java
@@ -0,0 +1,15 @@
+package com.eu.habbo.messages.incoming.mentions;
+
+import com.eu.habbo.Emulator;
+import com.eu.habbo.messages.incoming.MessageHandler;
+
+public class MarkMentionsReadEvent extends MessageHandler {
+ @Override
+ public void handle() throws Exception {
+ int userId = this.client.getHabbo().getHabboInfo().getId();
+ int mode = this.packet.readInt();
+ int mentionId = this.packet.readInt();
+
+ Emulator.getGameEnvironment().getMentionManager().markRead(userId, mode, mentionId);
+ }
+}
diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/mentions/RequestMentionsEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/mentions/RequestMentionsEvent.java
new file mode 100644
index 00000000..9878da92
--- /dev/null
+++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/mentions/RequestMentionsEvent.java
@@ -0,0 +1,19 @@
+package com.eu.habbo.messages.incoming.mentions;
+
+import com.eu.habbo.Emulator;
+import com.eu.habbo.habbohotel.mentions.HabboMention;
+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 {
+ int userId = this.client.getHabbo().getHabboInfo().getId();
+ int limit = Emulator.getConfig().getInt("mentions.store.limit", 50);
+
+ List mentions = Emulator.getGameEnvironment().getMentionManager().getMentions(userId, limit);
+ this.client.sendResponse(new MentionsListComposer(mentions));
+ }
+}
diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserShoutEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserShoutEvent.java
index 1c0e4fba..57f8c60f 100644
--- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserShoutEvent.java
+++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserShoutEvent.java
@@ -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() + "");
diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserTalkEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserTalkEvent.java
index 2201fb4c..8659f971 100644
--- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserTalkEvent.java
+++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/rooms/users/RoomUserTalkEvent.java
@@ -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() + "");
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 6c0a331f..e04d51b1 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
@@ -603,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;
}
diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/mentions/MentionReceivedComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/mentions/MentionReceivedComposer.java
new file mode 100644
index 00000000..b7a446c7
--- /dev/null
+++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/mentions/MentionReceivedComposer.java
@@ -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;
+ }
+}
diff --git a/Emulator/src/main/java/com/eu/habbo/messages/outgoing/mentions/MentionsListComposer.java b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/mentions/MentionsListComposer.java
new file mode 100644
index 00000000..e261fe3c
--- /dev/null
+++ b/Emulator/src/main/java/com/eu/habbo/messages/outgoing/mentions/MentionsListComposer.java
@@ -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 mentions;
+
+ public MentionsListComposer(List 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;
+ }
+}