Merge pull request #200 from Salanto/Jukebox2-Electric-Boogaloo

Implement a primitive Jukebox implementation
This commit is contained in:
Rosemary Witchaven 2021-09-13 11:33:36 -05:00 committed by GitHub
commit 16a65eea99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 458 additions and 66 deletions

View File

@ -0,0 +1,203 @@
[
{
"category":"==Music==",
"songs":[
{
"name":"Announce The Truth (AA).opus",
"length":79.5
},
{
"name":"Announce The Truth (AJ).opus",
"length":59.5
},
{
"name":"Announce The Truth (JFA).opus",
"length":98.5
},
{
"name":"Announce The Truth (Miles).opus",
"length":153.5
},
{
"name":"Announce The Truth (T&T).opus",
"length":126.5
},
{
"name":"Confrontation ~ Presto 2009.opus",
"length":187.5
},
{
"name":"Crises of Fate.opus",
"length":143.5
},
{
"name":"Forgotten Legend.opus",
"length":141.5
},
{
"name":"Godot - The Fragrance of Dark Coffee.opus",
"length":148.5
},
{
"name":"Great Revival ~ Franziska von Karma.opus",
"length":86.5
},
{
"name":"Great Revival ~ Miles Edgeworth.opus",
"length":89.5
},
{
"name":"Hotline of Fate.opus",
"length":51.5
},
{
"name":"Interesting People.opus",
"length":142.5
},
{
"name":"Logic and Trick.opus",
"length":152.5
},
{
"name":"Luke Atmey ~ I Just Want Love.opus",
"length":103.5
},
{
"name":"Noisy People.opus",
"length":91.5
},
{
"name":"OBJECTION (AA).opus",
"length":73.5
},
{
"name":"Objection (AJ).opus",
"length":96.5
},
{
"name":"OBJECTION (JFA).opus",
"length":93.5
},
{
"name":"Objection (Miles).opus",
"length":176
},
{
"name":"OBJECTION (T&T).opus",
"length":119
},
{
"name":"Others ~ Guilty love.opus",
"length":99
},
{
"name":"Prelude (AA).opus",
"length":78
},
{
"name":"Prelude (AJ).opus",
"length":71.5
},
{
"name":"Prologue (AA).opus",
"length":40
},
{
"name":"Pursuit (AA) - variation.opus",
"length":90.5
},
{
"name":"Pursuit (AA).opus",
"length":90.5
},
{
"name":"Pursuit (AJ).opus",
"length":109
},
{
"name":"Pursuit (DS).opus",
"length":226.5
},
{
"name":"Pursuit (JFA) - variation.opus",
"length":76
},
{
"name":"Pursuit (JFA).opus",
"length":120
},
{
"name":"Pursuit (Miles).opus",
"length":197
},
{
"name":"Pursuit (T&T) - variation.opus",
"length":114
},
{
"name":"Pursuit (T&T).opus",
"length":120
},
{
"name":"Pursuit ~ I Want to Find the Truth(Orchestra).opus",
"length":294.5
},
{
"name":"Questioning AA (Allegro).opus",
"length":127
},
{
"name":"Questioning AA (Moderato).opus",
"length":104.5
},
{
"name":"Questioning AJ (Allegro).opus",
"length":103.5
},
{
"name":"Questioning AJ (Moderato).opus",
"length":80.5
},
{
"name":"Questioning JFA (Allegro).opus",
"length":104
},
{
"name":"Questioning JFA (Moderato).opus",
"length":90
},
{
"name":"Questioning T&T (Allegro).opus",
"length":160
},
{
"name":"Questioning T&T (Moderato).opus",
"length":116
},
{
"name":"Speak up Pup.opus",
"length":167
},
{
"name":"Suspense (AA).opus",
"length":92.5
},
{
"name":"The Great Truth Burglar.opus",
"length":149
},
{
"name":"Trial (AA).opus",
"length":109
},
{
"name":"Trial (AJ).opus",
"length":126
},
{
"name":"Trial (Miles).opus",
"length":275.5
}
]
}
]

View File

