🆙 Small Fixes mention

This commit is contained in:
duckietm
2026-06-03 14:17:25 +02:00
parent 7c32bbfd2d
commit 7d4ffec74e
4 changed files with 315 additions and 53 deletions
@@ -1,37 +1,60 @@
package com.eu.habbo.habbohotel.mentions; package com.eu.habbo.habbohotel.mentions;
import com.eu.habbo.Emulator; 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.Room;
import com.eu.habbo.habbohotel.rooms.RoomChatType; import com.eu.habbo.habbohotel.rooms.RoomChatType;
import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.Habbo;
import com.eu.habbo.habbohotel.users.HabboManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.sql.Connection; import java.sql.*;
import java.sql.PreparedStatement; import java.util.*;
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; import java.util.concurrent.ConcurrentHashMap;
public class MentionManager { public class MentionManager {
private static final Logger LOGGER = LoggerFactory.getLogger(MentionManager.class); 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> cooldowns = new ConcurrentHashMap<>();
private final ConcurrentHashMap<Integer, Long> roomBroadcastCooldowns = new ConcurrentHashMap<>();
// Per-user request rate limits for the incoming packets that hit the DB.
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() { public boolean isEnabled() {
return Emulator.getConfig().getInt("mentions.enabled", 1) == 1; return Emulator.getConfig().getInt("mentions.enabled", 1) == 1;
} }
private Set<String> roomAliases() { /** Broadcast category resolved from a mention alias. */
public enum BroadcastScope {
NONE,
// @room / @stanza - reaches the people currently in the room.
ROOM,
// @friends / @amici - reaches the sender's online friends, requires acc_mention_friends.
FRIENDS,
// @all / @everyone / @tutti - reaches every online user, requires acc_mention_everyone.
EVERYONE
}
/** Permission key (DB column) required to send an "everyone" broadcast. */
public static final String PERMISSION_EVERYONE = "acc_mention_everyone";
/** Permission key (DB column) required to send a "friends" broadcast. */
public static final String PERMISSION_FRIENDS = "acc_mention_friends";
private Set<String> parseAliases(String configKey, String defaultValue) {
Set<String> aliases = new HashSet<>(); Set<String> aliases = new HashSet<>();
String raw = Emulator.getConfig().getValue("mentions.room.aliases", "amici,friends,all,everyone,tutti,room,stanza"); String raw = Emulator.getConfig().getValue(configKey, defaultValue);
for (String alias : raw.split(",")) { for (String alias : raw.split(",")) {
String trimmed = alias.trim().toLowerCase(); String trimmed = alias.trim().toLowerCase();
if (!trimmed.isEmpty()) { if (!trimmed.isEmpty()) {
@@ -41,6 +64,37 @@ public class MentionManager {
return aliases; 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");
}
/**
* Classify an alias candidate (lowercased, punctuation-trimmed) into a
* broadcast scope. {@link BroadcastScope#EVERYONE} wins over
* {@link BroadcastScope#FRIENDS} which wins over {@link BroadcastScope#ROOM}
* so an admin who's also configured the same word into two lists gets the
* most permissive scope (which is also the one requiring the strongest
* permission, so it can't be misused).
*/
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) { public void process(Habbo sender, Room room, String message, RoomChatType type) {
try { try {
if (!this.isEnabled()) { if (!this.isEnabled()) {
@@ -63,8 +117,11 @@ public class MentionManager {
return; return;
} }
Set<String> aliases = this.roomAliases(); Set<String> roomAliases = this.roomAliases();
boolean roomBroadcast = false; Set<String> friendsAliases = this.friendsAliases();
Set<String> everyoneAliases = this.everyoneAliases();
BroadcastScope broadcastScope = BroadcastScope.NONE;
LinkedHashSet<String> directTokens = new LinkedHashSet<>(); LinkedHashSet<String> directTokens = new LinkedHashSet<>();
for (String token : message.split("\\s+")) { for (String token : message.split("\\s+")) {
@@ -75,47 +132,80 @@ public class MentionManager {
String raw = token.substring(1); String raw = token.substring(1);
String aliasCandidate = trimTrailingPunctuation(raw).toLowerCase(); String aliasCandidate = trimTrailingPunctuation(raw).toLowerCase();
if (!aliasCandidate.isEmpty() && aliases.contains(aliasCandidate)) { BroadcastScope scope = this.classifyAlias(aliasCandidate, everyoneAliases, friendsAliases, roomAliases);
roomBroadcast = true;
if (scope != BroadcastScope.NONE) {
// Promote to the strongest detected scope so a message with
// both @room and @all routes through the @all permission.
if (scope.ordinal() > broadcastScope.ordinal()) {
broadcastScope = scope;
}
} else if (!raw.isEmpty()) { } else if (!raw.isEmpty()) {
directTokens.add(raw); directTokens.add(raw);
} }
} }
if (!roomBroadcast && directTokens.isEmpty()) { // Gate the broadcast on the matching permission. If the sender does
// not have the right to use it, drop the broadcast entirely but
// keep processing any direct @nick tokens in the same message.
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; return;
} }
// Stricter cooldown for broadcasts: one @all/@friends/@room expands to
// up to mentions.max.targets DB writes and packet sends, so rate-limit it
// separately from direct mentions.
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); int maxTargets = Emulator.getConfig().getInt("mentions.max.targets", 50);
if (maxTargets <= 0) maxTargets = 1;
// Bound the number of direct tokens we even attempt to resolve so a
// crafted message can't push us through the room iteration N times.
int maxDirectTokens = Math.min(directTokens.size(), maxTargets);
List<Habbo> targets = new ArrayList<>(); List<Habbo> targets = new ArrayList<>();
Set<Integer> seen = new HashSet<>(); Set<Integer> seen = new HashSet<>();
if (roomBroadcast) { switch (broadcastScope) {
for (Habbo habbo : room.getHabbos()) { case EVERYONE:
if (habbo == null || habbo.getHabboInfo().getId() == senderId) { this.collectEveryoneTargets(senderId, targets, seen, maxTargets);
continue; break;
case FRIENDS:
this.collectFriendsTargets(sender, senderId, targets, seen, maxTargets);
break;
case ROOM:
this.collectRoomTargets(room, senderId, targets, seen, maxTargets);
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 (seen.add(habbo.getHabboInfo().getId())) {
targets.add(habbo);
}
if (targets.size() >= maxTargets) {
break;
}
} }
if (seen.add(habbo.getHabboInfo().getId())) { break;
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()) { if (targets.isEmpty()) {
@@ -123,15 +213,13 @@ public class MentionManager {
} }
this.cooldowns.put(senderId, now); this.cooldowns.put(senderId, now);
if (broadcastScope != BroadcastScope.NONE) this.roomBroadcastCooldowns.put(senderId, now);
this.pruneCooldownsIfDue(now);
int mentionType = roomBroadcast ? HabboMention.TYPE_ROOM : HabboMention.TYPE_DIRECT; int mentionType = (broadcastScope != BroadcastScope.NONE) ? HabboMention.TYPE_ROOM : HabboMention.TYPE_DIRECT;
int timestamp = Emulator.getIntUnixTimestamp(); int timestamp = Emulator.getIntUnixTimestamp();
String roomName = room.getName(); String roomName = truncate(room.getName(), ROOM_NAME_MAX_LENGTH);
String storedMessage = truncate(message, MESSAGE_MAX_LENGTH);
String storedMessage = message;
if (storedMessage.length() > 255) {
storedMessage = storedMessage.substring(0, 255);
}
for (Habbo target : targets) { for (Habbo target : targets) {
this.store(target, sender, room, storedMessage, mentionType, timestamp, roomName); this.store(target, sender, room, storedMessage, mentionType, timestamp, roomName);
@@ -141,6 +229,36 @@ public class MentionManager {
} }
} }
private void collectRoomTargets(Room room, int senderId, List<Habbo> targets, Set<Integer> seen, int maxTargets) {
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;
}
}
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 (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 (seen.add(habbo.getHabboInfo().getId())) targets.add(habbo);
if (targets.size() >= maxTargets) break;
}
}
private void store(Habbo target, Habbo sender, Room room, String message, int mentionType, int timestamp, String roomName) { private void store(Habbo target, Habbo sender, Room room, String message, int mentionType, int timestamp, String roomName) {
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement( PreparedStatement statement = connection.prepareStatement(
@@ -163,6 +281,13 @@ public class MentionManager {
} }
} }
// Don't push a notification to the client when the INSERT did not
// return an id - the client dedup keys on id and a 0 would skip
// dedup entirely, opening a flood path on the next packet.
if (generatedId <= 0) {
return;
}
HabboMention mention = new HabboMention(target.getHabboInfo().getId(), generatedId, sender, room, roomName, message, mentionType, timestamp); HabboMention mention = new HabboMention(target.getHabboInfo().getId(), generatedId, sender, room, roomName, message, mentionType, timestamp);
if (target.getClient() != null) { if (target.getClient() != null) {
@@ -175,6 +300,8 @@ public class MentionManager {
public List<HabboMention> getMentions(int userId, int limit) { public List<HabboMention> getMentions(int userId, int limit) {
List<HabboMention> mentions = new ArrayList<>(); List<HabboMention> mentions = new ArrayList<>();
if (limit <= 0) limit = 50;
if (limit > 200) limit = 200;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement( PreparedStatement statement = connection.prepareStatement(
"SELECT * FROM habbo_mentions WHERE target_user_id = ? ORDER BY id DESC LIMIT ?")) { "SELECT * FROM habbo_mentions WHERE target_user_id = ? ORDER BY id DESC LIMIT ?")) {
@@ -192,9 +319,13 @@ public class MentionManager {
} }
public void markRead(int userId, int mode, int mentionId) { public void markRead(int userId, int mode, int mentionId) {
// Caller has already validated mode and mentionId; this method is defensive only.
if (mode != 0 && mode != 1) return;
if (mode == 1 && mentionId <= 0) return;
String query = mode == 1 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 = ? AND id = ? AND `read` = 0"
: "UPDATE habbo_mentions SET `read` = 1 WHERE target_user_id = ?"; : "UPDATE habbo_mentions SET `read` = 1 WHERE target_user_id = ? AND `read` = 0";
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(query)) { PreparedStatement statement = connection.prepareStatement(query)) {
statement.setInt(1, userId); statement.setInt(1, userId);
@@ -208,6 +339,7 @@ public class MentionManager {
} }
public void delete(int userId, int mentionId) { public void delete(int userId, int mentionId) {
if (mentionId <= 0) return;
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection(); try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement( PreparedStatement statement = connection.prepareStatement(
"DELETE FROM habbo_mentions WHERE target_user_id = ? AND id = ?")) { "DELETE FROM habbo_mentions WHERE target_user_id = ? AND id = ?")) {
@@ -219,6 +351,87 @@ public class MentionManager {
} }
} }
/**
* Per-user rate limit for {@code RequestMentionsEvent}. Returns true when
* the caller should be served, false when it must be silently dropped.
*/
public boolean tryAcquireRequestList(int userId) {
long cooldownMs = Emulator.getConfig().getInt("mentions.request.cooldown.ms", 2000);
return tryAcquire(this.requestListCooldowns, userId, cooldownMs);
}
/**
* Per-user rate limit for {@code MarkMentionsReadEvent}. The mark-single
* path (mode == 1) is cheap and gets a short window; the mark-all path
* (mode != 1) is a bulk UPDATE and gets a longer one.
*/
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);
}
/**
* Per-user rate limit for {@code DeleteMentionEvent}.
*/
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;
}
/**
* Periodically drop cooldown entries older than the largest window so the
* maps don't accumulate one entry per user-that-ever-played for the entire
* server lifetime.
*/
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 final String TRAILING_PUNCTUATION = ".,!?;:)]}\"'";
private static String trimTrailingPunctuation(String value) { private static String trimTrailingPunctuation(String value) {
@@ -229,6 +442,12 @@ public class MentionManager {
return value.substring(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);
}
/** /**
* Resolve a present room occupant from a raw mention token. Tries the token * Resolve a present room occupant from a raw mention token. Tries the token
* verbatim first (so usernames containing allowed punctuation such as '-', * verbatim first (so usernames containing allowed punctuation such as '-',
@@ -1,14 +1,27 @@
package com.eu.habbo.messages.incoming.mentions; package com.eu.habbo.messages.incoming.mentions;
import com.eu.habbo.Emulator; import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.mentions.MentionManager;
import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.incoming.MessageHandler;
public class DeleteMentionEvent extends MessageHandler { public class DeleteMentionEvent extends MessageHandler {
@Override @Override
public void handle() throws Exception { public void handle() throws Exception {
if (this.client == null || this.client.getHabbo() == null) return;
int userId = this.client.getHabbo().getHabboInfo().getId(); int userId = this.client.getHabbo().getHabboInfo().getId();
int mentionId = this.packet.readInt(); int mentionId = this.packet.readInt();
Emulator.getGameEnvironment().getMentionManager().delete(userId, mentionId); if (mentionId <= 0) {
return;
}
MentionManager manager = Emulator.getGameEnvironment().getMentionManager();
if (!manager.tryAcquireDelete(userId)) {
return;
}
manager.delete(userId, mentionId);
} }
} }
@@ -1,15 +1,35 @@
package com.eu.habbo.messages.incoming.mentions; package com.eu.habbo.messages.incoming.mentions;
import com.eu.habbo.Emulator; import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.mentions.MentionManager;
import com.eu.habbo.messages.incoming.MessageHandler; import com.eu.habbo.messages.incoming.MessageHandler;
public class MarkMentionsReadEvent extends MessageHandler { public class MarkMentionsReadEvent extends MessageHandler {
@Override @Override
public void handle() throws Exception { public void handle() throws Exception {
if (this.client == null || this.client.getHabbo() == null) return;
int userId = this.client.getHabbo().getHabboInfo().getId(); int userId = this.client.getHabbo().getHabboInfo().getId();
int mode = this.packet.readInt(); int mode = this.packet.readInt();
int mentionId = this.packet.readInt(); int mentionId = this.packet.readInt();
Emulator.getGameEnvironment().getMentionManager().markRead(userId, mode, mentionId); // 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);
} }
} }
@@ -2,6 +2,7 @@ package com.eu.habbo.messages.incoming.mentions;
import com.eu.habbo.Emulator; import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.mentions.HabboMention; 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.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.mentions.MentionsListComposer; import com.eu.habbo.messages.outgoing.mentions.MentionsListComposer;
@@ -10,10 +11,19 @@ import java.util.List;
public class RequestMentionsEvent extends MessageHandler { public class RequestMentionsEvent extends MessageHandler {
@Override @Override
public void handle() throws Exception { public void handle() throws Exception {
if (this.client == null || this.client.getHabbo() == null) return;
int userId = this.client.getHabbo().getHabboInfo().getId(); 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); int limit = Emulator.getConfig().getInt("mentions.store.limit", 50);
List<HabboMention> mentions = Emulator.getGameEnvironment().getMentionManager().getMentions(userId, limit); List<HabboMention> mentions = manager.getMentions(userId, limit);
this.client.sendResponse(new MentionsListComposer(mentions)); this.client.sendResponse(new MentionsListComposer(mentions));
} }
} }