Compare commits

...

14 Commits

Author SHA1 Message Date
github-actions[bot] 0b142d184c 🆙 Bump version to 4.2.37 [skip ci] 2026-06-05 19:21:31 +00:00
DuckieTM 867c8ff857 Merge pull request #155 from duckietm/dev
🆙 Fix the Admin Catalogue stuff
2026-06-05 21:20:31 +02:00
duckietm 5094d6ce4f 🆙 Fix the Admin Catalogue stuff 2026-06-05 14:23:05 +02:00
github-actions[bot] 2c0ef9873c 🆙 Bump version to 4.2.36 [skip ci] 2026-06-04 08:44:19 +00:00
DuckieTM dadc1b8aaf Merge pull request #153 from duckietm/dev
Dev
2026-06-04 10:43:21 +02:00
duckietm 85758b53fa 🆙 Updates Mention 2026-06-04 10:43:05 +02:00
DuckieTM 2171b5f2ec Merge pull request #152 from medievalshell/feat/mentions-hotelwide-figure
feat(mentions): hotel-wide @nick delivery + sender figure + disable-m…
2026-06-04 08:50:49 +02:00
medievalshell 46306c8205 feat(mentions): hotel-wide @nick delivery + sender figure + disable-mention persistence
- resolveHabbo() falls back to a hotel-wide online lookup so a direct @nick
  mention reaches the target even when they are in a different room (was
  resolved only within the sender's room).
- HabboMention now carries the sender figure (live from the sender Habbo,
  history from a users.look JOIN); MentionReceived/MentionsList composers
  append it so the client can render the sender avatar in the notification.
- 009: add users_settings.mentions_enabled / mass_mentions_enabled columns
  so :disablementions / :disablemassmentions actually persist.
2026-06-04 01:27:45 +02:00
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
simoleo89 9f36d95dbc fix(commands): structure commands alert output 2026-06-02 18:34:50 +02:00
15 changed files with 237 additions and 20 deletions
+14 -1
View File
@@ -73,4 +73,17 @@ INSERT IGNORE INTO `emulator_settings` (`key`, `value`, `comment`) VALUES
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`;
COMMENT 'When 1, this word only applies to custom prefixes, not to chat/motto/guild.' AFTER `mute`;
-- ----------------------------------------------------------------------------
-- 5. Per-user mention preferences (:disablementions / :disablemassmentions)
--
-- Read by HabboStats (default '1' = enabled), toggled by the commands.
-- Without these columns the toggle commands cannot persist.
-- ----------------------------------------------------------------------------
ALTER TABLE `users_settings`
ADD COLUMN IF NOT EXISTS `mentions_enabled` ENUM('0','1') NOT NULL DEFAULT '1'
COMMENT 'Receive @nick mention notifications.',
ADD COLUMN IF NOT EXISTS `mass_mentions_enabled` ENUM('0','1') NOT NULL DEFAULT '1'
COMMENT 'Receive broadcast (@all / @friends / @room) mentions.';
+1 -1
View File
@@ -6,7 +6,7 @@
<groupId>com.eu.habbo</groupId>
<artifactId>Habbo</artifactId>
<version>4.2.33</version>
<version>4.2.37</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -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()});
@@ -21,6 +21,7 @@ public class HabboMention {
private final int mentionType;
private final int timestamp;
private final boolean read;
private final String senderFigure;
public HabboMention(ResultSet set) throws SQLException {
this.id = set.getInt("id");
@@ -33,6 +34,16 @@ public class HabboMention {
this.mentionType = set.getInt("mention_type");
this.timestamp = set.getInt("timestamp");
this.read = set.getInt("read") == 1;
this.senderFigure = hasSenderFigure(set) ? set.getString("sender_figure") : "";
}
private static boolean hasSenderFigure(ResultSet set) {
try {
set.findColumn("sender_figure");
return true;
} catch (SQLException e) {
return false;
}
}
public HabboMention(int targetUserId, int id, Habbo sender, Room room, String roomName, String message, int mentionType, int timestamp) {
@@ -46,6 +57,7 @@ public class HabboMention {
this.mentionType = mentionType;
this.timestamp = timestamp;
this.read = false;
this.senderFigure = sender.getHabboInfo().getLook();
}
public int getId() {
@@ -87,4 +99,8 @@ public class HabboMention {
public boolean isRead() {
return this.read;
}
public String getSenderFigure() {
return this.senderFigure == null ? "" : this.senderFigure;
}
}
@@ -9,18 +9,8 @@ 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.sql.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class MentionManager {
@@ -43,7 +33,6 @@ public class MentionManager {
return Emulator.getConfig().getInt("mentions.enabled", 1) == 1;
}
/** Broadcast category resolved from a mention alias. */
public enum BroadcastScope {
NONE,
ROOM,
@@ -249,7 +238,9 @@ public class MentionManager {
}
private boolean acceptsMention(Habbo recipient, boolean isBroadcast) {
if (recipient == null || recipient.getHabboStats() == null) return true;
if (recipient == null) return false;
if (recipient.getClient() == null) return false;
if (recipient.getHabboStats() == null) return false;
if (!recipient.getHabboStats().mentionsEnabled()) return false;
if (isBroadcast && !recipient.getHabboStats().massMentionsEnabled()) return false;
return true;
@@ -297,7 +288,7 @@ public class MentionManager {
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 ?")) {
"SELECT habbo_mentions.*, users.look AS sender_figure FROM habbo_mentions LEFT JOIN users ON users.id = habbo_mentions.sender_user_id WHERE target_user_id = ? ORDER BY id DESC LIMIT ?")) {
statement.setInt(1, userId);
statement.setInt(2, limit);
try (ResultSet set = statement.executeQuery()) {
@@ -423,14 +414,49 @@ public class MentionManager {
return value.substring(0, max);
}
private boolean isBotOrPetName(Room room, String token) {
if (room == null || token == null || token.isEmpty()) return false;
List<com.eu.habbo.habbohotel.bots.Bot> bots = room.getBots(token);
if (bots != null && !bots.isEmpty()) return true;
if (room.getUnitManager() != null && room.getUnitManager().getPets() != null) {
for (com.eu.habbo.habbohotel.pets.Pet pet : room.getUnitManager().getPets()) {
if (pet != null && pet.getName() != null && pet.getName().equalsIgnoreCase(token)) {
return true;
}
}
}
return false;
}
private Habbo resolveHabbo(Room room, String rawToken) {
if (isBotOrPetName(room, rawToken)) {
return null;
}
String trimmedForBotCheck = trimTrailingPunctuation(rawToken);
if (!trimmedForBotCheck.equals(rawToken) && isBotOrPetName(room, trimmedForBotCheck)) {
return null;
}
Habbo habbo = room.getHabbo(rawToken);
if (habbo != null) {
return habbo;
}
HabboManager habboManager = Emulator.getGameEnvironment().getHabboManager();
habbo = habboManager.getHabbo(rawToken);
if (habbo != null) {
return habbo;
}
String trimmed = trimTrailingPunctuation(rawToken);
if (!trimmed.isEmpty() && !trimmed.equals(rawToken)) {
return room.getHabbo(trimmed);
habbo = room.getHabbo(trimmed);
if (habbo != null) {
return habbo;
}
return habboManager.getHabbo(trimmed);
}
return null;
}
@@ -298,6 +298,8 @@ public class PacketManager {
this.registerHandler(Incoming.CatalogAdminPublishEvent, CatalogAdminPublishEvent.class);
this.registerHandler(Incoming.CatalogAdminSavePageImagesEvent, CatalogAdminSavePageImagesEvent.class);
this.registerHandler(Incoming.CatalogAdminSavePageIconEvent, CatalogAdminSavePageIconEvent.class);
this.registerHandler(Incoming.CatalogAdminLoadOfferEvent, CatalogAdminLoadOfferEvent.class);
this.registerHandler(Incoming.CatalogAdminLoadPageEvent, CatalogAdminLoadPageEvent.class);
}
private void registerEvent() throws Exception {
@@ -444,6 +444,8 @@ public class Incoming {
public static final int CatalogAdminPublishEvent = 10058;
public static final int CatalogAdminSavePageImagesEvent = 10060;
public static final int CatalogAdminSavePageIconEvent = 10061;
public static final int CatalogAdminLoadOfferEvent = 10062;
public static final int CatalogAdminLoadPageEvent = 10063;
// Custom Prefixes
public static final int RequestUserPrefixesEvent = 7011;
@@ -0,0 +1,55 @@
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.catalog.CatalogPageType;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminOfferDetailsComposer;
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminResultComposer;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
public class CatalogAdminLoadOfferEvent extends MessageHandler {
@Override
public void handle() throws Exception {
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
this.client.sendResponse(new CatalogAdminResultComposer(false, "No permission"));
return;
}
int offerId = this.packet.readInt();
CatalogPageType pageType = CatalogPageType.fromString(this.packet.readString());
String sql = (pageType == CatalogPageType.BUILDER)
? "SELECT id, order_number FROM catalog_items_bc WHERE id = ? LIMIT 1"
: "SELECT id, offer_id, limited_stack, order_number FROM catalog_items WHERE id = ? LIMIT 1";
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
PreparedStatement statement = connection.prepareStatement(sql)) {
statement.setInt(1, offerId);
try (ResultSet set = statement.executeQuery()) {
if (!set.next()) return;
if (pageType == CatalogPageType.BUILDER) {
this.client.sendResponse(new CatalogAdminOfferDetailsComposer(
set.getInt("id"),
0,
0,
set.getInt("order_number")
));
} else {
this.client.sendResponse(new CatalogAdminOfferDetailsComposer(
set.getInt("id"),
set.getInt("offer_id"),
set.getInt("limited_stack"),
set.getInt("order_number")
));
}
}
}
}
}
@@ -0,0 +1,28 @@
package com.eu.habbo.messages.incoming.catalog.catalogadmin;
import com.eu.habbo.Emulator;
import com.eu.habbo.habbohotel.catalog.CatalogPage;
import com.eu.habbo.habbohotel.catalog.CatalogPageType;
import com.eu.habbo.habbohotel.permissions.Permission;
import com.eu.habbo.messages.incoming.MessageHandler;
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminPageDetailsComposer;
import com.eu.habbo.messages.outgoing.catalog.catalogadmin.CatalogAdminResultComposer;
public class CatalogAdminLoadPageEvent extends MessageHandler {
@Override
public void handle() throws Exception {
if (!this.client.getHabbo().hasPermission(Permission.ACC_CATALOGFURNI)) {
this.client.sendResponse(new CatalogAdminResultComposer(false, "No permission"));
return;
}
int pageId = this.packet.readInt();
CatalogPageType pageType = CatalogPageType.fromString(this.packet.readString());
CatalogPage page = Emulator.getGameEnvironment().getCatalogManager().getCatalogPage(pageId, pageType);
if (page == null) return;
this.client.sendResponse(new CatalogAdminPageDetailsComposer(page));
}
}
@@ -573,6 +573,8 @@ public class Outgoing {
// Catalog Admin
public static final int CatalogAdminResultComposer = 10059;
public static final int CatalogAdminOfferDetailsComposer = 10062;
public static final int CatalogAdminPageDetailsComposer = 10063;
// Custom Prefixes
public static final int UserPrefixesComposer = 7001;
@@ -0,0 +1,29 @@
package com.eu.habbo.messages.outgoing.catalog.catalogadmin;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
public class CatalogAdminOfferDetailsComposer extends MessageComposer {
private final int offerId;
private final int offerIdGroup;
private final int limitedStack;
private final int orderNumber;
public CatalogAdminOfferDetailsComposer(int offerId, int offerIdGroup, int limitedStack, int orderNumber) {
this.offerId = offerId;
this.offerIdGroup = offerIdGroup;
this.limitedStack = limitedStack;
this.orderNumber = orderNumber;
}
@Override
protected ServerMessage composeInternal() {
this.response.init(Outgoing.CatalogAdminOfferDetailsComposer);
this.response.appendInt(this.offerId);
this.response.appendInt(this.offerIdGroup);
this.response.appendInt(this.limitedStack);
this.response.appendInt(this.orderNumber);
return this.response;
}
}
@@ -0,0 +1,27 @@
package com.eu.habbo.messages.outgoing.catalog.catalogadmin;
import com.eu.habbo.habbohotel.catalog.CatalogPage;
import com.eu.habbo.messages.ServerMessage;
import com.eu.habbo.messages.outgoing.MessageComposer;
import com.eu.habbo.messages.outgoing.Outgoing;
public class CatalogAdminPageDetailsComposer extends MessageComposer {
private final CatalogPage page;
public CatalogAdminPageDetailsComposer(CatalogPage page) {
this.page = page;
}
@Override
protected ServerMessage composeInternal() {
this.response.init(Outgoing.CatalogAdminPageDetailsComposer);
this.response.appendInt(this.page.getId());
this.response.appendString(this.page.getCaption());
this.response.appendString(this.page.getPageName());
this.response.appendInt(this.page.getRank());
this.response.appendInt(this.page.getOrderNum());
this.response.appendBoolean(this.page.isVisible());
this.response.appendBoolean(this.page.isEnabled());
return this.response;
}
}
@@ -18,6 +18,7 @@ public class MentionReceivedComposer extends MessageComposer {
this.response.appendInt(this.mention.getId());
this.response.appendInt(this.mention.getSenderUserId());
this.response.appendString(this.mention.getSenderUsername());
this.response.appendString(this.mention.getSenderFigure());
this.response.appendInt(this.mention.getRoomId());
this.response.appendString(this.mention.getRoomName());
this.response.appendString(this.mention.getMessage());
@@ -23,6 +23,7 @@ public class MentionsListComposer extends MessageComposer {
this.response.appendInt(mention.getId());
this.response.appendInt(mention.getSenderUserId());
this.response.appendString(mention.getSenderUsername());
this.response.appendString(mention.getSenderFigure());
this.response.appendInt(mention.getRoomId());
this.response.appendString(mention.getRoomName());
this.response.appendString(mention.getMessage());
+1 -1
View File
@@ -10,7 +10,7 @@ and is developed for free by talented developers and is compatible with the foll
[Latest compiled version](https://github.com/duckietm/Arcturus-Morningstar-Extended/tree/main/Latest_Compiled_Version)
## Connection ##
Use the Websocket plugin!
Use the BUILD-IN Websocket so do NOT load any websocket plugin!
### How do I connect to my emulator using Secure Websockets (wss)?
You have several options to add WSS support to your websocket server.