@ -1,49 +0,0 @@
Announce The Truth (AA).opus
Announce The Truth (AJ).opus
Announce The Truth (JFA).opus
Announce The Truth (Miles).opus
Announce The Truth (T&T).opus
Confrontation ~ Presto 2009.opus
Crises of Fate.opus
Forgotten Legend.opus
Godot - The Fragrance of Dark Coffee.opus
Great Revival ~ Franziska von Karma.opus
Great Revival ~ Miles Edgeworth.opus
Hotline of Fate.opus
Interesting People.opus
Logic and Trick.opus
Luke Atmey ~ I Just Want Love.opus
Noisy People.opus
OBJECTION (AA).opus
Objection (AJ).opus
OBJECTION (JFA).opus
Objection (Miles).opus
OBJECTION (T&T).opus
Others ~ Guilty love.opus
Prelude (AA).opus
Prelude (AJ).opus
Prologue (AA).opus
Pursuit (AA) - variation.opus
Pursuit (AA).opus
Pursuit (AJ).opus
Pursuit (DS).opus
Pursuit (JFA) - variation.opus
Pursuit (JFA).opus
Pursuit (Miles).opus
Pursuit (T&T) - variation.opus
Pursuit (T&T).opus
Pursuit ~ I Want to Find the Truth (Orchestra).opus
Questioning AA (Allegro).opus
Questioning AA (Moderato).opus
Questioning AJ (Allegro).opus
Questioning AJ (Moderato).opus
Questioning JFA (Allegro).opus
Questioning JFA (Moderato).opus
Questioning T&T (Allegro).opus
Questioning T&T (Moderato).opus
Speak up Pup.opus
Suspense (AA).opus
The Great Truth Burglar.opus
Trial (AA).opus
Trial (AJ).opus
Trial (Miles).opus

View File

