You've already forked Arcturus-Morningstar-Extended
mirror of
https://github.com/duckietm/Arcturus-Morningstar-Extended.git
synced 2026-06-19 15:06:19 +00:00
feat: soundboard (room-scoped custom audio pads)
Server side of the soundboard feature: - rooms.soundboard_enabled flag + soundboard_sounds table (self-bootstraps at boot via SoundboardManager; migration 021 seeds up-front) - SoundboardManager loads enabled sounds and persists the per-room flag - SoundboardPlayEvent broadcasts the pressed pad to everyone in the room - SoundboardSetEnabledEvent (owner/staff) toggles the room flag and pushes refreshed settings - settings (flag + sound list) sent on room enter, alongside YouTube
This commit is contained in:
@@ -7,6 +7,7 @@ import com.eu.habbo.habbohotel.bots.BotManager;
|
||||
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.commands.CommandHandler;
|
||||
import com.eu.habbo.habbohotel.crafting.CraftingManager;
|
||||
import com.eu.habbo.habbohotel.guides.GuideManager;
|
||||
@@ -66,6 +67,7 @@ public class GameEnvironment {
|
||||
private CustomBadgeManager customBadgeManager;
|
||||
private InfostandBackgroundManager infostandBackgroundManager;
|
||||
private WheelManager wheelManager;
|
||||
private SoundboardManager soundboardManager;
|
||||
|
||||
public void load() throws Exception {
|
||||
LOGGER.info("GameEnvironment -> Loading...");
|
||||
@@ -96,6 +98,7 @@ public class GameEnvironment {
|
||||
this.customBadgeManager = new CustomBadgeManager();
|
||||
this.infostandBackgroundManager = new InfostandBackgroundManager();
|
||||
this.wheelManager = new WheelManager();
|
||||
this.soundboardManager = new SoundboardManager();
|
||||
|
||||
this.roomManager.loadPublicRooms();
|
||||
this.navigatorManager.loadNavigator();
|
||||
@@ -163,6 +166,10 @@ public class GameEnvironment {
|
||||
return this.wheelManager;
|
||||
}
|
||||
|
||||
public SoundboardManager getSoundboardManager() {
|
||||
return this.soundboardManager;
|
||||
}
|
||||
|
||||
public HotelViewManager getHotelViewManager() {
|
||||
return this.hotelViewManager;
|
||||
}
|
||||
|
||||
@@ -197,6 +197,7 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
private int wiredInspectMask = WIRED_ACCESS_DEFAULT_INSPECT_MASK;
|
||||
private int wiredModifyMask = WIRED_ACCESS_DEFAULT_MODIFY_MASK;
|
||||
private boolean youtubeEnabled = false;
|
||||
private boolean soundboardEnabled = false;
|
||||
private String youtubeCurrentVideo = "";
|
||||
private String youtubeSenderName = "";
|
||||
private final java.util.List<String> youtubePlaylist = new java.util.concurrent.CopyOnWriteArrayList<>();
|
||||
@@ -204,6 +205,8 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
|
||||
public boolean isYoutubeEnabled() { return this.youtubeEnabled; }
|
||||
public void setYoutubeEnabled(boolean enabled) { this.youtubeEnabled = enabled; }
|
||||
public boolean isSoundboardEnabled() { return this.soundboardEnabled; }
|
||||
public void setSoundboardEnabled(boolean enabled) { this.soundboardEnabled = enabled; }
|
||||
public String getYoutubeCurrentVideo() { return this.youtubeCurrentVideo; }
|
||||
public String getYoutubeSenderName() { return this.youtubeSenderName; }
|
||||
public java.util.List<String> getYoutubePlaylist() { return this.youtubePlaylist; }
|
||||
@@ -250,6 +253,7 @@ public class Room implements Comparable<Room>, ISerialize, Runnable {
|
||||
this.allowWalkthrough = set.getBoolean("allow_walkthrough");
|
||||
this.hideWall = set.getBoolean("allow_hidewall");
|
||||
try { this.youtubeEnabled = set.getBoolean("youtube_enabled"); } catch (Exception e) { this.youtubeEnabled = false; }
|
||||
try { this.soundboardEnabled = set.getBoolean("soundboard_enabled"); } catch (Exception e) { this.soundboardEnabled = false; }
|
||||
this.chatMode = set.getInt("chat_mode");
|
||||
this.chatWeight = set.getInt("chat_weight");
|
||||
this.chatSpeed = set.getInt("chat_speed");
|
||||
|
||||
@@ -1020,6 +1020,10 @@ public class RoomManager {
|
||||
room.getYoutubeWatchers()).compose());
|
||||
}
|
||||
|
||||
habbo.getClient().sendResponse(new com.eu.habbo.messages.outgoing.soundboard.SoundboardSettingsComposer(
|
||||
room.isSoundboardEnabled(),
|
||||
Emulator.getGameEnvironment().getSoundboardManager().getSounds()).compose());
|
||||
|
||||
WiredManager.triggerUserEntersRoom(room, habbo.getRoomUnit());
|
||||
room.habboEntered(habbo);
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.eu.habbo.habbohotel.soundboard;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
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.List;
|
||||
|
||||
public class SoundboardManager {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(SoundboardManager.class);
|
||||
|
||||
private final List<SoundboardSound> sounds = new ArrayList<>();
|
||||
|
||||
public SoundboardManager() {
|
||||
long millis = System.currentTimeMillis();
|
||||
this.bootstrap();
|
||||
this.reload();
|
||||
LOGGER.info("Soundboard Manager -> Loaded! ({} MS, {} sounds)", System.currentTimeMillis() - millis, this.sounds.size());
|
||||
}
|
||||
|
||||
// Self-bootstrap: room flag column + sounds table, so the feature works even
|
||||
// before the manual migration is applied.
|
||||
private void bootstrap() {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
Statement statement = connection.createStatement()) {
|
||||
statement.execute("ALTER TABLE `rooms` ADD COLUMN IF NOT EXISTS `soundboard_enabled` TINYINT(1) NOT NULL DEFAULT 0");
|
||||
statement.execute("CREATE TABLE IF NOT EXISTS `soundboard_sounds` (" +
|
||||
"`id` INT(11) NOT NULL AUTO_INCREMENT, `name` VARCHAR(64) NOT NULL DEFAULT '', " +
|
||||
"`url` VARCHAR(255) NOT NULL DEFAULT '', `enabled` TINYINT(1) NOT NULL DEFAULT 1, " +
|
||||
"`sort_order` INT(11) NOT NULL DEFAULT 0, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to bootstrap soundboard schema", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
this.sounds.clear();
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("SELECT id, name, url FROM soundboard_sounds WHERE enabled = 1 ORDER BY sort_order ASC, id ASC");
|
||||
ResultSet set = statement.executeQuery()) {
|
||||
while (set.next()) {
|
||||
this.sounds.add(new SoundboardSound(set));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to load soundboard sounds", e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<SoundboardSound> getSounds() {
|
||||
return this.sounds;
|
||||
}
|
||||
|
||||
public SoundboardSound getSound(int id) {
|
||||
for (SoundboardSound sound : this.sounds) {
|
||||
if (sound.id == id) return sound;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Owner toggle — persists the room flag with a dedicated UPDATE (kept out of
|
||||
// the big room-settings save to avoid touching that statement).
|
||||
public void setRoomEnabled(int roomId, boolean enabled) {
|
||||
try (Connection connection = Emulator.getDatabase().getDataSource().getConnection();
|
||||
PreparedStatement statement = connection.prepareStatement("UPDATE rooms SET soundboard_enabled = ? WHERE id = ? LIMIT 1")) {
|
||||
statement.setString(1, enabled ? "1" : "0");
|
||||
statement.setInt(2, roomId);
|
||||
statement.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
LOGGER.error("Failed to set soundboard_enabled for room {}", roomId, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.eu.habbo.habbohotel.soundboard;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
// One soundboard pad: a named audio clip served from a URL (uploaded via the CMS).
|
||||
public class SoundboardSound {
|
||||
public final int id;
|
||||
public final String name;
|
||||
public final String url;
|
||||
|
||||
public SoundboardSound(ResultSet set) throws SQLException {
|
||||
this.id = set.getInt("id");
|
||||
this.name = set.getString("name");
|
||||
this.url = set.getString("url");
|
||||
}
|
||||
}
|
||||
@@ -753,5 +753,8 @@ public class PacketManager {
|
||||
this.registerHandler(Incoming.WheelBuySpinEvent, com.eu.habbo.messages.incoming.wheel.WheelBuySpinEvent.class);
|
||||
this.registerHandler(Incoming.WheelAdminGetPrizesEvent, com.eu.habbo.messages.incoming.wheel.WheelAdminGetPrizesEvent.class);
|
||||
this.registerHandler(Incoming.WheelAdminSavePrizesEvent, com.eu.habbo.messages.incoming.wheel.WheelAdminSavePrizesEvent.class);
|
||||
|
||||
this.registerHandler(Incoming.SoundboardPlayEvent, com.eu.habbo.messages.incoming.soundboard.SoundboardPlayEvent.class);
|
||||
this.registerHandler(Incoming.SoundboardSetEnabledEvent, com.eu.habbo.messages.incoming.soundboard.SoundboardSetEnabledEvent.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,4 +494,6 @@ public class Incoming {
|
||||
public static final int WheelBuySpinEvent = 9303;
|
||||
public static final int WheelAdminGetPrizesEvent = 9304;
|
||||
public static final int WheelAdminSavePrizesEvent = 9305;
|
||||
public static final int SoundboardPlayEvent = 9306;
|
||||
public static final int SoundboardSetEnabledEvent = 9307;
|
||||
}
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.eu.habbo.messages.incoming.soundboard;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.soundboard.SoundboardSound;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.soundboard.SoundboardPlayComposer;
|
||||
|
||||
public class SoundboardPlayEvent extends MessageHandler {
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 250;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
Habbo habbo = this.client.getHabbo();
|
||||
if (habbo == null) return;
|
||||
|
||||
Room room = this.currentRoom();
|
||||
if (room == null || !room.isSoundboardEnabled()) return;
|
||||
|
||||
int soundId = this.packet.readInt();
|
||||
SoundboardSound sound = Emulator.getGameEnvironment().getSoundboardManager().getSound(soundId);
|
||||
if (sound == null) return;
|
||||
|
||||
// Broadcast to everyone in the room.
|
||||
room.sendComposer(new SoundboardPlayComposer(sound.id, sound.url, habbo.getHabboInfo().getUsername()).compose());
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.eu.habbo.messages.incoming.soundboard;
|
||||
|
||||
import com.eu.habbo.Emulator;
|
||||
import com.eu.habbo.habbohotel.permissions.Permission;
|
||||
import com.eu.habbo.habbohotel.rooms.Room;
|
||||
import com.eu.habbo.habbohotel.users.Habbo;
|
||||
import com.eu.habbo.messages.incoming.MessageHandler;
|
||||
import com.eu.habbo.messages.outgoing.soundboard.SoundboardSettingsComposer;
|
||||
|
||||
public class SoundboardSetEnabledEvent extends MessageHandler {
|
||||
@Override
|
||||
public int getRatelimit() {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle() throws Exception {
|
||||
Habbo habbo = this.client.getHabbo();
|
||||
if (habbo == null) return;
|
||||
|
||||
Room room = this.currentRoom();
|
||||
if (room == null) return;
|
||||
|
||||
// Only the room owner (or staff) may toggle the soundboard for the room.
|
||||
boolean isOwner = room.getOwnerId() == habbo.getHabboInfo().getId();
|
||||
if (!isOwner && !habbo.hasPermission(Permission.ACC_SUPPORTTOOL)) return;
|
||||
|
||||
boolean enabled = this.packet.readInt() == 1;
|
||||
|
||||
room.setSoundboardEnabled(enabled);
|
||||
Emulator.getGameEnvironment().getSoundboardManager().setRoomEnabled(room.getId(), enabled);
|
||||
|
||||
// Push the refreshed settings (flag + sound list) to everyone in the room
|
||||
// so the toolbar icon appears/disappears live.
|
||||
room.sendComposer(new SoundboardSettingsComposer(enabled, Emulator.getGameEnvironment().getSoundboardManager().getSounds()).compose());
|
||||
}
|
||||
}
|
||||
@@ -600,5 +600,7 @@ public class Outgoing {
|
||||
public static final int WheelResultComposer = 9402;
|
||||
public static final int WheelRecentWinsComposer = 9403;
|
||||
public static final int WheelAdminPrizesComposer = 9404;
|
||||
public static final int SoundboardSettingsComposer = 9405;
|
||||
public static final int SoundboardPlayComposer = 9406;
|
||||
|
||||
}
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.eu.habbo.messages.outgoing.soundboard;
|
||||
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.outgoing.MessageComposer;
|
||||
import com.eu.habbo.messages.outgoing.Outgoing;
|
||||
|
||||
// Broadcast to everyone in the room when a pad is pressed — they all play the clip.
|
||||
public class SoundboardPlayComposer extends MessageComposer {
|
||||
private final int soundId;
|
||||
private final String url;
|
||||
private final String username;
|
||||
|
||||
public SoundboardPlayComposer(int soundId, String url, String username) {
|
||||
this.soundId = soundId;
|
||||
this.url = url != null ? url : "";
|
||||
this.username = username != null ? username : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerMessage composeInternal() {
|
||||
this.response.init(Outgoing.SoundboardPlayComposer);
|
||||
this.response.appendInt(this.soundId);
|
||||
this.response.appendString(this.url);
|
||||
this.response.appendString(this.username);
|
||||
return this.response;
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.eu.habbo.messages.outgoing.soundboard;
|
||||
|
||||
import com.eu.habbo.habbohotel.soundboard.SoundboardSound;
|
||||
import com.eu.habbo.messages.ServerMessage;
|
||||
import com.eu.habbo.messages.outgoing.MessageComposer;
|
||||
import com.eu.habbo.messages.outgoing.Outgoing;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
// Sent on room enter (and on toggle): whether the soundboard is active in this
|
||||
// room + the available pads. The client shows the toolbar icon only if enabled.
|
||||
public class SoundboardSettingsComposer extends MessageComposer {
|
||||
private final boolean enabled;
|
||||
private final List<SoundboardSound> sounds;
|
||||
|
||||
public SoundboardSettingsComposer(boolean enabled, List<SoundboardSound> sounds) {
|
||||
this.enabled = enabled;
|
||||
this.sounds = sounds;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerMessage composeInternal() {
|
||||
this.response.init(Outgoing.SoundboardSettingsComposer);
|
||||
this.response.appendBoolean(this.enabled);
|
||||
this.response.appendInt(this.sounds.size());
|
||||
for (SoundboardSound sound : this.sounds) {
|
||||
this.response.appendInt(sound.id);
|
||||
this.response.appendString(sound.name);
|
||||
this.response.appendString(sound.url);
|
||||
}
|
||||
return this.response;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user