@ -241,6 +241,7 @@ class AOClient : public QObject {
{"BYPASS_LOCKS", 1ULL << 14},
{"IGNORE_BGLIST", 1ULL << 15},
{"SEND_NOTICE", 1ULL << 16},
{"JUKEBOX", 1ULL << 17},
{"SUPER", ~0ULL }
};
@ -1809,6 +1810,13 @@ class AOClient : public QObject {
*/
void cmdToggleMusic(int argc, QStringList argv);
/**
* @brief cmdToggleJukebox Toggles jukebox status in the current area.
*
* @details No arguments.
*/
void cmdToggleJukebox(int argc, QStringList argv);
///@}
/**
@ -2121,6 +2129,7 @@ class AOClient : public QObject {
{"ignore_bglist", {ACLFlags.value("IGNORE_BGLIST"),0, &AOClient::cmdIgnoreBgList}},
{"notice", {ACLFlags.value("SEND_NOTICE"), 1, &AOClient::cmdNotice}},
{"noticeg", {ACLFlags.value("SEND_NOTICE"), 1, &AOClient::cmdNoticeGlobal}},
{"togglejukebox", {ACLFlags.value("None"), 0, &AOClient::cmdToggleJukebox}},
};
/**

View File

@ -27,6 +27,7 @@
#include <QDebug>
#include <QTimer>
#include <QElapsedTimer>
#include <QRandomGenerator>
class Logger;
@ -295,6 +296,15 @@ class AreaData : public QObject {
*/
LockStatus lockStatus() const;
/**
* @brief Returns the jukebox status of the area.
*
* @return See short description.
*
* @see #m_jukebox
*/
bool isjukeboxEnabled() const;
/**
* @brief Locks the area, setting it to LOCKED.
*/
@ -567,6 +577,15 @@ class AreaData : public QObject {
*/
QString currentMusic() const;
/**
* @brief Sets the music currently being played in the area.
*
* @param Name of the song being played.
*
* @see #m_currentMusic
*/
void setCurrentMusic(QString f_current_song);
/**
* @brief Returns the showname of the client who played the music in the area.
*
@ -576,6 +595,15 @@ class AreaData : public QObject {
*/
QString musicPlayerBy() const;
/**
* @brief Sets the showname of the client who played the music in the area.
*
* @param Showname of the client.
*
* @see #m_musicPlayedBy
*/
void setMusicPlayedBy(const QString& f_music_player);
/**
* @brief Changes the music being played in the area.
*
@ -778,6 +806,30 @@ class AreaData : public QObject {
*/
void toggleIgnoreBgList();
/**
* @brief Toggles wether the jukebox is enabled or not.
*/
void toggleJukebox();
/**
* @brief Adds a song to the Jukeboxs queue.
*/
QString addJukeboxSong(QString f_song);
public slots:
/**
* @brief Plays a random song from the jukebox. Plays the same if only one is left.
*/
void switchJukeboxSong();
signals:
/**
* @brief Changes the song in the current area when the jukebox timer expires.
*/
void playJukeboxSong(AOPacket f_packet, int f_area_index);
private:
/**
* @brief The list of timers available in the area.
@ -966,6 +1018,28 @@ private:
* @brief Whether or not to ignore the server defined background list. If true, any background can be set in an area.
*/
bool m_ignoreBgList;
// Jukebox specific members
/**
* @brief Stores the songs added to the jukebox to be played.
*
* @details This contains the names of each song, noteworthy is that none of the songs are able to be entered twice.
*
*/
QVector<QString> m_jukebox_queue;
/**
* @brief Triggers the playing of the next song once the last song has fully played.
*
* @details While this may be considered bad design, I do not care.
* It triggers a direct broadcast of the MC packet in the area.
*/
QTimer* m_jukebox_timer;
/**
* @brief Wether or not the jukebox is enabled in this area.
*/
bool m_jukebox;
};
#endif // AREA_DATA_H

View File

@ -31,6 +31,11 @@
#include <QMetaEnum>
#include <QElapsedTimer>
//JSON loading requirements
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
/**
* @brief The config file handler class.
*/
@ -73,10 +78,18 @@ class ConfigManager {
static QStringList musiclist();
/**
* @brief Returns the content of
* @return
* @brief Returns the duration of a song in the songlist.
* @param The name of the song where duration is requested
* @return The duration of the song
*/
static QSettings *areaData();
static int songInformation(const QString& f_songName);
/**
* @brief Returns the content of
*
* @return See short description.
*/
static QSettings* areaData();
/**
* @brief Returns a sanitized QStringList of the areas.
@ -91,6 +104,7 @@ class ConfigManager {
* @return See short description.
*/
static QStringList rawAreaNames();
/**
* @brief Returns true if the server should advertise to the master server.
*
@ -481,6 +495,11 @@ private:
*/
static QElapsedTimer* m_uptimeTimer;
/**
* @brief Contains the musiclist with time durations.
*/
static QHash<QString,float>* m_musicList;
/**
* @brief Returns a stringlist with the contents of a .txt file from config/text/.
*

View File

@ -48,6 +48,7 @@ AreaData::AreaData(QString p_name, int p_index) :
m_toggleMusic = areas_ini->value("toggle_music", "true").toBool();
m_shownameAllowed = areas_ini->value("shownames_allowed", "true").toBool();
m_ignoreBgList = areas_ini->value("ignore_bglist", "false").toBool();
m_jukebox = areas_ini->value("jukebox_enabled", "false").toBool();
areas_ini->endGroup();
QTimer* timer1 = new QTimer();
m_timers.append(timer1);
@ -57,6 +58,9 @@ AreaData::AreaData(QString p_name, int p_index) :
m_timers.append(timer3);
QTimer* timer4 = new QTimer();
m_timers.append(timer4);
m_jukebox_timer = new QTimer();
connect(m_jukebox_timer, &QTimer::timeout,
this, &AreaData::switchJukeboxSong);
}
const QMap<QString, AreaData::Status> AreaData::map_statuses = {
@ -131,6 +135,11 @@ AreaData::LockStatus AreaData::lockStatus() const
return m_locked;
}
bool AreaData::isjukeboxEnabled() const
{
return m_jukebox;
}
void AreaData::lock()
{
m_locked = LockStatus::LOCKED;
@ -420,6 +429,11 @@ QString AreaData::musicPlayerBy() const
return m_musicPlayedBy;
}
void AreaData::setMusicPlayedBy(const QString& f_music_player)
{
m_musicPlayedBy = f_music_player;
}
void AreaData::changeMusic(const QString &f_source_r, const QString &f_newSong_r)
{
m_currentMusic = f_newSong_r;
@ -431,6 +445,11 @@ QString AreaData::currentMusic() const
return m_currentMusic;
}
void AreaData::setCurrentMusic(QString f_current_song)
{
m_currentMusic = f_current_song;
}
int AreaData::proHP() const
{
return m_proHP;
@ -504,3 +523,53 @@ void AreaData::toggleIgnoreBgList()
{
m_ignoreBgList = !m_ignoreBgList;
}
void AreaData::toggleJukebox()
{
m_jukebox = !m_jukebox;
if (!m_jukebox) {
m_jukebox_queue.clear();
m_jukebox_timer->stop();
}
}
QString AreaData::addJukeboxSong(QString f_song)
{
if(!m_jukebox_queue.contains(f_song)) {
if (m_jukebox_queue.size() == 0) {
int l_song_duration = ConfigManager::songInformation(f_song);
if (l_song_duration >= 0) {
emit playJukeboxSong(AOPacket("MC",{f_song,QString::number(-1)}), index());
m_jukebox_timer->start(l_song_duration * 1000);
setCurrentMusic(f_song);
setMusicPlayedBy("Jukebox");
m_jukebox_queue.append(f_song);
return "Song added to Jukebox.";
}
}
return "Unable to add song. Duration shorther than 1.";
}
else {
return "Unable to add song. Song already in Jukebox.";
}
}
void AreaData::switchJukeboxSong()
{
QString l_song_name;
if(m_jukebox_queue.size() == 1) {
l_song_name = m_jukebox_queue[0];
emit playJukeboxSong(AOPacket("MC",{l_song_name,"-1"}), m_index);
m_jukebox_timer->start(ConfigManager::songInformation(l_song_name) * 1000);
}
else {
int l_random_index = QRandomGenerator::system()->bounded(m_jukebox_queue.size() -1);
l_song_name = m_jukebox_queue[l_random_index];
emit playJukeboxSong(AOPacket("MC",{l_song_name,QString::number(-1)}), m_index);
m_jukebox_timer->start(ConfigManager::songInformation(m_jukebox_queue[l_random_index]) * 1000);
m_jukebox_queue.remove(l_random_index);
m_jukebox_queue.squeeze();
}
setCurrentMusic(l_song_name);
setMusicPlayedBy("Jukebox");
}

View File

@ -462,6 +462,7 @@ void AOClient::cmdReload(int argc, QStringList argv)
emit server->reloadRequest(ConfigManager::serverName(), ConfigManager::serverDescription());
server->updateHTTPAdvertiserConfig();
server->handleDiscordIntegration();
server->m_music_list = ConfigManager::musiclist();
sendServerMessage("Reloaded configurations");
}

View File

@ -16,7 +16,6 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. //
//////////////////////////////////////////////////////////////////////////////////////
#include "include/aoclient.h"
// This file is for commands under the music category in aoclient.h
// Be sure to register the command in the header before adding it here!
@ -42,7 +41,7 @@ void AOClient::cmdCurrentMusic(int argc, QStringList argv)
Q_UNUSED(argv);
AreaData* l_area = server->m_areas[m_current_area];
if (l_area->currentMusic() != "" && l_area->currentMusic() != "~stop.mp3") // dummy track for stopping music
if (!l_area->currentMusic().isEmpty() && !l_area->currentMusic().contains("~stop.mp3")) // dummy track for stopping music
sendServerMessage("The current song is " + l_area->currentMusic() + " played by " + l_area->musicPlayerBy());
else
sendServerMessage("There is no music playing.");
@ -112,3 +111,19 @@ void AOClient::cmdToggleMusic(int argc, QStringList argv)
QString l_state = l_area->isMusicAllowed() ? "allowed." : "disallowed.";
sendServerMessage("Music in this area is now " + l_state);
}
void AOClient::cmdToggleJukebox(int argc, QStringList argv)
{
Q_UNUSED(argc);
Q_UNUSED(argv);
if (checkAuth(ACLFlags.value("CM")) | checkAuth(ACLFlags.value("Jukebox"))) {
AreaData* l_area = server->m_areas.value(m_current_area);
l_area->toggleJukebox();
QString l_state = l_area->isjukeboxEnabled() ? "enabled." : "disabled.";
sendServerMessageArea("The jukebox in this area has been " + l_state);
}
else {
sendServerMessage("You do not have permission to change the jukebox status.");
}
}

View File

@ -24,6 +24,7 @@ QSettings* ConfigManager::m_discord = new QSettings("config/discord.ini", QSetti
QSettings* ConfigManager::m_areas = new QSettings("config/areas.ini", QSettings::IniFormat);
ConfigManager::CommandSettings* ConfigManager::m_commands = new CommandSettings();
QElapsedTimer* ConfigManager::m_uptimeTimer = new QElapsedTimer;
QHash<QString,float>* ConfigManager::m_musicList = new QHash<QString,float>;
bool ConfigManager::verifyServerConfig()
{
@ -127,16 +128,54 @@ QStringList ConfigManager::backgrounds()
QStringList ConfigManager::musiclist()
{
QStringList l_music_list;
QFile l_file("config/music.txt");
l_file.open(QIODevice::ReadOnly | QIODevice::Text);
while (!l_file.atEnd()) {
l_music_list.append(l_file.readLine().trimmed());
QFile l_music_json("config/music.json");
l_music_json.open(QIODevice::ReadOnly | QIODevice::Text);
QJsonParseError l_error;
QJsonDocument l_music_list_json = QJsonDocument::fromJson(l_music_json.readAll(), &l_error);
if (!(l_error.error == QJsonParseError::NoError)) { //Non-Terminating error.
qWarning() << "Unable to load musiclist. The following error was encounted : " + l_error.errorString();
return QStringList {}; //Server can still run without music.
}
l_file.close();
if(l_music_list[0].contains(".")) // Add a default category if none exists
l_music_list.insert(0, "==Music==");
return l_music_list;
// Akashi expects the musiclist to be contained in a JSON array, even if its only a single category.
QJsonArray l_Json_root_array = l_music_list_json.array();
QJsonObject l_child_obj;
QJsonArray l_child_array;
QStringList l_musiclist;
for (int i = 0; i <= l_Json_root_array.size() -1; i++){ //Iterate trough entire JSON file to assemble musiclist
l_child_obj = l_Json_root_array.at(i).toObject();
//Technically not a requirement, but neat for organisation.
QString l_category_name = l_child_obj["category"].toString();
if (!l_category_name.isEmpty()) {
m_musicList->insert(l_category_name,0);
l_musiclist.append(l_category_name);
}
else {
qWarning() << "Category name not set. This may cause the musiclist to be displayed incorrectly.";
}
l_child_array = l_child_obj["songs"].toArray();
for (int i = 0; i <= l_child_array.size() -1; i++){ // Inner for loop because a category can contain multiple songs.
QJsonObject l_song_obj = l_child_array.at(i).toObject();
QString l_song_name = l_song_obj["name"].toString();
int l_song_duration = l_song_obj["length"].toVariant().toFloat();
m_musicList->insert(l_song_name,l_song_duration);
l_musiclist.append(l_song_name);
}
}
l_music_json.close();
if(!l_musiclist[0].contains("==")) // Add a default category if none exists
l_musiclist.insert(0,"==Music==");
return l_musiclist;
}
int ConfigManager::songInformation(const QString &f_songName)
{
return m_musicList->value(f_songName);
}
QSettings* ConfigManager::areaData()

View File

@ -319,9 +319,18 @@ void AOClient::pktChangeMusic(AreaData* area, int argc, QStringList argv, AOPack
l_final_song = "~stop.mp3";
else
l_final_song = l_argument;
//Jukebox intercepts the direct playing of messages.
if (area->isjukeboxEnabled()) {
QString l_jukebox_reply = area->addJukeboxSong(l_final_song);
sendServerMessage(l_jukebox_reply);
return;
}
AOPacket l_music_change("MC", {l_final_song, argv[1], m_showname, "1", "0", l_effects});
area->currentMusic() = l_final_song;
area->musicPlayerBy() = m_showname;
area->setCurrentMusic(l_final_song);
area->setMusicPlayedBy(m_showname);
server->broadcast(l_music_change, m_current_area);
return;
}

View File

@ -91,7 +91,10 @@ void Server::start()
QStringList raw_area_names = ConfigManager::rawAreaNames();
for (int i = 0; i < raw_area_names.length(); i++) {
QString area_name = raw_area_names[i];
m_areas.insert(i, new AreaData(area_name, i));
AreaData* l_area = new AreaData(area_name, i);
m_areas.insert(i, l_area);
connect(l_area, &AreaData::playJukeboxSong,
this, QOverload<AOPacket,int>::of(&Server::broadcast));
}
//Rate-Limiter for IC-Chat